// Copyright 2016-2022, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package schema import ( "bytes" "encoding/json" "fmt" "net/url" "regexp" "sort" "strings" "github.com/blang/semver" "github.com/hashicorp/hcl/v2" "github.com/pulumi/pulumi/sdk/v3/go/common/slice" "gopkg.in/yaml.v3" ) // TODO: // - Providerless packages // Type represents a datatype in the Pulumi Schema. Types created by this package are identical if they are // equal values. type Type interface { String() string isType() } type primitiveType int const ( boolType primitiveType = 1 intType primitiveType = 2 numberType primitiveType = 3 stringType primitiveType = 4 archiveType primitiveType = 5 assetType primitiveType = 6 anyType primitiveType = 7 jsonType primitiveType = 8 anyResourceType primitiveType = 9 ) func (t primitiveType) String() string { switch t { case boolType: return "boolean" case intType: return "integer" case numberType: return "number" case stringType: return "string" case archiveType: return "pulumi:pulumi:Archive" case assetType: return "pulumi:pulumi:Asset" case jsonType: fallthrough case anyResourceType: fallthrough case anyType: return "pulumi:pulumi:Any" default: panic("unknown primitive type") } } func (primitiveType) isType() {} // IsPrimitiveType returns true if the given Type is a primitive type. The primitive types are bool, int, number, // string, archive, asset, and any. func IsPrimitiveType(t Type) bool { _, ok := plainType(t).(primitiveType) return ok } var ( // BoolType represents the set of boolean values. BoolType Type = boolType // IntType represents the set of 32-bit integer values. IntType Type = intType // NumberType represents the set of IEEE754 double-precision values. NumberType Type = numberType // StringType represents the set of UTF-8 string values. StringType Type = stringType // ArchiveType represents the set of Pulumi Archive values. ArchiveType Type = archiveType // AssetType represents the set of Pulumi Asset values. AssetType Type = assetType // JSONType represents the set of JSON-encoded values. JSONType Type = jsonType // AnyType represents the complete set of values. AnyType Type = anyType // AnyResourceType represents any Pulumi resource - custom or component AnyResourceType Type = anyResourceType ) // An InvalidType represents an invalid type with associated diagnostics. type InvalidType struct { Diagnostics hcl.Diagnostics } func (t *InvalidType) String() string { return "Invalid" } func (*InvalidType) isType() {} func invalidType(diags ...*hcl.Diagnostic) (Type, hcl.Diagnostics) { t := &InvalidType{Diagnostics: hcl.Diagnostics(diags)} return t, hcl.Diagnostics(diags) } // MapType represents maps from strings to particular element types. type MapType struct { // ElementType is the element type of the map. ElementType Type } func (t *MapType) String() string { return fmt.Sprintf("Map<%v>", t.ElementType) } func (*MapType) isType() {} // ArrayType represents arrays of particular element types. type ArrayType struct { // ElementType is the element type of the array. ElementType Type } func (t *ArrayType) String() string { return fmt.Sprintf("Array<%v>", t.ElementType) } func (*ArrayType) isType() {} // EnumType represents an enum. type EnumType struct { // PackageReference is the PackageReference that defines the resource. PackageReference PackageReference // Token is the type's Pulumi type token. Token string // Comment is the description of the type, if any. Comment string // Elements are the predefined enum values. Elements []*Enum // ElementType is the underlying type for the enum. ElementType Type // IsOverlay indicates whether the type is an overlay provided by the package. Overlay code is generated by the // package rather than using the core Pulumi codegen libraries. IsOverlay bool } // Enum contains information about an enum. type Enum struct { // Value is the value of the enum. Value interface{} // Comment is the description for the enum value. Comment string // Name for the enum. Name string // DeprecationMessage indicates whether or not the value is deprecated. DeprecationMessage string } func (t *EnumType) String() string { return t.Token } func (*EnumType) isType() {} // UnionType represents values that may be any one of a specified set of types. type UnionType struct { // ElementTypes are the allowable types for the union type. ElementTypes []Type // DefaultType is the default type, if any, for the union type. This can be used by targets that do not support // unions, or in positions where unions are not appropriate. DefaultType Type // Discriminator informs the consumer of an alternative schema based on the value associated with it. Discriminator string // Mapping is an optional object to hold mappings between payload values and schema names or references. Mapping map[string]string } func (t *UnionType) String() string { elements := make([]string, len(t.ElementTypes)) for i, e := range t.ElementTypes { elements[i] = e.String() } if t.DefaultType != nil { elements = append(elements, "default="+t.DefaultType.String()) } return fmt.Sprintf("Union<%v>", strings.Join(elements, ", ")) } func (*UnionType) isType() {} // ObjectType represents schematized maps from strings to particular types. type ObjectType struct { // PackageReference is the PackageReference that defines the resource. PackageReference PackageReference // Token is the type's Pulumi type token. Token string // Comment is the description of the type, if any. Comment string // Properties is the list of the type's properties. Properties []*Property // Language specifies additional language-specific data about the object type. Language map[string]interface{} // IsOverlay indicates whether the type is an overlay provided by the package. Overlay code is generated by the // package rather than using the core Pulumi codegen libraries. IsOverlay bool // OverlaySupportedLanguages indicates what languages the overlay supports. This only has an effect if // the Resource is an Overlay (IsOverlay == true). // Supported values are "nodejs", "python", "go", "csharp", "java", "yaml" OverlaySupportedLanguages []string // InputShape is the input shape for this object. Only valid if IsPlainShape returns true. InputShape *ObjectType // PlainShape is the plain shape for this object. Only valid if IsInputShape returns true. PlainShape *ObjectType properties map[string]*Property } // IsPlainShape returns true if this object type is the plain shape of a (plain, input) // pair. The plain shape of an object does not contain *InputType values and only // references other plain shapes. func (t *ObjectType) IsPlainShape() bool { return t.PlainShape == nil } // IsInputShape returns true if this object type is the input shape of a (plain, input) // pair. The input shape of an object may contain *InputType values and may // reference other input shapes. func (t *ObjectType) IsInputShape() bool { return t.PlainShape != nil } func (t *ObjectType) Property(name string) (*Property, bool) { if t.properties == nil && len(t.Properties) > 0 { t.properties = make(map[string]*Property) for _, p := range t.Properties { t.properties[p.Name] = p } } p, ok := t.properties[name] return p, ok } func (t *ObjectType) String() string { if t.PlainShape != nil { return t.Token + "•Input" } return t.Token } func (*ObjectType) isType() {} type ResourceType struct { // Token is the type's Pulumi type token. Token string // Resource is the type's underlying resource. Resource *Resource } func (t *ResourceType) String() string { return t.Token } func (t *ResourceType) isType() {} // TokenType represents an opaque type that is referred to only by its token. A TokenType may have an underlying type // that can be used in place of the token. type TokenType struct { // Token is the type's Pulumi type token. Token string // Underlying type is the type's underlying type, if any. UnderlyingType Type } func (t *TokenType) String() string { return t.Token } func (*TokenType) isType() {} // InputType represents a type that accepts either a prompt value or an output value. type InputType struct { // ElementType is the element type of the input. ElementType Type } func (t *InputType) String() string { return fmt.Sprintf("Input<%v>", t.ElementType) } func (*InputType) isType() {} // OptionalType represents a type that accepts an optional value. type OptionalType struct { // ElementType is the element type of the input. ElementType Type } func (t *OptionalType) String() string { return fmt.Sprintf("Optional<%v>", t.ElementType) } func (*OptionalType) isType() {} // DefaultValue describes a default value for a property. type DefaultValue struct { // Value specifies a static default value, if any. This value must be representable in the Pulumi schema type // system, and its type must be assignable to that of the property to which the default applies. Value interface{} // Environment specifies a set of environment variables to probe for a default value. Environment []string // Language specifies additional language-specific data about the default value. Language map[string]interface{} } // Property describes an object or resource property. type Property struct { // Name is the name of the property. Name string // Comment is the description of the property, if any. Comment string // Type is the type of the property. Type Type // ConstValue is the constant value for the property, if any. ConstValue interface{} // DefaultValue is the default value for the property, if any. DefaultValue *DefaultValue // DeprecationMessage indicates whether or not the property is deprecated. DeprecationMessage string // Language specifies additional language-specific data about the property. Language map[string]interface{} // Secret is true if the property is secret (default false). Secret bool // ReplaceOnChanges specifies if the property is to be replaced instead of updated (default false). ReplaceOnChanges bool // WillReplaceOnChanges indicates that the provider will replace the resource when // this property is changed. This property is used exclusively for docs. WillReplaceOnChanges bool Plain bool } // IsRequired returns true if this property is required (i.e. its type is not Optional). func (p *Property) IsRequired() bool { _, optional := p.Type.(*OptionalType) return !optional } // Alias describes an alias for a Pulumi resource. type Alias struct { // Name is the "name" portion of the alias, if any. Name *string // Project is the "project" portion of the alias, if any. Project *string // Type is the "type" portion of the alias, if any. Type *string } // Resource describes a Pulumi resource. type Resource struct { // PackageReference is the PackageReference that defines the resource. PackageReference PackageReference // Token is the resource's Pulumi type token. Token string // Comment is the description of the resource, if any. Comment string // IsProvider is true if the resource is a provider resource. IsProvider bool // InputProperties is the list of the resource's input properties. InputProperties []*Property // Properties is the list of the resource's output properties. This should be a superset of the input properties. Properties []*Property // StateInputs is the set of inputs used to get an existing resource, if any. StateInputs *ObjectType // Aliases is the list of aliases for the resource. Aliases []*Alias // DeprecationMessage indicates whether or not the resource is deprecated. DeprecationMessage string // Language specifies additional language-specific data about the resource. Language map[string]interface{} // IsComponent indicates whether the resource is a ComponentResource. IsComponent bool // Methods is the list of methods for the resource. Methods []*Method // IsOverlay indicates whether the type is an overlay provided by the package. Overlay code is generated by the // package rather than using the core Pulumi codegen libraries. IsOverlay bool // OverlaySupportedLanguages indicates what languages the overlay supports. This only has an effect if // the Resource is an Overlay (IsOverlay == true). // Supported values are "nodejs", "python", "go", "csharp", "java", "yaml" OverlaySupportedLanguages []string } // The set of resource paths where ReplaceOnChanges is true. // // For example, if you have the following resource struct: // // Resource A { // // Properties: { // Object B { // Object D: { // ReplaceOnChanges: true // } // Object F: {} // } // Object C { // ReplaceOnChanges: true // } // } // } // // A.ReplaceOnChanges() == [[B, D], [C]] func (r *Resource) ReplaceOnChanges() (changes [][]*Property, err []error) { for _, p := range r.Properties { if p.ReplaceOnChanges { changes = append(changes, []*Property{p}) } else { stack := map[string]struct{}{p.Type.String(): {}} childChanges, errList := replaceOnChangesType(p.Type, &stack) err = append(err, errList...) for _, c := range childChanges { changes = append(changes, append([]*Property{p}, c...)) } } } for i, e := range err { err[i] = fmt.Errorf("Failed to genereate full `ReplaceOnChanges`: %w", e) } return changes, err } func replaceOnChangesType(t Type, stack *map[string]struct{}) ([][]*Property, []error) { var errTmp []error if o, ok := t.(*OptionalType); ok { return replaceOnChangesType(o.ElementType, stack) } else if o, ok := t.(*ObjectType); ok { changes := [][]*Property{} err := []error{} for _, p := range o.Properties { if p.ReplaceOnChanges { changes = append(changes, []*Property{p}) } else if _, ok := (*stack)[p.Type.String()]; !ok { // We handle recursive objects (*stack)[p.Type.String()] = struct{}{} var object [][]*Property object, errTmp = replaceOnChangesType(p.Type, stack) err = append(err, errTmp...) for _, path := range object { changes = append(changes, append([]*Property{p}, path...)) } delete(*stack, p.Type.String()) } else { err = append(err, fmt.Errorf("Found recursive object %q", p.Name)) } } // We don't want to emit errors where replaceOnChanges is not used. if len(changes) == 0 { return nil, nil } return changes, err } else if a, ok := t.(*ArrayType); ok { // This looks for types internal to the array, not a property of the array. return replaceOnChangesType(a.ElementType, stack) } else if m, ok := t.(*MapType); ok { // This looks for types internal to the map, not a property of the array. return replaceOnChangesType(m.ElementType, stack) } return nil, nil } // Joins the output of `ReplaceOnChanges` into property path names. // // For example, given an input [[B, D], [C]] where each property has a name // equivalent to it's variable, this function should yield: ["B.D", "C"] func PropertyListJoinToString(propertyList [][]*Property, nameConverter func(string) string) []string { var nonOptional func(Type) Type nonOptional = func(t Type) Type { if o, ok := t.(*OptionalType); ok { return nonOptional(o.ElementType) } return t } out := make([]string, len(propertyList)) for i, p := range propertyList { names := make([]string, len(p)) for j, n := range p { if _, ok := nonOptional(n.Type).(*ArrayType); ok { names[j] = nameConverter(n.Name) + "[*]" } else if _, ok := nonOptional(n.Type).(*MapType); ok { names[j] = nameConverter(n.Name) + ".*" } else { names[j] = nameConverter(n.Name) } } out[i] = strings.Join(names, ".") } return out } type Method struct { // Name is the name of the method. Name string // Function is the function definition for the method. Function *Function } // Function describes a Pulumi function. type Function struct { // PackageReference is the PackageReference that defines the function. PackageReference PackageReference // Token is the function's Pulumi type token. Token string // Comment is the description of the function, if any. Comment string // Inputs is the bag of input values for the function, if any. Inputs *ObjectType // Determines whether the input bag should be treated as a single argument or as multiple arguments. MultiArgumentInputs bool // Outputs is the bag of output values for the function, if any. Outputs *ObjectType // The return type of the function, if any. ReturnType Type // The return type is plain and not wrapped in an Output. ReturnTypePlain bool // When InlineObjectAsReturnType is true, it means that the return type definition is defined inline // as an object type that should be generated as a separate type and it is not // a reference to a existing type in the schema. InlineObjectAsReturnType bool // DeprecationMessage indicates whether or not the function is deprecated. DeprecationMessage string // Language specifies additional language-specific data about the function. Language map[string]interface{} // IsMethod indicates whether the function is a method of a resource. IsMethod bool // IsOverlay indicates whether the function is an overlay provided by the package. Overlay code is generated by the // package rather than using the core Pulumi codegen libraries. IsOverlay bool // OverlaySupportedLanguages indicates what languages the overlay supports. This only has an effect if // the Resource is an Overlay (IsOverlay == true). // Supported values are "nodejs", "python", "go", "csharp", "java", "yaml" OverlaySupportedLanguages []string } // NeedsOutputVersion determines if codegen should emit a ${fn}Output version that // automatically accepts Inputs and returns ReturnType. func (fun *Function) NeedsOutputVersion() bool { // Skip functions that return no value. Arguably we could // support them and return `Task`, but there are no such // functions in `pulumi-azure-native` or `pulumi-aws` so we // omit to simplify. return fun.ReturnType != nil } // BaseProvider type BaseProvider struct { // Name is the name of the provider. Name string // Version is the version of the provider. Version semver.Version // PluginDownloadURL is the URL to use to acquire the provider plugin binary, if any. PluginDownloadURL string } type Parameterization struct { BaseProvider BaseProvider // Parameter is the parameter for the provider. Parameter []byte } // Package describes a Pulumi package. type Package struct { // True if this package should be written in the new style to support pack and conformance testing. SupportPack bool moduleFormat *regexp.Regexp // Name is the unqualified name of the package (e.g. "aws", "azure", "gcp", "kubernetes". "random") Name string // DisplayName is the human-friendly name of the package. DisplayName string // Version is the version of the package. Version *semver.Version // Description is the description of the package. Description string // Keywords is the list of keywords that are associated with the package, if any. // Some reserved keywords can be specified as well that help with categorizing the // package in the Pulumi registry. `category/<name>` and `kind/<type>` are the only // reserved keywords at this time, where `<name>` can be one of: // `cloud`, `database`, `infrastructure`, `monitoring`, `network`, `utility`, `vcs` // and `<type>` is either `native` or `component`. If the package is a bridged Terraform // provider, then don't include the `kind/` label. Keywords []string // Homepage is the package's homepage. Homepage string // License indicates which license is used for the package's contents. License string // Attribution allows freeform text attribution of derived work, if needed. Attribution string // Repository is the URL at which the source for the package can be found. Repository string // LogoURL is the URL for the package's logo, if any. LogoURL string // PluginDownloadURL is the URL to use to acquire the provider plugin binary, if any. PluginDownloadURL string // Publisher is the name of the person or organization that authored and published the package. Publisher string // A list of allowed package name in addition to the Name property. AllowedPackageNames []string // Types is the list of non-resource types defined by the package. Types []Type // Config is the set of configuration properties defined by the package. Config []*Property // Provider is the resource provider for the package, if any. Provider *Resource // Resources is the list of resource types defined by the package. Resources []*Resource // Functions is the list of functions defined by the package. Functions []*Function // Language specifies additional language-specific data about the package. Language map[string]interface{} // Parameterization is the optional parameterization for the package, if any. Parameterization *Parameterization resourceTable map[string]*Resource resourceTypeTable map[string]*ResourceType functionTable map[string]*Function typeTable map[string]Type importedLanguages map[string]struct{} } // Language provides hooks for importing language-specific metadata in a package. type Language interface { // ImportDefaultSpec decodes language-specific metadata associated with a DefaultValue. ImportDefaultSpec(def *DefaultValue, bytes json.RawMessage) (interface{}, error) // ImportPropertySpec decodes language-specific metadata associated with a Property. ImportPropertySpec(property *Property, bytes json.RawMessage) (interface{}, error) // ImportObjectTypeSpec decodes language-specific metadata associated with a ObjectType. ImportObjectTypeSpec(object *ObjectType, bytes json.RawMessage) (interface{}, error) // ImportResourceSpec decodes language-specific metadata associated with a Resource. ImportResourceSpec(resource *Resource, bytes json.RawMessage) (interface{}, error) // ImportFunctionSpec decodes language-specific metadata associated with a Function. ImportFunctionSpec(function *Function, bytes json.RawMessage) (interface{}, error) // ImportPackageSpec decodes language-specific metadata associated with a Package. ImportPackageSpec(pkg *Package, bytes json.RawMessage) (interface{}, error) } func sortedLanguageNames(metadata map[string]interface{}) []string { names := slice.Prealloc[string](len(metadata)) for lang := range metadata { names = append(names, lang) } sort.Strings(names) return names } func importDefaultLanguages(def *DefaultValue, languages map[string]Language) error { for _, name := range sortedLanguageNames(def.Language) { val := def.Language[name] if raw, ok := val.(json.RawMessage); ok { if lang, ok := languages[name]; ok { val, err := lang.ImportDefaultSpec(def, raw) if err != nil { return fmt.Errorf("importing %v metadata: %w", name, err) } def.Language[name] = val } } } return nil } func importPropertyLanguages(property *Property, languages map[string]Language) error { if property.DefaultValue != nil { if err := importDefaultLanguages(property.DefaultValue, languages); err != nil { return fmt.Errorf("importing default value: %w", err) } } for _, name := range sortedLanguageNames(property.Language) { val := property.Language[name] if raw, ok := val.(json.RawMessage); ok { if lang, ok := languages[name]; ok { val, err := lang.ImportPropertySpec(property, raw) if err != nil { return fmt.Errorf("importing %v metadata: %w", name, err) } property.Language[name] = val } } } return nil } func importObjectTypeLanguages(object *ObjectType, languages map[string]Language) error { for _, property := range object.Properties { if err := importPropertyLanguages(property, languages); err != nil { return fmt.Errorf("importing property %v: %w", property.Name, err) } } for _, name := range sortedLanguageNames(object.Language) { val := object.Language[name] if raw, ok := val.(json.RawMessage); ok { if lang, ok := languages[name]; ok { val, err := lang.ImportObjectTypeSpec(object, raw) if err != nil { return fmt.Errorf("importing %v metadata: %w", name, err) } object.Language[name] = val } } } return nil } func importResourceLanguages(resource *Resource, languages map[string]Language) error { for _, property := range resource.InputProperties { if err := importPropertyLanguages(property, languages); err != nil { return fmt.Errorf("importing input property %v: %w", property.Name, err) } } for _, property := range resource.Properties { if err := importPropertyLanguages(property, languages); err != nil { return fmt.Errorf("importing property %v: %w", property.Name, err) } } if resource.StateInputs != nil { for _, property := range resource.StateInputs.Properties { if err := importPropertyLanguages(property, languages); err != nil { return fmt.Errorf("importing state input property %v: %w", property.Name, err) } } } for _, name := range sortedLanguageNames(resource.Language) { val := resource.Language[name] if raw, ok := val.(json.RawMessage); ok { if lang, ok := languages[name]; ok { val, err := lang.ImportResourceSpec(resource, raw) if err != nil { return fmt.Errorf("importing %v metadata: %w", name, err) } resource.Language[name] = val } } } return nil } func importFunctionLanguages(function *Function, languages map[string]Language) error { if function.Inputs != nil { if err := importObjectTypeLanguages(function.Inputs, languages); err != nil { return fmt.Errorf("importing inputs: %w", err) } } if function.ReturnType != nil { if objectType, ok := function.ReturnType.(*ObjectType); ok && objectType != nil { if err := importObjectTypeLanguages(objectType, languages); err != nil { return fmt.Errorf("importing outputs: %w", err) } } } for _, name := range sortedLanguageNames(function.Language) { val := function.Language[name] if raw, ok := val.(json.RawMessage); ok { if lang, ok := languages[name]; ok { val, err := lang.ImportFunctionSpec(function, raw) if err != nil { return fmt.Errorf("importing %v metadata: %w", name, err) } function.Language[name] = val } } } return nil } func (pkg *Package) ImportLanguages(languages map[string]Language) error { if pkg.importedLanguages == nil { pkg.importedLanguages = map[string]struct{}{} } found := false for lang := range languages { if _, ok := pkg.importedLanguages[lang]; !ok { found = true break } } if !found { return nil } for _, t := range pkg.Types { if object, ok := t.(*ObjectType); ok { if err := importObjectTypeLanguages(object, languages); err != nil { return fmt.Errorf("importing object type %v: %w", object.Token, err) } } } for _, config := range pkg.Config { if err := importPropertyLanguages(config, languages); err != nil { return fmt.Errorf("importing configuration property %v: %w", config.Name, err) } } if pkg.Provider != nil { if err := importResourceLanguages(pkg.Provider, languages); err != nil { return fmt.Errorf("importing provider: %w", err) } } for _, resource := range pkg.Resources { if err := importResourceLanguages(resource, languages); err != nil { return fmt.Errorf("importing resource %v: %w", resource.Token, err) } } for _, function := range pkg.Functions { if err := importFunctionLanguages(function, languages); err != nil { return fmt.Errorf("importing function %v: %w", function.Token, err) } } for _, name := range sortedLanguageNames(pkg.Language) { val := pkg.Language[name] if raw, ok := val.(json.RawMessage); ok { if lang, ok := languages[name]; ok { val, err := lang.ImportPackageSpec(pkg, raw) if err != nil { return fmt.Errorf("importing %v metadata: %w", name, err) } pkg.Language[name] = val } } } for lang := range languages { pkg.importedLanguages[lang] = struct{}{} } return nil } func packageIdentity(name string, version *semver.Version) string { // The package's identity is its name and version (if any) separated buy a ':'. The ':' character is not allowed // in package names and so is safe to use as a separator. id := name + ":" if version != nil { return id + version.String() } return id } func (pkg *Package) Identity() string { return packageIdentity(pkg.Name, pkg.Version) } func (pkg *Package) Equals(other *Package) bool { return pkg == other || pkg.Identity() == other.Identity() } var defaultModuleFormat = regexp.MustCompile("(.*)") func (pkg *Package) TokenToModule(tok string) string { // token := pkg ":" module ":" member components := strings.Split(tok, ":") if len(components) != 3 { return "" } switch components[1] { case "providers": return "" default: format := pkg.moduleFormat if format == nil { format = defaultModuleFormat } matches := format.FindStringSubmatch(components[1]) if len(matches) < 2 || strings.HasPrefix(matches[1], "index") { return "" } return matches[1] } } func TokenToRuntimeModule(tok string) string { // token := pkg ":" module ":" member components := strings.Split(tok, ":") if len(components) != 3 { return "" } return components[1] } func (pkg *Package) TokenToRuntimeModule(tok string) string { return TokenToRuntimeModule(tok) } func (pkg *Package) GetResource(token string) (*Resource, bool) { r, ok := pkg.resourceTable[token] return r, ok } func (pkg *Package) GetFunction(token string) (*Function, bool) { f, ok := pkg.functionTable[token] return f, ok } func (pkg *Package) GetResourceType(token string) (*ResourceType, bool) { t, ok := pkg.resourceTypeTable[token] return t, ok } func (pkg *Package) GetType(token string) (Type, bool) { t, ok := pkg.typeTable[token] return t, ok } func (pkg *Package) Reference() PackageReference { return packageDefRef{pkg: pkg} } func (pkg *Package) MarshalSpec() (spec *PackageSpec, err error) { version := "" if pkg.Version != nil { version = pkg.Version.String() } var metadata *MetadataSpec if pkg.moduleFormat != nil || pkg.SupportPack { metadata = &MetadataSpec{SupportPack: pkg.SupportPack} if pkg.moduleFormat != nil { metadata.ModuleFormat = pkg.moduleFormat.String() } } var parameterization *ParameterizationSpec if pkg.Parameterization != nil { parameterization = &ParameterizationSpec{ BaseProvider: BaseProviderSpec{ Name: pkg.Parameterization.BaseProvider.Name, Version: pkg.Parameterization.BaseProvider.Version.String(), PluginDownloadURL: pkg.Parameterization.BaseProvider.PluginDownloadURL, }, Parameter: pkg.Parameterization.Parameter, } } spec = &PackageSpec{ Name: pkg.Name, Version: version, DisplayName: pkg.DisplayName, Publisher: pkg.Publisher, Description: pkg.Description, Keywords: pkg.Keywords, Homepage: pkg.Homepage, License: pkg.License, Attribution: pkg.Attribution, Repository: pkg.Repository, LogoURL: pkg.LogoURL, PluginDownloadURL: pkg.PluginDownloadURL, Meta: metadata, Types: map[string]ComplexTypeSpec{}, Resources: map[string]ResourceSpec{}, Functions: map[string]FunctionSpec{}, AllowedPackageNames: pkg.AllowedPackageNames, Parameterization: parameterization, } lang, err := marshalLanguage(pkg.Language) if err != nil { return nil, fmt.Errorf("marshaling package language: %w", err) } spec.Language = lang spec.Config.Required, spec.Config.Variables, err = pkg.marshalProperties(pkg.Config, true) if err != nil { return nil, fmt.Errorf("marshaling package config: %w", err) } spec.Provider, err = pkg.marshalResource(pkg.Provider) if err != nil { return nil, fmt.Errorf("marshaling provider: %w", err) } for _, t := range pkg.Types { switch t := t.(type) { case *ObjectType: if t.IsInputShape() { continue } // Use the input shape when marshaling in order to get the plain annotations right. o, err := pkg.marshalObject(t.InputShape, false) if err != nil { return nil, fmt.Errorf("marshaling type '%v': %w", t.Token, err) } spec.Types[t.Token] = o case *EnumType: spec.Types[t.Token] = pkg.marshalEnum(t) } } for _, res := range pkg.Resources { r, err := pkg.marshalResource(res) if err != nil { return nil, fmt.Errorf("marshaling resource '%v': %w", res.Token, err) } spec.Resources[res.Token] = r } for _, fn := range pkg.Functions { f, err := pkg.marshalFunction(fn) if err != nil { return nil, fmt.Errorf("marshaling function '%v': %w", fn.Token, err) } spec.Functions[fn.Token] = f } return spec, nil } func (pkg *Package) MarshalJSON() ([]byte, error) { spec, err := pkg.MarshalSpec() if err != nil { return nil, err } return jsonMarshal(spec) } func (pkg *Package) MarshalYAML() ([]byte, error) { spec, err := pkg.MarshalSpec() if err != nil { return nil, err } var b bytes.Buffer enc := yaml.NewEncoder(&b) enc.SetIndent(2) if err := enc.Encode(spec); err != nil { return nil, err } return b.Bytes(), nil } func (pkg *Package) marshalObjectData(comment string, properties []*Property, language map[string]interface{}, plain, isOverlay bool, overlaySupportedLanguages []string, ) (ObjectTypeSpec, error) { required, props, err := pkg.marshalProperties(properties, plain) if err != nil { return ObjectTypeSpec{}, err } lang, err := marshalLanguage(language) if err != nil { return ObjectTypeSpec{}, err } return ObjectTypeSpec{ Description: comment, Properties: props, Type: "object", Required: required, Language: lang, IsOverlay: isOverlay, OverlaySupportedLanguages: overlaySupportedLanguages, }, nil } func (pkg *Package) marshalObject(t *ObjectType, plain bool) (ComplexTypeSpec, error) { data, err := pkg.marshalObjectData(t.Comment, t.Properties, t.Language, plain, t.IsOverlay, nil) if err != nil { return ComplexTypeSpec{}, err } return ComplexTypeSpec{ObjectTypeSpec: data}, nil } func (pkg *Package) marshalEnum(t *EnumType) ComplexTypeSpec { values := make([]EnumValueSpec, len(t.Elements)) for i, el := range t.Elements { values[i] = EnumValueSpec{ Name: el.Name, Description: el.Comment, Value: el.Value, DeprecationMessage: el.DeprecationMessage, } } return ComplexTypeSpec{ ObjectTypeSpec: ObjectTypeSpec{ Description: t.Comment, Type: pkg.marshalType(t.ElementType, false).Type, IsOverlay: t.IsOverlay, }, Enum: values, } } func (pkg *Package) marshalResource(r *Resource) (ResourceSpec, error) { object, err := pkg.marshalObjectData(r.Comment, r.Properties, r.Language, true, r.IsOverlay, r.OverlaySupportedLanguages) if err != nil { return ResourceSpec{}, fmt.Errorf("marshaling properties: %w", err) } requiredInputs, inputs, err := pkg.marshalProperties(r.InputProperties, false) if err != nil { return ResourceSpec{}, fmt.Errorf("marshaling input properties: %w", err) } var stateInputs *ObjectTypeSpec if r.StateInputs != nil { o, err := pkg.marshalObject(r.StateInputs, false) if err != nil { return ResourceSpec{}, fmt.Errorf("marshaling state inputs: %w", err) } stateInputs = &o.ObjectTypeSpec } aliases := slice.Prealloc[AliasSpec](len(r.Aliases)) for _, a := range r.Aliases { aliases = append(aliases, AliasSpec{ Name: a.Name, Project: a.Project, Type: a.Type, }) } var methods map[string]string if len(r.Methods) != 0 { methods = map[string]string{} for _, m := range r.Methods { methods[m.Name] = m.Function.Token } } return ResourceSpec{ ObjectTypeSpec: object, InputProperties: inputs, RequiredInputs: requiredInputs, StateInputs: stateInputs, Aliases: aliases, DeprecationMessage: r.DeprecationMessage, IsComponent: r.IsComponent, Methods: methods, }, nil } func (pkg *Package) marshalFunction(f *Function) (FunctionSpec, error) { var inputs *ObjectTypeSpec if f.Inputs != nil { ins, err := pkg.marshalObject(f.Inputs, true) if err != nil { return FunctionSpec{}, fmt.Errorf("marshaling inputs: %w", err) } inputs = &ins.ObjectTypeSpec } var multiArgumentInputs []string if f.MultiArgumentInputs { multiArgumentInputs = make([]string, len(f.Inputs.Properties)) for i, prop := range f.Inputs.Properties { multiArgumentInputs[i] = prop.Name } } var outputs *ObjectTypeSpec if f.Outputs != nil { outs, err := pkg.marshalObject(f.Outputs, true) if err != nil { return FunctionSpec{}, fmt.Errorf("marshaling outputs: %w", err) } outputs = &outs.ObjectTypeSpec } var returnType *ReturnTypeSpec if f.ReturnType != nil { returnType = &ReturnTypeSpec{} if objectType, ok := f.ReturnType.(*ObjectType); ok { ret, err := pkg.marshalObject(objectType, true) if err != nil { return FunctionSpec{}, fmt.Errorf("marshaling object spec: %w", err) } returnType.ObjectTypeSpec = &ret.ObjectTypeSpec if f.ReturnTypePlain { returnType.ObjectTypeSpecIsPlain = true } } else { typeSpec := pkg.marshalType(f.ReturnType, true) returnType.TypeSpec = &typeSpec if f.ReturnTypePlain { returnType.TypeSpec.Plain = true } } } lang, err := marshalLanguage(f.Language) if err != nil { return FunctionSpec{}, err } return FunctionSpec{ Description: f.Comment, DeprecationMessage: f.DeprecationMessage, IsOverlay: f.IsOverlay, OverlaySupportedLanguages: f.OverlaySupportedLanguages, Inputs: inputs, MultiArgumentInputs: multiArgumentInputs, Outputs: outputs, ReturnType: returnType, Language: lang, }, nil } func (pkg *Package) marshalProperties(props []*Property, plain bool) (required []string, specs map[string]PropertySpec, err error, ) { if len(props) == 0 { return } specs = make(map[string]PropertySpec, len(props)) for _, p := range props { typ := p.Type if t, optional := typ.(*OptionalType); optional { typ = t.ElementType } else { required = append(required, p.Name) } var defaultValue interface{} var defaultSpec *DefaultSpec if p.DefaultValue != nil { defaultValue = p.DefaultValue.Value if len(p.DefaultValue.Environment) != 0 || len(p.DefaultValue.Language) != 0 { lang, err := marshalLanguage(p.DefaultValue.Language) if err != nil { return nil, nil, fmt.Errorf("property '%v': %w", p.Name, err) } defaultSpec = &DefaultSpec{ Environment: p.DefaultValue.Environment, Language: lang, } } } lang, err := marshalLanguage(p.Language) if err != nil { return nil, nil, fmt.Errorf("property '%v': %w", p.Name, err) } propertyType := pkg.marshalType(typ, plain) propertyType.Plain = p.Plain specs[p.Name] = PropertySpec{ TypeSpec: propertyType, Description: p.Comment, Const: p.ConstValue, Default: defaultValue, DefaultInfo: defaultSpec, DeprecationMessage: p.DeprecationMessage, Language: lang, Secret: p.Secret, ReplaceOnChanges: p.ReplaceOnChanges, WillReplaceOnChanges: p.WillReplaceOnChanges, } } return required, specs, nil } // marshalType marshals the given type into a TypeSpec. If plain is true, then the type is being marshaled within a // plain type context (e.g. a resource output property or a function input/output object type), and therefore does not // require `Plain` annotations (hence the odd-looking `Plain: !plain` fields below). func (pkg *Package) marshalType(t Type, plain bool) TypeSpec { switch t := t.(type) { case *InputType: el := pkg.marshalType(t.ElementType, false) el.Plain = false return el case *ArrayType: el := pkg.marshalType(t.ElementType, plain) return TypeSpec{ Type: "array", Items: &el, Plain: !plain, } case *MapType: el := pkg.marshalType(t.ElementType, plain) return TypeSpec{ Type: "object", AdditionalProperties: &el, Plain: !plain, } case *UnionType: oneOf := make([]TypeSpec, len(t.ElementTypes)) for i, el := range t.ElementTypes { oneOf[i] = pkg.marshalType(el, plain) } defaultType := "" if t.DefaultType != nil { defaultType = pkg.marshalType(t.DefaultType, plain).Type } var discriminator *DiscriminatorSpec if t.Discriminator != "" { discriminator = &DiscriminatorSpec{ PropertyName: t.Discriminator, Mapping: t.Mapping, } } return TypeSpec{ Type: defaultType, OneOf: oneOf, Discriminator: discriminator, Plain: !plain, } case *ObjectType: return TypeSpec{ Ref: pkg.marshalTypeRef(t.PackageReference, "types", t.Token), Plain: !plain, } case *EnumType: return TypeSpec{ Ref: pkg.marshalTypeRef(t.PackageReference, "types", t.Token), Plain: !plain, } case *ResourceType: return TypeSpec{ Ref: pkg.marshalTypeRef(t.Resource.PackageReference, "resources", t.Token), Plain: !plain, } case *TokenType: var defaultType string if t.UnderlyingType != nil { defaultType = pkg.marshalType(t.UnderlyingType, plain).Type } return TypeSpec{ Type: defaultType, Ref: pkg.marshalTypeRef(pkg.Reference(), "types", t.Token), Plain: !plain, } default: switch t { case BoolType: return TypeSpec{ Type: "boolean", Plain: !plain, } case StringType: return TypeSpec{ Type: "string", Plain: !plain, } case IntType: return TypeSpec{ Type: "integer", Plain: !plain, } case NumberType: return TypeSpec{ Type: "number", Plain: !plain, } case AnyType: return TypeSpec{ Ref: "pulumi.json#/Any", Plain: !plain, } case ArchiveType: return TypeSpec{ Ref: "pulumi.json#/Archive", Plain: !plain, } case AssetType: return TypeSpec{ Ref: "pulumi.json#/Asset", Plain: !plain, } case JSONType: return TypeSpec{ Ref: "pulumi.json#/Json", Plain: !plain, } default: panic(fmt.Errorf("unexepcted type %v (%T)", t, t)) } } } func (pkg *Package) marshalTypeRef(container PackageReference, section, token string) string { token = url.PathEscape(token) if p, err := container.Definition(); err == nil && p == pkg { return fmt.Sprintf("#/%s/%s", section, token) } // TODO(schema): this isn't quite right--it doesn't handle schemas sourced from URLs--but it's good enough for now. return fmt.Sprintf("/%s/v%v/schema.json#/%s/%s", container.Name(), container.Version(), section, token) } func marshalLanguage(lang map[string]interface{}) (map[string]RawMessage, error) { if len(lang) == 0 { return nil, nil } result := map[string]RawMessage{} for name, data := range lang { bytes, err := jsonMarshal(data) if err != nil { return nil, fmt.Errorf("marshaling %v language data: %w", name, err) } result[name] = RawMessage(bytes) } return result, nil } func jsonMarshal(v interface{}) ([]byte, error) { var b bytes.Buffer enc := json.NewEncoder(&b) enc.SetEscapeHTML(false) enc.SetIndent("", " ") if err := enc.Encode(v); err != nil { return nil, err } return b.Bytes(), nil } type RawMessage []byte func (m RawMessage) MarshalJSON() ([]byte, error) { return []byte(m), nil } func (m *RawMessage) UnmarshalJSON(bytes []byte) error { *m = make([]byte, len(bytes)) copy(*m, bytes) return nil } func (m RawMessage) MarshalYAML() ([]byte, error) { return []byte(m), nil } func (m *RawMessage) UnmarshalYAML(node *yaml.Node) error { var value interface{} if err := node.Decode(&value); err != nil { return err } bytes, err := jsonMarshal(value) if err != nil { return err } *m = bytes return nil } // TypeSpec is the serializable form of a reference to a type. type TypeSpec struct { // Type is the primitive or composite type, if any. May be "boolean", "string", "integer", "number", "array", or // "object". Type string `json:"type,omitempty" yaml:"type,omitempty"` // Ref is a reference to a type in this or another document. For example, the built-in Archive, Asset, and Any // types are referenced as "pulumi.json#/Archive", "pulumi.json#/Asset", and "pulumi.json#/Any", respectively. // A type from this document is referenced as "#/types/pulumi:type:token". // A type from another document is referenced as "path#/types/pulumi:type:token", where path is of the form: // "/provider/vX.Y.Z/schema.json" or "pulumi.json" or "http[s]://example.com/provider/vX.Y.Z/schema.json" // A resource from this document is referenced as "#/resources/pulumi:type:token". // A resource from another document is referenced as "path#/resources/pulumi:type:token", where path is of the form: // "/provider/vX.Y.Z/schema.json" or "pulumi.json" or "http[s]://example.com/provider/vX.Y.Z/schema.json" Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` // AdditionalProperties, if set, describes the element type of an "object" (i.e. a string -> value map). AdditionalProperties *TypeSpec `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` // Items, if set, describes the element type of an array. Items *TypeSpec `json:"items,omitempty" yaml:"items,omitempty"` // OneOf indicates that values of the type may be one of any of the listed types. OneOf []TypeSpec `json:"oneOf,omitempty" yaml:"oneOf,omitempty"` // Discriminator informs the consumer of an alternative schema based on the value associated with it. Discriminator *DiscriminatorSpec `json:"discriminator,omitempty" yaml:"discriminator,omitempty"` // Plain indicates that when used as an input, this type does not accept eventual values. Plain bool `json:"plain,omitempty" yaml:"plain,omitempty"` } // DiscriminatorSpec informs the consumer of an alternative schema based on the value associated with it. type DiscriminatorSpec struct { // PropertyName is the name of the property in the payload that will hold the discriminator value. PropertyName string `json:"propertyName" yaml:"propertyName"` // Mapping is an optional object to hold mappings between payload values and schema names or references. Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"` } // DefaultSpec is the serializable form of extra information about the default value for a property. type DefaultSpec struct { // Environment specifies a set of environment variables to probe for a default value. Environment []string `json:"environment,omitempty" yaml:"environment,omitempty"` // Language specifies additional language-specific data about the default value. Language map[string]RawMessage `json:"language,omitempty" yaml:"language,omitempty"` } // PropertySpec is the serializable form of an object or resource property. type PropertySpec struct { TypeSpec `yaml:",inline"` // Description is the description of the property, if any. Description string `json:"description,omitempty" yaml:"description,omitempty"` // Const is the constant value for the property, if any. The type of the value must be assignable to the type of // the property. Const interface{} `json:"const,omitempty" yaml:"const,omitempty"` // Default is the default value for the property, if any. The type of the value must be assignable to the type of // the property. Default interface{} `json:"default,omitempty" yaml:"default,omitempty"` // DefaultInfo contains additional information about the property's default value, if any. DefaultInfo *DefaultSpec `json:"defaultInfo,omitempty" yaml:"defaultInfo,omitempty"` // DeprecationMessage indicates whether or not the property is deprecated. DeprecationMessage string `json:"deprecationMessage,omitempty" yaml:"deprecationMessage,omitempty"` // Language specifies additional language-specific data about the property. Language map[string]RawMessage `json:"language,omitempty" yaml:"language,omitempty"` // Secret specifies if the property is secret (default false). Secret bool `json:"secret,omitempty" yaml:"secret,omitempty"` // ReplaceOnChanges specifies if the property is to be replaced instead of updated (default false). ReplaceOnChanges bool `json:"replaceOnChanges,omitempty" yaml:"replaceOnChanges,omitempty"` // WillReplaceOnChanges indicates that the provider will replace the resource when // this property is changed. This property is used exclusively for docs. WillReplaceOnChanges bool `json:"willReplaceOnChanges,omitempty" yaml:"willReplaceOnChanges,omitempty"` } // ObjectTypeSpec is the serializable form of an object type. type ObjectTypeSpec struct { // Description is the description of the type, if any. Description string `json:"description,omitempty" yaml:"description,omitempty"` // Properties, if present, is a map from property name to PropertySpec that describes the type's properties. Properties map[string]PropertySpec `json:"properties,omitempty" yaml:"properties,omitempty"` // Type must be "object" if this is an object type, or the underlying type for an enum. Type string `json:"type,omitempty" yaml:"type,omitempty"` // Required, if present, is a list of the names of an object type's required properties. These properties must be set // for inputs and will always be set for outputs. Required []string `json:"required,omitempty" yaml:"required,omitempty"` // Plain, was a list of the names of an object type's plain properties. This property is ignored: instead, property // types should be marked as plain where necessary. Plain []string `json:"plain,omitempty" yaml:"plain,omitempty"` // Language specifies additional language-specific data about the type. Language map[string]RawMessage `json:"language,omitempty" yaml:"language,omitempty"` // IsOverlay indicates whether the type is an overlay provided by the package. Overlay code is generated by the // package rather than using the core Pulumi codegen libraries. IsOverlay bool `json:"isOverlay,omitempty" yaml:"isOverlay,omitempty"` // OverlaySupportedLanguages indicates what languages the overlay supports. This only has an effect if // the Resource is an Overlay (IsOverlay == true). // Supported values are "nodejs", "python", "go", "csharp", "java", "yaml" OverlaySupportedLanguages []string `json:"overlaySupportedLanguages,omitempty" yaml:"overlaySupportedLanguages,omitempty"` //nolint:lll } // ComplexTypeSpec is the serializable form of an object or enum type. type ComplexTypeSpec struct { ObjectTypeSpec `yaml:",inline"` // Enum, if present, is the list of possible values for an enum type. Enum []EnumValueSpec `json:"enum,omitempty" yaml:"enum,omitempty"` } // EnumValueSpec is the serializable form of the values metadata associated with an enum type. type EnumValueSpec struct { // Name, if present, overrides the name of the enum value that would usually be derived from the value. Name string `json:"name,omitempty" yaml:"name,omitempty"` // Description of the enum value. Description string `json:"description,omitempty" yaml:"description,omitempty"` // Value is the enum value itself. Value interface{} `json:"value" yaml:"value"` // DeprecationMessage indicates whether or not the value is deprecated. DeprecationMessage string `json:"deprecationMessage,omitempty" yaml:"deprecationMessage,omitempty"` } // AliasSpec is the serializable form of an alias description. type AliasSpec struct { // Name is the name portion of the alias, if any. Name *string `json:"name,omitempty" yaml:"name,omitempty"` // Project is the project portion of the alias, if any. Project *string `json:"project,omitempty" yaml:"project,omitempty"` // Type is the type portion of the alias, if any. Type *string `json:"type,omitempty" yaml:"type,omitempty"` } // ResourceSpec is the serializable form of a resource description. type ResourceSpec struct { ObjectTypeSpec `yaml:",inline"` // InputProperties is a map from property name to PropertySpec that describes the resource's input properties. InputProperties map[string]PropertySpec `json:"inputProperties,omitempty" yaml:"inputProperties,omitempty"` // RequiredInputs is a list of the names of the resource's required input properties. RequiredInputs []string `json:"requiredInputs,omitempty" yaml:"requiredInputs,omitempty"` // PlainInputs was a list of the names of the resource's plain input properties. This property is ignored: // instead, property types should be marked as plain where necessary. PlainInputs []string `json:"plainInputs,omitempty" yaml:"plainInputs,omitempty"` // StateInputs is an optional ObjectTypeSpec that describes additional inputs that mau be necessary to get an // existing resource. If this is unset, only an ID is necessary. StateInputs *ObjectTypeSpec `json:"stateInputs,omitempty" yaml:"stateInputs,omitempty"` // Aliases is the list of aliases for the resource. Aliases []AliasSpec `json:"aliases,omitempty" yaml:"aliases,omitempty"` // DeprecationMessage indicates whether or not the resource is deprecated. DeprecationMessage string `json:"deprecationMessage,omitempty" yaml:"deprecationMessage,omitempty"` // IsComponent indicates whether the resource is a ComponentResource. IsComponent bool `json:"isComponent,omitempty" yaml:"isComponent,omitempty"` // Methods maps method names to functions in this schema. Methods map[string]string `json:"methods,omitempty" yaml:"methods,omitempty"` } // ReturnTypeSpec is either ObjectTypeSpec or TypeSpec. type ReturnTypeSpec struct { ObjectTypeSpec *ObjectTypeSpec // If ObjectTypeSpec is non-nil, it can also be marked with ObjectTypeSpecIsPlain: true // indicating that the generated code should not wrap in the result in an Output but return // it directly. This option is incompatible with marking individual properties with // ObjectTypSpec.Plain. ObjectTypeSpecIsPlain bool TypeSpec *TypeSpec } type returnTypeSpecObjectSerialForm struct { ObjectTypeSpec Plain any `json:"plain,omitempty"` } func (returnTypeSpec *ReturnTypeSpec) marshalJSONLikeObject() (map[string]interface{}, error) { ts := returnTypeSpec bytes, err := ts.MarshalJSON() if err != nil { return nil, err } var r map[string]interface{} if err := json.Unmarshal(bytes, &r); err != nil { return nil, err } return r, nil } func (returnTypeSpec *ReturnTypeSpec) MarshalJSON() ([]byte, error) { ts := returnTypeSpec if ts.ObjectTypeSpec != nil { form := returnTypeSpecObjectSerialForm{ ObjectTypeSpec: *ts.ObjectTypeSpec, } if ts.ObjectTypeSpecIsPlain { form.Plain = true } else if len(ts.ObjectTypeSpec.Plain) > 0 { form.Plain = ts.ObjectTypeSpec.Plain } return json.Marshal(form) } return json.Marshal(ts.TypeSpec) } func (returnTypeSpec *ReturnTypeSpec) UnmarshalJSON(inputJSON []byte) error { ts := returnTypeSpec var m returnTypeSpecObjectSerialForm err := json.Unmarshal(inputJSON, &m) if err == nil { if m.ObjectTypeSpec.Properties != nil { ts.ObjectTypeSpec = &m.ObjectTypeSpec if plain, ok := m.Plain.(bool); ok && plain { ts.ObjectTypeSpecIsPlain = true } if plain, ok := m.Plain.([]interface{}); ok { for _, p := range plain { if ps, ok := p.(string); ok { ts.ObjectTypeSpec.Plain = append(ts.ObjectTypeSpec.Plain, ps) } } } return nil } } return json.Unmarshal(inputJSON, &ts.TypeSpec) } // Deprecated. type Decoder func([]byte, interface{}) error // Deprecated. func (returnTypeSpec *ReturnTypeSpec) UnmarshalReturnTypeSpec(data []byte, decode Decoder) error { var objectMap map[string]interface{} if err := decode(data, &objectMap); err != nil { return err } if len(objectMap) == 0 { return nil } inputJSON, err := json.Marshal(objectMap) if err != nil { return err } return returnTypeSpec.UnmarshalJSON(inputJSON) } // Deprecated. func (returnTypeSpec *ReturnTypeSpec) UnmarshalYAML(inputYAML []byte) error { return returnTypeSpec.UnmarshalReturnTypeSpec(inputYAML, yaml.Unmarshal) } // FunctionSpec is the serializable form of a function description. type FunctionSpec struct { // Description is the description of the function, if any. Description string `json:"description,omitempty" yaml:"description,omitempty"` // Inputs is the bag of input values for the function, if any. Inputs *ObjectTypeSpec `json:"inputs,omitempty" yaml:"inputs,omitempty"` // Determines whether the input bag should be treated as a single argument or as multiple arguments. // When MultiArgumentInputs is non-empty, it must match up 1:1 with the property names in of the Inputs object. // The order in which the properties are listed in MultiArgumentInputs determines the order in which the // arguments are passed to the function. MultiArgumentInputs []string `json:"multiArgumentInputs,omitempty" yaml:"multiArgumentInputs,omitempty"` // Outputs is the bag of output values for the function, if any. // This field is DEPRECATED. Use ReturnType instead where it allows for more flexible types // to describe the outputs of the function definition. It is invalid to specify both Outputs and ReturnType. Outputs *ObjectTypeSpec `json:"outputs,omitempty" yaml:"outputs,omitempty"` // Specified the return type of the function definition ReturnType *ReturnTypeSpec // DeprecationMessage indicates whether the function is deprecated. DeprecationMessage string `json:"deprecationMessage,omitempty" yaml:"deprecationMessage,omitempty"` // Language specifies additional language-specific data about the function. Language map[string]RawMessage `json:"language,omitempty" yaml:"language,omitempty"` // IsOverlay indicates whether the function is an overlay provided by the package. Overlay code is generated by the // package rather than using the core Pulumi codegen libraries. IsOverlay bool `json:"isOverlay,omitempty" yaml:"isOverlay,omitempty"` // OverlaySupportedLanguages indicates what languages the overlay supports. This only has an effect if // the Resource is an Overlay (IsOverlay == true). // Supported values are "nodejs", "python", "go", "csharp", "java", "yaml" OverlaySupportedLanguages []string `json:"overlaySupportedLanguages,omitempty" yaml:"overlaySupportedLanguages,omitempty"` //nolint:lll } func emptyObject(data RawMessage) (bool, error) { var objectData *map[string]RawMessage if err := json.Unmarshal(data, &objectData); err != nil { return false, err } if objectData == nil { return true, nil } return len(*objectData) == 0, nil } func unmarshalFunctionSpec(funcSpec *FunctionSpec, data map[string]RawMessage) error { if description, ok := data["description"]; ok { if err := json.Unmarshal(description, &funcSpec.Description); err != nil { return err } } if inputs, ok := data["inputs"]; ok { if err := json.Unmarshal(inputs, &funcSpec.Inputs); err != nil { return err } } if multiArgumentInputs, ok := data["multiArgumentInputs"]; ok { if err := json.Unmarshal(multiArgumentInputs, &funcSpec.MultiArgumentInputs); err != nil { return err } } if returnType, ok := data["outputs"]; ok { isEmpty, err := emptyObject(returnType) if err != nil { return err } if !isEmpty { if err := json.Unmarshal(returnType, &funcSpec.ReturnType); err != nil { return err } } else { funcSpec.ReturnType = nil } } if deprecationMessage, ok := data["deprecationMessage"]; ok { if err := json.Unmarshal(deprecationMessage, &funcSpec.DeprecationMessage); err != nil { return err } } if language, ok := data["language"]; ok { if err := json.Unmarshal(language, &funcSpec.Language); err != nil { return err } } if isOverlay, ok := data["isOverlay"]; ok { if err := json.Unmarshal(isOverlay, &funcSpec.IsOverlay); err != nil { return err } } if overlaySupportedLanguages, ok := data["overlaySupportedLanguages"]; ok { if err := json.Unmarshal(overlaySupportedLanguages, &funcSpec.OverlaySupportedLanguages); err != nil { return err } } return nil } // UnmarshalJSON is custom unmarshalling logic for FunctionSpec so that we can derive Outputs from ReturnType // which otherwise isn't possible when both are retrieved from the same JSON field func (funcSpec *FunctionSpec) UnmarshalJSON(inputJSON []byte) error { var data map[string]RawMessage if err := json.Unmarshal(inputJSON, &data); err != nil { return err } return unmarshalFunctionSpec(funcSpec, data) } // UnmarshalYAML is custom unmarshalling logic for FunctionSpec so that we can derive Outputs from ReturnType // which otherwise isn't possible when both are retrieved from the same JSON field func (funcSpec *FunctionSpec) UnmarshalYAML(node *yaml.Node) error { var data map[string]RawMessage if err := node.Decode(&data); err != nil { return err } return unmarshalFunctionSpec(funcSpec, data) } func (funcSpec FunctionSpec) marshalFunctionSpec() (map[string]interface{}, error) { data := make(map[string]interface{}) if funcSpec.Description != "" { data["description"] = funcSpec.Description } if funcSpec.Inputs != nil { data["inputs"] = funcSpec.Inputs } if len(funcSpec.MultiArgumentInputs) > 0 { data["multiArgumentInputs"] = funcSpec.MultiArgumentInputs } if funcSpec.ReturnType != nil { rto, err := funcSpec.ReturnType.marshalJSONLikeObject() if err != nil { return nil, err } data["outputs"] = rto } // for backward-compat when we only specify the outputs object of the function if funcSpec.ReturnType == nil && funcSpec.Outputs != nil { data["outputs"] = funcSpec.Outputs } if funcSpec.DeprecationMessage != "" { data["deprecationMessage"] = funcSpec.DeprecationMessage } if funcSpec.IsOverlay { // the default is false, so only write the property when it is true data["isOverlay"] = true } if len(funcSpec.OverlaySupportedLanguages) > 0 { // by default it supports all languages the provider supports, so only write the property when it is not the default data["overlaySupportedLanguages"] = funcSpec.OverlaySupportedLanguages } if funcSpec.Language != nil && len(funcSpec.Language) > 0 { data["language"] = funcSpec.Language } return data, nil } func (funcSpec FunctionSpec) MarshalJSON() ([]byte, error) { data, err := funcSpec.marshalFunctionSpec() if err != nil { return nil, err } return json.Marshal(data) } func (funcSpec FunctionSpec) MarshalYAML() (interface{}, error) { return funcSpec.marshalFunctionSpec() } // ConfigSpec is the serializable description of a package's configuration variables. type ConfigSpec struct { // Variables is a map from variable name to PropertySpec that describes a package's configuration variables. Variables map[string]PropertySpec `json:"variables,omitempty" yaml:"variables,omitempty"` // Required is a list of the names of the package's required configuration variables. Required []string `json:"defaults,omitempty" yaml:"defaults,omitempty"` } // MetadataSpec contains information for the importer about this package. type MetadataSpec struct { // ModuleFormat is a regex that is used by the importer to extract a module name from the module portion of a // type token. Packages that use the module format "namespace1/namespace2/.../namespaceN" do not need to specify // a format. The regex must define one capturing group that contains the module name, which must be formatted as // "namespace1/namespace2/...namespaceN". ModuleFormat string `json:"moduleFormat,omitempty" yaml:"moduleFormat,omitempty"` // SupportPack indicates whether or not the package is written to support the pack command. This causes versions to // be written out, plugin.json files to be filled in, and package metadata to be written to the directory. // This defaults to false currently, but conformance testing _always_ turns it on. SupportPack bool `json:"supportPack,omitempty" yaml:"supportPack,omitempty"` } // PackageInfoSpec is the serializable description of a Pulumi package's metadata. type PackageInfoSpec struct { // Name is the unqualified name of the package (e.g. "aws", "azure", "gcp", "kubernetes", "random") Name string `json:"name" yaml:"name"` // DisplayName is the human-friendly name of the package. DisplayName string `json:"displayName,omitempty" yaml:"displayName,omitempty"` // Version is the version of the package. The version must be valid semver. Version string `json:"version,omitempty" yaml:"version,omitempty"` // Description is the description of the package. Description string `json:"description,omitempty" yaml:"description,omitempty"` // Keywords is the list of keywords that are associated with the package, if any. // Some reserved keywords can be specified as well that help with categorizing the // package in the Pulumi registry. `category/<name>` and `kind/<type>` are the only // reserved keywords at this time, where `<name>` can be one of: // `cloud`, `database`, `infrastructure`, `monitoring`, `network`, `utility`, `vcs` // and `<type>` is either `native` or `component`. If the package is a bridged Terraform // provider, then don't include the `kind/` label. Keywords []string `json:"keywords,omitempty" yaml:"keywords,omitempty"` // Homepage is the package's homepage. Homepage string `json:"homepage,omitempty" yaml:"homepage,omitempty"` // License indicates which license is used for the package's contents. License string `json:"license,omitempty" yaml:"license,omitempty"` // Attribution allows freeform text attribution of derived work, if needed. Attribution string `json:"attribution,omitempty" yaml:"attribution,omitempty"` // Repository is the URL at which the source for the package can be found. Repository string `json:"repository,omitempty" yaml:"repository,omitempty"` // LogoURL is the URL for the package's logo, if any. LogoURL string `json:"logoUrl,omitempty" yaml:"logoUrl,omitempty"` // PluginDownloadURL is the URL to use to acquire the provider plugin binary, if any. PluginDownloadURL string `json:"pluginDownloadURL,omitempty" yaml:"pluginDownloadURL,omitempty"` // Publisher is the name of the person or organization that authored and published the package. Publisher string `json:"publisher,omitempty" yaml:"publisher,omitempty"` // Meta contains information for the importer about this package. Meta *MetadataSpec `json:"meta,omitempty" yaml:"meta,omitempty"` // A list of allowed package name in addition to the Name property. AllowedPackageNames []string `json:"allowedPackageNames,omitempty" yaml:"allowedPackageNames,omitempty"` // Language specifies additional language-specific data about the package. Language map[string]RawMessage `json:"language,omitempty" yaml:"language,omitempty"` } // BaseProviderSpec is the serializable description of a Pulumi base provider. type BaseProviderSpec struct { // The name of the base provider. Name string `json:"name" yaml:"name"` // The version of the base provider. Version string `json:"version" yaml:"version"` // The plugin download URL for the base provider. PluginDownloadURL string `json:"pluginDownloadURL,omitempty" yaml:"pluginDownloadURL,omitempty"` } // ParameterizationSpec is the serializable description of a provider parameterization. type ParameterizationSpec struct { // The base provider to parameterize. BaseProvider BaseProviderSpec `json:"baseProvider" yaml:"baseProvider"` // The parameter to apply to the base provider. Parameter []byte `json:"parameter" yaml:"parameter"` } // PackageSpec is the serializable description of a Pulumi package. type PackageSpec struct { // Name is the unqualified name of the package (e.g. "aws", "azure", "gcp", "kubernetes", "random") Name string `json:"name" yaml:"name"` // DisplayName is the human-friendly name of the package. DisplayName string `json:"displayName,omitempty" yaml:"displayName,omitempty"` // Version is the version of the package. The version must be valid semver. Version string `json:"version,omitempty" yaml:"version,omitempty"` // Description is the description of the package. Description string `json:"description,omitempty" yaml:"description,omitempty"` // Keywords is the list of keywords that are associated with the package, if any. // Some reserved keywords can be specified as well that help with categorizing the // package in the Pulumi registry. `category/<name>` and `kind/<type>` are the only // reserved keywords at this time, where `<name>` can be one of: // `cloud`, `database`, `infrastructure`, `monitoring`, `network`, `utility`, `vcs` // and `<type>` is either `native` or `component`. If the package is a bridged Terraform // provider, then don't include the `kind/` label. Keywords []string `json:"keywords,omitempty" yaml:"keywords,omitempty"` // Homepage is the package's homepage. Homepage string `json:"homepage,omitempty" yaml:"homepage,omitempty"` // License indicates which license is used for the package's contents. License string `json:"license,omitempty" yaml:"license,omitempty"` // Attribution allows freeform text attribution of derived work, if needed. Attribution string `json:"attribution,omitempty" yaml:"attribution,omitempty"` // Repository is the URL at which the source for the package can be found. Repository string `json:"repository,omitempty" yaml:"repository,omitempty"` // LogoURL is the URL for the package's logo, if any. LogoURL string `json:"logoUrl,omitempty" yaml:"logoUrl,omitempty"` // PluginDownloadURL is the URL to use to acquire the provider plugin binary, if any. PluginDownloadURL string `json:"pluginDownloadURL,omitempty" yaml:"pluginDownloadURL,omitempty"` // Publisher is the name of the person or organization that authored and published the package. Publisher string `json:"publisher,omitempty" yaml:"publisher,omitempty"` // Meta contains information for the importer about this package. Meta *MetadataSpec `json:"meta,omitempty" yaml:"meta,omitempty"` // A list of allowed package name in addition to the Name property. AllowedPackageNames []string `json:"allowedPackageNames,omitempty" yaml:"allowedPackageNames,omitempty"` // Language specifies additional language-specific data about the package. Language map[string]RawMessage `json:"language,omitempty" yaml:"language,omitempty"` // Config describes the set of configuration variables defined by this package. Config ConfigSpec `json:"config,omitempty" yaml:"config"` // Types is a map from type token to ComplexTypeSpec that describes the set of complex types (ie. object, enum) // defined by this package. Types map[string]ComplexTypeSpec `json:"types,omitempty" yaml:"types,omitempty"` // Provider describes the provider type for this package. Provider ResourceSpec `json:"provider,omitempty" yaml:"provider"` // Resources is a map from type token to ResourceSpec that describes the set of resources defined by this package. Resources map[string]ResourceSpec `json:"resources,omitempty" yaml:"resources,omitempty"` // Functions is a map from token to FunctionSpec that describes the set of functions defined by this package. Functions map[string]FunctionSpec `json:"functions,omitempty" yaml:"functions,omitempty"` // Parameterization is the optional parameterization for this package. Parameterization *ParameterizationSpec `json:"parameterization,omitempty" yaml:"parameterization,omitempty"` } func (p *PackageSpec) Info() PackageInfoSpec { return PackageInfoSpec{ Name: p.Name, DisplayName: p.DisplayName, Version: p.Version, Description: p.Description, Keywords: p.Keywords, Homepage: p.Homepage, License: p.License, Attribution: p.Attribution, Repository: p.Repository, LogoURL: p.LogoURL, PluginDownloadURL: p.PluginDownloadURL, Publisher: p.Publisher, Meta: p.Meta, AllowedPackageNames: p.AllowedPackageNames, Language: p.Language, } } // PartialPackageSpec is a serializable description of a Pulumi package that defers the deserialization of most package // members until they are needed. Used to support PartialPackage and PackageReferences. type PartialPackageSpec struct { PackageInfoSpec `yaml:",inline"` // Config describes the set of configuration variables defined by this package. Config json.RawMessage `json:"config" yaml:"config"` // Types is a map from type token to ComplexTypeSpec that describes the set of complex types (ie. object, enum) // defined by this package. Types map[string]json.RawMessage `json:"types,omitempty" yaml:"types,omitempty"` // Provider describes the provider type for this package. Provider json.RawMessage `json:"provider" yaml:"provider"` // Resources is a map from type token to ResourceSpec that describes the set of resources defined by this package. Resources map[string]json.RawMessage `json:"resources,omitempty" yaml:"resources,omitempty"` // Functions is a map from token to FunctionSpec that describes the set of functions defined by this package. Functions map[string]json.RawMessage `json:"functions,omitempty" yaml:"functions,omitempty"` }