Man.. i'm starting to lose my mind on this whole O...
# general
b
Man.. i'm starting to lose my mind on this whole Output<T> stuff 😄 I'm trying to use an Output inside an object that is a parameter to a resource (hem chart values to be precise). No matter what I do - every permutation of .apply and interpolate and everything in between - it always fails. Anyone have a surefire way of converting an Output string to an actual string prior to resource execution?
r
Could you share some code for what you’re trying?
b
of course - silly of me. Here's my latest attempt:
Copy code
let akv2k8sValues = yaml.load(fs.readFileSync("./components/chart-values/akv2k8s-production.yml")) as any

    let tlsCrt = tlsCert.crt.certPem.apply(tlsCrt => {
        let curObj = akv2k8sValues;
        akv2k8sValues.env_injector.certificate = {
            useCertManager: false,
            custom: {
                enabled: true,
                server: {
                    tls: {
                        crt: tlsCert.crt.certPem.apply(x => x),
                        key: "toBeFilled"
                    }
                },
                ca: {
                    crt: "toBeFilled"
                }
            }        
        }
        return akv2k8sValues;
    })

    let tlsKey = pulumi.all([tlsCrt, tlsCert.key.privateKeyPem]).apply(([objValue, key]) => {
        objValue.env_injector.certificate.custom.server.tls.key = key;
        return objValue;
    })

    let finalAkv2k8sValues = pulumi.all([tlsKey, tlsCert.caCert.certPem]).apply(([objValue, cert]) => {
        objValue.env_injector.certificate.custom.ca.crt = cert;
        return objValue;
    })
    
    
    const SPVakv2k8s = new k8s.helm.v3.Chart("akv2k8s",{
        chart: "akv2k8s",
        version: "2.0.10",
        namespace: akv2k8sNamespace.metadata.name,
        fetchOpts: {
            repo: helmRepos.spv.url
        },
        values: finalAkv2k8sValues.apply(x => x)
    },{
        provider: cluster,
        dependsOn: [tlsCert.crt, tlsCert.key, tlsCert.caCert]
    })
With the following resulting output:
Copy code
Error: invocation of kubernetes:helm:template returned an error: failed to generate YAML for specified Helm chart: failed to create chart from template: YAML parse error on akv2k8s/templates/env-injector-apiservice.yaml: error converting YAML to JSON: yaml: line 15: could not find expected ':'
Tried with
up -f
in the event that
preview
couldn't materialize sufficient output, and tried multiple other permutations of .apply and .interpolate.
b
Your first tlsCert apply is incorrect. You are doing an apply again inside the provided delegate when you should be passing tlsCert input from the original delegate.
You need to think of the apply functions as a transmutation of the asynchronous value of the output. It is like a .then(..) function or a .continuewith(..) function. You are dealing with a type of promise. You cannot get the value out of it at compile time, but you can transform the eventual output so that when it is asynchronously resolved at runtime your transformation is applied. Have you used promises before? You know how you can keep calling .then(...) on the promise? All you’re doing there is modifying the TResult component of Promise<TResult>. It is the same functionality here. Only here you never await the promise, because Output<T> can be cast to Input<T> and Input<T> is smart enough to recognize that the value is asynchronous and to await it. You just need to make sure that the T component is what Input<T> expects by the time you pass it. But you cannot get T from Output<T> without awaiting it.
Doing Output<T>.apply(x => x) is a no-op. You are telling Output<T> that you want it to be Output<T>
to be specific, this:
Copy code
let tlsCrt = tlsCert.crt.certPem.apply(tlsCrt => {
        let curObj = akv2k8sValues;
        akv2k8sValues.env_injector.certificate = {
            useCertManager: false,
            custom: {
                enabled: true,
                server: {
                    tls: {
                        crt: tlsCert.crt.certPem.apply(x => x),
                        key: "toBeFilled"
                    }
                },
                ca: {
                    crt: "toBeFilled"
                }
            }        
        }
        return akv2k8sValues;
    })
should be this:
Copy code
let tlsCrt = tlsCert.crt.certPem.apply(tlsCrt => {
        let curObj = akv2k8sValues;
        akv2k8sValues.env_injector.certificate = {
            useCertManager: false,
            custom: {
                enabled: true,
                server: {
                    tls: {
                        crt: tlsCrt,
                        key: "toBeFilled"
                    }
                },
                ca: {
                    crt: "toBeFilled"
                }
            }        
        }
        return akv2k8sValues;
    })
and this:
Copy code
const SPVakv2k8s = new k8s.helm.v3.Chart("akv2k8s",{
        chart: "akv2k8s",
        version: "2.0.10",
        namespace: akv2k8sNamespace.metadata.name,
        fetchOpts: {
            repo: helmRepos.spv.url
        },
        values: finalAkv2k8sValues.apply(x => x)
    },{
        provider: cluster,
        dependsOn: [tlsCert.crt, tlsCert.key, tlsCert.caCert]
    })
