Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for attributes with nested_types #448

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions pkg/types/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -293,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)
Expand All @@ -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
Expand Down
85 changes: 81 additions & 4 deletions pkg/types/conversion/tfjson/tfjson.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -42,7 +47,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,
Expand Down Expand Up @@ -76,6 +85,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 = SchemaTypeObject
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),
Expand Down Expand Up @@ -104,7 +181,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
Expand Down Expand Up @@ -141,7 +218,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
}
Expand All @@ -157,7 +234,7 @@ func hasRequiredChild(nb *tfjson.SchemaBlockType) bool {
if b == nil {
continue
}
if hasRequiredChild(b) {
if hasBlockRequiredChild(b) {
return true
}
}
Expand Down
Loading