From 3c0f948644ccf91d15ce5651caa6f059b736a5a0 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Mon, 22 Jul 2024 17:07:26 -0700 Subject: [PATCH 1/4] enabled passing env vars into podman containers --- .../node-spec-tool/injectDefaultControllerConfig.ts | 12 ++++++++++++ src/main/podman/podman.ts | 13 ++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/common/node-spec-tool/injectDefaultControllerConfig.ts b/src/common/node-spec-tool/injectDefaultControllerConfig.ts index 1993ffd12..1d0bd7d9c 100644 --- a/src/common/node-spec-tool/injectDefaultControllerConfig.ts +++ b/src/common/node-spec-tool/injectDefaultControllerConfig.ts @@ -23,6 +23,18 @@ export const injectDefaultControllerConfig = (nodeSpec: NodeSpecification) => { nodeSpec.configTranslation = {}; } + if (!nodeSpec.configTranslation.envInput) { + nodeSpec.configTranslation.envInput = { + displayName: `${nodeSpec.displayName} ENV input`, + uiControl: { + type: 'text', + }, + defaultValue: '', + addNodeFlow: 'advanced', + infoDescription: 'Additional ENV input, comma separated', + }; + } + if (!nodeSpec.configTranslation.cliInput) { nodeSpec.configTranslation.cliInput = { displayName: `${nodeSpec.displayName} CLI input`, diff --git a/src/main/podman/podman.ts b/src/main/podman/podman.ts index 9b4d4d3a8..b2d261e17 100644 --- a/src/main/podman/podman.ts +++ b/src/main/podman/podman.ts @@ -485,6 +485,14 @@ const createPodmanPortInput = ( return result.join(' '); }; +const creatEnvInput = (input: string) => { + if (!input) { + return ''; + } + const pairs = input.split(','); + return pairs.map((pair) => `-e ${pair.trim()}`).join(' '); +}; + export const createRunCommand = (node: Node): string => { const { specId, execution, configTranslation } = node.spec; const { imageName, input } = execution as PodmanExecution; @@ -546,6 +554,7 @@ export const createRunCommand = (node: Node): string => { const excludeConfigKeys = [ 'dataDir', 'serviceVersion', + 'envInput', ...initCommandConfigKeys, ]; if ( @@ -562,6 +571,8 @@ export const createRunCommand = (node: Node): string => { }); nodeInput += ` ${cliConfigInput}`; + const envConfigInput = creatEnvInput(node.config.configValuesMap.envInput); + const imageTag = getImageTag(node); // if imageTage is empty, use then imageTag is already included in the imageName (backwards compatability) const imageNameWithTag = @@ -569,7 +580,7 @@ export const createRunCommand = (node: Node): string => { const containerName = getContainerName(node); // -q quiets podman logs (pulling new image logs) so we can parse the containerId - const podmanCommand = `run -q -d --name ${containerName} ${finalPodmanInput} ${imageNameWithTag} ${nodeInput}`; + const podmanCommand = `run -q -d ${envConfigInput} --name ${containerName} ${finalPodmanInput} ${imageNameWithTag} ${nodeInput}`; logger.info(`podman run command ${podmanCommand}`); return podmanCommand; }; From ed1d1e5f88dc0a854f2c36a954511b32ab18d94a Mon Sep 17 00:00:00 2001 From: cornpotage Date: Mon, 22 Jul 2024 17:21:49 -0700 Subject: [PATCH 2/4] fixed typo --- src/main/podman/podman.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/podman/podman.ts b/src/main/podman/podman.ts index b2d261e17..5d9d97d82 100644 --- a/src/main/podman/podman.ts +++ b/src/main/podman/podman.ts @@ -485,7 +485,7 @@ const createPodmanPortInput = ( return result.join(' '); }; -const creatEnvInput = (input: string) => { +const buildEnvInput = (input: string) => { if (!input) { return ''; } @@ -571,7 +571,7 @@ export const createRunCommand = (node: Node): string => { }); nodeInput += ` ${cliConfigInput}`; - const envConfigInput = creatEnvInput(node.config.configValuesMap.envInput); + const envConfigInput = buildEnvInput(node.config.configValuesMap.envInput); const imageTag = getImageTag(node); // if imageTage is empty, use then imageTag is already included in the imageName (backwards compatability) From c3348f0c74725d123db9517d2c7471c5fa0a5b03 Mon Sep 17 00:00:00 2001 From: jgresham Date: Fri, 20 Sep 2024 14:10:18 -0700 Subject: [PATCH 3/4] feat: node config can be an env var --- src/__tests__/node/buildCliConfig.test.ts | 205 ++++++++++++++++++++++ src/common/nodeConfig.ts | 28 ++- src/main/podman/podman.ts | 21 +-- 3 files changed, 241 insertions(+), 13 deletions(-) create mode 100644 src/__tests__/node/buildCliConfig.test.ts diff --git a/src/__tests__/node/buildCliConfig.test.ts b/src/__tests__/node/buildCliConfig.test.ts new file mode 100644 index 000000000..416401c0d --- /dev/null +++ b/src/__tests__/node/buildCliConfig.test.ts @@ -0,0 +1,205 @@ +import { describe, expect, it } from 'vitest'; +import { buildCliConfig, buildEnvInput } from '../../common/nodeConfig.js'; + +describe('Building cli config and env variable cli input', () => { + it('Successfully creates a single env variable cli input', async () => { + const configValuesMap = { plexClaimCode: 'claim123' }; + const configTranslationMap = { + plexClaimCode: { + displayName: 'Plex claim code', + cliConfigPrefix: 'PLEX_CLAIM=', + uiControl: { + type: 'text', + }, + isEnvVariable: true, + addNodeFlow: 'required', + infoDescription: + 'Sign in at https://www.plex.tv/claim/, get a code, and paste it here', + defaultValue: '', + documentation: 'https://www.plex.tv/claim/', + }, + }; + const cliConfig1 = buildCliConfig({ + configValuesMap, + configTranslationMap, + excludeConfigKeys: [], + isBuildingEnvInput: true, + }); + + expect(cliConfig1).toEqual('-e PLEX_CLAIM=claim123'); + }); + + it('Successfully creates multiple env variable cli input', async () => { + const configValuesMap = { plexClaimCode: 'claim123', httpPortEnv: '8080' }; + const configTranslationMap = { + plexClaimCode: { + displayName: 'Plex claim code', + cliConfigPrefix: 'PLEX_CLAIM=', + uiControl: { + type: 'text', + }, + isEnvVariable: true, + addNodeFlow: 'required', + infoDescription: + 'Sign in at https://www.plex.tv/claim/, get a code, and paste it here', + defaultValue: '', + documentation: 'https://www.plex.tv/claim/', + }, + httpPortEnv: { + displayName: 'HTTP PORT', + cliConfigPrefix: 'HTTP_PORT=', + uiControl: { + type: 'text', + }, + isEnvVariable: true, + }, + }; + const cliConfig1 = buildCliConfig({ + configValuesMap, + configTranslationMap, + excludeConfigKeys: [], + isBuildingEnvInput: true, + }); + + expect(cliConfig1).toEqual('-e PLEX_CLAIM=claim123 -e HTTP_PORT=8080'); + }); + + it('Successfully creates a single cli config input', async () => { + const configValuesMap = { httpPort: '8080' }; + const configTranslationMap = { + httpPort: { + displayName: 'HTTP PORT', + cliConfigPrefix: '--http-port=', + uiControl: { + type: 'text', + }, + }, + }; + const cliConfig1 = buildCliConfig({ + configValuesMap, + configTranslationMap, + excludeConfigKeys: [], + }); + + expect(cliConfig1).toEqual('--http-port=8080'); + }); + + it('Successfully creates multiple cli config input', async () => { + const configValuesMap = { + httpPort: '8080', + network: 'Mainnet', + plexClaimCode: 'claimIgnoreMe', + }; + const configTranslationMap = { + httpPort: { + displayName: 'HTTP PORT', + cliConfigPrefix: '--http-port=', + uiControl: { + type: 'text', + }, + }, + network: { + displayName: 'Network', + defaultValue: 'Mainnet', + hideFromUserAfterStart: true, + uiControl: { + type: 'select/single', + controlTranslations: [ + { + value: 'Mainnet', + config: '--mainnet', + }, + { + value: 'Holesky', + config: '--holesky', + }, + { + value: 'Sepolia', + config: '--sepolia', + }, + ], + }, + }, + plexClaimCode: { + displayName: 'Plex claim code', + cliConfigPrefix: 'PLEX_CLAIM=', + uiControl: { + type: 'text', + }, + isEnvVariable: true, + addNodeFlow: 'required', + infoDescription: + 'Sign in at https://www.plex.tv/claim/, get a code, and paste it here', + defaultValue: '', + documentation: 'https://www.plex.tv/claim/', + }, + }; + const cliConfig1 = buildCliConfig({ + configValuesMap, + configTranslationMap, + excludeConfigKeys: [], + }); + + expect(cliConfig1).toEqual('--http-port=8080 --mainnet'); + }); + + it('Successfully ignores a single cli config input when building env input', async () => { + const configValuesMap = { httpPort: '8080' }; + const configTranslationMap = { + httpPort: { + displayName: 'HTTP PORT', + cliConfigPrefix: '--http-port=', + uiControl: { + type: 'text', + }, + }, + }; + const cliConfig1 = buildCliConfig({ + configValuesMap, + configTranslationMap, + excludeConfigKeys: [], + isBuildingEnvInput: true, + }); + + expect(cliConfig1).toEqual(''); + }); + + it('Successfully ignores a single cli config input and adds 1 env var when building env input', async () => { + const configValuesMap = { httpPort: '8080', myFavEnvVar: 'muchwow' }; + const configTranslationMap = { + httpPort: { + displayName: 'HTTP PORT', + cliConfigPrefix: '--http-port=', + uiControl: { + type: 'text', + }, + }, + myFavEnvVar: { + displayName: 'Plex claim code', + cliConfigPrefix: 'MYFAVENVVAR=', + uiControl: { + type: 'text', + }, + isEnvVariable: true, + }, + }; + const cliConfig1 = buildCliConfig({ + configValuesMap, + configTranslationMap, + excludeConfigKeys: [], + isBuildingEnvInput: true, + }); + + expect(cliConfig1).toEqual('-e MYFAVENVVAR=muchwow'); + }); + + it('[buildEnvInput] Successfully builds env input for 2 user supplied env vars', async () => { + const configValuesMap = { + envInput: 'ETHPORTPORT=123456789,BASEPORT=987', + myFavEnvVar: 'muchwow', + }; + const cliConfig1 = buildEnvInput(configValuesMap.envInput); + + expect(cliConfig1).toEqual('-e ETHPORTPORT=123456789 -e BASEPORT=987'); + }); +}); diff --git a/src/common/nodeConfig.ts b/src/common/nodeConfig.ts index efcdf1cc5..d7079b0ab 100644 --- a/src/common/nodeConfig.ts +++ b/src/common/nodeConfig.ts @@ -53,6 +53,7 @@ export type ConfigTranslation = { addNodeFlow?: ConfigTranslationAddNodeFlow; initCommandConfig?: boolean; hideFromUserAfterStart?: boolean; + isEnvVariable?: boolean; }; export type ConfigKey = string; @@ -89,23 +90,31 @@ export const buildCliConfig = ({ configValuesMap, configTranslationMap, excludeConfigKeys, + isBuildingEnvInput, }: { configValuesMap: ConfigValuesMap; configTranslationMap?: ConfigTranslationMap; excludeConfigKeys?: string[]; + isBuildingEnvInput?: boolean; }): string => { if (!configTranslationMap) return ''; const cliConfigArray = Object.keys(configValuesMap).reduce( (cliString, configKey) => { - if (excludeConfigKeys?.includes(configKey)) { - return cliString; - } const configValue = configValuesMap[configKey]; const configTranslation: ConfigTranslation = configTranslationMap[configKey]; + // Only include env vars if building env input, ignore env vars if building cli input + if ( + excludeConfigKeys?.includes(configKey) || + isBuildingEnvInput !== configTranslation?.isEnvVariable + ) { + return cliString; + } + if (configTranslation && configValue) { let currCliString = ''; + if (configTranslation.cliConfigPrefix) { if (typeof configTranslation.cliConfigPrefix === 'string') { currCliString = configTranslation.cliConfigPrefix; @@ -161,12 +170,17 @@ export const buildCliConfig = ({ currCliString += ` ${configTranslation.cliConfigPrefix[i]}${configValue}`; } } + // each env var needs to be prefixed with -e for podman/docker + if (isBuildingEnvInput) { + currCliString = `-e ${currCliString}`; + } console.log( 'cliString, currCliString: ', JSON.stringify(cliString), JSON.stringify(currCliString), ); + // join the current config with the previous and a space between if (cliString) { return `${cliString} ${currCliString}`; @@ -179,3 +193,11 @@ export const buildCliConfig = ({ ); return cliConfigArray; }; + +export const buildEnvInput = (input: string) => { + if (!input) { + return ''; + } + const pairs = input.split(','); + return pairs.map((pair) => `-e ${pair.trim()}`).join(' '); +}; diff --git a/src/main/podman/podman.ts b/src/main/podman/podman.ts index 3a5be54c9..fbe50356a 100644 --- a/src/main/podman/podman.ts +++ b/src/main/podman/podman.ts @@ -30,6 +30,7 @@ import * as metricsPolling from './metricsPolling'; import { execPromise as podmanExecPromise } from './podman-desktop/podman-cli'; import { getPodmanEnvWithPath } from './podman-env-path'; import startPodman, { onStartUp } from './start'; +import { buildEnvInput } from '../../common/nodeConfig.js'; let podmanWatchProcess: ChildProcess; @@ -485,14 +486,6 @@ const createPodmanPortInput = ( return result.join(' '); }; -const buildEnvInput = (input: string) => { - if (!input) { - return ''; - } - const pairs = input.split(','); - return pairs.map((pair) => `-e ${pair.trim()}`).join(' '); -}; - export const createRunCommand = (node: Node): string => { const { specId, execution, configTranslation } = node.spec; const { imageName, input } = execution as PodmanExecution; @@ -571,7 +564,15 @@ export const createRunCommand = (node: Node): string => { }); nodeInput += ` ${cliConfigInput}`; - const envConfigInput = buildEnvInput(node.config.configValuesMap.envInput); + const directUserEnvConfigInput = buildEnvInput( + node.config.configValuesMap.envInput, + ); + const envConfigInput = buildCliConfig({ + configValuesMap: node.config.configValuesMap, + configTranslationMap: node.spec.configTranslation, + excludeConfigKeys, + isBuildingEnvInput: true, + }); const imageTag = getImageTag(node); // if imageTage is empty, use then imageTag is already included in the imageName (backwards compatability) @@ -580,7 +581,7 @@ export const createRunCommand = (node: Node): string => { const containerName = getContainerName(node); // -q quiets podman logs (pulling new image logs) so we can parse the containerId - const podmanCommand = `run -q -d ${envConfigInput} --name ${containerName} ${finalPodmanInput} ${imageNameWithTag} ${nodeInput}`; + const podmanCommand = `run -q -d ${envConfigInput} ${directUserEnvConfigInput} --name ${containerName} ${finalPodmanInput} ${imageNameWithTag} ${nodeInput}`; logger.info(`podman run command ${podmanCommand}`); return podmanCommand; }; From a3c68b4d260adc53798cab5c3d69a145890dca73 Mon Sep 17 00:00:00 2001 From: jgresham Date: Fri, 20 Sep 2024 14:11:47 -0700 Subject: [PATCH 4/4] lintfix --- src/main/podman/podman.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/podman/podman.ts b/src/main/podman/podman.ts index fbe50356a..471d6f7a1 100644 --- a/src/main/podman/podman.ts +++ b/src/main/podman/podman.ts @@ -20,6 +20,7 @@ import { type ConfigValuesMap, buildCliConfig, } from '../../common/nodeConfig'; +import { buildEnvInput } from '../../common/nodeConfig.js'; import { CHANNELS, send } from '../messenger.js'; import { restartNodes } from '../nodePackageManager'; import { isLinux } from '../platform'; @@ -30,7 +31,6 @@ import * as metricsPolling from './metricsPolling'; import { execPromise as podmanExecPromise } from './podman-desktop/podman-cli'; import { getPodmanEnvWithPath } from './podman-env-path'; import startPodman, { onStartUp } from './start'; -import { buildEnvInput } from '../../common/nodeConfig.js'; let podmanWatchProcess: ChildProcess;