Files
herolib/lib/builder/executor_crun.v
2025-10-12 12:30:19 +03:00

228 lines
6.5 KiB
V

module builder
import os
import rand
import incubaid.herolib.osal.core as osal
import incubaid.herolib.core.pathlib
import incubaid.herolib.ui.console
import incubaid.herolib.core.texttools
@[heap]
pub struct ExecutorCrun {
pub mut:
container_id string // container ID for crun
crun_root string // custom crun root directory
retry int = 1
debug bool = true
}
// Helper method to get crun command with custom root
fn (executor ExecutorCrun) crun_cmd(cmd string) string {
if executor.crun_root != '' {
return 'crun --root ${executor.crun_root} ${cmd}'
}
return 'crun ${cmd}'
}
pub fn (mut executor ExecutorCrun) init() ! {
// Verify container exists and is running
result := osal.exec(cmd: executor.crun_cmd('state ${executor.container_id}'), stdout: false) or {
return error('Container ${executor.container_id} not found or not accessible')
}
// Parse state to ensure container is running
if !result.output.contains('"status": "running"') {
return error('Container ${executor.container_id} is not running')
}
}
pub fn (mut executor ExecutorCrun) debug_on() {
executor.debug = true
}
pub fn (mut executor ExecutorCrun) debug_off() {
executor.debug = false
}
pub fn (mut executor ExecutorCrun) exec(args_ ExecArgs) !string {
mut args := args_
if executor.debug {
console.print_debug('execute in container ${executor.container_id}: ${args.cmd}')
}
mut cmd := executor.crun_cmd('exec ${executor.container_id} ${args.cmd}')
if args.cmd.contains('\n') {
// For multiline commands, write to temp file first
temp_script := '/tmp/crun_script_${rand.uuid_v4()}.sh'
script_content := texttools.dedent(args.cmd)
os.write_file(temp_script, script_content)!
// Copy script into container and execute
executor.file_write('/tmp/exec_script.sh', script_content)!
cmd = executor.crun_cmd('exec ${executor.container_id} bash /tmp/exec_script.sh')
}
res := osal.exec(cmd: cmd, stdout: args.stdout, debug: executor.debug)!
return res.output
}
pub fn (mut executor ExecutorCrun) exec_interactive(args_ ExecArgs) ! {
mut args := args_
if args.cmd.contains('\n') {
args.cmd = texttools.dedent(args.cmd)
executor.file_write('/tmp/interactive_script.sh', args.cmd)!
args.cmd = 'bash /tmp/interactive_script.sh'
}
cmd := executor.crun_cmd('exec -t ${executor.container_id} ${args.cmd}')
console.print_debug(cmd)
osal.execute_interactive(cmd)!
}
pub fn (mut executor ExecutorCrun) file_write(path string, text string) ! {
if executor.debug {
console.print_debug('Container ${executor.container_id} file write: ${path}')
}
// Write to temp file first, then copy into container
temp_file := '/tmp/crun_file_${rand.uuid_v4()}'
os.write_file(temp_file, text)!
defer { os.rm(temp_file) or {} }
// Use crun exec to copy file content
sbcmd := executor.crun_cmd('exec -i ${executor.container_id} tee ${path}')
cmd := 'cat ${temp_file} | ${sbcmd} > /dev/null'
osal.exec(cmd: cmd, stdout: false)!
}
pub fn (mut executor ExecutorCrun) file_read(path string) !string {
if executor.debug {
console.print_debug('Container ${executor.container_id} file read: ${path}')
}
return executor.exec(cmd: 'cat ${path}', stdout: false)
}
pub fn (mut executor ExecutorCrun) file_exists(path string) bool {
if executor.debug {
console.print_debug('Container ${executor.container_id} file exists: ${path}')
}
output := executor.exec(cmd: 'test -f ${path} && echo found || echo not found', stdout: false) or {
return false
}
return output.trim_space() == 'found'
}
pub fn (mut executor ExecutorCrun) delete(path string) ! {
if executor.debug {
console.print_debug('Container ${executor.container_id} delete: ${path}')
}
executor.exec(cmd: 'rm -rf ${path}', stdout: false)!
}
pub fn (mut executor ExecutorCrun) upload(args SyncArgs) ! {
// For container uploads, we need to copy files from host to container
// Use crun exec with tar for efficient transfer
mut src_path := pathlib.get(args.source)
if !src_path.exists() {
return error('Source path ${args.source} does not exist')
}
if src_path.is_dir() {
// For directories, use tar to transfer
temp_tar := '/tmp/crun_upload_${rand.uuid_v4()}.tar'
osal.exec(
cmd: 'tar -cf ${temp_tar} -C ${src_path.path_dir()} ${src_path.name()}'
stdout: false
)!
defer { os.rm(temp_tar) or {} }
// Extract in container
cmd := 'cat ${temp_tar} | crun exec -i ${executor.container_id} tar -xf - -C ${args.dest}'
osal.exec(cmd: cmd, stdout: args.stdout)!
} else {
// For single files
executor.file_write(args.dest, src_path.read()!)!
}
}
pub fn (mut executor ExecutorCrun) download(args SyncArgs) ! {
// Download from container to host
if executor.dir_exists(args.source) {
// For directories
temp_tar := '/tmp/crun_download_${rand.uuid_v4()}.tar'
cmd := 'crun exec ${executor.container_id} tar -cf - -C ${args.source} . > ${temp_tar}'
osal.exec(cmd: cmd, stdout: false)!
defer { os.rm(temp_tar) or {} }
// Extract on host
osal.exec(
cmd: 'mkdir -p ${args.dest} && tar -xf ${temp_tar} -C ${args.dest}'
stdout: args.stdout
)!
} else {
// For single files
content := executor.file_read(args.source)!
os.write_file(args.dest, content)!
}
}
pub fn (mut executor ExecutorCrun) environ_get() !map[string]string {
env := executor.exec(cmd: 'env', stdout: false) or {
return error('Cannot get environment from container ${executor.container_id}')
}
mut res := map[string]string{}
for line in env.split('\n') {
if line.contains('=') {
mut key, mut val := line.split_once('=') or { continue }
key = key.trim(' ')
val = val.trim(' ')
res[key] = val
}
}
return res
}
pub fn (mut executor ExecutorCrun) info() map[string]string {
return {
'category': 'crun'
'container_id': executor.container_id
'runtime': 'crun'
}
}
pub fn (mut executor ExecutorCrun) shell(cmd string) ! {
if cmd.len > 0 {
osal.execute_interactive('crun exec -t ${executor.container_id} ${cmd}')!
} else {
osal.execute_interactive('crun exec -t ${executor.container_id} /bin/sh')!
}
}
pub fn (mut executor ExecutorCrun) list(path string) ![]string {
if !executor.dir_exists(path) {
return error('Directory ${path} does not exist in container')
}
output := executor.exec(cmd: 'ls ${path}', stdout: false)!
mut res := []string{}
for line in output.split('\n') {
line_trimmed := line.trim_space()
if line_trimmed != '' {
res << line_trimmed
}
}
return res
}
pub fn (mut executor ExecutorCrun) dir_exists(path string) bool {
output := executor.exec(cmd: 'test -d ${path} && echo found || echo not found', stdout: false) or {
return false
}
return output.trim_space() == 'found'
}