terraform/builtin/providers/google/resource_compute_project_me...

245 lines
6.5 KiB
Go

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 {
v := val.(string)
md.Items = append(md.Items, &compute.MetadataItems{
Key: key,
Value: &v,
})
}
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 {
if kv.Value != nil {
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)
}