diff --git a/google/image.go b/google/image.go index 3d733e47a0f..dceb69e1b7e 100644 --- a/google/image.go +++ b/google/image.go @@ -196,3 +196,39 @@ func resolveImage(c *Config, project, name string) (string, error) { } return "", fmt.Errorf("Could not find image or family %s", name) } + +// resolvedImageSelfLink takes the output of resolveImage and coerces it into a self_link. +// In the event that a global/images/IMAGE or global/images/family/FAMILY reference is +// returned from resolveImage, providerProject will be used as the project for the self_link. +func resolvedImageSelfLink(providerProject, name string) (string, error) { + switch { + case resolveImageLink.MatchString(name): // https://www.googleapis.com/compute/v1/projects/xyz/global/images/xyz + return name, nil + case resolveImageProjectImage.MatchString(name): // projects/xyz/global/images/xyz + res := resolveImageProjectImage.FindStringSubmatch(name) + if err := sanityTestRegexMatches(2, res, "project image", name); err != nil { + return "", err + } + return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/images/%s", res[1], res[2]), nil + case resolveImageProjectFamily.MatchString(name): // projects/xyz/global/images/family/xyz + res := resolveImageProjectFamily.FindStringSubmatch(name) + if err := sanityTestRegexMatches(2, res, "project family", name); err != nil { + return "", err + } + return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/images/family/%s", res[1], res[2]), nil + case resolveImageGlobalImage.MatchString(name): // global/images/xyz + res := resolveImageGlobalImage.FindStringSubmatch(name) + if err := sanityTestRegexMatches(1, res, "global image", name); err != nil { + return "", err + } + return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/images/%s", providerProject, res[1]), nil + case resolveImageGlobalFamily.MatchString(name): // global/images/family/xyz + res := resolveImageGlobalFamily.FindStringSubmatch(name) + if err := sanityTestRegexMatches(1, res, "global family", name); err != nil { + return "", err + } + return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/images/family/%s", providerProject, res[1]), nil + } + return "", fmt.Errorf("Could not expand image or family %q into a self_link", name) + +} diff --git a/google/resource_compute_instance_template.go b/google/resource_compute_instance_template.go index e30c606894f..3030d3f691d 100644 --- a/google/resource_compute_instance_template.go +++ b/google/resource_compute_instance_template.go @@ -20,6 +20,7 @@ func resourceComputeInstanceTemplate() *schema.Resource { State: schema.ImportStatePassthrough, }, SchemaVersion: 1, + CustomizeDiff: resourceComputeInstanceTemplateSourceImageCustomizeDiff, MigrateState: resourceComputeInstanceTemplateMigrateState, // A compute instance template is more or less a subset of a compute @@ -99,10 +100,9 @@ func resourceComputeInstanceTemplate() *schema.Resource { }, "source_image": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - DiffSuppressFunc: compareSelfLinkRelativePaths, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Computed: true, }, "interface": &schema.Schema{ @@ -420,6 +420,57 @@ func resourceComputeInstanceTemplate() *schema.Resource { } } +func resourceComputeInstanceTemplateSourceImageCustomizeDiff(diff *schema.ResourceDiff, meta interface{}) error { + config := meta.(*Config) + project, err := getProjectFromDiff(diff, config) + if err != nil { + return err + } + numDisks := diff.Get("disk.#").(int) + for i := 0; i < numDisks; i++ { + key := fmt.Sprintf("disk.%d.source_image", i) + if diff.HasChange(key) { + old, new := diff.GetChange(key) + if old == "" || new == "" { + // no sense in resolving empty strings + err = diff.ForceNew(key) + if err != nil { + return err + } + continue + } + oldResolved, err := resolveImage(config, project, old.(string)) + if err != nil { + return err + } + oldResolved, err = resolvedImageSelfLink(project, oldResolved) + if err != nil { + return err + } + newResolved, err := resolveImage(config, project, new.(string)) + if err != nil { + return err + } + newResolved, err = resolvedImageSelfLink(project, newResolved) + if err != nil { + return err + } + if oldResolved != newResolved { + err = diff.ForceNew(key) + if err != nil { + return err + } + continue + } + err = diff.Clear(key) + if err != nil { + return err + } + } + } + return nil +} + func buildDisks(d *schema.ResourceData, config *Config) ([]*computeBeta.AttachedDisk, error) { project, err := getProject(d, config) if err != nil { diff --git a/google/utils.go b/google/utils.go index 0590222af4e..2ced913ff07 100644 --- a/google/utils.go +++ b/google/utils.go @@ -64,6 +64,20 @@ func getProject(d TerraformResourceData, config *Config) (string, error) { return getProjectFromSchema("project", d, config) } +// getProjectFromDiff reads the "project" field from the given diff and falls +// back to the provider's value if not given. If the provider's value is not +// given, an error is returned. +func getProjectFromDiff(d *schema.ResourceDiff, config *Config) (string, error) { + res, ok := d.GetOk("project") + if ok { + return res.(string), nil + } + if config.Project != "" { + return config.Project, nil + } + return "", fmt.Errorf("%s: required field is not set", "project") +} + func getProjectFromInstanceState(is *terraform.InstanceState, config *Config) (string, error) { res, ok := is.Attributes["project"] diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/data_source_resource_shim.go b/vendor/github.com/hashicorp/terraform/helper/schema/data_source_resource_shim.go index 5a03d2d8018..8d93750aede 100644 --- a/vendor/github.com/hashicorp/terraform/helper/schema/data_source_resource_shim.go +++ b/vendor/github.com/hashicorp/terraform/helper/schema/data_source_resource_shim.go @@ -32,7 +32,7 @@ func DataSourceResourceShim(name string, dataSource *Resource) *Resource { // FIXME: Link to some further docs either on the website or in the // changelog, once such a thing exists. - dataSource.deprecationMessage = fmt.Sprintf( + dataSource.DeprecationMessage = fmt.Sprintf( "using %s as a resource is deprecated; consider using the data source instead", name, ) diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource.go index 8f726bfbe47..d3be2d61489 100644 --- a/vendor/github.com/hashicorp/terraform/helper/schema/resource.go +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource.go @@ -124,9 +124,7 @@ type Resource struct { Importer *ResourceImporter // If non-empty, this string is emitted as a warning during Validate. - // This is a private interface for now, for use by DataSourceResourceShim, - // and not for general use. (But maybe later...) - deprecationMessage string + DeprecationMessage string // Timeouts allow users to specify specific time durations in which an // operation should time out, to allow them to extend an action to suit their @@ -269,8 +267,8 @@ func (r *Resource) Diff( func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) { warns, errs := schemaMap(r.Schema).Validate(c) - if r.deprecationMessage != "" { - warns = append(warns, r.deprecationMessage) + if r.DeprecationMessage != "" { + warns = append(warns, r.DeprecationMessage) } return warns, errs diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go index 22d57a5ee06..6cc01ee0bd4 100644 --- a/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go @@ -315,6 +315,7 @@ func (d *ResourceData) State() *terraform.InstanceState { mapW := &MapFieldWriter{Schema: d.schema} if err := mapW.WriteField(nil, rawMap); err != nil { + log.Printf("[ERR] Error writing fields: %s", err) return nil } diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go index 92b891fc5b3..7db3decc5f2 100644 --- a/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go @@ -231,7 +231,7 @@ func (d *ResourceDiff) UpdatedKeys() []string { // Note that this does not wipe an override. This function is only allowed on // computed keys. func (d *ResourceDiff) Clear(key string) error { - if err := d.checkKey(key, "Clear"); err != nil { + if err := d.checkKey(key, "Clear", true); err != nil { return err } @@ -287,7 +287,7 @@ func (d *ResourceDiff) diffChange(key string) (interface{}, interface{}, bool, b // // This function is only allowed on computed attributes. func (d *ResourceDiff) SetNew(key string, value interface{}) error { - if err := d.checkKey(key, "SetNew"); err != nil { + if err := d.checkKey(key, "SetNew", false); err != nil { return err } @@ -299,7 +299,7 @@ func (d *ResourceDiff) SetNew(key string, value interface{}) error { // // This function is only allowed on computed attributes. func (d *ResourceDiff) SetNewComputed(key string) error { - if err := d.checkKey(key, "SetNewComputed"); err != nil { + if err := d.checkKey(key, "SetNewComputed", false); err != nil { return err } @@ -535,12 +535,24 @@ func childAddrOf(child, parent string) bool { } // checkKey checks the key to make sure it exists and is computed. -func (d *ResourceDiff) checkKey(key, caller string) error { - s, ok := d.schema[key] - if !ok { +func (d *ResourceDiff) checkKey(key, caller string, nested bool) error { + var schema *Schema + if nested { + keyParts := strings.Split(key, ".") + schemaL := addrToSchema(keyParts, d.schema) + if len(schemaL) > 0 { + schema = schemaL[len(schemaL)-1] + } + } else { + s, ok := d.schema[key] + if ok { + schema = s + } + } + if schema == nil { return fmt.Errorf("%s: invalid key: %s", caller, key) } - if !s.Computed { + if !schema.Computed { return fmt.Errorf("%s only operates on computed keys - %s is not one", caller, key) } return nil diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/schema.go b/vendor/github.com/hashicorp/terraform/helper/schema/schema.go index f44010ea52e..0ea5aad5585 100644 --- a/vendor/github.com/hashicorp/terraform/helper/schema/schema.go +++ b/vendor/github.com/hashicorp/terraform/helper/schema/schema.go @@ -199,7 +199,7 @@ type Schema struct { Sensitive bool } -// SchemaDiffSuppresFunc is a function which can be used to determine +// SchemaDiffSuppressFunc is a function which can be used to determine // whether a detected diff on a schema element is "valid" or not, and // suppress it from the plan if necessary. // diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/set.go b/vendor/github.com/hashicorp/terraform/helper/schema/set.go index bb194ee6505..cba289035d9 100644 --- a/vendor/github.com/hashicorp/terraform/helper/schema/set.go +++ b/vendor/github.com/hashicorp/terraform/helper/schema/set.go @@ -17,6 +17,12 @@ func HashString(v interface{}) int { return hashcode.String(v.(string)) } +// HashInt hashes integers. If you want a Set of integers, this is the +// SchemaSetFunc you want. +func HashInt(v interface{}) int { + return hashcode.String(strconv.Itoa(v.(int))) +} + // HashResource hashes complex structures that are described using // a *Resource. This is the default set implementation used when a set's // element type is a full resource. diff --git a/vendor/vendor.json b/vendor/vendor.json index f9328fdffdc..128e07b1272 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -665,12 +665,10 @@ "versionExact": "v0.11.2" }, { - "checksumSHA1": "JHxGzmxcIS8NyLX9pGhK5beIra4=", + "checksumSHA1": "OOwTGBTHcUmQTPBdyscTMkjApbI=", "path": "github.com/hashicorp/terraform/helper/schema", - "revision": "41e50bd32a8825a84535e353c3674af8ce799161", - "revisionTime": "2018-04-10T16:50:42Z", - "version": "v0.11.2", - "versionExact": "v0.11.2" + "revision": "35d82b055591e9d47a254e68754216d8849ba67a", + "revisionTime": "2018-09-26T21:21:28Z" }, { "checksumSHA1": "Fzbv+N7hFXOtrR6E7ZcHT3jEE9s=",