feat: reconcile registry image updates
This commit is contained in:
@@ -19,6 +19,63 @@ export class ClusterManager {
|
||||
this.coreflowRef = coreflowRefArg;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* starts the cluster manager
|
||||
*/
|
||||
@@ -184,10 +241,21 @@ 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
|
||||
if (containerImageFromCloudly.data.location.internal) {
|
||||
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) {
|
||||
const imageStream = await containerImageFromCloudly.pullImageVersion(
|
||||
serviceArgFromCloudly.data.imageVersion,
|
||||
);
|
||||
@@ -199,8 +267,8 @@ export class ClusterManager {
|
||||
},
|
||||
);
|
||||
} else if (
|
||||
containerImageFromCloudly.data.location.externalRegistryId &&
|
||||
containerImageFromCloudly.data.location.externalImageTag
|
||||
containerImageFromCloudly.data.location?.externalRegistryId &&
|
||||
containerImageFromCloudly.data.location?.externalImageTag
|
||||
) {
|
||||
const externalRegistry =
|
||||
await this.coreflowRef.cloudlyConnector.cloudlyApiClient.externalRegistry.getRegistryById(
|
||||
@@ -243,13 +311,24 @@ export class ClusterManager {
|
||||
throw new Error(`Missing required Docker network ${this.commonDockerData.networkNames.sznWebgateway}`);
|
||||
}
|
||||
|
||||
if (containerService && (await containerService.needsUpdate())) {
|
||||
await containerService.remove();
|
||||
if (containerSecret) {
|
||||
await containerSecret.remove();
|
||||
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;
|
||||
}
|
||||
containerService = null;
|
||||
containerSecret = null;
|
||||
}
|
||||
|
||||
if (!containerService) {
|
||||
@@ -278,7 +357,7 @@ export class ClusterManager {
|
||||
networks: [webGatewayNetwork],
|
||||
secrets: [containerSecret],
|
||||
ports: [],
|
||||
labels: {},
|
||||
labels: deploymentLabels,
|
||||
resources: serviceArgFromCloudly.data.resources,
|
||||
// TODO: introduce a clean name here, that is guaranteed to work with APIs.
|
||||
networkAlias: serviceArgFromCloudly.data.name,
|
||||
|
||||
Reference in New Issue
Block a user