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:
@@ -1,6 +1,5 @@
|
|||||||
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
|
||||||
@@ -8,13 +7,13 @@ pub fn example_heropods_compatible() ! {
|
|||||||
|
|
||||||
// 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)
|
||||||
@@ -37,25 +36,26 @@ pub fn example_custom() ! {
|
|||||||
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()!
|
||||||
|
|||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)!
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user