mirror of https://github.com/pulumi/pulumi.git
2106 lines
72 KiB
Go
2106 lines
72 KiB
Go
// 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 may 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 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"`
|
|
// Parameterization contains parameterization information about the package.
|
|
Parameterization *ParameterizationSpec `json:"parameterization,omitempty" yaml:"parameterization,omitempty"`
|
|
}
|