2024-05-09 00:05:16 +02:00
|
|
|
import * as plugins from './coreflow.plugins.js';
|
|
|
|
|
import { logger } from './coreflow.logging.js';
|
|
|
|
|
import { Coreflow } from './coreflow.classes.coreflow.js';
|
|
|
|
|
|
|
|
|
|
export class ClusterManager {
|
|
|
|
|
public coreflowRef: Coreflow;
|
2026-04-28 12:02:22 +00:00
|
|
|
public configSubscription?: plugins.smartrx.rxjs.Subscription;
|
2024-05-09 00:05:16 +02:00
|
|
|
|
|
|
|
|
public readyDeferred = plugins.smartpromise.defer();
|
|
|
|
|
|
|
|
|
|
public commonDockerData = {
|
|
|
|
|
networkNames: {
|
|
|
|
|
sznWebgateway: 'sznwebgateway',
|
|
|
|
|
sznCorechat: 'szncorechat',
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
constructor(coreflowRefArg: Coreflow) {
|
|
|
|
|
this.coreflowRef = coreflowRefArg;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 16:57:54 +00:00
|
|
|
private async getDockerServiceByName(serviceNameArg: string): Promise<plugins.docker.DockerService | null> {
|
|
|
|
|
try {
|
|
|
|
|
return await this.coreflowRef.dockerHost.getServiceByName(serviceNameArg);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if ((error as Error).message === `Service not found: ${serviceNameArg}`) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getDockerSafeName(valueArg: string, maxLengthArg = 64) {
|
|
|
|
|
const safeName = valueArg
|
|
|
|
|
.replace(/[^a-zA-Z0-9_.-]+/g, '-')
|
|
|
|
|
.replace(/^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+$/g, '')
|
|
|
|
|
.slice(0, maxLengthArg)
|
|
|
|
|
.replace(/[^a-zA-Z0-9]+$/g, '');
|
|
|
|
|
return safeName || 'resource';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getWorkloadSecretName(serviceArgFromCloudly: plugins.servezoneInterfaces.data.IService) {
|
|
|
|
|
const serviceName = this.getDockerSafeName(serviceArgFromCloudly.data.name, 36);
|
|
|
|
|
const serviceId = this.getDockerSafeName(serviceArgFromCloudly.id, 20);
|
|
|
|
|
return this.getDockerSafeName(`${serviceName}-${serviceId}-secret`);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 16:07:32 +00:00
|
|
|
private getWorkloadServiceDeploymentLabels(
|
|
|
|
|
serviceArgFromCloudly: plugins.servezoneInterfaces.data.IService,
|
|
|
|
|
containerImageFromCloudly: plugins.servezoneInterfaces.data.IImage,
|
|
|
|
|
) {
|
|
|
|
|
const desiredImageVersion =
|
|
|
|
|
serviceArgFromCloudly.data.imageVersion ||
|
|
|
|
|
serviceArgFromCloudly.data.registryTarget?.tag ||
|
|
|
|
|
'latest';
|
|
|
|
|
const desiredImageVersionData = (containerImageFromCloudly.data.versions || []).find((versionArg) => {
|
|
|
|
|
return versionArg.versionString === desiredImageVersion;
|
|
|
|
|
});
|
|
|
|
|
const desiredRegistryDigest = desiredImageVersionData?.digest || (
|
|
|
|
|
containerImageFromCloudly.data.lastPushEvent?.tag === desiredImageVersion
|
|
|
|
|
? containerImageFromCloudly.data.lastPushEvent.digest
|
|
|
|
|
: ''
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
'serve.zone.serviceId': serviceArgFromCloudly.id,
|
|
|
|
|
'serve.zone.imageId': serviceArgFromCloudly.data.imageId || '',
|
|
|
|
|
'serve.zone.imageVersion': desiredImageVersion,
|
|
|
|
|
'serve.zone.registryImageUrl': serviceArgFromCloudly.data.registryTarget?.imageUrl || '',
|
|
|
|
|
'serve.zone.registryDigest': desiredRegistryDigest || '',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async pullRegistryTargetImage(
|
|
|
|
|
registryTargetArg: plugins.servezoneInterfaces.data.IRegistryTarget,
|
|
|
|
|
): Promise<plugins.docker.DockerImage> {
|
|
|
|
|
const registryImageName = `${registryTargetArg.registryHost}/${registryTargetArg.repository}`;
|
|
|
|
|
const registryImageTag = registryTargetArg.tag || 'latest';
|
|
|
|
|
const registryImageRef = `${registryImageName}:${registryImageTag}`;
|
|
|
|
|
const response = await this.coreflowRef.dockerHost.request(
|
|
|
|
|
'POST',
|
|
|
|
|
`/images/create?fromImage=${encodeURIComponent(registryImageName)}&tag=${encodeURIComponent(
|
|
|
|
|
registryImageTag,
|
|
|
|
|
)}`,
|
|
|
|
|
);
|
|
|
|
|
if (response.statusCode >= 300) {
|
|
|
|
|
const existingImage = await this.coreflowRef.dockerHost.getImageByName(registryImageRef);
|
|
|
|
|
if (existingImage) {
|
|
|
|
|
logger.log(
|
|
|
|
|
'warn',
|
|
|
|
|
`registry pull failed for ${registryImageRef}, using locally cached image`,
|
|
|
|
|
);
|
|
|
|
|
return existingImage;
|
|
|
|
|
}
|
|
|
|
|
throw new Error(`Failed to pull registry image ${registryImageRef}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const localDockerImage = await this.coreflowRef.dockerHost.getImageByName(registryImageRef);
|
|
|
|
|
if (!localDockerImage) {
|
|
|
|
|
throw new Error(`Registry image ${registryImageRef} not found after pull`);
|
|
|
|
|
}
|
|
|
|
|
return localDockerImage;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 00:05:16 +02:00
|
|
|
/**
|
|
|
|
|
* starts the cluster manager
|
|
|
|
|
*/
|
|
|
|
|
public async start() {
|
|
|
|
|
const config = await this.coreflowRef.cloudlyConnector.getConfigFromCloudly();
|
|
|
|
|
this.readyDeferred.resolve();
|
|
|
|
|
|
|
|
|
|
// subscriptions
|
2024-12-20 02:58:26 +01:00
|
|
|
// this subscription is the start point for most updates on the cluster
|
2024-05-09 00:05:16 +02:00
|
|
|
this.configSubscription =
|
2024-12-20 02:58:26 +01:00
|
|
|
this.coreflowRef.cloudlyConnector.cloudlyApiClient.configUpdateSubject.subscribe(
|
2024-05-09 00:05:16 +02:00
|
|
|
async (dataArg) => {
|
|
|
|
|
this.coreflowRef.taskManager.updateBaseServicesTask.trigger();
|
2024-12-29 14:14:46 +01:00
|
|
|
},
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* stops the clustermanager
|
|
|
|
|
*/
|
|
|
|
|
public async stop() {
|
|
|
|
|
this.configSubscription ? this.configSubscription.unsubscribe() : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* provisions base services
|
|
|
|
|
*/
|
|
|
|
|
public async provisionBaseServices() {
|
|
|
|
|
// swarm should be enabled by lower level serverconfig package
|
|
|
|
|
// get current situation
|
2026-04-28 12:02:22 +00:00
|
|
|
const networks = await this.coreflowRef.dockerHost.listNetworks();
|
2024-12-20 02:58:26 +01:00
|
|
|
logger.log('info', 'There are currently ' + networks.length + ' networks');
|
|
|
|
|
for (const network of networks) {
|
|
|
|
|
logger.log('info', 'Network: ' + network.Name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// make sure there is a network for the webgateway
|
2026-04-28 12:02:22 +00:00
|
|
|
let sznWebgatewayNetwork = await this.coreflowRef.dockerHost.getNetworkByName(
|
2024-12-29 14:14:46 +01:00
|
|
|
this.commonDockerData.networkNames.sznWebgateway,
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!sznWebgatewayNetwork) {
|
2024-12-20 02:58:26 +01:00
|
|
|
logger.log('info', 'Creating network: ' + this.commonDockerData.networkNames.sznWebgateway);
|
2026-04-28 12:02:22 +00:00
|
|
|
sznWebgatewayNetwork = await this.coreflowRef.dockerHost.createNetwork({
|
|
|
|
|
Name: this.commonDockerData.networkNames.sznWebgateway,
|
|
|
|
|
});
|
2024-05-09 00:05:16 +02:00
|
|
|
} else {
|
|
|
|
|
logger.log('ok', 'sznWebgateway is already present');
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-20 02:58:26 +01:00
|
|
|
// corechat network so base services can talk to each other
|
2026-04-28 12:02:22 +00:00
|
|
|
let sznCorechatNetwork = await this.coreflowRef.dockerHost.getNetworkByName(
|
2024-12-29 14:14:46 +01:00
|
|
|
this.commonDockerData.networkNames.sznCorechat,
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
|
|
|
|
if (!sznCorechatNetwork) {
|
2026-04-28 12:02:22 +00:00
|
|
|
sznCorechatNetwork = await this.coreflowRef.dockerHost.createNetwork({
|
|
|
|
|
Name: this.commonDockerData.networkNames.sznCorechat,
|
|
|
|
|
});
|
2024-05-09 00:05:16 +02:00
|
|
|
} else {
|
|
|
|
|
logger.log('ok', 'sznCorechat is already present');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.log('info', `Waiting for networks to be ready...`);
|
|
|
|
|
await plugins.smartdelay.delayFor(10000);
|
|
|
|
|
|
|
|
|
|
logger.log('success', `Successfully created networks for servezone`);
|
|
|
|
|
|
|
|
|
|
// Images
|
|
|
|
|
logger.log('info', `now updating docker images of base services...`);
|
|
|
|
|
|
2026-04-28 12:02:22 +00:00
|
|
|
const coretrafficImage = await this.coreflowRef.dockerHost.createImageFromRegistry({
|
|
|
|
|
imageUrl: 'code.foss.global/serve.zone/coretraffic',
|
|
|
|
|
});
|
2024-05-09 00:05:16 +02:00
|
|
|
|
2026-04-28 12:02:22 +00:00
|
|
|
const corelogImage = await this.coreflowRef.dockerHost.createImageFromRegistry({
|
|
|
|
|
imageUrl: 'code.foss.global/serve.zone/corelog',
|
|
|
|
|
});
|
2024-05-09 00:05:16 +02:00
|
|
|
|
|
|
|
|
// SERVICES
|
|
|
|
|
// lets deploy the base services
|
|
|
|
|
// coretraffic
|
2026-04-28 12:02:22 +00:00
|
|
|
let coretrafficService: plugins.docker.DockerService | null;
|
2026-04-28 16:57:54 +00:00
|
|
|
coretrafficService = await this.getDockerServiceByName(
|
2024-12-29 14:14:46 +01:00
|
|
|
'coretraffic',
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (coretrafficService && (await coretrafficService.needsUpdate())) {
|
|
|
|
|
await coretrafficService.remove();
|
|
|
|
|
await plugins.smartdelay.delayFor(5000);
|
|
|
|
|
coretrafficService = null;
|
|
|
|
|
} else {
|
|
|
|
|
logger.log('ok', `coretraffic service is up to date`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!coretrafficService) {
|
2026-04-28 12:02:22 +00:00
|
|
|
coretrafficService = await this.coreflowRef.dockerHost.createService({
|
|
|
|
|
image: coretrafficImage,
|
|
|
|
|
labels: {},
|
|
|
|
|
name: 'coretraffic',
|
|
|
|
|
networks: [sznCorechatNetwork, sznWebgatewayNetwork],
|
|
|
|
|
networkAlias: 'coretraffic',
|
|
|
|
|
ports: ['80:7999', '443:8000'],
|
|
|
|
|
secrets: [],
|
|
|
|
|
resources: {
|
|
|
|
|
memorySizeMB: 1100,
|
|
|
|
|
volumeMounts: [],
|
2024-05-09 00:05:16 +02:00
|
|
|
},
|
2026-04-28 12:02:22 +00:00
|
|
|
});
|
2024-05-09 00:05:16 +02:00
|
|
|
} else {
|
|
|
|
|
logger.log('ok', 'coretraffic service is already present');
|
|
|
|
|
}
|
|
|
|
|
logger.log('info', 'wait for coretraffic to be up and running');
|
|
|
|
|
await plugins.smartdelay.delayFor(10000);
|
|
|
|
|
|
|
|
|
|
// corelog
|
2026-04-28 12:02:22 +00:00
|
|
|
let corelogService: plugins.docker.DockerService | null;
|
2026-04-28 16:57:54 +00:00
|
|
|
corelogService = await this.getDockerServiceByName(
|
2024-12-29 14:14:46 +01:00
|
|
|
'corelog',
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (corelogService && (await corelogService.needsUpdate())) {
|
|
|
|
|
await corelogService.remove();
|
|
|
|
|
corelogService = null;
|
|
|
|
|
} else {
|
|
|
|
|
logger.log('ok', `corelog service is up to date`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!corelogService) {
|
2026-04-28 12:02:22 +00:00
|
|
|
corelogService = await this.coreflowRef.dockerHost.createService({
|
|
|
|
|
image: corelogImage,
|
|
|
|
|
labels: {},
|
|
|
|
|
name: 'corelog',
|
|
|
|
|
networks: [sznCorechatNetwork],
|
|
|
|
|
networkAlias: 'corelog',
|
|
|
|
|
ports: [],
|
|
|
|
|
secrets: [],
|
|
|
|
|
resources: {
|
|
|
|
|
memorySizeMB: 120,
|
|
|
|
|
volumeMounts: [],
|
2024-05-09 00:05:16 +02:00
|
|
|
},
|
2026-04-28 12:02:22 +00:00
|
|
|
});
|
2024-05-09 00:05:16 +02:00
|
|
|
} else {
|
|
|
|
|
logger.log('ok', 'corelog service is already present');
|
|
|
|
|
}
|
|
|
|
|
logger.log('info', 'waiting for corelog to be up and running');
|
|
|
|
|
await plugins.smartdelay.delayFor(10000);
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-20 02:58:26 +01:00
|
|
|
public async provisionWorkloadService(
|
2024-12-29 14:14:46 +01:00
|
|
|
serviceArgFromCloudly: plugins.servezoneInterfaces.data.IService,
|
2024-05-09 00:05:16 +02:00
|
|
|
) {
|
2024-12-29 14:14:46 +01:00
|
|
|
logger.log(
|
|
|
|
|
'info',
|
|
|
|
|
`deploying service ${serviceArgFromCloudly.data.name}@${serviceArgFromCloudly.data.imageVersion}...`,
|
|
|
|
|
);
|
2024-12-20 02:58:26 +01:00
|
|
|
|
|
|
|
|
// get the image from cloudly
|
2024-12-29 14:14:46 +01:00
|
|
|
logger.log(
|
|
|
|
|
'info',
|
|
|
|
|
`getting image for ${serviceArgFromCloudly.data.name}@${serviceArgFromCloudly.data.imageVersion}`,
|
|
|
|
|
);
|
|
|
|
|
const containerImageFromCloudly =
|
|
|
|
|
await this.coreflowRef.cloudlyConnector.cloudlyApiClient.image.getImageById(
|
|
|
|
|
serviceArgFromCloudly.data.imageId,
|
|
|
|
|
);
|
2026-04-28 16:07:32 +00:00
|
|
|
const deploymentLabels = this.getWorkloadServiceDeploymentLabels(
|
|
|
|
|
serviceArgFromCloudly,
|
|
|
|
|
containerImageFromCloudly,
|
|
|
|
|
);
|
2024-12-29 14:14:46 +01:00
|
|
|
let localDockerImage: plugins.docker.DockerImage;
|
|
|
|
|
|
2024-12-29 14:11:01 +01:00
|
|
|
// lets get the docker image for the service
|
2026-04-28 16:07:32 +00:00
|
|
|
if (serviceArgFromCloudly.data.registryTarget) {
|
|
|
|
|
await this.coreflowRef.dockerHost.auth({
|
|
|
|
|
username: this.coreflowRef.cloudlyConnector.identity.name,
|
|
|
|
|
password: this.coreflowRef.cloudlyConnector.coreflowJumpCode,
|
|
|
|
|
serveraddress: serviceArgFromCloudly.data.registryTarget.registryHost,
|
|
|
|
|
});
|
|
|
|
|
localDockerImage = await this.pullRegistryTargetImage(serviceArgFromCloudly.data.registryTarget);
|
|
|
|
|
} else if (containerImageFromCloudly.data.location?.internal) {
|
2024-12-29 14:14:46 +01:00
|
|
|
const imageStream = await containerImageFromCloudly.pullImageVersion(
|
|
|
|
|
serviceArgFromCloudly.data.imageVersion,
|
|
|
|
|
);
|
2026-04-28 12:02:22 +00:00
|
|
|
localDockerImage = await this.coreflowRef.dockerHost.createImageFromTarStream(
|
|
|
|
|
plugins.smartstream.nodewebhelpers.convertWebReadableToNodeReadable(imageStream),
|
2024-12-29 14:14:46 +01:00
|
|
|
{
|
2026-04-28 12:02:22 +00:00
|
|
|
imageUrl: containerImageFromCloudly.id,
|
|
|
|
|
imageTag: serviceArgFromCloudly.data.imageVersion,
|
2024-12-29 14:11:01 +01:00
|
|
|
},
|
2024-12-29 14:14:46 +01:00
|
|
|
);
|
2024-12-29 14:11:01 +01:00
|
|
|
} else if (
|
2026-04-28 16:07:32 +00:00
|
|
|
containerImageFromCloudly.data.location?.externalRegistryId &&
|
|
|
|
|
containerImageFromCloudly.data.location?.externalImageTag
|
2024-12-29 14:11:01 +01:00
|
|
|
) {
|
2024-12-29 14:14:46 +01:00
|
|
|
const externalRegistry =
|
|
|
|
|
await this.coreflowRef.cloudlyConnector.cloudlyApiClient.externalRegistry.getRegistryById(
|
|
|
|
|
containerImageFromCloudly.data.location.externalRegistryId,
|
|
|
|
|
);
|
2024-12-29 14:11:01 +01:00
|
|
|
// Lets authenticate against the external registry
|
|
|
|
|
// TODO: deduplicate this, check wether we are already authenticated
|
2026-04-28 12:02:22 +00:00
|
|
|
if (!externalRegistry.data.username || !externalRegistry.data.password) {
|
|
|
|
|
throw new Error(`External registry ${externalRegistry.id} is missing credentials`);
|
|
|
|
|
}
|
2024-12-29 14:11:01 +01:00
|
|
|
await this.coreflowRef.dockerHost.auth({
|
|
|
|
|
username: externalRegistry.data.username,
|
|
|
|
|
password: externalRegistry.data.password,
|
|
|
|
|
serveraddress: externalRegistry.data.url,
|
|
|
|
|
});
|
2026-04-28 12:02:22 +00:00
|
|
|
localDockerImage = await this.coreflowRef.dockerHost.createImageFromRegistry({
|
|
|
|
|
imageUrl: containerImageFromCloudly.id,
|
|
|
|
|
imageTag: serviceArgFromCloudly.data.imageVersion,
|
|
|
|
|
});
|
2024-12-29 14:11:01 +01:00
|
|
|
await localDockerImage.pullLatestImageFromRegistry();
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error('Invalid image location');
|
|
|
|
|
}
|
2024-05-09 00:05:16 +02:00
|
|
|
|
2026-04-28 16:57:54 +00:00
|
|
|
let containerService: plugins.docker.DockerService | null = await this.getDockerServiceByName(
|
2024-12-29 14:14:46 +01:00
|
|
|
serviceArgFromCloudly.data.name,
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
2024-12-20 02:58:26 +01:00
|
|
|
this.coreflowRef.cloudlyConnector.cloudlyApiClient;
|
2024-12-29 14:14:46 +01:00
|
|
|
|
2026-04-28 16:57:54 +00:00
|
|
|
const dockerSecretName = this.getWorkloadSecretName(serviceArgFromCloudly);
|
2026-04-28 12:02:22 +00:00
|
|
|
let containerSecret: plugins.docker.DockerSecret | undefined | null = await this.coreflowRef.dockerHost.getSecretByName(
|
2024-12-29 14:14:46 +01:00
|
|
|
dockerSecretName,
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// existing network to connect to
|
2026-04-28 12:02:22 +00:00
|
|
|
const webGatewayNetwork = await this.coreflowRef.dockerHost.getNetworkByName(
|
2024-12-29 14:14:46 +01:00
|
|
|
this.commonDockerData.networkNames.sznWebgateway,
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
2026-04-28 12:02:22 +00:00
|
|
|
if (!webGatewayNetwork) {
|
|
|
|
|
throw new Error(`Missing required Docker network ${this.commonDockerData.networkNames.sznWebgateway}`);
|
|
|
|
|
}
|
2024-05-09 00:05:16 +02:00
|
|
|
|
2026-04-28 16:07:32 +00:00
|
|
|
if (containerService) {
|
|
|
|
|
const existingLabels = containerService.Spec.Labels || {};
|
|
|
|
|
const cloudlyDeploymentLabelsChanged = Object.entries(deploymentLabels).some(([key, value]) => {
|
|
|
|
|
return existingLabels[key] !== value;
|
|
|
|
|
});
|
|
|
|
|
const dockerImageNeedsUpdate = serviceArgFromCloudly.data.registryTarget
|
|
|
|
|
? false
|
|
|
|
|
: await containerService.needsUpdate();
|
|
|
|
|
|
|
|
|
|
if (cloudlyDeploymentLabelsChanged || dockerImageNeedsUpdate) {
|
|
|
|
|
logger.log('info', `service ${serviceArgFromCloudly.data.name} desired state changed, recreating`);
|
|
|
|
|
await containerService.remove();
|
|
|
|
|
if (containerSecret) {
|
|
|
|
|
await containerSecret.remove();
|
|
|
|
|
}
|
|
|
|
|
containerService = null;
|
|
|
|
|
containerSecret = null;
|
2024-05-09 00:05:16 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!containerService) {
|
2026-04-28 12:02:22 +00:00
|
|
|
containerSecret = await this.coreflowRef.dockerHost.getSecretByName(
|
2024-12-29 14:14:46 +01:00
|
|
|
dockerSecretName,
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
|
|
|
|
if (containerSecret) {
|
|
|
|
|
await containerSecret.remove();
|
|
|
|
|
}
|
2024-12-20 02:58:26 +01:00
|
|
|
|
2024-12-29 14:14:46 +01:00
|
|
|
const secretBundle =
|
|
|
|
|
await this.coreflowRef.cloudlyConnector.cloudlyApiClient.secretbundle.getSecretBundleById(
|
|
|
|
|
serviceArgFromCloudly.data.secretBundleId,
|
|
|
|
|
);
|
2024-12-20 02:58:26 +01:00
|
|
|
|
2024-12-29 14:11:01 +01:00
|
|
|
// lets create the relevant stuff on the docker side
|
2026-04-28 12:02:22 +00:00
|
|
|
containerSecret = await this.coreflowRef.dockerHost.createSecret({
|
|
|
|
|
name: dockerSecretName,
|
|
|
|
|
contentArg: JSON.stringify(await secretBundle.getFlatKeyValueObjectForEnvironment()),
|
|
|
|
|
labels: {},
|
|
|
|
|
version: serviceArgFromCloudly.data.imageVersion,
|
|
|
|
|
});
|
|
|
|
|
containerService = await this.coreflowRef.dockerHost.createService({
|
|
|
|
|
name: serviceArgFromCloudly.data.name,
|
|
|
|
|
image: localDockerImage,
|
|
|
|
|
networks: [webGatewayNetwork],
|
|
|
|
|
secrets: [containerSecret],
|
|
|
|
|
ports: [],
|
2026-04-28 16:07:32 +00:00
|
|
|
labels: deploymentLabels,
|
2026-04-28 12:02:22 +00:00
|
|
|
resources: serviceArgFromCloudly.data.resources,
|
|
|
|
|
// TODO: introduce a clean name here, that is guaranteed to work with APIs.
|
|
|
|
|
networkAlias: serviceArgFromCloudly.data.name,
|
|
|
|
|
});
|
2024-05-09 00:05:16 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 14:16:58 +00:00
|
|
|
public async provisionWorkloadServices(
|
|
|
|
|
_clusterConfigArg: plugins.servezoneInterfaces.data.ICluster,
|
|
|
|
|
) {
|
|
|
|
|
const services =
|
|
|
|
|
(await this.coreflowRef.cloudlyConnector.cloudlyApiClient.services.getServices()) as unknown as plugins.servezoneInterfaces.data.IService[];
|
|
|
|
|
for (const service of services) {
|
|
|
|
|
if (service.data.serviceCategory && service.data.serviceCategory !== 'workload') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
await this.provisionWorkloadService(service);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 00:05:16 +02:00
|
|
|
/**
|
|
|
|
|
* update traffic routing
|
|
|
|
|
*/
|
2024-12-29 14:14:46 +01:00
|
|
|
public async updateTrafficRouting(
|
2026-04-25 14:16:58 +00:00
|
|
|
_clusterConfigArg: plugins.servezoneInterfaces.data.ICluster,
|
2024-12-29 14:14:46 +01:00
|
|
|
) {
|
2026-04-28 12:02:22 +00:00
|
|
|
const services = await this.coreflowRef.dockerHost.listServices();
|
|
|
|
|
const webGatewayNetwork = await this.coreflowRef.dockerHost.getNetworkByName(
|
2024-12-29 14:14:46 +01:00
|
|
|
this.commonDockerData.networkNames.sznWebgateway,
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
2026-04-28 12:02:22 +00:00
|
|
|
if (!webGatewayNetwork) {
|
|
|
|
|
throw new Error(`Missing required Docker network ${this.commonDockerData.networkNames.sznWebgateway}`);
|
|
|
|
|
}
|
2024-05-09 00:05:16 +02:00
|
|
|
const reverseProxyConfigs: plugins.servezoneInterfaces.data.IReverseProxyConfig[] = [];
|
|
|
|
|
|
|
|
|
|
const pushProxyConfig = async (
|
|
|
|
|
serviceNameArg: string,
|
|
|
|
|
hostNameArg: string,
|
|
|
|
|
containerDestinationIp: string,
|
2024-12-29 14:14:46 +01:00
|
|
|
webDestinationPort: string,
|
2024-05-09 00:05:16 +02:00
|
|
|
) => {
|
|
|
|
|
logger.log('ok', `trying to obtain a certificate for ${hostNameArg}`);
|
|
|
|
|
const certificate =
|
|
|
|
|
await this.coreflowRef.cloudlyConnector.getCertificateForDomainFromCloudly(hostNameArg);
|
|
|
|
|
reverseProxyConfigs.push({
|
2026-04-25 14:16:58 +00:00
|
|
|
destinationIps: [containerDestinationIp],
|
|
|
|
|
destinationPorts: [Number(webDestinationPort)],
|
2024-05-09 00:05:16 +02:00
|
|
|
hostName: hostNameArg,
|
|
|
|
|
privateKey: certificate.privateKey,
|
|
|
|
|
publicKey: certificate.publicKey,
|
|
|
|
|
});
|
|
|
|
|
logger.log(
|
|
|
|
|
'success',
|
2024-12-29 14:14:46 +01:00
|
|
|
`pushed routing config for ${hostNameArg} on workload service ${serviceNameArg}`,
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
logger.log('info', `Found ${services.length} services!`);
|
2026-04-25 14:16:58 +00:00
|
|
|
const workloadServices =
|
|
|
|
|
(await this.coreflowRef.cloudlyConnector.cloudlyApiClient.services.getServices()) as unknown as plugins.servezoneInterfaces.data.IService[];
|
2024-05-09 00:05:16 +02:00
|
|
|
for (const service of services) {
|
2026-04-25 14:16:58 +00:00
|
|
|
let workloadConfig: plugins.servezoneInterfaces.data.IService | undefined;
|
|
|
|
|
for (const workloadServiceConfig of workloadServices) {
|
|
|
|
|
if (service.Spec.Name === workloadServiceConfig.data.name) {
|
2024-05-09 00:05:16 +02:00
|
|
|
workloadConfig = workloadServiceConfig;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (workloadConfig) {
|
|
|
|
|
logger.log('ok', `found workload service ${service.Spec.Name}`);
|
2026-04-28 19:46:44 +00:00
|
|
|
const containersOfServicesOnNetwork = (
|
|
|
|
|
await webGatewayNetwork.getContainersOnNetworkForService(service)
|
|
|
|
|
).filter((container) => container.Name !== service.Spec.Name);
|
2024-05-09 00:05:16 +02:00
|
|
|
// TODO: make this multi container ready
|
|
|
|
|
if (!containersOfServicesOnNetwork[0]) {
|
|
|
|
|
logger.log(
|
|
|
|
|
'error',
|
2024-12-29 14:14:46 +01:00
|
|
|
`There seems to be no container available for service ${service.Spec.Name}`,
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const containerDestinationIp = containersOfServicesOnNetwork[0].IPv4Address.split('/')[0];
|
2026-04-25 14:16:58 +00:00
|
|
|
const hostNames = (workloadConfig.data.domains || []).map((domainEntry) => domainEntry.name);
|
2024-05-09 00:05:16 +02:00
|
|
|
|
|
|
|
|
// lets route the web port
|
2026-04-25 14:16:58 +00:00
|
|
|
const webDestinationPort: string = workloadConfig.data.ports.web.toString();
|
2024-05-09 00:05:16 +02:00
|
|
|
for (const hostName of hostNames) {
|
|
|
|
|
await pushProxyConfig(
|
2026-04-25 14:16:58 +00:00
|
|
|
workloadConfig.data.name,
|
2024-05-09 00:05:16 +02:00
|
|
|
hostName,
|
|
|
|
|
containerDestinationIp,
|
2024-12-29 14:14:46 +01:00
|
|
|
webDestinationPort,
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// lets route custom ports
|
2026-04-25 14:16:58 +00:00
|
|
|
if (workloadConfig.data.ports.custom) {
|
|
|
|
|
const customDomainKeys = Object.keys(workloadConfig.data.ports.custom);
|
2024-05-09 00:05:16 +02:00
|
|
|
for (const customDomainKey of customDomainKeys) {
|
|
|
|
|
await pushProxyConfig(
|
2026-04-25 14:16:58 +00:00
|
|
|
workloadConfig.data.name,
|
2024-05-09 00:05:16 +02:00
|
|
|
customDomainKey,
|
|
|
|
|
containerDestinationIp,
|
2026-04-25 14:16:58 +00:00
|
|
|
workloadConfig.data.ports.custom[customDomainKey],
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
logger.log(
|
|
|
|
|
'ok',
|
2024-12-29 14:14:46 +01:00
|
|
|
`service ${service.Spec.Name} is not a workload service and won't receive traffic`,
|
2024-05-09 00:05:16 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
console.log(reverseProxyConfigs);
|
|
|
|
|
await this.coreflowRef.corechatConnector.setReverseConfigs(reverseProxyConfigs);
|
|
|
|
|
}
|
|
|
|
|
}
|