feat: provision corestore bindings
This commit is contained in:
@@ -2,6 +2,7 @@ import * as plugins from './coreflow.plugins.js';
|
||||
import { logger } from './coreflow.logging.js';
|
||||
import { Coreflow } from './coreflow.classes.coreflow.js';
|
||||
import type { IExternalGatewayConfig } from './coreflow.connector.externalgateway.js';
|
||||
import * as crypto from 'node:crypto';
|
||||
|
||||
export class ClusterManager {
|
||||
public coreflowRef: Coreflow;
|
||||
@@ -49,6 +50,7 @@ export class ClusterManager {
|
||||
private getWorkloadServiceDeploymentLabels(
|
||||
serviceArgFromCloudly: plugins.servezoneInterfaces.data.IService,
|
||||
containerImageFromCloudly: plugins.servezoneInterfaces.data.IImage,
|
||||
secretHashArg = '',
|
||||
) {
|
||||
const desiredImageVersion =
|
||||
serviceArgFromCloudly.data.imageVersion ||
|
||||
@@ -69,9 +71,27 @@ export class ClusterManager {
|
||||
'serve.zone.imageVersion': desiredImageVersion,
|
||||
'serve.zone.registryImageUrl': serviceArgFromCloudly.data.registryTarget?.imageUrl || '',
|
||||
'serve.zone.registryDigest': desiredRegistryDigest || '',
|
||||
'serve.zone.secretHash': secretHashArg,
|
||||
};
|
||||
}
|
||||
|
||||
private stableStringify(valueArg: unknown): string {
|
||||
if (Array.isArray(valueArg)) {
|
||||
return `[${valueArg.map((itemArg) => this.stableStringify(itemArg)).join(',')}]`;
|
||||
}
|
||||
if (valueArg && typeof valueArg === 'object') {
|
||||
return `{${Object.keys(valueArg as Record<string, unknown>)
|
||||
.sort()
|
||||
.map((keyArg) => `${JSON.stringify(keyArg)}:${this.stableStringify((valueArg as Record<string, unknown>)[keyArg])}`)
|
||||
.join(',')}}`;
|
||||
}
|
||||
return JSON.stringify(valueArg);
|
||||
}
|
||||
|
||||
private hashSecretObject(secretObjectArg: Record<string, string>) {
|
||||
return crypto.createHash('sha256').update(this.stableStringify(secretObjectArg)).digest('hex');
|
||||
}
|
||||
|
||||
private async pullRegistryTargetImage(
|
||||
registryTargetArg: plugins.servezoneInterfaces.data.IRegistryTarget,
|
||||
): Promise<plugins.docker.DockerImage> {
|
||||
@@ -103,6 +123,70 @@ export class ClusterManager {
|
||||
return localDockerImage;
|
||||
}
|
||||
|
||||
private async createCorestoreGlobalService(
|
||||
corestoreImageArg: plugins.docker.DockerImage,
|
||||
networksArg: Array<plugins.docker.DockerNetwork>,
|
||||
) {
|
||||
const corestoreImage = corestoreImageArg as unknown as {
|
||||
RepoTags?: string[];
|
||||
Labels?: Record<string, string>;
|
||||
};
|
||||
const imageRef = corestoreImage.RepoTags?.[0] || 'code.foss.global/serve.zone/corestore:latest';
|
||||
const corestoreEnv = [
|
||||
'CORESTORE_DATA_DIR=/data/corestore',
|
||||
'CORESTORE_PUBLIC_HOST=corestore',
|
||||
...(process.env.CORESTORE_API_TOKEN
|
||||
? [`CORESTORE_API_TOKEN=${process.env.CORESTORE_API_TOKEN}`]
|
||||
: []),
|
||||
];
|
||||
const response = await this.coreflowRef.dockerHost.request('POST', '/services/create', {
|
||||
Name: 'corestore',
|
||||
Labels: {
|
||||
version: corestoreImage.Labels?.version || '',
|
||||
'serve.zone.serviceCategory': 'base',
|
||||
'serve.zone.provides': 'database,objectstorage',
|
||||
},
|
||||
TaskTemplate: {
|
||||
ContainerSpec: {
|
||||
Image: imageRef,
|
||||
Labels: {
|
||||
'serve.zone.serviceCategory': 'base',
|
||||
'serve.zone.provides': 'database,objectstorage',
|
||||
},
|
||||
Env: corestoreEnv,
|
||||
Mounts: [
|
||||
{
|
||||
Target: '/data/corestore',
|
||||
Source: '/var/lib/serve.zone/corestore',
|
||||
Type: 'bind',
|
||||
ReadOnly: false,
|
||||
Consistency: 'default',
|
||||
},
|
||||
],
|
||||
},
|
||||
Networks: networksArg.map((networkArg) => ({
|
||||
Target: networkArg.Name,
|
||||
Aliases: ['corestore'],
|
||||
})),
|
||||
RestartPolicy: {
|
||||
Condition: 'any',
|
||||
},
|
||||
Resources: {
|
||||
Limits: {
|
||||
MemoryBytes: 700 * 1000000,
|
||||
},
|
||||
},
|
||||
},
|
||||
Mode: {
|
||||
Global: {},
|
||||
},
|
||||
});
|
||||
if (response.statusCode >= 300) {
|
||||
throw new Error(`Failed to create corestore service: ${JSON.stringify(response.body)}`);
|
||||
}
|
||||
return this.getDockerServiceByName('corestore');
|
||||
}
|
||||
|
||||
/**
|
||||
* starts the cluster manager
|
||||
*/
|
||||
@@ -181,6 +265,10 @@ export class ClusterManager {
|
||||
imageUrl: 'code.foss.global/serve.zone/corelog',
|
||||
});
|
||||
|
||||
const corestoreImage = await this.coreflowRef.dockerHost.createImageFromRegistry({
|
||||
imageUrl: 'code.foss.global/serve.zone/corestore',
|
||||
});
|
||||
|
||||
// SERVICES
|
||||
// lets deploy the base services
|
||||
// coretraffic
|
||||
@@ -249,6 +337,32 @@ export class ClusterManager {
|
||||
}
|
||||
logger.log('info', 'waiting for corelog to be up and running');
|
||||
await plugins.smartdelay.delayFor(10000);
|
||||
|
||||
// corestore
|
||||
let corestoreService: plugins.docker.DockerService | null;
|
||||
corestoreService = await this.getDockerServiceByName('corestore');
|
||||
|
||||
if (
|
||||
corestoreService &&
|
||||
(((corestoreService.Spec as any).Mode && !(corestoreService.Spec as any).Mode.Global) ||
|
||||
(await corestoreService.needsUpdate()))
|
||||
) {
|
||||
await corestoreService.remove();
|
||||
corestoreService = null;
|
||||
} else {
|
||||
logger.log('ok', `corestore service is up to date`);
|
||||
}
|
||||
|
||||
if (!corestoreService) {
|
||||
corestoreService = await this.createCorestoreGlobalService(corestoreImage, [
|
||||
sznCorechatNetwork,
|
||||
sznWebgatewayNetwork,
|
||||
]);
|
||||
} else {
|
||||
logger.log('ok', 'corestore service is already present');
|
||||
}
|
||||
logger.log('info', 'waiting for corestore to be up and running');
|
||||
await plugins.smartdelay.delayFor(10000);
|
||||
}
|
||||
|
||||
public async provisionWorkloadService(
|
||||
@@ -268,10 +382,6 @@ export class ClusterManager {
|
||||
await this.coreflowRef.cloudlyConnector.cloudlyApiClient.image.getImageById(
|
||||
serviceArgFromCloudly.data.imageId,
|
||||
);
|
||||
const deploymentLabels = this.getWorkloadServiceDeploymentLabels(
|
||||
serviceArgFromCloudly,
|
||||
containerImageFromCloudly,
|
||||
);
|
||||
let localDockerImage: plugins.docker.DockerImage;
|
||||
|
||||
// lets get the docker image for the service
|
||||
@@ -330,6 +440,24 @@ export class ClusterManager {
|
||||
dockerSecretName,
|
||||
);
|
||||
|
||||
const secretBundle =
|
||||
await this.coreflowRef.cloudlyConnector.cloudlyApiClient.secretbundle.getSecretBundleById(
|
||||
serviceArgFromCloudly.data.secretBundleId,
|
||||
);
|
||||
const platformEnvObject = await this.coreflowRef.platformManager.provisionBindingsForService(
|
||||
serviceArgFromCloudly,
|
||||
);
|
||||
const secretObject = {
|
||||
...platformEnvObject,
|
||||
...(await secretBundle.getFlatKeyValueObjectForEnvironment()),
|
||||
};
|
||||
const secretHash = this.hashSecretObject(secretObject);
|
||||
const deploymentLabels = this.getWorkloadServiceDeploymentLabels(
|
||||
serviceArgFromCloudly,
|
||||
containerImageFromCloudly,
|
||||
secretHash,
|
||||
);
|
||||
|
||||
// existing network to connect to
|
||||
const webGatewayNetwork = await this.coreflowRef.dockerHost.getNetworkByName(
|
||||
this.commonDockerData.networkNames.sznWebgateway,
|
||||
@@ -366,15 +494,10 @@ export class ClusterManager {
|
||||
await containerSecret.remove();
|
||||
}
|
||||
|
||||
const secretBundle =
|
||||
await this.coreflowRef.cloudlyConnector.cloudlyApiClient.secretbundle.getSecretBundleById(
|
||||
serviceArgFromCloudly.data.secretBundleId,
|
||||
);
|
||||
|
||||
// lets create the relevant stuff on the docker side
|
||||
containerSecret = await this.coreflowRef.dockerHost.createSecret({
|
||||
name: dockerSecretName,
|
||||
contentArg: JSON.stringify(await secretBundle.getFlatKeyValueObjectForEnvironment()),
|
||||
contentArg: JSON.stringify(secretObject),
|
||||
labels: {},
|
||||
version: serviceArgFromCloudly.data.imageVersion,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user