I keep hitting situations where I want to access v...
# general
c
I keep hitting situations where I want to access values from an object and hitting the dreaded "Calling [toJSON] on an [Output<T>] is not supported". For the most part I've learned to reference the object and let Pulumi figure it out. But now I've hit a situation where that doesn't seem to work:
Copy code
let secretsToStore = {
                    "username": rdsInstance.username,
                    "password": password.result,
                    "engine": rdsInstance.engine,
                    "port": rdsInstance.port,
                    "dbname": rdsInstance.name,
                    "dbInstanceIdentifier": rdsInstance.identifier
                };

                const storedPassword = new aws.secretsmanager.SecretVersion(dbName + "_password", {
                    secretId: rotation.id,
                    secretString: JSON.stringify(secretsToStore)
                });
The secretsToStore object ends up with a bunch of "Calling [toJSON] on an [Output<T>] is not supported" strings. What's the proper way to handle this?
BTW, I've tried .apply(value => value) but no luck.
f
It’s easier to think of
Output
types as promises, so the JSON.stringify should happen within some kind of apply. In this case, you probably want something like:
Copy code
pulumi.all([rdsInstance.username, rdsInstance.engine, ...]).apply(([username, engine, ...]) => JSON.stringify({ ... }))
so, the idea here is you need to have all those outputs available before you can apply them and construct your object — the return value of the above statement is going to be an
Output<string>
which you can then pass as an argument to
secretString
c
Thanks, I'll give that a try. The challenge I've found with this approach (unless I misunderstand) is that there's no "await" equivalent, meaning the "new aws.secretsmanager.SecretVersion" call has to also be inside the apply() stanza for operations to complete in order.
f
It depends on what you’re doing. For example, if there are side effects, then those would need to live w/in the
apply
Resources should usually be able to take an
Output
as an arg
So that’s why the
new
doesn’t have to live w/in the
apply
c
In this case, though, what I'm sending is a JSON.stringify() to the new SecretVersion. Pulumi won't see that as an Output, I don't expect.
f
It should — the result of an
apply
is an Output<T> where T is the return type of the function you passed to
apply
c
Hmmm. When I was testing, I put inline rdsInstance.username.apply(value => value) but the problem is I was trying to put it into a JSON object and then stringify it.
Thanks all the same for the suggestion on how to fix it!
f
It’s how you can do things like:
Copy code
const bucket = new aws.s3.Bucket("my-bucket", ...);
const cdn = new aws.cloudfront.Distribution("cdn", {
     ...
     loggingConfig: {
       bucket: bucket.bucketDomainName
       ...
     }
})
In this case, bucketDomainName is an
Output
, which we’re now passing as an input to create the CDN
c
Yes, within a call to a new Pulumi object, it generally works. In the case of a new aws.secretsmanager.SecretVersion, however, the secretString object expects JSON.stringify(), not an Output object. Hence the problem in my case.
Since it’s expecting an
Input<string>
and
Output<string>
should work
If it’s not, can you please show me the error message? That would be a bug
c
What I'm reading is that secretString expects a string. Never mind. It's an Input<string>
f
So an
Input<string>
really translates to either a string, a promise that returns a string, or an output that returns a string
I’m guessing you’ve already read https://www.pulumi.com/docs/intro/concepts/programming-model/#outputs — but in case you haven’t it clarifies some of this. We definitely need to make it clearer/easier to understand how this works.
c
I've read it but really struggled with it. I've had to essentially cast out of mind how I would do things with "await" since that doesn't exist.
I generally find trouble whenever I try to cross between pure Pulumi objects and traditional Typescript objects. I have to do it carefully.
f
Yeah, I hear you there. I think one way to think about it is that pretty much any input to a resource is fine as a promise, so in essence, all the inputs are awaitables
c
Except actually calling "await" doesn't actually wait on anything...
Back to what you were asking about reproducibility, the code I posted at the beginning of this thread was an example of where I expected Pulumi to wait on the resolution of the objects before populating.
f
right, but in this case, JSON.stringify doesn’t know how to deal w/ awaitables
c
So how would I get that information into the secretString without using apply?
f
in this case, i don’t think there’s an easy way except for apply
sorry, i wasn’t trying to imply there was
c
Ah!
I suppose it could work if it were possible to manually create an Output object. Is it?
f
I’m not quite sure I understand what you mean
c
Manually construct an Output object so Pulumi knows to wait on it?
And avoid having to call apply
f
But at the end of the day, the properties of that rdsInstance are
Output<T>
c
It would be helpful if I could do the equivalent of new Output("https://"+Output<string>)
f
ah, that you can do
that’s
pulumi.interpolate
From the page I linked earlier:
Copy code
// concat takes a list of args and concatenates all of them into a single output:
const url1: Output<string> = pulumi.concat("http://", hostname, ":", port, "/");
// interpolate takes a JavaScript "template literal" and expands outputs correctly:
const url2: Output<string> = pulumi.interpolate `http://${hostname}:${port}/`;
i haven’t tested this, but you should be able to also just
pulumi.output({ … }).apply(JSON.stringify)
and pass that to the arg
you still have to use apply since you want to
JSON.stringify
, but maybe you feel like that’ll look cleaner
so i guess in your case,
pulumi.output(secretsToStore).apply(JSON.stringify)
hope that helps!
c
Thanks!
BTW, your suggestion works and is cleaner:
Copy code
let secretsToStore = {
                "username": rdsInstance.username,
                "password": password.result,
                "engine": rdsInstance.engine,
                "port": rdsInstance.port,
                "dbname": rdsInstance.name,
                "dbInstanceIdentifier": rdsInstance.identifier,
                "host": rdsInstance.address
            };

            const storedPassword = new aws.secretsmanager.SecretVersion(dbName + "_password", {
                secretId: rotation.id,
                secretString: pulumi.output(secretsToStore).apply(JSON.stringify)
            });