Convert os download to oclif, typescript

Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
Scott Lowe 2020-07-13 15:07:51 +02:00
parent c65dafd2ff
commit 1c17572db0
10 changed files with 257 additions and 148 deletions

View File

@ -121,6 +121,7 @@ const capitanoDoc = {
'build/actions/os.js',
'build/actions-oclif/os/configure.js',
'build/actions-oclif/os/versions.js',
'build/actions-oclif/os/download.js',
],
},
{

View File

@ -1538,14 +1538,14 @@ device type
## os download &#60;type&#62;
Use this command to download an unconfigured os image for a certain device type.
Download an unconfigured OS image for a certain device type.
Check available types with `balena devices supported`
> Note: Currently this command only works with balenaCloud, not openBalena.
> If using openBalena, please download the OS from: https://www.balena.io/os/
Note: Currently this command only works with balenaCloud, not openBalena.
If using openBalena, please download the OS from: https://www.balena.io/os/
If version is not specified the newest stable (non-pre-release) version of OS
is downloaded if available, or the newest version otherwise (if all existing
is downloaded (if available), otherwise the newest version (if all existing
versions for the given device type are pre-release).
You can pass `--version menu` to pick the OS version from the interactive menu
@ -1560,13 +1560,19 @@ Examples:
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version default
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu
### Arguments
#### TYPE
the device type
### Options
#### --output, -o &#60;output&#62;
#### -o, --output OUTPUT
output path
#### --version &#60;version&#62;
#### --version VERSION
exact version number, or a valid semver range,
or 'latest' (includes pre-releases),

View File

@ -0,0 +1,96 @@
/**
* @license
* Copyright 2016-2020 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 } from '@oclif/command';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { stripIndent } from '../../utils/lazy';
interface FlagsDef {
output: string;
version?: string;
help: void;
}
interface ArgsDef {
type: string;
}
export default class OsDownloadCmd extends Command {
public static description = stripIndent`
Download an unconfigured OS image.
Download an unconfigured OS image for a certain device type.
Check available types with \`balena devices supported\`
Note: Currently this command only works with balenaCloud, not openBalena.
If using openBalena, please download the OS from: https://www.balena.io/os/
If version is not specified the newest stable (non-pre-release) version of OS
is downloaded (if available), otherwise the newest version (if all existing
versions for the given device type are pre-release).
You can pass \`--version menu\` to pick the OS version from the interactive menu
of all available versions.
`;
public static examples = [
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img',
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 1.24.1',
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version ^1.20.0',
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version latest',
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version default',
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu',
];
public static args = [
{
name: 'type',
description: 'the device type',
required: true,
},
];
public static usage = 'os download <type>';
public static flags: flags.Input<FlagsDef> = {
output: flags.string({
description: 'output path',
char: 'o',
required: true,
}),
version: flags.string({
description: stripIndent`
exact version number, or a valid semver range,
or 'latest' (includes pre-releases),
or 'default' (excludes pre-releases if at least one stable version is available),
or 'recommended' (excludes pre-releases, will fail if only pre-release versions are available),
or 'menu' (will show the interactive menu)
`,
}),
help: cf.help,
};
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
OsDownloadCmd,
);
const { downloadOSImage } = await import('../../utils/cloud');
await downloadOSImage(params.type, options.output, options.version);
}
}

View File

@ -15,146 +15,8 @@ limitations under the License.
*/
import * as commandOptions from './command-options';
import * as _ from 'lodash';
import { getBalenaSdk, getVisuals, getCliForm } from '../utils/lazy';
const formatVersion = function (v, isRecommended) {
let result = `v${v}`;
if (isRecommended) {
result += ' (recommended)';
}
return result;
};
const resolveVersion = function (deviceType, version) {
if (version !== 'menu') {
if (version[0] === 'v') {
version = version.slice(1);
}
return Promise.resolve(version);
}
const balena = getBalenaSdk();
return balena.models.os
.getSupportedVersions(deviceType)
.then(function ({ versions: vs, recommended }) {
const choices = vs.map((v) => ({
value: v,
name: formatVersion(v, v === recommended),
}));
return getCliForm().ask({
message: 'Select the OS version:',
type: 'list',
choices,
default: recommended,
});
});
};
export const download = {
signature: 'os download <type>',
description: 'download an unconfigured os image',
help: `\
Use this command to download an unconfigured os image for a certain device type.
Check available types with \`balena devices supported\`
> Note: Currently this command only works with balenaCloud, not openBalena.
> If using openBalena, please download the OS from: https://www.balena.io/os/
If version is not specified the newest stable (non-pre-release) version of OS
is downloaded if available, or the newest version otherwise (if all existing
versions for the given device type are pre-release).
You can pass \`--version menu\` to pick the OS version from the interactive menu
of all available versions.
Examples:
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 1.24.1
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version ^1.20.0
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version latest
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version default
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu\
`,
options: [
{
signature: 'output',
description: 'output path',
parameter: 'output',
alias: 'o',
required: 'You have to specify the output location',
},
commandOptions.osVersionOrSemver,
],
action(params, options) {
const Bluebird = require('bluebird');
const unzip = require('node-unzip-2');
const fs = require('fs');
const manager = require('balena-image-manager');
console.info(`Getting device operating system for ${params.type}`);
let displayVersion = '';
return Bluebird.try(function () {
if (!options.version) {
console.warn(`OS version is not specified, using the default version: \
the newest stable (non-pre-release) version if available, \
or the newest version otherwise (if all existing \
versions for the given device type are pre-release).`);
return 'default';
}
return resolveVersion(params.type, options.version);
})
.then(function (version) {
if (version !== 'default') {
displayVersion = ` ${version}`;
}
return manager.get(params.type, version);
})
.then(async function (stream) {
const visuals = getVisuals();
const bar = new visuals.Progress(
`Downloading Device OS${displayVersion}`,
);
const spinner = new visuals.Spinner(
`Downloading Device OS${displayVersion} (size unknown)`,
);
stream.on('progress', function (state) {
if (state != null) {
return bar.update(state);
} else {
return spinner.start();
}
});
stream.on('end', () => {
spinner.stop();
});
// We completely rely on the `mime` custom property
// to make this decision.
// The actual stream should be checked instead.
let output;
if (stream.mime === 'application/zip') {
output = unzip.Extract({ path: options.output });
} else {
output = fs.createWriteStream(options.output);
}
const streamToPromise = await import('stream-to-promise');
await streamToPromise(stream.pipe(output));
return options.output;
})
.tap(() => {
console.info('The image was downloaded successfully');
});
},
};
import { getCliForm } from '../utils/lazy';
const buildConfigForDeviceType = function (deviceType, advanced) {
if (advanced == null) {

View File

@ -47,7 +47,6 @@ capitano.globalOption({
capitano.command(actions.help.help);
// ---------- OS Module ----------
capitano.command(actions.os.download);
capitano.command(actions.os.buildConfig);
capitano.command(actions.os.initialize);

View File

@ -181,6 +181,7 @@ export const convertedCommands = [
'note',
'os:configure',
'os:versions',
'os:download',
'scan',
'settings',
'ssh',

View File

@ -17,7 +17,7 @@
import type * as SDK from 'balena-sdk';
import * as _ from 'lodash';
import { stripIndent } from './lazy';
import { getBalenaSdk, getCliForm, getVisuals, stripIndent } from './lazy';
import { ExpectedError } from '../errors';
@ -106,3 +106,104 @@ export const getDeviceAndMaybeAppFromUUID = _.memoize(
// Memoize the call based on UUID
(_sdk, deviceUUID) => deviceUUID,
);
/**
* Download balenaOS image for the specified `deviceType`.
* `OSVersion` may be one of:
* - exact version number,
* - valid semver range,
* - `latest` (includes pre-releases),
* - `default` (excludes pre-releases if at least one stable version is available),
* - `recommended` (excludes pre-releases, will fail if only pre-release versions are available),
* - `menu` (will show the interactive menu )
* If not provided, OSVersion will be set to `default`
*
* @param deviceType
* @param outputPath
* @param OSVersion
*/
export async function downloadOSImage(
deviceType: string,
outputPath: string,
OSVersion?: string,
) {
console.info(`Getting device operating system for ${deviceType}`);
if (!OSVersion) {
console.warn(
'Using default OS version: the latest stable version, or the latest version for pre-release devices',
);
}
OSVersion = OSVersion
? await resolveOSVersion(deviceType, OSVersion)
: 'default';
const displayVersion = OSVersion === 'default' ? '' : ` ${OSVersion}`;
const manager = await import('balena-image-manager');
const stream = await manager.get(deviceType, OSVersion);
const visuals = getVisuals();
const bar = new visuals.Progress(`Downloading Device OS${displayVersion}`);
const spinner = new visuals.Spinner(
`Downloading Device OS${displayVersion} (size unknown)`,
);
stream.on('progress', (state: any) => {
if (state != null) {
return bar.update(state);
} else {
return spinner.start();
}
});
stream.on('end', () => {
spinner.stop();
});
// We completely rely on the `mime` custom property
// to make this decision.
// The actual stream should be checked instead.
let output;
if (stream.mime === 'application/zip') {
const unzip = await import('node-unzip-2');
output = unzip.Extract({ path: outputPath });
} else {
const fs = await import('fs');
output = fs.createWriteStream(outputPath);
}
const streamToPromise = await import('stream-to-promise');
await streamToPromise(stream.pipe(output));
console.info('The image was downloaded successfully');
return outputPath;
}
async function resolveOSVersion(deviceType: string, version: string) {
if (version !== 'menu') {
if (version[0] === 'v') {
version = version.slice(1);
}
return version;
}
const {
versions: vs,
recommended,
} = await getBalenaSdk().models.os.getSupportedVersions(deviceType);
const choices = vs.map((v) => ({
value: v,
name: `v${v}` + (v === recommended ? ' (recommended)' : ''),
}));
return getCliForm().ask({
message: 'Select the OS version:',
type: 'list',
choices,
default: recommended,
});
}

View File

@ -81,7 +81,7 @@ Additional commands:
note <|note> set a device note
os build-config <image> <device-type> build the OS config and save it to the JSON file
os configure <image> configure a previously downloaded balenaOS image
os download <type> download an unconfigured os image
os download <type> download an unconfigured OS image
os initialize <image> initialize an os image
os versions <type> show available balenaOS versions for the given device type
settings print current settings

25
typings/balena-image-manager/index.d.ts vendored Normal file
View File

@ -0,0 +1,25 @@
/**
* @license
* Copyright 2020 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.
*/
declare module 'balena-image-manager' {
export function cleanCache(): Promise<void>;
export function get(
deviceType: string,
versionOrRange: string,
): Promise<NodeJS.ReadableStream & { mime: string }>;
}

18
typings/node-unzip-2/index.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
/**
* @license
* Copyright 2020 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.
*/
declare module 'node-unzip-2';