I know I'm asking an `Output<string>` questi...
# typescript
i
I know I'm asking an
Output<string>
question again, but I don't get how to get the actual string when needed. The use case is generating a Tailscale key to by used in the UserData of an ec2 instance.
Copy code
import * as tailscale from "@pulumi/tailscale";
import * as aws from "@pulumi/aws";

const tn_key = new tailscale.TailnetKey("tailnet-key", {
  ephemeral: true,
  expiry: 3600,
  preauthorized: true,
  recreateIfInvalid: "always",
  reusable: false,
});

new aws.ec2.Instance("instance", {
  tags: {
    Name: "slartibartfast"
  },
  instanceType: "t3.micro",
  ami: "ami-01e2093820bf84df1",
  userData: `
curl -fsSL <https://tailscale.com/install.sh> | sh
tailscale up --auth-key=${tn_key.key.apply(v => v)}
  `
})
From
pulumi up
details, the text in the UserData is the dredged
OutputImpl
toString
response.
Copy code
Calling [toString] on an [Output<T>] is not supported.\n\nTo get the value of an Output<T> as an Output<string> consider either:\n1: o.apply(v => `prefix${v}suffix`)\n2: pulumi.interpolate `prefix${v}suffix`\n\nSee <https://www.pulumi.com/docs/concepts/inputs-outputs> for more details.\n\nOr use ESLint with <https://github.com/pulumi/eslint-plugin-pulumi> to warn or\nerror lint on using Output<T> in template literals.\nThis function may throw in a future version of @pulumi/pulumi.\n
Running
pulumi up --debug
outputs the following
Copy code
debug: Ignoring error in loadCompilerOptions(tsconfig.json}): Error: ENOENT: no such file or directory, open 'tsconfig.json'
    debug: Found typescript version 5.5.4 at /Users/garymiller/devel/nettex/workspace/nettex/iac/node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/typescript.js
    debug: Using vendored ts-node@7.0.1
    debug: Registering resource: t=pulumi:pulumi:Stack, name=nettex-preinfra, custom=false, remote=false
    debug: RegisterResource RPC prepared: t=pulumi:pulumi:Stack, name=nettex-preinfra
    debug: RegisterResource RPC finished: resource:nettex-preinfra[pulumi:pulumi:Stack]; err: null, resp: urn:pulumi:preinfra::nettex::pulumi:pulumi:Stack::nettex-preinfra,,,,
    debug: Running program '/Users/garymiller/devel/nettex/workspace/nettex/iac/stacks/preinfra' in pwd '/Users/garymiller/devel/nettex/workspace/nettex/iac/stacks/preinfra' w/ args: 
    debug: Registering resource: t=tailscale:index/tailnetKey:TailnetKey, name=tailnet-key, custom=true, remote=false
    debug: RegisterResource RPC prepared: t=tailscale:index/tailnetKey:TailnetKey, name=tailnet-key
    debug: Registering resource: t=aws:ec2/instance:Instance, name=instance, custom=true, remote=false
    debug: RegisterResource RPC prepared: t=aws:ec2/instance:Instance, name=instance
    debug: RegisterResourceOutputs RPC prepared: urn=urn:pulumi:preinfra::nettex::pulumi:pulumi:Stack::nettex-preinfra
    debug: RegisterResourceOutputs RPC finished: urn=urn:pulumi:preinfra::nettex::pulumi:pulumi:Stack::nettex-preinfra; err: null, resp: 
    debug: RegisterResourceOutputs RPC finished: urn=urn:pulumi:preinfra::nettex::pulumi:pulumi:Stack::nettex-preinfra; err: null, resp: 
    debug: [INFO] pulumi-aws: starting to validate credentials. Disable this by AWS_SKIP_CREDENTIALS_VALIDATION or skipCredentialsValidation option
    debug: [INFO] pulumi-aws: credentials are valid
    debug: RegisterResource RPC finished: resource:instance[aws:ec2/instance:Instance]; err: null, resp: urn:pulumi:preinfra::nettex::aws:ec2/instance:Instance::instance,,ami,,,ami-01e2093820bf84df1,getPasswordData,,,,false,id,,,,instanceType,,,t3.micro,sourceDestCheck,,,,true,tags,,,,,Name,,,slartibartfast,tagsAll,,,,,Name,,,slartibartfast,userData,,,
    apt-get update -y
    apt-get install -y ec2-instance-connect
    curl -fsSL <https://tailscale.com/install.sh> | sh
    tailscale up --auth-key=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://www.pulumi.com/docs/concepts/inputs-outputs> for more details.
    
    Or use ESLint with <https://github.com/pulumi/eslint-plugin-pulumi> to warn or
    error lint on using Output<T> in template literals.
    This function may throw in a future version of @pulumi/pulumi.
      ,userDataReplaceOnChange,,,,false,,
    debug: RegisterResource RPC finished: resource:tailnet-key[tailscale:index/tailnetKey:TailnetKey]; err: null, resp: urn:pulumi:preinfra::nettex::tailscale:index/tailnetKey:TailnetKey::tailnet-key,,createdAt,,,xxx,description,0,ephemeral,,,,true,expiresAt,,,xxx,expiry,,3600,id,,,xxx,invalid,,,xxx,key,,,,,xxx,preauthorized,,,,true,recreateIfInvalid,,,always,reusable,,,,false,tags,0,,
