2020-04-21 01:36:05 +02:00
|
|
|
// Copyright 2016-2020, Pulumi Corporation.
|
2020-01-21 23:45:48 +01:00
|
|
|
//
|
|
|
|
// 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 dotnet
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"reflect"
|
2021-01-15 19:53:32 +01:00
|
|
|
"sort"
|
2020-01-21 23:45:48 +01:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"unicode"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2021-03-17 14:20:05 +01:00
|
|
|
"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"
|
2020-01-21 23:45:48 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type stringSet map[string]struct{}
|
|
|
|
|
|
|
|
func (ss stringSet) add(s string) {
|
|
|
|
ss[s] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ss stringSet) has(s string) bool {
|
|
|
|
_, ok := ss[s]
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
|
|
|
type typeDetails struct {
|
|
|
|
outputType bool
|
|
|
|
inputType bool
|
|
|
|
stateType bool
|
|
|
|
functionType bool
|
|
|
|
}
|
|
|
|
|
2020-04-07 02:01:33 +02:00
|
|
|
// Title converts the input string to a title case
|
|
|
|
// where only the initial letter is upper-cased.
|
|
|
|
func Title(s string) string {
|
2020-01-21 23:45:48 +01:00
|
|
|
if s == "" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
runes := []rune(s)
|
|
|
|
return string(append([]rune{unicode.ToUpper(runes[0])}, runes[1:]...))
|
|
|
|
}
|
|
|
|
|
|
|
|
func csharpIdentifier(s string) string {
|
2020-05-01 12:45:47 +02:00
|
|
|
// Some schema field names may look like $ref or $schema. Remove the leading $ to make a valid identifier.
|
|
|
|
// This could lead to a clash if both `$foo` and `foo` are defined, but we don't try to de-duplicate now.
|
|
|
|
if strings.HasPrefix(s, "$") {
|
|
|
|
s = s[1:]
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
switch s {
|
|
|
|
case "abstract", "as", "base", "bool",
|
|
|
|
"break", "byte", "case", "catch",
|
|
|
|
"char", "checked", "class", "const",
|
|
|
|
"continue", "decimal", "default", "delegate",
|
|
|
|
"do", "double", "else", "enum",
|
|
|
|
"event", "explicit", "extern", "false",
|
|
|
|
"finally", "fixed", "float", "for",
|
|
|
|
"foreach", "goto", "if", "implicit",
|
|
|
|
"in", "int", "interface", "internal",
|
|
|
|
"is", "lock", "long", "namespace",
|
|
|
|
"new", "null", "object", "operator",
|
|
|
|
"out", "override", "params", "private",
|
|
|
|
"protected", "public", "readonly", "ref",
|
|
|
|
"return", "sbyte", "sealed", "short",
|
|
|
|
"sizeof", "stackalloc", "static", "string",
|
|
|
|
"struct", "switch", "this", "throw",
|
|
|
|
"true", "try", "typeof", "uint",
|
|
|
|
"ulong", "unchecked", "unsafe", "ushort",
|
|
|
|
"using", "virtual", "void", "volatile", "while":
|
|
|
|
return "@" + s
|
|
|
|
|
|
|
|
default:
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-14 10:30:25 +02:00
|
|
|
func isImmutableArrayType(t schema.Type, wrapInput bool) bool {
|
|
|
|
_, isArray := t.(*schema.ArrayType)
|
|
|
|
return isArray && !wrapInput
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
func isValueType(t schema.Type) bool {
|
2020-11-09 19:45:19 +01:00
|
|
|
if _, ok := t.(*schema.EnumType); ok {
|
|
|
|
return true
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
switch t {
|
|
|
|
case schema.BoolType, schema.IntType, schema.NumberType:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func namespaceName(namespaces map[string]string, name string) string {
|
|
|
|
if ns, ok := namespaces[name]; ok {
|
|
|
|
return ns
|
|
|
|
}
|
2020-04-07 02:01:33 +02:00
|
|
|
return Title(name)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type modContext struct {
|
2020-05-19 11:41:06 +02:00
|
|
|
pkg *schema.Package
|
|
|
|
mod string
|
|
|
|
propertyNames map[*schema.Property]string
|
|
|
|
types []*schema.ObjectType
|
2020-11-06 18:01:03 +01:00
|
|
|
enums []*schema.EnumType
|
2020-05-19 11:41:06 +02:00
|
|
|
resources []*schema.Resource
|
|
|
|
functions []*schema.Function
|
|
|
|
typeDetails map[*schema.ObjectType]*typeDetails
|
|
|
|
children []*modContext
|
|
|
|
tool string
|
|
|
|
namespaceName string
|
|
|
|
namespaces map[string]string
|
|
|
|
compatibility string
|
|
|
|
dictionaryConstructors bool
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) propertyName(p *schema.Property) string {
|
|
|
|
if n, ok := mod.propertyNames[p]; ok {
|
|
|
|
return n
|
|
|
|
}
|
2020-04-07 02:01:33 +02:00
|
|
|
return Title(p.Name)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) details(t *schema.ObjectType) *typeDetails {
|
|
|
|
details, ok := mod.typeDetails[t]
|
|
|
|
if !ok {
|
|
|
|
details = &typeDetails{}
|
|
|
|
mod.typeDetails[t] = details
|
|
|
|
}
|
|
|
|
return details
|
|
|
|
}
|
|
|
|
|
|
|
|
func tokenToName(tok string) string {
|
|
|
|
// token := pkg : module : member
|
|
|
|
// module := path/to/module
|
|
|
|
|
|
|
|
components := strings.Split(tok, ":")
|
|
|
|
contract.Assertf(len(components) == 3, "malformed token %v", tok)
|
2020-04-07 02:01:33 +02:00
|
|
|
return Title(components[2])
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func resourceName(r *schema.Resource) string {
|
|
|
|
if r.IsProvider {
|
|
|
|
return "Provider"
|
|
|
|
}
|
|
|
|
return tokenToName(r.Token)
|
|
|
|
}
|
|
|
|
|
2020-04-28 02:47:01 +02:00
|
|
|
func tokenToFunctionName(tok string) string {
|
|
|
|
return tokenToName(tok)
|
|
|
|
}
|
|
|
|
|
2020-05-19 11:41:06 +02:00
|
|
|
func (mod *modContext) isK8sCompatMode() bool {
|
|
|
|
return mod.compatibility == "kubernetes20"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) tokenToNamespace(tok string, qualifier string) string {
|
2020-01-21 23:45:48 +01:00
|
|
|
components := strings.Split(tok, ":")
|
|
|
|
contract.Assertf(len(components) == 3, "malformed token %v", tok)
|
|
|
|
|
2020-04-14 20:53:43 +02:00
|
|
|
pkg, nsName := "Pulumi."+namespaceName(mod.namespaces, components[0]), mod.pkg.TokenToModule(tok)
|
2020-05-19 11:41:06 +02:00
|
|
|
|
|
|
|
if mod.isK8sCompatMode() {
|
2020-12-16 17:58:42 +01:00
|
|
|
if qualifier != "" {
|
|
|
|
return pkg + ".Types." + qualifier + "." + namespaceName(mod.namespaces, nsName)
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2020-05-19 11:41:06 +02:00
|
|
|
typ := pkg
|
|
|
|
if nsName != "" {
|
|
|
|
typ += "." + namespaceName(mod.namespaces, nsName)
|
|
|
|
}
|
|
|
|
if qualifier != "" {
|
|
|
|
typ += "." + qualifier
|
|
|
|
}
|
|
|
|
return typ
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) typeString(t schema.Type, qualifier string, input, state, wrapInput, requireInitializers, optional bool) string {
|
|
|
|
var typ string
|
|
|
|
switch t := t.(type) {
|
2020-11-06 18:01:03 +01:00
|
|
|
case *schema.EnumType:
|
|
|
|
typ = mod.tokenToNamespace(t.Token, "")
|
|
|
|
typ += "."
|
|
|
|
typ += tokenToName(t.Token)
|
2020-01-21 23:45:48 +01:00
|
|
|
case *schema.ArrayType:
|
|
|
|
var listFmt string
|
|
|
|
switch {
|
|
|
|
case wrapInput:
|
|
|
|
listFmt, optional = "InputList<%v>", false
|
|
|
|
case requireInitializers:
|
|
|
|
listFmt = "List<%v>"
|
|
|
|
default:
|
|
|
|
listFmt, optional = "ImmutableArray<%v>", false
|
|
|
|
}
|
|
|
|
|
|
|
|
wrapInput = false
|
|
|
|
typ = fmt.Sprintf(listFmt, mod.typeString(t.ElementType, qualifier, input, state, false, false, false))
|
|
|
|
case *schema.MapType:
|
|
|
|
var mapFmt string
|
|
|
|
switch {
|
|
|
|
case wrapInput:
|
|
|
|
mapFmt, optional = "InputMap<%v>", false
|
|
|
|
case requireInitializers:
|
|
|
|
mapFmt = "Dictionary<string, %v>"
|
|
|
|
default:
|
|
|
|
mapFmt = "ImmutableDictionary<string, %v>"
|
|
|
|
}
|
|
|
|
|
|
|
|
wrapInput = false
|
|
|
|
typ = fmt.Sprintf(mapFmt, mod.typeString(t.ElementType, qualifier, input, state, false, false, false))
|
|
|
|
case *schema.ObjectType:
|
2020-11-20 21:09:34 +01:00
|
|
|
namingCtx := mod
|
|
|
|
if t.Package != mod.pkg {
|
2021-01-26 20:02:34 +01:00
|
|
|
// If object type belongs to another package, we apply naming conventions from that package,
|
2020-11-20 21:09:34 +01:00
|
|
|
// including namespace naming and compatibility mode.
|
2021-01-26 20:02:34 +01:00
|
|
|
extPkg := t.Package
|
2020-11-20 21:09:34 +01:00
|
|
|
var info CSharpPackageInfo
|
2021-01-26 20:02:34 +01:00
|
|
|
contract.AssertNoError(extPkg.ImportLanguages(map[string]schema.Language{"csharp": Importer}))
|
2020-11-20 21:09:34 +01:00
|
|
|
if v, ok := t.Package.Language["csharp"].(CSharpPackageInfo); ok {
|
|
|
|
info = v
|
|
|
|
}
|
|
|
|
namingCtx = &modContext{
|
2021-01-26 20:02:34 +01:00
|
|
|
pkg: extPkg,
|
2020-11-20 21:09:34 +01:00
|
|
|
namespaces: info.Namespaces,
|
|
|
|
compatibility: info.Compatibility,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
typ = namingCtx.tokenToNamespace(t.Token, qualifier)
|
|
|
|
if (typ == namingCtx.namespaceName && qualifier == "") || typ == namingCtx.namespaceName+"."+qualifier {
|
2020-01-21 23:45:48 +01:00
|
|
|
typ = qualifier
|
|
|
|
}
|
|
|
|
if typ != "" {
|
|
|
|
typ += "."
|
|
|
|
}
|
|
|
|
typ += tokenToName(t.Token)
|
|
|
|
switch {
|
|
|
|
case state:
|
|
|
|
typ += "GetArgs"
|
|
|
|
case input:
|
|
|
|
typ += "Args"
|
|
|
|
case mod.details(t).functionType:
|
|
|
|
typ += "Result"
|
|
|
|
}
|
2020-10-13 20:33:22 +02:00
|
|
|
case *schema.ResourceType:
|
2020-11-23 21:28:00 +01:00
|
|
|
if strings.HasPrefix(t.Token, "pulumi:providers:") {
|
|
|
|
pkgName := strings.TrimPrefix(t.Token, "pulumi:providers:")
|
|
|
|
typ = fmt.Sprintf("Pulumi.%s.Provider", namespaceName(mod.namespaces, pkgName))
|
|
|
|
} else {
|
2021-01-26 20:02:34 +01:00
|
|
|
namingCtx := mod
|
|
|
|
if t.Resource != nil && t.Resource.Package != mod.pkg {
|
|
|
|
// If resource type belongs to another package, we apply naming conventions from that package,
|
|
|
|
// including namespace naming and compatibility mode.
|
|
|
|
extPkg := t.Resource.Package
|
|
|
|
var info CSharpPackageInfo
|
|
|
|
contract.AssertNoError(extPkg.ImportLanguages(map[string]schema.Language{"csharp": Importer}))
|
|
|
|
if v, ok := t.Resource.Package.Language["csharp"].(CSharpPackageInfo); ok {
|
|
|
|
info = v
|
|
|
|
}
|
|
|
|
namingCtx = &modContext{
|
|
|
|
pkg: extPkg,
|
|
|
|
namespaces: info.Namespaces,
|
|
|
|
compatibility: info.Compatibility,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
typ = namingCtx.tokenToNamespace(t.Token, "")
|
2020-11-23 21:28:00 +01:00
|
|
|
if typ != "" {
|
|
|
|
typ += "."
|
|
|
|
}
|
|
|
|
typ += tokenToName(t.Token)
|
2020-10-13 20:33:22 +02:00
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
case *schema.TokenType:
|
|
|
|
// Use the underlying type for now.
|
|
|
|
if t.UnderlyingType != nil {
|
|
|
|
return mod.typeString(t.UnderlyingType, qualifier, input, state, wrapInput, requireInitializers, optional)
|
|
|
|
}
|
|
|
|
|
|
|
|
typ = tokenToName(t.Token)
|
2020-05-19 11:41:06 +02:00
|
|
|
if ns := mod.tokenToNamespace(t.Token, qualifier); ns != mod.namespaceName {
|
2020-01-21 23:45:48 +01:00
|
|
|
typ = ns + "." + typ
|
|
|
|
}
|
|
|
|
case *schema.UnionType:
|
|
|
|
elementTypeSet := stringSet{}
|
|
|
|
var elementTypes []string
|
|
|
|
for _, e := range t.ElementTypes {
|
2020-11-06 18:01:03 +01:00
|
|
|
// If this is an output and a "relaxed" enum, emit the type as the underlying primitive type rather than the union.
|
|
|
|
// Eg. Output<string> rather than Output<Union<EnumType, string>>
|
|
|
|
if typ, ok := e.(*schema.EnumType); ok && !input {
|
|
|
|
return mod.typeString(typ.ElementType, qualifier, input, state, wrapInput, requireInitializers, optional)
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
et := mod.typeString(e, qualifier, input, state, false, false, false)
|
|
|
|
if !elementTypeSet.has(et) {
|
|
|
|
elementTypeSet.add(et)
|
|
|
|
elementTypes = append(elementTypes, et)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-05 11:13:40 +01:00
|
|
|
switch len(elementTypes) {
|
|
|
|
case 1:
|
2020-01-21 23:45:48 +01:00
|
|
|
return mod.typeString(t.ElementTypes[0], qualifier, input, state, wrapInput, requireInitializers, optional)
|
2021-01-05 11:13:40 +01:00
|
|
|
case 2:
|
|
|
|
unionT := "Union"
|
|
|
|
if wrapInput {
|
|
|
|
unionT = "InputUnion"
|
|
|
|
}
|
|
|
|
typ = fmt.Sprintf("%s<%s>", unionT, strings.Join(elementTypes, ", "))
|
|
|
|
wrapInput = false
|
|
|
|
default:
|
|
|
|
typ = "object"
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
switch t {
|
|
|
|
case schema.BoolType:
|
|
|
|
typ = "bool"
|
|
|
|
case schema.IntType:
|
|
|
|
typ = "int"
|
|
|
|
case schema.NumberType:
|
|
|
|
typ = "double"
|
|
|
|
case schema.StringType:
|
|
|
|
typ = "string"
|
|
|
|
case schema.ArchiveType:
|
|
|
|
typ = "Archive"
|
|
|
|
case schema.AssetType:
|
|
|
|
typ = "AssetOrArchive"
|
2020-05-19 11:41:06 +02:00
|
|
|
case schema.JSONType:
|
|
|
|
if wrapInput {
|
|
|
|
typ = "InputJson"
|
|
|
|
wrapInput = false
|
|
|
|
} else {
|
|
|
|
typ = "System.Text.Json.JsonElement"
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
case schema.AnyType:
|
|
|
|
typ = "object"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if wrapInput {
|
|
|
|
typ = fmt.Sprintf("Input<%s>", typ)
|
|
|
|
}
|
|
|
|
if optional {
|
|
|
|
typ += "?"
|
|
|
|
}
|
|
|
|
return typ
|
|
|
|
}
|
|
|
|
|
|
|
|
var docCommentEscaper = strings.NewReplacer(
|
|
|
|
`&`, "&",
|
|
|
|
`<`, "<",
|
|
|
|
`>`, ">",
|
|
|
|
)
|
|
|
|
|
|
|
|
func printComment(w io.Writer, comment string, indent string) {
|
|
|
|
lines := strings.Split(docCommentEscaper.Replace(comment), "\n")
|
|
|
|
for len(lines) > 0 && lines[len(lines)-1] == "" {
|
|
|
|
lines = lines[:len(lines)-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(lines) > 0 {
|
|
|
|
fmt.Fprintf(w, "%s/// <summary>\n", indent)
|
|
|
|
for _, l := range lines {
|
|
|
|
fmt.Fprintf(w, "%s/// %s\n", indent, l)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "%s/// </summary>\n", indent)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type plainType struct {
|
|
|
|
mod *modContext
|
|
|
|
res *schema.Resource
|
|
|
|
name string
|
|
|
|
comment string
|
|
|
|
baseClass string
|
|
|
|
propertyTypeQualifier string
|
|
|
|
properties []*schema.Property
|
|
|
|
wrapInput bool
|
|
|
|
state bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pt *plainType) genInputProperty(w io.Writer, prop *schema.Property, indent string) {
|
|
|
|
wireName := prop.Name
|
|
|
|
propertyName := pt.mod.propertyName(prop)
|
2021-03-10 16:08:08 +01:00
|
|
|
propertyType := pt.mod.typeString(prop.Type, pt.propertyTypeQualifier, true, pt.state, pt.wrapInput && !prop.IsPlain, false, !prop.IsRequired)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// First generate the input attribute.
|
|
|
|
attributeArgs := ""
|
|
|
|
if prop.IsRequired {
|
|
|
|
attributeArgs = ", required: true"
|
|
|
|
}
|
2020-05-27 10:14:38 +02:00
|
|
|
if pt.res != nil && pt.res.IsProvider {
|
|
|
|
json := true
|
|
|
|
if prop.Type == schema.StringType {
|
|
|
|
json = false
|
|
|
|
} else if t, ok := prop.Type.(*schema.TokenType); ok && t.UnderlyingType == schema.StringType {
|
|
|
|
json = false
|
|
|
|
}
|
|
|
|
if json {
|
|
|
|
attributeArgs += ", json: true"
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2020-11-06 18:01:03 +01:00
|
|
|
indent = strings.Repeat(indent, 2)
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// Next generate the input property itself. The way this is generated depends on the type of the property:
|
|
|
|
// complex types like lists and maps need a backing field.
|
|
|
|
switch prop.Type.(type) {
|
|
|
|
case *schema.ArrayType, *schema.MapType:
|
|
|
|
backingFieldName := "_" + prop.Name
|
|
|
|
requireInitializers := !pt.wrapInput
|
2021-03-10 16:08:08 +01:00
|
|
|
backingFieldType := pt.mod.typeString(prop.Type, pt.propertyTypeQualifier, true, pt.state, pt.wrapInput && !prop.IsPlain, requireInitializers, false)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-11-06 18:01:03 +01:00
|
|
|
fmt.Fprintf(w, "%s[Input(\"%s\"%s)]\n", indent, wireName, attributeArgs)
|
|
|
|
fmt.Fprintf(w, "%sprivate %s? %s;\n", indent, backingFieldType, backingFieldName)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
if prop.Comment != "" {
|
|
|
|
fmt.Fprintf(w, "\n")
|
2020-11-06 18:01:03 +01:00
|
|
|
printComment(w, prop.Comment, indent)
|
2020-02-06 18:29:06 +01:00
|
|
|
}
|
2020-11-06 18:01:03 +01:00
|
|
|
printObsoleteAttribute(w, prop.DeprecationMessage, indent)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// Note that we use the backing field type--which is just the property type without any nullable annotation--to
|
|
|
|
// ensure that the user does not see warnings when initializing these properties using object or collection
|
|
|
|
// initializers.
|
2020-11-06 18:01:03 +01:00
|
|
|
fmt.Fprintf(w, "%spublic %s %s\n", indent, backingFieldType, propertyName)
|
|
|
|
fmt.Fprintf(w, "%s{\n", indent)
|
|
|
|
fmt.Fprintf(w, "%s get => %[2]s ?? (%[2]s = new %[3]s());\n", indent, backingFieldName, backingFieldType)
|
|
|
|
fmt.Fprintf(w, "%s set => %s = value;\n", indent, backingFieldName)
|
|
|
|
fmt.Fprintf(w, "%s}\n", indent)
|
2020-01-21 23:45:48 +01:00
|
|
|
default:
|
|
|
|
initializer := ""
|
|
|
|
if prop.IsRequired && (!isValueType(prop.Type) || pt.wrapInput) {
|
|
|
|
initializer = " = null!;"
|
|
|
|
}
|
|
|
|
|
2020-11-06 18:01:03 +01:00
|
|
|
printComment(w, prop.Comment, indent)
|
|
|
|
fmt.Fprintf(w, "%s[Input(\"%s\"%s)]\n", indent, wireName, attributeArgs)
|
|
|
|
fmt.Fprintf(w, "%spublic %s %s { get; set; }%s\n", indent, propertyType, propertyName, initializer)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 11:41:06 +02:00
|
|
|
// Set to avoid generating a class with the same name twice.
|
|
|
|
var generatedTypes = codegen.Set{}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
func (pt *plainType) genInputType(w io.Writer, level int) error {
|
2020-05-19 11:41:06 +02:00
|
|
|
// The way the legacy codegen for kubernetes is structured, inputs for a resource args type and resource args
|
|
|
|
// subtype could become a single class because of the name + namespace clash. We use a set of generated types
|
|
|
|
// to prevent generating classes with equal full names in multiple files. The check should be removed if we
|
|
|
|
// ever change the namespacing in the k8s SDK to the standard one.
|
|
|
|
if pt.mod.isK8sCompatMode() {
|
|
|
|
key := pt.mod.namespaceName + pt.name
|
|
|
|
if generatedTypes.Has(key) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
generatedTypes.Add(key)
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
indent := strings.Repeat(" ", level)
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
2020-05-19 11:41:06 +02:00
|
|
|
sealed := "sealed "
|
|
|
|
if pt.mod.isK8sCompatMode() && (pt.res == nil || !pt.res.IsProvider) {
|
|
|
|
sealed = ""
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// Open the class.
|
|
|
|
printComment(w, pt.comment, indent)
|
2020-05-19 11:41:06 +02:00
|
|
|
fmt.Fprintf(w, "%spublic %sclass %s : Pulumi.%s\n", indent, sealed, pt.name, pt.baseClass)
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, "%s{\n", indent)
|
|
|
|
|
|
|
|
// Declare each input property.
|
|
|
|
for _, p := range pt.properties {
|
|
|
|
pt.genInputProperty(w, p, indent)
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a constructor that will set default values.
|
|
|
|
fmt.Fprintf(w, "%s public %s()\n", indent, pt.name)
|
|
|
|
fmt.Fprintf(w, "%s {\n", indent)
|
|
|
|
for _, prop := range pt.properties {
|
|
|
|
if prop.DefaultValue != nil {
|
|
|
|
dv, err := pt.mod.getDefaultValue(prop.DefaultValue, prop.Type)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
propertyName := pt.mod.propertyName(prop)
|
|
|
|
fmt.Fprintf(w, "%s %s = %s;\n", indent, propertyName, dv)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "%s }\n", indent)
|
|
|
|
|
|
|
|
// Close the class.
|
|
|
|
fmt.Fprintf(w, "%s}\n", indent)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pt *plainType) genOutputType(w io.Writer, level int) {
|
|
|
|
indent := strings.Repeat(" ", level)
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
// Open the class and attribute it appropriately.
|
|
|
|
fmt.Fprintf(w, "%s[OutputType]\n", indent)
|
|
|
|
fmt.Fprintf(w, "%spublic sealed class %s\n", indent, pt.name)
|
|
|
|
fmt.Fprintf(w, "%s{\n", indent)
|
|
|
|
|
|
|
|
// Generate each output field.
|
|
|
|
for _, prop := range pt.properties {
|
|
|
|
fieldName := pt.mod.propertyName(prop)
|
2020-05-19 11:41:06 +02:00
|
|
|
required := prop.IsRequired || pt.mod.isK8sCompatMode()
|
|
|
|
fieldType := pt.mod.typeString(prop.Type, pt.propertyTypeQualifier, false, false, false, false, !required)
|
2020-01-21 23:45:48 +01:00
|
|
|
printComment(w, prop.Comment, indent+" ")
|
|
|
|
fmt.Fprintf(w, "%s public readonly %s %s;\n", indent, fieldType, fieldName)
|
|
|
|
}
|
|
|
|
if len(pt.properties) > 0 {
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate an appropriately-attributed constructor that will set this types' fields.
|
|
|
|
fmt.Fprintf(w, "%s [OutputConstructor]\n", indent)
|
|
|
|
fmt.Fprintf(w, "%s private %s(", indent, pt.name)
|
|
|
|
|
|
|
|
// Generate the constructor parameters.
|
|
|
|
for i, prop := range pt.properties {
|
|
|
|
paramName := csharpIdentifier(prop.Name)
|
2020-05-19 11:41:06 +02:00
|
|
|
required := prop.IsRequired || pt.mod.isK8sCompatMode()
|
|
|
|
paramType := pt.mod.typeString(prop.Type, pt.propertyTypeQualifier, false, false, false, false, !required)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
terminator := ""
|
|
|
|
if i != len(pt.properties)-1 {
|
|
|
|
terminator = ",\n"
|
|
|
|
}
|
|
|
|
|
|
|
|
paramDef := fmt.Sprintf("%s %s%s", paramType, paramName, terminator)
|
|
|
|
if len(pt.properties) > 1 {
|
|
|
|
paramDef = fmt.Sprintf("\n%s %s", indent, paramDef)
|
|
|
|
}
|
|
|
|
fmt.Fprint(w, paramDef)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, ")\n")
|
|
|
|
|
|
|
|
// Generate the constructor body.
|
|
|
|
fmt.Fprintf(w, "%s {\n", indent)
|
|
|
|
for _, prop := range pt.properties {
|
|
|
|
paramName := csharpIdentifier(prop.Name)
|
|
|
|
fieldName := pt.mod.propertyName(prop)
|
2020-05-01 12:45:47 +02:00
|
|
|
if fieldName == paramName {
|
|
|
|
// Avoid a no-op in case of field and property name collision.
|
|
|
|
fieldName = "this." + fieldName
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, "%s %s = %s;\n", indent, fieldName, paramName)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "%s }\n", indent)
|
|
|
|
|
|
|
|
// Close the class.
|
|
|
|
fmt.Fprintf(w, "%s}\n", indent)
|
|
|
|
}
|
|
|
|
|
|
|
|
func primitiveValue(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 (mod *modContext) getDefaultValue(dv *schema.DefaultValue, t schema.Type) (string, error) {
|
|
|
|
var val string
|
|
|
|
if dv.Value != nil {
|
2020-12-30 12:46:01 +01:00
|
|
|
switch enum := t.(type) {
|
|
|
|
case *schema.EnumType:
|
|
|
|
enumName := tokenToName(enum.Token)
|
|
|
|
for _, e := range enum.Elements {
|
|
|
|
if e.Value != dv.Value {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
elName := e.Name
|
|
|
|
if elName == "" {
|
|
|
|
elName = fmt.Sprintf("%v", e.Value)
|
|
|
|
}
|
|
|
|
safeName, err := makeSafeEnumName(elName, enumName)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
val = fmt.Sprintf("%s.%s.%s", mod.namespaceName, enumName, safeName)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if val == "" {
|
|
|
|
return "", errors.Errorf("default value '%v' not found in enum '%s'", dv.Value, enumName)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
v, err := primitiveValue(dv.Value)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
val = v
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(dv.Environment) != 0 {
|
|
|
|
getType := ""
|
|
|
|
switch t {
|
|
|
|
case schema.BoolType:
|
|
|
|
getType = "Boolean"
|
|
|
|
case schema.IntType:
|
|
|
|
getType = "Int32"
|
|
|
|
case schema.NumberType:
|
|
|
|
getType = "Double"
|
|
|
|
}
|
|
|
|
|
|
|
|
envVars := fmt.Sprintf("%q", dv.Environment[0])
|
|
|
|
for _, e := range dv.Environment[1:] {
|
|
|
|
envVars += fmt.Sprintf(", %q", e)
|
|
|
|
}
|
|
|
|
|
|
|
|
getEnv := fmt.Sprintf("Utilities.GetEnv%s(%s)", getType, envVars)
|
|
|
|
if val != "" {
|
|
|
|
val = fmt.Sprintf("%s ?? %s", getEnv, val)
|
|
|
|
} else {
|
|
|
|
val = getEnv
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return val, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func genAlias(w io.Writer, alias *schema.Alias) {
|
2020-08-28 18:57:57 +02:00
|
|
|
fmt.Fprintf(w, "new Pulumi.Alias { ")
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
parts := []string{}
|
|
|
|
if alias.Name != nil {
|
|
|
|
parts = append(parts, fmt.Sprintf("Name = \"%v\"", *alias.Name))
|
|
|
|
}
|
|
|
|
if alias.Project != nil {
|
|
|
|
parts = append(parts, fmt.Sprintf("Project = \"%v\"", *alias.Project))
|
|
|
|
}
|
|
|
|
if alias.Type != nil {
|
|
|
|
parts = append(parts, fmt.Sprintf("Type = \"%v\"", *alias.Type))
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, part := range parts {
|
|
|
|
if i > 0 {
|
|
|
|
fmt.Fprintf(w, ", ")
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "%s", part)
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "}")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) genResource(w io.Writer, r *schema.Resource) error {
|
|
|
|
// Create a resource module file into which all of this resource's types will go.
|
|
|
|
name := resourceName(r)
|
|
|
|
|
|
|
|
// Open the namespace.
|
|
|
|
fmt.Fprintf(w, "namespace %s\n", mod.namespaceName)
|
|
|
|
fmt.Fprintf(w, "{\n")
|
|
|
|
|
2020-11-06 18:01:03 +01:00
|
|
|
// Write the documentation comment for the resource class
|
2020-06-18 21:32:15 +02:00
|
|
|
printComment(w, codegen.FilterExamples(r.Comment, "csharp"), " ")
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// Open the class.
|
|
|
|
className := name
|
2020-10-13 20:33:22 +02:00
|
|
|
var baseType string
|
|
|
|
optionsType := "CustomResourceOptions"
|
|
|
|
switch {
|
|
|
|
case r.IsProvider:
|
2020-01-21 23:45:48 +01:00
|
|
|
baseType = "Pulumi.ProviderResource"
|
2020-10-13 20:33:22 +02:00
|
|
|
case mod.isK8sCompatMode():
|
|
|
|
baseType = "KubernetesResource"
|
|
|
|
case r.IsComponent:
|
|
|
|
baseType = "Pulumi.ComponentResource"
|
|
|
|
optionsType = "ComponentResourceOptions"
|
|
|
|
default:
|
|
|
|
baseType = "Pulumi.CustomResource"
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
2020-10-13 20:33:22 +02:00
|
|
|
|
2020-04-24 02:18:29 +02:00
|
|
|
if r.DeprecationMessage != "" {
|
|
|
|
fmt.Fprintf(w, " [Obsolete(@\"%s\")]\n", strings.Replace(r.DeprecationMessage, `"`, `""`, -1))
|
|
|
|
}
|
2020-12-09 23:28:58 +01:00
|
|
|
fmt.Fprintf(w, " [%sResourceType(\"%s\")]\n", namespaceName(mod.namespaces, mod.pkg.Name), r.Token)
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, " public partial class %s : %s\n", className, baseType)
|
|
|
|
fmt.Fprintf(w, " {\n")
|
|
|
|
|
2020-07-02 21:30:10 +02:00
|
|
|
var secretProps []string
|
2020-01-21 23:45:48 +01:00
|
|
|
// Emit all output properties.
|
|
|
|
for _, prop := range r.Properties {
|
|
|
|
// Write the property attribute
|
|
|
|
wireName := prop.Name
|
|
|
|
propertyName := mod.propertyName(prop)
|
2020-05-19 11:41:06 +02:00
|
|
|
required := prop.IsRequired || mod.isK8sCompatMode()
|
|
|
|
propertyType := mod.typeString(prop.Type, "Outputs", false, false, false, false, !required)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// Workaround the fact that provider inputs come back as strings.
|
|
|
|
if r.IsProvider && !schema.IsPrimitiveType(prop.Type) {
|
|
|
|
propertyType = "string"
|
|
|
|
if !prop.IsRequired {
|
|
|
|
propertyType += "?"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-02 21:30:10 +02:00
|
|
|
if prop.Secret {
|
|
|
|
secretProps = append(secretProps, prop.Name)
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
printComment(w, prop.Comment, " ")
|
|
|
|
fmt.Fprintf(w, " [Output(\"%s\")]\n", wireName)
|
|
|
|
fmt.Fprintf(w, " public Output<%s> %s { get; private set; } = null!;\n", propertyType, propertyName)
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
}
|
|
|
|
if len(r.Properties) > 0 {
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit the class constructor.
|
2020-05-19 11:41:06 +02:00
|
|
|
argsClassName := className + "Args"
|
|
|
|
if mod.isK8sCompatMode() && !r.IsProvider {
|
|
|
|
argsClassName = fmt.Sprintf("%s.%sArgs", mod.tokenToNamespace(r.Token, "Inputs"), className)
|
|
|
|
}
|
|
|
|
argsType := argsClassName
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
var argsDefault string
|
|
|
|
allOptionalInputs := true
|
2020-05-01 12:45:47 +02:00
|
|
|
hasConstInputs := false
|
2020-01-21 23:45:48 +01:00
|
|
|
for _, prop := range r.InputProperties {
|
|
|
|
allOptionalInputs = allOptionalInputs && !prop.IsRequired
|
2020-05-01 12:45:47 +02:00
|
|
|
hasConstInputs = hasConstInputs || prop.ConstValue != nil
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
2020-05-19 11:41:06 +02:00
|
|
|
if allOptionalInputs || mod.isK8sCompatMode() {
|
2020-01-21 23:45:48 +01:00
|
|
|
// If the number of required input properties was zero, we can make the args object optional.
|
|
|
|
argsDefault = " = null"
|
|
|
|
argsType += "?"
|
|
|
|
}
|
|
|
|
|
|
|
|
tok := r.Token
|
|
|
|
if r.IsProvider {
|
|
|
|
tok = mod.pkg.Name
|
|
|
|
}
|
|
|
|
|
2020-05-01 12:45:47 +02:00
|
|
|
argsOverride := fmt.Sprintf("args ?? new %sArgs()", className)
|
|
|
|
if hasConstInputs {
|
|
|
|
argsOverride = "MakeArgs(args)"
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// Write a comment prior to the constructor.
|
|
|
|
fmt.Fprintf(w, " /// <summary>\n")
|
|
|
|
fmt.Fprintf(w, " /// Create a %s resource with the given unique name, arguments, and options.\n", className)
|
|
|
|
fmt.Fprintf(w, " /// </summary>\n")
|
|
|
|
fmt.Fprintf(w, " ///\n")
|
|
|
|
fmt.Fprintf(w, " /// <param name=\"name\">The unique name of the resource</param>\n")
|
|
|
|
fmt.Fprintf(w, " /// <param name=\"args\">The arguments used to populate this resource's properties</param>\n")
|
|
|
|
fmt.Fprintf(w, " /// <param name=\"options\">A bag of options that control this resource's behavior</param>\n")
|
|
|
|
|
|
|
|
fmt.Fprintf(w, " public %s(string name, %s args%s, %s? options = null)\n", className, argsType, argsDefault, optionsType)
|
2020-10-13 20:33:22 +02:00
|
|
|
if r.IsComponent {
|
|
|
|
fmt.Fprintf(w, " : base(\"%s\", name, %s, MakeResourceOptions(options, \"\"), remote: true)\n", tok, argsOverride)
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, " : base(\"%s\", name, %s, MakeResourceOptions(options, \"\"))\n", tok, argsOverride)
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, " {\n")
|
|
|
|
fmt.Fprintf(w, " }\n")
|
|
|
|
|
2020-10-13 20:33:22 +02:00
|
|
|
// Write a dictionary constructor.
|
|
|
|
if mod.dictionaryConstructors && !r.IsComponent {
|
|
|
|
fmt.Fprintf(w, " internal %s(string name, ImmutableDictionary<string, object?> dictionary, %s? options = null)\n",
|
|
|
|
className, optionsType)
|
|
|
|
if r.IsComponent {
|
|
|
|
fmt.Fprintf(w, " : base(\"%s\", name, new DictionaryResourceArgs(dictionary), MakeResourceOptions(options, \"\"), remote: true)\n", tok)
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, " : base(\"%s\", name, new DictionaryResourceArgs(dictionary), MakeResourceOptions(options, \"\"))\n", tok)
|
|
|
|
}
|
2020-05-19 11:41:06 +02:00
|
|
|
fmt.Fprintf(w, " {\n")
|
|
|
|
fmt.Fprintf(w, " }\n")
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// Write a private constructor for the use of `Get`.
|
2020-10-13 20:33:22 +02:00
|
|
|
if !r.IsProvider && !r.IsComponent {
|
2020-01-21 23:45:48 +01:00
|
|
|
stateParam, stateRef := "", "null"
|
|
|
|
if r.StateInputs != nil {
|
|
|
|
stateParam, stateRef = fmt.Sprintf("%sState? state = null, ", className), "state"
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
fmt.Fprintf(w, " private %s(string name, Input<string> id, %s%s? options = null)\n", className, stateParam, optionsType)
|
|
|
|
fmt.Fprintf(w, " : base(\"%s\", name, %s, MakeResourceOptions(options, id))\n", tok, stateRef)
|
|
|
|
fmt.Fprintf(w, " {\n")
|
|
|
|
fmt.Fprintf(w, " }\n")
|
|
|
|
}
|
|
|
|
|
2020-05-01 12:45:47 +02:00
|
|
|
if hasConstInputs {
|
|
|
|
// Write the method that will calculate the resource arguments.
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
fmt.Fprintf(w, " private static %[1]s MakeArgs(%[1]s args)\n", argsType)
|
|
|
|
fmt.Fprintf(w, " {\n")
|
2020-05-19 11:41:06 +02:00
|
|
|
fmt.Fprintf(w, " args ??= new %s();\n", argsClassName)
|
2020-05-01 12:45:47 +02:00
|
|
|
for _, prop := range r.InputProperties {
|
|
|
|
if prop.ConstValue != nil {
|
|
|
|
v, err := primitiveValue(prop.ConstValue)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " args.%s = %s;\n", mod.propertyName(prop), v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " return args;\n")
|
|
|
|
fmt.Fprintf(w, " }\n")
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// Write the method that will calculate the resource options.
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
fmt.Fprintf(w, " private static %[1]s MakeResourceOptions(%[1]s? options, Input<string>? id)\n", optionsType)
|
|
|
|
fmt.Fprintf(w, " {\n")
|
|
|
|
fmt.Fprintf(w, " var defaultOptions = new %s\n", optionsType)
|
|
|
|
fmt.Fprintf(w, " {\n")
|
2020-07-02 21:30:10 +02:00
|
|
|
fmt.Fprintf(w, " Version = Utilities.Version,\n")
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-07-02 21:30:10 +02:00
|
|
|
if len(r.Aliases) > 0 {
|
|
|
|
fmt.Fprintf(w, " Aliases =\n")
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, " {\n")
|
|
|
|
for _, alias := range r.Aliases {
|
|
|
|
fmt.Fprintf(w, " ")
|
|
|
|
genAlias(w, alias)
|
|
|
|
fmt.Fprintf(w, ",\n")
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " },\n")
|
|
|
|
}
|
2020-07-02 21:30:10 +02:00
|
|
|
if len(secretProps) > 0 {
|
|
|
|
fmt.Fprintf(w, " AdditionalSecretOutputs =\n")
|
|
|
|
fmt.Fprintf(w, " {\n")
|
|
|
|
for _, sp := range secretProps {
|
|
|
|
fmt.Fprintf(w, " ")
|
|
|
|
fmt.Fprintf(w, "%q", sp)
|
|
|
|
fmt.Fprintf(w, ",\n")
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " },\n")
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
fmt.Fprintf(w, " };\n")
|
|
|
|
fmt.Fprintf(w, " var merged = %s.Merge(defaultOptions, options);\n", optionsType)
|
|
|
|
fmt.Fprintf(w, " // Override the ID if one was specified for consistency with other language SDKs.\n")
|
|
|
|
fmt.Fprintf(w, " merged.Id = id ?? merged.Id;\n")
|
|
|
|
fmt.Fprintf(w, " return merged;\n")
|
|
|
|
fmt.Fprintf(w, " }\n")
|
|
|
|
|
2020-10-13 20:33:22 +02:00
|
|
|
// Write the `Get` method for reading instances of this resource unless this is a provider resource or ComponentResource.
|
|
|
|
if !r.IsProvider && !r.IsComponent {
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, " /// <summary>\n")
|
|
|
|
fmt.Fprintf(w, " /// Get an existing %s resource's state with the given name, ID, and optional extra\n", className)
|
|
|
|
fmt.Fprintf(w, " /// properties used to qualify the lookup.\n")
|
|
|
|
fmt.Fprintf(w, " /// </summary>\n")
|
|
|
|
fmt.Fprintf(w, " ///\n")
|
|
|
|
fmt.Fprintf(w, " /// <param name=\"name\">The unique name of the resulting resource.</param>\n")
|
|
|
|
fmt.Fprintf(w, " /// <param name=\"id\">The unique provider ID of the resource to lookup.</param>\n")
|
2020-05-01 12:45:47 +02:00
|
|
|
|
|
|
|
stateParam, stateRef := "", ""
|
|
|
|
if r.StateInputs != nil {
|
|
|
|
stateParam, stateRef = fmt.Sprintf("%sState? state = null, ", className), "state, "
|
|
|
|
fmt.Fprintf(w, " /// <param name=\"state\">Any extra arguments used during the lookup.</param>\n")
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, " /// <param name=\"options\">A bag of options that control this resource's behavior</param>\n")
|
|
|
|
fmt.Fprintf(w, " public static %s Get(string name, Input<string> id, %s%s? options = null)\n", className, stateParam, optionsType)
|
|
|
|
fmt.Fprintf(w, " {\n")
|
|
|
|
fmt.Fprintf(w, " return new %s(name, id, %soptions);\n", className, stateRef)
|
|
|
|
fmt.Fprintf(w, " }\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close the class.
|
|
|
|
fmt.Fprintf(w, " }\n")
|
|
|
|
|
2020-05-19 11:41:06 +02:00
|
|
|
// Arguments are in a different namespace for the Kubernetes SDK.
|
|
|
|
if mod.isK8sCompatMode() && !r.IsProvider {
|
|
|
|
// Close the namespace.
|
|
|
|
fmt.Fprintf(w, "}\n")
|
|
|
|
|
|
|
|
// Open the namespace.
|
|
|
|
fmt.Fprintf(w, "namespace %s\n", mod.tokenToNamespace(r.Token, "Inputs"))
|
|
|
|
fmt.Fprintf(w, "{\n")
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// Generate the resource args type.
|
|
|
|
args := &plainType{
|
|
|
|
mod: mod,
|
|
|
|
res: r,
|
|
|
|
name: name + "Args",
|
|
|
|
baseClass: "ResourceArgs",
|
|
|
|
propertyTypeQualifier: "Inputs",
|
|
|
|
properties: r.InputProperties,
|
|
|
|
wrapInput: true,
|
|
|
|
}
|
|
|
|
if err := args.genInputType(w, 1); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate the `Get` args type, if any.
|
|
|
|
if r.StateInputs != nil {
|
|
|
|
state := &plainType{
|
|
|
|
mod: mod,
|
|
|
|
res: r,
|
|
|
|
name: name + "State",
|
|
|
|
baseClass: "ResourceArgs",
|
|
|
|
propertyTypeQualifier: "Inputs",
|
|
|
|
properties: r.StateInputs.Properties,
|
|
|
|
wrapInput: true,
|
|
|
|
state: true,
|
|
|
|
}
|
|
|
|
if err := state.genInputType(w, 1); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close the namespace.
|
|
|
|
fmt.Fprintf(w, "}\n")
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) genFunction(w io.Writer, fun *schema.Function) error {
|
2020-04-28 02:47:01 +02:00
|
|
|
className := tokenToFunctionName(fun.Token)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-05-19 11:41:06 +02:00
|
|
|
fmt.Fprintf(w, "namespace %s\n", mod.tokenToNamespace(fun.Token, ""))
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, "{\n")
|
|
|
|
|
|
|
|
var typeParameter string
|
|
|
|
if fun.Outputs != nil {
|
2020-04-14 10:30:25 +02:00
|
|
|
typeParameter = fmt.Sprintf("<%sResult>", className)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var argsParamDef string
|
|
|
|
argsParamRef := "InvokeArgs.Empty"
|
|
|
|
if fun.Inputs != nil {
|
|
|
|
allOptionalInputs := true
|
|
|
|
for _, prop := range fun.Inputs.Properties {
|
|
|
|
allOptionalInputs = allOptionalInputs && !prop.IsRequired
|
|
|
|
}
|
|
|
|
|
|
|
|
var argsDefault, sigil string
|
|
|
|
if allOptionalInputs {
|
|
|
|
// If the number of required input properties was zero, we can make the args object optional.
|
|
|
|
argsDefault, sigil = " = null", "?"
|
|
|
|
}
|
|
|
|
|
2020-04-14 10:30:25 +02:00
|
|
|
argsParamDef = fmt.Sprintf("%sArgs%s args%s, ", className, sigil, argsDefault)
|
|
|
|
argsParamRef = fmt.Sprintf("args ?? new %sArgs()", className)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2020-04-24 02:18:29 +02:00
|
|
|
if fun.DeprecationMessage != "" {
|
|
|
|
fmt.Fprintf(w, " [Obsolete(@\"%s\")]\n", strings.Replace(fun.DeprecationMessage, `"`, `""`, -1))
|
|
|
|
}
|
2020-04-14 10:30:25 +02:00
|
|
|
// Open the class we'll use for datasources.
|
|
|
|
fmt.Fprintf(w, " public static class %s\n", className)
|
|
|
|
fmt.Fprintf(w, " {\n")
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// Emit the doc comment, if any.
|
2020-04-14 10:30:25 +02:00
|
|
|
printComment(w, fun.Comment, " ")
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// Emit the datasource method.
|
2020-04-14 10:30:25 +02:00
|
|
|
fmt.Fprintf(w, " public static Task%s InvokeAsync(%sInvokeOptions? options = null)\n",
|
|
|
|
typeParameter, argsParamDef)
|
|
|
|
fmt.Fprintf(w, " => Pulumi.Deployment.Instance.InvokeAsync%s(\"%s\", %s, options.WithVersion());\n",
|
|
|
|
typeParameter, fun.Token, argsParamRef)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// Close the class.
|
|
|
|
fmt.Fprintf(w, " }\n")
|
|
|
|
|
|
|
|
// Emit the args and result types, if any.
|
|
|
|
if fun.Inputs != nil {
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
args := &plainType{
|
|
|
|
mod: mod,
|
2020-04-14 10:30:25 +02:00
|
|
|
name: className + "Args",
|
2020-01-21 23:45:48 +01:00
|
|
|
baseClass: "InvokeArgs",
|
|
|
|
propertyTypeQualifier: "Inputs",
|
|
|
|
properties: fun.Inputs.Properties,
|
|
|
|
}
|
|
|
|
if err := args.genInputType(w, 1); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if fun.Outputs != nil {
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
res := &plainType{
|
|
|
|
mod: mod,
|
2020-04-14 10:30:25 +02:00
|
|
|
name: className + "Result",
|
2020-01-21 23:45:48 +01:00
|
|
|
propertyTypeQualifier: "Outputs",
|
|
|
|
properties: fun.Outputs.Properties,
|
|
|
|
}
|
|
|
|
res.genOutputType(w, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close the namespace.
|
|
|
|
fmt.Fprintf(w, "}\n")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-06 18:01:03 +01:00
|
|
|
func (mod *modContext) genEnums(w io.Writer, enums []*schema.EnumType) error {
|
|
|
|
// Open the namespace.
|
|
|
|
fmt.Fprintf(w, "namespace %s\n", mod.namespaceName)
|
|
|
|
fmt.Fprintf(w, "{\n")
|
|
|
|
|
|
|
|
for i, enum := range enums {
|
|
|
|
err := mod.genEnum(w, enum)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if i != len(enums)-1 {
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close the namespace.
|
|
|
|
fmt.Fprintf(w, "}\n")
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func printObsoleteAttribute(w io.Writer, deprecationMessage, indent string) {
|
|
|
|
if deprecationMessage != "" {
|
|
|
|
fmt.Fprintf(w, "%s[Obsolete(@\"%s\")]\n", indent, strings.Replace(deprecationMessage, `"`, `""`, -1))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) genEnum(w io.Writer, enum *schema.EnumType) error {
|
|
|
|
indent := " "
|
|
|
|
enumName := tokenToName(enum.Token)
|
|
|
|
|
2020-11-09 20:33:22 +01:00
|
|
|
// Fix up identifiers for each enum value.
|
|
|
|
for _, e := range enum.Elements {
|
|
|
|
// If the enum doesn't have a name, set the value as the name.
|
|
|
|
if e.Name == "" {
|
|
|
|
e.Name = fmt.Sprintf("%v", e.Value)
|
|
|
|
}
|
|
|
|
|
2020-12-16 18:22:44 +01:00
|
|
|
safeName, err := makeSafeEnumName(e.Name, enumName)
|
2020-11-09 20:33:22 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
e.Name = safeName
|
|
|
|
}
|
|
|
|
|
2020-11-06 18:01:03 +01:00
|
|
|
// Print documentation comment
|
|
|
|
printComment(w, enum.Comment, indent)
|
|
|
|
|
|
|
|
underlyingType := mod.typeString(enum.ElementType, "", false, false, false, false, false)
|
|
|
|
switch enum.ElementType {
|
|
|
|
case schema.StringType, schema.NumberType:
|
|
|
|
// EnumType attribute
|
|
|
|
fmt.Fprintf(w, "%s[EnumType]\n", indent)
|
|
|
|
|
|
|
|
// Open struct declaration
|
|
|
|
fmt.Fprintf(w, "%[1]spublic readonly struct %[2]s : IEquatable<%[2]s>\n", indent, enumName)
|
|
|
|
fmt.Fprintf(w, "%s{\n", indent)
|
|
|
|
indent := strings.Repeat(indent, 2)
|
|
|
|
fmt.Fprintf(w, "%sprivate readonly %s _value;\n", indent, underlyingType)
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
// Constructor
|
|
|
|
fmt.Fprintf(w, "%sprivate %s(%s value)\n", indent, enumName, underlyingType)
|
|
|
|
fmt.Fprintf(w, "%s{\n", indent)
|
|
|
|
fmt.Fprintf(w, "%s _value = value", indent)
|
|
|
|
if enum.ElementType == schema.StringType {
|
|
|
|
fmt.Fprintf(w, " ?? throw new ArgumentNullException(nameof(value))")
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, ";\n")
|
|
|
|
fmt.Fprintf(w, "%s}\n", indent)
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
// Enum values
|
|
|
|
for _, e := range enum.Elements {
|
|
|
|
printComment(w, e.Comment, indent)
|
|
|
|
printObsoleteAttribute(w, e.DeprecationMessage, indent)
|
|
|
|
fmt.Fprintf(w, "%[1]spublic static %[2]s %[3]s { get; } = new %[2]s(", indent, enumName, e.Name)
|
|
|
|
if enum.ElementType == schema.StringType {
|
|
|
|
fmt.Fprintf(w, "%q", e.Value)
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, "%v", e.Value)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, ");\n")
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
// Equality and inequality operators
|
|
|
|
fmt.Fprintf(w, "%[1]spublic static bool operator ==(%[2]s left, %[2]s right) => left.Equals(right);\n", indent, enumName)
|
|
|
|
fmt.Fprintf(w, "%[1]spublic static bool operator !=(%[2]s left, %[2]s right) => !left.Equals(right);\n", indent, enumName)
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
// Explicit conversion operator
|
|
|
|
fmt.Fprintf(w, "%[1]spublic static explicit operator %s(%s value) => value._value;\n", indent, underlyingType, enumName)
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
// Equals override
|
|
|
|
fmt.Fprintf(w, "%s[EditorBrowsable(EditorBrowsableState.Never)]\n", indent)
|
|
|
|
fmt.Fprintf(w, "%spublic override bool Equals(object? obj) => obj is %s other && Equals(other);\n", indent, enumName)
|
|
|
|
fmt.Fprintf(w, "%spublic bool Equals(%s other) => ", indent, enumName)
|
|
|
|
if enum.ElementType == schema.StringType {
|
|
|
|
fmt.Fprintf(w, "string.Equals(_value, other._value, StringComparison.Ordinal)")
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, "_value == other._value")
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, ";\n")
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
// GetHashCode override
|
|
|
|
fmt.Fprintf(w, "%s[EditorBrowsable(EditorBrowsableState.Never)]\n", indent)
|
|
|
|
fmt.Fprintf(w, "%spublic override int GetHashCode() => _value", indent)
|
|
|
|
if enum.ElementType == schema.StringType {
|
|
|
|
fmt.Fprintf(w, "?")
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, ".GetHashCode()")
|
|
|
|
if enum.ElementType == schema.StringType {
|
|
|
|
fmt.Fprintf(w, " ?? 0")
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, ";\n")
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
// ToString override
|
|
|
|
fmt.Fprintf(w, "%spublic override string ToString() => _value", indent)
|
|
|
|
if enum.ElementType == schema.NumberType {
|
|
|
|
fmt.Fprintf(w, ".ToString()")
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, ";\n")
|
|
|
|
case schema.IntType:
|
|
|
|
// Open enum declaration
|
|
|
|
fmt.Fprintf(w, "%spublic enum %s\n", indent, enumName)
|
|
|
|
fmt.Fprintf(w, "%s{\n", indent)
|
|
|
|
for _, e := range enum.Elements {
|
|
|
|
indent := strings.Repeat(indent, 2)
|
|
|
|
printComment(w, e.Comment, indent)
|
|
|
|
printObsoleteAttribute(w, e.DeprecationMessage, indent)
|
|
|
|
fmt.Fprintf(w, "%s%s = %v,\n", indent, e.Name, e.Value)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
// Issue to implement boolean-based enums: https://github.com/pulumi/pulumi/issues/5652
|
|
|
|
return fmt.Errorf("enums of type %s are not yet implemented for this language", enum.ElementType.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close the declaration
|
|
|
|
fmt.Fprintf(w, "%s}\n", indent)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-01 12:45:47 +02:00
|
|
|
func visitObjectTypesAcc(t schema.Type, visitor func(*schema.ObjectType), visited codegen.Set) {
|
|
|
|
if visited.Has(t) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
visited.Add(t)
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
switch t := t.(type) {
|
|
|
|
case *schema.ArrayType:
|
2020-05-01 12:45:47 +02:00
|
|
|
visitObjectTypesAcc(t.ElementType, visitor, visited)
|
2020-01-21 23:45:48 +01:00
|
|
|
case *schema.MapType:
|
2020-05-01 12:45:47 +02:00
|
|
|
visitObjectTypesAcc(t.ElementType, visitor, visited)
|
2020-01-21 23:45:48 +01:00
|
|
|
case *schema.ObjectType:
|
|
|
|
for _, p := range t.Properties {
|
2020-05-01 12:45:47 +02:00
|
|
|
visitObjectTypesAcc(p.Type, visitor, visited)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
visitor(t)
|
|
|
|
case *schema.UnionType:
|
|
|
|
for _, e := range t.ElementTypes {
|
2020-05-01 12:45:47 +02:00
|
|
|
visitObjectTypesAcc(e, visitor, visited)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-01 12:45:47 +02:00
|
|
|
func visitObjectTypes(t schema.Type, visitor func(*schema.ObjectType)) {
|
|
|
|
// Accumulator to avoid visiting the same node twice in case of recursive types.
|
|
|
|
visited := codegen.Set{}
|
|
|
|
visitObjectTypesAcc(t, visitor, visited)
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
func (mod *modContext) genType(w io.Writer, obj *schema.ObjectType, propertyTypeQualifier string, input, state bool, level int) error {
|
|
|
|
name := tokenToName(obj.Token)
|
|
|
|
switch {
|
|
|
|
case state:
|
|
|
|
name += "GetArgs"
|
|
|
|
case input:
|
|
|
|
name += "Args"
|
|
|
|
case mod.details(obj).functionType:
|
|
|
|
name += "Result"
|
|
|
|
}
|
|
|
|
|
|
|
|
pt := &plainType{
|
|
|
|
mod: mod,
|
|
|
|
name: name,
|
|
|
|
comment: obj.Comment,
|
|
|
|
propertyTypeQualifier: propertyTypeQualifier,
|
|
|
|
properties: obj.Properties,
|
|
|
|
state: state,
|
|
|
|
}
|
|
|
|
|
|
|
|
if input {
|
|
|
|
pt.baseClass, pt.wrapInput = "ResourceArgs", true
|
|
|
|
if mod.details(obj).functionType {
|
|
|
|
pt.baseClass, pt.wrapInput = "InvokeArgs", false
|
|
|
|
}
|
|
|
|
return pt.genInputType(w, level)
|
|
|
|
}
|
|
|
|
|
|
|
|
pt.genOutputType(w, level)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-10-13 20:33:22 +02:00
|
|
|
// pulumiImports is a slice of common imports that are used with the genHeader method.
|
|
|
|
var pulumiImports = []string{
|
|
|
|
"System",
|
|
|
|
"System.Collections.Generic",
|
|
|
|
"System.Collections.Immutable",
|
|
|
|
"System.Threading.Tasks",
|
|
|
|
"Pulumi.Serialization",
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) getTypeImports(t schema.Type, recurse bool, imports map[string]codegen.StringSet, seen codegen.Set) {
|
|
|
|
if seen.Has(t) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
seen.Add(t)
|
|
|
|
|
|
|
|
switch t := t.(type) {
|
|
|
|
case *schema.ArrayType:
|
|
|
|
mod.getTypeImports(t.ElementType, recurse, imports, seen)
|
|
|
|
return
|
|
|
|
case *schema.MapType:
|
|
|
|
mod.getTypeImports(t.ElementType, recurse, imports, seen)
|
|
|
|
return
|
|
|
|
case *schema.ObjectType:
|
|
|
|
for _, p := range t.Properties {
|
|
|
|
mod.getTypeImports(p.Type, recurse, imports, seen)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
case *schema.ResourceType:
|
2021-01-26 20:02:34 +01:00
|
|
|
// If it's an external resource, we'll be using fully-qualified type names, so there's no need
|
|
|
|
// for an import.
|
|
|
|
if t.Resource != nil && t.Resource.Package != mod.pkg {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-10-13 20:33:22 +02:00
|
|
|
modName, name, modPath := mod.pkg.TokenToModule(t.Token), tokenToName(t.Token), ""
|
|
|
|
if modName != mod.mod {
|
|
|
|
mp, err := filepath.Rel(mod.mod, modName)
|
|
|
|
contract.Assert(err == nil)
|
|
|
|
if path.Base(mp) == "." {
|
|
|
|
mp = path.Dir(mp)
|
|
|
|
}
|
|
|
|
modPath = filepath.ToSlash(mp)
|
|
|
|
}
|
|
|
|
if len(modPath) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if imports[modPath] == nil {
|
|
|
|
imports[modPath] = codegen.NewStringSet()
|
|
|
|
}
|
|
|
|
imports[modPath].Add(name)
|
|
|
|
return
|
|
|
|
case *schema.TokenType:
|
|
|
|
return
|
|
|
|
case *schema.UnionType:
|
|
|
|
for _, e := range t.ElementTypes {
|
|
|
|
mod.getTypeImports(e, recurse, imports, seen)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) getImports(member interface{}, imports map[string]codegen.StringSet) {
|
|
|
|
seen := codegen.Set{}
|
|
|
|
switch member := member.(type) {
|
|
|
|
case *schema.ObjectType:
|
|
|
|
for _, p := range member.Properties {
|
|
|
|
mod.getTypeImports(p.Type, true, imports, seen)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
case *schema.ResourceType:
|
|
|
|
mod.getTypeImports(member, true, imports, seen)
|
|
|
|
return
|
|
|
|
case *schema.Resource:
|
|
|
|
for _, p := range member.Properties {
|
|
|
|
mod.getTypeImports(p.Type, false, imports, seen)
|
|
|
|
}
|
|
|
|
for _, p := range member.InputProperties {
|
|
|
|
mod.getTypeImports(p.Type, false, imports, seen)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
case *schema.Function:
|
|
|
|
if member.Inputs != nil {
|
|
|
|
mod.getTypeImports(member.Inputs, false, imports, seen)
|
|
|
|
}
|
|
|
|
if member.Outputs != nil {
|
|
|
|
mod.getTypeImports(member.Outputs, false, imports, seen)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
case []*schema.Property:
|
|
|
|
for _, p := range member {
|
|
|
|
mod.getTypeImports(p.Type, false, imports, seen)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
return
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) genHeader(w io.Writer, using []string) {
|
|
|
|
fmt.Fprintf(w, "// *** WARNING: this file was generated by %v. ***\n", mod.tool)
|
|
|
|
fmt.Fprintf(w, "// *** Do not edit by hand unless you're certain you know what you are doing! ***\n")
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
for _, u := range using {
|
|
|
|
fmt.Fprintf(w, "using %s;\n", u)
|
|
|
|
}
|
|
|
|
if len(using) > 0 {
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-15 19:59:02 +02:00
|
|
|
func (mod *modContext) getConfigProperty(schemaType schema.Type) (string, string) {
|
|
|
|
propertyType := mod.typeString(
|
|
|
|
schemaType, "Types", false, false, false /*wrapInputs*/, false /*requireInitializers*/, false)
|
|
|
|
|
|
|
|
var getFunc string
|
|
|
|
nullableSigil := "?"
|
|
|
|
switch schemaType {
|
|
|
|
case schema.StringType:
|
|
|
|
getFunc = "Get"
|
|
|
|
case schema.BoolType:
|
|
|
|
getFunc = "GetBoolean"
|
|
|
|
case schema.IntType:
|
|
|
|
getFunc = "GetInt32"
|
|
|
|
case schema.NumberType:
|
|
|
|
getFunc = "GetDouble"
|
|
|
|
default:
|
|
|
|
switch t := schemaType.(type) {
|
|
|
|
case *schema.TokenType:
|
|
|
|
if t.UnderlyingType != nil {
|
|
|
|
return mod.getConfigProperty(t.UnderlyingType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getFunc = "GetObject<" + propertyType + ">"
|
|
|
|
if _, ok := schemaType.(*schema.ArrayType); ok {
|
|
|
|
nullableSigil = ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return propertyType + nullableSigil, getFunc
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
func (mod *modContext) genConfig(variables []*schema.Property) (string, error) {
|
|
|
|
w := &bytes.Buffer{}
|
|
|
|
|
|
|
|
mod.genHeader(w, []string{"System.Collections.Immutable"})
|
|
|
|
// Use the root namespace to avoid `Pulumi.Provider.Config.Config.VarName` usage.
|
|
|
|
fmt.Fprintf(w, "namespace %s\n", mod.namespaceName)
|
|
|
|
fmt.Fprintf(w, "{\n")
|
|
|
|
|
|
|
|
// Open the config class.
|
|
|
|
fmt.Fprintf(w, " public static class Config\n")
|
|
|
|
fmt.Fprintf(w, " {\n")
|
|
|
|
|
|
|
|
// Create a config bag for the variables to pull from.
|
|
|
|
fmt.Fprintf(w, " private static readonly Pulumi.Config __config = new Pulumi.Config(\"%v\");", mod.pkg.Name)
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
// Emit an entry for all config variables.
|
|
|
|
for _, p := range variables {
|
2020-04-15 19:59:02 +02:00
|
|
|
propertyType, getFunc := mod.getConfigProperty(p.Type)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-05-01 12:45:47 +02:00
|
|
|
propertyName := mod.propertyName(p)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
initializer := fmt.Sprintf("__config.%s(\"%s\")", getFunc, p.Name)
|
|
|
|
if p.DefaultValue != nil {
|
|
|
|
dv, err := mod.getDefaultValue(p.DefaultValue, p.Type)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
initializer += " ?? " + dv
|
|
|
|
}
|
|
|
|
|
|
|
|
printComment(w, p.Comment, " ")
|
2020-04-15 19:59:02 +02:00
|
|
|
fmt.Fprintf(w, " public static %s %s { get; set; } = %s;\n", propertyType, propertyName, initializer)
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit any nested types.
|
|
|
|
if len(mod.types) > 0 {
|
|
|
|
fmt.Fprintf(w, " public static class Types\n")
|
|
|
|
fmt.Fprintf(w, " {\n")
|
|
|
|
|
|
|
|
for _, typ := range mod.types {
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
// Open the class.
|
|
|
|
fmt.Fprintf(w, " public class %s\n", tokenToName(typ.Token))
|
|
|
|
fmt.Fprintf(w, " {\n")
|
|
|
|
|
|
|
|
// Generate each output field.
|
|
|
|
for _, prop := range typ.Properties {
|
|
|
|
name := mod.propertyName(prop)
|
|
|
|
typ := mod.typeString(prop.Type, "Types", false, false, false /*wrapInput*/, false, !prop.IsRequired)
|
|
|
|
|
|
|
|
initializer := ""
|
2020-04-14 10:30:25 +02:00
|
|
|
if !prop.IsRequired && !isValueType(prop.Type) && !isImmutableArrayType(prop.Type, false) {
|
2020-01-21 23:45:48 +01:00
|
|
|
initializer = " = null!;"
|
|
|
|
}
|
|
|
|
|
|
|
|
printComment(w, prop.Comment, " ")
|
|
|
|
fmt.Fprintf(w, " public %s %s { get; set; }%s\n", typ, name, initializer)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close the class.
|
|
|
|
fmt.Fprintf(w, " }\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(w, " }\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close the config class and namespace.
|
|
|
|
fmt.Fprintf(w, " }\n")
|
|
|
|
|
|
|
|
// Close the namespace.
|
|
|
|
fmt.Fprintf(w, "}\n")
|
|
|
|
|
|
|
|
return w.String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type fs map[string][]byte
|
|
|
|
|
|
|
|
func (fs fs) add(path string, contents []byte) {
|
|
|
|
_, has := fs[path]
|
|
|
|
contract.Assertf(!has, "duplicate file: %s", path)
|
|
|
|
fs[path] = contents
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) genUtilities() (string, error) {
|
|
|
|
// Strip any 'v' off of the version.
|
|
|
|
w := &bytes.Buffer{}
|
|
|
|
err := csharpUtilitiesTemplate.Execute(w, csharpUtilitiesTemplateContext{
|
2020-12-09 23:28:58 +01:00
|
|
|
Name: namespaceName(mod.namespaces, mod.pkg.Name),
|
2020-01-21 23:45:48 +01:00
|
|
|
Namespace: mod.namespaceName,
|
|
|
|
ClassName: "Utilities",
|
|
|
|
Tool: mod.tool,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return w.String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) gen(fs fs) error {
|
|
|
|
nsComponents := strings.Split(mod.namespaceName, ".")
|
|
|
|
if len(nsComponents) > 0 {
|
|
|
|
// Trim off "Pulumi.Pkg"
|
|
|
|
nsComponents = nsComponents[2:]
|
|
|
|
}
|
|
|
|
|
|
|
|
dir := path.Join(nsComponents...)
|
|
|
|
if mod.mod == "config" {
|
|
|
|
dir = "Config"
|
|
|
|
}
|
|
|
|
|
|
|
|
var files []string
|
|
|
|
for p := range fs {
|
|
|
|
d := path.Dir(p)
|
|
|
|
if d == "." {
|
|
|
|
d = ""
|
|
|
|
}
|
|
|
|
if d == dir {
|
|
|
|
files = append(files, p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addFile := func(name, contents string) {
|
|
|
|
p := path.Join(dir, name)
|
|
|
|
files = append(files, p)
|
|
|
|
fs.add(p, []byte(contents))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that the target module directory contains a README.md file.
|
|
|
|
readme := mod.pkg.Description
|
|
|
|
if readme != "" && readme[len(readme)-1] != '\n' {
|
|
|
|
readme += "\n"
|
|
|
|
}
|
|
|
|
fs.add(filepath.Join(dir, "README.md"), []byte(readme))
|
|
|
|
|
|
|
|
// Utilities, config
|
|
|
|
switch mod.mod {
|
|
|
|
case "":
|
|
|
|
utilities, err := mod.genUtilities()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fs.add("Utilities.cs", []byte(utilities))
|
|
|
|
case "config":
|
|
|
|
if len(mod.pkg.Config) > 0 {
|
|
|
|
config, err := mod.genConfig(mod.pkg.Config)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
addFile("Config.cs", config)
|
2020-04-14 10:30:25 +02:00
|
|
|
return nil
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resources
|
|
|
|
for _, r := range mod.resources {
|
2020-10-13 20:33:22 +02:00
|
|
|
imports := map[string]codegen.StringSet{}
|
|
|
|
mod.getImports(r, imports)
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
buffer := &bytes.Buffer{}
|
2021-01-15 19:53:32 +01:00
|
|
|
var additionalImports []string
|
2020-10-13 20:33:22 +02:00
|
|
|
for _, i := range imports {
|
2021-01-15 19:53:32 +01:00
|
|
|
additionalImports = append(additionalImports, i.SortedValues()...)
|
2020-10-13 20:33:22 +02:00
|
|
|
}
|
2021-01-15 19:53:32 +01:00
|
|
|
sort.Strings(additionalImports)
|
|
|
|
importStrings := pulumiImports
|
|
|
|
importStrings = append(importStrings, additionalImports...)
|
2020-10-13 20:33:22 +02:00
|
|
|
mod.genHeader(buffer, importStrings)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
if err := mod.genResource(buffer, r); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
addFile(resourceName(r)+".cs", buffer.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Functions
|
|
|
|
for _, f := range mod.functions {
|
2020-10-13 20:33:22 +02:00
|
|
|
imports := map[string]codegen.StringSet{}
|
|
|
|
mod.getImports(f, imports)
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
buffer := &bytes.Buffer{}
|
2020-10-13 20:33:22 +02:00
|
|
|
importStrings := pulumiImports
|
|
|
|
for _, i := range imports {
|
|
|
|
importStrings = append(importStrings, i.SortedValues()...)
|
|
|
|
}
|
|
|
|
mod.genHeader(buffer, importStrings)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
if err := mod.genFunction(buffer, f); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
addFile(tokenToName(f.Token)+".cs", buffer.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Nested types
|
|
|
|
for _, t := range mod.types {
|
|
|
|
if mod.details(t).inputType {
|
|
|
|
buffer := &bytes.Buffer{}
|
2020-10-13 20:33:22 +02:00
|
|
|
mod.genHeader(buffer, pulumiImports)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-05-19 11:41:06 +02:00
|
|
|
fmt.Fprintf(buffer, "namespace %s\n", mod.tokenToNamespace(t.Token, "Inputs"))
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(buffer, "{\n")
|
|
|
|
if err := mod.genType(buffer, t, "Inputs", true, false, 1); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fmt.Fprintf(buffer, "}\n")
|
|
|
|
|
|
|
|
addFile(path.Join("Inputs", tokenToName(t.Token)+"Args.cs"), buffer.String())
|
|
|
|
}
|
|
|
|
if mod.details(t).stateType {
|
|
|
|
buffer := &bytes.Buffer{}
|
2020-10-13 20:33:22 +02:00
|
|
|
mod.genHeader(buffer, pulumiImports)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-05-19 11:41:06 +02:00
|
|
|
fmt.Fprintf(buffer, "namespace %s\n", mod.tokenToNamespace(t.Token, "Inputs"))
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(buffer, "{\n")
|
|
|
|
if err := mod.genType(buffer, t, "Inputs", true, true, 1); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fmt.Fprintf(buffer, "}\n")
|
|
|
|
|
|
|
|
addFile(path.Join("Inputs", tokenToName(t.Token)+"GetArgs.cs"), buffer.String())
|
|
|
|
}
|
|
|
|
if mod.details(t).outputType {
|
|
|
|
buffer := &bytes.Buffer{}
|
2020-10-13 20:33:22 +02:00
|
|
|
mod.genHeader(buffer, pulumiImports)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-05-19 11:41:06 +02:00
|
|
|
fmt.Fprintf(buffer, "namespace %s\n", mod.tokenToNamespace(t.Token, "Outputs"))
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(buffer, "{\n")
|
|
|
|
if err := mod.genType(buffer, t, "Outputs", false, false, 1); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fmt.Fprintf(buffer, "}\n")
|
|
|
|
|
|
|
|
suffix := ""
|
|
|
|
if mod.details(t).functionType {
|
|
|
|
suffix = "Result"
|
|
|
|
}
|
|
|
|
|
|
|
|
addFile(path.Join("Outputs", tokenToName(t.Token)+suffix+".cs"), buffer.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-06 18:01:03 +01:00
|
|
|
// Enums
|
|
|
|
if len(mod.enums) > 0 {
|
|
|
|
buffer := &bytes.Buffer{}
|
|
|
|
mod.genHeader(buffer, []string{"System", "System.ComponentModel", "Pulumi"})
|
|
|
|
|
|
|
|
if err := mod.genEnums(buffer, mod.enums); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
addFile("Enums.cs", buffer.String())
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// genPackageMetadata generates all the non-code metadata required by a Pulumi package.
|
|
|
|
func genPackageMetadata(pkg *schema.Package, assemblyName string, packageReferences map[string]string, files fs) error {
|
|
|
|
projectFile, err := genProjectFile(pkg, assemblyName, packageReferences)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
logo, err := getLogo(pkg)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
files.add(assemblyName+".csproj", projectFile)
|
|
|
|
files.add("logo.png", logo)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// genProjectFile emits a C# project file into the configured output directory.
|
|
|
|
func genProjectFile(pkg *schema.Package, assemblyName string, packageReferences map[string]string) ([]byte, error) {
|
|
|
|
w := &bytes.Buffer{}
|
|
|
|
err := csharpProjectFileTemplate.Execute(w, csharpProjectFileTemplateContext{
|
|
|
|
XMLDoc: fmt.Sprintf(`.\%s.xml`, assemblyName),
|
|
|
|
Package: pkg,
|
|
|
|
PackageReferences: packageReferences,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return w.Bytes(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// emitLogo downloads an image and saves it as logo.png into the configured output directory.
|
|
|
|
func getLogo(pkg *schema.Package) ([]byte, error) {
|
|
|
|
url := pkg.LogoURL
|
|
|
|
if url == "" {
|
|
|
|
// Default to a generic Pulumi logo from the parent repository.
|
|
|
|
url = "https://raw.githubusercontent.com/pulumi/pulumi/394c91d7f6ab7a4096f4454827690a460f665433/sdk/dotnet/pulumi_logo_64x64.png"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the data.
|
|
|
|
// nolint: gosec
|
|
|
|
resp, err := http.Get(url)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer contract.IgnoreClose(resp.Body)
|
|
|
|
|
|
|
|
return ioutil.ReadAll(resp.Body)
|
|
|
|
}
|
|
|
|
|
2020-04-21 01:36:05 +02:00
|
|
|
func computePropertyNames(props []*schema.Property, names map[*schema.Property]string) {
|
2020-01-21 23:45:48 +01:00
|
|
|
for _, p := range props {
|
2020-04-21 01:36:05 +02:00
|
|
|
if info, ok := p.Language["csharp"].(CSharpPropertyInfo); ok && info.Name != "" {
|
|
|
|
names[p] = info.Name
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-01 00:19:21 +02:00
|
|
|
// LanguageResource is derived from the schema and can be used by downstream codegen.
|
|
|
|
type LanguageResource struct {
|
|
|
|
*schema.Resource
|
|
|
|
|
|
|
|
Name string // The resource name (e.g. Deployment)
|
|
|
|
Package string // The package name (e.g. Apps.V1)
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-11-20 21:09:34 +01:00
|
|
|
func generateModuleContextMap(tool string, pkg *schema.Package) (map[string]*modContext, *CSharpPackageInfo, error) {
|
|
|
|
// Decode .NET-specific info for each package as we discover them.
|
|
|
|
infos := map[*schema.Package]*CSharpPackageInfo{}
|
|
|
|
var getPackageInfo = func(p *schema.Package) *CSharpPackageInfo {
|
|
|
|
info, ok := infos[p]
|
|
|
|
if !ok {
|
|
|
|
if err := p.ImportLanguages(map[string]schema.Language{"csharp": Importer}); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
csharpInfo, _ := pkg.Language["csharp"].(CSharpPackageInfo)
|
|
|
|
info = &csharpInfo
|
|
|
|
infos[p] = info
|
|
|
|
}
|
|
|
|
return info
|
|
|
|
}
|
|
|
|
infos[pkg] = getPackageInfo(pkg)
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
propertyNames := map[*schema.Property]string{}
|
2020-05-01 12:45:47 +02:00
|
|
|
computePropertyNames(pkg.Config, propertyNames)
|
|
|
|
computePropertyNames(pkg.Provider.InputProperties, propertyNames)
|
2020-01-21 23:45:48 +01:00
|
|
|
for _, r := range pkg.Resources {
|
2020-04-21 01:36:05 +02:00
|
|
|
computePropertyNames(r.Properties, propertyNames)
|
|
|
|
computePropertyNames(r.InputProperties, propertyNames)
|
2020-01-21 23:45:48 +01:00
|
|
|
if r.StateInputs != nil {
|
2020-04-21 01:36:05 +02:00
|
|
|
computePropertyNames(r.StateInputs.Properties, propertyNames)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, f := range pkg.Functions {
|
|
|
|
if f.Inputs != nil {
|
2020-04-21 01:36:05 +02:00
|
|
|
computePropertyNames(f.Inputs.Properties, propertyNames)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
if f.Outputs != nil {
|
2020-04-21 01:36:05 +02:00
|
|
|
computePropertyNames(f.Outputs.Properties, propertyNames)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, t := range pkg.Types {
|
|
|
|
if obj, ok := t.(*schema.ObjectType); ok {
|
2020-04-21 01:36:05 +02:00
|
|
|
computePropertyNames(obj.Properties, propertyNames)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// group resources, types, and functions into Go packages
|
|
|
|
modules := map[string]*modContext{}
|
|
|
|
details := map[*schema.ObjectType]*typeDetails{}
|
|
|
|
|
2020-11-20 21:09:34 +01:00
|
|
|
var getMod func(modName string, p *schema.Package) *modContext
|
|
|
|
getMod = func(modName string, p *schema.Package) *modContext {
|
2020-01-21 23:45:48 +01:00
|
|
|
mod, ok := modules[modName]
|
|
|
|
if !ok {
|
2020-11-20 21:09:34 +01:00
|
|
|
info := getPackageInfo(p)
|
|
|
|
ns := "Pulumi." + namespaceName(info.Namespaces, pkg.Name)
|
2020-01-21 23:45:48 +01:00
|
|
|
if modName != "" {
|
|
|
|
ns += "." + namespaceName(info.Namespaces, modName)
|
|
|
|
}
|
|
|
|
mod = &modContext{
|
2020-11-20 21:09:34 +01:00
|
|
|
pkg: p,
|
2020-05-19 11:41:06 +02:00
|
|
|
mod: modName,
|
|
|
|
tool: tool,
|
|
|
|
namespaceName: ns,
|
|
|
|
namespaces: info.Namespaces,
|
|
|
|
typeDetails: details,
|
|
|
|
propertyNames: propertyNames,
|
|
|
|
compatibility: info.Compatibility,
|
|
|
|
dictionaryConstructors: info.DictionaryConstructors,
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if modName != "" {
|
|
|
|
parentName := path.Dir(modName)
|
2020-06-03 03:15:21 +02:00
|
|
|
if parentName == "." {
|
|
|
|
parentName = ""
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
2020-11-20 21:09:34 +01:00
|
|
|
parent := getMod(parentName, p)
|
2020-01-21 23:45:48 +01:00
|
|
|
parent.children = append(parent.children, mod)
|
|
|
|
}
|
|
|
|
|
2021-01-26 20:02:34 +01:00
|
|
|
// Save the module only if it's for the current package.
|
|
|
|
// This way, modules for external packages are not saved.
|
|
|
|
if p == pkg {
|
|
|
|
modules[modName] = mod
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
return mod
|
|
|
|
}
|
|
|
|
|
2020-11-20 21:09:34 +01:00
|
|
|
getModFromToken := func(token string, p *schema.Package) *modContext {
|
|
|
|
return getMod(p.TokenToModule(token), p)
|
2020-06-03 03:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// Create the config module if necessary.
|
|
|
|
if len(pkg.Config) > 0 {
|
2020-11-20 21:09:34 +01:00
|
|
|
cfg := getMod("config", pkg)
|
|
|
|
cfg.namespaceName = "Pulumi." + namespaceName(infos[pkg].Namespaces, pkg.Name)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, v := range pkg.Config {
|
2020-11-20 21:09:34 +01:00
|
|
|
visitObjectTypes(v.Type, func(t *schema.ObjectType) { getModFromToken(t.Token, pkg).details(t).outputType = true })
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Find input and output types referenced by resources.
|
|
|
|
scanResource := func(r *schema.Resource) {
|
2020-11-20 21:09:34 +01:00
|
|
|
mod := getModFromToken(r.Token, pkg)
|
2020-01-21 23:45:48 +01:00
|
|
|
mod.resources = append(mod.resources, r)
|
|
|
|
for _, p := range r.Properties {
|
2020-06-03 03:15:21 +02:00
|
|
|
visitObjectTypes(p.Type, func(t *schema.ObjectType) {
|
2020-11-20 21:09:34 +01:00
|
|
|
getModFromToken(t.Token, t.Package).details(t).outputType = true
|
2020-06-03 03:15:21 +02:00
|
|
|
})
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
for _, p := range r.InputProperties {
|
|
|
|
visitObjectTypes(p.Type, func(t *schema.ObjectType) {
|
|
|
|
if r.IsProvider {
|
2020-11-20 21:09:34 +01:00
|
|
|
getModFromToken(t.Token, t.Package).details(t).outputType = true
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
2020-11-20 21:09:34 +01:00
|
|
|
getModFromToken(t.Token, t.Package).details(t).inputType = true
|
2020-01-21 23:45:48 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
if r.StateInputs != nil {
|
|
|
|
visitObjectTypes(r.StateInputs, func(t *schema.ObjectType) {
|
2020-11-20 21:09:34 +01:00
|
|
|
getModFromToken(t.Token, t.Package).details(t).inputType = true
|
|
|
|
getModFromToken(t.Token, t.Package).details(t).stateType = true
|
2020-01-21 23:45:48 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
scanResource(pkg.Provider)
|
|
|
|
for _, r := range pkg.Resources {
|
|
|
|
scanResource(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find input and output types referenced by functions.
|
|
|
|
for _, f := range pkg.Functions {
|
2020-11-20 21:09:34 +01:00
|
|
|
mod := getModFromToken(f.Token, pkg)
|
2020-01-21 23:45:48 +01:00
|
|
|
mod.functions = append(mod.functions, f)
|
|
|
|
if f.Inputs != nil {
|
|
|
|
visitObjectTypes(f.Inputs, func(t *schema.ObjectType) {
|
2020-11-20 21:09:34 +01:00
|
|
|
details := getModFromToken(t.Token, t.Package).details(t)
|
2020-09-15 09:33:41 +02:00
|
|
|
if !details.inputType {
|
|
|
|
details.inputType = true
|
|
|
|
details.functionType = true
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
if f.Outputs != nil {
|
|
|
|
visitObjectTypes(f.Outputs, func(t *schema.ObjectType) {
|
2020-11-20 21:09:34 +01:00
|
|
|
details := getModFromToken(t.Token, t.Package).details(t)
|
2020-09-15 09:33:41 +02:00
|
|
|
if !details.outputType {
|
|
|
|
details.outputType = true
|
|
|
|
details.functionType = true
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find nested types.
|
|
|
|
for _, t := range pkg.Types {
|
2020-11-06 18:01:03 +01:00
|
|
|
switch typ := t.(type) {
|
|
|
|
case *schema.ObjectType:
|
2020-11-20 21:09:34 +01:00
|
|
|
mod := getModFromToken(typ.Token, pkg)
|
2020-11-06 18:01:03 +01:00
|
|
|
mod.types = append(mod.types, typ)
|
|
|
|
case *schema.EnumType:
|
2020-11-20 21:09:34 +01:00
|
|
|
mod := getModFromToken(typ.Token, pkg)
|
2020-11-06 18:01:03 +01:00
|
|
|
mod.enums = append(mod.enums, typ)
|
|
|
|
default:
|
|
|
|
continue
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-20 21:09:34 +01:00
|
|
|
return modules, infos[pkg], nil
|
2020-07-01 00:19:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2020-11-20 21:09:34 +01:00
|
|
|
modules, info, err := generateModuleContextMap(tool, pkg)
|
2020-07-01 00:19:21 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
resources := map[string]LanguageResource{}
|
|
|
|
for modName, mod := range modules {
|
|
|
|
if modName == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, r := range mod.resources {
|
|
|
|
lr := LanguageResource{
|
|
|
|
Resource: r,
|
|
|
|
Package: namespaceName(info.Namespaces, modName),
|
|
|
|
Name: tokenToName(r.Token),
|
|
|
|
}
|
|
|
|
resources[r.Token] = lr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return resources, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func GeneratePackage(tool string, pkg *schema.Package, extraFiles map[string][]byte) (map[string][]byte, error) {
|
2020-11-20 21:09:34 +01:00
|
|
|
modules, info, err := generateModuleContextMap(tool, pkg)
|
2020-07-01 00:19:21 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
assemblyName := "Pulumi." + namespaceName(info.Namespaces, pkg.Name)
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// Generate each module.
|
|
|
|
files := fs{}
|
|
|
|
for p, f := range extraFiles {
|
|
|
|
files.add(p, f)
|
|
|
|
|
|
|
|
}
|
|
|
|
for _, mod := range modules {
|
|
|
|
if err := mod.gen(files); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-01 00:19:21 +02:00
|
|
|
// Finally emit the package metadata.
|
2020-01-21 23:45:48 +01:00
|
|
|
if err := genPackageMetadata(pkg, assemblyName, info.PackageReferences, files); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return files, nil
|
|
|
|
}
|