import type { DockerHost } from './classes.host.js'; import type { IVolumeCreationDescriptor, IVolumeListOptions, IVolumeRemoveOptions, TVolumeRemoveStatus, } from './interfaces/volume.js'; import type { TLabels } from './interfaces/label.js'; import { DockerResource } from './classes.base.js'; import { assertDockerResponseStatus, assertDockerResponseStatusOneOf, assertNonemptyDockerString, buildDockerFilterRoute, formatDockerResponseBody, rollbackFailedDockerCreation, } from './helpers.docker.js'; export class DockerVolume extends DockerResource { public static async _list( dockerHostArg: DockerHost, optionsArg: IVolumeListOptions = {}, ): Promise { const route = buildDockerFilterRoute('/volumes', optionsArg.filters); const response = await dockerHostArg.request('GET', route); assertDockerResponseStatus('Docker volume list', 200, response); const volumeObjects = response.body?.Volumes; if (volumeObjects === null || volumeObjects === undefined) { return []; } if (!Array.isArray(volumeObjects)) { throw new Error( `Docker volume list failed: expected response body.Volumes to be an array; response body: ${formatDockerResponseBody(response.body)}`, ); } return volumeObjects.map( (volumeObjectArg: Record) => new DockerVolume(dockerHostArg, volumeObjectArg), ); } public static async _fromName( dockerHostArg: DockerHost, nameArg: string, ): Promise { assertNonemptyDockerString(nameArg, 'Docker volume name'); const response = await dockerHostArg.request( 'GET', `/volumes/${encodeURIComponent(nameArg)}`, ); if (response.statusCode === 404) { return undefined; } assertDockerResponseStatus(`Docker volume inspect "${nameArg}"`, 200, response); const volume = new DockerVolume(dockerHostArg, response.body); if (volume.Name !== nameArg) { throw new Error( `Docker volume inspect "${nameArg}" failed: inspected Name "${volume.Name}" does not equal the requested immutable name`, ); } return volume; } public static async _create( dockerHostArg: DockerHost, descriptorArg: IVolumeCreationDescriptor, ): Promise { assertNonemptyDockerString(descriptorArg.name, 'Docker volume name'); const response = await dockerHostArg.request('POST', '/volumes/create', { Name: descriptorArg.name, Driver: descriptorArg.driver || 'local', Labels: descriptorArg.labels || {}, DriverOpts: descriptorArg.options || {}, }); assertDockerResponseStatus('Docker volume create', 201, response); try { if (response.body?.Name !== descriptorArg.name) { throw new Error( `Docker volume create failed: expected response body.Name to equal "${descriptorArg.name}"; response body: ${formatDockerResponseBody(response.body)}`, ); } const createdVolume = await DockerVolume._fromName( dockerHostArg, descriptorArg.name, ); if (!createdVolume) { throw new Error( `Docker volume create failed: direct inspect did not find created name "${descriptorArg.name}"`, ); } return createdVolume; } catch (verificationError) { return await rollbackFailedDockerCreation( `Docker volume "${descriptorArg.name}"`, verificationError, async () => { const rollbackResponse = await dockerHostArg.request( 'DELETE', `/volumes/${encodeURIComponent(descriptorArg.name)}?force=true`, ); assertDockerResponseStatusOneOf( `Docker volume rollback remove "${descriptorArg.name}"`, [204, 404], rollbackResponse, ); }, ); } } public Name!: string; public Driver!: string; public Mountpoint!: string; public CreatedAt?: string; public Status?: Record; public Labels!: TLabels; public Scope!: string; public Options!: Record; private readonly immutableName: string; constructor( dockerHostArg: DockerHost, dockerVolumeObjectArg: Record, ) { super(dockerHostArg); assertNonemptyDockerString( dockerVolumeObjectArg.Name, 'Docker volume response Name', ); this.immutableName = dockerVolumeObjectArg.Name; Object.assign(this, dockerVolumeObjectArg); } public async refresh(): Promise { const updatedVolume = await DockerVolume._fromName( this.dockerHost, this.immutableName, ); if (!updatedVolume) { throw new Error( `Docker volume refresh failed: volume "${this.immutableName}" was not found`, ); } Object.assign(this, updatedVolume); this.Name = this.immutableName; } public hasLabels(requiredLabelsArg: TLabels): boolean { return Object.entries(requiredLabelsArg).every( ([keyArg, valueArg]) => this.Labels?.[keyArg] === valueArg, ); } public async remove(optionsArg: IVolumeRemoveOptions = {}): Promise { const query = optionsArg.force ? '?force=true' : ''; const response = await this.dockerHost.request( 'DELETE', `/volumes/${encodeURIComponent(this.immutableName)}${query}`, ); assertDockerResponseStatusOneOf( `Docker volume remove "${this.immutableName}"`, [204, 404], response, ); return response.statusCode === 204 ? 'removed' : 'already-removed'; } }