is equivalent to this:
Copy code
const SPVakv2k8s = new k8s.helm.v3.Chart("akv2k8s",{
        chart: "akv2k8s",
        version: "2.0.10",
        namespace: akv2k8sNamespace.metadata.name,
        fetchOpts: {
            repo: helmRepos.spv.url
        },
        values: finalAkv2k8sValues
    },{
        provider: cluster,
        dependsOn: [tlsCert.crt, tlsCert.key, tlsCert.caCert]
    })
👍 1
Also I believe this:
dependsOn: [tlsCert.crt, tlsCert.key, tlsCert.caCert]
may be completely redundant. You are already providing inputs to the helm chart that are built from outputs that came from
tlsCert
, so pulumi is smart enough to recognize that the helm chart depends on
tlsCert
by inference.
👍 1
can you link the reference for
tlsCert
? I can't figure out what type it is so I can't find it but I suspect your code to build
finalAkv2k8sValues
could be much simpler.
👍 1
r
Following on from everything @bored-oyster-3147 mentioned, I think you could rewrite your code as:
Copy code
let akv2k8sValues = yaml.load(fs.readFileSync("./components/chart-values/akv2k8s-production.yml")) as any
    
const finalAkv2k8sValues = pulumi.all([akv2k8sValues, tlsCert.crt.certPem, tlSCert.key.privateKeyPem, tlsCert.caCert.certPem])
    .apply(([akv2k8sValues, crtCertPem, privateKeyPem, caCertCertPem]) => {
        const k8sValues = {...akv2k8sValues} // Copy to a new object to avoid mutating akv2k8sValues
        
        k8sValues.env_injector.certificate = {
            useCertManager: false,
            custom: {
                enabled: true,
                server: {
                    tls: {
                        crt: crtCertPem,
                        key: privateKeyPem
                    }
                },
                ca: {
                    crt: caCertCertPem
                }
            }        
        }

        return k8sValues;
    }

const SPVakv2k8s = new k8s.helm.v3.Chart("akv2k8s",{
    chart: "akv2k8s",
    version: "2.0.10",
    namespace: akv2k8sNamespace.metadata.name,
    fetchOpts: {
        repo: helmRepos.spv.url
    },
    values: finalAkv2k8sValues
},{
    provider: cluster
});
Or possibly even
Copy code
let akv2k8sValues = yaml.load(fs.readFileSync("./components/chart-values/akv2k8s-production.yml")) as any
    
const finalAkv2k8sValues = pulumi.all([akv2k8sValues, tlsCert])
    .apply(([akv2k8sValues, tlsCert]) => {
        const k8sValues = {...akv2k8sValues} // Copy to a new object to avoid mutating akv2k8sValues
        
        k8sValues.env_injector.certificate = {
            useCertManager: false,
            custom: {
                enabled: true,
                server: {
                    tls: {
                        crt: tlsCert.crt.CertPem,
                        key: tlsCert.key.privateKeyPem
                    }
                },
                ca: {
                    crt: tlsCert.caCert.CertPem
                }
            }        
        }

        return k8sValues;
    }

const SPVakv2k8s = new k8s.helm.v3.Chart("akv2k8s",{
    chart: "akv2k8s",
    version: "2.0.10",
    namespace: akv2k8sNamespace.metadata.name,
    fetchOpts: {
        repo: helmRepos.spv.url
    },
    values: finalAkv2k8sValues
},{
    provider: cluster
});
b
Thanks all - and yes - I added several layers of absurd redundancy in my attempts to get this to work. And thanks for noticing my blunder in the first double .apply - of course I should have been using the already applied value. Regardless - the approach does not work - my guess it's due to how the helm resource functions, and how preview functions (though that means it should work when I do
up -f
, but I get the same error there.
b
I’d wager there is still an output usage issue in there somewhere if you want to share the full sample?
r
Agreed, there should be no difference in preview vs up with regards to output functionality.
It looks like your error is unrelated to the code however, it seems to be pointing to your yaml template and an inability to parse it.
b
I thought it might fail on preview due to this:
Copy code
During some program executions, apply doesn't run. For example, it won't run during a preview, when resource output values may be unknown. Therefore, you should avoid side-effects within the callbacks. For this reason, you should not allocate new resources inside of your callbacks either, as it could lead to pulumi preview being wrong.
from https://www.pulumi.com/docs/intro/concepts/inputs-outputs/
because for helm to preview it needs to run and generate yaml - and that requires my .apply'd config 🙂
b
Your use-case doesn't really fall under "side-effects". The preview knows that it accepts an Input<string> and it knows that you are providing an Input<string> and that's it. That blurb that mentions side effects is mostly talking about external stateful actions. Like, you wouldn't want your apply delegate to be reaching out to an external API and making changes. And you don't want to declare pulumi resources in it because it isn't resolved during preview. Like I said I'm betting there is another bug in your code that we haven't caught yet