mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-20 14:13:07 +00:00
push: Add --release-tag flag
You can have 0 or multiple keys without values, if you use values then you should have as many values as you have keys. If you don't want to set a value for a key set its value to "" (bash, cmd.exe) or '""' (powershell). Connects-to: #892 Change-type: minor Signed-off-by: Marios Balamatsias <mbalamatsias@gmail.com>
This commit is contained in:
parent
3bff569758
commit
34557e35ee
@ -2544,6 +2544,7 @@ Examples:
|
|||||||
$ balena push myApp
|
$ balena push myApp
|
||||||
$ balena push myApp --source <source directory>
|
$ balena push myApp --source <source directory>
|
||||||
$ balena push myApp -s <source directory>
|
$ balena push myApp -s <source directory>
|
||||||
|
$ balena push myApp --release-tag key1 "" key2 "value2 with spaces"
|
||||||
|
|
||||||
$ 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>
|
||||||
@ -2658,6 +2659,12 @@ Consider .gitignore files in addition to the .dockerignore file. This reverts
|
|||||||
to the CLI v11 behavior/implementation (deprecated) if compatibility is
|
to the CLI v11 behavior/implementation (deprecated) if compatibility is
|
||||||
required until your project can be adapted.
|
required until your project can be adapted.
|
||||||
|
|
||||||
|
#### --release-tag RELEASE-TAG
|
||||||
|
|
||||||
|
Set release tags if the push to a cloud application is successful. Multiple
|
||||||
|
arguments may be provided, alternating tag keys and values (see examples).
|
||||||
|
Hint: Empty values may be specified with "" (bash, cmd.exe) or '""' (PowerShell).
|
||||||
|
|
||||||
# Settings
|
# Settings
|
||||||
|
|
||||||
## settings
|
## settings
|
||||||
|
@ -22,6 +22,7 @@ import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
|||||||
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
|
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
|
||||||
import type { BalenaSDK, Application, Organization } from 'balena-sdk';
|
import type { BalenaSDK, Application, Organization } from 'balena-sdk';
|
||||||
import { ExpectedError, instanceOf } from '../errors';
|
import { ExpectedError, instanceOf } from '../errors';
|
||||||
|
import type { RegistrySecrets } from 'resin-multibuild';
|
||||||
|
|
||||||
enum BuildTarget {
|
enum BuildTarget {
|
||||||
Cloud,
|
Cloud,
|
||||||
@ -46,6 +47,7 @@ interface FlagsDef {
|
|||||||
'convert-eol'?: boolean;
|
'convert-eol'?: boolean;
|
||||||
'noconvert-eol'?: boolean;
|
'noconvert-eol'?: boolean;
|
||||||
'multi-dockerignore'?: boolean;
|
'multi-dockerignore'?: boolean;
|
||||||
|
'release-tag'?: string[];
|
||||||
help: void;
|
help: void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +94,7 @@ export default class PushCmd extends Command {
|
|||||||
'$ balena push myApp',
|
'$ balena push myApp',
|
||||||
'$ balena push myApp --source <source directory>',
|
'$ balena push myApp --source <source directory>',
|
||||||
'$ balena push myApp -s <source directory>',
|
'$ balena push myApp -s <source directory>',
|
||||||
|
'$ balena push myApp --release-tag key1 "" key2 "value2 with spaces"',
|
||||||
'',
|
'',
|
||||||
'$ 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>',
|
||||||
@ -224,6 +227,15 @@ export default class PushCmd extends Command {
|
|||||||
char: 'g',
|
char: 'g',
|
||||||
exclusive: ['multi-dockerignore'],
|
exclusive: ['multi-dockerignore'],
|
||||||
}),
|
}),
|
||||||
|
'release-tag': flags.string({
|
||||||
|
description: stripIndent`
|
||||||
|
Set release tags if the push to a cloud application is successful. Multiple
|
||||||
|
arguments may be provided, alternating tag keys and values (see examples).
|
||||||
|
Hint: Empty values may be specified with "" (bash, cmd.exe) or '""' (PowerShell).
|
||||||
|
`,
|
||||||
|
multiple: true,
|
||||||
|
exclusive: ['detached'],
|
||||||
|
}),
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -259,19 +271,81 @@ export default class PushCmd extends Command {
|
|||||||
const buildTarget = await this.getBuildTarget(appOrDevice);
|
const buildTarget = await this.getBuildTarget(appOrDevice);
|
||||||
switch (buildTarget) {
|
switch (buildTarget) {
|
||||||
case BuildTarget.Cloud:
|
case BuildTarget.Cloud:
|
||||||
|
await this.pushToCloud(
|
||||||
|
options,
|
||||||
|
sdk,
|
||||||
|
appOrDevice,
|
||||||
|
dockerfilePath,
|
||||||
|
registrySecrets,
|
||||||
|
convertEol,
|
||||||
|
source,
|
||||||
|
nogitignore,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BuildTarget.Device:
|
||||||
|
await this.pushToDevice(
|
||||||
|
options,
|
||||||
|
sdk,
|
||||||
|
appOrDevice,
|
||||||
|
dockerfilePath,
|
||||||
|
registrySecrets,
|
||||||
|
convertEol,
|
||||||
|
source,
|
||||||
|
nogitignore,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ExpectedError(stripIndent`
|
||||||
|
Build target not recognized. Please provide either an application name or
|
||||||
|
device IP address.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async pushToCloud(
|
||||||
|
options: FlagsDef,
|
||||||
|
sdk: BalenaSDK,
|
||||||
|
appOrDevice: string,
|
||||||
|
dockerfilePath: string,
|
||||||
|
registrySecrets: RegistrySecrets,
|
||||||
|
convertEol: boolean,
|
||||||
|
source: string,
|
||||||
|
nogitignore: boolean,
|
||||||
|
) {
|
||||||
|
const _ = await import('lodash');
|
||||||
const remote = await import('../utils/remote-build');
|
const remote = await import('../utils/remote-build');
|
||||||
|
|
||||||
// Check for invalid options
|
// Check for invalid options
|
||||||
const localOnlyOptions = ['nolive', 'service', 'system', 'env'];
|
const localOnlyOptions: Array<keyof FlagsDef> = [
|
||||||
|
'nolive',
|
||||||
|
'service',
|
||||||
|
'system',
|
||||||
|
'env',
|
||||||
|
];
|
||||||
|
this.checkInvalidOptions(
|
||||||
|
localOnlyOptions,
|
||||||
|
options,
|
||||||
|
'is only valid when pushing to a local mode device',
|
||||||
|
);
|
||||||
|
|
||||||
localOnlyOptions.forEach((opt) => {
|
const releaseTags = options['release-tag'] ?? [];
|
||||||
// @ts-ignore : Not sure why typescript wont let me do this?
|
const releaseTagKeys = releaseTags.filter((_v, i) => i % 2 === 0);
|
||||||
if (options[opt]) {
|
const releaseTagValues = releaseTags.filter((_v, i) => i % 2 === 1);
|
||||||
|
|
||||||
|
releaseTagKeys.forEach((key) => {
|
||||||
|
if (key === '') {
|
||||||
|
throw new ExpectedError(`Error: --release-tag keys cannot be empty`);
|
||||||
|
}
|
||||||
|
if (/\s/.test(key)) {
|
||||||
throw new ExpectedError(
|
throw new ExpectedError(
|
||||||
`The --${opt} flag is only valid when pushing to a local mode device`,
|
`Error: --release-tag keys cannot contain whitespaces`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (releaseTagKeys.length !== releaseTagValues.length) {
|
||||||
|
releaseTagValues.push('');
|
||||||
|
}
|
||||||
|
|
||||||
const app = appOrDevice;
|
const app = appOrDevice;
|
||||||
await Command.checkLoggedIn();
|
await Command.checkLoggedIn();
|
||||||
@ -300,10 +374,41 @@ export default class PushCmd extends Command {
|
|||||||
sdk,
|
sdk,
|
||||||
opts,
|
opts,
|
||||||
};
|
};
|
||||||
await remote.startRemoteBuild(args);
|
const releaseId = await remote.startRemoteBuild(args);
|
||||||
break;
|
if (releaseId) {
|
||||||
|
// Above we have checked that releaseTagKeys and releaseTagValues are of the same size
|
||||||
|
await Promise.all(
|
||||||
|
(_.zip(releaseTagKeys, releaseTagValues) as Array<
|
||||||
|
[string, string]
|
||||||
|
>).map(async ([key, value]) => {
|
||||||
|
await sdk.models.release.tags.set(releaseId, key, value);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (releaseTagKeys.length > 0) {
|
||||||
|
throw new Error(stripIndent`
|
||||||
|
A release ID could not be parsed out of the builder's output.
|
||||||
|
As a result, the release tags have not been set.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async pushToDevice(
|
||||||
|
options: FlagsDef,
|
||||||
|
_sdk: BalenaSDK,
|
||||||
|
appOrDevice: string,
|
||||||
|
dockerfilePath: string,
|
||||||
|
registrySecrets: RegistrySecrets,
|
||||||
|
convertEol: boolean,
|
||||||
|
source: string,
|
||||||
|
nogitignore: boolean,
|
||||||
|
) {
|
||||||
|
// Check for invalid options
|
||||||
|
const remoteOnlyOptions: Array<keyof FlagsDef> = ['release-tag'];
|
||||||
|
this.checkInvalidOptions(
|
||||||
|
remoteOnlyOptions,
|
||||||
|
options,
|
||||||
|
'is only valid when pushing to an application',
|
||||||
|
);
|
||||||
|
|
||||||
case BuildTarget.Device:
|
|
||||||
const deviceDeploy = await import('../utils/device/deploy');
|
const deviceDeploy = await import('../utils/device/deploy');
|
||||||
const device = appOrDevice;
|
const device = appOrDevice;
|
||||||
const servicesToDisplay = options.service;
|
const servicesToDisplay = options.service;
|
||||||
@ -335,13 +440,6 @@ export default class PushCmd extends Command {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new ExpectedError(stripIndent`
|
|
||||||
Build target not recognized. Please provide either an application name or
|
|
||||||
device IP address.`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBuildTarget(appOrDevice: string): Promise<BuildTarget | null> {
|
async getBuildTarget(appOrDevice: string): Promise<BuildTarget | null> {
|
||||||
@ -416,4 +514,16 @@ export default class PushCmd extends Command {
|
|||||||
|
|
||||||
return selected.extra;
|
return selected.extra;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkInvalidOptions(
|
||||||
|
invalidOptions: Array<keyof FlagsDef>,
|
||||||
|
options: FlagsDef,
|
||||||
|
errorMessage: string,
|
||||||
|
) {
|
||||||
|
invalidOptions.forEach((opt) => {
|
||||||
|
if (options[opt]) {
|
||||||
|
throw new ExpectedError(`The --${opt} flag ${errorMessage}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,9 @@ async function getBuilderEndpoint(
|
|||||||
return `${builderUrl}/v3/build?${args}`;
|
return `${builderUrl}/v3/build?${args}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startRemoteBuild(build: RemoteBuild): Promise<void> {
|
export async function startRemoteBuild(
|
||||||
|
build: RemoteBuild,
|
||||||
|
): Promise<number | undefined> {
|
||||||
const [buildRequest, stream] = await getRemoteBuildStream(build);
|
const [buildRequest, stream] = await getRemoteBuildStream(build);
|
||||||
|
|
||||||
// Setup CTRL-C handler so the user can interrupt the build
|
// Setup CTRL-C handler so the user can interrupt the build
|
||||||
@ -133,7 +135,7 @@ export async function startRemoteBuild(build: RemoteBuild): Promise<void> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (build.opts.headless) {
|
if (build.opts.headless) {
|
||||||
await handleHeadlessBuildStream(stream);
|
await handleHeadlessBuildStream(build, stream);
|
||||||
} else {
|
} else {
|
||||||
await handleRemoteBuildStream(build, stream);
|
await handleRemoteBuildStream(build, stream);
|
||||||
}
|
}
|
||||||
@ -142,6 +144,7 @@ export async function startRemoteBuild(build: RemoteBuild): Promise<void> {
|
|||||||
globalLogger.outputDeferredMessages();
|
globalLogger.outputDeferredMessages();
|
||||||
await cancellationPromise;
|
await cancellationPromise;
|
||||||
}
|
}
|
||||||
|
return build.releaseId;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRemoteBuildStream(
|
async function handleRemoteBuildStream(
|
||||||
@ -159,7 +162,10 @@ async function handleRemoteBuildStream(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleHeadlessBuildStream(stream: Stream.Stream) {
|
async function handleHeadlessBuildStream(
|
||||||
|
build: RemoteBuild,
|
||||||
|
stream: Stream.Stream,
|
||||||
|
) {
|
||||||
// We're running a headless build, which means we'll
|
// We're running a headless build, which means we'll
|
||||||
// get a single object back, detailing if the build has
|
// get a single object back, detailing if the build has
|
||||||
// been started
|
// been started
|
||||||
@ -182,6 +188,7 @@ async function handleHeadlessBuildStream(stream: Stream.Stream) {
|
|||||||
if (message.started) {
|
if (message.started) {
|
||||||
console.log('Build successfully started');
|
console.log('Build successfully started');
|
||||||
console.log(` Release ID: ${message.releaseId!}`);
|
console.log(` Release ID: ${message.releaseId!}`);
|
||||||
|
build.releaseId = message.releaseId;
|
||||||
} else {
|
} else {
|
||||||
console.log('Failed to start remote build');
|
console.log('Failed to start remote build');
|
||||||
console.log(` Error: ${message.error!}`);
|
console.log(` Error: ${message.error!}`);
|
||||||
|
Loading…
Reference in New Issue
Block a user