Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add device start-service and stop-service commands #2689

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions automation/capitanodoc/capitanodoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ const capitanoDoc = {
'build/commands/device/rm.js',
'build/commands/device/shutdown.js',
'build/commands/device/track-fleet.js',
'build/commands/device/start-service.js',
'build/commands/device/stop-service.js',
],
},
{
Expand Down
2 changes: 1 addition & 1 deletion completion/_balena
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ _balena() {
app_cmds=( create )
block_cmds=( create )
config_cmds=( generate inject read reconfigure write )
device_cmds=( deactivate identify init local-mode move os-update pin public-url purge reboot register rename restart rm shutdown track-fleet )
device_cmds=( deactivate identify init local-mode move os-update pin public-url purge reboot register rename restart rm shutdown start-service stop-service track-fleet )
devices_cmds=( supported )
env_cmds=( add rename rm )
fleet_cmds=( create pin purge rename restart rm track-latest )
Expand Down
2 changes: 1 addition & 1 deletion completion/balena-completion.bash
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ _balena_complete()
app_cmds="create"
block_cmds="create"
config_cmds="generate inject read reconfigure write"
device_cmds="deactivate identify init local-mode move os-update pin public-url purge reboot register rename restart rm shutdown track-fleet"
device_cmds="deactivate identify init local-mode move os-update pin public-url purge reboot register rename restart rm shutdown start-service stop-service track-fleet"
devices_cmds="supported"
env_cmds="add rename rm"
fleet_cmds="create pin purge rename restart rm track-latest"
Expand Down
50 changes: 50 additions & 0 deletions docs/balena-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ are encouraged to regularly update the balena CLI to the latest version.
- [device rm <uuid(s)>](#device-rm-uuid-s)
- [device shutdown <uuid>](#device-shutdown-uuid)
- [device track-fleet <uuid>](#device-track-fleet-uuid)
- [device start-service <uuid>](#device-start-service-uuid)
- [device stop-service <uuid>](#device-stop-service-uuid)

- Devices

Expand Down Expand Up @@ -1709,6 +1711,54 @@ the uuid of the device to make track the fleet's release

### Options

## device start-service <uuid>

Start containers on a device.

Multiple devices and services may be specified with a comma-separated list
of values (no spaces).

Examples:

$ balena device start-service 23c73a1 myService
$ balena device start-service 23c73a1 myService1,myService2

### Arguments

#### UUID

comma-separated list (no blank spaces) of device UUIDs

#### SERVICE

comma-separated list (no blank spaces) of service names

### Options

## device stop-service <uuid>

Stop containers on a device.

Multiple devices and services may be specified with a comma-separated list
of values (no spaces).

Examples:

$ balena device stop-service 23c73a1 myService
$ balena device stop-service 23c73a1 myService1,myService2

### Arguments

#### UUID

comma-separated list (no blank spaces) of device UUIDs

#### SERVICE

comma-separated list (no blank spaces) of service names

### Options

# Devices

## devices
Expand Down
139 changes: 139 additions & 0 deletions lib/commands/device/start-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/**
* @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 { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
import type { BalenaSDK } from 'balena-sdk';

export default class DeviceStartServiceCmd extends Command {
public static description = stripIndent`
Start containers on a device.
Start containers on a device.
Multiple devices and services may be specified with a comma-separated list
of values (no spaces).
`;
public static examples = [
'$ balena device start-service 23c73a1 myService',
'$ balena device start-service 23c73a1 myService1,myService2',
];

public static args = {
uuid: Args.string({
description: 'comma-separated list (no blank spaces) of device UUIDs',
required: true,
}),
service: Args.string({
description: 'comma-separated list (no blank spaces) of service names',
required: true,
}),
};

public static usage = 'device start-service <uuid>';

public static flags = {
help: cf.help,
};

public static authenticated = true;

public async run() {
const { args: params } = await this.parse(DeviceStartServiceCmd);

const balena = getBalenaSdk();
const ux = getCliUx();

const deviceUuids = params.uuid.split(',');
const serviceNames = params.service.split(',');

// Iterate sequentially through deviceUuids.
// We may later want to add a batching feature,
// so that n devices are processed in parallel
for (const uuid of deviceUuids) {
ux.action.start(`Starting services on device ${uuid}`);
await this.startServices(balena, uuid, serviceNames);
ux.action.stop();
}
}

async startServices(
balena: BalenaSDK,
deviceUuid: string,
serviceNames: string[],
) {
const { ExpectedError } = await import('../../errors');
const { getExpandedProp } = await import('../../utils/pine');

// Get device
const device = await balena.models.device.getWithServiceDetails(
deviceUuid,
{
$expand: {
is_running__release: { $select: 'commit' },
},
},
);

const activeReleaseCommit = getExpandedProp(
device.is_running__release,
'commit',
);

// Check specified services exist on this device before startinganything
serviceNames.forEach((service) => {
if (!device.current_services[service]) {
throw new ExpectedError(
`Service ${service} not found on device ${deviceUuid}.`,
);
}
});

// Start services
const startPromises: Array<Promise<void>> = [];
for (const serviceName of serviceNames) {
const service = device.current_services[serviceName];
// Each service is an array of `CurrentServiceWithCommit`
// because when service is updating, it will actually hold 2 services
// Target commit matching `device.is_running__release`
const serviceContainer = service.find((s) => {
return s.commit === activeReleaseCommit;
});

if (serviceContainer) {
startPromises.push(
balena.models.device.startService(
deviceUuid,
serviceContainer.image_id,
),
);
}
}

try {
await Promise.all(startPromises);
} catch (e) {
if (e.message.toLowerCase().includes('no online device')) {
throw new ExpectedError(`Device ${deviceUuid} is not online.`);
} else {
throw e;
}
}
}
}
139 changes: 139 additions & 0 deletions lib/commands/device/stop-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/**
* @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 { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
import type { BalenaSDK } from 'balena-sdk';

export default class DeviceStopServiceCmd extends Command {
public static description = stripIndent`
Stop containers on a device.
Stop containers on a device.
Multiple devices and services may be specified with a comma-separated list
of values (no spaces).
`;
public static examples = [
'$ balena device stop-service 23c73a1 myService',
'$ balena device stop-service 23c73a1 myService1,myService2',
];

public static args = {
uuid: Args.string({
description: 'comma-separated list (no blank spaces) of device UUIDs',
required: true,
}),
service: Args.string({
description: 'comma-separated list (no blank spaces) of service names',
required: true,
}),
};

public static usage = 'device stop-service <uuid>';

public static flags = {
help: cf.help,
};

public static authenticated = true;

public async run() {
const { args: params } = await this.parse(DeviceStopServiceCmd);

const balena = getBalenaSdk();
const ux = getCliUx();

const deviceUuids = params.uuid.split(',');
const serviceNames = params.service.split(',');

// Iterate sequentially through deviceUuids.
// We may later want to add a batching feature,
// so that n devices are processed in parallel
for (const uuid of deviceUuids) {
ux.action.start(`Stopping services on device ${uuid}`);
await this.stopServices(balena, uuid, serviceNames);
ux.action.stop();
}
}

async stopServices(
balena: BalenaSDK,
deviceUuid: string,
serviceNames: string[],
) {
const { ExpectedError } = await import('../../errors');
const { getExpandedProp } = await import('../../utils/pine');

// Get device
const device = await balena.models.device.getWithServiceDetails(
deviceUuid,
{
$expand: {
is_running__release: { $select: 'commit' },
},
},
);

const activeReleaseCommit = getExpandedProp(
device.is_running__release,
'commit',
);

// Check specified services exist on this device before stoppinganything
serviceNames.forEach((service) => {
if (!device.current_services[service]) {
throw new ExpectedError(
`Service ${service} not found on device ${deviceUuid}.`,
);
}
});

// Stop services
const stopPromises: Array<Promise<void>> = [];
for (const serviceName of serviceNames) {
const service = device.current_services[serviceName];
// Each service is an array of `CurrentServiceWithCommit`
// because when service is updating, it will actually hold 2 services
// Target commit matching `device.is_running__release`
const serviceContainer = service.find((s) => {
return s.commit === activeReleaseCommit;
});

if (serviceContainer) {
stopPromises.push(
balena.models.device.stopService(
deviceUuid,
serviceContainer.image_id,
),
);
}
}

try {
await Promise.all(stopPromises);
} catch (e) {
if (e.message.toLowerCase().includes('no online device')) {
throw new ExpectedError(`Device ${deviceUuid} is not online.`);
} else {
throw e;
}
}
}
}
Loading