mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-02-07 03:29:57 +00:00
Add the ability to specify an environment variable when pushing to local
mode device Closes: #1255 Change-type: minor Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
parent
a6d6035725
commit
f77156772a
@ -1478,6 +1478,7 @@ Examples:
|
|||||||
$ balena push 10.0.0.1
|
$ balena push 10.0.0.1
|
||||||
$ balena push 10.0.0.1 --source <source directory>
|
$ balena push 10.0.0.1 --source <source directory>
|
||||||
$ balena push 10.0.0.1 --service my-service
|
$ balena push 10.0.0.1 --service my-service
|
||||||
|
$ balena push 10.0.0.1 --env MY_ENV_VAR=value --env my-service:SERVICE_VAR=value
|
||||||
|
|
||||||
$ balena push 23c73a1.local --system
|
$ balena push 23c73a1.local --system
|
||||||
$ balena push 23c73a1.local --system --service my-service
|
$ balena push 23c73a1.local --system --service my-service
|
||||||
@ -1529,6 +1530,16 @@ Only valid when pushing to a local mode device.
|
|||||||
Only show system logs. This can be used in combination with --service.
|
Only show system logs. This can be used in combination with --service.
|
||||||
Only valid when pushing to a local mode device.
|
Only valid when pushing to a local mode device.
|
||||||
|
|
||||||
|
#### --env <env>
|
||||||
|
|
||||||
|
When performing a push to device, run the built containers with environment
|
||||||
|
variables provided with this argument. Environment variables can be applied
|
||||||
|
to individual services by adding their service name before the argument,
|
||||||
|
separated by a colon, e.g:
|
||||||
|
--env main:MY_ENV=value
|
||||||
|
Note that if the service name cannot be found in the composition, the entire
|
||||||
|
left hand side of the = character will be treated as the variable name.
|
||||||
|
|
||||||
# Settings
|
# Settings
|
||||||
|
|
||||||
## settings
|
## settings
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2016-2018 Balena Ltd.
|
Copyright 2016-2019 Balena Ltd.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -114,6 +114,7 @@ export const push: CommandDefinition<
|
|||||||
detached: boolean;
|
detached: boolean;
|
||||||
service: string;
|
service: string;
|
||||||
system: boolean;
|
system: boolean;
|
||||||
|
env: string | string[];
|
||||||
}
|
}
|
||||||
> = {
|
> = {
|
||||||
signature: 'push <applicationOrDevice>',
|
signature: 'push <applicationOrDevice>',
|
||||||
@ -153,6 +154,7 @@ export const push: CommandDefinition<
|
|||||||
$ balena push 10.0.0.1
|
$ balena push 10.0.0.1
|
||||||
$ balena push 10.0.0.1 --source <source directory>
|
$ balena push 10.0.0.1 --source <source directory>
|
||||||
$ balena push 10.0.0.1 --service my-service
|
$ balena push 10.0.0.1 --service my-service
|
||||||
|
$ balena push 10.0.0.1 --env MY_ENV_VAR=value --env my-service:SERVICE_VAR=value
|
||||||
|
|
||||||
$ balena push 23c73a1.local --system
|
$ balena push 23c73a1.local --system
|
||||||
$ balena push 23c73a1.local --system --service my-service
|
$ balena push 23c73a1.local --system --service my-service
|
||||||
@ -224,6 +226,19 @@ export const push: CommandDefinition<
|
|||||||
Only valid when pushing to a local mode device.`,
|
Only valid when pushing to a local mode device.`,
|
||||||
boolean: true,
|
boolean: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
signature: 'env',
|
||||||
|
parameter: 'env',
|
||||||
|
description: stripIndent`
|
||||||
|
When performing a push to device, run the built containers with environment
|
||||||
|
variables provided with this argument. Environment variables can be applied
|
||||||
|
to individual services by adding their service name before the argument,
|
||||||
|
separated by a colon, e.g:
|
||||||
|
--env main:MY_ENV=value
|
||||||
|
Note that if the service name cannot be found in the composition, the entire
|
||||||
|
left hand side of the = character will be treated as the variable name.
|
||||||
|
`,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
async action(params, options, done) {
|
async action(params, options, done) {
|
||||||
const sdk = (await import('balena-sdk')).fromSharedOptions();
|
const sdk = (await import('balena-sdk')).fromSharedOptions();
|
||||||
@ -282,6 +297,11 @@ export const push: CommandDefinition<
|
|||||||
'The --system flag is only valid when pushing to a local mode device.',
|
'The --system flag is only valid when pushing to a local mode device.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (options.env) {
|
||||||
|
exitWithExpectedError(
|
||||||
|
'The --env flag is only valid when pushing to a local mode device.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const app = appOrDevice;
|
const app = appOrDevice;
|
||||||
await exitIfNotLoggedIn();
|
await exitIfNotLoggedIn();
|
||||||
@ -324,6 +344,10 @@ export const push: CommandDefinition<
|
|||||||
detached: options.detached || false,
|
detached: options.detached || false,
|
||||||
service: options.service,
|
service: options.service,
|
||||||
system: options.system || false,
|
system: options.system || false,
|
||||||
|
env:
|
||||||
|
typeof options.env === 'string'
|
||||||
|
? [options.env]
|
||||||
|
: options.env || [],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.catch(BuildError, e => {
|
.catch(BuildError, e => {
|
||||||
|
@ -50,6 +50,11 @@ export interface DeviceDeployOptions {
|
|||||||
detached: boolean;
|
detached: boolean;
|
||||||
service?: string;
|
service?: string;
|
||||||
system: boolean;
|
system: boolean;
|
||||||
|
env: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParsedEnvironment {
|
||||||
|
[serviceName: string]: { [key: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkSource(source: string): Promise<boolean> {
|
async function checkSource(source: string): Promise<boolean> {
|
||||||
@ -57,6 +62,58 @@ async function checkSource(source: string): Promise<boolean> {
|
|||||||
return (await fs.exists(source)) && (await fs.stat(source)).isDirectory();
|
return (await fs.exists(source)) && (await fs.stat(source)).isDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function environmentFromInput(
|
||||||
|
envs: string[],
|
||||||
|
serviceNames: string[],
|
||||||
|
logger: Logger,
|
||||||
|
): Promise<ParsedEnvironment> {
|
||||||
|
const { exitWithExpectedError } = await import('../patterns');
|
||||||
|
// A normal environment variable regex, with an added part
|
||||||
|
// to find a colon followed servicename at the start
|
||||||
|
const varRegex = /^(?:([^\s:]+):)?([^\s]+?)=(.*)$/;
|
||||||
|
|
||||||
|
const ret: ParsedEnvironment = {};
|
||||||
|
// Propolulate the object with the servicenames, as it
|
||||||
|
// also means that we can do a fast lookup of whether a
|
||||||
|
// service exists
|
||||||
|
for (const service of serviceNames) {
|
||||||
|
ret[service] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const env of envs) {
|
||||||
|
const maybeMatch = env.match(varRegex);
|
||||||
|
if (maybeMatch == null) {
|
||||||
|
exitWithExpectedError(`Unable to parse environment variable: ${env}`);
|
||||||
|
}
|
||||||
|
const match = maybeMatch!;
|
||||||
|
let service: string | undefined;
|
||||||
|
if (match[1]) {
|
||||||
|
// This is for a service, we check that it actually
|
||||||
|
// exists
|
||||||
|
if (!(match[1] in ret)) {
|
||||||
|
logger.logDebug(
|
||||||
|
`Warning: Cannot find a service with name ${
|
||||||
|
match[1]
|
||||||
|
}. Treating the string as part of the environment variable name.`,
|
||||||
|
);
|
||||||
|
match[2] = `${match[1]}:${match[2]}`;
|
||||||
|
} else {
|
||||||
|
service = match[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (service != null) {
|
||||||
|
ret[service][match[2]] = match[3];
|
||||||
|
} else {
|
||||||
|
for (const serviceName of serviceNames) {
|
||||||
|
ret[serviceName][match[2]] = match[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
||||||
const { loadProject, tarDirectory } = await import('../compose');
|
const { loadProject, tarDirectory } = await import('../compose');
|
||||||
const { exitWithExpectedError } = await import('../patterns');
|
const { exitWithExpectedError } = await import('../patterns');
|
||||||
@ -136,6 +193,12 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
|||||||
buildLogs,
|
buildLogs,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const envs = await environmentFromInput(
|
||||||
|
opts.env,
|
||||||
|
Object.getOwnPropertyNames(project.composition.services),
|
||||||
|
globalLogger,
|
||||||
|
);
|
||||||
|
|
||||||
globalLogger.logDebug('Setting device state...');
|
globalLogger.logDebug('Setting device state...');
|
||||||
// Now set the target state on the device
|
// Now set the target state on the device
|
||||||
|
|
||||||
@ -144,6 +207,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
|||||||
const targetState = generateTargetState(
|
const targetState = generateTargetState(
|
||||||
currentTargetState,
|
currentTargetState,
|
||||||
project.composition,
|
project.composition,
|
||||||
|
envs,
|
||||||
);
|
);
|
||||||
globalLogger.logDebug(`Sending target state: ${JSON.stringify(targetState)}`);
|
globalLogger.logDebug(`Sending target state: ${JSON.stringify(targetState)}`);
|
||||||
|
|
||||||
@ -376,6 +440,7 @@ function generateImageName(serviceName: string): string {
|
|||||||
export function generateTargetState(
|
export function generateTargetState(
|
||||||
currentTargetState: any,
|
currentTargetState: any,
|
||||||
composition: Composition,
|
composition: Composition,
|
||||||
|
env: ParsedEnvironment,
|
||||||
): any {
|
): any {
|
||||||
const services: { [serviceId: string]: any } = {};
|
const services: { [serviceId: string]: any } = {};
|
||||||
let idx = 1;
|
let idx = 1;
|
||||||
@ -390,6 +455,8 @@ export function generateTargetState(
|
|||||||
labels: {},
|
labels: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
opts.environment = _.merge(opts.environment, env[name]);
|
||||||
|
|
||||||
services[idx] = _.merge(defaults, opts, {
|
services[idx] = _.merge(defaults, opts, {
|
||||||
imageId: idx,
|
imageId: idx,
|
||||||
serviceName: name,
|
serviceName: name,
|
||||||
|
@ -348,7 +348,7 @@ export class LivepushManager {
|
|||||||
// we rebuilt
|
// we rebuilt
|
||||||
const comp = _.cloneDeep(this.composition);
|
const comp = _.cloneDeep(this.composition);
|
||||||
delete comp.services[serviceName];
|
delete comp.services[serviceName];
|
||||||
const intermediateState = generateTargetState(currentState, comp);
|
const intermediateState = generateTargetState(currentState, comp, {});
|
||||||
await this.api.setTargetState(intermediateState);
|
await this.api.setTargetState(intermediateState);
|
||||||
|
|
||||||
// Now we wait for the device state to settle
|
// Now we wait for the device state to settle
|
||||||
@ -356,7 +356,7 @@ export class LivepushManager {
|
|||||||
|
|
||||||
// And re-set the target state
|
// And re-set the target state
|
||||||
await this.api.setTargetState(
|
await this.api.setTargetState(
|
||||||
generateTargetState(currentState, this.composition),
|
generateTargetState(currentState, this.composition, {}),
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.awaitDeviceStateSettle();
|
await this.awaitDeviceStateSettle();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user