Files
docker/ts/classes.service.ts

331 lines
9.0 KiB
TypeScript
Raw Normal View History

import * as plugins from './plugins.js';
2022-10-17 09:36:35 +02:00
import * as interfaces from './interfaces/index.js';
2019-08-14 14:19:45 +02:00
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';
2019-08-14 14:19:45 +02:00
export class DockerService extends DockerResource {
// STATIC (Internal - prefixed with _ to indicate internal use)
/**
* Internal: Get all services
* Public API: Use dockerHost.listServices() instead
*/
public static async _list(dockerHost: DockerHost) {
2019-08-16 12:48:40 +02:00
const services: DockerService[] = [];
const response = await dockerHost.request('GET', '/services');
for (const serviceObject of response.body) {
2019-09-08 19:22:20 +02:00
const dockerService = new DockerService(dockerHost);
Object.assign(dockerService, serviceObject);
services.push(dockerService);
2019-08-16 12:48:40 +02:00
}
return services;
}
/**
* Internal: Get service by name
* Public API: Use dockerHost.getServiceByName(name) instead
*/
public static async _fromName(
2019-09-12 14:45:36 +02:00
dockerHost: DockerHost,
networkName: string,
2019-09-12 14:45:36 +02:00
): Promise<DockerService> {
const allServices = await DockerService._list(dockerHost);
2020-09-30 16:35:24 +00:00
const wantedService = allServices.find((service) => {
2019-09-08 19:22:20 +02:00
return service.Spec.Name === networkName;
});
return wantedService;
}
2019-08-16 12:48:40 +02:00
/**
* Internal: Create a service
* Public API: Use dockerHost.createService(descriptor) instead
2019-08-16 12:48:40 +02:00
*/
public static async _create(
2019-08-16 12:48:56 +02:00
dockerHost: DockerHost,
serviceCreationDescriptor: interfaces.IServiceCreationDescriptor,
2019-09-08 19:22:20 +02:00
): Promise<DockerService> {
logger.log(
'info',
`now creating service ${serviceCreationDescriptor.name}`,
);
2019-09-13 18:20:12 +02:00
// 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();
2019-09-13 14:40:38 +02:00
const labels: interfaces.TLabels = {
...serviceCreationDescriptor.labels,
2020-09-30 16:35:24 +00:00
version: serviceVersion,
2019-09-13 14:40:38 +02:00
};
2019-09-11 20:25:45 +02:00
2019-09-15 15:08:48 +02:00
const mounts: Array<{
/**
* the target inside the container
*/
Target: string;
/**
* The Source from which to mount the data (Volume or host path)
*/
Source: string;
Type: 'bind' | 'volume' | 'tmpfs' | 'npipe';
ReadOnly: boolean;
Consistency: 'default' | 'consistent' | 'cached' | 'delegated';
}> = [];
if (serviceCreationDescriptor.accessHostDockerSock) {
mounts.push({
Target: '/var/run/docker.sock',
Source: '/var/run/docker.sock',
Consistency: 'default',
ReadOnly: false,
2020-09-30 16:35:24 +00:00
Type: 'bind',
2019-09-15 15:08:48 +02:00
});
}
if (
serviceCreationDescriptor.resources &&
serviceCreationDescriptor.resources.volumeMounts
) {
for (const volumeMount of serviceCreationDescriptor.resources
.volumeMounts) {
2020-03-22 23:53:31 +00:00
mounts.push({
Target: volumeMount.containerFsPath,
Source: volumeMount.hostFsPath,
Consistency: 'default',
ReadOnly: false,
2020-09-30 16:35:24 +00:00
Type: 'bind',
2020-03-22 23:53:31 +00:00
});
}
}
// Resolve networks (support both string[] and DockerNetwork[])
2019-09-15 15:08:48 +02:00
const networkArray: Array<{
Target: string;
Aliases: string[];
}> = [];
2019-08-16 18:21:55 +02:00
for (const network of serviceCreationDescriptor.networks) {
// Skip null networks (can happen if network creation fails)
if (!network) {
logger.log('warn', 'Skipping null network in service creation');
continue;
}
// Resolve network name
const networkName = typeof network === 'string' ? network : network.Name;
2019-08-16 18:21:55 +02:00
networkArray.push({
Target: networkName,
2020-09-30 16:35:24 +00:00
Aliases: [serviceCreationDescriptor.networkAlias],
2019-08-16 18:21:55 +02:00
});
}
2019-09-13 22:37:38 +02:00
const ports = [];
2019-09-13 22:43:29 +02:00
for (const port of serviceCreationDescriptor.ports) {
const portArray = port.split(':');
const hostPort = portArray[0];
const containerPort = portArray[1];
ports.push({
2019-09-15 15:08:48 +02:00
Protocol: 'tcp',
2019-09-20 16:29:43 +02:00
PublishedPort: parseInt(hostPort, 10),
2020-09-30 16:35:24 +00:00
TargetPort: parseInt(containerPort, 10),
2019-09-13 22:43:29 +02:00
});
}
2019-09-13 22:37:38 +02:00
// Resolve secrets (support both string[] and DockerSecret[])
2019-09-12 14:45:36 +02:00
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;
}
2019-09-12 14:45:36 +02:00
secretArray.push({
File: {
2019-09-19 20:05:56 +02:00
Name: 'secret.json', // TODO: make sure that works with multiple secrets
2019-09-12 14:45:36 +02:00
UID: '33',
GID: '33',
2020-09-30 16:35:24 +00:00
Mode: 384,
2019-09-12 14:45:36 +02:00
},
SecretID: secretInstance.ID,
SecretName: secretInstance.Spec.Name,
2019-09-12 14:45:36 +02:00
});
}
2019-09-19 20:05:56 +02:00
// lets configure limits
2019-11-19 18:42:15 +00:00
const memoryLimitMB =
serviceCreationDescriptor.resources &&
serviceCreationDescriptor.resources.memorySizeMB
2019-11-19 18:42:15 +00:00
? serviceCreationDescriptor.resources.memorySizeMB
: 1000;
2019-09-19 20:05:56 +02:00
const limits = {
2020-09-30 16:35:24 +00:00
MemoryBytes: memoryLimitMB * 1000000,
2019-09-19 20:05:56 +02:00
};
if (serviceCreationDescriptor.resources) {
limits.MemoryBytes =
serviceCreationDescriptor.resources.memorySizeMB * 1000000;
2019-09-19 20:05:56 +02:00
}
2019-09-08 19:22:20 +02:00
const response = await dockerHost.request('POST', '/services/create', {
2019-09-13 14:40:38 +02:00
Name: serviceCreationDescriptor.name,
2019-08-16 12:48:40 +02:00
TaskTemplate: {
ContainerSpec: {
Image: imageInstance.RepoTags[0],
2019-09-13 14:40:38 +02:00
Labels: labels,
2019-09-15 15:08:48 +02:00
Secrets: secretArray,
2020-09-30 16:35:24 +00:00
Mounts: mounts,
2019-09-23 13:41:06 +02:00
/* DNSConfig: {
2019-09-22 17:42:28 +02:00
Nameservers: ['1.1.1.1']
2019-09-23 13:41:06 +02:00
} */
2019-09-12 14:45:36 +02:00
},
UpdateConfig: {
Parallelism: 0,
Delay: 0,
FailureAction: 'pause',
Monitor: 15000000000,
2020-09-30 16:35:24 +00:00
MaxFailureRatio: 0.15,
2019-09-11 20:25:45 +02:00
},
2019-09-19 20:05:56 +02:00
ForceUpdate: 1,
Resources: {
2020-09-30 16:35:24 +00:00
Limits: limits,
2019-10-05 15:56:46 +02:00
},
LogDriver: {
Name: 'json-file',
Options: {
'max-file': '3',
2020-09-30 16:35:24 +00:00
'max-size': '10M',
},
},
2019-08-16 12:48:40 +02:00
},
2019-09-13 22:31:03 +02:00
Labels: labels,
2019-09-13 22:37:38 +02:00
Networks: networkArray,
EndpointSpec: {
2020-09-30 16:35:24 +00:00
Ports: ports,
},
2019-08-16 12:48:40 +02:00
});
2019-09-08 19:22:20 +02:00
const createdService = await DockerService._fromName(
2019-09-12 14:45:36 +02:00
dockerHost,
serviceCreationDescriptor.name,
2019-09-12 14:45:36 +02:00
);
2019-09-08 19:22:20 +02:00
return createdService;
2019-08-16 12:48:40 +02:00
}
// INSTANCE PROPERTIES
// Note: dockerHost (not dockerHostRef) for consistency with base class
2019-09-12 14:45:36 +02:00
2019-09-08 19:22:20 +02:00
public ID: string;
public Version: { Index: number };
public CreatedAt: string;
public UpdatedAt: string;
public Spec: {
Name: string;
2019-09-13 13:20:01 +02:00
Labels: interfaces.TLabels;
2019-09-11 20:25:45 +02:00
TaskTemplate: {
ContainerSpec: {
Image: string;
Isolation: string;
2019-09-12 14:45:36 +02:00
Secrets: Array<{
File: {
Name: string;
UID: string;
GID: string;
Mode: number;
};
SecretID: string;
SecretName: string;
}>;
};
2019-09-11 20:25:45 +02:00
ForceUpdate: 0;
2019-09-12 14:45:36 +02:00
};
2019-09-11 20:25:45 +02:00
Mode: {};
2019-09-12 14:45:36 +02:00
Networks: [any[]];
2019-09-08 19:22:20 +02:00
};
2019-09-12 14:45:36 +02:00
public Endpoint: { Spec: {}; VirtualIPs: [any[]] };
2019-09-08 19:22:20 +02:00
constructor(dockerHostArg: DockerHost) {
super(dockerHostArg);
2019-08-16 12:48:40 +02:00
}
2019-08-16 21:07:59 +02:00
// 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
*/
2019-09-08 19:22:20 +02:00
public async remove() {
await this.dockerHost.request('DELETE', `/services/${this.ID}`);
2019-09-08 19:22:20 +02:00
}
2019-09-11 20:25:45 +02:00
/**
* Re-reads service data from Docker engine
* @deprecated Use refresh() instead
*/
2019-09-12 14:45:36 +02:00
public async reReadFromDockerEngine() {
const dockerData = await this.dockerHost.request(
'GET',
`/services/${this.ID}`,
);
2019-09-13 22:31:03 +02:00
// TODO: Better assign: Object.assign(this, dockerData);
2019-09-11 20:25:45 +02:00
}
/**
* Checks if this service needs an update based on image version
*/
2019-09-13 13:20:01 +02:00
public async needsUpdate(): Promise<boolean> {
2019-09-11 20:25:45 +02:00
// TODO: implement digest based update recognition
await this.reReadFromDockerEngine();
const dockerImage = await DockerImage._createFromRegistry(
this.dockerHost,
{
creationObject: {
imageUrl: this.Spec.TaskTemplate.ContainerSpec.Image,
},
},
);
2019-09-11 20:25:45 +02:00
const imageVersion = new plugins.smartversion.SmartVersion(
dockerImage.Labels.version,
);
const serviceVersion = new plugins.smartversion.SmartVersion(
this.Spec.Labels.version,
);
2019-09-11 20:25:45 +02:00
if (imageVersion.greaterThan(serviceVersion)) {
2019-09-13 13:20:01 +02:00
console.log(`service ${this.Spec.Name} needs to be updated`);
return true;
} else {
console.log(`service ${this.Spec.Name} is up to date.`);
}
}
2019-08-16 12:48:40 +02:00
}