Merge pull request #1934 from balena-io/bump-preload-v10

Adjustments for balena-preload v10 (SDK v14). Improved error reporting.
This commit is contained in:
bulldozer-balena[bot] 2020-07-28 17:20:34 +00:00 committed by GitHub
commit 644e643fab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 223 additions and 135 deletions

View File

@ -1929,7 +1929,7 @@ Examples:
#### --app, -a <appId>
id of the application to preload
Name, slug or numeric ID of the application to preload
#### --commit, -c <hash>

View File

@ -20,6 +20,27 @@ import * as dockerUtils from '../utils/docker';
const isCurrent = (commit) => commit === 'latest' || commit === 'current';
/** @type {any} */
const applicationExpandOptions = {
owns__release: {
$select: ['id', 'commit', 'end_timestamp', 'composition'],
$orderby: [{ end_timestamp: 'desc' }, { id: 'desc' }],
$expand: {
contains__image: {
$select: ['image'],
$expand: {
image: {
$select: ['image_size', 'is_stored_at__image_location'],
},
},
},
},
$filter: {
status: 'success',
},
},
};
let allDeviceTypes;
const getDeviceTypes = function () {
const Bluebird = require('bluebird');
@ -38,12 +59,14 @@ const getDeviceTypes = function () {
const getDeviceTypesWithSameArch = function (deviceTypeSlug) {
return getDeviceTypes().then(function (deviceTypes) {
const deviceType = _.find(deviceTypes, { slug: deviceTypeSlug });
if (!deviceType) {
throw new Error(`Device type "${deviceTypeSlug}" not found in API query`);
}
return _(deviceTypes).filter({ arch: deviceType.arch }).map('slug').value();
});
};
const getApplicationsWithSuccessfulBuilds = function (deviceType) {
const balenaPreload = require('balena-preload');
const balena = getBalenaSdk();
return getDeviceTypesWithSameArch(deviceType).then((deviceTypes) => {
@ -64,7 +87,7 @@ const getApplicationsWithSuccessfulBuilds = function (deviceType) {
},
},
},
$expand: balenaPreload.applicationExpandOptions,
$expand: applicationExpandOptions,
$select: [
'id',
'app_name',
@ -148,16 +171,23 @@ const offerToDisableAutomaticUpdates = function (
}
const message = `\
This application is set to automatically update all devices to the current version.
This might be unexpected behavior: with this enabled, the preloaded device will still
download and install the current release once it is online.
This application is set to track the latest release, and non-pinned devices
are automatically updated when a new release is available. This may lead to
unexpected behavior: The preloaded device will download and install the latest
release once it is online.
Do you want to disable automatic updates for this application?
This prompt gives you the opportunity to disable automatic updates for this
application now. Note that this would result in the application being pinned
to the current latest release, rather than some other release that may have
been selected for preloading. The pinned released may be further managed
through the web dashboard or programatically through the balena API / SDK.
Documentation about release policies and app/device pinning can be found at:
https://www.balena.io/docs/learn/deploy/release-strategy/release-policy/
Warning: To re-enable this requires direct api calls,
see https://balena.io/docs/reference/api/resources/device/#set-device-to-release
Alternatively, the --pin-device-to-release flag may be used to pin only the
preloaded device to the selected release.
Alternatively you can pass the --pin-device-to-release flag to pin only this device to the selected release.\
Would you like to disable automatic updates for this application now?\
`;
return getCliForm()
.ask({
@ -178,11 +208,73 @@ Alternatively you can pass the --pin-device-to-release flag to pin only this dev
});
};
/**
* @param {import('balena-sdk').BalenaSDK} balenaSdk
* @param {string | number} appId
* @returns {Promise<import('balena-sdk').Application>}
*/
async function getAppWithReleases(balenaSdk, appId) {
return balenaSdk.models.application.get(appId, {
$expand: applicationExpandOptions,
});
}
async function prepareAndPreload(preloader, balenaSdk, options) {
const { ExpectedError } = require('../errors');
await preloader.prepare();
preloader.config = { deviceType: 'intel-nuc' };
const application = options.appId
? await getAppWithReleases(balenaSdk, options.appId)
: await selectApplication(preloader.config.deviceType);
/** @type {string} commit hash or the strings 'latest' or 'current' */
let commit;
// Use the commit given as --commit or show an interactive commit selection menu
if (options.commit) {
if (isCurrent(options.commit)) {
if (!application.commit) {
throw new Error(
`Unexpected empty commit hash for app ID "${application.id}"`,
);
}
// handle `--commit current` (and its `--commit latest` synonym)
commit = 'latest';
} else {
const release = _.find(application.owns__release, (r) =>
r.commit.startsWith(options.commit),
);
if (!release) {
throw new ExpectedError(
`There is no release matching commit "${options.commit}"`,
);
}
commit = release.commit;
}
} else {
// this could have the value 'current'
commit = await selectApplicationCommit(application.owns__release);
}
await preloader.setAppIdAndCommit(
application.id,
isCurrent(commit) ? application.commit : commit,
);
// Propose to disable automatic app updates if the commit is not the current release
await offerToDisableAutomaticUpdates(application, commit, options.pinDevice);
// All options are ready: preload the image.
await preloader.preload();
}
const preloadOptions = dockerUtils.appendConnectionOptions([
{
signature: 'app',
parameter: 'appId',
description: 'id of the application to preload',
description: 'Name, slug or numeric ID of the application to preload',
alias: 'a',
},
{
@ -251,12 +343,12 @@ Examples:
permission: 'user',
primary: true,
options: preloadOptions,
action(params, options, done) {
async action(params, options) {
const balena = getBalenaSdk();
const balenaPreload = require('balena-preload');
const visuals = getVisuals();
const nodeCleanup = require('node-cleanup');
const { exitWithExpectedError } = require('../errors');
const { ExpectedError, instanceOf } = require('../errors');
const progressBars = {};
@ -296,7 +388,7 @@ Examples:
options.dontCheckArch = options['dont-check-arch'] || false;
delete options['dont-check-arch'];
if (options.dontCheckArch && !options.appId) {
exitWithExpectedError(
throw new ExpectedError(
'You need to specify an app id if you disable the architecture check.',
);
}
@ -314,106 +406,62 @@ Examples:
}
for (let certificate of certificates) {
if (!certificate.endsWith('.crt')) {
exitWithExpectedError('Certificate file name must end with ".crt"');
throw new ExpectedError('Certificate file name must end with ".crt"');
}
}
// Get a configured dockerode instance
return dockerUtils.getDocker(options).then(function (docker) {
const preloader = new balenaPreload.Preloader(
balena,
docker,
options.appId,
options.commit,
options.image,
options.splashImage,
options.proxy,
options.dontCheckArch,
options.pinDevice,
certificates,
);
const docker = await dockerUtils.getDocker(options);
const preloader = new balenaPreload.Preloader(
null,
docker,
options.appId,
options.commit,
options.image,
options.splashImage,
options.proxy,
options.dontCheckArch,
options.pinDevice,
certificates,
);
let gotSignal = false;
let gotSignal = false;
nodeCleanup(function (_exitCode, signal) {
if (signal) {
gotSignal = true;
nodeCleanup.uninstall(); // don't call cleanup handler again
preloader.cleanup().then(() => {
// calling process.exit() won't inform parent process of signal
process.kill(process.pid, signal);
});
return false;
}
});
if (process.env.DEBUG) {
preloader.stderr.pipe(process.stderr);
}
preloader.on('progress', progressHandler);
preloader.on('spinner', spinnerHandler);
return new Promise(function (resolve, reject) {
preloader.on('error', reject);
return preloader
.prepare()
.then(() => {
// If no appId was provided, show a list of matching apps
if (!preloader.appId) {
return selectApplication(
preloader.config.deviceType,
).then((application) => preloader.setApplication(application));
}
})
.then(() => {
// Use the commit given as --commit or show an interactive commit selection menu
if (options.commit) {
if (isCurrent(options.commit) && preloader.application.commit) {
// handle `--commit current` (and its `--commit latest` synonym)
return 'latest';
}
const release = _.find(preloader.application.owns__release, (r) =>
r.commit.startsWith(options.commit),
);
if (!release) {
exitWithExpectedError(
'There is no release matching this commit',
);
}
return release.commit;
}
return selectApplicationCommit(preloader.application.owns__release);
})
.then(function (commit) {
if (isCurrent(commit)) {
preloader.commit = preloader.application.commit;
} else {
preloader.commit = commit;
}
// Propose to disable automatic app updates if the commit is not the current release
return offerToDisableAutomaticUpdates(
preloader.application,
commit,
options.pinDevice,
);
})
.then(() =>
// All options are ready: preload the image.
preloader.preload(),
)
.catch(balena.errors.BalenaError, exitWithExpectedError)
.then(resolve)
.catch(reject);
})
.then(done)
.finally(function () {
if (!gotSignal) {
return preloader.cleanup();
}
nodeCleanup(function (_exitCode, signal) {
if (signal) {
gotSignal = true;
nodeCleanup.uninstall(); // don't call cleanup handler again
preloader.cleanup().then(() => {
// calling process.exit() won't inform parent process of signal
process.kill(process.pid, signal);
});
return false;
}
});
if (process.env.DEBUG) {
preloader.stderr.pipe(process.stderr);
}
preloader.on('progress', progressHandler);
preloader.on('spinner', spinnerHandler);
try {
await new Promise(function (resolve, reject) {
preloader.on('error', reject);
resolve(prepareAndPreload(preloader, balena, options));
});
} catch (err) {
if (instanceOf(err, balena.errors.BalenaError)) {
const code = err.code ? `(${err.code})` : '';
throw new ExpectedError(`${err.message} ${code}`);
} else {
throw err;
}
} finally {
if (!gotSignal) {
await preloader.cleanup();
}
}
},
};

74
npm-shrinkwrap.json generated
View File

@ -2447,12 +2447,12 @@
}
},
"balena-preload": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/balena-preload/-/balena-preload-9.0.0.tgz",
"integrity": "sha512-K1tmZMGQHzAFHkry8mOVwTyDwsPJnn4EXYl0Sv6Ke9+B8IbdfxJzQ3uIihkBvSASME1BvbkOLX2mE+0TC2929A==",
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/balena-preload/-/balena-preload-10.2.0.tgz",
"integrity": "sha512-2FDL/Piy70TDEj2JRLzx7LAfyo+wUP4Y/TDFk5OKVyjqu9bDQNgzVY0COHUMS9xHKjwpCqim6Yh62GyNQ6FyDA==",
"requires": {
"archiver": "^3.1.1",
"balena-sdk": "^13.8.0",
"balena-sdk": "^14.0.0",
"bluebird": "^3.7.2",
"compare-versions": "^3.6.0",
"docker-progress": "^3.0.5",
@ -2467,6 +2467,54 @@
"unzipper": "^0.8.14"
},
"dependencies": {
"balena-errors": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/balena-errors/-/balena-errors-4.4.1.tgz",
"integrity": "sha512-912lPp1LyBjkpxRg6m/EpOCssqMhgkzyYbrKwtT2uRvixm89WOlJrj5sPkxnbPnp5IoMNaoRONxFt1jtiQf50Q==",
"requires": {
"tslib": "^2.0.0",
"typed-error": "^3.0.0"
}
},
"balena-sdk": {
"version": "14.8.0",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-14.8.0.tgz",
"integrity": "sha512-GptB/Ju8BtE1E9NODHioScdF4HW8svcDhfdMHPrDjMFWbs38KOoTgiKogDjLlvEjoAg5FIvz8DaUeH27EtZ1Rg==",
"requires": {
"@balena/es-version": "^1.0.0",
"@types/bluebird": "^3.5.30",
"@types/lodash": "^4.14.150",
"@types/memoizee": "^0.4.3",
"@types/node": "^10.17.20",
"abortcontroller-polyfill": "^1.4.0",
"balena-auth": "^3.1.0",
"balena-errors": "^4.4.0",
"balena-hup-action-utils": "~4.0.1",
"balena-pine": "^11.2.0",
"balena-register-device": "^6.1.1",
"balena-request": "^10.0.9",
"balena-semver": "^2.3.0",
"balena-settings-client": "^4.0.4",
"bluebird": "^3.7.2",
"lodash": "^4.17.15",
"memoizee": "^0.4.14",
"moment": "~2.24.0 || ^2.25.1",
"ndjson": "^1.5.0",
"semver": "^7.3.2",
"tslib": "^2.0.0"
}
},
"balena-semver": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/balena-semver/-/balena-semver-2.3.0.tgz",
"integrity": "sha512-dlUBaYz22ZxHh3umciI/87aLcwX+HRlT1RjkpuiClO8wjkuR8U/2ZvtS16iMNe30rm2kNgAV2myamQ5eA8SxVQ==",
"requires": {
"@types/lodash": "^4.14.149",
"@types/semver": "^7.1.0",
"lodash": "^4.17.15",
"semver": "^7.1.3"
}
},
"docker-progress": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/docker-progress/-/docker-progress-3.0.5.tgz",
@ -2479,6 +2527,13 @@
"lodash": "^4.0.0",
"request": "^2.65.0",
"semver": "^5.3.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
}
},
"pump": {
@ -2491,9 +2546,9 @@
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
},
"tar-fs": {
"version": "2.1.0",
@ -2513,6 +2568,11 @@
"requires": {
"os-tmpdir": "~1.0.2"
}
},
"tslib": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz",
"integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g=="
}
}
},

View File

@ -189,7 +189,7 @@
"balena-device-init": "^5.0.2",
"balena-errors": "^4.3.0",
"balena-image-manager": "^7.0.1",
"balena-preload": "^9.0.0",
"balena-preload": "^10.2.0",
"balena-release": "^2.1.0",
"balena-sdk": "^13.6.0",
"balena-semver": "^2.2.0",

View File

@ -1,20 +0,0 @@
diff --git a/node_modules/balena-preload/src/preload.py b/node_modules/balena-preload/src/preload.py
index 887eaf3..0c5cc7f 100755
--- a/node_modules/balena-preload/src/preload.py
+++ b/node_modules/balena-preload/src/preload.py
@@ -489,8 +489,13 @@ def start_docker_daemon(storage_driver, docker_dir):
if running_dockerd.process.exit_code is not None:
# There is no reason for dockerd to exit with a 0 status now.
assert running_dockerd.process.exit_code != 0
- # This will raise an sh.ErrorReturnCode_X exception.
- running_dockerd.wait()
+ try:
+ # This will raise an sh.ErrorReturnCode_X exception.
+ running_dockerd.wait()
+ except:
+ print("An error has occurred executing 'dockerd':\n{}".format(
+ running_dockerd.stderr.decode("utf8")), file=sys.stderr, flush=True)
+ raise
# Check that we can connect to dockerd.
output = docker("version", _ok_code=[0, 1])
ok = output.exit_code == 0