hello! Is there any way to pass an output into aws...
# general
a
hello! Is there any way to pass an output into aws.s3.BucketObject source? Examples:
Copy code
const bucketObject = new aws.s3.BucketObject("index.html", {
    acl: "public-read",
    contentType: "text/html",
    bucket: bucket,
    source: new pulumi.asset.FileAsset(pulumi.interpolate`${yarnBuild.path}/index.html`)
});
☝️this fails because FileAsset don't accept an output
Copy code
const bucketObject = new aws.s3.BucketObject("index.html", {
    acl: "public-read",
    contentType: "text/html",
    bucket: bucket,
    source: pulumi.all([yarnBuild.path]).apply(([p]) => new pulumi.asset.FileAsset(`${p}/index.html`))
});
☝️this fails because p is undefined...
Some context: I'd like to deploy a full-stack app with Pulumi and I need to do the following: 1. Deploy backend (dynamoDB + API Gateway) 2. Build the frontend locally using the API URL from step 1 3. Deploy frontend (S3+Cloudfront) In order to do step 2 I created a custom Resource and Resource provider that gets the API URL as input, builds the frontend locally using yarn and outputs the local path of the static assets. I want to pass this output (
yarnBuild.path
) as an input to the BucketObject
b
You're close. I think you want something like
Copy code
pulumi
    .all([yarnBuild.path])
    .apply(([p]) => new aws.s3.BucketObject("index.html", {
        acl: "public-read",
        contentType: "text/html",
        bucket: bucket,
        source: new pulumi.asset.FileAsset(`${p}/index.html`)
    }));
b
Worth noting that .apply(...) documentation says not to declare new resources within an apply
b
Yes, the best practice is that you don't. If you do, you might find that the resources created there don't show up in the preview
a
Hi @brave-planet-10645 I just tried your solution but the preview fails with:
Copy code
error: Error: failed to register new resource index.html [aws:s3/bucketObject:BucketObject]: 2 UNKNOWN: failed to compute asset hash: failed to open asset file 'undefined/index.html': open undefined/index.html: no such file or directory
Then I tried to run with:
Copy code
pulumi
    .all([yarnBuild.path])
    .apply(([p]) => p && new aws.s3.BucketObject("index.html", {
        acl: "public-read",
        contentType: "text/html",
        bucket: bucket,
        source: new pulumi.asset.FileAsset(`${p}/index.html`)
    }));
Now the deploy works but it doesn't create the BucketObject (after the deploy, there's an empty S3 bucket)
b
That's probably because you've got the
p && new...
in there. I'm not sure that this would work. Where are you getting yarnBuild.path from?
a
if I don't put
p && new...
the preview fails as p is undefined. yarnBuild is a custom dynamic provider:
Copy code
export interface YarnResourceInputs {
    apiUrl: pulumi.Input<string>;
}
interface YarnResourceProviderInputs {
    apiUrl: string;
}
interface YarnResourceProviderOutputs {
    path: string;
}

const YarnResourceProvider: pulumi.dynamic.ResourceProvider = {
    async create(inputs: YarnResourceProviderInputs): Promise<pulumi.dynamic.CreateResult> {
       //build frontend with "yarn build"
 console.log('YarnResourceProvider:create ', inputs.apiUrl)
        return { id: "123", outs: { path: './dist' }};
    },
    async diff(id: string, olds: YarnResourceProviderInputs, news: YarnResourceProviderInputs): Promise<pulumi.dynamic.DiffResult> {
        return {changes: true, replaces: ['apiUrl']}
    }
}

class YarnResource extends pulumi.dynamic.Resource {
    public readonly path!: pulumi.Output<string>;
    constructor(name: string, props: YarnResourceInputs, opts?: pulumi.CustomResourceOptions) {
        super(YarnResourceProvider, name, props, opts);
    }
}
then:
Copy code
const yarnBuild = new YarnResource('yarn-build', {apiUrl: api.url})
api.url
comes from the backend resource
b
I don't understand why we would do
pulumi.all(...)
for a single output since that is meant to be used to get multiple outputs into the same apply delegate. Why not just
yarnBuild.path.apply(p => new aws.s3.BucketObject(...));
Also it doesn't look like
YarnResource
is setting and/or registering
path
as an output?
b
So I think you're both wrong ( 😉 )... this is a dynamic resource which I don't think uses outputs... so Joshua: outputs don't need to be registered and Eric: you don't need to use outputs here
💡 1
b
Gotcha - Haven't had the chance to use dynamic resources yet 🙂
a
@bored-oyster-3147 I tried
yarnBuild.path.apply(...)
but it fails in the preview run as
yarnBuild.path == undefined
b
Well that's because you're not setting it, right?
a
yes, in the
YarnResourceProvider
create function
b
but I don't think that sets
YarnResource.path
. You need to set it in your
YarnResource
constructor after calling the base constructor. And @brave-planet-10645 is saying that it can be
string
instead of
Output<string>
a
@brave-planet-10645 I don't get why you say I don't need outputs here. The
YarnResourceProvider
is supposed to run "yarn build", grab the path of the build and return it as an output to be used by the bucket resource
b
Does it actually convert the string into an
Output<string>
?
a
so far I'm just mocking it with:
Copy code
async create(inputs: YarnResourceProviderInputs): Promise<pulumi.dynamic.CreateResult> {
       //build frontend with "yarn build"
        return { id: "123", outs: { path: './dist' }};
    },
