diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 1a5492256..63feec241 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -4,6 +4,10 @@ appropriate, simplifying auto-generated examples. [#8433](https://github.com/pulumi/pulumi/pull/8433) +- [codegen/go] - Program generator now uses fnOutput forms where + appropriate, simplifying auto-generated examples. + [#8431](https://github.com/pulumi/pulumi/pull/8431) + ### Bug Fixes - [codegen/typescript] - Respect default values in Pulumi object types. diff --git a/pkg/codegen/go/gen.go b/pkg/codegen/go/gen.go index 0cb8e9f88..2da8b1e86 100644 --- a/pkg/codegen/go/gen.go +++ b/pkg/codegen/go/gen.go @@ -571,6 +571,10 @@ func (pkg *pkgContext) resolveObjectType(t *schema.ObjectType) string { } return name } + return pkg.contextForExternalReferenceType(t).typeString(t) +} + +func (pkg *pkgContext) contextForExternalReferenceType(t *schema.ObjectType) *pkgContext { extPkg := t.Package var goInfo GoPackageInfo @@ -584,7 +588,7 @@ func (pkg *pkgContext) resolveObjectType(t *schema.ObjectType) string { pkgImportAliases: goInfo.PackageImportAliases, modToPkg: goInfo.ModuleToPackage, } - return extPkgCtx.typeString(t) + return extPkgCtx } func (pkg *pkgContext) outputType(t schema.Type) string { diff --git a/pkg/codegen/go/gen_program.go b/pkg/codegen/go/gen_program.go index 22f3b3e74..f7973f060 100644 --- a/pkg/codegen/go/gen_program.go +++ b/pkg/codegen/go/gen_program.go @@ -244,9 +244,7 @@ func (g *generator) collectImports( } pulumiImports.Add(g.getPulumiImport(pkg, vPath, mod)) } else if call.Name == pcl.IntrinsicConvert { - if schemaType, ok := pcl.GetSchemaForType(call.Type()); ok { - g.collectTypeImports(program, schemaType, pulumiImports) - } + g.collectConvertImports(program, call, pulumiImports) } // Checking to see if this function call deserves its own dedicated helper method in the preamble @@ -277,6 +275,30 @@ func (g *generator) collectImports( return stdImports, pulumiImports, preambleHelperMethods } +func (g *generator) collectConvertImports( + program *pcl.Program, + call *model.FunctionCallExpression, + pulumiImports codegen.StringSet) { + if schemaType, ok := pcl.GetSchemaForType(call.Type()); ok { + // Sometimes code for a `__convert` call does not + // really use the import of the result type. In such + // cases it is important not to generate a + // non-compiling unused import. Detect some of these + // cases here. + // + // Fully solving this is deferred for later: + // TODO[pulumi/pulumi#8324]. + if expr, ok := call.Args[0].(*model.TemplateExpression); ok { + if lit, ok := expr.Parts[0].(*model.LiteralValueExpression); ok && + model.StringType.AssignableFrom(lit.Type()) && + call.Type().AssignableFrom(lit.Type()) { + return + } + } + g.collectTypeImports(program, schemaType, pulumiImports) + } +} + func (g *generator) getVersionPath(program *pcl.Program, pkg string) (string, error) { for _, p := range program.Packages() { if p.Name == pkg { @@ -614,11 +636,17 @@ func (g *generator) genLocalVariable(w io.Writer, v *pcl.LocalVariable) { case *model.FunctionCallExpression: switch expr.Name { case pcl.Invoke: - g.Fgenf(w, "%s, err %s %.3v;\n", name, assignment, expr) - g.isErrAssigned = true - g.Fgenf(w, "if err != nil {\n") - g.Fgenf(w, "return err\n") - g.Fgenf(w, "}\n") + // OutputVersionedInvoke does not return an error + noError, _, _ := pcl.RecognizeOutputVersionedInvoke(expr) + if noError { + g.Fgenf(w, "%s %s %.3v;\n", name, assignment, expr) + } else { + g.Fgenf(w, "%s, err %s %.3v;\n", name, assignment, expr) + g.isErrAssigned = true + g.Fgenf(w, "if err != nil {\n") + g.Fgenf(w, "return err\n") + g.Fgenf(w, "}\n") + } case "join", "toBase64", "mimeType", "fileAsset": g.Fgenf(w, "%s := %.3v;\n", name, expr) } diff --git a/pkg/codegen/go/gen_program_expressions.go b/pkg/codegen/go/gen_program_expressions.go index 63e20e555..0abe7b6e5 100644 --- a/pkg/codegen/go/gen_program_expressions.go +++ b/pkg/codegen/go/gen_program_expressions.go @@ -195,7 +195,18 @@ func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionC if module == "" { module = pkg } - name := fmt.Sprintf("%s.%s", module, fn) + isOut, outArgs, outArgsType := pcl.RecognizeOutputVersionedInvoke(expr) + if isOut { + outTypeName, err := outputVersionFunctionArgTypeName(outArgsType) + if err != nil { + panic(fmt.Errorf("Error when generating an output-versioned Invoke: %w", err)) + } + g.Fgenf(w, "%s.%sOutput(ctx, ", module, fn) + g.genObjectConsExpressionWithTypeName(w, outArgs, outArgsType, outTypeName) + } else { + g.Fgenf(w, "%s.%s(ctx, ", module, fn) + g.Fgenf(w, "%.v", expr.Args[1]) + } optionsBag := "" var buf bytes.Buffer @@ -205,9 +216,6 @@ func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionC g.Fgenf(&buf, ", nil") } optionsBag = buf.String() - - g.Fgenf(w, "%s(ctx, ", name) - g.Fgenf(w, "%.v", expr.Args[1]) g.Fgenf(w, "%v)", optionsBag) case "join": g.Fgenf(w, "strings.Join(%v, %v)", expr.Args[1], expr.Args[0]) @@ -246,6 +254,32 @@ func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionC } } +// Currently args type for output-versioned invokes are named +// `FOutputArgs`, but this is not yet understood by `tokenToType`. Use +// this function to compensate. +func outputVersionFunctionArgTypeName(t model.Type) (string, error) { + schemaType, ok := pcl.GetSchemaForType(t) + if !ok { + return "", fmt.Errorf("No schema.Type type found for the given model.Type") + } + + objType, ok := schemaType.(*schema.ObjectType) + if !ok { + return "", fmt.Errorf("Expected a schema.ObjectType, got %s", schemaType.String()) + } + + pkg := &pkgContext{pkg: &schema.Package{Name: "main"}} + + var ty string + if pkg.isExternalReference(objType) { + ty = pkg.contextForExternalReferenceType(objType).tokenToType(objType.Token) + } else { + ty = pkg.tokenToType(objType.Token) + } + + return fmt.Sprintf("%sOutputArgs", strings.TrimSuffix(ty, "Args")), nil +} + func (g *generator) GenIndexExpression(w io.Writer, expr *model.IndexExpression) { g.Fgenf(w, "%.20v[%.v]", expr.Collection, expr.Key) } @@ -316,15 +350,10 @@ func (g *generator) genObjectConsExpression( w io.Writer, expr *model.ObjectConsExpression, destType model.Type, - isInput bool, -) { - if len(expr.Items) == 0 { - g.Fgenf(w, "nil") - return - } + isInput bool) { - var temps []interface{} isInput = isInput || isInputty(destType) + typeName := g.argumentTypeName(expr, destType, isInput) if schemaType, ok := pcl.GetSchemaForType(destType); ok { if obj, ok := codegen.UnwrapType(schemaType).(*schema.ObjectType); ok { @@ -334,6 +363,21 @@ func (g *generator) genObjectConsExpression( } } + g.genObjectConsExpressionWithTypeName(w, expr, destType, typeName) +} + +func (g *generator) genObjectConsExpressionWithTypeName( + w io.Writer, + expr *model.ObjectConsExpression, + destType model.Type, + typeName string) { + + if len(expr.Items) == 0 { + g.Fgenf(w, "nil") + return + } + + var temps []interface{} // TODO: @pgavlin --- ineffectual assignment, was there some work in flight here? // if strings.HasSuffix(typeName, "Args") { // isInput = true @@ -360,7 +404,7 @@ func (g *generator) genObjectConsExpression( } g.genTemps(w, temps) - if isMap || !strings.HasSuffix(typeName, "Args") { + if isMap || !strings.HasSuffix(typeName, "Args") || strings.HasSuffix(typeName, "OutputArgs") { g.Fgenf(w, "%s", typeName) } else { g.Fgenf(w, "&%s", typeName) @@ -814,9 +858,15 @@ func (g *generator) genApply(w io.Writer, expr *model.FunctionCallExpression) { isInput := false retType := g.argumentTypeName(nil, then.Signature.ReturnType, isInput) // TODO account for outputs in other namespaces like aws - typeAssertion := fmt.Sprintf(".(%sOutput)", retType) - if !strings.HasPrefix(retType, "pulumi.") { - typeAssertion = fmt.Sprintf(".(pulumi.%sOutput)", Title(retType)) + // TODO[pulumi/pulumi#8453] incomplete pattern code below. + var typeAssertion string + if retType == "[]string" { + typeAssertion = ".(pulumi.StringArrayOutput)" + } else { + typeAssertion = fmt.Sprintf(".(%sOutput)", retType) + if !strings.HasPrefix(retType, "pulumi.") { + typeAssertion = fmt.Sprintf(".(pulumi.%sOutput)", Title(retType)) + } } if len(applyArgs) == 1 { diff --git a/pkg/codegen/go/gen_program_optionals.go b/pkg/codegen/go/gen_program_optionals.go index 4cb47e67c..7cf58094e 100644 --- a/pkg/codegen/go/gen_program_optionals.go +++ b/pkg/codegen/go/gen_program_optionals.go @@ -44,7 +44,9 @@ func (os *optionalSpiller) spillExpressionHelper( case *model.FunctionCallExpression: if x.Name == "invoke" { // recurse into invoke args - isInvoke = true + isOutputInvoke, _, _ := pcl.RecognizeOutputVersionedInvoke(x) + // ignore output-versioned invokes as they do not need converting + isInvoke = !isOutputInvoke _, diags := os.spillExpressionHelper(x.Args[1], x.Args[1].Type(), isInvoke) return x, diags } diff --git a/pkg/codegen/internal/test/program_driver.go b/pkg/codegen/internal/test/program_driver.go index 616797678..c6d023d24 100644 --- a/pkg/codegen/internal/test/program_driver.go +++ b/pkg/codegen/internal/test/program_driver.go @@ -49,8 +49,11 @@ var programTests = []programTest{ { Name: "aws-fargate", Description: "AWS Fargate", + + // TODO[pulumi/pulumi#8440] SkipCompile: codegen.NewStringSet("go"), - Skip: codegen.NewStringSet("go", "dotnet"), + + Skip: codegen.NewStringSet("dotnet"), }, { Name: "aws-s3-logging", @@ -127,7 +130,7 @@ var programTests = []programTest{ { Name: "output-funcs-aws", Description: "Output Versioned Functions", - Skip: codegen.NewStringSet("go", "dotnet"), + Skip: codegen.NewStringSet("dotnet"), }, } diff --git a/pkg/codegen/internal/test/testdata/aws-fargate-pp/go/aws-fargate.go b/pkg/codegen/internal/test/testdata/aws-fargate-pp/go/aws-fargate.go index d24b3640e..f201acb68 100644 --- a/pkg/codegen/internal/test/testdata/aws-fargate-pp/go/aws-fargate.go +++ b/pkg/codegen/internal/test/testdata/aws-fargate-pp/go/aws-fargate.go @@ -3,7 +3,6 @@ package main import ( "encoding/json" - "github.com/pulumi/pulumi-aws/sdk/v4/go/aws" "github.com/pulumi/pulumi-aws/sdk/v4/go/aws/ec2" "github.com/pulumi/pulumi-aws/sdk/v4/go/aws/ecs" "github.com/pulumi/pulumi-aws/sdk/v4/go/aws/elasticloadbalancingv2" diff --git a/pkg/codegen/internal/test/testdata/output-funcs-aws-pp/go/output-funcs-aws.go b/pkg/codegen/internal/test/testdata/output-funcs-aws-pp/go/output-funcs-aws.go new file mode 100644 index 000000000..1871f8dcd --- /dev/null +++ b/pkg/codegen/internal/test/testdata/output-funcs-aws-pp/go/output-funcs-aws.go @@ -0,0 +1,63 @@ +package main + +import ( + "github.com/pulumi/pulumi-aws/sdk/v4/go/aws/ec2" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + aws_vpc, err := ec2.NewVpc(ctx, "aws_vpc", &ec2.VpcArgs{ + CidrBlock: pulumi.String("10.0.0.0/16"), + InstanceTenancy: pulumi.String("default"), + }) + if err != nil { + return err + } + privateS3VpcEndpoint, err := ec2.NewVpcEndpoint(ctx, "privateS3VpcEndpoint", &ec2.VpcEndpointArgs{ + VpcId: aws_vpc.ID(), + ServiceName: pulumi.String("com.amazonaws.us-west-2.s3"), + }) + if err != nil { + return err + } + privateS3PrefixList := ec2.GetPrefixListOutput(ctx, ec2.GetPrefixListOutputArgs{ + PrefixListId: privateS3VpcEndpoint.PrefixListId, + }, nil) + bar, err := ec2.NewNetworkAcl(ctx, "bar", &ec2.NetworkAclArgs{ + VpcId: aws_vpc.ID(), + }) + if err != nil { + return err + } + _, err = ec2.NewNetworkAclRule(ctx, "privateS3NetworkAclRule", &ec2.NetworkAclRuleArgs{ + NetworkAclId: bar.ID(), + RuleNumber: pulumi.Int(200), + Egress: pulumi.Bool(false), + Protocol: pulumi.String("tcp"), + RuleAction: pulumi.String("allow"), + CidrBlock: privateS3PrefixList.ApplyT(func(privateS3PrefixList ec2.GetPrefixListResult) (string, error) { + return privateS3PrefixList.CidrBlocks[0], nil + }).(pulumi.StringOutput), + FromPort: pulumi.Int(443), + ToPort: pulumi.Int(443), + }) + if err != nil { + return err + } + _ = ec2.GetAmiIdsOutput(ctx, ec2.GetAmiIdsOutputArgs{ + Owners: pulumi.StringArray{ + bar.ID(), + }, + Filters: ec2.GetAmiIdsFilterArray{ + &ec2.GetAmiIdsFilterArgs{ + Name: bar.ID(), + Values: pulumi.StringArray{ + pulumi.String("pulumi*"), + }, + }, + }, + }, nil) + return nil + }) +}