UNPKG

@apiclient.xyz/docker

Version:

Provides easy communication with Docker remote API from Node.js, with TypeScript support.

231 lines 17.1 kB
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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