Hey there, I'm having some trouble comprehending w...
# general
m
Hey there, I'm having some trouble comprehending what is the correct way to deal with nested InputT objects, as the Pulumi AI is really giving me mixed answers. (Note: This became a little long and probably unnecessarily convoluted, but I'm really just trying to find the correct way of getting the nested properties, when passing an output object as an input argument in another resource.) So, is there anyone who is able to enlighten me a little. If I have a
ComponentResource
, that outputs an object of type
Output<Bucket>
, then naturally the properties of the bucket are also of some type
Output<T>
. Now, if I want to use the bucket as
Input<Bucket>
in some other resource, I seem to be unable to access the underlying properties of the bucket, such as id, name, etc. because the
Input<T>
does not allow me to do so. This is then where things gets a little weird for me. The type
Output<T>
has the
.apply()
, which let's me access the values of an output. Apparently, the
Input<T>
type does not implement this method, as of version 3.98.0. However, the Pulumi AI is telling me both that I can, and that I can't, use apply on an input, sometimes also saying to convert the Input to an Output and then use apply, and then I'm not really sure what to think. So, is there way to access nest properties of an
Input<T>
without having to converting things back and forth, or am I mistaken for doing this and should only pass concrete types as inputs, as to unwrap my object to it's individual properties as output instead?
b
In typescript,
Input<T>
is defined as
type Input<T> = T | Promise<T> | Output<T>
usually I'd use
pulumi.output
to force it into an
Output
and then
.apply
since it also unwraps nested `Input`s
l
Also, if you just want to access a property, then you can let Pulumi lift it for you. For an object
x
of type
{ id: string }
, Pulumi will interpret
x.id
as
x.apply((o) => o.id)
, so you can omit using
apply()
.
m
@boundless-petabyte-41580 it's just feels odd that you gotta map the input to an output to access the underlying values, but maybe that's just me complaining and it's intended that way?
@little-cartoon-10569 but if the Output is of type
const bucket: Output<Bucket>
, where
Bucket := { name: Output<string> }
, which is the case when I return the entire bucket object from the
ComponentResource
, and pass it as to a parameter
bucketInput: Input<Bucket>
,
bucketInput.name
is no longer reference-able because apparently
name
does not exist on
bucketInput
.
b
@most-napkin-55076 the
pulumi.output
is mostly for accessing nested structures. It also coerces every
Input
into
Output
so you have a predictable interface to work with. Theoretically, to stay in input land you could implement something like
Copy code
function apply<T, T1>(input: Input<T>, f: (t: T) => Input<T1>): Input<T1>
but you'd need path-dependent types, since you don't know if the
input
you're working with has the same monad as
f
l
Re: it's just feels odd that you gotta map the input to an output to access the underlying values : You don't have to. That's a convenience because the alternative (to access them via Input<>) is harder! So generally, we do. Having a consistent interface like Output can really tidy up code; I'm been known to wrap hardcoded strings (or Promises) in
pulumi.output()
just to force consistency in the code.
Re: if the Output is of type const bucket: Output<Bucket> Outputs should (probably) never wrap anything that isn't a future value. By inference, Outputs should never wrap anything you create yourself, only things that have been returned from cloud APIs. You never need an
Output<Bucket>
. Output only ever wrap primitives and arrays of primitives. And in some unfortunate cases, other Outputs or Inputs (though in my experience, those cases might have been able to be reduced to just primitives, with a bit more work in the provider classes).
I'm sure that with a little redesign, anywhere that's returning an
Output<Bucket>
can be changed to return either a
Bucket
or an
Output<string>
(being the ARN or bucket name), depending on requirements.
m
I see, I think I start to understand the design choices. The reason is just that we have the
ComponentResource
, that wraps other resources, such as the
Bucket
, as Output, and for me it would just be preferred to not having to unwrap all of the properties of each "child" resource, for then in turn having to pass multiple primitive values around to, potentially, a single resource, as is the case for me, because I have to use multiple properties of a
Bucket
in a separate component. So it's not as much as I can't get it to work, it's more that the way that works, really feels to overcomplicate something that, from the outside at least, is pretty simple., But maybe the way to go is just to map Inputs to Outputs whenever they're reference objects and call it a day?
l
The ComponentResource can have a Bucket property, it doesn't need to be an Output<Bucket>.
m
Oh I see. It's because we're using the
initialize()
and
.getData()
to initialize the components, which returns a promise, but I guess that's really not mandatory then? (note, I didn't initially make this, I've just been expanding on it, so a lot of learning still)
l
I don't know what those are. If you're constructing resources in the normal way, you don't get a Promise.
m
Those two methods are some implementation for async initialization on resources. It's apperently optional, so not exactly sure when it's actually needed. Line 1029 and 1037 https://github.com/pulumi/pulumi/blob/035a502d86403d815059615a9c047ccccc2cbdd5/sdk/nodejs/resource.ts#L1029C12-L1029C12
l
Those are specifically for asynchronous data. Resources aren't data, so you won't be initializing them in there. It would be for things like the boot logs of a compute instance (which wouldn't be available until after the resource has been allocated and booted, potentially many minutes after the resource was created).