Add multicontainer (microservices) support for 'balena env add'

Change-type: minor
Signed-off-by: Paulo Castro <paulo@balena.io>
This commit is contained in:
Paulo Castro 2019-12-10 00:07:16 +00:00
parent 38920a1c59
commit 90e184ea1f
3 changed files with 140 additions and 22 deletions

View File

@ -704,21 +704,36 @@ Run in non-interactive mode
## env add NAME [VALUE] ## env add NAME [VALUE]
Add an environment or config variable to an application or device, as selected Add an environment or config variable to an application, device or service,
by the respective command-line options. as selected by the respective command-line options. Either the --application
or the --device option must be provided, and either may be be used alongside
the --service option to define a service-specific variable. (A service is an
application container in a "microservices" application.) When the --service
option is used in conjunction with the --device option, the service variable
applies to the selected device only. Otherwise, it applies to all devices of
the selected application (i.e., the application's fleet). If the --service
option is omitted, the variable applies to all services.
If VALUE is omitted, the CLI will attempt to use the value of the environment If VALUE is omitted, the CLI will attempt to use the value of the environment
variable of same name in the CLI process' environment. In this case, a warning variable of same name in the CLI process' environment. In this case, a warning
message will be printed. Use `--quiet` to suppress it. message will be printed. Use `--quiet` to suppress it.
Service-specific variables are not currently supported. The given command line 'BALENA_' or 'RESIN_' are reserved variable name prefixes used to identify
examples variables that apply to all services in an app or device. "configuration variables". Configuration variables control balena platform
features and are treated specially by balenaOS and the balena supervisor
running on devices. They are also stored differently in the balenaCloud API
database. Configuration variables cannot be set for specific services,
therefore the --service option cannot be used when the variable name starts
with a reserved prefix. When defining custom application variables, please
avoid the reserved prefixes.
Examples: Examples:
$ balena env add TERM --application MyApp $ balena env add TERM --application MyApp
$ balena env add EDITOR vim --application MyApp $ balena env add EDITOR vim --application MyApp
$ balena env add EDITOR vim --application MyApp --service MyService
$ balena env add EDITOR vim --device 7cf02a6 $ balena env add EDITOR vim --device 7cf02a6
$ balena env add EDITOR vim --device 7cf02a6 --service MyService
### Arguments ### Arguments
@ -728,7 +743,7 @@ environment or config variable name
#### VALUE #### VALUE
variable value; if omitted, use value from CLI's environment variable value; if omitted, use value from this process' environment
### Options ### Options
@ -744,6 +759,10 @@ device UUID
suppress warning messages suppress warning messages
#### -s, --service SERVICE
service name
## env rename ID VALUE ## env rename ID VALUE
Change the value of an environment variable for an application or device, Change the value of an environment variable for an application or device,

View File

@ -16,6 +16,7 @@
*/ */
import { Command, flags } from '@oclif/command'; import { Command, flags } from '@oclif/command';
import * as BalenaSdk from 'balena-sdk';
import { stripIndent } from 'common-tags'; import { stripIndent } from 'common-tags';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -24,10 +25,11 @@ import * as cf from '../../utils/common-flags';
import { CommandHelp } from '../../utils/oclif-utils'; import { CommandHelp } from '../../utils/oclif-utils';
interface FlagsDef { interface FlagsDef {
application?: string; application?: string; // application name
device?: string; device?: string; // device UUID
help: void; help: void;
quiet: boolean; quiet: boolean;
service?: string; // service name
} }
interface ArgsDef { interface ArgsDef {
@ -37,22 +39,37 @@ interface ArgsDef {
export default class EnvAddCmd extends Command { export default class EnvAddCmd extends Command {
public static description = stripIndent` public static description = stripIndent`
Add an environment or config variable to an application or device. Add an environment or config variable to an application, device or service.
Add an environment or config variable to an application or device, as selected Add an environment or config variable to an application, device or service,
by the respective command-line options. as selected by the respective command-line options. Either the --application
or the --device option must be provided, and either may be be used alongside
the --service option to define a service-specific variable. (A service is an
application container in a "microservices" application.) When the --service
option is used in conjunction with the --device option, the service variable
applies to the selected device only. Otherwise, it applies to all devices of
the selected application (i.e., the application's fleet). If the --service
option is omitted, the variable applies to all services.
If VALUE is omitted, the CLI will attempt to use the value of the environment If VALUE is omitted, the CLI will attempt to use the value of the environment
variable of same name in the CLI process' environment. In this case, a warning variable of same name in the CLI process' environment. In this case, a warning
message will be printed. Use \`--quiet\` to suppress it. message will be printed. Use \`--quiet\` to suppress it.
Service-specific variables are not currently supported. The given command line 'BALENA_' or 'RESIN_' are reserved variable name prefixes used to identify
examples variables that apply to all services in an app or device. "configuration variables". Configuration variables control balena platform
features and are treated specially by balenaOS and the balena supervisor
running on devices. They are also stored differently in the balenaCloud API
database. Configuration variables cannot be set for specific services,
therefore the --service option cannot be used when the variable name starts
with a reserved prefix. When defining custom application variables, please
avoid the reserved prefixes.
`; `;
public static examples = [ public static examples = [
'$ balena env add TERM --application MyApp', '$ balena env add TERM --application MyApp',
'$ balena env add EDITOR vim --application MyApp', '$ balena env add EDITOR vim --application MyApp',
'$ balena env add EDITOR vim --application MyApp --service MyService',
'$ balena env add EDITOR vim --device 7cf02a6', '$ balena env add EDITOR vim --device 7cf02a6',
'$ balena env add EDITOR vim --device 7cf02a6 --service MyService',
]; ];
public static args = [ public static args = [
@ -65,7 +82,7 @@ export default class EnvAddCmd extends Command {
name: 'value', name: 'value',
required: false, required: false,
description: description:
"variable value; if omitted, use value from CLI's environment", "variable value; if omitted, use value from this process' environment",
}, },
]; ];
@ -78,6 +95,7 @@ export default class EnvAddCmd extends Command {
device: _.assign({ exclusive: ['application'] }, cf.device), device: _.assign({ exclusive: ['application'] }, cf.device),
help: cf.help, help: cf.help,
quiet: cf.quiet, quiet: cf.quiet,
service: cf.service,
}; };
public async run() { public async run() {
@ -88,6 +106,12 @@ export default class EnvAddCmd extends Command {
const balena = (await import('balena-sdk')).fromSharedOptions(); const balena = (await import('balena-sdk')).fromSharedOptions();
const { checkLoggedIn } = await import('../../utils/patterns'); const { checkLoggedIn } = await import('../../utils/patterns');
if (!options.application && !options.device) {
throw new ExpectedError(
'Either the --application or the --device option must always be used',
);
}
await checkLoggedIn(); await checkLoggedIn();
if (params.value == null) { if (params.value == null) {
@ -95,7 +119,7 @@ export default class EnvAddCmd extends Command {
if (params.value == null) { if (params.value == null) {
throw new Error( throw new Error(
`Environment value not found for variable: ${params.name}`, `Value not found for environment variable: ${params.name}`,
); );
} else if (!options.quiet) { } else if (!options.quiet) {
cmd.warn( cmd.warn(
@ -104,12 +128,25 @@ export default class EnvAddCmd extends Command {
} }
} }
const reservedPrefixes = await getReservedPrefixes(); const reservedPrefixes = await getReservedPrefixes(balena);
const isConfigVar = _.some(reservedPrefixes, prefix => const isConfigVar = _.some(reservedPrefixes, prefix =>
_.startsWith(params.name, prefix), _.startsWith(params.name, prefix),
); );
const varType = isConfigVar ? 'configVar' : 'envVar';
if (options.service) {
if (isConfigVar) {
throw new ExpectedError(stripIndent`
Configuration variables prefixed with "${reservedPrefixes.join(
'" or "',
)}" cannot be set per service.
Hint: remove the --service option or rename the variable.
`);
}
await setServiceVars(balena, params, options);
return;
}
const varType = isConfigVar ? 'configVar' : 'envVar';
if (options.application) { if (options.application) {
await balena.models.application[varType].set( await balena.models.application[varType].set(
options.application, options.application,
@ -122,16 +159,78 @@ export default class EnvAddCmd extends Command {
params.name, params.name,
params.value, params.value,
); );
} else {
throw new ExpectedError('You must specify an application or device');
} }
} }
} }
async function getReservedPrefixes(): Promise<string[]> { /**
const balena = (await import('balena-sdk')).fromSharedOptions(); * Add service variables for a device or application.
const settings = await balena.settings.getAll(); */
async function setServiceVars(
sdk: BalenaSdk.BalenaSDK,
params: ArgsDef,
options: FlagsDef,
) {
if (options.application) {
const serviceId = await getServiceIdForApp(
sdk,
options.application,
options.service!,
);
await sdk.models.service.var.set(serviceId, params.name, params.value!);
} else {
const { getDeviceAndAppFromUUID } = await import('../../utils/cloud');
const [device, app] = await getDeviceAndAppFromUUID(
sdk,
options.device!,
['id'],
['app_name'],
);
const serviceId = await getServiceIdForApp(
sdk,
app.app_name,
options.service!,
);
await sdk.models.device.serviceVar.set(
device.id,
serviceId,
params.name,
params.value!,
);
}
}
/**
* Return a sevice ID for the given app name and service name.
*/
async function getServiceIdForApp(
sdk: BalenaSdk.BalenaSDK,
appName: string,
serviceName: string,
): Promise<number> {
let serviceId: number | undefined;
const services = await sdk.models.service.getAllByApplication(appName, {
$filter: { service_name: serviceName },
});
if (!_.isEmpty(services)) {
serviceId = services[0].id;
}
if (serviceId === undefined) {
throw new ExpectedError(
`Cannot find service ${serviceName} for application ${appName}`,
);
}
return serviceId;
}
/**
* Return an array of variable name prefixes like: [ 'RESIN_', 'BALENA_' ].
* These prefixes can be used to identify "configuration variables".
*/
async function getReservedPrefixes(
balena: BalenaSdk.BalenaSDK,
): Promise<string[]> {
const settings = await balena.settings.getAll();
const response = await balena.request.send({ const response = await balena.request.send({
baseUrl: settings.apiUrl, baseUrl: settings.apiUrl,
url: '/config/vars', url: '/config/vars',

View File

@ -58,7 +58,7 @@ Additional commands:
device rm <uuid> remove a device device rm <uuid> remove a device
device shutdown <uuid> shutdown a device device shutdown <uuid> shutdown a device
devices supported list all supported devices devices supported list all supported devices
env add <name> [value] add an environment or config variable to an application or device env add <name> [value] add an environment or config variable to an application, device or service
env rename <id> <value> change the value of an environment variable for an app or device env rename <id> <value> change the value of an environment variable for an app or device
env rm <id> remove an environment variable from an application or device env rm <id> remove an environment variable from an application or device
envs list the environment or config variables of an application, device or service envs list the environment or config variables of an application, device or service