Go implementation of fn.Output program gen (#7949) (#8431)

* Modify Go codegen for fn.Output overloads

* Fix go tests

* Fixed CHANGELOG conflict

* ACCEPT changes reverting Fargate example to master, file issue

* Link an issue
This commit is contained in:
Anton Tayanovskyy 2021-11-18 17:50:51 -05:00 committed by GitHub
parent 1cedb29193
commit e846b3201f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 181 additions and 28 deletions

View file

@ -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.

View file

@ -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 {

View file

@ -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)
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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"),
},
}

View file

@ -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"

View file

@ -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
})
}