--- title: "Functions Now Accept Outputs" date: 2021-11-12T13:14:58-05:00 draft: false meta_desc: With Pulumi 3.17.1 you can now call functions directly with resource outputs without an extra apply. meta_image: meta.png authors: - anton-tayanovskyy tags: - features --- Pulumi 3.17.1 makes it easier to compose function calls and resources. In practice you often need to call a function with a resource output. Previous versions of Pulumi required an `apply` to do this, which was unfortunate: - new Pulumi users would get stuck and ask for help as the solution was not obvious - experienced users found the code unpleasant, upvoting the relevant [GitHub Issue](https://github.com/pulumi/pulumi/issues/5758) With Pulumi 3.17.1 you can now call functions directly with resource outputs without an extra `apply`. Every function now has an additional Output form that accepts `Input`-typed arguments and returns an `Output`-wrapped result. For a quick example, here is how you can call [aws.ecr.getCredentials](https://www.pulumi.com/registry/packages/aws/api-docs/ecr/getcredentials/) with a `registryId` of type `Output`: {{< chooser language "typescript,python,go,csharp" / >}} {{% choosable language typescript %}} ```typescript const registryId: Output = ... getCredentialsOutput({registryId: registryId}): Output ``` {{% /choosable %}} {{% choosable language python %}} ```python registry_id: Output[str] = ... get_credentials_output(registry_id=registryId): Output[GetCredentialsResult] ``` {{% /choosable %}} {{% choosable language go %}} ```go var registryId StringOutput var result GetCredentialsResultOutput result = GetCredentialsOutput(ctx, GetCredentialsOutputArgs{ RegistryId: result }) ``` {{% /choosable %}} {{% choosable language csharp %}} ```csharp Output registryId; GetCredentials.Invoke(new GetCredentialsInvokeArgs { RegistryId = registryId }); ``` {{% /choosable %}} ## Complete Example: Publish Docker Image to ECR Why would you call `aws.ecr.getCredentials` with an `Output`? Suppose you want to provision an AWS Elastic Container Registry (ECR) repository, build a Docker image locally and publish this image to the registry. - To configure the Docker Image resource, you need ECR credentials. - To acquire the credentials, you need to call the `aws.ecr.getCredentials` function with the ECR registry ID. - Because the ECR registry ID is only known once the actual repository is provisioned in the cloud, the registryId property of the ecr.Repository resource has the type `Output` rather than string (see [Inputs and Outputs](https://www.pulumi.com/docs/concepts/inputs-outputs/)). In the code below, note how `getCredentialsOutput` now accepts `appRepo.registryId` directly: {{< chooser language "typescript,python,go,csharp" / >}} {{% choosable language typescript %}} ```typescript import * as aws from "@pulumi/aws"; import * as docker from "@pulumi/docker"; function parseAuthToken(authToken: string): {username: string, password: string} { const parts = Buffer.from(authToken, "base64").toString("ascii").split(":"); console.log(parts); return { username: parts[0], password: parts[1] } } const appRepo = new aws.ecr.Repository("app-repo"); const creds = aws.ecr.getCredentialsOutput({registryId: appRepo.registryId}) .apply(creds => parseAuthToken(creds.authorizationToken)) const image = new docker.Image("app-img", { // ./my-app is a folder with a Dockerfile build: "./my-app", imageName: appRepo.repositoryUrl, registry: { server: appRepo.repositoryUrl, username: creds.username, password: creds.password } }); export const imageUrn = image.urn; ``` {{% /choosable %}} {{% choosable language python %}} ```python from collections import namedtuple import base64 from pulumi_aws import s3, ecr import pulumi_docker as docker Creds = namedtuple('Creds', 'username password') def parse_auth_token(token): (u, p) = base64.b64decode(token).decode().split(':') return Creds(username=u, password=p) repo = ecr.Repository('app-repo') creds = ecr.get_credentials_output(registry_id=repo.registry_id).apply( lambda creds: parse_auth_token(creds.authorization_token)) image = docker.Image( 'app-img', image_name=repo.repository_url, # ./my-app is a folder with a Dockerfile build=docker.DockerBuild(context='./my-app'), registry=docker.ImageRegistry( repo.repository_url, creds.username, creds.password)) ``` {{% /choosable %}} {{% choosable language go %}} ```go package main import ( "encoding/base64" "fmt" "strings" "github.com/pulumi/pulumi-aws/sdk/v4/go/aws/ecr" "github.com/pulumi/pulumi-docker/sdk/v3/go/docker" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) type creds struct { Username string Password string } func parseAuthToken(token string) (creds, error) { decoded, err := base64.StdEncoding.DecodeString(token) if err != nil { return creds{}, err } parts := strings.Split(string(decoded), ":") if len(parts) != 2 { return creds{}, fmt.Errorf("Failed to parse token") } return creds{parts[0], parts[1]}, nil } func main() { pulumi.Run(func(ctx *pulumi.Context) error { repo, err := ecr.NewRepository(ctx, "app-repo", &ecr.RepositoryArgs{}) if err != nil { return err } creds := ecr.GetCredentialsOutput(ctx, ecr.GetCredentialsOutputArgs{ RegistryId: repo.RegistryId, }) username := creds.AuthorizationToken(). ApplyT(func(token string) (string, error) { creds, err := parseAuthToken(token) return creds.Username, err }).(pulumi.StringOutput) password := creds.AuthorizationToken(). ApplyT(func(token string) (string, error) { creds, err := parseAuthToken(token) return creds.Password, err }).(pulumi.StringOutput) image, err := docker.NewImage(ctx, "app-img", &docker.ImageArgs{ Build: &docker.DockerBuildArgs{ Context: pulumi.String("./my-app"), }, ImageName: repo.RepositoryUrl, Registry: &docker.ImageRegistryArgs{ Server: repo.RepositoryUrl, Username: username, Password: password, }, }) if err != nil { return err } ctx.Export("imageName", image.ImageName) return nil }) } ``` {{% /choosable %}} {{% choosable language csharp %}} ```csharp class MyStack : Pulumi.Stack { public MyStack() { var repo = new Repository("app-repo"); var creds = GetCredentials.Invoke(new GetCredentialsInvokeArgs { RegistryId = repo.RegistryId }); var username = creds .Apply(c => ParseAuthToken(c.AuthorizationToken).Username); var password = creds .Apply(c => ParseAuthToken(c.AuthorizationToken).Password); var image = new Image("app-img", new ImageArgs { ImageName = repo.RepositoryUrl, Build = new DockerBuild { // ./my-app is a folder with a Dockerfile Context = "./my-app" }, Registry = new ImageRegistry { Server = repo.RepositoryUrl, Username = username, Password = password, } }); } public (string Username, string Password) ParseAuthToken(string token) { var parts = Encoding.UTF8.GetString( Convert.FromBase64String(token)).Split(":"); return (Username: parts[0], Password: parts[1]); } } ``` {{% /choosable %}} Prior to the ability to call `aws.ecr.getCredentials` directly with an `Output` this program required an `apply` form and was a lot more verbose and harder to read: {{< chooser language "typescript,python,go,csharp" / >}} {{% choosable language typescript %}} ```typescript const creds = appRepo.id .apply(id => aws.ecr.getCredentials({registryId: id}) ``` {{% /choosable %}} {{% choosable language python %}} ```python creds = app_repo.id.apply(lambda repo_id: ecr.get_credentials(registry_id=repo_id)) ``` {{% /choosable %}} {{% choosable language go %}} ```go creds := repo.ID().ToStringOutput(). ApplyT(func(id string) *ecr.GetCredentialsResult { creds, err := ecr.GetCredentials(ctx, &ecr.GetCredentialsArgs{RegistryId: id}) if err != nil { panic(err) } return creds }).(ecr.GetCredentialsResultOutput) ``` {{% /choosable %}} {{% choosable language csharp %}} ```csharp var creds = repo.Id.Apply(repoId => GetCredentials.InvokeAsync(new GetCredentialsArgs { RegistryId = repoId })); ``` {{% /choosable %}} ## More examples The above example is one of many practical situations where mixing function calls and resources benefits from the new form. To find out more, check out the following updated Pulumi examples: - [azure-py-appservice example](https://github.com/pulumi/examples/tree/master/azure-py-appservice) - [aws-ts-appsync example](https://github.com/pulumi/examples/tree/master/aws-ts-appsync) - [azure-cs-aks-cosmos-helm](https://github.com/pulumi/examples/tree/master/azure-cs-aks-cosmos-helm) ## Compatibility To keep existing Pulumi programs working without changes, the function forms are added as separate functions or methods in each Pulumi-supported language following a simple naming convention. To illustrate with the `getCredentials` function: | Language | Existing non-Output form | New Output form | |------------|----------------------------|--------------------------------------| | TypeScript | aws.ecr.getCredentials | aws.ecr.getCredentials**Output** | | Python | aws.ecr.get\_credentials | aws.ecr.get\_credentials\_**output** | | Go | ecr.getCredentials | ecr.getCredentials**Output** | | C# | GetCredentials.InvokeAsync | GetCredentials.**Invoke** | Note that there are cases where the existing non-`Output` form may still be the right choice. For example, retrieving the default VPC in Python utilizing the existing form is simpler as it returns a result that can be immediately inspected: ```python default_vpc = aws.ec2.get_vpc(default=True) print(default_vpc.id) ``` Prefer the new Output form when passing resource outputs to a function or else using the outputs of the function as inputs to resources. You may still want to use the existing non-Output form if you are using the outputs of the function to inform control flow (`if` conditionals or `for` loops). ## Get started To use Output-versioned functions, please upgrade your install of Pulumi to at least 3.17.1 and upgrade your providers to the latest available version. Example compatible versions for major Pulumi providers: | Provider | Version | |----------------------|---------| | pulumi-aws | 4.27.0 | | pulumi-azure-native | 1.45.0 | | pulumi-google-native | 0.8.0 | | pulumi-azure | 4.26.0 | | pulumi-gcp | 5.26.0 |