Files
herolib/lib/virt/heropods/container_create.v
Mahmoud-Emad 6c971ca689 feat: Add custom crun root and enhance container lifecycle
- Use custom `crun --root` for all container commands
- Implement `cleanup_crun_state` for factory reset
- Add retry logic for `crun create` on "File exists" error
- Improve OCI config with `set_terminal`, unique env/rlimits
- Add default mounts for `/dev/pts`, `/dev/shm`, `/dev/mqueue`, `/sys/fs/cgroup`
2025-09-10 13:44:32 +03:00

153 lines
4.3 KiB
V

module heropods
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.osal.core as osal
import freeflowuniverse.herolib.virt.crun
import freeflowuniverse.herolib.installers.virt.herorunner as herorunner_installer
import os
// Updated enum to be more flexible
pub enum ContainerImageType {
alpine_3_20
ubuntu_24_04
ubuntu_25_04
custom // For custom images downloaded via podman
}
@[params]
pub struct ContainerNewArgs {
pub:
name string @[required]
image ContainerImageType = .alpine_3_20
custom_image_name string // Used when image = .custom
docker_url string // Docker image URL for new images
reset bool
}
pub fn (mut self ContainerFactory) new(args ContainerNewArgs) !&Container {
if args.name in self.containers && !args.reset {
return self.containers[args.name] or { panic('bug: container should exist') }
}
// Determine image to use
mut image_name := ''
mut rootfs_path := ''
match args.image {
.alpine_3_20 {
image_name = 'alpine'
rootfs_path = '${self.base_dir}/images/alpine/rootfs'
}
.ubuntu_24_04 {
image_name = 'ubuntu_24_04'
rootfs_path = '${self.base_dir}/images/ubuntu/24.04/rootfs'
}
.ubuntu_25_04 {
image_name = 'ubuntu_25_04'
rootfs_path = '${self.base_dir}/images/ubuntu/25.04/rootfs'
}
.custom {
if args.custom_image_name == '' {
return error('custom_image_name is required when using custom image type')
}
image_name = args.custom_image_name
rootfs_path = '${self.base_dir}/images/${image_name}/rootfs'
// If image not yet extracted, pull and unpack it
if !os.is_dir(rootfs_path) && args.docker_url != '' {
console.print_debug('Pulling image ${args.docker_url} with podman...')
self.podman_pull_and_export(args.docker_url, image_name, rootfs_path)!
}
}
}
// Verify rootfs exists
if !os.is_dir(rootfs_path) {
return error('Image rootfs not found: ${rootfs_path}. Please ensure the image is available.')
}
// Create crun configuration using the crun module
mut crun_config := self.create_crun_config(args.name, rootfs_path)!
// Ensure crun is installed on host
if !osal.cmd_exists('crun') {
mut herorunner := herorunner_installer.new()!
herorunner.install()!
}
// Create container struct but don't create the actual container in crun yet
// The actual container creation will happen in container.start()
mut container := &Container{
name: args.name
crun_config: crun_config
factory: &self
}
self.containers[args.name] = container
return container
}
// Create crun configuration using the crun module
fn (mut self ContainerFactory) create_crun_config(container_name string, rootfs_path string) !&crun.CrunConfig {
// Create crun configuration using the factory pattern
mut config := crun.new(mut self.crun_configs, name: container_name)!
// Configure for heropods use case - disable terminal for background containers
config.set_terminal(false)
config.set_command(['/bin/sh', '-c', 'while true; do sleep 30; done'])
config.set_working_dir('/')
config.set_user(0, 0, [])
config.add_env('PATH', '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin')
config.add_env('TERM', 'xterm')
config.set_rootfs(rootfs_path, false)
config.set_hostname('container')
config.set_no_new_privileges(true)
// Add the specific rlimit for file descriptors
config.add_rlimit(.rlimit_nofile, 1024, 1024)
// Validate the configuration
config.validate()!
// Create config directory and save JSON
config_dir := '${self.base_dir}/configs/${container_name}'
osal.exec(cmd: 'mkdir -p ${config_dir}', stdout: false)!
config_path := '${config_dir}/config.json'
config.save_to_file(config_path)!
return config
}
// Use podman to pull image and extract rootfs
fn (self ContainerFactory) podman_pull_and_export(docker_url string, image_name string, rootfs_path string) ! {
// Pull image
osal.exec(
cmd: 'podman pull ${docker_url}'
stdout: true
)!
// Create temp container
temp_name := 'tmp_${image_name}_${os.getpid()}'
osal.exec(
cmd: 'podman create --name ${temp_name} ${docker_url}'
stdout: true
)!
// Export container filesystem
osal.exec(
cmd: 'mkdir -p ${rootfs_path}'
stdout: false
)!
osal.exec(
cmd: 'podman export ${temp_name} | tar -C ${rootfs_path} -xf -'
stdout: true
)!
// Cleanup temp container
osal.exec(
cmd: 'podman rm ${temp_name}'
stdout: false
)!
}