diff --git a/docs/balena-cli.md b/docs/balena-cli.md index 1ab208c2..7c27c9ea 100644 --- a/docs/balena-cli.md +++ b/docs/balena-cli.md @@ -282,9 +282,9 @@ are encouraged to regularly update the balena CLI to the latest version. - Releases - - [release export <commitorfleet>](#release-export-commitorfleet) + - [release export <commitorapplication>](#release-export-commitorapplication) - [release finalize <commitorid>](#release-finalize-commitorid) - - [release import <file> <fleet>](#release-import-file-fleet) + - [release import <file> <applicapplication>](#release-import-file-applicapplication) - [release <commitorid>](#release-commitorid) - [release invalidate <commitorid>](#release-invalidate-commitorid) - [release validate <commitorid>](#release-validate-commitorid) @@ -3347,27 +3347,29 @@ The notes for this release # Releases -## release export <commitOrFleet> +## release export <commitOrApplication> 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 <commitOrId> @@ -3405,22 +3407,22 @@ the commit or ID of the release to finalize ### Options -## release import <file> <fleet> +## release import <file> <applicapplication> -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 diff --git a/src/commands/release/export.ts b/src/commands/release/export.ts index bea9e78b..990910fd 100644 --- a/src/commands/release/export.ts +++ b/src/commands/release/export.ts @@ -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 '; + public static usage = 'release export '; 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}`, ); } } diff --git a/src/commands/release/import.ts b/src/commands/release/import.ts index 87c7c797..47e077a4 100644 --- a/src/commands/release/import.ts +++ b/src/commands/release/import.ts @@ -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 '; + public static usage = 'release import '; 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}`, ); } } diff --git a/tests/commands/release/export.spec.ts b/tests/commands/release/export.spec.ts index e046219e..c4deb935 100644 --- a/tests/commands/release/export.spec.ts +++ b/tests/commands/release/export.spec.ts @@ -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( diff --git a/tests/commands/release/import.spec.ts b/tests/commands/release/import.spec.ts index aeba2528..2aaf6194 100644 --- a/tests/commands/release/import.spec.ts +++ b/tests/commands/release/import.spec.ts @@ -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));