import { LoggerService } from '../../logger/logger.service.js'; import { LOGGER_MODULE_PROVIDER } from '../../logger/logger.constants.js'; import { Inject, Injectable } from '@nestjs/common'; import { ContainerService } from '../container.service.js'; import chalk from 'chalk'; import { customAlphabet } from 'nanoid/non-secure'; import { highlight } from 'cli-highlight'; import { K8sService } from '../../k8s/k8s.service.js'; import { throwIfFailed } from '../../util/exec.js'; @Injectable() export class K8sContainerService extends ContainerService { public migrateusPodName: string; constructor( @Inject(LOGGER_MODULE_PROVIDER) protected readonly logger: LoggerService, private readonly k8sService: K8sService, ) { super(); this.migrateusPodName = `migrateus-${customAlphabet('abcdef1234567890')(6)}`; } public async setup() { const podSpec = { apiVersion: 'v1', kind: 'Pod', metadata: { name: this.migrateusPodName }, spec: { activeDeadlineSeconds: 600, terminationGracePeriodSeconds: 0, containers: [ { name: 'mysql', image: this.image, command: ['bash', '-c', 'sleep infinity'], }, ], }, }; throwIfFailed( await this.k8sService.kubectlApply(podSpec), (o) => `Failed to start pod with code ${o.code}: ${o.stderr}`, ); await this.k8sService.kubectl( `wait --for=condition=ready --timeout=60s pod ${this.migrateusPodName}`, { silent: true }, ); } public async cleanUp() { this.logger.debug(`Deleting pod ${chalk.bold(this.migrateusPodName)}`); await this.k8sService.kubectl( `delete pod --ignore-not-found=true ${this.migrateusPodName}`, { silent: true, }, ); } public async cleanUpAll() { const resources = ( await this.k8sService.kubectl(`get pods -oname`, { silent: true }) ).stdout .split('\n') .filter((line: string) => line.startsWith(`pod/migrateus-`)); if (resources.length > 0) { this.logger.debug(`Deleting ${chalk.bold(resources.length)} pods`); await this.k8sService.kubectl(`delete ${resources.join(' ')}`, { silent: true, }); } } public async execute(command: string) { this.logger.debug( `Executing ${highlight(command, { language: 'bash' })} in pod/${chalk.bold(this.migrateusPodName)}`, ); return await this.k8sService.kubectl( `exec ${this.migrateusPodName} -- bash -c "${command}"`, { silent: true, }, ); } public execInDirectus(command: string) { return this.k8sService.execInDirectus(command); } public async exfilFile(source: string, destination: string): Promise { throwIfFailed( await this.k8sService.kubectl( `cp ${this.migrateusPodName}:${source} ${destination}`, { silent: true }, ), (o) => `Failed to copy ${this.migrateusPodName}:${chalk.bold(source)} to ${chalk.bold(destination)}: ${o.stderr}`, ); } public async infilFile(source: string, destination: string): Promise { throwIfFailed( await this.k8sService.kubectl( `cp ${source} ${this.migrateusPodName}:${destination}`, { silent: true }, ), (o) => `Failed to copy ${chalk.bold(source)} to ${this.migrateusPodName}:${chalk.bold(destination)}: ${o.stderr}`, ); } public async copyFromDirectus( _remotePath: string, _localPath: string, ): Promise { throw new Error( 'SQLite file access is only supported on docker/docker-compose platforms — use a server engine (PostgreSQL) on k8s/ACA', ); } public async copyToDirectus( _localPath: string, _remotePath: string, ): Promise { throw new Error( 'SQLite file access is only supported on docker/docker-compose platforms — use a server engine (PostgreSQL) on k8s/ACA', ); } }