mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-18 21:27:54 +00:00
Merge pull request #2334 from balena-os/circular-dependencies
Refactor code to reduce circular dependencies between modules
This commit is contained in:
commit
1264012fad
@ -3,8 +3,7 @@ import { stripIndent } from 'common-tags';
|
||||
import { isLeft } from 'fp-ts/lib/Either';
|
||||
import * as t from 'io-ts';
|
||||
import _ from 'lodash';
|
||||
import { PinejsClientRequest } from 'pinejs-client-request';
|
||||
import url from 'url';
|
||||
import type { PinejsClientRequest } from 'pinejs-client-request';
|
||||
|
||||
import * as config from '../config';
|
||||
import * as deviceConfig from '../device-config';
|
||||
@ -17,7 +16,6 @@ import {
|
||||
InternalInconsistencyError,
|
||||
TargetStateError,
|
||||
} from '../lib/errors';
|
||||
import * as request from '../lib/request';
|
||||
|
||||
import log from '../lib/supervisor-console';
|
||||
|
||||
@ -511,31 +509,18 @@ async function reportInitialName(
|
||||
}
|
||||
}
|
||||
|
||||
export let balenaApi: PinejsClientRequest | null = null;
|
||||
let balenaApi: PinejsClientRequest | null = null;
|
||||
|
||||
export const initialized = _.once(async () => {
|
||||
await config.initialized();
|
||||
await deviceState.initialized();
|
||||
|
||||
const { unmanaged, apiEndpoint, currentApiKey } = await config.getMany([
|
||||
'unmanaged',
|
||||
'apiEndpoint',
|
||||
'currentApiKey',
|
||||
]);
|
||||
const unmanaged = await config.get('unmanaged');
|
||||
|
||||
if (unmanaged) {
|
||||
log.debug('Unmanaged mode is set, skipping API client initialization');
|
||||
return;
|
||||
}
|
||||
|
||||
const baseUrl = url.resolve(apiEndpoint, '/v6/');
|
||||
const passthrough = structuredClone(await request.getRequestOptions());
|
||||
passthrough.headers = passthrough.headers != null ? passthrough.headers : {};
|
||||
passthrough.headers.Authorization = `Bearer ${currentApiKey}`;
|
||||
balenaApi = new PinejsClientRequest({
|
||||
apiPrefix: baseUrl,
|
||||
passthrough,
|
||||
});
|
||||
|
||||
log.info(`API Binder bound to: ${baseUrl}`);
|
||||
balenaApi = await apiHelper.getBalenaApi();
|
||||
});
|
||||
|
@ -2,15 +2,10 @@ import _ from 'lodash';
|
||||
import { promises as fs } from 'fs';
|
||||
import type { ImageInspectInfo } from 'dockerode';
|
||||
|
||||
import Network from './network';
|
||||
import Volume from './volume';
|
||||
import Service from './service';
|
||||
import { Network } from './network';
|
||||
import { Volume } from './volume';
|
||||
import { Service } from './service';
|
||||
import * as imageManager from './images';
|
||||
import type { Image } from './images';
|
||||
import type {
|
||||
CompositionStep,
|
||||
CompositionStepAction,
|
||||
} from './composition-steps';
|
||||
import { generateStep } from './composition-steps';
|
||||
import type * as targetStateCache from '../device-state/target-state-cache';
|
||||
import { getNetworkGateway } from '../lib/docker-utils';
|
||||
@ -25,7 +20,16 @@ import { checkTruthy } from '../lib/validation';
|
||||
import type { ServiceComposeConfig, DeviceMetadata } from './types/service';
|
||||
import { pathExistsOnRoot } from '../lib/host-utils';
|
||||
import { isSupervisor } from '../lib/supervisor-metadata';
|
||||
import type { LocksTakenMap } from '../lib/update-lock';
|
||||
import type {
|
||||
App as AppIface,
|
||||
UpdateState,
|
||||
AppsToLockMap,
|
||||
CompositionStep,
|
||||
CompositionStepAction,
|
||||
} from './types';
|
||||
|
||||
// Re export the type
|
||||
export type App = AppIface;
|
||||
|
||||
export interface AppConstructOpts {
|
||||
appId: number;
|
||||
@ -40,24 +44,12 @@ export interface AppConstructOpts {
|
||||
networks: Network[];
|
||||
}
|
||||
|
||||
export interface UpdateState {
|
||||
availableImages: Image[];
|
||||
containerIds: Dictionary<string>;
|
||||
downloading: string[];
|
||||
locksTaken: LocksTakenMap;
|
||||
force: boolean;
|
||||
}
|
||||
|
||||
interface ChangingPair<T> {
|
||||
current?: T;
|
||||
target?: T;
|
||||
}
|
||||
|
||||
export interface AppsToLockMap {
|
||||
[appId: number]: Set<string>;
|
||||
}
|
||||
|
||||
export class App {
|
||||
class AppImpl implements App {
|
||||
public appId: number;
|
||||
public appUuid?: string;
|
||||
// When setting up an application from current state, these values are not available
|
||||
@ -1027,7 +1019,7 @@ export class App {
|
||||
}),
|
||||
);
|
||||
|
||||
return new App(
|
||||
return new AppImpl(
|
||||
{
|
||||
appId: app.appId,
|
||||
appUuid: app.uuid,
|
||||
@ -1044,4 +1036,4 @@ export class App {
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
export const App = AppImpl;
|
||||
|
@ -20,27 +20,30 @@ import {
|
||||
import { getServicesLockedByAppId, LocksTakenMap } from '../lib/update-lock';
|
||||
import { checkTruthy } from '../lib/validation';
|
||||
|
||||
import App from './app';
|
||||
import type { UpdateState } from './app';
|
||||
import { App } from './app';
|
||||
import * as volumeManager from './volume-manager';
|
||||
import * as networkManager from './network-manager';
|
||||
import * as serviceManager from './service-manager';
|
||||
import * as imageManager from './images';
|
||||
import * as commitStore from './commit';
|
||||
import type Service from './service';
|
||||
import type Network from './network';
|
||||
import type Volume from './volume';
|
||||
import { generateStep, getExecutors } from './composition-steps';
|
||||
|
||||
import type {
|
||||
InstancedAppState,
|
||||
TargetApps,
|
||||
DeviceLegacyReport,
|
||||
AppState,
|
||||
ServiceState,
|
||||
} from '../types/state';
|
||||
import type { Image } from './images';
|
||||
import type { CompositionStep, CompositionStepT } from './composition-steps';
|
||||
} from '../types';
|
||||
import type {
|
||||
CompositionStep,
|
||||
CompositionStepT,
|
||||
UpdateState,
|
||||
Service,
|
||||
Network,
|
||||
Volume,
|
||||
Image,
|
||||
InstancedAppState,
|
||||
} from './types';
|
||||
|
||||
type ApplicationManagerEventEmitter = StrictEventEmitter<
|
||||
EventEmitter,
|
||||
|
@ -1,104 +1,19 @@
|
||||
import * as config from '../config';
|
||||
import type { Image } from './images';
|
||||
import type { CompositionStepArgs, Image, CompositionStep } from './types';
|
||||
import * as images from './images';
|
||||
import type Network from './network';
|
||||
import type Service from './service';
|
||||
import * as serviceManager from './service-manager';
|
||||
import * as networkManager from './network-manager';
|
||||
import * as volumeManager from './volume-manager';
|
||||
import type Volume from './volume';
|
||||
import * as commitStore from './commit';
|
||||
import * as updateLock from '../lib/update-lock';
|
||||
import type { DeviceLegacyReport } from '../types/state';
|
||||
import type { CompositionStepAction, CompositionStepT } from './types';
|
||||
|
||||
interface CompositionStepArgs {
|
||||
stop: {
|
||||
current: Service;
|
||||
options?: {
|
||||
wait?: boolean;
|
||||
};
|
||||
};
|
||||
kill: {
|
||||
current: Service;
|
||||
options?: {
|
||||
wait?: boolean;
|
||||
};
|
||||
};
|
||||
remove: {
|
||||
current: Service;
|
||||
};
|
||||
updateMetadata: {
|
||||
current: Service;
|
||||
target: Service;
|
||||
};
|
||||
restart: {
|
||||
current: Service;
|
||||
target: Service;
|
||||
};
|
||||
start: {
|
||||
target: Service;
|
||||
};
|
||||
updateCommit: {
|
||||
target: string;
|
||||
appId: number;
|
||||
};
|
||||
handover: {
|
||||
current: Service;
|
||||
target: Service;
|
||||
options?: {
|
||||
timeout?: number;
|
||||
};
|
||||
};
|
||||
fetch: {
|
||||
image: Image;
|
||||
serviceName: string;
|
||||
};
|
||||
removeImage: {
|
||||
image: Image;
|
||||
};
|
||||
saveImage: {
|
||||
image: Image;
|
||||
};
|
||||
cleanup: object;
|
||||
createNetwork: {
|
||||
target: Network;
|
||||
};
|
||||
createVolume: {
|
||||
target: Volume;
|
||||
};
|
||||
removeNetwork: {
|
||||
current: Network;
|
||||
};
|
||||
removeVolume: {
|
||||
current: Volume;
|
||||
};
|
||||
ensureSupervisorNetwork: object;
|
||||
noop: object;
|
||||
takeLock: {
|
||||
appId: number;
|
||||
services: string[];
|
||||
force: boolean;
|
||||
};
|
||||
releaseLock: {
|
||||
appId: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type CompositionStepAction = keyof CompositionStepArgs;
|
||||
export type CompositionStepT<T extends CompositionStepAction> = {
|
||||
action: T;
|
||||
} & CompositionStepArgs[T];
|
||||
export type CompositionStep = CompositionStepT<CompositionStepAction>;
|
||||
|
||||
export function generateStep<T extends CompositionStepAction>(
|
||||
action: T,
|
||||
args: CompositionStepArgs[T],
|
||||
): CompositionStep {
|
||||
return {
|
||||
action,
|
||||
...args,
|
||||
};
|
||||
}
|
||||
export type {
|
||||
CompositionStep,
|
||||
CompositionStepT,
|
||||
CompositionStepAction,
|
||||
} from './types';
|
||||
|
||||
type Executors<T extends CompositionStepAction> = {
|
||||
[key in T]: (step: CompositionStepT<key>) => Promise<unknown>;
|
||||
@ -114,6 +29,16 @@ interface CompositionCallbacks {
|
||||
bestDeltaSource: (image: Image, available: Image[]) => string | null;
|
||||
}
|
||||
|
||||
export function generateStep<T extends CompositionStepAction>(
|
||||
action: T,
|
||||
args: CompositionStepArgs[T],
|
||||
): CompositionStep {
|
||||
return {
|
||||
action,
|
||||
...args,
|
||||
};
|
||||
}
|
||||
|
||||
export function getExecutors(app: { callbacks: CompositionCallbacks }) {
|
||||
const executors: Executors<CompositionStepAction> = {
|
||||
stop: async (step) => {
|
||||
|
@ -24,40 +24,13 @@ import { strict as assert } from 'assert';
|
||||
import log from '../lib/supervisor-console';
|
||||
import { setTimeout } from 'timers/promises';
|
||||
|
||||
import type { Image } from './types';
|
||||
export type { Image } from './types';
|
||||
|
||||
interface FetchProgressEvent {
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
id?: number;
|
||||
/**
|
||||
* image [registry/]repo@digest or [registry/]repo:tag
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @deprecated to be removed in target state v4
|
||||
*/
|
||||
appId: number;
|
||||
appUuid: string;
|
||||
/**
|
||||
* @deprecated to be removed in target state v4
|
||||
*/
|
||||
serviceId: number;
|
||||
serviceName: string;
|
||||
/**
|
||||
* @deprecated to be removed in target state v4
|
||||
*/
|
||||
imageId: number;
|
||||
/**
|
||||
* @deprecated to be removed in target state v4
|
||||
*/
|
||||
releaseId: number;
|
||||
commit: string;
|
||||
dockerImageId?: string;
|
||||
status?: 'Downloading' | 'Downloaded' | 'Deleting';
|
||||
downloadProgress?: number | null;
|
||||
}
|
||||
|
||||
// Setup an event emitter
|
||||
interface ImageEvents {
|
||||
change: void;
|
||||
|
@ -11,12 +11,15 @@ import type {
|
||||
ComposeNetworkConfig,
|
||||
NetworkConfig,
|
||||
NetworkInspectInfo,
|
||||
} from './types/network';
|
||||
Network as NetworkIface,
|
||||
} from './types';
|
||||
|
||||
import { InvalidNetworkNameError } from './errors';
|
||||
import { InternalInconsistencyError } from '../lib/errors';
|
||||
|
||||
export class Network {
|
||||
export type Network = NetworkIface;
|
||||
|
||||
class NetworkImpl implements Network {
|
||||
public appId: number;
|
||||
public appUuid?: string;
|
||||
public name: string;
|
||||
@ -303,4 +306,4 @@ export class Network {
|
||||
}
|
||||
}
|
||||
|
||||
export default Network;
|
||||
export const Network = NetworkImpl;
|
||||
|
@ -20,8 +20,8 @@ import {
|
||||
} from '../lib/errors';
|
||||
import * as LogTypes from '../lib/log-types';
|
||||
import { checkInt, isValidDeviceName } from '../lib/validation';
|
||||
import type { ServiceStatus } from './service';
|
||||
import { Service } from './service';
|
||||
import type { ServiceStatus } from './types';
|
||||
import { serviceNetworksToDockerNetworks } from './utils';
|
||||
|
||||
import log from '../lib/supervisor-console';
|
||||
|
@ -23,7 +23,9 @@ import type {
|
||||
ConfigMap,
|
||||
DeviceMetadata,
|
||||
DockerDevice,
|
||||
} from './types/service';
|
||||
ServiceStatus,
|
||||
Service as ServiceIface,
|
||||
} from './types';
|
||||
import {
|
||||
ShortMount,
|
||||
ShortBind,
|
||||
@ -34,25 +36,16 @@ import {
|
||||
LongBind,
|
||||
LongAnonymousVolume,
|
||||
LongNamedVolume,
|
||||
} from './types/service';
|
||||
} from './types';
|
||||
|
||||
const SERVICE_NETWORK_MODE_REGEX = /service:\s*(.+)/;
|
||||
const CONTAINER_NETWORK_MODE_REGEX = /container:\s*(.+)/;
|
||||
|
||||
const unsupportedSecurityOpt = (opt: string) => /label=.*/.test(opt);
|
||||
|
||||
export type ServiceStatus =
|
||||
| 'Stopping'
|
||||
| 'Running'
|
||||
| 'Installing'
|
||||
| 'Installed'
|
||||
| 'Dead'
|
||||
| 'paused'
|
||||
| 'restarting'
|
||||
| 'removing'
|
||||
| 'exited';
|
||||
export type Service = ServiceIface;
|
||||
|
||||
export class Service {
|
||||
class ServiceImpl implements Service {
|
||||
public appId: number;
|
||||
public appUuid?: string;
|
||||
public imageId: number;
|
||||
@ -64,17 +57,8 @@ export class Service {
|
||||
public imageName: string | null;
|
||||
public containerId: string | null;
|
||||
public exitErrorMessage: string | null;
|
||||
|
||||
public dependsOn: string[] | null;
|
||||
|
||||
public dockerImageId: string | null;
|
||||
|
||||
// This looks weird, and it is. The lowercase statuses come from Docker,
|
||||
// except the dashboard takes these values and displays them on the dashboard.
|
||||
// What we should be doin is defining these container statuses, and have the
|
||||
// dashboard make these human readable instead. Until that happens we have
|
||||
// this halfways state of some captalised statuses, and others coming directly
|
||||
// from docker
|
||||
public status: ServiceStatus;
|
||||
public createdAt: Date | null;
|
||||
|
||||
@ -95,7 +79,7 @@ export class Service {
|
||||
'dnsSearch',
|
||||
];
|
||||
public static allConfigArrayFields: ServiceConfigArrayField[] =
|
||||
Service.configArrayFields.concat(Service.orderedConfigArrayFields);
|
||||
ServiceImpl.configArrayFields.concat(ServiceImpl.orderedConfigArrayFields);
|
||||
|
||||
// A list of fields to ignore when comparing container configuration
|
||||
private static omitFields = [
|
||||
@ -109,7 +93,7 @@ export class Service {
|
||||
// These fields are special case, due to network_mode:service:<service>
|
||||
'networkMode',
|
||||
'hostname',
|
||||
].concat(Service.allConfigArrayFields);
|
||||
].concat(ServiceImpl.allConfigArrayFields);
|
||||
|
||||
private constructor() {
|
||||
/* do not allow instancing a service object with `new` */
|
||||
@ -1170,4 +1154,4 @@ export class Service {
|
||||
}
|
||||
}
|
||||
|
||||
export default Service;
|
||||
export const Service = ServiceImpl;
|
||||
|
38
src/compose/types/app.ts
Normal file
38
src/compose/types/app.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import type { Network } from './network';
|
||||
import type { Volume } from './volume';
|
||||
import type { Service } from './service';
|
||||
import type { LocksTakenMap } from '../../lib/update-lock';
|
||||
import type { Image } from './image';
|
||||
import type { CompositionStep } from './composition-step';
|
||||
|
||||
export interface UpdateState {
|
||||
availableImages: Image[];
|
||||
containerIds: Dictionary<string>;
|
||||
downloading: string[];
|
||||
locksTaken: LocksTakenMap;
|
||||
force: boolean;
|
||||
}
|
||||
|
||||
export interface App {
|
||||
appId: number;
|
||||
appUuid?: string;
|
||||
// When setting up an application from current state, these values are not available
|
||||
appName?: string;
|
||||
commit?: string;
|
||||
source?: string;
|
||||
isHost?: boolean;
|
||||
// Services are stored as an array, as at any one time we could have more than one
|
||||
// service for a single service ID running (for example handover)
|
||||
services: Service[];
|
||||
networks: Network[];
|
||||
volumes: Volume[];
|
||||
|
||||
nextStepsForAppUpdate(state: UpdateState, target: App): CompositionStep[];
|
||||
stepsToRemoveApp(
|
||||
state: Omit<UpdateState, 'availableImages'> & { keepVolumes: boolean },
|
||||
): CompositionStep[];
|
||||
}
|
||||
|
||||
export interface AppsToLockMap {
|
||||
[appId: number]: Set<string>;
|
||||
}
|
3
src/compose/types/application-manager.ts
Normal file
3
src/compose/types/application-manager.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import type { App } from './app';
|
||||
|
||||
export type InstancedAppState = { [appId: number]: App };
|
83
src/compose/types/composition-step.ts
Normal file
83
src/compose/types/composition-step.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import type { Image } from './image';
|
||||
import type { Service } from './service';
|
||||
import type { Network } from './network';
|
||||
import type { Volume } from './volume';
|
||||
|
||||
export interface CompositionStepArgs {
|
||||
stop: {
|
||||
current: Service;
|
||||
options?: {
|
||||
wait?: boolean;
|
||||
};
|
||||
};
|
||||
kill: {
|
||||
current: Service;
|
||||
options?: {
|
||||
wait?: boolean;
|
||||
};
|
||||
};
|
||||
remove: {
|
||||
current: Service;
|
||||
};
|
||||
updateMetadata: {
|
||||
current: Service;
|
||||
target: Service;
|
||||
};
|
||||
restart: {
|
||||
current: Service;
|
||||
target: Service;
|
||||
};
|
||||
start: {
|
||||
target: Service;
|
||||
};
|
||||
updateCommit: {
|
||||
target: string;
|
||||
appId: number;
|
||||
};
|
||||
handover: {
|
||||
current: Service;
|
||||
target: Service;
|
||||
options?: {
|
||||
timeout?: number;
|
||||
};
|
||||
};
|
||||
fetch: {
|
||||
image: Image;
|
||||
serviceName: string;
|
||||
};
|
||||
removeImage: {
|
||||
image: Image;
|
||||
};
|
||||
saveImage: {
|
||||
image: Image;
|
||||
};
|
||||
cleanup: object;
|
||||
createNetwork: {
|
||||
target: Network;
|
||||
};
|
||||
createVolume: {
|
||||
target: Volume;
|
||||
};
|
||||
removeNetwork: {
|
||||
current: Network;
|
||||
};
|
||||
removeVolume: {
|
||||
current: Volume;
|
||||
};
|
||||
ensureSupervisorNetwork: object;
|
||||
noop: object;
|
||||
takeLock: {
|
||||
appId: number;
|
||||
services: string[];
|
||||
force: boolean;
|
||||
};
|
||||
releaseLock: {
|
||||
appId: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type CompositionStepAction = keyof CompositionStepArgs;
|
||||
export type CompositionStepT<T extends CompositionStepAction> = {
|
||||
action: T;
|
||||
} & CompositionStepArgs[T];
|
||||
export type CompositionStep = CompositionStepT<CompositionStepAction>;
|
29
src/compose/types/image.ts
Normal file
29
src/compose/types/image.ts
Normal file
@ -0,0 +1,29 @@
|
||||
export interface Image {
|
||||
id?: number;
|
||||
/**
|
||||
* image [registry/]repo@digest or [registry/]repo:tag
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @deprecated to be removed in target state v4
|
||||
*/
|
||||
appId: number;
|
||||
appUuid: string;
|
||||
/**
|
||||
* @deprecated to be removed in target state v4
|
||||
*/
|
||||
serviceId: number;
|
||||
serviceName: string;
|
||||
/**
|
||||
* @deprecated to be removed in target state v4
|
||||
*/
|
||||
imageId: number;
|
||||
/**
|
||||
* @deprecated to be removed in target state v4
|
||||
*/
|
||||
releaseId: number;
|
||||
commit: string;
|
||||
dockerImageId?: string;
|
||||
status?: 'Downloading' | 'Downloaded' | 'Deleting';
|
||||
downloadProgress?: number | null;
|
||||
}
|
7
src/compose/types/index.ts
Normal file
7
src/compose/types/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export * from './service';
|
||||
export * from './network';
|
||||
export type * from './volume';
|
||||
export type * from './image';
|
||||
export type * from './composition-step';
|
||||
export type * from './app';
|
||||
export type * from './application-manager';
|
@ -1,31 +1,16 @@
|
||||
import type { NetworkInspectInfo as DockerNetworkInspectInfo } from 'dockerode';
|
||||
import type {
|
||||
NetworkInspectInfo as DockerNetworkInspectInfo,
|
||||
NetworkCreateOptions,
|
||||
} from 'dockerode';
|
||||
|
||||
// TODO: ConfigOnly is part of @types/dockerode@v3.2.0, but that version isn't
|
||||
// compatible with `resin-docker-build` which is used for `npm run sync`.
|
||||
export interface NetworkInspectInfo extends DockerNetworkInspectInfo {
|
||||
ConfigOnly: boolean;
|
||||
}
|
||||
import type { ComposeNetworkConfig } from '../../types';
|
||||
export type { ComposeNetworkConfig } from '../../types';
|
||||
|
||||
export interface ComposeNetworkConfig {
|
||||
driver: string;
|
||||
driver_opts: Dictionary<string>;
|
||||
ipam: {
|
||||
driver: string;
|
||||
config: Array<
|
||||
Partial<{
|
||||
subnet: string;
|
||||
ip_range: string;
|
||||
gateway: string;
|
||||
aux_addresses: Dictionary<string>;
|
||||
}>
|
||||
>;
|
||||
options: Dictionary<string>;
|
||||
};
|
||||
enable_ipv6: boolean;
|
||||
internal: boolean;
|
||||
labels: Dictionary<string>;
|
||||
config_only: boolean;
|
||||
}
|
||||
export interface NetworkConfig {
|
||||
driver: string;
|
||||
ipam: {
|
||||
@ -44,3 +29,18 @@ export interface NetworkConfig {
|
||||
options: { [optName: string]: string };
|
||||
configOnly: boolean;
|
||||
}
|
||||
|
||||
export interface Network {
|
||||
appId: number;
|
||||
appUuid?: string;
|
||||
name: string;
|
||||
config: NetworkConfig;
|
||||
|
||||
isEqualConfig(network: Network): boolean;
|
||||
create(): Promise<void>;
|
||||
remove(): Promise<void>;
|
||||
toDockerConfig(): NetworkCreateOptions & {
|
||||
ConfigOnly: boolean;
|
||||
};
|
||||
toComposeObject(): ComposeNetworkConfig;
|
||||
}
|
||||
|
@ -336,3 +336,58 @@ export interface DockerDevice {
|
||||
PathInContainer: string;
|
||||
CgroupPermissions: string;
|
||||
}
|
||||
|
||||
export type ServiceStatus =
|
||||
| 'Stopping'
|
||||
| 'Running'
|
||||
| 'Installing'
|
||||
| 'Installed'
|
||||
| 'Dead'
|
||||
| 'paused'
|
||||
| 'restarting'
|
||||
| 'removing'
|
||||
| 'exited';
|
||||
|
||||
export interface Service {
|
||||
appId: number;
|
||||
appUuid?: string;
|
||||
imageId: number;
|
||||
config: ServiceConfig;
|
||||
serviceName: string;
|
||||
commit: string;
|
||||
releaseId: number;
|
||||
serviceId: number;
|
||||
imageName: string | null;
|
||||
containerId: string | null;
|
||||
exitErrorMessage: string | null;
|
||||
|
||||
dependsOn: string[] | null;
|
||||
|
||||
dockerImageId: string | null;
|
||||
// This looks weird, and it is. The lowercase statuses come from Docker,
|
||||
// except the dashboard takes these values and displays them on the dashboard.
|
||||
// What we should be doin is defining these container statuses, and have the
|
||||
// dashboard make these human readable instead. Until that happens we have
|
||||
// this halfways state of some captalised statuses, and others coming directly
|
||||
// from docker
|
||||
status: ServiceStatus;
|
||||
createdAt: Date | null;
|
||||
|
||||
hasNetwork(networkName: string): boolean;
|
||||
hasVolume(volumeName: string): boolean;
|
||||
isEqualExceptForRunningState(
|
||||
service: Service,
|
||||
currentContainerIds: Dictionary<string>,
|
||||
): boolean;
|
||||
isEqualConfig(
|
||||
service: Service,
|
||||
currentContainerIds: Dictionary<string>,
|
||||
): boolean;
|
||||
hasNetworkMode(networkName: string): boolean;
|
||||
extraNetworksToJoin(): ServiceConfig['networks'];
|
||||
toDockerContainer(opts: {
|
||||
deviceName: string;
|
||||
containerIds: Dictionary<string>;
|
||||
}): Dockerode.ContainerCreateOptions;
|
||||
handoverCompleteFullPathsOnHost(): string[];
|
||||
}
|
||||
|
20
src/compose/types/volume.ts
Normal file
20
src/compose/types/volume.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { LabelObject } from '../../types';
|
||||
import type { VolumeInspectInfo } from 'dockerode';
|
||||
export type { ComposeVolumeConfig } from '../../types';
|
||||
|
||||
export interface VolumeConfig {
|
||||
labels: LabelObject;
|
||||
driver: string;
|
||||
driverOpts: VolumeInspectInfo['Options'];
|
||||
}
|
||||
|
||||
export interface Volume {
|
||||
name: string;
|
||||
appId: number;
|
||||
appUuid: string;
|
||||
config: VolumeConfig;
|
||||
|
||||
isEqualConfig(volume: Volume): boolean;
|
||||
create(): Promise<void>;
|
||||
remove(): Promise<void>;
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import * as imageManager from './images';
|
||||
import type Service from './service';
|
||||
import type { Service } from './service';
|
||||
import type { CompositionStep } from './composition-steps';
|
||||
import { generateStep } from './composition-steps';
|
||||
import type { AppsToLockMap } from './app';
|
||||
import type { AppsToLockMap } from './types';
|
||||
import { InternalInconsistencyError } from '../lib/errors';
|
||||
import { checkString } from '../lib/validation';
|
||||
|
||||
|
@ -5,7 +5,6 @@ import { parse as parseCommand } from 'shell-quote';
|
||||
|
||||
import * as constants from '../lib/constants';
|
||||
import { checkTruthy } from '../lib/validation';
|
||||
import type { Service } from './service';
|
||||
import type {
|
||||
ComposeHealthcheck,
|
||||
ConfigMap,
|
||||
@ -16,11 +15,12 @@ import type {
|
||||
ServiceHealthcheck,
|
||||
LongDefinition,
|
||||
LongBind,
|
||||
} from './types/service';
|
||||
Service,
|
||||
} from './types';
|
||||
|
||||
import log from '../lib/supervisor-console';
|
||||
|
||||
import * as deviceApi from '../device-api';
|
||||
import * as apiKeys from '../lib/api-keys';
|
||||
|
||||
export function camelCaseConfig(
|
||||
literalConfig: ConfigMap,
|
||||
@ -369,7 +369,7 @@ export async function addFeaturesFromLabels(
|
||||
},
|
||||
'io.balena.features.supervisor-api': async () => {
|
||||
// create a app/service specific API secret
|
||||
const apiSecret = await deviceApi.generateScopedKey(
|
||||
const apiSecret = await apiKeys.generateScopedKey(
|
||||
service.appId,
|
||||
service.serviceName,
|
||||
);
|
||||
|
@ -10,8 +10,8 @@ import * as LogTypes from '../lib/log-types';
|
||||
import log from '../lib/supervisor-console';
|
||||
import * as logger from '../logger';
|
||||
import { ResourceRecreationAttemptError } from './errors';
|
||||
import type { VolumeConfig } from './volume';
|
||||
import Volume from './volume';
|
||||
import type { VolumeConfig } from './types';
|
||||
import { Volume } from './volume';
|
||||
|
||||
export interface VolumeNameOpts {
|
||||
name: string;
|
||||
|
@ -10,19 +10,15 @@ import type { LabelObject } from '../types';
|
||||
import * as logger from '../logger';
|
||||
import * as ComposeUtils from './utils';
|
||||
|
||||
export interface VolumeConfig {
|
||||
labels: LabelObject;
|
||||
driver: string;
|
||||
driverOpts: Docker.VolumeInspectInfo['Options'];
|
||||
}
|
||||
import type {
|
||||
Volume as VolumeIface,
|
||||
VolumeConfig,
|
||||
ComposeVolumeConfig,
|
||||
} from './types';
|
||||
|
||||
export interface ComposeVolumeConfig {
|
||||
driver: string;
|
||||
driver_opts: Dictionary<string>;
|
||||
labels: LabelObject;
|
||||
}
|
||||
export type Volume = VolumeIface;
|
||||
|
||||
export class Volume {
|
||||
class VolumeImpl implements Volume {
|
||||
private constructor(
|
||||
public name: string,
|
||||
public appId: number,
|
||||
@ -162,4 +158,4 @@ export class Volume {
|
||||
}
|
||||
}
|
||||
|
||||
export default Volume;
|
||||
export const Volume = VolumeImpl;
|
||||
|
@ -31,8 +31,13 @@ export const fnSchema = {
|
||||
osVersion: () => {
|
||||
return osRelease.getOSVersion(constants.hostOSVersionPath);
|
||||
},
|
||||
osVariant: () => {
|
||||
return osRelease.getOSVariant(constants.hostOSVersionPath);
|
||||
osVariant: async () => {
|
||||
const osVariant = await osRelease.getOSVariant(constants.hostOSVersionPath);
|
||||
if (osVariant === undefined) {
|
||||
const developmentMode = await config.get('developmentMode');
|
||||
return developmentMode === true ? 'dev' : 'prod';
|
||||
}
|
||||
return osVariant;
|
||||
},
|
||||
macAddress: () => {
|
||||
return macAddress.getAll(constants.macAddressPath);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { getGlobalApiKey, refreshKey } from '.';
|
||||
import { getGlobalApiKey, refreshKey } from '../lib/api-keys';
|
||||
import * as messages from './messages';
|
||||
import * as eventTracker from '../event-tracker';
|
||||
import * as deviceState from '../device-state';
|
||||
@ -11,7 +11,7 @@ import * as applicationManager from '../compose/application-manager';
|
||||
import type { CompositionStepAction } from '../compose/composition-steps';
|
||||
import { generateStep } from '../compose/composition-steps';
|
||||
import * as commitStore from '../compose/commit';
|
||||
import type Service from '../compose/service';
|
||||
import type { Service } from '../compose/service';
|
||||
import { getApp } from '../device-state/db-format';
|
||||
import * as TargetState from '../device-state/target-state';
|
||||
import log from '../lib/supervisor-console';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import express from 'express';
|
||||
|
||||
import * as middleware from './middleware';
|
||||
import * as apiKeys from './api-keys';
|
||||
import type * as apiKeys from '../lib/api-keys';
|
||||
import * as actions from './actions';
|
||||
import log from '../lib/supervisor-console';
|
||||
|
||||
@ -15,10 +15,6 @@ interface SupervisorAPIConstructOpts {
|
||||
// API key methods
|
||||
// For better black boxing, device-api should serve as the interface
|
||||
// to the rest of the Supervisor code for accessing API key related methods.
|
||||
export const getGlobalApiKey = apiKeys.getGlobalApiKey;
|
||||
export const refreshKey = apiKeys.refreshKey;
|
||||
export const generateScopedKey = apiKeys.generateScopedKey;
|
||||
export const getScopesForKey = apiKeys.getScopesForKey;
|
||||
|
||||
export class SupervisorAPI {
|
||||
private routers: express.Router[];
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as apiKeys from '../api-keys';
|
||||
import * as apiKeys from '../../lib/api-keys';
|
||||
import * as config from '../../config';
|
||||
|
||||
import type { Request } from 'express';
|
||||
|
@ -2,7 +2,7 @@ import express from 'express';
|
||||
import type { Response } from 'express';
|
||||
|
||||
import * as actions from './actions';
|
||||
import type { AuthorizedRequest } from './api-keys';
|
||||
import type { AuthorizedRequest } from '../lib/api-keys';
|
||||
import * as eventTracker from '../event-tracker';
|
||||
import type * as deviceState from '../device-state';
|
||||
|
||||
|
@ -7,7 +7,7 @@ import * as apiBinder from '../api-binder';
|
||||
import * as applicationManager from '../compose/application-manager';
|
||||
import type { CompositionStepAction } from '../compose/composition-steps';
|
||||
import type { Service } from '../compose/service';
|
||||
import Volume from '../compose/volume';
|
||||
import { Volume } from '../compose/volume';
|
||||
import * as commitStore from '../compose/commit';
|
||||
import * as config from '../config';
|
||||
import * as db from '../db';
|
||||
@ -26,7 +26,7 @@ import {
|
||||
BadRequestError,
|
||||
} from '../lib/errors';
|
||||
import { isVPNActive } from '../network';
|
||||
import type { AuthorizedRequest } from './api-keys';
|
||||
import type { AuthorizedRequest } from '../lib/api-keys';
|
||||
import { fromV2TargetState } from '../lib/legacy';
|
||||
import * as actions from './actions';
|
||||
import { v2ServiceEndpointError } from './messages';
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
import * as updateLock from './lib/update-lock';
|
||||
import { takeGlobalLockRO, takeGlobalLockRW } from './lib/process-lock';
|
||||
import * as dbFormat from './device-state/db-format';
|
||||
import { getGlobalApiKey } from './device-api';
|
||||
import { getGlobalApiKey } from './lib/api-keys';
|
||||
import * as sysInfo from './lib/system-info';
|
||||
import { log } from './lib/supervisor-console';
|
||||
import { loadTargetFromFile } from './device-state/preload';
|
||||
@ -34,7 +34,6 @@ import * as commitStore from './compose/commit';
|
||||
|
||||
import type {
|
||||
DeviceLegacyState,
|
||||
InstancedDeviceState,
|
||||
DeviceState,
|
||||
DeviceReport,
|
||||
AppState,
|
||||
@ -47,11 +46,20 @@ import type {
|
||||
import * as fsUtils from './lib/fs-utils';
|
||||
import { pathOnRoot } from './lib/host-utils';
|
||||
import { setTimeout } from 'timers/promises';
|
||||
import type { InstancedAppState } from './compose/types';
|
||||
|
||||
const TARGET_STATE_CONFIG_DUMP = pathOnRoot(
|
||||
'/tmp/balena-supervisor/target-state-config',
|
||||
);
|
||||
|
||||
interface InstancedDeviceState {
|
||||
local: {
|
||||
name: string;
|
||||
config: Dictionary<string>;
|
||||
apps: InstancedAppState;
|
||||
};
|
||||
}
|
||||
|
||||
function parseTargetState(state: unknown): TargetState {
|
||||
const res = TargetState.decode(state);
|
||||
|
||||
|
@ -4,16 +4,16 @@ import type * as db from '../db';
|
||||
import * as targetStateCache from './target-state-cache';
|
||||
import type { DatabaseApp, DatabaseService } from './target-state-cache';
|
||||
|
||||
import App from '../compose/app';
|
||||
import { App } from '../compose/app';
|
||||
import * as images from '../compose/images';
|
||||
|
||||
import type {
|
||||
InstancedAppState,
|
||||
TargetApp,
|
||||
TargetApps,
|
||||
TargetRelease,
|
||||
TargetService,
|
||||
} from '../types/state';
|
||||
import type { InstancedAppState } from '../compose/types';
|
||||
|
||||
type InstancedApp = InstancedAppState[0];
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { PinejsClientRequest } from 'pinejs-client-request';
|
||||
import { PinejsClientRequest } from 'pinejs-client-request';
|
||||
|
||||
import Bluebird from 'bluebird';
|
||||
import * as config from '../config';
|
||||
@ -14,6 +14,8 @@ import {
|
||||
isHttpConflictError,
|
||||
} from './errors';
|
||||
import log from './supervisor-console';
|
||||
import memoizee from 'memoizee';
|
||||
import url from 'url';
|
||||
|
||||
export type KeyExchangeOpts = config.ConfigType<'provisioningOptions'>;
|
||||
|
||||
@ -23,6 +25,29 @@ export interface Device {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export const getBalenaApi = memoizee(
|
||||
async () => {
|
||||
await config.initialized();
|
||||
|
||||
const { apiEndpoint, currentApiKey } = await config.getMany([
|
||||
'apiEndpoint',
|
||||
'currentApiKey',
|
||||
]);
|
||||
|
||||
const baseUrl = url.resolve(apiEndpoint, '/v6/');
|
||||
const passthrough = structuredClone(await request.getRequestOptions());
|
||||
passthrough.headers =
|
||||
passthrough.headers != null ? passthrough.headers : {};
|
||||
passthrough.headers.Authorization = `Bearer ${currentApiKey}`;
|
||||
log.info(`API Binder bound to: ${baseUrl}`);
|
||||
return new PinejsClientRequest({
|
||||
apiPrefix: baseUrl,
|
||||
passthrough,
|
||||
});
|
||||
},
|
||||
{ promise: true },
|
||||
);
|
||||
|
||||
export const fetchDevice = async (
|
||||
balenaApi: PinejsClientRequest,
|
||||
uuid: string,
|
||||
|
@ -3,14 +3,13 @@ import * as t from 'io-ts';
|
||||
import Reporter from 'io-ts-reporters';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Blueprint, Contract, ContractObject } from '@balena/contrato';
|
||||
import type { ContractObject } from '@balena/contrato';
|
||||
import { Blueprint, Contract } from '@balena/contrato';
|
||||
|
||||
import { ContractValidationError, InternalInconsistencyError } from './errors';
|
||||
import { checkTruthy } from './validation';
|
||||
import type { TargetApps } from '../types';
|
||||
|
||||
export { ContractObject };
|
||||
|
||||
export interface ApplicationContractResult {
|
||||
valid: boolean;
|
||||
unmetServices: string[];
|
||||
|
@ -1,5 +1,4 @@
|
||||
import * as path from 'path';
|
||||
import * as apiBinder from '../api-binder';
|
||||
import * as config from '../config';
|
||||
import * as db from '../db';
|
||||
import * as volumeManager from '../compose/volume-manager';
|
||||
@ -14,12 +13,13 @@ import {
|
||||
import { docker } from './docker-utils';
|
||||
import { log } from './supervisor-console';
|
||||
import { pathOnData } from './host-utils';
|
||||
import type Volume from '../compose/volume';
|
||||
import type { Volume } from '../compose/volume';
|
||||
import * as logger from '../logger';
|
||||
import type {
|
||||
DatabaseApp,
|
||||
DatabaseService,
|
||||
} from '../device-state/target-state-cache';
|
||||
import { getBalenaApi } from '../lib/api-helper';
|
||||
|
||||
import type { TargetApp, TargetApps, TargetState } from '../types';
|
||||
|
||||
@ -54,13 +54,7 @@ async function createVolumeFromLegacyData(
|
||||
* Gets proper database ids from the cloud for the app and app services
|
||||
*/
|
||||
export async function normaliseLegacyDatabase() {
|
||||
await apiBinder.initialized();
|
||||
|
||||
if (apiBinder.balenaApi == null) {
|
||||
throw new InternalInconsistencyError(
|
||||
'API binder is not initialized correctly',
|
||||
);
|
||||
}
|
||||
const balenaApi = await getBalenaApi();
|
||||
|
||||
// When legacy apps are present, we kill their containers and migrate their /data to a named volume
|
||||
log.info('Migrating ids for legacy app...');
|
||||
@ -97,7 +91,7 @@ export async function normaliseLegacyDatabase() {
|
||||
}
|
||||
|
||||
log.debug(`Getting release ${app.commit} for app ${app.appId} from API`);
|
||||
const releases = await apiBinder.balenaApi.get({
|
||||
const releases = await balenaApi.get({
|
||||
resource: 'release',
|
||||
options: {
|
||||
$filter: {
|
||||
@ -266,15 +260,7 @@ export async function fromV2TargetState(
|
||||
* @param appId
|
||||
*/
|
||||
const getUUIDFromAPI = async (appId: number) => {
|
||||
await apiBinder.initialized();
|
||||
|
||||
if (apiBinder.balenaApi == null) {
|
||||
throw new InternalInconsistencyError(
|
||||
'API binder is not initialized correctly',
|
||||
);
|
||||
}
|
||||
|
||||
const { balenaApi } = apiBinder;
|
||||
const balenaApi = await getBalenaApi();
|
||||
|
||||
const appDetails = await balenaApi.get({
|
||||
resource: 'application',
|
||||
|
@ -4,7 +4,6 @@ import { promises as fs } from 'fs';
|
||||
import { InternalInconsistencyError } from './errors';
|
||||
import { exec } from './fs-utils';
|
||||
import log from './supervisor-console';
|
||||
import * as conf from '../config';
|
||||
|
||||
// Retrieve the data for the OS once only per path
|
||||
const getOSReleaseData = _.memoize(
|
||||
@ -53,12 +52,15 @@ export async function getOSVersion(path: string): Promise<string | undefined> {
|
||||
return getOSReleaseField(path, 'PRETTY_NAME');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OS variant information from /etc/release
|
||||
*
|
||||
* OS variants no longer exist and this function only exists for legacy reasons
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export async function getOSVariant(path: string): Promise<string | undefined> {
|
||||
const osVariant = await getOSReleaseField(path, 'VARIANT_ID');
|
||||
if (osVariant === undefined) {
|
||||
const developmentMode = await conf.get('developmentMode');
|
||||
return developmentMode === true ? 'dev' : 'prod';
|
||||
}
|
||||
return osVariant;
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,7 @@ import Bluebird from 'bluebird';
|
||||
import once = require('lodash/once');
|
||||
import requestLib from 'request';
|
||||
import resumableRequestLib from 'resumable-request';
|
||||
|
||||
import * as constants from './constants';
|
||||
import * as osRelease from './os-release';
|
||||
import * as config from '../config';
|
||||
|
||||
import supervisorVersion = require('./supervisor-version');
|
||||
|
||||
@ -41,9 +39,12 @@ type PromisifiedRequest = typeof requestLib & {
|
||||
};
|
||||
|
||||
const getRequestInstances = once(async () => {
|
||||
await config.initialized();
|
||||
// Generate the user agents with out versions
|
||||
const osVersion = await osRelease.getOSVersion(constants.hostOSVersionPath);
|
||||
const osVariant = await osRelease.getOSVariant(constants.hostOSVersionPath);
|
||||
const { osVersion, osVariant } = await config.getMany([
|
||||
'osVersion',
|
||||
'osVariant',
|
||||
]);
|
||||
let userAgent = `Supervisor/${supervisorVersion}`;
|
||||
if (osVersion != null) {
|
||||
if (osVariant != null) {
|
||||
|
@ -58,8 +58,9 @@ export class Supervisor {
|
||||
|
||||
await deviceState.initialized();
|
||||
|
||||
const unmanaged = await config.get('unmanaged');
|
||||
logger.logSystemMessage('Supervisor starting', {}, 'Supervisor start');
|
||||
if (conf.legacyAppsPresent && apiBinder.balenaApi != null) {
|
||||
if (conf.legacyAppsPresent && !unmanaged) {
|
||||
log.info('Legacy app detected, running migration');
|
||||
await normaliseLegacyDatabase();
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
import * as t from 'io-ts';
|
||||
|
||||
// TODO: move all these exported types to ../compose/types
|
||||
import type { ComposeNetworkConfig } from '../compose/types/network';
|
||||
import type { ComposeVolumeConfig } from '../compose/volume';
|
||||
import type { ContractObject } from '../lib/contracts';
|
||||
import type { ContractObject } from '@balena/contrato';
|
||||
|
||||
import {
|
||||
DockerName,
|
||||
@ -16,7 +13,32 @@ import {
|
||||
nonEmptyRecord,
|
||||
} from './basic';
|
||||
|
||||
import type App from '../compose/app';
|
||||
export interface ComposeVolumeConfig {
|
||||
driver: string;
|
||||
driver_opts: Dictionary<string>;
|
||||
labels: LabelObject;
|
||||
}
|
||||
|
||||
export interface ComposeNetworkConfig {
|
||||
driver: string;
|
||||
driver_opts: Dictionary<string>;
|
||||
ipam: {
|
||||
driver: string;
|
||||
config: Array<
|
||||
Partial<{
|
||||
subnet: string;
|
||||
ip_range: string;
|
||||
gateway: string;
|
||||
aux_addresses: Dictionary<string>;
|
||||
}>
|
||||
>;
|
||||
options: Dictionary<string>;
|
||||
};
|
||||
enable_ipv6: boolean;
|
||||
internal: boolean;
|
||||
labels: Dictionary<string>;
|
||||
config_only: boolean;
|
||||
}
|
||||
|
||||
export type DeviceLegacyReport = Partial<{
|
||||
api_port: number;
|
||||
@ -356,13 +378,3 @@ export const AppsJsonFormat = t.intersection([
|
||||
t.partial({ pinDevice: t.boolean }),
|
||||
]);
|
||||
export type AppsJsonFormat = t.TypeOf<typeof AppsJsonFormat>;
|
||||
|
||||
export type InstancedAppState = { [appId: number]: App };
|
||||
|
||||
export interface InstancedDeviceState {
|
||||
local: {
|
||||
name: string;
|
||||
config: Dictionary<string>;
|
||||
apps: InstancedAppState;
|
||||
};
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import Docker from 'dockerode';
|
||||
import * as applicationManager from '~/src/compose/application-manager';
|
||||
import * as imageManager from '~/src/compose/images';
|
||||
import * as serviceManager from '~/src/compose/service-manager';
|
||||
import Network from '~/src/compose/network';
|
||||
import { Network } from '~/src/compose/network';
|
||||
import * as networkManager from '~/src/compose/network-manager';
|
||||
import Volume from '~/src/compose/volume';
|
||||
import { Volume } from '~/src/compose/volume';
|
||||
import * as config from '~/src/config';
|
||||
import { LocksTakenMap } from '~/lib/update-lock';
|
||||
import { createDockerImage } from '~/test-lib/docker-helper';
|
||||
@ -19,7 +19,7 @@ import {
|
||||
expectSteps,
|
||||
expectNoStep,
|
||||
} from '~/test-lib/state-helper';
|
||||
import type { InstancedAppState } from '~/src/types';
|
||||
import type { InstancedAppState } from '~/src/compose/types';
|
||||
|
||||
// TODO: application manager inferNextSteps still queries some stuff from
|
||||
// the engine instead of receiving that information as parameter. Refactoring
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { expect } from 'chai';
|
||||
|
||||
import Service from '~/src/compose/service';
|
||||
import * as deviceApi from '~/src/device-api';
|
||||
import { Service } from '~/src/compose/service';
|
||||
import * as apiKeys from '~/lib/api-keys';
|
||||
|
||||
describe('compose/service: integration tests', () => {
|
||||
describe('Feature labels', () => {
|
||||
@ -41,7 +41,7 @@ describe('compose/service: integration tests', () => {
|
||||
});
|
||||
|
||||
it('sets BALENA_API_KEY env var to the scoped API key value', async () => {
|
||||
const mykey = await deviceApi.generateScopedKey(123456, 'foobar');
|
||||
const mykey = await apiKeys.generateScopedKey(123456, 'foobar');
|
||||
|
||||
const service = await Service.fromComposeObject(
|
||||
{
|
||||
|
@ -2,7 +2,7 @@ import { expect } from 'chai';
|
||||
|
||||
import * as sinon from 'sinon';
|
||||
import * as volumeManager from '~/src/compose/volume-manager';
|
||||
import Volume from '~/src/compose/volume';
|
||||
import { Volume } from '~/src/compose/volume';
|
||||
import { createDockerImage } from '~/test-lib/docker-helper';
|
||||
|
||||
import Docker from 'dockerode';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { expect } from 'chai';
|
||||
import type { SinonStub } from 'sinon';
|
||||
import { stub } from 'sinon';
|
||||
import Volume from '~/src/compose/volume';
|
||||
import { Volume } from '~/src/compose/volume';
|
||||
import * as logTypes from '~/lib/log-types';
|
||||
import * as logger from '~/src/logger';
|
||||
|
||||
|
@ -9,7 +9,7 @@ import { testfs } from 'mocha-pod';
|
||||
import * as deviceState from '~/src/device-state';
|
||||
import * as config from '~/src/config';
|
||||
import * as hostConfig from '~/src/host-config';
|
||||
import * as deviceApi from '~/src/device-api';
|
||||
import * as apiKeys from '~/lib/api-keys';
|
||||
import * as actions from '~/src/device-api/actions';
|
||||
import * as TargetState from '~/src/device-state/target-state';
|
||||
import * as updateLock from '~/lib/update-lock';
|
||||
@ -56,10 +56,10 @@ describe('regenerates API keys', () => {
|
||||
afterEach(() => (deviceState.reportCurrentState as SinonStub).restore());
|
||||
|
||||
it("communicates new key to cloud if it's a global key", async () => {
|
||||
const originalGlobalKey = await deviceApi.getGlobalApiKey();
|
||||
const originalGlobalKey = await apiKeys.getGlobalApiKey();
|
||||
const newKey = await actions.regenerateKey(originalGlobalKey);
|
||||
expect(originalGlobalKey).to.not.equal(newKey);
|
||||
expect(newKey).to.equal(await deviceApi.getGlobalApiKey());
|
||||
expect(newKey).to.equal(await apiKeys.getGlobalApiKey());
|
||||
expect(deviceState.reportCurrentState as SinonStub).to.have.been.calledOnce;
|
||||
expect(
|
||||
(deviceState.reportCurrentState as SinonStub).firstCall.args[0],
|
||||
@ -69,10 +69,10 @@ describe('regenerates API keys', () => {
|
||||
});
|
||||
|
||||
it("doesn't communicate new key if it's a service key", async () => {
|
||||
const originalScopedKey = await deviceApi.generateScopedKey(111, 'main');
|
||||
const originalScopedKey = await apiKeys.generateScopedKey(111, 'main');
|
||||
const newKey = await actions.regenerateKey(originalScopedKey);
|
||||
expect(originalScopedKey).to.not.equal(newKey);
|
||||
expect(newKey).to.not.equal(await deviceApi.getGlobalApiKey());
|
||||
expect(newKey).to.not.equal(await apiKeys.getGlobalApiKey());
|
||||
expect(deviceState.reportCurrentState as SinonStub).to.not.have.been.called;
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type * as express from 'express';
|
||||
import request from 'supertest';
|
||||
|
||||
import * as apiKeys from '~/lib/api-keys';
|
||||
import * as deviceApi from '~/src/device-api';
|
||||
|
||||
describe('device-api/index', () => {
|
||||
@ -22,7 +23,7 @@ describe('device-api/index', () => {
|
||||
|
||||
await request(api)
|
||||
.get('/ping')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
|
@ -3,7 +3,7 @@ import request from 'supertest';
|
||||
|
||||
import * as config from '~/src/config';
|
||||
import * as testDb from '~/src/db';
|
||||
import * as deviceApi from '~/src/device-api';
|
||||
import * as apiKeys from '~/lib/api-keys';
|
||||
import * as middleware from '~/src/device-api/middleware';
|
||||
|
||||
describe('device-api/middleware', () => {
|
||||
@ -35,7 +35,7 @@ describe('device-api/middleware', () => {
|
||||
|
||||
it('validates API key from request query', async () => {
|
||||
await request(app)
|
||||
.get(`/?apikey=${await deviceApi.getGlobalApiKey()}`)
|
||||
.get(`/?apikey=${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
|
||||
await request(app).get(`/?apikey=${INVALID_KEY}`).expect(401);
|
||||
@ -44,7 +44,7 @@ describe('device-api/middleware', () => {
|
||||
const cases = ['ApiKey', 'apiKey', 'APIKEY', 'ApIKeY'];
|
||||
for (const query of cases) {
|
||||
await request(app)
|
||||
.get(`/?${query}=${await deviceApi.getGlobalApiKey()}`)
|
||||
.get(`/?${query}=${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(401);
|
||||
}
|
||||
});
|
||||
@ -55,10 +55,7 @@ describe('device-api/middleware', () => {
|
||||
for (const scheme of cases) {
|
||||
await request(app)
|
||||
.get('/')
|
||||
.set(
|
||||
'Authorization',
|
||||
`${scheme} ${await deviceApi.getGlobalApiKey()}`,
|
||||
)
|
||||
.set('Authorization', `${scheme} ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
|
||||
await request(app)
|
||||
@ -74,10 +71,7 @@ describe('device-api/middleware', () => {
|
||||
for (const scheme of cases) {
|
||||
await request(app)
|
||||
.get('/')
|
||||
.set(
|
||||
'Authorization',
|
||||
`${scheme} ${await deviceApi.getGlobalApiKey()}`,
|
||||
)
|
||||
.set('Authorization', `${scheme} ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
|
||||
await request(app)
|
||||
|
@ -7,7 +7,8 @@ import request from 'supertest';
|
||||
import * as config from '~/src/config';
|
||||
import * as db from '~/src/db';
|
||||
import * as hostConfig from '~/src/host-config';
|
||||
import type Service from '~/src/compose/service';
|
||||
import type { Service } from '~/src/compose/service';
|
||||
import * as apiKeys from '~/lib/api-keys';
|
||||
import * as deviceApi from '~/src/device-api';
|
||||
import * as actions from '~/src/device-api/actions';
|
||||
import * as v1 from '~/src/device-api/v1';
|
||||
@ -73,7 +74,7 @@ describe('device-api/v1', () => {
|
||||
it('responds with 200', async () => {
|
||||
await request(api)
|
||||
.post('/v1/blink')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
@ -84,7 +85,7 @@ describe('device-api/v1', () => {
|
||||
afterEach(() => (actions.regenerateKey as SinonStub).restore());
|
||||
|
||||
it('responds with 200 and valid new API key', async () => {
|
||||
const oldKey = await deviceApi.getGlobalApiKey();
|
||||
const oldKey = await apiKeys.getGlobalApiKey();
|
||||
const newKey = 'my_new_key';
|
||||
(actions.regenerateKey as SinonStub).resolves(newKey);
|
||||
|
||||
@ -98,7 +99,7 @@ describe('device-api/v1', () => {
|
||||
});
|
||||
|
||||
it('responds with 503 if regenerate was unsuccessful', async () => {
|
||||
const oldKey = await deviceApi.getGlobalApiKey();
|
||||
const oldKey = await apiKeys.getGlobalApiKey();
|
||||
(actions.regenerateKey as SinonStub).throws(new Error());
|
||||
|
||||
await request(api)
|
||||
@ -125,7 +126,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/restart')
|
||||
.send({ appId: 1234567, force: false })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(doRestartStub).to.have.been.calledWith(1234567, false);
|
||||
doRestartStub.resetHistory();
|
||||
@ -134,7 +135,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/restart')
|
||||
.send({ appId: 7654321, force: true })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(doRestartStub).to.have.been.calledWith(7654321, true);
|
||||
doRestartStub.resetHistory();
|
||||
@ -143,7 +144,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/restart')
|
||||
.send({ appId: 7654321 })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(doRestartStub).to.have.been.calledWith(7654321, false);
|
||||
});
|
||||
@ -151,12 +152,12 @@ describe('device-api/v1', () => {
|
||||
it('responds with 400 if appId is missing', async () => {
|
||||
await request(api)
|
||||
.post('/v1/restart')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("responds with 401 if caller's API key is not in scope of appId", async () => {
|
||||
const scopedKey = await deviceApi.generateScopedKey(1234567, 'main');
|
||||
const scopedKey = await apiKeys.generateScopedKey(1234567, 'main');
|
||||
await request(api)
|
||||
.post('/v1/restart')
|
||||
.send({ appId: 7654321 })
|
||||
@ -168,7 +169,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/restart')
|
||||
.send({ appId: 1234567 })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
@ -177,7 +178,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/restart')
|
||||
.send({ appId: 1234567 })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(423);
|
||||
});
|
||||
|
||||
@ -186,7 +187,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/restart')
|
||||
.send({ appId: 1234567 })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(503);
|
||||
});
|
||||
});
|
||||
@ -207,7 +208,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/purge')
|
||||
.send({ appId: 1234567, force: false })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(doPurgeStub).to.have.been.calledWith(1234567, false);
|
||||
doPurgeStub.resetHistory();
|
||||
@ -216,7 +217,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/purge')
|
||||
.send({ appId: 7654321, force: true })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(doPurgeStub).to.have.been.calledWith(7654321, true);
|
||||
doPurgeStub.resetHistory();
|
||||
@ -225,7 +226,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/purge')
|
||||
.send({ appId: 7654321 })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(doPurgeStub).to.have.been.calledWith(7654321, false);
|
||||
});
|
||||
@ -233,12 +234,12 @@ describe('device-api/v1', () => {
|
||||
it('responds with 400 if appId is missing', async () => {
|
||||
await request(api)
|
||||
.post('/v1/purge')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("responds with 401 if caller's API key is not in scope of appId", async () => {
|
||||
const scopedKey = await deviceApi.generateScopedKey(1234567, 'main');
|
||||
const scopedKey = await apiKeys.generateScopedKey(1234567, 'main');
|
||||
await request(api)
|
||||
.post('/v1/purge')
|
||||
.send({ appId: 7654321 })
|
||||
@ -250,7 +251,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/purge')
|
||||
.send({ appId: 1234567 })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
@ -259,7 +260,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/purge')
|
||||
.send({ appId: 1234567 })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(423);
|
||||
});
|
||||
|
||||
@ -268,7 +269,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/purge')
|
||||
.send({ appId: 1234567 })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(503);
|
||||
});
|
||||
});
|
||||
@ -297,7 +298,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/apps/1234567/stop')
|
||||
.send({ force: false })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'stop',
|
||||
@ -311,7 +312,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/apps/7654321/stop')
|
||||
.send({ force: true })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'stop',
|
||||
@ -324,7 +325,7 @@ describe('device-api/v1', () => {
|
||||
// Defaults to force: false
|
||||
await request(api)
|
||||
.post('/v1/apps/7654321/stop')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'stop',
|
||||
@ -335,7 +336,7 @@ describe('device-api/v1', () => {
|
||||
});
|
||||
|
||||
it("responds with 401 if caller's API key is not in scope of appId", async () => {
|
||||
const scopedKey = await deviceApi.generateScopedKey(1234567, 'main');
|
||||
const scopedKey = await apiKeys.generateScopedKey(1234567, 'main');
|
||||
await request(api)
|
||||
.post('/v1/apps/7654321/stop')
|
||||
.set('Authorization', `Bearer ${scopedKey}`)
|
||||
@ -345,7 +346,7 @@ describe('device-api/v1', () => {
|
||||
it('responds with 200 and containerId if service stop succeeded if service stop succeeded', async () => {
|
||||
await request(api)
|
||||
.post('/v1/apps/1234567/stop')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200, { containerId: 'abcdef' });
|
||||
});
|
||||
|
||||
@ -353,20 +354,20 @@ describe('device-api/v1', () => {
|
||||
executeServiceActionStub.throws(new NotFoundError());
|
||||
await request(api)
|
||||
.post('/v1/apps/1234567/stop')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('responds with 400 if invalid appId or appId corresponds to a multicontainer release', async () => {
|
||||
await request(api)
|
||||
.post('/v1/apps/badAppId/stop')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
|
||||
executeServiceActionStub.throws(new BadRequestError());
|
||||
await request(api)
|
||||
.post('/v1/apps/1234567/stop')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
@ -374,7 +375,7 @@ describe('device-api/v1', () => {
|
||||
executeServiceActionStub.throws(new UpdatesLockedError());
|
||||
await request(api)
|
||||
.post('/v1/apps/1234567/stop')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(423);
|
||||
});
|
||||
|
||||
@ -382,7 +383,7 @@ describe('device-api/v1', () => {
|
||||
executeServiceActionStub.throws(new Error());
|
||||
await request(api)
|
||||
.post('/v1/apps/1234567/stop')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(503);
|
||||
});
|
||||
});
|
||||
@ -411,7 +412,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/apps/1234567/start')
|
||||
.send({ force: false })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'start',
|
||||
@ -425,7 +426,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/apps/7654321/start')
|
||||
.send({ force: true })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'start',
|
||||
@ -438,7 +439,7 @@ describe('device-api/v1', () => {
|
||||
// Defaults to force: false
|
||||
await request(api)
|
||||
.post('/v1/apps/7654321/start')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'start',
|
||||
@ -449,7 +450,7 @@ describe('device-api/v1', () => {
|
||||
});
|
||||
|
||||
it("responds with 401 if caller's API key is not in scope of appId", async () => {
|
||||
const scopedKey = await deviceApi.generateScopedKey(1234567, 'main');
|
||||
const scopedKey = await apiKeys.generateScopedKey(1234567, 'main');
|
||||
await request(api)
|
||||
.post('/v1/apps/7654321/start')
|
||||
.set('Authorization', `Bearer ${scopedKey}`)
|
||||
@ -459,7 +460,7 @@ describe('device-api/v1', () => {
|
||||
it('responds with 200 and containerId if service start succeeded', async () => {
|
||||
await request(api)
|
||||
.post('/v1/apps/1234567/start')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200, { containerId: 'abcdef' });
|
||||
});
|
||||
|
||||
@ -467,20 +468,20 @@ describe('device-api/v1', () => {
|
||||
executeServiceActionStub.throws(new NotFoundError());
|
||||
await request(api)
|
||||
.post('/v1/apps/1234567/start')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('responds with 400 if invalid appId or appId corresponds to a multicontainer release', async () => {
|
||||
await request(api)
|
||||
.post('/v1/apps/badAppId/start')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
|
||||
executeServiceActionStub.throws(new BadRequestError());
|
||||
await request(api)
|
||||
.post('/v1/apps/1234567/start')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
@ -488,7 +489,7 @@ describe('device-api/v1', () => {
|
||||
executeServiceActionStub.throws(new UpdatesLockedError());
|
||||
await request(api)
|
||||
.post('/v1/apps/1234567/start')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(423);
|
||||
});
|
||||
|
||||
@ -496,7 +497,7 @@ describe('device-api/v1', () => {
|
||||
executeServiceActionStub.throws(new Error());
|
||||
await request(api)
|
||||
.post('/v1/apps/1234567/start')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(503);
|
||||
});
|
||||
});
|
||||
@ -513,7 +514,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/reboot')
|
||||
.send({ force: false })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`);
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`);
|
||||
expect(executeDeviceActionStub).to.have.been.calledWith(
|
||||
{
|
||||
action: 'reboot',
|
||||
@ -526,7 +527,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/reboot')
|
||||
.send({ force: true })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`);
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`);
|
||||
expect(executeDeviceActionStub).to.have.been.calledWith(
|
||||
{
|
||||
action: 'reboot',
|
||||
@ -538,7 +539,7 @@ describe('device-api/v1', () => {
|
||||
// Defaults to force: false
|
||||
await request(api)
|
||||
.post('/v1/reboot')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`);
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`);
|
||||
expect(executeDeviceActionStub).to.have.been.calledWith(
|
||||
{
|
||||
action: 'reboot',
|
||||
@ -550,7 +551,7 @@ describe('device-api/v1', () => {
|
||||
it('responds with 202 if request successful', async () => {
|
||||
await request(api)
|
||||
.post('/v1/reboot')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(202);
|
||||
});
|
||||
|
||||
@ -558,7 +559,7 @@ describe('device-api/v1', () => {
|
||||
executeDeviceActionStub.throws(new UpdatesLockedError());
|
||||
await request(api)
|
||||
.post('/v1/reboot')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(423);
|
||||
});
|
||||
|
||||
@ -566,7 +567,7 @@ describe('device-api/v1', () => {
|
||||
executeDeviceActionStub.throws(new Error());
|
||||
await request(api)
|
||||
.post('/v1/reboot')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(500);
|
||||
});
|
||||
});
|
||||
@ -583,7 +584,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/shutdown')
|
||||
.send({ force: false })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`);
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`);
|
||||
expect(executeDeviceActionStub).to.have.been.calledWith(
|
||||
{
|
||||
action: 'shutdown',
|
||||
@ -596,7 +597,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/shutdown')
|
||||
.send({ force: true })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`);
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`);
|
||||
expect(executeDeviceActionStub).to.have.been.calledWith(
|
||||
{
|
||||
action: 'shutdown',
|
||||
@ -608,7 +609,7 @@ describe('device-api/v1', () => {
|
||||
// Defaults to force: false
|
||||
await request(api)
|
||||
.post('/v1/shutdown')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`);
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`);
|
||||
expect(executeDeviceActionStub).to.have.been.calledWith(
|
||||
{
|
||||
action: 'shutdown',
|
||||
@ -620,7 +621,7 @@ describe('device-api/v1', () => {
|
||||
it('responds with 202 if request successful', async () => {
|
||||
await request(api)
|
||||
.post('/v1/shutdown')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(202);
|
||||
});
|
||||
|
||||
@ -628,7 +629,7 @@ describe('device-api/v1', () => {
|
||||
executeDeviceActionStub.throws(new UpdatesLockedError());
|
||||
await request(api)
|
||||
.post('/v1/shutdown')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(423);
|
||||
});
|
||||
|
||||
@ -636,7 +637,7 @@ describe('device-api/v1', () => {
|
||||
executeDeviceActionStub.throws(new Error());
|
||||
await request(api)
|
||||
.post('/v1/shutdown')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(500);
|
||||
});
|
||||
});
|
||||
@ -653,7 +654,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/update')
|
||||
.send({ force: false })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`);
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`);
|
||||
expect(updateTargetStub.lastCall.firstArg).to.be.false;
|
||||
updateTargetStub.resetHistory();
|
||||
|
||||
@ -661,14 +662,14 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.post('/v1/update')
|
||||
.send({ force: true })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`);
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`);
|
||||
expect(updateTargetStub.lastCall.firstArg).to.be.true;
|
||||
updateTargetStub.resetHistory();
|
||||
|
||||
// Defaults to force: false
|
||||
await request(api)
|
||||
.post('/v1/update')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`);
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`);
|
||||
expect(updateTargetStub.lastCall.firstArg).to.be.false;
|
||||
});
|
||||
|
||||
@ -676,7 +677,7 @@ describe('device-api/v1', () => {
|
||||
updateTargetStub.returns(true);
|
||||
await request(api)
|
||||
.post('/v1/update')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(204);
|
||||
});
|
||||
|
||||
@ -684,7 +685,7 @@ describe('device-api/v1', () => {
|
||||
updateTargetStub.returns(false);
|
||||
await request(api)
|
||||
.post('/v1/update')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(202);
|
||||
});
|
||||
});
|
||||
@ -706,21 +707,21 @@ describe('device-api/v1', () => {
|
||||
it('validates data from request body', async () => {
|
||||
await request(api)
|
||||
.get('/v1/apps/1234567')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`);
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`);
|
||||
expect(getSingleContainerAppStub).to.have.been.calledWith(1234567);
|
||||
});
|
||||
|
||||
it('responds with 200 if request successful', async () => {
|
||||
await request(api)
|
||||
.get('/v1/apps/1234567')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200, {});
|
||||
});
|
||||
|
||||
it('responds with 400 if invalid appId parameter', async () => {
|
||||
await request(api)
|
||||
.get('/v1/apps/badAppId')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
@ -728,12 +729,12 @@ describe('device-api/v1', () => {
|
||||
getSingleContainerAppStub.throws(new BadRequestError());
|
||||
await request(api)
|
||||
.get('/v1/apps/1234567')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("responds with 401 if caller's API key is not in scope of appId", async () => {
|
||||
const scopedKey = await deviceApi.generateScopedKey(7654321, 'main');
|
||||
const scopedKey = await apiKeys.generateScopedKey(7654321, 'main');
|
||||
await request(api)
|
||||
.get('/v1/apps/1234567')
|
||||
.set('Authorization', `Bearer ${scopedKey}`)
|
||||
@ -744,7 +745,7 @@ describe('device-api/v1', () => {
|
||||
getSingleContainerAppStub.throws(new Error());
|
||||
await request(api)
|
||||
.get('/v1/apps/1234567')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(503);
|
||||
});
|
||||
});
|
||||
@ -760,7 +761,7 @@ describe('device-api/v1', () => {
|
||||
getLegacyDeviceStateStub.resolves({ test_state: 'Success' });
|
||||
await request(api)
|
||||
.get('/v1/device')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200, { test_state: 'Success' });
|
||||
});
|
||||
|
||||
@ -768,7 +769,7 @@ describe('device-api/v1', () => {
|
||||
getLegacyDeviceStateStub.throws(new Error());
|
||||
await request(api)
|
||||
.get('/v1/device')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(503);
|
||||
});
|
||||
});
|
||||
@ -787,7 +788,7 @@ describe('device-api/v1', () => {
|
||||
getHostConfigStub.resolves({ network: { hostname: 'deadbeef' } });
|
||||
await request(api)
|
||||
.get('/v1/device/host-config')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200, { network: { hostname: 'deadbeef' } });
|
||||
});
|
||||
|
||||
@ -795,7 +796,7 @@ describe('device-api/v1', () => {
|
||||
getHostConfigStub.throws(new Error());
|
||||
await request(api)
|
||||
.get('/v1/device/host-config')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(503);
|
||||
});
|
||||
});
|
||||
@ -824,7 +825,7 @@ describe('device-api/v1', () => {
|
||||
for (const key of Object.keys(invalidProxyReqs)) {
|
||||
await request(api)
|
||||
.patch('/v1/device/host-config')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.send({ network: { proxy: { [key]: invalidProxyReqs[key] } } })
|
||||
.expect(200)
|
||||
.then(() => {
|
||||
@ -852,7 +853,7 @@ describe('device-api/v1', () => {
|
||||
await request(api)
|
||||
.patch('/v1/device/host-config')
|
||||
.send({})
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200)
|
||||
.then(() => {
|
||||
expect(log.warn as SinonStub).to.have.been.calledWith(
|
||||
|
@ -6,6 +6,7 @@ import request from 'supertest';
|
||||
|
||||
import * as config from '~/src/config';
|
||||
import * as db from '~/src/db';
|
||||
import * as apiKeys from '~/lib/api-keys';
|
||||
import * as deviceApi from '~/src/device-api';
|
||||
import * as actions from '~/src/device-api/actions';
|
||||
import * as v2 from '~/src/device-api/v2';
|
||||
@ -50,7 +51,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/restart')
|
||||
.send({ force: false })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(doRestartStub).to.have.been.calledWith(1234567, false);
|
||||
doRestartStub.resetHistory();
|
||||
@ -59,7 +60,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/restart')
|
||||
.send({ force: true })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(doRestartStub).to.have.been.calledWith(7654321, true);
|
||||
doRestartStub.resetHistory();
|
||||
@ -67,7 +68,7 @@ describe('device-api/v2', () => {
|
||||
// Defaults to force: false
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/restart')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(doRestartStub).to.have.been.calledWith(7654321, false);
|
||||
});
|
||||
@ -75,12 +76,12 @@ describe('device-api/v2', () => {
|
||||
it('responds with 400 if appId is missing', async () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/badAppId/restart')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("responds with 401 if caller's API key is not in scope of appId", async () => {
|
||||
const scopedKey = await deviceApi.generateScopedKey(1234567, 'main');
|
||||
const scopedKey = await apiKeys.generateScopedKey(1234567, 'main');
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/restart')
|
||||
.set('Authorization', `Bearer ${scopedKey}`)
|
||||
@ -90,7 +91,7 @@ describe('device-api/v2', () => {
|
||||
it('responds with 200 if restart succeeded', async () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/restart')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
@ -98,7 +99,7 @@ describe('device-api/v2', () => {
|
||||
doRestartStub.throws(new UpdatesLockedError());
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/restart')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(423);
|
||||
});
|
||||
|
||||
@ -106,7 +107,7 @@ describe('device-api/v2', () => {
|
||||
doRestartStub.throws(new Error());
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/restart')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(503);
|
||||
});
|
||||
});
|
||||
@ -128,7 +129,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/purge')
|
||||
.send({ force: false })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(doPurgeStub).to.have.been.calledWith(1234567, false);
|
||||
doPurgeStub.resetHistory();
|
||||
@ -137,7 +138,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/purge')
|
||||
.send({ force: true })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(doPurgeStub).to.have.been.calledWith(7654321, true);
|
||||
doPurgeStub.resetHistory();
|
||||
@ -145,7 +146,7 @@ describe('device-api/v2', () => {
|
||||
// Defaults to force: false
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/purge')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(doPurgeStub).to.have.been.calledWith(7654321, false);
|
||||
});
|
||||
@ -153,12 +154,12 @@ describe('device-api/v2', () => {
|
||||
it('responds with 400 if appId is missing', async () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/badAppId/purge')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("responds with 401 if caller's API key is not in scope of appId", async () => {
|
||||
const scopedKey = await deviceApi.generateScopedKey(1234567, 'main');
|
||||
const scopedKey = await apiKeys.generateScopedKey(1234567, 'main');
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/purge')
|
||||
.set('Authorization', `Bearer ${scopedKey}`)
|
||||
@ -168,7 +169,7 @@ describe('device-api/v2', () => {
|
||||
it('responds with 200 if purge succeeded', async () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/purge')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
@ -176,7 +177,7 @@ describe('device-api/v2', () => {
|
||||
doPurgeStub.throws(new UpdatesLockedError());
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/purge')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(423);
|
||||
});
|
||||
|
||||
@ -184,7 +185,7 @@ describe('device-api/v2', () => {
|
||||
doPurgeStub.throws(new Error());
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/purge')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(503);
|
||||
});
|
||||
});
|
||||
@ -209,7 +210,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/stop-service')
|
||||
.send({ force: false, serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'stop',
|
||||
@ -224,7 +225,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/stop-service')
|
||||
.send({ force: true, serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'stop',
|
||||
@ -239,7 +240,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/stop-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'stop',
|
||||
@ -254,7 +255,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/stop-service')
|
||||
.send({ imageId: 111 })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'stop',
|
||||
@ -269,7 +270,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/stop-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'stop',
|
||||
@ -281,7 +282,7 @@ describe('device-api/v2', () => {
|
||||
});
|
||||
|
||||
it("responds with 401 if caller's API key is not in scope of appId", async () => {
|
||||
const scopedKey = await deviceApi.generateScopedKey(1234567, 'main');
|
||||
const scopedKey = await apiKeys.generateScopedKey(1234567, 'main');
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/stop-service')
|
||||
.send({ serviceName: 'test' })
|
||||
@ -293,7 +294,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/stop-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
@ -302,21 +303,21 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/stop-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('responds with 400 if invalid appId or missing serviceName/imageId from request body', async () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/badAppId/stop-service')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
|
||||
executeServiceActionStub.throws(new BadRequestError());
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/stop-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
@ -325,7 +326,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/stop-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(423);
|
||||
});
|
||||
|
||||
@ -334,7 +335,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/stop-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(503);
|
||||
});
|
||||
});
|
||||
@ -359,7 +360,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/start-service')
|
||||
.send({ force: false, serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'start',
|
||||
@ -374,7 +375,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/start-service')
|
||||
.send({ force: true, serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'start',
|
||||
@ -389,7 +390,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/start-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'start',
|
||||
@ -404,7 +405,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/start-service')
|
||||
.send({ imageId: 111 })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'start',
|
||||
@ -419,7 +420,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/start-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'start',
|
||||
@ -431,7 +432,7 @@ describe('device-api/v2', () => {
|
||||
});
|
||||
|
||||
it("responds with 401 if caller's API key is not in scope of appId", async () => {
|
||||
const scopedKey = await deviceApi.generateScopedKey(1234567, 'main');
|
||||
const scopedKey = await apiKeys.generateScopedKey(1234567, 'main');
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/start-service')
|
||||
.send({ serviceName: 'test' })
|
||||
@ -443,7 +444,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/start-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
@ -452,21 +453,21 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/start-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('responds with 400 if invalid appId or missing serviceName/imageId from request body', async () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/badAppId/start-service')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
|
||||
executeServiceActionStub.throws(new BadRequestError());
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/start-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
@ -475,7 +476,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/start-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(423);
|
||||
});
|
||||
|
||||
@ -484,7 +485,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/start-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(503);
|
||||
});
|
||||
});
|
||||
@ -509,7 +510,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/restart-service')
|
||||
.send({ force: false, serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'restart',
|
||||
@ -524,7 +525,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/restart-service')
|
||||
.send({ force: true, serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'restart',
|
||||
@ -539,7 +540,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/restart-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'restart',
|
||||
@ -554,7 +555,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/restart-service')
|
||||
.send({ imageId: 111 })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'restart',
|
||||
@ -569,7 +570,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/restart-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
expect(executeServiceActionStub).to.have.been.calledWith({
|
||||
action: 'restart',
|
||||
@ -581,7 +582,7 @@ describe('device-api/v2', () => {
|
||||
});
|
||||
|
||||
it("responds with 401 if caller's API key is not in scope of appId", async () => {
|
||||
const scopedKey = await deviceApi.generateScopedKey(1234567, 'main');
|
||||
const scopedKey = await apiKeys.generateScopedKey(1234567, 'main');
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/restart-service')
|
||||
.send({ serviceName: 'test' })
|
||||
@ -593,7 +594,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/restart-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
@ -602,21 +603,21 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/restart-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('responds with 400 if invalid appId or missing serviceName/imageId from request body', async () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/badAppId/restart-service')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
|
||||
executeServiceActionStub.throws(new BadRequestError());
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/restart-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
@ -625,7 +626,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/1234567/restart-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(423);
|
||||
});
|
||||
|
||||
@ -634,7 +635,7 @@ describe('device-api/v2', () => {
|
||||
await request(api)
|
||||
.post('/v2/applications/7654321/restart-service')
|
||||
.send({ serviceName: 'test' })
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(503);
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { expect } from 'chai';
|
||||
import { isRight } from 'fp-ts/lib/Either';
|
||||
import App from '~/src/compose/app';
|
||||
import Network from '~/src/compose/network';
|
||||
import { App } from '~/src/compose/app';
|
||||
import { Network } from '~/src/compose/network';
|
||||
import * as config from '~/src/config';
|
||||
import * as testDb from '~/src/db';
|
||||
import * as dbFormat from '~/src/device-state/db-format';
|
||||
|
@ -9,7 +9,7 @@ import * as fs from 'fs/promises';
|
||||
import * as hostConfig from '~/src/host-config';
|
||||
import * as config from '~/src/config';
|
||||
import * as applicationManager from '~/src/compose/application-manager';
|
||||
import type { InstancedAppState } from '~/src/types/state';
|
||||
import type { InstancedAppState } from '~/src/compose/types';
|
||||
import * as updateLock from '~/lib/update-lock';
|
||||
import { UpdatesLockedError } from '~/lib/errors';
|
||||
import * as dbus from '~/lib/dbus';
|
||||
|
@ -8,7 +8,7 @@ import mockedAPI = require('~/test-lib/mocked-device-api');
|
||||
import * as apiBinder from '~/src/api-binder';
|
||||
import * as deviceState from '~/src/device-state';
|
||||
import type SupervisorAPI from '~/src/device-api';
|
||||
import * as deviceApi from '~/src/device-api';
|
||||
import * as apiKeys from '~/lib/api-keys';
|
||||
import * as serviceManager from '~/src/compose/service-manager';
|
||||
import * as images from '~/src/compose/images';
|
||||
import * as config from '~/src/config';
|
||||
@ -78,7 +78,7 @@ describe('SupervisorAPI [V2 Endpoints]', () => {
|
||||
await request
|
||||
.get('/v2/device/vpn')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(sampleResponses.V2.GET['/device/vpn'].statusCode)
|
||||
.then((response) => {
|
||||
@ -94,7 +94,7 @@ describe('SupervisorAPI [V2 Endpoints]', () => {
|
||||
await request
|
||||
.get('/v2/applications/1/state')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(sampleResponses.V2.GET['/applications/1/state'].statusCode)
|
||||
.expect('Content-Type', /json/)
|
||||
.then((response) => {
|
||||
@ -108,7 +108,7 @@ describe('SupervisorAPI [V2 Endpoints]', () => {
|
||||
await request
|
||||
.get('/v2/applications/123invalid/state')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(
|
||||
sampleResponses.V2.GET['/applications/123invalid/state'].statusCode,
|
||||
@ -124,7 +124,7 @@ describe('SupervisorAPI [V2 Endpoints]', () => {
|
||||
await request
|
||||
.get('/v2/applications/9000/state')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(sampleResponses.V2.GET['/applications/9000/state'].statusCode)
|
||||
.then((response) => {
|
||||
expect(response.body).to.deep.equal(
|
||||
@ -135,7 +135,7 @@ describe('SupervisorAPI [V2 Endpoints]', () => {
|
||||
|
||||
describe('Scoped API Keys', () => {
|
||||
it('returns 409 because app is out of scope of the key', async () => {
|
||||
const apiKey = await deviceApi.generateScopedKey(3, 'main');
|
||||
const apiKey = await apiKeys.generateScopedKey(3, 'main');
|
||||
await request
|
||||
.get('/v2/applications/2/state')
|
||||
.set('Accept', 'application/json')
|
||||
@ -157,7 +157,7 @@ describe('SupervisorAPI [V2 Endpoints]', () => {
|
||||
|
||||
it('should return scoped application', async () => {
|
||||
// Create scoped key for application
|
||||
const appScopedKey = await deviceApi.generateScopedKey(1658654, 'main');
|
||||
const appScopedKey = await apiKeys.generateScopedKey(1658654, 'main');
|
||||
// Setup device conditions
|
||||
serviceManagerMock.resolves([mockedAPI.mockService({ appId: 1658654 })]);
|
||||
imagesMock.resolves([mockedAPI.mockImage({ appId: 1658654 })]);
|
||||
@ -181,7 +181,7 @@ describe('SupervisorAPI [V2 Endpoints]', () => {
|
||||
|
||||
it('should return no application info due to lack of scope', async () => {
|
||||
// Create scoped key for wrong application
|
||||
const appScopedKey = await deviceApi.generateScopedKey(1, 'main');
|
||||
const appScopedKey = await apiKeys.generateScopedKey(1, 'main');
|
||||
// Setup device conditions
|
||||
serviceManagerMock.resolves([mockedAPI.mockService({ appId: 1658654 })]);
|
||||
imagesMock.resolves([mockedAPI.mockImage({ appId: 1658654 })]);
|
||||
@ -204,7 +204,7 @@ describe('SupervisorAPI [V2 Endpoints]', () => {
|
||||
|
||||
it('should return success when device has no applications', async () => {
|
||||
// Create scoped key for any application
|
||||
const appScopedKey = await deviceApi.generateScopedKey(1658654, 'main');
|
||||
const appScopedKey = await apiKeys.generateScopedKey(1658654, 'main');
|
||||
// Setup device conditions
|
||||
serviceManagerMock.resolves([]);
|
||||
imagesMock.resolves([]);
|
||||
@ -227,7 +227,7 @@ describe('SupervisorAPI [V2 Endpoints]', () => {
|
||||
|
||||
it('should only return 1 application when N > 1 applications on device', async () => {
|
||||
// Create scoped key for application
|
||||
const appScopedKey = await deviceApi.generateScopedKey(1658654, 'main');
|
||||
const appScopedKey = await apiKeys.generateScopedKey(1658654, 'main');
|
||||
// Setup device conditions
|
||||
serviceManagerMock.resolves([
|
||||
mockedAPI.mockService({ appId: 1658654 }),
|
||||
|
@ -4,9 +4,9 @@ import { expect } from 'chai';
|
||||
|
||||
import * as config from '~/src/config';
|
||||
import * as testDb from '~/src/db';
|
||||
import * as deviceApi from '~/src/device-api';
|
||||
import * as apiKeys from '~/lib/api-keys';
|
||||
import * as middleware from '~/src/device-api/middleware';
|
||||
import type { AuthorizedRequest } from '~/src/device-api/api-keys';
|
||||
import type { AuthorizedRequest } from '~/lib/api-keys';
|
||||
|
||||
describe('device-api/api-keys', () => {
|
||||
let app: express.Application;
|
||||
@ -30,8 +30,8 @@ describe('device-api/api-keys', () => {
|
||||
});
|
||||
|
||||
it('should generate a key which is scoped for a single application', async () => {
|
||||
const appOneKey = await deviceApi.generateScopedKey(111, 'one');
|
||||
const appTwoKey = await deviceApi.generateScopedKey(222, 'two');
|
||||
const appOneKey = await apiKeys.generateScopedKey(111, 'one');
|
||||
const appTwoKey = await apiKeys.generateScopedKey(222, 'two');
|
||||
|
||||
await request(app)
|
||||
.get('/test/111')
|
||||
@ -55,7 +55,7 @@ describe('device-api/api-keys', () => {
|
||||
});
|
||||
|
||||
it('should generate a key which is scoped for multiple applications', async () => {
|
||||
const multiAppKey = await deviceApi.generateScopedKey(111, 'three', {
|
||||
const multiAppKey = await apiKeys.generateScopedKey(111, 'three', {
|
||||
scopes: [111, 222].map((appId) => ({ type: 'app', appId })),
|
||||
});
|
||||
|
||||
@ -73,54 +73,54 @@ describe('device-api/api-keys', () => {
|
||||
it('should generate a key which is scoped for all applications', async () => {
|
||||
await request(app)
|
||||
.get('/test/111')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
|
||||
await request(app)
|
||||
.get('/test/222')
|
||||
.set('Authorization', `Bearer ${await deviceApi.getGlobalApiKey()}`)
|
||||
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('should have a cached lookup of key scopes', async () => {
|
||||
const globalScopes = await deviceApi.getScopesForKey(
|
||||
await deviceApi.getGlobalApiKey(),
|
||||
const globalScopes = await apiKeys.getScopesForKey(
|
||||
await apiKeys.getGlobalApiKey(),
|
||||
);
|
||||
|
||||
const key = 'my-new-key';
|
||||
await testDb
|
||||
.models('apiSecret')
|
||||
.where({ key: await deviceApi.getGlobalApiKey() })
|
||||
.where({ key: await apiKeys.getGlobalApiKey() })
|
||||
.update({ key });
|
||||
|
||||
// Key has been changed, but cache should retain the old key
|
||||
expect(
|
||||
await deviceApi.getScopesForKey(await deviceApi.getGlobalApiKey()),
|
||||
await apiKeys.getScopesForKey(await apiKeys.getGlobalApiKey()),
|
||||
).to.deep.equal(globalScopes);
|
||||
|
||||
// Bust the cache and generate a new global API key
|
||||
const refreshedKey = await deviceApi.refreshKey(
|
||||
await deviceApi.getGlobalApiKey(),
|
||||
const refreshedKey = await apiKeys.refreshKey(
|
||||
await apiKeys.getGlobalApiKey(),
|
||||
);
|
||||
|
||||
// Key that we changed in db is no longer valid
|
||||
expect(await deviceApi.getScopesForKey(key)).to.be.null;
|
||||
expect(await apiKeys.getScopesForKey(key)).to.be.null;
|
||||
|
||||
// Refreshed key should have the global scopes
|
||||
expect(await deviceApi.getScopesForKey(refreshedKey)).to.deep.equal(
|
||||
expect(await apiKeys.getScopesForKey(refreshedKey)).to.deep.equal(
|
||||
globalScopes,
|
||||
);
|
||||
});
|
||||
|
||||
it('should regenerate a key and invalidate the old one', async () => {
|
||||
const appScopedKey = await deviceApi.generateScopedKey(111, 'four');
|
||||
const appScopedKey = await apiKeys.generateScopedKey(111, 'four');
|
||||
|
||||
await request(app)
|
||||
.get('/test/111')
|
||||
.set('Authorization', `Bearer ${appScopedKey}`)
|
||||
.expect(200);
|
||||
|
||||
const newScopedKey = await deviceApi.refreshKey(appScopedKey);
|
||||
const newScopedKey = await apiKeys.refreshKey(appScopedKey);
|
||||
|
||||
await request(app)
|
||||
.get('/test/111')
|
@ -1,15 +1,15 @@
|
||||
import App from '~/src/compose/app';
|
||||
import { App } from '~/src/compose/app';
|
||||
import * as imageManager from '~/src/compose/images';
|
||||
import type { Image } from '~/src/compose/images';
|
||||
import Network from '~/src/compose/network';
|
||||
import Service from '~/src/compose/service';
|
||||
import { Network } from '~/src/compose/network';
|
||||
import { Service } from '~/src/compose/service';
|
||||
import type { ServiceComposeConfig } from '~/src/compose/types/service';
|
||||
import type Volume from '~/src/compose/volume';
|
||||
import type { Volume } from '~/src/compose/volume';
|
||||
import type {
|
||||
CompositionStep,
|
||||
CompositionStepAction,
|
||||
} from '~/src/compose/composition-steps';
|
||||
import type { InstancedAppState } from '~/src/types/state';
|
||||
import type { InstancedAppState } from '~/src/compose/types';
|
||||
|
||||
export const DEFAULT_NETWORK = Network.fromComposeObject(
|
||||
'default',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { expect } from 'chai';
|
||||
import type { Image } from '~/src/compose/images';
|
||||
import Network from '~/src/compose/network';
|
||||
import Volume from '~/src/compose/volume';
|
||||
import { Network } from '~/src/compose/network';
|
||||
import { Volume } from '~/src/compose/volume';
|
||||
import { LocksTakenMap } from '~/lib/update-lock';
|
||||
|
||||
import {
|
||||
|
@ -2,7 +2,7 @@ import { expect } from 'chai';
|
||||
import type * as sinon from 'sinon';
|
||||
|
||||
import { Network } from '~/src/compose/network';
|
||||
import type { NetworkInspectInfo } from '~/src/compose/types/network';
|
||||
import type { NetworkInspectInfo } from '~/src/compose/types';
|
||||
|
||||
import { log } from '~/lib/supervisor-console';
|
||||
|
||||
|
@ -3,8 +3,8 @@ import * as _ from 'lodash';
|
||||
import { expect } from 'chai';
|
||||
import { createContainer } from '~/test-lib/mockerode';
|
||||
|
||||
import Service from '~/src/compose/service';
|
||||
import Volume from '~/src/compose/volume';
|
||||
import { Service } from '~/src/compose/service';
|
||||
import { Volume } from '~/src/compose/volume';
|
||||
import * as ServiceT from '~/src/compose/types/service';
|
||||
import * as constants from '~/lib/constants';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai';
|
||||
import Volume from '~/src/compose/volume';
|
||||
import { Volume } from '~/src/compose/volume';
|
||||
|
||||
describe('compose/volume: unit tests', () => {
|
||||
describe('creating a volume from a compose object', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user