255 lines
7.5 KiB
TypeScript
255 lines
7.5 KiB
TypeScript
import * as plugins from './tsdocker.plugins.js';
|
|
import * as paths from './tsdocker.paths.js';
|
|
import { logger } from './tsdocker.logging.js';
|
|
import { Dockerfile } from './classes.dockerfile.js';
|
|
import { DockerRegistry } from './classes.dockerregistry.js';
|
|
import { RegistryStorage } from './classes.registrystorage.js';
|
|
import type { ITsDockerConfig } from './interfaces/index.js';
|
|
|
|
const smartshellInstance = new plugins.smartshell.Smartshell({
|
|
executor: 'bash',
|
|
});
|
|
|
|
/**
|
|
* Main orchestrator class for Docker operations
|
|
*/
|
|
export class TsDockerManager {
|
|
public registryStorage: RegistryStorage;
|
|
public config: ITsDockerConfig;
|
|
public projectInfo: any;
|
|
private dockerfiles: Dockerfile[] = [];
|
|
|
|
constructor(config: ITsDockerConfig) {
|
|
this.config = config;
|
|
this.registryStorage = new RegistryStorage();
|
|
}
|
|
|
|
/**
|
|
* Prepares the manager by loading project info and registries
|
|
*/
|
|
public async prepare(): Promise<void> {
|
|
// Load project info
|
|
try {
|
|
const projectinfoInstance = new plugins.projectinfo.ProjectInfo(paths.cwd);
|
|
this.projectInfo = {
|
|
npm: {
|
|
name: projectinfoInstance.npm.name,
|
|
version: projectinfoInstance.npm.version,
|
|
},
|
|
};
|
|
} catch (err) {
|
|
logger.log('warn', 'Could not load project info');
|
|
this.projectInfo = null;
|
|
}
|
|
|
|
// Load registries from environment
|
|
this.registryStorage.loadFromEnv();
|
|
|
|
// Add registries from config if specified
|
|
if (this.config.registries) {
|
|
for (const registryUrl of this.config.registries) {
|
|
// Check if already loaded from env
|
|
if (!this.registryStorage.getRegistryByUrl(registryUrl)) {
|
|
// Try to load credentials for this registry from env
|
|
const envVarName = registryUrl.replace(/\./g, '_').toUpperCase();
|
|
const envString = process.env[`DOCKER_REGISTRY_${envVarName}`];
|
|
if (envString) {
|
|
try {
|
|
const registry = DockerRegistry.fromEnvString(envString);
|
|
this.registryStorage.addRegistry(registry);
|
|
} catch (err) {
|
|
logger.log('warn', `Could not load credentials for registry ${registryUrl}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
logger.log('info', `Prepared TsDockerManager with ${this.registryStorage.getAllRegistries().length} registries`);
|
|
}
|
|
|
|
/**
|
|
* Logs in to all configured registries
|
|
*/
|
|
public async login(): Promise<void> {
|
|
if (this.registryStorage.getAllRegistries().length === 0) {
|
|
logger.log('warn', 'No registries configured');
|
|
return;
|
|
}
|
|
await this.registryStorage.loginAll();
|
|
}
|
|
|
|
/**
|
|
* Discovers and sorts Dockerfiles in the current directory
|
|
*/
|
|
public async discoverDockerfiles(): Promise<Dockerfile[]> {
|
|
this.dockerfiles = await Dockerfile.readDockerfiles(this);
|
|
this.dockerfiles = await Dockerfile.sortDockerfiles(this.dockerfiles);
|
|
this.dockerfiles = await Dockerfile.mapDockerfiles(this.dockerfiles);
|
|
return this.dockerfiles;
|
|
}
|
|
|
|
/**
|
|
* Builds all discovered Dockerfiles in dependency order
|
|
*/
|
|
public async build(): Promise<Dockerfile[]> {
|
|
if (this.dockerfiles.length === 0) {
|
|
await this.discoverDockerfiles();
|
|
}
|
|
|
|
if (this.dockerfiles.length === 0) {
|
|
logger.log('warn', 'No Dockerfiles found');
|
|
return [];
|
|
}
|
|
|
|
// Check if buildx is needed
|
|
if (this.config.platforms && this.config.platforms.length > 1) {
|
|
await this.ensureBuildx();
|
|
}
|
|
|
|
logger.log('info', `Building ${this.dockerfiles.length} Dockerfiles...`);
|
|
await Dockerfile.buildDockerfiles(this.dockerfiles);
|
|
logger.log('success', 'All Dockerfiles built successfully');
|
|
|
|
return this.dockerfiles;
|
|
}
|
|
|
|
/**
|
|
* Ensures Docker buildx is set up for multi-architecture builds
|
|
*/
|
|
private async ensureBuildx(): Promise<void> {
|
|
logger.log('info', 'Setting up Docker buildx for multi-platform builds...');
|
|
|
|
// Check if a buildx builder exists
|
|
const inspectResult = await smartshellInstance.exec('docker buildx inspect tsdocker-builder 2>/dev/null');
|
|
|
|
if (inspectResult.exitCode !== 0) {
|
|
// Create a new buildx builder
|
|
logger.log('info', 'Creating new buildx builder...');
|
|
await smartshellInstance.exec('docker buildx create --name tsdocker-builder --use');
|
|
await smartshellInstance.exec('docker buildx inspect --bootstrap');
|
|
} else {
|
|
// Use existing builder
|
|
await smartshellInstance.exec('docker buildx use tsdocker-builder');
|
|
}
|
|
|
|
logger.log('ok', 'Docker buildx ready');
|
|
}
|
|
|
|
/**
|
|
* Pushes all built images to specified registries
|
|
*/
|
|
public async push(registryUrls?: string[]): Promise<void> {
|
|
if (this.dockerfiles.length === 0) {
|
|
await this.discoverDockerfiles();
|
|
}
|
|
|
|
if (this.dockerfiles.length === 0) {
|
|
logger.log('warn', 'No Dockerfiles found to push');
|
|
return;
|
|
}
|
|
|
|
// Determine which registries to push to
|
|
let registriesToPush: DockerRegistry[] = [];
|
|
|
|
if (registryUrls && registryUrls.length > 0) {
|
|
// Push to specified registries
|
|
for (const url of registryUrls) {
|
|
const registry = this.registryStorage.getRegistryByUrl(url);
|
|
if (registry) {
|
|
registriesToPush.push(registry);
|
|
} else {
|
|
logger.log('warn', `Registry ${url} not found in storage`);
|
|
}
|
|
}
|
|
} else {
|
|
// Push to all configured registries
|
|
registriesToPush = this.registryStorage.getAllRegistries();
|
|
}
|
|
|
|
if (registriesToPush.length === 0) {
|
|
logger.log('warn', 'No registries available to push to');
|
|
return;
|
|
}
|
|
|
|
// Push each Dockerfile to each registry
|
|
for (const dockerfile of this.dockerfiles) {
|
|
for (const registry of registriesToPush) {
|
|
await dockerfile.push(registry);
|
|
}
|
|
}
|
|
|
|
logger.log('success', 'All images pushed successfully');
|
|
}
|
|
|
|
/**
|
|
* Pulls images from a specified registry
|
|
*/
|
|
public async pull(registryUrl: string): Promise<void> {
|
|
if (this.dockerfiles.length === 0) {
|
|
await this.discoverDockerfiles();
|
|
}
|
|
|
|
const registry = this.registryStorage.getRegistryByUrl(registryUrl);
|
|
if (!registry) {
|
|
throw new Error(`Registry ${registryUrl} not found`);
|
|
}
|
|
|
|
for (const dockerfile of this.dockerfiles) {
|
|
await dockerfile.pull(registry);
|
|
}
|
|
|
|
logger.log('success', 'All images pulled successfully');
|
|
}
|
|
|
|
/**
|
|
* Runs tests for all Dockerfiles
|
|
*/
|
|
public async test(): Promise<void> {
|
|
if (this.dockerfiles.length === 0) {
|
|
await this.discoverDockerfiles();
|
|
}
|
|
|
|
if (this.dockerfiles.length === 0) {
|
|
logger.log('warn', 'No Dockerfiles found to test');
|
|
return;
|
|
}
|
|
|
|
await Dockerfile.testDockerfiles(this.dockerfiles);
|
|
logger.log('success', 'All tests completed');
|
|
}
|
|
|
|
/**
|
|
* Lists all discovered Dockerfiles and their info
|
|
*/
|
|
public async list(): Promise<Dockerfile[]> {
|
|
if (this.dockerfiles.length === 0) {
|
|
await this.discoverDockerfiles();
|
|
}
|
|
|
|
console.log('\nDiscovered Dockerfiles:');
|
|
console.log('========================\n');
|
|
|
|
for (let i = 0; i < this.dockerfiles.length; i++) {
|
|
const df = this.dockerfiles[i];
|
|
console.log(`${i + 1}. ${df.filePath}`);
|
|
console.log(` Tag: ${df.cleanTag}`);
|
|
console.log(` Base Image: ${df.baseImage}`);
|
|
console.log(` Version: ${df.version}`);
|
|
if (df.localBaseImageDependent) {
|
|
console.log(` Depends on: ${df.localBaseDockerfile?.cleanTag}`);
|
|
}
|
|
console.log('');
|
|
}
|
|
|
|
return this.dockerfiles;
|
|
}
|
|
|
|
/**
|
|
* Gets the cached Dockerfiles (after discovery)
|
|
*/
|
|
public getDockerfiles(): Dockerfile[] {
|
|
return this.dockerfiles;
|
|
}
|
|
}
|