@apiclient.xyz/docker
Version:
Provides easy communication with Docker remote API from Node.js, with TypeScript support.
1,010 lines (900 loc) • 29.6 kB
text/typescript
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 interface IAuthData {
serveraddress: string;
username: string;
password: string;
}
export interface IDockerHostConstructorOptions {
socketPath?: string;
imageStoreDir?: string;
}
export interface IHijackedStreamingResponse {
stream: plugins.stream.Duplex;
close: () => Promise<void>;
statusCode: number;
headers: plugins.http.IncomingHttpHeaders;
}
export class DockerHost {
public options: IDockerHostConstructorOptions;
/**
* the path where the docker sock can be found
*/
public socketPath: string;
private registryToken: string = '';
private imageStore: DockerImageStore; // Now private - use storeImage/retrieveImage instead
public smartBucket!: plugins.smartbucket.SmartBucket;
/**
* the constructor to instantiate a new docker sock instance
* @param pathArg
*/
constructor(optionsArg: IDockerHostConstructorOptions) {
this.options = {
...{
imageStoreDir: plugins.path.join(
paths.nogitDir,
'temp-docker-image-store',
),
},
...optionsArg,
};
let pathToUse: string;
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!,
});
}
public async start() {
await this.imageStore.start();
}
public 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
*/
public async ping(): Promise<void> {
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.
*/
public async getVersion(): Promise<{
Version: string;
ApiVersion: string;
MinAPIVersion?: string;
GitCommit: string;
GoVersion: string;
Os: string;
Arch: string;
KernelVersion: string;
BuildTime?: string;
}> {
const response = await this.request('GET', '/version');
return response.body;
}
/**
* authenticate against a registry
* @param userArg
* @param passArg
*/
public async auth(authData: IAuthData) {
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
*/
public async getAuthTokenFromDockerConfig(registryUrlArg: string) {
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: string =
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
*/
public async listNetworks() {
return await DockerNetwork._list(this);
}
/**
* Gets a network by name
*/
public async getNetworkByName(networkNameArg: string) {
return await DockerNetwork._fromName(this, networkNameArg);
}
/**
* Creates a network
*/
public async createNetwork(
descriptor: interfaces.INetworkCreationDescriptor,
) {
return await DockerNetwork._create(this, descriptor);
}
// ==============
// CONTAINERS - Public Factory API
// ==============
/**
* Lists all containers
*/
public async listContainers() {
return await DockerContainer._list(this);
}
/**
* Gets a container by ID
* Returns undefined if container does not exist
*/
public async getContainerById(containerId: string): Promise<DockerContainer | undefined> {
return await DockerContainer._fromId(this, containerId);
}
/**
* Creates a container
*/
public async createContainer(
descriptor: interfaces.IContainerCreationDescriptor,
) {
return await DockerContainer._create(this, descriptor);
}
// ==============
// SERVICES - Public Factory API
// ==============
/**
* Lists all services
*/
public async listServices() {
return await DockerService._list(this);
}
/**
* Gets a service by name
*/
public async getServiceByName(serviceName: string) {
return await DockerService._fromName(this, serviceName);
}
/**
* Creates a service
*/
public async createService(
descriptor: interfaces.IServiceCreationDescriptor,
) {
return await DockerService._create(this, descriptor);
}
// ==============
// IMAGES - Public Factory API
// ==============
/**
* Lists all images
*/
public async listImages() {
return await DockerImage._list(this);
}
/**
* Gets an image by name
*/
public async getImageByName(imageNameArg: string) {
return await DockerImage._fromName(this, imageNameArg);
}
/**
* Creates an image from a registry
*/
public async createImageFromRegistry(
descriptor: interfaces.IImageCreationDescriptor,
) {
return await DockerImage._createFromRegistry(this, {
creationObject: descriptor,
});
}
/**
* Creates an image from a tar stream
*/
public async createImageFromTarStream(
tarStream: plugins.smartstream.stream.Readable,
descriptor: interfaces.IImageCreationDescriptor,
) {
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
*/
public async pruneImages(options?: {
dangling?: boolean;
filters?: Record<string, string[]>;
}): Promise<{
ImagesDeleted: Array<{ Untagged?: string; Deleted?: string }>;
SpaceReclaimed: number;
}> {
const filters: Record<string, string[]> = 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
*/
public async buildImage(imageTag: string) {
return await DockerImage._build(this, imageTag);
}
// ==============
// SECRETS - Public Factory API
// ==============
/**
* Lists all secrets
*/
public async listSecrets() {
return await DockerSecret._list(this);
}
/**
* Gets a secret by name
*/
public async getSecretByName(secretName: string) {
return await DockerSecret._fromName(this, secretName);
}
/**
* Gets a secret by ID
*/
public async getSecretById(secretId: string) {
return await DockerSecret._fromId(this, secretId);
}
/**
* Creates a secret
*/
public async createSecret(
descriptor: interfaces.ISecretCreationDescriptor,
) {
return await DockerSecret._create(this, descriptor);
}
// ==============
// IMAGE STORE - Public API
// ==============
/**
* Stores an image in the local image store
*/
public async storeImage(
imageName: string,
tarStream: plugins.smartstream.stream.Readable,
): Promise<void> {
return await this.imageStore.storeImage(imageName, tarStream);
}
/**
* Retrieves an image from the local image store
*/
public async retrieveImage(
imageName: string,
): Promise<plugins.smartstream.stream.Readable> {
return await this.imageStore.getImage(imageName);
}
/**
*
*/
public async getEventObservable(): Promise<plugins.rxjs.Observable<any>> {
const response = await this.requestStreaming('GET', '/events');
// requestStreaming now returns Node.js stream, not web stream
const nodeStream = response as plugins.smartstream.stream.Readable;
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 as any).code !== 'ECONNRESET') {
observer.error(err);
}
});
return () => {
nodeStream.destroy();
};
});
}
/**
* activates docker swarm
*/
public async activateSwarm(addvertisementIpArg?: string) {
// determine advertisement address
let addvertisementIp: string = '';
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
*/
public async request(methodArg: string, routeArg: string, 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;
}
public async requestStreaming(
methodArg: string,
routeArg: string,
readStream?: plugins.smartstream.stream.Readable,
jsonData?: any,
): Promise<plugins.smartstream.stream.Readable | { statusCode: number; body: string; headers: any }> {
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: plugins.smartrequest.ICoreResponse;
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 as any).statusCode = response.status;
(nodeStream as any).body = ''; // For compatibility
return nodeStream;
}
public async requestHijackedStreaming(
methodArg: string,
routeArg: string,
jsonData: Record<string, unknown> = {},
): Promise<IHijackedStreamingResponse> {
const body = JSON.stringify(jsonData);
const headers: Record<string, string | number> = {
'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<IHijackedStreamingResponse>((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);
});
}
private async requestHijackedStreamingOverRawSocket(
methodArg: string,
routeArg: string,
bodyArg: string,
headersArg: Record<string, string | number>,
): Promise<IHijackedStreamingResponse> {
const socketPath = this.socketPath.slice('http://unix:'.length, -1);
const denoGlobal = (globalThis as any).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<IHijackedStreamingResponse>((resolve, reject) => {
let settled = false;
let responseBuffer = Buffer.alloc(0);
const cleanupBeforeResolve = () => {
socket.off('data', handleData);
socket.off('error', handleConnectError);
};
const handleConnectError = (errorArg: Error) => {
if (!settled) {
settled = true;
reject(errorArg);
}
};
const rejectWithDockerError = (statusCodeArg: number, bodyHeadArg: Buffer) => {
const chunks: Buffer[] = [];
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: Buffer) => {
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);
});
}
private async requestHijackedStreamingOverDenoUnixSocket(
socketPathArg: string,
methodArg: string,
routeArg: string,
bodyArg: string,
headersArg: Record<string, string | number>,
): Promise<IHijackedStreamingResponse> {
const denoGlobal = (globalThis as any).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,
};
}
}
private getNodeRequestOptions(
methodArg: string,
routeArg: string,
headersArg: Record<string, string | number>,
): {
requestModule: typeof plugins.http | typeof plugins.https;
options: plugins.http.RequestOptions | plugins.https.RequestOptions;
} {
const options: plugins.http.RequestOptions | plugins.https.RequestOptions = {
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,
};
}
private createDuplexForHijackedResponse(
responseArg: plugins.http.IncomingMessage,
writableArg: plugins.stream.Duplex,
): plugins.stream.Duplex {
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;
}
private createDuplexForRawSocket(
socketArg: plugins.net.Socket,
bodyHeadArg: Buffer,
): plugins.stream.Duplex {
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;
}
private createDuplexForDenoConn(
connArg: any,
bodyHeadArg: Buffer,
): plugins.stream.Duplex {
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 as 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 as Error);
}
}
};
void readLoop();
duplex.on('finish', () => {
closeConn();
});
return duplex;
}
private parseRawHttpResponseHeaders(headerTextArg: string): {
statusCode: number;
headers: plugins.http.IncomingHttpHeaders;
} {
const [statusLine, ...headerLines] = headerTextArg.split('\r\n');
const statusCode = Number(statusLine.split(' ')[1]);
const headers: plugins.http.IncomingHttpHeaders = {};
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 };
}
private async collectErrorResponse(responseArg: plugins.http.IncomingMessage): Promise<string> {
const chunks: Buffer[] = [];
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
*/
public async addS3Storage(optionsArg: plugins.tsclass.storage.IS3Descriptor) {
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;
}
}