Ok, trying to grok Pulumi outputs. My use-case is ...
# getting-started
f
Ok, trying to grok Pulumi outputs. My use-case is creating an AWS CloudFront distribution with a custom OriginAccessIdentity, in NodeJS. I have
Copy code
const originAccessIdentity = new aws.cloudfront.OriginAccessIdentity("oai", {
  comment: "Some comment",
});
But now I need to dereference the newly created ETag/Id to use in
Copy code
...
  origins: [
    {
      domainName: bucketV2.bucketRegionalDomainName,
      originId: s3OriginId,
      s3OriginConfig: {
        originAccessIdentity: `origin-access-identity/cloudfront/${originAccessIdentity.id}` <=== Does not work
      },
    },
  ],
...
It does not seem possible to simply use
originAccessIdentity.id
as the returned value is an object wrapped in pulumi sugar. I have tried all sorts of ways to try and get properties from the returned object including: originAccessIdentity.id originAccessIdentity.get() originAccessIdentity.get("id") originAccessIdentity.id.apply(id=>id) I am following along on https://www.pulumi.com/docs/intro/concepts/inputs-outputs/ as well as using step-through debugging via VSCode to inspect the returned object.
b
I think you want to use pulumi.interpolate there. I'm on a plane right now so can't write an example
f
Oh gawd! I'm writing JavaScript so I just glossed over
.apply()
assuming it was JavaScript apply when in actual fact it is a Pulumi SDK method of the same name?
After reading the linked post it sounds like Pulumi is handling asynchronous logic internally rather than leaving it to the outer language to do that. I did see references to Promises when inspecting the returned results and even tried various permutations of
await
but to no avail. I found this about interpolation in the docs but it is in the context of converting inputs to outputs and my use case is very much about simply getting a property from an output object.
I have reduced my code to
Copy code
const originAccessIdentity = new aws.cloudfront.OriginAccessIdentity("test");
const cmt = pulumi.interpolate(originAccessIdentity.Name);
console.log("Name", cmt);
Which still returns the same non-string result
Copy code
Name Proxy {__pulumiOutput: true, resources: ƒ, allResources: ƒ, isKnown: Promise, isSecret: Promise, …}
arg1:Proxy {__pulumiOutput: true, resources: ƒ, allResources: ƒ, isKnown: Promise, isSecret: Promise, …}
[[Handler]]:Object
get:(obj, prop) => {…}
__proto__:Object
[[IsRevoked]]:false
[[Target]]:OutputImpl
__pulumiOutput:true
allResources:() => lifted.then(l => l.allResources)
isKnown:Promise {<pending>}
isSecret:Promise {<pending>}
promise:(withUnknowns) => OutputImpl.getPromisedValue(lifted.then(l => l.value), withUnknowns)
resources:() => resourcesCopy
toJSON:() => {\n            const message = `Calling [toJSON] on an [Output<T>] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n    1: o.apply(v => v.toJSON())\n    2: o.apply(v => JSON.stringify(v))\n\nSee <https://pulumi.io/help/outputs> for more details.\nThis function may throw in a future version of @pulumi/pulumi.`;\n            return message;\n        }
toString:() => {\n            const message = `Calling [toString] on an [Output<T>] is not supported.\n\nTo get the value of an Output<T> as an Output<string> consider either:\n1: o.apply(v => \\`prefix\\${v}suffix\\`)\n2: pulumi.interpolate \\`prefix\\${v}suffix\\`\n\nSee <https://pulumi.io/help/outputs> for more details.\nThis function may throw in a future version of @pulumi/pulumi.`;\n            return message;\n        }
__proto__:Object
b
Yeah you can't log a string like that, you have to do it inside the apply
f
I appreciate that you're travelling at the moment but any help you can provide later would be awesome. I am about to give up on Pulumi, even though I recognise it is really powerful. Just getting frustrated that something that should be easy seems really, really difficult. I'm no stranger to programming. I've been doing it for 30 years in more languages than I can remember. I just don't know what I am missing here.
Going through the GitHub examples repo trying to see if I can find an example there.
b
I'll be back home in the next hour and should be able to put an example together for you.
👍 1
Would you mind sharing the full example of what you're trying to achieve with cloud front?
f
Sure, it's this example https://www.pulumi.com/registry/packages/aws/api-docs/cloudfront/distribution/. But I don't have an existing
originAccessIdentity
(about line 17). Rather than create one in the AWS console I thought I would dogfood it and use Pulumi. So, between the bucket ACL and creating the distribution I have something like
Copy code
const originAccessIdentity = new aws.cloudfront.OriginAccessIdentity("test");
const cmt = pulumi.interpolate`${originAccessIdentity.id}`;
console.log("Name", cmt.toString());

