feat(tsdocker): add multi-registry and multi-arch Docker build/push/pull manager, registry storage, Dockerfile handling, and new CLI commands
This commit is contained in:
254
ts/classes.tsdockermanager.ts
Normal file
254
ts/classes.tsdockermanager.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user