just about every time i try and e.g. generate a co...
# general
b
just about every time i try and e.g. generate a config that's dependent on some output<>s it takes me 10 goes and i end up with something like
Copy code
"appsettings.Production.json": {
    "HttpProxy":{
        "HostMappings":{
            "v1.services":{
                "__pulumiOutput":true,
                "isKnown":{}
            }
        }
    }
}
would it be possible to get more varied examples in the documentation (there isn't one at the moment)
or super-awesome, something that detects that it's called toString() on a not-isKnown and errors out at preview time
c
cc @lemon-spoon-91807
b
its one of my major sticking points and i've been using it 6 months!
l
Great feedback. A couple of things:
1. even if the output was 'isKnown', this would still break. because being you can't toString asynchronously anyways
2. we have discussed (and i think we have a PR) that makes Output just explode if you call .toString on it
b
in this example i have a { service: kubernetes.core.v1.Service }[] and i'd like to be able to generate a string[] of service.metadata.name
l
so you can have a clear message that says "you are totally doing something not ok"
b
yeah that would be a great help
l
Totally understood! your use case is absolutely reasonable!
absolutely not saying you're in the wrong here
now... because you have outputs. our recommendation is to basically wrap the entire thing in a call like this:
pulumi.output(yourArrayThatHasDeepOutputsInIt).apply(unwrapped => whateverYouWant(... unwrapped ...))
you'll still get an Output at the end. but it could be an
Output<string[]>
.
so, for example (and you are right, we need good docs on this!), it's fairly common to see:
pulumi.output(dataThatHasDeepOutputInIt).apply(u => JSON.stringify(u))
b
aha ok great that sounds like what i want
i've been playing with .all() for hours
and trying to figure out unwrap
l
ok. that's on me. i need to give good, human level, explanations of this stuff in an obvious place 🙂
let me try to give a simple explanation, and you tell me if i've cleared things up, or made it worse
(i'm a compiler dev by trade, so i tend to be good at talking to computers, but not so good with my fellow humans ;-))
b
😄
l
First, Output plays a role similar to Promise, in that it represents 'async computation'. It has the downside though that there's no easy way to 'await' it like you can a Promise. So, instead, we've tried to beef up how you can work with them in ways that don't suck horrifically. they just suck mildly 🙂
first, .all plays the part of Promise.all. With Promise.all you could take
Promise.all([promiseOfString, promiseOfNumber])
and get
Promise<[string, number]>
👍 1
it's really useful when you have a list of different types of Promises, and you want to get a promise that represents that list with things "unpromisified"
pulumi.all is the same... just for Outputs. So it's really good when you have a list of disparate Output types and you want to get an Output of a tuple of all those types unwrapped
i.e.
pulumi.all([outputOfString, outputOfNumber])
gives you
Output<[string, number]>
aside: the reason we have 'Output' and don't just use 'Promise' is that Outputs track dependencies
b
yeah i've been using all before successfully but with pretty simple cases like
Copy code
export const appsettings = pulumi.all([
    sqlServer.fullyQualifiedDomainName,
    db.name,
    keyVault.vaultUri
]).apply(([
    sqlServerUri,
    sqlDb,
    keyVaultUri
]) =>
l
so, when you do:
pulumi.all([outputOfString, outputOfNumber])
and you get back an
Output<[string, number]>
that Output will track all of the dependencies of
outputOfString
and
outputOfNumber
🙂
b
when it came to porting that to get values from deeper i fluffed it and ended up with nested .applys inside the apply method body
l
right!
b
instead of e.g.
Copy code
export const hostMappings = pulumi.all([
    service.metadata.apply(x => x.name),
    namespace.metadata.apply(x => x.name)
]).apply(([
i believe the second is correct?
l
ok. so now that we've got at least a high level understanding of 'all', let's dive into the
pulumi.output(...)
function
when you say "second", which was the "first"?
b
sorry i didnt spam a first
well the first example didnt have deep queries
l
you should be able to write what you wrote or also write:
b
i mean i found it non-intuitive to go from getting a top level property (keyVault.keyVaultUri) to a 2nd level (service.metadata.name)
l
Copy code
export const hostMappings = pulumi.all([
    service.metadata,
    namespace.metadata
]).apply(([serviceMD, nsMD]) => /* can use serviceMD.name here, and it will be a string */
that *should work.
b
hm i thought i had perhaps not
maybe i need a simple testing stack to learn better
l
that sounds like a good idea!
(sorry, hope that didn't sound condescending)
b
😄 not at all
l
also, we're doing something here in our current milestone that can ***really*** help. primarily for the Kubernetes space
Specifically:
service.metadata.apply(x => x.name)
After this milestone, you won't have to write that!
instead, you can just write:
service.metadata.name
and that will already be the right
Output<string>
.
it's literally identical to writing
service.metadata.apply(x => x.name)
are you using TypeScript btw?
b
i am yeah
l
great!
b
that sounds like half my problem gone alreayd then in the next milestone
l
if so, then that means things like intellisense will also guide you along
ok. i think i've almost covered everythin
but there's one thing i'd love to help make clearer, in case you run into it in the future
so, both
pulumi.output(...)
and
pulumi.all(...)
are functions that take in some data, and return what we call `Unwrap`ed data.
let's just talk aobut
pulumi.output(...)
for now. It takes in data of type T, and gives you Output<Unwrap<T>> back. So what does that mean?
Intuitively, you can think if it this way: If T is some large and complex nested type, with Promises or Outputs appearing at any point deeply within it, then Unwrap<T> is that type/object, with all the Promises/Outputs gone and the data they point at in their place isntead.
So, for example, if you had
{ a: [Promise.resolve(1)] }
. That would normally be the type
{ : Promise<number>[] }
But Unwrap, basically gives you back
{ a: [1] }
.
b
oh and it will do that even for my custom objects?
l
as long as they're not Resources.
when you say 'custom obect' can you give an example
we recommend you not pass classes in here for example. or anything that might have a weird prototype chain
because we are literally generating new objects on the fly for you
so that
{ a: [1] }
is a new object
b
ah ok
l
it's not like we took the originally and edited it in place
b
can i just paste a simple test example i've cooked up
l
we're basically walkign the original, producin the new 'unwrapped' guy.
sure!
Also, has this made at least some amount of sense?
b
yeah interesting, i guess typescript is greaet for that compared to a lot of languages
yeah it has thanks
Copy code
import * as kubernetes from "@pulumi/kubernetes";
import * as pulumi from "@pulumi/pulumi";

export interface App {
    service: kubernetes.core.v1.Service;
}
export type AppList = { [name: string]: App };

function createAService(): kubernetes.core.v1.Service {
    return <kubernetes.core.v1.Service>{};
}
var list: AppList = {
    "A": { service: createAService() },
    "B": { service: createAService() },
    "C": { service: createAService() },
}

let t = pulumi
    .output(list)
    .apply(x => {
        let results: pulumi.Output<string>[] = [];
        for (let y in x) {
            results.push(x[y].service.metadata.apply(z => z.name));
        }
        return results;
    });
l
yeah, at least in TS we can describe what's going on.
b
ok so i have that
so i'm hoping i can come up with a way to basically get a JSON.stringify(t) result in ["A","B","C"]
l
ok!
b
actually thats not true
l
so, some things can already be simpler
b
a.metadata.name
not its key value i gave it
l
right, so in the future, you coudl just do:
one sec, let me look something up
i think you can take the above and rewrite it (don't have a compiler in front of me to verify):
b
t is being type inferred as Output<Output<string>[]>
which makes a lot of sense
l
Right. so let's tweak that like so:
`return pulumi.all(results)`;
then, after all, this, add a
.apply(JSON.stringify)
b
oh right yea thats coming out right
l
i was wrong. i thought we had a helper to make this easier. but we don't. so i think the final code would be what i just mentioned.
you can even just do it as:
return pulumi.all(results).apply(JSON.stringify)
(i believe)
b
ok fantastic i think i understand all a bit better now
l
Fanciest way (if you like one-liners):
return pulumi.all(Object.values(x).map(s => s.metadata.apply(m => m.name))).apply(JSON.stringify)
and, in the future, that would be:
return pulumi.all(Object.values(x).map(s => s.metadata.name)).apply(JSON.stringify)
gotta take a bio break 😄
good luck!
b
thats great thanks a lot
going to give it a try
awesome yeah all fixed thanks