mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-10-04 17:51:11 +00:00
* Revert "Revert metadata item changes (#225)"
This reverts commit 884158d891
.
* Use computeOperationWait instead of computeOperationWaitGlobal
This commit is contained in:
parent
96cef4df0e
commit
50c10d4e7d
@ -3,6 +3,7 @@
|
|||||||
BACKWARDS INCOMPATIBILITIES / NOTES:
|
BACKWARDS INCOMPATIBILITIES / NOTES:
|
||||||
|
|
||||||
FEATURES:
|
FEATURES:
|
||||||
|
* **New Resource:** `google_compute_project_metadata_item` - allows management of single key/value pairs within the project metadata map [GH-176]
|
||||||
|
|
||||||
IMPROVEMENTS:
|
IMPROVEMENTS:
|
||||||
* **New Resource:** `google_compute_network_peering` ([#259](https://github.com/terraform-providers/terraform-provider-google/issues/259))
|
* **New Resource:** `google_compute_network_peering` ([#259](https://github.com/terraform-providers/terraform-provider-google/issues/259))
|
||||||
|
25
google/import_compute_project_metadata_item_test.go
Normal file
25
google/import_compute_project_metadata_item_test.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeProjectMetadataItem_importBasic(t *testing.T) {
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckProjectMetadataItemDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccProjectMetadataItem_basic("myKey", "myValue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ResourceName: "google_compute_project_metadata_item.foobar",
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
@ -2,12 +2,15 @@ package google
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"google.golang.org/api/compute/v1"
|
"google.golang.org/api/compute/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const FINGERPRINT_RETRIES = 10
|
const FINGERPRINT_RETRIES = 10
|
||||||
const FINGERPRINT_FAIL = "Invalid fingerprint."
|
|
||||||
|
var FINGERPRINT_FAIL_ERRORS = []string{"Invalid fingerprint.", "Supplied fingerprint does not match current metadata fingerprint."}
|
||||||
|
|
||||||
// Since the google compute API uses optimistic locking, there is a chance
|
// Since the google compute API uses optimistic locking, there is a chance
|
||||||
// we need to resubmit our updated metadata. To do this, you need to provide
|
// we need to resubmit our updated metadata. To do this, you need to provide
|
||||||
@ -16,11 +19,25 @@ func MetadataRetryWrapper(update func() error) error {
|
|||||||
attempt := 0
|
attempt := 0
|
||||||
for attempt < FINGERPRINT_RETRIES {
|
for attempt < FINGERPRINT_RETRIES {
|
||||||
err := update()
|
err := update()
|
||||||
if err != nil && err.Error() == FINGERPRINT_FAIL {
|
if err == nil {
|
||||||
attempt++
|
return nil
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
// Check to see if the error matches any of our fingerprint-related failure messages
|
||||||
|
var fingerprintError bool
|
||||||
|
for _, msg := range FINGERPRINT_FAIL_ERRORS {
|
||||||
|
if strings.Contains(err.Error(), msg) {
|
||||||
|
fingerprintError = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fingerprintError {
|
||||||
|
// Something else went wrong, don't retry
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attempt++
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("Failed to update metadata after %d retries", attempt)
|
return fmt.Errorf("Failed to update metadata after %d retries", attempt)
|
||||||
@ -71,3 +88,43 @@ func MetadataFormatSchema(curMDMap map[string]interface{}, md *compute.Metadata)
|
|||||||
|
|
||||||
return newMD
|
return newMD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flattenComputeMetadata transforms a list of MetadataItems (as returned via the GCP client) into a simple map from key
|
||||||
|
// to value.
|
||||||
|
func flattenComputeMetadata(metadata []*compute.MetadataItems) map[string]string {
|
||||||
|
m := map[string]string{}
|
||||||
|
|
||||||
|
for _, item := range metadata {
|
||||||
|
// check for duplicates
|
||||||
|
if item.Value == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if val, ok := m[item.Key]; ok {
|
||||||
|
// warn loudly!
|
||||||
|
log.Printf("[WARN] Key '%s' already has value '%s' when flattening - ignoring incoming value '%s'",
|
||||||
|
item.Key,
|
||||||
|
val,
|
||||||
|
*item.Value)
|
||||||
|
}
|
||||||
|
m[item.Key] = *item.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// expandComputeMetadata transforms a map representing computing metadata into a list of compute.MetadataItems suitable
|
||||||
|
// for the GCP client.
|
||||||
|
func expandComputeMetadata(m map[string]string) []*compute.MetadataItems {
|
||||||
|
metadata := make([]*compute.MetadataItems, len(m))
|
||||||
|
|
||||||
|
idx := 0
|
||||||
|
for key, value := range m {
|
||||||
|
// Make a copy of value as we need a ptr type; if we directly use 'value' then all items will reference the same
|
||||||
|
// memory address
|
||||||
|
vtmp := value
|
||||||
|
metadata[idx] = &compute.MetadataItems{Key: key, Value: &vtmp}
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
@ -88,6 +88,7 @@ func Provider() terraform.ResourceProvider {
|
|||||||
"google_compute_network": resourceComputeNetwork(),
|
"google_compute_network": resourceComputeNetwork(),
|
||||||
"google_compute_network_peering": resourceComputeNetworkPeering(),
|
"google_compute_network_peering": resourceComputeNetworkPeering(),
|
||||||
"google_compute_project_metadata": resourceComputeProjectMetadata(),
|
"google_compute_project_metadata": resourceComputeProjectMetadata(),
|
||||||
|
"google_compute_project_metadata_item": resourceComputeProjectMetadataItem(),
|
||||||
"google_compute_region_backend_service": resourceComputeRegionBackendService(),
|
"google_compute_region_backend_service": resourceComputeRegionBackendService(),
|
||||||
"google_compute_route": resourceComputeRoute(),
|
"google_compute_route": resourceComputeRoute(),
|
||||||
"google_compute_router": resourceComputeRouter(),
|
"google_compute_router": resourceComputeRouter(),
|
||||||
|
178
google/resource_compute_project_metadata_item.go
Normal file
178
google/resource_compute_project_metadata_item.go
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"google.golang.org/api/compute/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceComputeProjectMetadataItem() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceComputeProjectMetadataItemCreate,
|
||||||
|
Read: resourceComputeProjectMetadataItemRead,
|
||||||
|
Update: resourceComputeProjectMetadataItemUpdate,
|
||||||
|
Delete: resourceComputeProjectMetadataItemDelete,
|
||||||
|
Importer: &schema.ResourceImporter{
|
||||||
|
State: schema.ImportStatePassthrough,
|
||||||
|
},
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"key": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeProjectMetadataItemCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
projectID, err := getProject(d, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
key := d.Get("key").(string)
|
||||||
|
val := d.Get("value").(string)
|
||||||
|
|
||||||
|
err = updateComputeCommonInstanceMetadata(config, projectID, key, &val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(key)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeProjectMetadataItemRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
projectID, err := getProject(d, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Loading project metadata: %s", projectID)
|
||||||
|
project, err := config.clientCompute.Projects.Get(projectID).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error loading project '%s': %s", projectID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
md := flattenComputeMetadata(project.CommonInstanceMetadata.Items)
|
||||||
|
val, ok := md[d.Id()]
|
||||||
|
if !ok {
|
||||||
|
// Resource no longer exists
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("key", d.Id())
|
||||||
|
d.Set("value", val)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeProjectMetadataItemUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
projectID, err := getProject(d, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("value") {
|
||||||
|
key := d.Get("key").(string)
|
||||||
|
_, n := d.GetChange("value")
|
||||||
|
new := n.(string)
|
||||||
|
|
||||||
|
err = updateComputeCommonInstanceMetadata(config, projectID, key, &new)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeProjectMetadataItemDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
projectID, err := getProject(d, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
key := d.Get("key").(string)
|
||||||
|
|
||||||
|
err = updateComputeCommonInstanceMetadata(config, projectID, key, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateComputeCommonInstanceMetadata(config *Config, projectID string, key string, afterVal *string) error {
|
||||||
|
updateMD := func() error {
|
||||||
|
log.Printf("[DEBUG] Loading project metadata: %s", projectID)
|
||||||
|
project, err := config.clientCompute.Projects.Get(projectID).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error loading project '%s': %s", projectID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
md := flattenComputeMetadata(project.CommonInstanceMetadata.Items)
|
||||||
|
|
||||||
|
val, ok := md[key]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
if afterVal == nil {
|
||||||
|
// Asked to set no value and we didn't find one - we're done
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if afterVal != nil && *afterVal == val {
|
||||||
|
// Asked to set a value and it's already set - we're done.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if afterVal == nil {
|
||||||
|
delete(md, key)
|
||||||
|
} else {
|
||||||
|
md[key] = *afterVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to write the new value now
|
||||||
|
op, err := config.clientCompute.Projects.SetCommonInstanceMetadata(
|
||||||
|
projectID,
|
||||||
|
&compute.Metadata{
|
||||||
|
Fingerprint: project.CommonInstanceMetadata.Fingerprint,
|
||||||
|
Items: expandComputeMetadata(md),
|
||||||
|
},
|
||||||
|
).Do()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] SetCommonInstanceMetadata: %d (%s)", op.Id, op.SelfLink)
|
||||||
|
|
||||||
|
return computeOperationWait(config, op, project.Name, "SetCommonInstanceMetadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
return MetadataRetryWrapper(updateMD)
|
||||||
|
}
|
118
google/resource_compute_project_metadata_item_test.go
Normal file
118
google/resource_compute_project_metadata_item_test.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeProjectMetadataItem_basic(t *testing.T) {
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckProjectMetadataItemDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccProjectMetadataItem_basic("myKey", "myValue"),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckProjectMetadataItem_hasMetadata("myKey", "myValue"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccComputeProjectMetadataItem_basicWithEmptyVal(t *testing.T) {
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckProjectMetadataItemDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccProjectMetadataItem_basic("myKey", ""),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckProjectMetadataItem_hasMetadata("myKey", ""),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccComputeProjectMetadataItem_basicUpdate(t *testing.T) {
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckProjectMetadataItemDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccProjectMetadataItem_basic("myKey", "myValue"),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckProjectMetadataItem_hasMetadata("myKey", "myValue"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: testAccProjectMetadataItem_basic("myKey", "myUpdatedValue"),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckProjectMetadataItem_hasMetadata("myKey", "myUpdatedValue"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckProjectMetadataItem_hasMetadata(key, value string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
project, err := config.clientCompute.Projects.Get(config.Project).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := flattenComputeMetadata(project.CommonInstanceMetadata.Items)
|
||||||
|
|
||||||
|
val, ok := metadata[key]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Unable to find a value for key '%s'", key)
|
||||||
|
}
|
||||||
|
if val != value {
|
||||||
|
return fmt.Errorf("Value for key '%s' does not match. Expected '%s' but found '%s'", key, value, val)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckProjectMetadataItemDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
project, err := config.clientCompute.Projects.Get(config.Project).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := flattenComputeMetadata(project.CommonInstanceMetadata.Items)
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "google_compute_project_metadata_item" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := metadata[rs.Primary.ID]
|
||||||
|
if ok {
|
||||||
|
return fmt.Errorf("Metadata key/value '%s': '%s' still exist", rs.Primary.Attributes["key"], rs.Primary.Attributes["value"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccProjectMetadataItem_basic(key, val string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "google_compute_project_metadata_item" "foobar" {
|
||||||
|
key = "%s"
|
||||||
|
value = "%s"
|
||||||
|
}
|
||||||
|
`, key, val)
|
||||||
|
}
|
@ -8,7 +8,10 @@ description: |-
|
|||||||
|
|
||||||
# google\_compute\_project\_metadata
|
# google\_compute\_project\_metadata
|
||||||
|
|
||||||
Manages metadata common to all instances for a project in GCE.
|
Manages metadata common to all instances for a project in GCE. If you
|
||||||
|
want to manage only single key/value pairs within the project metadata
|
||||||
|
rather than the entire set, then use
|
||||||
|
[google_compute_project_metadata_item](compute_project_metadata_item.html).
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
|
48
website/docs/r/compute_project_metadata_item.html.markdown
Normal file
48
website/docs/r/compute_project_metadata_item.html.markdown
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
layout: "google"
|
||||||
|
page_title: "Google: google_compute_project_metadata_item"
|
||||||
|
sidebar_current: "docs-google-compute-project-metadata-item"
|
||||||
|
description: |-
|
||||||
|
Manages a single key/value pair on common instance metadata
|
||||||
|
---
|
||||||
|
|
||||||
|
# google\_compute\_project\_metadata\_item
|
||||||
|
|
||||||
|
Manages a single key/value pair on metadata common to all instances for
|
||||||
|
a project in GCE. Using `google_compute_project_metadata_item` lets you
|
||||||
|
manage a single key/value setting in Terraform rather than the entire
|
||||||
|
project metadata map.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
resource "google_compute_project_metadata_item" "default" {
|
||||||
|
key = "my_metadata"
|
||||||
|
value = "my_value"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `key` - (Required) The metadata key to set.
|
||||||
|
|
||||||
|
* `value` - (Required) The value to set for the given metadata key.
|
||||||
|
|
||||||
|
- - -
|
||||||
|
|
||||||
|
* `project` - (Optional) The project in which the resource belongs. If it
|
||||||
|
is not provided, the provider project is used.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
Only the arguments listed above are exposed as attributes.
|
||||||
|
|
||||||
|
## Import
|
||||||
|
|
||||||
|
Project metadata items can be imported using the `key`, e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform import google_compute_project_metadata_item.default my_metadata
|
||||||
|
```
|
@ -153,6 +153,10 @@
|
|||||||
<a href="/docs/providers/google/r/compute_project_metadata.html">google_compute_project_metadata</a>
|
<a href="/docs/providers/google/r/compute_project_metadata.html">google_compute_project_metadata</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-google-compute-project-metadata-item") %>>
|
||||||
|
<a href="/docs/providers/google/r/compute_project_metadata_item.html">google_compute_project_metadata_item</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-google-compute-region-backend-service") %>>
|
<li<%= sidebar_current("docs-google-compute-region-backend-service") %>>
|
||||||
<a href="/docs/providers/google/r/compute_region_backend_service.html">google_compute_region_backend_service</a>
|
<a href="/docs/providers/google/r/compute_region_backend_service.html">google_compute_region_backend_service</a>
|
||||||
</li>
|
</li>
|
||||||
|
Loading…
Reference in New Issue
Block a user