From 665e0cf9d7f1f10d5067a1c8888790d4b46fa50a Mon Sep 17 00:00:00 2001 From: Scott Lowe Date: Wed, 9 Dec 2020 16:55:00 +0100 Subject: [PATCH] Add organizations support to app rename command Change-type: minor Connects-to: #2119 Signed-off-by: Scott Lowe --- doc/cli.markdown | 9 +++-- lib/commands/app/rename.ts | 83 ++++++++++++++++++-------------------- lib/utils/normalization.ts | 18 ++++----- 3 files changed, 51 insertions(+), 59 deletions(-) diff --git a/doc/cli.markdown b/doc/cli.markdown index cb7d91db..6738d520 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -165,7 +165,7 @@ Users are encouraged to regularly update the balena CLI to the latest version. - [app <nameorslug>](#app-nameorslug) - [app create <name>](#app-create-name) - [app purge <name>](#app-purge-name) - - [app rename <name> [newname]](#app-rename-name-newname) + - [app rename <nameorslug> [newname]](#app-rename-nameorslug-newname) - [app restart <name>](#app-restart-name) - [app rm <name>](#app-rm-name) @@ -386,7 +386,7 @@ application name or numeric ID ### Options -## app rename <name> [newName] +## app rename <nameOrSlug> [newName] Rename an application. @@ -397,12 +397,13 @@ Examples: $ balena app rename OldName $ balena app rename OldName NewName + $ balena app rename myorg/oldname NewName ### Arguments -#### NAME +#### NAMEORSLUG -application name or numeric ID +application name or org/name slug #### NEWNAME diff --git a/lib/commands/app/rename.ts b/lib/commands/app/rename.ts index 8174bd10..acca72ed 100644 --- a/lib/commands/app/rename.ts +++ b/lib/commands/app/rename.ts @@ -20,14 +20,15 @@ import type { IArg } from '@oclif/parser/lib/args'; import Command from '../../command'; import * as cf from '../../utils/common-flags'; import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy'; -import type { Application, ApplicationType, BalenaSDK } from 'balena-sdk'; +import { lowercaseIfSlug } from '../../utils/normalization'; +import type { ApplicationType } from 'balena-sdk'; interface FlagsDef { help: void; } interface ArgsDef { - name: string; + nameOrSlug: string; newName?: string; } @@ -39,16 +40,19 @@ export default class AppRenameCmd extends Command { Note, if the \`newName\` parameter is omitted, it will be prompted for interactively. - `; + `; + public static examples = [ '$ balena app rename OldName', '$ balena app rename OldName NewName', + '$ balena app rename myorg/oldname NewName', ]; public static args: Array> = [ { - name: 'name', - description: 'application name or numeric ID', + name: 'nameOrSlug', + description: 'application name or org/name slug', + parse: lowercaseIfSlug, required: true, }, { @@ -57,7 +61,7 @@ export default class AppRenameCmd extends Command { }, ]; - public static usage = 'app rename [newName]'; + public static usage = 'app rename [newName]'; public static flags: flags.Input = { help: cf.help, @@ -68,38 +72,37 @@ export default class AppRenameCmd extends Command { public async run() { const { args: params } = this.parse(AppRenameCmd); - const { ExpectedError, instanceOf } = await import('../../errors'); - const { getApplication } = await import('../../utils/sdk'); + const { validateApplicationName } = await import('../../utils/validation'); + const { ExpectedError } = await import('../../errors'); + const balena = getBalenaSdk(); - // Get app - let app; - try { - app = await getApplication(balena, params.name, { - $expand: { - application_type: { - $select: ['is_legacy'], - }, + // Disambiguate target application (if nameOrSlug is a number, it could either be an ID or a numerical name) + const { getApplication } = await import('../../utils/sdk'); + const application = await getApplication(balena, params.nameOrSlug, { + $expand: { + application_type: { + $select: ['is_legacy'], }, - }); - } catch (e) { - const { BalenaApplicationNotFound } = await import('balena-errors'); - if (instanceOf(e, BalenaApplicationNotFound)) { - throw new ExpectedError(`Application ${params.name} not found.`); - } else { - throw e; - } - } + }, + }); - // Check app supports renaming - const appType = (app.application_type as ApplicationType[])?.[0]; - if (appType.is_legacy) { + // Check app exists + if (!application) { throw new ExpectedError( - `Application ${params.name} is of 'legacy' type, and cannot be renamed.`, + 'Error: application ${params.nameOrSlug} not found.', ); } - const { validateApplicationName } = await import('../../utils/validation'); + // Check app supports renaming + const appType = (application.application_type as ApplicationType[])?.[0]; + if (appType.is_legacy) { + throw new ExpectedError( + `Application ${params.nameOrSlug} is of 'legacy' type, and cannot be renamed.`, + ); + } + + // Ascertain new name const newName = params.newName || (await getCliForm().ask({ @@ -109,28 +112,20 @@ export default class AppRenameCmd extends Command { })) || ''; + // Rename try { - await this.renameApplication(balena, app.id, newName); + await balena.models.application.rename(application.id, newName); } catch (e) { - // BalenaRequestError: Request error: Unique key constraint violated + // BalenaRequestError: Request error: "organization" and "app_name" must be unique. if ((e.message || '').toLowerCase().includes('unique')) { throw new ExpectedError( - `Error: application ${params.name} already exists.`, + `Error: application ${params.nameOrSlug} already exists.`, ); } throw e; } - console.log(`Application ${params.name} renamed to ${newName}`); - } - - async renameApplication(balena: BalenaSDK, id: number, newName: string) { - return balena.pine.patch({ - resource: 'application', - id, - body: { - app_name: newName, - }, - }); + // Output result + console.log(`Application ${params.nameOrSlug} renamed to ${newName}`); } } diff --git a/lib/utils/normalization.ts b/lib/utils/normalization.ts index 08d27b21..f19b00c5 100644 --- a/lib/utils/normalization.ts +++ b/lib/utils/normalization.ts @@ -16,19 +16,8 @@ */ import type { BalenaSDK } from 'balena-sdk'; -import _ = require('lodash'); import { ExpectedError } from '../errors'; -export function normalizeUuidProp( - params: { [key: string]: any }, - propName = 'uuid', -) { - if (typeof params[propName] === 'number') { - params[propName] = - params[propName + '_raw'] || _.toString(params[propName]); - } -} - /** * Takes a string which may represent one of: * - Integer release id @@ -85,3 +74,10 @@ export async function disambiguateReleaseParam( // Must be a number only uuid/hash (or nonexistent release) return (await balena.models.release.get(release, { $select: 'id' })).id; } + +/** + * Convert to lowercase if looks like slug + */ +export function lowercaseIfSlug(s: string) { + return s.includes('/') ? s.toLowerCase() : s; +}