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
`
This commit is contained in:
Mahmoud-Emad
2025-09-10 11:43:31 +03:00
parent 6c9f4b54e0
commit 7635732952
5 changed files with 132 additions and 192 deletions

View File

@@ -1,31 +1,30 @@
module crun module crun
pub fn example_heropods_compatible() ! { pub fn example_heropods_compatible() ! {
mut configs := map[string]&CrunConfig{} mut configs := map[string]&CrunConfig{}
// Create a container configuration compatible with heropods template // Create a container configuration compatible with heropods template
mut config := new(mut configs, name: 'heropods-example')! mut config := new(mut configs, name: 'heropods-example')!
// Configure to match the template // Configure to match the template
config.set_command(['/bin/sh']) config.set_command(['/bin/sh'])
.set_working_dir('/') config.set_working_dir('/')
.set_user(0, 0, []) config.set_user(0, 0, [])
.add_env('PATH', '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin') config.add_env('PATH', '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin')
.add_env('TERM', 'xterm') config.add_env('TERM', 'xterm')
.set_rootfs('${rootfs_path}', false) // This will be replaced by the actual path config.set_rootfs('/tmp/rootfs', false) // This will be replaced by the actual path
.set_hostname('container') config.set_hostname('container')
.set_no_new_privileges(true) config.set_no_new_privileges(true)
// Add the specific rlimit from template // Add the specific rlimit from template
config.add_rlimit(.rlimit_nofile, 1024, 1024) config.add_rlimit(.rlimit_nofile, 1024, 1024)
// Validate the configuration // Validate the configuration
config.validate()! config.validate()!
// Generate and print JSON // Generate and print JSON
json_output := config.to_json()! json_output := config.to_json()!
println(json_output) println(json_output)
// Save to file // Save to file
config.save_to_file('/tmp/heropods_config.json')! config.save_to_file('/tmp/heropods_config.json')!
println('Heropods-compatible configuration saved to /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{} mut configs := map[string]&CrunConfig{}
// Create a more complex container configuration // Create a more complex container configuration
mut config := new(mut configs, name: 'custom-container')! mut config := new(mut configs, name: 'custom-container')!
config.set_command(['/usr/bin/my-app', '--config', '/etc/myapp/config.yaml']) config.set_command(['/usr/bin/my-app', '--config', '/etc/myapp/config.yaml'])
.set_working_dir('/app') config.set_working_dir('/app')
.set_user(1000, 1000, [1001, 1002]) config.set_user(1000, 1000, [1001, 1002])
.add_env('MY_VAR', 'my_value') config.add_env('MY_VAR', 'my_value')
.add_env('ANOTHER_VAR', 'another_value') config.add_env('ANOTHER_VAR', 'another_value')
.set_rootfs('/path/to/rootfs', false) config.set_rootfs('/path/to/rootfs', false)
.set_hostname('my-custom-container') config.set_hostname('my-custom-container')
.set_memory_limit(1024 * 1024 * 1024) // 1GB config.set_memory_limit(1024 * 1024 * 1024) // 1GB
.set_cpu_limits(100000, 50000, 1024) // period, quota, shares config.set_cpu_limits(100000, 50000, 1024) // period, quota, shares
.set_pids_limit(500) config.set_pids_limit(500)
.add_mount('/host/path', '/container/path', .bind, [.rw]) config.add_mount('/host/path', '/container/path', .bind, [.rw])
.add_mount('/tmp/cache', '/app/cache', .tmpfs, [.rw, .noexec]) config.add_mount('/tmp/cache', '/app/cache', .tmpfs, [.rw, .noexec])
.add_capability(.cap_sys_admin) config.add_capability(.cap_sys_admin)
.remove_capability(.cap_net_raw) config.remove_capability(.cap_net_raw)
.add_rlimit(.rlimit_nproc, 100, 50) config.add_rlimit(.rlimit_nproc, 100, 50)
.set_no_new_privileges(true) config.set_no_new_privileges(true)
// Add some additional security hardening // Add some additional security hardening
config.add_masked_path('/proc/kcore') config.add_masked_path('/proc/kcore')
.add_readonly_path('/proc/sys') config.add_readonly_path('/proc/sys')
// Validate before use // Validate before use
config.validate()! config.validate()!
// Get the JSON // Get the JSON
json_str := config.to_json()! json_str := config.to_json()!
println('Custom container config:') println('Custom container config:')
println(json_str) println(json_str)
} }

View File

@@ -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"
]
}
}

View File

@@ -3,16 +3,19 @@ module heropods
import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.osal.tmux import freeflowuniverse.herolib.osal.tmux
import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.osal.core as osal
import freeflowuniverse.herolib.virt.crun
import time import time
import freeflowuniverse.herolib.builder import freeflowuniverse.herolib.builder
import json import json
@[heap]
pub struct Container { pub struct Container {
pub mut: pub mut:
name string name string
node ?&builder.Node node ?&builder.Node
tmux_pane ?&tmux.Pane tmux_pane ?&tmux.Pane
factory &ContainerFactory crun_config ?&crun.CrunConfig
factory &ContainerFactory
} }
// Struct to parse JSON output of `crun state` // Struct to parse JSON output of `crun state`
@@ -242,3 +245,58 @@ pub fn (mut self Container) node() !&builder.Node {
self.node = node self.node = node
return 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)!
}

View File

@@ -2,10 +2,9 @@ module heropods
import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.osal.core as osal 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 freeflowuniverse.herolib.installers.virt.herorunner as herorunner_installer
import os import os
import x.json2
// Updated enum to be more flexible // Updated enum to be more flexible
pub enum ContainerImageType { pub enum ContainerImageType {
@@ -27,7 +26,7 @@ pub:
pub fn (mut self ContainerFactory) new(args ContainerNewArgs) !&Container { pub fn (mut self ContainerFactory) new(args ContainerNewArgs) !&Container {
if args.name in self.containers && !args.reset { 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 // 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.') 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 // Create crun configuration using the crun module
self.create_container_config(args.name, rootfs_path)! mut crun_config := self.create_crun_config(args.name, rootfs_path)!
// Ensure crun is installed on host // Ensure crun is installed on host
if !osal.cmd_exists('crun') { 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 // Create container struct but don't create the actual container in crun yet
// The actual container creation will happen in container.start() // The actual container creation will happen in container.start()
mut container := &Container{ mut container := &Container{
name: args.name name: args.name
factory: &self crun_config: crun_config
factory: &self
} }
self.containers[args.name] = container self.containers[args.name] = container
return container return container
} }
// Create OCI config.json from template // Create crun configuration using the crun module
fn (self ContainerFactory) create_container_config(container_name string, rootfs_path string) ! { 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}' config_dir := '${self.base_dir}/configs/${container_name}'
osal.exec(cmd: 'mkdir -p ${config_dir}', stdout: false)! 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' config_path := '${config_dir}/config.json'
mut p := pathlib.get_file(path: config_path, create: true)! config.save_to_file(config_path)!
p.write(json2.encode_pretty(json2.Any(config)))!
return config
} }
// Use podman to pull image and extract rootfs // Use podman to pull image and extract rootfs

View File

@@ -2,7 +2,7 @@ module heropods
import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.osal.core as osal
import time import freeflowuniverse.herolib.virt.crun
import os import os
@[heap] @[heap]
@@ -11,6 +11,7 @@ pub mut:
tmux_session string tmux_session string
containers map[string]&Container containers map[string]&Container
images map[string]&ContainerImage images map[string]&ContainerImage
crun_configs map[string]&crun.CrunConfig
base_dir string base_dir string
} }
@@ -104,7 +105,7 @@ pub fn (mut self ContainerFactory) get(args ContainerNewArgs) !&Container {
if args.name !in self.containers { if args.name !in self.containers {
return error('Container "${args.name}" does not exist. Use factory.new() to create it first.') 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 // Get image by name
@@ -112,7 +113,7 @@ pub fn (mut self ContainerFactory) image_get(name string) !&ContainerImage {
if name !in self.images { if name !in self.images {
return error('Image "${name}" not found in cache. Try importing or downloading it.') 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 // List all containers currently managed by crun