mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-01-18 10:46:34 +00:00
Merge pull request #2013 from balena-io/async-await-oclif
Convert oclif actions to async/await
This commit is contained in:
commit
ac0ce8f702
@ -196,7 +196,6 @@ ${dockerignoreHelp}
|
|||||||
buildOpts: any; // arguments to forward to docker build command
|
buildOpts: any; // arguments to forward to docker build command
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const Bluebird = await import('bluebird');
|
|
||||||
const _ = await import('lodash');
|
const _ = await import('lodash');
|
||||||
const doodles = await import('resin-doodles');
|
const doodles = await import('resin-doodles');
|
||||||
const sdk = getBalenaSdk();
|
const sdk = getBalenaSdk();
|
||||||
@ -206,149 +205,139 @@ ${dockerignoreHelp}
|
|||||||
|
|
||||||
const appType = (opts.app?.application_type as ApplicationType[])?.[0];
|
const appType = (opts.app?.application_type as ApplicationType[])?.[0];
|
||||||
|
|
||||||
return loadProject(logger, composeOpts, opts.image)
|
try {
|
||||||
.then(function (project) {
|
const project = await loadProject(logger, composeOpts, opts.image);
|
||||||
if (
|
if (project.descriptors.length > 1 && !appType?.supports_multicontainer) {
|
||||||
project.descriptors.length > 1 &&
|
throw new ExpectedError(
|
||||||
!appType?.supports_multicontainer
|
'Target application does not support multiple containers. Aborting!',
|
||||||
) {
|
);
|
||||||
throw new ExpectedError(
|
}
|
||||||
'Target application does not support multiple containers. Aborting!',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// find which services use images that already exist locally
|
// find which services use images that already exist locally
|
||||||
return (
|
let servicesToSkip = await Promise.all(
|
||||||
Bluebird.map(project.descriptors, function (d: any) {
|
project.descriptors.map(async function (d: any) {
|
||||||
// unconditionally build (or pull) if explicitly requested
|
// unconditionally build (or pull) if explicitly requested
|
||||||
if (opts.shouldPerformBuild) {
|
if (opts.shouldPerformBuild) {
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
return docker
|
try {
|
||||||
|
await docker
|
||||||
.getImage(
|
.getImage(
|
||||||
(typeof d.image === 'string' ? d.image : d.image.tag) || '',
|
(typeof d.image === 'string' ? d.image : d.image.tag) || '',
|
||||||
)
|
)
|
||||||
.inspect()
|
.inspect();
|
||||||
.then(() => {
|
|
||||||
return d.serviceName;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// Ignore
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.filter((d) => !!d)
|
|
||||||
.then(function (servicesToSkip: any[]) {
|
|
||||||
// multibuild takes in a composition and always attempts to
|
|
||||||
// build or pull all services. we workaround that here by
|
|
||||||
// passing a modified composition.
|
|
||||||
const compositionToBuild = _.cloneDeep(project.composition);
|
|
||||||
compositionToBuild.services = _.omit(
|
|
||||||
compositionToBuild.services,
|
|
||||||
servicesToSkip,
|
|
||||||
);
|
|
||||||
if (_.size(compositionToBuild.services) === 0) {
|
|
||||||
logger.logInfo(
|
|
||||||
'Everything is up to date (use --build to force a rebuild)',
|
|
||||||
);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return compose
|
|
||||||
.buildProject(
|
|
||||||
docker,
|
|
||||||
logger,
|
|
||||||
project.path,
|
|
||||||
project.name,
|
|
||||||
compositionToBuild,
|
|
||||||
opts.app.arch,
|
|
||||||
(opts.app?.is_for__device_type as DeviceType[])?.[0].slug,
|
|
||||||
opts.buildEmulated,
|
|
||||||
opts.buildOpts,
|
|
||||||
composeOpts.inlineLogs,
|
|
||||||
composeOpts.convertEol,
|
|
||||||
composeOpts.dockerfilePath,
|
|
||||||
composeOpts.nogitignore,
|
|
||||||
composeOpts.multiDockerignore,
|
|
||||||
)
|
|
||||||
.then((builtImages) => _.keyBy(builtImages, 'serviceName'));
|
|
||||||
})
|
|
||||||
.then((builtImages: any) =>
|
|
||||||
project.descriptors.map(
|
|
||||||
(d) =>
|
|
||||||
builtImages[d.serviceName] ?? {
|
|
||||||
serviceName: d.serviceName,
|
|
||||||
name: typeof d.image === 'string' ? d.image : d.image.tag,
|
|
||||||
logs: 'Build skipped; image for service already exists.',
|
|
||||||
props: {},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
// @ts-ignore slightly different return types of partial vs non-partial release
|
|
||||||
.then(function (images) {
|
|
||||||
if (appType?.is_legacy) {
|
|
||||||
const { deployLegacy } = require('../utils/deploy-legacy');
|
|
||||||
|
|
||||||
const msg = getChalk().yellow(
|
return d.serviceName;
|
||||||
'Target application requires legacy deploy method.',
|
} catch {
|
||||||
);
|
// Ignore
|
||||||
logger.logWarn(msg);
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
servicesToSkip = servicesToSkip.filter((d) => !!d);
|
||||||
|
|
||||||
return Promise.all([
|
// multibuild takes in a composition and always attempts to
|
||||||
sdk.auth.getToken(),
|
// build or pull all services. we workaround that here by
|
||||||
sdk.auth.whoami(),
|
// passing a modified composition.
|
||||||
sdk.settings.get('balenaUrl'),
|
const compositionToBuild = _.cloneDeep(project.composition);
|
||||||
{
|
compositionToBuild.services = _.omit(
|
||||||
// opts.appName may be prefixed by 'owner/', unlike opts.app.app_name
|
compositionToBuild.services,
|
||||||
appName: opts.appName,
|
servicesToSkip,
|
||||||
imageName: images[0].name,
|
);
|
||||||
buildLogs: images[0].logs,
|
if (_.size(compositionToBuild.services) === 0) {
|
||||||
shouldUploadLogs: opts.shouldUploadLogs,
|
logger.logInfo(
|
||||||
},
|
'Everything is up to date (use --build to force a rebuild)',
|
||||||
])
|
|
||||||
.then(([token, username, url, options]) => {
|
|
||||||
return deployLegacy(
|
|
||||||
docker,
|
|
||||||
logger,
|
|
||||||
token,
|
|
||||||
username,
|
|
||||||
url,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then((releaseId) =>
|
|
||||||
sdk.models.release.get(releaseId, { $select: ['commit'] }),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Promise.all([
|
|
||||||
sdk.auth.getUserId(),
|
|
||||||
sdk.auth.getToken(),
|
|
||||||
sdk.settings.get('apiUrl'),
|
|
||||||
]).then(([userId, auth, apiEndpoint]) =>
|
|
||||||
$deployProject(
|
|
||||||
docker,
|
|
||||||
logger,
|
|
||||||
project.composition,
|
|
||||||
images,
|
|
||||||
opts.app.id,
|
|
||||||
userId,
|
|
||||||
`Bearer ${auth}`,
|
|
||||||
apiEndpoint,
|
|
||||||
!opts.shouldUploadLogs,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
})
|
return {};
|
||||||
.then(function (release: any) {
|
}
|
||||||
logger.outputDeferredMessages();
|
const builtImages = await compose.buildProject(
|
||||||
logger.logSuccess('Deploy succeeded!');
|
docker,
|
||||||
logger.logSuccess(`Release: ${release.commit}`);
|
logger,
|
||||||
console.log();
|
project.path,
|
||||||
console.log(doodles.getDoodle()); // Show charlie
|
project.name,
|
||||||
console.log();
|
compositionToBuild,
|
||||||
})
|
opts.app.arch,
|
||||||
.catch((err) => {
|
(opts.app?.is_for__device_type as DeviceType[])?.[0].slug,
|
||||||
logger.logError('Deploy failed');
|
opts.buildEmulated,
|
||||||
throw err;
|
opts.buildOpts,
|
||||||
});
|
composeOpts.inlineLogs,
|
||||||
|
composeOpts.convertEol,
|
||||||
|
composeOpts.dockerfilePath,
|
||||||
|
composeOpts.nogitignore,
|
||||||
|
composeOpts.multiDockerignore,
|
||||||
|
);
|
||||||
|
const builtImagesByService = _.keyBy(builtImages, 'serviceName');
|
||||||
|
|
||||||
|
const images = project.descriptors.map(
|
||||||
|
(d) =>
|
||||||
|
builtImagesByService[d.serviceName] ?? {
|
||||||
|
serviceName: d.serviceName,
|
||||||
|
name: typeof d.image === 'string' ? d.image : d.image.tag,
|
||||||
|
logs: 'Build skipped; image for service already exists.',
|
||||||
|
props: {},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let release;
|
||||||
|
if (appType?.is_legacy) {
|
||||||
|
const { deployLegacy } = require('../utils/deploy-legacy');
|
||||||
|
|
||||||
|
const msg = getChalk().yellow(
|
||||||
|
'Target application requires legacy deploy method.',
|
||||||
|
);
|
||||||
|
logger.logWarn(msg);
|
||||||
|
|
||||||
|
const [token, username, url, options] = await Promise.all([
|
||||||
|
sdk.auth.getToken(),
|
||||||
|
sdk.auth.whoami(),
|
||||||
|
sdk.settings.get('balenaUrl'),
|
||||||
|
{
|
||||||
|
// opts.appName may be prefixed by 'owner/', unlike opts.app.app_name
|
||||||
|
appName: opts.appName,
|
||||||
|
imageName: images[0].name,
|
||||||
|
buildLogs: images[0].logs,
|
||||||
|
shouldUploadLogs: opts.shouldUploadLogs,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const releaseId = await deployLegacy(
|
||||||
|
docker,
|
||||||
|
logger,
|
||||||
|
token,
|
||||||
|
username,
|
||||||
|
url,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
release = await sdk.models.release.get(releaseId, {
|
||||||
|
$select: ['commit'],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const [userId, auth, apiEndpoint] = await Promise.all([
|
||||||
|
sdk.auth.getUserId(),
|
||||||
|
sdk.auth.getToken(),
|
||||||
|
sdk.settings.get('apiUrl'),
|
||||||
|
]);
|
||||||
|
release = await $deployProject(
|
||||||
|
docker,
|
||||||
|
logger,
|
||||||
|
project.composition,
|
||||||
|
images,
|
||||||
|
opts.app.id,
|
||||||
|
userId,
|
||||||
|
`Bearer ${auth}`,
|
||||||
|
apiEndpoint,
|
||||||
|
!opts.shouldUploadLogs,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.outputDeferredMessages();
|
||||||
|
logger.logSuccess('Deploy succeeded!');
|
||||||
|
logger.logSuccess(`Release: ${release.commit}`);
|
||||||
|
console.log();
|
||||||
|
console.log(doodles.getDoodle()); // Show charlie
|
||||||
|
console.log();
|
||||||
|
} catch (err) {
|
||||||
|
logger.logError('Deploy failed');
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,26 +76,23 @@ export default class DevicesSupportedCmd extends Command {
|
|||||||
|
|
||||||
public async run() {
|
public async run() {
|
||||||
const { flags: options } = this.parse<FlagsDef, {}>(DevicesSupportedCmd);
|
const { flags: options } = this.parse<FlagsDef, {}>(DevicesSupportedCmd);
|
||||||
let deviceTypes: Array<Partial<
|
const dts = await getBalenaSdk().models.config.getDeviceTypes();
|
||||||
SDK.DeviceTypeJson.DeviceType
|
let deviceTypes: Array<Partial<SDK.DeviceTypeJson.DeviceType>> = dts.map(
|
||||||
>> = await getBalenaSdk()
|
(d) => {
|
||||||
.models.config.getDeviceTypes()
|
if (d.aliases && d.aliases.length) {
|
||||||
.then((dts) =>
|
// remove aliases that are equal to the slug
|
||||||
dts.map((d) => {
|
d.aliases = d.aliases.filter((alias: string) => alias !== d.slug);
|
||||||
if (d.aliases && d.aliases.length) {
|
if (!options.json) {
|
||||||
// remove aliases that are equal to the slug
|
// stringify the aliases array with commas and spaces
|
||||||
d.aliases = d.aliases.filter((alias: string) => alias !== d.slug);
|
d.aliases = [d.aliases.join(', ')];
|
||||||
if (!options.json) {
|
|
||||||
// stringify the aliases array with commas and spaces
|
|
||||||
d.aliases = [d.aliases.join(', ')];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// ensure it is always an array (for the benefit of JSON output)
|
|
||||||
d.aliases = [];
|
|
||||||
}
|
}
|
||||||
return d;
|
} else {
|
||||||
}),
|
// ensure it is always an array (for the benefit of JSON output)
|
||||||
);
|
d.aliases = [];
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
},
|
||||||
|
);
|
||||||
if (!options.discontinued) {
|
if (!options.discontinued) {
|
||||||
deviceTypes = deviceTypes.filter((dt) => dt.state !== 'DISCONTINUED');
|
deviceTypes = deviceTypes.filter((dt) => dt.state !== 'DISCONTINUED');
|
||||||
}
|
}
|
||||||
|
@ -89,20 +89,17 @@ export default class LocalConfigureCmd extends Command {
|
|||||||
const dmHandler = (cb: () => void) =>
|
const dmHandler = (cb: () => void) =>
|
||||||
reconfix
|
reconfix
|
||||||
.readConfiguration(configurationSchema, params.target)
|
.readConfiguration(configurationSchema, params.target)
|
||||||
.tap((config: any) => {
|
.then(async (config: any) => {
|
||||||
logger.logDebug('Current config:');
|
logger.logDebug('Current config:');
|
||||||
logger.logDebug(JSON.stringify(config));
|
logger.logDebug(JSON.stringify(config));
|
||||||
})
|
const answers = await this.getConfiguration(config);
|
||||||
.then((config: any) => this.getConfiguration(config))
|
|
||||||
.tap((config: any) => {
|
|
||||||
logger.logDebug('New config:');
|
logger.logDebug('New config:');
|
||||||
logger.logDebug(JSON.stringify(config));
|
logger.logDebug(JSON.stringify(answers));
|
||||||
})
|
|
||||||
.then(async (answers: any) => {
|
|
||||||
if (!answers.hostname) {
|
if (!answers.hostname) {
|
||||||
await this.removeHostname(configurationSchema);
|
await this.removeHostname(configurationSchema);
|
||||||
}
|
}
|
||||||
return reconfix.writeConfiguration(
|
return await reconfix.writeConfiguration(
|
||||||
configurationSchema,
|
configurationSchema,
|
||||||
answers,
|
answers,
|
||||||
params.target,
|
params.target,
|
||||||
@ -220,9 +217,8 @@ export default class LocalConfigureCmd extends Command {
|
|||||||
persistentLogging: data.persistentLogging || false,
|
persistentLogging: data.persistentLogging || false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return inquirer
|
const answers = await inquirer.prompt(this.inquirerOptions(data));
|
||||||
.prompt(this.inquirerOptions(data))
|
return _.merge(data, answers);
|
||||||
.then((answers: any) => _.merge(data, answers));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Taken from https://goo.gl/kr1kCt
|
// Taken from https://goo.gl/kr1kCt
|
||||||
@ -259,62 +255,50 @@ export default class LocalConfigureCmd extends Command {
|
|||||||
const _ = await import('lodash');
|
const _ = await import('lodash');
|
||||||
const imagefs = await import('resin-image-fs');
|
const imagefs = await import('resin-image-fs');
|
||||||
|
|
||||||
return imagefs
|
const files = await imagefs.listDirectory({
|
||||||
.listDirectory({
|
image: target,
|
||||||
image: target,
|
partition: this.BOOT_PARTITION,
|
||||||
partition: this.BOOT_PARTITION,
|
path: this.CONNECTIONS_FOLDER,
|
||||||
path: this.CONNECTIONS_FOLDER,
|
});
|
||||||
})
|
|
||||||
.then((files: string[]) => {
|
|
||||||
// The required file already exists
|
|
||||||
if (_.includes(files, 'resin-wifi')) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fresh image, new mode, accoding to https://github.com/balena-os/meta-balena/pull/770/files
|
let connectionFileName;
|
||||||
if (_.includes(files, 'resin-sample.ignore')) {
|
if (_.includes(files, 'resin-wifi')) {
|
||||||
return imagefs
|
// The required file already exists, nothing to do
|
||||||
.copy(
|
} else if (_.includes(files, 'resin-sample.ignore')) {
|
||||||
{
|
// Fresh image, new mode, accoding to https://github.com/balena-os/meta-balena/pull/770/files
|
||||||
image: target,
|
await imagefs.copy(
|
||||||
partition: this.BOOT_PARTITION,
|
{
|
||||||
path: `${this.CONNECTIONS_FOLDER}/resin-sample.ignore`,
|
image: target,
|
||||||
},
|
partition: this.BOOT_PARTITION,
|
||||||
{
|
path: `${this.CONNECTIONS_FOLDER}/resin-sample.ignore`,
|
||||||
image: target,
|
},
|
||||||
partition: this.BOOT_PARTITION,
|
{
|
||||||
path: `${this.CONNECTIONS_FOLDER}/resin-wifi`,
|
image: target,
|
||||||
},
|
partition: this.BOOT_PARTITION,
|
||||||
)
|
path: `${this.CONNECTIONS_FOLDER}/resin-wifi`,
|
||||||
.thenReturn(null);
|
},
|
||||||
}
|
|
||||||
|
|
||||||
// Legacy mode, to be removed later
|
|
||||||
// We return the file name override from this branch
|
|
||||||
// When it is removed the following cleanup should be done:
|
|
||||||
// * delete all the null returns from this method
|
|
||||||
// * turn `getConfigurationSchema` back into the constant, with the connection filename always being `resin-wifi`
|
|
||||||
// * drop the final `then` from this method
|
|
||||||
// * adapt the code in the main listener to not receive the config from this method, and use that constant instead
|
|
||||||
if (_.includes(files, 'resin-sample')) {
|
|
||||||
return 'resin-sample';
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case there's no file at all (shouldn't happen normally, but the file might have been removed)
|
|
||||||
return imagefs
|
|
||||||
.writeFile(
|
|
||||||
{
|
|
||||||
image: target,
|
|
||||||
partition: this.BOOT_PARTITION,
|
|
||||||
path: `${this.CONNECTIONS_FOLDER}/resin-wifi`,
|
|
||||||
},
|
|
||||||
this.CONNECTION_FILE,
|
|
||||||
)
|
|
||||||
.thenReturn(null);
|
|
||||||
})
|
|
||||||
.then((connectionFileName) =>
|
|
||||||
this.getConfigurationSchema(connectionFileName || undefined),
|
|
||||||
);
|
);
|
||||||
|
} else if (_.includes(files, 'resin-sample')) {
|
||||||
|
// Legacy mode, to be removed later
|
||||||
|
// We return the file name override from this branch
|
||||||
|
// When it is removed the following cleanup should be done:
|
||||||
|
// * delete all the null returns from this method
|
||||||
|
// * turn `getConfigurationSchema` back into the constant, with the connection filename always being `resin-wifi`
|
||||||
|
// * drop the final `then` from this method
|
||||||
|
// * adapt the code in the main listener to not receive the config from this method, and use that constant instead
|
||||||
|
connectionFileName = 'resin-sample';
|
||||||
|
} else {
|
||||||
|
// In case there's no file at all (shouldn't happen normally, but the file might have been removed)
|
||||||
|
await imagefs.writeFile(
|
||||||
|
{
|
||||||
|
image: target,
|
||||||
|
partition: this.BOOT_PARTITION,
|
||||||
|
path: `${this.CONNECTIONS_FOLDER}/resin-wifi`,
|
||||||
|
},
|
||||||
|
this.CONNECTION_FILE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return await this.getConfigurationSchema(connectionFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeHostname(schema: any) {
|
async removeHostname(schema: any) {
|
||||||
|
@ -302,86 +302,72 @@ Can be repeated to add multiple certificates.\
|
|||||||
|
|
||||||
allDeviceTypes: DeviceTypeJson.DeviceType[];
|
allDeviceTypes: DeviceTypeJson.DeviceType[];
|
||||||
async getDeviceTypes() {
|
async getDeviceTypes() {
|
||||||
if (this.allDeviceTypes !== undefined) {
|
if (this.allDeviceTypes === undefined) {
|
||||||
return this.allDeviceTypes;
|
const balena = getBalenaSdk();
|
||||||
|
const deviceTypes = await balena.models.config.getDeviceTypes();
|
||||||
|
this.allDeviceTypes = _.sortBy(deviceTypes, 'name');
|
||||||
}
|
}
|
||||||
const balena = getBalenaSdk();
|
return this.allDeviceTypes;
|
||||||
return balena.models.config
|
|
||||||
.getDeviceTypes()
|
|
||||||
.then((deviceTypes) => _.sortBy(deviceTypes, 'name'))
|
|
||||||
.then((deviceTypes) => {
|
|
||||||
this.allDeviceTypes = deviceTypes;
|
|
||||||
return deviceTypes;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isCurrentCommit(commit: string) {
|
isCurrentCommit(commit: string) {
|
||||||
return commit === 'latest' || commit === 'current';
|
return commit === 'latest' || commit === 'current';
|
||||||
}
|
}
|
||||||
|
|
||||||
getDeviceTypesWithSameArch(deviceTypeSlug: string) {
|
async getDeviceTypesWithSameArch(deviceTypeSlug: string) {
|
||||||
return this.getDeviceTypes().then((deviceTypes) => {
|
const deviceTypes = await this.getDeviceTypes();
|
||||||
const deviceType = _.find(deviceTypes, { slug: deviceTypeSlug });
|
const deviceType = _.find(deviceTypes, { slug: deviceTypeSlug });
|
||||||
if (!deviceType) {
|
if (!deviceType) {
|
||||||
throw new Error(
|
throw new Error(`Device type "${deviceTypeSlug}" not found in API query`);
|
||||||
`Device type "${deviceTypeSlug}" not found in API query`,
|
}
|
||||||
);
|
return _(deviceTypes).filter({ arch: deviceType.arch }).map('slug').value();
|
||||||
}
|
|
||||||
return _(deviceTypes)
|
|
||||||
.filter({ arch: deviceType.arch })
|
|
||||||
.map('slug')
|
|
||||||
.value();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getApplicationsWithSuccessfulBuilds(deviceTypeSlug: string) {
|
async getApplicationsWithSuccessfulBuilds(deviceTypeSlug: string) {
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
return this.getDeviceTypesWithSameArch(deviceTypeSlug).then(
|
const deviceTypes = await this.getDeviceTypesWithSameArch(deviceTypeSlug);
|
||||||
(deviceTypes) => {
|
// TODO: remove the explicit types once https://github.com/balena-io/balena-sdk/pull/889 gets merged
|
||||||
// TODO: remove the explicit types once https://github.com/balena-io/balena-sdk/pull/889 gets merged
|
return balena.pine.get<
|
||||||
return balena.pine.get<
|
Application,
|
||||||
Application,
|
Array<
|
||||||
Array<
|
ApplicationWithDeviceType & {
|
||||||
ApplicationWithDeviceType & {
|
should_be_running__release: [Release?];
|
||||||
should_be_running__release: [Release?];
|
}
|
||||||
}
|
>
|
||||||
>
|
>({
|
||||||
>({
|
resource: 'my_application',
|
||||||
resource: 'my_application',
|
options: {
|
||||||
options: {
|
$filter: {
|
||||||
$filter: {
|
is_for__device_type: {
|
||||||
is_for__device_type: {
|
$any: {
|
||||||
$any: {
|
$alias: 'dt',
|
||||||
$alias: 'dt',
|
$expr: {
|
||||||
$expr: {
|
dt: {
|
||||||
dt: {
|
slug: { $in: deviceTypes },
|
||||||
slug: { $in: deviceTypes },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
owns__release: {
|
|
||||||
$any: {
|
|
||||||
$alias: 'r',
|
|
||||||
$expr: {
|
|
||||||
r: {
|
|
||||||
status: 'success',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
$expand: this.applicationExpandOptions,
|
|
||||||
$select: ['id', 'app_name', 'should_track_latest_release'],
|
|
||||||
$orderby: 'app_name asc',
|
|
||||||
},
|
},
|
||||||
});
|
owns__release: {
|
||||||
|
$any: {
|
||||||
|
$alias: 'r',
|
||||||
|
$expr: {
|
||||||
|
r: {
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
$expand: this.applicationExpandOptions,
|
||||||
|
$select: ['id', 'app_name', 'should_track_latest_release'],
|
||||||
|
$orderby: 'app_name asc',
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
selectApplication(deviceTypeSlug: string) {
|
async selectApplication(deviceTypeSlug: string) {
|
||||||
const visuals = getVisuals();
|
const visuals = getVisuals();
|
||||||
|
|
||||||
const applicationInfoSpinner = new visuals.Spinner(
|
const applicationInfoSpinner = new visuals.Spinner(
|
||||||
@ -389,24 +375,23 @@ Can be repeated to add multiple certificates.\
|
|||||||
);
|
);
|
||||||
applicationInfoSpinner.start();
|
applicationInfoSpinner.start();
|
||||||
|
|
||||||
return this.getApplicationsWithSuccessfulBuilds(deviceTypeSlug).then(
|
const applications = await this.getApplicationsWithSuccessfulBuilds(
|
||||||
(applications) => {
|
deviceTypeSlug,
|
||||||
applicationInfoSpinner.stop();
|
|
||||||
if (applications.length === 0) {
|
|
||||||
throw new ExpectedError(
|
|
||||||
`You have no apps with successful releases for a '${deviceTypeSlug}' device type.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return getCliForm().ask({
|
|
||||||
message: 'Select an application',
|
|
||||||
type: 'list',
|
|
||||||
choices: applications.map((app) => ({
|
|
||||||
name: app.app_name,
|
|
||||||
value: app,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
applicationInfoSpinner.stop();
|
||||||
|
if (applications.length === 0) {
|
||||||
|
throw new ExpectedError(
|
||||||
|
`You have no apps with successful releases for a '${deviceTypeSlug}' device type.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return getCliForm().ask({
|
||||||
|
message: 'Select an application',
|
||||||
|
type: 'list',
|
||||||
|
choices: applications.map((app) => ({
|
||||||
|
name: app.app_name,
|
||||||
|
value: app,
|
||||||
|
})),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
selectApplicationCommit(releases: Release[]) {
|
selectApplicationCommit(releases: Release[]) {
|
||||||
@ -462,23 +447,20 @@ preloaded device to the selected release.
|
|||||||
|
|
||||||
Would you like to disable automatic updates for this application now?\
|
Would you like to disable automatic updates for this application now?\
|
||||||
`;
|
`;
|
||||||
return getCliForm()
|
const update = await getCliForm().ask({
|
||||||
.ask({
|
message,
|
||||||
message,
|
type: 'confirm',
|
||||||
type: 'confirm',
|
});
|
||||||
})
|
if (!update) {
|
||||||
.then(function (update) {
|
return;
|
||||||
if (!update) {
|
}
|
||||||
return;
|
return await balena.pine.patch({
|
||||||
}
|
resource: 'application',
|
||||||
return balena.pine.patch({
|
id: application.id,
|
||||||
resource: 'application',
|
body: {
|
||||||
id: application.id,
|
should_track_latest_release: false,
|
||||||
body: {
|
},
|
||||||
should_track_latest_release: false,
|
});
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAppWithReleases(balenaSdk: BalenaSDK, appId: string | number) {
|
getAppWithReleases(balenaSdk: BalenaSDK, appId: string | number) {
|
||||||
|
Loading…
Reference in New Issue
Block a user