Merge pull request #1256 from balena-io/1255-local-push-vars

Add the ability to specify an environment variable when pushing to local mode device
This commit is contained in:
CameronDiver 2019-05-27 05:45:41 -07:00 committed by GitHub
commit ba4301487f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 105 additions and 3 deletions

View File

@ -1478,6 +1478,7 @@ Examples:
$ balena push 10.0.0.1
$ balena push 10.0.0.1 --source <source directory>
$ 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 --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 valid when pushing to a local mode device.
#### --env &#60;env&#62;
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

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2018 Balena Ltd.
Copyright 2016-2019 Balena Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -114,6 +114,7 @@ export const push: CommandDefinition<
detached: boolean;
service: string;
system: boolean;
env: string | string[];
}
> = {
signature: 'push <applicationOrDevice>',
@ -153,6 +154,7 @@ export const push: CommandDefinition<
$ balena push 10.0.0.1
$ balena push 10.0.0.1 --source <source directory>
$ 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 --service my-service
@ -224,6 +226,19 @@ export const push: CommandDefinition<
Only valid when pushing to a local mode device.`,
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) {
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.',
);
}
if (options.env) {
exitWithExpectedError(
'The --env flag is only valid when pushing to a local mode device.',
);
}
const app = appOrDevice;
await exitIfNotLoggedIn();
@ -324,6 +344,10 @@ export const push: CommandDefinition<
detached: options.detached || false,
service: options.service,
system: options.system || false,
env:
typeof options.env === 'string'
? [options.env]
: options.env || [],
}),
)
.catch(BuildError, e => {

View File

@ -50,6 +50,11 @@ export interface DeviceDeployOptions {
detached: boolean;
service?: string;
system: boolean;
env: string[];
}
interface ParsedEnvironment {
[serviceName: string]: { [key: string]: string };
}
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();
}
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> {
const { loadProject, tarDirectory } = await import('../compose');
const { exitWithExpectedError } = await import('../patterns');
@ -136,6 +193,12 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
buildLogs,
);
const envs = await environmentFromInput(
opts.env,
Object.getOwnPropertyNames(project.composition.services),
globalLogger,
);
globalLogger.logDebug('Setting device state...');
// Now set the target state on the device
@ -144,6 +207,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
const targetState = generateTargetState(
currentTargetState,
project.composition,
envs,
);
globalLogger.logDebug(`Sending target state: ${JSON.stringify(targetState)}`);
@ -376,6 +440,7 @@ function generateImageName(serviceName: string): string {
export function generateTargetState(
currentTargetState: any,
composition: Composition,
env: ParsedEnvironment,
): any {
const services: { [serviceId: string]: any } = {};
let idx = 1;
@ -390,6 +455,8 @@ export function generateTargetState(
labels: {},
};
opts.environment = _.merge(opts.environment, env[name]);
services[idx] = _.merge(defaults, opts, {
imageId: idx,
serviceName: name,

View File

@ -348,7 +348,7 @@ export class LivepushManager {
// we rebuilt
const comp = _.cloneDeep(this.composition);
delete comp.services[serviceName];
const intermediateState = generateTargetState(currentState, comp);
const intermediateState = generateTargetState(currentState, comp, {});
await this.api.setTargetState(intermediateState);
// Now we wait for the device state to settle
@ -356,7 +356,7 @@ export class LivepushManager {
// And re-set the target state
await this.api.setTargetState(
generateTargetState(currentState, this.composition),
generateTargetState(currentState, this.composition, {}),
);
await this.awaitDeviceStateSettle();