Merge pull request from balena-io/app-disambiguation

Improve application-identifier disambiguation
This commit is contained in:
bulldozer-balena[bot] 2020-11-06 09:34:15 +00:00 committed by GitHub
commit e7ebf1ad12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 94 additions and 38 deletions

View File

@ -19,8 +19,7 @@ import { flags } from '@oclif/command';
import Command from '../../command'; import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation'; import type { Release } from 'balena-sdk';
import { Release } from 'balena-sdk';
interface FlagsDef { interface FlagsDef {
help: void; help: void;
@ -58,15 +57,14 @@ export default class AppCmd extends Command {
public async run() { public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(AppCmd); const { args: params } = this.parse<FlagsDef, ArgsDef>(AppCmd);
const application = (await getBalenaSdk().models.application.get( const { getApplication } = await import('../../utils/sdk');
tryAsInteger(params.name),
{ const application = (await getApplication(getBalenaSdk(), params.name, {
$expand: { $expand: {
is_for__device_type: { $select: 'slug' }, is_for__device_type: { $select: 'slug' },
should_be_running__release: { $select: 'commit' }, should_be_running__release: { $select: 'commit' },
},
}, },
)) as ApplicationWithDeviceType & { })) as ApplicationWithDeviceType & {
should_be_running__release: [Release?]; should_be_running__release: [Release?];
}; };

View File

@ -69,12 +69,13 @@ export default class AppRenameCmd extends Command {
const { args: params } = this.parse<FlagsDef, ArgsDef>(AppRenameCmd); const { args: params } = this.parse<FlagsDef, ArgsDef>(AppRenameCmd);
const { ExpectedError, instanceOf } = await import('../../errors'); const { ExpectedError, instanceOf } = await import('../../errors');
const { getApplication } = await import('../../utils/sdk');
const balena = getBalenaSdk(); const balena = getBalenaSdk();
// Get app // Get app
let app; let app;
try { try {
app = await balena.models.application.get(params.name, { app = await getApplication(balena, params.name, {
$expand: { $expand: {
application_type: { application_type: {
$select: ['is_legacy'], $select: ['is_legacy'],

View File

@ -126,6 +126,8 @@ export default class ConfigGenerateCmd extends Command {
public async run() { public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(ConfigGenerateCmd); const { flags: options } = this.parse<FlagsDef, {}>(ConfigGenerateCmd);
const { getApplication } = await import('../../utils/sdk');
const balena = getBalenaSdk(); const balena = getBalenaSdk();
await this.validateOptions(options); await this.validateOptions(options);
@ -152,7 +154,7 @@ export default class ConfigGenerateCmd extends Command {
}; };
resourceDeviceType = device.is_of__device_type[0].slug; resourceDeviceType = device.is_of__device_type[0].slug;
} else { } else {
application = (await balena.models.application.get(options.application!, { application = (await getApplication(balena, options.application!, {
$expand: { $expand: {
is_for__device_type: { $select: 'slug' }, is_for__device_type: { $select: 'slug' },
}, },

View File

@ -84,18 +84,19 @@ export default class DeviceInitCmd extends Command {
const tmp = await import('tmp'); const tmp = await import('tmp');
const tmpNameAsync = promisify(tmp.tmpName); const tmpNameAsync = promisify(tmp.tmpName);
tmp.setGracefulCleanup(); tmp.setGracefulCleanup();
const balena = getBalenaSdk();
const { downloadOSImage } = await import('../../utils/cloud'); const { downloadOSImage } = await import('../../utils/cloud');
const Logger = await import('../../utils/logger'); const { getApplication } = await import('../../utils/sdk');
const logger = Logger.getLogger(); const logger = await Command.getLogger();
const balena = getBalenaSdk();
// Consolidate application options // Consolidate application options
options.application = options.application || options.app; options.application = options.application || options.app;
delete options.app; delete options.app;
// Get application and // Get application and
const application = (await balena.models.application.get( const application = (await getApplication(
balena,
options['application'] || options['application'] ||
(await (await import('../../utils/patterns')).selectApplication()), (await (await import('../../utils/patterns')).selectApplication()),
{ {

View File

@ -20,7 +20,6 @@ import type { IArg } from '@oclif/parser/lib/args';
import Command from '../../command'; import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
interface FlagsDef { interface FlagsDef {
uuid?: string; uuid?: string;
@ -46,7 +45,6 @@ export default class DeviceRegisterCmd extends Command {
{ {
name: 'application', name: 'application',
description: 'the name or id of application to register device with', description: 'the name or id of application to register device with',
parse: (app) => tryAsInteger(app),
required: true, required: true,
}, },
]; ];
@ -68,9 +66,11 @@ export default class DeviceRegisterCmd extends Command {
DeviceRegisterCmd, DeviceRegisterCmd,
); );
const { getApplication } = await import('../../utils/sdk');
const balena = getBalenaSdk(); const balena = getBalenaSdk();
const application = await balena.models.application.get(params.application); const application = await getApplication(balena, params.application);
const uuid = options.uuid ?? balena.models.device.generateUniqueKey(); const uuid = options.uuid ?? balena.models.device.generateUniqueKey();
console.info(`Registering to ${application.app_name}: ${uuid}`); console.info(`Registering to ${application.app_name}: ${uuid}`);

View File

@ -184,6 +184,8 @@ export default class OsConfigureCmd extends Command {
'../../utils/config' '../../utils/config'
); );
const helpers = await import('../../utils/helpers'); const helpers = await import('../../utils/helpers');
const { getApplication } = await import('../../utils/sdk');
let app: ApplicationWithDeviceType | undefined; let app: ApplicationWithDeviceType | undefined;
let device; let device;
let deviceTypeSlug: string; let deviceTypeSlug: string;
@ -199,7 +201,7 @@ export default class OsConfigureCmd extends Command {
}; };
deviceTypeSlug = device.is_of__device_type[0].slug; deviceTypeSlug = device.is_of__device_type[0].slug;
} else { } else {
app = (await balena.models.application.get(options.application!, { app = (await getApplication(balena, options.application!, {
$expand: { $expand: {
is_for__device_type: { $select: 'slug' }, is_for__device_type: { $select: 'slug' },
}, },

View File

@ -457,10 +457,12 @@ Would you like to disable automatic updates for this application now?\
}); });
} }
getAppWithReleases(balenaSdk: BalenaSDK, appId: string | number) { async getAppWithReleases(balenaSdk: BalenaSDK, appId: string | number) {
return balenaSdk.models.application.get(appId, { const { getApplication } = await import('../utils/sdk');
return (await getApplication(balenaSdk, appId, {
$expand: this.applicationExpandOptions, $expand: this.applicationExpandOptions,
}) as Promise<Application & { should_be_running__release: [Release?] }>; })) as Application & { should_be_running__release: [Release?] };
} }
async prepareAndPreload( async prepareAndPreload(

View File

@ -1,18 +1,19 @@
/* /**
Copyright 2016-2018 Balena * @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. * Licensed under the Apache License, Version 2.0 (the "License");
You may obtain a copy of the License at * 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 *
* 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, * Unless required by applicable law or agreed to in writing, software
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * distributed under the License is distributed on an "AS IS" BASIS,
See the License for the specific language governing permissions and * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
limitations under the License. * See the License for the specific language governing permissions and
*/ * limitations under the License.
*/
import type { BalenaSDK } from 'balena-sdk'; import type { BalenaSDK } from 'balena-sdk';
import _ = require('lodash'); import _ = require('lodash');

49
lib/utils/sdk.ts Normal file
View File

@ -0,0 +1,49 @@
/**
* @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.
*/
import type { Application, BalenaSDK, PineOptions } from 'balena-sdk';
/**
* Wraps the sdk application.get method,
* adding disambiguation in cases where the provided
* identifier could be interpreted in multiple valid ways.
*/
export async function getApplication(
sdk: BalenaSDK,
nameOrSlugOrId: string | number,
options?: PineOptions<Application>,
): Promise<Application> {
// TODO: Consider whether it would be useful to generally include interactive selection of application here,
// when nameOrSlugOrId not provided.
// e.g. nameOrSlugOrId || (await (await import('../../utils/patterns')).selectApplication()),
// See commands/device/init.ts ~ln100 for example
const { looksLikeInteger } = await import('./validation');
if (looksLikeInteger(nameOrSlugOrId as string)) {
try {
// Test for existence of app with this numerical ID
return await sdk.models.application.get(Number(nameOrSlugOrId), options);
} catch (e) {
const { instanceOf } = await import('../errors');
const { BalenaApplicationNotFound } = await import('balena-errors');
if (!instanceOf(e, BalenaApplicationNotFound)) {
throw e;
}
// App with this numerical ID not found, but there may be an app with this numerical name.
}
}
return sdk.models.application.get(nameOrSlugOrId, options);
}