UNPKG

@apiclient.xyz/docker

Version:

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

797 lines 62.4 kB
import * as plugins from './plugins.js'; import * as paths from './paths.js'; import * as interfaces from './interfaces/index.js'; import { DockerContainer } from './classes.container.js'; import { DockerNetwork } from './classes.network.js'; import { DockerService } from './classes.service.js'; import { DockerSecret } from './classes.secret.js'; import { logger } from './logger.js'; import { DockerImageStore } from './classes.imagestore.js'; import { DockerImage } from './classes.image.js'; export class DockerHost { /** * the constructor to instantiate a new docker sock instance * @param pathArg */ constructor(optionsArg) { this.registryToken = ''; this.options = { ...{ imageStoreDir: plugins.path.join(paths.nogitDir, 'temp-docker-image-store'), }, ...optionsArg, }; let pathToUse; if (optionsArg.socketPath) { pathToUse = optionsArg.socketPath; } else if (process.env.DOCKER_HOST) { pathToUse = process.env.DOCKER_HOST; } else if (process.env.CI) { pathToUse = 'http://docker:2375/'; } else { pathToUse = 'http://unix:/var/run/docker.sock:'; } if (pathToUse.startsWith('unix:///')) { pathToUse = pathToUse.replace('unix://', 'http://unix:'); } if (pathToUse.endsWith('.sock')) { pathToUse = pathToUse.replace('.sock', '.sock:'); } console.log(`using docker sock at ${pathToUse}`); this.socketPath = pathToUse; this.imageStore = new DockerImageStore({ bucketDir: null, localDirPath: this.options.imageStoreDir, }); } async start() { await this.imageStore.start(); } async stop() { await this.imageStore.stop(); if (this.smartBucket) { this.smartBucket.storageClient.destroy(); } } /** * Ping the Docker daemon to check if it's running and accessible * @returns Promise that resolves if Docker is available, rejects otherwise * @throws Error if Docker ping fails */ async ping() { const response = await this.request('GET', '/_ping'); if (response.statusCode !== 200) { throw new Error(`Docker ping failed with status ${response.statusCode}`); } } /** * Get Docker daemon version information * @returns Version info including Docker version, API version, OS, architecture, etc. */ async getVersion() { const response = await this.request('GET', '/version'); return response.body; } /** * authenticate against a registry * @param userArg * @param passArg */ async auth(authData) { const response = await this.request('POST', '/auth', authData); if (response.body.Status !== 'Login Succeeded') { console.log(`Login failed with ${response.body.Status}`); throw new Error(response.body.Status); } console.log(response.body.Status); this.registryToken = plugins.smartstring.base64.encode(plugins.smartjson.stringify(authData)); } /** * gets the token from the .docker/config.json file for GitLab registry */ async getAuthTokenFromDockerConfig(registryUrlArg) { const dockerConfigPath = plugins.smartpath.get.home('~/.docker/config.json'); const configObject = JSON.parse(plugins.fs.readFileSync(dockerConfigPath, 'utf8')); const gitlabAuthBase64 = configObject.auths[registryUrlArg].auth; const gitlabAuth = plugins.smartstring.base64.decode(gitlabAuthBase64); const gitlabAuthArray = gitlabAuth.split(':'); await this.auth({ username: gitlabAuthArray[0], password: gitlabAuthArray[1], serveraddress: registryUrlArg, }); } // ============== // NETWORKS - Public Factory API // ============== /** * Lists all networks */ async listNetworks() { return await DockerNetwork._list(this); } /** * Gets a network by name */ async getNetworkByName(networkNameArg) { return await DockerNetwork._fromName(this, networkNameArg); } /** * Creates a network */ async createNetwork(descriptor) { return await DockerNetwork._create(this, descriptor); } // ============== // CONTAINERS - Public Factory API // ============== /** * Lists all containers */ async listContainers() { return await DockerContainer._list(this); } /** * Gets a container by ID * Returns undefined if container does not exist */ async getContainerById(containerId) { return await DockerContainer._fromId(this, containerId); } /** * Creates a container */ async createContainer(descriptor) { return await DockerContainer._create(this, descriptor); } // ============== // SERVICES - Public Factory API // ============== /** * Lists all services */ async listServices() { return await DockerService._list(this); } /** * Gets a service by name */ async getServiceByName(serviceName) { return await DockerService._fromName(this, serviceName); } /** * Creates a service */ async createService(descriptor) { return await DockerService._create(this, descriptor); } // ============== // IMAGES - Public Factory API // ============== /** * Lists all images */ async listImages() { return await DockerImage._list(this); } /** * Gets an image by name */ async getImageByName(imageNameArg) { return await DockerImage._fromName(this, imageNameArg); } /** * Creates an image from a registry */ async createImageFromRegistry(descriptor) { return await DockerImage._createFromRegistry(this, { creationObject: descriptor, }); } /** * Creates an image from a tar stream */ async createImageFromTarStream(tarStream, descriptor) { return await DockerImage._createFromTarStream(this, { creationObject: descriptor, tarStream: tarStream, }); } /** * Prune unused images * @param options Optional filters (dangling, until, label) * @returns Object with deleted images and space reclaimed */ async pruneImages(options) { const filters = options?.filters || {}; // Add dangling filter if specified if (options?.dangling !== undefined) { filters.dangling = [options.dangling.toString()]; } let route = '/images/prune'; if (filters && Object.keys(filters).length > 0) { route += `?filters=${encodeURIComponent(JSON.stringify(filters))}`; } const response = await this.request('POST', route); return response.body; } /** * Builds an image from a Dockerfile */ async buildImage(imageTag) { return await DockerImage._build(this, imageTag); } // ============== // SECRETS - Public Factory API // ============== /** * Lists all secrets */ async listSecrets() { return await DockerSecret._list(this); } /** * Gets a secret by name */ async getSecretByName(secretName) { return await DockerSecret._fromName(this, secretName); } /** * Gets a secret by ID */ async getSecretById(secretId) { return await DockerSecret._fromId(this, secretId); } /** * Creates a secret */ async createSecret(descriptor) { return await DockerSecret._create(this, descriptor); } // ============== // IMAGE STORE - Public API // ============== /** * Stores an image in the local image store */ async storeImage(imageName, tarStream) { return await this.imageStore.storeImage(imageName, tarStream); } /** * Retrieves an image from the local image store */ async retrieveImage(imageName) { return await this.imageStore.getImage(imageName); } /** * */ async getEventObservable() { const response = await this.requestStreaming('GET', '/events'); // requestStreaming now returns Node.js stream, not web stream const nodeStream = response; return plugins.rxjs.Observable.create((observer) => { nodeStream.on('data', (data) => { const eventString = data.toString(); try { const eventObject = JSON.parse(eventString); observer.next(eventObject); } catch (e) { console.log(e); } }); nodeStream.on('error', (err) => { // Connection resets are expected when the stream is destroyed if (err.code !== 'ECONNRESET') { observer.error(err); } }); return () => { nodeStream.destroy(); }; }); } /** * activates docker swarm */ async activateSwarm(addvertisementIpArg) { // determine advertisement address let addvertisementIp = ''; if (addvertisementIpArg) { addvertisementIp = addvertisementIpArg; } else { try { const smartnetworkInstance = new plugins.smartnetwork.SmartNetwork(); const defaultGateway = await smartnetworkInstance.getDefaultGateway(); if (defaultGateway) { addvertisementIp = defaultGateway.ipv4.address; } } catch (err) { // Failed to determine default gateway (e.g. in Deno without --allow-run) // Docker will auto-detect the advertise address } } const response = await this.request('POST', '/swarm/init', { ListenAddr: '0.0.0.0:2377', AdvertiseAddr: addvertisementIp, DataPathPort: 4789, DefaultAddrPool: ['10.10.0.0/8', '20.20.0.0/8'], SubnetSize: 24, ForceNewCluster: false, }); if (response.statusCode === 200) { logger.log('info', 'created Swam succesfully'); } else { logger.log('error', 'could not initiate swarm'); } } /** * fire a request */ async request(methodArg, routeArg, dataArg = {}) { const requestUrl = `${this.socketPath}${routeArg}`; // Build the request using the fluent API const smartRequest = plugins.smartrequest.SmartRequest.create() .url(requestUrl) .header('Content-Type', 'application/json') .header('X-Registry-Auth', this.registryToken) .header('Host', 'docker.sock') .options({ keepAlive: false }); // Add body for methods that support it if (dataArg && Object.keys(dataArg).length > 0) { smartRequest.json(dataArg); } // Execute the request based on method let response; switch (methodArg.toUpperCase()) { case 'GET': response = await smartRequest.get(); break; case 'POST': response = await smartRequest.post(); break; case 'PUT': response = await smartRequest.put(); break; case 'DELETE': response = await smartRequest.delete(); break; default: throw new Error(`Unsupported HTTP method: ${methodArg}`); } // Parse the response body based on content type let body; const contentType = response.headers['content-type'] || ''; // Docker's streaming endpoints (like /images/create) return newline-delimited JSON // which can't be parsed as a single JSON object const isStreamingEndpoint = routeArg.includes('/images/create') || routeArg.includes('/images/load') || routeArg.includes('/build'); if (contentType.includes('application/json') && !isStreamingEndpoint) { body = await response.json(); } else { body = await response.text(); // Try to parse as JSON if it looks like JSON and is not a streaming response if (!isStreamingEndpoint && body && (body.startsWith('{') || body.startsWith('['))) { try { body = JSON.parse(body); } catch { // Keep as text if parsing fails } } } // Create a response object compatible with existing code const legacyResponse = { statusCode: response.status, body: body, headers: response.headers, }; if (response.status !== 200) { console.log(body); } return legacyResponse; } async requestStreaming(methodArg, routeArg, readStream, jsonData) { const requestUrl = `${this.socketPath}${routeArg}`; // Build the request using the fluent API const smartRequest = plugins.smartrequest.SmartRequest.create() .url(requestUrl) .header('Content-Type', 'application/json') .header('X-Registry-Auth', this.registryToken) .header('Host', 'docker.sock') .timeout(30000) .options({ keepAlive: false, autoDrain: true }); // Disable auto-drain for streaming // If we have JSON data, add it to the request if (jsonData && Object.keys(jsonData).length > 0) { smartRequest.json(jsonData); } // If we have a readStream, use the new stream method with logging if (readStream) { let counter = 0; const smartduplex = new plugins.smartstream.SmartDuplex({ writeFunction: async (chunkArg) => { if (counter % 1000 === 0) { console.log(`posting chunk ${counter}`); } counter++; return chunkArg; }, }); // Pipe through the logging duplex stream const loggedStream = readStream.pipe(smartduplex); // Use the new stream method to stream the data smartRequest.stream(loggedStream, 'application/octet-stream'); } // Execute the request based on method let response; switch (methodArg.toUpperCase()) { case 'GET': response = await smartRequest.get(); break; case 'POST': response = await smartRequest.post(); break; case 'PUT': response = await smartRequest.put(); break; case 'DELETE': response = await smartRequest.delete(); break; default: throw new Error(`Unsupported HTTP method: ${methodArg}`); } console.log(response.status); // For streaming responses, get the web stream const webStream = response.stream(); if (!webStream) { // If no stream is available, consume the body as text const body = await response.text(); console.log(body); // Return a compatible response object return { statusCode: response.status, body: body, headers: response.headers, }; } // Convert web ReadableStream to Node.js stream for backward compatibility const nodeStream = plugins.smartstream.nodewebhelpers.convertWebReadableToNodeReadable(webStream); // Add a default error handler to prevent unhandled 'error' events from crashing the process. // Callers that attach their own 'error' listener will still receive the event. nodeStream.on('error', () => { }); // Add properties for compatibility nodeStream.statusCode = response.status; nodeStream.body = ''; // For compatibility return nodeStream; } async requestHijackedStreaming(methodArg, routeArg, jsonData = {}) { const body = JSON.stringify(jsonData); const headers = { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body), 'Connection': 'Upgrade', 'Upgrade': 'tcp', 'X-Registry-Auth': this.registryToken, 'Host': 'docker.sock', }; if (this.socketPath.startsWith('http://unix:')) { return await this.requestHijackedStreamingOverRawSocket(methodArg, routeArg, body, headers); } const { requestModule, options } = this.getNodeRequestOptions(methodArg, routeArg, headers); return await new Promise((resolve, reject) => { const request = requestModule.request(options, (response) => { if ((response.statusCode || 0) >= 400) { this.collectErrorResponse(response).then((bodyText) => { reject(new Error(`Docker hijack request failed with HTTP ${response.statusCode}: ${bodyText}`)); }).catch(reject); return; } if (!response.socket) { reject(new Error('Docker hijack response did not include a socket')); return; } resolve({ stream: this.createDuplexForHijackedResponse(response, response.socket), close: async () => { response.destroy(); response.socket?.destroy(); }, statusCode: response.statusCode || 0, headers: response.headers, }); }); request.on('upgrade', (response, socket, head) => { if (head.length > 0) { socket.unshift(head); } resolve({ stream: socket, close: async () => { socket.destroy(); }, statusCode: response.statusCode || 0, headers: response.headers, }); }); request.on('error', reject); request.end(body); }); } async requestHijackedStreamingOverRawSocket(methodArg, routeArg, bodyArg, headersArg) { const socketPath = this.socketPath.slice('http://unix:'.length, -1); const denoGlobal = globalThis.Deno; if (denoGlobal?.connect) { return await this.requestHijackedStreamingOverDenoUnixSocket(socketPath, methodArg, routeArg, bodyArg, headersArg); } const socket = plugins.net.connect(socketPath); const requestHead = [ `${methodArg.toUpperCase()} ${routeArg} HTTP/1.1`, ...Object.entries(headersArg).map(([key, value]) => `${key}: ${value}`), '', bodyArg, ].join('\r\n'); return await new Promise((resolve, reject) => { let settled = false; let responseBuffer = Buffer.alloc(0); const cleanupBeforeResolve = () => { socket.off('data', handleData); socket.off('error', handleConnectError); }; const handleConnectError = (errorArg) => { if (!settled) { settled = true; reject(errorArg); } }; const rejectWithDockerError = (statusCodeArg, bodyHeadArg) => { const chunks = []; if (bodyHeadArg.length > 0) { chunks.push(bodyHeadArg); } socket.on('data', (chunkArg) => chunks.push(Buffer.isBuffer(chunkArg) ? chunkArg : Buffer.from(chunkArg))); socket.on('end', () => { reject(new Error(`Docker hijack request failed with HTTP ${statusCodeArg}: ${Buffer.concat(chunks).toString('utf8')}`)); }); socket.end(); }; const handleData = (chunkArg) => { responseBuffer = Buffer.concat([responseBuffer, chunkArg]); const headerEndIndex = responseBuffer.indexOf('\r\n\r\n'); if (headerEndIndex === -1) { return; } cleanupBeforeResolve(); const headerBuffer = responseBuffer.subarray(0, headerEndIndex); const bodyHead = responseBuffer.subarray(headerEndIndex + 4); const { statusCode, headers } = this.parseRawHttpResponseHeaders(headerBuffer.toString('utf8')); if (statusCode >= 400) { if (!settled) { settled = true; rejectWithDockerError(statusCode, bodyHead); } return; } if (!settled) { settled = true; resolve({ stream: this.createDuplexForRawSocket(socket, bodyHead), close: async () => { socket.destroy(); }, statusCode, headers, }); } }; socket.once('connect', () => { socket.write(requestHead); }); socket.on('data', handleData); socket.on('error', handleConnectError); }); } async requestHijackedStreamingOverDenoUnixSocket(socketPathArg, methodArg, routeArg, bodyArg, headersArg) { const denoGlobal = globalThis.Deno; const conn = await denoGlobal.connect({ transport: 'unix', path: socketPathArg }); const requestHead = [ `${methodArg.toUpperCase()} ${routeArg} HTTP/1.1`, ...Object.entries(headersArg).map(([key, value]) => `${key}: ${value}`), '', bodyArg, ].join('\r\n'); await conn.write(new TextEncoder().encode(requestHead)); let responseBuffer = Buffer.alloc(0); const readBuffer = new Uint8Array(65536); while (true) { const bytesRead = await conn.read(readBuffer); if (bytesRead === null) { conn.close(); throw new Error('Docker hijack connection closed before response headers were received'); } responseBuffer = Buffer.concat([ responseBuffer, Buffer.from(readBuffer.subarray(0, bytesRead)), ]); const headerEndIndex = responseBuffer.indexOf('\r\n\r\n'); if (headerEndIndex === -1) { continue; } const headerBuffer = responseBuffer.subarray(0, headerEndIndex); const bodyHead = responseBuffer.subarray(headerEndIndex + 4); const { statusCode, headers } = this.parseRawHttpResponseHeaders(headerBuffer.toString('utf8')); if (statusCode >= 400) { conn.close(); throw new Error(`Docker hijack request failed with HTTP ${statusCode}: ${bodyHead.toString('utf8')}`); } const stream = this.createDuplexForDenoConn(conn, bodyHead); return { stream, close: async () => { stream.destroy(); }, statusCode, headers, }; } } getNodeRequestOptions(methodArg, routeArg, headersArg) { const options = { method: methodArg.toUpperCase(), headers: headersArg, }; if (this.socketPath.startsWith('http://unix:')) { options.socketPath = this.socketPath.slice('http://unix:'.length, -1); options.path = routeArg; return { requestModule: plugins.http, options }; } const requestUrl = new URL(routeArg, this.socketPath); options.protocol = requestUrl.protocol; options.hostname = requestUrl.hostname; options.port = requestUrl.port; options.path = `${requestUrl.pathname}${requestUrl.search}`; return { requestModule: requestUrl.protocol === 'https:' ? plugins.https : plugins.http, options, }; } createDuplexForHijackedResponse(responseArg, writableArg) { const duplex = new plugins.stream.Duplex({ write(chunkArg, encodingArg, callbackArg) { writableArg.write(chunkArg, encodingArg, callbackArg); }, read() { }, }); responseArg.on('data', (chunkArg) => duplex.push(chunkArg)); responseArg.on('end', () => duplex.push(null)); responseArg.on('error', (errorArg) => duplex.destroy(errorArg)); writableArg.on('error', (errorArg) => duplex.destroy(errorArg)); duplex.on('finish', () => writableArg.end()); return duplex; } createDuplexForRawSocket(socketArg, bodyHeadArg) { const duplex = new plugins.stream.Duplex({ write(chunkArg, encodingArg, callbackArg) { socketArg.write(chunkArg, encodingArg, callbackArg); }, read() { }, }); if (bodyHeadArg.length > 0) { duplex.push(bodyHeadArg); } socketArg.on('data', (chunkArg) => duplex.push(chunkArg)); socketArg.on('end', () => duplex.push(null)); socketArg.on('error', (errorArg) => duplex.destroy(errorArg)); duplex.on('finish', () => socketArg.end()); return duplex; } createDuplexForDenoConn(connArg, bodyHeadArg) { let closed = false; const closeConn = () => { if (closed) { return; } closed = true; try { connArg.close(); } catch { } }; const duplex = new plugins.stream.Duplex({ async write(chunkArg, encodingArg, callbackArg) { try { const chunkBuffer = Buffer.isBuffer(chunkArg) ? chunkArg : Buffer.from(chunkArg, encodingArg); await connArg.write(new Uint8Array(chunkBuffer)); callbackArg(); } catch (error) { callbackArg(error); } }, read() { }, destroy(errorArg, callbackArg) { closeConn(); callbackArg(errorArg || null); }, }); if (bodyHeadArg.length > 0) { duplex.push(bodyHeadArg); } const readLoop = async () => { const readBuffer = new Uint8Array(65536); try { while (!duplex.destroyed) { const bytesRead = await connArg.read(readBuffer); if (bytesRead === null) { break; } if (bytesRead > 0) { duplex.push(Buffer.from(readBuffer.subarray(0, bytesRead))); } } duplex.push(null); } catch (error) { if (!closed && !duplex.destroyed) { duplex.destroy(error); } } }; void readLoop(); duplex.on('finish', () => { closeConn(); }); return duplex; } parseRawHttpResponseHeaders(headerTextArg) { const [statusLine, ...headerLines] = headerTextArg.split('\r\n'); const statusCode = Number(statusLine.split(' ')[1]); const headers = {}; for (const headerLine of headerLines) { const separatorIndex = headerLine.indexOf(':'); if (separatorIndex === -1) { continue; } const key = headerLine.slice(0, separatorIndex).trim().toLowerCase(); const value = headerLine.slice(separatorIndex + 1).trim(); headers[key] = value; } return { statusCode, headers }; } async collectErrorResponse(responseArg) { const chunks = []; for await (const chunk of responseArg) { chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); } return Buffer.concat(chunks).toString('utf8'); } /** * add s3 storage * @param optionsArg */ async addS3Storage(optionsArg) { this.smartBucket = new plugins.smartbucket.SmartBucket(optionsArg); if (!optionsArg.bucketName) { throw new Error('bucketName is required'); } const bucket = await this.smartBucket.getBucketByName(optionsArg.bucketName); let wantedDirectory = await bucket.getBaseDirectory(); if (optionsArg.directoryPath) { wantedDirectory = await wantedDirectory.getSubDirectoryByName(optionsArg.directoryPath); } this.imageStore.options.bucketDir = wantedDirectory; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ob3N0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5ob3N0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sS0FBSyxLQUFLLE1BQU0sWUFBWSxDQUFDO0FBQ3BDLE9BQU8sS0FBSyxVQUFVLE1BQU0sdUJBQXVCLENBQUM7QUFDcEQsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ3pELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNyRCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDckQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ25ELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDckMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDM0QsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBb0JqRCxNQUFNLE9BQU8sVUFBVTtJQVdyQjs7O09BR0c7SUFDSCxZQUFZLFVBQXlDO1FBUjdDLGtCQUFhLEdBQVcsRUFBRSxDQUFDO1FBU2pDLElBQUksQ0FBQyxPQUFPLEdBQUc7WUFDYixHQUFHO2dCQUNELGFBQWEsRUFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FDOUIsS0FBSyxDQUFDLFFBQVEsRUFDZCx5QkFBeUIsQ0FDMUI7YUFDRjtZQUNELEdBQUcsVUFBVTtTQUNkLENBQUM7UUFDRixJQUFJLFNBQWlCLENBQUM7UUFDdEIsSUFBSSxVQUFVLENBQUMsVUFBVSxFQUFFLENBQUM7WUFDMUIsU0FBUyxHQUFHLFVBQVUsQ0FBQyxVQUFVLENBQUM7UUFDcEMsQ0FBQzthQUFNLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUNu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