Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for google_compute_project_metadata_item #176

Merged
merged 3 commits into from
Jul 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ FEATURES:

* **New Resource:** `google_bigtable_instance` [GH-177]
* **New Resource:** `google_bigtable_table` [GH-177]
* **New Resource:** `google_compute_project_metadata_item` - allows management of single key/value pairs within the project metadata map [GH-176]

IMPROVEMENTS:

Expand Down
25 changes: 25 additions & 0 deletions google/import_compute_project_metadata_item_test.go
Original file line number Diff line number Diff line change
@@ -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,
},
},
})
}
38 changes: 38 additions & 0 deletions google/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package google

import (
"fmt"
"log"

"google.golang.org/api/compute/v1"
)
Expand Down Expand Up @@ -71,3 +72,40 @@ func MetadataFormatSchema(curMDMap map[string]interface{}, md *compute.Metadata)

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 {
metadata[idx] = &compute.MetadataItems{Key: key, Value: &value}
idx++
}

return metadata
}
1 change: 1 addition & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func Provider() terraform.ResourceProvider {
"google_compute_instance_template": resourceComputeInstanceTemplate(),
"google_compute_network": resourceComputeNetwork(),
"google_compute_project_metadata": resourceComputeProjectMetadata(),
"google_compute_project_metadata_item": resourceComputeProjectMetadataItem(),
"google_compute_region_backend_service": resourceComputeRegionBackendService(),
"google_compute_route": resourceComputeRoute(),
"google_compute_router": resourceComputeRouter(),
Expand Down
178 changes: 178 additions & 0 deletions google/resource_compute_project_metadata_item.go
Original file line number Diff line number Diff line change
@@ -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 computeOperationWaitGlobal(config, op, project.Name, "SetCommonInstanceMetadata")
}

return MetadataRetryWrapper(updateMD)
}
118 changes: 118 additions & 0 deletions google/resource_compute_project_metadata_item_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading