https://pulumi.com logo
#typescript
Title
# typescript
b

better-shampoo-48884

03/12/2021, 8:27 PM
I think I'm hitting a bit of an antipattern here (probably in more ways than one) - but it has seemed to be working fine til now.. In
index.ts
I create a resource (resourceGroup) as
const rg = new azure.resource... etc
then afterwards place rg in an object
createdStuff = { rg: { name: rg.name.apply(name => name), id: rg.id.apply(id => id), resource: rg}}
and that's fine. then i create a complicated set of network operations, storage operations, etc - and put them each into their own module which follow the model (module.ts:
export const createNetwork = function(curStack : any) : any { .. I do all my stuff here  and update curStack with new resources .. until.. return curStack }
). So far this seems fine. Then I tried making my fourth resource this way - and relying on
createdStuff.rg.name
as part of the name of the resource I'm creating.. I've used
createdStuff.rg.name
many times before, in all references to
resourceGroupName
in at least 8 other resources, but THIS it doesn't like.. I'm getting ye old classic
ror: azure-native:keyvault:Vault resource 'Calling [toString] on an [Output<T>] is not supported.
though as you have seen,
createdStuff.rg.name == rg.name.apply(name => name)
. I just feel that it's weird.
f

faint-table-42725

03/13/2021, 2:55 AM
Whenever you have
apply(x => x)
you’re likely heading down the wrong path because the apply isn’t really “doing anything.” I’m having a bit of trouble following the code you have above. Do you mind dropping a larger snippet with what you’re trying to do?
I’m not sure if this is helpful, but one way to think about
Output
is that once you go
Output
, you can never really go back. Like how for a
Promise
then(…)
just returns another
Promise
, an
apply
on an
Output
gives you back yet another
Output
. You can feed an
Output
to a resource because they typically accept
Input
(which is defined as
T | Promise<T> | Output<T>
), but you can’t really use an
Output<T>
as
T
c

colossal-australia-65039

03/13/2021, 3:19 AM
As Lee said,
.apply(x=>x)
literally does nothing for your code. It sounds like you're trying to use an
Output<string>
in a field that only takes a
string
, like the
name
of a Pulumi resource. In that case, one option is
Copy code
const newResource = createdStuff.rg.name.apply(name => new SomeResource(name, {...}))
Although an annoyance is that
newResource
will be typed as
Output<SomeResource>
instead of just
SomeResource
If this property value isn't a dynamic value it might be best to put it as its own variable and use that in both places
f

faint-table-42725

03/13/2021, 3:28 AM
Also not recommended to create resources within an
apply
c

colossal-australia-65039

03/13/2021, 5:04 AM
though sometimes the only way is not a recommended way 🤷‍♂️
b

better-shampoo-48884

03/13/2021, 6:27 AM
I'll paste a bit more of a snippet so you get the jist.. but strange that it's an antipattern when it's basically recommended based on the output:
Copy code
Type                            Name
     pulumi:pulumi:Stack             modularity-modular
     └─ azure-native:keyvault:Vault  Calling [toString] on an [Output<T>] is not supported.

To get the value of an Output<T> as an Output<string> consider either:
1: o.apply(v => `prefix${v}suffix`)
 : pulumi.interpolate `prefix$
Diagnostics:
  azure-native:keyvault:Vault (Calling [toString] on an [Output<T>] is not supported.

To get the value of an Output<T> as an Output<string> consider either:
1: o.apply(v => `prefix${v}suffix`)
2: pulumi.interpolate `prefix${v}suffix`

See <https://pulumi.io/help/outputs> for more details.
So - a slightly longer snippet to see what's going on - first the index.ts:
Copy code
import * as pulumi from "@pulumi/pulumi";
import * as azure from "@pulumi/azure-native";


import { createNetworks } from "./components/network";
import { createStorageAccounts } from "./components/storageAccount";
import { createKeyVault } from "./components/keyvault";

const config = new pulumi.Config("azure");
const ssp : any = config.requireObject("ssp")
const shouldCreate : any = config.requireObject("create")

export = async () => {
    for (group of ssp) {
        const rg = new azure.resources.ResourceGroup(`${group.logicalName}_${group.location}`, {
            location: group.location,
            resourceGroupName: `${group.logicalName}_${group.location}`,
            tags: group.tags
        })
        let rgStack = {
            ssp: group,
            parameters: {
                name:  rg.name.apply(name => name),
                location: rg.location.apply(location => location), 
                id: rg.id.apply(id => id),
                resource: rg
            },
            resources: {}
        }
    }

    if (shouldCreate.network) {
        rgStack = await createNetworks(rgStack);
    }

    if (shouldCreate.storageAccount) {
        rgStack = await createStorageAccounts(rgStack);
    }
    
    if (shouldCreate.keyVault) {
        rgStack = await createKeyVault(rgStack);
    }

}
and the content of `./components/keyvault.ts`:
Copy code
import * as pulumi from "@pulumi/pulumi";
import * as azure from "@pulumi/azure-native";

import * as _ from "lodash" 


const config = new pulumi.Config("azure");
let kvConfiguration : any = config.getObject("keyvault") == undefined ? {} : config.getObject("keyvault")

export const createKeyVault = async function(kvStack : any) : Promise<any> {
    /***
     * Setting up default values
     * 
     */

    const kvDefaults = {       
        sku: {
            family: "A",
            name: "standard"
        },
        tenantId: "-my-tenant-uid", 
        enableRbacAuthorization: true,
        enabledForDiskEncryption: true,
        enabledForDeployment: true,
        enabledForTemplateDeployment: true,
        softDeleteRetentionInDays: 90
    }

    // for some reason erroring out when trying to use RG name in vaultName
    //const vaultName = `${kvStack.parameters.name}vault`
    const vaultName = "staticstringworksfineofcourse"
    kvStack.keyVault = {
        config: {
            vaultName: vaultName,
            location: kvStack.parameters.location,
            resourceGroupName: kvStack.parameters.name,
            tags: kvStack.ssp.tags,
            properties: _.defaultsDeep(kvConfiguration, kvDefaults)
        }
    }
    const kv = new azure.keyvault.Vault(vaultName, kvStack.keyVault.config)
    kvStack.keyVault.resource = kv;
    kvStack.keyVault.parameters = {
        name: kv.name.apply(name => name),
        id: kv.id.apply(id => id),
    }
    
    return kvStack;
}
oh and yes, this is my first time using typescript rather than plain javascript for node type stuff, so pardon my liberal use of <any> types
I also realize that it's rather pointless for the pulumi aspect to be using async/await in the context of resource creation, that's mainly there for auxiliary calls that might be a bit more dependent on flow later on.
f

faint-table-42725

03/13/2021, 5:31 PM
Right, this is as Mike mentioned earlier where the actual resource names need to be prompt values (and not outputs). Looking at the code above, it seems like this value will be known, so you’ll want to plumb it through and just have `name:
${group.logicalName}_${group.location}
in your parameters.