Skip to content

Commit a8e005e

Browse files
feat: enabled passing env vars into podman containers (#627)
* enabled passing [user defined] env vars into podman containers * feat: node config can be an env var --------- Co-authored-by: Johns Gresham <[email protected]>
1 parent 217d4e8 commit a8e005e

File tree

4 files changed

+255
-4
lines changed

4 files changed

+255
-4
lines changed
+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { buildCliConfig, buildEnvInput } from '../../common/nodeConfig.js';
3+
4+
describe('Building cli config and env variable cli input', () => {
5+
it('Successfully creates a single env variable cli input', async () => {
6+
const configValuesMap = { plexClaimCode: 'claim123' };
7+
const configTranslationMap = {
8+
plexClaimCode: {
9+
displayName: 'Plex claim code',
10+
cliConfigPrefix: 'PLEX_CLAIM=',
11+
uiControl: {
12+
type: 'text',
13+
},
14+
isEnvVariable: true,
15+
addNodeFlow: 'required',
16+
infoDescription:
17+
'Sign in at https://www.plex.tv/claim/, get a code, and paste it here',
18+
defaultValue: '',
19+
documentation: 'https://www.plex.tv/claim/',
20+
},
21+
};
22+
const cliConfig1 = buildCliConfig({
23+
configValuesMap,
24+
configTranslationMap,
25+
excludeConfigKeys: [],
26+
isBuildingEnvInput: true,
27+
});
28+
29+
expect(cliConfig1).toEqual('-e PLEX_CLAIM=claim123');
30+
});
31+
32+
it('Successfully creates multiple env variable cli input', async () => {
33+
const configValuesMap = { plexClaimCode: 'claim123', httpPortEnv: '8080' };
34+
const configTranslationMap = {
35+
plexClaimCode: {
36+
displayName: 'Plex claim code',
37+
cliConfigPrefix: 'PLEX_CLAIM=',
38+
uiControl: {
39+
type: 'text',
40+
},
41+
isEnvVariable: true,
42+
addNodeFlow: 'required',
43+
infoDescription:
44+
'Sign in at https://www.plex.tv/claim/, get a code, and paste it here',
45+
defaultValue: '',
46+
documentation: 'https://www.plex.tv/claim/',
47+
},
48+
httpPortEnv: {
49+
displayName: 'HTTP PORT',
50+
cliConfigPrefix: 'HTTP_PORT=',
51+
uiControl: {
52+
type: 'text',
53+
},
54+
isEnvVariable: true,
55+
},
56+
};
57+
const cliConfig1 = buildCliConfig({
58+
configValuesMap,
59+
configTranslationMap,
60+
excludeConfigKeys: [],
61+
isBuildingEnvInput: true,
62+
});
63+
64+
expect(cliConfig1).toEqual('-e PLEX_CLAIM=claim123 -e HTTP_PORT=8080');
65+
});
66+
67+
it('Successfully creates a single cli config input', async () => {
68+
const configValuesMap = { httpPort: '8080' };
69+
const configTranslationMap = {
70+
httpPort: {
71+
displayName: 'HTTP PORT',
72+
cliConfigPrefix: '--http-port=',
73+
uiControl: {
74+
type: 'text',
75+
},
76+
},
77+
};
78+
const cliConfig1 = buildCliConfig({
79+
configValuesMap,
80+
configTranslationMap,
81+
excludeConfigKeys: [],
82+
});
83+
84+
expect(cliConfig1).toEqual('--http-port=8080');
85+
});
86+
87+
it('Successfully creates multiple cli config input', async () => {
88+
const configValuesMap = {
89+
httpPort: '8080',
90+
network: 'Mainnet',
91+
plexClaimCode: 'claimIgnoreMe',
92+
};
93+
const configTranslationMap = {
94+
httpPort: {
95+
displayName: 'HTTP PORT',
96+
cliConfigPrefix: '--http-port=',
97+
uiControl: {
98+
type: 'text',
99+
},
100+
},
101+
network: {
102+
displayName: 'Network',
103+
defaultValue: 'Mainnet',
104+
hideFromUserAfterStart: true,
105+
uiControl: {
106+
type: 'select/single',
107+
controlTranslations: [
108+
{
109+
value: 'Mainnet',
110+
config: '--mainnet',
111+
},
112+
{
113+
value: 'Holesky',
114+
config: '--holesky',
115+
},
116+
{
117+
value: 'Sepolia',
118+
config: '--sepolia',
119+
},
120+
],
121+
},
122+
},
123+
plexClaimCode: {
124+
displayName: 'Plex claim code',
125+
cliConfigPrefix: 'PLEX_CLAIM=',
126+
uiControl: {
127+
type: 'text',
128+
},
129+
isEnvVariable: true,
130+
addNodeFlow: 'required',
131+
infoDescription:
132+
'Sign in at https://www.plex.tv/claim/, get a code, and paste it here',
133+
defaultValue: '',
134+
documentation: 'https://www.plex.tv/claim/',
135+
},
136+
};
137+
const cliConfig1 = buildCliConfig({
138+
configValuesMap,
139+
configTranslationMap,
140+
excludeConfigKeys: [],
141+
});
142+
143+
expect(cliConfig1).toEqual('--http-port=8080 --mainnet');
144+
});
145+
146+
it('Successfully ignores a single cli config input when building env input', async () => {
147+
const configValuesMap = { httpPort: '8080' };
148+
const configTranslationMap = {
149+
httpPort: {
150+
displayName: 'HTTP PORT',
151+
cliConfigPrefix: '--http-port=',
152+
uiControl: {
153+
type: 'text',
154+
},
155+
},
156+
};
157+
const cliConfig1 = buildCliConfig({
158+
configValuesMap,
159+
configTranslationMap,
160+
excludeConfigKeys: [],
161+
isBuildingEnvInput: true,
162+
});
163+
164+
expect(cliConfig1).toEqual('');
165+
});
166+
167+
it('Successfully ignores a single cli config input and adds 1 env var when building env input', async () => {
168+
const configValuesMap = { httpPort: '8080', myFavEnvVar: 'muchwow' };
169+
const configTranslationMap = {
170+
httpPort: {
171+
displayName: 'HTTP PORT',
172+
cliConfigPrefix: '--http-port=',
173+
uiControl: {
174+
type: 'text',
175+
},
176+
},
177+
myFavEnvVar: {
178+
displayName: 'Plex claim code',
179+
cliConfigPrefix: 'MYFAVENVVAR=',
180+
uiControl: {
181+
type: 'text',
182+
},
183+
isEnvVariable: true,
184+
},
185+
};
186+
const cliConfig1 = buildCliConfig({
187+
configValuesMap,
188+
configTranslationMap,
189+
excludeConfigKeys: [],
190+
isBuildingEnvInput: true,
191+
});
192+
193+
expect(cliConfig1).toEqual('-e MYFAVENVVAR=muchwow');
194+
});
195+
196+
it('[buildEnvInput] Successfully builds env input for 2 user supplied env vars', async () => {
197+
const configValuesMap = {
198+
envInput: 'ETHPORTPORT=123456789,BASEPORT=987',
199+
myFavEnvVar: 'muchwow',
200+
};
201+
const cliConfig1 = buildEnvInput(configValuesMap.envInput);
202+
203+
expect(cliConfig1).toEqual('-e ETHPORTPORT=123456789 -e BASEPORT=987');
204+
});
205+
});

src/common/node-spec-tool/injectDefaultControllerConfig.ts

+12
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ export const injectDefaultControllerConfig = (nodeSpec: NodeSpecification) => {
2323
nodeSpec.configTranslation = {};
2424
}
2525

26+
if (!nodeSpec.configTranslation.envInput) {
27+
nodeSpec.configTranslation.envInput = {
28+
displayName: `${nodeSpec.displayName} ENV input`,
29+
uiControl: {
30+
type: 'text',
31+
},
32+
defaultValue: '',
33+
addNodeFlow: 'advanced',
34+
infoDescription: 'Additional ENV input, comma separated',
35+
};
36+
}
37+
2638
if (!nodeSpec.configTranslation.cliInput) {
2739
nodeSpec.configTranslation.cliInput = {
2840
displayName: `${nodeSpec.displayName} CLI input`,

src/common/nodeConfig.ts

+25-3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export type ConfigTranslation = {
5353
addNodeFlow?: ConfigTranslationAddNodeFlow;
5454
initCommandConfig?: boolean;
5555
hideFromUserAfterStart?: boolean;
56+
isEnvVariable?: boolean;
5657
};
5758

5859
export type ConfigKey = string;
@@ -89,23 +90,31 @@ export const buildCliConfig = ({
8990
configValuesMap,
9091
configTranslationMap,
9192
excludeConfigKeys,
93+
isBuildingEnvInput,
9294
}: {
9395
configValuesMap: ConfigValuesMap;
9496
configTranslationMap?: ConfigTranslationMap;
9597
excludeConfigKeys?: string[];
98+
isBuildingEnvInput?: boolean;
9699
}): string => {
97100
if (!configTranslationMap) return '';
98101
const cliConfigArray = Object.keys(configValuesMap).reduce(
99102
(cliString, configKey) => {
100-
if (excludeConfigKeys?.includes(configKey)) {
101-
return cliString;
102-
}
103103
const configValue = configValuesMap[configKey];
104104
const configTranslation: ConfigTranslation =
105105
configTranslationMap[configKey];
106106

107+
// Only include env vars if building env input, ignore env vars if building cli input
108+
if (
109+
excludeConfigKeys?.includes(configKey) ||
110+
isBuildingEnvInput !== configTranslation?.isEnvVariable
111+
) {
112+
return cliString;
113+
}
114+
107115
if (configTranslation && configValue) {
108116
let currCliString = '';
117+
109118
if (configTranslation.cliConfigPrefix) {
110119
if (typeof configTranslation.cliConfigPrefix === 'string') {
111120
currCliString = configTranslation.cliConfigPrefix;
@@ -161,12 +170,17 @@ export const buildCliConfig = ({
161170
currCliString += ` ${configTranslation.cliConfigPrefix[i]}${configValue}`;
162171
}
163172
}
173+
// each env var needs to be prefixed with -e for podman/docker
174+
if (isBuildingEnvInput) {
175+
currCliString = `-e ${currCliString}`;
176+
}
164177

165178
console.log(
166179
'cliString, currCliString: ',
167180
JSON.stringify(cliString),
168181
JSON.stringify(currCliString),
169182
);
183+
170184
// join the current config with the previous and a space between
171185
if (cliString) {
172186
return `${cliString} ${currCliString}`;
@@ -179,3 +193,11 @@ export const buildCliConfig = ({
179193
);
180194
return cliConfigArray;
181195
};
196+
197+
export const buildEnvInput = (input: string) => {
198+
if (!input) {
199+
return '';
200+
}
201+
const pairs = input.split(',');
202+
return pairs.map((pair) => `-e ${pair.trim()}`).join(' ');
203+
};

src/main/podman/podman.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
type ConfigValuesMap,
2121
buildCliConfig,
2222
} from '../../common/nodeConfig';
23+
import { buildEnvInput } from '../../common/nodeConfig.js';
2324
import { CHANNELS, send } from '../messenger.js';
2425
import { restartNodes } from '../nodePackageManager';
2526
import { isLinux } from '../platform';
@@ -546,6 +547,7 @@ export const createRunCommand = (node: Node): string => {
546547
const excludeConfigKeys = [
547548
'dataDir',
548549
'serviceVersion',
550+
'envInput',
549551
...initCommandConfigKeys,
550552
];
551553
if (
@@ -562,14 +564,24 @@ export const createRunCommand = (node: Node): string => {
562564
});
563565
nodeInput += ` ${cliConfigInput}`;
564566

567+
const directUserEnvConfigInput = buildEnvInput(
568+
node.config.configValuesMap.envInput,
569+
);
570+
const envConfigInput = buildCliConfig({
571+
configValuesMap: node.config.configValuesMap,
572+
configTranslationMap: node.spec.configTranslation,
573+
excludeConfigKeys,
574+
isBuildingEnvInput: true,
575+
});
576+
565577
const imageTag = getImageTag(node);
566578
// if imageTage is empty, use then imageTag is already included in the imageName (backwards compatability)
567579
const imageNameWithTag =
568580
imageTag !== '' ? `${imageName}:${imageTag}` : imageName;
569581

570582
const containerName = getContainerName(node);
571583
// -q quiets podman logs (pulling new image logs) so we can parse the containerId
572-
const podmanCommand = `run -q -d --name ${containerName} ${finalPodmanInput} ${imageNameWithTag} ${nodeInput}`;
584+
const podmanCommand = `run -q -d ${envConfigInput} ${directUserEnvConfigInput} --name ${containerName} ${finalPodmanInput} ${imageNameWithTag} ${nodeInput}`;
573585
logger.info(`podman run command ${podmanCommand}`);
574586
return podmanCommand;
575587
};

0 commit comments

Comments
 (0)