tools/diff-processor/breaking_changes/field_diff.go (169 lines of code) (raw):
package breaking_changes
import (
"fmt"
"strconv"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/diff"
)
// FieldDiffRule provides structure for rules
// regarding field attribute changes
type FieldDiffRule struct {
Identifier string
Messages func(resource, field string, fieldDiff diff.FieldDiff) []string
}
// FieldDiffRules is a list of FieldDiffRule
// guarding against provider breaking changes
var FieldDiffRules = []FieldDiffRule{
FieldChangingType,
FieldBecomingRequired,
FieldBecomingComputedOnly,
FieldOptionalComputedToOptional,
FieldDefaultModification,
FieldGrowingMin,
FieldShrinkingMax,
FieldRemovingDiffSuppress,
}
var FieldChangingType = FieldDiffRule{
Identifier: "field-changing-type",
Messages: FieldChangingTypeMessages,
}
func FieldChangingTypeMessages(resource, field string, fieldDiff diff.FieldDiff) []string {
// Type change doesn't matter for added / removed fields
if fieldDiff.Old == nil || fieldDiff.New == nil {
return nil
}
tmpl := "Field `%s` changed from %s to %s on `%s`"
if fieldDiff.Old.Type != fieldDiff.New.Type {
oldType := getValueType(fieldDiff.Old.Type)
newType := getValueType(fieldDiff.New.Type)
return []string{fmt.Sprintf(tmpl, field, oldType, newType, resource)}
}
oldCasted, _ := fieldDiff.Old.Elem.(*schema.Schema)
newCasted, _ := fieldDiff.New.Elem.(*schema.Schema)
if oldCasted != nil && newCasted != nil && oldCasted.Type != newCasted.Type {
oldType := getValueType(fieldDiff.Old.Type) + "." + getValueType(oldCasted.Type)
newType := getValueType(fieldDiff.New.Type) + "." + getValueType(newCasted.Type)
return []string{fmt.Sprintf(tmpl, field, oldType, newType, resource)}
}
return nil
}
var FieldBecomingRequired = FieldDiffRule{
Identifier: "field-optional-to-required",
Messages: FieldBecomingRequiredMessages,
}
func FieldBecomingRequiredMessages(resource, field string, fieldDiff diff.FieldDiff) []string {
// Ignore for added / removed fields
if fieldDiff.Old == nil || fieldDiff.New == nil {
return nil
}
tmpl := "Field `%s` changed from optional to required on `%s`"
if !fieldDiff.Old.Required && fieldDiff.New.Required {
return []string{fmt.Sprintf(tmpl, field, resource)}
}
return nil
}
var FieldBecomingComputedOnly = FieldDiffRule{
Identifier: "field-becoming-computed",
Messages: FieldBecomingComputedOnlyMessages,
}
func FieldBecomingComputedOnlyMessages(resource, field string, fieldDiff diff.FieldDiff) []string {
// ignore for added / removed fields
if fieldDiff.Old == nil || fieldDiff.New == nil {
return nil
}
// if the field is computed only already
// this rule doesn't apply
if fieldDiff.Old.Computed && !fieldDiff.Old.Optional {
return nil
}
tmpl := "Field `%s` became Computed only on `%s`"
if fieldDiff.New.Computed && !fieldDiff.New.Optional {
return []string{fmt.Sprintf(tmpl, field, resource)}
}
return nil
}
var FieldOptionalComputedToOptional = FieldDiffRule{
Identifier: "field-oc-to-c",
Messages: FieldOptionalComputedToOptionalMessages,
}
func FieldOptionalComputedToOptionalMessages(resource, field string, fieldDiff diff.FieldDiff) []string {
// ignore for added / removed fields
if fieldDiff.Old == nil || fieldDiff.New == nil {
return nil
}
tmpl := "Field `%s` transitioned from optional+computed to optional `%s`"
if (fieldDiff.Old.Computed && fieldDiff.Old.Optional) && (fieldDiff.New.Optional && !fieldDiff.New.Computed) {
return []string{fmt.Sprintf(tmpl, field, resource)}
}
return nil
}
var FieldDefaultModification = FieldDiffRule{
Identifier: "field-changing-default-value",
Messages: FieldDefaultModificationMessages,
}
func FieldDefaultModificationMessages(resource, field string, fieldDiff diff.FieldDiff) []string {
// ignore for added / removed fields
if fieldDiff.Old == nil || fieldDiff.New == nil {
return nil
}
if fieldDiff.Old.Default != fieldDiff.New.Default {
tmpl := "Field `%s` default value changed from `%s` to `%s` on `%s`"
oldDefault := formatDefaultValue(fieldDiff.Old.Default)
newDefault := formatDefaultValue(fieldDiff.New.Default)
return []string{fmt.Sprintf(tmpl, field, oldDefault, newDefault, resource)}
}
return nil
}
// formatDefaultValue properly formats default values to distinguish between nil, empty string, and other values
func formatDefaultValue(value interface{}) string {
if value == nil {
return "<nil>"
}
// Special handling for empty strings
if s, ok := value.(string); ok && s == "" {
return `""`
}
return fmt.Sprintf("%v", value)
}
var FieldGrowingMin = FieldDiffRule{
Identifier: "field-growing-min",
Messages: FieldGrowingMinMessages,
}
func FieldGrowingMinMessages(resource, field string, fieldDiff diff.FieldDiff) []string {
// ignore for added / removed fields
if fieldDiff.Old == nil || fieldDiff.New == nil {
return nil
}
tmpl := "Field `%s` MinItems went from %s to %s on `%s`"
if fieldDiff.Old.MinItems < fieldDiff.New.MinItems {
oldMin := strconv.Itoa(fieldDiff.Old.MinItems)
if fieldDiff.Old.MinItems == 0 {
oldMin = "unset"
}
newMin := strconv.Itoa(fieldDiff.New.MinItems)
return []string{fmt.Sprintf(tmpl, field, oldMin, newMin, resource)}
}
return nil
}
var FieldShrinkingMax = FieldDiffRule{
Identifier: "field-shrinking-max",
Messages: FieldShrinkingMaxMessages,
}
func FieldShrinkingMaxMessages(resource, field string, fieldDiff diff.FieldDiff) []string {
// ignore for added / removed fields
if fieldDiff.Old == nil || fieldDiff.New == nil {
return nil
}
tmpl := "Field `%s` MaxItems went from %s to %s on `%s`"
if fieldDiff.New.MaxItems == 0 {
return nil
}
newMax := strconv.Itoa(fieldDiff.New.MaxItems)
if fieldDiff.Old.MaxItems == 0 {
return []string{fmt.Sprintf(tmpl, field, "unset", newMax, resource)}
}
oldMax := strconv.Itoa(fieldDiff.Old.MaxItems)
if fieldDiff.Old.MaxItems > fieldDiff.New.MaxItems {
return []string{fmt.Sprintf(tmpl, field, oldMax, newMax, resource)}
}
return nil
}
var FieldRemovingDiffSuppress = FieldDiffRule{
Identifier: "field-removing-diff-suppress",
Messages: FieldRemovingDiffSuppressMessages,
}
func FieldRemovingDiffSuppressMessages(resource, field string, fieldDiff diff.FieldDiff) []string {
// ignore for added / removed fields
if fieldDiff.Old == nil || fieldDiff.New == nil {
return nil
}
// TODO: Add resource to this message
tmpl := "Field `%s` lost its diff suppress function"
if fieldDiff.Old.DiffSuppressFunc != nil && fieldDiff.New.DiffSuppressFunc == nil {
return []string{fmt.Sprintf(tmpl, field)}
}
return nil
}