diff --git a/pkg/config/resource.go b/pkg/config/resource.go index f45a2f96..12c6173a 100644 --- a/pkg/config/resource.go +++ b/pkg/config/resource.go @@ -532,10 +532,28 @@ func (m SchemaElementOptions) AddToObservation(el string) bool { return m[el] != nil && m[el].AddToObservation } +// SetEmbeddedObject sets the EmbeddedObject for the specified key. +func (m SchemaElementOptions) SetEmbeddedObject(el string) { + if m[el] == nil { + m[el] = &SchemaElementOption{} + } + m[el].EmbeddedObject = true +} + +// EmbeddedObject returns true if the schema element at the specified path +// should be generated as an embedded object. +func (m SchemaElementOptions) EmbeddedObject(el string) bool { + return m[el] != nil && m[el].EmbeddedObject +} + // SchemaElementOption represents configuration options on a schema element. type SchemaElementOption struct { // AddToObservation is set to true if the field represented by // a schema element is to be added to the generated CRD type's // Observation type. AddToObservation bool + // EmbeddedObject is set to true if the field represented by + // a schema element is to be embedded into its parent instead of being + // generated as a single element list. + EmbeddedObject bool } diff --git a/pkg/types/builder.go b/pkg/types/builder.go index 9d11e1be..f61e79e3 100644 --- a/pkg/types/builder.go +++ b/pkg/types/builder.go @@ -206,7 +206,7 @@ func (g *Builder) AddToBuilder(typeNames *TypeNames, r *resource) (*types.Named, return paramType, obsType, initType } -func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, r *resource) (types.Type, types.Type, error) { //nolint:gocyclo +func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, cpath string, r *resource) (types.Type, types.Type, error) { //nolint:gocyclo switch f.Schema.Type { case schema.TypeBool: return types.NewPointer(types.Universe.Lookup("bool").Type()), nil, nil @@ -272,9 +272,16 @@ func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, r // that can go under spec. This check prevents the elimination of fields in parameter type, by checking // whether the schema in observation type has nested parameter (spec) fields. if paramType.Underlying().String() != emptyStruct { - field := types.NewField(token.NoPos, g.Package, f.Name.Camel, types.NewSlice(paramType), false) - r.addParameterField(f, field) - r.addInitField(f, field, g, nil) + var tParam, tInit types.Type + if cfg.SchemaElementOptions.EmbeddedObject(cpath) { + tParam = types.NewPointer(paramType) + tInit = types.NewPointer(initType) + } else { + tParam = types.NewSlice(paramType) + tInit = types.NewSlice(initType) + } + r.addParameterField(f, types.NewField(token.NoPos, g.Package, f.Name.Camel, tParam, false)) + r.addInitField(f, types.NewField(token.NoPos, g.Package, f.Name.Camel, tInit, false), g, nil) } default: if paramType == nil { @@ -285,7 +292,13 @@ func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, r // This check prevents the elimination of fields in observation type, by checking whether the schema in // parameter type has nested observation (status) fields. if obsType.Underlying().String() != emptyStruct { - field := types.NewField(token.NoPos, g.Package, f.Name.Camel, types.NewSlice(obsType), false) + var t types.Type + if cfg.SchemaElementOptions.EmbeddedObject(cpath) { + t = types.NewPointer(obsType) + } else { + t = types.NewSlice(obsType) + } + field := types.NewField(token.NoPos, g.Package, f.Name.Camel, t, false) r.addObservationField(f, field) } } @@ -298,6 +311,10 @@ func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, r return nil, nil, errors.Errorf("element type of %s should be either schema.Resource or schema.Schema", fieldPath(names)) } + // if the singleton list is to be replaced by an embedded object + if cfg.SchemaElementOptions.EmbeddedObject(cpath) { + return types.NewPointer(elemType), types.NewPointer(initElemType), nil + } // NOTE(muvaf): Maps and slices are already pointers, so we don't need to // wrap them even if they are optional. if f.Schema.Type == schema.TypeMap { diff --git a/pkg/types/conversion/tfjson/tfjson.go b/pkg/types/conversion/tfjson/tfjson.go index 2e6ef4e2..3fe37df6 100644 --- a/pkg/types/conversion/tfjson/tfjson.go +++ b/pkg/types/conversion/tfjson/tfjson.go @@ -94,7 +94,7 @@ func tfJSONBlockTypeToV2Schema(nb *tfjson.SchemaBlockType) *schemav2.Schema { // v2sch.Computed = true } - switch nb.NestingMode { + switch nb.NestingMode { //nolint:exhaustive case tfjson.SchemaNestingModeSet: v2sch.Type = schemav2.TypeSet case tfjson.SchemaNestingModeList: diff --git a/pkg/types/field.go b/pkg/types/field.go index 2e6fc2dc..1200b90a 100644 --- a/pkg/types/field.go +++ b/pkg/types/field.go @@ -154,7 +154,7 @@ func NewField(g *Builder, cfg *config.Resource, r *resource, sch *schema.Schema, } } - fieldType, initType, err := g.buildSchema(f, cfg, names, r) + fieldType, initType, err := g.buildSchema(f, cfg, names, fieldPath(append(tfPath, snakeFieldName)), r) if err != nil { return nil, errors.Wrapf(err, "cannot infer type from schema of field %s", f.Name.Snake) }