mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-19 21:57:51 +00:00
promote: Allow joining fleets of discontinued device types
Change-type: patch
This commit is contained in:
parent
d12d7996bc
commit
5628824bee
@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type * as BalenaSdk from 'balena-sdk';
|
||||
import { flags } from '@oclif/command';
|
||||
|
||||
import Command from '../command';
|
||||
@ -22,7 +23,7 @@ import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||
import type { DataSetOutputOptions } from '../framework';
|
||||
|
||||
interface ExtendedApplication extends ApplicationWithDeviceType {
|
||||
interface ExtendedApplication extends ApplicationWithDeviceTypeSlug {
|
||||
device_count: number;
|
||||
online_devices: number;
|
||||
device_type?: string;
|
||||
@ -60,15 +61,20 @@ export default class FleetsCmd extends Command {
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
// Get applications
|
||||
const applications =
|
||||
(await balena.models.application.getAllDirectlyAccessible({
|
||||
const pineOptions = {
|
||||
$select: ['id', 'app_name', 'slug'],
|
||||
$expand: {
|
||||
is_for__device_type: { $select: 'slug' },
|
||||
owns__device: { $select: 'is_online' },
|
||||
},
|
||||
})) as ExtendedApplication[];
|
||||
} satisfies BalenaSdk.PineOptions<BalenaSdk.Application>;
|
||||
// Get applications
|
||||
const applications =
|
||||
(await balena.models.application.getAllDirectlyAccessible(
|
||||
pineOptions,
|
||||
)) as Array<
|
||||
BalenaSdk.PineTypedResult<BalenaSdk.Application, typeof pineOptions>
|
||||
> as ExtendedApplication[];
|
||||
|
||||
// Add extended properties
|
||||
applications.forEach((application) => {
|
||||
|
@ -204,7 +204,7 @@ export default class OsConfigureCmd extends Command {
|
||||
const helpers = await import('../../utils/helpers');
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
|
||||
let app: ApplicationWithDeviceType | undefined;
|
||||
let app: ApplicationWithDeviceTypeSlug | undefined;
|
||||
let device;
|
||||
let deviceTypeSlug: string;
|
||||
|
||||
@ -223,7 +223,7 @@ export default class OsConfigureCmd extends Command {
|
||||
$expand: {
|
||||
is_for__device_type: { $select: 'slug' },
|
||||
},
|
||||
})) as ApplicationWithDeviceType;
|
||||
})) as ApplicationWithDeviceTypeSlug;
|
||||
await checkDeviceTypeCompatibility(options, app);
|
||||
deviceTypeSlug =
|
||||
options['device-type'] || app.is_for__device_type[0].slug;
|
||||
|
@ -228,8 +228,8 @@ async function selectLocalDevice(): Promise<string> {
|
||||
}
|
||||
|
||||
async function selectAppFromList(
|
||||
applications: ApplicationWithDeviceType[],
|
||||
): Promise<ApplicationWithDeviceType> {
|
||||
applications: ApplicationWithDeviceTypeSlug[],
|
||||
): Promise<ApplicationWithDeviceTypeSlug> {
|
||||
const _ = await import('lodash');
|
||||
const { selectFromList } = await import('../utils/patterns');
|
||||
|
||||
@ -247,7 +247,7 @@ async function getOrSelectApplication(
|
||||
sdk: BalenaSdk.BalenaSDK,
|
||||
deviceTypeSlug: string,
|
||||
appName?: string,
|
||||
): Promise<ApplicationWithDeviceType> {
|
||||
): Promise<ApplicationWithDeviceTypeSlug> {
|
||||
const pineOptions = {
|
||||
$select: 'slug',
|
||||
$expand: {
|
||||
@ -256,51 +256,72 @@ async function getOrSelectApplication(
|
||||
},
|
||||
},
|
||||
} satisfies BalenaSdk.PineOptions<BalenaSdk.DeviceType>;
|
||||
const [deviceType, allDeviceTypes] = await Promise.all([
|
||||
sdk.models.deviceType.get(deviceTypeSlug, pineOptions) as Promise<
|
||||
BalenaSdk.PineTypedResult<BalenaSdk.DeviceType, typeof pineOptions>
|
||||
>,
|
||||
sdk.models.deviceType.getAllSupported(pineOptions) as Promise<
|
||||
Array<BalenaSdk.PineTypedResult<BalenaSdk.DeviceType, typeof pineOptions>>
|
||||
>,
|
||||
]);
|
||||
const deviceType = (await sdk.models.deviceType.get(
|
||||
deviceTypeSlug,
|
||||
pineOptions,
|
||||
)) as BalenaSdk.PineTypedResult<BalenaSdk.DeviceType, typeof pineOptions>;
|
||||
const allCpuArches = await sdk.pine.get({
|
||||
resource: 'cpu_architecture',
|
||||
options: {
|
||||
$select: ['id', 'slug'],
|
||||
},
|
||||
});
|
||||
|
||||
const compatibleDeviceTypes = allDeviceTypes
|
||||
.filter((dt) =>
|
||||
const compatibleCpuArchIds = allCpuArches
|
||||
.filter((cpuArch) =>
|
||||
sdk.models.os.isArchitectureCompatibleWith(
|
||||
deviceType.is_of__cpu_architecture[0].slug,
|
||||
dt.is_of__cpu_architecture[0].slug,
|
||||
cpuArch.slug,
|
||||
),
|
||||
)
|
||||
.map((type) => type.slug);
|
||||
.map((cpu) => cpu.id);
|
||||
|
||||
if (!appName) {
|
||||
return createOrSelectApp(sdk, compatibleDeviceTypes, deviceTypeSlug);
|
||||
return createOrSelectApp(
|
||||
sdk,
|
||||
{
|
||||
is_for__device_type: {
|
||||
$any: {
|
||||
$alias: 'dt',
|
||||
$expr: {
|
||||
dt: {
|
||||
is_of__cpu_architecture: { $in: compatibleCpuArchIds },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
deviceTypeSlug,
|
||||
);
|
||||
}
|
||||
|
||||
const options: BalenaSdk.PineOptions<BalenaSdk.Application> = {
|
||||
const options = {
|
||||
$expand: {
|
||||
is_for__device_type: { $select: 'slug' },
|
||||
is_for__device_type: { $select: ['slug', 'is_of__cpu_architecture'] },
|
||||
},
|
||||
};
|
||||
} satisfies BalenaSdk.PineOptions<BalenaSdk.Application>;
|
||||
|
||||
// Check for a fleet slug of the form `user/fleet` and update the API query.
|
||||
let name: string;
|
||||
const match = appName.split('/');
|
||||
if (match.length > 1) {
|
||||
// These will match at most one fleet
|
||||
options.$filter = { slug: appName.toLowerCase() };
|
||||
(options as BalenaSdk.PineOptions<BalenaSdk.Application>).$filter = {
|
||||
slug: appName.toLowerCase(),
|
||||
};
|
||||
name = match[1];
|
||||
} else {
|
||||
// We're given an application; resolve it if it's ambiguous and also validate
|
||||
// it's of appropriate device type.
|
||||
options.$filter = { app_name: appName };
|
||||
(options as BalenaSdk.PineOptions<BalenaSdk.Application>).$filter = {
|
||||
app_name: appName,
|
||||
};
|
||||
name = appName;
|
||||
}
|
||||
|
||||
const applications = (await sdk.models.application.getAllDirectlyAccessible(
|
||||
options,
|
||||
)) as ApplicationWithDeviceType[];
|
||||
)) as Array<BalenaSdk.PineTypedResult<BalenaSdk.Application, typeof options>>;
|
||||
|
||||
if (applications.length === 0) {
|
||||
await confirm(
|
||||
@ -315,8 +336,11 @@ async function getOrSelectApplication(
|
||||
|
||||
// We've found at least one fleet with the given name.
|
||||
// Filter out fleets for non-matching device types and see what we're left with.
|
||||
const compatibleCpuArchIdsSet = new Set(compatibleCpuArchIds);
|
||||
const validApplications = applications.filter((app) =>
|
||||
compatibleDeviceTypes.includes(app.is_for__device_type[0].slug),
|
||||
compatibleCpuArchIdsSet.has(
|
||||
app.is_for__device_type[0].is_of__cpu_architecture.__id,
|
||||
),
|
||||
);
|
||||
|
||||
if (validApplications.length === 0) {
|
||||
@ -332,21 +356,14 @@ async function getOrSelectApplication(
|
||||
|
||||
async function createOrSelectApp(
|
||||
sdk: BalenaSdk.BalenaSDK,
|
||||
compatibleDeviceTypes: string[],
|
||||
compatibleDeviceTypesFilter: BalenaSdk.PineFilter<BalenaSdk.Application>,
|
||||
deviceType: string,
|
||||
): Promise<ApplicationWithDeviceType> {
|
||||
): Promise<ApplicationWithDeviceTypeSlug> {
|
||||
// No fleet specified, show a list to select one.
|
||||
const applications = (await sdk.models.application.getAllDirectlyAccessible({
|
||||
$expand: { is_for__device_type: { $select: 'slug' } },
|
||||
$filter: {
|
||||
is_for__device_type: {
|
||||
$any: {
|
||||
$alias: 'dt',
|
||||
$expr: { dt: { slug: { $in: compatibleDeviceTypes } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
})) as ApplicationWithDeviceType[];
|
||||
$filter: compatibleDeviceTypesFilter,
|
||||
})) as ApplicationWithDeviceTypeSlug[];
|
||||
|
||||
if (applications.length === 0) {
|
||||
await confirm(
|
||||
@ -366,7 +383,7 @@ async function createApplication(
|
||||
sdk: BalenaSdk.BalenaSDK,
|
||||
deviceType: string,
|
||||
name?: string,
|
||||
): Promise<ApplicationWithDeviceType> {
|
||||
): Promise<ApplicationWithDeviceTypeSlug> {
|
||||
const validation = await import('./validation');
|
||||
|
||||
const username = await sdk.auth.whoami();
|
||||
@ -414,12 +431,12 @@ async function createApplication(
|
||||
$expand: {
|
||||
is_for__device_type: { $select: 'slug' },
|
||||
},
|
||||
})) as ApplicationWithDeviceType;
|
||||
})) as ApplicationWithDeviceTypeSlug;
|
||||
}
|
||||
|
||||
async function generateApplicationConfig(
|
||||
sdk: BalenaSdk.BalenaSDK,
|
||||
app: ApplicationWithDeviceType,
|
||||
app: ApplicationWithDeviceTypeSlug,
|
||||
options: {
|
||||
version: string;
|
||||
appUpdatePollInterval?: number;
|
||||
|
7
typings/global.d.ts
vendored
7
typings/global.d.ts
vendored
@ -1,8 +1,11 @@
|
||||
import { Application, DeviceType, Device } from 'balena-sdk';
|
||||
|
||||
declare global {
|
||||
type ApplicationWithDeviceType = Application & {
|
||||
is_for__device_type: [DeviceType];
|
||||
type ApplicationWithDeviceTypeSlug = Omit<
|
||||
Application,
|
||||
'is_for__device_type'
|
||||
> & {
|
||||
is_for__device_type: [Pick<DeviceType, 'slug'>];
|
||||
};
|
||||
type DeviceWithDeviceType = Device & {
|
||||
is_of__device_type: [DeviceType];
|
||||
|
Loading…
Reference in New Issue
Block a user