in tpgtools/property.go [517:1003]
func createPropertiesFromSchema(schema *openapi.Schema, typeFetcher *TypeFetcher, overrides Overrides, resource *Resource, parent *Property, location string) (props []Property, err error) {
identityFields := []string{} // always empty if parent != nil
if parent == nil {
identityFields = idParts(resource.ID)
}
// Maps PackageJSONName back to property Name
// for conflict fields
conflictsMap := make(map[string]string)
for k, v := range schema.Properties {
ref := ""
packageName := ""
if pName, ok := v.Extension["x-dcl-go-name"].(string); ok {
packageName = pName
}
if v.Ref != "" {
ref = v.Ref
v, err = typeFetcher.ResolveSchema(v.Ref)
if err != nil {
return nil, err
}
ref = typeFetcher.PackagePathForReference(ref, v.Extension["x-dcl-go-type"].(string))
}
// Sub-properties are referenced by name, and the explicit title value
// won't be set initially.
v.Title = k
if parent == nil && v.Title == "id" {
// If top-level field is named `id`, rename to avoid collision with Terraform id
v.Title = fmt.Sprintf("%s%s", resource.Name(), "Id")
}
p := Property{
title: jsonToSnakeCase(v.Title).snakecase(),
Type: Type{typ: v},
PackageName: packageName,
Description: v.Description,
resource: resource,
parent: parent,
ref: ref,
}
if overrides.PropertyOverride(Exclude, p, location) {
continue
}
do := CustomDefaultDetails{}
doOk, err := overrides.PropertyOverrideWithDetails(CustomDefault, p, &do, location)
if err != nil {
return nil, fmt.Errorf("failed to decode custom list size details")
}
if v.Default != "" || doOk {
def := v.Default
if doOk {
def = do.Default
}
d, err := renderDefault(p.Type, def)
if err != nil {
return nil, fmt.Errorf("failed to render default: %v", err)
}
p.Default = &d
}
cn := CustomNameDetails{}
cnOk, err := overrides.PropertyOverrideWithDetails(CustomName, p, &cn, location)
if err != nil {
return nil, fmt.Errorf("failed to decode custom name details: %v", err)
}
if cnOk {
p.customName = cn.Name
}
if p.Type.String() == SchemaTypeMap {
e := "&schema.Schema{Type: schema.TypeString}"
p.Elem = &e
p.ElemIsBasicType = true
}
if sens, ok := v.Extension["x-dcl-sensitive"].(bool); ok {
p.Sensitive = sens
}
if v, ok := v.Extension["x-dcl-conflicts"].([]interface{}); ok {
// NOTE: DCL not label x-dcl-conflicts for reused types
// TODO(shuya): handle nested field when b/213503595 got fixed
if parent == nil {
for _, ci := range v {
p.JSONCaseConflictsWith = append(p.JSONCaseConflictsWith, ci.(string))
}
conflictsMap[p.PackageJSONName()] = p.Name()
}
}
// Do this before handling properties so we can check if the parent is readOnly
isSGP := false
if sgp, ok := v.Extension["x-dcl-server-generated-parameter"].(bool); ok {
isSGP = sgp
}
if v.ReadOnly || isSGP || (parent != nil && parent.Computed) {
p.Computed = true
if stringInSlice(p.Name(), identityFields) {
sg := p.DefaultStateGetter()
p.StateGetter = &sg
}
}
p.Parameter, _ = v.Extension["x-dcl-parameter"].(bool)
p.HasLongForm, _ = v.Extension["x-dcl-has-long-form"].(bool)
// Handle object properties
if len(v.Properties) > 0 {
props, err := createPropertiesFromSchema(v, typeFetcher, overrides, resource, &p, location)
if err != nil {
return nil, err
}
p.Properties = props
if !p.Computed {
// Computed fields cannot specify MaxItems
mi := int64(1)
p.MaxItems = &mi
}
e := fmt.Sprintf("%s%sSchema()", resource.PathType(), p.PackagePath())
p.Elem = &e
p.ElemIsBasicType = false
}
// Handle array properties
if v.Items != nil {
ls := CustomListSizeConstraintDetails{}
lsOk, err := overrides.PropertyOverrideWithDetails(CustomListSize, p, &ls, location)
if err != nil {
return nil, fmt.Errorf("failed to decode custom list size details")
}
if lsOk {
if ls.Max > 0 {
p.MaxItems = &ls.Max
}
if ls.Min > 0 {
p.MinItems = &ls.Min
}
}
// We end up handling arrays of objects very similarly to nested objects
// themselves
if len(v.Items.Properties) > 0 {
props, err := createPropertiesFromSchema(v.Items, typeFetcher, overrides, resource, &p, location)
if err != nil {
return nil, err
}
p.Properties = props
e := fmt.Sprintf("%s%sSchema()", resource.PathType(), p.PackagePath())
p.Elem = &e
p.ElemIsBasicType = false
} else {
i := Type{typ: v.Items}
e := fmt.Sprintf("&schema.Schema{Type: schema.%s}", i.String())
if _, ok := v.Extension["x-dcl-references"]; ok {
e = fmt.Sprintf("&schema.Schema{Type: schema.%s, DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, }", i.String())
}
p.Elem = &e
p.ElemIsBasicType = true
}
}
// Complex maps are represented as TypeSet but don't have v.Items set.
// Use AdditionalProperties instead, and add an additional `name` field
// that represents the key in the map
if p.Type.IsComplexMap() {
props, err := createPropertiesFromSchema(p.Type.typ.AdditionalProperties, typeFetcher, overrides, resource, &p, location)
if err != nil {
return nil, err
}
cm := ComplexMapKeyDetails{}
cmOk, err := overrides.PropertyOverrideWithDetails(ComplexMapKey, p, &cm, location)
if err != nil {
return nil, fmt.Errorf("failed to decode complex map key name details")
}
if !cmOk {
return nil, fmt.Errorf("failed to find complex map key name for map named: %s", p.Name())
}
keyProp := Property{
title: cm.KeyName,
Type: Type{&openapi.Schema{Type: "string"}},
resource: resource,
parent: &p,
Required: true,
Description: "The name for the key in the map for which this object is mapped to in the API",
}
props = append([]Property{keyProp}, props...)
p.Properties = props
e := fmt.Sprintf("%s%sSchema()", resource.PathType(), p.PackagePath())
p.Elem = &e
p.ElemIsBasicType = false
p.ComplexMapKeyName = cm.KeyName
}
if !p.Computed {
if stringInSlice(v.Title, schema.Required) {
p.Required = true
} else {
p.Optional = true
}
}
cr := CustomSchemaValuesDetails{}
crOk, err := overrides.PropertyOverrideWithDetails(CustomSchemaValues, p, &cr, location)
if err != nil {
return nil, fmt.Errorf("failed to decode custom required details")
}
if crOk {
p.Required = cr.Required
p.Optional = cr.Optional
p.Computed = cr.Computed
}
// Handle settable fields. If the field is computed it's not settable but
// if it's also optional (O+C), it is.
if !p.Computed || (p.Optional) {
p.Settable = true
// NOTE: x-kubernetes-immmutable implies that all children of a field
// are actually immutable. However, in practice, DCL specs will label
// every immutable subfield.
if isImmutable, ok := v.Extension["x-kubernetes-immutable"].(bool); ok && isImmutable {
p.ForceNew = true
}
serverDefault, _ := v.Extension["x-dcl-server-default"].(bool)
extractIfEmpty, _ := v.Extension["x-dcl-extract-if-empty"].(bool)
if serverDefault || extractIfEmpty {
p.Computed = true
}
if forwardSlashAllowed, ok := v.Extension["x-dcl-forward-slash-allowed"].(bool); ok && forwardSlashAllowed {
p.forwardSlashAllowed = true
}
// special handling for project/region/zone/other fields with
// provider defaults
if stringInSlice(p.title, []string{"project", "region", "zone"}) || stringInSlice(p.customName, []string{"region", "project", "zone"}) {
p.Optional = true
p.Required = false
p.Computed = true
sg := fmt.Sprintf("dcl.String(%v)", p.Name())
p.StateGetter = &sg
cig := &CustomIdentityGetterDetails{}
cigOk, err := overrides.PropertyOverrideWithDetails(CustomIdentityGetter, p, cig, location)
if err != nil {
return nil, fmt.Errorf("failed to decode custom identity getter details")
}
propertyName := p.title
if p.customName != "" {
propertyName = p.customName
}
ig := fmt.Sprintf("tpgresource.Get%s(d, config)", renderSnakeAsTitle(miscellaneousNameSnakeCase(propertyName)))
if cigOk {
ig = fmt.Sprintf("%s(d, config)", cig.Function)
}
p.IdentityGetter = &ig
} else {
sg := p.DefaultStateGetter()
p.StateGetter = &sg
}
}
ss := p.DefaultStateSetter()
p.StateSetter = &ss
if p.Sensitive && p.Settable {
p.StateSetter = nil
}
css := CustomStateSetterDetails{}
cssOk, err := overrides.PropertyOverrideWithDetails(CustomStateSetter, p, &css, location)
if err != nil {
return nil, fmt.Errorf("failed to decode custom stateSetter func: %v", err)
}
if cssOk {
p.StateSetter = &css.Function
}
irOk := overrides.PropertyOverride(IgnoreRead, p, location)
if irOk {
p.StateSetter = nil
}
cd := CustomDescriptionDetails{}
cdOk, err := overrides.PropertyOverrideWithDetails(CustomDescription, p, &cd, location)
if err != nil {
return nil, fmt.Errorf("failed to decode custom description details: %v", err)
}
if cdOk {
p.Description = cd.Description
}
dsf := CustomDiffSuppressFuncDetails{}
dsfOk, err := overrides.PropertyOverrideWithDetails(DiffSuppressFunc, p, &dsf, location)
if err != nil {
return nil, fmt.Errorf("failed to decode custom diff suppress func: %v", err)
}
if dsfOk {
p.DiffSuppressFunc = &dsf.DiffSuppressFunc
} else if !(p.Computed && !p.Optional) {
p.DiffSuppressFunc = p.DefaultDiffSuppress()
}
vf := CustomValidationDetails{}
vfOk, err := overrides.PropertyOverrideWithDetails(CustomValidation, p, &vf, location)
if err != nil {
return nil, fmt.Errorf("failed to decode custom validation func: %v", err)
}
if vfOk {
p.ValidateFunc = &vf.Function
}
if p.Type.IsSet() {
shf := SetHashFuncDetails{}
shfOk, err := overrides.PropertyOverrideWithDetails(SetHashFunc, p, &shf, location)
if err != nil {
return nil, fmt.Errorf("failed to decode set hash func: %v", err)
}
if shfOk {
p.SetHashFunc = &shf.Function
} else {
p.SetHashFunc = p.DefaultSetHashFunc()
}
}
cm := CustomConfigModeDetails{}
cmOk, err := overrides.PropertyOverrideWithDetails(CustomConfigMode, p, &cm, location)
if err != nil {
return nil, fmt.Errorf("failed to decode custom config mode func: %v", err)
}
if cmOk {
p.ConfigMode = &cm.Mode
}
rd := &RemovedDetails{}
rdOk, err := overrides.PropertyOverrideWithDetails(Removed, p, rd, location)
if err != nil {
return nil, fmt.Errorf("failed to decode removed details")
}
if rdOk {
p.Removed = &rd.Message
}
dd := &DeprecatedDetails{}
ddOk, err := overrides.PropertyOverrideWithDetails(Deprecated, p, dd, location)
if err != nil {
return nil, fmt.Errorf("failed to decode deprecated details")
}
if ddOk {
p.Deprecated = &dd.Message
}
if overrides.PropertyOverride(CollapsedObject, p, location) {
p.Collapsed = true
if p.parent == nil {
collapseSS := fmt.Sprintf("setStateForCollapsedObject(d, %s)", p.flattenGetterWithParent("res"))
p.StateSetter = &collapseSS
}
collapseSG := fmt.Sprintf("expand%s%sCollapsed(d)", p.resource.PathType(), p.PackagePath())
p.StateGetter = &collapseSG
}
// Add any new imports as needed
if ls := p.GetRequiredFileImports(); len(ls) > 0 {
resource.additionalFileImportSet.Add(ls...)
}
csgd := CustomStateGetterDetails{}
csgdOk, err := overrides.PropertyOverrideWithDetails(CustomStateGetter, p, &csgd, location)
if err != nil {
return nil, fmt.Errorf("failed to decode custom state getter details with err %v", err)
}
if csgdOk {
p.StateGetter = &csgd.Function
}
if overrides.PropertyOverride(EnumBool, p, location) {
p.EnumBool = true
p.Type.typ.Type = "string"
var parent string
if p.parent == nil {
parent = "res"
} else {
parent = "obj"
}
enumBoolSS := fmt.Sprintf("d.Set(%q, tpgdclresource.FlattenEnumBool(%s.%s))", p.Name(), parent, p.PackageName)
p.StateSetter = &enumBoolSS
enumBoolSG := fmt.Sprintf("tpgdclresource.ExpandEnumBool(d.Get(%q))", p.Name())
p.StateGetter = &enumBoolSG
}
if overrides.PropertyOverride(GenerateIfNotSet, p, location) {
p.Computed = true
p.Required = false
p.Optional = true
ig := fmt.Sprintf("generateIfNotSet(d, %q, %q)", p.Name(), "tfgen")
p.IdentityGetter = &ig
n := fmt.Sprintf("&%s", p.Name())
p.StateGetter = &n
}
if overrides.PropertyOverride(NamePrefix, p, location) {
p.Computed = true
p.Required = false
p.Optional = true
ig := fmt.Sprintf("generateIfNotSet(d, %q, d.Get(%q).(string))", p.Name(), "name_prefix")
p.IdentityGetter = &ig
n := fmt.Sprintf("&%s", p.Name())
p.StateGetter = &n
// plus, add the "name_prefix" property.
props = append(props, Property{
title: "name_prefix",
Type: p.Type,
resource: resource,
parent: parent,
Optional: true,
Computed: true,
ForceNew: true,
})
}
if p.ref != "" {
resource.ReusedTypes = resource.RegisterReusedType(p)
}
// Add the "effective_labels" property when the current property is top level "labels" or
// add the "effective_annotations" property when the current property is top level "annotations"
if p.IsResourceLabels() || p.IsResourceAnnotations() {
p.Description = fmt.Sprintf("%s\n\n%s", p.Description, get_labels_field_note(p.title))
p.Settable = false
p.StateGetter = nil
props = append(props, build_effective_labels_field(p, resource, parent))
if p.IsResourceLabels() {
props = append(props, build_terraform_labels_field(p, resource, parent))
p.ForceNew = false
}
}
props = append(props, p)
}
// handle conflict fields
for i, _ := range props {
p := &props[i]
if p.JSONCaseConflictsWith != nil {
for _, cf := range p.JSONCaseConflictsWith {
if val, ok := conflictsMap[cf]; ok {
p.ConflictsWith = append(p.ConflictsWith, val)
} else {
return nil, fmt.Errorf("Error generating conflict fields. %s is not labeled as a conflict field in DCL", cf)
}
}
}
}
// sort the properties so they're in a nice order
sort.SliceStable(props, propComparator(props))
return props, nil
}