Skip to content

Commit

Permalink
Document dependsOn workarounds for invokes (#12327)
Browse files Browse the repository at this point in the history
* 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 pulumi/pulumi#14243


Co-authored-by: Christian Nunciato <[email protected]>
  • Loading branch information
lunaris and cnunciato authored Aug 23, 2024
1 parent 1bec86d commit fe11fe2
Showing 1 changed file with 156 additions and 20 deletions.
176 changes: 156 additions & 20 deletions content/docs/concepts/resources/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>`) 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:

<div><pulumi-examples>
<div><pulumi-chooser type="language" options="typescript,python,go,csharp,java,yaml"></pulumi-chooser></div>
Expand Down Expand Up @@ -203,18 +339,18 @@ def get_kubeconfig(self,
<div>
<pulumi-choosable type="language" values="java">

(no example available for Java)
No example available for Java

</pulumi-choosable>
</div>
<div>
<pulumi-choosable type="language" values="yaml">

(no example available for YAML)
No example available for YAML

</pulumi-choosable>
</div>

</pulumi-examples></div>

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.

0 comments on commit fe11fe2

Please sign in to comment.