@twg-group/container-manager
Version:
Container management for Docker, Swarm, Kubernetes
216 lines • 8.76 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DockerStrategy = void 0;
const dockerode_1 = __importDefault(require("dockerode"));
const common_1 = require("@nestjs/common");
const base_strategy_1 = require("./base.strategy");
const nestjs_logger_1 = require("@twg-group/nestjs-logger");
let DockerStrategy = class DockerStrategy extends base_strategy_1.BaseStrategy {
logger;
docker;
constructor(logger) {
super(logger);
this.logger = logger;
this.docker = new dockerode_1.default({
socketPath: process.env.DOCKER_SOCKET || '/var/run/docker.sock',
});
}
async start(id) {
try {
const container = this.docker.getContainer(id);
await container.start();
}
catch (error) {
this.handleError(error, `Failed to start container ${id}`);
}
}
async stop(id, timeout = 10) {
try {
const container = this.docker.getContainer(id);
await container.stop({ t: timeout });
}
catch (error) {
this.handleError(error, `Failed to stop container ${id}`);
}
}
async deploy(config) {
this.validateConfig(config);
const containerName = config.name || this.generateName();
const portBindings = this.createPortBindings(config.ports);
const volumeBinds = this.createVolumeBinds(config.volumes);
try {
const container = await this.docker.createContainer({
Image: config.image,
name: containerName,
Env: this.formatEnvironment(config.env),
HostConfig: {
PortBindings: portBindings,
Binds: volumeBinds,
RestartPolicy: {
Name: config.restartPolicy ? 'always' : 'no',
},
},
Labels: config.labels,
});
await container.start();
return container.id;
}
catch (error) {
this.handleError(error, 'Docker deployment failed');
}
}
async list() {
try {
const containers = await this.docker.listContainers({ all: true });
return await Promise.all(containers.map((container) => this.formatContainerInfo(container)));
}
catch (error) {
this.handleError(error, 'Failed to list containers');
}
}
async remove(containerId) {
try {
const container = this.docker.getContainer(containerId);
await container.stop().catch(() => {
});
await container.remove();
}
catch (error) {
this.handleError(error, `Failed to remove container ${containerId}`);
}
}
async logs(containerId, since, tail) {
try {
const container = this.docker.getContainer(containerId);
const logs = await container.logs({
since,
tail,
stdout: true,
stderr: true,
timestamps: true,
});
return this.parseLogs(logs.toString());
}
catch (error) {
this.handleError(error, `Failed to get logs for ${containerId}`);
}
}
createPortBindings(ports) {
if (!ports?.length) {
return {};
}
return ports.reduce((acc, port) => {
if (!port.containerPort) {
return acc;
}
const protocol = port.protocol || 'tcp';
const key = `${port.containerPort}/${protocol}`;
acc[key] = port.hostPort ? [{ HostPort: port.hostPort }] : [];
return acc;
}, {});
}
createVolumeBinds(volumes) {
return volumes?.map((v) => `${v.hostPath}:${v.containerPath}:${v.mode || 'rw'}`);
}
formatEnvironment(env) {
return env ? Object.entries(env).map(([k, v]) => `${k}=${v}`) : [];
}
async formatContainerInfo(container) {
const containerInstance = this.docker.getContainer(container.Id);
const details = await containerInstance.inspect();
const ports = (container.Ports || [])
.map((p) => p.PublicPort ? `${p.PublicPort}:${p.PrivatePort}` : `${p.PrivatePort}`)
.filter(Boolean);
const env = (details.Config?.Env || []).reduce((acc, envLine) => {
const [key, ...value] = envLine.split('=');
if (key)
acc[key] = value.join('=');
return acc;
}, {});
return {
id: container.Id,
name: container.Names?.[0]?.replace(/^\//, '') || 'unnamed',
image: container.Image,
status: container.State,
ports: [...new Set(ports)].sort((a, b) => a.length - b.length || a.localeCompare(b)),
createdAt: new Date(container.Created * 1000).toISOString(),
labels: details.Config?.Labels || {},
env,
};
}
parseLogs(logs) {
const result = [];
const lines = logs.split('\n').filter((line) => line.trim().length > 0);
for (const line of lines) {
try {
const dockerMatch = line.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z)\s(stdout|stderr)\s(.+)/);
if (dockerMatch) {
result.push({
timestamp: dockerMatch[1].replace('T', ' ').replace('Z', ''),
stream: dockerMatch[2],
message: dockerMatch[3].trim(),
});
continue;
}
if (line.charCodeAt(0) <= 31) {
const timestampMatch = line.match(/(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}[.,]\d+Z?)/);
if (timestampMatch) {
const timestamp = timestampMatch[1]
.replace('T', ' ')
.replace(/[,Z]/g, '');
const messageStart = timestampMatch.index + timestampMatch[0].length;
const message = line
.slice(messageStart)
.replace(/[\x00-\x1F]/g, '')
.trim();
if (message) {
result.push({
timestamp,
stream: line.includes('stderr') ? 'stderr' : 'stdout',
message,
});
}
}
continue;
}
const cleanLine = line
.replace(/[\x00-\x1F]/g, ' ')
.replace(/\s+/g, ' ')
.trim();
const fallbackTimestamp = new Date()
.toISOString()
.replace('T', ' ')
.replace('Z', '');
result.push({
timestamp: fallbackTimestamp,
stream: cleanLine.toLowerCase().includes('error')
? 'stderr'
: 'stdout',
message: cleanLine,
});
}
catch (e) {
this.logger.error('Failed to parse log line:', line, e);
}
}
return result.filter((log) => log.message.length > 0 && log.message !== 'Z');
}
};
exports.DockerStrategy = DockerStrategy;
exports.DockerStrategy = DockerStrategy = __decorate([
(0, common_1.Injectable)({ scope: common_1.Scope.TRANSIENT }),
__metadata("design:paramtypes", [nestjs_logger_1.Logger])
], DockerStrategy);
//# sourceMappingURL=docker.strategy.js.map