Ok, with a bit of help from a college, this works
Copy code
const userData = tn_key.key.apply(v =>`
curl -fsSL <https://tailscale.com/install.sh> | sh
tailscale up --auth-key=${v}
`)

new aws.ec2.Instance("instance2", {
  tags: {
    Name: "slartibartfast"
  },
  instanceType: "t3.small",
  ami: "ami-01e2093820bf84df1",
  userData,
})
But this leads me to my next question, why doesn't the following work?
Copy code
import * as tailscale from "@pulumi/tailscale";
import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";

function makeinstance(key: pulumi.Input<string>) {
  new aws.ec2.Instance("instance", {
    tags: {
      Name: "slartibartfast2"
    },
    instanceType: "t3.small",
    ami: "ami-01e2093820bf84df1",
    userData: `
curl -fsSL <https://tailscale.com/install.sh> | sh
tailscale up --auth-key=${key}
  `
  })
}

const tn2_key = new tailscale.TailnetKey("tailnet-key", {
  ephemeral: true,
  expiry: 3600,
  preauthorized: true,
  recreateIfInvalid: "always",
  reusable: false,
});

makeinstance(tn2_key.key.apply(v => v))
I've also tried changing the function for a
pulumi.ComponentResource
(ie.
class MyInstance extends pulumi.ComponentResource
), with an
Input<string>
, but it didn't help 😠 also tried
Copy code
class MyInstance extends pulumi.ComponentResource {
  constructor(
    name: string,
    params: {
      tn_key: tailscale.TailnetKey,
      key: pulumi.Input<string>,
    },
    opts?: pulumi.ComponentResourceOptions,
  ) {
    super("MyInstance", name, {}, opts);

    new aws.ec2.Instance(`${name}-instance`, {
      tags: {
        Name: "slartibartfast"
      },
      instanceType: "t3.small",
      ami: "ami-01e2093820bf84df1",
      userData: `
curl -fsSL <https://tailscale.com/install.sh> | sh
tailscale up --auth-key=${params.key}
`
    }, {
      dependsOn: [params.tn_key],
      parent: this
    })

  }
}
not working 😡
l
Too many questions to answer in one thread :) Putting an output into a string won't work, because the value of the output isn't known at the time the string is being created. So you just get the error string. The version your colleague provided works because the logic happens inside the
apply
. That code runs later, when the value of the output is known. The correct value is available to the logic. The value returned from the
apply
is just a promise (output). The value it generates will be correct in the future, but not at the time your code is running. However, because you're passing that output to a constrictor of a Pulumi resource, Pulumi knows what to do with that future value, and uses it when it is eventually available.
q
There's also a helper for using template literals with inputs/outputs directly. You could do:
Copy code
pulumi.interpolate`
curl -fsSL <https://tailscale.com/install.sh> | sh
tailscale up --auth-key=${tn_key}
`
l
Really? That works on executable code too? I thought that works only on outputs.
Ah my bad, it's not actually running that all, it's just building a string from an output. My earlier explanation said "logic", but there is no logic. Maybe replace "logic" with "evaluation" and it reads better.