Skip to content

Commit

Permalink
Add commands for exporting and importing app/fleet releases.
Browse files Browse the repository at this point in the history
Change-type: minor
Signed-off-by: Carlo Miguel F. Cruz <[email protected]>
  • Loading branch information
cmfcruz committed Sep 3, 2024
1 parent c2dbcaa commit 2368d9d
Show file tree
Hide file tree
Showing 12 changed files with 339 additions and 2 deletions.
2 changes: 1 addition & 1 deletion completion/_balena
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ _balena() {
key_cmds=( add rm )
local_cmds=( configure flash )
os_cmds=( build-config configure download initialize versions )
release_cmds=( finalize invalidate validate )
release_cmds=( export finalize import invalidate validate )
tag_cmds=( rm set )


Expand Down
2 changes: 1 addition & 1 deletion completion/balena-completion.bash
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ _balena_complete()
key_cmds="add rm"
local_cmds="configure flash"
os_cmds="build-config configure download initialize versions"
release_cmds="finalize invalidate validate"
release_cmds="export finalize import invalidate validate"
tag_cmds="rm set"


Expand Down
57 changes: 57 additions & 0 deletions docs/balena-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,9 @@ are encouraged to regularly update the balena CLI to the latest version.

- Releases

- [release export &#60;commitorid&#62;](#release-export-commitorid)
- [release finalize &#60;commitorid&#62;](#release-finalize-commitorid)
- [release import &#60;file&#62;](#release-import-file)
- [release &#60;commitorid&#62;](#release-commitorid)
- [release invalidate &#60;commitorid&#62;](#release-invalidate-commitorid)
- [release validate &#60;commitorid&#62;](#release-validate-commitorid)
Expand Down Expand Up @@ -3345,6 +3347,31 @@ The notes for this release

# Releases

## release export &#60;commitOrId&#62;

Saving a release to a file allows you to import an exact
copy of the original release into another application.

Only successful releases can be saved into a file.

Examples:

$ balena release export a777f7345fe3d655c1c981aa642e5555 -o ../path/to/release.tar
$ balena release export 1234567 -o ../path/to/release.tar
$ balena release export myOrg/myFleet:1.2.3 -o ../path/to/release.tar

### Arguments

#### COMMITORID

commit, ID, or version of the release to export

### Options

#### -o, --output OUTPUT

output path

## release finalize &#60;commitOrId&#62;

Finalize a release. Releases can be "draft" or "final", and this command
Expand All @@ -3371,6 +3398,36 @@ the commit or ID of the release to finalize

### Options

## release import &#60;file&#62;

To save a release into a file, use 'balena release export'.

Use the --override-version option to specify the version
of the imported release, overriding the one saved in the file.

Examples:

$ balena release import ../path/to/release.tar -f 1234567
$ balena release import ../path/to/release.tar -f myFleet
$ balena release import ../path/to/release.tar -f myOrg/myFleet
$ balena release import ../path/to/release.tar -f myOrg/myFleet -V 1.2.3

### Arguments

#### BUNDLE

path to a file, e.g. "release.tar"

### Options

#### -f, --fleet FLEET

fleet name or slug (preferred)

#### -V, --override-version OVERRIDE-VERSION

Imports this release with the specified version overriding the version in the file.

## release &#60;commitOrId&#62;

The --json option is recommended when scripting the output of this command,
Expand Down
40 changes: 40 additions & 0 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@
"@balena/dockerignore": "^1.0.2",
"@balena/env-parsing": "^1.1.8",
"@balena/es-version": "^1.0.1",
"@balena/release-bundle": "^0.5.2",
"@oclif/core": "^4.0.8",
"@resin.io/valid-email": "^0.1.0",
"@sentry/node": "^6.16.1",
Expand Down
110 changes: 110 additions & 0 deletions src/commands/release/export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* @license
* Copyright 2016-2024 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { commitOrIdArg } from '.';
import { Flags } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { create } from '@balena/release-bundle';
import * as fs from 'fs/promises';
import * as semver from 'balena-semver';
import { ExpectedError } from '../../errors';

export default class ReleaseExportCmd extends Command {
public static description = stripIndent`
Saves a release into a file.
Saving a release to a file allows you to import an exact
copy of the original release into another application.
Only successful releases can be saved into a file.
`;
public static examples = [
'$ balena release export a777f7345fe3d655c1c981aa642e5555 -o ../path/to/release.tar',
'$ balena release export 1234567 -o ../path/to/release.tar',
'$ balena release export myOrg/myFleet:1.2.3 -o ../path/to/release.tar',
];

public static usage = 'release export <commitOrId>';

public static flags = {
output: Flags.string({
description: 'output path',
char: 'o',
required: true,
}),
help: cf.help,
};

public static args = {
commitOrId: commitOrIdArg({
description: 'commit, ID, or version of the release to export',
required: true,
}),
};

public static authenticated = true;

public async run() {
const { args: params, flags: options } = await this.parse(ReleaseExportCmd);

const balena = getBalenaSdk();

let release: balenaSdk.Release;
if (
typeof params.commitOrId === 'string' &&
params.commitOrId.includes(':')
) {
const fleet = params.commitOrId.split(':')[0];
const parsedVersion = semver.parse(params.commitOrId.split(':')[1]);
if (parsedVersion == null) {
throw new ExpectedError(
`Release ${params.commitOrId} could not be exported; version must be valid SemVer.`,
);
} else {
const rawVersion =
parsedVersion.build.length === 0
? parsedVersion.version
: `${parsedVersion.version}+${parsedVersion.build[0]}`;
release = await balena.models.release.get(
{ application: fleet, rawVersion },
{ $select: ['id'] },
);
}
} else {
release = await balena.models.release.get(params.commitOrId, {
$select: ['id'],
});
}

try {
const releaseBundle = await create({
sdk: balena,
releaseId: release.id,
});
await fs.writeFile(options.output, releaseBundle);
console.log(
`Release ${params.commitOrId} has been exported to ${options.output}.`,
);
} catch (error) {
throw new ExpectedError(
`Release ${params.commitOrId} could not be exported: ${error.message}`,
);
}
}
}
99 changes: 99 additions & 0 deletions src/commands/release/import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* @license
* Copyright 2016-2024 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { apply } from '@balena/release-bundle';
import { createReadStream } from 'fs';
import { ExpectedError } from '../../errors';

export default class ReleaseImportCmd extends Command {
public static description = stripIndent`
Imports a release from a file to an application or fleet.
To save a release into a file, use 'balena release export'.
Use the --override-version option to specify the version
of the imported release, overriding the one saved in the file.
`;
public static examples = [
'$ balena release import ../path/to/release.tar -f 1234567',
'$ balena release import ../path/to/release.tar -f myFleet',
'$ balena release import ../path/to/release.tar -f myOrg/myFleet',
'$ balena release import ../path/to/release.tar -f myOrg/myFleet -V 1.2.3',
];

public static usage = 'release import <file>';

public static flags = {
fleet: { ...cf.fleet, exclusive: ['device'] },
'override-version': Flags.string({
description:
'Imports this release with the specified version overriding the version in the file.',
char: 'V',
required: false,
}),
help: cf.help,
};

public static args = {
bundle: Args.string({
required: true,
description: 'path to a file, e.g. "release.tar"',
}),
};

public static authenticated = true;

public async run() {
const { args: params, flags: options } = await this.parse(ReleaseImportCmd);

const balena = getBalenaSdk();

const bundle = createReadStream(params.bundle);

try {
if (
typeof options.fleet !== 'number' &&
typeof options.fleet !== 'string'
) {
throw new ExpectedError('Fleet must be a number or slug.');
}

// TODO: validate if the path to the release bundle exists

const application = await balena.models.application.get(options.fleet, {
$select: ['id'],
});
await apply({
sdk: balena,
application: application.id,
stream: bundle,
version: options['override-version'],
});
console.log(
`Release bundle ${params.bundle} has been imported to ${options.fleet}.`,
);
} catch (error) {
throw new ExpectedError(
`Could not import release bundle ${params.bundle} to fleet ${options.fleet}. ${error.message}`,
);
}
}
}
6 changes: 6 additions & 0 deletions tests/test-data/pkg/expected-warnings-darwin-arm64.txt
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,12 @@
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/push/index.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/export.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/import.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/index.js
Expand Down
Loading

0 comments on commit 2368d9d

Please sign in to comment.