@apiclient.xyz/docker
Version:
Provides easy communication with Docker remote API from Node.js, with TypeScript support.
231 lines • 17.1 kB
JavaScript
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 extends DockerResource {
// STATIC (Internal - prefixed with _ to indicate internal use)
/**
* Internal: Get all services
* Public API: Use dockerHost.listServices() instead
*/
static async _list(dockerHost) {
const services = [];
const response = await dockerHost.request('GET', '/services');
for (const serviceObject of response.body) {
const dockerService = new DockerService(dockerHost);
Object.assign(dockerService, serviceObject);
services.push(dockerService);
}
return services;
}
/**
* Internal: Get service by name
* Public API: Use dockerHost.getServiceByName(name) instead
*/
static async _fromName(dockerHost, networkName) {
const allServices = await DockerService._list(dockerHost);
const wantedService = allServices.find((service) => {
return service.Spec.Name === networkName;
});
if (!wantedService) {
throw new Error(`Service not found: ${networkName}`);
}
return wantedService;
}
/**
* Internal: Create a service
* Public API: Use dockerHost.createService(descriptor) instead
*/
static async _create(dockerHost, serviceCreationDescriptor) {
logger.log('info', `now creating service ${serviceCreationDescriptor.name}`);
// Resolve image (support both string and DockerImage instance)
let imageInstance;
if (typeof serviceCreationDescriptor.image === 'string') {
const foundImage = await DockerImage._fromName(dockerHost, serviceCreationDescriptor.image);
if (!foundImage) {
throw new Error(`Image not found: ${serviceCreationDescriptor.image}`);
}
imageInstance = foundImage;
}
else {
imageInstance = serviceCreationDescriptor.image;
}
const serviceVersion = await imageInstance.getVersion();
const labels = {
...serviceCreationDescriptor.labels,
version: serviceVersion,
};
const mounts = [];
if (serviceCreationDescriptor.accessHostDockerSock) {
mounts.push({
Target: '/var/run/docker.sock',
Source: '/var/run/docker.sock',
Consistency: 'default',
ReadOnly: false,
Type: 'bind',
});
}
if (serviceCreationDescriptor.resources &&
serviceCreationDescriptor.resources.volumeMounts) {
for (const volumeMount of serviceCreationDescriptor.resources
.volumeMounts) {
mounts.push({
Target: volumeMount.containerFsPath,
Source: volumeMount.hostFsPath,
Consistency: 'default',
ReadOnly: false,
Type: 'bind',
});
}
}
// Resolve networks (support both string[] and DockerNetwork[])
const networkArray = [];
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;
networkArray.push({
Target: networkName,
Aliases: [serviceCreationDescriptor.networkAlias],
});
}
const ports = [];
for (const port of serviceCreationDescriptor.ports) {
const portArray = port.split(':');
const hostPort = portArray[0];
const containerPort = portArray[1];
ports.push({
Protocol: 'tcp',
PublishedPort: parseInt(hostPort, 10),
TargetPort: parseInt(containerPort, 10),
});
}
// Resolve secrets (support both string[] and DockerSecret[])
const secretArray = [];
for (const secret of serviceCreationDescriptor.secrets) {
// Resolve secret instance
let secretInstance;
if (typeof secret === 'string') {
const foundSecret = await DockerSecret._fromName(dockerHost, secret);
if (!foundSecret) {
throw new Error(`Secret not found: ${secret}`);
}
secretInstance = foundSecret;
}
else {
secretInstance = secret;
}
secretArray.push({
File: {
Name: 'secret.json', // TODO: make sure that works with multiple secrets
UID: '33',
GID: '33',
Mode: 384,
},
SecretID: secretInstance.ID,
SecretName: secretInstance.Spec.Name,
});
}
// lets configure limits
const memoryLimitMB = serviceCreationDescriptor.resources?.memorySizeMB ?? 1000;
const limits = {
MemoryBytes: memoryLimitMB * 1000000,
};
const response = await dockerHost.request('POST', '/services/create', {
Name: serviceCreationDescriptor.name,
TaskTemplate: {
ContainerSpec: {
Image: imageInstance.RepoTags[0],
Labels: labels,
Secrets: secretArray,
Mounts: mounts,
/* DNSConfig: {
Nameservers: ['1.1.1.1']
} */
},
UpdateConfig: {
Parallelism: 0,
Delay: 0,
FailureAction: 'pause',
Monitor: 15000000000,
MaxFailureRatio: 0.15,
},
ForceUpdate: 1,
Resources: {
Limits: limits,
},
Networks: networkArray,
LogDriver: {
Name: 'json-file',
Options: {
'max-file': '3',
'max-size': '10M',
},
},
},
Labels: labels,
EndpointSpec: {
Ports: ports,
},
});
const createdService = await DockerService._fromName(dockerHost, serviceCreationDescriptor.name);
return createdService;
}
constructor(dockerHostArg) {
super(dockerHostArg);
}
// INSTANCE METHODS
/**
* Refreshes this service's state from the Docker daemon
*/
async refresh() {
const updated = await DockerService._fromName(this.dockerHost, this.Spec.Name);
if (updated) {
Object.assign(this, updated);
}
}
/**
* Removes this service from the Docker daemon
*/
async remove() {
await this.dockerHost.request('DELETE', `/services/${this.ID}`);
}
/**
* Re-reads service data from Docker engine
* @deprecated Use refresh() instead
*/
async reReadFromDockerEngine() {
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
*/
async needsUpdate() {
// TODO: implement digest based update recognition
await this.reReadFromDockerEngine();
const dockerImage = await DockerImage._createFromRegistry(this.dockerHost, {
creationObject: {
imageUrl: this.Spec.TaskTemplate.ContainerSpec.Image,
},
});
const imageVersion = new plugins.smartversion.SmartVersion(dockerImage.Labels.version);
const serviceVersion = new plugins.smartversion.SmartVersion(this.Spec.Labels.version);
if (imageVersion.greaterThan(serviceVersion)) {
console.log(`service ${this.Spec.Name} needs to be updated`);
return true;
}
else {
console.log(`service ${this.Spec.Name} is up to date.`);
return false;
}
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sS0FBSyxVQUFVLE1BQU0sdUJBQXVCLENBQUM7QUFFcEQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQy9DLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUNuRCxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDakQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ25ELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFFckMsTUFBTSxPQUFPLGFBQWMsU0FBUSxjQUFjO0lBQy9DLCtEQUErRDtJQUUvRDs7O09BR0c7SUFDSSxNQUFNLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxVQUFzQjtRQUM5QyxNQUFNLFFBQVEsR0FBb0IsRUFBRSxDQUFDO1FBQ3JDLE1BQU0sUUFBUSxHQUFHLE1BQU0sVUFBVSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsV0FBVyxDQUFDLENBQUM7UUFDOUQsS0FBSyxNQUFNLGFBQWEsSUFBSSxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDMUMsTUFBTSxhQUFhLEdBQUcsSUFBSSxhQUFhLENBQUMsVUFBVSxDQUFDLENBQUM7WUFDcEQsTUFBTSxDQUFDLE1BQU0sQ0FBQyxhQUFhLEVBQUUsYUFBYSxDQUFDLENBQUM7WUFDNUMsUUFBUSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUMvQixDQUFDO1FBQ0QsT0FBTyxRQUFRLENBQUM7SUFDbEIsQ0FBQztJQUVEOzs7T0FHRztJQUNJLE1BQU0sQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUMzQixVQUFzQixFQUN0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