Merge pull request #1995 from terraform-providers/paddy_1916_diff_fix

Fix perma-diff on instance templates.
This commit is contained in:
Paddy 2018-10-02 14:13:15 -07:00 committed by GitHub
commit c1d404ddc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 139 additions and 23 deletions

View File

@ -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)
}

View File

@ -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 {

View File

@ -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"]

View File

@ -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,
)

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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.
//

View File

@ -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.

8
vendor/vendor.json vendored
View File

@ -695,12 +695,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=",