Merge pull request #2081 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 * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
import { Release } from 'balena-sdk';
import type { Release } from 'balena-sdk';
interface FlagsDef {
help: void;
@ -58,15 +57,14 @@ export default class AppCmd extends Command {
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(AppCmd);
const application = (await getBalenaSdk().models.application.get(
tryAsInteger(params.name),
{
const { getApplication } = await import('../../utils/sdk');
const application = (await getApplication(getBalenaSdk(), params.name, {
$expand: {
is_for__device_type: { $select: 'slug' },
should_be_running__release: { $select: 'commit' },
},
},
)) as ApplicationWithDeviceType & {
})) as ApplicationWithDeviceType & {
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 { ExpectedError, instanceOf } = await import('../../errors');
const { getApplication } = await import('../../utils/sdk');
const balena = getBalenaSdk();
// Get app
let app;
try {
app = await balena.models.application.get(params.name, {
app = await getApplication(balena, params.name, {
$expand: {
application_type: {
$select: ['is_legacy'],

View File

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

View File

@ -84,18 +84,19 @@ export default class DeviceInitCmd extends Command {
const tmp = await import('tmp');
const tmpNameAsync = promisify(tmp.tmpName);
tmp.setGracefulCleanup();
const balena = getBalenaSdk();
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
options.application = options.application || options.app;
delete options.app;
// Get application and
const application = (await balena.models.application.get(
const application = (await getApplication(
balena,
options['application'] ||
(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 * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
interface FlagsDef {
uuid?: string;
@ -46,7 +45,6 @@ export default class DeviceRegisterCmd extends Command {
{
name: 'application',
description: 'the name or id of application to register device with',
parse: (app) => tryAsInteger(app),
required: true,
},
];
@ -68,9 +66,11 @@ export default class DeviceRegisterCmd extends Command {
DeviceRegisterCmd,
);
const { getApplication } = await import('../../utils/sdk');
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();
console.info(`Registering to ${application.app_name}: ${uuid}`);

View File

@ -184,6 +184,8 @@ export default class OsConfigureCmd extends Command {
'../../utils/config'
);
const helpers = await import('../../utils/helpers');
const { getApplication } = await import('../../utils/sdk');
let app: ApplicationWithDeviceType | undefined;
let device;
let deviceTypeSlug: string;
@ -199,7 +201,7 @@ export default class OsConfigureCmd extends Command {
};
deviceTypeSlug = device.is_of__device_type[0].slug;
} else {
app = (await balena.models.application.get(options.application!, {
app = (await getApplication(balena, options.application!, {
$expand: {
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) {
return balenaSdk.models.application.get(appId, {
async getAppWithReleases(balenaSdk: BalenaSDK, appId: string | number) {
const { getApplication } = await import('../utils/sdk');
return (await getApplication(balenaSdk, appId, {
$expand: this.applicationExpandOptions,
}) as Promise<Application & { should_be_running__release: [Release?] }>;
})) as Application & { should_be_running__release: [Release?] };
}
async prepareAndPreload(

View File

@ -1,17 +1,18 @@
/*
Copyright 2016-2018 Balena
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.
/**
* @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 type { BalenaSDK } from 'balena-sdk';

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);
}