Add commands device support and fleet support matching support

Change-type: minor
This commit is contained in:
myarmolinsky 2024-10-23 08:06:03 -04:00
parent 746b6fa439
commit bbfa55aa79
6 changed files with 335 additions and 4 deletions

View File

@ -15,9 +15,9 @@ _balena() {
block_cmds=( create )
config_cmds=( generate inject read reconfigure write )
device_type_cmds=( list )
device_cmds=( deactivate detect identify init list local-mode logs move note os-update pin public-url purge reboot register rename restart rm shutdown ssh start-service stop-service track-fleet tunnel )
device_cmds=( deactivate detect identify init list local-mode logs move note os-update pin public-url purge reboot register rename restart rm shutdown ssh start-service stop-service support track-fleet tunnel )
env_cmds=( list rename rm set )
fleet_cmds=( create list pin purge rename restart rm track-latest )
fleet_cmds=( create list pin purge rename restart rm support track-latest )
internal_cmds=( osinit )
local_cmds=( configure flash )
organization_cmds=( list )

View File

@ -14,9 +14,9 @@ _balena_complete()
block_cmds="create"
config_cmds="generate inject read reconfigure write"
device_type_cmds="list"
device_cmds="deactivate detect identify init list local-mode logs move note os-update pin public-url purge reboot register rename restart rm shutdown ssh start-service stop-service track-fleet tunnel"
device_cmds="deactivate detect identify init list local-mode logs move note os-update pin public-url purge reboot register rename restart rm shutdown ssh start-service stop-service support track-fleet tunnel"
env_cmds="list rename rm set"
fleet_cmds="create list pin purge rename restart rm track-latest"
fleet_cmds="create list pin purge rename restart rm support track-latest"
internal_cmds="osinit"
local_cmds="configure flash"
organization_cmds="list"

View File

@ -222,6 +222,7 @@ are encouraged to regularly update the balena CLI to the latest version.
- [device ssh](#device-ssh)
- [device start-service](#device-start-service)
- [device stop-service](#device-stop-service)
- [device support](#device-support)
- [device track-fleet](#device-track-fleet)
- [device tunnel](#device-tunnel)
@ -242,6 +243,7 @@ are encouraged to regularly update the balena CLI to the latest version.
- [fleet rename](#fleet-rename)
- [fleet restart](#fleet-restart)
- [fleet rm](#fleet-rm)
- [fleet support](#fleet-support)
- [fleet track-latest](#fleet-track-latest)
- Local
@ -2104,6 +2106,40 @@ comma-separated list (no blank spaces) of service names
### Options
## device support
### Description
Grant or revoke balena support agent access to devices
on balenaCloud. (This command does not apply to openBalena.)
Access will be automatically revoked once the specified duration has elapsed.
Duration defaults to 24h, but can be specified using --duration flag in days
or hours, e.g. '12h', '2d'.
Multiple values can specified as a comma-separated list (with no spaces).
Examples:
balena support enable ab346f,cd457a --duration 3d
balena support disable ab346f,cd457a
### Arguments
#### ACTION
enable|disable support access
#### UUID
comma-separated list (no blank spaces) of device UUIDs to be moved
### Options
#### -t, --duration DURATION
length of time to enable support for, in (h)ours or (d)ays, e.g. 12h, 2d
## device track-fleet
### Description
@ -2752,6 +2788,50 @@ fleet name or slug (preferred)
answer "yes" to all questions (non interactive use)
## fleet support
### Description
Grant or revoke balena support agent access to fleets
on balenaCloud. (This command does not apply to openBalena.)
Access will be automatically revoked once the specified duration has elapsed.
Duration defaults to 24h, but can be specified using --duration flag in days
or hours, e.g. '12h', '2d'.
Multiple values can specified as a comma-separated list (with no spaces).
Fleets may be specified by fleet name or slug. Fleet slugs are
the recommended option, as they are unique and unambiguous. Slugs can be
listed with the `balena fleet list` command. Note that slugs may change if the
fleet is renamed. Fleet names are not unique and may result in "Fleet is
ambiguous" errors at any time (even if it "used to work in the past"), for
example if the name clashes with a newly created public fleet, or with fleets
from other balena accounts that you may be invited to join under any role.
For this reason, fleet names are especially discouraged in scripts (e.g. CI
environments).
Examples:
balena support enable myorg/myfleet,notmyorg/notmyfleet --duration 3d
balena support disable myorg/myfleet
### Arguments
#### ACTION
enable|disable support access
#### FLEET
comma-separated list (no spaces) of fleet names or slugs (preferred)
### Options
#### -t, --duration DURATION
length of time to enable support for, in (h)ours or (d)ays, e.g. 12h, 2d
## fleet track-latest
### Description

View File

@ -0,0 +1,111 @@
/**
* @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, Args, Command } from '@oclif/core';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
export default class DeviceSupportCmd extends Command {
public static description = stripIndent`
Grant or revoke support access for devices.
Grant or revoke balena support agent access to devices
on balenaCloud. (This command does not apply to openBalena.)
Access will be automatically revoked once the specified duration has elapsed.
Duration defaults to 24h, but can be specified using --duration flag in days
or hours, e.g. '12h', '2d'.
Multiple values can specified as a comma-separated list (with no spaces).
`;
public static examples = [
'balena support enable ab346f,cd457a --duration 3d',
'balena support disable ab346f,cd457a',
];
public static args = {
action: Args.string({
description: 'enable|disable support access',
options: ['enable', 'disable'],
required: true,
}),
uuid: Args.string({
description:
'comma-separated list (no blank spaces) of device UUIDs to be moved',
required: true,
}),
};
public static flags = {
duration: Flags.string({
description:
'length of time to enable support for, in (h)ours or (d)ays, e.g. 12h, 2d',
char: 't',
}),
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params, flags: options } = await this.parse(DeviceSupportCmd);
const balena = getBalenaSdk();
const ux = getCliUx();
const enabling = params.action === 'enable';
if (options.duration != null && !enabling) {
throw new ExpectedError(
'--duration option is only applicable when enabling support',
);
}
// Calculate expiry ts
const durationDefault = '24h';
const duration = options.duration || durationDefault;
const { parseDuration } = await import('../../utils/helpers');
const expiryTs = Date.now() + parseDuration(duration);
const deviceUuids = params.uuid?.split(',') || [];
const enablingMessage = 'Enabling support access for';
const disablingMessage = 'Disabling support access for';
// Process devices
for (const deviceUuid of deviceUuids) {
if (enabling) {
ux.action.start(`${enablingMessage} device ${deviceUuid}`);
await balena.models.device.grantSupportAccess(deviceUuid, expiryTs);
} else if (params.action === 'disable') {
ux.action.start(`${disablingMessage} device ${deviceUuid}`);
await balena.models.device.revokeSupportAccess(deviceUuid);
}
ux.action.stop();
}
if (enabling) {
console.log(
`Access has been granted for ${duration}, expiring ${new Date(
expiryTs,
).toISOString()}`,
);
}
}
}

View File

@ -0,0 +1,117 @@
/**
* @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, Args, Command } from '@oclif/core';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
export default class FleetSupportCmd extends Command {
public static description = stripIndent`
Grant or revoke support access for fleets.
Grant or revoke balena support agent access to fleets
on balenaCloud. (This command does not apply to openBalena.)
Access will be automatically revoked once the specified duration has elapsed.
Duration defaults to 24h, but can be specified using --duration flag in days
or hours, e.g. '12h', '2d'.
Multiple values can specified as a comma-separated list (with no spaces).
${applicationIdInfo.split('\n').join('\n\t\t')}
`;
public static examples = [
'balena support enable myorg/myfleet,notmyorg/notmyfleet --duration 3d',
'balena support disable myorg/myfleet',
];
public static args = {
action: Args.string({
description: 'enable|disable support access',
options: ['enable', 'disable'],
required: true,
}),
fleet: Args.string({
description:
'comma-separated list (no spaces) of fleet names or slugs (preferred)',
required: true,
}),
};
public static flags = {
duration: Flags.string({
description:
'length of time to enable support for, in (h)ours or (d)ays, e.g. 12h, 2d',
char: 't',
}),
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params, flags: options } = await this.parse(FleetSupportCmd);
const balena = getBalenaSdk();
const ux = getCliUx();
const enabling = params.action === 'enable';
if (options.duration != null && !enabling) {
throw new ExpectedError(
'--duration option is only applicable when enabling support',
);
}
// Calculate expiry ts
const durationDefault = '24h';
const duration = options.duration || durationDefault;
const { parseDuration } = await import('../../utils/helpers');
const expiryTs = Date.now() + parseDuration(duration);
const appNames = params.fleet?.split(',') || [];
const enablingMessage = 'Enabling support access for';
const disablingMessage = 'Disabling support access for';
const { getFleetSlug } = await import('../../utils/sdk');
// Process applications
for (const appName of appNames) {
const slug = await getFleetSlug(balena, appName);
if (enabling) {
ux.action.start(`${enablingMessage} fleet ${slug}`);
await balena.models.application.grantSupportAccess(slug, expiryTs);
} else if (params.action === 'disable') {
ux.action.start(`${disablingMessage} fleet ${slug}`);
await balena.models.application.revokeSupportAccess(slug);
}
ux.action.stop();
}
if (enabling) {
console.log(
`Access has been granted for ${duration}, expiring ${new Date(
expiryTs,
).toISOString()}`,
);
}
}
}

View File

@ -21,6 +21,7 @@ import * as _ from 'lodash';
import { promisify } from 'util';
import { getBalenaSdk, getChalk, getVisuals } from './lazy';
import { ExpectedError } from '../errors';
export function getGroupDefaults(group: {
options: Array<{ name: string; default: string | number }>;
@ -478,3 +479,25 @@ export function pickAndRename<T extends Dictionary<any>>(
});
return _.mapKeys(_.pick(obj, fields), (_val, key) => rename[key]);
}
export const parseDuration = (duration: string) => {
const parseErrorMsg =
'Duration must be specified as number followed by h or d, e.g. 24h, 1d';
const unit = duration.slice(duration.length - 1);
const amount = Number(duration.substring(0, duration.length - 1));
if (isNaN(amount)) {
throw new ExpectedError(parseErrorMsg);
}
let durationMs;
if (['h', 'H'].includes(unit)) {
durationMs = amount * 60 * 60 * 1000;
} else if (['d', 'D'].includes(unit)) {
durationMs = amount * 24 * 60 * 60 * 1000;
} else {
throw new ExpectedError(parseErrorMsg);
}
return durationMs;
};