From 7635732952264d526a9437b50164d8c14fe38c23 Mon Sep 17 00:00:00 2001 From: Mahmoud-Emad Date: Wed, 10 Sep 2025 11:43:31 +0300 Subject: [PATCH] feat: integrate crun module for container config - Replace manual OCI config generation - Store `crun.CrunConfig` in `Container` struct - Expose `crun` config management methods - Use `crun` module in container factory - Add `crun_configs` map to factory ` --- lib/virt/crun/example.v | 68 +++++++------- lib/virt/heropods/config_template.json | 121 ------------------------- lib/virt/heropods/container.v | 66 +++++++++++++- lib/virt/heropods/container_create.v | 62 +++++++------ lib/virt/heropods/factory.v | 7 +- 5 files changed, 132 insertions(+), 192 deletions(-) delete mode 100644 lib/virt/heropods/config_template.json diff --git a/lib/virt/crun/example.v b/lib/virt/crun/example.v index 8be266a9..6c207ba8 100644 --- a/lib/virt/crun/example.v +++ b/lib/virt/crun/example.v @@ -1,31 +1,30 @@ module crun - pub fn example_heropods_compatible() ! { mut configs := map[string]&CrunConfig{} // Create a container configuration compatible with heropods template mut config := new(mut configs, name: 'heropods-example')! - + // Configure to match the template config.set_command(['/bin/sh']) - .set_working_dir('/') - .set_user(0, 0, []) - .add_env('PATH', '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin') - .add_env('TERM', 'xterm') - .set_rootfs('${rootfs_path}', false) // This will be replaced by the actual path - .set_hostname('container') - .set_no_new_privileges(true) - + 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('/tmp/rootfs', false) // This will be replaced by the actual path + config.set_hostname('container') + config.set_no_new_privileges(true) + // Add the specific rlimit from template config.add_rlimit(.rlimit_nofile, 1024, 1024) - + // Validate the configuration config.validate()! - + // Generate and print JSON json_output := config.to_json()! println(json_output) - + // Save to file config.save_to_file('/tmp/heropods_config.json')! println('Heropods-compatible configuration saved to /tmp/heropods_config.json') @@ -35,33 +34,34 @@ pub fn example_custom() ! { mut configs := map[string]&CrunConfig{} // Create a more complex container configuration mut config := new(mut configs, name: 'custom-container')! - + config.set_command(['/usr/bin/my-app', '--config', '/etc/myapp/config.yaml']) - .set_working_dir('/app') - .set_user(1000, 1000, [1001, 1002]) - .add_env('MY_VAR', 'my_value') - .add_env('ANOTHER_VAR', 'another_value') - .set_rootfs('/path/to/rootfs', false) - .set_hostname('my-custom-container') - .set_memory_limit(1024 * 1024 * 1024) // 1GB - .set_cpu_limits(100000, 50000, 1024) // period, quota, shares - .set_pids_limit(500) - .add_mount('/host/path', '/container/path', .bind, [.rw]) - .add_mount('/tmp/cache', '/app/cache', .tmpfs, [.rw, .noexec]) - .add_capability(.cap_sys_admin) - .remove_capability(.cap_net_raw) - .add_rlimit(.rlimit_nproc, 100, 50) - .set_no_new_privileges(true) - + config.set_working_dir('/app') + config.set_user(1000, 1000, [1001, 1002]) + config.add_env('MY_VAR', 'my_value') + config.add_env('ANOTHER_VAR', 'another_value') + config.set_rootfs('/path/to/rootfs', false) + config.set_hostname('my-custom-container') + config.set_memory_limit(1024 * 1024 * 1024) // 1GB + config.set_cpu_limits(100000, 50000, 1024) // period, quota, shares + config.set_pids_limit(500) + config.add_mount('/host/path', '/container/path', .bind, [.rw]) + config.add_mount('/tmp/cache', '/app/cache', .tmpfs, [.rw, .noexec]) + config.add_capability(.cap_sys_admin) + config.remove_capability(.cap_net_raw) + config.add_rlimit(.rlimit_nproc, 100, 50) + config.set_no_new_privileges(true) + // Add some additional security hardening + config.add_masked_path('/proc/kcore') - .add_readonly_path('/proc/sys') - + config.add_readonly_path('/proc/sys') + // Validate before use config.validate()! - + // Get the JSON json_str := config.to_json()! println('Custom container config:') println(json_str) -} \ No newline at end of file +} diff --git a/lib/virt/heropods/config_template.json b/lib/virt/heropods/config_template.json deleted file mode 100644 index 51cd699a..00000000 --- a/lib/virt/heropods/config_template.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "ociVersion": "1.0.2", - "process": { - "terminal": true, - "user": { - "uid": 0, - "gid": 0 - }, - "args": [ - "/bin/sh", - "-c", - "while true; do sleep 30; done" - ], - "env": [ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "TERM=xterm" - ], - "cwd": "/", - "capabilities": { - "bounding": [ - "CAP_AUDIT_WRITE", - "CAP_KILL", - "CAP_NET_BIND_SERVICE" - ], - "effective": [ - "CAP_AUDIT_WRITE", - "CAP_KILL", - "CAP_NET_BIND_SERVICE" - ], - "inheritable": [ - "CAP_AUDIT_WRITE", - "CAP_KILL", - "CAP_NET_BIND_SERVICE" - ], - "permitted": [ - "CAP_AUDIT_WRITE", - "CAP_KILL", - "CAP_NET_BIND_SERVICE" - ] - }, - "rlimits": [ - { - "type": "RLIMIT_NOFILE", - "hard": 1024, - "soft": 1024 - } - ], - "noNewPrivileges": true - }, - "root": { - "path": "${rootfs_path}", - "readonly": false - }, - "mounts": [ - { - "destination": "/proc", - "type": "proc", - "source": "proc" - }, - { - "destination": "/dev", - "type": "tmpfs", - "source": "tmpfs", - "options": [ - "nosuid", - "strictatime", - "mode=755", - "size=65536k" - ] - }, - { - "destination": "/sys", - "type": "sysfs", - "source": "sysfs", - "options": [ - "nosuid", - "noexec", - "nodev", - "ro" - ] - } - ], - "linux": { - "namespaces": [ - { - "type": "pid" - }, - { - "type": "network" - }, - { - "type": "ipc" - }, - { - "type": "uts" - }, - { - "type": "mount" - } - ], - "maskedPaths": [ - "/proc/acpi", - "/proc/kcore", - "/proc/keys", - "/proc/latency_stats", - "/proc/timer_list", - "/proc/timer_stats", - "/proc/sched_debug", - "/proc/scsi", - "/sys/firmware" - ], - "readonlyPaths": [ - "/proc/asound", - "/proc/bus", - "/proc/fs", - "/proc/irq", - "/proc/sys", - "/proc/sysrq-trigger" - ] - } -} \ No newline at end of file diff --git a/lib/virt/heropods/container.v b/lib/virt/heropods/container.v index bb2e1289..20ddef06 100644 --- a/lib/virt/heropods/container.v +++ b/lib/virt/heropods/container.v @@ -3,16 +3,19 @@ module heropods import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.osal.tmux import freeflowuniverse.herolib.osal.core as osal +import freeflowuniverse.herolib.virt.crun import time import freeflowuniverse.herolib.builder import json +@[heap] pub struct Container { pub mut: - name string - node ?&builder.Node - tmux_pane ?&tmux.Pane - factory &ContainerFactory + name string + node ?&builder.Node + tmux_pane ?&tmux.Pane + crun_config ?&crun.CrunConfig + factory &ContainerFactory } // Struct to parse JSON output of `crun state` @@ -242,3 +245,58 @@ pub fn (mut self Container) node() !&builder.Node { self.node = node return node } + +// Get the crun configuration for this container +pub fn (self Container) config() !&crun.CrunConfig { + return self.crun_config or { return error('Container ${self.name} has no crun configuration') } +} + +// Container configuration customization methods +pub fn (mut self Container) set_memory_limit(limit_mb u64) !&Container { + mut config := self.config()! + config.set_memory_limit(limit_mb * 1024 * 1024) // Convert MB to bytes + return &self +} + +pub fn (mut self Container) set_cpu_limits(period u64, quota i64, shares u64) !&Container { + mut config := self.config()! + config.set_cpu_limits(period, quota, shares) + return &self +} + +pub fn (mut self Container) add_mount(source string, destination string, mount_type crun.MountType, options []crun.MountOption) !&Container { + mut config := self.config()! + config.add_mount(source, destination, mount_type, options) + return &self +} + +pub fn (mut self Container) add_capability(cap crun.Capability) !&Container { + mut config := self.config()! + config.add_capability(cap) + return &self +} + +pub fn (mut self Container) remove_capability(cap crun.Capability) !&Container { + mut config := self.config()! + config.remove_capability(cap) + return &self +} + +pub fn (mut self Container) add_env(key string, value string) !&Container { + mut config := self.config()! + config.add_env(key, value) + return &self +} + +pub fn (mut self Container) set_working_dir(dir string) !&Container { + mut config := self.config()! + config.set_working_dir(dir) + return &self +} + +// Save the current configuration to disk +pub fn (self Container) save_config() ! { + config := self.config()! + config_path := '${self.factory.base_dir}/configs/${self.name}/config.json' + config.save_to_file(config_path)! +} diff --git a/lib/virt/heropods/container_create.v b/lib/virt/heropods/container_create.v index 80ae4d80..aa2077c3 100644 --- a/lib/virt/heropods/container_create.v +++ b/lib/virt/heropods/container_create.v @@ -2,10 +2,9 @@ module heropods import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.osal.core as osal -import freeflowuniverse.herolib.core.pathlib +import freeflowuniverse.herolib.virt.crun import freeflowuniverse.herolib.installers.virt.herorunner as herorunner_installer import os -import x.json2 // Updated enum to be more flexible pub enum ContainerImageType { @@ -27,7 +26,7 @@ pub: pub fn (mut self ContainerFactory) new(args ContainerNewArgs) !&Container { if args.name in self.containers && !args.reset { - return self.containers[args.name] + return self.containers[args.name] or { panic('bug: container should exist') } } // Determine image to use @@ -67,8 +66,8 @@ pub fn (mut self ContainerFactory) new(args ContainerNewArgs) !&Container { return error('Image rootfs not found: ${rootfs_path}. Please ensure the image is available.') } - // Create container config (with terminal disabled) but don't create the container yet - self.create_container_config(args.name, rootfs_path)! + // 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') { @@ -79,41 +78,44 @@ pub fn (mut self ContainerFactory) new(args ContainerNewArgs) !&Container { // 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 - factory: &self + name: args.name + crun_config: crun_config + factory: &self } self.containers[args.name] = container return container } -// Create OCI config.json from template -fn (self ContainerFactory) create_container_config(container_name string, rootfs_path string) ! { +// 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_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)! - // Load template - mut config_content := $tmpl('config_template.json') - - // Parse JSON with json2 - mut root := json2.raw_decode(config_content)! - mut config := root.as_map() - - // Get or create process map - mut process := if 'process' in config { - config['process'].as_map() - } else { - map[string]json2.Any{} - } - - // Force disable terminal - process['terminal'] = json2.Any(false) - config['process'] = json2.Any(process) - - // Write back to config.json config_path := '${config_dir}/config.json' - mut p := pathlib.get_file(path: config_path, create: true)! - p.write(json2.encode_pretty(json2.Any(config)))! + config.save_to_file(config_path)! + + return config } // Use podman to pull image and extract rootfs diff --git a/lib/virt/heropods/factory.v b/lib/virt/heropods/factory.v index ef6516c1..5268599e 100644 --- a/lib/virt/heropods/factory.v +++ b/lib/virt/heropods/factory.v @@ -2,7 +2,7 @@ module heropods import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.osal.core as osal -import time +import freeflowuniverse.herolib.virt.crun import os @[heap] @@ -11,6 +11,7 @@ pub mut: tmux_session string containers map[string]&Container images map[string]&ContainerImage + crun_configs map[string]&crun.CrunConfig base_dir string } @@ -104,7 +105,7 @@ pub fn (mut self ContainerFactory) get(args ContainerNewArgs) !&Container { if args.name !in self.containers { return error('Container "${args.name}" does not exist. Use factory.new() to create it first.') } - return self.containers[args.name] + return self.containers[args.name] or { panic('bug: container should exist') } } // Get image by name @@ -112,7 +113,7 @@ pub fn (mut self ContainerFactory) image_get(name string) !&ContainerImage { if name !in self.images { return error('Image "${name}" not found in cache. Try importing or downloading it.') } - return self.images[name] + return self.images[name] or { panic('bug: image should exist') } } // List all containers currently managed by crun