https://pulumi.com logo
b

better-rainbow-14549

02/25/2019, 6:57 PM
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

creamy-potato-29402

02/25/2019, 6:58 PM
cc @lemon-spoon-91807
b

better-rainbow-14549

02/25/2019, 6:58 PM
its one of my major sticking points and i've been using it 6 months!
l

lemon-spoon-91807

02/25/2019, 6:59 PM
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

better-rainbow-14549

02/25/2019, 7:00 PM
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

lemon-spoon-91807

02/25/2019, 7:00 PM
so you can have a clear message that says "you are totally doing something not ok"
b

better-rainbow-14549

02/25/2019, 7:00 PM
yeah that would be a great help
l

lemon-spoon-91807

02/25/2019, 7:00 PM
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

better-rainbow-14549

02/25/2019, 7:02 PM
aha ok great that sounds like what i want
i've been playing with .all() for hours
and trying to figure out unwrap
l

lemon-spoon-91807

02/25/2019, 7:03 PM
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

better-rainbow-14549

02/25/2019, 7:04 PM
😄
l

lemon-spoon-91807

02/25/2019, 7:05 PM
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

better-rainbow-14549

02/25/2019, 7:08 PM
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

lemon-spoon-91807

02/25/2019, 7:09 PM
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

better-rainbow-14549

02/25/2019, 7:09 PM
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

lemon-spoon-91807

02/25/2019, 7:09 PM
right!
b

better-rainbow-14549

02/25/2019, 7:09 PM
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

lemon-spoon-91807

02/25/2019, 7:10 PM
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

better-rainbow-14549

02/25/2019, 7:11 PM
sorry i didnt spam a first
well the first example didnt have deep queries
l

lemon-spoon-91807

02/25/2019, 7:11 PM
you should be able to write what you wrote or also write:
b

better-rainbow-14549

02/25/2019, 7:12 PM
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

lemon-spoon-91807

02/25/2019, 7:12 PM
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

better-rainbow-14549

02/25/2019, 7:12 PM
hm i thought i had perhaps not
maybe i need a simple testing stack to learn better
l

lemon-spoon-91807

02/25/2019, 7:13 PM
that sounds like a good idea!
(sorry, hope that didn't sound condescending)
b

better-rainbow-14549

02/25/2019, 7:14 PM
😄 not at all
l

lemon-spoon-91807

02/25/2019, 7:14 PM
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

better-rainbow-14549

02/25/2019, 7:15 PM
i am yeah
l

lemon-spoon-91807

02/25/2019, 7:15 PM
great!
b

better-rainbow-14549

02/25/2019, 7:16 PM
that sounds like half my problem gone alreayd then in the next milestone
l

lemon-spoon-91807

02/25/2019, 7:16 PM
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

better-rainbow-14549

02/25/2019, 7:19 PM
oh and it will do that even for my custom objects?
l

lemon-spoon-91807

02/25/2019, 7:19 PM
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

better-rainbow-14549

02/25/2019, 7:20 PM
ah ok
l

lemon-spoon-91807

02/25/2019, 7:20 PM
it's not like we took the originally and edited it in place
b

better-rainbow-14549

02/25/2019, 7:20 PM
can i just paste a simple test example i've cooked up
l

lemon-spoon-91807

02/25/2019, 7:21 PM
we're basically walkign the original, producin the new 'unwrapped' guy.
sure!
Also, has this made at least some amount of sense?
b

better-rainbow-14549

02/25/2019, 7:21 PM
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

lemon-spoon-91807

02/25/2019, 7:21 PM
yeah, at least in TS we can describe what's going on.
b

better-rainbow-14549

02/25/2019, 7:21 PM
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

lemon-spoon-91807

02/25/2019, 7:22 PM
ok!
b

better-rainbow-14549

02/25/2019, 7:22 PM
actually thats not true
l

lemon-spoon-91807

02/25/2019, 7:22 PM
so, some things can already be simpler
b

better-rainbow-14549

02/25/2019, 7:22 PM
a.metadata.name
not its key value i gave it
l

lemon-spoon-91807

02/25/2019, 7:22 PM
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

better-rainbow-14549

02/25/2019, 7:23 PM
t is being type inferred as Output<Output<string>[]>
which makes a lot of sense
l

lemon-spoon-91807

02/25/2019, 7:24 PM
Right. so let's tweak that like so:
`return pulumi.all(results)`;
then, after all, this, add a
.apply(JSON.stringify)
b

better-rainbow-14549

02/25/2019, 7:25 PM
oh right yea thats coming out right
l

lemon-spoon-91807

02/25/2019, 7:25 PM
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

better-rainbow-14549

02/25/2019, 7:27 PM
ok fantastic i think i understand all a bit better now
l

lemon-spoon-91807

02/25/2019, 7:28 PM
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

better-rainbow-14549

02/25/2019, 7:29 PM
thats great thanks a lot
going to give it a try
awesome yeah all fixed thanks
3 Views