190 lines
5.7 KiB
V
190 lines
5.7 KiB
V
module heropods
|
|
|
|
import incubaid.herolib.osal.core as osal
|
|
import incubaid.herolib.virt.crun
|
|
import incubaid.herolib.installers.virt.crun_installer
|
|
import os
|
|
|
|
// ContainerImageType defines the available container base images
|
|
pub enum ContainerImageType {
|
|
alpine_3_20 // Alpine Linux 3.20
|
|
ubuntu_24_04 // Ubuntu 24.04 LTS
|
|
ubuntu_25_04 // Ubuntu 25.04
|
|
custom // Custom image downloaded via podman
|
|
}
|
|
|
|
// ContainerNewArgs defines parameters for creating a new container
|
|
@[params]
|
|
pub struct ContainerNewArgs {
|
|
pub:
|
|
name string @[required] // Unique container name
|
|
image ContainerImageType = .alpine_3_20 // Base image type
|
|
custom_image_name string // Used when image = .custom
|
|
docker_url string // Docker image URL for new images
|
|
reset bool // Reset if container already exists
|
|
}
|
|
|
|
// Create a new container
|
|
//
|
|
// This method:
|
|
// 1. Validates the container name
|
|
// 2. Determines the image to use (built-in or custom)
|
|
// 3. Creates crun configuration
|
|
// 4. Configures DNS in rootfs
|
|
//
|
|
// Note: The actual container creation in crun happens when start() is called.
|
|
// This method only prepares the configuration and rootfs.
|
|
//
|
|
// Thread Safety:
|
|
// This method doesn't interact with network_config, so no mutex is needed.
|
|
// Network setup happens later in container.start().
|
|
pub fn (mut self HeroPods) container_new(args ContainerNewArgs) !&Container {
|
|
// Validate container name to prevent shell injection and path traversal
|
|
validate_container_name(args.name) or { return error('Invalid container name: ${err}') }
|
|
|
|
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 != '' {
|
|
self.logger.log(
|
|
cat: 'images'
|
|
log: 'Pulling image ${args.docker_url} with podman...'
|
|
logtype: .stdout
|
|
) or {}
|
|
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 crun_inst := crun_installer.get()!
|
|
crun_inst.install(reset: false)!
|
|
}
|
|
|
|
// 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
|
|
|
|
// Configure DNS in container rootfs (uses network_config but doesn't modify it)
|
|
self.network_configure_dns(args.name, rootfs_path)!
|
|
|
|
return container
|
|
}
|
|
|
|
// Create crun configuration for a container
|
|
//
|
|
// This creates an OCI-compliant runtime configuration with:
|
|
// - No terminal (background container)
|
|
// - Long-running sleep process
|
|
// - Standard environment variables
|
|
// - Resource limits
|
|
fn (mut self HeroPods) 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 resource limits
|
|
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
|
|
}
|
|
|
|
// Pull a Docker image using podman and extract its rootfs
|
|
//
|
|
// This method:
|
|
// 1. Pulls the image from Docker registry
|
|
// 2. Creates a temporary container from the image
|
|
// 3. Exports the container filesystem to rootfs_path
|
|
// 4. Cleans up the temporary container
|
|
fn (self HeroPods) 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
|
|
)!
|
|
}
|