From 5022d9d29e41d6533bd858f52a1a70d70c6c934c Mon Sep 17 00:00:00 2001 From: Zaid Ajaj Date: Tue, 6 Dec 2022 22:15:40 +0100 Subject: [PATCH 1/2] Add InvokeSingle variants to dotnet and nodejs SDKs --- ...le-variants-to-dotnet-and-nodejs-sdks.yaml | 4 +++ .../Pulumi/Deployment/DeploymentInstance.cs | 29 +++++++++++++++++++ .../Pulumi/Deployment/Deployment_Invoke.cs | 13 +++++++++ sdk/dotnet/Pulumi/Deployment/IDeployment.cs | 24 +++++++++++++++ sdk/dotnet/Pulumi/PublicAPI.Shipped.txt | 4 ++- sdk/nodejs/runtime/invoke.ts | 13 +++++++++ 6 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 changelog/pending/20221206--sdk-dotnet-nodejs--add-inokesingle-variants-to-dotnet-and-nodejs-sdks.yaml diff --git a/changelog/pending/20221206--sdk-dotnet-nodejs--add-inokesingle-variants-to-dotnet-and-nodejs-sdks.yaml b/changelog/pending/20221206--sdk-dotnet-nodejs--add-inokesingle-variants-to-dotnet-and-nodejs-sdks.yaml new file mode 100644 index 000000000000..cb47bcc7621f --- /dev/null +++ b/changelog/pending/20221206--sdk-dotnet-nodejs--add-inokesingle-variants-to-dotnet-and-nodejs-sdks.yaml @@ -0,0 +1,4 @@ +changes: +- type: feat + scope: sdk/dotnet,nodejs + description: Add InvokeSingle variants to dotnet and nodejs SDKs diff --git a/sdk/dotnet/Pulumi/Deployment/DeploymentInstance.cs b/sdk/dotnet/Pulumi/Deployment/DeploymentInstance.cs index 9bfa46b379fe..4577595ed216 100644 --- a/sdk/dotnet/Pulumi/Deployment/DeploymentInstance.cs +++ b/sdk/dotnet/Pulumi/Deployment/DeploymentInstance.cs @@ -50,6 +50,22 @@ internal DeploymentInstance(IDeployment deployment) public Output Invoke(string token, InvokeArgs args, InvokeOptions? options = null) => _deployment.Invoke(token, args, options); + /// + /// Dynamically invokes the function '', which is offered by a + /// provider plugin. + /// + /// The result of will be a resolved to the + /// result value of the provider plugin. + /// + /// Similar to the earlier , but supports passing input values + /// and returns an Output value. + /// + /// The inputs can be a bag of computed values(including, `T`s, + /// s, s etc.). + /// + public Output InvokeSingle(string token, InvokeArgs args, InvokeOptions? options = null) + => _deployment.InvokeSingle(token, args, options); + /// /// Dynamically invokes the function '', which is offered by a /// provider plugin. @@ -63,6 +79,19 @@ public Output Invoke(string token, InvokeArgs args, InvokeOptions? options public Task InvokeAsync(string token, InvokeArgs args, InvokeOptions? options = null) => _deployment.InvokeAsync(token, args, options); + /// + /// Dynamically invokes the function '', which is offered by a + /// provider plugin. + /// + /// The result of will be a resolved to the + /// result value of the provider plugin which is expected to be a dictionary with single value. + /// + /// The inputs can be a bag of computed values(including, `T`s, + /// s, s etc.). + /// + public Task InvokeSingleAsync(string token, InvokeArgs args, InvokeOptions? options = null) + => _deployment.InvokeSingleAsync(token, args, options); + /// /// Same as , however the /// return value is ignored. diff --git a/sdk/dotnet/Pulumi/Deployment/Deployment_Invoke.cs b/sdk/dotnet/Pulumi/Deployment/Deployment_Invoke.cs index 6e806391040c..b1303275a6e0 100644 --- a/sdk/dotnet/Pulumi/Deployment/Deployment_Invoke.cs +++ b/sdk/dotnet/Pulumi/Deployment/Deployment_Invoke.cs @@ -1,6 +1,7 @@ // Copyright 2016-2021, Pulumi Corporation using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; @@ -18,9 +19,21 @@ Task IDeployment.InvokeAsync(string token, InvokeArgs args, InvokeOptions? optio Task IDeployment.InvokeAsync(string token, InvokeArgs args, InvokeOptions? options) => InvokeAsync(token, args, options, convertResult: true); + async Task IDeployment.InvokeSingleAsync(string token, InvokeArgs args, InvokeOptions? options) + { + var outputs = await InvokeAsync>(token, args, options, convertResult: true); + return outputs.Values.First(); + } + Output IDeployment.Invoke(string token, InvokeArgs args, InvokeOptions? options) => new Output(RawInvoke(token, args, options)); + Output IDeployment.InvokeSingle(string token, InvokeArgs args, InvokeOptions? options) + { + var outputResult = new Output>(RawInvoke>(token, args, options)); + return outputResult.Apply(outputs => outputs.Values.First()); + } + private async Task> RawInvoke(string token, InvokeArgs args, InvokeOptions? options) { // This method backs all `Fn.Invoke()` calls that generate diff --git a/sdk/dotnet/Pulumi/Deployment/IDeployment.cs b/sdk/dotnet/Pulumi/Deployment/IDeployment.cs index 20e07ab9cf72..282af655a3ac 100644 --- a/sdk/dotnet/Pulumi/Deployment/IDeployment.cs +++ b/sdk/dotnet/Pulumi/Deployment/IDeployment.cs @@ -38,6 +38,18 @@ internal interface IDeployment /// Task InvokeAsync(string token, InvokeArgs args, InvokeOptions? options = null); + /// + /// Dynamically invokes the function '', which is offered by a + /// provider plugin. + /// + /// The result of will be a resolved to the + /// result value of the provider plugin that returns a bag of properties with a single value that is returned. + /// + /// The inputs can be a bag of computed values(including, `T`s, + /// s, s etc.). + /// + Task InvokeSingleAsync(string token, InvokeArgs args, InvokeOptions? options = null); + /// /// Dynamically invokes the function '', which is offered by a /// provider plugin. @@ -50,6 +62,18 @@ internal interface IDeployment /// Output Invoke(string token, InvokeArgs args, InvokeOptions? options = null); + /// + /// Dynamically invokes the function '', which is offered by a + /// provider plugin. + /// + /// The result of will be a resolved to the + /// result value of the provider plugin that returns a bag of properties with a single value that is returned. + /// + /// The inputs can be a bag of computed values(including, `T`s, + /// s, s etc.). + /// + Output InvokeSingle(string token, InvokeArgs args, InvokeOptions? options = null); + /// /// Same as , however the /// return value is ignored. diff --git a/sdk/dotnet/Pulumi/PublicAPI.Shipped.txt b/sdk/dotnet/Pulumi/PublicAPI.Shipped.txt index 9355ea57b3df..9d5dd36bb174 100644 --- a/sdk/dotnet/Pulumi/PublicAPI.Shipped.txt +++ b/sdk/dotnet/Pulumi/PublicAPI.Shipped.txt @@ -91,6 +91,7 @@ Pulumi.DeploymentInstance.Call(string token, Pulumi.CallArgs args, Pulumi.Resour Pulumi.DeploymentInstance.Call(string token, Pulumi.CallArgs args, Pulumi.Resource self = null, Pulumi.CallOptions options = null) -> Pulumi.Output Pulumi.DeploymentInstance.InvokeAsync(string token, Pulumi.InvokeArgs args, Pulumi.InvokeOptions options = null) -> System.Threading.Tasks.Task Pulumi.DeploymentInstance.InvokeAsync(string token, Pulumi.InvokeArgs args, Pulumi.InvokeOptions options = null) -> System.Threading.Tasks.Task +Pulumi.DeploymentInstance.InvokeSingleAsync(string token, Pulumi.InvokeArgs args, Pulumi.InvokeOptions options = null) -> System.Threading.Tasks.Task Pulumi.DeploymentInstance.IsDryRun.get -> bool Pulumi.DeploymentInstance.ProjectName.get -> string Pulumi.DeploymentInstance.StackName.get -> string @@ -395,4 +396,5 @@ static Pulumi.Urn.Create(Pulumi.Input name, Pulumi.Input type, P static readonly Pulumi.CallArgs.Empty -> Pulumi.CallArgs static readonly Pulumi.InvokeArgs.Empty -> Pulumi.InvokeArgs static readonly Pulumi.ResourceArgs.Empty -> Pulumi.ResourceArgs -Pulumi.DeploymentInstance.Invoke(string token, Pulumi.InvokeArgs args, Pulumi.InvokeOptions options = null) -> Pulumi.Output \ No newline at end of file +Pulumi.DeploymentInstance.Invoke(string token, Pulumi.InvokeArgs args, Pulumi.InvokeOptions options = null) -> Pulumi.Output +Pulumi.DeploymentInstance.InvokeSingle(string token, Pulumi.InvokeArgs args, Pulumi.InvokeOptions options = null) -> Pulumi.Output \ No newline at end of file diff --git a/sdk/nodejs/runtime/invoke.ts b/sdk/nodejs/runtime/invoke.ts index 3b278ed01b30..e60f31d190cc 100644 --- a/sdk/nodejs/runtime/invoke.ts +++ b/sdk/nodejs/runtime/invoke.ts @@ -74,6 +74,19 @@ export function invoke(tok: string, props: Inputs, opts: InvokeOptions = {}): Pr return invokeAsync(tok, props, opts); } +/* + * `invokeSingle` dynamically invokes the function, `tok`, which is offered by a provider plugin. + * Similar to `invoke`, but returns a single value instead of an object with a single key. + */ +export function invokeSingle(tok: string, props: Inputs, opts: InvokeOptions = {}): Promise { + return invokeAsync(tok, props, opts).then(outputs => { + // assume outputs have a single key + const keys = Object.keys(outputs); + // return the first key's value from the outputs + return outputs[keys[0]]; + }); +} + export async function streamInvoke( tok: string, props: Inputs, From 6d4b3d619bc733c8e98cf547814641bbf9993542 Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Wed, 7 Dec 2022 11:32:04 +0100 Subject: [PATCH 2/2] Simplify `findFunctionSchema` Update test output Expose a token lookup method in PCL, and then use it instead of `schema.PackageReference.Functions().Get`. --- pkg/codegen/dotnet/gen_program.go | 36 +++++++++---------- pkg/codegen/pcl/binder_schema.go | 57 +++++++++++++++++++++++-------- 2 files changed, 59 insertions(+), 34 deletions(-) diff --git a/pkg/codegen/dotnet/gen_program.go b/pkg/codegen/dotnet/gen_program.go index 5521cd04d769..cac2587b0ba3 100644 --- a/pkg/codegen/dotnet/gen_program.go +++ b/pkg/codegen/dotnet/gen_program.go @@ -260,23 +260,23 @@ func (g *generator) warnf(location *hcl.Range, reason string, args ...interface{ }) } -func (g *generator) findFunctionSchema(function string, location *hcl.Range) (*schema.Function, bool) { - function = LowerCamelCase(function) +func (g *generator) findFunctionSchema(token string, location *hcl.Range) (*schema.Function, bool) { for _, pkg := range g.program.PackageReferences() { - for it := pkg.Functions().Range(); it.Next(); { - if strings.HasSuffix(it.Token(), function) { - fn, err := it.Function() - - if err != nil { - g.warnf(location, "Could not find function schema for '%s'; err %s", function, err.Error()) - return nil, false - } - - return fn, true - } + fn, ok, err := pcl.LookupFunction(pkg, token) + if !ok { + continue } + if err != nil { + g.diagnostics = append(g.diagnostics, &hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: fmt.Sprintf("Could not find function schema for '%s'", token), + Detail: err.Error(), + Subject: location, + }) + return nil, false + } + return fn, true } - return nil, false } @@ -287,12 +287,8 @@ func (g *generator) isFunctionInvoke(localVariable *pcl.LocalVariable) (*schema. call := value.(*model.FunctionCallExpression) switch call.Name { case pcl.Invoke: - args := call.Args[0] - _, fullFunctionName := g.functionName(args) - functionNameParts := strings.Split(fullFunctionName, ".") - functionName := functionNameParts[len(functionNameParts)-1] - location := value.SyntaxNode().Range().Ptr() - return g.findFunctionSchema(functionName, location) + token := call.Args[0].(*model.TemplateExpression).Parts[0].(*model.LiteralValueExpression).Value.AsString() + return g.findFunctionSchema(token, call.Args[0].SyntaxNode().Range().Ptr()) } } diff --git a/pkg/codegen/pcl/binder_schema.go b/pkg/codegen/pcl/binder_schema.go index 2d0b7d2df44c..5c88a4e01eda 100644 --- a/pkg/codegen/pcl/binder_schema.go +++ b/pkg/codegen/pcl/binder_schema.go @@ -33,6 +33,8 @@ type packageSchema struct { schema schema.PackageReference // These maps map from canonical tokens to actual tokens. + // + // Both maps take `nil` to mean uninitialized. resourceTokenMap map[string]string functionTokenMap map[string]string } @@ -42,9 +44,25 @@ type packageOpts struct { pluginDownloadURL string } +// Lookup a PCL invoke token in a schema. +func LookupFunction(pkg schema.PackageReference, token string) (*schema.Function, bool, error) { + s, _, ok, err := newPackageSchema(pkg).LookupFunction(token) + return s, ok, err +} + +// Lookup a PCL resource token in a schema. +func LookupResource(pkg schema.PackageReference, token string) (*schema.Resource, bool, error) { + r, _, ok, err := newPackageSchema(pkg).LookupResource(token) + return r, ok, err +} + func (ps *packageSchema) LookupFunction(token string) (*schema.Function, string, bool, error) { contract.Assert(ps != nil) + if ps.functionTokenMap == nil { + ps.initFunctionMap() + } + schemaToken, ok := ps.functionTokenMap[token] if !ok { token = canonicalizeToken(token, ps.schema) @@ -61,6 +79,10 @@ func (ps *packageSchema) LookupFunction(token string) (*schema.Function, string, func (ps *packageSchema) LookupResource(token string) (*schema.Resource, string, bool, error) { contract.Assert(ps != nil) + if ps.resourceTokenMap == nil { + ps.initResourceMap() + } + schemaToken, ok := ps.resourceTokenMap[token] if !ok { token = canonicalizeToken(token, ps.schema) @@ -74,6 +96,26 @@ func (ps *packageSchema) LookupResource(token string) (*schema.Resource, string, return res, token, ok, err } +func (ps *packageSchema) initFunctionMap() { + functionTokenMap := map[string]string{} + for it := ps.schema.Functions().Range(); it.Next(); { + functionTokenMap[canonicalizeToken(it.Token(), ps.schema)] = it.Token() + } + ps.functionTokenMap = functionTokenMap +} + +func (ps *packageSchema) initResourceMap() { + resourceTokenMap := map[string]string{} + for it := ps.schema.Resources().Range(); it.Next(); { + resourceTokenMap[canonicalizeToken(it.Token(), ps.schema)] = it.Token() + } + ps.resourceTokenMap = resourceTokenMap +} + +func newPackageSchema(pkg schema.PackageReference) *packageSchema { + return &packageSchema{schema: pkg} +} + type PackageInfo struct { name string version string @@ -122,20 +164,7 @@ func (c *PackageCache) loadPackageSchema(loader schema.Loader, name, version str return nil, err } - resourceTokenMap := map[string]string{} - for it := pkg.Resources().Range(); it.Next(); { - resourceTokenMap[canonicalizeToken(it.Token(), pkg)] = it.Token() - } - functionTokenMap := map[string]string{} - for it := pkg.Functions().Range(); it.Next(); { - functionTokenMap[canonicalizeToken(it.Token(), pkg)] = it.Token() - } - - schema := &packageSchema{ - schema: pkg, - resourceTokenMap: resourceTokenMap, - functionTokenMap: functionTokenMap, - } + schema := newPackageSchema(pkg) c.m.Lock() defer c.m.Unlock()