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 python
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"reflect"
|
|
|
|
"regexp"
|
|
|
|
"sort"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"unicode"
|
|
|
|
|
|
|
|
"github.com/blang/semver"
|
|
|
|
"github.com/pkg/errors"
|
2021-05-06 20:18:14 +02:00
|
|
|
|
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/resource/plugin"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
2020-01-21 23:45:48 +01:00
|
|
|
)
|
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
type typeDetails struct {
|
2021-04-20 01:40:39 +02:00
|
|
|
outputType bool
|
|
|
|
inputType bool
|
|
|
|
argsType bool
|
|
|
|
resourceOutputType bool
|
|
|
|
plainType bool
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
|
2021-03-01 18:54:25 +01:00
|
|
|
type imports codegen.StringSet
|
2020-08-19 10:16:47 +02:00
|
|
|
|
2021-02-13 00:54:19 +01:00
|
|
|
func (imports imports) addType(mod *modContext, t *schema.ObjectType, input bool) {
|
|
|
|
imports.addTypeIf(mod, t, input, nil /*predicate*/)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
|
2021-02-13 00:54:19 +01:00
|
|
|
func (imports imports) addTypeIf(mod *modContext, t *schema.ObjectType, input bool, predicate func(imp string) bool) {
|
|
|
|
if imp := mod.importObjectType(t, input); imp != "" && (predicate == nil || predicate(imp)) {
|
2021-03-01 18:54:25 +01:00
|
|
|
codegen.StringSet(imports).Add(imp)
|
2020-09-23 21:39:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-25 06:43:32 +01:00
|
|
|
func (imports imports) addEnum(mod *modContext, tok string) {
|
|
|
|
if imp := mod.importEnumFromToken(tok); imp != "" {
|
2021-03-01 18:54:25 +01:00
|
|
|
codegen.StringSet(imports).Add(imp)
|
2020-11-25 06:43:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-13 00:54:19 +01:00
|
|
|
func (imports imports) addResource(mod *modContext, r *schema.ResourceType) {
|
|
|
|
if imp := mod.importResourceType(r); imp != "" {
|
2021-03-01 18:54:25 +01:00
|
|
|
codegen.StringSet(imports).Add(imp)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (imports imports) strings() []string {
|
|
|
|
result := make([]string, 0, len(imports))
|
|
|
|
for imp := range imports {
|
|
|
|
result = append(result, imp)
|
|
|
|
}
|
|
|
|
sort.Strings(result)
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
func title(s string) string {
|
|
|
|
if s == "" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
runes := []rune(s)
|
|
|
|
return string(append([]rune{unicode.ToUpper(runes[0])}, runes[1:]...))
|
|
|
|
}
|
|
|
|
|
|
|
|
type modContext struct {
|
|
|
|
pkg *schema.Package
|
|
|
|
mod string
|
2021-05-06 20:18:14 +02:00
|
|
|
pyPkgName string
|
2020-08-19 10:16:47 +02:00
|
|
|
types []*schema.ObjectType
|
2020-11-25 06:43:32 +01:00
|
|
|
enums []*schema.EnumType
|
2020-01-21 23:45:48 +01:00
|
|
|
resources []*schema.Resource
|
|
|
|
functions []*schema.Function
|
2020-08-19 10:16:47 +02:00
|
|
|
typeDetails map[*schema.ObjectType]*typeDetails
|
2020-01-21 23:45:48 +01:00
|
|
|
children []*modContext
|
2021-04-29 22:08:22 +02:00
|
|
|
parent *modContext
|
2020-01-21 23:45:48 +01:00
|
|
|
snakeCaseToCamelCase map[string]string
|
|
|
|
camelCaseToSnakeCase map[string]string
|
|
|
|
tool string
|
2020-06-17 04:27:06 +02:00
|
|
|
extraSourceFiles []string
|
2020-08-20 20:07:09 +02:00
|
|
|
isConfig bool
|
2020-06-09 23:33:02 +02:00
|
|
|
|
|
|
|
// Name overrides set in PackageInfo
|
|
|
|
modNameOverrides map[string]string // Optional overrides for Pulumi module names
|
2020-06-10 22:55:10 +02:00
|
|
|
compatibility string // Toggle compatibility mode for a specified target.
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2021-04-29 22:08:22 +02:00
|
|
|
func (mod *modContext) isTopLevel() bool {
|
|
|
|
return mod.parent == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) walkSelfWithDescendants() []*modContext {
|
|
|
|
var found []*modContext
|
|
|
|
found = append(found, mod)
|
|
|
|
for _, childMod := range mod.children {
|
|
|
|
found = append(found, childMod.walkSelfWithDescendants()...)
|
|
|
|
}
|
|
|
|
return found
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) addChild(child *modContext) {
|
|
|
|
mod.children = append(mod.children, child)
|
|
|
|
child.parent = mod
|
|
|
|
}
|
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
func (mod *modContext) details(t *schema.ObjectType) *typeDetails {
|
|
|
|
details, ok := mod.typeDetails[t]
|
|
|
|
if !ok {
|
|
|
|
details = &typeDetails{}
|
|
|
|
if mod.typeDetails == nil {
|
|
|
|
mod.typeDetails = map[*schema.ObjectType]*typeDetails{}
|
|
|
|
}
|
|
|
|
mod.typeDetails[t] = details
|
|
|
|
}
|
|
|
|
return details
|
|
|
|
}
|
|
|
|
|
2021-04-20 01:40:39 +02:00
|
|
|
func (mod *modContext) modNameAndName(pkg *schema.Package, t schema.Type, input, args bool) (modName string, name string) {
|
2021-02-13 00:54:19 +01:00
|
|
|
var info PackageInfo
|
|
|
|
contract.AssertNoError(pkg.ImportLanguages(map[string]schema.Language{"python": Importer}))
|
|
|
|
if v, ok := pkg.Language["python"].(PackageInfo); ok {
|
|
|
|
info = v
|
|
|
|
}
|
2021-04-20 01:40:39 +02:00
|
|
|
|
|
|
|
var token string
|
|
|
|
switch t := t.(type) {
|
|
|
|
case *schema.EnumType:
|
|
|
|
token, name = t.Token, tokenToName(t.Token)
|
|
|
|
case *schema.ObjectType:
|
|
|
|
namingCtx := &modContext{
|
|
|
|
pkg: pkg,
|
|
|
|
modNameOverrides: info.ModuleNameOverrides,
|
|
|
|
compatibility: info.Compatibility,
|
|
|
|
}
|
|
|
|
token, name = t.Token, namingCtx.unqualifiedObjectTypeName(t, input, args)
|
|
|
|
case *schema.ResourceType:
|
|
|
|
token, name = t.Token, tokenToName(t.Token)
|
|
|
|
}
|
|
|
|
|
|
|
|
modName = tokenToModule(token, pkg, info.ModuleNameOverrides)
|
2021-02-13 00:54:19 +01:00
|
|
|
if modName == mod.mod {
|
|
|
|
modName = ""
|
|
|
|
}
|
|
|
|
if modName != "" {
|
|
|
|
modName = strings.ReplaceAll(modName, "/", ".") + "."
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-16 04:03:28 +02:00
|
|
|
func (mod *modContext) unqualifiedObjectTypeName(t *schema.ObjectType, input, args bool) string {
|
|
|
|
name := tokenToName(t.Token)
|
2021-02-13 00:54:19 +01:00
|
|
|
|
2021-04-16 20:30:25 +02:00
|
|
|
if mod.compatibility != tfbridge20 && mod.compatibility != kubernetes20 {
|
2021-04-16 04:03:28 +02:00
|
|
|
if args {
|
|
|
|
return name + "Args"
|
|
|
|
}
|
|
|
|
return name
|
2021-02-13 00:54:19 +01:00
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
|
|
|
|
switch {
|
|
|
|
case input:
|
2021-04-16 04:03:28 +02:00
|
|
|
return name + "Args"
|
2021-04-20 01:40:39 +02:00
|
|
|
case mod.details(t).plainType:
|
2021-04-16 04:03:28 +02:00
|
|
|
return name + "Result"
|
|
|
|
}
|
|
|
|
return name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) objectType(t *schema.ObjectType, input, args bool) string {
|
|
|
|
var prefix string
|
|
|
|
if !input {
|
|
|
|
prefix = "outputs."
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
|
2021-02-13 00:54:19 +01:00
|
|
|
// If it's an external type, reference it via fully qualified name.
|
|
|
|
if t.Package != mod.pkg {
|
2021-04-20 01:40:39 +02:00
|
|
|
modName, name := mod.modNameAndName(t.Package, t, input, args)
|
2021-04-16 04:03:28 +02:00
|
|
|
return fmt.Sprintf("'%s.%s%s%s'", pyPack(t.Package.Name), modName, prefix, name)
|
2021-02-13 00:54:19 +01:00
|
|
|
}
|
|
|
|
|
2021-04-20 01:40:39 +02:00
|
|
|
modName, name := mod.tokenToModule(t.Token), mod.unqualifiedObjectTypeName(t, input, args)
|
2020-08-19 10:16:47 +02:00
|
|
|
if modName == "" && modName != mod.mod {
|
|
|
|
rootModName := "_root_outputs."
|
|
|
|
if input {
|
|
|
|
rootModName = "_root_inputs."
|
|
|
|
}
|
2021-04-16 04:03:28 +02:00
|
|
|
return fmt.Sprintf("'%s%s'", rootModName, name)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if modName == mod.mod {
|
|
|
|
modName = ""
|
|
|
|
}
|
|
|
|
if modName != "" {
|
|
|
|
modName = "_" + strings.ReplaceAll(modName, "/", ".") + "."
|
|
|
|
}
|
|
|
|
|
2021-04-16 04:03:28 +02:00
|
|
|
return fmt.Sprintf("'%s%s%s'", modName, prefix, name)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
|
2020-11-25 06:43:32 +01:00
|
|
|
func (mod *modContext) tokenToEnum(tok string) string {
|
|
|
|
modName, name := mod.tokenToModule(tok), tokenToName(tok)
|
|
|
|
|
|
|
|
if modName == "" && modName != mod.mod {
|
|
|
|
return fmt.Sprintf("'_root_enums.%s'", name)
|
|
|
|
}
|
|
|
|
|
|
|
|
if modName == mod.mod {
|
|
|
|
modName = ""
|
|
|
|
}
|
|
|
|
if modName != "" {
|
|
|
|
modName = "_" + strings.ReplaceAll(modName, "/", ".") + "."
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("'%s%s'", modName, name)
|
|
|
|
}
|
|
|
|
|
2021-02-13 00:54:19 +01:00
|
|
|
func (mod *modContext) resourceType(r *schema.ResourceType) string {
|
|
|
|
if r.Resource == nil || r.Resource.Package == mod.pkg {
|
|
|
|
return mod.tokenToResource(r.Token)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is it a provider resource?
|
|
|
|
if strings.HasPrefix(r.Token, "pulumi:providers:") {
|
|
|
|
pkgName := strings.TrimPrefix(r.Token, "pulumi:providers:")
|
|
|
|
return fmt.Sprintf("pulumi_%s.Provider", pkgName)
|
|
|
|
}
|
|
|
|
|
|
|
|
pkg := r.Resource.Package
|
2021-04-20 01:40:39 +02:00
|
|
|
modName, name := mod.modNameAndName(pkg, r, false, false)
|
2021-02-13 00:54:19 +01:00
|
|
|
return fmt.Sprintf("%s.%s%s", pyPack(pkg.Name), modName, name)
|
|
|
|
}
|
|
|
|
|
2020-09-23 21:39:25 +02:00
|
|
|
func (mod *modContext) tokenToResource(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-11-23 21:28:00 +01:00
|
|
|
// Is it a provider resource?
|
|
|
|
if components[0] == "pulumi" && components[1] == "providers" {
|
|
|
|
return fmt.Sprintf("pulumi_%s.Provider", components[2])
|
|
|
|
}
|
|
|
|
|
2020-11-25 06:43:32 +01:00
|
|
|
modName, name := mod.tokenToModule(tok), tokenToName(tok)
|
2020-09-23 21:39:25 +02:00
|
|
|
|
|
|
|
if modName == mod.mod {
|
|
|
|
modName = ""
|
|
|
|
}
|
|
|
|
if modName != "" {
|
|
|
|
modName = "_" + strings.ReplaceAll(modName, "/", ".") + "."
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%s%s", modName, name)
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
func tokenToName(tok string) string {
|
2020-11-25 06:43:32 +01:00
|
|
|
// token := pkg : module : member
|
|
|
|
// module := path/to/module
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
components := strings.Split(tok, ":")
|
|
|
|
contract.Assertf(len(components) == 3, "malformed token %v", tok)
|
2020-11-25 06:43:32 +01:00
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
return title(components[2])
|
|
|
|
}
|
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
func tokenToModule(tok string, pkg *schema.Package, moduleNameOverrides map[string]string) string {
|
|
|
|
canonicalModName := pkg.TokenToModule(tok)
|
|
|
|
modName := PyName(strings.ToLower(canonicalModName))
|
|
|
|
if override, ok := moduleNameOverrides[canonicalModName]; ok {
|
|
|
|
modName = override
|
|
|
|
}
|
|
|
|
return modName
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) tokenToModule(tok string) string {
|
|
|
|
return tokenToModule(tok, mod.pkg, mod.modNameOverrides)
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
func printComment(w io.Writer, comment string, indent string) {
|
|
|
|
lines := strings.Split(comment, "\n")
|
|
|
|
for len(lines) > 0 && lines[len(lines)-1] == "" {
|
|
|
|
lines = lines[:len(lines)-1]
|
|
|
|
}
|
|
|
|
if len(lines) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-14 00:10:23 +02:00
|
|
|
// Known special characters that need escaping.
|
|
|
|
// TODO: Consider replacing the docstring with a raw string which may make sense if this
|
|
|
|
// list grows much further.
|
|
|
|
replacer := strings.NewReplacer(`"""`, `\"\"\"`, `\x`, `\\x`, `\N`, `\\N`)
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, "%s\"\"\"\n", indent)
|
|
|
|
for _, l := range lines {
|
|
|
|
if l == "" {
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
} else {
|
2020-06-30 11:19:45 +02:00
|
|
|
escaped := replacer.Replace(l)
|
2020-05-05 21:39:35 +02:00
|
|
|
fmt.Fprintf(w, "%s%s\n", indent, escaped)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "%s\"\"\"\n", indent)
|
|
|
|
}
|
|
|
|
|
2020-09-11 23:27:36 +02:00
|
|
|
func (mod *modContext) genHeader(w io.Writer, needsSDK bool, imports imports) {
|
2020-01-21 23:45:48 +01:00
|
|
|
// Set the encoding to UTF-8, in case the comments contain non-ASCII characters.
|
|
|
|
fmt.Fprintf(w, "# coding=utf-8\n")
|
|
|
|
|
|
|
|
// Emit a standard warning header ("do not edit", etc).
|
|
|
|
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\n")
|
|
|
|
|
|
|
|
// If needed, emit the standard Pulumi SDK import statement.
|
|
|
|
if needsSDK {
|
|
|
|
rel, err := filepath.Rel(mod.mod, "")
|
|
|
|
contract.Assert(err == nil)
|
|
|
|
relRoot := path.Dir(rel)
|
2020-06-11 18:10:53 +02:00
|
|
|
relImport := relPathToRelImport(relRoot)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
fmt.Fprintf(w, "import warnings\n")
|
|
|
|
fmt.Fprintf(w, "import pulumi\n")
|
|
|
|
fmt.Fprintf(w, "import pulumi.runtime\n")
|
2021-04-02 19:09:17 +02:00
|
|
|
fmt.Fprintf(w, "from typing import Any, Mapping, Optional, Sequence, Union, overload\n")
|
2021-04-07 21:35:19 +02:00
|
|
|
fmt.Fprintf(w, "from %s import _utilities\n", relImport)
|
2020-08-19 10:16:47 +02:00
|
|
|
for _, imp := range imports.strings() {
|
|
|
|
fmt.Fprintf(w, "%s\n", imp)
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-11 18:10:53 +02:00
|
|
|
func relPathToRelImport(relPath string) string {
|
|
|
|
// Convert relative path to relative import e.g. "../.." -> "..."
|
|
|
|
// https://realpython.com/absolute-vs-relative-python-imports/#relative-imports
|
|
|
|
relImport := "."
|
2020-06-12 00:31:39 +02:00
|
|
|
if relPath == "." {
|
|
|
|
return relImport
|
|
|
|
}
|
2020-06-11 18:10:53 +02:00
|
|
|
for _, component := range strings.Split(relPath, "/") {
|
|
|
|
if component == ".." {
|
|
|
|
relImport += "."
|
|
|
|
} else {
|
|
|
|
relImport += component
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return relImport
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
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) gen(fs fs) error {
|
2021-05-06 20:18:14 +02:00
|
|
|
dir := path.Join(mod.pyPkgName, mod.mod)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
var exports []string
|
|
|
|
for p := range fs {
|
|
|
|
d := path.Dir(p)
|
|
|
|
if d == "." {
|
|
|
|
d = ""
|
|
|
|
}
|
|
|
|
if d == dir {
|
2020-03-09 17:55:26 +01:00
|
|
|
exports = append(exports, strings.TrimSuffix(path.Base(p), ".py"))
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addFile := func(name, contents string) {
|
|
|
|
p := path.Join(dir, name)
|
|
|
|
exports = append(exports, name[:len(name)-len(".py")])
|
|
|
|
fs.add(p, []byte(contents))
|
|
|
|
}
|
|
|
|
|
2020-06-08 22:07:56 +02:00
|
|
|
// Utilities, config, readme
|
2020-01-21 23:45:48 +01:00
|
|
|
switch mod.mod {
|
|
|
|
case "":
|
|
|
|
buffer := &bytes.Buffer{}
|
2020-09-11 23:27:36 +02:00
|
|
|
mod.genHeader(buffer, false /*needsSDK*/, nil)
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(buffer, "%s", utilitiesFile)
|
2020-07-15 22:10:52 +02:00
|
|
|
fs.add(filepath.Join(dir, "_utilities.py"), buffer.Bytes())
|
2020-07-16 01:11:37 +02:00
|
|
|
fs.add(filepath.Join(dir, "py.typed"), []byte{})
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-06-08 22:07:56 +02:00
|
|
|
// Ensure that the top-level (provider) module directory contains a README.md file.
|
|
|
|
readme := mod.pkg.Language["python"].(PackageInfo).Readme
|
|
|
|
if readme == "" {
|
|
|
|
readme = mod.pkg.Description
|
|
|
|
if readme != "" && readme[len(readme)-1] != '\n' {
|
|
|
|
readme += "\n"
|
|
|
|
}
|
|
|
|
if mod.pkg.Attribution != "" {
|
|
|
|
if len(readme) != 0 {
|
|
|
|
readme += "\n"
|
|
|
|
}
|
|
|
|
readme += mod.pkg.Attribution
|
|
|
|
}
|
|
|
|
if readme != "" && readme[len(readme)-1] != '\n' {
|
|
|
|
readme += "\n"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fs.add(filepath.Join(dir, "README.md"), []byte(readme))
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
case "config":
|
|
|
|
if len(mod.pkg.Config) > 0 {
|
|
|
|
vars, err := mod.genConfig(mod.pkg.Config)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
addFile("vars.py", vars)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resources
|
|
|
|
for _, r := range mod.resources {
|
|
|
|
res, err := mod.genResource(r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
name := PyName(tokenToName(r.Token))
|
2020-06-23 22:02:22 +02:00
|
|
|
if mod.compatibility == kubernetes20 {
|
|
|
|
// To maintain backward compatibility for kubernetes, the file names
|
|
|
|
// need to be CamelCase instead of the standard snake_case.
|
|
|
|
name = tokenToName(r.Token)
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
if r.IsProvider {
|
2020-02-11 23:34:22 +01:00
|
|
|
name = "provider"
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
addFile(name+".py", res)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Functions
|
|
|
|
for _, f := range mod.functions {
|
|
|
|
fun, err := mod.genFunction(f)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
addFile(PyName(tokenToName(f.Token))+".py", fun)
|
|
|
|
}
|
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
// Nested types
|
|
|
|
if len(mod.types) > 0 {
|
|
|
|
if err := mod.genTypes(dir, fs); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-25 06:43:32 +01:00
|
|
|
// Enums
|
|
|
|
if len(mod.enums) > 0 {
|
|
|
|
buffer := &bytes.Buffer{}
|
|
|
|
if err := mod.genEnums(buffer, mod.enums); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
addFile("_enums.py", buffer.String())
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// Index
|
2020-08-19 10:16:47 +02:00
|
|
|
if !mod.isEmpty() {
|
|
|
|
fs.add(path.Join(dir, "__init__.py"), []byte(mod.genInit(exports)))
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
func (mod *modContext) hasTypes(input bool) bool {
|
|
|
|
for _, t := range mod.types {
|
|
|
|
if input && mod.details(t).inputType {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if !input && mod.details(t).outputType {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) isEmpty() bool {
|
2020-08-20 20:07:09 +02:00
|
|
|
if len(mod.extraSourceFiles) > 0 || len(mod.functions) > 0 || len(mod.resources) > 0 || len(mod.types) > 0 ||
|
|
|
|
mod.isConfig {
|
2020-08-19 10:16:47 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
for _, child := range mod.children {
|
|
|
|
if !child.isEmpty() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-06-24 20:41:03 +02:00
|
|
|
func (mod *modContext) submodulesExist() bool {
|
2020-07-09 03:34:59 +02:00
|
|
|
return len(mod.children) > 0
|
2020-06-24 20:41:03 +02:00
|
|
|
}
|
|
|
|
|
2021-04-29 22:08:22 +02:00
|
|
|
func (mod *modContext) unqualifiedImportName() string {
|
|
|
|
name := mod.mod
|
|
|
|
|
|
|
|
// Extract version suffix from child modules. Nested versions will have their own __init__.py file.
|
|
|
|
// Example: apps/v1beta1 -> v1beta1
|
|
|
|
parts := strings.SplitN(name, "/", 2)
|
|
|
|
if len(parts) == 2 {
|
|
|
|
name = parts[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
return PyName(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) fullyQualifiedImportName() string {
|
|
|
|
name := mod.unqualifiedImportName()
|
|
|
|
if mod.parent == nil && name == "" {
|
2021-05-06 20:18:14 +02:00
|
|
|
return mod.pyPkgName
|
2021-04-29 22:08:22 +02:00
|
|
|
}
|
|
|
|
if mod.parent == nil {
|
|
|
|
return fmt.Sprintf("%s.%s", pyPack(mod.pkg.Name), name)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%s.%s", mod.parent.fullyQualifiedImportName(), name)
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// genInit emits an __init__.py module, optionally re-exporting other members or submodules.
|
|
|
|
func (mod *modContext) genInit(exports []string) string {
|
|
|
|
w := &bytes.Buffer{}
|
2020-09-11 23:27:36 +02:00
|
|
|
mod.genHeader(w, false /*needsSDK*/, nil)
|
2021-04-29 22:08:22 +02:00
|
|
|
fmt.Fprintf(w, "%s\n", mod.genUtilitiesImport())
|
|
|
|
fmt.Fprintf(w, "import typing\n")
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-06-24 20:41:03 +02:00
|
|
|
// Import anything to export flatly that is a direct export rather than sub-module.
|
2020-01-21 23:45:48 +01:00
|
|
|
if len(exports) > 0 {
|
|
|
|
sort.Slice(exports, func(i, j int) bool {
|
|
|
|
return PyName(exports[i]) < PyName(exports[j])
|
|
|
|
})
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "# Export this package's modules as members:\n")
|
|
|
|
for _, exp := range exports {
|
2020-06-23 22:02:22 +02:00
|
|
|
name := PyName(exp)
|
|
|
|
if mod.compatibility == kubernetes20 {
|
|
|
|
// To maintain backward compatibility for kubernetes, the file names
|
|
|
|
// need to be CamelCase instead of the standard snake_case.
|
|
|
|
name = exp
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "from .%s import *\n", name)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
if mod.hasTypes(true /*input*/) {
|
|
|
|
fmt.Fprintf(w, "from ._inputs import *\n")
|
|
|
|
}
|
|
|
|
if mod.hasTypes(false /*input*/) {
|
|
|
|
fmt.Fprintf(w, "from . import outputs\n")
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-06-24 20:41:03 +02:00
|
|
|
// If there are subpackages, import them with importlib.
|
|
|
|
if mod.submodulesExist() {
|
2021-04-29 22:08:22 +02:00
|
|
|
|
|
|
|
children := make([]*modContext, len(mod.children))
|
|
|
|
copy(children, mod.children)
|
|
|
|
|
|
|
|
sort.Slice(children, func(i, j int) bool {
|
|
|
|
return PyName(children[i].mod) < PyName(children[j].mod)
|
2020-06-24 20:41:03 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "\n# Make subpackages available:\n")
|
2021-04-29 22:08:22 +02:00
|
|
|
fmt.Fprintf(w, "if typing.TYPE_CHECKING:\n")
|
|
|
|
|
|
|
|
for _, submod := range children {
|
|
|
|
if !submod.isEmpty() {
|
|
|
|
fmt.Fprintf(w, " import %s as %s\n",
|
|
|
|
submod.fullyQualifiedImportName(),
|
|
|
|
submod.unqualifiedImportName())
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
2021-04-29 22:08:22 +02:00
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
|
2021-04-29 22:08:22 +02:00
|
|
|
fmt.Fprintf(w, "else:\n")
|
|
|
|
|
|
|
|
for _, submod := range children {
|
|
|
|
if !submod.isEmpty() {
|
|
|
|
fmt.Fprintf(w, " %s = _utilities.lazy_import('%s')\n",
|
|
|
|
submod.unqualifiedImportName(),
|
|
|
|
submod.fullyQualifiedImportName())
|
2020-06-24 20:41:03 +02:00
|
|
|
}
|
|
|
|
}
|
2021-04-29 22:08:22 +02:00
|
|
|
|
|
|
|
fmt.Fprintf(w, "\n")
|
2020-06-24 20:41:03 +02:00
|
|
|
}
|
|
|
|
|
2020-11-24 21:43:23 +01:00
|
|
|
// If there are resources in this module, register the module with the runtime.
|
|
|
|
if len(mod.resources) != 0 {
|
2021-04-29 22:08:22 +02:00
|
|
|
err := genResourceMappings(mod, w)
|
|
|
|
contract.Assert(err == nil)
|
2020-11-24 21:43:23 +01:00
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
return w.String()
|
|
|
|
}
|
|
|
|
|
2020-11-25 06:43:32 +01:00
|
|
|
func (mod *modContext) getRelImportFromRoot() string {
|
|
|
|
rel, err := filepath.Rel(mod.mod, "")
|
|
|
|
contract.Assert(err == nil)
|
|
|
|
relRoot := path.Dir(rel)
|
|
|
|
return relPathToRelImport(relRoot)
|
|
|
|
}
|
|
|
|
|
2021-04-29 22:08:22 +02:00
|
|
|
func (mod *modContext) genUtilitiesImport() string {
|
2020-12-02 22:45:25 +01:00
|
|
|
rel, err := filepath.Rel(mod.mod, "")
|
|
|
|
contract.Assert(err == nil)
|
|
|
|
relRoot := path.Dir(rel)
|
|
|
|
relImport := relPathToRelImport(relRoot)
|
2021-04-29 22:08:22 +02:00
|
|
|
return fmt.Sprintf("from %s import _utilities", relImport)
|
2020-11-24 21:43:23 +01:00
|
|
|
}
|
|
|
|
|
2021-02-13 00:54:19 +01:00
|
|
|
func (mod *modContext) importObjectType(t *schema.ObjectType, input bool) string {
|
|
|
|
if t.Package != mod.pkg {
|
|
|
|
return fmt.Sprintf("import %s", pyPack(t.Package.Name))
|
|
|
|
}
|
|
|
|
|
|
|
|
tok := t.Token
|
2020-11-19 21:56:28 +01:00
|
|
|
parts := strings.Split(tok, ":")
|
|
|
|
contract.Assert(len(parts) == 3)
|
|
|
|
refPkgName := parts[0]
|
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
modName := mod.tokenToModule(tok)
|
|
|
|
if modName == mod.mod {
|
|
|
|
if input {
|
|
|
|
return "from ._inputs import *"
|
|
|
|
}
|
|
|
|
return "from . import outputs"
|
|
|
|
}
|
|
|
|
|
2020-11-25 06:43:32 +01:00
|
|
|
importPath := mod.getRelImportFromRoot()
|
2020-11-19 21:56:28 +01:00
|
|
|
if mod.pkg.Name != parts[0] {
|
|
|
|
importPath = fmt.Sprintf("pulumi_%s", refPkgName)
|
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
|
|
|
|
if modName == "" {
|
|
|
|
imp, as := "outputs", "_root_outputs"
|
|
|
|
if input {
|
|
|
|
imp, as = "_inputs", "_root_inputs"
|
|
|
|
}
|
2020-11-19 21:56:28 +01:00
|
|
|
return fmt.Sprintf("from %s import %s as %s", importPath, imp, as)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
components := strings.Split(modName, "/")
|
2020-11-19 21:56:28 +01:00
|
|
|
return fmt.Sprintf("from %s import %[2]s as _%[2]s", importPath, components[0])
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
|
2020-11-25 06:43:32 +01:00
|
|
|
func (mod *modContext) importEnumFromToken(tok string) string {
|
|
|
|
modName := mod.tokenToModule(tok)
|
|
|
|
if modName == mod.mod {
|
|
|
|
return "from ._enums import *"
|
|
|
|
}
|
|
|
|
|
|
|
|
importPath := mod.getRelImportFromRoot()
|
|
|
|
|
|
|
|
if modName == "" {
|
|
|
|
return fmt.Sprintf("from %s import _enums as _root_enums", importPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
components := strings.Split(modName, "/")
|
|
|
|
return fmt.Sprintf("from %s import %s", importPath, components[0])
|
|
|
|
}
|
|
|
|
|
2021-02-13 00:54:19 +01:00
|
|
|
func (mod *modContext) importResourceType(r *schema.ResourceType) string {
|
|
|
|
if r.Resource != nil && r.Resource.Package != mod.pkg {
|
|
|
|
return fmt.Sprintf("import %s", pyPack(r.Resource.Package.Name))
|
|
|
|
}
|
|
|
|
|
|
|
|
tok := r.Token
|
2020-11-19 21:56:28 +01:00
|
|
|
parts := strings.Split(tok, ":")
|
|
|
|
contract.Assert(len(parts) == 3)
|
2020-11-23 21:28:00 +01:00
|
|
|
|
|
|
|
// If it's a provider resource, import the top-level package.
|
|
|
|
if parts[0] == "pulumi" && parts[1] == "providers" {
|
|
|
|
return fmt.Sprintf("import pulumi_%s", parts[2])
|
|
|
|
}
|
|
|
|
|
2020-11-19 21:56:28 +01:00
|
|
|
refPkgName := parts[0]
|
|
|
|
|
2020-09-23 21:39:25 +02:00
|
|
|
modName := mod.tokenToResource(tok)
|
|
|
|
|
2020-11-25 06:43:32 +01:00
|
|
|
importPath := mod.getRelImportFromRoot()
|
2020-11-19 21:56:28 +01:00
|
|
|
if mod.pkg.Name != parts[0] {
|
|
|
|
importPath = fmt.Sprintf("pulumi_%s", refPkgName)
|
|
|
|
}
|
2020-09-23 21:39:25 +02:00
|
|
|
|
2021-02-13 00:54:19 +01:00
|
|
|
name := PyName(tokenToName(r.Token))
|
|
|
|
if mod.compatibility == kubernetes20 {
|
|
|
|
// To maintain backward compatibility for kubernetes, the file names
|
|
|
|
// need to be CamelCase instead of the standard snake_case.
|
|
|
|
name = tokenToName(r.Token)
|
|
|
|
}
|
|
|
|
if r.Resource != nil && r.Resource.IsProvider {
|
|
|
|
name = "provider"
|
|
|
|
}
|
|
|
|
|
2020-09-23 21:39:25 +02:00
|
|
|
components := strings.Split(modName, "/")
|
2021-02-13 00:54:19 +01:00
|
|
|
return fmt.Sprintf("from %s%s import %s", importPath, name, components[0])
|
2020-09-23 21:39:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// emitConfigVariables emits all config variables in the given module, returning the resulting file.
|
2020-01-21 23:45:48 +01:00
|
|
|
func (mod *modContext) genConfig(variables []*schema.Property) (string, error) {
|
|
|
|
w := &bytes.Buffer{}
|
2020-08-19 10:16:47 +02:00
|
|
|
|
2021-04-20 01:40:39 +02:00
|
|
|
imports := imports{}
|
|
|
|
mod.collectImports(variables, imports, false /*input*/)
|
2020-08-19 10:16:47 +02:00
|
|
|
|
2020-09-11 23:27:36 +02:00
|
|
|
mod.genHeader(w, true /*needsSDK*/, imports)
|
2020-08-19 10:16:47 +02:00
|
|
|
|
|
|
|
// Export only the symbols we want exported.
|
|
|
|
if len(variables) > 0 {
|
|
|
|
fmt.Fprintf(w, "__all__ = [\n")
|
|
|
|
for _, p := range variables {
|
|
|
|
fmt.Fprintf(w, " '%s',\n", PyName(p.Name))
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "]\n\n")
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// Create a config bag for the variables to pull from.
|
|
|
|
fmt.Fprintf(w, "__config__ = pulumi.Config('%s')\n", mod.pkg.Name)
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
// Emit an entry for all config variables.
|
|
|
|
for _, p := range variables {
|
|
|
|
configFetch := fmt.Sprintf("__config__.get('%s')", p.Name)
|
|
|
|
if p.DefaultValue != nil {
|
|
|
|
v, err := getDefaultValue(p.DefaultValue, p.Type)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
configFetch += " or " + v
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "%s = %s\n", PyName(p.Name), configFetch)
|
|
|
|
printComment(w, p.Comment, "")
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
return w.String(), nil
|
|
|
|
}
|
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
func (mod *modContext) genTypes(dir string, fs fs) error {
|
|
|
|
genTypes := func(file string, input bool) error {
|
|
|
|
w := &bytes.Buffer{}
|
|
|
|
|
2021-04-20 01:40:39 +02:00
|
|
|
imports := imports{}
|
2020-08-19 10:16:47 +02:00
|
|
|
for _, t := range mod.types {
|
|
|
|
if input && mod.details(t).inputType {
|
2021-04-20 01:40:39 +02:00
|
|
|
visitObjectTypes(t.Properties, func(t schema.Type, _ bool) {
|
|
|
|
switch t := t.(type) {
|
2020-09-23 21:39:25 +02:00
|
|
|
case *schema.ObjectType:
|
2021-04-20 01:40:39 +02:00
|
|
|
imports.addTypeIf(mod, t, true /*input*/, func(imp string) bool {
|
2020-09-23 21:39:25 +02:00
|
|
|
// No need to import `._inputs` inside _inputs.py.
|
|
|
|
return imp != "from ._inputs import *"
|
|
|
|
})
|
2020-11-25 06:43:32 +01:00
|
|
|
case *schema.EnumType:
|
2021-04-20 01:40:39 +02:00
|
|
|
imports.addEnum(mod, t.Token)
|
2020-09-23 21:39:25 +02:00
|
|
|
case *schema.ResourceType:
|
2021-04-20 01:40:39 +02:00
|
|
|
imports.addResource(mod, t)
|
2020-09-23 21:39:25 +02:00
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
if !input && mod.details(t).outputType {
|
2021-04-20 01:40:39 +02:00
|
|
|
mod.collectImports(t.Properties, imports, false /*input*/)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
}
|
2020-11-25 06:43:32 +01:00
|
|
|
for _, e := range mod.enums {
|
|
|
|
imports.addEnum(mod, e.Token)
|
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
|
2020-09-11 23:27:36 +02:00
|
|
|
mod.genHeader(w, true /*needsSDK*/, imports)
|
2020-08-19 10:16:47 +02:00
|
|
|
|
|
|
|
// Export only the symbols we want exported.
|
|
|
|
fmt.Fprintf(w, "__all__ = [\n")
|
|
|
|
for _, t := range mod.types {
|
2021-04-16 04:03:28 +02:00
|
|
|
if input && mod.details(t).inputType {
|
|
|
|
if mod.details(t).argsType {
|
|
|
|
fmt.Fprintf(w, " '%s',\n", mod.unqualifiedObjectTypeName(t, true, true))
|
|
|
|
}
|
2021-04-20 01:40:39 +02:00
|
|
|
if mod.details(t).plainType {
|
2021-04-16 04:03:28 +02:00
|
|
|
fmt.Fprintf(w, " '%s',\n", mod.unqualifiedObjectTypeName(t, true, false))
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
2021-04-16 04:03:28 +02:00
|
|
|
} else if !input && mod.details(t).outputType {
|
|
|
|
fmt.Fprintf(w, " '%s',\n", mod.unqualifiedObjectTypeName(t, false, false))
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "]\n\n")
|
|
|
|
|
|
|
|
var hasTypes bool
|
|
|
|
for _, t := range mod.types {
|
|
|
|
if input && mod.details(t).inputType {
|
2021-04-16 04:03:28 +02:00
|
|
|
if err := mod.genObjectType(w, t, true); err != nil {
|
2020-08-19 10:16:47 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
hasTypes = true
|
|
|
|
}
|
|
|
|
if !input && mod.details(t).outputType {
|
2021-04-16 04:03:28 +02:00
|
|
|
if err := mod.genObjectType(w, t, false); err != nil {
|
2020-08-19 10:16:47 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
hasTypes = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if hasTypes {
|
|
|
|
fs.add(path.Join(dir, file), w.Bytes())
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if err := genTypes("_inputs.py", true); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := genTypes("outputs.py", false); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func awaitableTypeNames(tok string) (baseName, awaitableName string) {
|
|
|
|
baseName = pyClassName(tokenToName(tok))
|
|
|
|
awaitableName = "Awaitable" + baseName
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
func (mod *modContext) genAwaitableType(w io.Writer, obj *schema.ObjectType) string {
|
2020-08-19 10:16:47 +02:00
|
|
|
baseName, awaitableName := awaitableTypeNames(obj.Token)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// Produce a class definition with optional """ comment.
|
2020-08-19 10:16:47 +02:00
|
|
|
fmt.Fprint(w, "@pulumi.output_type\n")
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, "class %s:\n", baseName)
|
|
|
|
printComment(w, obj.Comment, " ")
|
|
|
|
|
|
|
|
// Now generate an initializer with properties for all inputs.
|
|
|
|
fmt.Fprintf(w, " def __init__(__self__")
|
|
|
|
for _, prop := range obj.Properties {
|
|
|
|
fmt.Fprintf(w, ", %s=None", PyName(prop.Name))
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "):\n")
|
|
|
|
for _, prop := range obj.Properties {
|
|
|
|
// Check that required arguments are present. Also check that types are as expected.
|
|
|
|
pname := PyName(prop.Name)
|
2020-09-23 21:39:25 +02:00
|
|
|
ptype := mod.pyType(prop.Type)
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, " if %s and not isinstance(%s, %s):\n", pname, pname, ptype)
|
|
|
|
fmt.Fprintf(w, " raise TypeError(\"Expected argument '%s' to be a %s\")\n", pname, ptype)
|
|
|
|
|
2020-06-04 12:29:23 +02:00
|
|
|
if prop.DeprecationMessage != "" {
|
2020-06-17 19:14:11 +02:00
|
|
|
escaped := strings.ReplaceAll(prop.DeprecationMessage, `"`, `\"`)
|
2020-06-04 12:29:23 +02:00
|
|
|
fmt.Fprintf(w, " if %s is not None:\n", pname)
|
2020-11-09 15:28:28 +01:00
|
|
|
fmt.Fprintf(w, " warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n", escaped)
|
2021-02-26 06:22:04 +01:00
|
|
|
fmt.Fprintf(w, " pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n\n", pname, escaped)
|
2020-06-04 12:29:23 +02:00
|
|
|
}
|
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
// Now perform the assignment.
|
|
|
|
fmt.Fprintf(w, " pulumi.set(__self__, \"%[1]s\", %[1]s)\n", pname)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
fmt.Fprintf(w, "\n")
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
// Write out Python property getters for each property.
|
2020-08-28 17:55:02 +02:00
|
|
|
mod.genProperties(w, obj.Properties, false /*setters*/, func(prop *schema.Property) string {
|
2021-04-19 23:05:23 +02:00
|
|
|
return mod.typeString(prop.Type, false /*input*/, false /*wrapInput*/, false /*args*/, !prop.IsRequired,
|
2020-08-28 17:55:02 +02:00
|
|
|
false /*acceptMapping*/)
|
|
|
|
})
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// Produce an awaitable subclass.
|
2020-08-21 05:51:32 +02:00
|
|
|
fmt.Fprint(w, "\n")
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, "class %s(%s):\n", awaitableName, baseName)
|
|
|
|
|
|
|
|
// Emit __await__ and __iter__ in order to make this type awaitable.
|
|
|
|
//
|
|
|
|
// Note that we need __await__ to be an iterator, but we only want it to return one value. As such, we use
|
|
|
|
// `if False: yield` to construct this.
|
|
|
|
//
|
|
|
|
// We also need the result of __await__ to be a plain, non-awaitable value. We achieve this by returning a new
|
|
|
|
// instance of the base class.
|
|
|
|
fmt.Fprintf(w, " # pylint: disable=using-constant-test\n")
|
|
|
|
fmt.Fprintf(w, " def __await__(self):\n")
|
|
|
|
fmt.Fprintf(w, " if False:\n")
|
|
|
|
fmt.Fprintf(w, " yield self\n")
|
|
|
|
fmt.Fprintf(w, " return %s(\n", baseName)
|
|
|
|
for i, prop := range obj.Properties {
|
|
|
|
if i > 0 {
|
|
|
|
fmt.Fprintf(w, ",\n")
|
|
|
|
}
|
|
|
|
pname := PyName(prop.Name)
|
|
|
|
fmt.Fprintf(w, " %s=self.%s", pname, pname)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, ")\n")
|
|
|
|
|
|
|
|
return awaitableName
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) genResource(res *schema.Resource) (string, error) {
|
|
|
|
w := &bytes.Buffer{}
|
2020-08-19 10:16:47 +02:00
|
|
|
|
2021-04-20 01:40:39 +02:00
|
|
|
imports := imports{}
|
|
|
|
mod.collectImports(res.Properties, imports, false /*input*/)
|
|
|
|
mod.collectImports(res.InputProperties, imports, true /*input*/)
|
2020-08-19 10:16:47 +02:00
|
|
|
if res.StateInputs != nil {
|
2021-04-20 01:40:39 +02:00
|
|
|
mod.collectImports(res.StateInputs.Properties, imports, true /*input*/)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
|
2020-09-11 23:27:36 +02:00
|
|
|
mod.genHeader(w, true /*needsSDK*/, imports)
|
2020-08-19 10:16:47 +02:00
|
|
|
|
|
|
|
name := pyClassName(tokenToName(res.Token))
|
|
|
|
if res.IsProvider {
|
|
|
|
name = "Provider"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Export only the symbols we want exported.
|
2021-04-02 19:09:17 +02:00
|
|
|
fmt.Fprintf(w, "__all__ = ['%[1]sArgs', '%[1]s']\n\n", name)
|
|
|
|
|
|
|
|
// Produce an args class.
|
|
|
|
argsComment := fmt.Sprintf("The set of arguments for constructing a %s resource.", name)
|
2021-04-20 01:40:39 +02:00
|
|
|
err := mod.genType(w, fmt.Sprintf("%sArgs", name), argsComment, res.InputProperties, false, true, true, false)
|
2021-04-02 19:09:17 +02:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2021-04-07 21:35:19 +02:00
|
|
|
// Produce an unexported state class. It's currently only used internally inside the `get` method to opt-in to
|
|
|
|
// the type/name metadata based translation behavior.
|
|
|
|
// We can consider making use of it publicly in the future: removing the underscore prefix, exporting it from
|
|
|
|
// `__all__`, and adding a static `get` overload that accepts it as an argument.
|
|
|
|
hasStateInputs := !res.IsProvider && !res.IsComponent && res.StateInputs != nil &&
|
|
|
|
len(res.StateInputs.Properties) > 0
|
|
|
|
if hasStateInputs {
|
|
|
|
stateComment := fmt.Sprintf("Input properties used for looking up and filtering %s resources.", name)
|
2021-04-20 01:40:39 +02:00
|
|
|
err = mod.genType(w, fmt.Sprintf("_%sState", name), stateComment, res.StateInputs.Properties, false, true, true, false)
|
2021-04-07 21:35:19 +02:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-24 02:23:46 +02:00
|
|
|
var baseType string
|
|
|
|
switch {
|
|
|
|
case res.IsProvider:
|
2020-01-21 23:45:48 +01:00
|
|
|
baseType = "pulumi.ProviderResource"
|
2020-09-24 02:23:46 +02:00
|
|
|
case res.IsComponent:
|
|
|
|
baseType = "pulumi.ComponentResource"
|
|
|
|
default:
|
|
|
|
baseType = "pulumi.CustomResource"
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2020-07-01 21:50:26 +02:00
|
|
|
if !res.IsProvider && res.DeprecationMessage != "" && mod.compatibility != kubernetes20 {
|
2020-06-17 19:14:11 +02:00
|
|
|
escaped := strings.ReplaceAll(res.DeprecationMessage, `"`, `\"`)
|
2021-04-02 19:09:17 +02:00
|
|
|
fmt.Fprintf(w, "warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n\n\n", escaped)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Produce a class definition with optional """ comment.
|
2020-07-07 20:32:48 +02:00
|
|
|
fmt.Fprintf(w, "class %s(%s):\n", name, baseType)
|
2020-07-01 21:50:26 +02:00
|
|
|
if res.DeprecationMessage != "" && mod.compatibility != kubernetes20 {
|
2020-06-17 19:14:11 +02:00
|
|
|
escaped := strings.ReplaceAll(res.DeprecationMessage, `"`, `\"`)
|
2020-11-09 15:28:28 +01:00
|
|
|
fmt.Fprintf(w, " warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n\n", escaped)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2021-04-02 19:09:17 +02:00
|
|
|
// Determine if all inputs are optional.
|
|
|
|
allOptionalInputs := true
|
2020-01-21 23:45:48 +01:00
|
|
|
for _, prop := range res.InputProperties {
|
2021-04-02 19:09:17 +02:00
|
|
|
allOptionalInputs = allOptionalInputs && !prop.IsRequired
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2021-04-02 19:09:17 +02:00
|
|
|
// Emit __init__ overloads and implementation...
|
|
|
|
|
|
|
|
// Helper for generating an init method with inputs as function arguments.
|
|
|
|
emitInitMethodSignature := func(methodName string) {
|
|
|
|
fmt.Fprintf(w, " def %s(__self__,\n", methodName)
|
|
|
|
fmt.Fprintf(w, " resource_name: str,\n")
|
|
|
|
fmt.Fprintf(w, " opts: Optional[pulumi.ResourceOptions] = None")
|
|
|
|
|
|
|
|
// If there's an argument type, emit it.
|
|
|
|
for _, prop := range res.InputProperties {
|
|
|
|
wrapInput := !prop.IsPlain
|
2021-04-19 23:05:23 +02:00
|
|
|
ty := mod.typeString(prop.Type, true, wrapInput, wrapInput, true /*optional*/, true /*acceptMapping*/)
|
2021-04-02 19:09:17 +02:00
|
|
|
fmt.Fprintf(w, ",\n %s: %s = None", InitParamName(prop.Name), ty)
|
|
|
|
}
|
|
|
|
|
2021-04-14 11:21:16 +02:00
|
|
|
fmt.Fprintf(w, ",\n __props__=None):\n")
|
2021-04-02 19:09:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Emit an __init__ overload that accepts the resource's inputs as function arguments.
|
|
|
|
fmt.Fprintf(w, " @overload\n")
|
|
|
|
emitInitMethodSignature("__init__")
|
|
|
|
mod.genInitDocstring(w, res, name, false /*argsOverload*/)
|
|
|
|
fmt.Fprintf(w, " ...\n")
|
|
|
|
|
|
|
|
// Emit an __init__ overload that accepts the resource's inputs from the args class.
|
|
|
|
fmt.Fprintf(w, " @overload\n")
|
|
|
|
fmt.Fprintf(w, " def __init__(__self__,\n")
|
|
|
|
fmt.Fprintf(w, " resource_name: str,\n")
|
|
|
|
if allOptionalInputs {
|
|
|
|
fmt.Fprintf(w, " args: Optional[%sArgs] = None,\n", name)
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, " args: %sArgs,\n", name)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " opts: Optional[pulumi.ResourceOptions] = None):\n")
|
|
|
|
mod.genInitDocstring(w, res, name, true /*argsOverload*/)
|
|
|
|
fmt.Fprintf(w, " ...\n")
|
|
|
|
|
|
|
|
// Emit the actual implementation of __init__, which does the appropriate thing based on which
|
|
|
|
// overload was called.
|
|
|
|
fmt.Fprintf(w, " def __init__(__self__, resource_name: str, *args, **kwargs):\n")
|
|
|
|
fmt.Fprintf(w, " resource_args, opts = _utilities.get_resource_args_opts(%sArgs, pulumi.ResourceOptions, *args, **kwargs)\n", name)
|
|
|
|
fmt.Fprintf(w, " if resource_args is not None:\n")
|
|
|
|
fmt.Fprintf(w, " __self__._internal_init(resource_name, opts, **resource_args.__dict__)\n")
|
|
|
|
fmt.Fprintf(w, " else:\n")
|
|
|
|
fmt.Fprintf(w, " __self__._internal_init(resource_name, *args, **kwargs)\n")
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
// Emit the _internal_init helper method which provides the bulk of the __init__ implementation.
|
|
|
|
emitInitMethodSignature("_internal_init")
|
2020-07-01 21:50:26 +02:00
|
|
|
if res.DeprecationMessage != "" && mod.compatibility != kubernetes20 {
|
2021-02-26 06:22:04 +01:00
|
|
|
fmt.Fprintf(w, " pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n", name, res.DeprecationMessage)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " if opts is None:\n")
|
|
|
|
fmt.Fprintf(w, " opts = pulumi.ResourceOptions()\n")
|
|
|
|
fmt.Fprintf(w, " if not isinstance(opts, pulumi.ResourceOptions):\n")
|
|
|
|
fmt.Fprintf(w, " raise TypeError('Expected resource options to be a ResourceOptions instance')\n")
|
|
|
|
fmt.Fprintf(w, " if opts.version is None:\n")
|
2020-07-15 22:10:52 +02:00
|
|
|
fmt.Fprintf(w, " opts.version = _utilities.get_version()\n")
|
2020-09-30 18:16:25 +02:00
|
|
|
if res.IsComponent {
|
|
|
|
fmt.Fprintf(w, " if opts.id is not None:\n")
|
|
|
|
fmt.Fprintf(w, " raise ValueError('ComponentResource classes do not support opts.id')\n")
|
|
|
|
fmt.Fprintf(w, " else:\n")
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, " if opts.id is None:\n")
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, " if __props__ is not None:\n")
|
|
|
|
fmt.Fprintf(w, " raise TypeError(")
|
|
|
|
fmt.Fprintf(w, "'__props__ is only valid when passed in combination with a valid opts.id to get an existing resource')\n")
|
2021-04-07 21:35:19 +02:00
|
|
|
|
|
|
|
// We use an instance of the `<Resource>Args` class for `__props__` to opt-in to the type/name metadata based
|
|
|
|
// translation behavior. The instance is created using `__new__` to avoid any validation in the `__init__` method,
|
|
|
|
// values are set directly on its `__dict__`, including any additional output properties.
|
|
|
|
fmt.Fprintf(w, " __props__ = %[1]sArgs.__new__(%[1]sArgs)\n\n", name)
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, "")
|
|
|
|
|
2021-03-01 18:54:25 +01:00
|
|
|
ins := codegen.NewStringSet()
|
2020-01-21 23:45:48 +01:00
|
|
|
for _, prop := range res.InputProperties {
|
2020-08-19 17:34:06 +02:00
|
|
|
pname := InitParamName(prop.Name)
|
2020-06-22 19:58:13 +02:00
|
|
|
var arg interface{}
|
|
|
|
var err error
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// Fill in computed defaults for arguments.
|
|
|
|
if prop.DefaultValue != nil {
|
|
|
|
dv, err := getDefaultValue(prop.DefaultValue, prop.Type)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " if %s is None:\n", pname)
|
|
|
|
fmt.Fprintf(w, " %s = %s\n", pname, dv)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that required arguments are present.
|
|
|
|
if prop.IsRequired {
|
2020-11-24 21:43:23 +01:00
|
|
|
fmt.Fprintf(w, " if %s is None and not opts.urn:\n", pname)
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, " raise TypeError(\"Missing required property '%s'\")\n", pname)
|
|
|
|
}
|
|
|
|
|
2020-06-04 12:29:23 +02:00
|
|
|
// Check that the property isn't deprecated
|
|
|
|
if prop.DeprecationMessage != "" {
|
2020-06-17 19:14:11 +02:00
|
|
|
escaped := strings.ReplaceAll(prop.DeprecationMessage, `"`, `\"`)
|
2020-11-24 21:43:23 +01:00
|
|
|
fmt.Fprintf(w, " if %s is not None and not opts.urn:\n", pname)
|
2020-11-09 15:28:28 +01:00
|
|
|
fmt.Fprintf(w, " warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n", escaped)
|
2021-02-26 06:22:04 +01:00
|
|
|
fmt.Fprintf(w, " pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n", pname, escaped)
|
2020-06-04 12:29:23 +02:00
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// And add it to the dictionary.
|
2020-06-22 19:58:13 +02:00
|
|
|
arg = pname
|
|
|
|
|
|
|
|
if prop.ConstValue != nil {
|
|
|
|
arg, err = getConstValue(prop.ConstValue)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// If this resource is a provider then, regardless of the schema of the underlying provider
|
|
|
|
// type, we must project all properties as strings. For all properties that are not strings,
|
|
|
|
// we'll marshal them to JSON and use the JSON string as a string input.
|
2020-02-07 18:43:20 +01:00
|
|
|
if res.IsProvider && !isStringType(prop.Type) {
|
2020-09-11 23:27:36 +02:00
|
|
|
arg = fmt.Sprintf("pulumi.Output.from_input(%s).apply(pulumi.runtime.to_json) if %s is not None else None", arg, arg)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
2021-04-07 21:35:19 +02:00
|
|
|
fmt.Fprintf(w, " __props__.__dict__[%q] = %s\n", PyName(prop.Name), arg)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2021-03-01 18:54:25 +01:00
|
|
|
ins.Add(prop.Name)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2020-07-02 21:30:10 +02:00
|
|
|
var secretProps []string
|
2020-01-21 23:45:48 +01:00
|
|
|
for _, prop := range res.Properties {
|
|
|
|
// Default any pure output properties to None. This ensures they are available as properties, even if
|
|
|
|
// they don't ever get assigned a real value, and get documentation if available.
|
2021-03-01 18:54:25 +01:00
|
|
|
if !ins.Has(prop.Name) {
|
2021-04-07 21:35:19 +02:00
|
|
|
fmt.Fprintf(w, " __props__.__dict__[%q] = None\n", PyName(prop.Name))
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
2020-07-02 21:30:10 +02:00
|
|
|
|
|
|
|
if prop.Secret {
|
|
|
|
secretProps = append(secretProps, prop.Name)
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(res.Aliases) > 0 {
|
|
|
|
fmt.Fprintf(w, ` alias_opts = pulumi.ResourceOptions(aliases=[`)
|
|
|
|
|
|
|
|
for i, alias := range res.Aliases {
|
|
|
|
if i > 0 {
|
|
|
|
fmt.Fprintf(w, ", ")
|
|
|
|
}
|
|
|
|
mod.writeAlias(w, alias)
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "])\n")
|
|
|
|
fmt.Fprintf(w, " opts = pulumi.ResourceOptions.merge(opts, alias_opts)\n")
|
|
|
|
}
|
|
|
|
|
2020-07-02 21:30:10 +02:00
|
|
|
if len(secretProps) > 0 {
|
|
|
|
fmt.Fprintf(w, ` secret_opts = pulumi.ResourceOptions(additional_secret_outputs=[`)
|
|
|
|
|
|
|
|
for i, sp := range secretProps {
|
|
|
|
if i > 0 {
|
|
|
|
fmt.Fprintf(w, ", ")
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "%q", sp)
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "])\n")
|
|
|
|
fmt.Fprintf(w, " opts = pulumi.ResourceOptions.merge(opts, secret_opts)\n")
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// Finally, chain to the base constructor, which will actually register the resource.
|
|
|
|
tok := res.Token
|
|
|
|
if res.IsProvider {
|
|
|
|
tok = mod.pkg.Name
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " super(%s, __self__).__init__(\n", name)
|
|
|
|
fmt.Fprintf(w, " '%s',\n", tok)
|
|
|
|
fmt.Fprintf(w, " resource_name,\n")
|
|
|
|
fmt.Fprintf(w, " __props__,\n")
|
2020-09-24 02:23:46 +02:00
|
|
|
if res.IsComponent {
|
|
|
|
fmt.Fprintf(w, " opts,\n")
|
|
|
|
fmt.Fprintf(w, " remote=True)\n")
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, " opts)\n")
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
2020-09-30 18:16:25 +02:00
|
|
|
if !res.IsProvider && !res.IsComponent {
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, " @staticmethod\n")
|
2020-08-19 10:16:47 +02:00
|
|
|
fmt.Fprintf(w, " def get(resource_name: str,\n")
|
|
|
|
fmt.Fprintf(w, " id: pulumi.Input[str],\n")
|
|
|
|
fmt.Fprintf(w, " opts: Optional[pulumi.ResourceOptions] = None")
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2021-04-07 21:35:19 +02:00
|
|
|
if hasStateInputs {
|
2020-01-21 23:45:48 +01:00
|
|
|
for _, prop := range res.StateInputs.Properties {
|
2020-08-19 10:16:47 +02:00
|
|
|
pname := PyName(prop.Name)
|
2021-04-19 23:05:23 +02:00
|
|
|
ty := mod.typeString(prop.Type, true, true, true, true /*optional*/, true /*acceptMapping*/)
|
2020-08-19 10:16:47 +02:00
|
|
|
fmt.Fprintf(w, ",\n %s: %s = None", pname, ty)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
fmt.Fprintf(w, ") -> '%s':\n", name)
|
2020-01-21 23:45:48 +01:00
|
|
|
mod.genGetDocstring(w, res)
|
|
|
|
fmt.Fprintf(w,
|
|
|
|
" opts = pulumi.ResourceOptions.merge(opts, pulumi.ResourceOptions(id=id))\n")
|
|
|
|
fmt.Fprintf(w, "\n")
|
2021-04-07 21:35:19 +02:00
|
|
|
if hasStateInputs {
|
|
|
|
fmt.Fprintf(w, " __props__ = _%[1]sState.__new__(_%[1]sState)\n\n", name)
|
|
|
|
} else {
|
|
|
|
// If we don't have any state inputs, we'll just instantiate the `<Resource>Args` class,
|
|
|
|
// to opt-in to the improved translation behavior.
|
|
|
|
fmt.Fprintf(w, " __props__ = %[1]sArgs.__new__(%[1]sArgs)\n\n", name)
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2021-03-01 18:54:25 +01:00
|
|
|
stateInputs := codegen.NewStringSet()
|
2020-01-21 23:45:48 +01:00
|
|
|
if res.StateInputs != nil {
|
|
|
|
for _, prop := range res.StateInputs.Properties {
|
2021-03-01 18:54:25 +01:00
|
|
|
stateInputs.Add(prop.Name)
|
2021-04-07 21:35:19 +02:00
|
|
|
fmt.Fprintf(w, " __props__.__dict__[%[1]q] = %[1]s\n", PyName(prop.Name))
|
2021-03-01 18:54:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, prop := range res.Properties {
|
|
|
|
if !stateInputs.Has(prop.Name) {
|
2021-04-07 21:35:19 +02:00
|
|
|
fmt.Fprintf(w, " __props__.__dict__[%q] = None\n", PyName(prop.Name))
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-11 00:11:59 +02:00
|
|
|
fmt.Fprintf(w, " return %s(resource_name, opts=opts, __props__=__props__)\n\n", name)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2020-08-28 17:55:02 +02:00
|
|
|
// Write out Python property getters for each of the resource's properties.
|
|
|
|
mod.genProperties(w, res.Properties, false /*setters*/, func(prop *schema.Property) string {
|
2021-04-19 23:05:23 +02:00
|
|
|
ty := mod.typeString(prop.Type, false /*input*/, false /*wrapInput*/, false /*args*/, !prop.IsRequired, false /*acceptMapping*/)
|
2020-08-28 17:55:02 +02:00
|
|
|
return fmt.Sprintf("pulumi.Output[%s]", ty)
|
|
|
|
})
|
2020-08-19 10:16:47 +02:00
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
return w.String(), nil
|
|
|
|
}
|
|
|
|
|
2020-08-28 17:55:02 +02:00
|
|
|
func (mod *modContext) genProperties(w io.Writer, properties []*schema.Property, setters bool,
|
|
|
|
propType func(prop *schema.Property) string) {
|
|
|
|
// Write out Python properties for each property. If there is a property named "property", it will
|
|
|
|
// be emitted last to avoid conflicting with the built-in `@property` decorator function. We do
|
|
|
|
// this instead of importing `builtins` and fully qualifying the decorator as `@builtins.property`
|
|
|
|
// because that wouldn't address the problem if there was a property named "builtins".
|
|
|
|
emitProp := func(pname string, prop *schema.Property) {
|
|
|
|
ty := propType(prop)
|
|
|
|
fmt.Fprintf(w, " @property\n")
|
|
|
|
if pname == prop.Name {
|
|
|
|
fmt.Fprintf(w, " @pulumi.getter\n")
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, " @pulumi.getter(name=%q)\n", prop.Name)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " def %s(self) -> %s:\n", pname, ty)
|
|
|
|
if prop.Comment != "" {
|
|
|
|
printComment(w, prop.Comment, " ")
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " return pulumi.get(self, %q)\n\n", pname)
|
|
|
|
|
|
|
|
if setters {
|
|
|
|
fmt.Fprintf(w, " @%s.setter\n", pname)
|
|
|
|
fmt.Fprintf(w, " def %s(self, value: %s):\n", pname, ty)
|
|
|
|
fmt.Fprintf(w, " pulumi.set(self, %q, value)\n\n", pname)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var propNamedProperty *schema.Property
|
|
|
|
for _, prop := range properties {
|
|
|
|
pname := PyName(prop.Name)
|
|
|
|
// If there is a property named "property", skip it, and emit it last.
|
|
|
|
if pname == "property" {
|
|
|
|
propNamedProperty = prop
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
emitProp(pname, prop)
|
|
|
|
}
|
|
|
|
if propNamedProperty != nil {
|
|
|
|
emitProp("property", propNamedProperty)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
func (mod *modContext) writeAlias(w io.Writer, alias *schema.Alias) {
|
|
|
|
fmt.Fprint(w, "pulumi.Alias(")
|
|
|
|
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.Fprint(w, ", ")
|
|
|
|
}
|
|
|
|
fmt.Fprint(w, part)
|
|
|
|
}
|
|
|
|
fmt.Fprint(w, ")")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) genFunction(fun *schema.Function) (string, error) {
|
|
|
|
w := &bytes.Buffer{}
|
|
|
|
|
2021-04-20 01:40:39 +02:00
|
|
|
imports := imports{}
|
2020-08-19 10:16:47 +02:00
|
|
|
if fun.Inputs != nil {
|
2021-04-20 01:40:39 +02:00
|
|
|
mod.collectImports(fun.Inputs.Properties, imports, true)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
if fun.Outputs != nil {
|
2021-04-20 01:40:39 +02:00
|
|
|
mod.collectImports(fun.Outputs.Properties, imports, false)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
|
2020-09-11 23:27:36 +02:00
|
|
|
mod.genHeader(w, true /*needsSDK*/, imports)
|
2020-08-19 10:16:47 +02:00
|
|
|
|
|
|
|
baseName, awaitableName := awaitableTypeNames(fun.Outputs.Token)
|
2020-01-21 23:45:48 +01:00
|
|
|
name := PyName(tokenToName(fun.Token))
|
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
// Export only the symbols we want exported.
|
|
|
|
fmt.Fprintf(w, "__all__ = [\n")
|
|
|
|
if fun.Outputs != nil {
|
|
|
|
fmt.Fprintf(w, " '%s',\n", baseName)
|
|
|
|
fmt.Fprintf(w, " '%s',\n", awaitableName)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " '%s',\n", name)
|
|
|
|
fmt.Fprintf(w, "]\n\n")
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
if fun.DeprecationMessage != "" {
|
2020-06-17 19:14:11 +02:00
|
|
|
escaped := strings.ReplaceAll(fun.DeprecationMessage, `"`, `\"`)
|
2020-11-09 15:28:28 +01:00
|
|
|
fmt.Fprintf(w, "warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n\n", escaped)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// If there is a return type, emit it.
|
|
|
|
retTypeName := ""
|
|
|
|
var rets []*schema.Property
|
|
|
|
if fun.Outputs != nil {
|
|
|
|
retTypeName, rets = mod.genAwaitableType(w, fun.Outputs), fun.Outputs.Properties
|
2020-08-21 05:51:32 +02:00
|
|
|
fmt.Fprintf(w, "\n\n")
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var args []*schema.Property
|
|
|
|
if fun.Inputs != nil {
|
|
|
|
args = fun.Inputs.Properties
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write out the function signature.
|
2020-08-19 10:16:47 +02:00
|
|
|
def := fmt.Sprintf("def %s(", name)
|
2020-08-21 05:51:32 +02:00
|
|
|
var indent string
|
|
|
|
if len(args) > 0 {
|
|
|
|
indent = strings.Repeat(" ", len(def))
|
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
fmt.Fprintf(w, def)
|
|
|
|
for i, arg := range args {
|
|
|
|
var ind string
|
|
|
|
if i != 0 {
|
|
|
|
ind = indent
|
|
|
|
}
|
|
|
|
pname := PyName(arg.Name)
|
2021-04-19 23:05:23 +02:00
|
|
|
ty := mod.typeString(arg.Type, true, false /*wrapInput*/, false /*args*/, true /*optional*/, true /*acceptMapping*/)
|
2020-08-19 10:16:47 +02:00
|
|
|
fmt.Fprintf(w, "%s%s: %s = None,\n", ind, pname, ty)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "%sopts: Optional[pulumi.InvokeOptions] = None", indent)
|
|
|
|
if retTypeName != "" {
|
|
|
|
fmt.Fprintf(w, ") -> %s:\n", retTypeName)
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, "):\n")
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// If this func has documentation, write it at the top of the docstring, otherwise use a generic comment.
|
|
|
|
docs := &bytes.Buffer{}
|
|
|
|
if fun.Comment != "" {
|
2020-06-18 21:32:15 +02:00
|
|
|
fmt.Fprintln(docs, codegen.FilterExamples(fun.Comment, "python"))
|
2020-01-21 23:45:48 +01:00
|
|
|
} else {
|
|
|
|
fmt.Fprintln(docs, "Use this data source to access information about an existing resource.")
|
|
|
|
}
|
|
|
|
if len(args) > 0 {
|
|
|
|
fmt.Fprintln(docs, "")
|
|
|
|
for _, arg := range args {
|
2020-08-19 10:16:47 +02:00
|
|
|
mod.genPropDocstring(docs, PyName(arg.Name), arg, false /*wrapInputs*/, true /*acceptMapping*/)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
printComment(w, docs.String(), " ")
|
|
|
|
|
|
|
|
if fun.DeprecationMessage != "" {
|
2021-02-26 06:22:04 +01:00
|
|
|
fmt.Fprintf(w, " pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n", name, fun.DeprecationMessage)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Copy the function arguments into a dictionary.
|
2020-07-07 20:32:48 +02:00
|
|
|
fmt.Fprintf(w, " __args__ = dict()\n")
|
2020-01-21 23:45:48 +01:00
|
|
|
for _, arg := range args {
|
|
|
|
// TODO: args validation.
|
|
|
|
fmt.Fprintf(w, " __args__['%s'] = %s\n", arg.Name, PyName(arg.Name))
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the caller explicitly specified a version, use it, otherwise inject this package's version.
|
|
|
|
fmt.Fprintf(w, " if opts is None:\n")
|
|
|
|
fmt.Fprintf(w, " opts = pulumi.InvokeOptions()\n")
|
|
|
|
fmt.Fprintf(w, " if opts.version is None:\n")
|
2020-07-15 22:10:52 +02:00
|
|
|
fmt.Fprintf(w, " opts.version = _utilities.get_version()\n")
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// Now simply invoke the runtime function with the arguments.
|
2020-08-19 10:16:47 +02:00
|
|
|
var typ string
|
|
|
|
if fun.Outputs != nil {
|
|
|
|
// Pass along the private output_type we generated, so any nested outputs classes are instantiated by
|
|
|
|
// the call to invoke.
|
|
|
|
typ = fmt.Sprintf(", typ=%s", baseName)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " __ret__ = pulumi.runtime.invoke('%s', __args__, opts=opts%s).value\n", fun.Token, typ)
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
// And copy the results to an object, if there are indeed any expected returns.
|
|
|
|
if fun.Outputs != nil {
|
|
|
|
fmt.Fprintf(w, " return %s(\n", retTypeName)
|
|
|
|
for i, ret := range rets {
|
2020-08-19 10:16:47 +02:00
|
|
|
// Use the get_dict_value utility instead of calling __ret__.get directly in case the __ret__
|
|
|
|
// object has a get property that masks the underlying dict subclass's get method.
|
|
|
|
fmt.Fprintf(w, " %[1]s=__ret__.%[1]s", PyName(ret.Name))
|
2020-01-21 23:45:48 +01:00
|
|
|
if i == len(rets)-1 {
|
|
|
|
fmt.Fprintf(w, ")\n")
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, ",\n")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return w.String(), nil
|
|
|
|
}
|
|
|
|
|
2020-11-25 06:43:32 +01:00
|
|
|
func (mod *modContext) genEnums(w io.Writer, enums []*schema.EnumType) error {
|
|
|
|
// Header
|
|
|
|
mod.genHeader(w, false /*needsSDK*/, nil)
|
|
|
|
|
|
|
|
// Enum import
|
|
|
|
fmt.Fprintf(w, "from enum import Enum\n\n")
|
|
|
|
|
|
|
|
// Export only the symbols we want exported.
|
|
|
|
fmt.Fprintf(w, "__all__ = [\n")
|
|
|
|
for _, enum := range enums {
|
|
|
|
fmt.Fprintf(w, " '%s',\n", tokenToName(enum.Token))
|
|
|
|
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "]\n\n\n")
|
|
|
|
|
|
|
|
for i, enum := range enums {
|
|
|
|
if err := mod.genEnum(w, enum); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if i != len(enums)-1 {
|
|
|
|
fmt.Fprintf(w, "\n\n")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) genEnum(w io.Writer, enum *schema.EnumType) error {
|
|
|
|
indent := " "
|
|
|
|
enumName := tokenToName(enum.Token)
|
2021-04-19 23:05:23 +02:00
|
|
|
underlyingType := mod.typeString(enum.ElementType, false, false, false, false, false)
|
2020-11-25 06:43:32 +01:00
|
|
|
|
|
|
|
switch enum.ElementType {
|
|
|
|
case schema.StringType, schema.IntType, schema.NumberType:
|
|
|
|
fmt.Fprintf(w, "class %s(%s, Enum):\n", enumName, underlyingType)
|
|
|
|
printComment(w, enum.Comment, indent)
|
|
|
|
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
|
|
|
name, err := makeSafeEnumName(e.Name, enumName)
|
2020-11-25 06:43:32 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
e.Name = name
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "%s%s = ", indent, e.Name)
|
|
|
|
if val, ok := e.Value.(string); ok {
|
|
|
|
fmt.Fprintf(w, "%q\n", val)
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, "%v\n", e.Value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return errors.Errorf("enums of type %s are not yet implemented for this language", enum.ElementType.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-04-20 01:40:39 +02:00
|
|
|
func visitObjectTypes(properties []*schema.Property, visitor func(objectOrResource schema.Type, plain bool)) {
|
|
|
|
codegen.VisitTypeClosure(properties, func(t codegen.Type) {
|
|
|
|
switch st := t.Type.(type) {
|
|
|
|
case *schema.EnumType, *schema.ObjectType, *schema.ResourceType:
|
|
|
|
visitor(st, t.Plain)
|
|
|
|
}
|
|
|
|
})
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
|
2021-04-20 01:40:39 +02:00
|
|
|
func (mod *modContext) collectImports(properties []*schema.Property, imports imports, input bool) {
|
|
|
|
codegen.VisitTypeClosure(properties, func(t codegen.Type) {
|
|
|
|
switch t := t.Type.(type) {
|
|
|
|
case *schema.ObjectType:
|
|
|
|
imports.addType(mod, t, input)
|
|
|
|
case *schema.EnumType:
|
|
|
|
imports.addEnum(mod, t.Token)
|
|
|
|
case *schema.ResourceType:
|
|
|
|
imports.addResource(mod, t)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
2021-04-20 01:40:39 +02:00
|
|
|
})
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
var requirementRegex = regexp.MustCompile(`^>=([^,]+),<[^,]+$`)
|
|
|
|
var pep440AlphaRegex = regexp.MustCompile(`^(\d+\.\d+\.\d)+a(\d+)$`)
|
|
|
|
var pep440BetaRegex = regexp.MustCompile(`^(\d+\.\d+\.\d+)b(\d+)$`)
|
|
|
|
var pep440RCRegex = regexp.MustCompile(`^(\d+\.\d+\.\d+)rc(\d+)$`)
|
|
|
|
var pep440DevRegex = regexp.MustCompile(`^(\d+\.\d+\.\d+)\.dev(\d+)$`)
|
|
|
|
|
|
|
|
var oldestAllowedPulumi = semver.Version{
|
|
|
|
Major: 0,
|
|
|
|
Minor: 17,
|
|
|
|
Patch: 28,
|
|
|
|
}
|
|
|
|
|
2020-03-13 05:43:54 +01:00
|
|
|
func sanitizePackageDescription(description string) string {
|
|
|
|
lines := strings.SplitN(description, "\n", 2)
|
|
|
|
if len(lines) > 0 {
|
|
|
|
return lines[0]
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2020-12-04 04:22:16 +01:00
|
|
|
func genPulumiPluginFile(pkg *schema.Package) ([]byte, error) {
|
|
|
|
plugin := &plugin.PulumiPluginJSON{
|
|
|
|
Resource: true,
|
|
|
|
Name: pkg.Name,
|
|
|
|
Version: "${PLUGIN_VERSION}",
|
|
|
|
Server: pkg.PluginDownloadURL,
|
|
|
|
}
|
|
|
|
return plugin.JSON()
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// genPackageMetadata generates all the non-code metadata required by a Pulumi package.
|
2020-12-04 04:22:16 +01:00
|
|
|
func genPackageMetadata(
|
2021-05-06 20:18:14 +02:00
|
|
|
tool string, pkg *schema.Package, pyPkgName string, emitPulumiPluginFile bool, requires map[string]string) (string, error) {
|
2020-12-04 04:22:16 +01:00
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
w := &bytes.Buffer{}
|
2020-09-11 23:27:36 +02:00
|
|
|
(&modContext{tool: tool}).genHeader(w, false /*needsSDK*/, nil)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// Now create a standard Python package from the metadata.
|
|
|
|
fmt.Fprintf(w, "import errno\n")
|
|
|
|
fmt.Fprintf(w, "from setuptools import setup, find_packages\n")
|
|
|
|
fmt.Fprintf(w, "from setuptools.command.install import install\n")
|
|
|
|
fmt.Fprintf(w, "from subprocess import check_call\n")
|
2020-06-11 00:11:59 +02:00
|
|
|
fmt.Fprintf(w, "\n\n")
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// Create a command that will install the Pulumi plugin for this resource provider.
|
|
|
|
fmt.Fprintf(w, "class InstallPluginCommand(install):\n")
|
|
|
|
fmt.Fprintf(w, " def run(self):\n")
|
|
|
|
fmt.Fprintf(w, " install.run(self)\n")
|
|
|
|
fmt.Fprintf(w, " try:\n")
|
2020-07-02 18:25:49 +02:00
|
|
|
if pkg.PluginDownloadURL == "" {
|
2020-07-02 02:57:38 +02:00
|
|
|
fmt.Fprintf(w, " check_call(['pulumi', 'plugin', 'install', 'resource', '%s', '${PLUGIN_VERSION}'])\n", pkg.Name)
|
|
|
|
} else {
|
2020-07-02 18:25:49 +02:00
|
|
|
fmt.Fprintf(w, " check_call(['pulumi', 'plugin', 'install', 'resource', '%s', '${PLUGIN_VERSION}', '--server', '%s'])\n", pkg.Name, pkg.PluginDownloadURL)
|
2020-07-02 02:57:38 +02:00
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, " except OSError as error:\n")
|
|
|
|
fmt.Fprintf(w, " if error.errno == errno.ENOENT:\n")
|
|
|
|
fmt.Fprintf(w, " print(\"\"\"\n")
|
|
|
|
fmt.Fprintf(w, " There was an error installing the %s resource provider plugin.\n", pkg.Name)
|
|
|
|
fmt.Fprintf(w, " It looks like `pulumi` is not installed on your system.\n")
|
|
|
|
fmt.Fprintf(w, " Please visit https://pulumi.com/ to install the Pulumi CLI.\n")
|
|
|
|
fmt.Fprintf(w, " You may try manually installing the plugin by running\n")
|
|
|
|
fmt.Fprintf(w, " `pulumi plugin install resource %s ${PLUGIN_VERSION}`\n", pkg.Name)
|
|
|
|
fmt.Fprintf(w, " \"\"\")\n")
|
|
|
|
fmt.Fprintf(w, " else:\n")
|
|
|
|
fmt.Fprintf(w, " raise\n")
|
2020-06-11 00:11:59 +02:00
|
|
|
fmt.Fprintf(w, "\n\n")
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// Generate a readme method which will load README.rst, we use this to fill out the
|
|
|
|
// long_description field in the setup call.
|
|
|
|
fmt.Fprintf(w, "def readme():\n")
|
|
|
|
fmt.Fprintf(w, " with open('README.md', encoding='utf-8') as f:\n")
|
|
|
|
fmt.Fprintf(w, " return f.read()\n")
|
2020-06-11 00:11:59 +02:00
|
|
|
fmt.Fprintf(w, "\n\n")
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// Finally, the actual setup part.
|
2021-05-06 20:18:14 +02:00
|
|
|
fmt.Fprintf(w, "setup(name='%s',\n", pyPkgName)
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintf(w, " version='${VERSION}',\n")
|
|
|
|
if pkg.Description != "" {
|
2020-03-13 05:43:54 +01:00
|
|
|
fmt.Fprintf(w, " description=%q,\n", sanitizePackageDescription(pkg.Description))
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " long_description=readme(),\n")
|
|
|
|
fmt.Fprintf(w, " long_description_content_type='text/markdown',\n")
|
|
|
|
fmt.Fprintf(w, " cmdclass={\n")
|
|
|
|
fmt.Fprintf(w, " 'install': InstallPluginCommand,\n")
|
|
|
|
fmt.Fprintf(w, " },\n")
|
|
|
|
if pkg.Keywords != nil {
|
|
|
|
fmt.Fprintf(w, " keywords='")
|
|
|
|
for i, kw := range pkg.Keywords {
|
|
|
|
if i > 0 {
|
|
|
|
fmt.Fprint(w, " ")
|
|
|
|
}
|
|
|
|
fmt.Fprint(w, kw)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "',\n")
|
|
|
|
}
|
|
|
|
if pkg.Homepage != "" {
|
|
|
|
fmt.Fprintf(w, " url='%s',\n", pkg.Homepage)
|
|
|
|
}
|
|
|
|
if pkg.Repository != "" {
|
|
|
|
fmt.Fprintf(w, " project_urls={\n")
|
|
|
|
fmt.Fprintf(w, " 'Repository': '%s'\n", pkg.Repository)
|
|
|
|
fmt.Fprintf(w, " },\n")
|
|
|
|
}
|
|
|
|
if pkg.License != "" {
|
|
|
|
fmt.Fprintf(w, " license='%s',\n", pkg.License)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " packages=find_packages(),\n")
|
|
|
|
|
2020-02-07 18:43:20 +01:00
|
|
|
// Publish type metadata: PEP 561
|
|
|
|
fmt.Fprintf(w, " package_data={\n")
|
2021-05-06 20:18:14 +02:00
|
|
|
fmt.Fprintf(w, " '%s': [\n", pyPkgName)
|
2020-12-04 04:22:16 +01:00
|
|
|
fmt.Fprintf(w, " 'py.typed',\n")
|
|
|
|
if emitPulumiPluginFile {
|
|
|
|
fmt.Fprintf(w, " 'pulumiplugin.json',\n")
|
|
|
|
}
|
|
|
|
|
2020-02-07 18:43:20 +01:00
|
|
|
fmt.Fprintf(w, " ]\n")
|
|
|
|
fmt.Fprintf(w, " },\n")
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// Ensure that the Pulumi SDK has an entry if not specified. If the SDK _is_ specified, ensure
|
|
|
|
// that it specifies an acceptable version range.
|
|
|
|
if pulumiReq, ok := requires["pulumi"]; ok {
|
|
|
|
// We expect a specific pattern of ">=version,<version" here.
|
|
|
|
matches := requirementRegex.FindStringSubmatch(pulumiReq)
|
|
|
|
if len(matches) != 2 {
|
|
|
|
return "", errors.Errorf("invalid requirement specifier \"%s\"; expected \">=version1,<version2\"", pulumiReq)
|
|
|
|
}
|
|
|
|
|
|
|
|
lowerBound, err := pep440VersionToSemver(matches[1])
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Errorf("invalid version for lower bound: %v", err)
|
|
|
|
}
|
|
|
|
if lowerBound.LT(oldestAllowedPulumi) {
|
|
|
|
return "", errors.Errorf("lower version bound must be at least %v", oldestAllowedPulumi)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if requires == nil {
|
|
|
|
requires = map[string]string{}
|
|
|
|
}
|
|
|
|
requires["pulumi"] = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort the entries so they are deterministic.
|
|
|
|
reqNames := []string{
|
|
|
|
"semver>=2.8.1",
|
|
|
|
"parver>=0.2.1",
|
|
|
|
}
|
|
|
|
for req := range requires {
|
|
|
|
reqNames = append(reqNames, req)
|
|
|
|
}
|
|
|
|
sort.Strings(reqNames)
|
|
|
|
|
|
|
|
fmt.Fprintf(w, " install_requires=[\n")
|
|
|
|
for i, req := range reqNames {
|
|
|
|
var comma string
|
|
|
|
if i < len(reqNames)-1 {
|
|
|
|
comma = ","
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " '%s%s'%s\n", req, requires[req], comma)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " ],\n")
|
|
|
|
|
|
|
|
fmt.Fprintf(w, " zip_safe=False)\n")
|
|
|
|
return w.String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func pep440VersionToSemver(v string) (semver.Version, error) {
|
|
|
|
switch {
|
|
|
|
case pep440AlphaRegex.MatchString(v):
|
|
|
|
parts := pep440AlphaRegex.FindStringSubmatch(v)
|
|
|
|
v = parts[1] + "-alpha." + parts[2]
|
|
|
|
case pep440BetaRegex.MatchString(v):
|
|
|
|
parts := pep440BetaRegex.FindStringSubmatch(v)
|
|
|
|
v = parts[1] + "-beta." + parts[2]
|
|
|
|
case pep440RCRegex.MatchString(v):
|
|
|
|
parts := pep440RCRegex.FindStringSubmatch(v)
|
|
|
|
v = parts[1] + "-rc." + parts[2]
|
|
|
|
case pep440DevRegex.MatchString(v):
|
|
|
|
parts := pep440DevRegex.FindStringSubmatch(v)
|
|
|
|
v = parts[1] + "-dev." + parts[2]
|
|
|
|
}
|
|
|
|
|
|
|
|
return semver.ParseTolerant(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// recordProperty records the given property's name and member names. For each property name contained in the given
|
|
|
|
// property, the name is converted to snake case and recorded in the snake case to camel case table.
|
|
|
|
//
|
|
|
|
// Once all resources have been emitted, the table is written out to a format usable for implementations of
|
|
|
|
// translate_input_property and translate_output_property.
|
2020-06-26 20:47:13 +02:00
|
|
|
func buildCaseMappingTables(pkg *schema.Package, snakeCaseToCamelCase, camelCaseToSnakeCase map[string]string, seenTypes codegen.Set) {
|
2020-08-18 01:58:59 +02:00
|
|
|
// Add provider input properties to translation tables.
|
|
|
|
for _, p := range pkg.Provider.InputProperties {
|
|
|
|
recordProperty(p, snakeCaseToCamelCase, camelCaseToSnakeCase, seenTypes)
|
2020-08-13 03:50:10 +02:00
|
|
|
}
|
|
|
|
|
2020-04-30 23:16:56 +02:00
|
|
|
for _, r := range pkg.Resources {
|
|
|
|
// Calculate casing tables. We do this up front because our docstring generator (which is run during
|
|
|
|
// genResource) requires them.
|
|
|
|
for _, prop := range r.Properties {
|
2020-06-26 20:47:13 +02:00
|
|
|
recordProperty(prop, snakeCaseToCamelCase, camelCaseToSnakeCase, seenTypes)
|
2020-04-30 23:16:56 +02:00
|
|
|
}
|
|
|
|
for _, prop := range r.InputProperties {
|
2020-06-26 20:47:13 +02:00
|
|
|
recordProperty(prop, snakeCaseToCamelCase, camelCaseToSnakeCase, seenTypes)
|
2020-04-30 23:16:56 +02:00
|
|
|
}
|
|
|
|
}
|
2020-06-22 18:49:34 +02:00
|
|
|
for _, typ := range pkg.Types {
|
|
|
|
typ, ok := typ.(*schema.ObjectType)
|
|
|
|
if ok {
|
|
|
|
for _, prop := range typ.Properties {
|
2020-06-26 20:47:13 +02:00
|
|
|
recordProperty(prop, snakeCaseToCamelCase, camelCaseToSnakeCase, seenTypes)
|
2020-06-22 18:49:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-30 23:16:56 +02:00
|
|
|
}
|
|
|
|
|
2020-06-26 20:47:13 +02:00
|
|
|
func recordProperty(prop *schema.Property, snakeCaseToCamelCase, camelCaseToSnakeCase map[string]string, seenTypes codegen.Set) {
|
2020-03-11 23:42:15 +01:00
|
|
|
mapCase := true
|
2020-01-21 23:45:48 +01:00
|
|
|
if python, ok := prop.Language["python"]; ok {
|
2021-02-13 00:54:19 +01:00
|
|
|
v, ok := python.(PropertyInfo)
|
|
|
|
mapCase = ok && v.MapCase
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
2020-03-11 23:42:15 +01:00
|
|
|
if mapCase {
|
2020-08-05 19:08:52 +02:00
|
|
|
snakeCaseName := PyNameLegacy(prop.Name)
|
2020-08-18 01:44:55 +02:00
|
|
|
if snakeCaseToCamelCase != nil {
|
|
|
|
if _, ok := snakeCaseToCamelCase[snakeCaseName]; !ok {
|
|
|
|
snakeCaseToCamelCase[snakeCaseName] = prop.Name
|
2020-06-25 20:51:57 +02:00
|
|
|
}
|
2020-08-18 01:44:55 +02:00
|
|
|
}
|
|
|
|
if camelCaseToSnakeCase != nil {
|
|
|
|
if _, ok := camelCaseToSnakeCase[prop.Name]; !ok {
|
|
|
|
camelCaseToSnakeCase[prop.Name] = snakeCaseName
|
2020-06-25 20:51:57 +02:00
|
|
|
}
|
2020-04-30 23:16:56 +02:00
|
|
|
}
|
2020-03-11 23:42:15 +01:00
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
if obj, ok := prop.Type.(*schema.ObjectType); ok {
|
2020-06-26 20:47:13 +02:00
|
|
|
if !seenTypes.Has(prop.Type) {
|
|
|
|
// Avoid infinite calls in case of recursive types.
|
|
|
|
seenTypes.Add(prop.Type)
|
2020-04-10 23:07:52 +02:00
|
|
|
|
2020-06-26 20:47:13 +02:00
|
|
|
for _, p := range obj.Properties {
|
|
|
|
recordProperty(p, snakeCaseToCamelCase, camelCaseToSnakeCase, seenTypes)
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// genInitDocstring emits the docstring for the __init__ method of the given resource type.
|
|
|
|
//
|
|
|
|
// Sphinx (the documentation generator that we use to generate Python docs) does not draw a
|
|
|
|
// distinction between documentation comments on the class itself and documentation comments on the
|
|
|
|
// __init__ method of a class. The docs repo instructs Sphinx to concatenate the two together, which
|
|
|
|
// means that we don't need to emit docstrings on the class at all as long as the __init__ docstring
|
|
|
|
// is good enough.
|
|
|
|
//
|
|
|
|
// The docstring we generate here describes both the class itself and the arguments to the class's
|
|
|
|
// constructor. The format of the docstring is in "Sphinx form":
|
|
|
|
// 1. Parameters are introduced using the syntax ":param <type> <name>: <comment>". Sphinx parses this and uses it
|
|
|
|
// to populate the list of parameters for this function.
|
|
|
|
// 2. The doc string of parameters is expected to be indented to the same indentation as the type of the parameter.
|
|
|
|
// Sphinx will complain and make mistakes if this is not the case.
|
|
|
|
// 3. The doc string can't have random newlines in it, or Sphinx will complain.
|
|
|
|
//
|
|
|
|
// This function does the best it can to navigate these constraints and produce a docstring that
|
|
|
|
// Sphinx can make sense of.
|
2021-04-02 19:09:17 +02:00
|
|
|
func (mod *modContext) genInitDocstring(w io.Writer, res *schema.Resource, name string, argOverload bool) {
|
2020-01-21 23:45:48 +01:00
|
|
|
// b contains the full text of the docstring, without the leading and trailing triple quotes.
|
|
|
|
b := &bytes.Buffer{}
|
|
|
|
|
|
|
|
// If this resource has documentation, write it at the top of the docstring, otherwise use a generic comment.
|
|
|
|
if res.Comment != "" {
|
2020-06-18 21:32:15 +02:00
|
|
|
fmt.Fprintln(b, codegen.FilterExamples(res.Comment, "python"))
|
2020-01-21 23:45:48 +01:00
|
|
|
} else {
|
|
|
|
fmt.Fprintf(b, "Create a %s resource with the given unique name, props, and options.\n", tokenToName(res.Token))
|
|
|
|
}
|
|
|
|
|
|
|
|
// All resources have a resource_name parameter and opts parameter.
|
|
|
|
fmt.Fprintln(b, ":param str resource_name: The name of the resource.")
|
2021-04-02 19:09:17 +02:00
|
|
|
if argOverload {
|
|
|
|
fmt.Fprintf(b, ":param %sArgs args: The arguments to use to populate this resource's properties.\n", name)
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintln(b, ":param pulumi.ResourceOptions opts: Options for the resource.")
|
2021-04-02 19:09:17 +02:00
|
|
|
if !argOverload {
|
|
|
|
for _, prop := range res.InputProperties {
|
|
|
|
mod.genPropDocstring(b, InitParamName(prop.Name), prop, true /*wrapInput*/, true /*acceptMapping*/)
|
|
|
|
}
|
2020-06-16 01:17:05 +02:00
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// printComment handles the prefix and triple quotes.
|
|
|
|
printComment(w, b.String(), " ")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) genGetDocstring(w io.Writer, res *schema.Resource) {
|
|
|
|
// "buf" contains the full text of the docstring, without the leading and trailing triple quotes.
|
|
|
|
b := &bytes.Buffer{}
|
|
|
|
|
|
|
|
fmt.Fprintf(b, "Get an existing %s resource's state with the given name, id, and optional extra\n"+
|
|
|
|
"properties used to qualify the lookup.\n", tokenToName(res.Token))
|
|
|
|
fmt.Fprintln(b, "")
|
|
|
|
|
|
|
|
fmt.Fprintln(b, ":param str resource_name: The unique name of the resulting resource.")
|
2020-08-19 10:16:47 +02:00
|
|
|
fmt.Fprintln(b, ":param pulumi.Input[str] id: The unique provider ID of the resource to lookup.")
|
2020-01-21 23:45:48 +01:00
|
|
|
fmt.Fprintln(b, ":param pulumi.ResourceOptions opts: Options for the resource.")
|
2020-06-08 21:44:57 +02:00
|
|
|
if res.StateInputs != nil {
|
|
|
|
for _, prop := range res.StateInputs.Properties {
|
2020-08-19 10:16:47 +02:00
|
|
|
mod.genPropDocstring(b, PyName(prop.Name), prop, true /*wrapInput*/, true /*acceptMapping*/)
|
2020-06-08 21:44:57 +02:00
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// printComment handles the prefix and triple quotes.
|
|
|
|
printComment(w, b.String(), " ")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mod *modContext) genTypeDocstring(w io.Writer, comment string, properties []*schema.Property, wrapInput bool) {
|
|
|
|
// b contains the full text of the docstring, without the leading and trailing triple quotes.
|
|
|
|
b := &bytes.Buffer{}
|
|
|
|
|
|
|
|
// If this type has documentation, write it at the top of the docstring.
|
|
|
|
if comment != "" {
|
|
|
|
fmt.Fprintln(b, comment)
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
for _, prop := range properties {
|
|
|
|
mod.genPropDocstring(b, PyName(prop.Name), prop, wrapInput, false /*acceptMapping*/)
|
2020-06-08 21:44:57 +02:00
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// printComment handles the prefix and triple quotes.
|
|
|
|
printComment(w, b.String(), " ")
|
|
|
|
}
|
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
func (mod *modContext) genPropDocstring(w io.Writer, name string, prop *schema.Property, wrapInput bool,
|
|
|
|
acceptMapping bool) {
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
if prop.Comment == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-19 23:05:23 +02:00
|
|
|
ty := mod.typeString(prop.Type, true, wrapInput && !prop.IsPlain, wrapInput && !prop.IsPlain, false /*optional*/, acceptMapping)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
|
|
|
// If this property has some documentation associated with it, we need to split it so that it is indented
|
|
|
|
// in a way that Sphinx can understand.
|
|
|
|
lines := strings.Split(prop.Comment, "\n")
|
|
|
|
for len(lines) > 0 && lines[len(lines)-1] == "" {
|
|
|
|
lines = lines[:len(lines)-1]
|
|
|
|
}
|
|
|
|
for i, docLine := range lines {
|
|
|
|
// If it's the first line, print the :param header.
|
|
|
|
if i == 0 {
|
|
|
|
fmt.Fprintf(w, ":param %s %s: %s\n", ty, name, docLine)
|
|
|
|
} else {
|
|
|
|
// Otherwise, print out enough padding to align with the first char of the type.
|
|
|
|
fmt.Fprintf(w, " %s\n", docLine)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-19 23:05:23 +02:00
|
|
|
func (mod *modContext) typeString(t schema.Type, input, wrapInput, args, optional, acceptMapping bool) string {
|
2020-08-19 10:16:47 +02:00
|
|
|
var typ string
|
|
|
|
switch t := t.(type) {
|
2020-11-25 06:43:32 +01:00
|
|
|
case *schema.EnumType:
|
|
|
|
typ = mod.tokenToEnum(t.Token)
|
2020-08-19 10:16:47 +02:00
|
|
|
case *schema.ArrayType:
|
2021-04-19 23:05:23 +02:00
|
|
|
typ = fmt.Sprintf("Sequence[%s]", mod.typeString(t.ElementType, input, wrapInput, args, false, acceptMapping))
|
2020-08-19 10:16:47 +02:00
|
|
|
case *schema.MapType:
|
2021-04-19 23:05:23 +02:00
|
|
|
typ = fmt.Sprintf("Mapping[str, %s]", mod.typeString(t.ElementType, input, wrapInput, args, false, acceptMapping))
|
2020-08-19 10:16:47 +02:00
|
|
|
case *schema.ObjectType:
|
2021-04-16 04:03:28 +02:00
|
|
|
typ = mod.objectType(t, input, args)
|
2020-08-19 10:16:47 +02:00
|
|
|
if acceptMapping {
|
|
|
|
typ = fmt.Sprintf("pulumi.InputType[%s]", typ)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
2020-09-23 21:39:25 +02:00
|
|
|
case *schema.ResourceType:
|
2021-02-13 00:54:19 +01:00
|
|
|
typ = fmt.Sprintf("'%s'", mod.resourceType(t))
|
2020-08-19 10:16:47 +02:00
|
|
|
case *schema.TokenType:
|
|
|
|
// Use the underlying type for now.
|
|
|
|
if t.UnderlyingType != nil {
|
2021-04-19 23:05:23 +02:00
|
|
|
return mod.typeString(t.UnderlyingType, input, wrapInput, args, optional, acceptMapping)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
typ = "Any"
|
|
|
|
case *schema.UnionType:
|
|
|
|
if !input {
|
2020-11-25 06:43:32 +01:00
|
|
|
for _, e := range t.ElementTypes {
|
|
|
|
// If this is an output and a "relaxed" enum, emit the type as the underlying primitive type rather than the union.
|
|
|
|
// Eg. Output[str] rather than Output[Any]
|
|
|
|
if typ, ok := e.(*schema.EnumType); ok {
|
2021-04-19 23:05:23 +02:00
|
|
|
return mod.typeString(typ.ElementType, input, wrapInput, args, optional, acceptMapping)
|
2020-11-25 06:43:32 +01:00
|
|
|
}
|
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
if t.DefaultType != nil {
|
2021-04-19 23:05:23 +02:00
|
|
|
return mod.typeString(t.DefaultType, input, wrapInput, args, optional, acceptMapping)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
typ = "Any"
|
|
|
|
} else {
|
2021-03-01 18:54:25 +01:00
|
|
|
elementTypeSet := codegen.NewStringSet()
|
2020-08-19 10:16:47 +02:00
|
|
|
var elementTypes []schema.Type
|
|
|
|
for _, e := range t.ElementTypes {
|
2021-04-19 23:05:23 +02:00
|
|
|
et := mod.typeString(e, input, wrapInput, args, false, acceptMapping)
|
2021-03-01 18:54:25 +01:00
|
|
|
if !elementTypeSet.Has(et) {
|
|
|
|
elementTypeSet.Add(et)
|
2020-08-19 10:16:47 +02:00
|
|
|
elementTypes = append(elementTypes, e)
|
|
|
|
}
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
if len(elementTypes) == 1 {
|
2021-04-19 23:05:23 +02:00
|
|
|
return mod.typeString(elementTypes[0], input, wrapInput, args, optional, acceptMapping)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
var elements []string
|
|
|
|
for _, e := range elementTypes {
|
2021-04-19 23:05:23 +02:00
|
|
|
t := mod.typeString(e, input, wrapInput, args, false, acceptMapping)
|
2021-04-16 04:03:28 +02:00
|
|
|
if args && strings.HasPrefix(t, "pulumi.Input[") {
|
2020-08-19 10:16:47 +02:00
|
|
|
contract.Assert(t[len(t)-1] == ']')
|
|
|
|
// Strip off the leading `pulumi.Input[` and the trailing `]`
|
|
|
|
t = t[len("pulumi.Input[") : len(t)-1]
|
|
|
|
}
|
|
|
|
elements = append(elements, t)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
typ = fmt.Sprintf("Union[%s]", strings.Join(elements, ", "))
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
switch t {
|
|
|
|
case schema.BoolType:
|
|
|
|
typ = "bool"
|
2020-09-11 23:56:29 +02:00
|
|
|
case schema.IntType:
|
|
|
|
typ = "int"
|
|
|
|
case schema.NumberType:
|
2020-08-19 10:16:47 +02:00
|
|
|
typ = "float"
|
|
|
|
case schema.StringType:
|
|
|
|
typ = "str"
|
|
|
|
case schema.ArchiveType:
|
|
|
|
typ = "pulumi.Archive"
|
|
|
|
case schema.AssetType:
|
|
|
|
typ = "Union[pulumi.Asset, pulumi.Archive]"
|
|
|
|
case schema.JSONType:
|
|
|
|
fallthrough
|
|
|
|
case schema.AnyType:
|
|
|
|
typ = "Any"
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-19 23:05:23 +02:00
|
|
|
if wrapInput && typ != "Any" {
|
2020-08-19 10:16:47 +02:00
|
|
|
typ = fmt.Sprintf("pulumi.Input[%s]", typ)
|
2020-06-08 21:44:57 +02:00
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
if optional {
|
|
|
|
return fmt.Sprintf("Optional[%s]", typ)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
return typ
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// pyType returns the expected runtime type for the given variable. Of course, being a dynamic language, this
|
|
|
|
// check is not exhaustive, but it should be good enough to catch 80% of the cases early on.
|
2020-09-23 21:39:25 +02:00
|
|
|
func (mod *modContext) pyType(typ schema.Type) string {
|
2020-02-05 20:43:39 +01:00
|
|
|
switch typ := typ.(type) {
|
2020-11-25 06:43:32 +01:00
|
|
|
case *schema.EnumType:
|
|
|
|
return mod.pyType(typ.ElementType)
|
2020-01-21 23:45:48 +01:00
|
|
|
case *schema.ArrayType:
|
|
|
|
return "list"
|
2020-02-05 20:43:39 +01:00
|
|
|
case *schema.MapType, *schema.ObjectType, *schema.UnionType:
|
|
|
|
return "dict"
|
2020-09-23 21:39:25 +02:00
|
|
|
case *schema.ResourceType:
|
2021-02-13 00:54:19 +01:00
|
|
|
return mod.resourceType(typ)
|
2020-02-05 20:43:39 +01:00
|
|
|
case *schema.TokenType:
|
|
|
|
if typ.UnderlyingType != nil {
|
2020-09-23 21:39:25 +02:00
|
|
|
return mod.pyType(typ.UnderlyingType)
|
2020-02-05 20:43:39 +01:00
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
return "dict"
|
|
|
|
default:
|
|
|
|
switch typ {
|
|
|
|
case schema.BoolType:
|
|
|
|
return "bool"
|
2020-09-11 23:56:29 +02:00
|
|
|
case schema.IntType:
|
|
|
|
return "int"
|
|
|
|
case schema.NumberType:
|
2020-01-21 23:45:48 +01:00
|
|
|
return "float"
|
|
|
|
case schema.StringType:
|
|
|
|
return "str"
|
|
|
|
case schema.ArchiveType:
|
|
|
|
return "pulumi.Archive"
|
|
|
|
case schema.AssetType:
|
|
|
|
return "Union[pulumi.Asset, pulumi.Archive]"
|
|
|
|
default:
|
|
|
|
return "dict"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-07 18:43:20 +01:00
|
|
|
func isStringType(t schema.Type) bool {
|
|
|
|
for tt, ok := t.(*schema.TokenType); ok; tt, ok = t.(*schema.TokenType) {
|
|
|
|
t = tt.UnderlyingType
|
|
|
|
}
|
|
|
|
|
|
|
|
return t == schema.StringType
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// pyPack returns the suggested package name for the given string.
|
|
|
|
func pyPack(s string) string {
|
2020-09-15 13:56:58 +02:00
|
|
|
return "pulumi_" + strings.ReplaceAll(s, "-", "_")
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// pyClassName turns a raw name into one that is suitable as a Python class name.
|
|
|
|
func pyClassName(name string) string {
|
|
|
|
return EnsureKeywordSafe(name)
|
|
|
|
}
|
|
|
|
|
2020-08-19 17:34:06 +02:00
|
|
|
// InitParamName returns a PyName-encoded name but also deduplicates the name against built-in parameters of resource __init__.
|
|
|
|
func InitParamName(name string) string {
|
2020-07-30 16:29:36 +02:00
|
|
|
result := PyName(name)
|
|
|
|
switch result {
|
|
|
|
case "resource_name", "opts":
|
|
|
|
return result + "_"
|
|
|
|
default:
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-16 04:03:28 +02:00
|
|
|
func (mod *modContext) genObjectType(w io.Writer, obj *schema.ObjectType, input bool) error {
|
|
|
|
if input {
|
|
|
|
if mod.details(obj).argsType {
|
|
|
|
name := mod.unqualifiedObjectTypeName(obj, input, true)
|
2021-04-20 01:40:39 +02:00
|
|
|
if err := mod.genType(w, name, obj.Comment, obj.Properties, mod.details(obj).plainType, input, true, false); err != nil {
|
2021-04-16 04:03:28 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2021-04-20 01:40:39 +02:00
|
|
|
if mod.details(obj).plainType {
|
2021-04-16 04:03:28 +02:00
|
|
|
name := mod.unqualifiedObjectTypeName(obj, input, false)
|
2021-04-20 01:40:39 +02:00
|
|
|
if err := mod.genType(w, name, obj.Comment, obj.Properties, mod.details(obj).plainType, input, false, false); err != nil {
|
2021-04-16 04:03:28 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
name := mod.unqualifiedObjectTypeName(obj, input, false)
|
2021-04-20 01:40:39 +02:00
|
|
|
return mod.genType(w, name, obj.Comment, obj.Properties, mod.details(obj).plainType, false, false, mod.details(obj).resourceOutputType)
|
2021-04-16 04:03:28 +02:00
|
|
|
}
|
|
|
|
|
2021-04-20 01:40:39 +02:00
|
|
|
func (mod *modContext) genType(w io.Writer, name, comment string, properties []*schema.Property, plainType, input, args, resourceOutput bool) error {
|
2020-08-19 10:16:47 +02:00
|
|
|
// Sort required props first.
|
2021-04-02 19:09:17 +02:00
|
|
|
props := make([]*schema.Property, len(properties))
|
|
|
|
copy(props, properties)
|
2020-08-19 10:16:47 +02:00
|
|
|
sort.Slice(props, func(i, j int) bool {
|
|
|
|
pi, pj := props[i], props[j]
|
|
|
|
switch {
|
|
|
|
case pi.IsRequired != pj.IsRequired:
|
|
|
|
return pi.IsRequired && !pj.IsRequired
|
|
|
|
default:
|
|
|
|
return pi.Name < pj.Name
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
decorator := "@pulumi.output_type"
|
|
|
|
if input {
|
|
|
|
decorator = "@pulumi.input_type"
|
|
|
|
}
|
|
|
|
|
|
|
|
var suffix string
|
|
|
|
if !input {
|
|
|
|
suffix = "(dict)"
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "%s\n", decorator)
|
|
|
|
fmt.Fprintf(w, "class %s%s:\n", name, suffix)
|
2021-04-02 19:09:17 +02:00
|
|
|
if !input && comment != "" {
|
|
|
|
printComment(w, comment, " ")
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
|
2021-04-07 21:35:19 +02:00
|
|
|
// To help users migrate to using the properly snake_cased property getters, emit warnings when camelCase keys are
|
|
|
|
// accessed. We emit this at the top of the class in case we have a `get` property that will be redefined later.
|
2021-04-20 01:40:39 +02:00
|
|
|
if resourceOutput {
|
2021-04-07 21:35:19 +02:00
|
|
|
var needsCaseWarning bool
|
|
|
|
for _, prop := range props {
|
|
|
|
pname := PyName(prop.Name)
|
|
|
|
if pname != prop.Name {
|
|
|
|
needsCaseWarning = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if needsCaseWarning {
|
|
|
|
fmt.Fprintf(w, " @staticmethod\n")
|
|
|
|
fmt.Fprintf(w, " def __key_warning(key: str):\n")
|
|
|
|
fmt.Fprintf(w, " suggest = None\n")
|
|
|
|
prefix := "if"
|
|
|
|
for _, prop := range props {
|
|
|
|
pname := PyName(prop.Name)
|
|
|
|
if pname == prop.Name {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " %s key == %q:\n", prefix, prop.Name)
|
|
|
|
fmt.Fprintf(w, " suggest = %q\n", pname)
|
|
|
|
prefix = "elif"
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
fmt.Fprintf(w, " if suggest:\n")
|
|
|
|
fmt.Fprintf(w, " pulumi.log.warn(f\"Key '{key}' not found in %s. Access the value via the '{suggest}' property getter instead.\")\n", name)
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
fmt.Fprintf(w, " def __getitem__(self, key: str) -> Any:\n")
|
|
|
|
fmt.Fprintf(w, " %s.__key_warning(key)\n", name)
|
|
|
|
fmt.Fprintf(w, " return super().__getitem__(key)\n")
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
fmt.Fprintf(w, " def get(self, key: str, default = None) -> Any:\n")
|
|
|
|
fmt.Fprintf(w, " %s.__key_warning(key)\n", name)
|
|
|
|
fmt.Fprintf(w, " return super().get(key, default)\n")
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
// Generate an __init__ method.
|
|
|
|
fmt.Fprintf(w, " def __init__(__self__")
|
|
|
|
// Bare `*` argument to force callers to use named arguments.
|
|
|
|
if len(props) > 0 {
|
|
|
|
fmt.Fprintf(w, ", *")
|
|
|
|
}
|
|
|
|
for _, prop := range props {
|
|
|
|
pname := PyName(prop.Name)
|
2021-04-19 23:05:23 +02:00
|
|
|
ty := mod.typeString(prop.Type, input, args && !prop.IsPlain, args && !prop.IsPlain, !prop.IsRequired, false /*acceptMapping*/)
|
2020-08-19 10:16:47 +02:00
|
|
|
var defaultValue string
|
|
|
|
if !prop.IsRequired {
|
|
|
|
defaultValue = " = None"
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, ",\n %s: %s%s", pname, ty, defaultValue)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "):\n")
|
2021-04-16 04:03:28 +02:00
|
|
|
mod.genTypeDocstring(w, comment, props, args)
|
2020-08-19 10:16:47 +02:00
|
|
|
if len(props) == 0 {
|
|
|
|
fmt.Fprintf(w, " pass\n")
|
|
|
|
}
|
|
|
|
for _, prop := range props {
|
|
|
|
pname := PyName(prop.Name)
|
|
|
|
var arg interface{}
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// Fill in computed defaults for arguments.
|
|
|
|
if prop.DefaultValue != nil {
|
|
|
|
dv, err := getDefaultValue(prop.DefaultValue, prop.Type)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " if %s is None:\n", pname)
|
|
|
|
fmt.Fprintf(w, " %s = %s\n", pname, dv)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that the property isn't deprecated.
|
|
|
|
if input && prop.DeprecationMessage != "" {
|
|
|
|
escaped := strings.ReplaceAll(prop.DeprecationMessage, `"`, `\"`)
|
|
|
|
fmt.Fprintf(w, " if %s is not None:\n", pname)
|
2020-11-09 15:28:28 +01:00
|
|
|
fmt.Fprintf(w, " warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n", escaped)
|
2021-02-26 06:22:04 +01:00
|
|
|
fmt.Fprintf(w, " pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n", pname, escaped)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// And add it to the dictionary.
|
|
|
|
arg = pname
|
|
|
|
|
|
|
|
if prop.ConstValue != nil {
|
|
|
|
arg, err = getConstValue(prop.ConstValue)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var indent string
|
|
|
|
if !prop.IsRequired {
|
|
|
|
fmt.Fprintf(w, " if %s is not None:\n", pname)
|
|
|
|
indent = " "
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "%s pulumi.set(__self__, \"%s\", %s)\n", indent, pname, arg)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
|
|
|
|
// Generate properties. Input types have getters and setters, output types only have getters.
|
2020-08-28 17:55:02 +02:00
|
|
|
mod.genProperties(w, props, input /*setters*/, func(prop *schema.Property) string {
|
2021-04-19 23:05:23 +02:00
|
|
|
return mod.typeString(prop.Type, input, args && !prop.IsPlain, args && !prop.IsPlain, !prop.IsRequired, false /*acceptMapping*/)
|
2020-08-28 17:55:02 +02:00
|
|
|
})
|
2020-08-19 10:16:47 +02:00
|
|
|
|
|
|
|
fmt.Fprintf(w, "\n")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
func getPrimitiveValue(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("'%s'", v.String()), nil
|
|
|
|
default:
|
|
|
|
return "", errors.Errorf("unsupported default value of type %T", value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-22 19:58:13 +02:00
|
|
|
func getConstValue(cv interface{}) (string, error) {
|
|
|
|
if cv == nil {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
return getPrimitiveValue(cv)
|
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
func getDefaultValue(dv *schema.DefaultValue, t schema.Type) (string, error) {
|
|
|
|
defaultValue := ""
|
|
|
|
if dv.Value != nil {
|
|
|
|
v, err := getPrimitiveValue(dv.Value)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defaultValue = v
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(dv.Environment) > 0 {
|
2020-07-15 22:10:52 +02:00
|
|
|
envFunc := "_utilities.get_env"
|
2020-01-21 23:45:48 +01:00
|
|
|
switch t {
|
|
|
|
case schema.BoolType:
|
2020-07-15 22:10:52 +02:00
|
|
|
envFunc = "_utilities.get_env_bool"
|
2020-01-21 23:45:48 +01:00
|
|
|
case schema.IntType:
|
2020-07-15 22:10:52 +02:00
|
|
|
envFunc = "_utilities.get_env_int"
|
2020-01-21 23:45:48 +01:00
|
|
|
case schema.NumberType:
|
2020-07-15 22:10:52 +02:00
|
|
|
envFunc = "_utilities.get_env_float"
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
envVars := fmt.Sprintf("'%s'", dv.Environment[0])
|
|
|
|
for _, e := range dv.Environment[1:] {
|
|
|
|
envVars += fmt.Sprintf(", '%s'", e)
|
|
|
|
}
|
|
|
|
if defaultValue == "" {
|
|
|
|
defaultValue = fmt.Sprintf("%s(%s)", envFunc, envVars)
|
|
|
|
} else {
|
|
|
|
defaultValue = fmt.Sprintf("(%s(%s) or %s)", envFunc, envVars, defaultValue)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return defaultValue, nil
|
|
|
|
}
|
|
|
|
|
2020-06-19 00:46:17 +02:00
|
|
|
func generateModuleContextMap(tool string, pkg *schema.Package, info PackageInfo, extraFiles map[string][]byte) (map[string]*modContext, error) {
|
2020-04-30 23:16:56 +02:00
|
|
|
// Build case mapping tables
|
|
|
|
snakeCaseToCamelCase, camelCaseToSnakeCase := map[string]string{}, map[string]string{}
|
2020-06-26 20:47:13 +02:00
|
|
|
seenTypes := codegen.Set{}
|
|
|
|
buildCaseMappingTables(pkg, snakeCaseToCamelCase, camelCaseToSnakeCase, seenTypes)
|
2020-04-30 23:16:56 +02:00
|
|
|
|
2021-05-06 20:18:14 +02:00
|
|
|
// determine whether to use the default Python package name
|
|
|
|
pyPkgName := info.PackageName
|
|
|
|
if pyPkgName == "" {
|
|
|
|
pyPkgName = fmt.Sprintf("pulumi_%s", strings.ReplaceAll(pkg.Name, "-", "_"))
|
|
|
|
}
|
|
|
|
|
2020-06-09 23:33:02 +02:00
|
|
|
// group resources, types, and functions into modules
|
2020-01-21 23:45:48 +01:00
|
|
|
modules := map[string]*modContext{}
|
|
|
|
|
2021-02-13 00:54:19 +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 {
|
|
|
|
mod = &modContext{
|
2021-02-13 00:54:19 +01:00
|
|
|
pkg: p,
|
2021-05-06 20:18:14 +02:00
|
|
|
pyPkgName: pyPkgName,
|
2020-01-21 23:45:48 +01:00
|
|
|
mod: modName,
|
|
|
|
tool: tool,
|
|
|
|
snakeCaseToCamelCase: snakeCaseToCamelCase,
|
|
|
|
camelCaseToSnakeCase: camelCaseToSnakeCase,
|
2020-06-09 23:33:02 +02:00
|
|
|
modNameOverrides: info.ModuleNameOverrides,
|
2020-06-10 22:55:10 +02:00
|
|
|
compatibility: info.Compatibility,
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2021-02-13 00:54:19 +01:00
|
|
|
if modName != "" && p == pkg {
|
2020-01-21 23:45:48 +01:00
|
|
|
parentName := path.Dir(modName)
|
2020-06-03 03:15:21 +02:00
|
|
|
if parentName == "." {
|
|
|
|
parentName = ""
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
2021-02-13 00:54:19 +01:00
|
|
|
parent := getMod(parentName, p)
|
2021-04-29 22:08:22 +02:00
|
|
|
parent.addChild(mod)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2021-02-13 00:54:19 +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
|
|
|
|
}
|
|
|
|
|
2021-02-13 00:54:19 +01:00
|
|
|
getModFromToken := func(tok string, p *schema.Package) *modContext {
|
|
|
|
modName := tokenToModule(tok, p, info.ModuleNameOverrides)
|
|
|
|
return getMod(modName, p)
|
2020-06-03 03:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// Create the config module if necessary.
|
2020-06-11 20:52:26 +02:00
|
|
|
if len(pkg.Config) > 0 &&
|
2020-06-22 17:38:20 +02:00
|
|
|
info.Compatibility != kubernetes20 { // k8s SDK doesn't use config.
|
2021-02-13 00:54:19 +01:00
|
|
|
configMod := getMod("config", pkg)
|
2020-08-20 20:07:09 +02:00
|
|
|
configMod.isConfig = true
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2021-04-20 01:40:39 +02:00
|
|
|
visitObjectTypes(pkg.Config, func(t schema.Type, _ bool) {
|
|
|
|
if t, ok := t.(*schema.ObjectType); ok {
|
|
|
|
getModFromToken(t.Token, t.Package).details(t).outputType = true
|
2020-09-23 21:39:25 +02:00
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
// Find input and output types referenced by resources.
|
2020-04-30 23:16:56 +02:00
|
|
|
scanResource := func(r *schema.Resource) {
|
2021-02-13 00:54:19 +01:00
|
|
|
mod := getModFromToken(r.Token, pkg)
|
2020-01-21 23:45:48 +01:00
|
|
|
mod.resources = append(mod.resources, r)
|
2021-04-20 01:40:39 +02:00
|
|
|
visitObjectTypes(r.Properties, func(t schema.Type, _ bool) {
|
2020-09-23 21:39:25 +02:00
|
|
|
switch T := t.(type) {
|
|
|
|
case *schema.ObjectType:
|
2021-02-13 00:54:19 +01:00
|
|
|
getModFromToken(T.Token, T.Package).details(T).outputType = true
|
2021-04-20 01:40:39 +02:00
|
|
|
getModFromToken(T.Token, T.Package).details(T).resourceOutputType = true
|
2020-09-23 21:39:25 +02:00
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
})
|
2021-04-20 01:40:39 +02:00
|
|
|
visitObjectTypes(r.InputProperties, func(t schema.Type, plain bool) {
|
2020-09-23 21:39:25 +02:00
|
|
|
switch T := t.(type) {
|
|
|
|
case *schema.ObjectType:
|
|
|
|
if r.IsProvider {
|
2021-02-13 00:54:19 +01:00
|
|
|
getModFromToken(T.Token, T.Package).details(T).outputType = true
|
2020-09-23 21:39:25 +02:00
|
|
|
}
|
2021-02-13 00:54:19 +01:00
|
|
|
getModFromToken(T.Token, T.Package).details(T).inputType = true
|
2021-04-20 01:40:39 +02:00
|
|
|
if plain {
|
|
|
|
getModFromToken(T.Token, T.Package).details(T).plainType = true
|
|
|
|
} else {
|
|
|
|
getModFromToken(T.Token, T.Package).details(T).argsType = true
|
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
if r.StateInputs != nil {
|
2021-04-20 01:40:39 +02:00
|
|
|
visitObjectTypes(r.StateInputs.Properties, func(t schema.Type, _ bool) {
|
2020-09-23 21:39:25 +02:00
|
|
|
switch T := t.(type) {
|
|
|
|
case *schema.ObjectType:
|
2021-02-13 00:54:19 +01:00
|
|
|
getModFromToken(T.Token, T.Package).details(T).inputType = true
|
2021-04-16 04:03:28 +02:00
|
|
|
getModFromToken(T.Token, T.Package).details(T).argsType = true
|
2020-09-23 21:39:25 +02:00
|
|
|
case *schema.ResourceType:
|
2021-02-13 00:54:19 +01:00
|
|
|
getModFromToken(T.Token, T.Resource.Package)
|
2020-09-23 21:39:25 +02:00
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
})
|
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2020-04-30 23:16:56 +02:00
|
|
|
scanResource(pkg.Provider)
|
2020-01-21 23:45:48 +01:00
|
|
|
for _, r := range pkg.Resources {
|
2020-04-30 23:16:56 +02:00
|
|
|
scanResource(r)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
// Find input and output types referenced by functions.
|
2020-01-21 23:45:48 +01:00
|
|
|
for _, f := range pkg.Functions {
|
2021-02-13 00:54:19 +01:00
|
|
|
mod := getModFromToken(f.Token, f.Package)
|
2020-01-21 23:45:48 +01:00
|
|
|
mod.functions = append(mod.functions, f)
|
2020-08-19 10:16:47 +02:00
|
|
|
if f.Inputs != nil {
|
2021-04-20 01:40:39 +02:00
|
|
|
visitObjectTypes(f.Inputs.Properties, func(t schema.Type, _ bool) {
|
2020-09-23 21:39:25 +02:00
|
|
|
switch T := t.(type) {
|
|
|
|
case *schema.ObjectType:
|
2021-02-13 00:54:19 +01:00
|
|
|
getModFromToken(T.Token, T.Package).details(T).inputType = true
|
2021-04-20 01:40:39 +02:00
|
|
|
getModFromToken(T.Token, T.Package).details(T).plainType = true
|
2020-09-23 21:39:25 +02:00
|
|
|
case *schema.ResourceType:
|
2021-02-13 00:54:19 +01:00
|
|
|
getModFromToken(T.Token, T.Resource.Package)
|
2020-09-23 21:39:25 +02:00
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
if f.Outputs != nil {
|
2021-04-20 01:40:39 +02:00
|
|
|
visitObjectTypes(f.Outputs.Properties, func(t schema.Type, _ bool) {
|
2020-09-23 21:39:25 +02:00
|
|
|
switch T := t.(type) {
|
|
|
|
case *schema.ObjectType:
|
2021-02-13 00:54:19 +01:00
|
|
|
getModFromToken(T.Token, T.Package).details(T).outputType = true
|
2021-04-20 01:40:39 +02:00
|
|
|
getModFromToken(T.Token, T.Package).details(T).plainType = true
|
2020-09-23 21:39:25 +02:00
|
|
|
case *schema.ResourceType:
|
2021-02-13 00:54:19 +01:00
|
|
|
getModFromToken(T.Token, T.Resource.Package)
|
2020-09-23 21:39:25 +02:00
|
|
|
}
|
2020-08-19 10:16:47 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find nested types.
|
|
|
|
for _, t := range pkg.Types {
|
2020-11-25 06:43:32 +01:00
|
|
|
switch typ := t.(type) {
|
|
|
|
case *schema.ObjectType:
|
2021-02-13 00:54:19 +01:00
|
|
|
mod := getModFromToken(typ.Token, typ.Package)
|
2020-11-25 06:43:32 +01:00
|
|
|
d := mod.details(typ)
|
2020-08-19 10:16:47 +02:00
|
|
|
if d.inputType || d.outputType {
|
2020-11-25 06:43:32 +01:00
|
|
|
mod.types = append(mod.types, typ)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
2020-11-25 06:43:32 +01:00
|
|
|
case *schema.EnumType:
|
2021-02-13 00:54:19 +01:00
|
|
|
mod := getModFromToken(typ.Token, pkg)
|
2020-11-25 06:43:32 +01:00
|
|
|
mod.enums = append(mod.enums, typ)
|
|
|
|
default:
|
|
|
|
continue
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
|
|
|
|
2020-06-19 00:46:17 +02:00
|
|
|
// Add python source files to the corresponding modules. Note that we only add the file names; the contents are
|
|
|
|
// still laid out manually in GeneratePackage.
|
|
|
|
for p := range extraFiles {
|
2020-06-17 04:27:06 +02:00
|
|
|
if path.Ext(p) != ".py" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
modName := path.Dir(p)
|
|
|
|
if modName == "/" || modName == "." {
|
|
|
|
modName = ""
|
|
|
|
}
|
2021-02-13 00:54:19 +01:00
|
|
|
mod := getMod(modName, pkg)
|
2020-06-17 04:27:06 +02:00
|
|
|
mod.extraSourceFiles = append(mod.extraSourceFiles, p)
|
2020-06-19 00:46:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return modules, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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. pulumi_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{"python": Importer}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
info, _ := pkg.Language["python"].(PackageInfo)
|
|
|
|
|
|
|
|
modules, err := generateModuleContextMap(tool, pkg, info, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for modName, mod := range modules {
|
|
|
|
if modName == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, r := range mod.resources {
|
|
|
|
packagePath := strings.Replace(modName, "/", ".", -1)
|
|
|
|
lr := LanguageResource{
|
|
|
|
Resource: r,
|
|
|
|
Package: packagePath,
|
|
|
|
Name: pyClassName(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) {
|
|
|
|
// Decode python-specific info
|
|
|
|
if err := pkg.ImportLanguages(map[string]schema.Language{"python": Importer}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
info, _ := pkg.Language["python"].(PackageInfo)
|
2020-01-21 23:45:48 +01:00
|
|
|
|
2020-06-19 00:46:17 +02:00
|
|
|
modules, err := generateModuleContextMap(tool, pkg, info, extraFiles)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-05-06 20:18:14 +02:00
|
|
|
pkgName := info.PackageName
|
|
|
|
if pkgName == "" {
|
|
|
|
pkgName = pyPack(pkg.Name)
|
|
|
|
}
|
|
|
|
|
2020-06-19 00:46:17 +02:00
|
|
|
files := fs{}
|
|
|
|
for p, f := range extraFiles {
|
2021-05-06 20:18:14 +02:00
|
|
|
files.add(filepath.Join(pkgName, p), f)
|
2020-01-21 23:45:48 +01:00
|
|
|
}
|
2020-06-17 04:27:06 +02:00
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
for _, mod := range modules {
|
|
|
|
if err := mod.gen(files); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-04 04:22:16 +01:00
|
|
|
// Generate pulumiplugin.json, if requested.
|
|
|
|
if info.EmitPulumiPluginFile {
|
|
|
|
plugin, err := genPulumiPluginFile(pkg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-05-06 20:18:14 +02:00
|
|
|
files.add(filepath.Join(pkgName, "pulumiplugin.json"), plugin)
|
2020-12-04 04:22:16 +01:00
|
|
|
}
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
// Finally emit the package metadata (setup.py).
|
2021-05-06 20:18:14 +02:00
|
|
|
setup, err := genPackageMetadata(tool, pkg, pkgName, info.EmitPulumiPluginFile, info.Requires)
|
2020-01-21 23:45:48 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
files.add("setup.py", []byte(setup))
|
|
|
|
|
|
|
|
return files, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
const utilitiesFile = `
|
2021-04-29 22:08:22 +02:00
|
|
|
import json
|
2020-01-21 23:45:48 +01:00
|
|
|
import os
|
2021-04-29 22:08:22 +02:00
|
|
|
import sys
|
|
|
|
import importlib.util
|
2020-01-21 23:45:48 +01:00
|
|
|
import pkg_resources
|
|
|
|
|
2021-04-29 22:08:22 +02:00
|
|
|
import pulumi
|
|
|
|
import pulumi.runtime
|
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
from semver import VersionInfo as SemverVersion
|
|
|
|
from parver import Version as PEP440Version
|
|
|
|
|
2020-06-11 00:11:59 +02:00
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
def get_env(*args):
|
|
|
|
for v in args:
|
|
|
|
value = os.getenv(v)
|
|
|
|
if value is not None:
|
|
|
|
return value
|
|
|
|
return None
|
|
|
|
|
2020-06-11 00:11:59 +02:00
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
def get_env_bool(*args):
|
|
|
|
str = get_env(*args)
|
|
|
|
if str is not None:
|
|
|
|
# NOTE: these values are taken from https://golang.org/src/strconv/atob.go?s=351:391#L1, which is what
|
|
|
|
# Terraform uses internally when parsing boolean values.
|
|
|
|
if str in ["1", "t", "T", "true", "TRUE", "True"]:
|
|
|
|
return True
|
|
|
|
if str in ["0", "f", "F", "false", "FALSE", "False"]:
|
|
|
|
return False
|
|
|
|
return None
|
|
|
|
|
2020-06-11 00:11:59 +02:00
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
def get_env_int(*args):
|
|
|
|
str = get_env(*args)
|
|
|
|
if str is not None:
|
|
|
|
try:
|
|
|
|
return int(str)
|
|
|
|
except:
|
|
|
|
return None
|
|
|
|
return None
|
|
|
|
|
2020-06-11 00:11:59 +02:00
|
|
|
|
2020-01-21 23:45:48 +01:00
|
|
|
def get_env_float(*args):
|
|
|
|
str = get_env(*args)
|
|
|
|
if str is not None:
|
|
|
|
try:
|
|
|
|
return float(str)
|
|
|
|
except:
|
|
|
|
return None
|
|
|
|
return None
|
|
|
|
|
2020-06-11 00:11:59 +02:00
|
|
|
|
2020-12-02 22:45:25 +01:00
|
|
|
def get_semver_version():
|
2020-01-21 23:45:48 +01:00
|
|
|
# __name__ is set to the fully-qualified name of the current module, In our case, it will be
|
2020-07-15 22:10:52 +02:00
|
|
|
# <some module>._utilities. <some module> is the module we want to query the version for.
|
2020-01-21 23:45:48 +01:00
|
|
|
root_package, *rest = __name__.split('.')
|
|
|
|
|
|
|
|
# pkg_resources uses setuptools to inspect the set of installed packages. We use it here to ask
|
|
|
|
# for the currently installed version of the root package (i.e. us) and get its version.
|
|
|
|
|
|
|
|
# Unfortunately, PEP440 and semver differ slightly in incompatible ways. The Pulumi engine expects
|
|
|
|
# to receive a valid semver string when receiving requests from the language host, so it's our
|
|
|
|
# responsibility as the library to convert our own PEP440 version into a valid semver string.
|
|
|
|
|
|
|
|
pep440_version_string = pkg_resources.require(root_package)[0].version
|
|
|
|
pep440_version = PEP440Version.parse(pep440_version_string)
|
|
|
|
(major, minor, patch) = pep440_version.release
|
|
|
|
prerelease = None
|
|
|
|
if pep440_version.pre_tag == 'a':
|
|
|
|
prerelease = f"alpha.{pep440_version.pre}"
|
|
|
|
elif pep440_version.pre_tag == 'b':
|
|
|
|
prerelease = f"beta.{pep440_version.pre}"
|
|
|
|
elif pep440_version.pre_tag == 'rc':
|
|
|
|
prerelease = f"rc.{pep440_version.pre}"
|
|
|
|
elif pep440_version.dev is not None:
|
|
|
|
prerelease = f"dev.{pep440_version.dev}"
|
|
|
|
|
|
|
|
# The only significant difference between PEP440 and semver as it pertains to us is that PEP440 has explicit support
|
|
|
|
# for dev builds, while semver encodes them as "prerelease" versions. In order to bridge between the two, we convert
|
|
|
|
# our dev build version into a prerelease tag. This matches what all of our other packages do when constructing
|
|
|
|
# their own semver string.
|
2020-12-02 22:45:25 +01:00
|
|
|
return SemverVersion(major=major, minor=minor, patch=patch, prerelease=prerelease)
|
|
|
|
|
2021-04-02 19:09:17 +02:00
|
|
|
|
2020-12-02 22:45:25 +01:00
|
|
|
def get_version():
|
2021-04-02 19:09:17 +02:00
|
|
|
return str(get_semver_version())
|
|
|
|
|
|
|
|
|
|
|
|
def get_resource_args_opts(resource_args_type, resource_options_type, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Return the resource args and options given the *args and **kwargs of a resource's
|
|
|
|
__init__ method.
|
|
|
|
"""
|
|
|
|
|
|
|
|
resource_args, opts = None, None
|
|
|
|
|
|
|
|
# If the first item is the resource args type, save it and remove it from the args list.
|
|
|
|
if args and isinstance(args[0], resource_args_type):
|
|
|
|
resource_args, args = args[0], args[1:]
|
|
|
|
|
|
|
|
# Now look at the first item in the args list again.
|
|
|
|
# If the first item is the resource options class, save it.
|
|
|
|
if args and isinstance(args[0], resource_options_type):
|
|
|
|
opts = args[0]
|
|
|
|
|
|
|
|
# If resource_args is None, see if "args" is in kwargs, and, if so, if it's typed as the
|
|
|
|
# the resource args type.
|
|
|
|
if resource_args is None:
|
|
|
|
a = kwargs.get("args")
|
|
|
|
if isinstance(a, resource_args_type):
|
|
|
|
resource_args = a
|
|
|
|
|
|
|
|
# If opts is None, look it up in kwargs.
|
|
|
|
if opts is None:
|
|
|
|
opts = kwargs.get("opts")
|
|
|
|
|
|
|
|
return resource_args, opts
|
2021-04-29 22:08:22 +02:00
|
|
|
|
|
|
|
|
2021-05-12 04:48:08 +02:00
|
|
|
# Temporary: just use pulumi._utils.lazy_import once everyone upgrades.
|
2021-04-29 22:08:22 +02:00
|
|
|
def lazy_import(fullname):
|
|
|
|
|
2021-05-12 04:48:08 +02:00
|
|
|
import pulumi._utils as u
|
|
|
|
f = getattr(u, 'lazy_import', None)
|
|
|
|
if f is None:
|
|
|
|
f = _lazy_import_temp
|
|
|
|
|
|
|
|
return f(fullname)
|
|
|
|
|
|
|
|
|
|
|
|
# Copied from pulumi._utils.lazy_import, see comments there.
|
|
|
|
def _lazy_import_temp(fullname):
|
|
|
|
m = sys.modules.get(fullname, None)
|
|
|
|
if m is not None:
|
|
|
|
return m
|
2021-04-29 22:08:22 +02:00
|
|
|
|
|
|
|
spec = importlib.util.find_spec(fullname)
|
2021-05-12 04:48:08 +02:00
|
|
|
|
|
|
|
m = sys.modules.get(fullname, None)
|
|
|
|
if m is not None:
|
|
|
|
return m
|
|
|
|
|
2021-04-29 22:08:22 +02:00
|
|
|
loader = importlib.util.LazyLoader(spec.loader)
|
|
|
|
spec.loader = loader
|
|
|
|
module = importlib.util.module_from_spec(spec)
|
2021-05-12 04:48:08 +02:00
|
|
|
|
|
|
|
m = sys.modules.get(fullname, None)
|
|
|
|
if m is not None:
|
|
|
|
return m
|
|
|
|
|
2021-04-29 22:08:22 +02:00
|
|
|
sys.modules[fullname] = module
|
|
|
|
loader.exec_module(module)
|
|
|
|
return module
|
|
|
|
|
|
|
|
|
|
|
|
class Package(pulumi.runtime.ResourcePackage):
|
|
|
|
_version = get_semver_version()
|
|
|
|
|
|
|
|
def __init__(self, pkg_info):
|
|
|
|
super().__init__()
|
|
|
|
self.pkg_info = pkg_info
|
|
|
|
|
|
|
|
def version(self):
|
|
|
|
return Package._version
|
|
|
|
|
|
|
|
def construct_provider(self, name: str, typ: str, urn: str) -> pulumi.ProviderResource:
|
|
|
|
if typ != self.pkg_info['token']:
|
|
|
|
raise Exception(f"unknown provider type {typ}")
|
|
|
|
Provider = getattr(lazy_import(self.pkg_info['fqn']), self.pkg_info['class'])
|
|
|
|
return Provider(name, pulumi.ResourceOptions(urn=urn))
|
|
|
|
|
|
|
|
|
|
|
|
class Module(pulumi.runtime.ResourceModule):
|
|
|
|
_version = get_semver_version()
|
|
|
|
|
|
|
|
def __init__(self, mod_info):
|
|
|
|
super().__init__()
|
|
|
|
self.mod_info = mod_info
|
|
|
|
|
|
|
|
def version(self):
|
|
|
|
return Module._version
|
|
|
|
|
|
|
|
def construct(self, name: str, typ: str, urn: str) -> pulumi.Resource:
|
|
|
|
class_name = self.mod_info['classes'].get(typ, None)
|
|
|
|
|
|
|
|
if class_name is None:
|
|
|
|
raise Exception(f"unknown resource type {typ}")
|
|
|
|
|
|
|
|
TheClass = getattr(lazy_import(self.mod_info['fqn']), class_name)
|
|
|
|
return TheClass(name, pulumi.ResourceOptions(urn=urn))
|
|
|
|
|
|
|
|
|
|
|
|
def register(resource_modules, resource_packages):
|
|
|
|
resource_modules = json.loads(resource_modules)
|
|
|
|
resource_packages = json.loads(resource_packages)
|
|
|
|
|
|
|
|
for pkg_info in resource_packages:
|
|
|
|
pulumi.runtime.register_resource_package(pkg_info['pkg'], Package(pkg_info))
|
|
|
|
|
|
|
|
for mod_info in resource_modules:
|
|
|
|
pulumi.runtime.register_resource_module(
|
|
|
|
mod_info['pkg'],
|
|
|
|
mod_info['mod'],
|
|
|
|
Module(mod_info))
|
2020-01-21 23:45:48 +01:00
|
|
|
`
|