From e60d6bf248b0804504bfbe55409985114c273063 Mon Sep 17 00:00:00 2001 From: Anton Tayanovskyy Date: Thu, 18 Nov 2021 17:53:17 -0500 Subject: [PATCH] Programgen support for F.Invoke forms in .NET (#7949) (#8432) * Implement .NET codegen for F.Invoke forms * Add tests for .NET Invoke * Fixed conflict * Accept changes reverting fargate example --- CHANGELOG_PENDING.md | 4 ++ pkg/codegen/dotnet/gen_program.go | 29 ++++++++-- pkg/codegen/dotnet/gen_program_expressions.go | 50 ++++++++++++++-- pkg/codegen/internal/test/program_driver.go | 3 - .../dotnet/output-funcs-aws.cs | 57 +++++++++++++++++++ 5 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 pkg/codegen/internal/test/testdata/output-funcs-aws-pp/dotnet/output-funcs-aws.cs diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 63feec241..d6b0f7df0 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -8,6 +8,10 @@ appropriate, simplifying auto-generated examples. [#8431](https://github.com/pulumi/pulumi/pull/8431) +- [codegen/dotnet] - Program generator now uses `Invoke` forms where + appropriate, simplifying auto-generated examples. + [#8432](https://github.com/pulumi/pulumi/pull/8432) + ### Bug Fixes - [codegen/typescript] - Respect default values in Pulumi object types. diff --git a/pkg/codegen/dotnet/gen_program.go b/pkg/codegen/dotnet/gen_program.go index 65b65e66b..94c28dd12 100644 --- a/pkg/codegen/dotnet/gen_program.go +++ b/pkg/codegen/dotnet/gen_program.go @@ -1,4 +1,4 @@ -// Copyright 2016-2020, Pulumi Corporation. +// Copyright 2016-2021, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -46,6 +46,9 @@ type generator struct { asyncInit bool configCreated bool diagnostics hcl.Diagnostics + // Helper map to emit custom type name suffixes that match + // those emitted by codegen. + usedInFunctionOutputVersionInputs map[schema.Type]bool } const pulumiPackage = "pulumi" @@ -343,14 +346,32 @@ func (g *generator) functionName(tokenArg model.Expression) (string, string) { return rootNamespace, fmt.Sprintf("%s%s.%s", rootNamespace, namespace, Title(member)) } +func (g *generator) toSchemaType(destType model.Type) (schema.Type, bool) { + schemaType, ok := pcl.GetSchemaForType(destType.(model.Type)) + if !ok { + return nil, false + } + return codegen.UnwrapType(schemaType), true +} + // argumentTypeName computes the C# argument class name for the given expression and model type. func (g *generator) argumentTypeName(expr model.Expression, destType model.Type) string { - schemaType, ok := pcl.GetSchemaForType(destType.(model.Type)) + schemaType, ok := g.toSchemaType(destType) if !ok { return "" } + suffix := "Args" + if g.usedInFunctionOutputVersionInputs[schemaType] { + suffix = "InputArgs" + } + return g.argumentTypeNameWithSuffix(expr, destType, suffix) +} - schemaType = codegen.UnwrapType(schemaType) +func (g *generator) argumentTypeNameWithSuffix(expr model.Expression, destType model.Type, suffix string) string { + schemaType, ok := g.toSchemaType(destType) + if !ok { + return "" + } objType, ok := schemaType.(*schema.ObjectType) if !ok { @@ -382,7 +403,7 @@ func (g *generator) argumentTypeName(expr model.Expression, destType model.Type) } else if qualifier != "" { namespace = namespace + "." + qualifier } - member = member + "Args" + member = member + suffix return fmt.Sprintf("%s%s.%s", rootNamespace, namespace, Title(member)) } diff --git a/pkg/codegen/dotnet/gen_program_expressions.go b/pkg/codegen/dotnet/gen_program_expressions.go index 1a20ba7fe..f404ade53 100644 --- a/pkg/codegen/dotnet/gen_program_expressions.go +++ b/pkg/codegen/dotnet/gen_program_expressions.go @@ -255,6 +255,28 @@ func (g *generator) genFunctionUsings(x *model.FunctionCallExpression) []string return []string{fmt.Sprintf("%s = Pulumi.%[1]s", pkg)} } +func (g *generator) markTypeAsUsedInFunctionOutputVersionInputs(t model.Type) { + if g.usedInFunctionOutputVersionInputs == nil { + g.usedInFunctionOutputVersionInputs = make(map[schema.Type]bool) + } + schemaType, ok := g.toSchemaType(t) + if !ok { + return + } + g.usedInFunctionOutputVersionInputs[schemaType] = true +} + +func (g *generator) visitToMarkTypesUsedInFunctionOutputVersionInputs(expr model.Expression) { + visitor := func(expr model.Expression) (model.Expression, hcl.Diagnostics) { + isCons, _, t := pcl.RecognizeTypedObjectCons(expr) + if isCons { + g.markTypeAsUsedInFunctionOutputVersionInputs(t) + } + return expr, nil + } + model.VisitExpression(expr, nil, visitor) // nolint:errcheck +} + func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionCallExpression) { switch expr.Name { case pcl.IntrinsicConvert: @@ -294,10 +316,19 @@ func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionC case pcl.Invoke: _, name := g.functionName(expr.Args[0]) - g.Fprintf(w, "%s.InvokeAsync(", name) - if len(expr.Args) >= 2 { - g.Fgenf(w, "%.v", expr.Args[1]) + isOut, outArgs, outArgsTy := pcl.RecognizeOutputVersionedInvoke(expr) + if isOut { + g.visitToMarkTypesUsedInFunctionOutputVersionInputs(outArgs) + g.Fprintf(w, "%s.Invoke(", name) + typeName := g.argumentTypeNameWithSuffix(expr, outArgsTy, "InvokeArgs") + g.genObjectConsExpressionWithTypeName(w, outArgs, typeName) + } else { + g.Fprintf(w, "%s.InvokeAsync(", name) + if len(expr.Args) >= 2 { + g.Fgenf(w, "%.v", expr.Args[1]) + } } + if len(expr.Args) == 3 { g.Fgenf(w, ", %.v", expr.Args[2]) } @@ -446,7 +477,18 @@ func (g *generator) genObjectConsExpression(w io.Writer, expr *model.ObjectConsE return } - typeName := g.argumentTypeName(expr, destType) + destTypeName := g.argumentTypeName(expr, destType) + g.genObjectConsExpressionWithTypeName(w, expr, destTypeName) +} + +func (g *generator) genObjectConsExpressionWithTypeName( + w io.Writer, expr *model.ObjectConsExpression, destTypeName string) { + + if len(expr.Items) == 0 { + return + } + + typeName := destTypeName if typeName != "" { g.Fgenf(w, "new %s", typeName) g.Fgenf(w, "\n%s{\n", g.Indent) diff --git a/pkg/codegen/internal/test/program_driver.go b/pkg/codegen/internal/test/program_driver.go index c6d023d24..ae00abca3 100644 --- a/pkg/codegen/internal/test/program_driver.go +++ b/pkg/codegen/internal/test/program_driver.go @@ -52,8 +52,6 @@ var programTests = []programTest{ // TODO[pulumi/pulumi#8440] SkipCompile: codegen.NewStringSet("go"), - - Skip: codegen.NewStringSet("dotnet"), }, { Name: "aws-s3-logging", @@ -130,7 +128,6 @@ var programTests = []programTest{ { Name: "output-funcs-aws", Description: "Output Versioned Functions", - Skip: codegen.NewStringSet("dotnet"), }, } diff --git a/pkg/codegen/internal/test/testdata/output-funcs-aws-pp/dotnet/output-funcs-aws.cs b/pkg/codegen/internal/test/testdata/output-funcs-aws-pp/dotnet/output-funcs-aws.cs new file mode 100644 index 000000000..7c33707c5 --- /dev/null +++ b/pkg/codegen/internal/test/testdata/output-funcs-aws-pp/dotnet/output-funcs-aws.cs @@ -0,0 +1,57 @@ +using Pulumi; +using Aws = Pulumi.Aws; + +class MyStack : Stack +{ + public MyStack() + { + var aws_vpc = new Aws.Ec2.Vpc("aws_vpc", new Aws.Ec2.VpcArgs + { + CidrBlock = "10.0.0.0/16", + InstanceTenancy = "default", + }); + var privateS3VpcEndpoint = new Aws.Ec2.VpcEndpoint("privateS3VpcEndpoint", new Aws.Ec2.VpcEndpointArgs + { + VpcId = aws_vpc.Id, + ServiceName = "com.amazonaws.us-west-2.s3", + }); + var privateS3PrefixList = Aws.Ec2.GetPrefixList.Invoke(new Aws.Ec2.GetPrefixListInvokeArgs + { + PrefixListId = privateS3VpcEndpoint.PrefixListId, + }); + var bar = new Aws.Ec2.NetworkAcl("bar", new Aws.Ec2.NetworkAclArgs + { + VpcId = aws_vpc.Id, + }); + var privateS3NetworkAclRule = new Aws.Ec2.NetworkAclRule("privateS3NetworkAclRule", new Aws.Ec2.NetworkAclRuleArgs + { + NetworkAclId = bar.Id, + RuleNumber = 200, + Egress = false, + Protocol = "tcp", + RuleAction = "allow", + CidrBlock = privateS3PrefixList.Apply(privateS3PrefixList => privateS3PrefixList.CidrBlocks[0]), + FromPort = 443, + ToPort = 443, + }); + var amis = Aws.Ec2.GetAmiIds.Invoke(new Aws.Ec2.GetAmiIdsInvokeArgs + { + Owners = + { + bar.Id, + }, + Filters = + { + new Aws.Ec2.Inputs.GetAmiIdsFilterInputArgs + { + Name = bar.Id, + Values = + { + "pulumi*", + }, + }, + }, + }); + } + +}