261 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			261 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import * as plugins from './mod.plugins.js';
 | 
						|
import * as helpers from './helpers.js';
 | 
						|
import { logger } from '../gitzone.logging.js';
 | 
						|
 | 
						|
export type ContainerStatus = 'running' | 'stopped' | 'not_exists';
 | 
						|
 | 
						|
export interface IDockerRunOptions {
 | 
						|
  name: string;
 | 
						|
  image: string;
 | 
						|
  ports?: { [key: string]: string };
 | 
						|
  volumes?: { [key: string]: string };
 | 
						|
  environment?: { [key: string]: string };
 | 
						|
  restart?: string;
 | 
						|
  command?: string;
 | 
						|
}
 | 
						|
 | 
						|
export class DockerContainer {
 | 
						|
  private smartshell: plugins.smartshell.Smartshell;
 | 
						|
  
 | 
						|
  constructor() {
 | 
						|
    this.smartshell = new plugins.smartshell.Smartshell({
 | 
						|
      executor: 'bash',
 | 
						|
    });
 | 
						|
  }
 | 
						|
  
 | 
						|
  /**
 | 
						|
   * Check if Docker is installed and available
 | 
						|
   */
 | 
						|
  public async checkDocker(): Promise<boolean> {
 | 
						|
    try {
 | 
						|
      const result = await this.smartshell.exec('docker --version');
 | 
						|
      return result.exitCode === 0;
 | 
						|
    } catch (error) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  
 | 
						|
  /**
 | 
						|
   * Get container status
 | 
						|
   */
 | 
						|
  public async getStatus(containerName: string): Promise<ContainerStatus> {
 | 
						|
    try {
 | 
						|
      // Check if running
 | 
						|
      const runningResult = await this.smartshell.exec(
 | 
						|
        `docker ps --format '{{.Names}}' | grep -q "^${containerName}$"`
 | 
						|
      );
 | 
						|
      
 | 
						|
      if (runningResult.exitCode === 0) {
 | 
						|
        return 'running';
 | 
						|
      }
 | 
						|
      
 | 
						|
      // Check if exists but stopped
 | 
						|
      const existsResult = await this.smartshell.exec(
 | 
						|
        `docker ps -a --format '{{.Names}}' | grep -q "^${containerName}$"`
 | 
						|
      );
 | 
						|
      
 | 
						|
      if (existsResult.exitCode === 0) {
 | 
						|
        return 'stopped';
 | 
						|
      }
 | 
						|
      
 | 
						|
      return 'not_exists';
 | 
						|
    } catch (error) {
 | 
						|
      return 'not_exists';
 | 
						|
    }
 | 
						|
  }
 | 
						|
  
 | 
						|
  /**
 | 
						|
   * Start a container
 | 
						|
   */
 | 
						|
  public async start(containerName: string): Promise<boolean> {
 | 
						|
    try {
 | 
						|
      const result = await this.smartshell.exec(`docker start ${containerName}`);
 | 
						|
      return result.exitCode === 0;
 | 
						|
    } catch (error) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  
 | 
						|
  /**
 | 
						|
   * Stop a container
 | 
						|
   */
 | 
						|
  public async stop(containerName: string): Promise<boolean> {
 | 
						|
    try {
 | 
						|
      const result = await this.smartshell.exec(`docker stop ${containerName}`);
 | 
						|
      return result.exitCode === 0;
 | 
						|
    } catch (error) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  
 | 
						|
  /**
 | 
						|
   * Remove a container
 | 
						|
   */
 | 
						|
  public async remove(containerName: string, force: boolean = false): Promise<boolean> {
 | 
						|
    try {
 | 
						|
      const forceFlag = force ? '-f' : '';
 | 
						|
      const result = await this.smartshell.exec(`docker rm ${forceFlag} ${containerName}`);
 | 
						|
      return result.exitCode === 0;
 | 
						|
    } catch (error) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  
 | 
						|
  /**
 | 
						|
   * Run a new container
 | 
						|
   */
 | 
						|
  public async run(options: IDockerRunOptions): Promise<boolean> {
 | 
						|
    let command = 'docker run -d';
 | 
						|
    
 | 
						|
    // Add name
 | 
						|
    command += ` --name ${options.name}`;
 | 
						|
    
 | 
						|
    // Add ports
 | 
						|
    if (options.ports) {
 | 
						|
      for (const [hostPort, containerPort] of Object.entries(options.ports)) {
 | 
						|
        command += ` -p ${hostPort}:${containerPort}`;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Add volumes
 | 
						|
    if (options.volumes) {
 | 
						|
      for (const [hostPath, containerPath] of Object.entries(options.volumes)) {
 | 
						|
        command += ` -v "${hostPath}:${containerPath}"`;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Add environment variables
 | 
						|
    if (options.environment) {
 | 
						|
      for (const [key, value] of Object.entries(options.environment)) {
 | 
						|
        command += ` -e ${key}="${value}"`;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Add restart policy
 | 
						|
    if (options.restart) {
 | 
						|
      command += ` --restart ${options.restart}`;
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Add image
 | 
						|
    command += ` ${options.image}`;
 | 
						|
    
 | 
						|
    // Add command if provided
 | 
						|
    if (options.command) {
 | 
						|
      command += ` ${options.command}`;
 | 
						|
    }
 | 
						|
    
 | 
						|
    try {
 | 
						|
      const result = await this.smartshell.exec(command);
 | 
						|
      return result.exitCode === 0;
 | 
						|
    } catch (error) {
 | 
						|
      logger.log('error', `Failed to run container: ${error.message}`);
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  
 | 
						|
  /**
 | 
						|
   * Execute a command in a running container
 | 
						|
   */
 | 
						|
  public async exec(containerName: string, command: string): Promise<string> {
 | 
						|
    try {
 | 
						|
      const result = await this.smartshell.exec(`docker exec ${containerName} ${command}`);
 | 
						|
      if (result.exitCode === 0) {
 | 
						|
        return result.stdout;
 | 
						|
      }
 | 
						|
      return '';
 | 
						|
    } catch (error) {
 | 
						|
      return '';
 | 
						|
    }
 | 
						|
  }
 | 
						|
  
 | 
						|
  /**
 | 
						|
   * Get container logs
 | 
						|
   */
 | 
						|
  public async logs(containerName: string, lines?: number): Promise<string> {
 | 
						|
    try {
 | 
						|
      const tailFlag = lines ? `--tail ${lines}` : '';
 | 
						|
      const result = await this.smartshell.exec(`docker logs ${tailFlag} ${containerName}`);
 | 
						|
      return result.stdout;
 | 
						|
    } catch (error) {
 | 
						|
      return `Error getting logs: ${error.message}`;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  
 | 
						|
  /**
 | 
						|
   * Check if a container exists
 | 
						|
   */
 | 
						|
  public async exists(containerName: string): Promise<boolean> {
 | 
						|
    const status = await this.getStatus(containerName);
 | 
						|
    return status !== 'not_exists';
 | 
						|
  }
 | 
						|
  
 | 
						|
  /**
 | 
						|
   * Check if a container is running
 | 
						|
   */
 | 
						|
  public async isRunning(containerName: string): Promise<boolean> {
 | 
						|
    const status = await this.getStatus(containerName);
 | 
						|
    return status === 'running';
 | 
						|
  }
 | 
						|
  
 | 
						|
  /**
 | 
						|
   * Wait for a container to be ready
 | 
						|
   */
 | 
						|
  public async waitForReady(containerName: string, maxAttempts: number = 30): Promise<boolean> {
 | 
						|
    for (let i = 0; i < maxAttempts; i++) {
 | 
						|
      if (await this.isRunning(containerName)) {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
      await plugins.smartdelay.delayFor(1000);
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  
 | 
						|
  /**
 | 
						|
   * Get container information
 | 
						|
   */
 | 
						|
  public async inspect(containerName: string): Promise<any> {
 | 
						|
    try {
 | 
						|
      const result = await this.smartshell.execSilent(`docker inspect ${containerName}`);
 | 
						|
      if (result.exitCode === 0) {
 | 
						|
        return JSON.parse(result.stdout);
 | 
						|
      }
 | 
						|
      return null;
 | 
						|
    } catch (error) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  
 | 
						|
  /**
 | 
						|
   * Get port mappings for a container
 | 
						|
   */
 | 
						|
  public async getPortMappings(containerName: string): Promise<{ [key: string]: string } | null> {
 | 
						|
    try {
 | 
						|
      // Use docker inspect without format to get full JSON, then extract PortBindings
 | 
						|
      const result = await this.smartshell.execSilent(`docker inspect ${containerName}`);
 | 
						|
      
 | 
						|
      if (result.exitCode === 0 && result.stdout) {
 | 
						|
        const inspectData = JSON.parse(result.stdout);
 | 
						|
        if (inspectData && inspectData[0] && inspectData[0].HostConfig && inspectData[0].HostConfig.PortBindings) {
 | 
						|
          const portBindings = inspectData[0].HostConfig.PortBindings;
 | 
						|
          const mappings: { [key: string]: string } = {};
 | 
						|
          
 | 
						|
          // Convert Docker's port binding format to simple host:container mapping
 | 
						|
          for (const [containerPort, hostBindings] of Object.entries(portBindings)) {
 | 
						|
            if (Array.isArray(hostBindings) && hostBindings.length > 0) {
 | 
						|
              const hostPort = (hostBindings[0] as any).HostPort;
 | 
						|
              if (hostPort) {
 | 
						|
                mappings[containerPort.replace('/tcp', '').replace('/udp', '')] = hostPort;
 | 
						|
              }
 | 
						|
            }
 | 
						|
          }
 | 
						|
          
 | 
						|
          return mappings;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      return null;
 | 
						|
    } catch (error) {
 | 
						|
      // Silently fail - container might not exist
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
  }
 | 
						|
} |