`CopyToRemote` doesn't support side-effects when defining its `source`
What happened?
I am experiencing issues with the CopyToRemote resource when declaring the source as a result of a side-effect. The very same code used to work when used from sst but now that I am migrating to raw Pulumi it doesn't work anymore.
When running pulumi up and the preview pops up it errors with this message:
error: command:remote:CopyToRemote resource 'CopyTraefikConfig': property asset value {<nil>} has a problem: either asset or archive must be set
Example
const copyTraefikConfig = new command.remote.CopyToRemote(
'CopyTraefikConfig',
{
connection: sshConnection,
source: pulumi.all([tlsCfKey.privateKeyPem, originCert.certificate]).apply(([key, cert]) => {
const originDir = `${INFRA_DIR}/traefik/dynamic/cf-origin`
fs.writeFileSync(path.join(originDir, 'privkey.key'), key)
fs.writeFileSync(path.join(originDir, 'chain.crt'), cert)
return new pulumi.asset.FileArchive(path.resolve(`${INFRA_DIR}/traefik`))
}),
remotePath: `/home/${APP_NAME}/app`,
},
{ dependsOn: [waitForReboot, downloadAopCert] },
)
In an attempt to reduce this example even further I noticed that it fails the moment there is at least one element in the array passed to pulumi.all.
const copyTraefikConfig = new command.remote.CopyToRemote(
'CopyTraefikConfig',
{
connection: sshConnection,
source: pulumi.all([someOutput]).apply(() => {
return new pulumi.asset.FileArchive(path.resolve(`${INFRA_DIR}/traefik`))
}),
remotePath: `/home/${APP_NAME}/app`,
},
{ dependsOn: [waitForReboot, downloadAopCert] },
)
Output of pulumi about
CLI
Version 3.165.0
Go Version go1.24.2
Go Compiler gc
Plugins
KIND NAME VERSION
resource cloudflare 6.1.1
resource command 1.0.2
resource docker 4.6.2
resource hcloud 1.22.1
language nodejs 3.165.0
resource tls 5.2.0
Host
OS darwin
Version 15.4.1
Arch arm64
This project is written in nodejs: executable='/Users/near/.nvm/versions/node/v22.14.0/bin/node' version='v22.14.0'
Additional context
Removing the side-effects and declaring
source: new pulumi.asset.FileArchive(path.resolve(`${INFRA_DIR}/traefik`))
throws no errors during preview though, but isn't equivalent to what I intend to do.
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).
Unfortunately I think this is a limitation of Asset/Archive, ref. https://github.com/pulumi/pulumi/issues/3017 https://github.com/pulumi/pulumi/issues/3017.
What's baffling though is that the very same code works when called within sst and Dax recently mentioned that he has no idea if they are doing something under the hood that could fix this bug on their end.
@ChristianIvicevic are you able to share the working SST code, and have you confirmed the asset round-trips successfully?
Here's a stripped down version of my SST code where I tried to keep as much as possible to give context to it:
/// <reference path="./.sst/platform/config.d.ts" />
const APP_NAME = 'example'
export default $config({
app(input) {
return {
name: APP_NAME,
removal: input?.stage === 'production' ? 'retain' : 'remove',
protect: ['production'].includes(input?.stage),
home: 'local',
providers: {
command: '1.0.2',
hcloud: '1.22.0',
tls: '5.1.1',
},
}
},
async run() {
const pulumi = await import('@pulumi/pulumi')
const fs = await import('node:fs')
const path = await import('node:path')
const tlsServerKey = new tls.PrivateKey('TlsServerKey', { algorithm: 'ED25519' })
const sshKey = new hcloud.SshKey('SshKey', { publicKey: tlsServerKey.publicKeyOpenssh })
const firewall = new hcloud.Firewall('Firewall', {
rules: [
{ direction: 'in', protocol: 'tcp', port: '22', sourceIps: ['0.0.0.0/0', '::/0'] },
{ direction: 'in', protocol: 'tcp', port: '443', sourceIps: ['0.0.0.0/0', '::/0'] },
{ direction: 'out', protocol: 'tcp', port: '1-65535', destinationIps: ['0.0.0.0/0', '::/0'] },
{ direction: 'out', protocol: 'udp', port: '1-65535', destinationIps: ['0.0.0.0/0', '::/0'] },
],
})
const server = new hcloud.Server('Server', {
image: 'debian-12',
serverType: 'cax21',
location: 'fsn1',
sshKeys: [sshKey.id],
firewallIds: [firewall.id.apply(Number)],
})
const rootConnection = pulumi.all([tlsServerKey, server]).apply(([key, server]) => ({
host: server.ipv4Address,
user: 'root',
privateKey: key.privateKeyOpenssh,
}))
const tlsCfKey = new tls.PrivateKey('TlsCfKey', { algorithm: 'ECDSA', ecdsaCurve: 'P256' })
const copyTraefikConfig = new command.remote.CopyToRemote(
'CopyTraefikConfig',
{
connection: sshConnection,
source: pulumi.all([tlsCfKey.privateKeyPem]).apply(([key]) => {
return new pulumi.asset.FileArchive(path.resolve('./infra/traefik'))
}),
remotePath: `/home/${APP_NAME}/app`,
},
)
},
})
In my initial snippet in the original post you can see how I technically perform some more side-effects in the apply block but this reduced example already causes the error in Pulumi while working completely fine in SST.
I have the same error, could you guys fix this problem?
I can't contribute any new insights to this as I've just migrated to Terraform.
Haha, what was the reason?
Trying to keep the off-topic to a minimum: just more robust since it's not an abstraction on top of an abstraction on top of it. Pulumi and SST enable a ton of cool things, but for my use cases Terraform was a solid choice and did the job.