I'm using examples from your docs. Do I need to convert the
'./dist'
part to an output object? I thought Pulumi did this automatically
b
I would expect Pulumi to do that automatically. I'll have a play with resource providers tomorrow morning and see what I can find out. This looks like we need some better examples in the examples repo as well
b
Ok I just scanned the docs for this and you're right about not needing to set that property explicitly, I'm sorry. There is some magic happening to set that from your
CreateResult
. Not a fan of magic implementations like that but my bad for confusing the discussion I did notice that your code differs from the documentation example slightly on 1 point:
Copy code
...

interface MyResourceProviderOutputs {
    myNumberOutput: number;
    myStringOutput: string;
}

class MyResourceProvider implements pulumi.dynamic.ResourceProvider {
    async create(inputs: MyResourceProviderInputs): Promise<pulumi.dynamic.CreateResult> {
        ...
        // Values are for an example only.
        return { id: "...", outs: { myNumberOutput: 12, myStringOutput: "some value" }};
    }
}

export class MyResource extends pulumi.dynamic.Resource {
    public readonly myStringOutput!: pulumi.Output<string>;
    public readonly myNumberOutput!: pulumi.Output<number>;

    constructor(name: string, props: MyResourceInputs, opts?: pulumi.CustomResourceOptions) {
        super(myprovider, name, { myStringOutput: undefined, myNumberOutput: undefined, ...props }, opts);
    }
}
Maybe that object expansion in the
super
call is important? They are doing something similar in all of the other SDK examples. Maybe your
super
call should be
super(YarnResourceProvider, name, { path: undefined, ...props }, opts);
a
Hi @bored-oyster-3147 I don't think that makes any difference
b
Weird to be in the documentation then. Why would they use object expansion to add null output properties to the input obj? very weird
b
So I've taken a look, and I think Joshua is correct here. You do need to expand the object. So using Eric's original code and adding the expanded object:
Copy code
import * as pulumi from "@pulumi/pulumi";

export interface YarnResourceInputs {
    apiUrl: pulumi.Input<string>;
}
interface YarnResourceProviderInputs {
    apiUrl: string;
}
interface YarnResourceProviderOutputs {
    path: string;
}
const YarnResourceProvider: pulumi.dynamic.ResourceProvider = {
    async create(inputs: YarnResourceProviderInputs): Promise<pulumi.dynamic.CreateResult> {
       //build frontend with "yarn build"
//  console.log('YarnResourceProvider:create ', inputs.apiUrl)
        return { id: "123", outs: { path: './dist' }};
    },
    async diff(id: string, olds: YarnResourceProviderInputs, news: YarnResourceProviderInputs): Promise<pulumi.dynamic.DiffResult> {
        return {changes: true, replaces: ['apiUrl']}
    }
}
export class YarnResource extends pulumi.dynamic.Resource {
    public readonly path!: pulumi.Output<string>;
    constructor(name: string, props: YarnResourceInputs, opts?: pulumi.CustomResourceOptions) {
        super(YarnResourceProvider, name, {path: undefined, ...props}, opts);
    }
}
And then updating the program slightly so the bucketobject isn't created in the apply, but the fileasset, you get something like this:
Copy code
import * as pulumi from "@pulumi/pulumi";
import * as provider from "./provider";
import * as aws from "@pulumi/aws";

const yarn = new provider.YarnResource("yarn", {
    apiUrl: "<http://thing.com|thing.com>"
});

export const path = yarn.path;

const bucket = new aws.s3.Bucket("bucket");

const file = yarn.path.apply(x => new pulumi.asset.FileAsset(x));

const bucketObject = new aws.s3.BucketObject("object", {
    source: file,
    bucket
})
Give that a go
a
Thanks @brave-planet-10645! That worked 😃
🎉 1