Functions Now Accept Outputs

Posted on

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

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 with a registryId of type Output<string>:

const registryId: Output<string> = ...
getCredentialsOutput({registryId: registryId}): Output<GetCredentialsResult>
registry_id: Output[str] = ...
get_credentials_output(registry_id=registryId): Output[GetCredentialsResult]
var registryId StringOutput
var result GetCredentialsResultOutput
result = GetCredentialsOutput(ctx, GetCredentialsOutputArgs{
    RegistryId: result
Output<string> registryId;
GetCredentials.Invoke(new GetCredentialsInvokeArgs
   RegistryId = registryId

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<string> rather than string (see Inputs and Outputs).

In the code below, note how getCredentialsOutput now accepts appRepo.registryId directly:

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(":");
    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;
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(
    # ./my-app is a folder with a Dockerfile
package main

import (


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

		password := creds.AuthorizationToken().
			ApplyT(func(token string) (string, error) {
				creds, err := parseAuthToken(token)
				return creds.Password, err

		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
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(
        return (Username: parts[0], Password: parts[1]);

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:

const creds =
    .apply(id => aws.ecr.getCredentials({registryId: id})
creds = repo_id: ecr.get_credentials(registry_id=repo_id))
creds := repo.ID().ToStringOutput().
	ApplyT(func(id string) *ecr.GetCredentialsResult {
		creds, err := ecr.GetCredentials(ctx,
			&ecr.GetCredentialsArgs{RegistryId: id})
		if err != nil {
		return creds
var creds = repo.Id.Apply(repoId =>
                          GetCredentials.InvokeAsync(new GetCredentialsArgs
                              RegistryId = repoId

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:


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:

LanguageExisting non-Output formNew Output form

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:

default_vpc = aws.ec2.get_vpc(default=True)

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: