mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-02-21 09:51:58 +00:00
Convert tags
, tag set
, tag rm
to oclif.
Change-type: patch Resolves: #1805 Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
parent
bc4aa6006e
commit
979284b071
@ -66,7 +66,11 @@ const capitanoDoc = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Tags',
|
title: 'Tags',
|
||||||
files: ['build/actions/tags.js'],
|
files: [
|
||||||
|
'build/actions-oclif/tags.js',
|
||||||
|
'build/actions-oclif/tag/rm.js',
|
||||||
|
'build/actions-oclif/tag/set.js',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Help and Version',
|
title: 'Help and Version',
|
||||||
|
100
doc/cli.markdown
100
doc/cli.markdown
@ -194,8 +194,8 @@ Users are encouraged to regularly update the balena CLI to the latest version.
|
|||||||
- Tags
|
- Tags
|
||||||
|
|
||||||
- [tags](#tags)
|
- [tags](#tags)
|
||||||
- [tag set <tagKey> [value]](#tag-set-tagkey-value)
|
- [tag rm <tagkey>](#tag-rm-tagkey)
|
||||||
- [tag rm <tagKey>](#tag-rm-tagkey)
|
- [tag set <tagkey> [value]](#tag-set-tagkey-value)
|
||||||
|
|
||||||
- Help and Version
|
- Help and Version
|
||||||
|
|
||||||
@ -991,12 +991,10 @@ select a service variable (may be used together with the --device option)
|
|||||||
|
|
||||||
## tags
|
## tags
|
||||||
|
|
||||||
Use this command to list all tags for
|
List all tags and their values for a particular application,
|
||||||
a particular application, device or release.
|
device or release.
|
||||||
|
|
||||||
This command lists all application/device/release tags.
|
Examples:
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
$ balena tags --application MyApp
|
$ balena tags --application MyApp
|
||||||
$ balena tags --device 7cf02a6
|
$ balena tags --device 7cf02a6
|
||||||
@ -1005,24 +1003,63 @@ Example:
|
|||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
#### --application, -a, --app <application>
|
#### -a, --application APPLICATION
|
||||||
|
|
||||||
application name
|
application name
|
||||||
|
|
||||||
#### --device, -d <device>
|
#### -d, --device DEVICE
|
||||||
|
|
||||||
device uuid
|
device UUID
|
||||||
|
|
||||||
#### --release, -r <release>
|
#### -r, --release RELEASE
|
||||||
|
|
||||||
release id
|
release id
|
||||||
|
|
||||||
|
#### --app APP
|
||||||
|
|
||||||
|
same as '--application'
|
||||||
|
|
||||||
|
## tag rm <tagKey>
|
||||||
|
|
||||||
|
Remove a tag from an application, device or release.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
#### TAGKEY
|
||||||
|
|
||||||
|
the key string of the tag
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
#### -a, --application APPLICATION
|
||||||
|
|
||||||
|
application name
|
||||||
|
|
||||||
|
#### -d, --device DEVICE
|
||||||
|
|
||||||
|
device UUID
|
||||||
|
|
||||||
|
#### -r, --release RELEASE
|
||||||
|
|
||||||
|
release id
|
||||||
|
|
||||||
|
#### --app APP
|
||||||
|
|
||||||
|
same as '--application'
|
||||||
|
|
||||||
## tag set <tagKey> [value]
|
## tag set <tagKey> [value]
|
||||||
|
|
||||||
Use this command to set a tag to an application, device or release.
|
Set a tag on an application, device or release.
|
||||||
|
|
||||||
You can optionally provide a value to be associated with the created
|
You can optionally provide a value to be associated with the created
|
||||||
tag, as an extra argument after the tag key. When the value isn't
|
tag, as an extra argument after the tag key. If a value isn't
|
||||||
provided, a tag with an empty value is created.
|
provided, a tag with an empty value is created.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@ -1035,45 +1072,34 @@ Examples:
|
|||||||
$ balena tag set myCompositeTag --release 1234
|
$ balena tag set myCompositeTag --release 1234
|
||||||
$ balena tag set myCompositeTag --release b376b0e544e9429483b656490e5b9443b4349bd6
|
$ balena tag set myCompositeTag --release b376b0e544e9429483b656490e5b9443b4349bd6
|
||||||
|
|
||||||
### Options
|
### Arguments
|
||||||
|
|
||||||
#### --application, -a, --app <application>
|
#### TAGKEY
|
||||||
|
|
||||||
application name
|
the key string of the tag
|
||||||
|
|
||||||
#### --device, -d <device>
|
#### VALUE
|
||||||
|
|
||||||
device uuid
|
the optional value associated with the tag
|
||||||
|
|
||||||
#### --release, -r <release>
|
|
||||||
|
|
||||||
release id
|
|
||||||
|
|
||||||
## tag rm <tagKey>
|
|
||||||
|
|
||||||
Use this command to remove a tag from an application, device or release.
|
|
||||||
|
|
||||||
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
|
### Options
|
||||||
|
|
||||||
#### --application, -a, --app <application>
|
#### -a, --application APPLICATION
|
||||||
|
|
||||||
application name
|
application name
|
||||||
|
|
||||||
#### --device, -d <device>
|
#### -d, --device DEVICE
|
||||||
|
|
||||||
device uuid
|
device UUID
|
||||||
|
|
||||||
#### --release, -r <release>
|
#### -r, --release RELEASE
|
||||||
|
|
||||||
release id
|
release id
|
||||||
|
|
||||||
|
#### --app APP
|
||||||
|
|
||||||
|
same as '--application'
|
||||||
|
|
||||||
# Help and Version
|
# Help and Version
|
||||||
|
|
||||||
## help [command...]
|
## help [command...]
|
||||||
|
134
lib/actions-oclif/tag/rm.ts
Normal file
134
lib/actions-oclif/tag/rm.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
* @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 { flags } from '@oclif/command';
|
||||||
|
import { stripIndent } from 'common-tags';
|
||||||
|
import Command from '../../command';
|
||||||
|
import { ExpectedError } from '../../errors';
|
||||||
|
import * as cf from '../../utils/common-flags';
|
||||||
|
import { getBalenaSdk } from '../../utils/lazy';
|
||||||
|
import { disambiguateReleaseParam } from '../../utils/normalization';
|
||||||
|
import { tryAsInteger } from '../../utils/validation';
|
||||||
|
|
||||||
|
interface FlagsDef {
|
||||||
|
application?: string;
|
||||||
|
device?: string;
|
||||||
|
release?: string;
|
||||||
|
help: void;
|
||||||
|
app?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArgsDef {
|
||||||
|
tagKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TagRmCmd extends Command {
|
||||||
|
public static description = stripIndent`
|
||||||
|
Remove a tag from an application, device or release.
|
||||||
|
|
||||||
|
Remove a tag from an application, device or release.
|
||||||
|
`;
|
||||||
|
|
||||||
|
public static 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',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static args = [
|
||||||
|
{
|
||||||
|
name: 'tagKey',
|
||||||
|
description: 'the key string of the tag',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
public static usage = 'tag rm <tagKey>';
|
||||||
|
|
||||||
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
application: {
|
||||||
|
...cf.application,
|
||||||
|
exclusive: ['app', 'device', 'release'],
|
||||||
|
},
|
||||||
|
device: {
|
||||||
|
...cf.device,
|
||||||
|
exclusive: ['app', 'application', 'release'],
|
||||||
|
},
|
||||||
|
release: {
|
||||||
|
...cf.release,
|
||||||
|
exclusive: ['app', 'application', 'device'],
|
||||||
|
},
|
||||||
|
help: cf.help,
|
||||||
|
app: flags.string({
|
||||||
|
description: "same as '--application'",
|
||||||
|
exclusive: ['application', 'device', 'release'],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static authenticated = true;
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||||
|
TagRmCmd,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Prefer options.application over options.app
|
||||||
|
options.application = options.application || options.app;
|
||||||
|
delete options.app;
|
||||||
|
|
||||||
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
|
// Check user has specified one of application/device/release
|
||||||
|
if (!options.application && !options.device && !options.release) {
|
||||||
|
throw new ExpectedError(TagRmCmd.missingResourceMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.application) {
|
||||||
|
return balena.models.application.tags.remove(
|
||||||
|
tryAsInteger(options.application),
|
||||||
|
params.tagKey,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (options.device) {
|
||||||
|
return balena.models.device.tags.remove(
|
||||||
|
tryAsInteger(options.device),
|
||||||
|
params.tagKey,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (options.release) {
|
||||||
|
const releaseParam = await disambiguateReleaseParam(
|
||||||
|
balena,
|
||||||
|
options.release,
|
||||||
|
);
|
||||||
|
|
||||||
|
return balena.models.release.tags.remove(releaseParam, params.tagKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static missingResourceMessage = stripIndent`
|
||||||
|
To remove a resource tag, you must provide exactly one of:
|
||||||
|
|
||||||
|
* An application, with --application <appname>
|
||||||
|
* A device, with --device <uuid>
|
||||||
|
* A release, with --release <id or commit>
|
||||||
|
|
||||||
|
See the help page for examples:
|
||||||
|
|
||||||
|
$ balena help tag rm
|
||||||
|
`;
|
||||||
|
}
|
157
lib/actions-oclif/tag/set.ts
Normal file
157
lib/actions-oclif/tag/set.ts
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
/**
|
||||||
|
* @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 { flags } from '@oclif/command';
|
||||||
|
import { stripIndent } from 'common-tags';
|
||||||
|
import Command from '../../command';
|
||||||
|
import { ExpectedError } from '../../errors';
|
||||||
|
import * as cf from '../../utils/common-flags';
|
||||||
|
import { getBalenaSdk } from '../../utils/lazy';
|
||||||
|
import { disambiguateReleaseParam } from '../../utils/normalization';
|
||||||
|
import { tryAsInteger } from '../../utils/validation';
|
||||||
|
|
||||||
|
interface FlagsDef {
|
||||||
|
application?: string;
|
||||||
|
device?: string;
|
||||||
|
release?: string;
|
||||||
|
help: void;
|
||||||
|
app?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArgsDef {
|
||||||
|
tagKey: string;
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TagSetCmd extends Command {
|
||||||
|
public static description = stripIndent`
|
||||||
|
Set a tag on an application, device or release.
|
||||||
|
|
||||||
|
Set a tag on an application, device or release.
|
||||||
|
|
||||||
|
You can optionally provide a value to be associated with the created
|
||||||
|
tag, as an extra argument after the tag key. If a value isn't
|
||||||
|
provided, a tag with an empty value is created.
|
||||||
|
`;
|
||||||
|
|
||||||
|
public static 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 --release 1234',
|
||||||
|
'$ balena tag set myCompositeTag --release b376b0e544e9429483b656490e5b9443b4349bd6',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static args = [
|
||||||
|
{
|
||||||
|
name: 'tagKey',
|
||||||
|
description: 'the key string of the tag',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'value',
|
||||||
|
description: 'the optional value associated with the tag',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
public static usage = 'tag set <tagKey> [value]';
|
||||||
|
|
||||||
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
application: {
|
||||||
|
...cf.application,
|
||||||
|
exclusive: ['app', 'device', 'release'],
|
||||||
|
},
|
||||||
|
device: {
|
||||||
|
...cf.device,
|
||||||
|
exclusive: ['app', 'application', 'release'],
|
||||||
|
},
|
||||||
|
release: {
|
||||||
|
...cf.release,
|
||||||
|
exclusive: ['app', 'application', 'device'],
|
||||||
|
},
|
||||||
|
help: cf.help,
|
||||||
|
app: flags.string({
|
||||||
|
description: "same as '--application'",
|
||||||
|
exclusive: ['application', 'device', 'release'],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static authenticated = true;
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||||
|
TagSetCmd,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Prefer options.application over options.app
|
||||||
|
options.application = options.application || options.app;
|
||||||
|
delete options.app;
|
||||||
|
|
||||||
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
|
// Check user has specified one of application/device/release
|
||||||
|
if (!options.application && !options.device && !options.release) {
|
||||||
|
throw new ExpectedError(TagSetCmd.missingResourceMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.value == null) {
|
||||||
|
params.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.application) {
|
||||||
|
return balena.models.application.tags.set(
|
||||||
|
tryAsInteger(options.application),
|
||||||
|
params.tagKey,
|
||||||
|
params.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (options.device) {
|
||||||
|
return balena.models.device.tags.set(
|
||||||
|
tryAsInteger(options.device),
|
||||||
|
params.tagKey,
|
||||||
|
params.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (options.release) {
|
||||||
|
const releaseParam = await disambiguateReleaseParam(
|
||||||
|
balena,
|
||||||
|
options.release,
|
||||||
|
);
|
||||||
|
|
||||||
|
return balena.models.release.tags.set(
|
||||||
|
releaseParam,
|
||||||
|
params.tagKey,
|
||||||
|
params.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static missingResourceMessage = stripIndent`
|
||||||
|
To set a resource tag, you must provide exactly one of:
|
||||||
|
|
||||||
|
* An application, with --application <appname>
|
||||||
|
* A device, with --device <uuid>
|
||||||
|
* A release, with --release <id or commit>
|
||||||
|
|
||||||
|
See the help page for examples:
|
||||||
|
|
||||||
|
$ balena help tag set
|
||||||
|
`;
|
||||||
|
}
|
129
lib/actions-oclif/tags.ts
Normal file
129
lib/actions-oclif/tags.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/**
|
||||||
|
* @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 { flags } from '@oclif/command';
|
||||||
|
import { stripIndent } from 'common-tags';
|
||||||
|
import Command from '../command';
|
||||||
|
import { ExpectedError } from '../errors';
|
||||||
|
import * as cf from '../utils/common-flags';
|
||||||
|
import { getBalenaSdk, getVisuals } from '../utils/lazy';
|
||||||
|
import { disambiguateReleaseParam } from '../utils/normalization';
|
||||||
|
import { tryAsInteger } from '../utils/validation';
|
||||||
|
|
||||||
|
interface FlagsDef {
|
||||||
|
application?: string;
|
||||||
|
device?: string;
|
||||||
|
release?: string;
|
||||||
|
help: void;
|
||||||
|
app?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TagsCmd extends Command {
|
||||||
|
public static description = stripIndent`
|
||||||
|
List all tags for an application, device or release.
|
||||||
|
|
||||||
|
List all tags and their values for a particular application,
|
||||||
|
device or release.
|
||||||
|
`;
|
||||||
|
|
||||||
|
public static examples = [
|
||||||
|
'$ balena tags --application MyApp',
|
||||||
|
'$ balena tags --device 7cf02a6',
|
||||||
|
'$ balena tags --release 1234',
|
||||||
|
'$ balena tags --release b376b0e544e9429483b656490e5b9443b4349bd6',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static usage = 'tags';
|
||||||
|
|
||||||
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
application: {
|
||||||
|
...cf.application,
|
||||||
|
exclusive: ['app', 'device', 'release'],
|
||||||
|
},
|
||||||
|
device: {
|
||||||
|
...cf.device,
|
||||||
|
exclusive: ['app', 'application', 'release'],
|
||||||
|
},
|
||||||
|
release: {
|
||||||
|
...cf.release,
|
||||||
|
exclusive: ['app', 'application', 'device'],
|
||||||
|
},
|
||||||
|
help: cf.help,
|
||||||
|
app: flags.string({
|
||||||
|
description: "same as '--application'",
|
||||||
|
exclusive: ['application', 'device', 'release'],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static authenticated = true;
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
const { flags: options } = this.parse<FlagsDef, {}>(TagsCmd);
|
||||||
|
|
||||||
|
// Prefer options.application over options.app
|
||||||
|
options.application = options.application || options.app;
|
||||||
|
delete options.app;
|
||||||
|
|
||||||
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
|
// Check user has specified one of application/device/release
|
||||||
|
if (!options.application && !options.device && !options.release) {
|
||||||
|
throw new ExpectedError(this.missingResourceMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tags;
|
||||||
|
|
||||||
|
if (options.application) {
|
||||||
|
tags = await balena.models.application.tags.getAllByApplication(
|
||||||
|
tryAsInteger(options.application),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (options.device) {
|
||||||
|
tags = await balena.models.device.tags.getAllByDevice(
|
||||||
|
tryAsInteger(options.device),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (options.release) {
|
||||||
|
const releaseParam = await disambiguateReleaseParam(
|
||||||
|
balena,
|
||||||
|
options.release,
|
||||||
|
);
|
||||||
|
|
||||||
|
tags = await balena.models.release.tags.getAllByRelease(releaseParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tags || tags.length === 0) {
|
||||||
|
throw new ExpectedError('No tags found');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
getVisuals().table.horizontal(tags, ['id', 'tag_key', 'value']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected missingResourceMessage = stripIndent`
|
||||||
|
To list tags for a resource, you must provide exactly one of:
|
||||||
|
|
||||||
|
* An application, with --application <appname>
|
||||||
|
* A device, with --device <uuid>
|
||||||
|
* A release, with --release <id or commit>
|
||||||
|
|
||||||
|
See the help page for examples:
|
||||||
|
|
||||||
|
$ balena help tags
|
||||||
|
`;
|
||||||
|
}
|
@ -23,24 +23,10 @@ import * as logs from './logs';
|
|||||||
import * as os from './os';
|
import * as os from './os';
|
||||||
import * as push from './push';
|
import * as push from './push';
|
||||||
import * as ssh from './ssh';
|
import * as ssh from './ssh';
|
||||||
import * as tags from './tags';
|
|
||||||
import * as tunnel from './tunnel';
|
import * as tunnel from './tunnel';
|
||||||
import * as util from './util';
|
import * as util from './util';
|
||||||
|
|
||||||
export {
|
export { auth, device, logs, local, help, os, config, ssh, util, push, tunnel };
|
||||||
auth,
|
|
||||||
device,
|
|
||||||
tags,
|
|
||||||
logs,
|
|
||||||
local,
|
|
||||||
help,
|
|
||||||
os,
|
|
||||||
config,
|
|
||||||
ssh,
|
|
||||||
util,
|
|
||||||
push,
|
|
||||||
tunnel,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { build } from './build';
|
export { build } from './build';
|
||||||
|
|
||||||
|
@ -1,295 +0,0 @@
|
|||||||
/*
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { ApplicationTag, DeviceTag, ReleaseTag } from 'balena-sdk';
|
|
||||||
import { CommandDefinition } from 'capitano';
|
|
||||||
import { stripIndent } from 'common-tags';
|
|
||||||
import { getBalenaSdk, getVisuals } from '../utils/lazy';
|
|
||||||
import {
|
|
||||||
disambiguateReleaseParam,
|
|
||||||
normalizeUuidProp,
|
|
||||||
} from '../utils/normalization';
|
|
||||||
import * as commandOptions from './command-options';
|
|
||||||
|
|
||||||
export const list: CommandDefinition<
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
application?: string;
|
|
||||||
device?: string;
|
|
||||||
release?: number | string;
|
|
||||||
release_raw?: string;
|
|
||||||
}
|
|
||||||
> = {
|
|
||||||
signature: 'tags',
|
|
||||||
description: 'list all resource tags',
|
|
||||||
help: stripIndent`
|
|
||||||
Use this command to list all tags for
|
|
||||||
a particular application, device or release.
|
|
||||||
|
|
||||||
This command lists all application/device/release tags.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
$ balena tags --application MyApp
|
|
||||||
$ balena tags --device 7cf02a6
|
|
||||||
$ balena tags --release 1234
|
|
||||||
$ balena tags --release b376b0e544e9429483b656490e5b9443b4349bd6
|
|
||||||
`,
|
|
||||||
options: [
|
|
||||||
commandOptions.optionalApplication,
|
|
||||||
commandOptions.optionalDevice,
|
|
||||||
commandOptions.optionalRelease,
|
|
||||||
],
|
|
||||||
permission: 'user',
|
|
||||||
async action(_params, options) {
|
|
||||||
normalizeUuidProp(options, 'device');
|
|
||||||
const Bluebird = await import('bluebird');
|
|
||||||
const _ = await import('lodash');
|
|
||||||
const balena = getBalenaSdk();
|
|
||||||
|
|
||||||
const { exitWithExpectedError } = await import('../errors');
|
|
||||||
|
|
||||||
return Bluebird.try<ApplicationTag[] | DeviceTag[] | ReleaseTag[]>(
|
|
||||||
async () => {
|
|
||||||
const wrongParametersError = stripIndent`
|
|
||||||
To list resource tags, you must provide exactly one of:
|
|
||||||
|
|
||||||
* An application, with --application <appname>
|
|
||||||
* A device, with --device <uuid>
|
|
||||||
* A release, with --release <id or commit>
|
|
||||||
|
|
||||||
See the help page for examples:
|
|
||||||
|
|
||||||
$ balena help tags
|
|
||||||
`;
|
|
||||||
|
|
||||||
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);
|
|
||||||
},
|
|
||||||
).tap(function(environmentVariables) {
|
|
||||||
if (_.isEmpty(environmentVariables)) {
|
|
||||||
exitWithExpectedError('No tags found');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
getVisuals().table.horizontal(environmentVariables, [
|
|
||||||
'id',
|
|
||||||
'tag_key',
|
|
||||||
'value',
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const set: CommandDefinition<
|
|
||||||
{
|
|
||||||
tagKey: string;
|
|
||||||
value?: string;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
application?: string;
|
|
||||||
device?: string;
|
|
||||||
release?: number | string;
|
|
||||||
release_raw: string;
|
|
||||||
}
|
|
||||||
> = {
|
|
||||||
signature: 'tag set <tagKey> [value]',
|
|
||||||
description: 'set a resource tag',
|
|
||||||
help: stripIndent`
|
|
||||||
Use this command to set a tag to an application, device or release.
|
|
||||||
|
|
||||||
You can optionally provide a value to be associated with the created
|
|
||||||
tag, as an extra argument after the tag key. When the value isn't
|
|
||||||
provided, a tag with an empty value is created.
|
|
||||||
|
|
||||||
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 --release 1234
|
|
||||||
$ balena tag set myCompositeTag --release b376b0e544e9429483b656490e5b9443b4349bd6
|
|
||||||
`,
|
|
||||||
options: [
|
|
||||||
commandOptions.optionalApplication,
|
|
||||||
commandOptions.optionalDevice,
|
|
||||||
commandOptions.optionalRelease,
|
|
||||||
],
|
|
||||||
permission: 'user',
|
|
||||||
async action(params, options) {
|
|
||||||
normalizeUuidProp(options, 'device');
|
|
||||||
const _ = await import('lodash');
|
|
||||||
const balena = getBalenaSdk();
|
|
||||||
|
|
||||||
const { exitWithExpectedError } = await import('../errors');
|
|
||||||
|
|
||||||
if (_.isEmpty(params.tagKey)) {
|
|
||||||
return exitWithExpectedError('No tag key was provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
_.filter([options.application, options.device, options.release])
|
|
||||||
.length !== 1
|
|
||||||
) {
|
|
||||||
return exitWithExpectedError(stripIndent`
|
|
||||||
To set a resource tag, you must provide exactly one of:
|
|
||||||
|
|
||||||
* An application, with --application <appname>
|
|
||||||
* A device, with --device <uuid>
|
|
||||||
* A release, with --release <id or commit>
|
|
||||||
|
|
||||||
See the help page for examples:
|
|
||||||
|
|
||||||
$ balena help tag set
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.value == null) {
|
|
||||||
params.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.application) {
|
|
||||||
return balena.models.application.tags.set(
|
|
||||||
options.application,
|
|
||||||
params.tagKey,
|
|
||||||
params.value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (options.device) {
|
|
||||||
return balena.models.device.tags.set(
|
|
||||||
options.device,
|
|
||||||
params.tagKey,
|
|
||||||
params.value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (options.release) {
|
|
||||||
const releaseParam = await disambiguateReleaseParam(
|
|
||||||
balena,
|
|
||||||
options.release,
|
|
||||||
options.release_raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
return balena.models.release.tags.set(
|
|
||||||
releaseParam,
|
|
||||||
params.tagKey,
|
|
||||||
params.value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const remove: CommandDefinition<
|
|
||||||
{
|
|
||||||
tagKey: string;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
application?: string;
|
|
||||||
device?: string;
|
|
||||||
release?: number | string;
|
|
||||||
release_raw?: string;
|
|
||||||
}
|
|
||||||
> = {
|
|
||||||
signature: 'tag rm <tagKey>',
|
|
||||||
description: 'remove a resource tag',
|
|
||||||
help: stripIndent`
|
|
||||||
Use this command to remove a tag from an application, device or release.
|
|
||||||
|
|
||||||
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: [
|
|
||||||
commandOptions.optionalApplication,
|
|
||||||
commandOptions.optionalDevice,
|
|
||||||
commandOptions.optionalRelease,
|
|
||||||
],
|
|
||||||
permission: 'user',
|
|
||||||
async action(params, options) {
|
|
||||||
const _ = await import('lodash');
|
|
||||||
const balena = getBalenaSdk();
|
|
||||||
const { exitWithExpectedError } = await import('../errors');
|
|
||||||
|
|
||||||
if (_.isEmpty(params.tagKey)) {
|
|
||||||
return exitWithExpectedError('No tag key was provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
_.filter([options.application, options.device, options.release])
|
|
||||||
.length !== 1
|
|
||||||
) {
|
|
||||||
return exitWithExpectedError(stripIndent`
|
|
||||||
To remove a resource tag, you must provide exactly one of:
|
|
||||||
|
|
||||||
* An application, with --application <appname>
|
|
||||||
* A device, with --device <uuid>
|
|
||||||
* A release, with --release <id or commit>
|
|
||||||
|
|
||||||
See the help page for examples:
|
|
||||||
|
|
||||||
$ balena help tag rm
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.application) {
|
|
||||||
return balena.models.application.tags.remove(
|
|
||||||
options.application,
|
|
||||||
params.tagKey,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (options.device) {
|
|
||||||
return balena.models.device.tags.remove(options.device, params.tagKey);
|
|
||||||
}
|
|
||||||
if (options.release) {
|
|
||||||
const releaseParam = await disambiguateReleaseParam(
|
|
||||||
balena,
|
|
||||||
options.release,
|
|
||||||
options.release_raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
return balena.models.release.tags.remove(releaseParam, params.tagKey);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
@ -71,11 +71,6 @@ capitano.command(actions.device.move);
|
|||||||
capitano.command(actions.device.osUpdate);
|
capitano.command(actions.device.osUpdate);
|
||||||
capitano.command(actions.device.info);
|
capitano.command(actions.device.info);
|
||||||
|
|
||||||
// ---------- Tags Module ----------
|
|
||||||
capitano.command(actions.tags.list);
|
|
||||||
capitano.command(actions.tags.set);
|
|
||||||
capitano.command(actions.tags.remove);
|
|
||||||
|
|
||||||
// ---------- OS Module ----------
|
// ---------- OS Module ----------
|
||||||
capitano.command(actions.os.versions);
|
capitano.command(actions.os.versions);
|
||||||
capitano.command(actions.os.download);
|
capitano.command(actions.os.download);
|
||||||
|
@ -159,6 +159,9 @@ export const convertedCommands = [
|
|||||||
'note',
|
'note',
|
||||||
'os:configure',
|
'os:configure',
|
||||||
'settings',
|
'settings',
|
||||||
|
'tags',
|
||||||
|
'tag:rm',
|
||||||
|
'tag:set',
|
||||||
'version',
|
'version',
|
||||||
'scan',
|
'scan',
|
||||||
];
|
];
|
||||||
|
@ -37,6 +37,11 @@ export const quiet: IBooleanFlag<boolean> = flags.boolean({
|
|||||||
default: false,
|
default: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const release = flags.string({
|
||||||
|
char: 'r',
|
||||||
|
description: 'release id',
|
||||||
|
});
|
||||||
|
|
||||||
export const service = flags.string({
|
export const service = flags.string({
|
||||||
char: 's',
|
char: 's',
|
||||||
description: 'service name',
|
description: 'service name',
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import { BalenaSDK } from 'balena-sdk';
|
import { BalenaSDK } from 'balena-sdk';
|
||||||
import _ = require('lodash');
|
import _ = require('lodash');
|
||||||
|
import { ExpectedError } from '../errors';
|
||||||
|
|
||||||
export function normalizeUuidProp(
|
export function normalizeUuidProp(
|
||||||
params: { [key: string]: any },
|
params: { [key: string]: any },
|
||||||
@ -27,25 +28,59 @@ export function normalizeUuidProp(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a string which may represent one of:
|
||||||
|
* - Integer release id
|
||||||
|
* - String uuid, 7, 32, or 62 char
|
||||||
|
* - String commit hash, 40 char, with short hashes being 7+ chars (more as needed to avoid collisions)
|
||||||
|
* and returns the correctly typed value (integer|string).
|
||||||
|
* @param balena balena sdk
|
||||||
|
* @param release string representation of release reference (id/hash)
|
||||||
|
*/
|
||||||
export async function disambiguateReleaseParam(
|
export async function disambiguateReleaseParam(
|
||||||
balena: BalenaSDK,
|
balena: BalenaSDK,
|
||||||
param: string | number,
|
release: string,
|
||||||
paramRaw: string | undefined,
|
|
||||||
) {
|
) {
|
||||||
// the user has passed an argument that was parsed as a string
|
// Reject empty values or invalid characters
|
||||||
if (typeof param !== 'number') {
|
const mixedCaseHex = /^[a-fA-F0-9]+$/;
|
||||||
return param;
|
if (!release || !mixedCaseHex.test(release)) {
|
||||||
|
throw new ExpectedError('Invalid release parameter');
|
||||||
}
|
}
|
||||||
|
|
||||||
// check whether the argument was indeed an ID
|
// Accepting short hashes of 7,8,9 chars.
|
||||||
return balena.models.release
|
const possibleUuidHashLength = [7, 8, 9, 32, 40, 62].includes(release.length);
|
||||||
.get(param, { $select: 'id' })
|
const hasLeadingZero = release[0] === '0';
|
||||||
.catch(error => {
|
const isOnlyNumerical = /^[0-9]+$/.test(release);
|
||||||
// we couldn't find a release by id,
|
|
||||||
// try whether it was a commit with all numeric digits
|
// Reject non-numerical values with invalid uuid/hash lengths
|
||||||
return balena.models.release
|
if (!isOnlyNumerical && !possibleUuidHashLength) {
|
||||||
.get(paramRaw || _.toString(param), { $select: 'id' })
|
throw new ExpectedError('Invalid release parameter');
|
||||||
.catchThrow(error);
|
}
|
||||||
})
|
|
||||||
.then(({ id }) => id);
|
// Reject leading-zero values with invalid uuid/hash lengths
|
||||||
|
if (hasLeadingZero && !possibleUuidHashLength) {
|
||||||
|
throw new ExpectedError('Invalid release parameter');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If alphanumeric, or has leading zero must now be uuid/hash.
|
||||||
|
if (!isOnlyNumerical || hasLeadingZero) {
|
||||||
|
return release;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now very likely an integer id (but still could be number only uuid/hash)
|
||||||
|
// Check integer id with API
|
||||||
|
try {
|
||||||
|
return (
|
||||||
|
await balena.models.release.get(parseInt(release, 10), {
|
||||||
|
$select: 'id',
|
||||||
|
})
|
||||||
|
).id;
|
||||||
|
} catch (e) {
|
||||||
|
if (e.name !== 'BalenaReleaseNotFound') {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be a number only uuid/hash (or nonexistent release)
|
||||||
|
return (await balena.models.release.get(release, { $select: 'id' })).id;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ export function validateDotLocalUrl(input: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function validateLongUuid(input: string): boolean {
|
export function validateLongUuid(input: string): boolean {
|
||||||
if (input.length !== 32 && input.length !== 64) {
|
if (input.length !== 32 && input.length !== 62) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return UUID_REGEX.test(input);
|
return UUID_REGEX.test(input);
|
||||||
@ -89,12 +89,9 @@ export function parseAsInteger(input: string, paramName?: string) {
|
|||||||
return Number(input);
|
return Number(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tryAsInteger(
|
export function tryAsInteger(input: string): number | string {
|
||||||
input: string,
|
|
||||||
paramName?: string,
|
|
||||||
): number | string {
|
|
||||||
try {
|
try {
|
||||||
return parseAsInteger(input, paramName);
|
return parseAsInteger(input);
|
||||||
} catch {
|
} catch {
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
@ -69,9 +69,9 @@ Additional commands:
|
|||||||
os initialize <image> initialize an os image
|
os initialize <image> initialize an os image
|
||||||
os versions <type> show the available balenaOS versions for the given device type
|
os versions <type> show the available balenaOS versions for the given device type
|
||||||
settings print current settings
|
settings print current settings
|
||||||
tag rm <tagKey> remove a resource tag
|
tag rm <tagkey> remove a tag from an application, device or release
|
||||||
tag set <tagKey> [value] set a resource tag
|
tag set <tagkey> [value] set a tag on an application, device or release
|
||||||
tags list all resource tags
|
tags list all tags for an application, device or release
|
||||||
util available-drives list available drives
|
util available-drives list available drives
|
||||||
version display version information for the balena CLI and/or Node.js
|
version display version information for the balena CLI and/or Node.js
|
||||||
whoami get current username and email address
|
whoami get current username and email address
|
||||||
|
209
tests/utils/normalization.spec.ts
Normal file
209
tests/utils/normalization.spec.ts
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
/**
|
||||||
|
* @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 { BalenaReleaseNotFound } from 'balena-errors';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import * as sinon from 'sinon';
|
||||||
|
import { ExpectedError } from '../../build/errors';
|
||||||
|
import { disambiguateReleaseParam } from '../../build/utils/normalization';
|
||||||
|
|
||||||
|
describe('disambiguateReleaseParam() function', () => {
|
||||||
|
it('should reject empty values', async () => {
|
||||||
|
try {
|
||||||
|
await disambiguateReleaseParam(null as any, '');
|
||||||
|
throw new Error('should not be reached');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).to.be.an.instanceOf(ExpectedError);
|
||||||
|
expect(e.message).to.equal('Invalid release parameter');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject values containing invalid chars', async () => {
|
||||||
|
const invalidCharExamples = ' .,-_=!@#$%^&*() ';
|
||||||
|
|
||||||
|
for (const char of invalidCharExamples) {
|
||||||
|
try {
|
||||||
|
await disambiguateReleaseParam(null as any, char);
|
||||||
|
throw new Error('should not be reached');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).to.be.an.instanceOf(ExpectedError);
|
||||||
|
expect(e.message).to.equal('Invalid release parameter');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject non-numerical values with invalid uuid/hash lengths', async () => {
|
||||||
|
const invalidLengthValue = 'abcd';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await disambiguateReleaseParam(null as any, invalidLengthValue);
|
||||||
|
throw new Error('should not be reached');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).to.be.an.instanceOf(ExpectedError);
|
||||||
|
expect(e.message).to.equal('Invalid release parameter');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject leading-zero numerical values with invalid uuid/hash lengths', async () => {
|
||||||
|
const invalidLengthValue = '01234';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await disambiguateReleaseParam(null as any, invalidLengthValue);
|
||||||
|
throw new Error('should not be reached');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).to.be.an.instanceOf(ExpectedError);
|
||||||
|
expect(e.message).to.equal('Invalid release parameter');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return non-numerical values with valid hash lengths as string, without SDK calls', async () => {
|
||||||
|
const uuid7 = 'a'.repeat(7);
|
||||||
|
const uuid32 = 'a'.repeat(32);
|
||||||
|
const uuid62 = 'a'.repeat(62);
|
||||||
|
const hash8 = 'a'.repeat(8);
|
||||||
|
const hash9 = 'a'.repeat(9);
|
||||||
|
const hash40 = 'a'.repeat(40);
|
||||||
|
|
||||||
|
expect(await disambiguateReleaseParam(null as any, uuid7)).to.equal(uuid7);
|
||||||
|
expect(await disambiguateReleaseParam(null as any, uuid32)).to.equal(
|
||||||
|
uuid32,
|
||||||
|
);
|
||||||
|
expect(await disambiguateReleaseParam(null as any, uuid62)).to.equal(
|
||||||
|
uuid62,
|
||||||
|
);
|
||||||
|
expect(await disambiguateReleaseParam(null as any, hash8)).to.equal(hash8);
|
||||||
|
expect(await disambiguateReleaseParam(null as any, hash9)).to.equal(hash9);
|
||||||
|
expect(await disambiguateReleaseParam(null as any, hash40)).to.equal(
|
||||||
|
hash40,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return numerical, leading zero values with valid uuid/hash lengths as string, without SDK calls', async () => {
|
||||||
|
const uuid7 = '0' + '1'.repeat(6);
|
||||||
|
const uuid32 = '0' + '1'.repeat(31);
|
||||||
|
const uuid62 = '0' + '1'.repeat(61);
|
||||||
|
const hash8 = '0' + '1'.repeat(7);
|
||||||
|
const hash9 = '0' + '1'.repeat(8);
|
||||||
|
const hash40 = '0' + '1'.repeat(39);
|
||||||
|
|
||||||
|
expect(await disambiguateReleaseParam(null as any, uuid7)).to.equal(uuid7);
|
||||||
|
expect(await disambiguateReleaseParam(null as any, uuid32)).to.equal(
|
||||||
|
uuid32,
|
||||||
|
);
|
||||||
|
expect(await disambiguateReleaseParam(null as any, uuid62)).to.equal(
|
||||||
|
uuid62,
|
||||||
|
);
|
||||||
|
expect(await disambiguateReleaseParam(null as any, hash8)).to.equal(hash8);
|
||||||
|
expect(await disambiguateReleaseParam(null as any, hash9)).to.equal(hash9);
|
||||||
|
expect(await disambiguateReleaseParam(null as any, hash40)).to.equal(
|
||||||
|
hash40,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return id from SDK on first call, if match is found', async () => {
|
||||||
|
const input = '1234';
|
||||||
|
const output = 1234;
|
||||||
|
const getRelease = sinon.stub().returns(Promise.resolve({ id: output }));
|
||||||
|
const sdk: any = {
|
||||||
|
models: {
|
||||||
|
release: {
|
||||||
|
get: getRelease,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await disambiguateReleaseParam(sdk, input);
|
||||||
|
|
||||||
|
expect(result).to.equal(output);
|
||||||
|
expect(getRelease.calledOnce).to.be.true;
|
||||||
|
expect(getRelease.getCall(0).args[0]).to.equal(parseInt(input, 10));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return id from SDK on second call, if match is found', async () => {
|
||||||
|
const input = '1234';
|
||||||
|
const output = 1234;
|
||||||
|
const getRelease = sinon
|
||||||
|
.stub()
|
||||||
|
.onCall(0)
|
||||||
|
.returns(Promise.reject(new BalenaReleaseNotFound(input)))
|
||||||
|
.onCall(1)
|
||||||
|
.returns(Promise.resolve({ id: output }));
|
||||||
|
|
||||||
|
const sdk: any = {
|
||||||
|
models: {
|
||||||
|
release: {
|
||||||
|
get: getRelease,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await disambiguateReleaseParam(sdk, input);
|
||||||
|
|
||||||
|
expect(result).to.equal(output);
|
||||||
|
expect(getRelease.calledTwice).to.be.true;
|
||||||
|
expect(getRelease.getCall(0).args[0]).to.equal(parseInt(input, 10));
|
||||||
|
expect(getRelease.getCall(1).args[0]).to.equal(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if no match found', async () => {
|
||||||
|
const input = '1234';
|
||||||
|
const getRelease = sinon
|
||||||
|
.stub()
|
||||||
|
.returns(Promise.reject(new BalenaReleaseNotFound(input)));
|
||||||
|
|
||||||
|
const sdk: any = {
|
||||||
|
models: {
|
||||||
|
release: {
|
||||||
|
get: getRelease,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await disambiguateReleaseParam(sdk, input);
|
||||||
|
throw new Error('should not be reached');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).to.be.an.instanceOf(BalenaReleaseNotFound);
|
||||||
|
expect(getRelease.calledTwice).to.be.true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if unknown error returned from SDK', async () => {
|
||||||
|
const input = '1234';
|
||||||
|
|
||||||
|
const getRelease = sinon
|
||||||
|
.stub()
|
||||||
|
.returns(Promise.reject(new Error('some error')));
|
||||||
|
|
||||||
|
const sdk: any = {
|
||||||
|
models: {
|
||||||
|
release: {
|
||||||
|
get: getRelease,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await disambiguateReleaseParam(sdk, input);
|
||||||
|
throw new Error('should not be reached');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).to.be.an.instanceOf(Error);
|
||||||
|
expect(e.message).to.equal('some error');
|
||||||
|
expect(getRelease.calledOnce).to.be.true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -114,17 +114,17 @@ describe('validateDotLocalUrl() function', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('validateLongUuid() function', () => {
|
describe('validateLongUuid() function', () => {
|
||||||
it('should return false for strings with length other than 32 or 64', () => {
|
it('should return false for strings with length other than 32 or 62', () => {
|
||||||
expect(v.validateLongUuid('')).to.equal(false);
|
expect(v.validateLongUuid('')).to.equal(false);
|
||||||
expect(v.validateLongUuid('abc')).to.equal(false);
|
expect(v.validateLongUuid('abc')).to.equal(false);
|
||||||
expect(v.validateLongUuid('a'.repeat(31))).to.equal(false);
|
expect(v.validateLongUuid('a'.repeat(31))).to.equal(false);
|
||||||
expect(v.validateLongUuid('a'.repeat(33))).to.equal(false);
|
expect(v.validateLongUuid('a'.repeat(33))).to.equal(false);
|
||||||
expect(v.validateLongUuid('a'.repeat(63))).to.equal(false);
|
expect(v.validateLongUuid('a'.repeat(64))).to.equal(false);
|
||||||
expect(v.validateLongUuid('a'.repeat(65))).to.equal(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false for strings with characters other than a-z,0-9', () => {
|
it('should return false for strings with characters other than a-f,0-9', () => {
|
||||||
expect(v.validateLongUuid('a'.repeat(31) + 'A')).to.equal(false);
|
expect(v.validateLongUuid('a'.repeat(31) + 'A')).to.equal(false);
|
||||||
|
expect(v.validateLongUuid('a'.repeat(31) + 'g')).to.equal(false);
|
||||||
expect(v.validateLongUuid('a'.repeat(31) + '.')).to.equal(false);
|
expect(v.validateLongUuid('a'.repeat(31) + '.')).to.equal(false);
|
||||||
expect(v.validateLongUuid('a'.repeat(31) + '-')).to.equal(false);
|
expect(v.validateLongUuid('a'.repeat(31) + '-')).to.equal(false);
|
||||||
expect(v.validateLongUuid('a'.repeat(31) + '_')).to.equal(false);
|
expect(v.validateLongUuid('a'.repeat(31) + '_')).to.equal(false);
|
||||||
@ -134,9 +134,7 @@ describe('validateLongUuid() function', () => {
|
|||||||
expect(v.validateLongUuid('8ab84942d20b4753e08243a9e3a177e2')).to.equal(
|
expect(v.validateLongUuid('8ab84942d20b4753e08243a9e3a177e2')).to.equal(
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
expect(
|
expect(v.validateLongUuid('a'.repeat(62))).to.equal(true);
|
||||||
v.validateLongUuid('8ab84942d20b4753e08243a9e3a177e2'.repeat(2)),
|
|
||||||
).to.equal(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -161,19 +159,19 @@ describe('validateShortUuid() function', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('validateUuid() function', () => {
|
describe('validateUuid() function', () => {
|
||||||
it('should return false for strings with length other than 7, 32 or 64', () => {
|
it('should return false for strings with length other than 7, 32 or 62', () => {
|
||||||
expect(v.validateUuid('')).to.equal(false);
|
expect(v.validateUuid('')).to.equal(false);
|
||||||
expect(v.validateUuid('abc')).to.equal(false);
|
expect(v.validateUuid('abc')).to.equal(false);
|
||||||
expect(v.validateUuid('a'.repeat(6))).to.equal(false);
|
expect(v.validateUuid('a'.repeat(6))).to.equal(false);
|
||||||
expect(v.validateUuid('a'.repeat(8))).to.equal(false);
|
expect(v.validateUuid('a'.repeat(8))).to.equal(false);
|
||||||
expect(v.validateUuid('a'.repeat(31))).to.equal(false);
|
expect(v.validateUuid('a'.repeat(31))).to.equal(false);
|
||||||
expect(v.validateUuid('a'.repeat(33))).to.equal(false);
|
expect(v.validateUuid('a'.repeat(33))).to.equal(false);
|
||||||
expect(v.validateUuid('a'.repeat(63))).to.equal(false);
|
expect(v.validateUuid('a'.repeat(64))).to.equal(false);
|
||||||
expect(v.validateUuid('a'.repeat(65))).to.equal(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false for strings with characters other than a-z,0-9', () => {
|
it('should return false for strings with characters other than a-f,0-9', () => {
|
||||||
expect(v.validateUuid('a'.repeat(31) + 'A')).to.equal(false);
|
expect(v.validateUuid('a'.repeat(31) + 'A')).to.equal(false);
|
||||||
|
expect(v.validateUuid('a'.repeat(31) + 'g')).to.equal(false);
|
||||||
expect(v.validateUuid('a'.repeat(31) + '.')).to.equal(false);
|
expect(v.validateUuid('a'.repeat(31) + '.')).to.equal(false);
|
||||||
expect(v.validateUuid('a'.repeat(31) + '-')).to.equal(false);
|
expect(v.validateUuid('a'.repeat(31) + '-')).to.equal(false);
|
||||||
expect(v.validateUuid('a'.repeat(31) + '_')).to.equal(false);
|
expect(v.validateUuid('a'.repeat(31) + '_')).to.equal(false);
|
||||||
@ -182,9 +180,7 @@ describe('validateUuid() function', () => {
|
|||||||
it('should return true for valid UUIDs', () => {
|
it('should return true for valid UUIDs', () => {
|
||||||
expect(v.validateUuid('8ab8494')).to.equal(true);
|
expect(v.validateUuid('8ab8494')).to.equal(true);
|
||||||
expect(v.validateUuid('8ab84942d20b4753e08243a9e3a177e2')).to.equal(true);
|
expect(v.validateUuid('8ab84942d20b4753e08243a9e3a177e2')).to.equal(true);
|
||||||
expect(
|
expect(v.validateLongUuid('a'.repeat(62))).to.equal(true);
|
||||||
v.validateUuid('8ab84942d20b4753e08243a9e3a177e2'.repeat(2)),
|
|
||||||
).to.equal(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -221,3 +217,24 @@ describe('parseAsInteger() function', () => {
|
|||||||
expect(v.parseAsInteger('0')).to.be.a('number');
|
expect(v.parseAsInteger('0')).to.be.a('number');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('tryAsInteger() function', () => {
|
||||||
|
it('should return string with non-numeric characters as string', () => {
|
||||||
|
expect(v.tryAsInteger('abc')).to.be.a('string');
|
||||||
|
expect(v.tryAsInteger('1a')).to.be.a('string');
|
||||||
|
expect(v.tryAsInteger('a1')).to.be.a('string');
|
||||||
|
expect(v.tryAsInteger('a')).to.be.a('string');
|
||||||
|
expect(v.tryAsInteger('1.0')).to.be.a('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return numerical strings with leading zeros as string', () => {
|
||||||
|
expect(v.tryAsInteger('01')).to.be.a('string');
|
||||||
|
expect(v.tryAsInteger('001')).to.be.a('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return numerical strings without leading zeros as number', () => {
|
||||||
|
expect(v.tryAsInteger('100')).to.be.a('number');
|
||||||
|
expect(v.tryAsInteger('256')).to.be.a('number');
|
||||||
|
expect(v.tryAsInteger('0')).to.be.a('number');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user