balena-cli
Version:
The official balena Command Line Interface
139 lines (121 loc) • 4.06 kB
text/typescript
/**
* @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, Command } from '@oclif/core';
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
import type { BalenaSDK, CurrentService } 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 authenticated = true;
public async run() {
const { args: params } = await this.parse(DeviceStartServiceCmd);
const balena = getBalenaSdk();
const ux = getCliUx();
const { resolveDeviceUuidsParam } = await import('../../utils/sdk');
const fullDeviceUuids = await resolveDeviceUuidsParam(
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 fullDeviceUuids) {
ux.action.start(`Starting services on device ${uuid}`);
await this.startServices(balena, uuid, serviceNames);
ux.action.stop();
}
}
async startServices(
balena: BalenaSDK,
deviceFullUuid: string,
serviceNames: string[],
) {
const { ExpectedError } = await import('../../errors');
const { getExpandedProp } = await import('../../utils/pine');
// Get device
const device = await balena.models.device.getWithServiceDetails(
deviceFullUuid,
{
$expand: {
belongs_to__application: { $select: 'slug' },
is_running__release: { $select: 'commit' },
},
},
);
const activeReleaseCommit = getExpandedProp(
device.is_running__release,
'commit',
);
// Check specified services exist on this device before startinganything
const fleetSlug = device.belongs_to__application[0].slug;
const serviceInfos = serviceNames.map((serviceName) => {
const serviceInfo =
device.current_services_by_app[fleetSlug]?.[serviceName];
if (serviceInfo == null) {
throw new ExpectedError(
`Service ${serviceName} not found on device ${deviceFullUuid}.`,
);
}
return serviceInfo;
});
// Start services
const startPromises: Array<Promise<void>> = [];
for (const service of serviceInfos) {
// 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: CurrentService) => {
return s.commit === activeReleaseCommit;
});
if (serviceContainer) {
startPromises.push(
balena.models.device.startService(
deviceFullUuid,
serviceContainer.image_id,
),
);
}
}
try {
await Promise.all(startPromises);
} catch (e) {
if (e.message.toLowerCase().includes('no online device')) {
throw new ExpectedError(`Device ${deviceFullUuid} is not online.`);
} else {
throw e;
}
}
}
}