mirror of https://github.com/pulumi/pulumi.git
5343 lines
166 KiB
Go
5343 lines
166 KiB
Go
// Copyright 2016-2021, 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.
|
|
|
|
// Pulling out some of the repeated strings tokens into constants would harm readability, so we just ignore the
|
|
// goconst linter's warning.
|
|
//
|
|
//nolint:lll, goconst
|
|
package gen
|
|
|
|
import (
|
|
"bytes"
|
|
_ "embed"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"go/format"
|
|
"io"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"golang.org/x/mod/modfile"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen"
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/cgstrings"
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/slice"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
|
)
|
|
|
|
// A signifier that the module is external, and will never match.
|
|
//
|
|
// This token is always an invalid module since ':' is not allowed within modules.
|
|
const ExternalModuleSig = ":always-external:"
|
|
|
|
const (
|
|
GenericsSettingNone = "none"
|
|
GenericsSettingSideBySide = "side-by-side"
|
|
GenericsSettingGenericsOnly = "generics-only"
|
|
)
|
|
|
|
type typeDetails struct {
|
|
// Note: if any of {ptr,array,map}Input are set, input and the corresponding output field must also be set. The
|
|
// mark* functions ensure that these invariants hold.
|
|
input bool
|
|
ptrInput bool
|
|
arrayInput bool
|
|
mapInput bool
|
|
|
|
// Note: if any of {ptr,array,map}Output are set, output must also be set. The mark* functions ensure that these
|
|
// invariants hold.
|
|
output bool
|
|
ptrOutput bool
|
|
arrayOutput bool
|
|
mapOutput bool
|
|
}
|
|
|
|
func (d *typeDetails) hasOutputs() bool {
|
|
return d.output || d.ptrOutput || d.arrayOutput || d.mapOutput
|
|
}
|
|
|
|
func (d *typeDetails) mark(input, output bool) {
|
|
d.input = d.input || input
|
|
d.output = d.output || input || output
|
|
}
|
|
|
|
func (d *typeDetails) markPtr(input, output bool) {
|
|
d.mark(input, output)
|
|
d.ptrInput = d.ptrInput || input
|
|
d.ptrOutput = d.ptrOutput || input || output
|
|
}
|
|
|
|
func (d *typeDetails) markArray(input, output bool) {
|
|
d.mark(input, output)
|
|
d.arrayInput = d.arrayInput || input
|
|
d.arrayOutput = d.arrayOutput || input || output
|
|
}
|
|
|
|
func (d *typeDetails) markMap(input, output bool) {
|
|
d.mark(input, output)
|
|
d.mapInput = d.mapInput || input
|
|
d.mapOutput = d.mapOutput || input || output
|
|
}
|
|
|
|
// Title converts the input string to a title case
|
|
// where only the initial letter is upper-cased.
|
|
// It also removes $-prefix if any.
|
|
func Title(s string) string {
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
if s[0] == '$' {
|
|
return Title(s[1:])
|
|
}
|
|
s = cgstrings.UppercaseFirst(s)
|
|
s = cgstrings.Unhyphenate(s)
|
|
return s
|
|
}
|
|
|
|
func tokenToPackage(pkg schema.PackageReference, overrides map[string]string, tok string) string {
|
|
mod := pkg.TokenToModule(tok)
|
|
if override, ok := overrides[mod]; ok {
|
|
mod = override
|
|
}
|
|
return strings.ToLower(mod)
|
|
}
|
|
|
|
// A threadsafe cache for sharing between invocations of GenerateProgram.
|
|
type Cache struct {
|
|
externalPackages map[*schema.Package]map[string]*pkgContext
|
|
m *sync.Mutex
|
|
}
|
|
|
|
var globalCache = NewCache()
|
|
|
|
func NewCache() *Cache {
|
|
return &Cache{
|
|
externalPackages: map[*schema.Package]map[string]*pkgContext{},
|
|
m: new(sync.Mutex),
|
|
}
|
|
}
|
|
|
|
func (c *Cache) lookupContextMap(pkg *schema.Package) (map[string]*pkgContext, bool) {
|
|
c.m.Lock()
|
|
defer c.m.Unlock()
|
|
m, ok := c.externalPackages[pkg]
|
|
return m, ok
|
|
}
|
|
|
|
func (c *Cache) setContextMap(pkg *schema.Package, m map[string]*pkgContext) {
|
|
c.m.Lock()
|
|
defer c.m.Unlock()
|
|
c.externalPackages[pkg] = m
|
|
}
|
|
|
|
type pkgContext struct {
|
|
pkg schema.PackageReference
|
|
mod string
|
|
importBasePath string
|
|
rootPackageName string
|
|
typeDetails map[schema.Type]*typeDetails
|
|
enums []*schema.EnumType
|
|
types []*schema.ObjectType
|
|
resources []*schema.Resource
|
|
functions []*schema.Function
|
|
|
|
// schemaNames tracks the names of types/resources as specified in the schema
|
|
schemaNames codegen.StringSet
|
|
names codegen.StringSet
|
|
renamed map[string]string
|
|
|
|
// A mapping between external packages and their bound contents.
|
|
externalPackages *Cache
|
|
|
|
// duplicateTokens tracks tokens that exist for both types and resources
|
|
duplicateTokens map[string]bool
|
|
functionNames map[*schema.Function]string
|
|
tool string
|
|
packages map[string]*pkgContext
|
|
|
|
// Name overrides set in GoPackageInfo
|
|
modToPkg map[string]string // Module name -> package name
|
|
pkgImportAliases map[string]string // Package name -> import alias
|
|
// the name used for the internal module, defaults to "internal" if not set by the schema
|
|
internalModuleName string
|
|
|
|
// Determines whether to make single-return-value methods return an output struct or the value
|
|
liftSingleValueMethodReturns bool
|
|
|
|
// Determines if we should emit type registration code
|
|
disableInputTypeRegistrations bool
|
|
|
|
// Determines if we should emit object defaults code
|
|
disableObjectDefaults bool
|
|
}
|
|
|
|
func (pkg *pkgContext) detailsForType(t schema.Type) *typeDetails {
|
|
if obj, ok := t.(*schema.ObjectType); ok && obj.IsInputShape() {
|
|
t = obj.PlainShape
|
|
}
|
|
|
|
details, ok := pkg.typeDetails[t]
|
|
if !ok {
|
|
details = &typeDetails{}
|
|
pkg.typeDetails[t] = details
|
|
}
|
|
return details
|
|
}
|
|
|
|
func (pkg *pkgContext) tokenToPackage(tok string) string {
|
|
return tokenToPackage(pkg.pkg, pkg.modToPkg, tok)
|
|
}
|
|
|
|
func (pkg *pkgContext) tokenToType(tok string) string {
|
|
// token := pkg : module : member
|
|
// module := path/to/module
|
|
|
|
components := strings.Split(tok, ":")
|
|
contract.Assertf(len(components) == 3, "tok: %s", tok)
|
|
contract.Assertf(pkg != nil, "pkg is nil. token %s", tok)
|
|
contract.Assertf(pkg.pkg != nil, "pkg.pkg is nil. token %s", tok)
|
|
|
|
mod, name := pkg.tokenToPackage(tok), components[2]
|
|
|
|
name = Title(name)
|
|
if modPkg, ok := pkg.packages[mod]; ok {
|
|
newName, renamed := modPkg.renamed[name]
|
|
if renamed {
|
|
name = newName
|
|
} else if modPkg.duplicateTokens[strings.ToLower(tok)] {
|
|
// maintain support for duplicate tokens for types and resources in Kubernetes
|
|
name += "Type"
|
|
}
|
|
}
|
|
|
|
if mod == pkg.mod {
|
|
return name
|
|
}
|
|
if mod == "" {
|
|
var err error
|
|
mod, err = packageRoot(pkg.pkg)
|
|
contract.AssertNoErrorf(err, "Unable to determine package root")
|
|
}
|
|
|
|
var importPath string
|
|
if alias, hasAlias := pkg.pkgImportAliases[path.Join(pkg.importBasePath, mod)]; hasAlias {
|
|
importPath = alias
|
|
} else {
|
|
importPath = mod[strings.IndexRune(mod, '/')+1:]
|
|
importPath = strings.ReplaceAll(importPath, "-", "")
|
|
}
|
|
|
|
return strings.ReplaceAll(importPath+"."+name, "-provider", "")
|
|
}
|
|
|
|
// Resolve a enum type to its name.
|
|
func (pkg *pkgContext) resolveEnumType(t *schema.EnumType) string {
|
|
if !pkg.isExternalReference(t) {
|
|
return pkg.tokenToEnum(t.Token)
|
|
}
|
|
|
|
extPkgCtx, _ := pkg.contextForExternalReference(t)
|
|
enumType := extPkgCtx.typeString(t)
|
|
return enumType
|
|
}
|
|
|
|
func (pkg *pkgContext) tokenToEnum(tok string) string {
|
|
// token := pkg : module : member
|
|
// module := path/to/module
|
|
|
|
components := strings.Split(tok, ":")
|
|
contract.Assertf(len(components) == 3, "Token must have 3 components, got %d", len(components))
|
|
contract.Assertf(pkg != nil, "pkg is nil. token %s", tok)
|
|
contract.Assertf(pkg.pkg != nil, "pkg.pkg is nil. token %s", tok)
|
|
|
|
mod, name := pkg.tokenToPackage(tok), components[2]
|
|
|
|
name = Title(name)
|
|
|
|
if modPkg, ok := pkg.packages[mod]; ok {
|
|
newName, renamed := modPkg.renamed[name]
|
|
if renamed {
|
|
name = newName
|
|
} else if modPkg.duplicateTokens[tok] {
|
|
// If the package containing the enum's token already has a resource or type with the
|
|
// same name, add an `Enum` suffix.
|
|
name += "Enum"
|
|
}
|
|
}
|
|
|
|
if mod == pkg.mod {
|
|
return name
|
|
}
|
|
if mod == "" {
|
|
mod = components[0]
|
|
}
|
|
|
|
var importPath string
|
|
if alias, hasAlias := pkg.pkgImportAliases[path.Join(pkg.importBasePath, mod)]; hasAlias {
|
|
importPath = alias
|
|
} else {
|
|
importPath = strings.ReplaceAll(mod, "/", "")
|
|
}
|
|
|
|
return importPath + "." + name
|
|
}
|
|
|
|
func (pkg *pkgContext) tokenToResource(tok string) string {
|
|
// token := pkg : module : member
|
|
// module := path/to/module
|
|
|
|
components := strings.Split(tok, ":")
|
|
contract.Assertf(len(components) == 3, "Token must have 3 components, got %d", len(components))
|
|
if pkg == nil {
|
|
panic(fmt.Errorf("pkg is nil. token %s", tok))
|
|
}
|
|
if pkg.pkg == nil {
|
|
panic(fmt.Errorf("pkg.pkg is nil. token %s", tok))
|
|
}
|
|
|
|
// Is it a provider resource?
|
|
if components[0] == "pulumi" && components[1] == "providers" {
|
|
return components[2] + ".Provider"
|
|
}
|
|
|
|
mod, name := pkg.tokenToPackage(tok), components[2]
|
|
|
|
name = Title(name)
|
|
|
|
if mod == pkg.mod {
|
|
return name
|
|
}
|
|
if mod == "" {
|
|
mod = components[0]
|
|
}
|
|
|
|
var importPath string
|
|
if alias, hasAlias := pkg.pkgImportAliases[path.Join(pkg.importBasePath, mod)]; hasAlias {
|
|
importPath = alias
|
|
} else {
|
|
importPath = strings.ReplaceAll(mod, "/", "")
|
|
}
|
|
|
|
return importPath + "." + name
|
|
}
|
|
|
|
func tokenToModule(tok string) string {
|
|
// token := pkg : module : member
|
|
// module := path/to/module
|
|
|
|
components := strings.Split(tok, ":")
|
|
contract.Assertf(len(components) == 3, "Token must have 3 components, got %d", len(components))
|
|
return components[1]
|
|
}
|
|
|
|
func tokenToName(tok string) string {
|
|
components := strings.Split(tok, ":")
|
|
contract.Assertf(len(components) == 3, "Token must have 3 components, got %d", len(components))
|
|
return Title(components[2])
|
|
}
|
|
|
|
// disambiguatedResourceName gets the name of a resource as it should appear in source, resolving conflicts in the process.
|
|
func disambiguatedResourceName(r *schema.Resource, pkg *pkgContext) string {
|
|
name := rawResourceName(r)
|
|
if renamed, ok := pkg.renamed[name]; ok {
|
|
name = renamed
|
|
}
|
|
return name
|
|
}
|
|
|
|
// rawResourceName produces raw resource name translated from schema type token without resolving conflicts or dupes.
|
|
func rawResourceName(r *schema.Resource) string {
|
|
if r.IsProvider {
|
|
return "Provider"
|
|
}
|
|
return tokenToName(r.Token)
|
|
}
|
|
|
|
// If `nil` is a valid value of type `t`.
|
|
func isNilType(t schema.Type) bool {
|
|
switch t := t.(type) {
|
|
case *schema.OptionalType, *schema.ArrayType, *schema.MapType, *schema.ResourceType, *schema.InputType:
|
|
return true
|
|
case *schema.TokenType:
|
|
// Use the underlying type for now.
|
|
if t.UnderlyingType != nil {
|
|
return isNilType(t.UnderlyingType)
|
|
}
|
|
case *schema.UnionType:
|
|
// If the union is actually a relaxed enum type, use the underlying
|
|
// type for the enum instead
|
|
for _, e := range t.ElementTypes {
|
|
if typ, ok := e.(*schema.EnumType); ok {
|
|
return isNilType(typ.ElementType)
|
|
}
|
|
}
|
|
default:
|
|
switch t {
|
|
case schema.ArchiveType, schema.AssetType, schema.JSONType, schema.AnyType:
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (pkg *pkgContext) inputType(t schema.Type) (result string) {
|
|
switch t := codegen.SimplifyInputUnion(t).(type) {
|
|
case *schema.OptionalType:
|
|
return pkg.typeString(t)
|
|
case *schema.InputType:
|
|
return pkg.inputType(t.ElementType)
|
|
case *schema.EnumType:
|
|
// Since enum type is itself an input
|
|
return pkg.resolveEnumType(t) + "Input"
|
|
case *schema.ArrayType:
|
|
en := pkg.inputType(t.ElementType)
|
|
return strings.TrimSuffix(en, "Input") + "ArrayInput"
|
|
case *schema.MapType:
|
|
en := pkg.inputType(t.ElementType)
|
|
return strings.TrimSuffix(en, "Input") + "MapInput"
|
|
case *schema.ObjectType:
|
|
if t.IsInputShape() {
|
|
t = t.PlainShape
|
|
}
|
|
return pkg.resolveObjectType(t) + "Input"
|
|
case *schema.ResourceType:
|
|
return pkg.resolveResourceType(t) + "Input"
|
|
case *schema.TokenType:
|
|
// Use the underlying type for now.
|
|
if t.UnderlyingType != nil {
|
|
return pkg.inputType(t.UnderlyingType)
|
|
}
|
|
return pkg.tokenToType(t.Token) + "Input"
|
|
case *schema.UnionType:
|
|
// If the union is actually a relaxed enum type, use the underlying
|
|
// type for the input instead
|
|
for _, e := range t.ElementTypes {
|
|
if typ, ok := e.(*schema.EnumType); ok {
|
|
return pkg.inputType(typ.ElementType)
|
|
}
|
|
}
|
|
// TODO(pdg): union types
|
|
return "pulumi.Input"
|
|
default:
|
|
switch t {
|
|
case schema.BoolType:
|
|
return "pulumi.BoolInput"
|
|
case schema.IntType:
|
|
return "pulumi.IntInput"
|
|
case schema.NumberType:
|
|
return "pulumi.Float64Input"
|
|
case schema.StringType:
|
|
return "pulumi.StringInput"
|
|
case schema.ArchiveType:
|
|
return "pulumi.ArchiveInput"
|
|
case schema.AssetType:
|
|
return "pulumi.AssetOrArchiveInput"
|
|
case schema.JSONType:
|
|
fallthrough
|
|
case schema.AnyType:
|
|
return "pulumi.Input"
|
|
}
|
|
}
|
|
|
|
panic(fmt.Errorf("unexpected type %T", t))
|
|
}
|
|
|
|
func (pkg *pkgContext) genericInputTypeImpl(t schema.Type) string {
|
|
switch t := codegen.SimplifyInputUnion(t).(type) {
|
|
case *schema.OptionalType:
|
|
return pkg.genericInputTypeImpl(t.ElementType)
|
|
case *schema.InputType:
|
|
return pkg.genericInputTypeImpl(t.ElementType)
|
|
case *schema.EnumType:
|
|
return pkg.resolveEnumType(t)
|
|
case *schema.ArrayType:
|
|
elementType := pkg.genericInputTypeImpl(t.ElementType)
|
|
return "[]" + elementType
|
|
case *schema.MapType:
|
|
elementType := pkg.genericInputTypeImpl(t.ElementType)
|
|
return "map[string]" + elementType
|
|
case *schema.ObjectType:
|
|
elementType := pkg.resolveObjectType(t)
|
|
return "*" + elementType
|
|
case *schema.UnionType:
|
|
// If the union is actually a relaxed enum type, use the underlying
|
|
// type for the input instead
|
|
for _, e := range t.ElementTypes {
|
|
if typ, ok := e.(*schema.EnumType); ok {
|
|
return pkg.genericInputTypeImpl(typ.ElementType)
|
|
}
|
|
}
|
|
|
|
return "any"
|
|
default:
|
|
elementType, _ := pkg.genericElementType(t)
|
|
return elementType
|
|
}
|
|
}
|
|
|
|
func isArrayType(t schema.Type) bool {
|
|
switch t.(type) {
|
|
case *schema.ArrayType:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isMapType(t schema.Type) bool {
|
|
switch t.(type) {
|
|
case *schema.MapType:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isOptionalType(t schema.Type) bool {
|
|
switch t.(type) {
|
|
case *schema.OptionalType:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func reduceInputType(t schema.Type) schema.Type {
|
|
switch t := t.(type) {
|
|
case *schema.InputType:
|
|
return reduceInputType(t.ElementType)
|
|
default:
|
|
return t
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) genericTypeNeedsPointer(t schema.Type) bool {
|
|
return isOptionalType(reduceInputType(t)) && !isArrayType(codegen.UnwrapType(t)) && !isMapType(codegen.UnwrapType(t))
|
|
}
|
|
|
|
func (pkg *pkgContext) genericInputType(t schema.Type) string {
|
|
optionalPointer := ""
|
|
if pkg.genericTypeNeedsPointer(t) {
|
|
optionalPointer = "*"
|
|
}
|
|
|
|
inputType := pkg.genericInputTypeImpl(t)
|
|
if strings.HasPrefix(inputType, "*") {
|
|
optionalPointer = ""
|
|
}
|
|
|
|
return fmt.Sprintf("pulumix.Input[%s%s]", optionalPointer, inputType)
|
|
}
|
|
|
|
func (pkg *pkgContext) plainGenericInputType(t schema.Type) string {
|
|
optionalPointer := ""
|
|
if pkg.genericTypeNeedsPointer(t) {
|
|
optionalPointer = "*"
|
|
}
|
|
|
|
inputType := pkg.genericInputTypeImpl(t)
|
|
if strings.HasPrefix(inputType, "*") {
|
|
optionalPointer = ""
|
|
}
|
|
|
|
return fmt.Sprintf("%s%s", optionalPointer, inputType)
|
|
}
|
|
|
|
func (pkg *pkgContext) argsTypeImpl(t schema.Type) (result string) {
|
|
switch t := codegen.SimplifyInputUnion(t).(type) {
|
|
case *schema.OptionalType:
|
|
return pkg.typeStringImpl(t, true)
|
|
case *schema.InputType:
|
|
return pkg.argsTypeImpl(t.ElementType)
|
|
case *schema.EnumType:
|
|
// Since enum type is itself an input
|
|
return pkg.resolveEnumType(t)
|
|
case *schema.ArrayType:
|
|
en := pkg.argsTypeImpl(t.ElementType)
|
|
if en == "pulumi.Any" {
|
|
en = strings.TrimSuffix(en, "Any")
|
|
}
|
|
return strings.TrimSuffix(en, "Args") + "Array"
|
|
case *schema.MapType:
|
|
en := pkg.argsTypeImpl(t.ElementType)
|
|
if en == "pulumi.Any" {
|
|
en = strings.TrimSuffix(en, "Any")
|
|
}
|
|
return strings.TrimSuffix(en, "Args") + "Map"
|
|
case *schema.ObjectType:
|
|
return pkg.resolveObjectType(t)
|
|
case *schema.ResourceType:
|
|
return pkg.resolveResourceType(t)
|
|
case *schema.TokenType:
|
|
// Use the underlying type for now.
|
|
if t.UnderlyingType != nil {
|
|
return pkg.argsTypeImpl(t.UnderlyingType)
|
|
}
|
|
return pkg.tokenToType(t.Token)
|
|
case *schema.UnionType:
|
|
// If the union is actually a relaxed enum type, use the underlying
|
|
// type for the input instead
|
|
for _, e := range t.ElementTypes {
|
|
if typ, ok := e.(*schema.EnumType); ok {
|
|
return pkg.argsTypeImpl(typ.ElementType)
|
|
}
|
|
}
|
|
return "pulumi.Any"
|
|
default:
|
|
switch t {
|
|
case schema.BoolType:
|
|
return "pulumi.Bool"
|
|
case schema.IntType:
|
|
return "pulumi.Int"
|
|
case schema.NumberType:
|
|
return "pulumi.Float64"
|
|
case schema.StringType:
|
|
return "pulumi.String"
|
|
case schema.ArchiveType:
|
|
return "pulumi.Archive"
|
|
case schema.AssetType:
|
|
return "pulumi.AssetOrArchive"
|
|
case schema.JSONType:
|
|
fallthrough
|
|
case schema.AnyType:
|
|
return "pulumi.Any"
|
|
}
|
|
}
|
|
|
|
panic(fmt.Errorf("unexpected type %T", t))
|
|
}
|
|
|
|
func (pkg *pkgContext) argsType(t schema.Type) string {
|
|
return pkg.typeStringImpl(t, true)
|
|
}
|
|
|
|
func (pkg *pkgContext) typeStringImpl(t schema.Type, argsType bool) string {
|
|
switch t := t.(type) {
|
|
case *schema.OptionalType:
|
|
if input, isInputType := t.ElementType.(*schema.InputType); isInputType {
|
|
elem := pkg.inputType(input.ElementType)
|
|
if isNilType(input.ElementType) || elem == "pulumi.Input" {
|
|
return elem
|
|
}
|
|
if pkg.isExternalReference(input.ElementType) {
|
|
_, details := pkg.contextForExternalReference(input.ElementType)
|
|
|
|
switch input.ElementType.(type) {
|
|
case *schema.ObjectType:
|
|
if !details.ptrInput {
|
|
return "*" + elem
|
|
}
|
|
case *schema.EnumType:
|
|
if !(details.ptrInput || details.input) {
|
|
return "*" + elem
|
|
}
|
|
}
|
|
}
|
|
if argsType {
|
|
return elem + "Ptr"
|
|
}
|
|
return strings.TrimSuffix(elem, "Input") + "PtrInput"
|
|
}
|
|
|
|
elementType := pkg.typeStringImpl(t.ElementType, argsType)
|
|
if isNilType(t.ElementType) || elementType == "interface{}" {
|
|
return elementType
|
|
}
|
|
return "*" + elementType
|
|
case *schema.InputType:
|
|
if argsType {
|
|
return pkg.argsTypeImpl(t.ElementType)
|
|
}
|
|
return pkg.inputType(t.ElementType)
|
|
case *schema.EnumType:
|
|
return pkg.resolveEnumType(t)
|
|
case *schema.ArrayType:
|
|
typ := "[]"
|
|
return typ + pkg.typeStringImpl(t.ElementType, argsType)
|
|
case *schema.MapType:
|
|
typ := "map[string]"
|
|
return typ + pkg.typeStringImpl(t.ElementType, argsType)
|
|
case *schema.ObjectType:
|
|
return pkg.resolveObjectType(t)
|
|
case *schema.ResourceType:
|
|
return "*" + pkg.resolveResourceType(t)
|
|
case *schema.TokenType:
|
|
// Use the underlying type for now.
|
|
if t.UnderlyingType != nil {
|
|
return pkg.typeStringImpl(t.UnderlyingType, argsType)
|
|
}
|
|
return pkg.tokenToType(t.Token)
|
|
case *schema.UnionType:
|
|
// If the union is actually a relaxed enum type, use the underlying
|
|
// type for the enum instead
|
|
for _, e := range t.ElementTypes {
|
|
if typ, ok := e.(*schema.EnumType); ok {
|
|
return pkg.typeStringImpl(typ.ElementType, argsType)
|
|
}
|
|
}
|
|
// TODO(pdg): union types
|
|
return "interface{}"
|
|
default:
|
|
switch t {
|
|
case schema.BoolType:
|
|
return "bool"
|
|
case schema.IntType:
|
|
return "int"
|
|
case schema.NumberType:
|
|
return "float64"
|
|
case schema.StringType:
|
|
return "string"
|
|
case schema.ArchiveType:
|
|
return "pulumi.Archive"
|
|
case schema.AssetType:
|
|
return "pulumi.AssetOrArchive"
|
|
case schema.JSONType:
|
|
fallthrough
|
|
case schema.AnyType:
|
|
return "interface{}"
|
|
}
|
|
}
|
|
|
|
panic(fmt.Errorf("unexpected type %T", t))
|
|
}
|
|
|
|
func (pkg *pkgContext) typeString(t schema.Type) string {
|
|
s := pkg.typeStringImpl(t, false)
|
|
if s == "pulumi." {
|
|
return "pulumi.Any"
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (pkg *pkgContext) isExternalReference(t schema.Type) bool {
|
|
isExternal, _, _ := pkg.isExternalReferenceWithPackage(t)
|
|
return isExternal
|
|
}
|
|
|
|
// Return if `t` is external to `pkg`. If so, the associated foreign schema.Package is returned.
|
|
func (pkg *pkgContext) isExternalReferenceWithPackage(t schema.Type) (
|
|
isExternal bool, extPkg schema.PackageReference, token string,
|
|
) {
|
|
switch typ := t.(type) {
|
|
case *schema.ObjectType:
|
|
isExternal = typ.PackageReference != nil && !codegen.PkgEquals(typ.PackageReference, pkg.pkg)
|
|
if isExternal {
|
|
extPkg = typ.PackageReference
|
|
token = typ.Token
|
|
}
|
|
return
|
|
case *schema.ResourceType:
|
|
isExternal = typ.Resource != nil && pkg.pkg != nil && !codegen.PkgEquals(typ.Resource.PackageReference, pkg.pkg)
|
|
if isExternal {
|
|
extPkg = typ.Resource.PackageReference
|
|
token = typ.Token
|
|
}
|
|
return
|
|
case *schema.EnumType:
|
|
isExternal = pkg.pkg != nil && !codegen.PkgEquals(typ.PackageReference, pkg.pkg)
|
|
if isExternal {
|
|
extPkg = typ.PackageReference
|
|
token = typ.Token
|
|
}
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// resolveResourceType resolves resource references in properties while
|
|
// taking into account potential external resources. Returned type is
|
|
// always marked as required. Caller should check if the property is
|
|
// optional and convert the type to a pointer if necessary.
|
|
func (pkg *pkgContext) resolveResourceType(t *schema.ResourceType) string {
|
|
if !pkg.isExternalReference(t) {
|
|
return pkg.tokenToResource(t.Token)
|
|
}
|
|
extPkgCtx, _ := pkg.contextForExternalReference(t)
|
|
resType := extPkgCtx.tokenToResource(t.Token)
|
|
if !strings.Contains(resType, ".") {
|
|
resType = fmt.Sprintf("%s.%s", extPkgCtx.pkg.Name(), resType)
|
|
}
|
|
return resType
|
|
}
|
|
|
|
// resolveObjectType resolves resource references in properties while
|
|
// taking into account potential external resources. Returned type is
|
|
// always marked as required. Caller should check if the property is
|
|
// optional and convert the type to a pointer if necessary.
|
|
func (pkg *pkgContext) resolveObjectType(t *schema.ObjectType) string {
|
|
isExternal, _, _ := pkg.isExternalReferenceWithPackage(t)
|
|
|
|
if !isExternal {
|
|
name := pkg.tokenToType(t.Token)
|
|
if t.IsInputShape() {
|
|
return name + "Args"
|
|
}
|
|
return name
|
|
}
|
|
extPkg, _ := pkg.contextForExternalReference(t)
|
|
return extPkg.typeString(t)
|
|
}
|
|
|
|
func (pkg *pkgContext) contextForExternalReference(t schema.Type) (*pkgContext, typeDetails) {
|
|
isExternal, extPkg, token := pkg.isExternalReferenceWithPackage(t)
|
|
contract.Assertf(isExternal, "Expected external reference for %v", t)
|
|
|
|
var goInfo GoPackageInfo
|
|
extDef, err := extPkg.Definition()
|
|
contract.AssertNoErrorf(err, "Could not load definition for %q", extPkg.Name())
|
|
contract.AssertNoErrorf(extDef.ImportLanguages(map[string]schema.Language{"go": Importer}),
|
|
"Failed to import languages")
|
|
if info, ok := extDef.Language["go"].(GoPackageInfo); ok {
|
|
goInfo = info
|
|
} else {
|
|
goInfo.ImportBasePath = extractImportBasePath(extPkg)
|
|
}
|
|
|
|
pkgImportAliases := goInfo.PackageImportAliases
|
|
|
|
// Ensure that any package import aliases we have specified locally take precedence over those
|
|
// specified in the remote package.
|
|
def, err := pkg.pkg.Definition()
|
|
contract.AssertNoErrorf(err, "Could not load definition for %q", pkg.pkg.Name())
|
|
if ourPkgGoInfoI, has := def.Language["go"]; has {
|
|
ourPkgGoInfo := ourPkgGoInfoI.(GoPackageInfo)
|
|
if len(ourPkgGoInfo.PackageImportAliases) > 0 {
|
|
pkgImportAliases = make(map[string]string)
|
|
// Copy the external import aliases.
|
|
for k, v := range goInfo.PackageImportAliases {
|
|
pkgImportAliases[k] = v
|
|
}
|
|
// Copy the local import aliases, overwriting any external aliases.
|
|
for k, v := range ourPkgGoInfo.PackageImportAliases {
|
|
pkgImportAliases[k] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
var maps map[string]*pkgContext
|
|
|
|
if extMap, ok := pkg.externalPackages.lookupContextMap(extDef); ok {
|
|
maps = extMap
|
|
} else {
|
|
maps, err = generatePackageContextMap(pkg.tool, extPkg, goInfo, pkg.externalPackages)
|
|
contract.AssertNoErrorf(err, "Could not generate package context map")
|
|
pkg.externalPackages.setContextMap(extDef, maps)
|
|
}
|
|
extPkgCtx := maps[""]
|
|
extPkgCtx.pkgImportAliases = pkgImportAliases
|
|
extPkgCtx.externalPackages = pkg.externalPackages
|
|
mod := tokenToPackage(extPkg, goInfo.ModuleToPackage, token)
|
|
extPkgCtx.mod = ExternalModuleSig
|
|
|
|
return extPkgCtx, *maps[mod].detailsForType(t)
|
|
}
|
|
|
|
// outputTypeImpl does the meat of the generation of output type names from schema types. This function should only be
|
|
// called with a fully-resolved type (e.g. the result of codegen.ResolvedType). Instead of calling this function, you
|
|
// probably want to call pkgContext.outputType, which ensures that its argument is resolved.
|
|
func (pkg *pkgContext) outputTypeImpl(t schema.Type) string {
|
|
switch t := t.(type) {
|
|
case *schema.OptionalType:
|
|
elem := pkg.outputTypeImpl(t.ElementType)
|
|
if isNilType(t.ElementType) || elem == "pulumi.AnyOutput" {
|
|
return elem
|
|
}
|
|
if pkg.isExternalReference(t.ElementType) {
|
|
_, details := pkg.contextForExternalReference(t.ElementType)
|
|
switch t.ElementType.(type) {
|
|
case *schema.ObjectType:
|
|
if !details.ptrOutput {
|
|
return "*" + elem
|
|
}
|
|
case *schema.EnumType:
|
|
if !(details.ptrOutput || details.output) {
|
|
return "*" + elem
|
|
}
|
|
}
|
|
}
|
|
return strings.TrimSuffix(elem, "Output") + "PtrOutput"
|
|
case *schema.EnumType:
|
|
return pkg.resolveEnumType(t) + "Output"
|
|
case *schema.ArrayType:
|
|
en := strings.TrimSuffix(pkg.outputTypeImpl(t.ElementType), "Output")
|
|
if en == "pulumi.Any" {
|
|
return "pulumi.ArrayOutput"
|
|
}
|
|
return en + "ArrayOutput"
|
|
case *schema.MapType:
|
|
en := strings.TrimSuffix(pkg.outputTypeImpl(t.ElementType), "Output")
|
|
if en == "pulumi.Any" {
|
|
return "pulumi.MapOutput"
|
|
}
|
|
return en + "MapOutput"
|
|
case *schema.ObjectType:
|
|
return pkg.resolveObjectType(t) + "Output"
|
|
case *schema.ResourceType:
|
|
return pkg.resolveResourceType(t) + "Output"
|
|
case *schema.TokenType:
|
|
// Use the underlying type for now.
|
|
if t.UnderlyingType != nil {
|
|
return pkg.outputTypeImpl(t.UnderlyingType)
|
|
}
|
|
return pkg.tokenToType(t.Token) + "Output"
|
|
case *schema.UnionType:
|
|
// If the union is actually a relaxed enum type, use the underlying
|
|
// type for the output instead
|
|
for _, e := range t.ElementTypes {
|
|
if typ, ok := e.(*schema.EnumType); ok {
|
|
return pkg.outputTypeImpl(typ.ElementType)
|
|
}
|
|
}
|
|
// TODO(pdg): union types
|
|
return "pulumi.AnyOutput"
|
|
case *schema.InputType:
|
|
// We can't make output types for input types. We instead strip the input and try again.
|
|
return pkg.outputTypeImpl(t.ElementType)
|
|
default:
|
|
switch t {
|
|
case schema.BoolType:
|
|
return "pulumi.BoolOutput"
|
|
case schema.IntType:
|
|
return "pulumi.IntOutput"
|
|
case schema.NumberType:
|
|
return "pulumi.Float64Output"
|
|
case schema.StringType:
|
|
return "pulumi.StringOutput"
|
|
case schema.ArchiveType:
|
|
return "pulumi.ArchiveOutput"
|
|
case schema.AssetType:
|
|
return "pulumi.AssetOrArchiveOutput"
|
|
case schema.JSONType:
|
|
fallthrough
|
|
case schema.AnyType:
|
|
return "pulumi.AnyOutput"
|
|
}
|
|
}
|
|
|
|
panic(fmt.Errorf("unexpected type %T", t))
|
|
}
|
|
|
|
func isAssetOrArchive(t schema.Type) bool {
|
|
switch t {
|
|
case schema.ArchiveType, schema.AssetType:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) genericElementType(schemaType schema.Type) (string, bool) {
|
|
switch schemaType {
|
|
case schema.StringType:
|
|
return "string", true
|
|
case schema.BoolType:
|
|
return "bool", true
|
|
case schema.IntType:
|
|
return "int", true
|
|
case schema.NumberType:
|
|
return "float64", true
|
|
case schema.ArchiveType:
|
|
return "pulumi.Archive", true
|
|
case schema.AssetType:
|
|
return "pulumi.AssetOrArchive", true
|
|
default:
|
|
switch schemaType := schemaType.(type) {
|
|
case *schema.ObjectType:
|
|
return pkg.resolveObjectType(schemaType), false
|
|
case *schema.EnumType:
|
|
return pkg.resolveEnumType(schemaType), true
|
|
case *schema.ResourceType:
|
|
return pkg.resolveResourceType(schemaType), false
|
|
case *schema.TokenType:
|
|
return pkg.genericElementType(schemaType.UnderlyingType)
|
|
case *schema.ArrayType:
|
|
elementType, _ := pkg.genericElementType(schemaType.ElementType)
|
|
return "[]" + elementType, false
|
|
case *schema.MapType:
|
|
elementType, _ := pkg.genericElementType(schemaType.ElementType)
|
|
return "map[string]" + elementType, false
|
|
case *schema.UnionType:
|
|
for _, e := range schemaType.ElementTypes {
|
|
if enumType, ok := e.(*schema.EnumType); ok {
|
|
return pkg.genericElementType(enumType.ElementType)
|
|
}
|
|
}
|
|
return "any", true
|
|
default:
|
|
return "any", true
|
|
}
|
|
}
|
|
}
|
|
|
|
// genericOutputTypeImpl is similar to outputTypeImpl, but it generates the generic variant.
|
|
// for example instead of pulumi.StringOutput, it generates pulumix.Output[string]
|
|
func (pkg *pkgContext) genericOutputTypeImpl(t schema.Type) string {
|
|
switch t := t.(type) {
|
|
case *schema.OptionalType:
|
|
elementType, isPrimitive := pkg.genericElementType(t.ElementType)
|
|
if elementType == "any" {
|
|
return fmt.Sprintf("pulumix.Output[%s]", elementType)
|
|
}
|
|
|
|
if isPrimitive {
|
|
// for example OptionalType{StringType} becomes pulumix.Output[*string]
|
|
return fmt.Sprintf("pulumix.Output[*%s]", elementType)
|
|
}
|
|
|
|
if pkg.isExternalReference(t.ElementType) {
|
|
_, details := pkg.contextForExternalReference(t.ElementType)
|
|
switch t.ElementType.(type) {
|
|
case *schema.ObjectType:
|
|
if !details.ptrOutput {
|
|
return "*" + elementType
|
|
}
|
|
case *schema.EnumType:
|
|
if !(details.ptrOutput || details.output) {
|
|
return "*" + elementType
|
|
}
|
|
}
|
|
}
|
|
|
|
return pkg.genericOutputTypeImpl(t.ElementType)
|
|
case *schema.EnumType:
|
|
elementType, _ := pkg.genericElementType(t)
|
|
return fmt.Sprintf("pulumix.Output[%s]", elementType)
|
|
case *schema.ArrayType:
|
|
elementType, isPrimitive := pkg.genericElementType(t.ElementType)
|
|
if isPrimitive {
|
|
return fmt.Sprintf("pulumix.ArrayOutput[%s]", elementType)
|
|
}
|
|
|
|
// for non-primitive types such as objects and resources
|
|
// use GArrayOutput[Type, TypeOutput]
|
|
return fmt.Sprintf("pulumix.GArrayOutput[%s, %sOutput]", elementType, elementType)
|
|
case *schema.MapType:
|
|
elementType, isPrimitive := pkg.genericElementType(t.ElementType)
|
|
if isPrimitive {
|
|
return fmt.Sprintf("pulumix.MapOutput[%s]", elementType)
|
|
}
|
|
|
|
// for non-primitive types such as objects and resources
|
|
// use GMapOutput[Type, TypeOutput]
|
|
return fmt.Sprintf("pulumix.GMapOutput[%s, %sOutput]", elementType, elementType)
|
|
case *schema.ObjectType:
|
|
objectTypeName, _ := pkg.genericElementType(t)
|
|
return fmt.Sprintf("pulumix.GPtrOutput[%s, %sOutput]", objectTypeName, objectTypeName)
|
|
case *schema.ResourceType:
|
|
resourceTypeName, _ := pkg.genericElementType(t)
|
|
// element type of a ResourceOutput is Resource
|
|
return fmt.Sprintf("pulumix.GPtrOutput[%s, %sOutput]", resourceTypeName, resourceTypeName)
|
|
case *schema.TokenType:
|
|
// Use the underlying type for now.
|
|
if t.UnderlyingType != nil {
|
|
return pkg.genericOutputType(t.UnderlyingType)
|
|
}
|
|
|
|
tokenType := pkg.tokenToType(t.Token)
|
|
return fmt.Sprintf("pulumix.Output[%s]", tokenType)
|
|
case *schema.UnionType:
|
|
// If the union is actually a relaxed enum type, use the underlying
|
|
// type for the enum instead
|
|
for _, e := range t.ElementTypes {
|
|
if typ, ok := e.(*schema.EnumType); ok {
|
|
return pkg.genericOutputTypeImpl(typ.ElementType)
|
|
}
|
|
}
|
|
// TODO(pdg): union types
|
|
return "pulumix.Output[interface{}]"
|
|
case *schema.InputType:
|
|
// We can't make output types for input types. We instead strip the input and try again.
|
|
return pkg.genericOutputTypeImpl(t.ElementType)
|
|
default:
|
|
elementType, _ := pkg.genericElementType(t)
|
|
return fmt.Sprintf("pulumix.Output[%s]", elementType)
|
|
}
|
|
}
|
|
|
|
// outputType returns a reference to the Go output type that corresponds to the given schema type. For example, given
|
|
// a schema.String, outputType returns "pulumi.String", and given a *schema.ObjectType with the token pkg:mod:Name,
|
|
// outputType returns "mod.NameOutput" or "NameOutput", depending on whether or not the object type lives in a
|
|
// different module than the one associated with the receiver.
|
|
func (pkg *pkgContext) outputType(t schema.Type) string {
|
|
return pkg.outputTypeImpl(codegen.ResolvedType(t))
|
|
}
|
|
|
|
// genericOutputType returns a reference to the Go output type that corresponds to the given schema type.
|
|
// For example, given a schema.StringType, genericOutputType returns "pulumix.Output[string]",
|
|
// and given a *schema.ObjectType with the token pkg:mod:Name,
|
|
// outputType returns "mod.NameOutput" or "NameOutput", depending on whether the object type lives in a
|
|
// different module than the one associated with the receiver.
|
|
func (pkg *pkgContext) genericOutputType(t schema.Type) string {
|
|
return pkg.genericOutputTypeImpl(codegen.ResolvedType(t))
|
|
}
|
|
|
|
// toOutputMethod returns the name of the "ToXXXOutput" method for the given schema type. For example, given a
|
|
// schema.String, toOutputMethod returns "ToStringOutput", and given a *schema.ObjectType with the token pkg:mod:Name,
|
|
// outputType returns "ToNameOutput".
|
|
func (pkg *pkgContext) toOutputMethod(t schema.Type) string {
|
|
outputTypeName := pkg.outputType(t)
|
|
if i := strings.LastIndexByte(outputTypeName, '.'); i != -1 {
|
|
outputTypeName = outputTypeName[i+1:]
|
|
}
|
|
return "To" + outputTypeName
|
|
}
|
|
|
|
// printComment filters examples for the Go languages and prepends double forward slash to each line in the given
|
|
// comment. If indent is true, each line is indented with tab character. It returns the number of lines in the
|
|
// resulting comment. It guarantees that each line is terminated with newline character.
|
|
func printComment(w io.Writer, comment string, indent bool) int {
|
|
comment = codegen.FilterExamples(comment, "go")
|
|
|
|
lines := strings.Split(comment, "\n")
|
|
for len(lines) > 0 && lines[len(lines)-1] == "" {
|
|
lines = lines[:len(lines)-1]
|
|
}
|
|
for _, l := range lines {
|
|
if indent {
|
|
fmt.Fprintf(w, "\t")
|
|
}
|
|
if l == "" {
|
|
fmt.Fprintf(w, "//\n")
|
|
} else {
|
|
fmt.Fprintf(w, "// %s\n", l)
|
|
}
|
|
}
|
|
return len(lines)
|
|
}
|
|
|
|
func printCommentWithDeprecationMessage(w io.Writer, comment, deprecationMessage string, indent bool) {
|
|
lines := printComment(w, comment, indent)
|
|
if deprecationMessage != "" {
|
|
if lines > 0 {
|
|
fmt.Fprintf(w, "//\n")
|
|
}
|
|
printComment(w, "Deprecated: "+deprecationMessage, indent)
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) genInputInterface(w io.Writer, name string) {
|
|
printComment(w, pkg.getInputUsage(name), false)
|
|
fmt.Fprintf(w, "type %sInput interface {\n", name)
|
|
fmt.Fprintf(w, "\tpulumi.Input\n\n")
|
|
fmt.Fprintf(w, "\tTo%sOutput() %sOutput\n", Title(name), name)
|
|
fmt.Fprintf(w, "\tTo%sOutputWithContext(context.Context) %sOutput\n", Title(name), name)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
func (pkg *pkgContext) genEnumInputInterface(w io.Writer, name string, enumType *schema.EnumType) {
|
|
enumCases := []string{}
|
|
for _, enumCase := range enumType.Elements {
|
|
if enumCase.DeprecationMessage != "" {
|
|
// skip deprecated enum cases
|
|
continue
|
|
}
|
|
enumCases = append(enumCases, "\t\t"+enumCase.Name)
|
|
}
|
|
|
|
enumUsage := strings.Join([]string{
|
|
fmt.Sprintf("%sInput is an input type that accepts values of the %s enum", name, name),
|
|
fmt.Sprintf("A concrete instance of `%sInput` can be one of the following:", name),
|
|
"",
|
|
strings.Join(enumCases, "\n"),
|
|
" ",
|
|
}, "\n")
|
|
|
|
printComment(w, enumUsage, false)
|
|
fmt.Fprintf(w, "type %sInput interface {\n", name)
|
|
fmt.Fprintf(w, "\tpulumi.Input\n\n")
|
|
fmt.Fprintf(w, "\tTo%sOutput() %sOutput\n", Title(name), name)
|
|
fmt.Fprintf(w, "\tTo%sOutputWithContext(context.Context) %sOutput\n", Title(name), name)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
func (pkg *pkgContext) getUsageForNestedType(name, baseTypeName string) string {
|
|
const defaultExampleFormat = "%sArgs{...}"
|
|
example := fmt.Sprintf(defaultExampleFormat, baseTypeName)
|
|
|
|
trimmer := func(typeName string) string {
|
|
if strings.HasSuffix(typeName, "Array") {
|
|
return typeName[:strings.LastIndex(typeName, "Array")]
|
|
}
|
|
if strings.HasSuffix(typeName, "Map") {
|
|
return typeName[:strings.LastIndex(typeName, "Map")]
|
|
}
|
|
return typeName
|
|
}
|
|
|
|
// If not a nested collection type, use the default example format
|
|
if trimmer(name) == name {
|
|
return example
|
|
}
|
|
|
|
if strings.HasSuffix(name, "Map") {
|
|
if pkg.schemaNames.Has(baseTypeName) {
|
|
return fmt.Sprintf("%s{ \"key\": %s }", name, example)
|
|
}
|
|
return fmt.Sprintf("%s{ \"key\": %s }", name, pkg.getUsageForNestedType(baseTypeName, trimmer(baseTypeName)))
|
|
}
|
|
|
|
if strings.HasSuffix(name, "Array") {
|
|
if pkg.schemaNames.Has(baseTypeName) {
|
|
return fmt.Sprintf("%s{ %s }", name, example)
|
|
}
|
|
return fmt.Sprintf("%s{ %s }", name, pkg.getUsageForNestedType(baseTypeName, trimmer(baseTypeName)))
|
|
}
|
|
return example
|
|
}
|
|
|
|
func (pkg *pkgContext) getInputUsage(name string) string {
|
|
if strings.HasSuffix(name, "Array") {
|
|
baseTypeName := name[:strings.LastIndex(name, "Array")]
|
|
return strings.Join([]string{
|
|
fmt.Sprintf("%sInput is an input type that accepts %s and %sOutput values.", name, name, name),
|
|
fmt.Sprintf("You can construct a concrete instance of `%sInput` via:", name),
|
|
"",
|
|
"\t\t " + pkg.getUsageForNestedType(name, baseTypeName),
|
|
" ",
|
|
}, "\n")
|
|
}
|
|
|
|
if strings.HasSuffix(name, "Map") {
|
|
baseTypeName := name[:strings.LastIndex(name, "Map")]
|
|
return strings.Join([]string{
|
|
fmt.Sprintf("%sInput is an input type that accepts %s and %sOutput values.", name, name, name),
|
|
fmt.Sprintf("You can construct a concrete instance of `%sInput` via:", name),
|
|
"",
|
|
"\t\t " + pkg.getUsageForNestedType(name, baseTypeName),
|
|
" ",
|
|
}, "\n")
|
|
}
|
|
|
|
if strings.HasSuffix(name, "Ptr") {
|
|
baseTypeName := name[:strings.LastIndex(name, "Ptr")]
|
|
return strings.Join([]string{
|
|
fmt.Sprintf("%sInput is an input type that accepts %sArgs, %s and %sOutput values.", name, baseTypeName, name, name),
|
|
fmt.Sprintf("You can construct a concrete instance of `%sInput` via:", name),
|
|
"",
|
|
fmt.Sprintf("\t\t %sArgs{...}", baseTypeName),
|
|
"",
|
|
" or:",
|
|
"",
|
|
"\t\t nil",
|
|
" ",
|
|
}, "\n")
|
|
}
|
|
|
|
return strings.Join([]string{
|
|
fmt.Sprintf("%sInput is an input type that accepts %sArgs and %sOutput values.", name, name, name),
|
|
fmt.Sprintf("You can construct a concrete instance of `%sInput` via:", name),
|
|
"",
|
|
fmt.Sprintf("\t\t %sArgs{...}", name),
|
|
" ",
|
|
}, "\n")
|
|
}
|
|
|
|
type genInputImplementationArgs struct {
|
|
name string
|
|
receiverType string
|
|
elementType string
|
|
ptrMethods bool
|
|
toOutputMethods bool
|
|
usingGenericTypes bool
|
|
goPackageinfo GoPackageInfo
|
|
}
|
|
|
|
func (pkg *pkgContext) genInputImplementation(
|
|
w io.Writer,
|
|
name string,
|
|
receiverType string,
|
|
elementType string,
|
|
ptrMethods bool,
|
|
usingGenericTypes bool,
|
|
) {
|
|
genInputImplementationWithArgs(w, genInputImplementationArgs{
|
|
name: name,
|
|
receiverType: receiverType,
|
|
elementType: elementType,
|
|
ptrMethods: ptrMethods,
|
|
toOutputMethods: true,
|
|
usingGenericTypes: usingGenericTypes,
|
|
goPackageinfo: goPackageInfo(pkg.pkg),
|
|
})
|
|
}
|
|
|
|
func genInputImplementationWithArgs(w io.Writer, genArgs genInputImplementationArgs) {
|
|
name := genArgs.name
|
|
receiverType := genArgs.receiverType
|
|
elementType := genArgs.elementType
|
|
|
|
fmt.Fprintf(w, "func (%s) ElementType() reflect.Type {\n", receiverType)
|
|
fmt.Fprintf(w, "\treturn reflect.TypeOf((*%s)(nil)).Elem()\n", elementType)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
var hasToOutput bool
|
|
if genArgs.toOutputMethods {
|
|
fmt.Fprintf(w, "func (i %s) To%sOutput() %sOutput {\n", receiverType, Title(name), name)
|
|
fmt.Fprintf(w, "\treturn i.To%sOutputWithContext(context.Background())\n", Title(name))
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (i %s) To%sOutputWithContext(ctx context.Context) %sOutput {\n", receiverType, Title(name), name)
|
|
fmt.Fprintf(w, "\treturn pulumi.ToOutputWithContext(ctx, i).(%sOutput)\n", name)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
if !genArgs.usingGenericTypes {
|
|
// Generate 'ToOuput(context.Context) pulumix.Output[T]' method
|
|
// to satisfy pulumix.Input[T].
|
|
if genArgs.goPackageinfo.Generics == GenericsSettingSideBySide {
|
|
fmt.Fprintf(w, "func (i %s) ToOutput(ctx context.Context) pulumix.Output[%s] {\n", receiverType, elementType)
|
|
fmt.Fprintf(w, "\treturn pulumix.Output[%s]{\n", elementType)
|
|
fmt.Fprintf(w, "\t\tOutputState: i.To%sOutputWithContext(ctx).OutputState,\n", Title(name))
|
|
fmt.Fprintf(w, "\t}\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
hasToOutput = true
|
|
}
|
|
} else {
|
|
// Generate 'ToOuput(context.Context) pulumix.Output[T]' method which lifts the receiver type *T
|
|
// to satisfy pulumix.Input[*T].
|
|
fmt.Fprintf(w, "func (i *%s) ToOutput(ctx context.Context) pulumix.Output[*%s] {\n", receiverType, receiverType)
|
|
fmt.Fprint(w, "\treturn pulumix.Val(i)\n")
|
|
fmt.Fprint(w, "}\n\n")
|
|
}
|
|
}
|
|
|
|
if genArgs.ptrMethods && !genArgs.usingGenericTypes {
|
|
fmt.Fprintf(w, "func (i %s) To%sPtrOutput() %sPtrOutput {\n", receiverType, Title(name), name)
|
|
fmt.Fprintf(w, "\treturn i.To%sPtrOutputWithContext(context.Background())\n", Title(name))
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (i %s) To%sPtrOutputWithContext(ctx context.Context) %sPtrOutput {\n", receiverType, Title(name), name)
|
|
if strings.HasSuffix(receiverType, "Args") {
|
|
fmt.Fprintf(w, "\treturn pulumi.ToOutputWithContext(ctx, i).(%[1]sOutput).To%[1]sPtrOutputWithContext(ctx)\n", name)
|
|
} else {
|
|
fmt.Fprintf(w, "\treturn pulumi.ToOutputWithContext(ctx, i).(%sPtrOutput)\n", name)
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
if !hasToOutput {
|
|
// Generate 'ToOuput(context.Context) pulumix.Output[*T]' method
|
|
// to satisfy pulumix.Input[*T].
|
|
if genArgs.goPackageinfo.Generics == GenericsSettingSideBySide {
|
|
fmt.Fprintf(w, "func (i %s) ToOutput(ctx context.Context) pulumix.Output[*%s] {\n", receiverType, elementType)
|
|
fmt.Fprintf(w, "\treturn pulumix.Output[*%s]{\n", elementType)
|
|
fmt.Fprintf(w, "\t\tOutputState: i.To%sPtrOutputWithContext(ctx).OutputState,\n", Title(name))
|
|
fmt.Fprintf(w, "\t}\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) genOutputType(w io.Writer, baseName, elementType string, ptrMethods, usingGenericTypes bool) {
|
|
fmt.Fprintf(w, "type %sOutput struct { *pulumi.OutputState }\n\n", baseName)
|
|
|
|
fmt.Fprintf(w, "func (%sOutput) ElementType() reflect.Type {\n", baseName)
|
|
fmt.Fprintf(w, "\treturn reflect.TypeOf((*%s)(nil)).Elem()\n", elementType)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sOutput() %[1]sOutput {\n", baseName, Title(baseName))
|
|
fmt.Fprintf(w, "\treturn o\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sOutputWithContext(ctx context.Context) %[1]sOutput {\n", baseName, Title(baseName))
|
|
fmt.Fprintf(w, "\treturn o\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
if ptrMethods && !usingGenericTypes {
|
|
fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sPtrOutput() %[1]sPtrOutput {\n", baseName, Title(baseName))
|
|
fmt.Fprintf(w, "\treturn o.To%sPtrOutputWithContext(context.Background())\n", Title(baseName))
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sPtrOutputWithContext(ctx context.Context) %[1]sPtrOutput {\n", baseName, Title(baseName))
|
|
fmt.Fprintf(w, "\treturn o.ApplyTWithContext(ctx, func(_ context.Context, v %[1]s) *%[1]s {\n", elementType)
|
|
fmt.Fprintf(w, "\t\treturn &v\n")
|
|
fmt.Fprintf(w, "\t}).(%sPtrOutput)\n", baseName)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
// Generate 'ToOuput(context.Context) pulumix.Output[T]' method
|
|
// to satisfy pulumix.Input[T].
|
|
goPackageInfo := goPackageInfo(pkg.pkg)
|
|
if goPackageInfo.Generics == GenericsSettingSideBySide || goPackageInfo.Generics == GenericsSettingGenericsOnly {
|
|
fmt.Fprintf(w, "func (o %sOutput) ToOutput(ctx context.Context) pulumix.Output[%s] {\n", baseName, elementType)
|
|
fmt.Fprintf(w, "\treturn pulumix.Output[%s]{\n", elementType)
|
|
fmt.Fprintf(w, "\t\tOutputState: o.OutputState,\n")
|
|
fmt.Fprintf(w, "\t}\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) genArrayOutput(w io.Writer, baseName, elementType string) {
|
|
pkg.genOutputType(w, baseName+"Array", "[]"+elementType, false, false)
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sArrayOutput) Index(i pulumi.IntInput) %[1]sOutput {\n", baseName)
|
|
fmt.Fprintf(w, "\treturn pulumi.All(o, i).ApplyT(func (vs []interface{}) %s {\n", elementType)
|
|
fmt.Fprintf(w, "\t\treturn vs[0].([]%s)[vs[1].(int)]\n", elementType)
|
|
fmt.Fprintf(w, "\t}).(%sOutput)\n", baseName)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
func (pkg *pkgContext) genMapOutput(w io.Writer, baseName, elementType string) {
|
|
pkg.genOutputType(w, baseName+"Map", "map[string]"+elementType, false, false)
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sMapOutput) MapIndex(k pulumi.StringInput) %[1]sOutput {\n", baseName)
|
|
fmt.Fprintf(w, "\treturn pulumi.All(o, k).ApplyT(func (vs []interface{}) %s{\n", elementType)
|
|
fmt.Fprintf(w, "\t\treturn vs[0].(map[string]%s)[vs[1].(string)]\n", elementType)
|
|
fmt.Fprintf(w, "\t}).(%sOutput)\n", baseName)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
func (pkg *pkgContext) genPtrOutput(w io.Writer, baseName, elementType string) {
|
|
pkg.genOutputType(w, baseName+"Ptr", "*"+elementType, false, false)
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sPtrOutput) Elem() %[1]sOutput {\n", baseName)
|
|
fmt.Fprintf(w, "\treturn o.ApplyT(func(v *%[1]s) %[1]s {\n", baseName)
|
|
fmt.Fprint(w, "\t\tif v != nil {\n")
|
|
fmt.Fprintf(w, "\t\t\treturn *v\n")
|
|
fmt.Fprint(w, "\t\t}\n")
|
|
fmt.Fprintf(w, "\t\tvar ret %s\n", baseName)
|
|
fmt.Fprint(w, "\t\treturn ret\n")
|
|
fmt.Fprintf(w, "\t}).(%sOutput)\n", baseName)
|
|
fmt.Fprint(w, "}\n\n")
|
|
}
|
|
|
|
func (pkg *pkgContext) genEnum(w io.Writer, enumType *schema.EnumType, usingGenericTypes bool) error {
|
|
name := pkg.tokenToEnum(enumType.Token)
|
|
|
|
mod := pkg.tokenToPackage(enumType.Token)
|
|
modPkg, ok := pkg.packages[mod]
|
|
contract.Assertf(ok, "Context for module %q not found", mod)
|
|
|
|
printCommentWithDeprecationMessage(w, enumType.Comment, "", false)
|
|
|
|
elementArgsType := pkg.argsTypeImpl(enumType.ElementType)
|
|
elementGoType := pkg.typeString(enumType.ElementType)
|
|
asFuncName := strings.TrimPrefix(elementArgsType, "pulumi.")
|
|
|
|
fmt.Fprintf(w, "type %s %s\n\n", name, elementGoType)
|
|
|
|
fmt.Fprintln(w, "const (")
|
|
for _, e := range enumType.Elements {
|
|
printCommentWithDeprecationMessage(w, e.Comment, e.DeprecationMessage, true)
|
|
|
|
elementName := e.Name
|
|
if e.Name == "" {
|
|
elementName = fmt.Sprintf("%v", e.Value)
|
|
}
|
|
enumName, err := makeSafeEnumName(elementName, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.Name = enumName
|
|
contract.Assertf(!modPkg.names.Has(e.Name), "Name collision for enum constant: %s for %s",
|
|
e.Name, enumType.Token)
|
|
|
|
//nolint:exhaustive // Default case handles the rest of the values
|
|
switch reflect.TypeOf(e.Value).Kind() {
|
|
case reflect.String:
|
|
fmt.Fprintf(w, "%s = %s(%q)\n", e.Name, name, e.Value)
|
|
default:
|
|
fmt.Fprintf(w, "%s = %s(%v)\n", e.Name, name, e.Value)
|
|
}
|
|
}
|
|
fmt.Fprintln(w, ")")
|
|
|
|
if usingGenericTypes {
|
|
// no need to generate the rest of the enum output/input types
|
|
return nil
|
|
}
|
|
|
|
details := pkg.detailsForType(enumType)
|
|
if details.input || details.ptrInput {
|
|
inputType := pkg.inputType(enumType)
|
|
pkg.genEnumInputFuncs(w, name, enumType, elementArgsType, inputType, asFuncName)
|
|
}
|
|
|
|
if details.output || details.ptrOutput {
|
|
pkg.genEnumOutputTypes(w, name, elementArgsType, elementGoType, asFuncName)
|
|
}
|
|
if details.input || details.ptrInput {
|
|
pkg.genEnumInputTypes(w, name, enumType, elementGoType)
|
|
}
|
|
|
|
// Generate the array input.
|
|
if details.arrayInput {
|
|
pkg.genInputInterface(w, name+"Array")
|
|
|
|
fmt.Fprintf(w, "type %[1]sArray []%[1]s\n\n", name)
|
|
|
|
pkg.genInputImplementation(w, name+"Array", name+"Array", "[]"+name, false, usingGenericTypes)
|
|
}
|
|
|
|
// Generate the map input.
|
|
if details.mapInput {
|
|
pkg.genInputInterface(w, name+"Map")
|
|
|
|
fmt.Fprintf(w, "type %[1]sMap map[string]%[1]s\n\n", name)
|
|
|
|
pkg.genInputImplementation(w, name+"Map", name+"Map", "map[string]"+name, false, usingGenericTypes)
|
|
}
|
|
|
|
// Generate the array output
|
|
if details.arrayOutput {
|
|
pkg.genArrayOutput(w, name, name)
|
|
}
|
|
|
|
// Generate the map output.
|
|
if details.mapOutput {
|
|
pkg.genMapOutput(w, name, name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (pkg *pkgContext) genEnumOutputTypes(w io.Writer, name, elementArgsType, elementGoType, asFuncName string) {
|
|
pkg.genOutputType(w, name, name, true, false)
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sOutput() %[3]sOutput {\n", name, asFuncName, elementArgsType)
|
|
fmt.Fprintf(w, "return o.To%sOutputWithContext(context.Background())\n", asFuncName)
|
|
fmt.Fprint(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sOutputWithContext(ctx context.Context) %[3]sOutput {\n", name, asFuncName, elementArgsType)
|
|
fmt.Fprintf(w, "return o.ApplyTWithContext(ctx, func(_ context.Context, e %s) %s {\n", name, elementGoType)
|
|
fmt.Fprintf(w, "return %s(e)\n", elementGoType)
|
|
fmt.Fprintf(w, "}).(%sOutput)\n", elementArgsType)
|
|
fmt.Fprint(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sPtrOutput() %[3]sPtrOutput {\n", name, asFuncName, elementArgsType)
|
|
fmt.Fprintf(w, "return o.To%sPtrOutputWithContext(context.Background())\n", asFuncName)
|
|
fmt.Fprint(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sPtrOutputWithContext(ctx context.Context) %[3]sPtrOutput {\n", name, asFuncName, elementArgsType)
|
|
fmt.Fprintf(w, "return o.ApplyTWithContext(ctx, func(_ context.Context, e %s) *%s {\n", name, elementGoType)
|
|
fmt.Fprintf(w, "v := %s(e)\n", elementGoType)
|
|
fmt.Fprintf(w, "return &v\n")
|
|
fmt.Fprintf(w, "}).(%sPtrOutput)\n", elementArgsType)
|
|
fmt.Fprint(w, "}\n\n")
|
|
|
|
pkg.genPtrOutput(w, name, name)
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sPtrOutput) To%[2]sPtrOutput() %[3]sPtrOutput {\n", name, asFuncName, elementArgsType)
|
|
fmt.Fprintf(w, "return o.To%sPtrOutputWithContext(context.Background())\n", asFuncName)
|
|
fmt.Fprint(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sPtrOutput) To%[2]sPtrOutputWithContext(ctx context.Context) %[3]sPtrOutput {\n", name, asFuncName, elementArgsType)
|
|
fmt.Fprintf(w, "return o.ApplyTWithContext(ctx, func(_ context.Context, e *%s) *%s {\n", name, elementGoType)
|
|
fmt.Fprintf(w, "if e == nil {\n")
|
|
fmt.Fprintf(w, "return nil\n")
|
|
fmt.Fprintf(w, "}\n")
|
|
fmt.Fprintf(w, "v := %s(*e)\n", elementGoType)
|
|
fmt.Fprintf(w, "return &v\n")
|
|
fmt.Fprintf(w, "}).(%sPtrOutput)\n", elementArgsType)
|
|
fmt.Fprint(w, "}\n\n")
|
|
}
|
|
|
|
func (pkg *pkgContext) genEnumInputTypes(w io.Writer, name string, enumType *schema.EnumType, elementGoType string) {
|
|
pkg.genEnumInputInterface(w, name, enumType)
|
|
goPkgInfo := goPackageInfo(pkg.pkg)
|
|
typeName := cgstrings.Camel(name)
|
|
fmt.Fprintf(w, "var %sPtrType = reflect.TypeOf((**%s)(nil)).Elem()\n", typeName, name)
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "type %sPtrInput interface {\n", name)
|
|
fmt.Fprint(w, "pulumi.Input\n\n")
|
|
fmt.Fprintf(w, "To%[1]sPtrOutput() %[1]sPtrOutput\n", name)
|
|
fmt.Fprintf(w, "To%[1]sPtrOutputWithContext(context.Context) %[1]sPtrOutput\n", name)
|
|
fmt.Fprintf(w, "}\n")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "type %sPtr %s\n", typeName, elementGoType)
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func %[1]sPtr(v %[2]s) %[1]sPtrInput {\n", name, elementGoType)
|
|
fmt.Fprintf(w, "return (*%sPtr)(&v)\n", typeName)
|
|
fmt.Fprintf(w, "}\n")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func (*%sPtr) ElementType() reflect.Type {\n", typeName)
|
|
fmt.Fprintf(w, "return %sPtrType\n", typeName)
|
|
fmt.Fprintf(w, "}\n")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func (in *%[1]sPtr) To%[2]sPtrOutput() %[2]sPtrOutput {\n", typeName, name)
|
|
fmt.Fprintf(w, "return pulumi.ToOutput(in).(%sPtrOutput)\n", name)
|
|
fmt.Fprintf(w, "}\n")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func (in *%[1]sPtr) To%[2]sPtrOutputWithContext(ctx context.Context) %[2]sPtrOutput {\n", cgstrings.Camel(name), name)
|
|
fmt.Fprintf(w, "return pulumi.ToOutputWithContext(ctx, in).(%sPtrOutput)\n", name)
|
|
fmt.Fprintf(w, "}\n")
|
|
fmt.Fprintln(w)
|
|
|
|
if goPkgInfo.Generics != GenericsSettingNone {
|
|
// ToOutput implementation for pulumix.Input.
|
|
fmt.Fprintf(w, "func (in *%sPtr) ToOutput(ctx context.Context) pulumix.Output[*%s] {\n", typeName, name)
|
|
fmt.Fprintf(w, "\treturn pulumix.Output[*%s]{\n", name)
|
|
fmt.Fprintf(w, "\t\tOutputState: in.To%sPtrOutputWithContext(ctx).OutputState,\n", name)
|
|
fmt.Fprintf(w, "\t}\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) genEnumInputFuncs(w io.Writer, typeName string, enum *schema.EnumType, elementArgsType, inputType, asFuncName string) {
|
|
fmt.Fprintln(w)
|
|
fmt.Fprintf(w, "func (%s) ElementType() reflect.Type {\n", typeName)
|
|
fmt.Fprintf(w, "return reflect.TypeOf((*%s)(nil)).Elem()\n", typeName)
|
|
fmt.Fprintln(w, "}")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func (e %[1]s) To%[1]sOutput() %[1]sOutput {\n", typeName)
|
|
fmt.Fprintf(w, "return pulumi.ToOutput(e).(%sOutput)\n", typeName)
|
|
fmt.Fprintln(w, "}")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func (e %[1]s) To%[1]sOutputWithContext(ctx context.Context) %[1]sOutput {\n", typeName)
|
|
fmt.Fprintf(w, "return pulumi.ToOutputWithContext(ctx, e).(%sOutput)\n", typeName)
|
|
fmt.Fprintln(w, "}")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func (e %[1]s) To%[1]sPtrOutput() %[1]sPtrOutput {\n", typeName)
|
|
fmt.Fprintf(w, "return e.To%sPtrOutputWithContext(context.Background())\n", typeName)
|
|
fmt.Fprintln(w, "}")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func (e %[1]s) To%[1]sPtrOutputWithContext(ctx context.Context) %[1]sPtrOutput {\n", typeName)
|
|
fmt.Fprintf(w, "return %[1]s(e).To%[1]sOutputWithContext(ctx).To%[1]sPtrOutputWithContext(ctx)\n", typeName)
|
|
fmt.Fprintln(w, "}")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func (e %[1]s) To%[2]sOutput() %[3]sOutput {\n", typeName, asFuncName, elementArgsType)
|
|
fmt.Fprintf(w, "return pulumi.ToOutput(%[1]s(e)).(%[1]sOutput)\n", elementArgsType)
|
|
fmt.Fprintln(w, "}")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func (e %[1]s) To%[2]sOutputWithContext(ctx context.Context) %[3]sOutput {\n", typeName, asFuncName, elementArgsType)
|
|
fmt.Fprintf(w, "return pulumi.ToOutputWithContext(ctx, %[1]s(e)).(%[1]sOutput)\n", elementArgsType)
|
|
fmt.Fprintln(w, "}")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func (e %[1]s) To%[2]sPtrOutput() %[3]sPtrOutput {\n", typeName, asFuncName, elementArgsType)
|
|
fmt.Fprintf(w, "return %s(e).To%sPtrOutputWithContext(context.Background())\n", elementArgsType, asFuncName)
|
|
fmt.Fprintln(w, "}")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func (e %[1]s) To%[2]sPtrOutputWithContext(ctx context.Context) %[3]sPtrOutput {\n", typeName, asFuncName, elementArgsType)
|
|
fmt.Fprintf(w, "return %[1]s(e).To%[2]sOutputWithContext(ctx).To%[2]sPtrOutputWithContext(ctx)\n", elementArgsType, asFuncName)
|
|
fmt.Fprintln(w, "}")
|
|
fmt.Fprintln(w)
|
|
}
|
|
|
|
func (pkg *pkgContext) assignProperty(
|
|
w io.Writer,
|
|
p *schema.Property,
|
|
object,
|
|
value string,
|
|
indirectAssign bool,
|
|
useGenericTypes bool,
|
|
) {
|
|
t := strings.TrimSuffix(pkg.typeString(p.Type), "Input")
|
|
if useGenericTypes {
|
|
t = "pulumix.Val"
|
|
if isOptionalType(reduceInputType(p.Type)) {
|
|
t = "pulumix.Ptr"
|
|
}
|
|
}
|
|
switch codegen.UnwrapType(p.Type).(type) {
|
|
case *schema.EnumType:
|
|
if !useGenericTypes {
|
|
t = ""
|
|
}
|
|
}
|
|
|
|
if codegen.IsNOptionalInput(p.Type) {
|
|
if t != "" {
|
|
value = fmt.Sprintf("%s(%s)", t, value)
|
|
}
|
|
fmt.Fprintf(w, "\t%s.%s = %s\n", object, pkg.fieldName(nil, p), value)
|
|
} else if indirectAssign {
|
|
tmpName := cgstrings.Camel(p.Name) + "_"
|
|
fmt.Fprintf(w, "%s := %s\n", tmpName, value)
|
|
fmt.Fprintf(w, "%s.%s = &%s\n", object, pkg.fieldName(nil, p), tmpName)
|
|
} else {
|
|
fmt.Fprintf(w, "%s.%s = %s\n", object, pkg.fieldName(nil, p), value)
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) fieldName(r *schema.Resource, field *schema.Property) string {
|
|
contract.Assertf(field != nil, "Field must not be nil")
|
|
return fieldName(pkg, r, field)
|
|
}
|
|
|
|
func (pkg *pkgContext) genPlainType(w io.Writer, name, comment, deprecationMessage string,
|
|
properties []*schema.Property,
|
|
) {
|
|
printCommentWithDeprecationMessage(w, comment, deprecationMessage, false)
|
|
fmt.Fprintf(w, "type %s struct {\n", name)
|
|
for _, p := range properties {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
|
|
fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", pkg.fieldName(nil, p), pkg.typeString(codegen.ResolvedType(p.Type)), p.Name)
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
// genGenericPlainType is the same as genPlainType, but used for generic variant SDKs
|
|
// where it maintains optionalness of property types
|
|
func (pkg *pkgContext) genGenericPlainType(w io.Writer, name, comment, deprecationMessage string,
|
|
properties []*schema.Property,
|
|
) {
|
|
printCommentWithDeprecationMessage(w, comment, deprecationMessage, false)
|
|
fmt.Fprintf(w, "type %s struct {\n", name)
|
|
for _, p := range properties {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
|
|
fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", pkg.fieldName(nil, p), pkg.plainGenericInputType(p.Type), p.Name)
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
func (pkg *pkgContext) genObjectDefaultFunc(w io.Writer, name string,
|
|
properties []*schema.Property,
|
|
useGenericTypes bool,
|
|
) error {
|
|
defaults := []*schema.Property{}
|
|
for _, p := range properties {
|
|
if p.DefaultValue != nil || codegen.IsProvideDefaultsFuncRequired(p.Type) {
|
|
defaults = append(defaults, p)
|
|
}
|
|
}
|
|
|
|
// There are no defaults, so we don't need to generate a defaults function.
|
|
if len(defaults) == 0 {
|
|
return nil
|
|
}
|
|
|
|
printComment(w, fmt.Sprintf("%s sets the appropriate defaults for %s", ProvideDefaultsMethodName, name), false)
|
|
fmt.Fprintf(w, "func (val *%[1]s) %[2]s() *%[1]s {\n", name, ProvideDefaultsMethodName)
|
|
fmt.Fprintf(w, "if val == nil {\n return nil\n}\n")
|
|
fmt.Fprintf(w, "tmp := *val\n")
|
|
for _, p := range defaults {
|
|
if p.DefaultValue != nil {
|
|
if isNilType(p.Type) {
|
|
fmt.Fprintf(w, "if tmp.%s == nil {\n", pkg.fieldName(nil, p))
|
|
} else {
|
|
fmt.Fprintf(w, "if %s.IsZero(tmp.%s) {\n", pkg.internalModuleName, pkg.fieldName(nil, p))
|
|
}
|
|
err := pkg.setDefaultValue(w, p.DefaultValue, codegen.UnwrapType(p.Type), func(w io.Writer, dv string) error {
|
|
pkg.assignProperty(w, p, "tmp", dv, !p.IsRequired(), useGenericTypes)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(w, "}\n")
|
|
} else if funcName := pkg.provideDefaultsFuncName(p.Type); funcName != "" {
|
|
var member string
|
|
if codegen.IsNOptionalInput(p.Type) {
|
|
// f := fmt.Sprintf("func(v %[1]s) %[1]s { return *v.%[2]s() }", name, funcName)
|
|
// member = fmt.Sprintf("tmp.%[1]s.ApplyT(%[2]s)", pkg.fieldName(nil, p), f)
|
|
} else {
|
|
member = fmt.Sprintf("tmp.%[1]s.%[2]s()", pkg.fieldName(nil, p), funcName)
|
|
sigil := ""
|
|
if p.IsRequired() {
|
|
sigil = "*"
|
|
}
|
|
pkg.assignProperty(w, p, "tmp", sigil+member, false, useGenericTypes)
|
|
}
|
|
fmt.Fprintln(w)
|
|
} else {
|
|
panic(fmt.Sprintf("Property %s[%s] should not be in the default list", p.Name, p.Type.String()))
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(w, "return &tmp\n}\n")
|
|
return nil
|
|
}
|
|
|
|
// The name of the method used to instantiate defaults.
|
|
const ProvideDefaultsMethodName = "Defaults"
|
|
|
|
func (pkg *pkgContext) provideDefaultsFuncName(typ schema.Type) string {
|
|
if !codegen.IsProvideDefaultsFuncRequired(typ) {
|
|
return ""
|
|
}
|
|
return ProvideDefaultsMethodName
|
|
}
|
|
|
|
func (pkg *pkgContext) genInputTypes(
|
|
w io.Writer,
|
|
t *schema.ObjectType,
|
|
details *typeDetails,
|
|
usingGenericTypes bool,
|
|
) error {
|
|
contract.Assertf(t.IsInputShape(), "Object type must have input shape")
|
|
|
|
name := pkg.tokenToType(t.Token)
|
|
|
|
// Generate the plain inputs.
|
|
if details.input {
|
|
if !usingGenericTypes {
|
|
pkg.genInputInterface(w, name)
|
|
}
|
|
|
|
inputName := name + "Args"
|
|
pkg.genInputArgsStruct(w, inputName, t, usingGenericTypes)
|
|
if !pkg.disableObjectDefaults {
|
|
if err := pkg.genObjectDefaultFunc(w, inputName, t.Properties, usingGenericTypes); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
pkg.genInputImplementation(w, name, inputName, name, details.ptrInput, usingGenericTypes)
|
|
}
|
|
|
|
// Generate the pointer input.
|
|
if details.ptrInput && !usingGenericTypes {
|
|
pkg.genInputInterface(w, name+"Ptr")
|
|
|
|
ptrTypeName := cgstrings.Camel(name) + "PtrType"
|
|
|
|
fmt.Fprintf(w, "type %s %sArgs\n\n", ptrTypeName, name)
|
|
|
|
fmt.Fprintf(w, "func %[1]sPtr(v *%[1]sArgs) %[1]sPtrInput {", name)
|
|
fmt.Fprintf(w, "\treturn (*%s)(v)\n", ptrTypeName)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
pkg.genInputImplementation(w, name+"Ptr", "*"+ptrTypeName, "*"+name, false, usingGenericTypes)
|
|
}
|
|
|
|
// Generate the array input.
|
|
if details.arrayInput && !pkg.names.Has(name+"Array") && !usingGenericTypes {
|
|
pkg.genInputInterface(w, name+"Array")
|
|
|
|
fmt.Fprintf(w, "type %[1]sArray []%[1]sInput\n\n", name)
|
|
|
|
pkg.genInputImplementation(w, name+"Array", name+"Array", "[]"+name, false, usingGenericTypes)
|
|
}
|
|
|
|
// Generate the map input.
|
|
if details.mapInput && !pkg.names.Has(name+"Map") && !usingGenericTypes {
|
|
pkg.genInputInterface(w, name+"Map")
|
|
|
|
fmt.Fprintf(w, "type %[1]sMap map[string]%[1]sInput\n\n", name)
|
|
|
|
pkg.genInputImplementation(w, name+"Map", name+"Map", "map[string]"+name, false, usingGenericTypes)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (pkg *pkgContext) genInputArgsStruct(
|
|
w io.Writer,
|
|
typeName string,
|
|
t *schema.ObjectType,
|
|
useGenericTypes bool,
|
|
) {
|
|
contract.Assertf(t.IsInputShape(), "Object type must have input shape")
|
|
|
|
printComment(w, t.Comment, false)
|
|
fmt.Fprintf(w, "type %s struct {\n", typeName)
|
|
for _, p := range t.Properties {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
|
|
inputType := pkg.typeString(p.Type)
|
|
if useGenericTypes {
|
|
if p.Plain {
|
|
inputType = pkg.plainGenericInputType(p.Type)
|
|
} else {
|
|
inputType = pkg.genericInputType(p.Type)
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", pkg.fieldName(nil, p), inputType, p.Name)
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
type genOutputTypesArgs struct {
|
|
t *schema.ObjectType
|
|
usingGenericTypes bool
|
|
|
|
// optional type name override
|
|
name string
|
|
output bool
|
|
}
|
|
|
|
func (pkg *pkgContext) genOutputTypes(w io.Writer, genArgs genOutputTypesArgs) {
|
|
t := genArgs.t
|
|
details := pkg.detailsForType(t)
|
|
|
|
contract.Assertf(!t.IsInputShape(), "Object type must not have input shape")
|
|
|
|
name := genArgs.name
|
|
if name == "" {
|
|
name = pkg.tokenToType(t.Token)
|
|
}
|
|
|
|
if details.output || genArgs.output {
|
|
printComment(w, t.Comment, false)
|
|
pkg.genOutputType(w,
|
|
name, /* baseName */
|
|
name, /* elementType */
|
|
details.ptrInput, /* ptrMethods */
|
|
genArgs.usingGenericTypes, /* usingGenericTypes */
|
|
)
|
|
|
|
for _, p := range t.Properties {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, false)
|
|
outputType, applyType := pkg.outputType(p.Type), pkg.typeString(p.Type)
|
|
if genArgs.usingGenericTypes {
|
|
outputType = pkg.genericOutputType(p.Type)
|
|
}
|
|
|
|
propName := pkg.fieldName(nil, p)
|
|
switch strings.ToLower(p.Name) {
|
|
case "elementtype", "issecret":
|
|
propName = "Get" + propName
|
|
}
|
|
fmt.Fprintf(w, "func (o %sOutput) %s() %s {\n", name, propName, outputType)
|
|
if !genArgs.usingGenericTypes {
|
|
fmt.Fprintf(w, "\treturn o.ApplyT(func (v %s) %s { return v.%s }).(%s)\n",
|
|
name, applyType, pkg.fieldName(nil, p), outputType)
|
|
} else {
|
|
needsCast := genericTypeNeedsExplicitCasting(outputType)
|
|
if !needsCast {
|
|
fmt.Fprintf(w, "\treturn pulumix.Apply[%s](o, func (v %s) %s { return v.%s })\n",
|
|
name, name, pkg.plainGenericInputType(p.Type), pkg.fieldName(nil, p))
|
|
} else {
|
|
fmt.Fprintf(w, "\tvalue := pulumix.Apply[%s](o, func (v %s) %s { return v.%s })\n",
|
|
name, name, pkg.plainGenericInputType(p.Type), pkg.fieldName(nil, p))
|
|
fmt.Fprintf(w, "\treturn %s{OutputState: value.OutputState}\n", outputType)
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
}
|
|
|
|
if details.ptrOutput && !genArgs.usingGenericTypes {
|
|
pkg.genPtrOutput(w, name, name)
|
|
|
|
for _, p := range t.Properties {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, false)
|
|
optionalType := codegen.OptionalType(p)
|
|
outputType, applyType := pkg.outputType(optionalType), pkg.typeString(optionalType)
|
|
deref := ""
|
|
// If the property was required, but the type it needs to return is an explicit pointer type, then we need
|
|
// to dereference it, unless it is a resource type which should remain a pointer.
|
|
_, isResourceType := p.Type.(*schema.ResourceType)
|
|
if p.IsRequired() && applyType[0] == '*' && !isResourceType {
|
|
deref = "&"
|
|
}
|
|
|
|
funcName := Title(p.Name)
|
|
// Avoid conflicts with Output interface for lifted attributes.
|
|
switch funcName {
|
|
case "IsSecret", "ElementType":
|
|
funcName = funcName + "Prop"
|
|
}
|
|
|
|
fmt.Fprintf(w, "func (o %sPtrOutput) %s() %s {\n", name, funcName, outputType)
|
|
fmt.Fprintf(w, "\treturn o.ApplyT(func (v *%s) %s {\n", name, applyType)
|
|
fmt.Fprintf(w, "\t\tif v == nil {\n")
|
|
fmt.Fprintf(w, "\t\t\treturn nil\n")
|
|
fmt.Fprintf(w, "\t\t}\n")
|
|
fmt.Fprintf(w, "\t\treturn %sv.%s\n", deref, pkg.fieldName(nil, p))
|
|
fmt.Fprintf(w, "\t}).(%s)\n", outputType)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
}
|
|
|
|
if details.arrayOutput && !pkg.names.Has(name+"Array") && !genArgs.usingGenericTypes {
|
|
pkg.genArrayOutput(w, name, name)
|
|
}
|
|
|
|
if details.mapOutput && !pkg.names.Has(name+"Map") && !genArgs.usingGenericTypes {
|
|
pkg.genMapOutput(w, name, name)
|
|
}
|
|
}
|
|
|
|
func goPrimitiveValue(value interface{}) (string, error) {
|
|
v := reflect.ValueOf(value)
|
|
if v.Kind() == reflect.Interface {
|
|
v = v.Elem()
|
|
}
|
|
|
|
//nolint:exhaustive // Only a subset of types have a default value.
|
|
switch v.Kind() {
|
|
case reflect.Bool:
|
|
if v.Bool() {
|
|
return "true", nil
|
|
}
|
|
return "false", nil
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
|
|
return strconv.FormatInt(v.Int(), 10), nil
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
|
return strconv.FormatUint(v.Uint(), 10), nil
|
|
case reflect.Float32, reflect.Float64:
|
|
value := strconv.FormatFloat(v.Float(), 'f', -1, 64)
|
|
if !strings.ContainsRune(value, '.') {
|
|
value += ".0"
|
|
}
|
|
return value, nil
|
|
case reflect.String:
|
|
return fmt.Sprintf("%q", v.String()), nil
|
|
default:
|
|
return "", fmt.Errorf("unsupported default value of type %T", value)
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) getConstValue(cv interface{}) (string, error) {
|
|
var val string
|
|
if cv != nil {
|
|
v, err := goPrimitiveValue(cv)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
val = v
|
|
}
|
|
|
|
return val, nil
|
|
}
|
|
|
|
// setDefaultValue generates a statement that assigns the default value of a
|
|
// property to a variable.
|
|
//
|
|
// The assign function is invoked with an expression that evaluates to the
|
|
// default value.
|
|
// It should return a statement that assigns that default value to the relevant
|
|
// variable.
|
|
// For example,
|
|
//
|
|
// err := pkg.setDefaultValue(w, dv, t, func(w io.Writer, value string) error {
|
|
// _, err := fmt.Fprintf(w, "v.%s = %s", fieldName, value)
|
|
// return err
|
|
// })
|
|
func (pkg *pkgContext) setDefaultValue(
|
|
w io.Writer,
|
|
dv *schema.DefaultValue,
|
|
t schema.Type,
|
|
assign func(io.Writer, string) error,
|
|
) error {
|
|
contract.Requiref(dv.Value != nil || len(dv.Environment) > 0,
|
|
"dv", "must have either a value or an environment variable override")
|
|
|
|
var val string
|
|
if dv.Value != nil {
|
|
v, err := goPrimitiveValue(dv.Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
val = v
|
|
switch t.(type) {
|
|
case *schema.EnumType:
|
|
typeName := strings.TrimSuffix(pkg.typeString(codegen.UnwrapType(t)), "Input")
|
|
val = fmt.Sprintf("%s(%s)", typeName, val)
|
|
}
|
|
}
|
|
|
|
if len(dv.Environment) == 0 {
|
|
// If there's no environment variable override,
|
|
// assign and we're done.
|
|
return assign(w, val)
|
|
}
|
|
|
|
// For environment variable override, we will assign only
|
|
// if the environment variable is set.
|
|
|
|
parser, typ := "nil", "string"
|
|
switch codegen.UnwrapType(t).(type) {
|
|
case *schema.ArrayType:
|
|
parser, typ = pkg.internalModuleName+".ParseEnvStringArray", "pulumi.StringArray"
|
|
}
|
|
switch t {
|
|
case schema.BoolType:
|
|
parser, typ = pkg.internalModuleName+".ParseEnvBool", "bool"
|
|
case schema.IntType:
|
|
parser, typ = pkg.internalModuleName+".ParseEnvInt", "int"
|
|
case schema.NumberType:
|
|
parser, typ = pkg.internalModuleName+".ParseEnvFloat", "float64"
|
|
}
|
|
|
|
if val == "" {
|
|
// If there's no explicit default value,
|
|
// use nil so that we can assign conditionally.
|
|
val = "nil"
|
|
}
|
|
|
|
// Roughly, we generate:
|
|
//
|
|
// if d := internal.getEnvOrDefault(defaultValue, parser, "ENV_VAR"); d != nil {
|
|
// $assign(d.(type))
|
|
// }
|
|
//
|
|
// This has the following effect:
|
|
//
|
|
// - if an environment variable was set, read from that
|
|
// - if a default value was specified, use that
|
|
// - otherwise, leave the variable unset
|
|
fmt.Fprintf(w, "if d := %s.GetEnvOrDefault(%s, %s", pkg.internalModuleName, val, parser)
|
|
for _, e := range dv.Environment {
|
|
fmt.Fprintf(w, ", %q", e)
|
|
}
|
|
fmt.Fprintf(w, "); d != nil {\n\t")
|
|
if err := assign(w, fmt.Sprintf("d.(%v)", typ)); err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(w, "}\n")
|
|
return nil
|
|
}
|
|
|
|
func (pkg *pkgContext) genResource(
|
|
w io.Writer,
|
|
r *schema.Resource,
|
|
generateResourceContainerTypes bool,
|
|
useGenericVariant bool,
|
|
) error {
|
|
name := disambiguatedResourceName(r, pkg)
|
|
printCommentWithDeprecationMessage(w, r.Comment, r.DeprecationMessage, false)
|
|
fmt.Fprintf(w, "type %s struct {\n", name)
|
|
|
|
switch {
|
|
case r.IsProvider:
|
|
fmt.Fprintf(w, "\tpulumi.ProviderResourceState\n\n")
|
|
case r.IsComponent:
|
|
fmt.Fprintf(w, "\tpulumi.ResourceState\n\n")
|
|
default:
|
|
fmt.Fprintf(w, "\tpulumi.CustomResourceState\n\n")
|
|
}
|
|
|
|
var secretProps []*schema.Property
|
|
var secretInputProps []*schema.Property
|
|
|
|
for _, p := range r.Properties {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
|
|
outputType := pkg.outputType(p.Type)
|
|
if useGenericVariant {
|
|
outputType = pkg.genericOutputType(p.Type)
|
|
}
|
|
|
|
fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", pkg.fieldName(r, p), outputType, p.Name)
|
|
|
|
if p.Secret {
|
|
secretProps = append(secretProps, p)
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
// Create a constructor function that registers a new instance of this resource.
|
|
fmt.Fprintf(w, "// New%s registers a new resource with the given unique name, arguments, and options.\n", name)
|
|
fmt.Fprintf(w, "func New%s(ctx *pulumi.Context,\n", name)
|
|
fmt.Fprintf(w, "\tname string, args *%[1]sArgs, opts ...pulumi.ResourceOption) (*%[1]s, error) {\n", name)
|
|
|
|
// Ensure required arguments are present.
|
|
hasRequired := false
|
|
for _, p := range r.InputProperties {
|
|
if p.IsRequired() {
|
|
hasRequired = true
|
|
}
|
|
}
|
|
|
|
// Various validation checks
|
|
fmt.Fprintf(w, "\tif args == nil {\n")
|
|
if !hasRequired {
|
|
fmt.Fprintf(w, "\t\targs = &%sArgs{}\n", name)
|
|
} else {
|
|
fmt.Fprintln(w, "\t\treturn nil, errors.New(\"missing one or more required arguments\")")
|
|
}
|
|
fmt.Fprintf(w, "\t}\n\n")
|
|
|
|
// Produce the inputs.
|
|
|
|
// Check all required inputs are present
|
|
for _, p := range r.InputProperties {
|
|
if p.IsRequired() && isNilType(p.Type) && p.DefaultValue == nil {
|
|
fmt.Fprintf(w, "\tif args.%s == nil {\n", pkg.fieldName(r, p))
|
|
fmt.Fprintf(w, "\t\treturn nil, errors.New(\"invalid value for required argument '%s'\")\n", pkg.fieldName(r, p))
|
|
fmt.Fprintf(w, "\t}\n")
|
|
}
|
|
|
|
if p.Secret {
|
|
secretInputProps = append(secretInputProps, p)
|
|
}
|
|
}
|
|
|
|
assign := func(w io.Writer, p *schema.Property, value string) {
|
|
pkg.assignProperty(w, p, "args", value, isNilType(p.Type), useGenericVariant)
|
|
}
|
|
|
|
for _, p := range r.InputProperties {
|
|
if p.ConstValue != nil {
|
|
v, err := pkg.getConstValue(p.ConstValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
assign(w, p, v)
|
|
} else if p.DefaultValue != nil {
|
|
if isNilType(p.Type) {
|
|
fmt.Fprintf(w, "\tif args.%s == nil {\n", pkg.fieldName(r, p))
|
|
} else {
|
|
fmt.Fprintf(w, "\tif %s.IsZero(args.%s) {\n", pkg.internalModuleName, pkg.fieldName(r, p))
|
|
}
|
|
|
|
err := pkg.setDefaultValue(w, p.DefaultValue, codegen.UnwrapType(p.Type), func(w io.Writer, dv string) error {
|
|
assign(w, p, dv)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(w, "\t}\n")
|
|
} else if name := pkg.provideDefaultsFuncName(p.Type); name != "" && !pkg.disableObjectDefaults {
|
|
optionalDeref := ""
|
|
if p.IsRequired() {
|
|
optionalDeref = "*"
|
|
}
|
|
|
|
toOutputMethod := pkg.toOutputMethod(p.Type)
|
|
outputType := pkg.outputType(p.Type)
|
|
resolvedType := pkg.typeString(codegen.ResolvedType(p.Type))
|
|
originalValue := fmt.Sprintf("args.%s.%s()", pkg.fieldName(r, p), toOutputMethod)
|
|
valueWithDefaults := fmt.Sprintf("%[1]v.ApplyT(func (v %[2]s) %[2]s { return %[3]sv.%[4]s() }).(%[5]s)",
|
|
originalValue, resolvedType, optionalDeref, name, outputType)
|
|
if p.Plain {
|
|
valueWithDefaults = fmt.Sprintf("args.%v.Defaults()", pkg.fieldName(r, p))
|
|
}
|
|
|
|
if useGenericVariant {
|
|
fmt.Fprintf(w, "if args.%s != nil {\n", pkg.fieldName(r, p))
|
|
t := p.Type
|
|
optionalPointer := ""
|
|
if isOptionalType(reduceInputType(t)) && !isArrayType(codegen.UnwrapType(t)) && !isMapType(codegen.UnwrapType(t)) {
|
|
optionalPointer = "*"
|
|
}
|
|
|
|
inputType := pkg.genericInputTypeImpl(t)
|
|
if strings.HasPrefix(inputType, "*") {
|
|
optionalPointer = ""
|
|
}
|
|
|
|
fmt.Fprintf(w, "args.%s = pulumix.Apply(args.%s, func(o %s%s) %s%s { return o.Defaults() })\n",
|
|
pkg.fieldName(r, p),
|
|
pkg.fieldName(r, p),
|
|
optionalPointer,
|
|
inputType,
|
|
optionalPointer,
|
|
inputType)
|
|
|
|
fmt.Fprintf(w, "}\n")
|
|
} else {
|
|
if !p.IsRequired() {
|
|
fmt.Fprintf(w, "if args.%s != nil {\n", pkg.fieldName(r, p))
|
|
fmt.Fprintf(w, "args.%[1]s = %s\n", pkg.fieldName(r, p), valueWithDefaults)
|
|
fmt.Fprint(w, "}\n")
|
|
} else {
|
|
fmt.Fprintf(w, "args.%[1]s = %s\n", pkg.fieldName(r, p), valueWithDefaults)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set any defined aliases.
|
|
if len(r.Aliases) > 0 {
|
|
fmt.Fprintf(w, "\taliases := pulumi.Aliases([]pulumi.Alias{\n")
|
|
for _, alias := range r.Aliases {
|
|
s := "\t\t{\n"
|
|
if alias.Type != nil {
|
|
s += fmt.Sprintf("\t\t\tType: pulumi.String(%q),\n", *alias.Type)
|
|
}
|
|
s += "\t\t},\n"
|
|
fmt.Fprint(w, s)
|
|
}
|
|
fmt.Fprintf(w, "\t})\n")
|
|
fmt.Fprintf(w, "\topts = append(opts, aliases)\n")
|
|
}
|
|
|
|
// Setup secrets
|
|
for _, p := range secretInputProps {
|
|
fmt.Fprintf(w, "\tif args.%s != nil {\n", pkg.fieldName(r, p))
|
|
|
|
if !useGenericVariant {
|
|
fmt.Fprintf(w, "\t\targs.%[1]s = pulumi.ToSecret(args.%[1]s).(%[2]s)\n",
|
|
pkg.fieldName(r, p),
|
|
pkg.typeString(p.Type))
|
|
} else {
|
|
fmt.Fprintf(w, "\t\tuntypedSecretValue := pulumi.ToSecret(args.%s.ToOutput(ctx.Context()).Untyped())\n",
|
|
pkg.fieldName(r, p))
|
|
|
|
t := p.Type
|
|
optionalPointer := ""
|
|
if isOptionalType(reduceInputType(t)) && !isArrayType(codegen.UnwrapType(t)) && !isMapType(codegen.UnwrapType(t)) {
|
|
optionalPointer = "*"
|
|
}
|
|
|
|
inputType := pkg.genericInputTypeImpl(t)
|
|
if strings.HasPrefix(inputType, "*") {
|
|
optionalPointer = ""
|
|
}
|
|
fmt.Fprintf(w, "\t\targs.%s = pulumix.MustConvertTyped[%s%s](untypedSecretValue)\n",
|
|
pkg.fieldName(r, p),
|
|
optionalPointer,
|
|
inputType)
|
|
}
|
|
|
|
fmt.Fprintf(w, "\t}\n")
|
|
}
|
|
if len(secretProps) > 0 {
|
|
fmt.Fprintf(w, "\tsecrets := pulumi.AdditionalSecretOutputs([]string{\n")
|
|
for _, sp := range secretProps {
|
|
fmt.Fprintf(w, "\t\t\t%q,\n", sp.Name)
|
|
}
|
|
fmt.Fprintf(w, "\t})\n")
|
|
fmt.Fprintf(w, "\topts = append(opts, secrets)\n")
|
|
}
|
|
|
|
// Setup replaceOnChange
|
|
replaceOnChangesProps, errList := r.ReplaceOnChanges()
|
|
for _, err := range errList {
|
|
cmdutil.Diag().Warningf(&diag.Diag{Message: err.Error()})
|
|
}
|
|
replaceOnChangesStrings := schema.PropertyListJoinToString(replaceOnChangesProps,
|
|
func(x string) string { return x })
|
|
if len(replaceOnChangesProps) > 0 {
|
|
fmt.Fprint(w, "\treplaceOnChanges := pulumi.ReplaceOnChanges([]string{\n")
|
|
for _, p := range replaceOnChangesStrings {
|
|
fmt.Fprintf(w, "\t\t%q,\n", p)
|
|
}
|
|
fmt.Fprint(w, "\t})\n")
|
|
fmt.Fprint(w, "\topts = append(opts, replaceOnChanges)\n")
|
|
}
|
|
|
|
err := pkg.GenPkgDefaultsOptsCall(w, false /*invoke*/)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If this is a parameterized resource we need the package ref.
|
|
def, err := pkg.pkg.Definition()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
assignment := ":="
|
|
packageRef := ""
|
|
packageArg := ""
|
|
if def.Parameterization != nil {
|
|
assignment = "="
|
|
packageRef = "Package"
|
|
packageArg = "ref, "
|
|
err = pkg.GenPkgGetPackageRefCall(w, "nil")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Finally make the call to registration.
|
|
fmt.Fprintf(w, "\tvar resource %s\n", name)
|
|
component := ""
|
|
if r.IsComponent {
|
|
component = "RemoteComponent"
|
|
}
|
|
|
|
fmt.Fprintf(w, "\terr %s ctx.Register%s%sResource(\"%s\", name, args, &resource, %sopts...)\n",
|
|
assignment, packageRef, component, r.Token, packageArg)
|
|
fmt.Fprintf(w, "\tif err != nil {\n")
|
|
fmt.Fprintf(w, "\t\treturn nil, err\n")
|
|
fmt.Fprintf(w, "\t}\n")
|
|
fmt.Fprintf(w, "\treturn &resource, nil\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
// Emit a factory function that reads existing instances of this resource.
|
|
if !r.IsProvider && !r.IsComponent {
|
|
fmt.Fprintf(w, "// Get%[1]s gets an existing %[1]s resource's state with the given name, ID, and optional\n", name)
|
|
fmt.Fprintf(w, "// state properties that are used to uniquely qualify the lookup (nil if not required).\n")
|
|
fmt.Fprintf(w, "func Get%s(ctx *pulumi.Context,\n", name)
|
|
fmt.Fprintf(w, "\tname string, id pulumi.IDInput, state *%[1]sState, opts ...pulumi.ResourceOption) (*%[1]s, error) {\n", name)
|
|
fmt.Fprintf(w, "\tvar resource %s\n", name)
|
|
|
|
// If this is a parameterized resource we need the package ref.
|
|
def, err := pkg.pkg.Definition()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
assignment := ":="
|
|
packageRef := ""
|
|
packageArg := ""
|
|
if def.Parameterization != nil {
|
|
assignment = "="
|
|
packageRef = "Package"
|
|
packageArg = "ref, "
|
|
err = pkg.GenPkgGetPackageRefCall(w, "nil")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(w, "\terr %s ctx.Read%sResource(\"%s\", name, id, state, &resource, %sopts...)\n",
|
|
assignment, packageRef, r.Token, packageArg)
|
|
fmt.Fprintf(w, "\tif err != nil {\n")
|
|
fmt.Fprintf(w, "\t\treturn nil, err\n")
|
|
fmt.Fprintf(w, "\t}\n")
|
|
fmt.Fprintf(w, "\treturn &resource, nil\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
// Emit the state types for get methods.
|
|
fmt.Fprintf(w, "// Input properties used for looking up and filtering %s resources.\n", name)
|
|
fmt.Fprintf(w, "type %sState struct {\n", cgstrings.Camel(name))
|
|
if r.StateInputs != nil {
|
|
for _, p := range r.StateInputs.Properties {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
|
|
fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", pkg.fieldName(r, p), pkg.typeString(codegen.ResolvedType(codegen.OptionalType(p))), p.Name)
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "type %sState struct {\n", name)
|
|
if r.StateInputs != nil {
|
|
for _, p := range r.StateInputs.Properties {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
|
|
inputType := pkg.inputType(p.Type)
|
|
if useGenericVariant {
|
|
inputType = pkg.genericInputType(codegen.OptionalType(p))
|
|
}
|
|
fmt.Fprintf(w, "\t%s %s\n", pkg.fieldName(r, p), inputType)
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (%sState) ElementType() reflect.Type {\n", name)
|
|
fmt.Fprintf(w, "\treturn reflect.TypeOf((*%sState)(nil)).Elem()\n", cgstrings.Camel(name))
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
// Emit the args types.
|
|
fmt.Fprintf(w, "type %sArgs struct {\n", cgstrings.Camel(name))
|
|
for _, p := range r.InputProperties {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
|
|
inputTypeName := pkg.typeString(codegen.ResolvedType(p.Type))
|
|
fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", pkg.fieldName(r, p), inputTypeName, p.Name)
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "// The set of arguments for constructing a %s resource.\n", name)
|
|
fmt.Fprintf(w, "type %sArgs struct {\n", name)
|
|
for _, p := range r.InputProperties {
|
|
typ := p.Type
|
|
if p.Plain {
|
|
typ = codegen.MapOptionalType(typ, func(typ schema.Type) schema.Type {
|
|
if input, ok := typ.(*schema.InputType); ok {
|
|
return input.ElementType
|
|
}
|
|
return typ
|
|
})
|
|
}
|
|
|
|
inputTypeName := pkg.inputType(typ)
|
|
if p.Plain {
|
|
inputTypeName = pkg.typeString(typ)
|
|
}
|
|
|
|
if useGenericVariant {
|
|
if p.Plain {
|
|
inputTypeName = pkg.typeString(codegen.ResolvedType(typ))
|
|
} else {
|
|
inputTypeName = pkg.genericInputType(typ)
|
|
}
|
|
}
|
|
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
|
|
fmt.Fprintf(w, "\t%s %s\n", pkg.fieldName(r, p), inputTypeName)
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (%sArgs) ElementType() reflect.Type {\n", name)
|
|
fmt.Fprintf(w, "\treturn reflect.TypeOf((*%sArgs)(nil)).Elem()\n", cgstrings.Camel(name))
|
|
fmt.Fprintf(w, "}\n")
|
|
|
|
// Emit resource methods.
|
|
for _, method := range r.Methods {
|
|
methodName := Title(method.Name)
|
|
f := method.Function
|
|
|
|
var objectReturnType *schema.ObjectType
|
|
if f.ReturnType != nil {
|
|
if objectType, ok := f.ReturnType.(*schema.ObjectType); ok && objectType != nil {
|
|
objectReturnType = objectType
|
|
}
|
|
}
|
|
|
|
liftReturn := pkg.liftSingleValueMethodReturns && objectReturnType != nil && len(objectReturnType.Properties) == 1
|
|
|
|
var args []*schema.Property
|
|
if f.Inputs != nil {
|
|
for _, arg := range f.Inputs.InputShape.Properties {
|
|
if arg.Name == "__self__" {
|
|
continue
|
|
}
|
|
args = append(args, arg)
|
|
}
|
|
}
|
|
|
|
// Now emit the method signature.
|
|
argsig := "ctx *pulumi.Context"
|
|
if len(args) > 0 {
|
|
argsig = fmt.Sprintf("%s, args *%s%sArgs", argsig, name, methodName)
|
|
}
|
|
var retty string
|
|
if f.ReturnTypePlain {
|
|
if objectReturnType == nil {
|
|
t := pkg.typeString(codegen.ResolvedType(f.ReturnType))
|
|
retty = fmt.Sprintf("(o %s, e error)", t)
|
|
} else {
|
|
retty = fmt.Sprintf("(o %s%sResult, e error)", name, methodName)
|
|
}
|
|
} else if objectReturnType == nil {
|
|
retty = "error"
|
|
} else if liftReturn {
|
|
if useGenericVariant {
|
|
retty = fmt.Sprintf("(%s, error)", pkg.genericOutputType(objectReturnType.Properties[0].Type))
|
|
} else {
|
|
retty = fmt.Sprintf("(%s, error)", pkg.outputType(objectReturnType.Properties[0].Type))
|
|
}
|
|
} else {
|
|
retty = fmt.Sprintf("(%s%sResultOutput, error)", name, methodName)
|
|
}
|
|
fmt.Fprintf(w, "\n")
|
|
printCommentWithDeprecationMessage(w, f.Comment, f.DeprecationMessage, false)
|
|
fmt.Fprintf(w, "func (r *%s) %s(%s) %s {\n", name, methodName, argsig, retty)
|
|
|
|
resultVar := "_"
|
|
if objectReturnType != nil {
|
|
resultVar = "out"
|
|
}
|
|
|
|
// Make a map of inputs to pass to the runtime function.
|
|
inputsVar := "nil"
|
|
if len(args) > 0 {
|
|
inputsVar = "args"
|
|
}
|
|
|
|
// Now simply invoke the runtime function with the arguments.
|
|
outputsType := "pulumi.AnyOutput"
|
|
if objectReturnType != nil || f.ReturnTypePlain {
|
|
if liftReturn {
|
|
outputsType = fmt.Sprintf("%s%sResultOutput", cgstrings.Camel(name), methodName)
|
|
} else {
|
|
outputsType = fmt.Sprintf("%s%sResultOutput", name, methodName)
|
|
}
|
|
}
|
|
|
|
// If this is a parameterized resource we need the package ref.
|
|
def, err := pkg.pkg.Definition()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
packageRef := ""
|
|
packageArg := ""
|
|
if def.Parameterization != nil {
|
|
packageRef = "Package"
|
|
packageArg = ", ref"
|
|
err = pkg.GenPkgGetPackageRefCall(w, outputsType+"{}")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if !f.ReturnTypePlain {
|
|
fmt.Fprintf(w, "\t%s, err := ctx.Call%s(%q, %s, %s{}, r%s)\n",
|
|
resultVar, packageRef, f.Token, inputsVar, outputsType, packageArg)
|
|
}
|
|
|
|
if f.ReturnTypePlain {
|
|
// single-value returning methods use a magic property "res" on the wire
|
|
property := ""
|
|
if objectReturnType == nil {
|
|
property = cgstrings.UppercaseFirst("res")
|
|
}
|
|
fmt.Fprintf(w, "\tinternal.CallPlain(ctx, %q, %s, %s{}, r, %q, reflect.ValueOf(&o), &e)\n",
|
|
f.Token, inputsVar, outputsType, property)
|
|
fmt.Fprintf(w, "\treturn\n")
|
|
} else if objectReturnType == nil {
|
|
fmt.Fprintf(w, "\treturn err\n")
|
|
} else if liftReturn {
|
|
// Check the error before proceeding.
|
|
fmt.Fprintf(w, "\tif err != nil {\n")
|
|
if useGenericVariant {
|
|
fmt.Fprint(w, "\t\treturn nil, err\n")
|
|
} else {
|
|
fmt.Fprintf(w, "\t\treturn %s{}, err\n", pkg.outputType(objectReturnType.Properties[0].Type))
|
|
}
|
|
|
|
fmt.Fprintf(w, "\t}\n")
|
|
|
|
// Get the name of the method to return the output
|
|
fmt.Fprintf(w, "\treturn %s.(%s).%s(), nil\n", resultVar, cgstrings.Camel(outputsType), Title(objectReturnType.Properties[0].Name))
|
|
} else {
|
|
// Check the error before proceeding.
|
|
fmt.Fprintf(w, "\tif err != nil {\n")
|
|
fmt.Fprintf(w, "\t\treturn %s{}, err\n", outputsType)
|
|
fmt.Fprintf(w, "\t}\n")
|
|
|
|
// Return the result.
|
|
fmt.Fprintf(w, "\treturn %s.(%s), nil\n", resultVar, outputsType)
|
|
}
|
|
fmt.Fprintf(w, "}\n")
|
|
|
|
// If there are argument and/or return types, emit them.
|
|
if len(args) > 0 {
|
|
fmt.Fprintf(w, "\n")
|
|
fmt.Fprintf(w, "type %s%sArgs struct {\n", cgstrings.Camel(name), methodName)
|
|
for _, p := range args {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
|
|
inputTypeName := pkg.typeString(codegen.ResolvedType(p.Type))
|
|
if useGenericVariant {
|
|
inputTypeName = pkg.genericInputType(codegen.ResolvedType(p.Type))
|
|
}
|
|
fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", pkg.fieldName(nil, p), inputTypeName, p.Name)
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "// The set of arguments for the %s method of the %s resource.\n", methodName, name)
|
|
fmt.Fprintf(w, "type %s%sArgs struct {\n", name, methodName)
|
|
for _, p := range args {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
|
|
inputTypeName := pkg.typeString(p.Type)
|
|
if useGenericVariant {
|
|
inputTypeName = pkg.genericInputType(codegen.ResolvedType(p.Type))
|
|
}
|
|
fmt.Fprintf(w, "\t%s %s\n", pkg.fieldName(nil, p), inputTypeName)
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (%s%sArgs) ElementType() reflect.Type {\n", name, methodName)
|
|
fmt.Fprintf(w, "\treturn reflect.TypeOf((*%s%sArgs)(nil)).Elem()\n", cgstrings.Camel(name), methodName)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
if objectReturnType != nil || f.ReturnTypePlain {
|
|
outputStructName := name
|
|
|
|
var comment string
|
|
var properties []*schema.Property
|
|
if f.ReturnTypePlain && objectReturnType == nil {
|
|
properties = []*schema.Property{
|
|
{
|
|
Name: "res",
|
|
Type: f.ReturnType,
|
|
Plain: true,
|
|
},
|
|
}
|
|
} else {
|
|
properties = objectReturnType.Properties
|
|
comment = objectReturnType.Comment
|
|
}
|
|
|
|
// Don't export the result struct if we're lifting the value
|
|
if liftReturn {
|
|
outputStructName = cgstrings.Camel(name)
|
|
}
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
pkg.genPlainType(w, fmt.Sprintf("%s%sResult", outputStructName, methodName), comment, "", properties)
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
fmt.Fprintf(w, "type %s%sResultOutput struct{ *pulumi.OutputState }\n\n", outputStructName, methodName)
|
|
|
|
fmt.Fprintf(w, "func (%s%sResultOutput) ElementType() reflect.Type {\n", outputStructName, methodName)
|
|
fmt.Fprintf(w, "\treturn reflect.TypeOf((*%s%sResult)(nil)).Elem()\n", outputStructName, methodName)
|
|
fmt.Fprintf(w, "}\n")
|
|
|
|
for _, p := range properties {
|
|
fmt.Fprintf(w, "\n")
|
|
outputTypeName := pkg.outputType(p.Type)
|
|
if useGenericVariant {
|
|
outputTypeName = pkg.genericOutputType(p.Type)
|
|
}
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, false)
|
|
fmt.Fprintf(w, "func (o %s%sResultOutput) %s() %s {\n", outputStructName, methodName, Title(p.Name),
|
|
outputTypeName)
|
|
if !useGenericVariant {
|
|
fmt.Fprintf(w, "\treturn o.ApplyT(func (v %s%sResult) %s { return v.%s }).(%s)\n", outputStructName, methodName,
|
|
pkg.typeString(codegen.ResolvedType(p.Type)), Title(p.Name), outputTypeName)
|
|
} else {
|
|
fmt.Fprintf(w, "\treturn pulumix.Apply(o, func(v %s%sResult) %s { return v.%s })\n", outputStructName, methodName,
|
|
pkg.typeString(codegen.ResolvedType(p.Type)), Title(p.Name))
|
|
}
|
|
|
|
fmt.Fprintf(w, "}\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
if !useGenericVariant {
|
|
// Emit the resource input type.
|
|
fmt.Fprintf(w, "\n")
|
|
fmt.Fprintf(w, "type %sInput interface {\n", name)
|
|
fmt.Fprintf(w, "\tpulumi.Input\n\n")
|
|
fmt.Fprintf(w, "\tTo%[1]sOutput() %[1]sOutput\n", name)
|
|
fmt.Fprintf(w, "\tTo%[1]sOutputWithContext(ctx context.Context) %[1]sOutput\n", name)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
pkg.genInputImplementation(w, name, "*"+name, "*"+name, false, false)
|
|
|
|
if generateResourceContainerTypes && !r.IsProvider {
|
|
// Generate the resource array input.
|
|
pkg.genInputInterface(w, name+"Array")
|
|
fmt.Fprintf(w, "type %[1]sArray []%[1]sInput\n\n", name)
|
|
pkg.genInputImplementation(w, name+"Array", name+"Array", "[]*"+name, false, false)
|
|
|
|
// Generate the resource map input.
|
|
pkg.genInputInterface(w, name+"Map")
|
|
fmt.Fprintf(w, "type %[1]sMap map[string]%[1]sInput\n\n", name)
|
|
pkg.genInputImplementation(w, name+"Map", name+"Map", "map[string]*"+name, false, false)
|
|
}
|
|
}
|
|
|
|
outputElementType := "*" + name
|
|
if useGenericVariant {
|
|
outputElementType = name
|
|
}
|
|
pkg.genOutputType(w, name, outputElementType, false, useGenericVariant)
|
|
|
|
// Emit chaining methods for the resource output type.
|
|
for _, p := range r.Properties {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, false)
|
|
outputType := pkg.outputType(p.Type)
|
|
if useGenericVariant {
|
|
outputType = pkg.genericOutputType(p.Type)
|
|
}
|
|
|
|
propName := pkg.fieldName(r, p)
|
|
switch strings.ToLower(p.Name) {
|
|
case "elementtype", "issecret":
|
|
propName = "Get" + propName
|
|
}
|
|
fmt.Fprintf(w, "func (o %sOutput) %s() %s {\n", name, propName, outputType)
|
|
if !useGenericVariant {
|
|
fmt.Fprintf(w, "\treturn o.ApplyT(func (v *%s) %s { return v.%s }).(%s)\n",
|
|
name, outputType, pkg.fieldName(r, p), outputType)
|
|
} else {
|
|
needsCast := genericTypeNeedsExplicitCasting(outputType)
|
|
|
|
elementType := pkg.typeString(codegen.ResolvedType(p.Type))
|
|
|
|
if strings.HasPrefix(outputType, "pulumix.GPtrOutput") && !strings.HasPrefix(elementType, "*") {
|
|
elementType = "*" + elementType
|
|
}
|
|
|
|
isOptionalAssetOrArchive := isOptionalType(reduceInputType(p.Type)) &&
|
|
isAssetOrArchive(codegen.UnwrapType(p.Type))
|
|
if isOptionalAssetOrArchive && !strings.HasPrefix(elementType, "*") {
|
|
elementType = "*" + elementType
|
|
}
|
|
|
|
if needsCast {
|
|
// needs an explicit cast operation to align the types
|
|
fmt.Fprintf(w, "\tvalue := pulumix.Apply[%s](o, func (v %s) %s { return v.%s })\n",
|
|
name, name, outputType, pkg.fieldName(r, p))
|
|
fmt.Fprintf(w, "\tunwrapped := pulumix.Flatten[%s, %s](value)\n",
|
|
elementType, outputType)
|
|
fmt.Fprintf(w, "\treturn %s{OutputState: unwrapped.OutputState}\n", outputType)
|
|
} else {
|
|
fmt.Fprintf(w, "\tvalue := pulumix.Apply[%s](o, func (v %s) %s { return v.%s })\n",
|
|
name, name, outputType, pkg.fieldName(r, p))
|
|
if !p.Plain {
|
|
fmt.Fprintf(w, "\treturn pulumix.Flatten[%s, %s](value)\n",
|
|
elementType, outputType)
|
|
} else {
|
|
fmt.Fprintf(w, "\treturn value\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
if generateResourceContainerTypes && !r.IsProvider && !useGenericVariant {
|
|
pkg.genArrayOutput(w, name, "*"+name)
|
|
pkg.genMapOutput(w, name, "*"+name)
|
|
}
|
|
|
|
pkg.genResourceRegistrations(w, r, generateResourceContainerTypes, useGenericVariant)
|
|
|
|
return nil
|
|
}
|
|
|
|
func goPackageInfo(packageReference schema.PackageReference) GoPackageInfo {
|
|
if packageReference == nil {
|
|
return GoPackageInfo{}
|
|
}
|
|
|
|
def, err := packageReference.Definition()
|
|
contract.AssertNoErrorf(err, "Could not load definition for %q", packageReference.Name())
|
|
contract.AssertNoErrorf(def.ImportLanguages(map[string]schema.Language{"go": Importer}),
|
|
"Could not import languages")
|
|
if info, ok := def.Language["go"].(GoPackageInfo); ok {
|
|
if info.Generics == "" {
|
|
info.Generics = GenericsSettingNone
|
|
}
|
|
return info
|
|
}
|
|
return GoPackageInfo{}
|
|
}
|
|
|
|
func NeedsGoOutputVersion(f *schema.Function) bool {
|
|
goInfo := goPackageInfo(f.PackageReference)
|
|
|
|
if goInfo.DisableFunctionOutputVersions {
|
|
return false
|
|
}
|
|
|
|
return f.ReturnType != nil
|
|
}
|
|
|
|
func (pkg *pkgContext) genFunctionCodeFile(f *schema.Function) (string, error) {
|
|
importsAndAliases := map[string]string{}
|
|
pkg.getImports(f, importsAndAliases)
|
|
importsAndAliases["github.com/pulumi/pulumi/sdk/v3/go/pulumi"] = ""
|
|
importsAndAliases[path.Join(pkg.importBasePath, pkg.internalModuleName)] = ""
|
|
buffer := &bytes.Buffer{}
|
|
goInfo := goPackageInfo(pkg.pkg)
|
|
var imports []string
|
|
if f.ReturnType != nil {
|
|
imports = []string{"context", "reflect"}
|
|
if goInfo.Generics == GenericsSettingSideBySide {
|
|
importsAndAliases["github.com/pulumi/pulumi/sdk/v3/go/pulumix"] = ""
|
|
}
|
|
}
|
|
|
|
pkg.genHeader(buffer, imports, importsAndAliases, false /* isUtil */)
|
|
emitGenericVariant := false
|
|
if err := pkg.genFunction(buffer, f, emitGenericVariant); err != nil {
|
|
return "", err
|
|
}
|
|
pkg.genFunctionOutputVersion(buffer, f, emitGenericVariant)
|
|
return buffer.String(), nil
|
|
}
|
|
|
|
func (pkg *pkgContext) genGenericVariantFunctionCodeFile(f *schema.Function) (string, error) {
|
|
importsAndAliases := map[string]string{}
|
|
pkg.getImports(f, importsAndAliases)
|
|
importsAndAliases["github.com/pulumi/pulumi/sdk/v3/go/pulumi"] = ""
|
|
if f.NeedsOutputVersion() {
|
|
importsAndAliases["github.com/pulumi/pulumi/sdk/v3/go/pulumix"] = ""
|
|
}
|
|
|
|
importsAndAliases[path.Join(pkg.importBasePath, pkg.internalModuleName)] = ""
|
|
buffer := &bytes.Buffer{}
|
|
|
|
var imports []string
|
|
if NeedsGoOutputVersion(f) {
|
|
imports = []string{"context", "reflect"}
|
|
}
|
|
|
|
pkg.genHeader(buffer, imports, importsAndAliases, false /* isUtil */)
|
|
useGenericTypes := true
|
|
if err := pkg.genFunction(buffer, f, useGenericTypes); err != nil {
|
|
return "", err
|
|
}
|
|
pkg.genFunctionOutputVersion(buffer, f, useGenericTypes)
|
|
return buffer.String(), nil
|
|
}
|
|
|
|
func (pkg *pkgContext) genFunction(w io.Writer, f *schema.Function, useGenericTypes bool) error {
|
|
name := pkg.functionName(f)
|
|
|
|
if f.MultiArgumentInputs {
|
|
return fmt.Errorf("go SDK-gen does not implement MultiArgumentInputs for function '%s'", f.Token)
|
|
}
|
|
|
|
var objectReturnType *schema.ObjectType
|
|
if f.ReturnType != nil {
|
|
if objectType, ok := f.ReturnType.(*schema.ObjectType); ok {
|
|
objectReturnType = objectType
|
|
} else {
|
|
// TODO: remove when we add support for generalized return type for go
|
|
return fmt.Errorf("go sdk-gen doesn't support non-Object return types for function %s", f.Token)
|
|
}
|
|
}
|
|
|
|
printCommentWithDeprecationMessage(w, f.Comment, f.DeprecationMessage, false)
|
|
|
|
// Now, emit the function signature.
|
|
argsig := "ctx *pulumi.Context"
|
|
if f.Inputs != nil {
|
|
argsig = fmt.Sprintf("%s, args *%sArgs", argsig, name)
|
|
}
|
|
var retty string
|
|
if objectReturnType == nil {
|
|
retty = "error"
|
|
} else {
|
|
retty = fmt.Sprintf("(*%sResult, error)", name)
|
|
}
|
|
fmt.Fprintf(w, "func %s(%s, opts ...pulumi.InvokeOption) %s {\n", name, argsig, retty)
|
|
|
|
// Make a map of inputs to pass to the runtime function.
|
|
var inputsVar string
|
|
if f.Inputs == nil {
|
|
inputsVar = "nil"
|
|
} else if codegen.IsProvideDefaultsFuncRequired(f.Inputs) && !pkg.disableObjectDefaults {
|
|
inputsVar = "args.Defaults()"
|
|
} else {
|
|
inputsVar = "args"
|
|
}
|
|
|
|
// Now simply invoke the runtime function with the arguments.
|
|
var outputsType string
|
|
if objectReturnType == nil {
|
|
outputsType = "struct{}"
|
|
} else {
|
|
outputsType = name + "Result"
|
|
}
|
|
|
|
err := pkg.GenPkgDefaultsOptsCall(w, true /*invoke*/)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If this is a parameterized resource we need the package ref.
|
|
def, err := pkg.pkg.Definition()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
assignment := ":="
|
|
packageRef := ""
|
|
packageArg := ""
|
|
if def.Parameterization != nil {
|
|
assignment = "="
|
|
packageRef = "Package"
|
|
packageArg = "ref, "
|
|
err = pkg.GenPkgGetPackageRefCall(w, "nil")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(w, "\tvar rv %s\n", outputsType)
|
|
fmt.Fprintf(w, "\terr %s ctx.Invoke%s(\"%s\", %s, &rv, %sopts...)\n",
|
|
assignment, packageRef, f.Token, inputsVar, packageArg)
|
|
|
|
if objectReturnType == nil {
|
|
fmt.Fprintf(w, "\treturn err\n")
|
|
} else {
|
|
// Check the error before proceeding.
|
|
fmt.Fprintf(w, "\tif err != nil {\n")
|
|
fmt.Fprintf(w, "\t\treturn nil, err\n")
|
|
fmt.Fprintf(w, "\t}\n")
|
|
|
|
// Return the result.
|
|
var retValue string
|
|
if codegen.IsProvideDefaultsFuncRequired(objectReturnType) && !pkg.disableObjectDefaults {
|
|
retValue = "rv.Defaults()"
|
|
} else {
|
|
retValue = "&rv"
|
|
}
|
|
fmt.Fprintf(w, "\treturn %s, nil\n", retValue)
|
|
}
|
|
fmt.Fprintf(w, "}\n")
|
|
|
|
// If there are argument and/or return types, emit them.
|
|
if f.Inputs != nil {
|
|
fmt.Fprintf(w, "\n")
|
|
fnInputsName := pkg.functionArgsTypeName(f)
|
|
pkg.genPlainType(w, fnInputsName, f.Inputs.Comment, "", f.Inputs.Properties)
|
|
if codegen.IsProvideDefaultsFuncRequired(f.Inputs) && !pkg.disableObjectDefaults {
|
|
if err := pkg.genObjectDefaultFunc(w, fnInputsName, f.Inputs.Properties, useGenericTypes); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if objectReturnType != nil {
|
|
fmt.Fprintf(w, "\n")
|
|
fnOutputsName := pkg.functionResultTypeName(f)
|
|
pkg.genPlainType(w, fnOutputsName, objectReturnType.Comment, "", objectReturnType.Properties)
|
|
if codegen.IsProvideDefaultsFuncRequired(objectReturnType) && !pkg.disableObjectDefaults {
|
|
if err := pkg.genObjectDefaultFunc(w, fnOutputsName, objectReturnType.Properties, useGenericTypes); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (pkg *pkgContext) functionName(f *schema.Function) string {
|
|
// If the function starts with New or Get, it will conflict; so rename them.
|
|
name, hasName := pkg.functionNames[f]
|
|
|
|
if !hasName {
|
|
panic(fmt.Sprintf("No function name found for %v", f))
|
|
}
|
|
|
|
return name
|
|
}
|
|
|
|
func (pkg *pkgContext) functionArgsTypeName(f *schema.Function) string {
|
|
name := pkg.functionName(f)
|
|
return name + "Args"
|
|
}
|
|
|
|
func (pkg *pkgContext) functionResultTypeName(f *schema.Function) string {
|
|
name := pkg.functionName(f)
|
|
return name + "Result"
|
|
}
|
|
|
|
func genericTypeNeedsExplicitCasting(outputType string) bool {
|
|
return strings.HasPrefix(outputType, "pulumix.ArrayOutput") ||
|
|
strings.HasPrefix(outputType, "pulumix.MapOutput") ||
|
|
strings.HasPrefix(outputType, "pulumix.GPtrOutput") ||
|
|
strings.HasPrefix(outputType, "pulumix.GArrayOutput") ||
|
|
strings.HasPrefix(outputType, "pulumix.GMapOutput")
|
|
}
|
|
|
|
func (pkg *pkgContext) genFunctionOutputGenericVersion(w io.Writer, f *schema.Function) {
|
|
originalName := pkg.functionName(f)
|
|
name := originalName + "Output"
|
|
originalResultTypeName := pkg.functionResultTypeName(f)
|
|
resultTypeName := originalResultTypeName + "Output"
|
|
|
|
var code string
|
|
|
|
if f.Inputs != nil {
|
|
code = `
|
|
func ${fn}Output(ctx *pulumi.Context, args ${fn}OutputArgs, opts ...pulumi.InvokeOption) ${outputType} {
|
|
outputResult := pulumix.ApplyErr[*${fn}Args](args.ToOutput(), func(plainArgs *${fn}Args) (*${fn}Result, error) {
|
|
return ${fn}(ctx, plainArgs, opts...)
|
|
})
|
|
|
|
return pulumix.Cast[${outputType}, *${fn}Result](outputResult)
|
|
}
|
|
`
|
|
} else {
|
|
code = `
|
|
func ${fn}Output(ctx *pulumi.Context, opts ...pulumi.InvokeOption) ${outputType} {
|
|
outputResult := pulumix.ApplyErr[int](pulumix.Val(0), func(_ int) (*${fn}Result, error) {
|
|
return ${fn}(ctx, opts...)
|
|
})
|
|
|
|
return pulumix.Cast[${outputType}, *${fn}Result](outputResult)
|
|
}
|
|
`
|
|
}
|
|
|
|
code = strings.ReplaceAll(code, "${fn}", originalName)
|
|
code = strings.ReplaceAll(code, "${outputType}", resultTypeName)
|
|
fmt.Fprint(w, code)
|
|
|
|
if f.Inputs != nil {
|
|
useGenericTypes := true
|
|
pkg.genInputArgsStruct(w, name+"Args", f.Inputs.InputShape, useGenericTypes)
|
|
|
|
receiverType := name + "Args"
|
|
plainType := originalName + "Args"
|
|
|
|
fmt.Fprintf(w, "func (args %s) ToOutput() pulumix.Output[*%s] {\n", receiverType, plainType)
|
|
fmt.Fprint(w, "\tallArgs := pulumix.All(\n")
|
|
for i, p := range f.Inputs.Properties {
|
|
fmt.Fprintf(w, "\t\targs.%s.ToOutput(context.Background()).AsAny()", pkg.fieldName(nil, p))
|
|
if i < len(f.Inputs.Properties)-1 {
|
|
fmt.Fprint(w, ",\n")
|
|
}
|
|
}
|
|
fmt.Fprint(w, ")\n")
|
|
|
|
fmt.Fprintf(w, "\treturn pulumix.Apply[[]any](allArgs, func(resolvedArgs []interface{}) *%s {\n", plainType)
|
|
fmt.Fprintf(w, "\t\treturn &%s{\n", plainType)
|
|
for i, p := range f.Inputs.Properties {
|
|
fmt.Fprintf(w, "\t\t\t%s: resolvedArgs[%d].(%s),\n",
|
|
pkg.fieldName(nil, p),
|
|
i,
|
|
pkg.typeString(p.Type))
|
|
}
|
|
fmt.Fprintf(w, "\t\t}\n")
|
|
fmt.Fprintf(w, "\t})\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
var objectReturnType *schema.ObjectType
|
|
if f.ReturnType != nil {
|
|
if objectType, ok := f.ReturnType.(*schema.ObjectType); ok && objectType != nil {
|
|
objectReturnType = objectType
|
|
}
|
|
}
|
|
|
|
if objectReturnType != nil {
|
|
fmt.Fprintf(w, "type %sOutput struct { *pulumi.OutputState }\n\n", originalResultTypeName)
|
|
|
|
fmt.Fprintf(w, "func (%sOutput) ElementType() reflect.Type {\n", originalResultTypeName)
|
|
fmt.Fprintf(w, "\treturn reflect.TypeOf((*%s)(nil)).Elem()\n", originalResultTypeName)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (o %sOutput) ToOutput(context.Context) pulumix.Output[*%s] {\n",
|
|
originalResultTypeName,
|
|
originalResultTypeName)
|
|
fmt.Fprintf(w, "\treturn pulumix.Output[*%s]{\n", originalResultTypeName)
|
|
fmt.Fprint(w, "\t\tOutputState: o.OutputState,\n")
|
|
fmt.Fprint(w, "\t}\n")
|
|
fmt.Fprint(w, "}\n\n")
|
|
|
|
// generate accessors for each property of the output
|
|
for _, p := range objectReturnType.Properties {
|
|
outputType := pkg.genericOutputType(p.Type)
|
|
|
|
fmt.Fprintf(w, "func (o %s) %s() %s {\n", resultTypeName, pkg.fieldName(nil, p), outputType)
|
|
|
|
needsCast := genericTypeNeedsExplicitCasting(outputType)
|
|
|
|
if !needsCast {
|
|
fmt.Fprintf(w, "\treturn pulumix.Apply[*%s](o, func (v *%s) %s { return v.%s })\n",
|
|
originalResultTypeName,
|
|
originalResultTypeName,
|
|
pkg.typeString(p.Type),
|
|
pkg.fieldName(nil, p))
|
|
} else {
|
|
fmt.Fprintf(w, "\tvalue := pulumix.Apply[*%s](o, func (v *%s) %s { return v.%s })\n",
|
|
originalResultTypeName,
|
|
originalResultTypeName,
|
|
pkg.typeString(p.Type),
|
|
pkg.fieldName(nil, p))
|
|
|
|
fmt.Fprintf(w, "\treturn %s{\n", outputType)
|
|
fmt.Fprintf(w, "\t\tOutputState: value.OutputState,\n")
|
|
fmt.Fprintf(w, "\t}\n")
|
|
}
|
|
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) genFunctionOutputVersion(w io.Writer, f *schema.Function, useGenericTypes bool) {
|
|
if f.ReturnType == nil {
|
|
return
|
|
}
|
|
|
|
if useGenericTypes {
|
|
pkg.genFunctionOutputGenericVersion(w, f)
|
|
return
|
|
}
|
|
|
|
originalName := pkg.functionName(f)
|
|
name := originalName + "Output"
|
|
originalResultTypeName := pkg.functionResultTypeName(f)
|
|
resultTypeName := originalResultTypeName + "Output"
|
|
|
|
var code string
|
|
|
|
var inputsVar string
|
|
if f.Inputs == nil {
|
|
inputsVar = "nil"
|
|
} else if codegen.IsProvideDefaultsFuncRequired(f.Inputs) && !pkg.disableObjectDefaults {
|
|
inputsVar = "args.Defaults()"
|
|
} else {
|
|
inputsVar = "args"
|
|
}
|
|
|
|
if f.Inputs != nil {
|
|
code = `
|
|
func ${fn}Output(ctx *pulumi.Context, args ${fn}OutputArgs, opts ...pulumi.InvokeOption) ${outputType} {
|
|
return pulumi.ToOutputWithContext(context.Background(), args).
|
|
ApplyT(func(v interface{}) (${outputType}, error) {
|
|
args := v.(${fn}Args)
|
|
opts = ${internalModule}.PkgInvokeDefaultOpts(opts)
|
|
var rv ${fn}Result
|
|
secret, err := ctx.InvokePackageRaw("${token}", ${args}, &rv, "", opts...)
|
|
if err != nil {
|
|
return ${outputType}{}, err
|
|
}
|
|
|
|
output := pulumi.ToOutput(rv).(${outputType})
|
|
if secret {
|
|
return pulumi.ToSecret(output).(${outputType}), nil
|
|
}
|
|
return output, nil
|
|
}).(${outputType})
|
|
}
|
|
|
|
`
|
|
} else {
|
|
code = `
|
|
func ${fn}Output(ctx *pulumi.Context, opts ...pulumi.InvokeOption) ${outputType} {
|
|
return pulumi.ToOutput(0).ApplyT(func(int) (${outputType}, error) {
|
|
opts = ${internalModule}.PkgInvokeDefaultOpts(opts)
|
|
var rv ${fn}Result
|
|
secret, err := ctx.InvokePackageRaw("${token}", nil, &rv, "", opts...)
|
|
if err != nil {
|
|
return ${outputType}{}, err
|
|
}
|
|
|
|
output := pulumi.ToOutput(rv).(${outputType})
|
|
if secret {
|
|
return pulumi.ToSecret(output).(${outputType}), nil
|
|
}
|
|
return output, nil
|
|
}).(${outputType})
|
|
}
|
|
|
|
`
|
|
}
|
|
|
|
code = strings.ReplaceAll(code, "${fn}", originalName)
|
|
code = strings.ReplaceAll(code, "${outputType}", resultTypeName)
|
|
code = strings.ReplaceAll(code, "${token}", f.Token)
|
|
code = strings.ReplaceAll(code, "${args}", inputsVar)
|
|
code = strings.ReplaceAll(code, "${internalModule}", pkg.internalModuleName)
|
|
fmt.Fprint(w, code)
|
|
|
|
if f.Inputs != nil {
|
|
pkg.genInputArgsStruct(w, name+"Args", f.Inputs.InputShape, false /*emitGenericVariant*/)
|
|
|
|
genInputImplementationWithArgs(w, genInputImplementationArgs{
|
|
name: name + "Args",
|
|
receiverType: name + "Args",
|
|
elementType: pkg.functionArgsTypeName(f),
|
|
usingGenericTypes: useGenericTypes,
|
|
})
|
|
}
|
|
if f.ReturnType != nil {
|
|
if objectType, ok := f.ReturnType.(*schema.ObjectType); ok && objectType != nil {
|
|
pkg.genOutputTypes(w, genOutputTypesArgs{
|
|
t: objectType,
|
|
name: originalResultTypeName,
|
|
output: true,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Assuming the file represented by `w` only has one function,
|
|
// generate an `init()` for Output type init.
|
|
initCode := `
|
|
func init() {
|
|
pulumi.RegisterOutputType(${outputType}{})
|
|
}
|
|
|
|
`
|
|
initCode = strings.ReplaceAll(initCode, "${outputType}", resultTypeName)
|
|
fmt.Fprint(w, initCode)
|
|
}
|
|
|
|
type objectProperty struct {
|
|
object *schema.ObjectType
|
|
property *schema.Property
|
|
}
|
|
|
|
// When computing the type name for a field of an object type, we must ensure that we do not generate invalid recursive
|
|
// struct types. A struct type T contains invalid recursion if the closure of its fields and its struct-typed fields'
|
|
// fields includes a field of type T. A few examples:
|
|
//
|
|
// Directly invalid:
|
|
//
|
|
// type T struct {
|
|
// Invalid T
|
|
// }
|
|
//
|
|
// Indirectly invalid:
|
|
//
|
|
// type T struct {
|
|
// Invalid S
|
|
// }
|
|
//
|
|
// type S struct {
|
|
// Invalid T
|
|
// }
|
|
//
|
|
// In order to avoid generating invalid struct types, we replace all references to types involved in a cyclical
|
|
// definition with *T. The examples above therefore become:
|
|
//
|
|
// (1)
|
|
//
|
|
// type T struct {
|
|
// Valid *T
|
|
// }
|
|
//
|
|
// (2)
|
|
//
|
|
// type T struct {
|
|
// Valid *S
|
|
// }
|
|
//
|
|
// type S struct {
|
|
// Valid *T
|
|
// }
|
|
//
|
|
// We do this using a rewriter that turns all fields involved in reference cycles into optional fields.
|
|
func rewriteCyclicField(rewritten codegen.Set, path []objectProperty, op objectProperty) {
|
|
// If this property refers to an Input<> type, unwrap the type. This ensures that the plain and input shapes of an
|
|
// object type remain identical.
|
|
t := op.property.Type
|
|
if inputType, isInputType := op.property.Type.(*schema.InputType); isInputType {
|
|
t = inputType.ElementType
|
|
}
|
|
|
|
// If this property does not refer to an object type, it cannot be involved in a cycle. Skip it.
|
|
objectType, isObjectType := t.(*schema.ObjectType)
|
|
if !isObjectType {
|
|
return
|
|
}
|
|
|
|
path = append(path, op)
|
|
|
|
// Check the current path for cycles by crawling backwards until reaching the start of the path
|
|
// or finding a property that is a member of the current object type.
|
|
var cycle []objectProperty
|
|
for i := len(path) - 1; i > 0; i-- {
|
|
if path[i].object == objectType {
|
|
cycle = path[i:]
|
|
break
|
|
}
|
|
}
|
|
|
|
// If the current path does not involve a cycle, recur into the current object type.
|
|
if len(cycle) == 0 {
|
|
rewriteCyclicFields(rewritten, path, objectType)
|
|
return
|
|
}
|
|
|
|
// If we've found a cycle, mark each property involved in the cycle as optional.
|
|
//
|
|
// NOTE: this overestimates the set of properties that must be marked as optional. For example, in case (2) above,
|
|
// only one of T.Invalid or S.Invalid needs to be marked as optional in order to break the cycle. However, choosing
|
|
// a minimal set of properties that is also deterministic and resilient to changes in visit order is difficult and
|
|
// seems to add little value.
|
|
for _, p := range cycle {
|
|
p.property.Type = codegen.OptionalType(p.property)
|
|
}
|
|
}
|
|
|
|
func rewriteCyclicFields(rewritten codegen.Set, path []objectProperty, obj *schema.ObjectType) {
|
|
if !rewritten.Has(obj) {
|
|
rewritten.Add(obj)
|
|
for _, property := range obj.Properties {
|
|
rewriteCyclicField(rewritten, path, objectProperty{obj, property})
|
|
}
|
|
}
|
|
}
|
|
|
|
func rewriteCyclicObjectFields(pkg *schema.Package) {
|
|
rewritten := codegen.Set{}
|
|
for _, t := range pkg.Types {
|
|
if obj, ok := t.(*schema.ObjectType); ok && !obj.IsInputShape() {
|
|
rewriteCyclicFields(rewritten, nil, obj)
|
|
rewriteCyclicFields(rewritten, nil, obj.InputShape)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) genType(w io.Writer, obj *schema.ObjectType, usingGenericTypes bool) error {
|
|
contract.Assertf(!obj.IsInputShape(), "Object type must not have input shape")
|
|
if obj.IsOverlay {
|
|
// This type is generated by the provider, so no further action is required.
|
|
return nil
|
|
}
|
|
|
|
plainName := pkg.tokenToType(obj.Token)
|
|
if !usingGenericTypes {
|
|
pkg.genPlainType(w, plainName, obj.Comment, "", obj.Properties)
|
|
} else {
|
|
pkg.genGenericPlainType(w, plainName, obj.Comment, "", obj.Properties)
|
|
}
|
|
|
|
if !pkg.disableObjectDefaults {
|
|
if err := pkg.genObjectDefaultFunc(w, plainName, obj.Properties, usingGenericTypes); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := pkg.genInputTypes(w, obj.InputShape, pkg.detailsForType(obj), usingGenericTypes); err != nil {
|
|
return err
|
|
}
|
|
pkg.genOutputTypes(w, genOutputTypesArgs{
|
|
t: obj,
|
|
usingGenericTypes: usingGenericTypes,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (pkg *pkgContext) addSuffixesToName(typ schema.Type, name string) []string {
|
|
var names []string
|
|
details := pkg.detailsForType(typ)
|
|
if details.arrayInput {
|
|
names = append(names, name+"ArrayInput")
|
|
}
|
|
if details.arrayOutput || details.arrayInput {
|
|
names = append(names, name+"ArrayOutput")
|
|
}
|
|
if details.mapInput {
|
|
names = append(names, name+"MapInput")
|
|
}
|
|
if details.mapOutput || details.mapInput {
|
|
names = append(names, name+"MapOutput")
|
|
}
|
|
return names
|
|
}
|
|
|
|
type nestedTypeInfo struct {
|
|
resolvedElementType string
|
|
names map[string]bool
|
|
}
|
|
|
|
func innerMostType(t schema.Type) schema.Type {
|
|
switch t := t.(type) {
|
|
case *schema.ArrayType:
|
|
return innerMostType(t.ElementType)
|
|
case *schema.MapType:
|
|
return innerMostType(t.ElementType)
|
|
case *schema.OptionalType:
|
|
return innerMostType(t.ElementType)
|
|
case *schema.InputType:
|
|
return innerMostType(t.ElementType)
|
|
default:
|
|
return t
|
|
}
|
|
}
|
|
|
|
// collectNestedCollectionTypes builds a deduped mapping of element types -> associated collection types.
|
|
// different shapes of known types can resolve to the same element type. by collecting types in one step and emitting types
|
|
// in a second step, we avoid collision and redeclaration.
|
|
func (pkg *pkgContext) collectNestedCollectionTypes(types map[string]*nestedTypeInfo, typ schema.Type) {
|
|
var elementTypeName string
|
|
var names []string
|
|
switch t := typ.(type) {
|
|
case *schema.ArrayType:
|
|
// Builtins already cater to primitive arrays
|
|
if schema.IsPrimitiveType(innerMostType(t.ElementType)) {
|
|
return
|
|
}
|
|
elementTypeName = pkg.nestedTypeToType(t.ElementType)
|
|
elementTypeName = strings.TrimSuffix(elementTypeName, "Args") + "Array"
|
|
|
|
// We make sure that subsidiary elements are marked for array as well
|
|
details := pkg.detailsForType(t)
|
|
pkg.detailsForType(t.ElementType).markArray(details.arrayInput, details.arrayOutput)
|
|
|
|
names = pkg.addSuffixesToName(t, elementTypeName)
|
|
defer pkg.collectNestedCollectionTypes(types, t.ElementType)
|
|
case *schema.MapType:
|
|
// Builtins already cater to primitive maps
|
|
if schema.IsPrimitiveType(innerMostType(t.ElementType)) {
|
|
return
|
|
}
|
|
elementTypeName = pkg.nestedTypeToType(t.ElementType)
|
|
elementTypeName = strings.TrimSuffix(elementTypeName, "Args") + "Map"
|
|
|
|
// We make sure that subsidiary elements are marked for map as well
|
|
details := pkg.detailsForType(t)
|
|
pkg.detailsForType(t.ElementType).markMap(details.mapInput, details.mapOutput)
|
|
|
|
names = pkg.addSuffixesToName(t, elementTypeName)
|
|
defer pkg.collectNestedCollectionTypes(types, t.ElementType)
|
|
default:
|
|
return
|
|
}
|
|
nti, ok := types[elementTypeName]
|
|
if !ok {
|
|
nti = &nestedTypeInfo{
|
|
names: map[string]bool{},
|
|
resolvedElementType: pkg.typeString(codegen.ResolvedType(typ)),
|
|
}
|
|
types[elementTypeName] = nti
|
|
}
|
|
for _, n := range names {
|
|
nti.names[n] = true
|
|
}
|
|
}
|
|
|
|
// genNestedCollectionTypes emits nested collection types given the deduped mapping of element types -> associated collection types.
|
|
// different shapes of known types can resolve to the same element type. by collecting types in one step and emitting types
|
|
// in a second step, we avoid collision and redeclaration.
|
|
func (pkg *pkgContext) genNestedCollectionTypes(w io.Writer, types map[string]*nestedTypeInfo) []string {
|
|
var names []string
|
|
|
|
// map iteration is unstable so sort items for deterministic codegen
|
|
sortedElems := []string{}
|
|
for k := range types {
|
|
sortedElems = append(sortedElems, k)
|
|
}
|
|
sort.Strings(sortedElems)
|
|
|
|
for _, elementTypeName := range sortedElems {
|
|
info := types[elementTypeName]
|
|
|
|
collectionTypes := []string{}
|
|
for k := range info.names {
|
|
collectionTypes = append(collectionTypes, k)
|
|
}
|
|
sort.Strings(collectionTypes)
|
|
for _, name := range collectionTypes {
|
|
names = append(names, name)
|
|
switch {
|
|
case strings.HasSuffix(name, "ArrayInput"):
|
|
name = strings.TrimSuffix(name, "Input")
|
|
fmt.Fprintf(w, "type %s []%sInput\n\n", name, elementTypeName)
|
|
pkg.genInputImplementation(w, name, name, "[]"+info.resolvedElementType, false, false)
|
|
|
|
pkg.genInputInterface(w, name)
|
|
case strings.HasSuffix(name, "ArrayOutput"):
|
|
pkg.genArrayOutput(w, strings.TrimSuffix(name, "ArrayOutput"), info.resolvedElementType)
|
|
case strings.HasSuffix(name, "MapInput"):
|
|
name = strings.TrimSuffix(name, "Input")
|
|
fmt.Fprintf(w, "type %s map[string]%sInput\n\n", name, elementTypeName)
|
|
pkg.genInputImplementation(w, name, name, "map[string]"+info.resolvedElementType, false, false)
|
|
|
|
pkg.genInputInterface(w, name)
|
|
case strings.HasSuffix(name, "MapOutput"):
|
|
pkg.genMapOutput(w, strings.TrimSuffix(name, "MapOutput"), info.resolvedElementType)
|
|
}
|
|
}
|
|
}
|
|
|
|
return names
|
|
}
|
|
|
|
func (pkg *pkgContext) nestedTypeToType(typ schema.Type) string {
|
|
switch t := codegen.UnwrapType(typ).(type) {
|
|
case *schema.ArrayType:
|
|
return pkg.nestedTypeToType(t.ElementType) + "Array"
|
|
case *schema.MapType:
|
|
return pkg.nestedTypeToType(t.ElementType) + "Map"
|
|
case *schema.ObjectType:
|
|
return pkg.resolveObjectType(t)
|
|
case *schema.EnumType:
|
|
return pkg.resolveEnumType(t)
|
|
}
|
|
return strings.TrimSuffix(pkg.tokenToType(typ.String()), "Args")
|
|
}
|
|
|
|
func (pkg *pkgContext) genTypeRegistrations(
|
|
w io.Writer,
|
|
objTypes []*schema.ObjectType,
|
|
usingGenericTypes bool,
|
|
types ...string,
|
|
) {
|
|
fmt.Fprintf(w, "func init() {\n")
|
|
|
|
// Input types.
|
|
if !pkg.disableInputTypeRegistrations && !usingGenericTypes {
|
|
for _, obj := range objTypes {
|
|
if obj.IsOverlay {
|
|
// This type is generated by the provider, so no further action is required.
|
|
continue
|
|
}
|
|
name, details := pkg.tokenToType(obj.Token), pkg.detailsForType(obj)
|
|
if details.input {
|
|
fmt.Fprintf(w,
|
|
"\tpulumi.RegisterInputType(reflect.TypeOf((*%[1]sInput)(nil)).Elem(), %[1]sArgs{})\n", name)
|
|
}
|
|
if details.ptrInput {
|
|
fmt.Fprintf(w,
|
|
"\tpulumi.RegisterInputType(reflect.TypeOf((*%[1]sPtrInput)(nil)).Elem(), %[1]sArgs{})\n", name)
|
|
}
|
|
if details.arrayInput && !pkg.names.Has(name+"Array") {
|
|
fmt.Fprintf(w,
|
|
"\tpulumi.RegisterInputType(reflect.TypeOf((*%[1]sArrayInput)(nil)).Elem(), %[1]sArray{})\n", name)
|
|
}
|
|
if details.mapInput && !pkg.names.Has(name+"Map") {
|
|
fmt.Fprintf(w,
|
|
"\tpulumi.RegisterInputType(reflect.TypeOf((*%[1]sMapInput)(nil)).Elem(), %[1]sMap{})\n", name)
|
|
}
|
|
}
|
|
for _, t := range types {
|
|
if strings.HasSuffix(t, "Input") {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterInputType(reflect.TypeOf((*%s)(nil)).Elem(), %s{})\n", t, strings.TrimSuffix(t, "Input"))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Output types.
|
|
for _, obj := range objTypes {
|
|
if obj.IsOverlay {
|
|
// This type is generated by the provider, so no further action is required.
|
|
continue
|
|
}
|
|
name, details := pkg.tokenToType(obj.Token), pkg.detailsForType(obj)
|
|
if details.output {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sOutput{})\n", name)
|
|
}
|
|
if details.ptrOutput && !usingGenericTypes {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sPtrOutput{})\n", name)
|
|
}
|
|
if details.arrayOutput && !usingGenericTypes {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sArrayOutput{})\n", name)
|
|
}
|
|
if details.mapOutput && !usingGenericTypes {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sMapOutput{})\n", name)
|
|
}
|
|
}
|
|
for _, t := range types {
|
|
if strings.HasSuffix(t, "Output") {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%s{})\n", t)
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(w, "}\n")
|
|
}
|
|
|
|
func (pkg *pkgContext) genEnumRegistrations(w io.Writer) {
|
|
fmt.Fprintf(w, "func init() {\n")
|
|
// Register all input types
|
|
if !pkg.disableInputTypeRegistrations {
|
|
for _, e := range pkg.enums {
|
|
// Enums are guaranteed to have at least one element when they are
|
|
// bound into a schema.
|
|
contract.Assertf(len(e.Elements) > 0, "Enum must have at least one element")
|
|
name, details := pkg.tokenToEnum(e.Token), pkg.detailsForType(e)
|
|
instance := fmt.Sprintf("%#v", e.Elements[0].Value)
|
|
if details.input || details.ptrInput {
|
|
fmt.Fprintf(w,
|
|
"\tpulumi.RegisterInputType(reflect.TypeOf((*%[1]sInput)(nil)).Elem(), %[1]s(%[2]s))\n",
|
|
name, instance)
|
|
fmt.Fprintf(w,
|
|
"\tpulumi.RegisterInputType(reflect.TypeOf((*%[1]sPtrInput)(nil)).Elem(), %[1]s(%[2]s))\n",
|
|
name, instance)
|
|
}
|
|
if details.arrayInput {
|
|
fmt.Fprintf(w,
|
|
"\tpulumi.RegisterInputType(reflect.TypeOf((*%[1]sArrayInput)(nil)).Elem(), %[1]sArray{})\n",
|
|
name)
|
|
}
|
|
if details.mapInput {
|
|
fmt.Fprintf(w,
|
|
"\tpulumi.RegisterInputType(reflect.TypeOf((*%[1]sMapInput)(nil)).Elem(), %[1]sMap{})\n",
|
|
name)
|
|
}
|
|
}
|
|
}
|
|
// Register all output types
|
|
for _, e := range pkg.enums {
|
|
name, details := pkg.tokenToEnum(e.Token), pkg.detailsForType(e)
|
|
if details.output || details.ptrOutput {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sOutput{})\n", name)
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sPtrOutput{})\n", name)
|
|
}
|
|
if details.arrayOutput {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sArrayOutput{})\n", name)
|
|
}
|
|
if details.mapOutput {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sMapOutput{})\n", name)
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
func (pkg *pkgContext) genResourceRegistrations(
|
|
w io.Writer,
|
|
r *schema.Resource,
|
|
generateResourceContainerTypes bool,
|
|
usingGenericTypes bool,
|
|
) {
|
|
name := disambiguatedResourceName(r, pkg)
|
|
fmt.Fprintf(w, "func init() {\n")
|
|
// Register input type
|
|
if !pkg.disableInputTypeRegistrations && !usingGenericTypes {
|
|
fmt.Fprintf(w,
|
|
"\tpulumi.RegisterInputType(reflect.TypeOf((*%[1]sInput)(nil)).Elem(), &%[1]s{})\n",
|
|
name)
|
|
if generateResourceContainerTypes && !r.IsProvider {
|
|
fmt.Fprintf(w,
|
|
"\tpulumi.RegisterInputType(reflect.TypeOf((*%[1]sArrayInput)(nil)).Elem(), %[1]sArray{})\n",
|
|
name)
|
|
fmt.Fprintf(w,
|
|
"\tpulumi.RegisterInputType(reflect.TypeOf((*%[1]sMapInput)(nil)).Elem(), %[1]sMap{})\n",
|
|
name)
|
|
}
|
|
}
|
|
// Register all output types
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sOutput{})\n", name)
|
|
for _, method := range r.Methods {
|
|
var objectReturnType *schema.ObjectType
|
|
if method.Function.ReturnType != nil {
|
|
if objectType, ok := method.Function.ReturnType.(*schema.ObjectType); ok && objectType != nil {
|
|
objectReturnType = objectType
|
|
}
|
|
}
|
|
|
|
if objectReturnType != nil {
|
|
if pkg.liftSingleValueMethodReturns && len(objectReturnType.Properties) == 1 {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%s%sResultOutput{})\n", cgstrings.Camel(name), Title(method.Name))
|
|
} else {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%s%sResultOutput{})\n", name, Title(method.Name))
|
|
}
|
|
}
|
|
}
|
|
|
|
if generateResourceContainerTypes && !r.IsProvider && !usingGenericTypes {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sArrayOutput{})\n", name)
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sMapOutput{})\n", name)
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
func (pkg *pkgContext) getTypeImports(t schema.Type, recurse bool, importsAndAliases map[string]string, seen map[schema.Type]struct{}) {
|
|
if _, ok := seen[t]; ok {
|
|
return
|
|
}
|
|
seen[t] = struct{}{}
|
|
|
|
// Import an external type with `token` and return true.
|
|
// If the type is not external, return false.
|
|
importExternal := func(token string) bool {
|
|
if pkg.isExternalReference(t) {
|
|
extPkgCtx, _ := pkg.contextForExternalReference(t)
|
|
mod := extPkgCtx.tokenToPackage(token)
|
|
imp := path.Join(extPkgCtx.importBasePath, mod)
|
|
importsAndAliases[imp] = extPkgCtx.pkgImportAliases[imp]
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
switch t := t.(type) {
|
|
case *schema.OptionalType:
|
|
pkg.getTypeImports(t.ElementType, recurse, importsAndAliases, seen)
|
|
case *schema.InputType:
|
|
pkg.getTypeImports(t.ElementType, recurse, importsAndAliases, seen)
|
|
case *schema.EnumType:
|
|
if importExternal(t.Token) {
|
|
break
|
|
}
|
|
|
|
mod := pkg.tokenToPackage(t.Token)
|
|
if mod != pkg.mod {
|
|
p := path.Join(pkg.importBasePath, mod)
|
|
importsAndAliases[path.Join(pkg.importBasePath, mod)] = pkg.pkgImportAliases[p]
|
|
}
|
|
case *schema.ArrayType:
|
|
pkg.getTypeImports(t.ElementType, recurse, importsAndAliases, seen)
|
|
case *schema.MapType:
|
|
pkg.getTypeImports(t.ElementType, recurse, importsAndAliases, seen)
|
|
case *schema.ObjectType:
|
|
if importExternal(t.Token) {
|
|
break
|
|
}
|
|
|
|
mod := pkg.tokenToPackage(t.Token)
|
|
if mod != pkg.mod {
|
|
p := path.Join(pkg.importBasePath, mod)
|
|
importsAndAliases[path.Join(pkg.importBasePath, mod)] = pkg.pkgImportAliases[p]
|
|
}
|
|
|
|
if recurse {
|
|
for _, p := range t.Properties {
|
|
// We only recurse one level into objects, since we need to name
|
|
// their properties but not the properties named in their
|
|
// properties.
|
|
pkg.getTypeImports(p.Type, false, importsAndAliases, seen)
|
|
}
|
|
}
|
|
case *schema.ResourceType:
|
|
if importExternal(t.Token) {
|
|
break
|
|
}
|
|
mod := pkg.tokenToPackage(t.Token)
|
|
if mod != pkg.mod {
|
|
p := path.Join(pkg.importBasePath, mod)
|
|
importsAndAliases[path.Join(pkg.importBasePath, mod)] = pkg.pkgImportAliases[p]
|
|
}
|
|
case *schema.UnionType:
|
|
for _, e := range t.ElementTypes {
|
|
pkg.getTypeImports(e, recurse, importsAndAliases, seen)
|
|
}
|
|
}
|
|
}
|
|
|
|
func extractModulePath(extPkg schema.PackageReference) string {
|
|
var vPath string
|
|
version := extPkg.Version()
|
|
name := extPkg.Name()
|
|
if version != nil && version.Major > 1 {
|
|
vPath = fmt.Sprintf("/v%d", version.Major)
|
|
}
|
|
|
|
// Default to example.com/pulumi-pkg if we have no other information.
|
|
root := "example.com/pulumi-" + name
|
|
// But if we have a publisher use that instead, assuming it's from github
|
|
if extPkg.Publisher() != "" {
|
|
root = fmt.Sprintf("github.com/%s/pulumi-%s", extPkg.Publisher(), name)
|
|
}
|
|
// And if we have a repository, use that instead of the publisher
|
|
if extPkg.Repository() != "" {
|
|
url, err := url.Parse(extPkg.Repository())
|
|
if err == nil {
|
|
// If there's any errors parsing the URL ignore it. Else use the host and path as go doesn't expect http://
|
|
root = url.Host + url.Path
|
|
}
|
|
}
|
|
|
|
// Support pack sdks write a go mod inside the go folder. Old legacy sdks would manually write a go.mod in the sdk
|
|
// folder. This happened to mean that sdk/dotnet, sdk/nodejs etc where also considered part of the go sdk module.
|
|
if extPkg.SupportPack() {
|
|
return fmt.Sprintf("%s/sdk/go%s", root, vPath)
|
|
}
|
|
|
|
return fmt.Sprintf("%s/sdk%s", root, vPath)
|
|
}
|
|
|
|
func extractImportBasePath(extPkg schema.PackageReference) string {
|
|
modpath := extractModulePath(extPkg)
|
|
name := extPkg.Name()
|
|
|
|
// Support pack sdks write a go mod inside the go folder. Old legacy sdks would manually write a go.mod in the sdk
|
|
// folder. This happened to mean that sdk/dotnet, sdk/nodejs etc where also considered part of the go sdk module.
|
|
if extPkg.SupportPack() {
|
|
return fmt.Sprintf("%s/%s", modpath, goPackage(name))
|
|
}
|
|
|
|
return fmt.Sprintf("%s/go/%s", modpath, name)
|
|
}
|
|
|
|
func (pkg *pkgContext) getImports(member interface{}, importsAndAliases map[string]string) {
|
|
seen := map[schema.Type]struct{}{}
|
|
switch member := member.(type) {
|
|
case *schema.ObjectType:
|
|
pkg.getTypeImports(member, true, importsAndAliases, seen)
|
|
case *schema.ResourceType:
|
|
pkg.getTypeImports(member, true, importsAndAliases, seen)
|
|
case *schema.Resource:
|
|
for _, p := range member.Properties {
|
|
pkg.getTypeImports(p.Type, false, importsAndAliases, seen)
|
|
}
|
|
for _, p := range member.InputProperties {
|
|
pkg.getTypeImports(p.Type, false, importsAndAliases, seen)
|
|
|
|
if p.IsRequired() {
|
|
importsAndAliases["errors"] = ""
|
|
}
|
|
}
|
|
for _, method := range member.Methods {
|
|
if method.Function.Inputs != nil {
|
|
for _, p := range method.Function.Inputs.InputShape.Properties {
|
|
if p.Name == "__self__" {
|
|
continue
|
|
}
|
|
pkg.getTypeImports(p.Type, false, importsAndAliases, seen)
|
|
}
|
|
}
|
|
|
|
if method.Function.ReturnType != nil {
|
|
if objectType, ok := method.Function.ReturnType.(*schema.ObjectType); ok && objectType != nil {
|
|
for _, p := range objectType.Properties {
|
|
pkg.getTypeImports(p.Type, false, importsAndAliases, seen)
|
|
}
|
|
} else if method.Function.ReturnTypePlain {
|
|
pkg.getTypeImports(method.Function.ReturnType, false, importsAndAliases, seen)
|
|
}
|
|
}
|
|
}
|
|
case *schema.Function:
|
|
if member.Inputs != nil {
|
|
pkg.getTypeImports(member.Inputs, true, importsAndAliases, seen)
|
|
}
|
|
|
|
var returnType *schema.ObjectType
|
|
if member.ReturnType != nil {
|
|
if objectType, ok := member.ReturnType.(*schema.ObjectType); ok && objectType != nil {
|
|
returnType = objectType
|
|
}
|
|
}
|
|
|
|
if returnType != nil {
|
|
pkg.getTypeImports(returnType, true, importsAndAliases, seen)
|
|
}
|
|
case []*schema.Property:
|
|
for _, p := range member {
|
|
pkg.getTypeImports(p.Type, false, importsAndAliases, seen)
|
|
}
|
|
default:
|
|
return
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) genHeader(w io.Writer, goImports []string, importsAndAliases map[string]string, isUtil bool) {
|
|
fmt.Fprintf(w, "// Code generated by %v DO NOT EDIT.\n", pkg.tool)
|
|
fmt.Fprintf(w, "// *** WARNING: Do not edit by hand unless you're certain you know what you are doing! ***\n\n")
|
|
|
|
var pkgName string
|
|
if pkg.mod == "" {
|
|
if isUtil {
|
|
// we place pulumiVersion and pulumiUtilities in an ./internal folder
|
|
// the name of the folder can be overridden by the schema
|
|
// so we use the computed internalModuleName which defaults to "internal" if not set
|
|
pkgName = pkg.internalModuleName
|
|
} else {
|
|
def, err := pkg.pkg.Definition()
|
|
contract.AssertNoErrorf(err, "Could not retrieve definition")
|
|
pkgName = packageName(def)
|
|
}
|
|
} else {
|
|
pkgName = path.Base(pkg.mod)
|
|
}
|
|
|
|
fmt.Fprintf(w, "package %s\n\n", pkgName)
|
|
|
|
var imports []string
|
|
if len(importsAndAliases) > 0 {
|
|
for k := range importsAndAliases {
|
|
imports = append(imports, k)
|
|
}
|
|
sort.Strings(imports)
|
|
|
|
for i, k := range imports {
|
|
if alias := importsAndAliases[k]; alias != "" {
|
|
imports[i] = fmt.Sprintf(`%s "%s"`, alias, k)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(goImports) > 0 {
|
|
if len(imports) > 0 {
|
|
goImports = append(goImports, "")
|
|
}
|
|
imports = append(goImports, imports...)
|
|
}
|
|
if len(imports) > 0 {
|
|
fmt.Fprintf(w, "import (\n")
|
|
for _, i := range imports {
|
|
if i == "" {
|
|
fmt.Fprintf(w, "\n")
|
|
} else {
|
|
if strings.Contains(i, `"`) { // Imports with aliases already include quotes.
|
|
fmt.Fprintf(w, "\t%s\n", i)
|
|
} else {
|
|
fmt.Fprintf(w, "\t%q\n", i)
|
|
}
|
|
}
|
|
}
|
|
fmt.Fprintf(w, ")\n\n")
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) genConfig(w io.Writer, variables []*schema.Property) error {
|
|
importsAndAliases := map[string]string{
|
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config": "",
|
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi": "",
|
|
}
|
|
importsAndAliases[path.Join(pkg.importBasePath, pkg.internalModuleName)] = ""
|
|
pkg.genHeader(w, nil, importsAndAliases, false /* isUtil */)
|
|
|
|
// in case we're not using the internal package, assign to a blank var
|
|
fmt.Fprintf(w, "var _ = %s.GetEnvOrDefault\n", pkg.internalModuleName)
|
|
|
|
for _, p := range variables {
|
|
getfunc := "Get"
|
|
|
|
var getType string
|
|
var funcType string
|
|
switch codegen.UnwrapType(p.Type) {
|
|
case schema.BoolType:
|
|
getType, funcType = "bool", "Bool"
|
|
case schema.IntType:
|
|
getType, funcType = "int", "Int"
|
|
case schema.NumberType:
|
|
getType, funcType = "float64", "Float64"
|
|
default:
|
|
getType, funcType = "string", ""
|
|
}
|
|
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, false)
|
|
configKey := fmt.Sprintf("\"%s:%s\"", pkg.pkg.Name(), cgstrings.Camel(p.Name))
|
|
|
|
fmt.Fprintf(w, "func Get%s(ctx *pulumi.Context) %s {\n", Title(p.Name), getType)
|
|
if p.DefaultValue != nil {
|
|
fmt.Fprintf(w, "\tv, err := config.Try%s(ctx, %s)\n", funcType, configKey)
|
|
fmt.Fprintf(w, "\tif err == nil {\n")
|
|
fmt.Fprintf(w, "\t\treturn v\n")
|
|
fmt.Fprintf(w, "\t}\n")
|
|
|
|
fmt.Fprintf(w, "\tvar value %s\n", getType)
|
|
err := pkg.setDefaultValue(w, p.DefaultValue, codegen.UnwrapType(p.Type), func(w io.Writer, dv string) error {
|
|
_, err := fmt.Fprintf(w, "\tvalue = %s\n", dv)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Fprintf(w, "\treturn value\n")
|
|
} else {
|
|
fmt.Fprintf(w, "\treturn config.%s%s(ctx, %s)\n", getfunc, funcType, configKey)
|
|
}
|
|
fmt.Fprintf(w, "}\n")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// genResourceModule generates a ResourceModule definition and the code to register an instance thereof with the
|
|
// Pulumi runtime. The generated ResourceModule supports the deserialization of resource references into fully-
|
|
// hydrated Resource instances. If this is the root module, this function also generates a ResourcePackage
|
|
// definition and its registration to support rehydrating providers.
|
|
func (pkg *pkgContext) genResourceModule(w io.Writer) error {
|
|
contract.Assertf(len(pkg.resources) != 0, "Package must have at least one resource")
|
|
allResourcesAreOverlays := true
|
|
for _, r := range pkg.resources {
|
|
if !r.IsOverlay {
|
|
allResourcesAreOverlays = false
|
|
break
|
|
}
|
|
}
|
|
if allResourcesAreOverlays {
|
|
// If all resources in this module are overlays, skip further code generation.
|
|
return nil
|
|
}
|
|
|
|
imports := map[string]string{
|
|
"github.com/blang/semver": "",
|
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi": "",
|
|
}
|
|
imports[path.Join(pkg.importBasePath, pkg.internalModuleName)] = ""
|
|
|
|
// If there are any internal dependencies, include them as blank imports.
|
|
def, err := pkg.pkg.Definition()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if goInfo, ok := def.Language["go"].(GoPackageInfo); ok {
|
|
for _, dep := range goInfo.InternalDependencies {
|
|
imports[dep] = "_"
|
|
}
|
|
}
|
|
|
|
pkg.genHeader(w, []string{"fmt"}, imports, false /* isUtil */)
|
|
|
|
var provider *schema.Resource
|
|
registrations := codegen.StringSet{}
|
|
if providerOnly := len(pkg.resources) == 1 && pkg.resources[0].IsProvider; providerOnly {
|
|
provider = pkg.resources[0]
|
|
} else {
|
|
fmt.Fprintf(w, "type module struct {\n")
|
|
fmt.Fprintf(w, "\tversion semver.Version\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (m *module) Version() semver.Version {\n")
|
|
fmt.Fprintf(w, "\treturn m.version\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (m *module) Construct(ctx *pulumi.Context, name, typ, urn string) (r pulumi.Resource, err error) {\n")
|
|
fmt.Fprintf(w, "\tswitch typ {\n")
|
|
for _, r := range pkg.resources {
|
|
if r.IsOverlay {
|
|
// This resource code is generated by the provider, so no further action is required.
|
|
continue
|
|
}
|
|
if r.IsProvider {
|
|
contract.Assertf(provider == nil, "Provider must not be specified for Provider resources")
|
|
provider = r
|
|
continue
|
|
}
|
|
|
|
registrations.Add(tokenToModule(r.Token))
|
|
fmt.Fprintf(w, "\tcase %q:\n", r.Token)
|
|
fmt.Fprintf(w, "\t\tr = &%s{}\n", disambiguatedResourceName(r, pkg))
|
|
}
|
|
fmt.Fprintf(w, "\tdefault:\n")
|
|
fmt.Fprintf(w, "\t\treturn nil, fmt.Errorf(\"unknown resource type: %%s\", typ)\n")
|
|
fmt.Fprintf(w, "\t}\n\n")
|
|
fmt.Fprintf(w, "\terr = ctx.RegisterResource(typ, name, nil, r, pulumi.URN_(urn))\n")
|
|
fmt.Fprintf(w, "\treturn\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
if provider != nil {
|
|
fmt.Fprintf(w, "type pkg struct {\n")
|
|
fmt.Fprintf(w, "\tversion semver.Version\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (p *pkg) Version() semver.Version {\n")
|
|
fmt.Fprintf(w, "\treturn p.version\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (p *pkg) ConstructProvider(ctx *pulumi.Context, name, typ, urn string) (pulumi.ProviderResource, error) {\n")
|
|
fmt.Fprintf(w, "\tif typ != \"pulumi:providers:%s\" {\n", pkg.pkg.Name())
|
|
fmt.Fprintf(w, "\t\treturn nil, fmt.Errorf(\"unknown provider type: %%s\", typ)\n")
|
|
fmt.Fprintf(w, "\t}\n\n")
|
|
fmt.Fprintf(w, "\tr := &Provider{}\n")
|
|
fmt.Fprintf(w, "\terr := ctx.RegisterResource(typ, name, nil, r, pulumi.URN_(urn))\n")
|
|
fmt.Fprintf(w, "\treturn r, err\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
fmt.Fprintf(w, "func init() {\n")
|
|
|
|
fmt.Fprintf(w, "\tversion, err := %s.PkgVersion()\n", pkg.internalModuleName)
|
|
// To avoid breaking compatibility, we don't change the function
|
|
// signature. We instead just ignore the error.
|
|
fmt.Fprintf(w, "\tif err != nil {\n")
|
|
fmt.Fprintf(w, "\t\tversion = semver.Version{Major: 1}\n")
|
|
fmt.Fprintf(w, "\t}\n")
|
|
if len(registrations) > 0 {
|
|
for _, mod := range registrations.SortedValues() {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterResourceModule(\n")
|
|
fmt.Fprintf(w, "\t\t%q,\n", pkg.pkg.Name())
|
|
fmt.Fprintf(w, "\t\t%q,\n", mod)
|
|
fmt.Fprintf(w, "\t\t&module{version},\n")
|
|
fmt.Fprintf(w, "\t)\n")
|
|
}
|
|
}
|
|
if provider != nil {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterResourcePackage(\n")
|
|
fmt.Fprintf(w, "\t\t%q,\n", pkg.pkg.Name())
|
|
fmt.Fprintf(w, "\t\t&pkg{version},\n")
|
|
fmt.Fprintf(w, "\t)\n")
|
|
}
|
|
_, err = fmt.Fprintf(w, "}\n")
|
|
return err
|
|
}
|
|
|
|
// generatePackageContextMap groups resources, types, and functions into Go packages.
|
|
func generatePackageContextMap(tool string, pkg schema.PackageReference, goInfo GoPackageInfo, externalPkgs *Cache) (map[string]*pkgContext, error) {
|
|
packages := map[string]*pkgContext{}
|
|
|
|
// Share the cache
|
|
if externalPkgs == nil {
|
|
externalPkgs = globalCache
|
|
}
|
|
|
|
getPkg := func(mod string) *pkgContext {
|
|
pack, ok := packages[mod]
|
|
if !ok {
|
|
internalModuleName := "internal"
|
|
if goInfo.InternalModuleName != "" {
|
|
internalModuleName = goInfo.InternalModuleName
|
|
}
|
|
|
|
importBasePath := goInfo.ImportBasePath
|
|
if importBasePath == "" {
|
|
// Default to a path based on the package name.
|
|
importBasePath = extractImportBasePath(pkg)
|
|
}
|
|
|
|
pack = &pkgContext{
|
|
pkg: pkg,
|
|
mod: mod,
|
|
importBasePath: importBasePath,
|
|
rootPackageName: goInfo.RootPackageName,
|
|
typeDetails: map[schema.Type]*typeDetails{},
|
|
names: codegen.NewStringSet(),
|
|
schemaNames: codegen.NewStringSet(),
|
|
renamed: map[string]string{},
|
|
duplicateTokens: map[string]bool{},
|
|
functionNames: map[*schema.Function]string{},
|
|
tool: tool,
|
|
modToPkg: goInfo.ModuleToPackage,
|
|
pkgImportAliases: goInfo.PackageImportAliases,
|
|
packages: packages,
|
|
liftSingleValueMethodReturns: goInfo.LiftSingleValueMethodReturns,
|
|
disableInputTypeRegistrations: goInfo.DisableInputTypeRegistrations,
|
|
disableObjectDefaults: goInfo.DisableObjectDefaults,
|
|
internalModuleName: internalModuleName,
|
|
externalPackages: externalPkgs,
|
|
}
|
|
packages[mod] = pack
|
|
}
|
|
return pack
|
|
}
|
|
|
|
getPkgFromToken := func(token string) *pkgContext {
|
|
return getPkg(tokenToPackage(pkg, goInfo.ModuleToPackage, token))
|
|
}
|
|
|
|
var getPkgFromType func(schema.Type) *pkgContext
|
|
getPkgFromType = func(typ schema.Type) *pkgContext {
|
|
switch t := codegen.UnwrapType(typ).(type) {
|
|
case *schema.ArrayType:
|
|
return getPkgFromType(t.ElementType)
|
|
case *schema.MapType:
|
|
return getPkgFromType(t.ElementType)
|
|
case *schema.ObjectType:
|
|
return getPkgFromToken(t.Token)
|
|
case *schema.EnumType:
|
|
return getPkgFromToken(t.Token)
|
|
default:
|
|
return getPkgFromToken(t.String())
|
|
}
|
|
}
|
|
|
|
config, err := pkg.Config()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(config) > 0 {
|
|
_ = getPkg("config")
|
|
}
|
|
|
|
// For any optional properties, we must generate a pointer type for the corresponding property type.
|
|
// In addition, if the optional property's type is itself an object type, we also need to generate pointer
|
|
// types corresponding to all of it's nested properties, as our accessor methods will lift `nil` into
|
|
// those nested types.
|
|
var populateDetailsForPropertyTypes func(seen codegen.StringSet, props []*schema.Property, optional, input, output bool)
|
|
var populateDetailsForTypes func(seen codegen.StringSet, schemaType schema.Type, optional, input, output bool)
|
|
|
|
seenKey := func(t schema.Type, optional, input, output bool) string {
|
|
var key string
|
|
switch t := t.(type) {
|
|
case *schema.ObjectType:
|
|
key = t.Token
|
|
case *schema.EnumType:
|
|
key = t.Token
|
|
default:
|
|
key = t.String()
|
|
}
|
|
if optional {
|
|
key += ",optional"
|
|
}
|
|
if input {
|
|
key += ",input"
|
|
}
|
|
if output {
|
|
key += ",output"
|
|
}
|
|
return key
|
|
}
|
|
|
|
populateDetailsForPropertyTypes = func(seen codegen.StringSet, props []*schema.Property, optional, input, output bool) {
|
|
for _, p := range props {
|
|
if obj, ok := codegen.UnwrapType(p.Type).(*schema.ObjectType); ok && p.Plain {
|
|
pkg := getPkgFromToken(obj.Token)
|
|
details := pkg.detailsForType(obj)
|
|
details.mark(true, false)
|
|
input = true
|
|
_, hasOptional := p.Type.(*schema.OptionalType)
|
|
details.markPtr(hasOptional, false)
|
|
}
|
|
populateDetailsForTypes(seen, p.Type, !p.IsRequired() || optional, input, output)
|
|
}
|
|
}
|
|
|
|
populateDetailsForTypes = func(seen codegen.StringSet, schemaType schema.Type, optional, input, output bool) {
|
|
key := seenKey(schemaType, optional, input, output)
|
|
if seen.Has(key) {
|
|
return
|
|
}
|
|
seen.Add(key)
|
|
|
|
switch typ := schemaType.(type) {
|
|
case *schema.InputType:
|
|
populateDetailsForTypes(seen, typ.ElementType, optional, true, false)
|
|
case *schema.OptionalType:
|
|
populateDetailsForTypes(seen, typ.ElementType, true, input, output)
|
|
case *schema.ObjectType:
|
|
pkg := getPkgFromToken(typ.Token)
|
|
pkg.detailsForType(typ).mark(input || goInfo.GenerateExtraInputTypes, output)
|
|
|
|
if optional {
|
|
pkg.detailsForType(typ).markPtr(input || goInfo.GenerateExtraInputTypes, output)
|
|
}
|
|
|
|
pkg.schemaNames.Add(tokenToName(typ.Token))
|
|
|
|
populateDetailsForPropertyTypes(seen, typ.Properties, optional, input, output)
|
|
case *schema.UnionType:
|
|
for _, e := range typ.ElementTypes {
|
|
populateDetailsForTypes(seen, e, optional, input, output)
|
|
}
|
|
case *schema.EnumType:
|
|
pkg := getPkgFromToken(typ.Token)
|
|
pkg.detailsForType(typ).mark(input || goInfo.GenerateExtraInputTypes, output)
|
|
|
|
if optional {
|
|
pkg.detailsForType(typ).markPtr(input || goInfo.GenerateExtraInputTypes, output)
|
|
}
|
|
|
|
pkg.schemaNames.Add(tokenToName(typ.Token))
|
|
case *schema.ArrayType:
|
|
details := getPkgFromType(typ.ElementType).detailsForType(codegen.UnwrapType(typ.ElementType))
|
|
details.markArray(input || goInfo.GenerateExtraInputTypes, output)
|
|
populateDetailsForTypes(seen, typ.ElementType, false, input, output)
|
|
case *schema.MapType:
|
|
details := getPkgFromType(typ.ElementType).detailsForType(codegen.UnwrapType(typ.ElementType))
|
|
details.markMap(input || goInfo.GenerateExtraInputTypes, output)
|
|
populateDetailsForTypes(seen, typ.ElementType, false, input, output)
|
|
}
|
|
}
|
|
|
|
// Rewrite cyclic types. See the docs on rewriteCyclicFields for the motivation.
|
|
def, err := pkg.Definition()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rewriteCyclicObjectFields(def)
|
|
|
|
// Use a string set to track object types that have already been processed.
|
|
// This avoids recursively processing the same type. For example, in the
|
|
// Kubernetes package, JSONSchemaProps have properties whose type is itself.
|
|
seenMap := codegen.NewStringSet()
|
|
for _, t := range def.Types {
|
|
switch typ := t.(type) {
|
|
case *schema.ArrayType:
|
|
details := getPkgFromType(typ.ElementType).detailsForType(codegen.UnwrapType(typ.ElementType))
|
|
details.markArray(goInfo.GenerateExtraInputTypes, false)
|
|
case *schema.MapType:
|
|
details := getPkgFromType(typ.ElementType).detailsForType(codegen.UnwrapType(typ.ElementType))
|
|
details.markMap(goInfo.GenerateExtraInputTypes, false)
|
|
case *schema.ObjectType:
|
|
pkg := getPkgFromToken(typ.Token)
|
|
if !typ.IsInputShape() {
|
|
pkg.types = append(pkg.types, typ)
|
|
}
|
|
populateDetailsForTypes(seenMap, typ, false, false, false)
|
|
case *schema.EnumType:
|
|
if !typ.IsOverlay {
|
|
pkg := getPkgFromToken(typ.Token)
|
|
pkg.enums = append(pkg.enums, typ)
|
|
|
|
populateDetailsForTypes(seenMap, typ, false, false, false)
|
|
}
|
|
}
|
|
}
|
|
|
|
resSeen := map[string]bool{}
|
|
typeSeen := map[string]bool{}
|
|
|
|
// compute set of names generated by a resource
|
|
// handling any potential collisions via remapping along the way
|
|
scanResource := func(r *schema.Resource) {
|
|
if resSeen[strings.ToLower(r.Token)] {
|
|
return
|
|
}
|
|
resSeen[strings.ToLower(r.Token)] = true
|
|
pkg := getPkgFromToken(r.Token)
|
|
pkg.resources = append(pkg.resources, r)
|
|
pkg.schemaNames.Add(tokenToName(r.Token))
|
|
|
|
getNames := func(suffix string) []string {
|
|
names := []string{}
|
|
names = append(names, rawResourceName(r)+suffix)
|
|
names = append(names, rawResourceName(r)+suffix+"Input")
|
|
names = append(names, rawResourceName(r)+suffix+"Output")
|
|
names = append(names, rawResourceName(r)+suffix+"Args")
|
|
names = append(names, cgstrings.Camel(rawResourceName(r))+suffix+"Args")
|
|
names = append(names, "New"+rawResourceName(r)+suffix)
|
|
if !r.IsProvider && !r.IsComponent {
|
|
names = append(names, rawResourceName(r)+suffix+"State")
|
|
names = append(names, cgstrings.Camel(rawResourceName(r))+suffix+"State")
|
|
names = append(names, "Get"+rawResourceName(r)+suffix)
|
|
}
|
|
if goInfo.GenerateResourceContainerTypes && !r.IsProvider {
|
|
names = append(names, rawResourceName(r)+suffix+"Array")
|
|
names = append(names, rawResourceName(r)+suffix+"Map")
|
|
}
|
|
return names
|
|
}
|
|
|
|
suffixes := []string{"", "Resource", "Res"}
|
|
suffix := ""
|
|
suffixIndex := 0
|
|
canGenerate := false
|
|
|
|
for !canGenerate && suffixIndex <= len(suffixes) {
|
|
suffix = suffixes[suffixIndex]
|
|
candidates := getNames(suffix)
|
|
conflict := false
|
|
for _, c := range candidates {
|
|
if pkg.names.Has(c) {
|
|
conflict = true
|
|
}
|
|
}
|
|
if !conflict {
|
|
canGenerate = true
|
|
break
|
|
}
|
|
|
|
suffixIndex++
|
|
}
|
|
|
|
if !canGenerate {
|
|
panic("unable to generate Go SDK, schema has unresolvable overlapping resource: " + rawResourceName(r))
|
|
}
|
|
|
|
names := getNames(suffix)
|
|
originalNames := getNames("")
|
|
for i, n := range names {
|
|
pkg.names.Add(n)
|
|
if suffix != "" {
|
|
pkg.renamed[originalNames[i]] = names[i]
|
|
}
|
|
}
|
|
|
|
populateDetailsForPropertyTypes(seenMap, r.InputProperties, r.IsProvider, false, false)
|
|
populateDetailsForPropertyTypes(seenMap, r.Properties, r.IsProvider, false, true)
|
|
|
|
if r.StateInputs != nil {
|
|
populateDetailsForPropertyTypes(seenMap, r.StateInputs.Properties,
|
|
r.IsProvider, false /*input*/, false /*output*/)
|
|
}
|
|
|
|
for _, method := range r.Methods {
|
|
if method.Function.Inputs != nil {
|
|
pkg.names.Add(rawResourceName(r) + Title(method.Name) + "Args")
|
|
}
|
|
if method.Function.ReturnType != nil {
|
|
if _, ok := method.Function.ReturnType.(*schema.ObjectType); ok {
|
|
pkg.names.Add(rawResourceName(r) + Title(method.Name) + "Result")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
scanResource(def.Provider)
|
|
for _, r := range def.Resources {
|
|
scanResource(r)
|
|
}
|
|
|
|
// compute set of names generated by a type
|
|
// handling any potential collisions via remapping along the way
|
|
scanType := func(t schema.Type) {
|
|
getNames := func(name, suffix string) []string {
|
|
return []string{name + suffix, name + suffix + "Input", name + suffix + "Output"}
|
|
}
|
|
|
|
switch t := t.(type) {
|
|
case *schema.ObjectType:
|
|
pkg := getPkgFromToken(t.Token)
|
|
// maintain support for duplicate tokens for types and resources in Kubernetes
|
|
if resSeen[strings.ToLower(t.Token)] {
|
|
pkg.duplicateTokens[strings.ToLower(t.Token)] = true
|
|
}
|
|
if typeSeen[strings.ToLower(t.Token)] {
|
|
return
|
|
}
|
|
typeSeen[strings.ToLower(t.Token)] = true
|
|
|
|
name := pkg.tokenToType(t.Token)
|
|
suffixes := []string{"", "Type", "Typ"}
|
|
suffix := ""
|
|
suffixIndex := 0
|
|
canGenerate := false
|
|
|
|
for !canGenerate && suffixIndex <= len(suffixes) {
|
|
suffix = suffixes[suffixIndex]
|
|
candidates := getNames(name, suffix)
|
|
conflict := false
|
|
for _, c := range candidates {
|
|
if pkg.names.Has(c) {
|
|
conflict = true
|
|
}
|
|
}
|
|
if !conflict {
|
|
canGenerate = true
|
|
break
|
|
}
|
|
|
|
suffixIndex++
|
|
}
|
|
|
|
if !canGenerate {
|
|
panic("unable to generate Go SDK, schema has unresolvable overlapping type: " + name)
|
|
}
|
|
|
|
names := getNames(name, suffix)
|
|
originalNames := getNames(name, "")
|
|
for i, n := range names {
|
|
pkg.names.Add(n)
|
|
if suffix != "" {
|
|
pkg.renamed[originalNames[i]] = names[i]
|
|
}
|
|
}
|
|
case *schema.EnumType:
|
|
pkg := getPkgFromToken(t.Token)
|
|
if resSeen[t.Token] {
|
|
pkg.duplicateTokens[strings.ToLower(t.Token)] = true
|
|
}
|
|
if typeSeen[t.Token] {
|
|
return
|
|
}
|
|
typeSeen[t.Token] = true
|
|
|
|
name := pkg.tokenToEnum(t.Token)
|
|
suffixes := []string{"", "Enum"}
|
|
suffix := ""
|
|
suffixIndex := 0
|
|
canGenerate := false
|
|
|
|
for !canGenerate && suffixIndex <= len(suffixes) {
|
|
suffix = suffixes[suffixIndex]
|
|
candidates := getNames(name, suffix)
|
|
conflict := false
|
|
for _, c := range candidates {
|
|
if pkg.names.Has(c) {
|
|
conflict = true
|
|
}
|
|
}
|
|
if !conflict {
|
|
canGenerate = true
|
|
break
|
|
}
|
|
|
|
suffixIndex++
|
|
}
|
|
|
|
if !canGenerate {
|
|
panic("unable to generate Go SDK, schema has unresolvable overlapping type: " + name)
|
|
}
|
|
|
|
names := getNames(name, suffix)
|
|
originalNames := getNames(name, "")
|
|
for i, n := range names {
|
|
pkg.names.Add(n)
|
|
if suffix != "" {
|
|
pkg.renamed[originalNames[i]] = names[i]
|
|
}
|
|
}
|
|
default:
|
|
return
|
|
}
|
|
}
|
|
|
|
for _, t := range def.Types {
|
|
scanType(t)
|
|
}
|
|
|
|
// For fnApply function versions, we need to register any
|
|
// input or output property type metadata, in case they have
|
|
// types used in array or pointer element positions.
|
|
if !goInfo.DisableFunctionOutputVersions || goInfo.GenerateExtraInputTypes {
|
|
for _, f := range def.Functions {
|
|
if f.NeedsOutputVersion() || goInfo.GenerateExtraInputTypes {
|
|
optional := false
|
|
if f.Inputs != nil {
|
|
populateDetailsForPropertyTypes(seenMap, f.Inputs.InputShape.Properties, optional, false, false)
|
|
}
|
|
if f.ReturnType != nil {
|
|
populateDetailsForTypes(seenMap, f.ReturnType, optional, false, true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, f := range def.Functions {
|
|
if f.IsMethod {
|
|
continue
|
|
}
|
|
|
|
pkg := getPkgFromToken(f.Token)
|
|
pkg.functions = append(pkg.functions, f)
|
|
|
|
name := tokenToName(f.Token)
|
|
|
|
if pkg.names.Has(name) ||
|
|
pkg.names.Has(name+"Args") ||
|
|
pkg.names.Has(name+"Result") {
|
|
switch {
|
|
case strings.HasPrefix(name, "New"):
|
|
name = "Create" + name[3:]
|
|
case strings.HasPrefix(name, "Get"):
|
|
name = "Lookup" + name[3:]
|
|
}
|
|
}
|
|
pkg.names.Add(name)
|
|
pkg.functionNames[f] = name
|
|
|
|
if f.Inputs != nil && !f.MultiArgumentInputs {
|
|
pkg.names.Add(name + "Args")
|
|
}
|
|
|
|
if f.ReturnType != nil {
|
|
if objectType, ok := f.ReturnType.(*schema.ObjectType); ok && objectType != nil {
|
|
pkg.names.Add(name + "Result")
|
|
}
|
|
}
|
|
}
|
|
|
|
return packages, nil
|
|
}
|
|
|
|
// LanguageResource is derived from the schema and can be used by downstream codegen.
|
|
type LanguageResource struct {
|
|
*schema.Resource
|
|
|
|
Alias string // The package alias (e.g. appsv1)
|
|
Name string // The resource name (e.g. Deployment)
|
|
Package string // The package name (e.g. github.com/pulumi/pulumi-kubernetes/sdk/v2/go/kubernetes/apps/v1)
|
|
}
|
|
|
|
// LanguageResources returns a map of resources that can be used by downstream codegen. The map
|
|
// key is the resource schema token.
|
|
func LanguageResources(tool string, pkg *schema.Package) (map[string]LanguageResource, error) {
|
|
resources := map[string]LanguageResource{}
|
|
|
|
if err := pkg.ImportLanguages(map[string]schema.Language{"go": Importer}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var goPkgInfo GoPackageInfo
|
|
if goInfo, ok := pkg.Language["go"].(GoPackageInfo); ok {
|
|
goPkgInfo = goInfo
|
|
}
|
|
packages, err := generatePackageContextMap(tool, pkg.Reference(), goPkgInfo, globalCache)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// emit each package
|
|
pkgMods := slice.Prealloc[string](len(packages))
|
|
for mod := range packages {
|
|
pkgMods = append(pkgMods, mod)
|
|
}
|
|
sort.Strings(pkgMods)
|
|
|
|
for _, mod := range pkgMods {
|
|
if mod == "" {
|
|
continue
|
|
}
|
|
pkg := packages[mod]
|
|
|
|
for _, r := range pkg.resources {
|
|
if r.IsOverlay {
|
|
// This resource code is generated by the provider, so no further action is required.
|
|
continue
|
|
}
|
|
|
|
packagePath := path.Join(goPkgInfo.ImportBasePath, pkg.mod)
|
|
resources[r.Token] = LanguageResource{
|
|
Resource: r,
|
|
Alias: goPkgInfo.PackageImportAliases[packagePath],
|
|
Name: tokenToName(r.Token),
|
|
Package: packagePath,
|
|
}
|
|
}
|
|
}
|
|
|
|
return resources, nil
|
|
}
|
|
|
|
// packageRoot is the relative root file for go code. That means that every go
|
|
// source file should be under this root. For example:
|
|
//
|
|
// root = aws => sdk/go/aws/*.go
|
|
func packageRoot(pkg schema.PackageReference) (string, error) {
|
|
def, err := pkg.Definition()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var info GoPackageInfo
|
|
if goInfo, ok := def.Language["go"].(GoPackageInfo); ok {
|
|
info = goInfo
|
|
}
|
|
if info.RootPackageName != "" {
|
|
// package structure is flat
|
|
return "", nil
|
|
}
|
|
if info.ImportBasePath != "" {
|
|
return path.Base(info.ImportBasePath), nil
|
|
}
|
|
return goPackage(pkg.Name()), nil
|
|
}
|
|
|
|
// packageName is the go package name for the generated package.
|
|
func packageName(pkg *schema.Package) string {
|
|
var info GoPackageInfo
|
|
if goInfo, ok := pkg.Language["go"].(GoPackageInfo); ok {
|
|
info = goInfo
|
|
}
|
|
if info.RootPackageName != "" {
|
|
return info.RootPackageName
|
|
}
|
|
root, err := packageRoot(pkg.Reference())
|
|
contract.AssertNoErrorf(err, "We generated the ref from a pkg, so we know its a valid ref")
|
|
return goPackage(root)
|
|
}
|
|
|
|
func GeneratePackage(tool string,
|
|
pkg *schema.Package,
|
|
localDependencies map[string]string,
|
|
) (map[string][]byte, error) {
|
|
if err := pkg.ImportLanguages(map[string]schema.Language{"go": Importer}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var goPkgInfo GoPackageInfo
|
|
if goInfo, ok := pkg.Language["go"].(GoPackageInfo); ok {
|
|
goPkgInfo = goInfo
|
|
}
|
|
|
|
if goPkgInfo.ImportBasePath == "" {
|
|
goPkgInfo.ImportBasePath = extractImportBasePath(pkg.Reference())
|
|
}
|
|
|
|
packages, err := generatePackageContextMap(tool, pkg.Reference(), goPkgInfo, NewCache())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// emit each package
|
|
pkgMods := slice.Prealloc[string](len(packages))
|
|
for mod := range packages {
|
|
pkgMods = append(pkgMods, mod)
|
|
}
|
|
sort.Strings(pkgMods)
|
|
|
|
name := packageName(pkg)
|
|
pathPrefix, err := packageRoot(pkg.Reference())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
files := codegen.Fs{}
|
|
|
|
// If the package is parameterized generate a go.mod for it
|
|
if pkg.Parameterization != nil {
|
|
mod := modfile.File{}
|
|
err = mod.AddModuleStmt(goPkgInfo.ImportBasePath)
|
|
contract.AssertNoErrorf(err, "could not add module statement to go.mod")
|
|
err = mod.AddGoStmt("1.21")
|
|
contract.AssertNoErrorf(err, "could not add Go statement to go.mod")
|
|
// Parameterized packages need the pulumi SDK >= v3.129.0
|
|
pulumiPackagePath := "github.com/pulumi/pulumi/sdk/v3"
|
|
pulumiVersion := "v3.129.0"
|
|
err = mod.AddRequire(pulumiPackagePath, pulumiVersion)
|
|
contract.AssertNoErrorf(err, "could not add require statement to go.mod")
|
|
bytes, err := mod.Format()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("format go.mod: %w", err)
|
|
}
|
|
files.Add(path.Join(pathPrefix, "go.mod"), bytes)
|
|
}
|
|
|
|
// Generate pulumi-plugin.json
|
|
pulumiPlugin := &plugin.PulumiPluginJSON{
|
|
Resource: true,
|
|
Name: pkg.Name,
|
|
Server: pkg.PluginDownloadURL,
|
|
}
|
|
if goPkgInfo.RespectSchemaVersion && pkg.Version != nil {
|
|
pulumiPlugin.Version = pkg.Version.String()
|
|
}
|
|
|
|
if pkg.Parameterization != nil {
|
|
pulumiPlugin.Name = pkg.Parameterization.BaseProvider.Name
|
|
pulumiPlugin.Version = pkg.Parameterization.BaseProvider.Version.String()
|
|
pulumiPlugin.Server = pkg.Parameterization.BaseProvider.PluginDownloadURL
|
|
}
|
|
|
|
pulumiPluginJSON, err := pulumiPlugin.JSON()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to format pulumi-plugin.json: %w", err)
|
|
}
|
|
files.Add(path.Join(pathPrefix, "pulumi-plugin.json"), pulumiPluginJSON)
|
|
|
|
setFileContent := func(root, relPath, contents string) {
|
|
relPath = path.Join(root, relPath)
|
|
|
|
// Run Go formatter on the code before saving to disk
|
|
formattedSource, err := format.Source([]byte(contents))
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Invalid content:\n%s\n%s\n", relPath, contents)
|
|
panic(fmt.Errorf("invalid Go source code:\n\n%s\n: %w", relPath, err))
|
|
}
|
|
|
|
files.Add(relPath, formattedSource)
|
|
}
|
|
|
|
if goPkgInfo.Generics == "" {
|
|
// default is emitting the non-generic variant only
|
|
goPkgInfo.Generics = GenericsSettingNone
|
|
}
|
|
|
|
emitOnlyGenericVariant := goPkgInfo.Generics == GenericsSettingGenericsOnly
|
|
emitOnlyLegacyVariant := goPkgInfo.Generics == GenericsSettingNone
|
|
|
|
setFile := func(relPath, contents string) {
|
|
if emitOnlyGenericVariant {
|
|
// if we only want the generic variant to be emitted
|
|
// skip generating the default "legacy" variant
|
|
return
|
|
}
|
|
|
|
setFileContent(pathPrefix, relPath, contents)
|
|
}
|
|
|
|
setGenericVariantFile := func(relPath, contents string) {
|
|
if emitOnlyLegacyVariant {
|
|
// if we only want the legacy variant to be emitted
|
|
// skip generating the generic variant
|
|
return
|
|
}
|
|
|
|
root := path.Join(pathPrefix, "x")
|
|
if emitOnlyGenericVariant {
|
|
// if we only want the generic variant to be emitted
|
|
// emit it at the root of the package as the default package
|
|
root = pathPrefix
|
|
}
|
|
setFileContent(root, relPath, contents)
|
|
}
|
|
|
|
for _, mod := range pkgMods {
|
|
pkg := packages[mod]
|
|
|
|
// Config, description
|
|
switch mod {
|
|
case "":
|
|
buffer := &bytes.Buffer{}
|
|
if pkg.pkg.Description() != "" {
|
|
printComment(buffer, pkg.pkg.Description(), false)
|
|
} else {
|
|
fmt.Fprintf(buffer, "// Package %[1]s exports types, functions, subpackages for provisioning %[1]s resources.\n", name)
|
|
}
|
|
fmt.Fprintf(buffer, "package %s\n", name)
|
|
|
|
setFile(path.Join(mod, "doc.go"), buffer.String())
|
|
setGenericVariantFile(path.Join(mod, "doc.go"), buffer.String())
|
|
|
|
// Version
|
|
versionBuf := &bytes.Buffer{}
|
|
importsAndAliases := map[string]string{}
|
|
pkg.genHeader(versionBuf, []string{"github.com/blang/semver"}, importsAndAliases, true /* isUtil */)
|
|
err = pkg.GenVersionFile(versionBuf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
versionFilePath := pkg.internalModuleName + "/pulumiVersion.go"
|
|
setFile(path.Join(mod, versionFilePath), versionBuf.String())
|
|
if emitOnlyGenericVariant {
|
|
setGenericVariantFile(path.Join(mod, versionFilePath), versionBuf.String())
|
|
}
|
|
|
|
case "config":
|
|
config, err := pkg.pkg.Config()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(config) > 0 {
|
|
buffer := &bytes.Buffer{}
|
|
if err := pkg.genConfig(buffer, config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
configFilePath := path.Join(mod, "config.go")
|
|
setFile(configFilePath, buffer.String())
|
|
setGenericVariantFile(configFilePath, buffer.String())
|
|
}
|
|
}
|
|
|
|
// Resources
|
|
for _, resource := range pkg.resources {
|
|
if resource.IsOverlay {
|
|
// This resource code is generated by the provider, so no further action is required.
|
|
continue
|
|
}
|
|
|
|
importsAndAliases := map[string]string{}
|
|
pkg.getImports(resource, importsAndAliases)
|
|
importsAndAliases["github.com/pulumi/pulumi/sdk/v3/go/pulumi"] = ""
|
|
importsAndAliases[path.Join(pkg.importBasePath, pkg.internalModuleName)] = ""
|
|
if goPkgInfo.Generics == GenericsSettingSideBySide {
|
|
importsAndAliases["github.com/pulumi/pulumi/sdk/v3/go/pulumix"] = ""
|
|
}
|
|
|
|
buffer := &bytes.Buffer{}
|
|
pkg.genHeader(buffer, []string{"context", "reflect"}, importsAndAliases, false /* isUtil */)
|
|
|
|
if err := pkg.genResource(
|
|
buffer,
|
|
resource,
|
|
goPkgInfo.GenerateResourceContainerTypes,
|
|
false /* useGenericVariant */); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resourceFilePath := path.Join(mod, cgstrings.Camel(rawResourceName(resource))+".go")
|
|
setFile(resourceFilePath, buffer.String())
|
|
|
|
genericVariantBuffer := &bytes.Buffer{}
|
|
importsAndAliases["github.com/pulumi/pulumi/sdk/v3/go/pulumix"] = ""
|
|
pkg.genHeader(genericVariantBuffer, []string{"context", "reflect"}, importsAndAliases, false /* isUtil */)
|
|
if err := pkg.genResource(
|
|
genericVariantBuffer,
|
|
resource,
|
|
goPkgInfo.GenerateResourceContainerTypes,
|
|
true /* useGenericVariant */); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
setGenericVariantFile(resourceFilePath, genericVariantBuffer.String())
|
|
}
|
|
|
|
// Functions
|
|
for _, f := range pkg.functions {
|
|
if f.IsOverlay {
|
|
// This function code is generated by the provider, so no further action is required.
|
|
continue
|
|
}
|
|
|
|
fileName := path.Join(mod, cgstrings.Camel(tokenToName(f.Token))+".go")
|
|
code, err := pkg.genFunctionCodeFile(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
setFile(fileName, code)
|
|
|
|
genericCodeVariant, err := pkg.genGenericVariantFunctionCodeFile(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
setGenericVariantFile(fileName, genericCodeVariant)
|
|
}
|
|
|
|
knownTypes := make(map[schema.Type]struct{}, len(pkg.typeDetails))
|
|
for t := range pkg.typeDetails {
|
|
knownTypes[t] = struct{}{}
|
|
}
|
|
|
|
// Enums
|
|
if len(pkg.enums) > 0 {
|
|
hasOutputs, imports := false, map[string]string{}
|
|
for _, e := range pkg.enums {
|
|
pkg.getImports(e, imports)
|
|
hasOutputs = hasOutputs || pkg.detailsForType(e).hasOutputs()
|
|
}
|
|
var goImports []string
|
|
if hasOutputs {
|
|
goImports = []string{"context", "reflect"}
|
|
imports["github.com/pulumi/pulumi/sdk/v3/go/pulumi"] = ""
|
|
if goPkgInfo.Generics != GenericsSettingNone {
|
|
imports["github.com/pulumi/pulumi/sdk/v3/go/pulumix"] = ""
|
|
}
|
|
}
|
|
|
|
buffer := &bytes.Buffer{}
|
|
genericVariantBuffer := &bytes.Buffer{}
|
|
pkg.genHeader(buffer, goImports, imports, false /* isUtil */)
|
|
// we do not need any imports for the generic variant
|
|
pkg.genHeader(genericVariantBuffer, []string{}, map[string]string{}, false /* isUtil */)
|
|
|
|
for _, e := range pkg.enums {
|
|
// generate enums for legacy variant
|
|
if err := pkg.genEnum(buffer, e, false); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// generate enums for generic variant
|
|
if err := pkg.genEnum(genericVariantBuffer, e, true); err != nil {
|
|
return nil, err
|
|
}
|
|
delete(knownTypes, e)
|
|
}
|
|
pkg.genEnumRegistrations(buffer)
|
|
setFile(path.Join(mod, "pulumiEnums.go"), buffer.String())
|
|
setGenericVariantFile(path.Join(mod, "pulumiEnums.go"), genericVariantBuffer.String())
|
|
}
|
|
|
|
// Types
|
|
sortedKnownTypes := slice.Prealloc[schema.Type](len(knownTypes))
|
|
for k := range knownTypes {
|
|
sortedKnownTypes = append(sortedKnownTypes, k)
|
|
}
|
|
sort.Slice(sortedKnownTypes, func(i, j int) bool {
|
|
return sortedKnownTypes[i].String() < sortedKnownTypes[j].String()
|
|
})
|
|
|
|
if len(pkg.types) == 0 && len(pkg.enums) > 0 {
|
|
// If there are no types, but there are enums, we still need to generate the types file.
|
|
// with the associated nested collection enum types such as arrays of enums, maps of enums etc.
|
|
|
|
collectionTypes := map[string]*nestedTypeInfo{}
|
|
for _, t := range sortedKnownTypes {
|
|
pkg.collectNestedCollectionTypes(collectionTypes, t)
|
|
}
|
|
|
|
if len(collectionTypes) > 0 {
|
|
buffer := &bytes.Buffer{}
|
|
useGenericVariant := false
|
|
err := generateTypes(buffer, pkg, []*schema.ObjectType{}, sortedKnownTypes, useGenericVariant)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
typeFilePath := path.Join(mod, "pulumiTypes.go")
|
|
setFile(typeFilePath, buffer.String())
|
|
|
|
genericVariantBuffer := &bytes.Buffer{}
|
|
useGenericVariant = true
|
|
err = generateTypes(genericVariantBuffer, pkg, []*schema.ObjectType{}, sortedKnownTypes, useGenericVariant)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
setGenericVariantFile(typeFilePath, genericVariantBuffer.String())
|
|
}
|
|
}
|
|
|
|
for types, i := pkg.types, 0; len(types) > 0; i++ {
|
|
// 500 types corresponds to approximately 5M or 40_000 lines of code.
|
|
const chunkSize = 500
|
|
chunk := types
|
|
if len(chunk) > chunkSize {
|
|
chunk = chunk[:chunkSize]
|
|
}
|
|
types = types[len(chunk):]
|
|
|
|
// To avoid duplicating collection types into every chunk, only pass known to chunk i=0.
|
|
known := sortedKnownTypes
|
|
if i != 0 {
|
|
known = nil
|
|
}
|
|
|
|
buffer := &bytes.Buffer{}
|
|
useGenericVariant := false
|
|
err := generateTypes(buffer, pkg, chunk, known, useGenericVariant)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
typePath := "pulumiTypes"
|
|
if i != 0 {
|
|
typePath = fmt.Sprintf("%s%d", typePath, i)
|
|
}
|
|
|
|
typeFilePath := path.Join(mod, typePath+".go")
|
|
setFile(typeFilePath, buffer.String())
|
|
|
|
genericVariantBuffer := &bytes.Buffer{}
|
|
useGenericVariant = true
|
|
err = generateTypes(genericVariantBuffer, pkg, chunk, known, useGenericVariant)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
setGenericVariantFile(typeFilePath, genericVariantBuffer.String())
|
|
}
|
|
|
|
// Utilities
|
|
if len(mod) == 0 {
|
|
buffer := &bytes.Buffer{}
|
|
importsAndAliases := map[string]string{
|
|
"github.com/blang/semver": "",
|
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi": "",
|
|
}
|
|
|
|
imports := []string{"fmt", "os", "reflect", "regexp", "strconv", "strings"}
|
|
|
|
def, err := pkg.pkg.Definition()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if def.Parameterization != nil {
|
|
imports = append(imports, "encoding/base64")
|
|
importsAndAliases["github.com/pulumi/pulumi/sdk/v3/proto/go"] = "pulumirpc"
|
|
}
|
|
|
|
pkg.genHeader(buffer, imports, importsAndAliases, true /* isUtil */)
|
|
|
|
packageRegex := fmt.Sprintf("^.*/pulumi-%s/sdk(/v\\d+)?", pkg.pkg.Name())
|
|
if pkg.rootPackageName != "" {
|
|
packageRegex = fmt.Sprintf("^%s(/v\\d+)?", pkg.importBasePath)
|
|
}
|
|
err = pkg.GenUtilitiesFile(buffer, packageRegex)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
utilFilePath := pkg.internalModuleName + "/pulumiUtilities.go"
|
|
setFile(path.Join(mod, utilFilePath), buffer.String())
|
|
if emitOnlyGenericVariant {
|
|
setGenericVariantFile(path.Join(mod, utilFilePath), buffer.String())
|
|
}
|
|
}
|
|
|
|
// If there are resources in this module, register the module with the runtime.
|
|
if len(pkg.resources) != 0 && !allResourcesAreOverlays(pkg.resources) {
|
|
buffer := &bytes.Buffer{}
|
|
err := pkg.genResourceModule(buffer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
setFile(path.Join(mod, "init.go"), buffer.String())
|
|
|
|
genericVariantBuffer := &bytes.Buffer{}
|
|
if err := pkg.genResourceModule(genericVariantBuffer); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
setGenericVariantFile(path.Join(mod, "init.go"), genericVariantBuffer.String())
|
|
}
|
|
}
|
|
|
|
// create a go.mod file with references to local dependencies
|
|
if pkg.SupportPack {
|
|
var vPath string
|
|
if pkg.Version != nil && pkg.Version.Major > 1 {
|
|
vPath = fmt.Sprintf("/v%d", pkg.Version.Major)
|
|
}
|
|
|
|
modulePath := extractModulePath(pkg.Reference())
|
|
if langInfo, found := pkg.Language["go"]; found {
|
|
goInfo, ok := langInfo.(GoPackageInfo)
|
|
if ok && goInfo.ModulePath != "" {
|
|
modulePath = goInfo.ModulePath
|
|
} else if ok && goInfo.ImportBasePath != "" {
|
|
separatorIndex := strings.Index(goInfo.ImportBasePath, vPath)
|
|
if separatorIndex >= 0 {
|
|
modulePrefix := goInfo.ImportBasePath[:separatorIndex]
|
|
modulePath = fmt.Sprintf("%s%s", modulePrefix, vPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
var gomod modfile.File
|
|
err = gomod.AddModuleStmt(modulePath)
|
|
contract.AssertNoErrorf(err, "could not add module statement to go.mod")
|
|
err = gomod.AddGoStmt("1.20")
|
|
contract.AssertNoErrorf(err, "could not add Go statement to go.mod")
|
|
pulumiPackagePath := "github.com/pulumi/pulumi/sdk/v3"
|
|
pulumiVersion := "v3.30.0"
|
|
if pkg.Parameterization != nil {
|
|
pulumiVersion = "v3.133.0"
|
|
}
|
|
err = gomod.AddRequire(pulumiPackagePath, pulumiVersion)
|
|
contract.AssertNoErrorf(err, "could not add require statement to go.mod")
|
|
if replacementPath, hasReplacement := localDependencies["pulumi"]; hasReplacement {
|
|
err = gomod.AddReplace(pulumiPackagePath, "", replacementPath, "")
|
|
contract.AssertNoErrorf(err, "could not add replace statement to go.mod")
|
|
}
|
|
|
|
files["go.mod"], err = gomod.Format()
|
|
contract.AssertNoErrorf(err, "could not format go.mod")
|
|
}
|
|
|
|
return files, nil
|
|
}
|
|
|
|
func generateTypes(
|
|
w io.Writer,
|
|
pkg *pkgContext,
|
|
types []*schema.ObjectType,
|
|
knownTypes []schema.Type,
|
|
useGenericTypes bool,
|
|
) error {
|
|
hasOutputs, importsAndAliases := false, map[string]string{}
|
|
for _, t := range types {
|
|
pkg.getImports(t, importsAndAliases)
|
|
hasOutputs = hasOutputs || pkg.detailsForType(t).hasOutputs()
|
|
}
|
|
|
|
collectionTypes := map[string]*nestedTypeInfo{}
|
|
for _, t := range knownTypes {
|
|
pkg.collectNestedCollectionTypes(collectionTypes, t)
|
|
}
|
|
|
|
// All collection types have Outputs
|
|
if len(collectionTypes) > 0 {
|
|
hasOutputs = true
|
|
}
|
|
|
|
goInfo := goPackageInfo(pkg.pkg)
|
|
var goImports []string
|
|
if hasOutputs {
|
|
goImports = []string{"context", "reflect"}
|
|
importsAndAliases["github.com/pulumi/pulumi/sdk/v3/go/pulumi"] = ""
|
|
if goInfo.Generics == GenericsSettingSideBySide {
|
|
importsAndAliases["github.com/pulumi/pulumi/sdk/v3/go/pulumix"] = ""
|
|
}
|
|
}
|
|
|
|
if useGenericTypes && hasOutputs {
|
|
importsAndAliases["github.com/pulumi/pulumi/sdk/v3/go/pulumix"] = ""
|
|
}
|
|
|
|
importsAndAliases[path.Join(pkg.importBasePath, pkg.internalModuleName)] = ""
|
|
pkg.genHeader(w, goImports, importsAndAliases, false /* isUtil */)
|
|
// in case we're not using the internal package, assign to a blank var
|
|
fmt.Fprintf(w, "var _ = %s.GetEnvOrDefault\n", pkg.internalModuleName)
|
|
|
|
for _, t := range types {
|
|
if err := pkg.genType(w, t, useGenericTypes); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
typeNames := []string{}
|
|
if !useGenericTypes {
|
|
typeNames = pkg.genNestedCollectionTypes(w, collectionTypes)
|
|
}
|
|
|
|
pkg.genTypeRegistrations(w, types, useGenericTypes, typeNames...)
|
|
return nil
|
|
}
|
|
|
|
func allResourcesAreOverlays(resources []*schema.Resource) bool {
|
|
for _, r := range resources {
|
|
if !r.IsOverlay {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// goPackage returns the suggested package name for the given string.
|
|
func goPackage(name string) string {
|
|
return strings.ReplaceAll(name, "-", "")
|
|
}
|
|
|
|
//go:embed embeddedUtilities.go
|
|
var embeddedUtilities string
|
|
|
|
func (pkg *pkgContext) GenUtilitiesFile(w io.Writer, packageRegex string) error {
|
|
subtitutions := map[string]string{
|
|
`"${packageRegex}"`: fmt.Sprintf("%q", packageRegex),
|
|
}
|
|
i := strings.Index(embeddedUtilities, "package utilities")
|
|
code := embeddedUtilities[i+len("package utilities"):]
|
|
for x, y := range subtitutions {
|
|
code = strings.ReplaceAll(code, x, y)
|
|
}
|
|
_, err := fmt.Fprintf(w, "%s", code)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return pkg.GenPkgDefaultOpts(w)
|
|
}
|
|
|
|
func (pkg *pkgContext) GenVersionFile(w io.Writer) error {
|
|
const versionFile = `var SdkVersion semver.Version = semver.Version{}
|
|
var pluginDownloadURL string = ""
|
|
`
|
|
_, err := fmt.Fprint(w, versionFile)
|
|
return err
|
|
}
|
|
|
|
func (pkg *pkgContext) GenPkgDefaultOpts(w io.Writer) error {
|
|
p, err := pkg.pkg.Definition()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
url := p.PluginDownloadURL
|
|
const template string = `
|
|
// Pkg%[1]sDefaultOpts provides package level defaults to pulumi.Option%[1]s.
|
|
func Pkg%[1]sDefaultOpts(opts []pulumi.%[1]sOption) []pulumi.%[1]sOption {
|
|
defaults := []pulumi.%[1]sOption{}
|
|
%[2]s
|
|
version := %[3]s
|
|
if !version.Equals(semver.Version{}){
|
|
defaults = append(defaults, pulumi.Version(version.String()))
|
|
}
|
|
return append(defaults, opts...)
|
|
}
|
|
`
|
|
var pluginDownloadURL string
|
|
if url != "" {
|
|
pluginDownloadURL = fmt.Sprintf(`defaults = append(defaults, pulumi.PluginDownloadURL("%s"))`, url)
|
|
}
|
|
|
|
versionPackageRef := "SdkVersion"
|
|
|
|
versionPkgName := strings.ReplaceAll(pkg.pkg.Name(), "-", "")
|
|
|
|
if pkg.mod != "" {
|
|
versionPackageRef = versionPkgName + "." + versionPackageRef
|
|
}
|
|
if info := p.Language["go"]; info != nil {
|
|
if info.(GoPackageInfo).RespectSchemaVersion && pkg.pkg.Version() != nil {
|
|
versionPackageRef = fmt.Sprintf("semver.MustParse(%q)", p.Version.String())
|
|
}
|
|
} else if pkg.pkg.SupportPack() && pkg.pkg.Version() != nil {
|
|
versionPackageRef = fmt.Sprintf("semver.MustParse(%q)", p.Version.String())
|
|
}
|
|
// Parameterized schemas _always_ respect schema version.
|
|
if p.Parameterization != nil {
|
|
if p.Version == nil {
|
|
return errors.New("package version is required")
|
|
}
|
|
versionPackageRef = fmt.Sprintf("semver.MustParse(%q)", p.Version.String())
|
|
|
|
const packageRefTemplate string = `
|
|
var packageRef *string
|
|
// PkgGetPackageRef returns the package reference for the current package.
|
|
func PkgGetPackageRef(ctx *pulumi.Context) (string, error) {
|
|
if packageRef == nil {
|
|
|
|
parameter, err := base64.StdEncoding.DecodeString(%q)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
resp, err := ctx.RegisterPackage(&pulumirpc.RegisterPackageRequest{
|
|
Name: %q,
|
|
Version: %q,
|
|
DownloadUrl: %q,
|
|
Parameterization: &pulumirpc.Parameterization{
|
|
Name: %q,
|
|
Version: %q,
|
|
Value: parameter,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
packageRef = &resp.Ref
|
|
}
|
|
|
|
return *packageRef, nil
|
|
}
|
|
`
|
|
|
|
value := base64.StdEncoding.EncodeToString(p.Parameterization.Parameter)
|
|
_, err = fmt.Fprintf(w, packageRefTemplate,
|
|
value,
|
|
p.Parameterization.BaseProvider.Name, p.Parameterization.BaseProvider.Version.String(), p.PluginDownloadURL,
|
|
p.Name, p.Version.String(),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, typ := range []string{"Resource", "Invoke"} {
|
|
_, err := fmt.Fprintf(w, template, typ, pluginDownloadURL, versionPackageRef)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GenPkgDefaultsOptsCall generates a call to Pkg{TYPE}DefaultsOpts.
|
|
func (pkg *pkgContext) GenPkgDefaultsOptsCall(w io.Writer, invoke bool) error {
|
|
typ := "Resource"
|
|
if invoke {
|
|
typ = "Invoke"
|
|
}
|
|
|
|
_, err := fmt.Fprintf(w, "\topts = %s.Pkg%sDefaultOpts(opts)\n", pkg.internalModuleName, typ)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GenPkgGetPackageRefCall generates a call to PkgGetPackageRef.
|
|
func (pkg *pkgContext) GenPkgGetPackageRefCall(w io.Writer, errorResult string) error {
|
|
_, err := fmt.Fprintf(w, "\tref, err := %s.PkgGetPackageRef(ctx)\n", pkg.internalModuleName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = fmt.Fprintf(w, "\tif err != nil {\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = fmt.Fprintf(w, "\t\treturn %s, err\n", errorResult)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = fmt.Fprintf(w, "\t}\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|