diff --git a/docs/reference.md b/docs/reference.md index a9f4d6b38..e43788078 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -760,8 +760,8 @@ DESCRIPTION USAGE $ apify actors push [actorId] [--allow-missing-secrets] - [-b ] [--dir ] [-f] [--json] [--open] - [-v ] [-w ] + [--apply-env-vars-to-build] [-b ] [--dir ] + [-f] [--json] [--open] [-v ] [-w ] ARGUMENTS actorId Name or ID of the Actor to push (e.g. "apify/hello-world" or @@ -772,6 +772,14 @@ FLAGS --allow-missing-secrets Allow the command to continue even when secret values are not found in the local secrets storage. + --apply-env-vars-to-build Make the environment + variables also available to the Actor build + process. To turn the setting off, use + --no-apply-env-vars-to-build. Overrides the value + of the 'applyEnvVarsToBuild' field in the + '.actor/actor.json' file. When both the field and + the flag are omitted, the setting currently stored + on the platform is kept. -b, --build-tag= Build tag to be applied to the successful Actor build. By default, it is taken from the '.actor/actor.json' file. diff --git a/docs/vars.md b/docs/vars.md index 2ca91338e..27f3fd3c2 100644 --- a/docs/vars.md +++ b/docs/vars.md @@ -74,3 +74,22 @@ You can use the CLI to manage secrets environment variables: ... } ``` + +### Apply environment variables to the build + +By default, custom environment variables are available only at runtime. To make them available also to the Actor build process, for example, as Docker build arguments, set `applyEnvVarsToBuild` in `.actor/actor.json`: + +```json +{ + "actorSpecification": 1, + "name": "dataset-to-mysql", + "version": "0.1", + "buildTag": "latest", + "applyEnvVarsToBuild": true, + "environmentVariables": { + "MYSQL_PASSWORD": "@mySecretPassword" + } +} +``` + +To apply the environment variables to a single push, add the `--apply-env-vars-to-build` flag to the `apify push` command. To turn off the setting for a single push, add the `--no-apply-env-vars-to-build` flag. The flag overrides the value of the `applyEnvVarsToBuild` field. If you use neither the field nor a flag, the Apify platform keeps the stored setting. diff --git a/src/commands/actors/push.ts b/src/commands/actors/push.ts index d2d2c546a..a5214f32f 100644 --- a/src/commands/actors/push.ts +++ b/src/commands/actors/push.ts @@ -191,6 +191,10 @@ export class ActorsPushCommand extends ApifyCommand { required: false, default: false, }), + 'apply-env-vars-to-build': Flags.boolean({ + description: `Make the environment variables also available to the Actor build process. To turn the setting off, use --no-apply-env-vars-to-build. Overrides the value of the 'applyEnvVarsToBuild' field in the '${LOCAL_CONFIG_PATH}' file. When both the field and the flag are omitted, the setting currently stored on the platform is kept.`, + required: false, + }), }; static override args = { @@ -406,9 +410,12 @@ Skipping push. Use --force to override.`, allowMissing: this.flags.allowMissingSecrets, }) : undefined; + // undefined when neither the flag nor the actor.json field is set, so the value stored on the platform is preserved + const applyEnvVarsToBuild = + this.flags.applyEnvVarsToBuild ?? (actorConfig!.applyEnvVarsToBuild as boolean | undefined); if (actorCurrentVersion) { - const actorVersionModifier = { tarballUrl, sourceFiles, buildTag, sourceType, envVars }; + const actorVersionModifier = { tarballUrl, sourceFiles, buildTag, sourceType, envVars, applyEnvVarsToBuild }; // TODO: fix this type too -.- await actorClient.version(version).update(actorVersionModifier as never); run({ message: `Updated version ${version} for Actor ${actor.name}.` }); @@ -420,6 +427,7 @@ Skipping push. Use --force to override.`, buildTag, sourceType, envVars, + applyEnvVarsToBuild, }; await actorClient.versions().create({ diff --git a/src/lib/command-framework/apify-command.ts b/src/lib/command-framework/apify-command.ts index 06b024537..f4718581e 100644 --- a/src/lib/command-framework/apify-command.ts +++ b/src/lib/command-framework/apify-command.ts @@ -500,7 +500,9 @@ export abstract class ApifyCommand rawFlags[matcher]); + // Check for presence, not truthiness: the real CLI path always yields arrays (`multiple: true`), but + // internalRunCommand/testRunCommand inject scalar values, where an explicit `false` must match too + const matchingFlags = allMatchers.filter((matcher) => typeof rawFlags[matcher] !== 'undefined'); if (matchingFlags.length > 1) { throw new CommandError({ @@ -514,7 +516,7 @@ export abstract class ApifyCommand { TEST_TIMEOUT, ); + it( + 'should set applyEnvVarsToBuild when the flag is passed and keep it when omitted', + async () => { + const testActor = await testUserClient.actors().create(TEST_ACTOR); + actorsForCleanup.add(testActor.id); + const testActorClient = testUserClient.actor(testActor.id); + const actorJson = JSON.parse(readFileSync(joinPath(LOCAL_CONFIG_PATH), 'utf8')); + + await testRunCommand(ActorsPushCommand, { + args_actorId: testActor.id, + flags_noPrompt: true, + flags_force: true, + flags_applyEnvVarsToBuild: true, + }); + + const versionWithFlag = await testActorClient.version(actorJson.version).get(); + + await testRunCommand(ActorsPushCommand, { + args_actorId: testActor.id, + flags_noPrompt: true, + flags_force: true, + }); + + const versionWithoutFlag = await testActorClient.version(actorJson.version).get(); + + // false is what --no-apply-env-vars-to-build parses to + await testRunCommand(ActorsPushCommand, { + args_actorId: testActor.id, + flags_noPrompt: true, + flags_force: true, + flags_applyEnvVarsToBuild: false, + }); + + const versionWithNegatedFlag = await testActorClient.version(actorJson.version).get(); + + await testActorClient.delete(); + + expect(versionWithFlag!.applyEnvVarsToBuild).to.be.eql(true); + // omitting the flag must preserve the value stored on the platform + expect(versionWithoutFlag!.applyEnvVarsToBuild).to.be.eql(true); + // the negated flag must actively turn the setting off + expect(versionWithNegatedFlag!.applyEnvVarsToBuild).to.be.eql(false); + }, + TEST_TIMEOUT, + ); + + it( + 'should read applyEnvVarsToBuild from actor.json, with the flag taking precedence', + async () => { + const testActor = await testUserClient.actors().create(TEST_ACTOR); + actorsForCleanup.add(testActor.id); + const testActorClient = testUserClient.actor(testActor.id); + const actorJson = JSON.parse(readFileSync(joinPath(LOCAL_CONFIG_PATH), 'utf8')); + + try { + actorJson.applyEnvVarsToBuild = true; + writeFileSync(joinPath(LOCAL_CONFIG_PATH), JSON.stringify(actorJson, null, '\t'), { flag: 'w' }); + + await testRunCommand(ActorsPushCommand, { + args_actorId: testActor.id, + flags_noPrompt: true, + flags_force: true, + }); + + const versionWithFieldTrue = await testActorClient.version(actorJson.version).get(); + + actorJson.applyEnvVarsToBuild = false; + writeFileSync(joinPath(LOCAL_CONFIG_PATH), JSON.stringify(actorJson, null, '\t'), { flag: 'w' }); + // the actor config is cached per cwd, so mid-test rewrites need a reset + resetCwdCaches(); + + await testRunCommand(ActorsPushCommand, { + args_actorId: testActor.id, + flags_noPrompt: true, + flags_force: true, + }); + + const versionWithFieldFalse = await testActorClient.version(actorJson.version).get(); + + // the file still says false, but the flag must win + await testRunCommand(ActorsPushCommand, { + args_actorId: testActor.id, + flags_noPrompt: true, + flags_force: true, + flags_applyEnvVarsToBuild: true, + }); + + const versionWithFlagOverride = await testActorClient.version(actorJson.version).get(); + + // and the negated flag must also win over a true in the file + actorJson.applyEnvVarsToBuild = true; + writeFileSync(joinPath(LOCAL_CONFIG_PATH), JSON.stringify(actorJson, null, '\t'), { flag: 'w' }); + resetCwdCaches(); + + await testRunCommand(ActorsPushCommand, { + args_actorId: testActor.id, + flags_noPrompt: true, + flags_force: true, + flags_applyEnvVarsToBuild: false, + }); + + const versionWithNegatedFlagOverride = await testActorClient.version(actorJson.version).get(); + + expect(versionWithFieldTrue!.applyEnvVarsToBuild).to.be.eql(true); + expect(versionWithFieldFalse!.applyEnvVarsToBuild).to.be.eql(false); + expect(versionWithFlagOverride!.applyEnvVarsToBuild).to.be.eql(true); + expect(versionWithNegatedFlagOverride!.applyEnvVarsToBuild).to.be.eql(false); + } finally { + delete actorJson.applyEnvVarsToBuild; + writeFileSync(joinPath(LOCAL_CONFIG_PATH), JSON.stringify(actorJson, null, '\t'), { flag: 'w' }); + await testActorClient.delete(); + } + }, + TEST_TIMEOUT, + ); + it( 'should upload zip for source files larger that 3MB', async () => {