From 0162c7f3ff223c70eb5f29d70cec77d8dbaf52a7 Mon Sep 17 00:00:00 2001 From: Pat Gavlin Date: Thu, 5 Dec 2019 16:00:25 -0800 Subject: [PATCH] Unify type representations in gen. (#905) Rather than importing all types three times--once each for the inputs, outputs, and provider APIs--simply import the types once, and calculate the input, output, and provider API type for each property. These changes also track whether or not a kind is nested s.t. the unified set of types can be used to generate the input, output, and provider APIs. This is in preparation for Go codegen, which needs all of this information at once. --- pkg/gen/dotnet-templates/kind.cs.mustache | 2 +- .../dotnet-templates/typesInput.cs.mustache | 16 +- .../dotnet-templates/typesOutput.cs.mustache | 4 +- pkg/gen/dotnet.go | 14 +- pkg/gen/nodejs-templates/kind.ts.mustache | 2 +- .../providerIndex.ts.mustache | 4 +- .../nodejs-templates/typesInput.ts.mustache | 4 +- .../nodejs-templates/typesOutput.ts.mustache | 2 +- pkg/gen/nodejs-templates/yaml.ts.mustache | 20 +- pkg/gen/nodejs.go | 17 +- pkg/gen/python-templates/kind.py.mustache | 6 +- .../python-templates/root__init__.py.mustache | 2 + .../version__init__.py.mustache | 4 +- pkg/gen/python-templates/yaml.py.mustache | 4 +- pkg/gen/python.go | 13 +- pkg/gen/typegen.go | 260 ++++++++++-------- 16 files changed, 203 insertions(+), 171 deletions(-) diff --git a/pkg/gen/dotnet-templates/kind.cs.mustache b/pkg/gen/dotnet-templates/kind.cs.mustache index fedbb51501..d8f2752012 100644 --- a/pkg/gen/dotnet-templates/kind.cs.mustache +++ b/pkg/gen/dotnet-templates/kind.cs.mustache @@ -15,7 +15,7 @@ namespace Pulumi.Kubernetes.{{Group}}.{{Version}} {{#Properties}} {{{Comment}}} [Output("{{Name}}")] - public {{{PropType}}} {{LanguageName}} { get; private set; } = null!; + public {{{ProviderType}}} {{LanguageName}} { get; private set; } = null!; {{/Properties}} diff --git a/pkg/gen/dotnet-templates/typesInput.cs.mustache b/pkg/gen/dotnet-templates/typesInput.cs.mustache index 89b18e5c06..e4f71c6883 100644 --- a/pkg/gen/dotnet-templates/typesInput.cs.mustache +++ b/pkg/gen/dotnet-templates/typesInput.cs.mustache @@ -20,38 +20,38 @@ namespace Pulumi.Kubernetes.Types.Inputs.{{Group}} {{#RequiredInputProperties}} {{#DotnetIsListOrMap}} [Input("{{Name}}", required: true)] - private {{{PropType}}}? _{{Name}}; + private {{{InputsAPIType}}}? _{{Name}}; {{{Comment}}} - public {{{PropType}}} {{LanguageName}} + public {{{InputsAPIType}}} {{LanguageName}} { - get => _{{Name}} ?? (_{{Name}} = new {{{PropType}}}()); + get => _{{Name}} ?? (_{{Name}} = new {{{InputsAPIType}}}()); set => _{{Name}} = value; } {{/DotnetIsListOrMap}} {{^DotnetIsListOrMap}} {{{Comment}}} [Input("{{Name}}", required: true)] - public {{{PropType}}} {{LanguageName}} { get; set; } = null!; + public {{{InputsAPIType}}} {{LanguageName}} { get; set; } = null!; {{/DotnetIsListOrMap}} {{/RequiredInputProperties}} {{#OptionalInputProperties}} {{#DotnetIsListOrMap}} [Input("{{Name}}")] - private {{{PropType}}}? _{{Name}}; + private {{{InputsAPIType}}}? _{{Name}}; {{{Comment}}} - public {{{PropType}}} {{LanguageName}} + public {{{InputsAPIType}}} {{LanguageName}} { - get => _{{Name}} ?? (_{{Name}} = new {{{PropType}}}()); + get => _{{Name}} ?? (_{{Name}} = new {{{InputsAPIType}}}()); set => _{{Name}} = value; } {{/DotnetIsListOrMap}} {{^DotnetIsListOrMap}} {{{Comment}}} [Input("{{Name}}")] - public {{{PropType}}}? {{LanguageName}} { get; set; } + public {{{InputsAPIType}}}? {{LanguageName}} { get; set; } {{/DotnetIsListOrMap}} {{/OptionalInputProperties}} diff --git a/pkg/gen/dotnet-templates/typesOutput.cs.mustache b/pkg/gen/dotnet-templates/typesOutput.cs.mustache index 45c821177e..abcfde4ef7 100644 --- a/pkg/gen/dotnet-templates/typesOutput.cs.mustache +++ b/pkg/gen/dotnet-templates/typesOutput.cs.mustache @@ -20,13 +20,13 @@ namespace Pulumi.Kubernetes.Types.Outputs.{{Group}} { {{#Properties}} {{{Comment}}} - public readonly {{{PropType}}} {{LanguageName}}; + public readonly {{{OutputsAPIType}}} {{LanguageName}}; {{/Properties}} [OutputConstructor] private {{Kind}}( {{#Properties}} - {{{PropType}}} {{DotnetVarName}}{{^IsLast}},{{/IsLast}}{{#IsLast}}){{/IsLast}} + {{{OutputsAPIType}}} {{DotnetVarName}}{{^IsLast}},{{/IsLast}}{{#IsLast}}){{/IsLast}} {{/Properties}} { {{#Properties}} diff --git a/pkg/gen/dotnet.go b/pkg/gen/dotnet.go index 0ef41a3304..5049780963 100644 --- a/pkg/gen/dotnet.go +++ b/pkg/gen/dotnet.go @@ -80,32 +80,28 @@ func DotnetClient( ) (inputsts, outputsts string, groups map[string]string, err error) { definitions := swagger["definitions"].(map[string]interface{}) - inputGroupsSlice := createGroups(definitions, dotnetInputs()) + groupsSlice := createGroups(definitions, dotnetOpts()) + inputsts, err = mustache.RenderFile(fmt.Sprintf("%s/typesInput.cs.mustache", templateDir), map[string]interface{}{ - "Groups": inputGroupsSlice, + "Groups": groupsSlice, }) if err != nil { return } - outputGroupsSlice := createGroups(definitions, dotnetOutputs()) outputsts, err = mustache.RenderFile(fmt.Sprintf("%s/typesOutput.cs.mustache", templateDir), map[string]interface{}{ - "Groups": outputGroupsSlice, + "Groups": groupsSlice, }) if err != nil { return } - groupsSlice := createGroups(definitions, dotnetProvider()) - fmt.Printf("%v\n", groupsSlice) - groups = make(map[string]string) - for _, group := range groupsSlice { for _, version := range group.Versions() { - for _, kind := range version.Kinds() { + for _, kind := range version.TopLevelKinds() { inputMap := map[string]interface{}{ "RawAPIVersion": kind.RawAPIVersion(), "Comment": kind.Comment(), diff --git a/pkg/gen/nodejs-templates/kind.ts.mustache b/pkg/gen/nodejs-templates/kind.ts.mustache index 262dee90c9..ae89cc149e 100644 --- a/pkg/gen/nodejs-templates/kind.ts.mustache +++ b/pkg/gen/nodejs-templates/kind.ts.mustache @@ -13,7 +13,7 @@ import { getVersion } from "../../version"; export class {{Kind}} extends pulumi.CustomResource { {{#Properties}} {{{Comment}}} - public readonly {{Name}}: {{{PropType}}}; + public readonly {{Name}}: {{{ProviderType}}}; {{/Properties}} /** diff --git a/pkg/gen/nodejs-templates/providerIndex.ts.mustache b/pkg/gen/nodejs-templates/providerIndex.ts.mustache index 8f3bddf617..1773d67db5 100644 --- a/pkg/gen/nodejs-templates/providerIndex.ts.mustache +++ b/pkg/gen/nodejs-templates/providerIndex.ts.mustache @@ -8,11 +8,13 @@ export { helm, yaml }; // Import groups {{#Groups}} +{{#HasTopLevelKinds}} import * as {{Group}} from "./{{Group}}/index"; +{{/HasTopLevelKinds}} {{/Groups}} // Export sub-modules -export { {{#Groups}}{{Group}}, {{/Groups}} }; +export { {{#Groups}}{{#HasTopLevelKinds}}{{Group}}, {{/HasTopLevelKinds}}{{/Groups}} }; // Import and export sub-modules for all Kubernetes types. import * as types from "./types"; diff --git a/pkg/gen/nodejs-templates/typesInput.ts.mustache b/pkg/gen/nodejs-templates/typesInput.ts.mustache index 68490a7819..c5231fb8fe 100644 --- a/pkg/gen/nodejs-templates/typesInput.ts.mustache +++ b/pkg/gen/nodejs-templates/typesInput.ts.mustache @@ -15,12 +15,12 @@ export namespace {{Group}} { export interface {{Kind}} { {{#RequiredInputProperties}} {{{Comment}}} - {{Name}}: {{{PropType}}} + {{Name}}: {{{InputsAPIType}}} {{/RequiredInputProperties}} {{#OptionalInputProperties}} {{{Comment}}} - {{Name}}?: {{{PropType}}} + {{Name}}?: {{{InputsAPIType}}} {{/OptionalInputProperties}} } diff --git a/pkg/gen/nodejs-templates/typesOutput.ts.mustache b/pkg/gen/nodejs-templates/typesOutput.ts.mustache index a493f513c0..6f7bb8fadf 100644 --- a/pkg/gen/nodejs-templates/typesOutput.ts.mustache +++ b/pkg/gen/nodejs-templates/typesOutput.ts.mustache @@ -12,7 +12,7 @@ export namespace {{Group}} { export interface {{Kind}} { {{#Properties}} {{{Comment}}} - readonly {{Name}}: {{{PropType}}} + readonly {{Name}}: {{{OutputsAPIType}}} {{/Properties}} } diff --git a/pkg/gen/nodejs-templates/yaml.ts.mustache b/pkg/gen/nodejs-templates/yaml.ts.mustache index 768036f519..f3a19e4118 100644 --- a/pkg/gen/nodejs-templates/yaml.ts.mustache +++ b/pkg/gen/nodejs-templates/yaml.ts.mustache @@ -212,10 +212,10 @@ import * as outputs from "../types/output"; */ {{#Groups}} {{#Versions}} - {{#KindsAndAliases}} + {{#TopLevelKindsAndAliases}} public getResource(groupVersionKind: "{{RawAPIVersion}}/{{Kind}}", name: string): pulumi.Output; public getResource(groupVersionKind: "{{RawAPIVersion}}/{{Kind}}", namespace: string, name: string): pulumi.Output; - {{/KindsAndAliases}} + {{/TopLevelKindsAndAliases}} {{/Versions}} {{/Groups}} public getResource(groupVersionKind: string, namespaceOrName: string, name?: string): pulumi.Output { @@ -230,12 +230,12 @@ import * as outputs from "../types/output"; */ {{#Groups}} {{#Versions}} - {{#KindsAndAliases}} + {{#TopLevelKindsAndAliases}} {{#Properties}} - public getResourceProperty(groupVersionKind: "{{RawAPIVersion}}/{{Kind}}", name: string, property: "{{LanguageName}}"): {{{PropType}}}; - public getResourceProperty(groupVersionKind: "{{RawAPIVersion}}/{{Kind}}", namespace: string, name: string, property: "{{LanguageName}}"): {{{PropType}}}; + public getResourceProperty(groupVersionKind: "{{RawAPIVersion}}/{{Kind}}", name: string, property: "{{LanguageName}}"): {{{ProviderType}}}; + public getResourceProperty(groupVersionKind: "{{RawAPIVersion}}/{{Kind}}", namespace: string, name: string, property: "{{LanguageName}}"): {{{ProviderType}}}; {{/Properties}} - {{/KindsAndAliases}} + {{/TopLevelKindsAndAliases}} {{/Versions}} {{/Groups}} public getResourceProperty(groupVersionKind: string, namespaceOrName: string, nameOrProperty: string, property?: string): pulumi.Output { @@ -400,9 +400,9 @@ import * as outputs from "../types/output"; (apiVersion == "v1" && kind == "List") {{#Groups}} {{#Versions}} - {{#ListKindsAndAliases}} + {{#ListTopLevelKindsAndAliases}} || (apiVersion == "{{RawAPIVersion}}" && kind == "{{Kind}}") - {{/ListKindsAndAliases}} + {{/ListTopLevelKindsAndAliases}} {{/Versions}} {{/Groups}} ) { @@ -430,13 +430,13 @@ import * as outputs from "../types/output"; switch (`${apiVersion}/${kind}`) { {{#Groups}} {{#Versions}} - {{#KindsAndAliases}} + {{#TopLevelKindsAndAliases}} case "{{RawAPIVersion}}/{{Kind}}": return [id.apply(id => ({ name: `{{RawAPIVersion}}/{{Kind}}::${id}`, resource: new k8s.{{Group}}.{{Version}}.{{Kind}}(id, obj, opts), }))]; - {{/KindsAndAliases}} + {{/TopLevelKindsAndAliases}} {{/Versions}} {{/Groups}} default: diff --git a/pkg/gen/nodejs.go b/pkg/gen/nodejs.go index 344d703415..2612c02e47 100644 --- a/pkg/gen/nodejs.go +++ b/pkg/gen/nodejs.go @@ -42,7 +42,8 @@ func NodeJSClient(swagger map[string]interface{}, templateDir string, ) (inputsts, outputsts, indexts, yamlts, packagejson string, groupsts map[string]*GroupTS, err error) { definitions := swagger["definitions"].(map[string]interface{}) - groupsSlice := createGroups(definitions, nodeJSInputs()) + groupsSlice := createGroups(definitions, nodeJSOpts()) + inputsts, err = mustache.RenderFile(fmt.Sprintf("%s/typesInput.ts.mustache", templateDir), map[string]interface{}{ "Groups": groupsSlice, @@ -51,7 +52,6 @@ func NodeJSClient(swagger map[string]interface{}, templateDir string, return } - groupsSlice = createGroups(definitions, nodeJSOutputs()) outputsts, err = mustache.RenderFile(fmt.Sprintf("%s/typesOutput.ts.mustache", templateDir), map[string]interface{}{ "Groups": groupsSlice, @@ -60,16 +60,23 @@ func NodeJSClient(swagger map[string]interface{}, templateDir string, return } - groupsSlice = createGroups(definitions, nodeJSProvider()) groupsts = make(map[string]*GroupTS) for _, group := range groupsSlice { + if !group.HasTopLevelKinds() { + continue + } + groupTS := &GroupTS{} for _, version := range group.Versions() { + if !version.HasTopLevelKinds() { + continue + } + if groupTS.Versions == nil { groupTS.Versions = make(map[string]*VersionTS) } versionTS := &VersionTS{} - for _, kind := range version.Kinds() { + for _, kind := range version.TopLevelKinds() { if versionTS.Kinds == nil { versionTS.Kinds = make(map[string]string) } @@ -103,7 +110,7 @@ func NodeJSClient(swagger map[string]interface{}, templateDir string, kindIndexTS, err := mustache.RenderFile(fmt.Sprintf("%s/kindIndex.ts.mustache", templateDir), map[string]interface{}{ - "Kinds": version.Kinds(), + "Kinds": version.TopLevelKinds(), }) if err != nil { return "", "", "", "", "", nil, err diff --git a/pkg/gen/python-templates/kind.py.mustache b/pkg/gen/python-templates/kind.py.mustache index df70e8e971..79a0656dd2 100644 --- a/pkg/gen/python-templates/kind.py.mustache +++ b/pkg/gen/python-templates/kind.py.mustache @@ -31,7 +31,7 @@ class {{Kind}}(pulumi.CustomResource): """ {{#Properties}} - {{LanguageName}}: {{{PropType}}} + {{LanguageName}}: {{{ProviderType}}} {{{Comment}}} {{/Properties}} @@ -42,10 +42,10 @@ class {{Kind}}(pulumi.CustomResource): :param str resource_name: The _unique_ name of the resource. :param pulumi.ResourceOptions opts: A bag of options that control this resource's behavior. {{#RequiredInputProperties}} - :param {{{PythonConstructorPropType}}} {{LanguageName}}: {{{PythonConstructorComment}}} + :param {{{InputsAPIType}}} {{LanguageName}}: {{{PythonConstructorComment}}} {{/RequiredInputProperties}} {{#OptionalInputProperties}} - :param {{{PythonConstructorPropType}}} {{LanguageName}}: {{{PythonConstructorComment}}} + :param {{{InputsAPIType}}} {{LanguageName}}: {{{PythonConstructorComment}}} {{/OptionalInputProperties}} """ if __name__ is not None: diff --git a/pkg/gen/python-templates/root__init__.py.mustache b/pkg/gen/python-templates/root__init__.py.mustache index 433898b82a..054f0ac358 100644 --- a/pkg/gen/python-templates/root__init__.py.mustache +++ b/pkg/gen/python-templates/root__init__.py.mustache @@ -5,7 +5,9 @@ # Make subpackages available: __all__ = [ {{#Groups}} + {{#HasTopLevelKinds}} "{{Group}}", + {{/HasTopLevelKinds}} {{/Groups}} "helm", "provider", diff --git a/pkg/gen/python-templates/version__init__.py.mustache b/pkg/gen/python-templates/version__init__.py.mustache index 279878a42c..f72dcb152c 100644 --- a/pkg/gen/python-templates/version__init__.py.mustache +++ b/pkg/gen/python-templates/version__init__.py.mustache @@ -3,6 +3,6 @@ # *** Do not edit by hand unless you're certain you know what you are doing! *** # Export this package's modules as members: -{{#Kinds}} +{{#TopLevelKinds}} from .{{Kind}} import ({{Kind}}) -{{/Kinds}} +{{/TopLevelKinds}} diff --git a/pkg/gen/python-templates/yaml.py.mustache b/pkg/gen/python-templates/yaml.py.mustache index 00cb7f6084..ed62d9ebcc 100644 --- a/pkg/gen/python-templates/yaml.py.mustache +++ b/pkg/gen/python-templates/yaml.py.mustache @@ -188,14 +188,14 @@ def _parse_yaml_object( gvk = f"{api_version}/{kind}" {{#Groups}} {{#Versions}} -{{#Kinds}} +{{#TopLevelKinds}} if gvk == "{{RawAPIVersion}}/{{Kind}}": # Import locally to avoid name collisions. from pulumi_kubernetes.{{Group}}.{{Version}} import {{Kind}} return [identifier.apply( lambda x: (f"{{RawAPIVersion}}/{{Kind}}:{x}", {{Kind}}(f"{x}", opts, **obj)))] -{{/Kinds}} +{{/TopLevelKinds}} {{/Versions}} {{/Groups}} return [identifier.apply( diff --git a/pkg/gen/python.go b/pkg/gen/python.go index 471dd7258b..d5ed58f447 100644 --- a/pkg/gen/python.go +++ b/pkg/gen/python.go @@ -45,7 +45,7 @@ func PythonClient( // Generate casing tables from property names. // { properties: [ {name: fooBar, casedName: foo_bar}, ]} - properties := allCamelCasePropertyNames(definitions, pythonProvider()) + properties := allCamelCasePropertyNames(definitions, pythonOpts()) cases := map[string][]map[string]string{"properties": make([]map[string]string, 0)} for _, name := range properties { cases["properties"] = append(cases["properties"], @@ -61,7 +61,7 @@ func PythonClient( return err } - groupsSlice := createGroups(definitions, pythonProvider()) + groupsSlice := createGroups(definitions, pythonOpts()) yamlPy, err := mustache.RenderFile( fmt.Sprintf("%s/yaml.py.mustache", templateDir), @@ -90,6 +90,9 @@ func PythonClient( } for _, group := range groupsSlice { + if !group.HasTopLevelKinds() { + continue + } groupInitPy, err := mustache.RenderFile( fmt.Sprintf("%s/group__init__.py.mustache", templateDir), group) @@ -118,6 +121,10 @@ from .CustomResource import (CustomResource) } for _, version := range group.Versions() { + if !version.HasTopLevelKinds() { + continue + } + versionInitPy, err := mustache.RenderFile( fmt.Sprintf("%s/version__init__.py.mustache", templateDir), version) if err != nil { @@ -129,7 +136,7 @@ from .CustomResource import (CustomResource) return err } - for _, kind := range version.Kinds() { + for _, kind := range version.TopLevelKinds() { inputMap := map[string]interface{}{ "RawAPIVersion": kind.RawAPIVersion(), "Comment": kind.Comment(), diff --git a/pkg/gen/typegen.go b/pkg/gen/typegen.go index 5497df3fb0..9814670959 100644 --- a/pkg/gen/typegen.go +++ b/pkg/gen/typegen.go @@ -55,6 +55,8 @@ const ( type GroupConfig struct { group string versions []*VersionConfig + + hasTopLevelKinds bool } // Group returns the name of the group (e.g., `core` for core, etc.) @@ -64,6 +66,9 @@ func (gc *GroupConfig) Group() string { return gc.group } // has `v1beta1`, `v1beta2`, and `v1`. func (gc *GroupConfig) Versions() []*VersionConfig { return gc.versions } +// HasTopLevelKinds returns true if this group has top-level kinds. +func (gc *GroupConfig) HasTopLevelKinds() bool { return gc.hasTopLevelKinds } + // VersionConfig represents a version of a Kubernetes API group (e.g., the `apps` group has // `v1beta1`, `v1beta2`, and `v1`.) type VersionConfig struct { @@ -73,6 +78,8 @@ type VersionConfig struct { gv *schema.GroupVersion // Used for sorting. apiVersion string rawAPIVersion string + + hasTopLevelKinds bool } // Version returns the name of the version (e.g., `apps/v1beta1` would return `v1beta1`). @@ -82,11 +89,25 @@ func (vc *VersionConfig) Version() string { return vc.version } // `apps/v1beta1` has the `Deployment` kind, etc.). func (vc *VersionConfig) Kinds() []*KindConfig { return vc.kinds } -// KindsAndAliases will produce a list of kinds, including aliases (e.g., both `apiregistration` and +// HasTopLevelKinds returns true if this group has top-level kinds. +func (vc *VersionConfig) HasTopLevelKinds() bool { return vc.hasTopLevelKinds } + +// TopLevelKinds returns the set of kinds that are not nested. +func (vc *VersionConfig) TopLevelKinds() []*KindConfig { + var kinds []*KindConfig + for _, k := range vc.kinds { + if !k.IsNested() { + kinds = append(kinds, k) + } + } + return kinds +} + +// TopLevelKindsAndAliases will produce a list of kinds, including aliases (e.g., both `apiregistration` and // `apiregistration.k8s.io`). -func (vc *VersionConfig) KindsAndAliases() []*KindConfig { +func (vc *VersionConfig) TopLevelKindsAndAliases() []*KindConfig { kindsAndAliases := []*KindConfig{} - for _, kind := range vc.kinds { + for _, kind := range vc.TopLevelKinds() { kindsAndAliases = append(kindsAndAliases, kind) if strings.HasPrefix(kind.APIVersion(), apiRegistration) { alias := KindConfig{} @@ -102,12 +123,12 @@ func (vc *VersionConfig) KindsAndAliases() []*KindConfig { return kindsAndAliases } -// ListKindsAndAliases will return all known `Kind`s that are lists, or aliases of lists. These +// ListTopLevelKindsAndAliases will return all known `Kind`s that are lists, or aliases of lists. These // `Kind`s are not instantiated by the API server, and we must "flatten" them client-side to get an // accurate view of what resource operations we need to perform. -func (vc *VersionConfig) ListKindsAndAliases() []*KindConfig { +func (vc *VersionConfig) ListTopLevelKindsAndAliases() []*KindConfig { listKinds := []*KindConfig{} - for _, kind := range vc.KindsAndAliases() { + for _, kind := range vc.TopLevelKindsAndAliases() { hasItems := false for _, prop := range kind.properties { if prop.name == "items" { @@ -146,6 +167,8 @@ type KindConfig struct { apiVersion string rawAPIVersion string typeGuard string + + isNested bool } // Kind returns the name of the Kubernetes API kind (e.g., `Deployment` for @@ -195,19 +218,23 @@ func (kc *KindConfig) URNAPIVersion() string { // TypeGuard returns the text of a TypeScript type guard for the given kind. func (kc *KindConfig) TypeGuard() string { return kc.typeGuard } +// IsNested returns true if this is a nested kind. +func (kc *KindConfig) IsNested() bool { return kc.isNested } + // Property represents a property we want to expose on a Kubernetes API kind (i.e., things that we // will want to `.` into, like `thing.apiVersion`, `thing.kind`, `thing.metadata`, etc.). type Property struct { - name string - languageName string - comment string - pythonConstructorComment string - propType string - pythonConstructorPropType string - defaultValue string - isLast bool - dotnetVarName string - dotnetIsListOrMap bool + name string + languageName string + comment string + pythonConstructorComment string + inputsAPIType string + outputsAPIType string + providerType string + defaultValue string + isLast bool + dotnetVarName string + dotnetIsListOrMap bool } // Name returns the name of the property. @@ -223,12 +250,14 @@ func (p *Property) Comment() string { return p.comment } // constructor documentation. func (p *Property) PythonConstructorComment() string { return p.pythonConstructorComment } -// PropType returns the type of the property. -func (p *Property) PropType() string { return p.propType } +// InputsAPIType returns the type of the property for the inputs API. +func (p *Property) InputsAPIType() string { return p.inputsAPIType } + +// OutputsAPIType returns the type of the property for the outputs API. +func (p *Property) OutputsAPIType() string { return p.outputsAPIType } -// PythonConstructorPropType returns the type of the property, typed for the Python constructor -// resource inputs. -func (p *Property) PythonConstructorPropType() string { return p.pythonConstructorPropType } +// ProviderType returns the type of the property for the provider API. +func (p *Property) ProviderType() string { return p.providerType } // DefaultValue returns the type of the property. func (p *Property) DefaultValue() string { return p.defaultValue } @@ -395,9 +424,9 @@ const ( v1CRSubresourceStatus = apiextensionsV1 + ".CustomResourceSubresourceStatus" ) -func makeTypescriptType(resourceType, propName string, prop map[string]interface{}, opts groupOpts) string { +func makeTypescriptType(resourceType, propName string, prop map[string]interface{}, gentype gentype) string { wrapType := func(typ string) string { - switch opts.generatorType { + switch gentype { case provider: return fmt.Sprintf("pulumi.Output<%s>", typ) case outputsAPI: @@ -405,12 +434,12 @@ func makeTypescriptType(resourceType, propName string, prop map[string]interface case inputsAPI: return fmt.Sprintf("pulumi.Input<%s>", typ) default: - panic(fmt.Sprintf("unrecognized generator type %d", opts.generatorType)) + panic(fmt.Sprintf("unrecognized generator type %d", gentype)) } } refPrefix := "" - if opts.generatorType == provider { + if gentype == provider { refPrefix = "outputs" } @@ -418,8 +447,8 @@ func makeTypescriptType(resourceType, propName string, prop map[string]interface tstr := t.(string) if tstr == "array" { elemType := makeTypescriptType( - resourceType, propName, prop["items"].(map[string]interface{}), opts) - switch opts.generatorType { + resourceType, propName, prop["items"].(map[string]interface{}), gentype) + switch gentype { case provider: return fmt.Sprintf("%s[]>", elemType[:len(elemType)-1]) case outputsAPI: @@ -435,7 +464,7 @@ func makeTypescriptType(resourceType, propName string, prop map[string]interface if additionalProperties, exists := prop["additionalProperties"]; exists { mapType := additionalProperties.(map[string]interface{}) if ktype, exists := mapType["type"]; exists && len(mapType) == 1 { - switch opts.generatorType { + switch gentype { case inputsAPI: return fmt.Sprintf("pulumi.Input<{[key: %s]: pulumi.Input<%s>}>", ktype, ktype) case outputsAPI: @@ -449,7 +478,7 @@ func makeTypescriptType(resourceType, propName string, prop map[string]interface // Special case: `.metadata.namespace` should either take a string or a namespace object // itself. - switch opts.generatorType { + switch gentype { case inputsAPI: // TODO: Enable metadata to take explicit namespaces, like: // return "pulumi.Input | Namespace" @@ -504,9 +533,9 @@ func makeTypescriptType(resourceType, propName string, prop map[string]interface return wrapType(gvkRefStr) } -func makePythonType(resourceType, propName string, prop map[string]interface{}, opts groupOpts) string { +func makePythonType(resourceType, propName string, prop map[string]interface{}, gentype gentype) string { wrapType := func(typ string) string { - switch opts.generatorType { + switch gentype { case provider: return fmt.Sprintf("pulumi.Output[%s]", typ) case outputsAPI: @@ -514,7 +543,7 @@ func makePythonType(resourceType, propName string, prop map[string]interface{}, case inputsAPI: return fmt.Sprintf("pulumi.Input[%s]", typ) default: - panic(fmt.Sprintf("unrecognized generator type %d", opts.generatorType)) + panic(fmt.Sprintf("unrecognized generator type %d", gentype)) } } @@ -560,18 +589,18 @@ func makePythonType(resourceType, propName string, prop map[string]interface{}, return wrapType(ref) } -func makeDotnetType(resourceType, propName string, prop map[string]interface{}, opts groupOpts) string { +func makeDotnetType(resourceType, propName string, prop map[string]interface{}, gentype gentype, forceNoWrap bool) string { refPrefix := "" - if opts.generatorType == provider { + if gentype == provider { refPrefix = "Types.Outputs" } wrapType := func(typ string) string { - if opts.forceNoWrap { + if forceNoWrap { return typ } - switch opts.generatorType { + switch gentype { case provider: return fmt.Sprintf("Output<%s>", typ) case outputsAPI: @@ -579,15 +608,15 @@ func makeDotnetType(resourceType, propName string, prop map[string]interface{}, case inputsAPI: return fmt.Sprintf("Input<%s>", typ) default: - panic(fmt.Sprintf("unrecognized generator type %d", opts.generatorType)) + panic(fmt.Sprintf("unrecognized generator type %d", gentype)) } } oneOf := func(typeA string, typeB string) string { - if opts.forceNoWrap { + if forceNoWrap { return fmt.Sprintf("Union<%s,%s>", typeA, typeB) } - switch opts.generatorType { + switch gentype { case provider: return fmt.Sprintf("Output>", typeA, typeB) case outputsAPI: @@ -595,27 +624,25 @@ func makeDotnetType(resourceType, propName string, prop map[string]interface{}, case inputsAPI: return fmt.Sprintf("InputUnion<%s,%s>", typeA, typeB) default: - panic(fmt.Sprintf("unrecognized generator type %d", opts.generatorType)) + panic(fmt.Sprintf("unrecognized generator type %d", gentype)) } } if t, exists := prop["type"]; exists { tstr := t.(string) if tstr == "array" { - switch opts.generatorType { + switch gentype { case provider: elemType := makeDotnetType( - resourceType, propName, prop["items"].(map[string]interface{}), opts) + resourceType, propName, prop["items"].(map[string]interface{}), gentype, forceNoWrap) return fmt.Sprintf("%s[]>", elemType[:len(elemType)-1]) case outputsAPI: elemType := makeDotnetType( - resourceType, propName, prop["items"].(map[string]interface{}), opts) + resourceType, propName, prop["items"].(map[string]interface{}), gentype, forceNoWrap) return fmt.Sprintf("ImmutableArray<%s>", elemType) case inputsAPI: - elemOpts := opts - elemOpts.forceNoWrap = true elemType := makeDotnetType( - resourceType, propName, prop["items"].(map[string]interface{}), elemOpts) + resourceType, propName, prop["items"].(map[string]interface{}), gentype, true) return fmt.Sprintf("InputList<%s>", elemType) } } else if tstr == "integer" { @@ -627,23 +654,19 @@ func makeDotnetType(resourceType, propName string, prop map[string]interface{}, } else if tstr == "object" { vtype := "string" if additionalProperties, exists := prop["additionalProperties"]; exists { - switch opts.generatorType { + switch gentype { case provider: - elemOpts := opts - elemOpts.forceNoWrap = true vtype = makeDotnetType( - resourceType, propName, additionalProperties.(map[string]interface{}), elemOpts) + resourceType, propName, additionalProperties.(map[string]interface{}), gentype, true) case outputsAPI: vtype = makeDotnetType( - resourceType, propName, additionalProperties.(map[string]interface{}), opts) + resourceType, propName, additionalProperties.(map[string]interface{}), gentype, forceNoWrap) case inputsAPI: - elemOpts := opts - elemOpts.forceNoWrap = true vtype = makeDotnetType( - resourceType, propName, additionalProperties.(map[string]interface{}), elemOpts) + resourceType, propName, additionalProperties.(map[string]interface{}), gentype, true) } } - switch opts.generatorType { + switch gentype { case inputsAPI: return fmt.Sprintf("InputMap<%s>", vtype) case outputsAPI: @@ -655,7 +678,7 @@ func makeDotnetType(resourceType, propName string, prop map[string]interface{}, // Special case: `.metadata.namespace` should either take a string or a namespace object // itself. - switch opts.generatorType { + switch gentype { case inputsAPI: // TODO: Enable metadata to take explicit namespaces, like: // return "pulumi.Input | Namespace" @@ -672,7 +695,7 @@ func makeDotnetType(resourceType, propName string, prop map[string]interface{}, var argsSuffix string var stringArr string var jsonType string - switch opts.generatorType { + switch gentype { case inputsAPI: argsSuffix = "Args" stringArr = "InputList" @@ -686,7 +709,7 @@ func makeDotnetType(resourceType, propName string, prop map[string]interface{}, stringArr = "string[]" jsonType = "Output" default: - panic(fmt.Sprintf("unrecognized generator type %d", opts.generatorType)) + panic(fmt.Sprintf("unrecognized generator type %d", gentype)) } isSimpleRef := true @@ -721,7 +744,7 @@ func makeDotnetType(resourceType, propName string, prop map[string]interface{}, group := pascalCase(gvk.Group) version := pascalCase(gvk.Version) kind := gvk.Kind - if opts.generatorType == inputsAPI { + if gentype == inputsAPI { kind = kind + "Args" } @@ -735,16 +758,23 @@ func makeDotnetType(resourceType, propName string, prop map[string]interface{}, return wrapType(gvkRefStr) } -func makeType(resourceType, propName string, prop map[string]interface{}, opts groupOpts) string { - switch opts.language { +func makeTypes(resourceType, propName string, prop map[string]interface{}, language language) (string, string, string) { + inputsAPIType := makeType(resourceType, propName, prop, language, inputsAPI) + outputsAPIType := makeType(resourceType, propName, prop, language, outputsAPI) + providerType := makeType(resourceType, propName, prop, language, provider) + return inputsAPIType, outputsAPIType, providerType +} + +func makeType(resourceType, propName string, prop map[string]interface{}, language language, gentype gentype) string { + switch language { case typescript: - return makeTypescriptType(resourceType, propName, prop, opts) + return makeTypescriptType(resourceType, propName, prop, gentype) case python: - return makePythonType(resourceType, propName, prop, opts) + return makePythonType(resourceType, propName, prop, gentype) case dotnet: - return makeDotnetType(resourceType, propName, prop, opts) + return makeDotnetType(resourceType, propName, prop, gentype, false) default: - panic(fmt.Sprintf("Unsupported language '%s'", opts.language)) + panic(fmt.Sprintf("Unsupported language '%s'", language)) } } @@ -812,20 +842,12 @@ const ( ) type groupOpts struct { - generatorType gentype - language language - forceNoWrap bool + language language } -func nodeJSInputs() groupOpts { return groupOpts{generatorType: inputsAPI, language: typescript} } -func nodeJSOutputs() groupOpts { return groupOpts{generatorType: outputsAPI, language: typescript} } -func nodeJSProvider() groupOpts { return groupOpts{generatorType: provider, language: typescript} } - -func pythonProvider() groupOpts { return groupOpts{generatorType: provider, language: python} } - -func dotnetInputs() groupOpts { return groupOpts{generatorType: inputsAPI, language: dotnet} } -func dotnetOutputs() groupOpts { return groupOpts{generatorType: outputsAPI, language: dotnet} } -func dotnetProvider() groupOpts { return groupOpts{generatorType: provider, language: dotnet} } +func nodeJSOpts() groupOpts { return groupOpts{language: typescript} } +func pythonOpts() groupOpts { return groupOpts{language: python} } +func dotnetOpts() groupOpts { return groupOpts{language: dotnet} } func allCamelCasePropertyNames(definitionsJSON map[string]interface{}, opts groupOpts) []string { // Map definition JSON object -> `definition` with metadata. @@ -941,21 +963,19 @@ func createGroups(definitionsJSON map[string]interface{}, opts groupOpts) []*Gro prop := d.data["properties"].(map[string]interface{})[propName].(map[string]interface{}) var prefix string - var t, pyConstructorT string + var inputsAPIType, outputsAPIType, providerType string isListOrMap := false switch opts.language { case typescript: prefix = " " - t = makeType(d.name, propName, prop, opts) + inputsAPIType, outputsAPIType, providerType = makeTypes(d.name, propName, prop, typescript) case python: prefix = " " - t = makeType(d.name, propName, prop, opts) - pyConstructorT = makeType(d.name, propName, prop, - groupOpts{language: python, generatorType: inputsAPI}) + inputsAPIType, outputsAPIType, providerType = makeTypes(d.name, propName, prop, python) case dotnet: prefix = " " - t = makeType(d.name, propName, prop, opts) - if strings.HasPrefix(t, "InputList") || strings.HasPrefix(t, "InputMap") { + inputsAPIType, outputsAPIType, providerType = makeTypes(d.name, propName, prop, dotnet) + if strings.HasPrefix(inputsAPIType, "InputList") || strings.HasPrefix(inputsAPIType, "InputMap") { isListOrMap = true } default: @@ -971,26 +991,16 @@ func createGroups(definitionsJSON map[string]interface{}, opts groupOpts) []*Gro case "apiVersion": defaultValue = fmt.Sprintf(`"%s"`, defaultGroupVersion) if opts.language == typescript && isTopLevel { - switch opts.generatorType { - case provider: - t = fmt.Sprintf(`pulumi.Output<"%s">`, defaultGroupVersion) - case outputsAPI: - t = fmt.Sprintf(`"%s"`, defaultGroupVersion) - case inputsAPI: - t = fmt.Sprintf(`pulumi.Input<"%s">`, defaultGroupVersion) - } + inputsAPIType = fmt.Sprintf(`pulumi.Input<"%s">`, defaultGroupVersion) + outputsAPIType = fmt.Sprintf(`"%s"`, defaultGroupVersion) + providerType = fmt.Sprintf(`pulumi.Output<"%s">`, defaultGroupVersion) } case "kind": defaultValue = fmt.Sprintf(`"%s"`, d.gvk.Kind) if opts.language == typescript && isTopLevel { - switch opts.generatorType { - case provider: - t = fmt.Sprintf(`pulumi.Output<"%s">`, d.gvk.Kind) - case outputsAPI: - t = fmt.Sprintf(`"%s"`, d.gvk.Kind) - case inputsAPI: - t = fmt.Sprintf(`pulumi.Input<"%s">`, d.gvk.Kind) - } + inputsAPIType = fmt.Sprintf(`pulumi.Input<"%s">`, d.gvk.Kind) + outputsAPIType = fmt.Sprintf(`"%s"`, d.gvk.Kind) + providerType = fmt.Sprintf(`pulumi.Output<"%s">`, d.gvk.Kind) } } @@ -1019,16 +1029,17 @@ func createGroups(definitionsJSON map[string]interface{}, opts groupOpts) []*Gro } return &Property{ - comment: fmtComment(prop["description"], prefix, false, opts, d.gvk), - pythonConstructorComment: fmtComment(prop["description"], prefix+prefix+" ", true, opts, d.gvk), - propType: t, - pythonConstructorPropType: pyConstructorT, - name: propName, - languageName: languageName, - dotnetVarName: dotnetVarName, - defaultValue: defaultValue, - isLast: false, - dotnetIsListOrMap: isListOrMap, + comment: fmtComment(prop["description"], prefix, false, opts, d.gvk), + pythonConstructorComment: fmtComment(prop["description"], prefix+prefix+" ", true, opts, d.gvk), + inputsAPIType: inputsAPIType, + outputsAPIType: outputsAPIType, + providerType: providerType, + name: propName, + languageName: languageName, + dotnetVarName: dotnetVarName, + defaultValue: defaultValue, + isLast: false, + dotnetIsListOrMap: isListOrMap, } }) @@ -1065,10 +1076,6 @@ func createGroups(definitionsJSON map[string]interface{}, opts groupOpts) []*Gro return linq.From([]*KindConfig{}) } - if opts.generatorType == provider && (!isTopLevel) { - return linq.From([]*KindConfig{}) - } - var typeGuard string props := d.data["properties"].(map[string]interface{}) _, apiVersionExists := props["apiVersion"] @@ -1095,6 +1102,7 @@ func createGroups(definitionsJSON map[string]interface{}, opts groupOpts) []*Gro apiVersion: fqGroupVersion, rawAPIVersion: defaultGroupVersion, typeGuard: typeGuard, + isNested: !isTopLevel, }, }) }). @@ -1125,13 +1133,18 @@ func createGroups(definitionsJSON map[string]interface{}, opts groupOpts) []*Gro version = pascalCase(version) } + hasTopLevelKinds := linq.From(kindsGroup).WhereT(func(k *KindConfig) bool { + return !k.IsNested() + }).Any() + return linq.From([]*VersionConfig{ { - version: version, - kinds: kindsGroup, - gv: &gv, - apiVersion: kindsGroup[0].apiVersion, // NOTE: This is safe. - rawAPIVersion: kindsGroup[0].rawAPIVersion, // NOTE: This is safe. + version: version, + kinds: kindsGroup, + gv: &gv, + apiVersion: kindsGroup[0].apiVersion, // NOTE: This is safe. + rawAPIVersion: kindsGroup[0].rawAPIVersion, // NOTE: This is safe. + hasTopLevelKinds: hasTopLevelKinds, }, }) }). @@ -1159,10 +1172,15 @@ func createGroups(definitionsJSON map[string]interface{}, opts groupOpts) []*Gro group = pascalCase(group) } + hasTopLevelKinds := linq.From(versionsGroup).WhereT(func(v *VersionConfig) bool { + return v.HasTopLevelKinds() + }).Any() + return linq.From([]*GroupConfig{ { - group: group, - versions: versionsGroup, + group: group, + versions: versionsGroup, + hasTopLevelKinds: hasTopLevelKinds, }, }) }).