feat: reconcile registry image updates

This commit is contained in:
2026-04-28 16:07:32 +00:00
parent b221e9fe43
commit f8e8cc43c4
3 changed files with 102 additions and 23 deletions
+89 -10
View File
@@ -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,