Merge pull request #3065 from lwander/gce-project-metadata
GCE project metadata
This commit is contained in:
commit
06c7ff4864
|
@ -43,6 +43,7 @@ func Provider() terraform.ResourceProvider {
|
|||
"google_compute_instance": resourceComputeInstance(),
|
||||
"google_compute_instance_template": resourceComputeInstanceTemplate(),
|
||||
"google_compute_network": resourceComputeNetwork(),
|
||||
"google_compute_project_metadata": resourceComputeProjectMetadata(),
|
||||
"google_compute_route": resourceComputeRoute(),
|
||||
"google_compute_target_pool": resourceComputeTargetPool(),
|
||||
"google_container_cluster": resourceContainerCluster(),
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
// "github.com/hashicorp/terraform/helper/hashcode"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"google.golang.org/api/compute/v1"
|
||||
// "google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
func resourceComputeProjectMetadata() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceComputeProjectMetadataCreate,
|
||||
Read: resourceComputeProjectMetadataRead,
|
||||
Update: resourceComputeProjectMetadataUpdate,
|
||||
Delete: resourceComputeProjectMetadataDelete,
|
||||
|
||||
SchemaVersion: 0,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"metadata": &schema.Schema {
|
||||
Elem: schema.TypeString,
|
||||
Type: schema.TypeMap,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const FINGERPRINT_RETRIES = 10
|
||||
const FINGERPRINT_FAIL = "Invalid fingerprint."
|
||||
|
||||
func resourceOperationWaitGlobal(config *Config, op *compute.Operation, activity string) error {
|
||||
w := &OperationWaiter{
|
||||
Service: config.clientCompute,
|
||||
Op: op,
|
||||
Project: config.Project,
|
||||
Type: OperationWaitGlobal,
|
||||
}
|
||||
|
||||
state := w.Conf()
|
||||
state.Timeout = 2 * time.Minute
|
||||
state.MinTimeout = 1 * time.Second
|
||||
opRaw, err := state.WaitForState()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error waiting for %s: %s", activity, err)
|
||||
}
|
||||
|
||||
op = opRaw.(*compute.Operation)
|
||||
if op.Error != nil {
|
||||
return OperationError(*op.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceComputeProjectMetadataCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
attempt := 0
|
||||
|
||||
config := meta.(*Config)
|
||||
|
||||
for attempt < FINGERPRINT_RETRIES {
|
||||
// Load project service
|
||||
log.Printf("[DEBUG] Loading project service: %s", config.Project)
|
||||
project, err := config.clientCompute.Projects.Get(config.Project).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error loading project '%s': %s", config.Project, 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, config.Project)
|
||||
}
|
||||
}
|
||||
|
||||
// Append new metadata to existing metadata
|
||||
for key, val := range(newMDMap) {
|
||||
md.Items = append(md.Items, &compute.MetadataItems {
|
||||
Key: key,
|
||||
Value: val.(string),
|
||||
})
|
||||
}
|
||||
|
||||
op, err := config.clientCompute.Projects.SetCommonInstanceMetadata(config.Project, 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 recieved to match
|
||||
// the fingerprint we send the server, if there is a mismatch then we
|
||||
// are working on old data, and must retry
|
||||
err = resourceOperationWaitGlobal(config, op, "SetCommonMetadata")
|
||||
if err == nil {
|
||||
return resourceComputeProjectMetadataRead(d, meta)
|
||||
} else if err.Error() == FINGERPRINT_FAIL {
|
||||
attempt++
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Error, unable to set metadata resource after %d attempts", attempt)
|
||||
}
|
||||
|
||||
func resourceComputeProjectMetadataRead(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
// Load project service
|
||||
log.Printf("[DEBUG] Loading project service: %s", config.Project)
|
||||
project, err := config.clientCompute.Projects.Get(config.Project).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error loading project '%s': %s", config.Project, err)
|
||||
}
|
||||
|
||||
md := project.CommonInstanceMetadata
|
||||
|
||||
newMD := make(map[string]interface{})
|
||||
|
||||
for _, kv := range(md.Items) {
|
||||
newMD[kv.Key] = kv.Value
|
||||
}
|
||||
|
||||
if err = d.Set("metadata", newMD); err != nil {
|
||||
return fmt.Errorf("Error setting metadata: %s", err);
|
||||
}
|
||||
|
||||
d.SetId("common_metadata")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceComputeProjectMetadataUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
attempt := 0
|
||||
|
||||
config := meta.(*Config)
|
||||
|
||||
if d.HasChange("metadata") {
|
||||
o, n := d.GetChange("metadata")
|
||||
oMDMap, nMDMap := o.(map[string]interface{}), n.(map[string]interface{})
|
||||
|
||||
for attempt < FINGERPRINT_RETRIES {
|
||||
// Load project service
|
||||
log.Printf("[DEBUG] Loading project service: %s", config.Project)
|
||||
project, err := config.clientCompute.Projects.Get(config.Project).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error loading project '%s': %s", config.Project, err)
|
||||
}
|
||||
|
||||
md := project.CommonInstanceMetadata
|
||||
|
||||
curMDMap := make(map[string]string)
|
||||
// Load metadata on server into map
|
||||
for _, kv := range(md.Items) {
|
||||
// If the server state has a key that we had in our old
|
||||
// state, but not in our new state, we should delete it
|
||||
_, okOld := oMDMap[kv.Key]
|
||||
_, okNew := nMDMap[kv.Key]
|
||||
if okOld && !okNew {
|
||||
continue
|
||||
} else {
|
||||
curMDMap[kv.Key] = kv.Value
|
||||
}
|
||||
}
|
||||
|
||||
// Insert new metadata into existing metadata (overwriting when needed)
|
||||
for key, val := range(nMDMap) {
|
||||
curMDMap[key] = val.(string)
|
||||
}
|
||||
|
||||
// Reformat old metadata into a list
|
||||
md.Items = nil
|
||||
for key, val := range(curMDMap) {
|
||||
md.Items = append(md.Items, &compute.MetadataItems {
|
||||
Key: key,
|
||||
Value: val,
|
||||
})
|
||||
}
|
||||
|
||||
op, err := config.clientCompute.Projects.SetCommonInstanceMetadata(config.Project, 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 recieved to match
|
||||
// the fingerprint we send the server, if there is a mismatch then we
|
||||
// are working on old data, and must retry
|
||||
err = resourceOperationWaitGlobal(config, op, "SetCommonMetadata")
|
||||
if err == nil {
|
||||
return resourceComputeProjectMetadataRead(d, meta)
|
||||
} else if err.Error() == FINGERPRINT_FAIL {
|
||||
attempt++
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Error, unable to set metadata resource after %d attempts", attempt)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceComputeProjectMetadataDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
// Load project service
|
||||
log.Printf("[DEBUG] Loading project service: %s", config.Project)
|
||||
project, err := config.clientCompute.Projects.Get(config.Project).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error loading project '%s': %s", config.Project, err)
|
||||
}
|
||||
|
||||
md := project.CommonInstanceMetadata
|
||||
|
||||
// Remove all items
|
||||
md.Items = nil
|
||||
|
||||
op, err := config.clientCompute.Projects.SetCommonInstanceMetadata(config.Project, md).Do()
|
||||
|
||||
log.Printf("[DEBUG] SetCommonMetadata: %d (%s)", op.Id, op.SelfLink)
|
||||
|
||||
err = resourceOperationWaitGlobal(config, op, "SetCommonMetadata")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return resourceComputeProjectMetadataRead(d, meta)
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"google.golang.org/api/compute/v1"
|
||||
)
|
||||
|
||||
// Add two key value pairs
|
||||
func TestAccComputeProjectMetadata_basic(t *testing.T) {
|
||||
var project compute.Project
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckComputeProjectMetadataDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_basic0_metadata,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "banana", "orange"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "sofa", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 2),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Add three key value pairs, then replace one and modify a second
|
||||
func TestAccComputeProjectMetadata_modify_1(t *testing.T) {
|
||||
var project compute.Project
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckComputeProjectMetadataDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_modify0_metadata,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "paper", "pen"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "genghis_khan", "french bread"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "happy", "smiling"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 3),
|
||||
),
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_modify1_metadata,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "paper", "pen"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "paris", "french bread"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "happy", "laughing"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 3),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Add two key value pairs, and replace both
|
||||
func TestAccComputeProjectMetadata_modify_2(t *testing.T) {
|
||||
var project compute.Project
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckComputeProjectMetadataDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_basic0_metadata,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "banana", "orange"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "sofa", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 2),
|
||||
),
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_basic1_metadata,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "kiwi", "papaya"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "finches", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 2),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckComputeProjectMetadataDestroy(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
|
||||
project, err := config.clientCompute.Projects.Get(config.Project).Do()
|
||||
if err == nil && len(project.CommonInstanceMetadata.Items) > 0 {
|
||||
return fmt.Errorf("Error, metadata items still exist")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckComputeProjectExists(n string, project *compute.Project) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
|
||||
found, err := config.clientCompute.Projects.Get(
|
||||
config.Project).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if "common_metadata" != rs.Primary.ID {
|
||||
return fmt.Errorf("Common metadata not found, found %s", rs.Primary.ID)
|
||||
}
|
||||
|
||||
*project = *found
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckComputeProjectMetadataContains(project *compute.Project, key string, 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 fmt.Errorf("Error, failed to load project service for %s: %s", config.Project, err)
|
||||
}
|
||||
|
||||
for _, kv := range(project.CommonInstanceMetadata.Items) {
|
||||
if kv.Key == key {
|
||||
if (kv.Value == value) {
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("Error, key value mismatch, wanted (%s, %s), got (%s, %s)",
|
||||
key, value, kv.Key, kv.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Error, key %s not present", key)
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckComputeProjectMetadataSize(project *compute.Project, size int) 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 fmt.Errorf("Error, failed to load project service for %s: %s", config.Project, err)
|
||||
}
|
||||
|
||||
if size > len(project.CommonInstanceMetadata.Items) {
|
||||
return fmt.Errorf("Error, expected at least %d metadata items, got %d", size,
|
||||
len(project.CommonInstanceMetadata.Items))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccComputeProject_basic0_metadata = `
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
metadata {
|
||||
banana = "orange"
|
||||
sofa = "darwinism"
|
||||
}
|
||||
}`
|
||||
|
||||
const testAccComputeProject_basic1_metadata = `
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
metadata {
|
||||
kiwi = "papaya"
|
||||
finches = "darwinism"
|
||||
}
|
||||
}`
|
||||
|
||||
const testAccComputeProject_modify0_metadata = `
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
metadata {
|
||||
paper = "pen"
|
||||
genghis_khan = "french bread"
|
||||
happy = "smiling"
|
||||
}
|
||||
}`
|
||||
|
||||
const testAccComputeProject_modify1_metadata = `
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
metadata {
|
||||
paper = "pen"
|
||||
paris = "french bread"
|
||||
happy = "laughing"
|
||||
}
|
||||
}`
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
layout: "google"
|
||||
page_title: "Google: google_compute_project_metadata"
|
||||
sidebar_current: "docs-google-resource-project-metadata"
|
||||
description: |-
|
||||
Manages common instance metadata
|
||||
---
|
||||
|
||||
# google\_compute\_project\_metadata
|
||||
|
||||
Manages metadata common to all instances for a project in GCE.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "google_compute_project_metadata" "default" {
|
||||
metadata {
|
||||
foo = "bar"
|
||||
fizz = "buzz"
|
||||
13 = "42"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `metadata` - (Required) A series of key value pairs. Changing this resource updates
|
||||
the GCE state.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `metadata` - Common instance metadata.
|
|
@ -53,6 +53,10 @@
|
|||
<a href="/docs/providers/google/r/compute_network.html">google_compute_network</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-google-resource-project-metadata") %>>
|
||||
<a href="/docs/providers/google/r/compute_project_metadata.html">google_compute_project_metadata</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-google-resource-route") %>>
|
||||
<a href="/docs/providers/google/r/compute_route.html">google_compute_route</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue