app-wizard-cli
Version:
CLI pour gérer votre projet Nest/Angular
153 lines (152 loc) • 7.55 kB
JavaScript
import fs from 'fs-extra';
import path from 'path';
import yaml from 'js-yaml';
import { deduceDeploymentOrder } from '../utils/docker-compose-utils.js';
export class DockerComposeService {
/**
* Retourne le nom du fichier docker-compose à utiliser selon l'environnement.
*/
static getComposeFileName(env) {
return env === 'prod' ? 'docker-compose.prod.yml' : 'docker-compose.dev.yml';
}
/**
* Récupère le contexte de build pour un service en lisant le fichier docker-compose.(dev|prod).yml.
* Si le champ build est une chaîne, il est traité comme le contexte.
* Sinon, s'il s'agit d'un objet, on utilise build.context.
*
* @param serviceName Nom du service.
* @param env Environnement ('dev' ou 'prod').
*/
static async getServiceBuildContext(serviceName, env) {
var _a;
const composeFileName = DockerComposeService.getComposeFileName(env);
const composePath = path.join(process.cwd(), composeFileName);
if (!(await fs.pathExists(composePath))) {
throw new Error(`Fichier ${composeFileName} introuvable dans ${process.cwd()}`);
}
const composeContent = await fs.readFile(composePath, 'utf8');
const composeData = yaml.load(composeContent);
const serviceDef = (_a = composeData.services) === null || _a === void 0 ? void 0 : _a[serviceName];
if (!serviceDef) {
throw new Error(`Service '${serviceName}' non trouvé dans ${composeFileName}`);
}
if (typeof serviceDef.build === 'string') {
return path.resolve(process.cwd(), serviceDef.build);
}
else if (typeof serviceDef.build === 'object' && serviceDef.build.context) {
return path.resolve(process.cwd(), serviceDef.build.context);
}
return path.join(process.cwd(), 'containers', serviceName);
}
/**
* Déduit l'ordre d'un service à partir du docker-compose.(dev|prod).yml en se basant sur l'ordre d'apparition des clés.
* Cette fonction est utilisée si le champ "order" n'est pas défini dans la configuration du service.
*
* @param serviceName Nom du service.
* @param env Environnement ('dev' ou 'prod').
* @returns Un nombre représentant l'ordre (commençant à 1).
*/
static async getServiceOrder(serviceName, env) {
const composeFileName = DockerComposeService.getComposeFileName(env);
const composePath = path.join(process.cwd(), composeFileName);
const orderList = await deduceDeploymentOrder(composePath);
const index = orderList.indexOf(serviceName);
if (index === -1) {
throw new Error(`Service '${serviceName}' non trouvé dans l'ordre déduit du ${composeFileName}.`);
}
return index + 1;
}
/**
* Récupère le health check d'un service depuis le docker-compose.(dev|prod).yml.
* On s'attend à ce que le champ healthcheck.test contienne la commande (souvent ["CMD", "/scripts/check_health.sh"]).
*
* @param serviceName Nom du service.
* @param env Environnement ('dev' ou 'prod').
* @returns La commande de health check sous forme de chaîne.
*/
static async getServiceHealthCheck(serviceName, env) {
var _a;
const composeFileName = DockerComposeService.getComposeFileName(env);
const composePath = path.join(process.cwd(), composeFileName);
if (!(await fs.pathExists(composePath))) {
throw new Error(`Fichier ${composeFileName} introuvable dans ${process.cwd()}`);
}
const composeContent = await fs.readFile(composePath, 'utf8');
const composeData = yaml.load(composeContent);
const serviceDef = (_a = composeData.services) === null || _a === void 0 ? void 0 : _a[serviceName];
if (!serviceDef) {
throw new Error(`Service '${serviceName}' non trouvé dans ${composeFileName}`);
}
if (serviceDef.healthcheck && serviceDef.healthcheck.test) {
const test = serviceDef.healthcheck.test;
if (Array.isArray(test)) {
return test.join(' ');
}
else if (typeof test === 'string') {
return test;
}
}
throw new Error(`Le service '${serviceName}' ne possède pas de healthcheck défini dans ${composeFileName}`);
}
/**
* Vérifie les noms d'images dans le fichier docker-compose.(dev|prod).yml.
* Pour chaque service, si la propriété "image" est absente ou ne correspond pas au format attendu,
* on retourne une liste d'écarts.
*
* @param env Environnement ('dev' ou 'prod').
* @param artifactRegistry L'URL ou l'identifiant du registry à utiliser pour composer l'image.
* @returns Une liste d'objets décrivant les écarts (serviceName, currentImage, expectedImage).
*/
static async checkImageNames(env, artifactRegistry) {
const composeFileName = DockerComposeService.getComposeFileName(env);
const composePath = path.join(process.cwd(), composeFileName);
if (!(await fs.pathExists(composePath))) {
throw new Error(`Fichier ${composeFileName} introuvable dans ${process.cwd()}`);
}
const composeContent = await fs.readFile(composePath, 'utf8');
const composeData = yaml.load(composeContent);
const discrepancies = [];
for (const serviceName in composeData.services) {
const serviceDef = composeData.services[serviceName];
const expectedImage = `${artifactRegistry}/${serviceName}:latest`;
const currentImage = serviceDef.image || '';
if (currentImage !== expectedImage) {
discrepancies.push({ serviceName, currentImage, expectedImage });
}
}
return discrepancies;
}
/**
* Corrige les noms d'images dans le fichier docker-compose.(dev|prod).yml pour qu'ils soient conformes.
*
* @param env Environnement ('dev' ou 'prod').
* @param artifactRegistry L'URL ou l'identifiant du registry à utiliser.
*/
static async correctImageNames(env, artifactRegistry) {
const composeFileName = DockerComposeService.getComposeFileName(env);
const composePath = path.join(process.cwd(), composeFileName);
if (!(await fs.pathExists(composePath))) {
throw new Error(`Fichier ${composeFileName} introuvable dans ${process.cwd()}`);
}
const composeContent = await fs.readFile(composePath, 'utf8');
const composeData = yaml.load(composeContent);
let modified = false;
for (const serviceName in composeData.services) {
const serviceDef = composeData.services[serviceName];
const expectedImage = `${artifactRegistry}/${serviceName}:latest`;
if (serviceDef.image !== expectedImage) {
console.log(`Service ${serviceName} : image actuelle "${serviceDef.image || 'absente'}" -> attendue "${expectedImage}"`);
serviceDef.image = expectedImage;
modified = true;
}
}
if (modified) {
const updatedContent = yaml.dump(composeData);
await fs.writeFile(composePath, updatedContent, 'utf8');
console.log(`Fichier ${composeFileName} mis à jour avec les noms d'images conformes.`);
}
else {
console.log(`Les noms d'images dans ${composeFileName} sont déjà conformes.`);
}
}
}