diff --git a/changelog/pending/20221017--sdkgen-go-python--handle-hyphenated-names.yaml b/changelog/pending/20221017--sdkgen-go-python--handle-hyphenated-names.yaml new file mode 100644 index 000000000000..43ac9f52516f --- /dev/null +++ b/changelog/pending/20221017--sdkgen-go-python--handle-hyphenated-names.yaml @@ -0,0 +1,4 @@ +changes: +- type: fix + scope: sdkgen/go,python + description: Handle hypheneated names in go and python diff --git a/pkg/codegen/cgstrings/cgstrings.go b/pkg/codegen/cgstrings/cgstrings.go new file mode 100644 index 000000000000..716edc3979f7 --- /dev/null +++ b/pkg/codegen/cgstrings/cgstrings.go @@ -0,0 +1,63 @@ +// package cgstrings has various string processing functions that are useful during code generation. +package cgstrings + +import ( + "strings" + "unicode" +) + +// Unhyphenate removes all hyphens from s, then uppercasing the letter following each hyphen. +// For example, "abc-def-ghi" becomes "abcDefGhi". +func Unhyphenate(str string) string { + return ModifyStringAroundDelimeter(str, "-", UppercaseFirst) +} + +// Camel converts s to camelCase. +func Camel(s string) string { + if s == "" { + return "" + } + s = Unhyphenate(s) + runes := []rune(s) + res := make([]rune, 0, len(runes)) + for i, r := range runes { + if unicode.IsLower(r) { + res = append(res, runes[i:]...) + break + } + res = append(res, unicode.ToLower(r)) + } + return string(res) +} + +// UppercaseFirst uppercases the first letter of s. +// E.g. "abc" -> "Abc" +func UppercaseFirst(s string) string { + if s == "" { + return "" + } + runes := []rune(s) + runes[0] = unicode.ToUpper(runes[0]) + return string(runes) +} + +func ModifyStringAroundDelimeter(str, delim string, modifyNext func(next string) string) string { + if delim == "" { + return str + } + i := strings.Index(str, delim) + if i < 0 { + return str + } + nextIdx := i + len(delim) + if nextIdx >= len(str) { + // Nothing left after the delimeter, it's at the end of the string. + return str[:len(str)-len(delim)] + } + prev := str[:nextIdx-1] + next := str[nextIdx:] + if next != "" { + next = modifyNext(next) + } + return prev + ModifyStringAroundDelimeter(next, delim, modifyNext) +} diff --git a/pkg/codegen/cgstrings/cgstrings_test.go b/pkg/codegen/cgstrings/cgstrings_test.go new file mode 100644 index 000000000000..e3fa6a989dd2 --- /dev/null +++ b/pkg/codegen/cgstrings/cgstrings_test.go @@ -0,0 +1,46 @@ +package cgstrings + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCamel(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + assert.Equal("", Camel("")) + assert.Equal("plugh", Camel("plugh")) + assert.Equal("waldoThudFred", Camel("WaldoThudFred")) + assert.Equal("graultBaz", Camel("Grault-Baz")) + assert.Equal("graultBaz", Camel("grault-baz")) + assert.Equal("graultBaz", Camel("graultBaz")) + assert.Equal("grault_Baz", Camel("Grault_Baz")) + assert.Equal("graultBaz", Camel("Grault-baz")) +} + +func TestUnhyphenate(t *testing.T) { + t.Parallel() + testcases := []struct { + input, expected string + }{ + {"", ""}, + {"waldo", "waldo"}, + {"waldo-thud-fred", "waldoThudFred"}, + {"waldo-Thud-Fred", "waldoThudFred"}, + {"waldo-Thud-Fred-", "waldoThudFred"}, + {"-waldo-Thud-Fred", "WaldoThudFred"}, + {"waldoThudFred", "waldoThudFred"}, + {"WaldoThudFred", "WaldoThudFred"}, + } + for _, tc := range testcases { + tc := tc + t.Run(fmt.Sprintf("Subtest:%q", tc.input), func(t *testing.T) { + t.Parallel() + assert := assert.New(t) + assert.Equal(tc.expected, Unhyphenate(tc.input)) + }) + } +} diff --git a/pkg/codegen/go/gen.go b/pkg/codegen/go/gen.go index 30caa95aef78..5e1597aa5cbf 100644 --- a/pkg/codegen/go/gen.go +++ b/pkg/codegen/go/gen.go @@ -30,9 +30,9 @@ import ( "strconv" "strings" "sync" - "unicode" "github.com/pulumi/pulumi/pkg/v3/codegen" + "github.com/pulumi/pulumi/pkg/v3/codegen/cgstrings" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" @@ -93,24 +93,9 @@ func Title(s string) string { if s[0] == '$' { return Title(s[1:]) } - runes := []rune(s) - return string(append([]rune{unicode.ToUpper(runes[0])}, runes[1:]...)) -} - -func camel(s string) string { - if s == "" { - return "" - } - runes := []rune(s) - res := make([]rune, 0, len(runes)) - for i, r := range runes { - if unicode.IsLower(r) { - res = append(res, runes[i:]...) - break - } - res = append(res, unicode.ToLower(r)) - } - return string(res) + s = cgstrings.UppercaseFirst(s) + s = cgstrings.Unhyphenate(s) + return s } func tokenToPackage(pkg *schema.Package, overrides map[string]string, tok string) string { @@ -1216,7 +1201,8 @@ func (pkg *pkgContext) genEnumOutputTypes(w io.Writer, name, elementArgsType, el func (pkg *pkgContext) genEnumInputTypes(w io.Writer, name string, enumType *schema.EnumType, elementGoType string) { pkg.genInputInterface(w, name) - fmt.Fprintf(w, "var %sPtrType = reflect.TypeOf((**%s)(nil)).Elem()\n", camel(name), name) + typeName := cgstrings.Camel(name) + fmt.Fprintf(w, "var %sPtrType = reflect.TypeOf((**%s)(nil)).Elem()\n", typeName, name) fmt.Fprintln(w) fmt.Fprintf(w, "type %sPtrInput interface {\n", name) @@ -1226,25 +1212,25 @@ func (pkg *pkgContext) genEnumInputTypes(w io.Writer, name string, enumType *sch fmt.Fprintf(w, "}\n") fmt.Fprintln(w) - fmt.Fprintf(w, "type %sPtr %s\n", camel(name), elementGoType) + fmt.Fprintf(w, "type %sPtr %s\n", typeName, elementGoType) fmt.Fprintln(w) fmt.Fprintf(w, "func %[1]sPtr(v %[2]s) %[1]sPtrInput {\n", name, elementGoType) - fmt.Fprintf(w, "return (*%sPtr)(&v)\n", camel(name)) + fmt.Fprintf(w, "return (*%sPtr)(&v)\n", typeName) fmt.Fprintf(w, "}\n") fmt.Fprintln(w) - fmt.Fprintf(w, "func (*%sPtr) ElementType() reflect.Type {\n", camel(name)) - fmt.Fprintf(w, "return %sPtrType\n", camel(name)) + fmt.Fprintf(w, "func (*%sPtr) ElementType() reflect.Type {\n", typeName) + fmt.Fprintf(w, "return %sPtrType\n", typeName) fmt.Fprintf(w, "}\n") fmt.Fprintln(w) - fmt.Fprintf(w, "func (in *%[1]sPtr) To%[2]sPtrOutput() %[2]sPtrOutput {\n", camel(name), name) + fmt.Fprintf(w, "func (in *%[1]sPtr) To%[2]sPtrOutput() %[2]sPtrOutput {\n", typeName, name) fmt.Fprintf(w, "return pulumi.ToOutput(in).(%sPtrOutput)\n", name) fmt.Fprintf(w, "}\n") fmt.Fprintln(w) - fmt.Fprintf(w, "func (in *%[1]sPtr) To%[2]sPtrOutputWithContext(ctx context.Context) %[2]sPtrOutput {\n", camel(name), name) + fmt.Fprintf(w, "func (in *%[1]sPtr) To%[2]sPtrOutputWithContext(ctx context.Context) %[2]sPtrOutput {\n", cgstrings.Camel(name), name) fmt.Fprintf(w, "return pulumi.ToOutputWithContext(ctx, in).(%sPtrOutput)\n", name) fmt.Fprintf(w, "}\n") fmt.Fprintln(w) @@ -1311,7 +1297,7 @@ func (pkg *pkgContext) assignProperty(w io.Writer, p *schema.Property, object, v } fmt.Fprintf(w, "\t%s.%s = %s\n", object, Title(p.Name), value) } else if indirectAssign { - tmpName := camel(p.Name) + "_" + tmpName := cgstrings.Camel(p.Name) + "_" fmt.Fprintf(w, "%s := %s\n", tmpName, value) fmt.Fprintf(w, "%s.%s = &%s\n", object, Title(p.Name), tmpName) } else { @@ -1417,7 +1403,7 @@ func (pkg *pkgContext) genInputTypes(w io.Writer, t *schema.ObjectType, details if details.ptrInput { pkg.genInputInterface(w, name+"Ptr") - ptrTypeName := camel(name) + "PtrType" + ptrTypeName := cgstrings.Camel(name) + "PtrType" fmt.Fprintf(w, "type %s %sArgs\n\n", ptrTypeName, name) @@ -1827,7 +1813,7 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource, generateReso // Emit the state types for get methods. fmt.Fprintf(w, "// Input properties used for looking up and filtering %s resources.\n", name) - fmt.Fprintf(w, "type %sState struct {\n", camel(name)) + fmt.Fprintf(w, "type %sState struct {\n", cgstrings.Camel(name)) if r.StateInputs != nil { for _, p := range r.StateInputs.Properties { printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true) @@ -1846,12 +1832,12 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource, generateReso fmt.Fprintf(w, "}\n\n") fmt.Fprintf(w, "func (%sState) ElementType() reflect.Type {\n", name) - fmt.Fprintf(w, "\treturn reflect.TypeOf((*%sState)(nil)).Elem()\n", camel(name)) + fmt.Fprintf(w, "\treturn reflect.TypeOf((*%sState)(nil)).Elem()\n", cgstrings.Camel(name)) fmt.Fprintf(w, "}\n\n") } // Emit the args types. - fmt.Fprintf(w, "type %sArgs struct {\n", camel(name)) + fmt.Fprintf(w, "type %sArgs struct {\n", cgstrings.Camel(name)) for _, p := range r.InputProperties { printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true) fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", Title(p.Name), pkg.typeString(codegen.ResolvedType(p.Type)), p.Name) @@ -1877,7 +1863,7 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource, generateReso fmt.Fprintf(w, "}\n\n") fmt.Fprintf(w, "func (%sArgs) ElementType() reflect.Type {\n", name) - fmt.Fprintf(w, "\treturn reflect.TypeOf((*%sArgs)(nil)).Elem()\n", camel(name)) + fmt.Fprintf(w, "\treturn reflect.TypeOf((*%sArgs)(nil)).Elem()\n", cgstrings.Camel(name)) fmt.Fprintf(w, "}\n") // Emit resource methods. @@ -1929,7 +1915,7 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource, generateReso outputsType := "pulumi.AnyOutput" if f.Outputs != nil { if shouldLiftReturn { - outputsType = fmt.Sprintf("%s%sResultOutput", camel(name), methodName) + outputsType = fmt.Sprintf("%s%sResultOutput", cgstrings.Camel(name), methodName) } else { outputsType = fmt.Sprintf("%s%sResultOutput", name, methodName) } @@ -1944,7 +1930,7 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource, generateReso fmt.Fprintf(w, "\t}\n") // Get the name of the method to return the output - fmt.Fprintf(w, "\treturn %s.(%s).%s(), nil\n", resultVar, camel(outputsType), Title(f.Outputs.Properties[0].Name)) + fmt.Fprintf(w, "\treturn %s.(%s).%s(), nil\n", resultVar, cgstrings.Camel(outputsType), Title(f.Outputs.Properties[0].Name)) } else { // Check the error before proceeding. fmt.Fprintf(w, "\tif err != nil {\n") @@ -1959,7 +1945,7 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource, generateReso // If there are argument and/or return types, emit them. if len(args) > 0 { fmt.Fprintf(w, "\n") - fmt.Fprintf(w, "type %s%sArgs struct {\n", camel(name), methodName) + fmt.Fprintf(w, "type %s%sArgs struct {\n", cgstrings.Camel(name), methodName) for _, p := range args { printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, true) fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", Title(p.Name), pkg.typeString(codegen.ResolvedType(p.Type)), @@ -1976,7 +1962,7 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource, generateReso fmt.Fprintf(w, "}\n\n") fmt.Fprintf(w, "func (%s%sArgs) ElementType() reflect.Type {\n", name, methodName) - fmt.Fprintf(w, "\treturn reflect.TypeOf((*%s%sArgs)(nil)).Elem()\n", camel(name), methodName) + fmt.Fprintf(w, "\treturn reflect.TypeOf((*%s%sArgs)(nil)).Elem()\n", cgstrings.Camel(name), methodName) fmt.Fprintf(w, "}\n\n") } if f.Outputs != nil { @@ -1984,7 +1970,7 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource, generateReso // Don't export the result struct if we're lifting the value if shouldLiftReturn { - outputStructName = camel(name) + outputStructName = cgstrings.Camel(name) } fmt.Fprintf(w, "\n") @@ -2652,7 +2638,7 @@ func (pkg *pkgContext) genResourceRegistrations(w io.Writer, r *schema.Resource, for _, method := range r.Methods { if method.Function.Outputs != nil { if pkg.liftSingleValueMethodReturns && len(method.Function.Outputs.Properties) == 1 { - fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%s%sResultOutput{})\n", camel(name), Title(method.Name)) + fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%s%sResultOutput{})\n", cgstrings.Camel(name), Title(method.Name)) } else { fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%s%sResultOutput{})\n", name, Title(method.Name)) } @@ -2873,7 +2859,7 @@ func (pkg *pkgContext) genConfig(w io.Writer, variables []*schema.Property) erro } printCommentWithDeprecationMessage(w, p.Comment, p.DeprecationMessage, false) - configKey := fmt.Sprintf("\"%s:%s\"", pkg.pkg.Name, camel(p.Name)) + configKey := fmt.Sprintf("\"%s:%s\"", pkg.pkg.Name, cgstrings.Camel(p.Name)) fmt.Fprintf(w, "func Get%s(ctx *pulumi.Context) %s {\n", Title(p.Name), getType) if p.DefaultValue != nil { @@ -3230,11 +3216,11 @@ func generatePackageContextMap(tool string, pkg *schema.Package, goInfo GoPackag names = append(names, rawResourceName(r)+suffix+"Input") names = append(names, rawResourceName(r)+suffix+"Output") names = append(names, rawResourceName(r)+suffix+"Args") - names = append(names, camel(rawResourceName(r))+suffix+"Args") + names = append(names, cgstrings.Camel(rawResourceName(r))+suffix+"Args") names = append(names, "New"+rawResourceName(r)+suffix) if !r.IsProvider && !r.IsComponent { names = append(names, rawResourceName(r)+suffix+"State") - names = append(names, camel(rawResourceName(r))+suffix+"State") + names = append(names, cgstrings.Camel(rawResourceName(r))+suffix+"State") names = append(names, "Get"+rawResourceName(r)+suffix) } return names @@ -3644,7 +3630,7 @@ func GeneratePackage(tool string, pkg *schema.Package) (map[string][]byte, error return nil, err } - setFile(path.Join(mod, camel(rawResourceName(r))+".go"), buffer.String()) + setFile(path.Join(mod, cgstrings.Camel(rawResourceName(r))+".go"), buffer.String()) } // Functions @@ -3654,7 +3640,7 @@ func GeneratePackage(tool string, pkg *schema.Package) (map[string][]byte, error continue } - fileName := path.Join(mod, camel(tokenToName(f.Token))+".go") + fileName := path.Join(mod, cgstrings.Camel(tokenToName(f.Token))+".go") code, err := pkg.genFunctionCodeFile(f) if err != nil { return nil, err diff --git a/pkg/codegen/go/gen_test.go b/pkg/codegen/go/gen_test.go index 63a840e0226e..3f94022ab2d1 100644 --- a/pkg/codegen/go/gen_test.go +++ b/pkg/codegen/go/gen_test.go @@ -403,3 +403,16 @@ import ( } assert.Truef(t, found, `Didn't find a line that complies with "%v"`, autogenerated) } +func TestTitle(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + assert.Equal("", Title("")) + assert.Equal("Plugh", Title("plugh")) + assert.Equal("WaldoThudFred", Title("WaldoThudFred")) + assert.Equal("WaldoThudFred", Title("waldoThudFred")) + assert.Equal("WaldoThudFred", Title("waldo-Thud-Fred")) + assert.Equal("WaldoThudFred", Title("waldo-ThudFred")) + assert.Equal("WaldoThud_Fred", Title("waldo-Thud_Fred")) + assert.Equal("WaldoThud_Fred", Title("waldo-thud_Fred")) +} diff --git a/pkg/codegen/go/utilities.go b/pkg/codegen/go/utilities.go index 1c64461dfeda..b7f5635f6a3f 100644 --- a/pkg/codegen/go/utilities.go +++ b/pkg/codegen/go/utilities.go @@ -21,6 +21,7 @@ import ( "unicode" "github.com/pulumi/pulumi/pkg/v3/codegen" + "github.com/pulumi/pulumi/pkg/v3/codegen/cgstrings" ) // isReservedWord returns true if s is a Go reserved word as per @@ -87,7 +88,8 @@ func makeSafeEnumName(name, typeName string) (string, error) { } // Capitalize and make a valid identifier. - safeName = makeValidIdentifier(Title(safeName)) + safeName = enumTitle(safeName) + safeName = makeValidIdentifier(safeName) // If there are multiple underscores in a row, replace with one. regex := regexp.MustCompile(`_+`) @@ -102,3 +104,19 @@ func makeSafeEnumName(name, typeName string) (string, error) { return safeName, nil } + +// Title converts the input string to a title case +// where only the initial letter is upper-cased. +// It also removes $-prefix if any. +func enumTitle(s string) string { + if s == "" { + return "" + } + if s[0] == '$' { + return Title(s[1:]) + } + s = cgstrings.UppercaseFirst(s) + return cgstrings.ModifyStringAroundDelimeter(s, "-", func(next string) string { + return "_" + cgstrings.UppercaseFirst(next) + }) +} diff --git a/pkg/codegen/python/gen.go b/pkg/codegen/python/gen.go index c82bf3d45a97..c2af1f7aca9e 100644 --- a/pkg/codegen/python/gen.go +++ b/pkg/codegen/python/gen.go @@ -2439,6 +2439,7 @@ func (mod *modContext) genType(w io.Writer, name, comment string, properties []* suffix = "(dict)" } + name = pythonCase(name) fmt.Fprintf(w, "%s\n", decorator) fmt.Fprintf(w, "class %s%s:\n", name, suffix) if !input && comment != "" { diff --git a/pkg/codegen/python/utilities.go b/pkg/codegen/python/utilities.go index adc57bd4aa76..911bded0e697 100644 --- a/pkg/codegen/python/utilities.go +++ b/pkg/codegen/python/utilities.go @@ -9,6 +9,7 @@ import ( "github.com/blang/semver" "github.com/pulumi/pulumi/pkg/v3/codegen" + "github.com/pulumi/pulumi/pkg/v3/codegen/cgstrings" ) // isLegalIdentifierStart returns true if it is legal for c to be the first character of a Python identifier as per @@ -185,3 +186,17 @@ func pypiVersion(v semver.Version) string { } return fmt.Sprintf("%d.%d.%d%s%s%s%s", v.Major, v.Minor, v.Patch, release, dev, post, local) } + +// pythonCase converts s to PascalCase, ignoring underscores, e.g. __myWords -> __MyWords. +func pythonCase(s string) string { + var underscores string + noUnderscores := strings.TrimLeftFunc(s, func(r rune) bool { + if r != '_' { + return false + } + underscores += "_" + return true + }) + c := cgstrings.Unhyphenate(noUnderscores) + return underscores + cgstrings.UppercaseFirst(c) +} diff --git a/pkg/codegen/python/utilities_test.go b/pkg/codegen/python/utilities_test.go index 313df023733b..4fc5c0ff812f 100644 --- a/pkg/codegen/python/utilities_test.go +++ b/pkg/codegen/python/utilities_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/blang/semver" + "github.com/stretchr/testify/assert" "github.com/hashicorp/hcl/v2" "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax" @@ -101,3 +102,20 @@ func TestMakePyPiVersion(t *testing.T) { }) } } + +func TestPythonCase(t *testing.T) { + t.Parallel() + + tests := []struct{ input, expected string }{ + {"FOOBarInput", "FOOBarInput"}, + {"foo-bar", "FooBar"}, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.input, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, pythonCase(tt.input)) + }) + } +} diff --git a/pkg/codegen/testing/test/sdk_driver.go b/pkg/codegen/testing/test/sdk_driver.go index 0f1ca56f23ba..a105126bb887 100644 --- a/pkg/codegen/testing/test/sdk_driver.go +++ b/pkg/codegen/testing/test/sdk_driver.go @@ -309,6 +309,11 @@ var PulumiPulumiSDKTests = []*SDKTest{ Description: "Regresses pulumi/pulumi-terraform-bridge#611", Skip: allLanguages.Except("python/any").Union(codegen.NewStringSet("python/test", "python/py_compile")), }, + { + Directory: "hyphenated-symbols", + Description: "Test that types can have names with hyphens in them", + Skip: allLanguages.Except("go/any").Except("python/any"), + }, } var genSDKOnly bool diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/codegen-manifest.json b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/codegen-manifest.json new file mode 100644 index 000000000000..7bd728dc38c3 --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/codegen-manifest.json @@ -0,0 +1,11 @@ +{ + "emittedFiles": [ + "repro/doc.go", + "repro/foo.go", + "repro/init.go", + "repro/provider.go", + "repro/pulumi-plugin.json", + "repro/pulumiTypes.go", + "repro/pulumiUtilities.go" + ] +} diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/doc.go b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/doc.go new file mode 100644 index 000000000000..48ad71d390bd --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/doc.go @@ -0,0 +1,3 @@ +// Package repro exports types, functions, subpackages for provisioning repro resources. + +package repro diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/foo.go b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/foo.go new file mode 100644 index 000000000000..4a690ca5d88b --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/foo.go @@ -0,0 +1,202 @@ +// Code generated by test DO NOT EDIT. +// *** WARNING: Do not edit by hand unless you're certain you know what you are doing! *** + +package repro + +import ( + "context" + "reflect" + + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type Foo struct { + pulumi.CustomResourceState + + ConditionSets BarArrayArrayArrayOutput `pulumi:"conditionSets"` +} + +// NewFoo registers a new resource with the given unique name, arguments, and options. +func NewFoo(ctx *pulumi.Context, + name string, args *FooArgs, opts ...pulumi.ResourceOption) (*Foo, error) { + if args == nil { + args = &FooArgs{} + } + + var resource Foo + err := ctx.RegisterResource("repro:index:Foo", name, args, &resource, opts...) + if err != nil { + return nil, err + } + return &resource, nil +} + +// GetFoo gets an existing Foo resource's state with the given name, ID, and optional +// state properties that are used to uniquely qualify the lookup (nil if not required). +func GetFoo(ctx *pulumi.Context, + name string, id pulumi.IDInput, state *FooState, opts ...pulumi.ResourceOption) (*Foo, error) { + var resource Foo + err := ctx.ReadResource("repro:index:Foo", name, id, state, &resource, opts...) + if err != nil { + return nil, err + } + return &resource, nil +} + +// Input properties used for looking up and filtering Foo resources. +type fooState struct { +} + +type FooState struct { +} + +func (FooState) ElementType() reflect.Type { + return reflect.TypeOf((*fooState)(nil)).Elem() +} + +type fooArgs struct { +} + +// The set of arguments for constructing a Foo resource. +type FooArgs struct { +} + +func (FooArgs) ElementType() reflect.Type { + return reflect.TypeOf((*fooArgs)(nil)).Elem() +} + +type FooInput interface { + pulumi.Input + + ToFooOutput() FooOutput + ToFooOutputWithContext(ctx context.Context) FooOutput +} + +func (*Foo) ElementType() reflect.Type { + return reflect.TypeOf((**Foo)(nil)).Elem() +} + +func (i *Foo) ToFooOutput() FooOutput { + return i.ToFooOutputWithContext(context.Background()) +} + +func (i *Foo) ToFooOutputWithContext(ctx context.Context) FooOutput { + return pulumi.ToOutputWithContext(ctx, i).(FooOutput) +} + +// FooArrayInput is an input type that accepts FooArray and FooArrayOutput values. +// You can construct a concrete instance of `FooArrayInput` via: +// +// FooArray{ FooArgs{...} } +type FooArrayInput interface { + pulumi.Input + + ToFooArrayOutput() FooArrayOutput + ToFooArrayOutputWithContext(context.Context) FooArrayOutput +} + +type FooArray []FooInput + +func (FooArray) ElementType() reflect.Type { + return reflect.TypeOf((*[]*Foo)(nil)).Elem() +} + +func (i FooArray) ToFooArrayOutput() FooArrayOutput { + return i.ToFooArrayOutputWithContext(context.Background()) +} + +func (i FooArray) ToFooArrayOutputWithContext(ctx context.Context) FooArrayOutput { + return pulumi.ToOutputWithContext(ctx, i).(FooArrayOutput) +} + +// FooMapInput is an input type that accepts FooMap and FooMapOutput values. +// You can construct a concrete instance of `FooMapInput` via: +// +// FooMap{ "key": FooArgs{...} } +type FooMapInput interface { + pulumi.Input + + ToFooMapOutput() FooMapOutput + ToFooMapOutputWithContext(context.Context) FooMapOutput +} + +type FooMap map[string]FooInput + +func (FooMap) ElementType() reflect.Type { + return reflect.TypeOf((*map[string]*Foo)(nil)).Elem() +} + +func (i FooMap) ToFooMapOutput() FooMapOutput { + return i.ToFooMapOutputWithContext(context.Background()) +} + +func (i FooMap) ToFooMapOutputWithContext(ctx context.Context) FooMapOutput { + return pulumi.ToOutputWithContext(ctx, i).(FooMapOutput) +} + +type FooOutput struct{ *pulumi.OutputState } + +func (FooOutput) ElementType() reflect.Type { + return reflect.TypeOf((**Foo)(nil)).Elem() +} + +func (o FooOutput) ToFooOutput() FooOutput { + return o +} + +func (o FooOutput) ToFooOutputWithContext(ctx context.Context) FooOutput { + return o +} + +func (o FooOutput) ConditionSets() BarArrayArrayArrayOutput { + return o.ApplyT(func(v *Foo) BarArrayArrayArrayOutput { return v.ConditionSets }).(BarArrayArrayArrayOutput) +} + +type FooArrayOutput struct{ *pulumi.OutputState } + +func (FooArrayOutput) ElementType() reflect.Type { + return reflect.TypeOf((*[]*Foo)(nil)).Elem() +} + +func (o FooArrayOutput) ToFooArrayOutput() FooArrayOutput { + return o +} + +func (o FooArrayOutput) ToFooArrayOutputWithContext(ctx context.Context) FooArrayOutput { + return o +} + +func (o FooArrayOutput) Index(i pulumi.IntInput) FooOutput { + return pulumi.All(o, i).ApplyT(func(vs []interface{}) *Foo { + return vs[0].([]*Foo)[vs[1].(int)] + }).(FooOutput) +} + +type FooMapOutput struct{ *pulumi.OutputState } + +func (FooMapOutput) ElementType() reflect.Type { + return reflect.TypeOf((*map[string]*Foo)(nil)).Elem() +} + +func (o FooMapOutput) ToFooMapOutput() FooMapOutput { + return o +} + +func (o FooMapOutput) ToFooMapOutputWithContext(ctx context.Context) FooMapOutput { + return o +} + +func (o FooMapOutput) MapIndex(k pulumi.StringInput) FooOutput { + return pulumi.All(o, k).ApplyT(func(vs []interface{}) *Foo { + return vs[0].(map[string]*Foo)[vs[1].(string)] + }).(FooOutput) +} + +func init() { + pulumi.RegisterInputType(reflect.TypeOf((*FooInput)(nil)).Elem(), &Foo{}) + pulumi.RegisterInputType(reflect.TypeOf((*FooArrayInput)(nil)).Elem(), FooArray{}) + pulumi.RegisterInputType(reflect.TypeOf((*FooMapInput)(nil)).Elem(), FooMap{}) + pulumi.RegisterOutputType(FooOutput{}) + pulumi.RegisterOutputType(FooArrayOutput{}) + pulumi.RegisterOutputType(FooMapOutput{}) +} diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/init.go b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/init.go new file mode 100644 index 000000000000..687d50dd6e90 --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/init.go @@ -0,0 +1,62 @@ +// Code generated by test DO NOT EDIT. +// *** WARNING: Do not edit by hand unless you're certain you know what you are doing! *** + +package repro + +import ( + "fmt" + + "github.com/blang/semver" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type module struct { + version semver.Version +} + +func (m *module) Version() semver.Version { + return m.version +} + +func (m *module) Construct(ctx *pulumi.Context, name, typ, urn string) (r pulumi.Resource, err error) { + switch typ { + case "repro:index:Foo": + r = &Foo{} + default: + return nil, fmt.Errorf("unknown resource type: %s", typ) + } + + err = ctx.RegisterResource(typ, name, nil, r, pulumi.URN_(urn)) + return +} + +type pkg struct { + version semver.Version +} + +func (p *pkg) Version() semver.Version { + return p.version +} + +func (p *pkg) ConstructProvider(ctx *pulumi.Context, name, typ, urn string) (pulumi.ProviderResource, error) { + if typ != "pulumi:providers:repro" { + return nil, fmt.Errorf("unknown provider type: %s", typ) + } + + r := &Provider{} + err := ctx.RegisterResource(typ, name, nil, r, pulumi.URN_(urn)) + return r, err +} + +func init() { + version, _ := PkgVersion() + pulumi.RegisterResourceModule( + "repro", + "index", + &module{version}, + ) + pulumi.RegisterResourcePackage( + "repro", + &pkg{version}, + ) +} diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/provider.go b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/provider.go new file mode 100644 index 000000000000..89de8bdda5f4 --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/provider.go @@ -0,0 +1,79 @@ +// Code generated by test DO NOT EDIT. +// *** WARNING: Do not edit by hand unless you're certain you know what you are doing! *** + +package repro + +import ( + "context" + "reflect" + + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type Provider struct { + pulumi.ProviderResourceState +} + +// NewProvider registers a new resource with the given unique name, arguments, and options. +func NewProvider(ctx *pulumi.Context, + name string, args *ProviderArgs, opts ...pulumi.ResourceOption) (*Provider, error) { + if args == nil { + args = &ProviderArgs{} + } + + var resource Provider + err := ctx.RegisterResource("pulumi:providers:repro", name, args, &resource, opts...) + if err != nil { + return nil, err + } + return &resource, nil +} + +type providerArgs struct { +} + +// The set of arguments for constructing a Provider resource. +type ProviderArgs struct { +} + +func (ProviderArgs) ElementType() reflect.Type { + return reflect.TypeOf((*providerArgs)(nil)).Elem() +} + +type ProviderInput interface { + pulumi.Input + + ToProviderOutput() ProviderOutput + ToProviderOutputWithContext(ctx context.Context) ProviderOutput +} + +func (*Provider) ElementType() reflect.Type { + return reflect.TypeOf((**Provider)(nil)).Elem() +} + +func (i *Provider) ToProviderOutput() ProviderOutput { + return i.ToProviderOutputWithContext(context.Background()) +} + +func (i *Provider) ToProviderOutputWithContext(ctx context.Context) ProviderOutput { + return pulumi.ToOutputWithContext(ctx, i).(ProviderOutput) +} + +type ProviderOutput struct{ *pulumi.OutputState } + +func (ProviderOutput) ElementType() reflect.Type { + return reflect.TypeOf((**Provider)(nil)).Elem() +} + +func (o ProviderOutput) ToProviderOutput() ProviderOutput { + return o +} + +func (o ProviderOutput) ToProviderOutputWithContext(ctx context.Context) ProviderOutput { + return o +} + +func init() { + pulumi.RegisterInputType(reflect.TypeOf((*ProviderInput)(nil)).Elem(), &Provider{}) + pulumi.RegisterOutputType(ProviderOutput{}) +} diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/pulumi-plugin.json b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/pulumi-plugin.json new file mode 100644 index 000000000000..e3b81a20cf5c --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/pulumi-plugin.json @@ -0,0 +1,4 @@ +{ + "resource": true, + "name": "repro" +} diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/pulumiTypes.go b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/pulumiTypes.go new file mode 100644 index 000000000000..70cb715ed9cd --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/pulumiTypes.go @@ -0,0 +1,100 @@ +// Code generated by test DO NOT EDIT. +// *** WARNING: Do not edit by hand unless you're certain you know what you are doing! *** + +package repro + +import ( + "context" + "reflect" + + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type Bar struct { + HasAHyphen *string `pulumi:"has-a-hyphen"` +} + +type BarOutput struct{ *pulumi.OutputState } + +func (BarOutput) ElementType() reflect.Type { + return reflect.TypeOf((*Bar)(nil)).Elem() +} + +func (o BarOutput) ToBarOutput() BarOutput { + return o +} + +func (o BarOutput) ToBarOutputWithContext(ctx context.Context) BarOutput { + return o +} + +func (o BarOutput) HasAHyphen() pulumi.StringPtrOutput { + return o.ApplyT(func(v Bar) *string { return v.HasAHyphen }).(pulumi.StringPtrOutput) +} + +type BarArrayOutput struct{ *pulumi.OutputState } + +func (BarArrayOutput) ElementType() reflect.Type { + return reflect.TypeOf((*[]Bar)(nil)).Elem() +} + +func (o BarArrayOutput) ToBarArrayOutput() BarArrayOutput { + return o +} + +func (o BarArrayOutput) ToBarArrayOutputWithContext(ctx context.Context) BarArrayOutput { + return o +} + +func (o BarArrayOutput) Index(i pulumi.IntInput) BarOutput { + return pulumi.All(o, i).ApplyT(func(vs []interface{}) Bar { + return vs[0].([]Bar)[vs[1].(int)] + }).(BarOutput) +} + +type BarArrayArrayOutput struct{ *pulumi.OutputState } + +func (BarArrayArrayOutput) ElementType() reflect.Type { + return reflect.TypeOf((*[][]Bar)(nil)).Elem() +} + +func (o BarArrayArrayOutput) ToBarArrayArrayOutput() BarArrayArrayOutput { + return o +} + +func (o BarArrayArrayOutput) ToBarArrayArrayOutputWithContext(ctx context.Context) BarArrayArrayOutput { + return o +} + +func (o BarArrayArrayOutput) Index(i pulumi.IntInput) BarArrayOutput { + return pulumi.All(o, i).ApplyT(func(vs []interface{}) []Bar { + return vs[0].([][]Bar)[vs[1].(int)] + }).(BarArrayOutput) +} + +type BarArrayArrayArrayOutput struct{ *pulumi.OutputState } + +func (BarArrayArrayArrayOutput) ElementType() reflect.Type { + return reflect.TypeOf((*[][][]Bar)(nil)).Elem() +} + +func (o BarArrayArrayArrayOutput) ToBarArrayArrayArrayOutput() BarArrayArrayArrayOutput { + return o +} + +func (o BarArrayArrayArrayOutput) ToBarArrayArrayArrayOutputWithContext(ctx context.Context) BarArrayArrayArrayOutput { + return o +} + +func (o BarArrayArrayArrayOutput) Index(i pulumi.IntInput) BarArrayArrayOutput { + return pulumi.All(o, i).ApplyT(func(vs []interface{}) [][]Bar { + return vs[0].([][][]Bar)[vs[1].(int)] + }).(BarArrayArrayOutput) +} + +func init() { + pulumi.RegisterOutputType(BarOutput{}) + pulumi.RegisterOutputType(BarArrayOutput{}) + pulumi.RegisterOutputType(BarArrayArrayOutput{}) + pulumi.RegisterOutputType(BarArrayArrayArrayOutput{}) +} diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/pulumiUtilities.go b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/pulumiUtilities.go new file mode 100644 index 000000000000..261284bce66e --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/go/repro/pulumiUtilities.go @@ -0,0 +1,87 @@ +// Code generated by test DO NOT EDIT. +// *** WARNING: Do not edit by hand unless you're certain you know what you are doing! *** + +package repro + +import ( + "fmt" + "os" + "reflect" + "regexp" + "strconv" + "strings" + + "github.com/blang/semver" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type envParser func(v string) interface{} + +func parseEnvBool(v string) interface{} { + b, err := strconv.ParseBool(v) + if err != nil { + return nil + } + return b +} + +func parseEnvInt(v string) interface{} { + i, err := strconv.ParseInt(v, 0, 0) + if err != nil { + return nil + } + return int(i) +} + +func parseEnvFloat(v string) interface{} { + f, err := strconv.ParseFloat(v, 64) + if err != nil { + return nil + } + return f +} + +func parseEnvStringArray(v string) interface{} { + var result pulumi.StringArray + for _, item := range strings.Split(v, ";") { + result = append(result, pulumi.String(item)) + } + return result +} + +func getEnvOrDefault(def interface{}, parser envParser, vars ...string) interface{} { + for _, v := range vars { + if value := os.Getenv(v); value != "" { + if parser != nil { + return parser(value) + } + return value + } + } + return def +} + +// PkgVersion uses reflection to determine the version of the current package. +// If a version cannot be determined, v1 will be assumed. The second return +// value is always nil. +func PkgVersion() (semver.Version, error) { + type sentinal struct{} + pkgPath := reflect.TypeOf(sentinal{}).PkgPath() + re := regexp.MustCompile("^.*/pulumi-repro/sdk(/v\\d+)?") + if match := re.FindStringSubmatch(pkgPath); match != nil { + vStr := match[1] + if len(vStr) == 0 { // If the version capture group was empty, default to v1. + return semver.Version{Major: 1}, nil + } + return semver.MustParse(fmt.Sprintf("%s.0.0", vStr[2:])), nil + } + return semver.Version{Major: 1}, nil +} + +// isZero is a null safe check for if a value is it's types zero value. +func isZero(v interface{}) bool { + if v == nil { + return true + } + return reflect.ValueOf(v).IsZero() +} diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/codegen-manifest.json b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/codegen-manifest.json new file mode 100644 index 000000000000..2e9d126a79b1 --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/codegen-manifest.json @@ -0,0 +1,13 @@ +{ + "emittedFiles": [ + "pulumi_repro/README.md", + "pulumi_repro/__init__.py", + "pulumi_repro/_utilities.py", + "pulumi_repro/foo.py", + "pulumi_repro/outputs.py", + "pulumi_repro/provider.py", + "pulumi_repro/pulumi-plugin.json", + "pulumi_repro/py.typed", + "setup.py" + ] +} diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/README.md b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/__init__.py b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/__init__.py new file mode 100644 index 000000000000..5d60eac58359 --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/__init__.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# *** WARNING: this file was generated by test. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + +from . import _utilities +import typing +# Export this package's modules as members: +from .foo import * +from .provider import * +from . import outputs +_utilities.register( + resource_modules=""" +[ + { + "pkg": "repro", + "mod": "index", + "fqn": "pulumi_repro", + "classes": { + "repro:index:Foo": "Foo" + } + } +] +""", + resource_packages=""" +[ + { + "pkg": "repro", + "token": "pulumi:providers:repro", + "fqn": "pulumi_repro", + "class": "Provider" + } +] +""" +) diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/_utilities.py b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/_utilities.py new file mode 100644 index 000000000000..be001eda26c9 --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/_utilities.py @@ -0,0 +1,250 @@ +# coding=utf-8 +# *** WARNING: this file was generated by test. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + + +import importlib.util +import inspect +import json +import os +import pkg_resources +import sys +import typing + +import pulumi +import pulumi.runtime + +from semver import VersionInfo as SemverVersion +from parver import Version as PEP440Version + + +def get_env(*args): + for v in args: + value = os.getenv(v) + if value is not None: + return value + return None + + +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 + + +def get_env_int(*args): + str = get_env(*args) + if str is not None: + try: + return int(str) + except: + return None + return None + + +def get_env_float(*args): + str = get_env(*args) + if str is not None: + try: + return float(str) + except: + return None + return None + + +def _get_semver_version(): + # __name__ is set to the fully-qualified name of the current module, In our case, it will be + # ._utilities. is the module we want to query the version for. + 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. + return SemverVersion(major=major, minor=minor, patch=patch, prerelease=prerelease) + + +# Determine the version once and cache the value, which measurably improves program performance. +_version = _get_semver_version() +_version_str = str(_version) + + +def get_version(): + return _version_str + +def get_resource_opts_defaults() -> pulumi.ResourceOptions: + return pulumi.ResourceOptions( + version=get_version(), + plugin_download_url=get_plugin_download_url(), + ) + +def get_invoke_opts_defaults() -> pulumi.InvokeOptions: + return pulumi.InvokeOptions( + version=get_version(), + plugin_download_url=get_plugin_download_url(), + ) + +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 + + +# Temporary: just use pulumi._utils.lazy_import once everyone upgrades. +def lazy_import(fullname): + + 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 + + spec = importlib.util.find_spec(fullname) + + m = sys.modules.get(fullname, None) + if m is not None: + return m + + loader = importlib.util.LazyLoader(spec.loader) + spec.loader = loader + module = importlib.util.module_from_spec(spec) + + m = sys.modules.get(fullname, None) + if m is not None: + return m + + 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): + return _version + + def construct_provider(self, name: str, typ: str, urn: str) -> pulumi.ProviderResource: + if typ != self.pkg_info['token']: + raise Exception(f"unknown provider type {typ}") + Provider = getattr(lazy_import(self.pkg_info['fqn']), self.pkg_info['class']) + return Provider(name, pulumi.ResourceOptions(urn=urn)) + + +class Module(pulumi.runtime.ResourceModule): + def __init__(self, mod_info): + super().__init__() + self.mod_info = mod_info + + def version(self): + return _version + + def construct(self, name: str, typ: str, urn: str) -> pulumi.Resource: + class_name = self.mod_info['classes'].get(typ, None) + + if class_name is None: + raise Exception(f"unknown resource type {typ}") + + TheClass = getattr(lazy_import(self.mod_info['fqn']), class_name) + return TheClass(name, pulumi.ResourceOptions(urn=urn)) + + +def register(resource_modules, resource_packages): + resource_modules = json.loads(resource_modules) + resource_packages = json.loads(resource_packages) + + for pkg_info in resource_packages: + pulumi.runtime.register_resource_package(pkg_info['pkg'], Package(pkg_info)) + + for mod_info in resource_modules: + pulumi.runtime.register_resource_module( + mod_info['pkg'], + mod_info['mod'], + Module(mod_info)) + + +_F = typing.TypeVar('_F', bound=typing.Callable[..., typing.Any]) + + +def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]: + """Decorator internally used on {fn}_output lifted function versions + to implement them automatically from the un-lifted function.""" + + func_sig = inspect.signature(func) + + def lifted_func(*args, opts=None, **kwargs): + bound_args = func_sig.bind(*args, **kwargs) + # Convert tuple to list, see pulumi/pulumi#8172 + args_list = list(bound_args.args) + return pulumi.Output.from_input({ + 'args': args_list, + 'kwargs': bound_args.kwargs + }).apply(lambda resolved_args: func(*resolved_args['args'], + opts=opts, + **resolved_args['kwargs'])) + + return (lambda _: lifted_func) + +def get_plugin_download_url(): + return None diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/foo.py b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/foo.py new file mode 100644 index 000000000000..794dfea5f121 --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/foo.py @@ -0,0 +1,97 @@ +# coding=utf-8 +# *** WARNING: this file was generated by test. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + +import copy +import warnings +import pulumi +import pulumi.runtime +from typing import Any, Mapping, Optional, Sequence, Union, overload +from . import _utilities +from . import outputs + +__all__ = ['FooArgs', 'Foo'] + +@pulumi.input_type +class FooArgs: + def __init__(__self__): + """ + The set of arguments for constructing a Foo resource. + """ + pass + + +class Foo(pulumi.CustomResource): + @overload + def __init__(__self__, + resource_name: str, + opts: Optional[pulumi.ResourceOptions] = None, + __props__=None): + """ + Create a Foo resource with the given unique name, props, and options. + :param str resource_name: The name of the resource. + :param pulumi.ResourceOptions opts: Options for the resource. + """ + ... + @overload + def __init__(__self__, + resource_name: str, + args: Optional[FooArgs] = None, + opts: Optional[pulumi.ResourceOptions] = None): + """ + Create a Foo resource with the given unique name, props, and options. + :param str resource_name: The name of the resource. + :param FooArgs args: The arguments to use to populate this resource's properties. + :param pulumi.ResourceOptions opts: Options for the resource. + """ + ... + def __init__(__self__, resource_name: str, *args, **kwargs): + resource_args, opts = _utilities.get_resource_args_opts(FooArgs, pulumi.ResourceOptions, *args, **kwargs) + if resource_args is not None: + __self__._internal_init(resource_name, opts, **resource_args.__dict__) + else: + __self__._internal_init(resource_name, *args, **kwargs) + + def _internal_init(__self__, + resource_name: str, + opts: Optional[pulumi.ResourceOptions] = None, + __props__=None): + opts = pulumi.ResourceOptions.merge(_utilities.get_resource_opts_defaults(), opts) + if not isinstance(opts, pulumi.ResourceOptions): + raise TypeError('Expected resource options to be a ResourceOptions instance') + if opts.id is None: + if __props__ is not None: + raise TypeError('__props__ is only valid when passed in combination with a valid opts.id to get an existing resource') + __props__ = FooArgs.__new__(FooArgs) + + __props__.__dict__["condition_sets"] = None + super(Foo, __self__).__init__( + 'repro:index:Foo', + resource_name, + __props__, + opts) + + @staticmethod + def get(resource_name: str, + id: pulumi.Input[str], + opts: Optional[pulumi.ResourceOptions] = None) -> 'Foo': + """ + Get an existing Foo resource's state with the given name, id, and optional extra + properties used to qualify the lookup. + + :param str resource_name: The unique name of the resulting resource. + :param pulumi.Input[str] id: The unique provider ID of the resource to lookup. + :param pulumi.ResourceOptions opts: Options for the resource. + """ + opts = pulumi.ResourceOptions.merge(opts, pulumi.ResourceOptions(id=id)) + + __props__ = FooArgs.__new__(FooArgs) + + __props__.__dict__["condition_sets"] = None + return Foo(resource_name, opts=opts, __props__=__props__) + + @property + @pulumi.getter(name="conditionSets") + def condition_sets(self) -> pulumi.Output[Optional[Sequence[Sequence[Sequence['outputs.Bar']]]]]: + return pulumi.get(self, "condition_sets") + diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/outputs.py b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/outputs.py new file mode 100644 index 000000000000..a8b8cedbaf12 --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/outputs.py @@ -0,0 +1,45 @@ +# coding=utf-8 +# *** WARNING: this file was generated by test. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + +import copy +import warnings +import pulumi +import pulumi.runtime +from typing import Any, Mapping, Optional, Sequence, Union, overload +from . import _utilities + +__all__ = [ + 'Bar', +] + +@pulumi.output_type +class Bar(dict): + @staticmethod + def __key_warning(key: str): + suggest = None + if key == "has-a-hyphen": + suggest = "has_a_hyphen" + + if suggest: + pulumi.log.warn(f"Key '{key}' not found in Bar. Access the value via the '{suggest}' property getter instead.") + + def __getitem__(self, key: str) -> Any: + Bar.__key_warning(key) + return super().__getitem__(key) + + def get(self, key: str, default = None) -> Any: + Bar.__key_warning(key) + return super().get(key, default) + + def __init__(__self__, *, + has_a_hyphen: Optional[str] = None): + if has_a_hyphen is not None: + pulumi.set(__self__, "has_a_hyphen", has_a_hyphen) + + @property + @pulumi.getter(name="has-a-hyphen") + def has_a_hyphen(self) -> Optional[str]: + return pulumi.get(self, "has_a_hyphen") + + diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/provider.py b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/provider.py new file mode 100644 index 000000000000..04fbba6abd0e --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/provider.py @@ -0,0 +1,71 @@ +# coding=utf-8 +# *** WARNING: this file was generated by test. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + +import copy +import warnings +import pulumi +import pulumi.runtime +from typing import Any, Mapping, Optional, Sequence, Union, overload +from . import _utilities + +__all__ = ['ProviderArgs', 'Provider'] + +@pulumi.input_type +class ProviderArgs: + def __init__(__self__): + """ + The set of arguments for constructing a Provider resource. + """ + pass + + +class Provider(pulumi.ProviderResource): + @overload + def __init__(__self__, + resource_name: str, + opts: Optional[pulumi.ResourceOptions] = None, + __props__=None): + """ + Create a Repro resource with the given unique name, props, and options. + :param str resource_name: The name of the resource. + :param pulumi.ResourceOptions opts: Options for the resource. + """ + ... + @overload + def __init__(__self__, + resource_name: str, + args: Optional[ProviderArgs] = None, + opts: Optional[pulumi.ResourceOptions] = None): + """ + Create a Repro resource with the given unique name, props, and options. + :param str resource_name: The name of the resource. + :param ProviderArgs args: The arguments to use to populate this resource's properties. + :param pulumi.ResourceOptions opts: Options for the resource. + """ + ... + def __init__(__self__, resource_name: str, *args, **kwargs): + resource_args, opts = _utilities.get_resource_args_opts(ProviderArgs, pulumi.ResourceOptions, *args, **kwargs) + if resource_args is not None: + __self__._internal_init(resource_name, opts, **resource_args.__dict__) + else: + __self__._internal_init(resource_name, *args, **kwargs) + + def _internal_init(__self__, + resource_name: str, + opts: Optional[pulumi.ResourceOptions] = None, + __props__=None): + opts = pulumi.ResourceOptions.merge(_utilities.get_resource_opts_defaults(), opts) + if not isinstance(opts, pulumi.ResourceOptions): + raise TypeError('Expected resource options to be a ResourceOptions instance') + if opts.id is None: + if __props__ is not None: + raise TypeError('__props__ is only valid when passed in combination with a valid opts.id to get an existing resource') + __props__ = ProviderArgs.__new__(ProviderArgs) + + super(Provider, __self__).__init__( + 'repro', + resource_name, + __props__, + opts) + diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/pulumi-plugin.json b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/pulumi-plugin.json new file mode 100644 index 000000000000..e3b81a20cf5c --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/pulumi-plugin.json @@ -0,0 +1,4 @@ +{ + "resource": true, + "name": "repro" +} diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/py.typed b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/pulumi_repro/py.typed new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/setup.py b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/setup.py new file mode 100644 index 000000000000..185424c40399 --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/python/setup.py @@ -0,0 +1,59 @@ +# coding=utf-8 +# *** WARNING: this file was generated by test. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + +import errno +from setuptools import setup, find_packages +from setuptools.command.install import install +from subprocess import check_call + + +VERSION = "0.0.0" +PLUGIN_VERSION = "0.0.0" + +class InstallPluginCommand(install): + def run(self): + install.run(self) + try: + check_call(['pulumi', 'plugin', 'install', 'resource', 'repro', PLUGIN_VERSION]) + except OSError as error: + if error.errno == errno.ENOENT: + print(f""" + There was an error installing the repro resource provider plugin. + It looks like `pulumi` is not installed on your system. + Please visit https://pulumi.com/ to install the Pulumi CLI. + You may try manually installing the plugin by running + `pulumi plugin install resource repro {PLUGIN_VERSION}` + """) + else: + raise + + +def readme(): + try: + with open('README.md', encoding='utf-8') as f: + return f.read() + except FileNotFoundError: + return "repro Pulumi Package - Development Version" + + +setup(name='pulumi_repro', + version=VERSION, + long_description=readme(), + long_description_content_type='text/markdown', + cmdclass={ + 'install': InstallPluginCommand, + }, + packages=find_packages(), + package_data={ + 'pulumi_repro': [ + 'py.typed', + 'pulumi-plugin.json', + ] + }, + install_requires=[ + 'parver>=0.2.1', + 'pulumi', + 'semver>=2.8.1' + ], + zip_safe=False) diff --git a/pkg/codegen/testing/test/testdata/hyphenated-symbols/schema.json b/pkg/codegen/testing/test/testdata/hyphenated-symbols/schema.json new file mode 100644 index 000000000000..94152cf047d3 --- /dev/null +++ b/pkg/codegen/testing/test/testdata/hyphenated-symbols/schema.json @@ -0,0 +1,39 @@ +{ + "name": "repro", + "version": "0.1.0", + "resources": { + "repro:index:Foo": { + "properties": { + "conditionSets": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/types/repro:index:Bar" + } + } + } + } + } + } + }, + "types": { + "repro:index:Bar": { + "type": "object", + "properties": { + "has-a-hyphen": { + "type": "string" + } + } + } + }, + "language": { + "go": { + "generateResourceContainerTypes": true, + "importBasePath": "go-plain-ref-repro/repro", + "liftSingleValueMethodReturns": true + } + } +} \ No newline at end of file diff --git a/pkg/testing/integration/program.go b/pkg/testing/integration/program.go index 1bfc633e096b..d902e231c32b 100644 --- a/pkg/testing/integration/program.go +++ b/pkg/testing/integration/program.go @@ -2167,10 +2167,14 @@ func (pt *ProgramTester) prepareGoProject(projinfo *engine.Projinfo) error { // link local dependencies for _, pkg := range pt.opts.Dependencies { - - dep := getRewritePath(pkg, gopath, depRoot) - - editStr := fmt.Sprintf("%s=%s", pkg, dep) + var editStr string + if strings.ContainsRune(pkg, '=') { + // Use a literal replacement path. + editStr = pkg + } else { + dep := getRewritePath(pkg, gopath, depRoot) + editStr = fmt.Sprintf("%s=%s", pkg, dep) + } err = pt.runCommand("go-mod-edit", []string{goBin, "mod", "edit", "-replace", editStr}, cwd) if err != nil { return err