pulumi/pkg/codegen/go/gen.go

2389 lines
74 KiB
Go
Raw Normal View History

// 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
2021-03-31 07:23:04 +02:00
// 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
}
2021-03-11 06:04:30 +01:00
func (pkg *pkgContext) detailsForType(t schema.Type) *typeDetails {
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]
}
2020-11-03 08:02:56 +01:00
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 (pkg *pkgContext) plainType(t schema.Type, optional bool) string {
var typ string
switch t := t.(type) {
2020-11-03 08:02:56 +01:00
case *schema.EnumType:
2020-11-14 08:05:39 +01:00
return pkg.plainType(t.ElementType, optional)
case *schema.ArrayType:
typ = "[]"
typ += pkg.plainType(t.ElementType, false)
return typ
case *schema.MapType:
typ = "map[string]"
typ += pkg.plainType(t.ElementType, false)
return typ
case *schema.ObjectType:
typ = pkg.resolveObjectType(t)
case *schema.ResourceType:
typ = pkg.resolveResourceType(t)
// Set optional to true because resources are pointers.
optional = true
case *schema.TokenType:
// Use the underlying type for now.
if t.UnderlyingType != nil {
return pkg.plainType(t.UnderlyingType, optional)
}
typ = pkg.tokenToType(t.Token)
case *schema.UnionType:
2020-11-03 08:02:56 +01:00
// 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.plainType(typ.ElementType, optional)
}
}
// TODO(pdg): union types
return "interface{}"
default:
switch t {
case schema.BoolType:
typ = "bool"
case schema.IntType:
typ = "int"
case schema.NumberType:
typ = "float64"
case schema.StringType:
typ = "string"
case schema.ArchiveType:
2020-01-23 19:42:38 +01:00
return "pulumi.Archive"
case schema.AssetType:
2020-01-23 19:42:38 +01:00
return "pulumi.AssetOrArchive"
case schema.JSONType:
fallthrough
case schema.AnyType:
return "interface{}"
}
}
if optional {
return "*" + typ
}
return typ
}
func (pkg *pkgContext) inputType(t schema.Type, optional bool) string {
var typ string
switch t := t.(type) {
2020-11-03 08:02:56 +01:00
case *schema.EnumType:
2021-01-11 19:00:13 +01:00
var prefix string
if optional {
2021-01-11 19:00:13 +01:00
prefix = "*"
}
2020-11-03 08:02:56 +01:00
// Since enum type is itself an input
2021-01-11 19:00:13 +01:00
return prefix + pkg.tokenToEnum(t.Token)
case *schema.ArrayType:
en := pkg.inputType(t.ElementType, false)
return strings.TrimSuffix(en, "Input") + "ArrayInput"
case *schema.MapType:
en := pkg.inputType(t.ElementType, false)
return strings.TrimSuffix(en, "Input") + "MapInput"
case *schema.ObjectType:
typ = pkg.resolveObjectType(t)
case *schema.ResourceType:
typ = pkg.resolveResourceType(t)
return typ + "Input"
case *schema.TokenType:
// Use the underlying type for now.
if t.UnderlyingType != nil {
return pkg.inputType(t.UnderlyingType, optional)
}
typ = pkg.tokenToType(t.Token)
case *schema.UnionType:
2020-11-03 08:02:56 +01:00
// 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, optional)
}
}
// TODO(pdg): union types
return "pulumi.Input"
default:
switch t {
case schema.BoolType:
typ = "pulumi.Bool"
case schema.IntType:
typ = "pulumi.Int"
case schema.NumberType:
typ = "pulumi.Float64"
case schema.StringType:
typ = "pulumi.String"
case schema.ArchiveType:
return "pulumi.ArchiveInput"
case schema.AssetType:
return "pulumi.AssetOrArchiveInput"
case schema.JSONType:
fallthrough
case schema.AnyType:
return "pulumi.Input"
}
}
if optional {
return typ + "PtrInput"
}
return typ + "Input"
}
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
}
// 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) {
2021-01-19 18:55:40 +01:00
return pkg.tokenToResource(t.Token)
}
extPkg := t.Resource.Package
var goInfo GoPackageInfo
2021-01-19 18:55:40 +01:00
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,
}
2021-01-19 18:55:40 +01:00
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) {
2021-01-19 18:55:40 +01:00
return pkg.tokenToType(t.Token)
}
extPkg := t.Package
var goInfo GoPackageInfo
2021-01-19 18:55:40 +01:00
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,
}
2021-01-19 18:55:40 +01:00
return extPkgCtx.plainType(t, false)
}
func (pkg *pkgContext) outputType(t schema.Type, optional bool) string {
var typ string
switch t := t.(type) {
2020-11-03 08:02:56 +01:00
case *schema.EnumType:
return pkg.outputType(t.ElementType, optional)
case *schema.ArrayType:
en := strings.TrimSuffix(pkg.outputType(t.ElementType, false), "Output")
if en == "pulumi.Any" {
return "pulumi.ArrayOutput"
}
return en + "ArrayOutput"
case *schema.MapType:
en := strings.TrimSuffix(pkg.outputType(t.ElementType, false), "Output")
if en == "pulumi.Any" {
return "pulumi.MapOutput"
}
return en + "MapOutput"
case *schema.ObjectType:
typ = pkg.resolveObjectType(t)
case *schema.ResourceType:
typ = pkg.resolveResourceType(t)
return typ + "Output"
case *schema.TokenType:
// Use the underlying type for now.
if t.UnderlyingType != nil {
return pkg.outputType(t.UnderlyingType, optional)
}
typ = pkg.tokenToType(t.Token)
case *schema.UnionType:
2020-11-03 08:02:56 +01:00
// 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, optional)
}
}
// TODO(pdg): union types
return "pulumi.AnyOutput"
default:
switch t {
case schema.BoolType:
typ = "pulumi.Bool"
case schema.IntType:
typ = "pulumi.Int"
case schema.NumberType:
typ = "pulumi.Float64"
case schema.StringType:
typ = "pulumi.String"
case schema.ArchiveType:
return "pulumi.ArchiveOutput"
case schema.AssetType:
2020-01-23 19:00:18 +01:00
return "pulumi.AssetOrArchiveOutput"
case schema.JSONType:
fallthrough
case schema.AnyType:
return "pulumi.AnyOutput"
}
}
if optional {
return typ + "PtrOutput"
}
return typ + "Output"
}
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)
}
}
2021-03-31 07:23:04 +02:00
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")
}
2021-03-31 07:23:04 +02:00
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
}
2021-03-31 07:23:04 +02:00
// If not a nested collection type, use the default example format
if trimmer(name) == name {
return example
}
if strings.HasSuffix(name, "Map") {
2021-03-31 07:23:04 +02:00
if pkg.schemaNames.Has(baseTypeName) {
return fmt.Sprintf("%s{ \"key\": %s }", name, example)
}
2021-03-31 07:23:04 +02:00
return fmt.Sprintf("%s{ \"key\": %s }", name, pkg.getUsageForNestedType(baseTypeName, trimmer(baseTypeName)))
}
if strings.HasSuffix(name, "Array") {
2021-03-31 07:23:04 +02:00
if pkg.schemaNames.Has(baseTypeName) {
return fmt.Sprintf("%s{ %s }", name, example)
}
2021-03-31 07:23:04 +02:00
return fmt.Sprintf("%s{ %s }", name, pkg.getUsageForNestedType(baseTypeName, trimmer(baseTypeName)))
}
return example
}
2021-03-31 07:23:04 +02:00
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),
"",
2021-03-31 07:23:04 +02:00
"\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),
"",
2021-03-31 07:23:04 +02:00
"\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)
2020-11-03 08:02:56 +01:00
}
func (pkg *pkgContext) genEnumType(w io.Writer, name string, enumType *schema.EnumType) error {
2020-11-09 22:05:59 +01:00
mod := pkg.tokenToPackage(enumType.Token)
modPkg, ok := pkg.packages[mod]
contract.Assert(ok)
2020-11-03 08:02:56 +01:00
printCommentWithDeprecationMessage(w, enumType.Comment, "", false)
elementType := pkg.enumElementType(enumType.ElementType, false)
fmt.Fprintf(w, "type %s %s\n\n", name, elementType)
2021-01-19 18:55:40 +01:00
fmt.Fprintln(w, "const (")
2020-11-03 08:02:56 +01:00
for _, e := range enumType.Elements {
printCommentWithDeprecationMessage(w, e.Comment, e.DeprecationMessage, true)
2020-11-03 08:02:56 +01:00
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",
2020-11-09 22:05:59 +01:00
e.Name, enumType.Token)
2020-11-03 08:02:56 +01:00
switch reflect.TypeOf(e.Value).Kind() {
case reflect.String:
fmt.Fprintf(w, "%s = %s(%q)\n", e.Name, name, e.Value)
2020-11-03 08:02:56 +01:00
default:
fmt.Fprintf(w, "%s = %s(%v)\n", e.Name, name, e.Value)
}
}
fmt.Fprintln(w, ")")
2020-11-09 22:05:59 +01:00
inputType := pkg.inputType(enumType, false)
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
2020-11-03 08:02:56 +01:00
}
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))
2020-11-03 08:02:56 +01:00
}
}
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)
2020-11-03 08:02:56 +01:00
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)
2021-03-11 06:04:30 +01:00
details := pkg.detailsForType(enum)
// Generate the array input.
if details.arrayElement {
2021-03-31 07:23:04 +02:00
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 {
2021-03-31 07:23:04 +02:00
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")
}
2020-11-03 08:02:56 +01:00
}
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.plainType(p.Type, !p.IsRequired), p.Name)
}
fmt.Fprintf(w, "}\n\n")
}
func (pkg *pkgContext) genInputTypes(w io.Writer, t *schema.ObjectType, details *typeDetails) {
name := pkg.tokenToType(t.Token)
// Generate the plain inputs.
2021-03-31 07:23:04 +02:00
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)
typ := pkg.inputType(p.Type, !p.IsRequired)
if p.IsPlain {
typ = pkg.plainType(p.Type, !p.IsRequired)
}
fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", Title(p.Name), typ, p.Name)
}
fmt.Fprintf(w, "}\n\n")
genInputMethods(w, name, name+"Args", name, details.ptrElement, false)
// Generate the pointer input.
if details.ptrElement {
2021-03-31 07:23:04 +02:00
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 {
2021-03-31 07:23:04 +02:00
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 {
2021-03-31 07:23:04 +02:00
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) {
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, !p.IsRequired), pkg.plainType(p.Type, !p.IsRequired)
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)
[codegen/go] Fix accessors on struct ptr outputs (#4456) * [codegen/go] Fix accessors on struct ptr outputs The accesor methods on nestred struct Ptr outputs were previously not accepting pointer typed inputs as they should, and would thus always panic if used. The "simple" fix would be to just accept the pointer type and blindly dereference it. But this doesn't seem like the right experience - it would make these accessors very unsafe to use in practice. Instead, this PR implements the accessors on pointer-typed outputs as nil-coaslescing, always lifting the output type into a pointer type and flowing a nil value into the result type. This ensures the accessor will not nil-deref, and that user code can handle the `nil` value itself (or use `.Apply` directly to implement more specialized behaviour). Before: ```go // Name of your S3 bucket. func (o BuildStorageLocationPtrOutput) Bucket() pulumi.StringOutput { return o.ApplyT(func(v BuildStorageLocation) string { return v.Bucket }).(pulumi.StringOutput) } ``` After: ```go // Name of your S3 bucket. func (o BuildStorageLocationPtrOutput) Bucket() pulumi.StringPtrOutput { return o.ApplyT(func(v *BuildStorageLocation) *string { if v == nil { return nil } return &v.Bucket }).(pulumi.StringPtrOutput) } ``` However, due to the decision to have this more usable behaviour, this is a breaking change, as some/many accessors now return a pointer type when they previously did not. Fixes pulumi/pulumi-azure#530. * Mark nested property types as requiring ptr types * Add CHANGELOG * More fixes
2020-04-21 22:33:38 +02:00
outputType, applyType := pkg.outputType(p.Type, true), pkg.plainType(p.Type, true)
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 {
[codegen/go] Fix accessors on struct ptr outputs (#4456) * [codegen/go] Fix accessors on struct ptr outputs The accesor methods on nestred struct Ptr outputs were previously not accepting pointer typed inputs as they should, and would thus always panic if used. The "simple" fix would be to just accept the pointer type and blindly dereference it. But this doesn't seem like the right experience - it would make these accessors very unsafe to use in practice. Instead, this PR implements the accessors on pointer-typed outputs as nil-coaslescing, always lifting the output type into a pointer type and flowing a nil value into the result type. This ensures the accessor will not nil-deref, and that user code can handle the `nil` value itself (or use `.Apply` directly to implement more specialized behaviour). Before: ```go // Name of your S3 bucket. func (o BuildStorageLocationPtrOutput) Bucket() pulumi.StringOutput { return o.ApplyT(func(v BuildStorageLocation) string { return v.Bucket }).(pulumi.StringOutput) } ``` After: ```go // Name of your S3 bucket. func (o BuildStorageLocationPtrOutput) Bucket() pulumi.StringPtrOutput { return o.ApplyT(func(v *BuildStorageLocation) *string { if v == nil { return nil } return &v.Bucket }).(pulumi.StringPtrOutput) } ``` However, due to the decision to have this more usable behaviour, this is a breaking change, as some/many accessors now return a pointer type when they previously did not. Fixes pulumi/pulumi-azure#530. * Mark nested property types as requiring ptr types * Add CHANGELOG * More fixes
2020-04-21 22:33:38 +02:00
deref = "&"
}
2021-03-11 06:04:30 +01:00
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)
[codegen/go] Fix accessors on struct ptr outputs (#4456) * [codegen/go] Fix accessors on struct ptr outputs The accesor methods on nestred struct Ptr outputs were previously not accepting pointer typed inputs as they should, and would thus always panic if used. The "simple" fix would be to just accept the pointer type and blindly dereference it. But this doesn't seem like the right experience - it would make these accessors very unsafe to use in practice. Instead, this PR implements the accessors on pointer-typed outputs as nil-coaslescing, always lifting the output type into a pointer type and flowing a nil value into the result type. This ensures the accessor will not nil-deref, and that user code can handle the `nil` value itself (or use `.Apply` directly to implement more specialized behaviour). Before: ```go // Name of your S3 bucket. func (o BuildStorageLocationPtrOutput) Bucket() pulumi.StringOutput { return o.ApplyT(func(v BuildStorageLocation) string { return v.Bucket }).(pulumi.StringOutput) } ``` After: ```go // Name of your S3 bucket. func (o BuildStorageLocationPtrOutput) Bucket() pulumi.StringPtrOutput { return o.ApplyT(func(v *BuildStorageLocation) *string { if v == nil { return nil } return &v.Bucket }).(pulumi.StringPtrOutput) } ``` However, due to the decision to have this more usable behaviour, this is a breaking change, as some/many accessors now return a pointer type when they previously did not. Fixes pulumi/pulumi-azure#530. * Mark nested property types as requiring ptr types * Add CHANGELOG * More fixes
2020-04-21 22:33:38 +02:00
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 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.IsRequired), 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.
2020-11-03 08:02:56 +01:00
hasRequired := false
for _, p := range r.InputProperties {
if p.IsRequired {
2020-11-03 08:02:56 +01:00
hasRequired = true
}
}
// Various validation checks
fmt.Fprintf(w, "\tif args == nil {\n")
2020-11-03 08:02:56 +01:00
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")
2020-11-03 08:02:56 +01:00
// Produce the inputs.
2020-11-03 08:02:56 +01:00
for _, p := range r.InputProperties {
switch p.Type.(type) {
case *schema.EnumType:
// not a pointer type and already handled above
2020-11-03 08:02:56 +01:00
default:
if p.IsRequired && !p.IsPlain {
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")
}
2020-11-03 08:02:56 +01:00
}
}
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, !p.IsRequired), "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, p.Type)
if err != nil {
return err
}
t := strings.TrimSuffix(pkg.inputType(p.Type, !p.IsRequired), "Input")
if t == "pulumi." {
t = "pulumi.Any"
}
2021-01-22 18:53:21 +01:00
switch typ := 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(p.Type, false), 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, false))
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))
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.plainType(p.Type, true), p.Name)
}
fmt.Fprintf(w, "}\n\n")
fmt.Fprintf(w, "type %sState struct {\n", name)
for _, p := range r.Properties {
printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true)
fmt.Fprintf(w, "\t%s %s\n", Title(p.Name), pkg.inputType(p.Type, true))
}
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.plainType(p.Type, !p.IsRequired), 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)
typ := pkg.inputType(p.Type, !p.IsRequired)
if p.IsPlain {
typ = pkg.plainType(p.Type, !p.IsRequired)
}
fmt.Fprintf(w, "\t%s %s\n", Title(p.Name), typ)
}
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.
2021-03-31 07:23:04 +02:00
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.
2021-03-31 07:23:04 +02:00
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) {
pkg.genPlainType(w, pkg.tokenToType(obj.Token), obj.Comment, "", obj.Properties)
2020-11-03 08:02:56 +01:00
pkg.genInputTypes(w, obj, pkg.detailsForType(obj))
pkg.genOutputTypes(w, obj, pkg.detailsForType(obj))
}
2021-03-11 06:04:30 +01:00
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
}
2021-03-15 07:44:21 +01:00
elementTypeName = pkg.nestedTypeToType(t.ElementType)
2021-03-11 06:04:30 +01:00
elementTypeName += "Array"
names = pkg.addSuffixesToName(t, elementTypeName)
case *schema.MapType:
// Builtins already cater to primitive maps
if schema.IsPrimitiveType(t.ElementType) {
return nil
}
2021-03-15 07:44:21 +01:00
elementTypeName = pkg.nestedTypeToType(t.ElementType)
2021-03-11 06:04:30 +01:00
elementTypeName += "Map"
names = pkg.addSuffixesToName(t, elementTypeName)
}
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")
}
2021-03-31 07:23:04 +02:00
pkg.genInputInterface(w, name)
2021-03-11 06:04:30 +01:00
}
return names
}
2021-03-15 07:44:21 +01:00
func (pkg *pkgContext) nestedTypeToType(typ schema.Type) string {
2021-03-11 06:04:30 +01:00
switch t := typ.(type) {
case *schema.ArrayType:
return pkg.nestedTypeToType(t.ElementType)
2021-03-11 06:04:30 +01:00
case *schema.MapType:
return pkg.nestedTypeToType(t.ElementType)
2021-03-11 06:04:30 +01:00
}
return pkg.tokenToType(typ.String())
2021-03-11 06:04:30 +01:00
}
2020-11-03 08:02:56 +01:00
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]
2020-11-03 08:02:56 +01:00
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
}
}
2020-11-03 08:02:56 +01:00
if mod == pkg.mod {
return name
}
if mod == "" {
mod = components[0]
}
return strings.Replace(mod, "/", "", -1) + "." + name
}
2021-03-11 06:04:30 +01:00
func (pkg *pkgContext) genTypeRegistrations(w io.Writer, objTypes []*schema.ObjectType, types ...string) {
fmt.Fprintf(w, "func init() {\n")
2021-03-11 06:04:30 +01:00
for _, obj := range objTypes {
2020-11-03 08:02:56 +01:00
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)
}
}
2021-03-11 06:04:30 +01:00
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) {
2021-02-26 07:05:02 +01:00
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)
}
2020-11-03 08:02:56 +01:00
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 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, 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,
2021-03-11 06:04:30 +01:00
typeDetails: map[schema.Type]*typeDetails{},
names: codegen.NewStringSet(),
2021-03-31 07:23:04 +02:00
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))
}
2021-03-11 06:04:30 +01:00
var getPkgFromType func(schema.Type) *pkgContext
getPkgFromType = func(typ schema.Type) *pkgContext {
switch t := 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")
}
[codegen/go] Fix accessors on struct ptr outputs (#4456) * [codegen/go] Fix accessors on struct ptr outputs The accesor methods on nestred struct Ptr outputs were previously not accepting pointer typed inputs as they should, and would thus always panic if used. The "simple" fix would be to just accept the pointer type and blindly dereference it. But this doesn't seem like the right experience - it would make these accessors very unsafe to use in practice. Instead, this PR implements the accessors on pointer-typed outputs as nil-coaslescing, always lifting the output type into a pointer type and flowing a nil value into the result type. This ensures the accessor will not nil-deref, and that user code can handle the `nil` value itself (or use `.Apply` directly to implement more specialized behaviour). Before: ```go // Name of your S3 bucket. func (o BuildStorageLocationPtrOutput) Bucket() pulumi.StringOutput { return o.ApplyT(func(v BuildStorageLocation) string { return v.Bucket }).(pulumi.StringOutput) } ``` After: ```go // Name of your S3 bucket. func (o BuildStorageLocationPtrOutput) Bucket() pulumi.StringPtrOutput { return o.ApplyT(func(v *BuildStorageLocation) *string { if v == nil { return nil } return &v.Bucket }).(pulumi.StringPtrOutput) } ``` However, due to the decision to have this more usable behaviour, this is a breaking change, as some/many accessors now return a pointer type when they previously did not. Fixes pulumi/pulumi-azure#530. * Mark nested property types as requiring ptr types * Add CHANGELOG * More fixes
2020-04-21 22:33:38 +02:00
// 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)
2021-03-11 06:04:30 +01:00
var populateDetailsForTypes func(seen codegen.StringSet, schemaType schema.Type, isRequired bool, parentOptional bool)
populateDetailsForPropertyTypes = func(seen codegen.StringSet, props []*schema.Property, parentOptional bool) {
[codegen/go] Fix accessors on struct ptr outputs (#4456) * [codegen/go] Fix accessors on struct ptr outputs The accesor methods on nestred struct Ptr outputs were previously not accepting pointer typed inputs as they should, and would thus always panic if used. The "simple" fix would be to just accept the pointer type and blindly dereference it. But this doesn't seem like the right experience - it would make these accessors very unsafe to use in practice. Instead, this PR implements the accessors on pointer-typed outputs as nil-coaslescing, always lifting the output type into a pointer type and flowing a nil value into the result type. This ensures the accessor will not nil-deref, and that user code can handle the `nil` value itself (or use `.Apply` directly to implement more specialized behaviour). Before: ```go // Name of your S3 bucket. func (o BuildStorageLocationPtrOutput) Bucket() pulumi.StringOutput { return o.ApplyT(func(v BuildStorageLocation) string { return v.Bucket }).(pulumi.StringOutput) } ``` After: ```go // Name of your S3 bucket. func (o BuildStorageLocationPtrOutput) Bucket() pulumi.StringPtrOutput { return o.ApplyT(func(v *BuildStorageLocation) *string { if v == nil { return nil } return &v.Bucket }).(pulumi.StringPtrOutput) } ``` However, due to the decision to have this more usable behaviour, this is a breaking change, as some/many accessors now return a pointer type when they previously did not. Fixes pulumi/pulumi-azure#530. * Mark nested property types as requiring ptr types * Add CHANGELOG * More fixes
2020-04-21 22:33:38 +02:00
for _, p := range props {
2021-03-11 06:04:30 +01:00
populateDetailsForTypes(seen, p.Type, p.IsRequired, parentOptional)
}
}
2021-03-11 06:04:30 +01:00
populateDetailsForTypes = func(seen codegen.StringSet, schemaType schema.Type, isRequired bool, parentOptional bool) {
switch typ := schemaType.(type) {
case *schema.ObjectType:
2021-03-31 07:23:04 +02:00
pkg := getPkgFromToken(typ.Token)
2021-03-11 06:04:30 +01:00
if !isRequired || parentOptional {
if seen.Has(typ.Token) {
2021-03-11 06:04:30 +01:00
return
}
2021-03-11 06:04:30 +01:00
seen.Add(typ.Token)
2021-03-31 07:23:04 +02:00
pkg.detailsForType(typ).ptrElement = true
2021-03-11 06:04:30 +01:00
populateDetailsForPropertyTypes(seen, typ.Properties, true)
2020-11-03 08:02:56 +01:00
}
2021-03-31 07:23:04 +02:00
pkg.schemaNames.Add(tokenToName(typ.Token))
2021-03-11 06:04:30 +01:00
case *schema.EnumType:
if seen.Has(typ.Token) {
return
}
2021-03-31 07:23:04 +02:00
seen.Add(typ.Token)
pkg := getPkgFromToken(typ.Token)
2021-03-11 06:04:30 +01:00
if !isRequired || parentOptional {
2021-03-31 07:23:04 +02:00
pkg.detailsForType(typ).ptrElement = true
2021-03-11 06:04:30 +01:00
}
2021-03-31 07:23:04 +02:00
pkg.schemaNames.Add(tokenToName(typ.Token))
2021-03-11 06:04:30 +01:00
case *schema.ArrayType:
if seen.Has(typ.String()) {
return
}
seen.Add(typ.String())
getPkgFromType(typ.ElementType).detailsForType(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(typ.ElementType).mapElement = true
populateDetailsForTypes(seen, typ.ElementType, true, false)
[codegen/go] Fix accessors on struct ptr outputs (#4456) * [codegen/go] Fix accessors on struct ptr outputs The accesor methods on nestred struct Ptr outputs were previously not accepting pointer typed inputs as they should, and would thus always panic if used. The "simple" fix would be to just accept the pointer type and blindly dereference it. But this doesn't seem like the right experience - it would make these accessors very unsafe to use in practice. Instead, this PR implements the accessors on pointer-typed outputs as nil-coaslescing, always lifting the output type into a pointer type and flowing a nil value into the result type. This ensures the accessor will not nil-deref, and that user code can handle the `nil` value itself (or use `.Apply` directly to implement more specialized behaviour). Before: ```go // Name of your S3 bucket. func (o BuildStorageLocationPtrOutput) Bucket() pulumi.StringOutput { return o.ApplyT(func(v BuildStorageLocation) string { return v.Bucket }).(pulumi.StringOutput) } ``` After: ```go // Name of your S3 bucket. func (o BuildStorageLocationPtrOutput) Bucket() pulumi.StringPtrOutput { return o.ApplyT(func(v *BuildStorageLocation) *string { if v == nil { return nil } return &v.Bucket }).(pulumi.StringPtrOutput) } ``` However, due to the decision to have this more usable behaviour, this is a breaking change, as some/many accessors now return a pointer type when they previously did not. Fixes pulumi/pulumi-azure#530. * Mark nested property types as requiring ptr types * Add CHANGELOG * More fixes
2020-04-21 22:33:38 +02:00
}
}
// 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 {
2020-11-03 08:02:56 +01:00
switch typ := t.(type) {
case *schema.ArrayType:
2021-03-11 06:04:30 +01:00
getPkgFromType(typ.ElementType).detailsForType(typ.ElementType).arrayElement = true
case *schema.MapType:
2021-03-11 06:04:30 +01:00
getPkgFromType(typ.ElementType).detailsForType(typ.ElementType).mapElement = true
case *schema.ObjectType:
2020-11-03 08:02:56 +01:00
pkg := getPkgFromToken(typ.Token)
pkg.types = append(pkg.types, typ)
populateDetailsForPropertyTypes(seenMap, typ.Properties, false)
2020-11-03 08:02:56 +01:00
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)
2021-03-31 07:23:04 +02:00
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)
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 f.Outputs != nil {
pkg.names.Add(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
}
2020-08-19 22:58:13 +02:00
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 {
2020-08-19 22:58:13 +02:00
packagePath := path.Join(goPkgInfo.ImportBasePath, pkg.mod)
resources[r.Token] = LanguageResource{
Resource: r,
2020-08-19 22:58:13 +02:00
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
}
2020-08-19 22:58:13 +02:00
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())
}
2021-03-11 06:04:30 +01:00
knownTypes := make(map[schema.Type]struct{}, len(pkg.typeDetails))
for t := range pkg.typeDetails {
knownTypes[t] = struct{}{}
}
2020-11-03 08:02:56 +01:00
// Enums
if len(pkg.enums) > 0 {
imports := map[string]string{}
2020-11-03 08:02:56 +01:00
for _, e := range pkg.enums {
pkg.getImports(e, imports)
}
buffer := &bytes.Buffer{}
pkg.genHeader(buffer, []string{"context", "reflect"}, imports)
2020-11-03 08:02:56 +01:00
for _, e := range pkg.enums {
2020-11-18 19:57:01 +01:00
if err := pkg.genEnum(buffer, e); err != nil {
return nil, err
}
2021-03-11 06:04:30 +01:00
delete(knownTypes, e)
2020-11-03 08:02:56 +01:00
}
setFile(path.Join(mod, "pulumiEnums.go"), buffer.String())
}
2021-03-11 06:04:30 +01:00
// 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)
}
`