pulumi-eks icon indicating copy to clipboard operation
pulumi-eks copied to clipboard

Calling [toString] on an [Output<T>] is not supported

Open polkx opened this issue 1 year ago • 6 comments

What happened?

When i use pulumi eks i get below error:

 Error: Invalid EC2 instance type: 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.
    This function may throw in a future version of @pulumi/pulumi.: Error: Invalid EC2 instance type: 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.
    This function may throw in a future version of @pulumi/pulumi.
        at isGravitonInstance (/snapshot/eks/bin/nodegroup.js:979:15)
        at getAMIType (/snapshot/eks/bin/nodegroup.js:998:25)
        at getRecommendedAMI (/snapshot/eks/bin/nodegroup.js:950:21)
        at createNodeGroupInternal (/snapshot/eks/bin/nodegroup.js:297:33)
        at createNodeGroup (/snapshot/eks/bin/nodegroup.js:155:12)
        at createCluster (/snapshot/eks/bin/cluster.js:842:60)
        at new ClusterInternal (/snapshot/eks/bin/cluster.js:906:25)
        at Object.construct (/snapshot/eks/bin/cmd/provider/cluster.js:21:29)
        at Provider.construct (/snapshot/eks/bin/cmd/provider/index.js:131:24)
        at Server.<anonymous> (/snapshot/eks/node_modules/@pulumi/pulumi/provider/server.js:315:52)

I think the problem is there: https://github.com/pulumi/pulumi-eks/blob/b6200bbf073449479c2a47b52e96f1e79d89e1f4/nodejs/eks/nodegroup.ts#L1860

You get Input<string> | undefined and then you eliminate undefined but on Input<string> call .toString()

Example

I use Scala language, Besom (Scala SDK for Pulumi) and scala-cli. If you want to run the example below, you'll need to install the scala lang plugin. I use IntelijIDEA. (https://www.jetbrains.com/help/idea/get-started-with-scala.html).

project.scala

//> using scala 3.3.1
//> using dep org.virtuslab::besom-core:0.3.1
//> using dep org.virtuslab::besom-eks:2.3.0-core.0.3

Pulumi.yaml

name: test
runtime: scala
description: Test

Main.scala

import besom.*
import besom.api.eks

@main def main = Pulumi.run {

  val cluster = eks.Cluster(
    "eks-cluster",
    eks.ClusterArgs(
     instanceType = "t2.medium"
    )
  )

  Stack.exports(kubeconfig = cluster.kubeconfig)
}

Output of pulumi about

CLI
Version 3.116.1 Go Version go1.22.3 Go Compiler gc

Plugins KIND NAME VERSION resource aws 6.5.0 resource eks 2.3.0 resource kubernetes 4.4.0

Host
OS darwin Version 14.4.1 Arch arm64

Additional context

No response

Contributing

