Release export/import code enhancements/fixes.

Change-type: patch
Signed-off-by: Carlo Miguel F. Cruz <carloc@balena.io>
This commit is contained in:
Carlo Miguel F. Cruz 2024-09-17 00:40:06 +08:00
parent 8a588706bb
commit 7cc0d8b155
No known key found for this signature in database
GPG Key ID: 997DBC01FCE3FD53
5 changed files with 78 additions and 79 deletions

View File

@ -282,9 +282,9 @@ are encouraged to regularly update the balena CLI to the latest version.
- Releases
- [release export &#60;commitorfleet&#62;](#release-export-commitorfleet)
- [release export &#60;commitorapplication&#62;](#release-export-commitorapplication)
- [release finalize &#60;commitorid&#62;](#release-finalize-commitorid)
- [release import &#60;file&#62; &#60;fleet&#62;](#release-import-file-fleet)
- [release import &#60;file&#62; &#60;applicapplication&#62;](#release-import-file-applicapplication)
- [release &#60;commitorid&#62;](#release-commitorid)
- [release invalidate &#60;commitorid&#62;](#release-invalidate-commitorid)
- [release validate &#60;commitorid&#62;](#release-validate-commitorid)
@ -3347,27 +3347,29 @@ The notes for this release
# Releases
## release export &#60;commitOrFleet&#62;
## release export &#60;commitOrApplication&#62;
Exports a release to a file that you can use to import an exact
copy of the original release into another app.
copy of the original release into another app, block, or fleet.
If the SemVer of a release is provided using the --version option,
the first argument is assumed to be the fleet's slug.
the first argument is assumed to be the app's slug.
Only successful releases can be exported.
To import a release to an app, block, or fleet, use 'balena release import'.
Examples:
$ balena release export a777f7345fe3d655c1c981aa642e5555 -o ../path/to/release.tar
$ balena release export myOrg/myFleet --version 1.2.3 -o ../path/to/release.tar
$ balena release export myFleet --version 1.2.3 -o ../path/to/release.tar
$ balena release export myApp --version 1.2.3 -o ../path/to/release.tar
### Arguments
#### COMMITORFLEET
#### COMMITORAPPLICATION
release commit or fleet if used in conjunction with the --version option
release commit or app if used in conjunction with the --version option
### Options
@ -3377,7 +3379,7 @@ output path
#### --version VERSION
version of the release to export from the specified fleet
version of the release to export from the specified app, block, or fleet
## release finalize &#60;commitOrId&#62;
@ -3405,22 +3407,22 @@ the commit or ID of the release to finalize
### Options
## release import &#60;file&#62; &#60;fleet&#62;
## release import &#60;file&#62; &#60;applicapplication&#62;
Imports a release from a file to an app or fleet. The revision field of the release
is automatically omitted when importing a release. The backend will auto-increment
the revision field of the imported release if a release exists with the same semver.
A release will not be imported if a successful release with the same commit already
exists.
To export a release to a file, use 'balena release export'.
Imports a release from a file to an app, block, or fleet. The revision field of the
release is automatically omitted when importing a release. The balena API will
auto-increment the revision field of the imported release if a release exists with
the same semver. A release will not be imported if a successful release with the
same commit already exists.
Use the --override-version option to specify the version
of the imported release, overriding the one saved in the file.
To export a release to a file, use 'balena release export'.
Examples:
$ balena release import ../path/to/release.tar myFleet
$ balena release import ../path/to/release.tar myApp
$ balena release import ../path/to/release.tar myOrg/myFleet
$ balena release import ../path/to/release.tar myOrg/myFleet --override-version 1.2.3
@ -3430,9 +3432,9 @@ Examples:
path to a file, e.g. "./release.tar"
#### FLEET
#### APPLICATION
fleet that the release will be imported to, e.g. "myOrg/myFleet"
app, block, or fleet that the release will be imported to, e.g. "myOrg/myFleet"
### Options

View File

@ -29,20 +29,22 @@ export default class ReleaseExportCmd extends Command {
Exports a release into a file.
Exports a release to a file that you can use to import an exact
copy of the original release into another app.
copy of the original release into another app, block, or fleet.
If the SemVer of a release is provided using the --version option,
the first argument is assumed to be the fleet's slug.
the first argument is assumed to be the app's slug.
Only successful releases can be exported.
To import a release to an app, block, or fleet, use 'balena release import'.
`;
public static examples = [
'$ balena release export a777f7345fe3d655c1c981aa642e5555 -o ../path/to/release.tar',
'$ balena release export myOrg/myFleet --version 1.2.3 -o ../path/to/release.tar',
'$ balena release export myFleet --version 1.2.3 -o ../path/to/release.tar',
'$ balena release export myApp --version 1.2.3 -o ../path/to/release.tar',
];
public static usage = 'release export <commitOrFleet>';
public static usage = 'release export <commitOrApplication>';
public static flags = {
output: Flags.string({
@ -51,15 +53,16 @@ export default class ReleaseExportCmd extends Command {
required: true,
}),
version: Flags.string({
description: 'version of the release to export from the specified fleet',
description:
'version of the release to export from the specified app, block, or fleet',
}),
help: cf.help,
};
public static args = {
commitOrFleet: Args.string({
commitOrApplication: Args.string({
description:
'release commit or fleet if used in conjunction with the --version option',
'release commit or app if used in conjunction with the --version option',
required: true,
}),
};
@ -69,44 +72,46 @@ export default class ReleaseExportCmd extends Command {
public async run() {
const { args: params, flags: options } = await this.parse(ReleaseExportCmd);
const balena = getBalenaSdk();
let versionInfo = '';
try {
let releaseDetails:
| string
| number
| { application: string | number; rawVersion: string }; // ReleaseRawVersionApplicationPair
const balena = getBalenaSdk();
let releaseDetails: string | { application: number; rawVersion: string }; // ReleaseRawVersionApplicationPair
if (options.version != null) {
versionInfo = ` version ${options.version}`;
const parsedVersion = semver.parse(options.version);
if (parsedVersion == null) {
throw new ExpectedError(`version must be valid SemVer`);
} else {
const { getApplication } = await import('../../utils/sdk');
const application = (
await getApplication(balena, params.commitOrFleet)
).id;
releaseDetails = { application, rawVersion: parsedVersion.raw };
}
const { getApplication } = await import('../../utils/sdk');
const { id: application } = await getApplication(
balena,
params.commitOrApplication,
);
releaseDetails = { application, rawVersion: parsedVersion.raw };
} else {
releaseDetails = params.commitOrFleet;
releaseDetails = params.commitOrApplication;
}
const release = await balena.models.release.get(releaseDetails, {
$select: ['id'],
});
const { id: releaseId } = await balena.models.release.get(
releaseDetails,
{
$select: ['id'],
},
);
const releaseBundle = await create({
sdk: balena,
releaseId: release.id,
releaseId,
});
await fs.writeFile(options.output, releaseBundle);
console.log(
`Release ${params.commitOrFleet}${versionInfo} has been exported to ${options.output}.`,
`Release ${params.commitOrApplication}${versionInfo} has been exported to ${options.output}.`,
);
} catch (error) {
throw new ExpectedError(
`Release ${params.commitOrFleet}${versionInfo} could not be exported: ${error.message}`,
`Release ${params.commitOrApplication}${versionInfo} could not be exported: ${error.message}`,
);
}
}

View File

@ -26,26 +26,26 @@ import { ExpectedError } from '../../errors';
export default class ReleaseImportCmd extends Command {
public static description = stripIndent`
Imports a release from a file to an app or fleet.
Imports a release from a file to an app, block, or fleet.
Imports a release from a file to an app or fleet. The revision field of the release
is automatically omitted when importing a release. The backend will auto-increment
the revision field of the imported release if a release exists with the same semver.
A release will not be imported if a successful release with the same commit already
exists.
To export a release to a file, use 'balena release export'.
Imports a release from a file to an app, block, or fleet. The revision field of the
release is automatically omitted when importing a release. The balena API will
auto-increment the revision field of the imported release if a release exists with
the same semver. A release will not be imported if a successful release with the
same commit already exists.
Use the --override-version option to specify the version
of the imported release, overriding the one saved in the file.
To export a release to a file, use 'balena release export'.
`;
public static examples = [
'$ balena release import ../path/to/release.tar myFleet',
'$ balena release import ../path/to/release.tar myApp',
'$ balena release import ../path/to/release.tar myOrg/myFleet',
'$ balena release import ../path/to/release.tar myOrg/myFleet --override-version 1.2.3',
];
public static usage = 'release import <file> <fleet>';
public static usage = 'release import <file> <applicapplication>';
public static flags = {
'override-version': Flags.string({
@ -61,10 +61,10 @@ export default class ReleaseImportCmd extends Command {
required: true,
description: 'path to a file, e.g. "./release.tar"',
}),
fleet: Args.string({
application: Args.string({
required: true,
description:
'fleet that the release will be imported to, e.g. "myOrg/myFleet"',
'app, block, or fleet that the release will be imported to, e.g. "myOrg/myFleet"',
}),
};
@ -73,23 +73,24 @@ export default class ReleaseImportCmd extends Command {
public async run() {
const { args: params, flags: options } = await this.parse(ReleaseImportCmd);
const balena = getBalenaSdk();
let bundle: ReadStream;
try {
const balena = getBalenaSdk();
let bundle: ReadStream;
try {
const fileHandle = await fs.open(params.bundle);
bundle = fileHandle.createReadStream();
} catch (error) {
throw new ExpectedError(
`${params.bundle} does not exist or is not accessible`,
);
throw new Error(`${params.bundle} does not exist or is not accessible`);
}
const { getApplication } = await import('../../utils/sdk');
const application = (await getApplication(balena, params.fleet)).id;
const { id: application } = await getApplication(
balena,
params.application,
);
if (application == null) {
throw new ExpectedError(`Fleet ${params.fleet} not found`);
throw new ExpectedError(`Fleet ${params.application} not found`);
}
await apply({
sdk: balena,
@ -98,11 +99,11 @@ export default class ReleaseImportCmd extends Command {
version: options['override-version'],
});
console.log(
`Release bundle ${params.bundle} has been imported to ${params.fleet}.`,
`Release bundle ${params.bundle} has been imported to ${params.application}.`,
);
} catch (error) {
throw new ExpectedError(
`Could not import release bundle ${params.bundle} to ${params.fleet}: ${error.message}`,
`Could not import release bundle ${params.bundle} to ${params.application}: ${error.message}`,
);
}
}

View File

@ -23,6 +23,7 @@ describe('balena release export', function () {
this.beforeEach(async function () {
api = new BalenaAPIMock();
api.expectGetWhoAmI();
releaseFileBuffer = await fs.readFile(
path.join('tests', 'test-data', 'release.tar'),
);
@ -38,7 +39,6 @@ describe('balena release export', function () {
});
itSS('should export a release to a file', async () => {
api.expectGetWhoAmI();
api.expectGetRelease();
releaseBundleCreateStub.resolves(stream.Readable.from(releaseFileBuffer));
@ -54,7 +54,6 @@ describe('balena release export', function () {
});
itSS('should fail if the create throws an error', async () => {
api.expectGetWhoAmI();
api.expectGetRelease();
const expectedError = `BalenaReleaseNotFound: Release not found: ${appCommit}`;
releaseBundleCreateStub.rejects(new Error(expectedError));
@ -69,7 +68,6 @@ describe('balena release export', function () {
});
itSS('should parse with application slug and version', async () => {
api.expectGetWhoAmI();
api.expectGetRelease();
api.expectGetApplication({ times: 2 });
releaseBundleCreateStub.resolves(stream.Readable.from(releaseFileBuffer));
@ -86,7 +84,6 @@ describe('balena release export', function () {
});
it('should fail if the app slug is provided without the release version', async () => {
api.expectGetWhoAmI();
api.expectGetRelease({ notFound: true });
const expectedError = `Release not found: ${appSlug}`;
@ -100,7 +97,6 @@ describe('balena release export', function () {
});
it('should fail if the semver is invalid', async () => {
api.expectGetWhoAmI();
const expectedError = 'version must be valid SemVer';
const { err } = await runCommand(

View File

@ -11,13 +11,13 @@ const itSS = process.env.BALENA_CLI_TEST_TYPE === 'standalone' ? it.skip : it;
describe('balena release import', function () {
const appCommit = '4c8becf0780ca69d33b638ea8fa163d7';
const appSlug = 'myOrg/myFleet';
// const appVersion = '1.2.3+rev1';
const releasePath = path.join('tests', 'test-data', 'release.tar');
let api: BalenaAPIMock;
const releaseBundleApplyStub = sinon.stub();
this.beforeEach(async function () {
api = new BalenaAPIMock();
api.expectGetWhoAmI();
mock('@balena/release-bundle', {
apply: releaseBundleApplyStub,
});
@ -30,7 +30,6 @@ describe('balena release import', function () {
});
itSS('should import a release to an app', async () => {
api.expectGetWhoAmI();
api.expectGetApplication();
releaseBundleApplyStub.resolves(123);
@ -48,7 +47,6 @@ describe('balena release import', function () {
itSS(
'should import a release to an app with a version override',
async () => {
api.expectGetWhoAmI();
api.expectGetApplication();
releaseBundleApplyStub.resolves(123);
@ -65,7 +63,6 @@ describe('balena release import', function () {
);
it('should fail if release file does not exist', async () => {
api.expectGetWhoAmI();
const nonExistentFile = path.join(
'tests',
'test-data',
@ -83,7 +80,6 @@ describe('balena release import', function () {
});
itSS('should fail if overriding version is not a valid semver', async () => {
api.expectGetWhoAmI();
api.expectGetApplication();
const expectedError = `Manifest is malformed: Expected version to be a valid semantic version but found '${appCommit}'`;
releaseBundleApplyStub.rejects(new Error(expectedError));
@ -100,7 +96,6 @@ describe('balena release import', function () {
itSS(
'should fail if a successful release with the same commit already exists',
async () => {
api.expectGetWhoAmI();
api.expectGetApplication();
const expectedError = `A successful release with commit ${appCommit} (1.2.3) already exists; nothing to do`;
releaseBundleApplyStub.rejects(new Error(expectedError));