From 1b943bdf7dacff9be7c202fce08fc95ae59dab7b Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Wed, 16 Oct 2019 00:57:34 +0300 Subject: [PATCH] Support managing tags using release commit hashes The sdk version in the shrinkwrap already supports setting tags by commit hashes and as a result this already works in the cli as of v11.9.6. This PR just adds some docs and some extra handling when the commit param prefix is all numeric. Resolves: #1064 Change-type: minor Signed-off-by: Thodoris Greasidis --- doc/cli.markdown | 6 ++- lib/actions/tags.ts | 98 ++++++++++++++++++++++++-------------- lib/utils/normalization.ts | 24 ++++++++++ 3 files changed, 91 insertions(+), 37 deletions(-) diff --git a/doc/cli.markdown b/doc/cli.markdown index cd00432b..150d58e6 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -743,6 +743,7 @@ Example: $ balena tags --application MyApp $ balena tags --device 7cf02a6 $ balena tags --release 1234 + $ balena tags --release b376b0e544e9429483b656490e5b9443b4349bd6 ### Options @@ -771,8 +772,10 @@ Examples: $ balena tag set mySimpleTag --application MyApp $ balena tag set myCompositeTag myTagValue --application MyApp $ balena tag set myCompositeTag myTagValue --device 7cf02a6 + $ balena tag set myCompositeTag "my tag value with whitespaces" --device 7cf02a6 $ balena tag set myCompositeTag myTagValue --release 1234 - $ balena tag set myCompositeTag "my tag value with whitespaces" --release 1234 + $ balena tag set myCompositeTag --release 1234 + $ balena tag set myCompositeTag --release b376b0e544e9429483b656490e5b9443b4349bd6 ### Options @@ -797,6 +800,7 @@ Examples: $ balena tag rm myTagKey --application MyApp $ balena tag rm myTagKey --device 7cf02a6 $ balena tag rm myTagKey --release 1234 + $ balena tag rm myTagKey --release b376b0e544e9429483b656490e5b9443b4349bd6 ### Options diff --git a/lib/actions/tags.ts b/lib/actions/tags.ts index c8be2653..2f984a86 100644 --- a/lib/actions/tags.ts +++ b/lib/actions/tags.ts @@ -17,7 +17,10 @@ limitations under the License. import { ApplicationTag, DeviceTag, ReleaseTag } from 'balena-sdk'; import { CommandDefinition } from 'capitano'; import { stripIndent } from 'common-tags'; -import { normalizeUuidProp } from '../utils/normalization'; +import { + disambiguateReleaseParam, + normalizeUuidProp, +} from '../utils/normalization'; import * as commandOptions from './command-options'; export const list: CommandDefinition< @@ -25,7 +28,8 @@ export const list: CommandDefinition< { application?: string; device?: string; - release?: number; + release?: number | string; + release_raw?: string; } > = { signature: 'tags', @@ -41,6 +45,7 @@ export const list: CommandDefinition< $ balena tags --application MyApp $ balena tags --device 7cf02a6 $ balena tags --release 1234 + $ balena tags --release b376b0e544e9429483b656490e5b9443b4349bd6 `, options: [ commandOptions.optionalApplication, @@ -57,41 +62,48 @@ export const list: CommandDefinition< const { exitWithExpectedError } = await import('../utils/patterns'); - return Bluebird.try(() => { - const wrongParametersError = stripIndent` + return Bluebird.try( + async () => { + const wrongParametersError = stripIndent` To list resource tags, you must provide exactly one of: * An application, with --application * A device, with --device - * A release, with --release + * A release, with --release See the help page for examples: $ balena help tags `; - if ( - _.filter([options.application, options.device, options.release]) - .length !== 1 - ) { + if ( + _.filter([options.application, options.device, options.release]) + .length !== 1 + ) { + return exitWithExpectedError(wrongParametersError); + } + + if (options.application) { + return balena.models.application.tags.getAllByApplication( + options.application, + ); + } + if (options.device) { + return balena.models.device.tags.getAllByDevice(options.device); + } + if (options.release) { + const releaseParam = await disambiguateReleaseParam( + balena, + options.release, + options.release_raw, + ); + return balena.models.release.tags.getAllByRelease(releaseParam); + } + + // return never, so that TS typings are happy return exitWithExpectedError(wrongParametersError); - } - - if (options.application) { - return balena.models.application.tags.getAllByApplication( - options.application, - ); - } - if (options.device) { - return balena.models.device.tags.getAllByDevice(options.device); - } - if (options.release) { - return balena.models.release.tags.getAllByRelease(options.release); - } - - // return never, so that TS typings are happy - return exitWithExpectedError(wrongParametersError); - }) + }, + ) .tap(function(environmentVariables) { if (_.isEmpty(environmentVariables)) { exitWithExpectedError('No tags found'); @@ -117,7 +129,8 @@ export const set: CommandDefinition< { application?: string; device?: string; - release?: number; + release?: number | string; + release_raw: string; } > = { signature: 'tag set [value]', @@ -134,8 +147,10 @@ export const set: CommandDefinition< $ balena tag set mySimpleTag --application MyApp $ balena tag set myCompositeTag myTagValue --application MyApp $ balena tag set myCompositeTag myTagValue --device 7cf02a6 + $ balena tag set myCompositeTag "my tag value with whitespaces" --device 7cf02a6 $ balena tag set myCompositeTag myTagValue --release 1234 - $ balena tag set myCompositeTag "my tag value with whitespaces" --release 1234 + $ balena tag set myCompositeTag --release 1234 + $ balena tag set myCompositeTag --release b376b0e544e9429483b656490e5b9443b4349bd6 `, options: [ commandOptions.optionalApplication, @@ -151,7 +166,7 @@ export const set: CommandDefinition< const { exitWithExpectedError } = await import('../utils/patterns'); - return Bluebird.try(() => { + return Bluebird.try(async () => { if (_.isEmpty(params.tagKey)) { return exitWithExpectedError('No tag key was provided'); } @@ -165,7 +180,7 @@ export const set: CommandDefinition< * An application, with --application * A device, with --device - * A release, with --release + * A release, with --release See the help page for examples: @@ -192,8 +207,14 @@ export const set: CommandDefinition< ); } if (options.release) { - return balena.models.release.tags.set( + const releaseParam = await disambiguateReleaseParam( + balena, options.release, + options.release_raw, + ); + + return balena.models.release.tags.set( + releaseParam, params.tagKey, params.value, ); @@ -209,7 +230,8 @@ export const remove: CommandDefinition< { application?: string; device?: string; - release?: number; + release?: number | string; + release_raw?: string; } > = { signature: 'tag rm ', @@ -222,6 +244,7 @@ export const remove: CommandDefinition< $ balena tag rm myTagKey --application MyApp $ balena tag rm myTagKey --device 7cf02a6 $ balena tag rm myTagKey --release 1234 + $ balena tag rm myTagKey --release b376b0e544e9429483b656490e5b9443b4349bd6 `, options: [ commandOptions.optionalApplication, @@ -235,7 +258,7 @@ export const remove: CommandDefinition< const balena = (await import('balena-sdk')).fromSharedOptions(); const { exitWithExpectedError } = await import('../utils/patterns'); - return Bluebird.try(() => { + return Bluebird.try(async () => { if (_.isEmpty(params.tagKey)) { return exitWithExpectedError('No tag key was provided'); } @@ -249,7 +272,7 @@ export const remove: CommandDefinition< * An application, with --application * A device, with --device - * A release, with --release + * A release, with --release See the help page for examples: @@ -267,10 +290,13 @@ export const remove: CommandDefinition< return balena.models.device.tags.remove(options.device, params.tagKey); } if (options.release) { - return balena.models.release.tags.remove( + const releaseParam = await disambiguateReleaseParam( + balena, options.release, - params.tagKey, + options.release_raw, ); + + return balena.models.release.tags.remove(releaseParam, params.tagKey); } }).nodeify(done); }, diff --git a/lib/utils/normalization.ts b/lib/utils/normalization.ts index 4da0b62a..1eee06a9 100644 --- a/lib/utils/normalization.ts +++ b/lib/utils/normalization.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { BalenaSDK } from 'balena-sdk'; import _ = require('lodash'); export function normalizeUuidProp( @@ -25,3 +26,26 @@ export function normalizeUuidProp( params[propName + '_raw'] || _.toString(params[propName]); } } + +export async function disambiguateReleaseParam( + balena: BalenaSDK, + param: string | number, + paramRaw: string | undefined, +) { + // the user has passed an argument that was parsed as a string + if (!_.isNumber(param)) { + return param; + } + + // check whether the argument was indeed an ID + return balena.models.release + .get(param, { $select: 'id' }) + .catch(error => { + // we couldn't find a release by id, + // try whether it was a commit with all numeric digits + return balena.models.release + .get(paramRaw || _.toString(param), { $select: 'id' }) + .catchThrow(error); + }) + .then(({ id }) => id); +};