Vote on this issue by adding a 👍 reaction. To contribute a fix for this issue, leave a comment (and link to your pull request, if you've opened one already).

polkx avatar May 22 '24 13:05 polkx

@polkx Would you be able to provide a code sample for us to reproduce this issue? Thanks!

rquitales avatar May 22 '24 15:05 rquitales

Example added. But I think in this situation the example is not so important.

polkx avatar May 23 '24 08:05 polkx

Unfortunately I'm unable to reproduce this issue in any of our supported languages. I've ported the provided example you have to Java, and am able to create the cluster without issues using pulumi-eks v2.5.0. Our existing tests covers this scenario for all the other supported languages.

Example Pulumi Java code:

package myproject;

import com.pulumi.Pulumi;
import com.pulumi.eks.Cluster;
import com.pulumi.eks.ClusterArgs;

import java.util.Map;

public class App {
    public static void main(String[] args) {
        Pulumi.run(ctx -> {

                var cluster = new Cluster("my-cluster", ClusterArgs.builder()
                    .instanceType("t2.medium")
                    .build());

            ctx.export("kcfg", cluster.kubeconfig());
        });
    }
}

Since the issue appears to be from your usage of besom and Scala, it's quite likely that something is wrong with besom's type handling since we are expecting a Pulumi string input for the instanceType variable, but your program is generating a Pulumi output variable. Unfortunately we do not have capacity to investigate further since this example works in all our supported languages. I would recommend filing a issue within besom for further investigation.

rquitales avatar May 23 '24 22:05 rquitales

Hi @rquitales, I'm the maintainer of pulumi-scala. This thing is a new problem on our side that surfaced once we implemented keep outputs feature in our sdk. I would like to ask you why do you think this code on TSs'/JS's side is correct to better understand why there's a problem on our side (because apparently all other langs implement keep outputs differently).

My understanding is that instanceType property's type is Input<string> | undefined (https://github.com/pulumi/pulumi-eks/blob/b6200bbf073449479c2a47b52e96f1e79d89e1f4/nodejs/eks/nodegroup.ts#L1860) where Input used here is the pulumi.Input<T> as defined here: https://github.com/pulumi/pulumi/blob/ee831fc5b6445bd6a1cd49292792ed147eac3d5d/sdk/nodejs/output.ts#L769 so: export type Input<T> = T | Promise<T> | OutputInstance<T>;

This type explicitly says that if something is Input<T> it can be an OutputInstance<T> and that's exactly what happens. Moreover, if I get this right - our SDK sends a request to Pulumi engine in CLI which then does some structural checking and if the message looks correct it passes this message to provider plugin, pulumi-eks in this case, where the request is deserialized. This in turn means that JS deserializer does understand that this field might be an Output and deserializes it without a crash as an Output. This logic also matches the type - the property is typed with Input<string>, not with string.

My best guess is that there's something hidden in the contract of keep outputs feature that we have missed in the implementation but the typescript types on pulumi-eks side do not lie - calling .toString() on Input<string> may lead to this stack trace:

    Error: Invalid EC2 instance type: 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.
    This function may throw in a future version of @pulumi/pulumi.: Error: Invalid EC2 instance type: 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.
    This function may throw in a future version of @pulumi/pulumi.
        at isGravitonInstance (/snapshot/eks/bin/nodegroup.js:979:15)
        at getAMIType (/snapshot/eks/bin/nodegroup.js:998:25)
        at getRecommendedAMI (/snapshot/eks/bin/nodegroup.js:950:21)
        at createNodeGroupInternal (/snapshot/eks/bin/nodegroup.js:297:33)
        at createNodeGroup (/snapshot/eks/bin/nodegroup.js:155:12)
        at createCluster (/snapshot/eks/bin/cluster.js:842:60)
        at new ClusterInternal (/snapshot/eks/bin/cluster.js:906:25)
        at Object.construct (/snapshot/eks/bin/cmd/provider/cluster.js:21:29)
        at Provider.construct (/snapshot/eks/bin/cmd/provider/index.js:131:24)
        at Server.<anonymous> (/snapshot/eks/node_modules/@pulumi/pulumi/provider/server.js:315:52)

lbialy avatar May 25 '24 12:05 lbialy

Let me reiterate - I strongly believe the problem is on your side because this pulumi-typescript program fails with exactly same exception:

import * as pulumi from "@pulumi/pulumi";
import * as eks from "@pulumi/eks";

const projectName = "myproject";

const myCluster = new eks.Cluster("my-cluster", {
    instanceType: pulumi.output(Promise.resolve("t2.medium")),
});

export const kubeconfig = myCluster.kubeconfig;
λ pulumi up
Enter your passphrase to unlock config/secrets
    (set PULUMI_CONFIG_PASSPHRASE or PULUMI_CONFIG_PASSPHRASE_FILE to remember):
Enter your passphrase to unlock config/secrets
Previewing update (dev):
     Type                                   Name                                          Plan       Info
 +   pulumi:pulumi:Stack                    pulumi-eks-issue-1160-dev                     create     1 error; 2 messages
 +   └─ eks:index:Cluster                   my-cluster                                    create
 +      ├─ eks:index:ServiceRole            my-cluster-eksRole                            create
 +      │  ├─ aws:iam:Role                  my-cluster-eksRole-role                       create
 +      │  └─ aws:iam:RolePolicyAttachment  my-cluster-eksRole-4b490823                   create
 +      ├─ eks:index:ServiceRole            my-cluster-instanceRole                       create
 +      │  ├─ aws:iam:Role                  my-cluster-instanceRole-role                  create
 +      │  ├─ aws:iam:RolePolicyAttachment  my-cluster-instanceRole-e1b295bd              create
 +      │  ├─ aws:iam:RolePolicyAttachment  my-cluster-instanceRole-03516f97              create
 +      │  └─ aws:iam:RolePolicyAttachment  my-cluster-instanceRole-3eb088f2              create
 +      ├─ eks:index:RandomSuffix           my-cluster-cfnStackName                       create
 +      ├─ aws:iam:InstanceProfile          my-cluster-instanceProfile                    create
 +      ├─ aws:ec2:SecurityGroup            my-cluster-eksClusterSecurityGroup            create
 +      ├─ aws:ec2:SecurityGroupRule        my-cluster-eksClusterInternetEgressRule       create
 +      ├─ aws:eks:Cluster                  my-cluster-eksCluster                         create
 +      ├─ aws:ec2:SecurityGroup            my-cluster-nodeSecurityGroup                  create
 +      ├─ eks:index:VpcCni                 my-cluster-vpc-cni                            create
 +      ├─ pulumi:providers:kubernetes      my-cluster-eks-k8s                            create
 +      ├─ aws:ec2:SecurityGroupRule        my-cluster-eksNodeIngressRule                 create
 +      ├─ kubernetes:core/v1:ConfigMap     my-cluster-nodeAccess                         create
 +      ├─ aws:ec2:SecurityGroupRule        my-cluster-eksNodeClusterIngressRule          create
 +      ├─ aws:ec2:SecurityGroupRule        my-cluster-eksClusterIngressRule              create
 +      ├─ aws:ec2:SecurityGroupRule        my-cluster-eksNodeInternetEgressRule          create
 +      └─ aws:ec2:SecurityGroupRule        my-cluster-eksExtApiServerClusterIngressRule  create

Diagnostics:
  pulumi:pulumi:Stack (pulumi-eks-issue-1160-dev):
    (node:49921) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
    (Use `node --trace-deprecation ...` to show where the warning was created)

    error: Running program '/Users/lbialy/Projects/foss/pulumi/pulumi-eks-issue-1160/index.ts' failed with an unhandled exception:
    Error: Invalid EC2 instance type: 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.
    This function may throw in a future version of @pulumi/pulumi.
        at isGravitonInstance (/Users/lbialy/Projects/foss/pulumi/pulumi-eks-issue-1160/node_modules/@pulumi/nodegroup.ts:1857:15)
        at getAMIType (/Users/lbialy/Projects/foss/pulumi/pulumi-eks-issue-1160/node_modules/@pulumi/nodegroup.ts:1884:25)
        at getRecommendedAMI (/Users/lbialy/Projects/foss/pulumi/pulumi-eks-issue-1160/node_modules/@pulumi/nodegroup.ts:1825:21)
        at createNodeGroupInternal (/Users/lbialy/Projects/foss/pulumi/pulumi-eks-issue-1160/node_modules/@pulumi/nodegroup.ts:748:55)
        at createNodeGroup (/Users/lbialy/Projects/foss/pulumi/pulumi-eks-issue-1160/node_modules/@pulumi/nodegroup.ts:561:12)
        at createCluster (/Users/lbialy/Projects/foss/pulumi/pulumi-eks-issue-1160/node_modules/@pulumi/cluster.ts:1815:43)
        at new Cluster (/Users/lbialy/Projects/foss/pulumi/pulumi-eks-issue-1160/node_modules/@pulumi/cluster.ts:1673:25)
        at Object.<anonymous> (/Users/lbialy/Projects/foss/pulumi/pulumi-eks-issue-1160/index.ts:7:19)
        at Module._compile (node:internal/modules/cjs/loader:1480:14)
        at Module.m._compile (/Users/lbialy/Projects/foss/pulumi/pulumi-eks-issue-1160/node_modules/@pulumi/pulumi/vendor/[email protected]/index.js:3009:23)

lbialy avatar May 26 '24 14:05 lbialy

I agree with @lbialy's assessment this is an eks problem. Being an MLC rather than an standard resource provider it needs to correctly deal with output values, it can't just assume the engine sends Input<string> as a plain string.

Frassle avatar May 26 '24 14:05 Frassle

Scala SDK is sending OutputValue for every field of every arg class because it was easier for us to implement inputs and generic derivation for serde this way. This will change as we will have to implement "plain": true fields and serialize them correctly without wrapping them in Outputs. The field in question is not a plain field however, typescript sdk allows passing pulumi.output(Promise.resolve("")) there so it's only a matter of time somebody else using any other sdk will push this value as Output derived from a field of, let's say, StackReference.

The fact that this started crashing on our side after 0.3.0 is related to the fact that we now use outputValues feature. Before that, everything was resolved to plain values before sending a request. Now the fact that we send an Output for every field that is an Input (so every field in any args class) is just more apparent.

On Mon 27. May 2024 at 14:25, Matthew (Matt) Jeffryes < @.***> wrote:

I think it's possible that the scala SDK is sending an Input<String> that's implemented by an OutputInstance, but the the java example (and the equivalent example in our other SDKs) would be sending an Input<String> implemented by a plain String. (Since the example given is just using a static string for InstanceType.)

I wonder if the error would reproduce in other SDKs if we used an output from another resource to populate Instance type

— Reply to this email directly, view it on GitHub https://github.com/pulumi/pulumi-eks/issues/1160#issuecomment-2133374281, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACBVNUWR4ZNG3ONXVAI7A73ZEMQ2XAVCNFSM6AAAAABIDTOACKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCMZTGM3TIMRYGE . You are receiving this because you were mentioned.Message ID: @.***>

lbialy avatar May 27 '24 14:05 lbialy