From fe11fe272ad457a12813244096af29454590c6e8 Mon Sep 17 00:00:00 2001 From: Will Jones Date: Fri, 23 Aug 2024 11:24:31 +0100 Subject: [PATCH] Document `dependsOn` workarounds for invokes (#12327) * Document `dependsOn` workarounds for invokes Invokes, or provider functions, are exposed in many SDKs alongside resources, and allow programs to call arbitrary provider-specific APIs. Just as resources may be passed resource options, so may invokes be passed invoke options, with the usual suspects such as `parent` and `provider` making an appearance and appearing as one might expect. One resource option that is currently not available as an invoke option, despite appearing a natural addition, is `dependsOn`, which allows Pulumi users to make explicit dependencies and ordering constraints in their programs that Pulumi cannot otherwise track. One reason that `dependsOn` does not currently exist as an invoke option is that any given invoke can be called in one of two ways: * A "direct form", whereby the function accepts plain arguments and either blocks until it returns a value or returns a language-native asynchronous value (such as a `Promise` in NodeJS or a `Task` in Python, for instance). * An "output form", whereby the function accepts any Pulumi `Input` values and returns a Pulumi `Output` value. Since direct-form invocations do not use Pulumi `Input`s or `Output`s, they do not form part of the Pulumi dependency graph. Passing `dependsOn` to a direct-form invocation would therefore make no sense. The sharing of the set of invoke options between the two forms means that `dependsOn` is consequently not currently available. Thankfully, one can work around the absence of `dependsOn` for output-form invocations using e.g. `pulumi.all` and `.apply`. This commit updates the documentation on invokes/provider functions to (hopefully) make this clear and provide examples where possible. While we are here we also bolster some of the other pieces of documentation on provider functions, such as defining the terms "direct form" and "output form" which are already referenced on other pages. Fixes https://github.com/pulumi/pulumi/issues/14243 Co-authored-by: Christian Nunciato --- content/docs/concepts/resources/functions.md | 176 ++++++++++++++++--- 1 file changed, 156 insertions(+), 20 deletions(-) diff --git a/content/docs/concepts/resources/functions.md b/content/docs/concepts/resources/functions.md index deb587b4e237..6a9bfaee3588 100644 --- a/content/docs/concepts/resources/functions.md +++ b/content/docs/concepts/resources/functions.md @@ -131,34 +131,170 @@ variables: Provider functions are exposed in each language as regular functions, in two variations: - 1. A function that accepts plain arguments (strings and so on) and returns a Promise, or blocks until the result is available. - 2. A function that accepts `Input` values and returns an [Output](/docs/concepts/inputs-outputs/). + 1. The **direct form** accepts plain arguments (e.g. `string`, as opposed to `pulumi.Input`) and returns an asynchronous value (e.g. a `Promise` in NodeJS, or a `Task` in Python), or blocks until the result is available. These functions are typically named, e.g., `getFoo()`. + 2. The **output form** accepts `Input` values (or plain values) and returns an [Output](/docs/concepts/inputs-outputs/). These functions are typically named, e.g., `getFooOutput()`. -The documentation for a provider function will tell you the name and signature for each of the variations. +The [Pulumi Registry](/registry) contains authoritative documentation for all provider functions. #### Invoke options -Each function and method also accepts "invoke options", either as an object or as varargs depending on the host language. The options are as follows: +Functions also accept "invoke options", similar to the way Pulumi resources accept [resource options](/docs/concepts/options/). Invoke options may be specified either as an object or as a list of arguments depending on the language you're writing your Pulumi program in. The options are as follows: -| Option | Explanation | -|--------|--------------------------------------------------------------| -| parent | Supply a parent resource, which will be used to determine default providers | -| provider | Supply the provider to use explicitly. | -| version | Use exactly this version of the provider plugin. | -| pluginDownloadURL | Download the provider plugin from this URL. The download URL is otherwise inferred from the provider package. | -| _async_ | _This option is deprecated and will be removed in a future release_ | +- `parent`: Supply a parent resource for this function call. Much like the [parent resource option](/docs/concepts/options/parent/), the parent will be consulted when determining the provider to use. -The `parent` option has a similar purpose to the [parent option](/docs/concepts/options/parent/) used when creating a resource. The parent is consulted when determining the provider to use. +- `pluginDownloadURL`: Pass a URL from which the provider plugin should be fetched. This may be necessary for third-party packages such as those not hosted at [https://get.pulumi.com](https://get.pulumi.com). -The `provider` option gives an explicit provider to use when running the invoked function. This is useful, for example, if you want to invoke a function in each of a set of AWS regions. +- `provider`: Pass an [explicitly configured provider](/docs/concepts/resources/providers/#explicit-provider-configuration) to use for this function call, instead of using the default provider. This is useful, for example, if you want to invoke a function in each of a set of AWS regions. -The `version` option specifies an exact version for the provider plugin. This can be used when you need to pin to a specific version to avoid a backward-incompatible change. +- `version`: Pass a provider plugin version that should be used when invoking the function. -The `pluginDownloadURL` option gives a URL for fetching the provider plugin. It may be necessary to supply this for third-party packages (those not hosted at [https://get.pulumi.com](https://get.pulumi.com)). +- `async`: _This option is deprecated and will be removed in a future release_. -### Provider methods +### Dependencies and ordering -Provider SDKs may also include methods attached to a resource type. For example, in the [EKS](/registry/packages/eks/api-docs/) SDK, the `Cluster` resource has a method [.GetKubeconfig](/registry/packages/eks/api-docs/cluster/#method_GetKubeconfig): +While the direct and output forms of a provider function are equivalent in terms of the results they produce when invoked, they differ in how they interact with the rest of the Pulumi program and the order in which they may be executed. Specifically: + +- Direct form invocations execute just like any other function call in the language. Since they do not accept Pulumi `Input`s nor return Pulumi `Output`s, they are not tracked by the Pulumi engine and do not participate in the dependency graph. + +- Output form invocations, on the other hand, are tracked by the Pulumi engine and participate in the dependency graph. This means, for example, that Pulumi will ensure that input resources are created or updated before an invocation and that the invocation is executed before its dependent resources are created or updated. + +If you require that dependent resources are created or updated before an invocation, you must use a provider function's output form. If you need to specify a dependency that can't be captured by passing an appropriate input (that is, if you wish to simulate something like the [`dependsOn` resource option](/docs/concepts/options/dependson/)), you can use Pulumi's [`all`](/docs/concepts/inputs-outputs/all/) function and `Output`'s [`apply`](/docs/concepts/inputs-outputs/apply/) method: + +{{< chooser language "typescript,python,go,csharp,java,yaml" >}} +{{% choosable language typescript %}} + + ```typescript +import * as pulumi from "@pulumi/pulumi"; + +const res1 = new MyResource("res1", {}); +const res2 = new MyResource("res2", {}); + +// Assuming `myFunctionOutput` is an output-form invocation of the `myFunction` +// provider function, this use of `all` and `apply` will ensure that it does not +// happen until `res1` and `res2` have been processed. This will work for any +// set of resources, even those with no explicit outputs, since the `.urn` +// output is always available. +pulumi.all([res1.urn, res2.urn]).apply(() => myFunctionOutput()); +``` + +{{% /choosable %}} +{{% choosable language csharp %}} + +```csharp +using System.Collections.Generic; +using System.Linq; +using Pulumi; + +return await Deployment.RunAsync(() => +{ + var res1 = new MyResource("res1", new MyResourceArgs()); + var res2 = new MyResource("res2", new MyResourceArgs()); + + // Assuming `myFunctionOutput` is an output-form invocation of the `myFunction` + // provider function, this use of `Tuple` and `Apply` will ensure that it does + // not happen until `res1` and `res2` have been processed. This will work for + // any set of resources, even those with no explicit outputs, since the `.Urn` + // output is always available. + Output.Tuple(res1.Urn, res2.Urn).Apply(t => myFunctionOutput()); +}); +``` + +{{% /choosable %}} +{{% choosable language go %}} + +```go +package main + +import ( + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + res1, err := NewMyResource(ctx, "res1", nil) + if err != nil { + return err + } + + res2, err := NewMyResource(ctx, "res2", nil) + if err != nil { + return err + } + + // Assuming `myFunctionOutput` is an output-form invocation of the `myFunction` + // provider function, this use of `All` and `ApplyT` will ensure that it does not + // happen until `res1` and `res2` have been processed. This will work for any set + // of resources, even those with no explicit outputs, since the `.URN` output is + // always available. + _, err = pulumi.All(res1.URN, res2.URN).ApplyT(func(args []interface{}) (interface{}, error) { + return myFunctionOutput() + }) + if err != nil { + return err + } + + return nil + }) +} +``` + +{{% /choosable %}} +{{% choosable language python %}} + +```python +import pulumi + +res1 = MyResource("res1", {}) +res2 = MyResource("res2", {}) + +# Assuming `my_function_output` is an output-form invocation of the `my_function` +# provider function, this use of `all` and `apply` will ensure that it does not +# happen until `res1` and `res2` have been processed. This will work for any set +# of resources, even those with no explicit outputs, since the `.urn` output is +# always available. +pulumi.all(res1.urn, res2.urn).apply(lambda args: my_function_output()) +``` + +{{% /choosable %}} +{{% choosable language java %}} + +```java +package generated_program; + +import com.pulumi.Context; +import com.pulumi.Output; +import com.pulumi.Pulumi; + +public class App { + public static void main(String[] args) { + Pulumi.run(App::stack); + } + + public static void stack(Context ctx) { + final var res1 = new MyResource("res1", new MyResourceArgs()); + final var res2 = new MyResource("res2", new MyResourceArgs()); + + // Assuming `myFunctionOutput` is an output-form invocation of the `myFunction` + // provider function, this use of `tuple` and `applyValue` will ensure that it does + // not happen until `res1` and `res2` have been processed. This will work for + // any set of resources, even those with no explicit outputs, since the `.urn` + // output is always available. + Output.tuple(res1.getUrn(), res2.getUrn()).applyValue(t -> myFunctionOutput()); + } +} +``` + +{{% /choosable %}} +{{% choosable language yaml %}} + +Output form invocations are not yet supported in YAML. + +{{% /choosable %}} +{{< /chooser >}} + +### Resource methods + +Provider SDKs may also include _methods_ attached to a resource type. For example, in the [EKS](/registry/packages/eks/api-docs/) SDK, the `Cluster` resource has a [.GetKubeconfig](/registry/packages/eks/api-docs/cluster/#method_GetKubeconfig) method:
@@ -203,18 +339,18 @@ def get_kubeconfig(self,
-(no example available for Java) +No example available for Java
-(no example available for YAML) +No example available for YAML
-Unlike provider functions, methods always take `Input` arguments, and return an `Output`. Methods do not have invoke options. +Unlike provider functions, methods always appear in the _output form_: they take `Input` arguments, and return an `Output`. Moreover, methods do not accept invoke options.