hi all, I keep pondering inputs and outputs, and w...
# general
l
hi all, I keep pondering inputs and outputs, and wondering if anyone have any recommendations around this: I'm authoring a
ComponentResource
, which takes a number of args. Let's say one of them is a
containerImage
string. Should I define that as
Input<string>
or just
string
in my component resource args class/interface? I'd like to do some logic on it, like add a
version
if the image seems untagged. Am I okay to do this:
Copy code
export interface CustomComponentResourceArgs {
    containerImage: pulumi.Input<string>;
}
...and then cast it to string to perform logic on it?
Copy code
let image = args.containerImage.toString()
        image = image.includes(":") ? image : `${image}:${args.version}`;
...or is there a more pulumi-esque way of doing this? I'd like to allow whoever's using my component resource to be able to pass in both regular "hard-coded" args as well as outputs from other resources they might define
I do notice that the examples in the examples repo seem to tend to use regular types instead of inputs. Not sure what the pros/cons are, would love to learn more
e
Use input<T> if your just passing it to another resource input. If you need to make decisions based on the value it's probably better as a plain value.
q
Adding to what Fraser suggested, in this case you could turn the input into an output and operate on it using apply. E.g.
Copy code
const image = pulumi.all([args.containerImage, args.version]).apply(([containerImage, version]) => {
    return containerImage.includes(":") ? containerImage : `${containerImage}:${version}`;
});
Casting the input to a string would fail if users pass an output or promise for this arg.
l
Thanks for the input! I want a clean interface so this module is simple to use. So I guess Input<t> is my best choice then, as it provides the most persistent interface. I still feel like inputs and outputs are an incredibly complex/nuanced thing, that are only very lightly touched upon in the docs.
hm I keep going back and forth on this. Is there any "official" guidance on this? On one hand accepting args as inputs makes my composite resources a lot more complex with more boilerplate than I'd like. On the other hand just accepting regular types forces the user to deal with this. This is the thing I keep coming back to with Pulumi. Would be good if there was some "official" guidance around this with inputs even something as simple as setting default values and iterating over arrays takes a lot of code
b
Just throwing another opinion: Using
Input<T>
matches what provider resources do. For example, even though in our docs it looks like the
cidrBlock
parameter of a VPC is a
string
(see screenshot below) it's actually
Input<string>
. This will also mean that if you convert your components into multi-language ones, the types will remain the same rather than being converted to an input. At the end of the day, I don't think it really matters which one as long as you're consistent on what you do. Personally, I use
Input<T>
when building component resources.
l
yup. I guess it's all about forcing users to deal with complexity rather than doing it internally in the component resource. I have to say tho, that the api is super-clunky and I'm finding it by far the most frustrating part of writing pulumi code. I really hope there's some improvements planned.
e
Any ideas about what would make it less clunky? To a degree it's a similar problem to that of promises/tasks without 'await', but if there's any functions/combinators we could provider to help that would be useful to know. Or even if its just a case of needing more docs and examples, but what do those need to show?
l
for one I would definetely raise exceptions instead of just printing that default "this wont work and here's why" stuff inside the string. The other day I had to dig into a kubernets secret and decode it to see if I got it right. I can't think if any situation where I wouldn't want it to throw.
apart from that, i don't know. I've worked with cdk and cdktf for years, and never had to "bend my mind" like this. I don't know which design descision in pulumi that made this input/output apply a requirement - is it at all possible to solve it in another way? I would be willing to give up a lot of speed (parallelization) in order to not have to deal with apply.
but as far as examples go: it's barefly touched on in the examples. I feel like there's no explanation about when apply has to be used and when not. for example, if I'm building helm chart values I tend to do that outside of the resource defintiion because I'll need to dynamically add or remove some section. That suddenly becomes completely different than definiting stuff inline, since inputs are handled differently. That's not mentioned at all int he docs, they're mostly "if you want console.writeline do this". So the fact that I have to think before I decide where to build up a resource argument, is just... not a good development experience. We still like Pulumi, but it would be 1000x better without this awkwardness
e
I don't know which design descision in pulumi that made this input/output apply a requirement
cdk and cdktf use tokens instead, so you often have to use a limited set of built-in functions to operate on those values. Pulumi reifies the values fully, but to handle that the values are returned async, and sometimes not returned at all (e.g. during preview a resources might not have an output value) we have to wrap them in Inputs/Outputs and use apply. We're definitely interested in trying to make sure that all the most basic uses cases are handled by simple builtin functions, similar to cdk/cdktf.
I feel like there's no explanation about when apply has to be used and when not.
I think we've historically assumed it was clear that you use apply when you have to because of the shape of your inputs/outputs. But that gets tricker when your the one defining the inputs/outputs for components. I've raised this thread to a docs team, we'll give it a think.
l
it was clear that you use apply when you have to because of the shape of your inputs/outputs
Speaking for myself this was definetely not the case when starting out with Pulumi, it was incredibly confusing and not clear at all (esp fueled by the fact that it doesn't throw even when you're clearly doing something wrong). Now a couple of months in, it's still not completely clear to me whats going on, I'm still not able to fully "predict" if I have to use apply or not, but it's something I now know I have to look out for.
I can imagine this gets more confusing in a situation where a company has inviested in creating some component resources that may or may not take input vs regular types. If I use some component resources mixed with some "native" resources in my stack I have to be super-vigilant regarding which types are used with which resources.
e
Yeh, I think this would probably be helped somewhat by being more strict with that toString error but I know we'll break a load of people who happen to be getting away with sending bad inputs for some fields with that error message today.
l
wouldn't you be able to control pulumi's behavior by setting some flag either in the project yaml or in the global (userprofile) pulumi config? Seems to me that would be the best way to introduce improvements while avoiding breaking. I would happily drop a
mode: strict
somewhere
e
Yes, that's probably what we'll have to do. Initially defaulting it to non-strict, then defaulting to strict, then hopefully one day removing it.
l
well I'm really glad it's being thought of at least. As for the limitations in cdk's "token" way of resolving vars, I've never bumped into it being a limitation. For the most part we're looping over some variable, or assemling some string from others. I don't think I've ever thought "I wish I could do more here". If pulumi would take the same approach and at least allow doing relatively simple manouvers inline it would be a tremendous step forward. I can't think of a single situation where I'd need to use apply if pulumi had parity to inline cdk tokens