diff --git a/pkg/config/common.go b/pkg/config/common.go index 64aff8b7..f1c50363 100644 --- a/pkg/config/common.go +++ b/pkg/config/common.go @@ -82,6 +82,9 @@ func DefaultResource(name string, terraformSchema *schema.Resource, terraformReg func MoveToStatus(sch *schema.Resource, fieldpaths ...string) { for _, f := range fieldpaths { s := GetSchema(sch, f) + if s == nil { + return + } s.Optional = false s.Computed = true @@ -104,9 +107,10 @@ func MoveToStatus(sch *schema.Resource, fieldpaths ...string) { // schemas. func MarkAsRequired(sch *schema.Resource, fieldpaths ...string) { for _, fieldpath := range fieldpaths { - s := GetSchema(sch, fieldpath) - s.Computed = false - s.Optional = false + if s := GetSchema(sch, fieldpath); s != nil { + s.Computed = false + s.Optional = false + } } } diff --git a/pkg/config/common_test.go b/pkg/config/common_test.go index 4d8d2363..35cf3bbb 100644 --- a/pkg/config/common_test.go +++ b/pkg/config/common_test.go @@ -135,6 +135,27 @@ func TestMoveToStatus(t *testing.T) { args want }{ + "DoesNotExist": { + args: args{ + fields: []string{"topD"}, + sch: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "topA": {Type: schema.TypeString}, + "topB": {Type: schema.TypeInt}, + "topC": {Type: schema.TypeString, Optional: true}, + }, + }, + }, + want: want{ + sch: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "topA": {Type: schema.TypeString}, + "topB": {Type: schema.TypeInt}, + "topC": {Type: schema.TypeString, Optional: true}, + }, + }, + }, + }, "TopLevelBasicFields": { args: args{ fields: []string{"topA", "topB"}, @@ -263,6 +284,27 @@ func TestMarkAsRequired(t *testing.T) { args want }{ + "DoesNotExist": { + args: args{ + fields: []string{"topD"}, + sch: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "topA": {Type: schema.TypeString}, + "topB": {Type: schema.TypeInt, Computed: true}, + "topC": {Type: schema.TypeString, Optional: true}, + }, + }, + }, + want: want{ + sch: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "topA": {Type: schema.TypeString}, + "topB": {Type: schema.TypeInt, Computed: true}, + "topC": {Type: schema.TypeString, Optional: true}, + }, + }, + }, + }, "TopLevelBasicFields": { args: args{ fields: []string{"topB", "topC"}, diff --git a/pkg/examples/example.go b/pkg/examples/example.go index 278f1cfd..7130d6cd 100644 --- a/pkg/examples/example.go +++ b/pkg/examples/example.go @@ -16,6 +16,7 @@ import ( "strings" "github.com/crossplane/crossplane-runtime/pkg/fieldpath" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/pkg/errors" "sigs.k8s.io/yaml" @@ -23,6 +24,7 @@ import ( "github.com/upbound/upjet/pkg/registry/reference" "github.com/upbound/upjet/pkg/resource/json" tjtypes "github.com/upbound/upjet/pkg/types" + "github.com/upbound/upjet/pkg/types/name" ) var ( @@ -92,7 +94,7 @@ func (eg *Generator) StoreExamples() error { // nolint:gocyclo if err := json.TFParser.Unmarshal([]byte(re.Dependencies[dn]), &exampleParams); err != nil { return errors.Wrapf(err, "cannot unmarshal example manifest for resource: %s", dr.Config.Name) } - pmd := paveCRManifest(exampleParams, dr.FieldTransformations, dr.Config, + pmd := paveCRManifest(exampleParams, dr.Config, reference.NewRefPartsFromResourceName(dn).ExampleName, dr.Group, dr.Version) if err := eg.writeManifest(&buff, pmd, context); err != nil { return errors.Wrapf(err, "cannot store example manifest for %s dependency: %s", rn, dn) @@ -107,8 +109,10 @@ func (eg *Generator) StoreExamples() error { // nolint:gocyclo return nil } -func paveCRManifest(exampleParams map[string]any, fieldTransformations map[string]tjtypes.Transformation, r *config.Resource, eName, group, version string) *reference.PavedWithManifest { - transformFields(r, exampleParams, r.ExternalName.OmittedFields, fieldTransformations, "") +func paveCRManifest(exampleParams map[string]any, r *config.Resource, eName, group, version string) *reference.PavedWithManifest { + delete(exampleParams, "depends_on") + delete(exampleParams, "lifecycle") + transformFields(r, exampleParams, r.ExternalName.OmittedFields, "") example := map[string]any{ "apiVersion": fmt.Sprintf("%s/%s", group, version), "kind": r.Kind, @@ -122,12 +126,11 @@ func paveCRManifest(exampleParams map[string]any, fieldTransformations map[strin }, } return &reference.PavedWithManifest{ - Paved: fieldpath.Pave(example), - ParamsPrefix: []string{"spec", "forProvider"}, - FieldTransformations: fieldTransformations, - Config: r, - Group: group, - Version: version, + Paved: fieldpath.Pave(example), + ParamsPrefix: []string{"spec", "forProvider"}, + Config: r, + Group: group, + Version: version, } } @@ -148,7 +151,6 @@ func (eg *Generator) writeManifest(writer io.Writer, pm *reference.PavedWithMani return errors.Wrapf(err, `cannot set "metadata.name" for resource %q:%s`, pm.Config.Name, pm.ExampleName) } u := pm.Paved.UnstructuredContent() - delete(u["spec"].(map[string]any)["forProvider"].(map[string]any), "depends_on") buff, err := yaml.Marshal(u) if err != nil { return errors.Wrap(err, "cannot marshal example resource manifest") @@ -161,12 +163,12 @@ func (eg *Generator) writeManifest(writer io.Writer, pm *reference.PavedWithMani } // Generate generates an example manifest for the specified Terraform resource. -func (eg *Generator) Generate(group, version string, r *config.Resource, fieldTransformations map[string]tjtypes.Transformation) error { +func (eg *Generator) Generate(group, version string, r *config.Resource) error { rm := eg.configResources[r.Name].MetaResource if rm == nil || len(rm.Examples) == 0 { return nil } - pm := paveCRManifest(rm.Examples[0].Paved.UnstructuredContent(), fieldTransformations, r, rm.Examples[0].Name, group, version) + pm := paveCRManifest(rm.Examples[0].Paved.UnstructuredContent(), r, rm.Examples[0].Name, group, version) manifestDir := filepath.Join(eg.rootDir, "examples-generated", strings.ToLower(strings.Split(group, ".")[0])) pm.ManifestPath = filepath.Join(manifestDir, fmt.Sprintf("%s.yaml", strings.ToLower(r.Kind))) eg.resources[fmt.Sprintf("%s.%s", r.Name, reference.Wildcard)] = pm @@ -188,7 +190,7 @@ func isStatus(r *config.Resource, attr string) bool { return tjtypes.IsObservation(s) } -func transformFields(r *config.Resource, params map[string]any, omittedFields []string, t map[string]tjtypes.Transformation, namePrefix string) { // nolint:gocyclo +func transformFields(r *config.Resource, params map[string]any, omittedFields []string, namePrefix string) { // nolint:gocyclo for n := range params { hName := getHierarchicalName(namePrefix, n) if isStatus(r, hName) { @@ -206,7 +208,7 @@ func transformFields(r *config.Resource, params map[string]any, omittedFields [] for n, v := range params { switch pT := v.(type) { case map[string]any: - transformFields(r, pT, omittedFields, t, getHierarchicalName(namePrefix, n)) + transformFields(r, pT, omittedFields, getHierarchicalName(namePrefix, n)) case []any: for _, e := range pT { @@ -214,38 +216,41 @@ func transformFields(r *config.Resource, params map[string]any, omittedFields [] if !ok { continue } - transformFields(r, eM, omittedFields, t, getHierarchicalName(namePrefix, n)) + transformFields(r, eM, omittedFields, getHierarchicalName(namePrefix, n)) } } } for n, v := range params { - hName := getHierarchicalName(namePrefix, n) - for hn, transform := range t { - if hn != hName { - continue - } - delete(params, n) - switch { - case !transform.IsRef: - params[transform.TransformedName] = v - case transform.IsSensitive: - secretName, secretKey := getSecretRef(v) - params[transform.TransformedName] = getRefField(v, - map[string]any{ - "name": secretName, - "namespace": defaultNamespace, - "key": secretKey, - }) + fieldPath := getHierarchicalName(namePrefix, n) + sch := config.GetSchema(r.TerraformResource, fieldPath) + if sch == nil { + continue + } + // At this point, we confirmed that the field is part of the schema, + // so we'll need to perform at least name change on it. + delete(params, n) + fn := name.NewFromSnake(n) + switch { + case sch.Sensitive: + secretName, secretKey := getSecretRef(v) + params[fn.LowerCamelComputed+"SecretRef"] = getRefField(v, map[string]any{ + "name": secretName, + "namespace": defaultNamespace, + "key": secretKey, + }) + case r.References[fieldPath] != config.Reference{}: + switch v.(type) { + case []any: + l := sch.Type == schema.TypeList || sch.Type == schema.TypeSet + ref := name.ReferenceFieldName(fn, l, r.References[fieldPath].RefFieldName) + params[ref.LowerCamelComputed] = getNameRefField(v) default: - switch v.(type) { - case []any: - params[transform.TransformedName] = getNameRefField(v) - default: - params[transform.SelectorName] = getSelectorField(v) - } + sel := name.SelectorFieldName(fn, r.References[fieldPath].SelectorFieldName) + params[sel.LowerCamelComputed] = getSelectorField(v) } - break + default: + params[fn.LowerCamelComputed] = v } } } diff --git a/pkg/pipeline/run.go b/pkg/pipeline/run.go index 4cc49e46..38f5a766 100644 --- a/pkg/pipeline/run.go +++ b/pkg/pipeline/run.go @@ -83,7 +83,7 @@ func Run(pc *config.Provider, rootDir string) { // nolint:gocyclo panic(errors.Wrapf(err, "cannot generate controller for resource %s", name)) } controllerPkgList = append(controllerPkgList, ctrlPkgPath) - if err := exampleGen.Generate(group, version, resources[name], crdGen.Generated.FieldTransformations); err != nil { + if err := exampleGen.Generate(group, version, resources[name]); err != nil { panic(errors.Wrapf(err, "cannot generate example manifest for resource %s", name)) } count++ diff --git a/pkg/registry/reference/resolver.go b/pkg/registry/reference/resolver.go index 5267247f..91906d51 100644 --- a/pkg/registry/reference/resolver.go +++ b/pkg/registry/reference/resolver.go @@ -16,7 +16,6 @@ import ( "github.com/upbound/upjet/pkg/config" "github.com/upbound/upjet/pkg/registry" "github.com/upbound/upjet/pkg/resource/json" - tjtypes "github.com/upbound/upjet/pkg/types" ) const ( @@ -92,15 +91,14 @@ func NewRefPartsFromResourceName(rn string) Parts { // PavedWithManifest represents an example manifest with a fieldpath.Paved type PavedWithManifest struct { - Paved *fieldpath.Paved - ManifestPath string - ParamsPrefix []string - refsResolved bool - FieldTransformations map[string]tjtypes.Transformation - Config *config.Resource - Group string - Version string - ExampleName string + Paved *fieldpath.Paved + ManifestPath string + ParamsPrefix []string + refsResolved bool + Config *config.Resource + Group string + Version string + ExampleName string } // ResolutionContext represents a reference resolution context where diff --git a/pkg/types/builder.go b/pkg/types/builder.go index 41815da9..1d291913 100644 --- a/pkg/types/builder.go +++ b/pkg/types/builder.go @@ -26,16 +26,6 @@ const ( emptyStruct = "struct{}" ) -// Transformation represents a transformation applied to a -// Terraform resource attribute. It's used to record any -// transformations applied by the CRD generation pipeline. -type Transformation struct { - TransformedName string - SelectorName string - IsRef bool - IsSensitive bool -} - // Generated is a struct that holds generated types type Generated struct { Types []*types.Named @@ -43,8 +33,6 @@ type Generated struct { ForProviderType *types.Named AtProviderType *types.Named - - FieldTransformations map[string]Transformation } // Builder is used to generate Go type equivalence of given Terraform schema. @@ -65,18 +53,16 @@ func NewBuilder(pkg *types.Package) *Builder { // Build returns parameters and observation types built out of Terraform schema. func (g *Builder) Build(cfg *config.Resource) (Generated, error) { - fieldTransformations := make(map[string]Transformation) - fp, ap, err := g.buildResource(cfg.TerraformResource, cfg, nil, nil, false, fieldTransformations, cfg.Kind) + fp, ap, err := g.buildResource(cfg.TerraformResource, cfg, nil, nil, false, cfg.Kind) return Generated{ - Types: g.genTypes, - Comments: g.comments, - ForProviderType: fp, - AtProviderType: ap, - FieldTransformations: fieldTransformations, + Types: g.genTypes, + Comments: g.comments, + ForProviderType: fp, + AtProviderType: ap, }, errors.Wrapf(err, "cannot build the Types") } -func (g *Builder) buildResource(res *schema.Resource, cfg *config.Resource, tfPath []string, xpPath []string, asBlocksMode bool, t map[string]Transformation, names ...string) (*types.Named, *types.Named, error) { //nolint:gocyclo +func (g *Builder) buildResource(res *schema.Resource, cfg *config.Resource, tfPath []string, xpPath []string, asBlocksMode bool, names ...string) (*types.Named, *types.Named, error) { //nolint:gocyclo // NOTE(muvaf): There can be fields in the same CRD with same name but in // different types. Since we generate the type using the field name, there // can be collisions. In order to be able to generate unique names consistently, @@ -98,35 +84,28 @@ func (g *Builder) buildResource(res *schema.Resource, cfg *config.Resource, tfPa } var f *Field - tr := Transformation{} switch { case res.Schema[snakeFieldName].Sensitive: var drop bool - f, drop, err = NewSensitiveField(g, cfg, r, res.Schema[snakeFieldName], snakeFieldName, tfPath, xpPath, names, asBlocksMode, t) + f, drop, err = NewSensitiveField(g, cfg, r, res.Schema[snakeFieldName], snakeFieldName, tfPath, xpPath, names, asBlocksMode) if err != nil { return nil, nil, err } if drop { continue } - tr.IsSensitive = true - tr.IsRef = true case reference != nil: - f, err = NewReferenceField(g, cfg, r, res.Schema[snakeFieldName], reference, snakeFieldName, tfPath, xpPath, names, asBlocksMode, t) + f, err = NewReferenceField(g, cfg, r, res.Schema[snakeFieldName], reference, snakeFieldName, tfPath, xpPath, names, asBlocksMode) if err != nil { return nil, nil, err } - tr.IsRef = true default: - f, err = NewField(g, cfg, r, res.Schema[snakeFieldName], snakeFieldName, tfPath, xpPath, names, asBlocksMode, t) + f, err = NewField(g, cfg, r, res.Schema[snakeFieldName], snakeFieldName, tfPath, xpPath, names, asBlocksMode) if err != nil { return nil, nil, err } } f.AddToResource(g, r, typeNames) - tr.TransformedName = f.TransformedName - tr.SelectorName = f.SelectorName - t[fieldPath(f.TerraformPaths)] = tr } paramType, obsType := g.AddToBuilder(typeNames, r) @@ -151,7 +130,7 @@ func (g *Builder) AddToBuilder(typeNames *TypeNames, r *resource) (*types.Named, return paramType, obsType } -func (g *Builder) buildSchema(f *Field, cfg *config.Resource, t map[string]Transformation, names []string, r *resource) (types.Type, error) { // nolint:gocyclo +func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, r *resource) (types.Type, error) { // nolint:gocyclo switch f.Schema.Type { case schema.TypeBool: return types.NewPointer(types.Universe.Lookup("bool").Type()), nil @@ -181,7 +160,7 @@ func (g *Builder) buildSchema(f *Field, cfg *config.Resource, t map[string]Trans return nil, errors.Errorf("element type of %s is basic but not one of known basic types", fieldPath(names)) } case *schema.Schema: - newf, err := NewField(g, cfg, r, et, f.Name.Snake, f.TerraformPaths, f.CRDPaths, names, false, t) + newf, err := NewField(g, cfg, r, et, f.Name.Snake, f.TerraformPaths, f.CRDPaths, names, false) if err != nil { return nil, err } @@ -193,7 +172,7 @@ func (g *Builder) buildSchema(f *Field, cfg *config.Resource, t map[string]Trans if f.Schema.ConfigMode == schema.SchemaConfigModeAttr { asBlocksMode = true } - paramType, obsType, err := g.buildResource(et, cfg, f.TerraformPaths, f.CRDPaths, asBlocksMode, t, names...) + paramType, obsType, err := g.buildResource(et, cfg, f.TerraformPaths, f.CRDPaths, asBlocksMode, names...) if err != nil { return nil, errors.Wrapf(err, "cannot infer type from resource schema of element type of %s", fieldPath(names)) } diff --git a/pkg/types/field.go b/pkg/types/field.go index 91ef5856..2af5f734 100644 --- a/pkg/types/field.go +++ b/pkg/types/field.go @@ -30,7 +30,7 @@ type Field struct { } // NewField returns a constructed Field object. -func NewField(g *Builder, cfg *config.Resource, r *resource, sch *schema.Schema, snakeFieldName string, tfPath, xpPath, names []string, asBlocksMode bool, t map[string]Transformation) (*Field, error) { +func NewField(g *Builder, cfg *config.Resource, r *resource, sch *schema.Schema, snakeFieldName string, tfPath, xpPath, names []string, asBlocksMode bool) (*Field, error) { f := &Field{ Schema: sch, Name: name.NewFromSnake(snakeFieldName), @@ -65,7 +65,7 @@ func NewField(g *Builder, cfg *config.Resource, r *resource, sch *schema.Schema, } } - fieldType, err := g.buildSchema(f, cfg, t, names, r) + fieldType, err := g.buildSchema(f, cfg, names, r) if err != nil { return nil, errors.Wrapf(err, "cannot infer type from schema of field %s", f.Name.Snake) } @@ -75,8 +75,8 @@ func NewField(g *Builder, cfg *config.Resource, r *resource, sch *schema.Schema, } // NewSensitiveField returns a constructed sensitive Field object. -func NewSensitiveField(g *Builder, cfg *config.Resource, r *resource, sch *schema.Schema, snakeFieldName string, tfPath, xpPath, names []string, asBlocksMode bool, t map[string]Transformation) (*Field, bool, error) { //nolint:gocyclo - f, err := NewField(g, cfg, r, sch, snakeFieldName, tfPath, xpPath, names, asBlocksMode, t) +func NewSensitiveField(g *Builder, cfg *config.Resource, r *resource, sch *schema.Schema, snakeFieldName string, tfPath, xpPath, names []string, asBlocksMode bool) (*Field, bool, error) { //nolint:gocyclo + f, err := NewField(g, cfg, r, sch, snakeFieldName, tfPath, xpPath, names, asBlocksMode) if err != nil { return nil, false, err } @@ -118,8 +118,8 @@ func NewSensitiveField(g *Builder, cfg *config.Resource, r *resource, sch *schem } // NewReferenceField returns a constructed reference Field object. -func NewReferenceField(g *Builder, cfg *config.Resource, r *resource, sch *schema.Schema, ref *config.Reference, snakeFieldName string, tfPath, xpPath, names []string, asBlocksMode bool, t map[string]Transformation) (*Field, error) { - f, err := NewField(g, cfg, r, sch, snakeFieldName, tfPath, xpPath, names, asBlocksMode, t) +func NewReferenceField(g *Builder, cfg *config.Resource, r *resource, sch *schema.Schema, ref *config.Reference, snakeFieldName string, tfPath, xpPath, names []string, asBlocksMode bool) (*Field, error) { + f, err := NewField(g, cfg, r, sch, snakeFieldName, tfPath, xpPath, names, asBlocksMode) if err != nil { return nil, err } diff --git a/pkg/types/name/reference.go b/pkg/types/name/reference.go new file mode 100644 index 00000000..4e63f3d9 --- /dev/null +++ b/pkg/types/name/reference.go @@ -0,0 +1,33 @@ +/* +Copyright 2022 Upbound Inc. +*/ + +package name + +// NOTE(muvaf): We try to rely on snake for name calculations because it is more +// accurate in cases where two words are all acronyms and full capital, i.e. +// APIID would be converted to apiid when you convert it to lower camel computed +// but if you start with api_id, then it becomes apiId as lower camel computed +// and APIID as camel, which is what we want. + +// ReferenceFieldName returns the field name for a reference field whose +// value field name is given. +func ReferenceFieldName(n Name, plural bool, camelOverride string) Name { + if camelOverride != "" { + return NewFromCamel(camelOverride) + } + temp := n.Snake + "_ref" + if plural { + temp += "s" + } + return NewFromSnake(temp) +} + +// SelectorFieldName returns the field name for a selector field whose +// value field name is given. +func SelectorFieldName(n Name, camelOverride string) Name { + if camelOverride != "" { + return NewFromCamel(camelOverride) + } + return NewFromSnake(n.Snake + "_selector") +} diff --git a/pkg/types/name/reference_test.go b/pkg/types/name/reference_test.go new file mode 100644 index 00000000..f4d45448 --- /dev/null +++ b/pkg/types/name/reference_test.go @@ -0,0 +1,126 @@ +/* +Copyright 2022 Upbound Inc. +*/ + +package name + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestReferenceFieldName(t *testing.T) { + type args struct { + n Name + isList bool + camelOverride string + } + type want struct { + n Name + } + cases := map[string]struct { + reason string + args + want + }{ + "NonListDefault": { + reason: "It should work with normal case without override.", + args: args{ + n: NewFromSnake("some_field"), + isList: false, + camelOverride: "", + }, + want: want{ + n: NewFromSnake("some_field_ref"), + }, + }, + "ListDefault": { + reason: "It should work with list case without override.", + args: args{ + n: NewFromSnake("some_field"), + isList: true, + camelOverride: "", + }, + want: want{ + n: NewFromSnake("some_field_refs"), + }, + }, + "ListOverridden": { + reason: "It should work with list case even though it's overridden.", + args: args{ + n: NewFromSnake("some_field"), + isList: true, + camelOverride: "AnotherField", + }, + want: want{ + n: NewFromSnake("another_field"), + }, + }, + "NonListOverridden": { + reason: "It should work with normal case even though it's overridden.", + args: args{ + n: NewFromSnake("some_field"), + isList: false, + camelOverride: "AnotherField", + }, + want: want{ + n: NewFromSnake("another_field"), + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got := ReferenceFieldName(tc.args.n, tc.args.isList, tc.args.camelOverride) + if diff := cmp.Diff(tc.want.n, got); diff != "" { + t.Errorf("\nReferenceFieldName(...): -want, +got:\n%s", diff) + } + }) + } +} + +func TestSelectorFieldName(t *testing.T) { + type args struct { + n Name + camelOverride string + } + type want struct { + n Name + } + cases := map[string]struct { + reason string + args + want + }{ + "Default": { + reason: "It should work with normal case without override.", + args: args{ + n: NewFromSnake("some_field"), + camelOverride: "", + }, + want: want{ + n: NewFromSnake("some_field_selector"), + }, + }, + "Overridden": { + reason: "It should return the override if given.", + args: args{ + n: NewFromSnake("some_field"), + camelOverride: "AnotherFieldSelector", + }, + want: want{ + n: NewFromSnake("another_field_selector"), + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got := SelectorFieldName(tc.args.n, tc.args.camelOverride) + if diff := cmp.Diff(tc.want.n, got); diff != "" { + t.Errorf("\nSelectorFieldName(...): -want, +got:\n%s", diff) + } + }) + } +} diff --git a/pkg/types/reference.go b/pkg/types/reference.go index 37373794..c0228807 100644 --- a/pkg/types/reference.go +++ b/pkg/types/reference.go @@ -48,24 +48,8 @@ var ( func (g *Builder) generateReferenceFields(t *types.TypeName, f *Field) (fields []*types.Var, tags []string) { _, isSlice := f.FieldType.(*types.Slice) - // We try to rely on snake for name calculations because it is more accurate - // in cases where two words are all acronyms and full capital, i.e. APIID - // would be converted to apiid when you convert it to lower camel computed - // but if you start with api_id, then it becomes apiId as lower camel computed - // and APIID as camel, which is what we want. - rfn := name.NewFromCamel(f.Reference.RefFieldName) - if f.Reference.RefFieldName == "" { - temp := f.Name.Snake + "_ref" - if isSlice { - temp += "s" - } - rfn = name.NewFromSnake(temp) - } - - sfn := name.NewFromCamel(f.Reference.SelectorFieldName) - if f.Reference.SelectorFieldName == "" { - sfn = name.NewFromSnake(f.Name.Snake + "_selector") - } + rfn := name.ReferenceFieldName(f.Name, isSlice, f.Reference.RefFieldName) + sfn := name.SelectorFieldName(f.Name, f.Reference.SelectorFieldName) refTag := fmt.Sprintf(`json:"%s,omitempty" tf:"-"`, rfn.LowerCamelComputed) selTag := fmt.Sprintf(`json:"%s,omitempty" tf:"-"`, sfn.LowerCamelComputed)