BREAKING CHANGE(DockerHost): Refactor public API to DockerHost facade; introduce DockerResource base; make resource static methods internal; support flexible descriptors and stream compatibility
This commit is contained in:
@@ -2,13 +2,19 @@ import * as plugins from './plugins.js';
|
||||
import * as interfaces from './interfaces/index.js';
|
||||
|
||||
import { DockerHost } from './classes.host.js';
|
||||
import { DockerResource } from './classes.base.js';
|
||||
import { DockerImage } from './classes.image.js';
|
||||
import { DockerSecret } from './classes.secret.js';
|
||||
import { logger } from './logger.js';
|
||||
|
||||
export class DockerService {
|
||||
// STATIC
|
||||
public static async getServices(dockerHost: DockerHost) {
|
||||
export class DockerService extends DockerResource {
|
||||
// STATIC (Internal - prefixed with _ to indicate internal use)
|
||||
|
||||
/**
|
||||
* Internal: Get all services
|
||||
* Public API: Use dockerHost.getServices() instead
|
||||
*/
|
||||
public static async _list(dockerHost: DockerHost) {
|
||||
const services: DockerService[] = [];
|
||||
const response = await dockerHost.request('GET', '/services');
|
||||
for (const serviceObject of response.body) {
|
||||
@@ -19,11 +25,15 @@ export class DockerService {
|
||||
return services;
|
||||
}
|
||||
|
||||
public static async getServiceByName(
|
||||
/**
|
||||
* Internal: Get service by name
|
||||
* Public API: Use dockerHost.getServiceByName(name) instead
|
||||
*/
|
||||
public static async _fromName(
|
||||
dockerHost: DockerHost,
|
||||
networkName: string,
|
||||
): Promise<DockerService> {
|
||||
const allServices = await DockerService.getServices(dockerHost);
|
||||
const allServices = await DockerService._list(dockerHost);
|
||||
const wantedService = allServices.find((service) => {
|
||||
return service.Spec.Name === networkName;
|
||||
});
|
||||
@@ -31,20 +41,30 @@ export class DockerService {
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a service
|
||||
* Internal: Create a service
|
||||
* Public API: Use dockerHost.createService(descriptor) instead
|
||||
*/
|
||||
public static async createService(
|
||||
public static async _create(
|
||||
dockerHost: DockerHost,
|
||||
serviceCreationDescriptor: interfaces.IServiceCreationDescriptor,
|
||||
): Promise<DockerService> {
|
||||
// lets get the image
|
||||
logger.log(
|
||||
'info',
|
||||
`now creating service ${serviceCreationDescriptor.name}`,
|
||||
);
|
||||
|
||||
// await serviceCreationDescriptor.image.pullLatestImageFromRegistry();
|
||||
const serviceVersion = await serviceCreationDescriptor.image.getVersion();
|
||||
// Resolve image (support both string and DockerImage instance)
|
||||
let imageInstance: DockerImage;
|
||||
if (typeof serviceCreationDescriptor.image === 'string') {
|
||||
imageInstance = await DockerImage._fromName(dockerHost, serviceCreationDescriptor.image);
|
||||
if (!imageInstance) {
|
||||
throw new Error(`Image not found: ${serviceCreationDescriptor.image}`);
|
||||
}
|
||||
} else {
|
||||
imageInstance = serviceCreationDescriptor.image;
|
||||
}
|
||||
|
||||
const serviceVersion = await imageInstance.getVersion();
|
||||
|
||||
const labels: interfaces.TLabels = {
|
||||
...serviceCreationDescriptor.labels,
|
||||
@@ -90,6 +110,7 @@ export class DockerService {
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve networks (support both string[] and DockerNetwork[])
|
||||
const networkArray: Array<{
|
||||
Target: string;
|
||||
Aliases: string[];
|
||||
@@ -101,8 +122,11 @@ export class DockerService {
|
||||
logger.log('warn', 'Skipping null network in service creation');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve network name
|
||||
const networkName = typeof network === 'string' ? network : network.Name;
|
||||
networkArray.push({
|
||||
Target: network.Name,
|
||||
Target: networkName,
|
||||
Aliases: [serviceCreationDescriptor.networkAlias],
|
||||
});
|
||||
}
|
||||
@@ -119,9 +143,20 @@ export class DockerService {
|
||||
});
|
||||
}
|
||||
|
||||
// lets configure secrets
|
||||
// Resolve secrets (support both string[] and DockerSecret[])
|
||||
const secretArray: any[] = [];
|
||||
for (const secret of serviceCreationDescriptor.secrets) {
|
||||
// Resolve secret instance
|
||||
let secretInstance: DockerSecret;
|
||||
if (typeof secret === 'string') {
|
||||
secretInstance = await DockerSecret._fromName(dockerHost, secret);
|
||||
if (!secretInstance) {
|
||||
throw new Error(`Secret not found: ${secret}`);
|
||||
}
|
||||
} else {
|
||||
secretInstance = secret;
|
||||
}
|
||||
|
||||
secretArray.push({
|
||||
File: {
|
||||
Name: 'secret.json', // TODO: make sure that works with multiple secrets
|
||||
@@ -129,8 +164,8 @@ export class DockerService {
|
||||
GID: '33',
|
||||
Mode: 384,
|
||||
},
|
||||
SecretID: secret.ID,
|
||||
SecretName: secret.Spec.Name,
|
||||
SecretID: secretInstance.ID,
|
||||
SecretName: secretInstance.Spec.Name,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -155,7 +190,7 @@ export class DockerService {
|
||||
Name: serviceCreationDescriptor.name,
|
||||
TaskTemplate: {
|
||||
ContainerSpec: {
|
||||
Image: serviceCreationDescriptor.image.RepoTags[0],
|
||||
Image: imageInstance.RepoTags[0],
|
||||
Labels: labels,
|
||||
Secrets: secretArray,
|
||||
Mounts: mounts,
|
||||
@@ -189,15 +224,15 @@ export class DockerService {
|
||||
},
|
||||
});
|
||||
|
||||
const createdService = await DockerService.getServiceByName(
|
||||
const createdService = await DockerService._fromName(
|
||||
dockerHost,
|
||||
serviceCreationDescriptor.name,
|
||||
);
|
||||
return createdService;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
public dockerHostRef: DockerHost;
|
||||
// INSTANCE PROPERTIES
|
||||
// Note: dockerHost (not dockerHostRef) for consistency with base class
|
||||
|
||||
public ID: string;
|
||||
public Version: { Index: number };
|
||||
@@ -229,27 +264,49 @@ export class DockerService {
|
||||
public Endpoint: { Spec: {}; VirtualIPs: [any[]] };
|
||||
|
||||
constructor(dockerHostArg: DockerHost) {
|
||||
this.dockerHostRef = dockerHostArg;
|
||||
super(dockerHostArg);
|
||||
}
|
||||
|
||||
// INSTANCE METHODS
|
||||
|
||||
/**
|
||||
* Refreshes this service's state from the Docker daemon
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
const updated = await DockerService._fromName(this.dockerHost, this.Spec.Name);
|
||||
if (updated) {
|
||||
Object.assign(this, updated);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes this service from the Docker daemon
|
||||
*/
|
||||
public async remove() {
|
||||
await this.dockerHostRef.request('DELETE', `/services/${this.ID}`);
|
||||
await this.dockerHost.request('DELETE', `/services/${this.ID}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-reads service data from Docker engine
|
||||
* @deprecated Use refresh() instead
|
||||
*/
|
||||
public async reReadFromDockerEngine() {
|
||||
const dockerData = await this.dockerHostRef.request(
|
||||
const dockerData = await this.dockerHost.request(
|
||||
'GET',
|
||||
`/services/${this.ID}`,
|
||||
);
|
||||
// TODO: Better assign: Object.assign(this, dockerData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this service needs an update based on image version
|
||||
*/
|
||||
public async needsUpdate(): Promise<boolean> {
|
||||
// TODO: implement digest based update recognition
|
||||
|
||||
await this.reReadFromDockerEngine();
|
||||
const dockerImage = await DockerImage.createFromRegistry(
|
||||
this.dockerHostRef,
|
||||
const dockerImage = await DockerImage._createFromRegistry(
|
||||
this.dockerHost,
|
||||
{
|
||||
creationObject: {
|
||||
imageUrl: this.Spec.TaskTemplate.ContainerSpec.Image,
|
||||
|
||||
Reference in New Issue
Block a user