From a9b6e3f5e1e9ebcd718975ec95273f97767a087b Mon Sep 17 00:00:00 2001 From: Riley Karson Date: Wed, 10 Oct 2018 15:13:25 -0700 Subject: [PATCH] Make google_compute_project_metadata authoritative. (#2205) --- google/metadata.go | 23 ++- google/resource_compute_project_metadata.go | 156 ++++-------------- .../r/compute_project_metadata.html.markdown | 10 +- 3 files changed, 49 insertions(+), 140 deletions(-) diff --git a/google/metadata.go b/google/metadata.go index 428a83d1..55dc9aac 100644 --- a/google/metadata.go +++ b/google/metadata.go @@ -111,18 +111,15 @@ func BetaMetadataUpdate(oldMDMap map[string]interface{}, newMDMap map[string]int } } -// 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 { +func expandComputeMetadata(m map[string]interface{}) []*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++ + // Append new metadata to existing metadata + for key, val := range m { + v := val.(string) + metadata = append(metadata, &compute.MetadataItems{ + Key: key, + Value: &v, + }) } return metadata @@ -140,8 +137,8 @@ func flattenMetadataBeta(metadata *computeBeta.Metadata) map[string]string { // compute.metadata rather than computeBeta.metadata as an argument. It should // be removed in favour of flattenMetadataBeta if/when all resources using it get // beta support. -func flattenMetadata(metadata *compute.Metadata) map[string]string { - metadataMap := make(map[string]string) +func flattenMetadata(metadata *compute.Metadata) map[string]interface{} { + metadataMap := make(map[string]interface{}) for _, item := range metadata.Items { metadataMap[item.Key] = *item.Value } diff --git a/google/resource_compute_project_metadata.go b/google/resource_compute_project_metadata.go index 2335750f..497005ff 100644 --- a/google/resource_compute_project_metadata.go +++ b/google/resource_compute_project_metadata.go @@ -10,9 +10,9 @@ import ( func resourceComputeProjectMetadata() *schema.Resource { return &schema.Resource{ - Create: resourceComputeProjectMetadataCreate, + Create: resourceComputeProjectMetadataCreateOrUpdate, Read: resourceComputeProjectMetadataRead, - Update: resourceComputeProjectMetadataUpdate, + Update: resourceComputeProjectMetadataCreateOrUpdate, Delete: resourceComputeProjectMetadataDelete, SchemaVersion: 0, @@ -34,7 +34,7 @@ func resourceComputeProjectMetadata() *schema.Resource { } } -func resourceComputeProjectMetadataCreate(d *schema.ResourceData, meta interface{}) error { +func resourceComputeProjectMetadataCreateOrUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) projectID, err := getProject(d, config) @@ -42,47 +42,13 @@ func resourceComputeProjectMetadataCreate(d *schema.ResourceData, meta interface return err } - createMD := func() error { - // Load project service - log.Printf("[DEBUG] Loading project service: %s", projectID) - project, err := config.clientCompute.Projects.Get(projectID).Do() - if err != nil { - return fmt.Errorf("Error loading project '%s': %s", projectID, err) - } - - md := project.CommonInstanceMetadata - - newMDMap := d.Get("metadata").(map[string]interface{}) - // Ensure that we aren't overwriting entries that already exist - for _, kv := range md.Items { - if _, ok := newMDMap[kv.Key]; ok { - return fmt.Errorf("Error, key '%s' already exists in project '%s'", kv.Key, projectID) - } - } - - // Append new metadata to existing metadata - for key, val := range newMDMap { - v := val.(string) - md.Items = append(md.Items, &compute.MetadataItems{ - Key: key, - Value: &v, - }) - } - - op, err := config.clientCompute.Projects.SetCommonInstanceMetadata(projectID, md).Do() - - if err != nil { - return fmt.Errorf("SetCommonInstanceMetadata failed: %s", err) - } - - log.Printf("[DEBUG] SetCommonMetadata: %d (%s)", op.Id, op.SelfLink) - - return computeOperationWait(config.clientCompute, op, project.Name, "SetCommonMetadata") + md := &compute.Metadata{ + Items: expandComputeMetadata(d.Get("metadata").(map[string]interface{})), } - err = MetadataRetryWrapper(createMD) + err = resourceComputeProjectMetadataSet(projectID, config, md) if err != nil { - return err + return fmt.Errorf("SetCommonInstanceMetadata failed: %s", err) } return resourceComputeProjectMetadataRead(d, meta) @@ -103,70 +69,13 @@ func resourceComputeProjectMetadataRead(d *schema.ResourceData, meta interface{} return handleNotFoundError(err, d, fmt.Sprintf("Project metadata for project %q", projectID)) } - md := flattenMetadata(project.CommonInstanceMetadata) - existingMetadata := d.Get("metadata").(map[string]interface{}) - // Remove all keys not explicitly mentioned in the terraform config - for k := range md { - if _, ok := existingMetadata[k]; !ok { - delete(md, k) - } - } - - if err = d.Set("metadata", md); err != nil { + err = d.Set("metadata", flattenMetadata(project.CommonInstanceMetadata)) + if err != nil { return fmt.Errorf("Error setting metadata: %s", err) } d.Set("project", projectID) d.SetId("common_metadata") - - return nil -} - -func resourceComputeProjectMetadataUpdate(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - - projectID, err := getProject(d, config) - if err != nil { - return err - } - - if d.HasChange("metadata") { - o, n := d.GetChange("metadata") - - updateMD := func() error { - // Load project service - log.Printf("[DEBUG] Loading project service: %s", projectID) - project, err := config.clientCompute.Projects.Get(projectID).Do() - if err != nil { - return fmt.Errorf("Error loading project '%s': %s", projectID, err) - } - - md := project.CommonInstanceMetadata - - MetadataUpdate(o.(map[string]interface{}), n.(map[string]interface{}), md) - - op, err := config.clientCompute.Projects.SetCommonInstanceMetadata(projectID, md).Do() - - if err != nil { - return fmt.Errorf("SetCommonInstanceMetadata failed: %s", err) - } - - log.Printf("[DEBUG] SetCommonMetadata: %d (%s)", op.Id, op.SelfLink) - - // Optimistic locking requires the fingerprint received to match - // the fingerprint we send the server, if there is a mismatch then we - // are working on old data, and must retry - return computeOperationWait(config.clientCompute, op, project.Name, "SetCommonMetadata") - } - - err := MetadataRetryWrapper(updateMD) - if err != nil { - return err - } - - return resourceComputeProjectMetadataRead(d, meta) - } - return nil } @@ -178,30 +87,33 @@ func resourceComputeProjectMetadataDelete(d *schema.ResourceData, meta interface return err } - // Load project service - log.Printf("[DEBUG] Loading project service: %s", projectID) - project, err := config.clientCompute.Projects.Get(projectID).Do() + md := &compute.Metadata{} + err = resourceComputeProjectMetadataSet(projectID, config, md) if err != nil { - return fmt.Errorf("Error loading project '%s': %s", projectID, err) - } - - md := project.CommonInstanceMetadata - - // Remove all items - md.Items = nil - - op, err := config.clientCompute.Projects.SetCommonInstanceMetadata(projectID, md).Do() - - if err != nil { - return fmt.Errorf("Error removing metadata from project %s: %s", projectID, err) - } - - log.Printf("[DEBUG] SetCommonMetadata: %d (%s)", op.Id, op.SelfLink) - - err = computeOperationWait(config.clientCompute, op, project.Name, "SetCommonMetadata") - if err != nil { - return err + return fmt.Errorf("SetCommonInstanceMetadata failed: %s", err) } return resourceComputeProjectMetadataRead(d, meta) } + +func resourceComputeProjectMetadataSet(projectID string, config *Config, md *compute.Metadata) error { + createMD := func() error { + log.Printf("[DEBUG] Loading project service: %s", projectID) + project, err := config.clientCompute.Projects.Get(projectID).Do() + if err != nil { + return fmt.Errorf("Error loading project '%s': %s", projectID, err) + } + + md.Fingerprint = project.CommonInstanceMetadata.Fingerprint + op, err := config.clientCompute.Projects.SetCommonInstanceMetadata(projectID, md).Do() + if err != nil { + return fmt.Errorf("SetCommonInstanceMetadata failed: %s", err) + } + + log.Printf("[DEBUG] SetCommonMetadata: %d (%s)", op.Id, op.SelfLink) + return computeOperationWait(config.clientCompute, op, project.Name, "SetCommonMetadata") + } + + err := MetadataRetryWrapper(createMD) + return err +} diff --git a/website/docs/r/compute_project_metadata.html.markdown b/website/docs/r/compute_project_metadata.html.markdown index e262822a..00b0d72a 100644 --- a/website/docs/r/compute_project_metadata.html.markdown +++ b/website/docs/r/compute_project_metadata.html.markdown @@ -8,13 +8,14 @@ description: |- # google\_compute\_project\_metadata -Manages metadata common to all instances for a project in GCE. For more information see +Authoritatively manages metadata common to all instances for a project in GCE. For more information see [the official documentation](https://cloud.google.com/compute/docs/storing-retrieving-metadata) and [API](https://cloud.google.com/compute/docs/reference/latest/projects/setCommonInstanceMetadata). -~> **Note:** If you want to manage only single key/value pairs within the project metadata -rather than the entire set, then use +~> **Note:** This resource manages all project-level metadata including project-level ssh keys. +Keys unset in config but set on the server will be removed. 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 @@ -33,8 +34,7 @@ resource "google_compute_project_metadata" "default" { The following arguments are supported: -* `metadata` - (Required) A series of key value pairs. Changing this resource - updates the GCE state. +* `metadata` - (Required) A series of key value pairs. - - -