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