From 239e4926c51194e356645d508922b75a7d623256 Mon Sep 17 00:00:00 2001 From: Lukas Hoehl Date: Wed, 6 Nov 2024 17:27:56 +0100 Subject: [PATCH 1/3] support for nested types Signed-off-by: Lukas Hoehl --- pkg/types/conversion/tfjson/tfjson.go | 80 +++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/pkg/types/conversion/tfjson/tfjson.go b/pkg/types/conversion/tfjson/tfjson.go index 3fe37df6..dac92bfb 100644 --- a/pkg/types/conversion/tfjson/tfjson.go +++ b/pkg/types/conversion/tfjson/tfjson.go @@ -42,7 +42,11 @@ func v2ResourceFromTFJSONSchema(s *tfjson.Schema) *schemav2.Resource { toSchemaMap := make(map[string]*schemav2.Schema, len(s.Block.Attributes)+len(s.Block.NestedBlocks)) for k, v := range s.Block.Attributes { - toSchemaMap[k] = tfJSONAttributeToV2Schema(v) + if v.AttributeNestedType != nil { + toSchemaMap[k] = tfJSONNestedAttributeTypeToV2Schema(v.AttributeNestedType) + } else { + toSchemaMap[k] = tfJSONAttributeToV2Schema(v) + } } for k, v := range s.Block.NestedBlocks { // CRUD timeouts are not part of the generated MR API, @@ -76,6 +80,74 @@ func tfJSONAttributeToV2Schema(attr *tfjson.SchemaAttribute) *schemav2.Schema { return v2sch } +func tfJSONNestedAttributeTypeToV2Schema(na *tfjson.SchemaNestedAttributeType) *schemav2.Schema { + v2sch := &schemav2.Schema{ + MinItems: int(na.MinItems), + MaxItems: int(na.MaxItems), + } + switch na.NestingMode { //nolint:exhaustive + case tfjson.SchemaNestingModeSet: + v2sch.Type = schemav2.TypeSet + case tfjson.SchemaNestingModeList: + v2sch.Type = schemav2.TypeList + case tfjson.SchemaNestingModeMap: + v2sch.Type = schemav2.TypeMap + case tfjson.SchemaNestingModeSingle: + v2sch.Type = schemav2.TypeList + v2sch.MinItems = 0 + v2sch.Required = hasNestedAttributeRequiredChild(na) + v2sch.Optional = !v2sch.Required + if v2sch.Required { + v2sch.MinItems = 1 + } + v2sch.MaxItems = 1 + default: + panic("unhandled nesting mode: " + na.NestingMode) + } + + res := &schemav2.Resource{} + res.Schema = make(map[string]*schemav2.Schema, len(na.Attributes)) + for key, attr := range na.Attributes { + if attr.AttributeNestedType != nil { + res.Schema[key] = tfJSONNestedAttributeTypeToV2Schema(attr.AttributeNestedType) + } else { + res.Schema[key] = tfJSONAttributeToV2Schema(attr) + } + } + v2sch.Elem = res + return v2sch +} + +// checks whether the given tfjson.SchemaBlockType has any required children. +// Children which are themselves blocks (nested blocks) are +// checked recursively. +func hasNestedAttributeRequiredChild(na *tfjson.SchemaNestedAttributeType) bool { + if na.Attributes == nil { + return false + } + for _, a := range na.Attributes { + if a == nil { + continue + } + if a.Required { + return true + } + + if a.AttributeNestedType == nil { + continue + } + for _, nested := range a.AttributeNestedType.Attributes { + if nested.AttributeNestedType != nil { + return hasNestedAttributeRequiredChild(nested.AttributeNestedType) + } + if a.Required { + return true + } + } + } + return false +} + func tfJSONBlockTypeToV2Schema(nb *tfjson.SchemaBlockType) *schemav2.Schema { //nolint:gocyclo v2sch := &schemav2.Schema{ MinItems: int(nb.MinItems), @@ -104,7 +176,7 @@ func tfJSONBlockTypeToV2Schema(nb *tfjson.SchemaBlockType) *schemav2.Schema { // case tfjson.SchemaNestingModeSingle: v2sch.Type = schemav2.TypeList v2sch.MinItems = 0 - v2sch.Required = hasRequiredChild(nb) + v2sch.Required = hasBlockRequiredChild(nb) v2sch.Optional = !v2sch.Required if v2sch.Required { v2sch.MinItems = 1 @@ -141,7 +213,7 @@ func tfJSONBlockTypeToV2Schema(nb *tfjson.SchemaBlockType) *schemav2.Schema { // // checks whether the given tfjson.SchemaBlockType has any required children. // Children which are themselves blocks (nested blocks) are // checked recursively. -func hasRequiredChild(nb *tfjson.SchemaBlockType) bool { +func hasBlockRequiredChild(nb *tfjson.SchemaBlockType) bool { if nb.Block == nil { return false } @@ -157,7 +229,7 @@ func hasRequiredChild(nb *tfjson.SchemaBlockType) bool { if b == nil { continue } - if hasRequiredChild(b) { + if hasBlockRequiredChild(b) { return true } } From 2fc87b9a011f955fbdf41f07cf2396f95b9b5a75 Mon Sep 17 00:00:00 2001 From: Lukas Hoehl Date: Fri, 8 Nov 2024 17:59:24 +0100 Subject: [PATCH 2/3] nested_types singlemode object Signed-off-by: Lukas Hoehl --- pkg/types/builder.go | 8 +++++--- pkg/types/conversion/tfjson/tfjson.go | 7 ++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pkg/types/builder.go b/pkg/types/builder.go index b6dcd9f9..651c0f7d 100644 --- a/pkg/types/builder.go +++ b/pkg/types/builder.go @@ -18,6 +18,7 @@ import ( "github.com/crossplane/upjet/pkg/config" "github.com/crossplane/upjet/pkg/schema/traverser" + conversiontfjson "github.com/crossplane/upjet/pkg/types/conversion/tfjson" ) const ( @@ -216,7 +217,7 @@ func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, cp return types.NewPointer(types.Universe.Lookup("int64").Type()), nil, nil case schema.TypeString: return types.NewPointer(types.Universe.Lookup("string").Type()), nil, nil - case schema.TypeMap, schema.TypeList, schema.TypeSet: + case schema.TypeMap, schema.TypeList, schema.TypeSet, conversiontfjson.SchemaTypeObject: names = append(names, f.Name.Camel) if f.Schema.Type != schema.TypeMap { // We don't want to have a many-to-many relationship in case of a Map, since we use SecretReference as @@ -311,8 +312,9 @@ func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, cp return nil, nil, errors.Errorf("element type of %s should be either schema.Resource or schema.Schema", traverser.FieldPath(names)) } - // if the singleton list is to be replaced by an embedded object - if cfg.SchemaElementOptions.EmbeddedObject(cpath) { + // if the singleton list is to be replaced by an embedded object or schema + // is an object + if cfg.SchemaElementOptions.EmbeddedObject(cpath) || f.Schema.Type == conversiontfjson.SchemaTypeObject { return types.NewPointer(elemType), types.NewPointer(initElemType), nil } // NOTE(muvaf): Maps and slices are already pointers, so we don't need to diff --git a/pkg/types/conversion/tfjson/tfjson.go b/pkg/types/conversion/tfjson/tfjson.go index dac92bfb..3009ef2a 100644 --- a/pkg/types/conversion/tfjson/tfjson.go +++ b/pkg/types/conversion/tfjson/tfjson.go @@ -11,6 +11,11 @@ import ( "github.com/zclconf/go-cty/cty" ) +// SchemaTypeObject is the exported version of schema.typeObject to support +// nested types. +// TODO:remove once not dependent of terrafom sdkv2 schema +const SchemaTypeObject = schemav2.ValueType(9) + // GetV2ResourceMap converts input resource schemas with // "terraform-json" representation to terraform-plugin-sdk representation which // is what Upjet expects today. @@ -93,7 +98,7 @@ func tfJSONNestedAttributeTypeToV2Schema(na *tfjson.SchemaNestedAttributeType) * case tfjson.SchemaNestingModeMap: v2sch.Type = schemav2.TypeMap case tfjson.SchemaNestingModeSingle: - v2sch.Type = schemav2.TypeList + v2sch.Type = SchemaTypeObject v2sch.MinItems = 0 v2sch.Required = hasNestedAttributeRequiredChild(na) v2sch.Optional = !v2sch.Required From 7e8882c725e34fcdc79e767c0c64308be6d1cb20 Mon Sep 17 00:00:00 2001 From: Lukas Hoehl Date: Wed, 20 Nov 2024 15:09:49 +0100 Subject: [PATCH 3/3] use pointer for observation struct Signed-off-by: Lukas Hoehl --- pkg/types/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/types/builder.go b/pkg/types/builder.go index 651c0f7d..bf44e077 100644 --- a/pkg/types/builder.go +++ b/pkg/types/builder.go @@ -294,7 +294,7 @@ func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, cp // parameter type has nested observation (status) fields. if obsType.Underlying().String() != emptyStruct { var t types.Type - if cfg.SchemaElementOptions.EmbeddedObject(cpath) { + if cfg.SchemaElementOptions.EmbeddedObject(cpath) || f.Schema.Type == conversiontfjson.SchemaTypeObject { t = types.NewPointer(obsType) } else { t = types.NewSlice(obsType)