feat: reconcile registry image updates
This commit is contained in:
+2
-2
@@ -79,8 +79,8 @@
|
||||
"@push.rocks/smartstream": "^3.4.0",
|
||||
"@push.rocks/smartstring": "^4.1.0",
|
||||
"@push.rocks/taskbuffer": "^8.0.2",
|
||||
"@serve.zone/api": "^5.3.2",
|
||||
"@serve.zone/interfaces": "^5.4.5",
|
||||
"@serve.zone/api": "^5.3.4",
|
||||
"@serve.zone/interfaces": "^5.4.6",
|
||||
"@tsclass/tsclass": "^9.5.0",
|
||||
"@types/node": "25.6.0"
|
||||
},
|
||||
|
||||
Generated
+11
-11
@@ -69,11 +69,11 @@ importers:
|
||||
specifier: ^8.0.2
|
||||
version: 8.0.2
|
||||
'@serve.zone/api':
|
||||
specifier: ^5.3.2
|
||||
version: 5.3.2(@push.rocks/smartserve@2.0.3)
|
||||
specifier: ^5.3.4
|
||||
version: 5.3.4(@push.rocks/smartserve@2.0.3)
|
||||
'@serve.zone/interfaces':
|
||||
specifier: ^5.4.5
|
||||
version: 5.4.5
|
||||
specifier: ^5.4.6
|
||||
version: 5.4.6
|
||||
'@tsclass/tsclass':
|
||||
specifier: ^9.5.0
|
||||
version: 9.5.0
|
||||
@@ -1516,11 +1516,11 @@ packages:
|
||||
'@sec-ant/readable-stream@0.4.1':
|
||||
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
||||
|
||||
'@serve.zone/api@5.3.2':
|
||||
resolution: {integrity: sha512-ETQ4KSNfhDP7O1WxXXLcMn/A+jZtDfd7FjuQ0k3n8tnXG9hExh8ZmqvMwVj8eT2CnXO+xQVlbAgT0HLMLnxCfA==}
|
||||
'@serve.zone/api@5.3.4':
|
||||
resolution: {integrity: sha512-3CqyeZkZPCJ4775UoNPKfknhTlAk6zmU/MVVSu6DoIAWgUaOuAlLUHlV45xIGtHmKAppsiYUoyoEhBLTZf9iMw==}
|
||||
|
||||
'@serve.zone/interfaces@5.4.5':
|
||||
resolution: {integrity: sha512-asqUUjem3MGfIbseovHR8SxE+6FvjeQEYtV+PxcyY8YRXJ/vE3hNCDs7ePXgBbh4JXa+vNMaXHsFfz5Vrk6Ggg==}
|
||||
'@serve.zone/interfaces@5.4.6':
|
||||
resolution: {integrity: sha512-o4k7Wr6t3NLiP6gfAZZz8Jx8RlQ4sZYHTbhr4WkXzGf78vczFRIuFLyY1Y+TTNzDLEIzLVIyMsuECMV1KTwB2Q==}
|
||||
|
||||
'@sindresorhus/is@5.6.0':
|
||||
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
||||
@@ -6929,7 +6929,7 @@ snapshots:
|
||||
|
||||
'@sec-ant/readable-stream@0.4.1': {}
|
||||
|
||||
'@serve.zone/api@5.3.2(@push.rocks/smartserve@2.0.3)':
|
||||
'@serve.zone/api@5.3.4(@push.rocks/smartserve@2.0.3)':
|
||||
dependencies:
|
||||
'@api.global/typedrequest': 3.1.10
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
@@ -6938,7 +6938,7 @@ snapshots:
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smartstream': 3.4.0
|
||||
'@serve.zone/interfaces': 5.4.5
|
||||
'@serve.zone/interfaces': 5.4.6
|
||||
'@tsclass/tsclass': 9.5.0
|
||||
transitivePeerDependencies:
|
||||
- '@nuxt/kit'
|
||||
@@ -6949,7 +6949,7 @@ snapshots:
|
||||
- utf-8-validate
|
||||
- vue
|
||||
|
||||
'@serve.zone/interfaces@5.4.5':
|
||||
'@serve.zone/interfaces@5.4.6':
|
||||
dependencies:
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
'@push.rocks/smartlog-interfaces': 3.0.2
|
||||
|
||||
@@ -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,7 +311,17 @@ export class ClusterManager {
|
||||
throw new Error(`Missing required Docker network ${this.commonDockerData.networkNames.sznWebgateway}`);
|
||||
}
|
||||
|
||||
if (containerService && (await containerService.needsUpdate())) {
|
||||
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();
|
||||
@@ -251,6 +329,7 @@ export class ClusterManager {
|
||||
containerService = null;
|
||||
containerSecret = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!containerService) {
|
||||
containerSecret = await this.coreflowRef.dockerHost.getSecretByName(
|
||||
@@ -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