These changes support arbitrary combinations of input + plain types within a schema. Handling plain types at the property level was not sufficient to support such combinations. Reifying these types required updating quite a bit of code. This is likely to have caused some temporary complications, but should eventually lead to substantial simplification in the SDK and program code generators. With the new design, input and optional types are explicit in the schema type system. Optionals will only appear at the outermost level of a type (i.e. Input<Optional<>>, Array<Optional<>>, etc. will not occur). In addition to explicit input types, each object type now has a "plain" shape and an "input" shape. The former uses only plain types; the latter uses input shapes wherever a plain type is not specified. Plain types are indicated in the schema by setting the "plain" property of a type spec to true.
2545 lines
79 KiB
Go
2545 lines
79 KiB
Go
// Copyright 2016-2020, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// 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"
|
|
"fmt"
|
|
"go/format"
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen"
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
|
)
|
|
|
|
type typeDetails struct {
|
|
ptrElement bool
|
|
arrayElement bool
|
|
mapElement bool
|
|
}
|
|
|
|
// 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:])
|
|
}
|
|
runes := []rune(s)
|
|
return string(append([]rune{unicode.ToUpper(runes[0])}, runes[1:]...))
|
|
}
|
|
|
|
func camel(s string) string {
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
runes := []rune(s)
|
|
res := make([]rune, 0, len(runes))
|
|
for i, r := range runes {
|
|
if unicode.IsLower(r) {
|
|
res = append(res, runes[i:]...)
|
|
break
|
|
}
|
|
res = append(res, unicode.ToLower(r))
|
|
}
|
|
return string(res)
|
|
}
|
|
|
|
func tokenToPackage(pkg *schema.Package, overrides map[string]string, tok string) string {
|
|
mod := pkg.TokenToModule(tok)
|
|
if override, ok := overrides[mod]; ok {
|
|
mod = override
|
|
}
|
|
return strings.ToLower(mod)
|
|
}
|
|
|
|
type pkgContext struct {
|
|
pkg *schema.Package
|
|
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
|
|
functionNames map[*schema.Function]string
|
|
needsUtils bool
|
|
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
|
|
}
|
|
|
|
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)
|
|
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))
|
|
}
|
|
|
|
mod, name := pkg.tokenToPackage(tok), components[2]
|
|
|
|
modPkg, ok := pkg.packages[mod]
|
|
name = Title(name)
|
|
|
|
if ok {
|
|
newName, renamed := modPkg.renamed[name]
|
|
if renamed {
|
|
name = newName
|
|
} else if modPkg.names.Has(name) {
|
|
// If the package containing the type's token already has a resource with the
|
|
// same name, add a `Type` suffix.
|
|
newName = name + "Type"
|
|
modPkg.renamed[name] = newName
|
|
modPkg.names.Add(newName)
|
|
name = newName
|
|
}
|
|
}
|
|
|
|
if mod == pkg.mod {
|
|
return name
|
|
}
|
|
if mod == "" {
|
|
mod = components[0]
|
|
}
|
|
mod = strings.Replace(mod, "/", "", -1) + "." + name
|
|
return strings.Replace(mod, "-provider", "", -1)
|
|
}
|
|
|
|
func (pkg *pkgContext) tokenToResource(tok string) string {
|
|
// token := pkg : module : member
|
|
// module := path/to/module
|
|
|
|
components := strings.Split(tok, ":")
|
|
contract.Assert(len(components) == 3)
|
|
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 fmt.Sprintf("%s.Provider", components[2])
|
|
}
|
|
|
|
mod, name := pkg.tokenToPackage(tok), components[2]
|
|
|
|
name = Title(name)
|
|
|
|
if mod == pkg.mod {
|
|
return name
|
|
}
|
|
if mod == "" {
|
|
mod = components[0]
|
|
}
|
|
return strings.Replace(mod, "/", "", -1) + "." + name
|
|
}
|
|
|
|
func tokenToModule(tok string) string {
|
|
// token := pkg : module : member
|
|
// module := path/to/module
|
|
|
|
components := strings.Split(tok, ":")
|
|
contract.Assert(len(components) == 3)
|
|
return components[1]
|
|
}
|
|
|
|
func tokenToName(tok string) string {
|
|
components := strings.Split(tok, ":")
|
|
contract.Assert(len(components) == 3)
|
|
return Title(components[2])
|
|
}
|
|
|
|
func resourceName(r *schema.Resource) string {
|
|
if r.IsProvider {
|
|
return "Provider"
|
|
}
|
|
return tokenToName(r.Token)
|
|
}
|
|
|
|
func isNilType(t schema.Type) bool {
|
|
switch t := t.(type) {
|
|
case *schema.OptionalType, *schema.ArrayType, *schema.MapType, *schema.ResourceType:
|
|
return true
|
|
case *schema.InputType:
|
|
// Enums are value types.
|
|
//
|
|
// FIXME: enum inputs are not proper inputs: they do not accept output values.
|
|
_, isEnum := t.ElementType.(*schema.EnumType)
|
|
return !isEnum
|
|
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.tokenToEnum(t.Token)
|
|
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) 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.tokenToEnum(t.Token)
|
|
case *schema.ArrayType:
|
|
en := pkg.argsTypeImpl(t.ElementType)
|
|
return strings.TrimSuffix(en, "Args") + "Array"
|
|
case *schema.MapType:
|
|
en := pkg.argsTypeImpl(t.ElementType)
|
|
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 _, isEnum := input.ElementType.(*schema.EnumType); isEnum {
|
|
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.typeStringImpl(t.ElementType, argsType)
|
|
case *schema.ArrayType:
|
|
typ := "[]"
|
|
if !argsType && pkg.isExternalObjectType(t.ElementType) {
|
|
typ += "*"
|
|
}
|
|
return typ + pkg.typeStringImpl(t.ElementType, argsType)
|
|
case *schema.MapType:
|
|
typ := "map[string]"
|
|
if !argsType && pkg.isExternalObjectType(t.ElementType) {
|
|
typ += "*"
|
|
}
|
|
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 {
|
|
return pkg.typeStringImpl(t, false)
|
|
}
|
|
|
|
func (pkg *pkgContext) isExternalReference(t schema.Type) bool {
|
|
switch typ := t.(type) {
|
|
case *schema.ObjectType:
|
|
return typ.Package != nil && pkg.pkg != nil && typ.Package != pkg.pkg
|
|
case *schema.ResourceType:
|
|
return typ.Resource != nil && pkg.pkg != nil && typ.Resource.Package != pkg.pkg
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (pkg *pkgContext) isExternalObjectType(t schema.Type) bool {
|
|
obj, ok := t.(*schema.ObjectType)
|
|
return ok && obj.Package != nil && pkg.pkg != nil && obj.Package != pkg.pkg
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
extPkg := t.Resource.Package
|
|
var goInfo GoPackageInfo
|
|
|
|
contract.AssertNoError(extPkg.ImportLanguages(map[string]schema.Language{"go": Importer}))
|
|
if info, ok := extPkg.Language["go"].(GoPackageInfo); ok {
|
|
goInfo = info
|
|
}
|
|
extPkgCtx := &pkgContext{
|
|
pkg: extPkg,
|
|
importBasePath: goInfo.ImportBasePath,
|
|
pkgImportAliases: goInfo.PackageImportAliases,
|
|
modToPkg: goInfo.ModuleToPackage,
|
|
}
|
|
resType := extPkgCtx.tokenToResource(t.Token)
|
|
if !strings.Contains(resType, ".") {
|
|
resType = fmt.Sprintf("%s.%s", extPkg.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 {
|
|
if !pkg.isExternalReference(t) {
|
|
name := pkg.tokenToType(t.Token)
|
|
if t.IsInputShape() {
|
|
return name + "Args"
|
|
}
|
|
return name
|
|
}
|
|
extPkg := t.Package
|
|
var goInfo GoPackageInfo
|
|
|
|
contract.AssertNoError(extPkg.ImportLanguages(map[string]schema.Language{"go": Importer}))
|
|
if info, ok := extPkg.Language["go"].(GoPackageInfo); ok {
|
|
goInfo = info
|
|
}
|
|
extPkgCtx := &pkgContext{
|
|
pkg: extPkg,
|
|
importBasePath: goInfo.ImportBasePath,
|
|
pkgImportAliases: goInfo.PackageImportAliases,
|
|
modToPkg: goInfo.ModuleToPackage,
|
|
}
|
|
return extPkgCtx.typeString(t)
|
|
}
|
|
|
|
func (pkg *pkgContext) outputType(t schema.Type) string {
|
|
switch t := t.(type) {
|
|
case *schema.OptionalType:
|
|
elem := pkg.outputType(t.ElementType)
|
|
if isNilType(t.ElementType) || elem == "pulumi.AnyOutput" {
|
|
return elem
|
|
}
|
|
return strings.TrimSuffix(elem, "Output") + "PtrOutput"
|
|
case *schema.EnumType:
|
|
return pkg.outputType(t.ElementType)
|
|
case *schema.ArrayType:
|
|
en := strings.TrimSuffix(pkg.outputType(t.ElementType), "Output")
|
|
if en == "pulumi.Any" {
|
|
return "pulumi.ArrayOutput"
|
|
}
|
|
return en + "ArrayOutput"
|
|
case *schema.MapType:
|
|
en := strings.TrimSuffix(pkg.outputType(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.outputType(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.outputType(typ.ElementType)
|
|
}
|
|
}
|
|
// TODO(pdg): union types
|
|
return "pulumi.AnyOutput"
|
|
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 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, fmt.Sprintf("Deprecated: %s", 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) 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")
|
|
}
|
|
|
|
// genResourceContainerInput handles generating container (slice/map) wrappers around
|
|
// resources to facilitate external references.
|
|
func genResourceContainerInput(w io.Writer, name, receiverType, elementType string) {
|
|
fmt.Fprintf(w, "func (%s) ElementType() reflect.Type {\n", receiverType)
|
|
fmt.Fprintf(w, "\treturn reflect.TypeOf((%s)(nil))\n", elementType)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
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)
|
|
if strings.HasSuffix(name, "Ptr") {
|
|
base := name[:len(name)-3]
|
|
fmt.Fprintf(w, "\treturn pulumi.ToOutputWithContext(ctx, i).(%sOutput).To%sOutput()\n", base, Title(name))
|
|
} else {
|
|
fmt.Fprintf(w, "\treturn pulumi.ToOutputWithContext(ctx, i).(%sOutput)\n", name)
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
func genInputMethods(w io.Writer, name, receiverType, elementType string, ptrMethods, resourceType bool) {
|
|
fmt.Fprintf(w, "func (%s) ElementType() reflect.Type {\n", receiverType)
|
|
if resourceType {
|
|
fmt.Fprintf(w, "\treturn reflect.TypeOf((*%s)(nil))\n", elementType)
|
|
} else {
|
|
fmt.Fprintf(w, "\treturn reflect.TypeOf((*%s)(nil)).Elem()\n", elementType)
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
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 ptrMethods {
|
|
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")
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) genEnum(w io.Writer, enum *schema.EnumType) error {
|
|
return pkg.genEnumType(w, pkg.tokenToEnum(enum.Token), enum)
|
|
}
|
|
|
|
func (pkg *pkgContext) genEnumType(w io.Writer, name string, enumType *schema.EnumType) error {
|
|
mod := pkg.tokenToPackage(enumType.Token)
|
|
modPkg, ok := pkg.packages[mod]
|
|
contract.Assert(ok)
|
|
printCommentWithDeprecationMessage(w, enumType.Comment, "", false)
|
|
elementType := pkg.enumElementType(enumType.ElementType, false)
|
|
fmt.Fprintf(w, "type %s %s\n\n", name, elementType)
|
|
|
|
fmt.Fprintln(w, "const (")
|
|
for _, e := range enumType.Elements {
|
|
printCommentWithDeprecationMessage(w, e.Comment, e.DeprecationMessage, true)
|
|
|
|
var 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)
|
|
|
|
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, ")")
|
|
inputType := pkg.inputType(enumType)
|
|
contract.Assertf(name == inputType,
|
|
"expect inputType (%s) for enums to be the same as enum type (%s)", inputType, enumType)
|
|
pkg.genEnumInputFuncs(w, name, enumType, elementType, inputType)
|
|
return nil
|
|
}
|
|
|
|
func (pkg *pkgContext) enumElementType(t schema.Type, optional bool) string {
|
|
suffix := ""
|
|
if optional {
|
|
suffix = "Ptr"
|
|
}
|
|
switch t {
|
|
case schema.BoolType:
|
|
return "pulumi.Bool" + suffix
|
|
case schema.IntType:
|
|
return "pulumi.Int" + suffix
|
|
case schema.NumberType:
|
|
return "pulumi.Float64" + suffix
|
|
case schema.StringType:
|
|
return "pulumi.String" + suffix
|
|
default:
|
|
// We only expect to support the above element types for enums
|
|
panic(fmt.Sprintf("Invalid enum type: %s", t))
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) genEnumInputFuncs(w io.Writer, typeName string, enum *schema.EnumType, elementType, inputType string) {
|
|
fmt.Fprintln(w)
|
|
asFuncName := Title(strings.Replace(elementType, "pulumi.", "", -1))
|
|
fmt.Fprintf(w, "func (%s) ElementType() reflect.Type {\n", typeName)
|
|
fmt.Fprintf(w, "return reflect.TypeOf((*%s)(nil)).Elem()\n", elementType)
|
|
fmt.Fprintln(w, "}")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func (e %s) To%sOutput() %sOutput {\n", typeName, asFuncName, elementType)
|
|
fmt.Fprintf(w, "return pulumi.ToOutput(%[1]s(e)).(%[1]sOutput)\n", elementType)
|
|
fmt.Fprintln(w, "}")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func (e %[1]s) To%[2]sOutputWithContext(ctx context.Context) %[3]sOutput {\n", typeName, asFuncName, elementType)
|
|
fmt.Fprintf(w, "return pulumi.ToOutputWithContext(ctx, %[1]s(e)).(%[1]sOutput)\n", elementType)
|
|
fmt.Fprintln(w, "}")
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "func (e %[1]s) To%[2]sPtrOutput() %[3]sPtrOutput {\n", typeName, asFuncName, elementType)
|
|
fmt.Fprintf(w, "return %[1]s(e).To%[2]sPtrOutputWithContext(context.Background())\n", elementType, 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, elementType)
|
|
fmt.Fprintf(w, "return %[1]s(e).To%[2]sOutputWithContext(ctx).To%[2]sPtrOutputWithContext(ctx)\n", elementType, asFuncName)
|
|
fmt.Fprintln(w, "}")
|
|
fmt.Fprintln(w)
|
|
|
|
details := pkg.detailsForType(enum)
|
|
// Generate the array input.
|
|
if details.arrayElement {
|
|
pkg.genInputInterface(w, typeName+"Array")
|
|
|
|
fmt.Fprintf(w, "type %[1]sArray []%[1]s\n\n", typeName)
|
|
|
|
genInputMethods(w, typeName+"Array", typeName+"Array", "[]"+typeName, false, false)
|
|
}
|
|
|
|
// Generate the map input.
|
|
if details.mapElement {
|
|
pkg.genInputInterface(w, typeName+"Map")
|
|
|
|
fmt.Fprintf(w, "type %[1]sMap map[string]%[1]s\n\n", typeName)
|
|
|
|
genInputMethods(w, typeName+"Map", typeName+"Map", "map[string]"+typeName, false, false)
|
|
}
|
|
|
|
// Generate the array output
|
|
if details.arrayElement {
|
|
fmt.Fprintf(w, "type %sArrayOutput struct { *pulumi.OutputState }\n\n", typeName)
|
|
|
|
genOutputMethods(w, typeName+"Array", "[]"+typeName, false)
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sArrayOutput) Index(i pulumi.IntInput) %[2]sOutput {\n", typeName, elementType)
|
|
fmt.Fprintf(w, "\treturn pulumi.All(o, i).ApplyT(func (vs []interface{}) %sOutput {\n", elementType)
|
|
fmt.Fprintf(w, "\t\treturn vs[0].([]%s)[vs[1].(int)].To%sOutput()\n", typeName, asFuncName)
|
|
fmt.Fprintf(w, "\t}).(%sOutput)\n", elementType)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
// Generate the map output.
|
|
if details.mapElement {
|
|
fmt.Fprintf(w, "type %sMapOutput struct { *pulumi.OutputState }\n\n", typeName)
|
|
|
|
genOutputMethods(w, typeName+"Map", "map[string]"+typeName, false)
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sMapOutput) MapIndex(k pulumi.StringInput) %[2]sOutput {\n", typeName, elementType)
|
|
fmt.Fprintf(w, "\treturn pulumi.All(o, k).ApplyT(func (vs []interface{}) %sOutput {\n", elementType)
|
|
fmt.Fprintf(w, "\t\treturn vs[0].(map[string]%s)[vs[1].(string)].To%sOutput()\n", typeName, asFuncName)
|
|
fmt.Fprintf(w, "\t}).(%sOutput)\n", elementType)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
}
|
|
|
|
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", Title(p.Name), pkg.typeString(codegen.ResolvedType(p.Type)), p.Name)
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
func (pkg *pkgContext) genInputTypes(w io.Writer, t *schema.ObjectType, details *typeDetails) {
|
|
contract.Assert(t.IsInputShape())
|
|
|
|
name := pkg.tokenToType(t.Token)
|
|
|
|
// Generate the plain inputs.
|
|
pkg.genInputInterface(w, name)
|
|
|
|
printComment(w, t.Comment, false)
|
|
fmt.Fprintf(w, "type %sArgs struct {\n", name)
|
|
for _, p := range t.Properties {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
|
|
fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", Title(p.Name), pkg.typeString(p.Type), p.Name)
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
genInputMethods(w, name, name+"Args", name, details.ptrElement, false)
|
|
|
|
// Generate the pointer input.
|
|
if details.ptrElement {
|
|
pkg.genInputInterface(w, name+"Ptr")
|
|
|
|
ptrTypeName := 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")
|
|
|
|
genInputMethods(w, name+"Ptr", "*"+ptrTypeName, "*"+name, false, false)
|
|
}
|
|
|
|
// Generate the array input.
|
|
if details.arrayElement {
|
|
pkg.genInputInterface(w, name+"Array")
|
|
|
|
fmt.Fprintf(w, "type %[1]sArray []%[1]sInput\n\n", name)
|
|
|
|
genInputMethods(w, name+"Array", name+"Array", "[]"+name, false, false)
|
|
}
|
|
|
|
// Generate the map input.
|
|
if details.mapElement {
|
|
pkg.genInputInterface(w, name+"Map")
|
|
|
|
fmt.Fprintf(w, "type %[1]sMap map[string]%[1]sInput\n\n", name)
|
|
|
|
genInputMethods(w, name+"Map", name+"Map", "map[string]"+name, false, false)
|
|
}
|
|
}
|
|
|
|
func genOutputMethods(w io.Writer, name, elementType string, resourceType bool) {
|
|
fmt.Fprintf(w, "func (%sOutput) ElementType() reflect.Type {\n", name)
|
|
if resourceType {
|
|
fmt.Fprintf(w, "\treturn reflect.TypeOf((*%s)(nil))\n", elementType)
|
|
} else {
|
|
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", name, Title(name))
|
|
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", name, Title(name))
|
|
fmt.Fprintf(w, "\treturn o\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
func (pkg *pkgContext) genOutputTypes(w io.Writer, t *schema.ObjectType, details *typeDetails) {
|
|
contract.Assert(!t.IsInputShape())
|
|
|
|
name := pkg.tokenToType(t.Token)
|
|
|
|
printComment(w, t.Comment, false)
|
|
fmt.Fprintf(w, "type %sOutput struct { *pulumi.OutputState }\n\n", name)
|
|
|
|
genOutputMethods(w, name, name, false)
|
|
|
|
if details.ptrElement {
|
|
fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sPtrOutput() %[1]sPtrOutput {\n", name, Title(name))
|
|
fmt.Fprintf(w, "\treturn o.To%sPtrOutputWithContext(context.Background())\n", Title(name))
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sPtrOutputWithContext(ctx context.Context) %[1]sPtrOutput {\n", name, Title(name))
|
|
fmt.Fprintf(w, "\treturn o.ApplyT(func(v %[1]s) *%[1]s {\n", name)
|
|
fmt.Fprintf(w, "\t\treturn &v\n")
|
|
fmt.Fprintf(w, "\t}).(%sPtrOutput)\n", name)
|
|
fmt.Fprintf(w, "}\n")
|
|
}
|
|
|
|
for _, p := range t.Properties {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, false)
|
|
outputType, applyType := pkg.outputType(p.Type), pkg.typeString(p.Type)
|
|
|
|
propName := Title(p.Name)
|
|
switch strings.ToLower(p.Name) {
|
|
case "elementtype", "issecret":
|
|
propName = "Get" + propName
|
|
}
|
|
fmt.Fprintf(w, "func (o %sOutput) %s() %s {\n", name, propName, outputType)
|
|
fmt.Fprintf(w, "\treturn o.ApplyT(func (v %s) %s { return v.%s }).(%s)\n", name, applyType, Title(p.Name), outputType)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
if details.ptrElement {
|
|
fmt.Fprintf(w, "type %sPtrOutput struct { *pulumi.OutputState }\n\n", name)
|
|
|
|
genOutputMethods(w, name+"Ptr", "*"+name, false)
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sPtrOutput) Elem() %[1]sOutput {\n", name)
|
|
fmt.Fprintf(w, "\treturn o.ApplyT(func (v *%[1]s) %[1]s { return *v }).(%[1]sOutput)\n", name)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
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, Title(p.Name))
|
|
fmt.Fprintf(w, "\t}).(%s)\n", outputType)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
}
|
|
|
|
if details.arrayElement {
|
|
fmt.Fprintf(w, "type %sArrayOutput struct { *pulumi.OutputState }\n\n", name)
|
|
|
|
genOutputMethods(w, name+"Array", "[]"+name, false)
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sArrayOutput) Index(i pulumi.IntInput) %[1]sOutput {\n", name)
|
|
fmt.Fprintf(w, "\treturn pulumi.All(o, i).ApplyT(func (vs []interface{}) %s {\n", name)
|
|
fmt.Fprintf(w, "\t\treturn vs[0].([]%s)[vs[1].(int)]\n", name)
|
|
fmt.Fprintf(w, "\t}).(%sOutput)\n", name)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
if details.mapElement {
|
|
fmt.Fprintf(w, "type %sMapOutput struct { *pulumi.OutputState }\n\n", name)
|
|
|
|
genOutputMethods(w, name+"Map", "map[string]"+name, false)
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sMapOutput) MapIndex(k pulumi.StringInput) %[1]sOutput {\n", name)
|
|
fmt.Fprintf(w, "\treturn pulumi.All(o, k).ApplyT(func (vs []interface{}) %s {\n", name)
|
|
fmt.Fprintf(w, "\t\treturn vs[0].(map[string]%s)[vs[1].(string)]\n", name)
|
|
fmt.Fprintf(w, "\t}).(%sOutput)\n", name)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
}
|
|
|
|
func goPrimitiveValue(value interface{}) (string, error) {
|
|
v := reflect.ValueOf(value)
|
|
if v.Kind() == reflect.Interface {
|
|
v = v.Elem()
|
|
}
|
|
|
|
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:
|
|
return strconv.FormatFloat(v.Float(), 'f', -1, 64), nil
|
|
case reflect.String:
|
|
return fmt.Sprintf("%q", v.String()), nil
|
|
default:
|
|
return "", errors.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
|
|
}
|
|
|
|
func (pkg *pkgContext) getDefaultValue(dv *schema.DefaultValue, t schema.Type) (string, error) {
|
|
var val string
|
|
if dv.Value != nil {
|
|
v, err := goPrimitiveValue(dv.Value)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
val = v
|
|
}
|
|
|
|
if len(dv.Environment) > 0 {
|
|
pkg.needsUtils = true
|
|
|
|
parser, typDefault, typ := "nil", "\"\"", "string"
|
|
switch codegen.UnwrapType(t).(type) {
|
|
case *schema.ArrayType:
|
|
parser, typDefault, typ = "parseEnvStringArray", "pulumi.StringArray{}", "pulumi.StringArray"
|
|
}
|
|
switch t {
|
|
case schema.BoolType:
|
|
parser, typDefault, typ = "parseEnvBool", "false", "bool"
|
|
case schema.IntType:
|
|
parser, typDefault, typ = "parseEnvInt", "0", "int"
|
|
case schema.NumberType:
|
|
parser, typDefault, typ = "parseEnvFloat", "0.0", "float64"
|
|
}
|
|
|
|
if val == "" {
|
|
val = typDefault
|
|
}
|
|
|
|
val = fmt.Sprintf("getEnvOrDefault(%s, %s", val, parser)
|
|
for _, e := range dv.Environment {
|
|
val += fmt.Sprintf(", %q", e)
|
|
}
|
|
val = fmt.Sprintf("%s).(%s)", val, typ)
|
|
}
|
|
|
|
return val, nil
|
|
}
|
|
|
|
func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource, generateResourceContainerTypes bool) error {
|
|
name := resourceName(r)
|
|
|
|
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
|
|
for _, p := range r.Properties {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
|
|
fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", Title(p.Name), pkg.outputType(p.Type), 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.
|
|
for _, p := range r.InputProperties {
|
|
switch codegen.UnwrapType(p.Type).(type) {
|
|
case *schema.EnumType:
|
|
// not a pointer type and already handled above
|
|
default:
|
|
if p.IsRequired() && isNilType(p.Type) {
|
|
fmt.Fprintf(w, "\tif args.%s == nil {\n", Title(p.Name))
|
|
fmt.Fprintf(w, "\t\treturn nil, errors.New(\"invalid value for required argument '%s'\")\n", Title(p.Name))
|
|
fmt.Fprintf(w, "\t}\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, p := range r.InputProperties {
|
|
if p.ConstValue != nil {
|
|
v, err := pkg.getConstValue(p.ConstValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
t := strings.TrimSuffix(pkg.inputType(p.Type), "Input")
|
|
if t == "pulumi." {
|
|
t = "pulumi.Any"
|
|
}
|
|
|
|
fmt.Fprintf(w, "\targs.%s = %s(%s)\n", Title(p.Name), t, v)
|
|
}
|
|
if p.DefaultValue != nil {
|
|
v, err := pkg.getDefaultValue(p.DefaultValue, codegen.UnwrapType(p.Type))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
t := strings.TrimSuffix(pkg.inputType(p.Type), "Input")
|
|
if t == "pulumi." {
|
|
t = "pulumi.Any"
|
|
}
|
|
|
|
switch typ := codegen.UnwrapType(p.Type).(type) {
|
|
case *schema.EnumType:
|
|
if p.IsRequired() {
|
|
switch typ.ElementType {
|
|
// Only string and numeric types are supported for enums
|
|
case schema.StringType:
|
|
fmt.Fprintf(w, "\tif args.%s == \"\" {\n", Title(p.Name))
|
|
case schema.IntType, schema.NumberType:
|
|
fmt.Fprintf(w, "\tif args.%s == 0 {\n", Title(p.Name))
|
|
default:
|
|
contract.Assertf(false, "unxpected type %T for enum: %s", typ, typ.Token)
|
|
}
|
|
fmt.Fprintf(w, "\t\targs.%s = %s(%s)\n", Title(p.Name), t, v)
|
|
fmt.Fprintf(w, "\t}\n")
|
|
} else {
|
|
fmt.Fprintf(w, "\tif args.%s == nil {\n", Title(p.Name))
|
|
|
|
// Enum types are themselves inputs so pkg.InputType() returns *<EnumType>
|
|
// when the type is optional. We want the generated code to look like this:
|
|
// e:= <EnumType>(<Default>)
|
|
// args.<Name> = &e
|
|
fmt.Fprintf(w, "\te := %s(%s)\n", pkg.inputType(codegen.RequiredType(p)), v)
|
|
fmt.Fprintf(w, "\t\targs.%s = &e\n", Title(p.Name))
|
|
fmt.Fprintf(w, "\t}\n")
|
|
}
|
|
default:
|
|
fmt.Fprintf(w, "\tif args.%s == nil {\n", Title(p.Name))
|
|
fmt.Fprintf(w, "\t\targs.%s = %s(%s)\n", Title(p.Name), t, v)
|
|
fmt.Fprintf(w, "\t}\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.Name != nil {
|
|
s += fmt.Sprintf("\t\t\tName: pulumi.String(%q),\n", *alias.Name)
|
|
}
|
|
if alias.Project != nil {
|
|
s += fmt.Sprintf("\t\t\tProject: pulumi.String(%q),\n", *alias.Project)
|
|
}
|
|
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")
|
|
}
|
|
if len(secretProps) > 0 {
|
|
for _, p := range secretProps {
|
|
fmt.Fprintf(w, "\tif args.%s != nil {\n", Title(p.Name))
|
|
fmt.Fprintf(w, "\t\targs.%[1]s = pulumi.ToSecret(args.%[1]s).(%[2]s)\n", Title(p.Name), pkg.outputType(p.Type))
|
|
fmt.Fprintf(w, "\t}\n")
|
|
}
|
|
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")
|
|
}
|
|
|
|
// Finally make the call to registration.
|
|
fmt.Fprintf(w, "\tvar resource %s\n", name)
|
|
if r.IsComponent {
|
|
fmt.Fprintf(w, "\terr := ctx.RegisterRemoteComponentResource(\"%s\", name, args, &resource, opts...)\n", r.Token)
|
|
} else {
|
|
fmt.Fprintf(w, "\terr := ctx.RegisterResource(\"%s\", name, args, &resource, opts...)\n", r.Token)
|
|
}
|
|
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)
|
|
fmt.Fprintf(w, "\terr := ctx.ReadResource(\"%s\", name, id, state, &resource, opts...)\n", r.Token)
|
|
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", 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", Title(p.Name), 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)
|
|
fmt.Fprintf(w, "\t%s %s\n", Title(p.Name), pkg.inputType(p.Type))
|
|
}
|
|
}
|
|
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", camel(name))
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
// Emit the args types.
|
|
fmt.Fprintf(w, "type %sArgs struct {\n", camel(name))
|
|
for _, p := range r.InputProperties {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
|
|
fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", Title(p.Name), pkg.typeString(codegen.ResolvedType(p.Type)), 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 {
|
|
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
|
|
fmt.Fprintf(w, "\t%s %s\n", Title(p.Name), pkg.typeString(p.Type))
|
|
}
|
|
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", camel(name))
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
// Emit the resource input type.
|
|
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")
|
|
|
|
genInputMethods(w, name, "*"+name, name, generateResourceContainerTypes, true)
|
|
|
|
if generateResourceContainerTypes {
|
|
// Emit the resource pointer input type.
|
|
fmt.Fprintf(w, "type %sPtrInput interface {\n", name)
|
|
fmt.Fprintf(w, "\tpulumi.Input\n\n")
|
|
fmt.Fprintf(w, "\tTo%[1]sPtrOutput() %[1]sPtrOutput\n", name)
|
|
fmt.Fprintf(w, "\tTo%[1]sPtrOutputWithContext(ctx context.Context) %[1]sPtrOutput\n", name)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
ptrTypeName := camel(name) + "PtrType"
|
|
fmt.Fprintf(w, "type %s %sArgs\n\n", ptrTypeName, name)
|
|
genInputMethods(w, name+"Ptr", "*"+ptrTypeName, "*"+name, false, true)
|
|
|
|
if !r.IsProvider {
|
|
// Generate the resource array input.
|
|
pkg.genInputInterface(w, name+"Array")
|
|
fmt.Fprintf(w, "type %[1]sArray []%[1]sInput\n\n", name)
|
|
genResourceContainerInput(w, name+"Array", name+"Array", "[]*"+name)
|
|
|
|
// Generate the resource map input.
|
|
pkg.genInputInterface(w, name+"Map")
|
|
fmt.Fprintf(w, "type %[1]sMap map[string]%[1]sInput\n\n", name)
|
|
genResourceContainerInput(w, name+"Map", name+"Map", "map[string]*"+name)
|
|
}
|
|
}
|
|
|
|
// Emit the resource output type.
|
|
fmt.Fprintf(w, "type %sOutput struct {\n", name)
|
|
fmt.Fprintf(w, "\t*pulumi.OutputState\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
genOutputMethods(w, name, name, true)
|
|
fmt.Fprintf(w, "\n")
|
|
if generateResourceContainerTypes {
|
|
fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sPtrOutput() %[1]sPtrOutput {\n", name, Title(name))
|
|
fmt.Fprintf(w, "\treturn o.To%sPtrOutputWithContext(context.Background())\n", Title(name))
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sPtrOutputWithContext(ctx context.Context) %[1]sPtrOutput {\n", name, Title(name))
|
|
fmt.Fprintf(w, "\treturn o.ApplyT(func(v %[1]s) *%[1]s {\n", name)
|
|
fmt.Fprintf(w, "\t\treturn &v\n")
|
|
fmt.Fprintf(w, "\t}).(%sPtrOutput)\n", name)
|
|
fmt.Fprintf(w, "}\n")
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
// Emit the resource pointer output type.
|
|
fmt.Fprintf(w, "type %sOutput struct {\n", name+"Ptr")
|
|
fmt.Fprintf(w, "\t*pulumi.OutputState\n")
|
|
fmt.Fprintf(w, "}\n\n")
|
|
genOutputMethods(w, name+"Ptr", "*"+name, true)
|
|
|
|
if !r.IsProvider {
|
|
// Emit the array output type
|
|
fmt.Fprintf(w, "type %sArrayOutput struct { *pulumi.OutputState }\n\n", name)
|
|
genOutputMethods(w, name+"Array", "[]"+name, true)
|
|
fmt.Fprintf(w, "func (o %[1]sArrayOutput) Index(i pulumi.IntInput) %[1]sOutput {\n", name)
|
|
fmt.Fprintf(w, "\treturn pulumi.All(o, i).ApplyT(func (vs []interface{}) %s {\n", name)
|
|
fmt.Fprintf(w, "\t\treturn vs[0].([]%s)[vs[1].(int)]\n", name)
|
|
fmt.Fprintf(w, "\t}).(%sOutput)\n", name)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
// Emit the map output type
|
|
fmt.Fprintf(w, "type %sMapOutput struct { *pulumi.OutputState }\n\n", name)
|
|
genOutputMethods(w, name+"Map", "map[string]"+name, true)
|
|
fmt.Fprintf(w, "func (o %[1]sMapOutput) MapIndex(k pulumi.StringInput) %[1]sOutput {\n", name)
|
|
fmt.Fprintf(w, "\treturn pulumi.All(o, k).ApplyT(func (vs []interface{}) %s {\n", name)
|
|
fmt.Fprintf(w, "\t\treturn vs[0].(map[string]%s)[vs[1].(string)]\n", name)
|
|
fmt.Fprintf(w, "\t}).(%sOutput)\n", name)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
}
|
|
// Register all output types
|
|
fmt.Fprintf(w, "func init() {\n")
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sOutput{})\n", name)
|
|
|
|
if generateResourceContainerTypes {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sPtrOutput{})\n", name)
|
|
if !r.IsProvider {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sArrayOutput{})\n", name)
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sMapOutput{})\n", name)
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "}\n\n")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (pkg *pkgContext) genFunction(w io.Writer, f *schema.Function) {
|
|
// If the function starts with New or Get, it will conflict; so rename them.
|
|
name := pkg.functionNames[f]
|
|
|
|
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 f.Outputs == 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 {
|
|
inputsVar = "args"
|
|
}
|
|
|
|
// Now simply invoke the runtime function with the arguments.
|
|
var outputsType string
|
|
if f.Outputs == nil {
|
|
outputsType = "struct{}"
|
|
} else {
|
|
outputsType = name + "Result"
|
|
}
|
|
fmt.Fprintf(w, "\tvar rv %s\n", outputsType)
|
|
fmt.Fprintf(w, "\terr := ctx.Invoke(\"%s\", %s, &rv, opts...)\n", f.Token, inputsVar)
|
|
|
|
if f.Outputs == 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.
|
|
fmt.Fprintf(w, "\treturn &rv, nil\n")
|
|
}
|
|
fmt.Fprintf(w, "}\n")
|
|
|
|
// If there are argument and/or return types, emit them.
|
|
if f.Inputs != nil {
|
|
fmt.Fprintf(w, "\n")
|
|
pkg.genPlainType(w, fmt.Sprintf("%sArgs", name), f.Inputs.Comment, "", f.Inputs.Properties)
|
|
}
|
|
if f.Outputs != nil {
|
|
fmt.Fprintf(w, "\n")
|
|
pkg.genPlainType(w, fmt.Sprintf("%sResult", name), f.Outputs.Comment, "", f.Outputs.Properties)
|
|
}
|
|
}
|
|
|
|
func (pkg *pkgContext) genType(w io.Writer, obj *schema.ObjectType) {
|
|
contract.Assert(!obj.IsInputShape())
|
|
|
|
pkg.genPlainType(w, pkg.tokenToType(obj.Token), obj.Comment, "", obj.Properties)
|
|
pkg.genInputTypes(w, obj.InputShape, pkg.detailsForType(obj))
|
|
pkg.genOutputTypes(w, obj, pkg.detailsForType(obj))
|
|
}
|
|
|
|
func (pkg *pkgContext) addSuffixesToName(typ schema.Type, name string) []string {
|
|
var names []string
|
|
details := pkg.detailsForType(typ)
|
|
if details.arrayElement {
|
|
names = append(names, name+"Array")
|
|
}
|
|
if details.mapElement {
|
|
names = append(names, name+"Map")
|
|
}
|
|
return names
|
|
}
|
|
|
|
func (pkg *pkgContext) genNestedCollectionType(w io.Writer, typ schema.Type) []string {
|
|
var elementTypeName string
|
|
var names []string
|
|
switch t := typ.(type) {
|
|
case *schema.ArrayType:
|
|
// Builtins already cater to primitive arrays
|
|
if schema.IsPrimitiveType(t.ElementType) {
|
|
return nil
|
|
}
|
|
elementTypeName = pkg.nestedTypeToType(t.ElementType)
|
|
elementTypeName += "Array"
|
|
names = pkg.addSuffixesToName(t, elementTypeName)
|
|
case *schema.MapType:
|
|
// Builtins already cater to primitive maps
|
|
if schema.IsPrimitiveType(t.ElementType) {
|
|
return nil
|
|
}
|
|
elementTypeName = pkg.nestedTypeToType(t.ElementType)
|
|
elementTypeName += "Map"
|
|
names = pkg.addSuffixesToName(t, elementTypeName)
|
|
default:
|
|
contract.Failf("unexpected type %T in genNestedCollectionType", t)
|
|
}
|
|
|
|
for _, name := range names {
|
|
if strings.HasSuffix(name, "Array") {
|
|
fmt.Fprintf(w, "type %s []%sInput\n\n", name, elementTypeName)
|
|
genInputMethods(w, name, name, elementTypeName, false, false)
|
|
|
|
fmt.Fprintf(w, "type %sOutput struct { *pulumi.OutputState }\n\n", name)
|
|
genOutputMethods(w, name, elementTypeName, false)
|
|
|
|
fmt.Fprintf(w, "func (o %sOutput) Index(i pulumi.IntInput) %sOutput {\n", name, elementTypeName)
|
|
fmt.Fprintf(w, "\treturn pulumi.All(o, i).ApplyT(func (vs []interface{}) %s {\n", elementTypeName)
|
|
fmt.Fprintf(w, "\t\treturn vs[0].([]%s)[vs[1].(int)]\n", elementTypeName)
|
|
fmt.Fprintf(w, "\t}).(%sOutput)\n", elementTypeName)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
|
|
if strings.HasSuffix(name, "Map") {
|
|
fmt.Fprintf(w, "type %s map[string]%sInput\n\n", name, elementTypeName)
|
|
genInputMethods(w, name, name, elementTypeName, false, false)
|
|
|
|
fmt.Fprintf(w, "type %sOutput struct { *pulumi.OutputState }\n\n", name)
|
|
genOutputMethods(w, name, elementTypeName, false)
|
|
|
|
// Emit the map output type
|
|
fmt.Fprintf(w, "func (o %sOutput) MapIndex(k pulumi.StringInput) %sOutput {\n", name, elementTypeName)
|
|
fmt.Fprintf(w, "\treturn pulumi.All(o, k).ApplyT(func (vs []interface{}) %s {\n", elementTypeName)
|
|
fmt.Fprintf(w, "\t\treturn vs[0].(map[string]%s)[vs[1].(string)]\n", elementTypeName)
|
|
fmt.Fprintf(w, "\t}).(%sOutput)\n", elementTypeName)
|
|
fmt.Fprintf(w, "}\n\n")
|
|
}
|
|
pkg.genInputInterface(w, name)
|
|
}
|
|
|
|
return names
|
|
}
|
|
|
|
func (pkg *pkgContext) nestedTypeToType(typ schema.Type) string {
|
|
switch t := codegen.UnwrapType(typ).(type) {
|
|
case *schema.ArrayType:
|
|
return pkg.nestedTypeToType(t.ElementType)
|
|
case *schema.MapType:
|
|
return pkg.nestedTypeToType(t.ElementType)
|
|
case *schema.ObjectType:
|
|
return pkg.resolveObjectType(t)
|
|
}
|
|
return pkg.tokenToType(typ.String())
|
|
}
|
|
|
|
func (pkg *pkgContext) tokenToEnum(tok string) string {
|
|
// token := pkg : module : member
|
|
// module := path/to/module
|
|
|
|
components := strings.Split(tok, ":")
|
|
contract.Assert(len(components) == 3)
|
|
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))
|
|
}
|
|
|
|
mod, name := pkg.tokenToPackage(tok), components[2]
|
|
|
|
modPkg, ok := pkg.packages[mod]
|
|
name = Title(name)
|
|
|
|
if ok {
|
|
newName, renamed := modPkg.renamed[name]
|
|
if renamed {
|
|
name = newName
|
|
} else if modPkg.names.Has(name) {
|
|
// If the package containing the enum's token already has a resource with the
|
|
// same name, add a `Enum` suffix.
|
|
newName := name + "Enum"
|
|
modPkg.renamed[name] = newName
|
|
modPkg.names.Add(newName)
|
|
name = newName
|
|
}
|
|
}
|
|
|
|
if mod == pkg.mod {
|
|
return name
|
|
}
|
|
if mod == "" {
|
|
mod = components[0]
|
|
}
|
|
return strings.Replace(mod, "/", "", -1) + "." + name
|
|
}
|
|
|
|
func (pkg *pkgContext) genTypeRegistrations(w io.Writer, objTypes []*schema.ObjectType, types ...string) {
|
|
fmt.Fprintf(w, "func init() {\n")
|
|
for _, obj := range objTypes {
|
|
name, details := pkg.tokenToType(obj.Token), pkg.detailsForType(obj)
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sOutput{})\n", name)
|
|
if details.ptrElement {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sPtrOutput{})\n", name)
|
|
}
|
|
if details.arrayElement {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sArrayOutput{})\n", name)
|
|
}
|
|
if details.mapElement {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sMapOutput{})\n", name)
|
|
}
|
|
}
|
|
|
|
for _, t := range types {
|
|
fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sOutput{})\n", t)
|
|
}
|
|
fmt.Fprintf(w, "}\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{}{}
|
|
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:
|
|
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 t.Package != nil && pkg.pkg != nil && t.Package != pkg.pkg {
|
|
extPkg := t.Package
|
|
var goInfo GoPackageInfo
|
|
|
|
contract.AssertNoError(extPkg.ImportLanguages(map[string]schema.Language{"go": Importer}))
|
|
if info, ok := extPkg.Language["go"].(GoPackageInfo); ok {
|
|
goInfo = info
|
|
} else {
|
|
// tests don't include ImportBasePath
|
|
goInfo.ImportBasePath = extractImportBasePath(extPkg)
|
|
}
|
|
extPkgCtx := &pkgContext{
|
|
pkg: extPkg,
|
|
importBasePath: goInfo.ImportBasePath,
|
|
pkgImportAliases: goInfo.PackageImportAliases,
|
|
modToPkg: goInfo.ModuleToPackage,
|
|
}
|
|
mod := extPkgCtx.tokenToPackage(t.Token)
|
|
imp := path.Join(goInfo.ImportBasePath, mod)
|
|
importsAndAliases[imp] = goInfo.PackageImportAliases[imp]
|
|
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 {
|
|
pkg.getTypeImports(p.Type, recurse, importsAndAliases, seen)
|
|
}
|
|
}
|
|
case *schema.ResourceType:
|
|
if t.Resource != nil && pkg.pkg != nil && t.Resource.Package != pkg.pkg {
|
|
extPkg := t.Resource.Package
|
|
var goInfo GoPackageInfo
|
|
|
|
contract.AssertNoError(extPkg.ImportLanguages(map[string]schema.Language{"go": Importer}))
|
|
if info, ok := extPkg.Language["go"].(GoPackageInfo); ok {
|
|
goInfo = info
|
|
} else {
|
|
// tests don't include ImportBasePath
|
|
goInfo.ImportBasePath = extractImportBasePath(extPkg)
|
|
}
|
|
extPkgCtx := &pkgContext{
|
|
pkg: extPkg,
|
|
importBasePath: goInfo.ImportBasePath,
|
|
pkgImportAliases: goInfo.PackageImportAliases,
|
|
modToPkg: goInfo.ModuleToPackage,
|
|
}
|
|
mod := extPkgCtx.tokenToPackage(t.Token)
|
|
imp := path.Join(goInfo.ImportBasePath, mod)
|
|
importsAndAliases[imp] = goInfo.PackageImportAliases[imp]
|
|
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 extractImportBasePath(extPkg *schema.Package) string {
|
|
version := extPkg.Version.Major
|
|
var vPath string
|
|
if version > 1 {
|
|
vPath = fmt.Sprintf("/v%d", version)
|
|
}
|
|
return fmt.Sprintf("github.com/pulumi/pulumi-%s/sdk%s/go/%s", extPkg.Name, vPath, extPkg.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["github.com/pkg/errors"] = ""
|
|
}
|
|
}
|
|
case *schema.Function:
|
|
if member.Inputs != nil {
|
|
pkg.getTypeImports(member.Inputs, true, importsAndAliases, seen)
|
|
}
|
|
if member.Outputs != nil {
|
|
pkg.getTypeImports(member.Outputs, true, importsAndAliases, seen)
|
|
}
|
|
case []*schema.Property:
|
|
for _, p := range member {
|
|
pkg.getTypeImports(p.Type, false, importsAndAliases, seen)
|
|
}
|
|
case *schema.EnumType: // Just need pulumi sdk, see below
|
|
default:
|
|
return
|
|
}
|
|
|
|
importsAndAliases["github.com/pulumi/pulumi/sdk/v3/go/pulumi"] = ""
|
|
}
|
|
|
|
func (pkg *pkgContext) genHeader(w io.Writer, goImports []string, importsAndAliases map[string]string) {
|
|
fmt.Fprintf(w, "// *** WARNING: this file was generated by %v. ***\n", pkg.tool)
|
|
fmt.Fprintf(w, "// *** Do not edit by hand unless you're certain you know what you are doing! ***\n\n")
|
|
|
|
var pkgName string
|
|
if pkg.mod == "" {
|
|
pkgName = pkg.rootPackageName
|
|
if pkgName == "" {
|
|
pkgName = goPackage(pkg.pkg.Name)
|
|
}
|
|
} 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": ""}
|
|
pkg.getImports(variables, importsAndAliases)
|
|
|
|
pkg.genHeader(w, nil, importsAndAliases)
|
|
|
|
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, camel(p.Name))
|
|
|
|
fmt.Fprintf(w, "func Get%s(ctx *pulumi.Context) %s {\n", Title(p.Name), getType)
|
|
if p.DefaultValue != nil {
|
|
defaultValue, err := pkg.getDefaultValue(p.DefaultValue, codegen.UnwrapType(p.Type))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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, "\treturn %s", defaultValue)
|
|
} 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) {
|
|
contract.Assert(len(pkg.resources) != 0)
|
|
|
|
basePath := pkg.importBasePath
|
|
|
|
// TODO: importBasePath isn't currently set for schemas generated by pulumi-terraform-bridge.
|
|
// Remove this once the linked issue is fixed. https://github.com/pulumi/pulumi-terraform-bridge/issues/320
|
|
if len(basePath) == 0 {
|
|
basePath = fmt.Sprintf("github.com/pulumi/pulumi-%[1]s/sdk/v2/go/%[1]s", pkg.pkg.Name)
|
|
}
|
|
|
|
imports := map[string]string{
|
|
"github.com/blang/semver": "",
|
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi": "",
|
|
}
|
|
topLevelModule := pkg.mod == ""
|
|
if !topLevelModule {
|
|
imports[basePath] = ""
|
|
}
|
|
|
|
pkg.genHeader(w, []string{"fmt"}, imports)
|
|
|
|
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.IsProvider {
|
|
contract.Assert(provider == nil)
|
|
provider = r
|
|
continue
|
|
}
|
|
|
|
registrations.Add(tokenToModule(r.Token))
|
|
fmt.Fprintf(w, "\tcase %q:\n", r.Token)
|
|
fmt.Fprintf(w, "\t\tr = &%s{}\n", resourceName(r))
|
|
}
|
|
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")
|
|
if topLevelModule {
|
|
fmt.Fprintf(w, "\tversion, err := PkgVersion()\n")
|
|
} else {
|
|
// Some package names contain '-' characters, so grab the name from the base path.
|
|
pkgName := basePath[strings.LastIndex(basePath, "/")+1:]
|
|
fmt.Fprintf(w, "\tversion, err := %s.PkgVersion()\n", pkgName)
|
|
}
|
|
fmt.Fprintf(w, "\tif err != nil {\n")
|
|
fmt.Fprintf(w, "\t\tfmt.Printf(\"failed to determine package version. defaulting to v1: %%v\\n\", err)\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")
|
|
}
|
|
fmt.Fprintf(w, "}\n")
|
|
}
|
|
|
|
// generatePackageContextMap groups resources, types, and functions into Go packages.
|
|
func generatePackageContextMap(tool string, pkg *schema.Package, goInfo GoPackageInfo) map[string]*pkgContext {
|
|
packages := map[string]*pkgContext{}
|
|
getPkg := func(mod string) *pkgContext {
|
|
pack, ok := packages[mod]
|
|
if !ok {
|
|
pack = &pkgContext{
|
|
pkg: pkg,
|
|
mod: mod,
|
|
importBasePath: goInfo.ImportBasePath,
|
|
rootPackageName: goInfo.RootPackageName,
|
|
typeDetails: map[schema.Type]*typeDetails{},
|
|
names: codegen.NewStringSet(),
|
|
schemaNames: codegen.NewStringSet(),
|
|
renamed: map[string]string{},
|
|
functionNames: map[*schema.Function]string{},
|
|
tool: tool,
|
|
modToPkg: goInfo.ModuleToPackage,
|
|
pkgImportAliases: goInfo.PackageImportAliases,
|
|
packages: packages,
|
|
}
|
|
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)
|
|
default:
|
|
return getPkgFromToken(t.String())
|
|
}
|
|
}
|
|
|
|
if len(pkg.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, parentOptional bool)
|
|
var populateDetailsForTypes func(seen codegen.StringSet, schemaType schema.Type, isRequired bool, parentOptional bool)
|
|
|
|
populateDetailsForPropertyTypes = func(seen codegen.StringSet, props []*schema.Property, parentOptional bool) {
|
|
for _, p := range props {
|
|
populateDetailsForTypes(seen, p.Type, p.IsRequired(), parentOptional)
|
|
}
|
|
}
|
|
|
|
populateDetailsForTypes = func(seen codegen.StringSet, schemaType schema.Type, isRequired bool, parentOptional bool) {
|
|
switch typ := schemaType.(type) {
|
|
case *schema.InputType:
|
|
populateDetailsForTypes(seen, typ.ElementType, isRequired, parentOptional)
|
|
case *schema.OptionalType:
|
|
populateDetailsForTypes(seen, typ.ElementType, false, true)
|
|
case *schema.ObjectType:
|
|
pkg := getPkgFromToken(typ.Token)
|
|
if !isRequired || parentOptional {
|
|
if seen.Has(typ.Token) {
|
|
return
|
|
}
|
|
seen.Add(typ.Token)
|
|
pkg.detailsForType(typ).ptrElement = true
|
|
populateDetailsForPropertyTypes(seen, typ.Properties, true)
|
|
}
|
|
pkg.schemaNames.Add(tokenToName(typ.Token))
|
|
case *schema.EnumType:
|
|
if seen.Has(typ.Token) {
|
|
return
|
|
}
|
|
seen.Add(typ.Token)
|
|
pkg := getPkgFromToken(typ.Token)
|
|
if !isRequired || parentOptional {
|
|
pkg.detailsForType(typ).ptrElement = true
|
|
}
|
|
pkg.schemaNames.Add(tokenToName(typ.Token))
|
|
case *schema.ArrayType:
|
|
if seen.Has(typ.String()) {
|
|
return
|
|
}
|
|
seen.Add(typ.String())
|
|
getPkgFromType(typ.ElementType).detailsForType(codegen.UnwrapType(typ.ElementType)).arrayElement = true
|
|
populateDetailsForTypes(seen, typ.ElementType, true, false)
|
|
case *schema.MapType:
|
|
if seen.Has(typ.String()) {
|
|
return
|
|
}
|
|
seen.Add(typ.String())
|
|
getPkgFromType(typ.ElementType).detailsForType(codegen.UnwrapType(typ.ElementType)).mapElement = true
|
|
populateDetailsForTypes(seen, typ.ElementType, true, false)
|
|
}
|
|
}
|
|
|
|
// 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 pkg.Types {
|
|
switch typ := t.(type) {
|
|
case *schema.ArrayType:
|
|
getPkgFromType(typ.ElementType).detailsForType(typ.ElementType).arrayElement = true
|
|
case *schema.MapType:
|
|
getPkgFromType(typ.ElementType).detailsForType(typ.ElementType).mapElement = true
|
|
case *schema.ObjectType:
|
|
pkg := getPkgFromToken(typ.Token)
|
|
if !typ.IsInputShape() {
|
|
pkg.types = append(pkg.types, typ)
|
|
}
|
|
populateDetailsForPropertyTypes(seenMap, typ.Properties, false)
|
|
case *schema.EnumType:
|
|
pkg := getPkgFromToken(typ.Token)
|
|
pkg.enums = append(pkg.enums, typ)
|
|
}
|
|
}
|
|
|
|
scanResource := func(r *schema.Resource) {
|
|
pkg := getPkgFromToken(r.Token)
|
|
pkg.resources = append(pkg.resources, r)
|
|
pkg.schemaNames.Add(tokenToName(r.Token))
|
|
|
|
pkg.names.Add(resourceName(r))
|
|
pkg.names.Add(resourceName(r) + "Input")
|
|
pkg.names.Add(resourceName(r) + "Output")
|
|
pkg.names.Add(resourceName(r) + "Args")
|
|
pkg.names.Add(camel(resourceName(r)) + "Args")
|
|
pkg.names.Add("New" + resourceName(r))
|
|
if !r.IsProvider && !r.IsComponent {
|
|
pkg.names.Add(resourceName(r) + "State")
|
|
pkg.names.Add(camel(resourceName(r)) + "State")
|
|
pkg.names.Add("Get" + resourceName(r))
|
|
}
|
|
|
|
populateDetailsForPropertyTypes(seenMap, r.InputProperties, !r.IsProvider)
|
|
populateDetailsForPropertyTypes(seenMap, r.Properties, !r.IsProvider)
|
|
}
|
|
|
|
scanResource(pkg.Provider)
|
|
for _, r := range pkg.Resources {
|
|
scanResource(r)
|
|
}
|
|
|
|
for _, f := range pkg.Functions {
|
|
pkg := getPkgFromToken(f.Token)
|
|
pkg.functions = append(pkg.functions, f)
|
|
|
|
name := tokenToName(f.Token)
|
|
originalName := name
|
|
if pkg.names.Has(name) {
|
|
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 {
|
|
pkg.names.Add(name + "Args")
|
|
if originalName != name {
|
|
pkg.renamed[originalName+"Args"] = name + "Args"
|
|
}
|
|
}
|
|
if f.Outputs != nil {
|
|
pkg.names.Add(name + "Result")
|
|
if originalName != name {
|
|
pkg.renamed[originalName+"Result"] = name + "Result"
|
|
}
|
|
}
|
|
}
|
|
|
|
return packages
|
|
}
|
|
|
|
// 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 := generatePackageContextMap(tool, pkg, goPkgInfo)
|
|
|
|
// emit each package
|
|
var pkgMods []string
|
|
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 {
|
|
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
|
|
}
|
|
|
|
func GeneratePackage(tool string, pkg *schema.Package) (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
|
|
}
|
|
packages := generatePackageContextMap(tool, pkg, goPkgInfo)
|
|
|
|
// emit each package
|
|
var pkgMods []string
|
|
for mod := range packages {
|
|
pkgMods = append(pkgMods, mod)
|
|
}
|
|
sort.Strings(pkgMods)
|
|
|
|
name := goPkgInfo.RootPackageName
|
|
if name == "" {
|
|
name = goPackage(pkg.Name)
|
|
}
|
|
|
|
files := map[string][]byte{}
|
|
setFile := func(relPath, contents string) {
|
|
if goPkgInfo.RootPackageName == "" {
|
|
relPath = path.Join(goPackage(name), relPath)
|
|
}
|
|
if _, ok := files[relPath]; ok {
|
|
panic(errors.Errorf("duplicate file: %s", 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(errors.Wrapf(err, "invalid Go source code:\n\n%s\n", relPath))
|
|
}
|
|
|
|
files[relPath] = formattedSource
|
|
}
|
|
|
|
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)
|
|
fmt.Fprintf(buffer, "//\n")
|
|
} else {
|
|
fmt.Fprintf(buffer, "// Package %[1]s exports types, functions, subpackages for provisioning %[1]s resources.\n", pkg.pkg.Name)
|
|
fmt.Fprintf(buffer, "//\n")
|
|
}
|
|
fmt.Fprintf(buffer, "package %s\n", name)
|
|
|
|
setFile(path.Join(mod, "doc.go"), buffer.String())
|
|
|
|
case "config":
|
|
if len(pkg.pkg.Config) > 0 {
|
|
buffer := &bytes.Buffer{}
|
|
if err := pkg.genConfig(buffer, pkg.pkg.Config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
setFile(path.Join(mod, "config.go"), buffer.String())
|
|
}
|
|
}
|
|
|
|
// Resources
|
|
for _, r := range pkg.resources {
|
|
importsAndAliases := map[string]string{}
|
|
pkg.getImports(r, importsAndAliases)
|
|
|
|
buffer := &bytes.Buffer{}
|
|
pkg.genHeader(buffer, []string{"context", "reflect"}, importsAndAliases)
|
|
|
|
if err := pkg.genResource(buffer, r, goPkgInfo.GenerateResourceContainerTypes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
setFile(path.Join(mod, camel(resourceName(r))+".go"), buffer.String())
|
|
}
|
|
|
|
// Functions
|
|
for _, f := range pkg.functions {
|
|
importsAndAliases := map[string]string{}
|
|
pkg.getImports(f, importsAndAliases)
|
|
|
|
buffer := &bytes.Buffer{}
|
|
pkg.genHeader(buffer, nil, importsAndAliases)
|
|
|
|
pkg.genFunction(buffer, f)
|
|
|
|
setFile(path.Join(mod, camel(tokenToName(f.Token))+".go"), buffer.String())
|
|
}
|
|
|
|
knownTypes := make(map[schema.Type]struct{}, len(pkg.typeDetails))
|
|
for t := range pkg.typeDetails {
|
|
knownTypes[t] = struct{}{}
|
|
}
|
|
|
|
// Enums
|
|
if len(pkg.enums) > 0 {
|
|
imports := map[string]string{}
|
|
for _, e := range pkg.enums {
|
|
pkg.getImports(e, imports)
|
|
}
|
|
|
|
buffer := &bytes.Buffer{}
|
|
pkg.genHeader(buffer, []string{"context", "reflect"}, imports)
|
|
|
|
for _, e := range pkg.enums {
|
|
if err := pkg.genEnum(buffer, e); err != nil {
|
|
return nil, err
|
|
}
|
|
delete(knownTypes, e)
|
|
}
|
|
setFile(path.Join(mod, "pulumiEnums.go"), buffer.String())
|
|
}
|
|
|
|
// Types
|
|
if len(pkg.types) > 0 {
|
|
importsAndAliases := map[string]string{}
|
|
for _, t := range pkg.types {
|
|
pkg.getImports(t, importsAndAliases)
|
|
}
|
|
|
|
buffer := &bytes.Buffer{}
|
|
pkg.genHeader(buffer, []string{"context", "reflect"}, importsAndAliases)
|
|
|
|
for _, t := range pkg.types {
|
|
pkg.genType(buffer, t)
|
|
delete(knownTypes, t)
|
|
}
|
|
|
|
sortedKnownTypes := make([]schema.Type, 0, 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()
|
|
})
|
|
|
|
var types []string
|
|
for _, t := range sortedKnownTypes {
|
|
switch typ := t.(type) {
|
|
case *schema.ArrayType, *schema.MapType:
|
|
types = pkg.genNestedCollectionType(buffer, typ)
|
|
}
|
|
}
|
|
|
|
pkg.genTypeRegistrations(buffer, pkg.types, types...)
|
|
|
|
setFile(path.Join(mod, "pulumiTypes.go"), buffer.String())
|
|
}
|
|
|
|
// Utilities
|
|
if pkg.needsUtils || len(mod) == 0 {
|
|
buffer := &bytes.Buffer{}
|
|
importsAndAliases := map[string]string{
|
|
"github.com/blang/semver": "",
|
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi": "",
|
|
}
|
|
pkg.genHeader(buffer, []string{"fmt", "os", "reflect", "regexp", "strconv", "strings"}, importsAndAliases)
|
|
|
|
packageRegex := fmt.Sprintf("^.*/pulumi-%s/sdk(/v\\d+)?", pkg.pkg.Name)
|
|
if pkg.rootPackageName != "" {
|
|
packageRegex = fmt.Sprintf("^%s(/v\\d+)?", pkg.importBasePath)
|
|
}
|
|
|
|
_, err := fmt.Fprintf(buffer, utilitiesFile, packageRegex)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
setFile(path.Join(mod, "pulumiUtilities.go"), buffer.String())
|
|
}
|
|
|
|
// If there are resources in this module, register the module with the runtime.
|
|
if len(pkg.resources) != 0 {
|
|
buffer := &bytes.Buffer{}
|
|
pkg.genResourceModule(buffer)
|
|
|
|
setFile(path.Join(mod, "init.go"), buffer.String())
|
|
}
|
|
}
|
|
|
|
return files, nil
|
|
}
|
|
|
|
// goPackage returns the suggested package name for the given string.
|
|
func goPackage(name string) string {
|
|
return strings.Split(name, "-")[0]
|
|
}
|
|
|
|
const utilitiesFile = `
|
|
type envParser func(v string) interface{}
|
|
|
|
func parseEnvBool(v string) interface{} {
|
|
b, err := strconv.ParseBool(v)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return b
|
|
}
|
|
|
|
func parseEnvInt(v string) interface{} {
|
|
i, err := strconv.ParseInt(v, 0, 0)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return int(i)
|
|
}
|
|
|
|
func parseEnvFloat(v string) interface{} {
|
|
f, err := strconv.ParseFloat(v, 64)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return f
|
|
}
|
|
|
|
func parseEnvStringArray(v string) interface{} {
|
|
var result pulumi.StringArray
|
|
for _, item := range strings.Split(v, ";") {
|
|
result = append(result, pulumi.String(item))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func getEnvOrDefault(def interface{}, parser envParser, vars ...string) interface{} {
|
|
for _, v := range vars {
|
|
if value := os.Getenv(v); value != "" {
|
|
if parser != nil {
|
|
return parser(value)
|
|
}
|
|
return value
|
|
}
|
|
}
|
|
return def
|
|
}
|
|
|
|
// PkgVersion uses reflection to determine the version of the current package.
|
|
func PkgVersion() (semver.Version, error) {
|
|
type sentinal struct{}
|
|
pkgPath := reflect.TypeOf(sentinal{}).PkgPath()
|
|
re := regexp.MustCompile(%q)
|
|
if match := re.FindStringSubmatch(pkgPath); match != nil {
|
|
vStr := match[1]
|
|
if len(vStr) == 0 { // If the version capture group was empty, default to v1.
|
|
return semver.Version{Major: 1}, nil
|
|
}
|
|
return semver.MustParse(fmt.Sprintf("%%s.0.0", vStr[2:])), nil
|
|
}
|
|
return semver.Version{}, fmt.Errorf("failed to determine the package version from %%s", pkgPath)
|
|
}
|
|
`
|