Accessing single outputs with Apply
Outputs are asynchronous, meaning their actual plain values are not immediately available. Their values will only become available once the resource has finished provisioning. The asynchronous nature of Outputs is also why, when certain operations such as pulumi preview
runs, the outputs for a new resource do not yet have any possible values. As such, there are limitations on the ways in which you can retrieve and interact with these values.
To demonstrate, let’s say you have the following simple program that creates an AWSX VPC resource in AWS. In this program, you want to view all of the properties of this resource, so you have added a print/log statement to print the vpc
variable.
"use strict";
const pulumi = require("@pulumi/pulumi");
const awsx = require("@pulumi/awsx");
const vpc = new awsx.ec2.Vpc("vpc");
console.log(vpc);
import * as pulumi from "@pulumi/pulumi";
import * as awsx from "@pulumi/awsx";
const vpc = new awsx.ec2.Vpc("vpc");
console.log(vpc);
import pulumi
import pulumi_awsx as awsx
vpc = awsx.ec2.Vpc("vpc")
print(vpc)
package main
import (
"fmt"
"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
vpc, err := ec2.NewVpc(ctx, "vpc", nil)
if err != nil {
return err
}
fmt.Println(vpc)
return nil
})
}
using Pulumi;
using System.Collections.Generic;
using Pulumi.Awsx.Ec2;
return await Deployment.RunAsync(() =>
{
var vpc = new Vpc("vpc");
Console.WriteLine(vpc);
});
package myproject;
import com.pulumi.Pulumi;
import com.pulumi.awsx.ec2.Vpc;
public class App {
public static void main(String[] args) {
Pulumi.run(ctx -> {
var vpc = new Vpc("vpc");
System.out.println(vpc);
});
}
}
This example is not applicable in YAML.
However, deploying this program will show CLI output similar to the following:
# Example CLI output (truncated)
Updating (pulumi/dev)
Type Name Status Info
+ pulumi:pulumi:Stack aws-js-dev created (1s) 391 messages
+ └─ awsx:ec2:Vpc vpc created (1s)
...
...
Diagnostics:
pulumi:pulumi:Stack (aws-js-dev):
<ref *1> Vpc {
__pulumiResource: true,
__pulumiType: 'awsx:ec2:Vpc',
...
...
vpc: OutputImpl {
__pulumiOutput: true,
resources: [Function (anonymous)],
allResources: [Function (anonymous)],
isKnown: Promise { <pending> },
...
...
},
...
}
Resources:
+ 34 created
Duration: 2m17s
# Example CLI output (truncated)
Updating (pulumi/dev)
Type Name Status Info
+ pulumi:pulumi:Stack aws-ts-dev created (1s) 391 messages
+ └─ awsx:ec2:Vpc vpc created (1s)
...
...
Diagnostics:
pulumi:pulumi:Stack (aws-ts-dev):
<ref *1> Vpc {
__pulumiResource: true,
__pulumiType: 'awsx:ec2:Vpc',
...
...
vpc: OutputImpl {
__pulumiOutput: true,
resources: [Function (anonymous)],
allResources: [Function (anonymous)],
isKnown: Promise { <pending> },
...
...
},
...
}
Resources:
+ 34 created
Duration: 2m17s
# Example CLI output (truncated)
Updating (pulumi/dev)
Type Name Status Info
+ pulumi:pulumi:Stack aws-py-dev created (1s) 391 messages
+ └─ awsx:ec2:Vpc vpc created (1s)
...
...
Diagnostics:
pulumi:pulumi:Stack (aws-py-dev):
<pulumi_awsx.ec2.vpc.Vpc object at 0x7f77ac256130>
Resources:
+ 34 created
Duration: 2m17s
# Example CLI output (truncated)
Updating (pulumi/dev)
Type Name Status Info
+ pulumi:pulumi:Stack aws-go-dev created (1s) 391 messages
+ └─ awsx:ec2:Vpc vpc created (1s)
...
...
Diagnostics:
pulumi:pulumi:Stack (aws-go-dev):
&{{{} {{0 0} 0 0 {{} 0} {{} 0}} {0xc000196e00} {0xc000196d90} map[] map[] <nil> [] vpc [] true} {0xc0001961c0} {0xc000196230} {0xc0001962a0} {0xc000196460} {0xc0001964d0} {0xc000196690} {0xc000196700} {0xc0001967e0} {0xc000196850} {0xc0001968c0} {0xc000196930} {0xc000196a10} {0xc000196a80} {0xc000196b60}}
Resources:
+ 34 created
Duration: 3m7s
# Example CLI output (truncated)
Updating (pulumi/dev)
Type Name Status Info
+ pulumi:pulumi:Stack aws-csharp-dev created (1s) 391 messages
+ └─ awsx:ec2:Vpc vpc created (1s)
...
...
Diagnostics:
pulumi:pulumi:Stack (aws-csharp-dev):
Pulumi.Awsx.Ec2.Vpc
Resources:
34 created
Duration: 3m7s
# Example CLI output (truncated)
Updating (pulumi/dev)
Type Name Status Info
+ pulumi:pulumi:Stack aws-java-dev created (1s) 391 messages
+ └─ awsx:ec2:Vpc vpc created (1s)
...
...
# Nothing is printed
Resources:
+ 34 created
Duration: 2m17s
This example is not applicable in YAML.
As shown above, using this method will not provide a JSON representation of the VPC resource complete with its properties and associated property values. This is because, when it comes to Pulumi resource classes, there is no way to output this kind of JSON representation for a resource.
Ultimately, if you want to view the properties of a resource, you will need to access them at the individual property level. The rest of this guide will demonstrate how to access and interact with a single property using apply
apply
Apply
Apply
apply
method is great for when you need to access single values. However, if you need to access multiple output values across multiple resources, you will need to use Pulumi’s all
method instead.Accessing single output values
Let’s say you want to print the ID of the VPC you’ve created. Given that this is an individual resouce property and not the entire resource itself, you might try logging the value like normal:
"use strict";
const pulumi = require("@pulumi/pulumi");
const awsx = require("@pulumi/awsx");
const vpc = new awsx.ec2.Vpc("vpc");
console.log(vpc.vpcId);
import * as pulumi from "@pulumi/pulumi";
import * as awsx from "@pulumi/awsx";
const vpc = new awsx.ec2.Vpc("vpc");
console.log(vpc.vpcId);
import pulumi
import pulumi_awsx as awsx
vpc = awsx.ec2.Vpc("vpc")
print(vpc.vpc_id)
package main
import (
"fmt"
"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
vpc, err := ec2.NewVpc(ctx, "vpc", nil)
if err != nil {
return err
}
fmt.Println(vpc.VpcId)
return nil
})
}
using Pulumi;
using System.Collections.Generic;
using Pulumi.Awsx.Ec2;
return await Deployment.RunAsync(() =>
{
var vpc = new Vpc("vpc");
Console.WriteLine(vpc.VpcId);
});
package myproject;
import com.pulumi.Pulumi;
import com.pulumi.awsx.ec2.Vpc;
public class App {
public static void main(String[] args) {
Pulumi.run(ctx -> {
var vpc = new Vpc("vpc");
System.out.println(vpc.vpcId());
});
}
}
However, if you update the program as shown above and run pulumi up
, you will still not receive the value you are looking for as shown in the following CLI output:
# Example CLI output (truncated)
Diagnostics:
pulumi:pulumi:Stack (aws-js-dev):
OutputImpl {
__pulumiOutput: true,
resources: [Function (anonymous)],
allResources: [Function (anonymous)],
isKnown: Promise { <pending> },
isSecret: Promise { <pending> },
promise: [Function (anonymous)],
toString: [Function (anonymous)],
toJSON: [Function (anonymous)]
}
# Example CLI output (truncated)
Diagnostics:
pulumi:pulumi:Stack (aws-js-dev):
OutputImpl {
__pulumiOutput: true,
resources: [Function (anonymous)],
allResources: [Function (anonymous)],
isKnown: Promise { <pending> },
isSecret: Promise { <pending> },
promise: [Function (anonymous)],
toString: [Function (anonymous)],
toJSON: [Function (anonymous)]
}
# Example CLI output (truncated)
Diagnostics:
pulumi:pulumi:Stack (aws-iac-dev):
Calling __str__ on an Output[T] is not supported.
To get the value of an Output[T] as an Output[str] consider:
1. o.apply(lambda v: f"prefix{v}suffix")
See https://www.pulumi.com/docs/concepts/inputs-outputs for more details.
This function may throw in a future version of Pulumi.
# Example CLI output (truncated)
Diagnostics:
pulumi:pulumi:Stack (aws-go-dev):
{0xc000137180}
# Example CLI output (truncated)
Diagnostics:
pulumi:pulumi:Stack (aws-csharp-dev):
Calling [ToString] on an [Output<T>] is not supported.
To get the value of an Output<T> as an Output<string> consider:
1. o.Apply(v => $"prefix{v}suffix")
2. Output.Format($"prefix{hostname}suffix");
See https://www.pulumi.com/docs/concepts/inputs-outputs for more details.
This function may throw in a future version of Pulumi.
# Example CLI output (truncated)
Updating (pulumi/dev)
Type Name Status Info
+ pulumi:pulumi:Stack aws-java-dev created (1s) 391 messages
+ └─ awsx:ec2:Vpc vpc created (1s)
...
...
# Nothing is printed
Resources:
+ 34 created
Duration: 2m17s
This example is not applicable in YAML.
This is where apply
apply
Apply
Apply
pulumi up
, the apply
apply
Apply
Apply
print | log
statement is capable of doing.
The syntax of apply
apply
Apply
Apply
<resource>.<property-name>.apply(<property-name> => <function-to-apply>)
<resource>.<property-name>.apply(<property-name> => <function-to-apply>)
<resource>.<property-name>.apply(lambda <property-name>: <function-to-apply>)
<resource>.<property-name>.ApplyT(func(<property-name> string) error {
<function-to-apply>
return nil
})
<resource>.<property-name>.Apply(<property-name> => {
<function-to-apply>;
return <property-name>;
});
<resource>.<property-name>().applyValue(<property-name> -> {
<function-to-apply>;
return null;
});
You can directly access resource properties without the use of Apply in YAML.
The breakdown of the different parts of the syntax is as follows:
<resource>
is the name of the resource (i.e.vpc
)<property-name>
is the name of the property to retrieve (i.e.vpc_id
)<function-to-apply>
is the function to apply against the value of the property
This means that if you want to print out the value of the VPC ID, the program needs to look like the following:
"use strict";
const pulumi = require("@pulumi/pulumi");
const awsx = require("@pulumi/awsx");
const vpc = new awsx.ec2.Vpc("vpc");
vpc.vpcId.apply(id => console.log(`VPC ID: {id}`));
import * as pulumi from "@pulumi/pulumi";
import * as awsx from "@pulumi/awsx";
const vpc = new awsx.ec2.Vpc("vpc");
vpc.vpcId.apply(id => console.log(`VPC ID: {id}`));
import pulumi
import pulumi_awsx as awsx
vpc = awsx.ec2.Vpc("vpc")
vpc.vpc_id.apply(lambda id: print('VPC ID:', id))
package main
import (
"fmt"
"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
vpc, err := ec2.NewVpc(ctx, "vpc", nil)
if err != nil {
return err
}
vpc.VpcId().ApplyT(func(id string) error {
fmt.Printf("VPC ID: %s", id)
return nil
})
return nil
})
}
ApplyT
spawns a Goroutine to await the availability of the implicated dependencies. This function accepts a T
or (T, error)
signature; the latter accomodates for error handling. Alternatively, one may use the ApplyTWithContext
function in which the provided context can be used to reject the output as canceled. Error handling may also be achieved using an error
chan
.using Pulumi;
using System.Collections.Generic;
using Pulumi.Awsx.Ec2;
return await Deployment.RunAsync(() =>
{
var vpc = new Vpc("vpc");
vpc.VpcId.Apply(id => { Console.WriteLine($"VPC ID: {id}"); return id; });
});
package myproject;
import com.pulumi.Pulumi;
import com.pulumi.awsx.ec2.Vpc;
public class App {
public static void main(String[] args) {
Pulumi.run(ctx -> {
var vpc = new Vpc("vpc");
vpc.vpcId().applyValue(i -> {
System.out.println("VPC ID: " + i);
return null;
});
});
}
}
# YAML does not have the Apply method, but you can access values directly.
name: awsx-vpc-yaml
runtime: yaml
description: An example that creates a new VPC using the default settings.
resources:
vpc:
type: awsx:ec2:Vpc
outputs:
vpcId: ${vpc.vpcId}
The above example will wait for the value to be returned from the API and print it to the console as shown below:
Updating (pulumi/dev)
Type Name Status Info
pulumi:pulumi:Stack aws-iac-dev 1 message
Diagnostics:
pulumi:pulumi:Stack (aws-iac-dev):
VPC ID: vpc-0f8a025738f2fbf2f
Resources:
34 unchanged
Duration: 12s
You can now see the value of the VPC ID property that you couldn’t see before when using a regular print | log
statement.
Accessing nested output values
Sometimes an output has an object with deeply nested values, and there may be times where the values of these nested properties need to be passed as inputs to other resources. For example, let’s say you have created an AWS Certificate Manager certificate resource as shown below:
"use strict";
const pulumi = require("@pulumi/pulumi");
const aws = require("@pulumi/aws");
let certCertificate = new aws.acm.Certificate("cert", {
domainName: "example.com",
validationMethod: "DNS",
});
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
let certCertificate = new aws.acm.Certificate("cert", {
domainName: "example.com",
validationMethod: "DNS",
});
import pulumi
import pulumi_aws as aws
certificate = aws.acm.Certificate('cert',
domain_name='example.com',
validation_method='DNS'
)
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/acm"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := acm.NewCertificate(ctx, "cert", &acm.CertificateArgs{
DomainName: pulumi.String("example.com"),
ValidationMethod: pulumi.String("DNS"),
})
if err != nil {
return err
}
return nil
})
}
using Pulumi;
using Pulumi.Aws.Acm;
using System.Collections.Generic;
return await Deployment.RunAsync(() =>
{
var cert = new Certificate("cert", new CertificateArgs
{
DomainName = "example",
ValidationMethod = "DNS",
});
});
package myproject;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.acm.Certificate;
import com.pulumi.aws.acm.CertificateArgs;
public class App {
public static void main(String[] args) {
Pulumi.run(ctx -> {
var cert = new Certificate("cert",
CertificateArgs.builder()
.domainName("example")
.validationMethod("DNS")
.build());
});
}
}
name: aws-acm-certificate-yaml
runtime: yaml
resources:
cert:
type: aws:acm:Certificate
properties:
domainName: example.com
validationMethod: DNS
This resource will have outputs that resemble the following:
# Example truncated output of the ACM certificate resource
cert: {
arn : "arn:aws:acm:eu-central-1..."
certificate_authority_arn: ""
certificate_body : <null>
certificate_chain : <null>
domain_name : "example.com"
domain_validation_options: [
[0]: {
domain_name : "example.com"
resource_record_name : "_0a822dde6347292b.example.com."
resource_record_type : "CNAME"
resource_record_value: "_527b1cdf2159204b.mhbtsbpdnt.acm-validations.aws."
}
]
...
...
}
Suppose you want to validate your certificate by creating an Amazon Route 53 record. To do so, you will need to retrieve the value of the resource record from the ACM certificate. This value is nested in the domain validation options property of the certificate resource, which is an array. Because that value is an output, you would normally need to use apply
apply
Apply
Apply
let certValidation = new aws.route53.Record("cert_validation", {
records: [
// Need to pass along a deep subproperty of this Output
certCertificate.domainValidationOptions.apply(
domainValidationOptions => domainValidationOptions[0].resourceRecordValue),
],
...
});
let certValidation = new aws.route53.Record("cert_validation", {
records: [
// Need to pass along a deep subproperty of this Output
certCertificate.domainValidationOptions.apply(
domainValidationOptions => domainValidationOptions[0].resourceRecordValue),
],
...
});
record = aws.route53.Record('validation',
records=[
# Need to pass along a deep subproperty of this Output
certificate.domain_validation_options.apply(
lambda domain_validation_options: domain_validation_options[0]['resourceRecordValue']
)
],
...
)
record, err := route53.NewRecord(ctx, "validation", &route53.RecordArgs{
Records: pulumi.StringArray{
cert.DomainValidationOptions.ApplyT(func(opts []acm.CertificateDomainValidationOption) string {
return *opts[0].ResourceRecordValue
}).(pulumi.StringOutput),
},
...
})
if err != nil {
return err
}
var record = new Record("validation", new RecordArgs
{
Records = {
cert.DomainValidationOptions.Apply(opts => opts[0].ResourceRecordValue!)
},
...
});
var record = new Record("validation",
RecordArgs.builder()
.records(
cert.domainValidationOptions()
.applyValue(opts -> opts.get(0).resourceRecordValue().get())
.applyValue(String::valueOf)
.applyValue(List::of))
.build());
This example is not applicable in YAML.
An easier way to access deeply nested properties is by using lifting. Lifting allows you to access properties and elements directly from the Output
Output<T>
Output[T]
Output
Output<T>
apply
apply
Apply
Apply
let certValidation = new aws.route53.Record("cert_validation", {
records: [
certCertificate.domainValidationOptions[0].resourceRecordValue
],
...
let certValidation = new aws.route53.Record("cert_validation", {
records: [
certCertificate.domainValidationOptions[0].resourceRecordValue
],
...
record = aws.route53.Record('validation',
records=[
certificate.domain_validation_options[0].resource_record_value
],
...
record, err := route53.NewRecord(ctx, "validation", &route53.RecordArgs{
Records: pulumi.StringArray{
// Notes:
// * `Index` looks up an index in an `ArrayOutput` and returns a new `Output`.
// * Accessor methods like `ResourceRecordValue` lookup properties of a custom struct `Output` and return a new `Output`.
// * `Elem` dereferences a `PtrOutput` to an `Output`, equivalent to `*`.
cert.DomainValidationOptions.Index(pulumi.Int(0)).ResourceRecordValue().Elem(),
},
...
})
if err != nil {
return err
}
var record = new Record("validation", new RecordArgs
{
// Notes:
// * `GetAt` looks up an index in an `Output<ImmutableArray<T>>` and returns a new `Output<T>`
// * There are not yet accessor methods for referencing properties like `ResourceRecordValue` on an `Output<T>` directly,
// so the `Apply` is still needed for the property access.
Records = cert.DomainValidationOptions.GetAt(0).Apply(opt => opt.ResourceRecordValue!),
});
// Lifting is currently not supported in Java.
resources:
cert:
type: aws:acm:Certificate
properties:
domainName: example
validationMethod: DNS
record:
type: aws:route53:Record
properties:
records:
# YAML handles inputs and outputs transparently.
- ${cert.domainValidationOptions[0].resourceRecordValue}
This approach is easier to read and write and does not lose any important dependency information that is needed to properly create and maintain the stack. This approach doesn’t work in all cases, but when it does, it can be a great help.
Creating new output values
Outputs and Strings
Outputs that return to the engine as strings cannot be used directly in operations such as string concatenation until the output value has returned to Pulumi. In these scenarios, you’ll need to wait for the value to return using apply
.
For example, the following code creates an HTTPS URL from the DNS name (the plain value) of a virtual machine (in this case an EC2 instance):
"use strict";
const pulumi = require("@pulumi/pulumi");
const aws = require("@pulumi/aws");
const server = new aws.ec2.Instance("web-server", {
ami: "ami-0319ef1a70c93d5c8",
instanceType: "t2.micro",
});
const url = server.publicDns.apply(dnsName => `https://${dnsName}`);
exports.InstanceUrl = url;
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const server = new aws.ec2.Instance("web-server", {
ami: "ami-0319ef1a70c93d5c8",
instanceType: "t2.micro",
});
const url = server.publicDns.apply(dnsName => `https://${dnsName}`);
export const InstanceUrl = url;
import pulumi
import pulumi_aws as aws
server = aws.ec2.Instance(
"web-server",
ami="ami-0319ef1a70c93d5c8",
instance_type="t2.micro",
)
url = instance.public_dns.apply(
lambda dns_name: "https://" + dns_name
)
pulumi.export("InstanceUrl", url)
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
server, err := ec2.NewInstance(ctx, "web-server", &ec2.InstanceArgs{
Ami: pulumi.String("ami-0319ef1a70c93d5c8"),
InstanceType: pulumi.String("t2.micro"),
})
if err != nil {
return err
}
url := server.PublicDns.ApplyT(func(dns string) string {
return "https://" + dns
}).(pulumi.StringOutput)
ctx.Export("InstanceUrl", url)
return nil
})
}
using Pulumi;
using Pulumi.Aws.Ec2;
using Pulumi.Aws.Ec2.Inputs;
using System.Collections.Generic;
return await Deployment.RunAsync(() =>
{
var server = new Instance("web-server", new InstanceArgs {
Ami = "ami-0319ef1a70c93d5c8",
InstanceType = "t2.micro",
});
var url = server.PublicDns.Apply(dns => $"https://{dns}");
return new Dictionary<string, object?>
{
["InstanceUrl"] = url,
};
});
package myproject;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.aws.ec2.Instance;
import com.pulumi.aws.ec2.InstanceArgs;
import com.pulumi.aws.ec2.SecurityGroup;
import com.pulumi.aws.ec2.SecurityGroupArgs;
import com.pulumi.aws.ec2.inputs.SecurityGroupIngressArgs;
import java.util.List;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var server = new Instance("web-server",
InstanceArgs.builder()
.ami("ami-0319ef1a70c93d5c8")
.instanceType("t2.micro")
.build());
var url = server.publicDns().applyValue(dns -> "https://" + dns);
ctx.export("InstanceUrl", url);
}
}
# YAML does not have the Apply method, but you can access values directly.
name: aws-ec2-instance-yaml
runtime: yaml
description: An example that shows how to create an EC2 instance and security group.
resources:
server:
type: aws:ec2:Instance
properties:
ami: ami-0319ef1a70c93d5c8
instanceType: t2.micro
outputs:
InstanceUrl: https://${server.publicDns}
The CLI output of this code would look something like the following:
Updating (pulumi/dev)
Type Name Status
pulumi:pulumi:Stack aws-iac-dev
- └─ awsx:ec2:Vpc vpc
Outputs:
InstanceUrl: "https://ec2-52-59-110-22.eu-central-1.compute.amazonaws.com"
Duration: 5s
The result of the call to apply
apply
Apply
Apply
url
variable is now of type Output. This variable will wait for the new value to be returned from the apply
apply
Apply
Apply
public DNS
property of the server
resource) are also kept in the resulting Output
Outputs and JSON
Often in the course of working with web technologies, you encounter JavaScript Object Notation (JSON) which is a popular specification for representing data. In many scenarios, you’ll need to embed resource outputs into a JSON string. In these scenarios, you need to first wait for the returned output, then build the JSON string.
For example, let’s say you want to create an S3 bucket and a bucket policy that allows the Lambda service to write objects to that bucket. The example below shows how to use apply
to create the bucket policy JSON object using an output value from the S3 bucket resource (in this case the bucket’s ARN):
"use strict";
const pulumi = require("@pulumi/pulumi");
const aws = require("@pulumi/aws");
// Create an S3 bucket
const s3Bucket = new aws.s3.Bucket("myBucket");
// IAM Policy Document that allows the Lambda service to write to the S3 bucket
const s3BucketPolicyDocument = s3Bucket.arn.apply(arn =>
JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: { Service: "lambda.amazonaws.com" },
Action: ["s3:PutObject", "s3:PutObjectAcl"],
Resource: `${arn}/*`,
},
],
}),
);
// Attach the policy to the bucket
const s3BucketPolicy = new aws.s3.BucketPolicy("myBucketPolicy", {
bucket: s3Bucket.id,
policy: s3BucketPolicyDocument,
});
// Export the names and ARNs of the created resources
exports.bucketName = s3Bucket.id;
exports.bucketArn = s3Bucket.arn;
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// Create an S3 bucket
const s3Bucket = new aws.s3.Bucket("myBucket");
// IAM Policy Document that allows the Lambda service to write to the S3 bucket
const s3BucketPolicyDocument = s3Bucket.arn.apply(arn =>
JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: { Service: "lambda.amazonaws.com" },
Action: ["s3:PutObject", "s3:PutObjectAcl"],
Resource: `${arn}/*`,
},
],
}),
);
// Attach the policy to the bucket
const s3BucketPolicy = new aws.s3.BucketPolicy("myBucketPolicy", {
bucket: s3Bucket.id,
policy: s3BucketPolicyDocument,
});
// Export the names and ARNs of the created resources
export const bucketName = s3Bucket.id;
export const bucketArn = s3Bucket.arn;
import pulumi
import pulumi_aws as aws
import json
# Create an S3 bucket
s3_bucket = aws.s3.Bucket("myBucket")
# IAM Policy Document that allows the Lambda service to write to the S3 bucket
s3_bucket_policy_document = s3_bucket.arn.apply(
lambda arn: json.dumps(
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": ["s3:PutObject", "s3:PutObjectAcl"],
"Resource": f"{arn}/*",
}
],
}
)
)
# Attach the policy to the bucket
s3_bucket_policy = aws.s3.BucketPolicy(
"myBucketPolicy",
bucket=s3_bucket.id,
policy=s3_bucket_policy_document,
)
# Export the names and ARNs of the created resources
pulumi.export("bucket_name", s3_bucket.id)
pulumi.export("bucket_arn", s3_bucket.arn)
package main
import (
"fmt"
"github.com/pulumi/pulumi-aws/sdk/v4/go/aws/s3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Create an S3 bucket
s3Bucket, err := s3.NewBucket(ctx, "myBucket", nil)
if err != nil {
return err
}
// IAM Policy Document that allows the Lambda service to write to the S3 bucket
s3Bucket.Arn.ApplyT(func(arn string) (string, error) {
policy := fmt.Sprintf(`{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": ["s3:PutObject", "s3:PutObjectAcl"],
"Resource": "%s/*"
}]
}`, arn)
// Attach the policy to the bucket
_, err := s3.NewBucketPolicy(ctx, "myBucketPolicy", &s3.BucketPolicyArgs{
Bucket: s3Bucket.ID(),
Policy: pulumi.String(policy),
})
if err != nil {
return "", err
}
return "", nil
})
// Export the names and ARNs of the created resources
ctx.Export("bucketName", s3Bucket.ID())
ctx.Export("bucketArn", s3Bucket.Arn)
return nil
})
}
using Pulumi;
using Pulumi.Aws.Iam;
using Pulumi.Aws.S3;
using System.Collections.Generic;
using System.Text.Json;
return await Deployment.RunAsync(() =>
{
var bucket = new Bucket("myBucket");
var s3BucketPolicyDocument = bucket.Arn.Apply(arn => JsonSerializer.Serialize(new
{
Version = "2012-10-17",
Statement = new[]
{
new
{
Effect = "Allow",
Principal = new { Service = "lambda.amazonaws.com" },
Action = new[] { "s3:PutObject", "s3:PutObjectAcl" },
Resource = $"{arn}/*"
}
}
}));
var bucketPolicy = new BucketPolicy("myBucketPolicy", new BucketPolicyArgs
{
Bucket = bucket.Id,
Policy = s3BucketPolicyDocument
});
return new Dictionary<string, object?>
{
["bucketName"] = bucket.Id,
["bucketArn"] = bucket.Arn
};
});
package myproject;
import com.pulumi.Pulumi;
import com.pulumi.aws.s3.Bucket;
import com.pulumi.aws.s3.BucketPolicy;
import com.pulumi.aws.s3.BucketPolicyArgs;
import static com.pulumi.codegen.internal.Serialization.*;
public class App {
public static void main(String[] args) {
Pulumi.run(ctx -> {
var bucket = new Bucket("myBucket");
var policyDocument = bucket.arn().applyValue(arn -> serializeJson(
jsonObject(
jsonProperty("Version", "2012-10-17"),
jsonProperty("Statement", jsonArray(jsonObject(
jsonProperty("Effect", "Allow"),
jsonProperty("Action", jsonArray("s3:PutObject", "s3:PutObjectAcl")),
jsonProperty("Principal", jsonObject(
jsonProperty("Service", "lambda.amazonaws.com")
)),
jsonProperty("Resource", arn + "/*")
)))
)
));
var bucketPolicy = new BucketPolicy("myBucketPolicy", BucketPolicyArgs.builder()
.bucket(bucket.id())
.policy(policyDocument)
.build());
ctx.export("bucketName", bucket.id());
ctx.export("bucketArn", bucket.arn());
});
}
}
name: aws-s3bucket-bucketpolicy-yaml
runtime: yaml
description: An example that deploys an S3 bucket and bucket policy on AWS.
resources:
myBucket:
type: aws:s3/bucket:Bucket
myBucketPolicy:
type: aws:s3/bucketPolicy:BucketPolicy
properties:
bucket: ${myBucket.id}
policy:
fn::toJSON:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "lambda.amazonaws.com"
Action:
- "s3:PutObject"
- "s3:PutObjectAcl"
Resource: "${myBucket.arn}/*"
outputs:
bucket_name: ${myBucket.id}
bucket_arn: ${myBucket.arn}
This operation is so common that Pulumi provides first-class helper functions to make it much easier. These helper functions can:
- convert native objects into JSON strings (i.e., serialization)
- convert JSON strings into native objects (i.e., deserialization)
Converting JSON objects to strings
If you need to construct a JSON string using output values from Pulumi resources, you can easily do so using a JSON stringify helper. These helpers unwrap Pulumi outputs without requiring the use of apply
and produce JSON string outputs suitable for passing to other resources as inputs.
In this example, a JSON string for an S3 bucket policy is composed with two outputs: the authenticated user’s account ID and the bucket’s computed Amazon Resource Name (ARN). Here, since the statement’s Resource
property uses only an ARN, the ARN output can be passed as a value directly. To compose more complex values, you can either use apply
or all
to assemble and return a new output or, for string values, use a string helper such as below, which composes the ARN for the Principal
property using the account ID output:
"use strict";
const pulumi = require("@pulumi/pulumi");
const aws = require("@pulumi/aws");
// Get the account ID of the current user as a Pulumi output.
const accountID = aws.getCallerIdentityOutput().accountId;
// Create an S3 bucket.
const bucket = new aws.s3.Bucket("my-bucket");
// Create an S3 bucket policy allowing anyone in the account to list the contents of the bucket.
const policy = new aws.s3.BucketPolicy("my-bucket-policy", {
bucket: bucket.id,
policy: pulumi.jsonStringify({
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: {
AWS: pulumi.interpolate`arn:aws:iam::${accountID}:root`,
},
Action: "s3:ListBucket",
Resource: bucket.arn,
},
],
}),
});
// Export the name of the bucket
exports.bucketName = bucket.id;
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// Get the account ID of the current user as a Pulumi output.
const accountID = aws.getCallerIdentityOutput().accountId;
// Create an S3 bucket.
const bucket = new aws.s3.Bucket("my-bucket");
// Create an S3 bucket policy allowing anyone in the account to list the contents of the bucket.
const policy = new aws.s3.BucketPolicy("my-bucket-policy", {
bucket: bucket.id,
policy: pulumi.jsonStringify({
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: {
AWS: pulumi.interpolate`arn:aws:iam::${accountID}:root`,
},
Action: "s3:ListBucket",
Resource: bucket.arn,
},
],
}),
});
// Export the name of the bucket.
export const bucketName = bucket.id;
import pulumi
import pulumi_aws as aws
# Get the account ID of the current user as a Pulumi output.
account_id = aws.get_caller_identity_output().apply(
lambda identity: identity.account_id
)
# Create an S3 bucket.
bucket = aws.s3.Bucket("my-bucket")
# Create an S3 bucket policy allowing anyone in the account to list the contents of the bucket.
policy = aws.s3.BucketPolicy(
"my-bucket-policy",
bucket=bucket.id,
policy=pulumi.Output.json_dumps(
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": pulumi.Output.format("arn:aws:iam::{0}:root", account_id)
},
"Action": "s3:ListBucket",
"Resource": bucket.arn,
}
],
}
),
)
# Export the name of the bucket
pulumi.export("bucketName", bucket.id)
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws"
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/s3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Get the account ID of the current user as a Pulumi Output.
callerIdentity, err := aws.GetCallerIdentity(ctx, nil, nil)
if err != nil {
return err
}
accountID := callerIdentity.AccountId
// Create an AWS resource (S3 Bucket)
bucket, err := s3.NewBucket(ctx, "my-bucket", nil)
if err != nil {
return err
}
// Create an S3 bucket policy allowing anyone in the account to list the contents of the bucket.
_, err = s3.NewBucketPolicy(ctx, "my-bucket-policy", &s3.BucketPolicyArgs{
Bucket: bucket.ID(),
Policy: pulumi.JSONMarshal(map[string]interface{}{
"Version": pulumi.ToOutput("2012-10-17"),
"Statement": pulumi.ToOutput([]interface{}{
pulumi.ToMapOutput(map[string]pulumi.Output{
"Effect": pulumi.ToOutput("Allow"),
"Principal": pulumi.ToMapOutput(map[string]pulumi.Output{
"AWS": pulumi.Sprintf("arn:aws:iam::%s:root", accountID),
}),
"Action": pulumi.ToOutput("s3:ListBucket"),
"Resource": bucket.Arn,
}),
}),
}),
})
if err != nil {
return err
}
// Export the name of the bucket
ctx.Export("bucketName", bucket.ID())
return nil
})
}
using System.Collections.Generic;
using Pulumi;
using Pulumi.Aws.S3;
return await Deployment.RunAsync(() =>
{
// Get the account ID of the current user as a Pulumi output.
var accountID = Pulumi.Aws.GetCallerIdentity.Invoke().Apply(identity => identity.AccountId);
// Create an S3 bucket.
var bucket = new Bucket("my-bucket");
// Create an S3 bucket policy allowing anyone in the account to list the contents of the bucket.
var policy = new BucketPolicy("my-bucket-policy", new BucketPolicyArgs
{
Bucket = bucket.Id,
Policy = Output.JsonSerialize(Output.Create(
new
{
Version = "2012-10-17",
Statement = new[]
{
new
{
Effect = "Allow",
Principal = new
{
AWS = Output.Format($"arn:aws:iam::{accountID}:root")
},
Action = "s3:ListBucket",
Resource = bucket.Arn,
}
}
}
))
}
);
// Export the name of the bucket.
return new Dictionary<string, object?> { ["bucketName"] = bucket.Id };
});
Converting JSON strings to outputs
If you have an output in the form of a JSON string and you need to interact with it like you would a regular JSON object, you can use Pulumi’s parsing helper function.
In the example below, you can parse a JSON string into a JSON object and then, inside of an apply
, manipulate the object to remove all of the policy statements:
"use strict";
const pulumi = require("@pulumi/pulumi");
const jsonIAMPolicy = pulumi.output(`{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation"
],
"Resource": "*"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "s3:*",
"Resource": "arn:aws:s3:::my-bucket"
}
]
}`);
// Parse the string output.
const policyWithNoStatements = pulumi.jsonParse(jsonIAMPolicy).apply(policy => {
// Empty the policy's Statements list.
policy.Statement = [];
return policy;
});
// Export the modified policy.
exports.policy = policyWithNoStatements;
import * as pulumi from "@pulumi/pulumi";
const jsonIAMPolicy = pulumi.output(`{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation"
],
"Resource": "*"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "s3:*",
"Resource": "arn:aws:s3:::my-bucket"
}
]
}`);
// Parse the string output.
const policyWithNoStatements: pulumi.Output<object> = pulumi.jsonParse(jsonIAMPolicy).apply(policy => {
// Empty the policy's Statements list.
policy.Statement = [];
return policy;
});
// Export the modified policy.
export const policy = policyWithNoStatements;
import pulumi
json_iam_policy = pulumi.Output.from_input(
"""
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation"
],
"Resource": "*"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "s3:*",
"Resource": "arn:aws:s3:::my-bucket"
}
]
}
"""
)
def update_policy(policy):
# Empty the policy's Statements list.
policy.update({"Statement": []})
return policy
# Parse the string output.
policy_with_no_statements = pulumi.Output.json_loads(json_iam_policy).apply(
lambda policy: update_policy
)
# Export the modified policy.
pulumi.export("policy", policy_with_no_statements)
package main
import (
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
jsonIAMPolicy := pulumi.ToOutput(`{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation"
],
"Resource": "*"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "s3:*",
"Resource": "arn:aws:s3:::my-bucket"
}
]
}`).(pulumi.StringInput)
// Parse the string output.
policyWithNoStatements := pulumi.JSONUnmarshal(jsonIAMPolicy).ApplyT(
func(v interface{}) (interface{}, error) {
// Empty the policy's Statements list.
v.(map[string]interface{})["Statement"] = []pulumi.ArrayOutput{}
return v, nil
},
)
// Export the modified policy.
ctx.Export("policy", policyWithNoStatements)
return nil
})
}
using System.Collections.Generic;
using Pulumi;
return await Deployment.RunAsync(() =>
{
var jsonIAMPolicy = Output.Create(
@"
{
""Version"": ""2012-10-17"",
""Statement"": [
{
""Sid"": ""VisualEditor0"",
""Effect"": ""Allow"",
""Action"": [
""s3:ListAllMyBuckets"",
""s3:GetBucketLocation""
],
""Resource"": ""*""
},
{
""Sid"": ""VisualEditor1"",
""Effect"": ""Allow"",
""Action"": [
""s3:*""
],
""Resource"": ""arn:aws:s3:::my-bucket""
}
]
}
"
);
// Parse the Output<string> into a C# Dictionary.
var policyWithNoStatements = Output
.JsonDeserialize<Dictionary<string, object?>>(jsonIAMPolicy)
.Apply(policy =>
{
// Empty the policy's Statements list.
policy["Statement"] = Output.Create(new List<Dictionary<string, object?>> { });
return policy;
});
// Export the modified policy.
return new Dictionary<string, object?> { ["policy"] = policyWithNoStatements, };
});
For more details view the Node.js documentation.
For more details view the Node.js documentation.
For more details view the Python documentation.
For more details view the Go documentation.
For more details view the .NET documentation.
Thank you for your feedback!
If you have a question about how to use Pulumi, reach out in Community Slack.
Open an issue on GitHub to report a problem or suggest an improvement.