Skip to content

Commit

Permalink
Add commands device support and fleet support matching support
Browse files Browse the repository at this point in the history
Change-type: minor
  • Loading branch information
myarmolinsky committed Oct 23, 2024
1 parent f305d5d commit 46c6a0b
Show file tree
Hide file tree
Showing 6 changed files with 335 additions and 4 deletions.
4 changes: 2 additions & 2 deletions completion/_balena
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ _balena() {
app_cmds=( create )
block_cmds=( create )
config_cmds=( generate inject read reconfigure write )
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 )
devices_cmds=( supported )
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 )
Expand Down
4 changes: 2 additions & 2 deletions completion/balena-completion.bash
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ _balena_complete()
app_cmds="create"
block_cmds="create"
config_cmds="generate inject read reconfigure write"
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"
devices_cmds="supported"
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"
Expand Down
80 changes: 80 additions & 0 deletions docs/balena-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,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)
- [devices supported](#devices-supported)
Expand All @@ -239,6 +240,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
Expand Down Expand Up @@ -2060,6 +2062,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
Expand Down Expand Up @@ -2731,6 +2767,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
Expand Down
111 changes: 111 additions & 0 deletions src/commands/device/support.ts
Original file line number Diff line number Diff line change
@@ -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()}`,
);
}
}
}
117 changes: 117 additions & 0 deletions src/commands/fleet/support.ts
Original file line number Diff line number Diff line change
@@ -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()}`,
);
}
}
}
23 changes: 23 additions & 0 deletions src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }>;
Expand Down Expand Up @@ -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;
};

0 comments on commit 46c6a0b

Please sign in to comment.