UNPKG

@nasriya/orchestriq

Version:

A package to generate Docker files

947 lines (946 loc) 88.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const ContainerTemplate_1 = __importDefault(require("./templates/ContainerTemplate")); const DockerContainer_1 = __importDefault(require("./DockerContainer")); const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const helpers_1 = __importDefault(require("../../utils/helpers")); const stream_1 = require("stream"); const Tarball_1 = __importDefault(require("../../utils/Tarball")); class ContainersManager { #_containers = []; #_socket; constructor(socket) { this.#_socket = socket; } /** * Creates a new container template and adds it to the list of containers. * @returns {ContainerTemplate} The newly created container template. */ newTemplate() { const container = new ContainerTemplate_1.default(this.#_socket); this.#_containers.push(container); return container; } /** * Lists all containers managed by this instance. * * @returns {Promise<DockerContainer[]>} A promise that resolves to an array of DockerContainer objects, representing all containers managed by this instance. * @throws {Error} If the request fails to list containers. */ async list() { try { const containers = await this.#_socket.fetch('containers/json?all=true'); return containers.map(container => new DockerContainer_1.default(container, this)); } catch (error) { if (error instanceof Error) { error.message = `Unable to list containers: ${error.message}`; } throw error; } } /** * Creates a new container and returns its ID. * @param container The container to create. This can be either a `ContainerTemplate` instance or a `CreateContainerOptions` object with the following properties: * @param verbose Whether to log the creation of the container. * @returns A promise that resolves to an object with two properties: id and name. The id is the ID of the created container, and the name is the name of the container. * @throws {Error} If the container could not be created. */ async create(container, verbose = false) { const params = new URLSearchParams(); const request = { method: 'POST', headers: { 'Content-Type': 'application/json' }, returnJSON: false, }; try { if (typeof verbose !== 'boolean') { throw new Error('The "verbose" argument (when provided) must be a boolean.'); } if (container instanceof ContainerTemplate_1.default) { const services = Object.values(container.services.list); if (services.length === 0) { throw new Error('The container must have at least one service.'); } const createdContainers = []; for (const service of services) { if (service.image === undefined) { throw new Error(`The service ${service.name} must have an image.`); } if (verbose) { console.log(`Creating the ${service.name} service${service.container_name ? ` (${service.container_name})` : ''}...`); } const requestBody = { Image: service.image, }; if (service.userns_mode && service.userns_mode !== 'default') { requestBody.HostConfig = requestBody.HostConfig || {}; requestBody.HostConfig.UsernsMode = service.userns_mode; } if (service.user) { requestBody.User = service.user; } if (service.command) { requestBody.Cmd = service.command; } if (service.entrypoint) { requestBody.Entrypoint = service.entrypoint; } // Preparing the environment variables { const env = new Map(); // #1: Check the container's environment files for (const envFilePath of container.env_files) { if (path_1.default.isAbsolute(envFilePath)) { const contentStr = await fs_1.default.promises.readFile(envFilePath, 'utf8'); const pairs = contentStr.split('\n').map(line => line.trim()).filter(line => line.length > 0); for (const pair of pairs) { const [key, value] = pair.split('='); env.set(key, value); } } else { if (verbose) { console.warn(`Skipping the container's environment file path "${envFilePath}" since it is not an absolute path.`); } } } // #2: Check the container's environment variables for (const [key, value] of Object.entries(container.environment.list)) { env.set(key, value); } // #3: Check the service's environment files for (const envFilePath of service.env_files) { if (path_1.default.isAbsolute(envFilePath)) { const contentStr = await fs_1.default.promises.readFile(envFilePath, 'utf8'); const pairs = contentStr.split('\n').map(line => line.trim()).filter(line => line.length > 0); for (const pair of pairs) { const [key, value] = pair.split('='); env.set(key, value); } } else { if (verbose) { console.warn(`Skipping the service's environment file path "${envFilePath}" since it is not an absolute path.`); } } } // #4: Check the service's environment variables for (const [key, value] of Object.entries(service.environment.list)) { env.set(key, value); } // Add the environment variables to the request body if (env.size > 0) { requestBody.Env = Array.from(env).map(([key, value]) => `${key}=${value}`); } } // Preparing the healthcheck { const healthcheck = service.healthcheck.toJSON(); if (healthcheck.test.length > 0) { const obj = { test: healthcheck.test, interval: healthcheck.interval, timeout: healthcheck.timeout, retries: healthcheck.retries, start_period: healthcheck.start_period, disable: healthcheck.disable, }; if (Object.values(obj).some(value => value !== undefined)) { requestBody.Healthcheck = obj; } } } // Preparing the volumes { const anonymousVolumes = service.volumes.filter(volume => volume.type === 'anonymous'); const namedVolumes = service.volumes.filter(volume => volume.type === 'named'); const bindMounts = service.volumes.filter(volume => volume.type === 'bind'); if (anonymousVolumes.length > 0) { requestBody.Volumes = {}; for (const volume of anonymousVolumes) { requestBody.Volumes[volume.containerPath] = {}; } } if (namedVolumes.length > 0 || bindMounts.length > 0) { if (!helpers_1.default.hasOwnProperty(requestBody, 'HostConfig')) { requestBody.HostConfig = {}; } if (!helpers_1.default.hasOwnProperty(requestBody.HostConfig, 'Binds')) { requestBody.HostConfig.Binds = []; } for (const volume of namedVolumes) { requestBody.HostConfig.Binds.push(`${volume.name}:${volume.containerPath}`); } for (const volume of bindMounts) { requestBody.HostConfig.Binds.push(`${volume.hostPath}:${volume.containerPath}`); } } } // Prepare the service ports { if (service.ports.length > 0) { requestBody.ExposedPorts = {}; requestBody.HostConfig = requestBody.HostConfig || {}; requestBody.HostConfig.PortBindings = {}; for (const port of service.ports) { const containerPort = `${port.internal}/tcp`; requestBody.ExposedPorts[containerPort] = {}; requestBody.HostConfig.PortBindings[containerPort] = [{ HostPort: `${port.external || port.internal}` }]; } } } // Prepare the network { if (service.networks && service.networks.length > 0) { // Use the first network as the primary one requestBody.HostConfig = requestBody.HostConfig || {}; requestBody.HostConfig.NetworkMode = service.networks[0]; requestBody.NetworkingConfig = { EndpointsConfig: {} }; for (const network of service.networks) { requestBody.NetworkingConfig.EndpointsConfig[network] = { Aliases: [service.name] // or other custom values }; } } else { requestBody.HostConfig = requestBody.HostConfig || {}; requestBody.HostConfig.NetworkMode = service.network_mode; } } // Prepare the restart policy { requestBody.HostConfig = requestBody.HostConfig || {}; const restart = service.restart; const restartPolicy = { Name: 'unless-stopped' // default }; if (typeof restart === 'string') { restartPolicy.Name = restart; } else if (typeof restart === 'object' && restart !== null) { restartPolicy.Name = restart.policy; if (restart.policy === 'on-failure') { restartPolicy.MaximumRetryCount = restart.times ?? 5; } } requestBody.HostConfig.RestartPolicy = restartPolicy; } // Preparing the request const containerName = service.container_name || service.name; params.set('name', containerName); request.body = JSON.stringify(requestBody); // The request to create the container const response = await this.#_socket.fetch(`containers/create${params.size > 0 ? `?${params.toString()}` : ''}`, request); const data = await response.json(); if (response.ok) { createdContainers.push({ id: data.Id, name: containerName }); } else { throw new Error(data.message); } } if (createdContainers.length === 0) { throw new Error('No containers were created.'); } if (createdContainers.length === 1) { return createdContainers[0]; } else { return createdContainers; } } else { // Check if the container is a an object and validate it if (!helpers_1.default.isObject(container)) { throw new Error('The container must be an object.'); } if (helpers_1.default.hasOwnProperty(container, 'name')) { if (typeof container.name !== 'string') { throw new Error('The container name must be a string.'); } if (container.name.length === 0) { throw new Error('The container name must be defined.'); } params.set('name', container.name); } const requestBody = { ...container }; delete requestBody.name; request.body = JSON.stringify(requestBody); const response = await this.#_socket.fetch(`containers/create${params.size > 0 ? `?${params.toString()}` : ''}`, request); const data = await response.json(); if (response.ok) { return { id: data.Id, name: container.name || '' }; } else { throw new Error(data.message); } } } catch (error) { if (error instanceof Error) { error.message = `Unable to create the ${container.name ? `(${container.name})` : ''} container: ${error.message}`; } throw error; } } /** * Retrieves the details of a Docker container from the Docker daemon. * * @param {string} id - The ID of the container to inspect. * @returns {Promise<DockerContainer | undefined>} A promise that resolves to a DockerContainer object containing the details of the container, or undefined if the container does not exist. * @throws {Error} Throws an error if the request fails or the response is not OK. */ async get(id) { try { if (typeof id !== 'string') { throw new Error('The container ID must be a string.'); } if (id.length === 0) { throw new Error('The container ID cannot be empty.'); } const response = await this.#_socket.fetch(`containers/${id}/json`, { method: 'GET', returnJSON: false }); const data = await response.json(); if (response.ok) { return new DockerContainer_1.default(data, this); } if (response.status === 404) { return; } if (response.status === 500) { throw new Error(data.message); } } catch (error) { if (error instanceof Error) { error.message = `Unable to get the details of the container${typeof id === 'string' && id.length < 0 ? ` with ID "${id}"` : ''}: ${error.message}`; } throw error; } } /** * Retrieves the top processes running in a Docker container from the Docker daemon. * * @param {string} id - The ID of the container to inspect. * @returns {Promise<ContainerTopResponse | undefined>} A promise that resolves to a ContainerTopResponse object containing the top processes of the container, or undefined if the container does not exist. * @throws {Error} Throws an error if the request fails or the response is not OK. */ async top(id) { if (typeof id !== 'string') { throw new Error('The container ID must be a string.'); } if (id.length === 0) { throw new Error('The container ID cannot be empty.'); } try { const response = await this.#_socket.fetch(`containers/${id}/top`, { method: 'GET', returnJSON: false }); const data = await response.json(); if (response.ok) { return data; } if (response.status === 404) { return; } if (response.status === 500) { throw new Error(data.message); } } catch (error) { if (error instanceof Error) { error.message = `Unable to get the top processes of the container with ID "${id}": ${error.message}`; } throw error; } } /** * Retrieves the logs of a Docker container. * * @param {string} id - The ID of the container whose logs are to be retrieved. * @param {ContainerLogsOptions} options - The options for retrieving the logs. * @param {boolean} [options.stdout] - If true, includes standard output logs. * @param {boolean} [options.stderr] - If true, includes standard error logs. * @param {Date} [options.since] - Filters logs to entries since this date. * @param {Date} [options.until] - Filters logs to entries until this date. * @param {boolean} [options.follow] - If true, continuously streams new logs. * @param {boolean} [options.timestamps] - If true, includes timestamps in logs. * @param {number | string} [options.tail] - Limits the number of log entries returned. * @returns {Promise<string | undefined>} A promise that resolves to the container logs as a string, or undefined if the container does not exist. * @throws {Error} Throws an error if the request fails or the response is not OK. */ async logs(id, options) { if (typeof id !== 'string') { throw new Error('The container ID must be a string.'); } if (id.length === 0) { throw new Error('The container ID cannot be empty.'); } try { if (!helpers_1.default.isObject(options)) { throw new Error('The options argument must be an object.'); } if (!('stdout' in options || 'stderr' in options)) { throw new Error('At least one of the "stdout" or "stderr" options must be provided.'); } const params = new URLSearchParams(); if (helpers_1.default.hasOwnProperty(options, 'stdout')) { if (typeof options.stdout !== 'boolean') { throw new Error('The "stdout" option must be a boolean.'); } if (options.stdout) { params.set('stdout', 'true'); } } if (helpers_1.default.hasOwnProperty(options, 'stderr')) { if (typeof options.stderr !== 'boolean') { throw new Error('The "stderr" option must be a boolean.'); } if (options.stderr) { params.set('stderr', 'true'); } } if (helpers_1.default.hasOwnProperty(options, 'since')) { if (!(options.since instanceof Date)) { throw new Error('The "since" option must be a Date object.'); } params.set('since', (Math.floor(options.since.getTime() / 1000)).toString()); } if (helpers_1.default.hasOwnProperty(options, 'until')) { if (!(options.until instanceof Date)) { throw new Error('The "until" option must be a Date object.'); } params.set('until', (Math.floor(options.until.getTime() / 1000)).toString()); } if (helpers_1.default.hasOwnProperty(options, 'follow')) { if (typeof options.follow !== 'boolean') { throw new Error('The "follow" option must be a boolean.'); } if (options.follow) { params.set('follow', 'true'); } } if (helpers_1.default.hasOwnProperty(options, 'timestamps')) { if (typeof options.timestamps !== 'boolean') { throw new Error('The "timestamps" option must be a boolean.'); } if (options.timestamps) { params.set('timestamps', 'true'); } } if (helpers_1.default.hasOwnProperty(options, 'tail')) { if (!['number', 'string'].includes(typeof options.tail)) { throw new Error('The "tail" option must be a number or a string.'); } if (typeof options.tail === 'number') { options.tail = String(options.tail); } params.set('tail', options.tail); } const response = await this.#_socket.fetch(`/containers/${id}/logs${params.size > 0 ? `?${params.toString()}` : ''}`, { method: 'GET', returnJSON: false }); // console.log(response) const data = await response.text(); if (response.ok) { return data || ''; } if (response.status === 404) { return undefined; } throw new Error(`${data.message || 'Empty response'} - Status: ${response.status}`); } catch (error) { if (error instanceof Error) { error.message = `Unable to get the logs of the container with ID "${id}": ${error.message}`; } throw error; } } /** * Retrieves the changes of a Docker container from the Docker daemon. * * @param {string} id - The ID of the container whose changes are to be retrieved. * @returns {Promise<ContainerChangesResponse[] | undefined>} A promise that resolves to an array of ContainerChangesResponse objects containing the changes of the container, or undefined if the container does not exist. * @throws {Error} Throws an error if the request fails or the response is not OK. */ async changes(id) { if (typeof id !== 'string') { throw new Error('The container ID must be a string.'); } if (id.length === 0) { throw new Error('The container ID cannot be empty.'); } try { const response = await this.#_socket.fetch(`containers/${id}/changes`, { method: 'GET', returnJSON: false }); const data = await response.json(); if (response.ok) { return data; } if (response.status === 404) { return undefined; } throw new Error(`${data.message || 'Empty response'} - Status: ${response.status}`); } catch (error) { if (error instanceof Error) { error.message = `Unable to get the changes of the container with ID "${id}": ${error.message}`; } throw error; } } /** * Exports the filesystem of a Docker container as a tar archive. * * @param {string} id - The ID of the container to export. * @param {string} dest - The destination path where the tar archive will be saved. Must be an absolute path ending with a `.tar` extension. * @returns {Promise<string | undefined>} A promise that resolves to the path where the tar archive is saved, or undefined if the container does not exist. * @throws {Error} Throws an error if the container ID or destination path is invalid, or if the request fails. */ async export(id, dest) { if (typeof id !== 'string') { throw new Error('The container ID must be a string.'); } if (id.length === 0) { throw new Error('The container ID cannot be empty.'); } try { if (typeof dest !== 'string') { throw new Error('The destination path must be a string.'); } if (dest.length === 0) { throw new Error('The destination path cannot be empty.'); } if (!path_1.default.isAbsolute(dest)) { dest = path_1.default.resolve(dest); } const folder = path_1.default.dirname(dest); const basename = path_1.default.basename(dest); if (!basename.toLowerCase().endsWith('.tar')) { throw new Error('The destination file must have a `.tar` extension.'); } if (!fs_1.default.existsSync(folder)) { await fs_1.default.promises.mkdir(folder, { recursive: true }); } const outputPath = basename ? dest : path_1.default.join(dest, `${id}.tar`); const response = await this.#_socket.fetch(`containers/${id}/export`, { method: 'GET', returnJSON: false }); if (response.ok) { const stream = fs_1.default.createWriteStream(outputPath); for await (const chunk of response.body) { stream.write(chunk); } stream.end(); return outputPath; } const data = await response.json(); if (response.status === 404) { return undefined; } throw new Error(`${data.message || 'Empty response'} - Status: ${response.status}`); } catch (error) { if (error instanceof Error) { error.message = `Unable to export the container with ID "${id}": ${error.message}`; } throw error; } } /** * Retrieves the stats of a Docker container from the Docker daemon. * * @param {string} id - The ID of the container whose stats are to be retrieved. * @param {ContainerStatsOptions} [options] - The options for retrieving the stats. * @param {boolean} [options.stream] - If true, returns a ReadableStream. If false, returns the latest stats snapshot as an object. * @param {boolean} [options.oneShot] - If true, retrieves only one stats entry (no continuous streaming). * @returns {Promise<ReadableStream | object | undefined>} A promise resolving to a ReadableStream (if streaming) or an object (if not). * @throws {Error} Throws an error if the request fails or the response is not OK. */ async stats(id, options = { stream: true, oneShot: false }) { if (typeof id !== 'string') { throw new Error('The container ID must be a string.'); } if (id.length === 0) { throw new Error('The container ID cannot be empty.'); } try { const params = new URLSearchParams(); if (helpers_1.default.hasOwnProperty(options, 'stream')) { if (typeof options.stream !== 'boolean') { throw new Error('The "stream" option must be a boolean.'); } params.set('stream', options.stream.toString()); } if (helpers_1.default.hasOwnProperty(options, 'oneShot')) { if (typeof options.oneShot !== 'boolean') { throw new Error('The "oneShot" option must be a boolean.'); } params.set('one-shot', options.oneShot.toString()); } const response = await this.#_socket.fetch(`containers/${id}/stats${params.size > 0 ? `?${params.toString()}` : ''}`, { method: 'GET', returnJSON: false }); if (response.ok) { return options.stream ? response.body : await response.json(); } const error = await response.json(); if (response.status === 404) { return undefined; } throw new Error(`${error.message || 'Empty response'} - Status: ${response.status}`); } catch (error) { if (error instanceof Error) { error.message = `Unable to get the stats of the container with ID "${id}": ${error.message}`; } throw error; } } /** * Starts a stopped or paused container. * * @param {string} id - The ID of the container to start. * @param {number} [timeout] - The time to wait for the container to start before returning an error. Defaults to no timeout. * @returns {Promise<void>} A promise that resolves when the container is started successfully. * @throws {Error} Throws an error if the request fails or the response is not OK. */ async start(id, timeout) { if (typeof id !== 'string') { throw new Error('The container ID must be a string.'); } if (id.length === 0) { throw new Error('The container ID cannot be empty.'); } try { if (timeout !== undefined && typeof timeout !== 'number') { throw new Error('The timeout must be a number.'); } const params = new URLSearchParams(); if (typeof timeout === 'number') { params.set('timeout', timeout.toString()); } const response = await this.#_socket.fetch(`containers/${id}/start${params.size > 0 ? `?${params.toString()}` : ''}`, { method: 'POST', returnJSON: false }); if (response.status === 204 || response.status === 304) { return; } const error = await response.json(); throw new Error(`${error.message || 'Empty response'} - Status: ${response.status}`); } catch (error) { if (error instanceof Error) { error.message = `Unable to start the container with ID "${id}": ${error.message}`; } throw error; } } /** * Stops a running container. * * @param {string} id - The ID of the container to stop. * @param {number} [timeout] - The time to wait for the container to stop before returning an error. Defaults to no timeout. * @returns {Promise<void>} A promise that resolves when the container is stopped successfully. * @throws {Error} Throws an error if the request fails or the response is not OK. */ async stop(id, timeout) { if (typeof id !== 'string') { throw new Error('The container ID must be a string.'); } if (id.length === 0) { throw new Error('The container ID cannot be empty.'); } try { if (timeout !== undefined && typeof timeout !== 'number') { throw new Error('The timeout must be a number.'); } const params = new URLSearchParams(); if (typeof timeout === 'number') { params.set('timeout', timeout.toString()); } const response = await this.#_socket.fetch(`containers/${id}/stop${params.size > 0 ? `?${params.toString()}` : ''}`, { method: 'POST', returnJSON: false }); if (response.status === 204 || response.status === 304) { return; } const error = await response.json(); throw new Error(`${error.message || 'Empty response'} - Status: ${response.status}`); } catch (error) { if (error instanceof Error) { error.message = `Unable to stop the container with ID "${id}": ${error.message}`; } throw error; } } /** * Restarts a running container. * * @param {string} id - The ID of the container to restart. * @param {string} [signal] - The signal to send to the container to restart it. Defaults to SIGTERM. * @returns {Promise<void>} A promise that resolves when the container is restarted successfully. * @throws {Error} Throws an error if the request fails or the response is not OK. */ async restart(id, signal) { if (typeof id !== 'string') { throw new Error('The container ID must be a string.'); } if (id.length === 0) { throw new Error('The container ID cannot be empty.'); } try { if (signal !== undefined && typeof signal !== 'string') { throw new Error('The signal must be a string.'); } const params = new URLSearchParams(); if (typeof signal === 'string') { params.set('signal', signal); } const response = await this.#_socket.fetch(`containers/${id}/restart${params.size > 0 ? `?${params.toString()}` : ''}`, { method: 'POST', returnJSON: false }); if (response.ok) { return; } const error = await response.json(); throw new Error(`${error.message || 'Empty response'} - Status: ${response.status}`); } catch (error) { if (error instanceof Error) { error.message = `Unable to restart the container with ID "${id}": ${error.message}`; } throw error; } } /** * Sends a signal to a container, killing it. Note that when using a signal other than the default (SIGKILL), the container is not removed from the list of running containers until it exits. This is a limitation of the Docker API. * * @param {string} id - The ID of the container to kill. * @param {string} [signal] - The signal to send to the container. Defaults to SIGKILL. * @returns {Promise<void>} A promise that resolves when the container is killed successfully. * @throws {Error} Throws an error if the request fails or the response is not OK. */ async kill(id, signal) { if (typeof id !== 'string') { throw new Error('The container ID must be a string.'); } if (id.length === 0) { throw new Error('The container ID cannot be empty.'); } try { if (signal !== undefined && typeof signal !== 'string') { throw new Error('The signal must be a string.'); } const params = new URLSearchParams(); if (typeof signal === 'string') { params.set('signal', signal); } const response = await this.#_socket.fetch(`containers/${id}/kill${params.size > 0 ? `?${params.toString()}` : ''}`, { method: 'POST', returnJSON: false }); if (response.ok) { return; } const error = await response.json(); throw new Error(`${error.message || 'Empty response'} - Status: ${response.status}`); } catch (error) { if (error instanceof Error) { error.message = `Unable to kill the container with ID "${id}": ${error.message}`; } throw error; } } /** * Updates a container with the given options. Note that the container must be stopped (not paused) for this function to work. * * @param {string} id - The ID of the container to update. * @param {ContainerUpdateOptions} options - The options to update the container with. * @returns {Promise<void>} A promise that resolves when the container is updated successfully. * @throws {Error} Throws an error if the request fails or the response is not OK. * @see https://docs.docker.com/engine/api/v1.41/#operation/ContainerUpdate */ async update(id, options) { if (typeof id !== 'string') { throw new Error('The container ID must be a string.'); } if (id.length === 0) { throw new Error('The container ID cannot be empty.'); } try { if (!helpers_1.default.isObject(options)) { throw new TypeError('The options argument must be an object.'); } if (!helpers_1.default.isNotEmptyObject(options)) { throw new SyntaxError('The options object cannot be empty.'); } const requestBody = {}; // validate the request options { if (helpers_1.default.hasOwnProperty(options, 'CpuShares')) { if (typeof options.CpuShares !== 'number') { throw new TypeError(`The "CpuShares" (when provided) option must be a number, instead got ${typeof options.CpuShares}`); } requestBody.CpuShares = options.CpuShares; } if (helpers_1.default.hasOwnProperty(options, 'CpuPeriod')) { if (typeof options.CpuPeriod !== 'number') { throw new TypeError(`The "CpuPeriod" (when provided) option must be a number, instead got ${typeof options.CpuPeriod}`); } requestBody.CpuPeriod = options.CpuPeriod; } if (helpers_1.default.hasOwnProperty(options, 'CpuQuota')) { if (typeof options.CpuQuota !== 'number') { throw new TypeError(`The "CpuQuota" (when provided) option must be a number, instead got ${typeof options.CpuQuota}`); } requestBody.CpuQuota = options.CpuQuota; } if (helpers_1.default.hasOwnProperty(options, 'CpuRealtimePeriod')) { if (typeof options.CpuRealtimePeriod !== 'number') { throw new TypeError(`The "CpuRealtimePeriod" (when provided) option must be a number, instead got ${typeof options.CpuRealtimePeriod}`); } requestBody.CpuRealtimePeriod = options.CpuRealtimePeriod; } if (helpers_1.default.hasOwnProperty(options, 'CpuRealtimeRuntime')) { if (typeof options.CpuRealtimeRuntime !== 'number') { throw new TypeError(`The "CpuRealtimeRuntime" (when provided) option must be a number, instead got ${typeof options.CpuRealtimeRuntime}`); } requestBody.CpuRealtimeRuntime = options.CpuRealtimeRuntime; } if (helpers_1.default.hasOwnProperty(options, 'NanoCpus')) { if (typeof options.NanoCpus !== 'number') { throw new TypeError(`The "NanoCpus" (when provided) option must be a number, instead got ${typeof options.NanoCpus}`); } requestBody.NanoCpus = options.NanoCpus; } if (helpers_1.default.hasOwnProperty(options, 'Memory')) { if (typeof options.Memory !== 'number') { throw new TypeError(`The "Memory" (when provided) option must be a number, instead got ${typeof options.Memory}`); } requestBody.Memory = options.Memory; } if (helpers_1.default.hasOwnProperty(options, 'MemoryReservation')) { if (typeof options.MemoryReservation !== 'number') { throw new TypeError(`The "MemoryReservation" (when provided) option must be a number, instead got ${typeof options.MemoryReservation}`); } requestBody.MemoryReservation = options.MemoryReservation; } if (helpers_1.default.hasOwnProperty(options, 'MemorySwap')) { if (typeof options.MemorySwap !== 'number') { throw new TypeError(`The "MemorySwap" (when provided) option must be a number, instead got ${typeof options.MemorySwap}`); } requestBody.MemorySwap = options.MemorySwap; } if (helpers_1.default.hasOwnProperty(options, 'KernelMemory')) { if (typeof options.KernelMemory !== 'number') { throw new TypeError(`The "KernelMemory" (when provided) option must be a number, instead got ${typeof options.KernelMemory}`); } requestBody.KernelMemory = options.KernelMemory; } if (helpers_1.default.hasOwnProperty(options, 'PidsLimit')) { if (typeof options.PidsLimit !== 'number') { throw new TypeError(`The "PidsLimit" (when provided) option must be a number, instead got ${typeof options.PidsLimit}`); } requestBody.PidsLimit = options.PidsLimit; } if (helpers_1.default.hasOwnProperty(options, 'BlkioWeight')) { if (typeof options.BlkioWeight !== 'number') { throw new TypeError(`The "BlkioWeight" (when provided) option must be a number, instead got ${typeof options.BlkioWeight}`); } requestBody.BlkioWeight = options.BlkioWeight; } if (helpers_1.default.hasOwnProperty(options, 'BlkioWeightDevice')) { if (!Array.isArray(options.BlkioWeightDevice)) { throw new TypeError(`The "BlkioWeightDevice" (when provided) option must be an array, instead got ${typeof options.BlkioWeightDevice}`); } for (const item of options.BlkioWeightDevice) { if (!helpers_1.default.isObject(item)) { throw new TypeError(`The "BlkioWeightDevice" array must contain objects, instead one of them was ${typeof item}`); } if (!('Path' in item)) { throw new SyntaxError(`The "BlkioWeightDevice" object must have a "Path" property.`); } if (!('Weight' in item)) { throw new SyntaxError(`The "BlkioWeightDevice" object must have a "Weight" property.`); } if (typeof item.Path !== 'string') { throw new TypeError(`The "Path" property of the "BlkioWeightDevice" object must be a string, instead got ${typeof item.Path}`); } if (typeof item.Weight !== 'number') { throw new TypeError(`The "Weight" property of the "BlkioWeightDevice" object must be a number, instead got ${typeof item.Weight}`); } } requestBody.BlkioWeightDevice = options.BlkioWeightDevice; } if (helpers_1.default.hasOwnProperty(options, 'BlkioDeviceReadBps')) { if (!Array.isArray(options.BlkioDeviceReadBps)) { throw new TypeError(`The "BlkioDeviceReadBps" (when provided) option must be an array, instead got ${typeof options.BlkioDeviceReadBps}`); } for (const item of options.BlkioDeviceReadBps) { if (!helpers_1.default.isObject(item)) { throw new TypeError(`The "BlkioDeviceReadBps" array must contain objects, instead one of them was ${typeof item}`); } if (!('Path' in item)) { throw new SyntaxError(`The "BlkioDeviceReadBps" object must have a "Path" property.`); } if (!('Rate' in item)) { throw new SyntaxError(`The "BlkioDeviceReadBps" object must have a "Rate" property.`); } if (typeof item.Path !== 'string') { throw new TypeError(`The "Path" property of the "BlkioDeviceReadBps" object must be a string, instead got ${typeof item.Path}`); } if (typeof item.Rate !== 'number') { throw new TypeError(`The "Rate" property of the "BlkioDeviceReadBps" object must be a number, instead got ${typeof item.Rate}`); } } requestBody.BlkioDeviceReadBps = options.BlkioDeviceReadBps; } if (helpers_1.default.hasOwnProperty(options, 'BlkioDeviceWriteBps')) { if (!Array.isArray(options.BlkioDeviceWriteBps)) { throw new TypeError(`The "BlkioDeviceWriteBps" (when provided) option must be an array, instead got ${typeof options.BlkioDeviceWriteBps}`); } for (const item of options.BlkioDeviceWriteBps) { if (!helpers_1.default.isObject(item)) { throw new TypeError(`The "BlkioDeviceWriteBps" array must contain objects, instead one of them was ${typeof item}`); } if (!('Path' in item)) { throw new SyntaxError(`The "BlkioDeviceWriteBps" object must have a "Path" property.`); } if (!('Rate' in item)) { throw new SyntaxError(`The "BlkioDeviceWriteBps" object must have a "Rate" property.`); } if (typeof item.Path !== 'string') { throw new TypeError(`The "Path" property of the "BlkioDeviceWriteBps" object must be a string, instead got ${typeof item.Path}`); } if (typeof item.Rate !== 'number') { throw new TypeError(`The "Rate" property of the "BlkioDeviceWriteBps" object must be a number, instead got ${typeof item.Rate}`); } } requestBody.BlkioDeviceWriteBps = options.BlkioDeviceWriteBps; } if (helpers_1.default.hasOwnProperty(options, 'BlkioDeviceReadIOps')) { if (!Array.isArray(options.BlkioDeviceReadIOps)) { throw new TypeError(`The "BlkioDeviceReadIOps" (when provided) option must be an array, instead got ${typeof options.BlkioDeviceReadIOps}`); } for (const item of options.BlkioDeviceReadIOps) { if (!helpers_1.default.isObject(item)) { throw new TypeError(`The "BlkioDeviceReadIOps" array must contain objects, instead one of them was ${typeof item}`); } if (!('Path' in item)) { throw new SyntaxError(`The "BlkioDeviceReadIOps" object must have a "Path" property.`); } if (!('Rate' in item)) { throw new SyntaxError(`The "BlkioDeviceReadIOps" object must have a "Rate" property.`); } if (typeof item.Path !== 'string') { throw new TypeError(`The "Path" property of the "BlkioDeviceReadIOps" object must be a string, instead got ${typeof item.Path}`); } if (typeof item.Rate !== 'number') { throw new TypeError(`The "Rate" property of the "BlkioDeviceReadIOps" object must be a number, instead got ${typeof item.Rate}`); } } requestBody.BlkioDeviceReadIOps = options.BlkioDeviceReadIOps; } if (helpers_1.default.hasOwnProperty(options, 'BlkioDeviceWriteIOps')) { if (!Array.isArray(options.BlkioDeviceWriteIOps)) { throw new TypeError(`The "BlkioDeviceWriteIOps" (when provided) option must be an array, instead got ${typeof options.BlkioDeviceWriteIOps}`); } for (const item of options.BlkioDeviceWriteIOps) { if (!helpers_1.default.isObject(item)) { throw new TypeError(`The "BlkioDeviceWriteIOps" array must contain objects, instead one of them was ${typeof item}`); } if (!('Path' in item)) { throw new SyntaxError(`The "BlkioDeviceWriteIOps" object must have a "Path" property.`); }