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

`CopyToRemote` doesn't support side-effects when defining its `source`

Open ChristianIvicevic opened this issue 8 months ago • 8 comments

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).

ChristianIvicevic avatar Apr 29 '25 12:04 ChristianIvicevic

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.

blampe avatar May 05 '25 23:05 blampe

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 avatar May 06 '25 03:05 ChristianIvicevic

@ChristianIvicevic are you able to share the working SST code, and have you confirmed the asset round-trips successfully?

blampe avatar May 06 '25 17:05 blampe

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.

ChristianIvicevic avatar May 07 '25 02:05 ChristianIvicevic

I have the same error, could you guys fix this problem?

Mondonno avatar Jun 26 '25 11:06 Mondonno

I can't contribute any new insights to this as I've just migrated to Terraform.

ChristianIvicevic avatar Jun 26 '25 11:06 ChristianIvicevic

Haha, what was the reason?

Mondonno avatar Jun 26 '25 11:06 Mondonno

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.

ChristianIvicevic avatar Jun 26 '25 11:06 ChristianIvicevic