//console.log(originAccessIdentity.apply(cloudfrontAccessIdentityPath));
I say "something like" because I have been editing those same four lines for a few hours now. Once I can understand how to reference the returned property I can clean it all up
b
@full-king-49894 luckily I already had an example of this 🙂 https://github.com/jaxxstorm/pulumi-examples/blob/main/typescript/aws/s3-cloudfront/index.ts the reason your
console.log
isn't working is because it's the wrong way round, it should be:
Copy code
originAccessIdentity.cloudfrontAccessIdentityPath.apply(value => {
  console.log(value)
})
let me know if that helps
f
That did. Thanks. So .apply() is acting like a Promise? Or is it an actual promise? Can I use it like a promise and await it?
b
it is very similar in nature to a promise, but it isn't handled by the js runtime so it's not actually a promise. it's handled in the pulumi engine. it can't be awaited like a usual promise no, that's basically what
apply
is handling for you
f
As I have been going through this exercise today I have butchered my environment a bit. Which made me realise that Pulumi is storing state somewhere. Now I am getting errors on
pulumi up
because I have deleted Origin access identities from the AWS console. Up is now failing with 404 errors saying it cannot access the identities I have deleted. I have tried
pulumi destroy
but that does not seem to clear things up. Do I need to recreate all those test identities in the console THEN delete them via Pulumi?
Hmm the custom promises thing might be an issue for me. I was hoping Pulumi would be easier to write than the myriad of shell scripts I currently have. But the syntax is a bit confusing. Do you know if anyone has created any sort of JS wrapper to abstract that? I am thinking I could probably write a simply promisify() function that could take
Copy code
originAccessIdentity.cloudfrontAccessIdentityPath.apply(value => {
  console.log(value)
})
as an example, and promisify the object that gets returned.
b
Yes Pulumi does store state. If you run a Pulumi refresh it should sync the state with your environment
f
ah, that's damn useful 🙂
Going back to your example
Copy code
originAccessIdentity.cloudfrontAccessIdentityPath.apply(value => {
  console.log(value)
})
Is it possible to get the object? E.g. one level up? Let's say that in a different case the call results in half a dozen properties that are useful. Do I need to do something like
Copy code
someMethod.property1.apply(value => {
  console.log(value)
})
someMethod.property2.apply(value => {
  console.log(value)
})
someMethod.property3.apply(value => {
  console.log(value)
})
... etc
I tried .apply on originAccessIdentity and that resulted in an error
b
what are you trying to do with said object? return it so you can use values with another resource? or view them from the pulumi console so you know what they are?
f
use them elsewhere
mostly - I can see it being useful to be able to view them later in some cases. But mostly I am thinking about the architecture of the code that I am going to be creating
b
I almost never need to log values like you're doing except for debugging. an output value can be passed to another pulumi resource without the need to get its async value. there are some rare occurrences of this, like the originaccessidentity one where the AWS API only allows a string value rather than an inputty value if you want to just view the value, you can export it:
Copy code
export const path = originAccessIdentity.cloudfrontAccessIdentityPath
to further expand on this:
an output value can be passed to another pulumi resource without the need to get its async value
you almost never want to use the plain value because passing an output from one resource to another is how Pulumi builds its dependency graph
f
Ah
I need to change the way I architect and code in order to support the dependency graph
b
I think (but apologies if I'm wrong) you're coming from an imperative mindset, where you have to check if a resource creation succeeded, Pulumi handles all of that for you because it's declarative
f
the export above works when going across modules. I wasn't even thinking that far yet
Yes, you're right
Especially with AWS which is, pardon my language, a bit of a pig
b
no arguments from me :)
f
LOL
I've been working with it for years, and cursing it for years. If it was still 2012 I would be more forgiving but it hasn't really polished up like Google and Azure
And I've written bat, powershell, bash and Node wrappers over the years
Ok, I have taken more than enough of your time today, thank you !!!
I'm going to read up some of the other example code and shift my approach to a more "Pulumi" centric one
b
one last thing. if we go back to the cloudfront distribution example you posted earlier... https://www.pulumi.com/registry/packages/aws/api-docs/cloudfront/distribution/ You'll notice here we create an S3 bucket, give it a name etc
Copy code
# here we create an S3 bucket
const bucketV2 = new aws.s3.BucketV2("bucketV2", {tags: {
    Name: "My bucket",
}});
then here, we create another resource, a bucket acl. you'll notice we pass the
bucketV2.id
from the first resource into here. This is telling Pulumi that the bAcl depends on a value from bucketV2 and builds a dependency graph. This is going to be really hard to do in your imperative code, Pulumi handles it all for you
Copy code
const bAcl = new aws.s3.BucketAclV2("bAcl", {
    bucket: bucketV2.id,
    acl: "private",
});
f
Funny, that was the code that derailed me in the first place. My mind immediately went to async and because I did not see any error handling or obvious async/await/promises I thought "That's a failure waiting to happen" and started down the path of writing it declaratively.
A nice four hour detour on a Friday afternoon. But, I have a much stronger understanding of Pulumi is a result
❤️ 1