From 21d76558858c54e2326b4f61a685f2bb42266918 Mon Sep 17 00:00:00 2001 From: Ramon Quitales Date: Mon, 16 Sep 2024 15:10:19 -0700 Subject: [PATCH] fix: create nested types for map of array of objects We were previously not nesting maps of arrays, and treating them as generic types instead. This is an issue in Golang codegen, generating generic Pulumi input/output types that don't exists. This commit ensures we also flatten map of arrays so Golang codegen works. Fixes: https://github.com/pulumi/crd2pulumi/issues/147 --- pkg/codegen/customresourcegenerator.go | 73 ++++++++++++++++++-------- tests/crds_test.go | 5 ++ 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/pkg/codegen/customresourcegenerator.go b/pkg/codegen/customresourcegenerator.go index a7d5aa7..b9302dc 100644 --- a/pkg/codegen/customresourcegenerator.go +++ b/pkg/codegen/customresourcegenerator.go @@ -77,7 +77,7 @@ func flattenOpenAPI(sw *spec.Swagger) error { func flattenRecursively(sw *spec.Swagger, parentName string, currSpec spec.Schema) (spec.Schema, error) { // If at bottom of the stack, return the spec. - if currSpec.Properties == nil && currSpec.Items == nil { + if currSpec.Properties == nil && currSpec.Items == nil && currSpec.AdditionalProperties == nil { return currSpec, nil } @@ -86,43 +86,50 @@ func flattenRecursively(sw *spec.Swagger, parentName string, currSpec spec.Schem return currSpec, nil } - // If the property is an object with additional properties, we can skip it. We only care about + // If the property is an object with additional properties, we can skip it if it is not an array of inline objects. We only care about // nested objects that are explicitly defined. if currSpec.AdditionalProperties != nil { + // Not an array of inline objects, so we can skip processing. + if currSpec.AdditionalProperties.Schema.Items == nil { + return currSpec, nil + } + + // Property is an array of inline objects. + s, ref, err := flattedArrayObject(parentName, sw, currSpec.AdditionalProperties.Schema.Items.Schema) + if err != nil { + return currSpec, fmt.Errorf("error flattening OpenAPI object of array property: %w", err) + } + + currSpec.AdditionalProperties.Schema.Items.Schema = &s + + if ref != nil { + currSpec.AdditionalProperties.Schema.Items.Schema.Ref = spec.Ref{Ref: *ref} + currSpec.AdditionalProperties.Schema.Items.Schema.Type = nil + currSpec.AdditionalProperties.Schema.Items.Schema.Properties = nil + } + return currSpec, nil } + // If the property is an array, we need to remove any inline objects and replace them with references. if currSpec.Items != nil { if currSpec.Items.Schema == nil { return currSpec, fmt.Errorf("error flattening OpenAPI spec: items schema is nil") } - nestedDefinitionName := parentName - - s, err := flattenRecursively(sw, nestedDefinitionName, *(currSpec.Items.Schema)) + s, ref, err := flattedArrayObject(parentName, sw, currSpec.Items.Schema) if err != nil { - return currSpec, fmt.Errorf("error flattening OpenAPI spec: %w", err) + return currSpec, fmt.Errorf("error flattening OpenAPI array property: %w", err) } currSpec.Items.Schema = &s - if len(currSpec.Items.Schema.Properties) == 0 { - return currSpec, nil + if ref != nil { + currSpec.Items.Schema.Ref = spec.Ref{Ref: *ref} + currSpec.Items.Schema.Type = nil + currSpec.Items.Schema.Properties = nil } - sw.Definitions[nestedDefinitionName] = s - - // Reset the property to be a reference to the nested object if the item is an object. - refName := definitionPrefix + nestedDefinitionName - ref, err := jsonreference.New(refName) - if err != nil { - return currSpec, fmt.Errorf("error creating OpenAPI json reference for nested object: %w", err) - } - - currSpec.Items.Schema.Ref = spec.Ref{Ref: ref} - currSpec.Items.Schema.Type = nil - currSpec.Items.Schema.Properties = nil - return currSpec, nil } @@ -169,6 +176,30 @@ func flattenRecursively(sw *spec.Swagger, parentName string, currSpec spec.Schem return currSpec, nil } +// flattedArrayObject flattens an OpenAPI array property. +func flattedArrayObject(parentName string, sw *spec.Swagger, itemsSchema *spec.Schema) (spec.Schema, *jsonreference.Ref, error) { + nestedDefinitionName := parentName + + s, err := flattenRecursively(sw, nestedDefinitionName, *itemsSchema) + if err != nil { + return s, nil, fmt.Errorf("error flattening OpenAPI spec: %w", err) + } + + if len(s.Properties) == 0 { + return s, nil, nil + } + + sw.Definitions[nestedDefinitionName] = s + + refName := definitionPrefix + nestedDefinitionName + ref, err := jsonreference.New(refName) + if err != nil { + return s, nil, fmt.Errorf("error creating OpenAPI json reference for nested object: %w", err) + } + + return s, &ref, nil +} + func sanitizeReferenceName(fieldName string) string { // If the field name is "arg" or "args", we need to change it to "Arguments" to avoid conflicts with Go reserved words. if s := strings.ToLower(fieldName); s == "arg" || s == "args" { diff --git a/tests/crds_test.go b/tests/crds_test.go index b2a0ad5..6a84ecc 100644 --- a/tests/crds_test.go +++ b/tests/crds_test.go @@ -177,6 +177,11 @@ func TestCRDsFromUrl(t *testing.T) { name: "Argo Application Set", url: "https://raw.githubusercontent.com/argoproj/argo-cd/master/manifests/crds/applicationset-crd.yaml", }, + { + // https://github.com/pulumi/crd2pulumi/issues/147 + name: "Prometheus Operator", + url: "https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.76.2/bundle.yaml", + }, } for _, tt := range tests {