2024-05-15 17:44:05 +00:00
|
|
|
import { App } from '~/src/compose/app';
|
2022-12-08 19:38:11 +00:00
|
|
|
import * as imageManager from '~/src/compose/images';
|
2024-02-29 22:00:39 +00:00
|
|
|
import type { Image } from '~/src/compose/images';
|
2024-05-15 17:44:05 +00:00
|
|
|
import { Network } from '~/src/compose/network';
|
|
|
|
import { Service } from '~/src/compose/service';
|
2024-02-29 22:00:39 +00:00
|
|
|
import type { ServiceComposeConfig } from '~/src/compose/types/service';
|
2024-05-15 17:44:05 +00:00
|
|
|
import type { Volume } from '~/src/compose/volume';
|
2024-02-29 22:00:39 +00:00
|
|
|
import type {
|
2023-03-07 22:32:10 +00:00
|
|
|
CompositionStep,
|
|
|
|
CompositionStepAction,
|
|
|
|
} from '~/src/compose/composition-steps';
|
2024-05-15 19:06:32 +00:00
|
|
|
import type { InstancedAppState } from '~/src/compose/types';
|
2022-12-08 19:38:11 +00:00
|
|
|
|
|
|
|
export const DEFAULT_NETWORK = Network.fromComposeObject(
|
|
|
|
'default',
|
|
|
|
1,
|
|
|
|
'appuuid',
|
|
|
|
{},
|
|
|
|
);
|
|
|
|
|
|
|
|
export async function createService(
|
|
|
|
{
|
|
|
|
appId = 1,
|
|
|
|
appUuid = 'appuuid',
|
|
|
|
serviceName = 'main',
|
|
|
|
commit = 'main-commit',
|
|
|
|
...conf
|
|
|
|
} = {} as Partial<ServiceComposeConfig>,
|
|
|
|
{ state = {} as Partial<Service>, options = {} as any } = {},
|
|
|
|
) {
|
|
|
|
const svc = await Service.fromComposeObject(
|
|
|
|
{
|
|
|
|
appId,
|
|
|
|
appUuid,
|
|
|
|
serviceName,
|
|
|
|
commit,
|
|
|
|
// db ids should not be used for target state calculation, but images
|
|
|
|
// are compared using _.isEqual so leaving this here to have image comparisons
|
|
|
|
// match
|
|
|
|
serviceId: 1,
|
|
|
|
imageId: 1,
|
|
|
|
releaseId: 1,
|
|
|
|
...conf,
|
|
|
|
},
|
|
|
|
options,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Add additonal configuration
|
|
|
|
for (const k of Object.keys(state)) {
|
|
|
|
(svc as any)[k] = (state as any)[k];
|
|
|
|
}
|
|
|
|
return svc;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createImage(
|
|
|
|
{
|
|
|
|
appId = 1,
|
|
|
|
appUuid = 'appuuid',
|
|
|
|
name = 'test-image',
|
|
|
|
serviceName = 'main',
|
|
|
|
commit = 'main-commit',
|
|
|
|
...extra
|
|
|
|
} = {} as Partial<Image>,
|
|
|
|
) {
|
|
|
|
return {
|
|
|
|
appId,
|
|
|
|
appUuid,
|
|
|
|
name,
|
|
|
|
serviceName,
|
|
|
|
commit,
|
|
|
|
// db ids should not be used for target state calculation, but images
|
|
|
|
// are compared using _.isEqual so leaving this here to have image comparisons
|
|
|
|
// match
|
|
|
|
imageId: 1,
|
|
|
|
releaseId: 1,
|
|
|
|
serviceId: 1,
|
|
|
|
...extra,
|
|
|
|
} as Image;
|
|
|
|
}
|
|
|
|
|
2023-03-07 22:32:10 +00:00
|
|
|
export function createApp({
|
|
|
|
services = [] as Service[],
|
|
|
|
networks = [] as Network[],
|
|
|
|
volumes = [] as Volume[],
|
|
|
|
isTarget = false,
|
|
|
|
appId = 1,
|
|
|
|
appUuid = 'appuuid',
|
2024-06-27 15:53:24 +00:00
|
|
|
isRejected = false,
|
2023-03-07 22:32:10 +00:00
|
|
|
} = {}) {
|
|
|
|
return new App(
|
|
|
|
{
|
|
|
|
appId,
|
|
|
|
appUuid,
|
|
|
|
services,
|
|
|
|
networks,
|
|
|
|
volumes,
|
2024-06-27 15:53:24 +00:00
|
|
|
isRejected,
|
2023-03-07 22:32:10 +00:00
|
|
|
},
|
|
|
|
isTarget,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-08 19:38:11 +00:00
|
|
|
export function createApps(
|
|
|
|
{
|
|
|
|
services = [] as Service[],
|
|
|
|
networks = [] as Network[],
|
|
|
|
volumes = [] as Volume[],
|
2024-06-27 15:53:24 +00:00
|
|
|
rejectedAppIds = [] as number[],
|
2022-12-08 19:38:11 +00:00
|
|
|
},
|
|
|
|
target = false,
|
|
|
|
) {
|
|
|
|
const servicesByAppId = services.reduce(
|
|
|
|
(svcs, s) => ({ ...svcs, [s.appId]: [s].concat(svcs[s.appId] || []) }),
|
|
|
|
{} as Dictionary<Service[]>,
|
|
|
|
);
|
|
|
|
const volumesByAppId = volumes.reduce(
|
|
|
|
(vols, v) => ({ ...vols, [v.appId]: [v].concat(vols[v.appId] || []) }),
|
|
|
|
{} as Dictionary<Volume[]>,
|
|
|
|
);
|
|
|
|
const networksByAppId = networks.reduce(
|
|
|
|
(nets, n) => ({ ...nets, [n.appId]: [n].concat(nets[n.appId] || []) }),
|
|
|
|
{} as Dictionary<Network[]>,
|
|
|
|
);
|
|
|
|
|
|
|
|
const allAppIds = [
|
|
|
|
...new Set([
|
|
|
|
...Object.keys(servicesByAppId),
|
|
|
|
...Object.keys(networksByAppId),
|
|
|
|
...Object.keys(volumesByAppId),
|
|
|
|
]),
|
|
|
|
].map((i) => parseInt(i, 10));
|
|
|
|
|
|
|
|
const apps: InstancedAppState = {};
|
|
|
|
for (const appId of allAppIds) {
|
2024-06-27 15:53:24 +00:00
|
|
|
const isRejected = rejectedAppIds.includes(appId);
|
2023-03-07 22:32:10 +00:00
|
|
|
apps[appId] = createApp({
|
|
|
|
services: servicesByAppId[appId] ?? [],
|
|
|
|
networks: networksByAppId[appId] ?? [],
|
|
|
|
volumes: volumesByAppId[appId] ?? [],
|
|
|
|
appId,
|
|
|
|
appUuid: servicesByAppId[appId]?.[0]?.appUuid ?? 'deadbeef',
|
|
|
|
isTarget: target,
|
2024-06-27 15:53:24 +00:00
|
|
|
isRejected,
|
2023-03-07 22:32:10 +00:00
|
|
|
});
|
2022-12-08 19:38:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return apps;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createCurrentState({
|
|
|
|
services = [] as Service[],
|
|
|
|
networks = [] as Network[],
|
|
|
|
volumes = [] as Volume[],
|
|
|
|
images = services.map((s) => ({
|
|
|
|
// Infer images from services by default
|
|
|
|
dockerImageId: s.dockerImageId,
|
|
|
|
...imageManager.imageFromService(s),
|
|
|
|
})) as Image[],
|
|
|
|
downloading = [] as string[],
|
|
|
|
}) {
|
|
|
|
const currentApps = createApps({ services, networks, volumes });
|
|
|
|
|
|
|
|
const containerIdsByAppId = services.reduce(
|
|
|
|
(ids, s) => ({
|
|
|
|
...ids,
|
|
|
|
[s.appId]: {
|
|
|
|
...ids[s.appId],
|
|
|
|
...(s.serviceName &&
|
|
|
|
s.containerId && { [s.serviceName]: s.containerId }),
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
{} as { [appId: number]: Dictionary<string> },
|
|
|
|
);
|
|
|
|
|
|
|
|
return {
|
|
|
|
currentApps,
|
|
|
|
availableImages: images,
|
|
|
|
downloading,
|
|
|
|
containerIdsByAppId,
|
|
|
|
};
|
|
|
|
}
|
2023-03-07 22:32:10 +00:00
|
|
|
|
|
|
|
export const expectSteps = (
|
|
|
|
action: CompositionStepAction,
|
|
|
|
steps: CompositionStep[],
|
|
|
|
min = 1,
|
|
|
|
max = min,
|
|
|
|
message = `Expected to find ${min} step(s) with action '${action}', instead found ${JSON.stringify(
|
|
|
|
steps.map((s) => s.action),
|
|
|
|
)}`,
|
|
|
|
) => {
|
|
|
|
const filtered = steps.filter((s) => s.action === action);
|
|
|
|
|
|
|
|
if (filtered.length < min || filtered.length > max) {
|
|
|
|
throw new Error(message);
|
|
|
|
}
|
|
|
|
return filtered;
|
|
|
|
};
|
|
|
|
|
|
|
|
export function expectNoStep(
|
|
|
|
action: CompositionStepAction,
|
|
|
|
steps: CompositionStep[],
|
|
|
|
) {
|
|
|
|
expectSteps(action, steps, 0, 0);
|
|
|
|
}
|