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 {
|
2021-06-23 00:43:21 +02:00
|
|
|
// See if there's a manually-overridden module name.
|
2020-08-19 10:16:47 +02:00
|
|
|
canonicalModName := pkg.TokenToModule(tok)
|
|
|
|
if override, ok := moduleNameOverrides[canonicalModName]; ok {
|
2021-06-23 00:43:21 +02:00
|
|
|
return override
|
|
|
|
}
|
|
|
|
// A module can include fileparts, which we want to preserve.
|
|
|
|
var modName string
|
|
|
|
for i, part := range strings.Split(strings.ToLower(canonicalModName), "/") {
|
|
|
|
if i > 0 {
|
|
|
|
modName += "/"
|
|
|
|
}
|
|
|
|
modName += PyName(part)
|
2020-08-19 10:16:47 +02:00
|
|
|
}
|
|
|
|
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"
|
|
|
|
}
|
|
|
|
|
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 19:41:49 +02:00
|
|
|
resourceArgsName := fmt.Sprintf("%sArgs", name)
|
|
|
|
// Some providers (e.g. Kubernetes) have types with the same name as resources (e.g. StorageClass in Kubernetes).
|
|
|
|
// We've already shipped the input type (e.g. StorageClassArgs) in the same module as the resource, so we can't use
|
|
|
|
// the same name for the resource's args class. When an input type exists that would conflict with the name of the
|
|
|
|
// resource args class, we'll use a different name: `<Resource>InitArgs` instead of `<Resource>Args`.
|
|
|
|
const alternateSuffix = "InitArgs"
|
|
|
|
for _, t := range mod.types {
|
|
|
|
if mod.details(t).inputType {
|
|
|
|
if mod.unqualifiedObjectTypeName(t, true, true) == resourceArgsName {
|
|
|
|
resourceArgsName = name + alternateSuffix
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If we're using the alternate name, ensure the alternate name doesn't conflict with an input type.
|
|
|
|
if strings.HasSuffix(resourceArgsName, alternateSuffix) {
|
|
|
|
for _, t := range mod.types {
|
|
|
|
if mod.details(t).inputType {
|
|
|
|
if mod.unqualifiedObjectTypeName(t, true, true) == resourceArgsName {
|
|
|
|
return "", errors.Errorf(
|
|
|
|
"resource args class named %s in %s conflicts with input type", resourceArgsName, mod.mod)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-19 10:16:47 +02:00
|
|
|
// Export only the symbols we want exported.
|
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 19:41:49 +02:00
|
|
|
fmt.Fprintf(w, "__all__ = ['%s', '%s']\n\n", resourceArgsName, name)
|
2021-04-02 19:09:17 +02:00
|
|
|
|
|
|
|
// Produce an args class.
|
|
|
|
argsComment := fmt.Sprintf("The set of arguments for constructing a %s resource.", name)
|
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 19:41:49 +02:00
|
|
|
err := mod.genType(w, resourceArgsName, 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__")
|
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 19:41:49 +02:00
|
|
|
mod.genInitDocstring(w, res, resourceArgsName, false /*argsOverload*/)
|
2021-04-02 19:09:17 +02:00
|
|
|
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 {
|
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 19:41:49 +02:00
|
|
|
fmt.Fprintf(w, " args: Optional[%s] = None,\n", resourceArgsName)
|
2021-04-02 19:09:17 +02:00
|
|
|
} else {
|
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 19:41:49 +02:00
|
|
|
fmt.Fprintf(w, " args: %s,\n", resourceArgsName)
|
2021-04-02 19:09:17 +02:00
|
|
|
}
|
|
|
|
fmt.Fprintf(w, " opts: Optional[pulumi.ResourceOptions] = None):\n")
|
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 19:41:49 +02:00
|
|
|
mod.genInitDocstring(w, res, resourceArgsName, true /*argsOverload*/)
|
2021-04-02 19:09:17 +02:00
|
|
|
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")
|
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 19:41:49 +02:00
|
|
|
fmt.Fprintf(w, " resource_args, opts = _utilities.get_resource_args_opts(%s, pulumi.ResourceOptions, *args, **kwargs)\n", resourceArgsName)
|
2021-04-02 19:09:17 +02:00
|
|
|
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.
|
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 19:41:49 +02:00
|
|
|
fmt.Fprintf(w, " __props__ = %[1]s.__new__(%[1]s)\n\n", resourceArgsName)
|
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-05-27 00:00:51 +02:00
|
|
|
name := PyName(prop.Name)
|
|
|
|
if prop.Secret {
|
|
|
|
fmt.Fprintf(w, " __props__.__dict__[%[1]q] = None if %[2]s is None else pulumi.Output.secret(%[2]s)\n", name, arg)
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, " __props__.__dict__[%q] = %s\n", 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 {
|
2021-05-27 00:00:51 +02:00
|
|
|
fmt.Fprintf(w, ` secret_opts = pulumi.ResourceOptions(additional_secret_outputs=["%s"])`, strings.Join(secretProps, `", "`))
|
|
|
|
fmt.Fprintf(w, "\n opts = pulumi.ResourceOptions.merge(opts, secret_opts)\n")
|
2020-07-02 21:30:10 +02:00
|
|
|
}
|
|
|
|
|
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 {
|
2021-06-23 13:55:23 +02:00
|
|
|
pname := InitParamName(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.
|
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 19:41:49 +02:00
|
|
|
fmt.Fprintf(w, " __props__ = %[1]s.__new__(%[1]s)\n\n", resourceArgsName)
|
2021-04-07 21:35:19 +02:00
|
|
|
}
|
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-06-23 13:55:23 +02:00
|
|
|
fmt.Fprintf(w, " __props__.__dict__[%q] = %s\n", PyName(prop.Name), InitParamName(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")
|
2021-05-21 05:40:30 +02:00
|
|
|
fmt.Fprintf(w, " try:\n")
|
|
|
|
fmt.Fprintf(w, " with open('README.md', encoding='utf-8') as f:\n")
|
|
|
|
fmt.Fprintf(w, " return f.read()\n")
|
|
|
|
fmt.Fprintf(w, " except FileNotFoundError:\n")
|
|
|
|
fmt.Fprintf(w, " return \"%s Pulumi Package - Development Version\"\n", pkg.Name)
|
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.
|
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 19:41:49 +02:00
|
|
|
func (mod *modContext) genInitDocstring(w io.Writer, res *schema.Resource, resourceArgsName 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 {
|
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 19:41:49 +02:00
|
|
|
fmt.Fprintf(b, ":param %s args: The arguments to use to populate this resource's properties.\n",
|
|
|
|
resourceArgsName)
|
2021-04-02 19:09:17 +02:00
|
|
|
}
|
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 {
|
2021-06-23 13:55:23 +02:00
|
|
|
mod.genPropDocstring(b, InitParamName(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
|
|
|
|
2021-06-14 19:22:57 +02: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
|
|
|
|
2021-06-14 19:22:57 +02:00
|
|
|
# Determine the version once and cache the value, which measurably improves program performance.
|
|
|
|
_version = _get_semver_version()
|
|
|
|
_version_str = str(_version)
|
|
|
|
|
|
|
|
|
2020-12-02 22:45:25 +01:00
|
|
|
def get_version():
|
2021-06-14 19:22:57 +02:00
|
|
|
return _version_str
|
2021-04-02 19:09:17 +02:00
|
|
|
|
|
|
|
|
|
|
|
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):
|
|
|
|
def __init__(self, pkg_info):
|
|
|
|
super().__init__()
|
|
|
|
self.pkg_info = pkg_info
|
|
|
|
|
|
|
|
def version(self):
|
2021-06-14 19:22:57 +02:00
|
|
|
return _version
|
2021-04-29 22:08:22 +02:00
|
|
|
|
|
|
|
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):
|
|
|
|
def __init__(self, mod_info):
|
|
|
|
super().__init__()
|
|
|
|
self.mod_info = mod_info
|
|
|
|
|
|
|
|
def version(self):
|
2021-06-14 19:22:57 +02:00
|
|
|
return _version
|
2021-04-29 22:08:22 +02:00
|
|
|
|
|
|
|
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
|
|
|
`
|