// Copyright 2016-2020, 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 ( "encoding/json" "fmt" "math" "net/url" "os" "path" "regexp" "sort" "strings" "github.com/blang/semver" "github.com/pkg/errors" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" ) // 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 ) //nolint: goconst 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 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 ) // 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 { // Package is the type's package. Package *Package // 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 } // 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)+1) for i, e := range t.ElementTypes { elements[i] = e.String() } def := "default=" if t.DefaultType != nil { def += t.DefaultType.String() } elements[len(elements)-1] = def return fmt.Sprintf("Union<%v>", strings.Join(elements, ", ")) } func (*UnionType) isType() {} // ObjectType represents schematized maps from strings to particular types. type ObjectType struct { // Package is the package that defines the resource. Package *Package // 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{} // 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 plain 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 } // 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 { // Package is the package that defines the resource. Package *Package // 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 } 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 { // Package is the package that defines the function. Package *Package // 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 // Outputs is the bag of output values for the function, if any. Outputs *ObjectType // 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 } // Package describes a Pulumi package. type Package struct { moduleFormat *regexp.Regexp // Name is the unqualified name of the package (e.g. "aws", "azure", "gcp", "kubernetes". "random") Name 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. 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 // 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{} resourceTable map[string]*Resource resourceTypeTable map[string]*ResourceType functionTable map[string]*Function typeTable map[string]Type } // 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 := make([]string, 0, 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 errors.Wrapf(err, "importing %v metadata", name) } 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 errors.Wrapf(err, "importing default value") } } 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 errors.Wrapf(err, "importing %v metadata", name) } 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 errors.Wrapf(err, "importing property %v", property.Name) } } 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 errors.Wrapf(err, "importing %v metadata", name) } 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 errors.Wrapf(err, "importing input property %v", property.Name) } } for _, property := range resource.Properties { if err := importPropertyLanguages(property, languages); err != nil { return errors.Wrapf(err, "importing property %v", property.Name) } } if resource.StateInputs != nil { for _, property := range resource.StateInputs.Properties { if err := importPropertyLanguages(property, languages); err != nil { return errors.Wrapf(err, "importing state input property %v", property.Name) } } } 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 errors.Wrapf(err, "importing %v metadata", name) } 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 errors.Wrapf(err, "importing inputs") } } if function.Outputs != nil { if err := importObjectTypeLanguages(function.Outputs, languages); err != nil { return errors.Wrapf(err, "importing outputs") } } 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 errors.Wrapf(err, "importing %v metadata", name) } function.Language[name] = val } } } return nil } func (pkg *Package) ImportLanguages(languages map[string]Language) error { if len(languages) == 0 { return nil } for _, t := range pkg.Types { if object, ok := t.(*ObjectType); ok { if err := importObjectTypeLanguages(object, languages); err != nil { return errors.Wrapf(err, "importing object type %v", object.Token) } } } for _, config := range pkg.Config { if err := importPropertyLanguages(config, languages); err != nil { return errors.Wrapf(err, "importing configuration property %v", config.Name) } } if pkg.Provider != nil { if err := importResourceLanguages(pkg.Provider, languages); err != nil { return errors.Wrapf(err, "importing provider") } } for _, resource := range pkg.Resources { if err := importResourceLanguages(resource, languages); err != nil { return errors.Wrapf(err, "importing resource %v", resource.Token) } } for _, function := range pkg.Functions { if err := importFunctionLanguages(function, languages); err != nil { return errors.Wrapf(err, "importing function %v", function.Token) } } 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 errors.Wrapf(err, "importing %v metadata", name) } pkg.Language[name] = val } } } return nil } 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: matches := pkg.moduleFormat.FindStringSubmatch(components[1]) if len(matches) < 2 || strings.HasPrefix(matches[1], "index") { return "" } return matches[1] } } func (pkg *Package) TokenToRuntimeModule(tok string) string { // token := pkg ":" module ":" member components := strings.Split(tok, ":") if len(components) != 3 { return "" } return components[1] } 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 } // 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 "bool", "integer", "number", "string", "array", or // "object". Type string `json:"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"` // AdditionalProperties, if set, describes the element type of an "object" (i.e. a string -> value map). AdditionalProperties *TypeSpec `json:"additionalProperties,omitempty"` // Items, if set, describes the element type of an array. Items *TypeSpec `json:"items,omitempty"` // OneOf indicates that values of the type may be one of any of the listed types. OneOf []TypeSpec `json:"oneOf,omitempty"` // Discriminator informs the consumer of an alternative schema based on the value associated with it. Discriminator *DiscriminatorSpec `json:"discriminator,omitempty"` // Plain indicates that when used as an input, this type does not accept eventual values. Plain bool `json:"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"` // Mapping is an optional object to hold mappings between payload values and schema names or references. Mapping map[string]string `json:"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"` // Language specifies additional language-specific data about the default value. Language map[string]json.RawMessage `json:"language,omitempty"` } // PropertySpec is the serializable form of an object or resource property. type PropertySpec struct { TypeSpec // Description is the description of the property, if any. Description string `json:"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"` // 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"` // DefaultInfo contains additional information about the property's default value, if any. DefaultInfo *DefaultSpec `json:"defaultInfo,omitempty"` // DeprecationMessage indicates whether or not the property is deprecated. DeprecationMessage string `json:"deprecationMessage,omitempty"` // Language specifies additional language-specific data about the property. Language map[string]json.RawMessage `json:"language,omitempty"` // Secret specifies if the property is secret (default false). Secret bool `json:"secret,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"` // Properties, if present, is a map from property name to PropertySpec that describes the type's properties. Properties map[string]PropertySpec `json:"properties,omitempty"` // Type must be "object" if this is an object type, or the underlying type for an enum. Type string `json:"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"` // 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"` // Language specifies additional language-specific data about the type. Language map[string]json.RawMessage `json:"language,omitempty"` } // ComplexTypeSpec is the serializable form of an object or enum type. type ComplexTypeSpec struct { ObjectTypeSpec // Enum, if present, is the list of possible values for an enum type. Enum []*EnumValueSpec `json:"enum,omitempty"` } // EnumValuesSpec 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"` // Description of the enum value. Description string `json:"description,omitempty"` // Value is the enum value itself. Value interface{} `json:"value"` // DeprecationMessage indicates whether or not the value is deprecated. DeprecationMessage string `json:"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"` // Project is the project portion of the alias, if any. Project *string `json:"project,omitempty"` // Type is the type portion of the alias, if any. Type *string `json:"type,omitempty"` } // ResourceSpec is the serializable form of a resource description. type ResourceSpec struct { ObjectTypeSpec // InputProperties is a map from property name to PropertySpec that describes the resource's input properties. InputProperties map[string]PropertySpec `json:"inputProperties,omitempty"` // RequiredInputs is a list of the names of the resource's required input properties. RequiredInputs []string `json:"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"` // 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"` // Aliases is the list of aliases for the resource. Aliases []AliasSpec `json:"aliases,omitempty"` // DeprecationMessage indicates whether or not the resource is deprecated. DeprecationMessage string `json:"deprecationMessage,omitempty"` // Language specifies additional language-specific data about the resource. Language map[string]json.RawMessage `json:"language,omitempty"` // IsComponent indicates whether the resource is a ComponentResource. IsComponent bool `json:"isComponent,omitempty"` // Methods maps method names to functions in this schema. Methods map[string]string `json:"methods,omitempty"` } // 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"` // Inputs is the bag of input values for the function, if any. Inputs *ObjectTypeSpec `json:"inputs,omitempty"` // Outputs is the bag of output values for the function, if any. Outputs *ObjectTypeSpec `json:"outputs,omitempty"` // DeprecationMessage indicates whether or not the function is deprecated. DeprecationMessage string `json:"deprecationMessage,omitempty"` // Language specifies additional language-specific data about the function. Language map[string]json.RawMessage `json:"language,omitempty"` } // 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"` // Required is a list of the names of the package's required configuration variables. Required []string `json:"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"` } // 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"` // Version is the version of the package. The version must be valid semver. Version string `json:"version,omitempty"` // Description is the description of the package. Description string `json:"description,omitempty"` // Keywords is the list of keywords that are associated with the package, if any. Keywords []string `json:"keywords,omitempty"` // Homepage is the package's homepage. Homepage string `json:"homepage,omitempty"` // License indicates which license is used for the package's contents. License string `json:"license,omitempty"` // Attribution allows freeform text attribution of derived work, if needed. Attribution string `json:"attribution,omitempty"` // Repository is the URL at which the source for the package can be found. Repository string `json:"repository,omitempty"` // LogoURL is the URL for the package's logo, if any. LogoURL string `json:"logoUrl,omitempty"` // PluginDownloadURL is the URL to use to acquire the provider plugin binary, if any. PluginDownloadURL string `json:"pluginDownloadURL,omitempty"` // Meta contains information for the importer about this package. Meta *MetadataSpec `json:"meta,omitempty"` // Config describes the set of configuration variables defined by this package. Config ConfigSpec `json:"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"` // Provider describes the provider type for this package. Provider ResourceSpec `json:"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"` // 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"` // Language specifies additional language-specific data about the package. Language map[string]json.RawMessage `json:"language,omitempty"` } // importSpec converts a serializable PackageSpec into a Package. This function includes a loader parameter which // works as a singleton -- if it is nil, a new loader is instantiated, else the provided loader is used. This avoids // breaking downstream consumers of ImportSpec while allowing us to extend schema support to external packages. func importSpec(spec PackageSpec, languages map[string]Language, loader Loader) (*Package, error) { // Parse the version, if any. var version *semver.Version if spec.Version != "" { v, err := semver.ParseTolerant(spec.Version) if err != nil { return nil, errors.Wrap(err, "parsing package version") } version = &v } // Parse the module format, if any. moduleFormat := "(.*)" if spec.Meta != nil && spec.Meta.ModuleFormat != "" { moduleFormat = spec.Meta.ModuleFormat } moduleFormatRegexp, err := regexp.Compile(moduleFormat) if err != nil { return nil, errors.Wrap(err, "compiling module format regexp") } pkg := &Package{} // We want to use the same loader instance for all referenced packages, so only instantiate the loader if the // reference is nil. if loader == nil { cwd, err := os.Getwd() if err != nil { return nil, err } ctx, err := plugin.NewContext(nil, nil, nil, nil, cwd, nil, false, nil) if err != nil { return nil, err } defer contract.IgnoreClose(ctx) loader = NewPluginLoader(ctx.Host) } types, err := bindTypes(pkg, spec.Types, loader) if err != nil { return nil, errors.Wrap(err, "binding types") } config, err := bindConfig(spec.Config, types) if err != nil { return nil, errors.Wrap(err, "binding config") } functions, functionTable, err := bindFunctions(spec.Functions, types) if err != nil { return nil, errors.Wrap(err, "binding functions") } provider, err := bindProvider(spec.Name, spec.Provider, types, functionTable) if err != nil { return nil, errors.Wrap(err, "binding provider") } resources, resourceTable, err := bindResources(spec.Resources, types, functionTable) if err != nil { return nil, errors.Wrap(err, "binding resources") } // Build the type list. var typeList []Type for _, t := range types.resources { typeList = append(typeList, t) } for _, t := range types.objects { // t is a plain shape: add it and its coresponding input shape to the type list. typeList = append(typeList, t) typeList = append(typeList, t.InputShape) } for _, t := range types.arrays { typeList = append(typeList, t) } for _, t := range types.maps { typeList = append(typeList, t) } for _, t := range types.unions { typeList = append(typeList, t) } for _, t := range types.tokens { typeList = append(typeList, t) } for _, t := range types.enums { typeList = append(typeList, t) } sort.Slice(typeList, func(i, j int) bool { return typeList[i].String() < typeList[j].String() }) language := make(map[string]interface{}) for name, raw := range spec.Language { language[name] = raw } *pkg = Package{ moduleFormat: moduleFormatRegexp, Name: spec.Name, Version: version, Description: spec.Description, Keywords: spec.Keywords, Homepage: spec.Homepage, License: spec.License, Attribution: spec.Attribution, Repository: spec.Repository, PluginDownloadURL: spec.PluginDownloadURL, Config: config, Types: typeList, Provider: provider, Resources: resources, Functions: functions, Language: language, resourceTable: resourceTable, functionTable: functionTable, typeTable: types.named, resourceTypeTable: types.resources, } if err := pkg.ImportLanguages(languages); err != nil { return nil, err } return pkg, nil } // ImportSpec converts a serializable PackageSpec into a Package. func ImportSpec(spec PackageSpec, languages map[string]Language) (*Package, error) { // Call the internal implementation that includes a loader parameter. return importSpec(spec, languages, nil) } // types facilitates interning (only storing a single reference to an object) during schema processing. The fields // correspond to fields in the schema, and are populated during the binding process. type types struct { pkg *Package loader Loader resources map[string]*ResourceType objects map[string]*ObjectType arrays map[Type]*ArrayType maps map[Type]*MapType unions map[string]*UnionType tokens map[string]*TokenType enums map[string]*EnumType named map[string]Type // objects and enums inputs map[Type]*InputType optionals map[Type]*OptionalType } func (t *types) bindPrimitiveType(name string) (Type, error) { switch name { case "boolean": return BoolType, nil case "integer": return IntType, nil case "number": return NumberType, nil case "string": return StringType, nil default: return nil, errors.Errorf("unknown primitive type %v", name) } } // typeSpecRef contains the parsed fields from a type spec reference. type typeSpecRef struct { URL *url.URL // The parsed URL Package string // The package component of the schema ref Version *semver.Version // The version component of the schema ref Kind string // The kind of reference: 'resources', 'types', or 'provider' Token string // The type token } const ( resourcesRef = "resources" typesRef = "types" providerRef = "provider" ) // Regex used to parse external schema paths. This is declared at the package scope to avoid repeated recompilation. var refPathRegex = regexp.MustCompile(`^/?(?P\w+)/(?Pv[^/]*)/schema\.json$`) func (t *types) parseTypeSpecRef(ref string) (typeSpecRef, error) { parsedURL, err := url.Parse(ref) if err != nil { return typeSpecRef{}, errors.Wrapf(err, "failed to parse ref URL: %s", ref) } // Parse the package name and version if the URL contains a path. If there is no path--if the URL is just a // fragment--then the reference refers to the package being bound. pkgName, pkgVersion := t.pkg.Name, t.pkg.Version if len(parsedURL.Path) > 0 { path, err := url.PathUnescape(parsedURL.Path) if err != nil { return typeSpecRef{}, errors.Wrapf(err, "failed to unescape path: %s", parsedURL.Path) } pathMatch := refPathRegex.FindStringSubmatch(path) if len(pathMatch) != 3 { return typeSpecRef{}, fmt.Errorf("failed to parse path: %s", path) } pkg, versionToken := pathMatch[1], pathMatch[2] version, err := semver.ParseTolerant(versionToken) if err != nil { return typeSpecRef{}, errors.Wrapf(err, "failed to parse package version: %s", versionToken) } pkgName, pkgVersion = pkg, &version } // Parse the fragment into a reference kind and token. The fragment is in one of two forms: // 1. #/provider // 2. #/(resources|types)/some:type:token // // Unfortunately, early code generators were lax and emitted unescaped backslashes in the type token, so we can't // just split on "/". fragment := path.Clean(parsedURL.EscapedFragment()) if path.IsAbs(fragment) { fragment = fragment[1:] } kind, token := "", "" slash := strings.Index(fragment, "/") if slash == -1 { kind = fragment } else { kind, token = fragment[:slash], fragment[slash+1:] } switch kind { case "provider": if token != "" { return typeSpecRef{}, fmt.Errorf("invalid provider reference '%v'", ref) } token = "pulumi:providers:" + pkgName case "resources", "types": token, err = url.PathUnescape(token) if err != nil { return typeSpecRef{}, errors.Wrapf(err, "failed to unescape token: %s", token) } default: return typeSpecRef{}, fmt.Errorf("invalid type reference '%v'", ref) } return typeSpecRef{ URL: parsedURL, Package: pkgName, Version: pkgVersion, Kind: kind, Token: token, }, nil } func versionEquals(a, b *semver.Version) bool { // We treat "nil" as "unconstrained". if a == nil || b == nil { return true } return a.Equals(*b) } func (t *types) newInputType(elementType Type) Type { if _, ok := elementType.(*InputType); ok { return elementType } typ, ok := t.inputs[elementType] if !ok { typ = &InputType{ElementType: elementType} t.inputs[elementType] = typ } return typ } func (t *types) newOptionalType(elementType Type) Type { if _, ok := elementType.(*OptionalType); ok { return elementType } typ, ok := t.optionals[elementType] if !ok { typ = &OptionalType{ElementType: elementType} t.optionals[elementType] = typ } return typ } func (t *types) newMapType(elementType Type) Type { typ, ok := t.maps[elementType] if !ok { typ = &MapType{ElementType: elementType} t.maps[elementType] = typ } return typ } func (t *types) newArrayType(elementType Type) Type { typ, ok := t.arrays[elementType] if !ok { typ = &ArrayType{ElementType: elementType} t.arrays[elementType] = typ } return typ } func (t *types) newUnionType( elements []Type, defaultType Type, discriminator string, mapping map[string]string) *UnionType { union := &UnionType{ ElementTypes: elements, DefaultType: defaultType, Discriminator: discriminator, Mapping: mapping, } if typ, ok := t.unions[union.String()]; ok { return typ } t.unions[union.String()] = union return union } func (t *types) bindTypeSpecRef(spec TypeSpec, inputShape bool) (Type, error) { // Explicitly handle built-in types so that we don't have to handle this type of path during ref parsing. switch spec.Ref { case "pulumi.json#/Archive": return ArchiveType, nil case "pulumi.json#/Asset": return AssetType, nil case "pulumi.json#/Json": return JSONType, nil case "pulumi.json#/Any": return AnyType, nil } ref, err := t.parseTypeSpecRef(spec.Ref) if err != nil { return nil, err } // If this is a reference to an external sch referencesExternalSchema := ref.Package != t.pkg.Name || !versionEquals(ref.Version, t.pkg.Version) if referencesExternalSchema { pkg, err := t.loader.LoadPackage(ref.Package, ref.Version) if err != nil { return nil, errors.Wrapf(err, "resolving package %v", ref.URL) } switch ref.Kind { case typesRef: typ, ok := pkg.GetType(ref.Token) if !ok { return nil, fmt.Errorf("type %v not found in package %v", ref.Token, ref.Package) } if obj, ok := typ.(*ObjectType); ok && inputShape { typ = obj.InputShape } return typ, nil case resourcesRef, providerRef: typ, ok := pkg.GetResourceType(ref.Token) if !ok { return nil, fmt.Errorf("resource type %v not found in package %v", ref.Token, ref.Package) } return typ, nil } } switch ref.Kind { case typesRef: if typ, ok := t.objects[ref.Token]; ok { if inputShape { return typ.InputShape, nil } return typ, nil } if typ, ok := t.enums[ref.Token]; ok { return typ, nil } typ, ok := t.tokens[ref.Token] if !ok { typ = &TokenType{Token: ref.Token} if spec.Type != "" { ut, err := t.bindPrimitiveType(spec.Type) if err != nil { return nil, err } typ.UnderlyingType = ut } t.tokens[ref.Token] = typ } return typ, nil case resourcesRef, providerRef: typ, ok := t.resources[ref.Token] if !ok { typ = &ResourceType{Token: ref.Token} t.resources[ref.Token] = typ } return typ, nil default: return nil, errors.Errorf("failed to parse ref %s", spec.Ref) } } func (t *types) bindType(spec TypeSpec, inputShape bool) (result Type, err error) { if inputShape && !spec.Plain { defer func() { result = t.newInputType(result) }() } if spec.Ref != "" { return t.bindTypeSpecRef(spec, inputShape) } if spec.OneOf != nil { if len(spec.OneOf) < 2 { return nil, errors.New("oneOf should list at least two types") } var defaultType Type if spec.Type != "" { dt, err := t.bindPrimitiveType(spec.Type) if err != nil { return nil, err } defaultType = dt } elements := make([]Type, len(spec.OneOf)) for i, spec := range spec.OneOf { e, err := t.bindType(spec, inputShape) if err != nil { return nil, err } elements[i] = e } var discriminator string var mapping map[string]string if spec.Discriminator != nil { discriminator = spec.Discriminator.PropertyName mapping = spec.Discriminator.Mapping } return t.newUnionType(elements, defaultType, discriminator, mapping), nil } // nolint: goconst switch spec.Type { case "boolean", "integer", "number", "string": return t.bindPrimitiveType(spec.Type) case "array": if spec.Items == nil { return nil, errors.Errorf("missing \"items\" property in type spec") } elementType, err := t.bindType(*spec.Items, inputShape) if err != nil { return nil, err } return t.newArrayType(elementType), nil case "object": elementType := StringType if spec.AdditionalProperties != nil { et, err := t.bindType(*spec.AdditionalProperties, inputShape) if err != nil { return nil, err } elementType = et } return t.newMapType(elementType), nil default: return nil, errors.Errorf("unknown type kind %v", spec.Type) } } func plainType(typ Type) Type { for { switch t := typ.(type) { case *InputType: typ = t.ElementType case *OptionalType: typ = t.ElementType case *ObjectType: if t.PlainShape == nil { return t } typ = t.PlainShape default: return t } } } func bindConstValue(value interface{}, typ Type) (interface{}, error) { if value == nil { return nil, nil } switch plainType(typ) { case BoolType: if _, ok := value.(bool); !ok { return nil, errors.Errorf("invalid constant of type %T for boolean property", value) } case IntType: v, ok := value.(float64) if !ok { return nil, errors.Errorf("invalid constant of type %T for integer property", value) } if math.Trunc(v) != v || v < math.MinInt32 || v > math.MaxInt32 { return nil, errors.Errorf("invalid constant of type number for integer property") } value = int32(v) case NumberType: if _, ok := value.(float64); !ok { return nil, errors.Errorf("invalid constant of type %T for number property", value) } case StringType: if _, ok := value.(string); !ok { return nil, errors.Errorf("invalid constant of type %T for string property", value) } default: return nil, errors.Errorf("constant values may only be provided for boolean, integer, number, and string properties") } return value, nil } func bindDefaultValue(value interface{}, spec *DefaultSpec, typ Type) (*DefaultValue, error) { if value == nil && spec == nil { return nil, nil } if value != nil { typ = plainType(typ) switch typ := typ.(type) { case *UnionType: if typ.DefaultType != nil { return bindDefaultValue(value, spec, typ.DefaultType) } for _, elementType := range typ.ElementTypes { v, err := bindDefaultValue(value, spec, elementType) if err == nil { return v, nil } } case *EnumType: return bindDefaultValue(value, spec, typ.ElementType) } switch typ { case BoolType: if _, ok := value.(bool); !ok { return nil, errors.Errorf("invalid default of type %T for boolean property", value) } case IntType: v, ok := value.(float64) if !ok { return nil, errors.Errorf("invalid default of type %T for integer property", value) } if math.Trunc(v) != v || v < math.MinInt32 || v > math.MaxInt32 { return nil, errors.Errorf("invalid default of type number for integer property") } value = int32(v) case NumberType: if _, ok := value.(float64); !ok { return nil, errors.Errorf("invalid default of type %T for number property", value) } case StringType: if _, ok := value.(string); !ok { return nil, errors.Errorf("invalid default of type %T for string property", value) } default: return nil, errors.Errorf("default values may only be provided for boolean, integer, number, and string properties") } } dv := &DefaultValue{Value: value} if spec != nil { language := make(map[string]interface{}) for name, raw := range spec.Language { language[name] = raw } dv.Environment, dv.Language = spec.Environment, language } return dv, nil } // bindProperties binds the map of property specs and list of required properties into a sorted list of properties and // a lookup table. func (t *types) bindProperties(properties map[string]PropertySpec, required []string, inputShape bool) ([]*Property, map[string]*Property, error) { // Bind property types and constant or default values. propertyMap := map[string]*Property{} var result []*Property for name, spec := range properties { typ, err := t.bindType(spec.TypeSpec, inputShape) if err != nil { return nil, nil, errors.Wrapf(err, "error binding type for property %q", name) } cv, err := bindConstValue(spec.Const, typ) if err != nil { return nil, nil, errors.Wrapf(err, "error binding constant value for property %q", name) } dv, err := bindDefaultValue(spec.Default, spec.DefaultInfo, typ) if err != nil { return nil, nil, errors.Wrapf(err, "error binding default value for property %q", name) } language := make(map[string]interface{}) for name, raw := range spec.Language { language[name] = raw } p := &Property{ Name: name, Comment: spec.Description, Type: t.newOptionalType(typ), ConstValue: cv, DefaultValue: dv, DeprecationMessage: spec.DeprecationMessage, Language: language, Secret: spec.Secret, } propertyMap[name], result = p, append(result, p) } // Compute required properties. for _, name := range required { p, ok := propertyMap[name] if !ok { return nil, nil, errors.Errorf("unknown required property %q", name) } p.Type = p.Type.(*OptionalType).ElementType } sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name }) return result, propertyMap, nil } func (t *types) bindObjectTypeDetails(obj *ObjectType, token string, spec ObjectTypeSpec) error { if len(spec.Plain) > 0 { return errors.New("plain has been removed; the property type must be marked as plain instead") } properties, propertyMap, err := t.bindProperties(spec.Properties, spec.Required, false) if err != nil { return err } inputProperties, inputPropertyMap, err := t.bindProperties(spec.Properties, spec.Required, true) if err != nil { return err } language := make(map[string]interface{}) for name, raw := range spec.Language { language[name] = raw } obj.Package = t.pkg obj.Token = token obj.Comment = spec.Description obj.Language = language obj.Properties = properties obj.properties = propertyMap obj.InputShape.Package = t.pkg obj.InputShape.Token = token obj.InputShape.Comment = spec.Description obj.InputShape.Language = language obj.InputShape.Properties = inputProperties obj.InputShape.properties = inputPropertyMap return nil } func (t *types) bindObjectType(token string, spec ObjectTypeSpec) (*ObjectType, error) { obj := &ObjectType{} obj.InputShape = &ObjectType{PlainShape: obj} if err := t.bindObjectTypeDetails(obj, token, spec); err != nil { return nil, err } return obj, nil } func (t *types) bindResourceTypeDetails(obj *ResourceType, token string) error { obj.Token = token return nil } func (t *types) bindResourceType(token string) (*ResourceType, error) { r := &ResourceType{} if err := t.bindResourceTypeDetails(r, token); err != nil { return nil, err } return r, nil } func (t *types) bindEnumTypeDetails(enum *EnumType, token string, spec ComplexTypeSpec) error { typ, err := t.bindPrimitiveType(spec.Type) if err != nil { return err } values, err := t.bindEnumValues(spec.Enum, typ) if err != nil { return err } enum.Package = t.pkg enum.Token = token enum.Elements = values enum.ElementType = typ enum.Comment = spec.Description return nil } func (t *types) bindEnumValues(values []*EnumValueSpec, typ Type) ([]*Enum, error) { var enums []*Enum errorMessage := func(val interface{}, expectedType string) error { return fmt.Errorf("cannot assign enum value of type '%T' to enum of type '%s'", val, expectedType) } for _, spec := range values { switch typ { case StringType: if _, ok := spec.Value.(string); !ok { return nil, errorMessage(spec.Value, typ.String()) } case IntType: v, ok := spec.Value.(float64) if !ok { return nil, errorMessage(spec.Value, typ.String()) } if math.Trunc(v) != v || v < math.MinInt32 || v > math.MaxInt32 { return nil, errors.Errorf("cannot assign enum value of type 'number' to enum of type 'integer'") } spec.Value = int32(v) case NumberType: if _, ok := spec.Value.(float64); !ok { return nil, errorMessage(spec.Value, typ.String()) } case BoolType: if _, ok := spec.Value.(bool); !ok { return nil, errorMessage(spec.Value, typ.String()) } default: return nil, fmt.Errorf("enum values may only be of string, integer, number or boolean types") } enum := &Enum{ Value: spec.Value, Comment: spec.Description, Name: spec.Name, DeprecationMessage: spec.DeprecationMessage, } enums = append(enums, enum) } return enums, nil } func (t *types) bindEnumType(token string, spec ComplexTypeSpec) (*EnumType, error) { enum := &EnumType{} if err := t.bindEnumTypeDetails(enum, token, spec); err != nil { return nil, err } return enum, nil } func bindTypes(pkg *Package, complexTypes map[string]ComplexTypeSpec, loader Loader) (*types, error) { typs := &types{ pkg: pkg, loader: loader, resources: map[string]*ResourceType{}, objects: map[string]*ObjectType{}, arrays: map[Type]*ArrayType{}, maps: map[Type]*MapType{}, unions: map[string]*UnionType{}, tokens: map[string]*TokenType{}, enums: map[string]*EnumType{}, named: map[string]Type{}, inputs: map[Type]*InputType{}, optionals: map[Type]*OptionalType{}, } // Declare object and enum types before processing properties. for token, spec := range complexTypes { if spec.Type == "object" { // It's important that we set the token here. This package interns types so that they can be equality-compared // for identity. Types are interned based on their string representation, and the string representation of an // object type is its token. While this doesn't affect object types directly, it breaks the interning of types // that reference object types (e.g. arrays, maps, unions) typ := &ObjectType{Token: token} typ.InputShape = &ObjectType{Token: token, PlainShape: typ} typs.objects[token] = typ typs.named[token] = typ } else if len(spec.Enum) > 0 { typ := &EnumType{Token: token} typs.enums[token] = typ typs.named[token] = typ // Bind enums before object types because object type generation depends on enum values to be present. if err := typs.bindEnumTypeDetails(typs.enums[token], token, spec); err != nil { return nil, errors.Wrapf(err, "failed to bind type %s", token) } } } // Process resources. for _, r := range pkg.Resources { typs.resources[r.Token] = &ResourceType{Token: r.Token} } // Process object types. for token, spec := range complexTypes { if spec.Type == "object" { if err := typs.bindObjectTypeDetails( typs.objects[token], token, spec.ObjectTypeSpec); err != nil { return nil, errors.Wrapf(err, "failed to bind type %s", token) } } } return typs, nil } func bindMethods(resourceToken string, methods map[string]string, functionTable map[string]*Function) ([]*Method, error) { names := make([]string, 0, len(methods)) for name := range methods { names = append(names, name) } sort.Strings(names) result := make([]*Method, 0, len(methods)) for _, name := range names { token := methods[name] function, ok := functionTable[token] if !ok { return nil, errors.Errorf("unknown function %s for method %s", token, name) } if function.IsMethod { return nil, errors.Errorf("function %s for method %s is already a method", token, name) } idx := strings.LastIndex(function.Token, "/") if idx == -1 || function.Token[:idx] != resourceToken { return nil, errors.Errorf("invalid function token format %s for method %s", token, name) } if function.Inputs == nil || function.Inputs.Properties == nil || len(function.Inputs.Properties) == 0 || function.Inputs.Properties[0].Name != "__self__" { return nil, errors.Errorf("function %s for method %s is missing __self__ parameter", token, name) } function.IsMethod = true result = append(result, &Method{ Name: name, Function: function, }) } return result, nil } func bindConfig(spec ConfigSpec, types *types) ([]*Property, error) { properties, _, err := types.bindProperties(spec.Variables, spec.Required, false) return properties, err } func bindResource(token string, spec ResourceSpec, types *types, functionTable map[string]*Function) (*Resource, error) { if len(spec.Plain) > 0 { return nil, errors.New("plain has been removed; property types must be marked as plain instead") } if len(spec.PlainInputs) > 0 { return nil, errors.New("plainInputs has been removed; individual property types must be marked as plain instead") } properties, _, err := types.bindProperties(spec.Properties, spec.Required, false) if err != nil { return nil, errors.Wrap(err, "failed to bind properties") } inputProperties, _, err := types.bindProperties(spec.InputProperties, spec.RequiredInputs, true) if err != nil { return nil, errors.Wrap(err, "failed to bind properties") } methods, err := bindMethods(token, spec.Methods, functionTable) if err != nil { return nil, errors.Wrap(err, "failed to bind methods") } for _, method := range methods { if _, ok := spec.Properties[method.Name]; ok { return nil, errors.Errorf("property and method have the same name %s", method.Name) } } var stateInputs *ObjectType if spec.StateInputs != nil { si, err := types.bindObjectType(token+"Args", *spec.StateInputs) if err != nil { return nil, errors.Wrap(err, "error binding inputs") } stateInputs = si.InputShape } var aliases []*Alias for _, a := range spec.Aliases { aliases = append(aliases, &Alias{Name: a.Name, Project: a.Project, Type: a.Type}) } language := make(map[string]interface{}) for name, raw := range spec.Language { language[name] = raw } return &Resource{ Package: types.pkg, Token: token, Comment: spec.Description, InputProperties: inputProperties, Properties: properties, StateInputs: stateInputs, Aliases: aliases, DeprecationMessage: spec.DeprecationMessage, Language: language, IsComponent: spec.IsComponent, Methods: methods, }, nil } func bindProvider(pkgName string, spec ResourceSpec, types *types, functionTable map[string]*Function) (*Resource, error) { res, err := bindResource("pulumi:providers:"+pkgName, spec, types, functionTable) if err != nil { return nil, errors.Wrap(err, "error binding provider") } res.IsProvider = true // Since non-primitive provider configuration is currently JSON serialized, we can't handle it without // modifying the path by which it's looked up. As a temporary workaround to enable access to config which // values which are primitives, we'll simply remove any properties for the provider resource which are not // strings, or types with an underlying type of string, before we generate the provider code. var stringProperties []*Property for _, prop := range res.Properties { typ := plainType(prop.Type) if tokenType, isTokenType := typ.(*TokenType); isTokenType { if tokenType.UnderlyingType != stringType { continue } } else { if typ != stringType { continue } } stringProperties = append(stringProperties, prop) } res.Properties = stringProperties types.resources[res.Token] = &ResourceType{ Token: res.Token, Resource: res, } return res, nil } func bindResources(specs map[string]ResourceSpec, types *types, functionTable map[string]*Function) ([]*Resource, map[string]*Resource, error) { resourceTable := map[string]*Resource{} var resources []*Resource for token, spec := range specs { res, err := bindResource(token, spec, types, functionTable) if err != nil { return nil, nil, errors.Wrapf(err, "error binding resource %v", token) } resourceTable[token] = res if rt, ok := types.resources[token]; ok { if rt.Resource == nil { rt.Resource = res } } else { types.resources[token] = &ResourceType{ Token: res.Token, Resource: res, } } resources = append(resources, res) } sort.Slice(resources, func(i, j int) bool { return resources[i].Token < resources[j].Token }) return resources, resourceTable, nil } func bindFunction(token string, spec FunctionSpec, types *types) (*Function, error) { var inputs *ObjectType if spec.Inputs != nil { ins, err := types.bindObjectType(token+"Args", *spec.Inputs) if err != nil { return nil, errors.Wrap(err, "error binding inputs") } inputs = ins } var outputs *ObjectType if spec.Outputs != nil { outs, err := types.bindObjectType(token+"Result", *spec.Outputs) if err != nil { return nil, errors.Wrap(err, "error binding inputs") } outputs = outs } language := make(map[string]interface{}) for name, raw := range spec.Language { language[name] = raw } return &Function{ Package: types.pkg, Token: token, Comment: spec.Description, Inputs: inputs, Outputs: outputs, DeprecationMessage: spec.DeprecationMessage, Language: language, }, nil } func bindFunctions(specs map[string]FunctionSpec, types *types) ([]*Function, map[string]*Function, error) { functionTable := map[string]*Function{} var functions []*Function for token, spec := range specs { f, err := bindFunction(token, spec, types) if err != nil { return nil, nil, errors.Wrapf(err, "error binding function %v", token) } functionTable[token] = f functions = append(functions, f) } sort.Slice(functions, func(i, j int) bool { return functions[i].Token < functions[j].Token }) return functions, functionTable, nil }