Add organizations support to app create command

Change-type: minor
Connects-to: #2119
Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
Scott Lowe
2020-12-09 16:05:12 +01:00
parent c6430274e5
commit 611f59a0da
5 changed files with 97 additions and 74 deletions

View File

@ -20,9 +20,10 @@ import Command from '../../command';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import type * as BalenaSDK from 'balena-sdk';
import type { Application } from 'balena-sdk';
interface FlagsDef {
organization?: string;
type?: string; // application device type
help: void;
}
@ -37,16 +38,26 @@ export default class AppCreateCmd extends Command {
Create a new balena application.
You can specify the application device type with the \`--type\` option.
Otherwise, an interactive dropdown will be shown for you to select from.
You can specify the organization the application should belong to using
the \`--organization\` option. The organization's handle, not its name,
should be provided. Organization handles can be listed with the
\`balena orgs\` command.
You can see a list of supported device types with:
The application's default device type is specified with the \`--type\` option.
The \`balena devices supported\` command can be used to list the available
device types.
Interactive dropdowns will be shown for selection if no device type or
organization is specified and there are multiple options to choose from.
If there is a single option to choose from, it will be chosen automatically.
This interactive behavior can be disabled by explicitly specifying a device
type and organization.
`;
$ balena devices supported
`;
public static examples = [
'$ balena app create MyApp',
'$ balena app create MyApp --type raspberry-pi',
'$ balena app create MyApp --organization mmyorg',
'$ balena app create MyApp -o myorg --type raspberry-pi',
];
public static args = [
@ -60,6 +71,11 @@ export default class AppCreateCmd extends Command {
public static usage = 'app create <name>';
public static flags: flags.Input<FlagsDef> = {
organization: flags.string({
char: 'o',
description:
'handle of the organization the application should belong to',
}),
type: flags.string({
char: 't',
description:
@ -75,30 +91,62 @@ export default class AppCreateCmd extends Command {
AppCreateCmd,
);
const balena = getBalenaSdk();
// Create application
// Ascertain device type
const deviceType =
options.type ||
(await (await import('../../utils/patterns')).selectDeviceType());
let application: BalenaSDK.Application;
// Ascertain organization
const organization =
options.organization?.toLowerCase() || (await this.getOrganization());
// Create application
let application: Application;
try {
application = await balena.models.application.create({
application = await getBalenaSdk().models.application.create({
name: params.name,
deviceType,
organization: (await balena.auth.whoami())!,
organization,
});
} catch (err) {
// BalenaRequestError: Request error: Unique key constraint violated
if ((err.message || '').toLowerCase().includes('unique')) {
// BalenaRequestError: Request error: "organization" and "app_name" must be unique.
throw new ExpectedError(
`Error: application "${params.name}" already exists`,
`Error: application "${params.name}" already exists in organization "${organization}".`,
);
} else if ((err.message || '').toLowerCase().includes('unauthorized')) {
// BalenaRequestError: Request error: Unauthorized
throw new ExpectedError(
`Error: You are not authorized to create applications in organization "${organization}".`,
);
}
throw err;
}
console.info(
`Application created: ${application.slug} (${deviceType}, id ${application.id})`,
// Output
const { isV13 } = await import('../../utils/version');
console.log(
isV13()
? `Application created: slug "${application.slug}", device type "${deviceType}"`
: `Application created: ${application.slug} (${deviceType}, id ${application.id})`,
);
}
async getOrganization() {
const { getOwnOrganizations } = await import('../../utils/sdk');
const organizations = await getOwnOrganizations(getBalenaSdk());
if (organizations.length === 0) {
// User is not a member of any organizations (should not happen).
throw new Error('This account is not a member of any organizations');
} else if (organizations.length === 1) {
// User is a member of only one organization - use this.
return organizations[0].handle;
} else {
// User is a member of multiple organizations -
const { selectOrganization } = await import('../../utils/patterns');
return selectOrganization(organizations);
}
}
}