refactor: enhance container lifecycle and Crun executor
- Refactor container definition and creation flow - Implement idempotent behavior for `container.start()` - Add comprehensive `ExecutorCrun` support to all Node methods - Standardize OCI image pulling and rootfs export via Podman - Update default OCI config for persistent containers and no terminal
This commit is contained in:
@@ -1,16 +1,62 @@
|
|||||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||||
|
|
||||||
import freeflowuniverse.herolib.virt.heropods
|
import freeflowuniverse.herolib.virt.heropods
|
||||||
// Initialize factory
|
|
||||||
|
|
||||||
|
// Initialize factory
|
||||||
mut factory := heropods.new(
|
mut factory := heropods.new(
|
||||||
reset: false
|
reset: false
|
||||||
use_podman: true
|
use_podman: true
|
||||||
) or { panic('Failed to init ContainerFactory: ${err}') }
|
) or { panic('Failed to init ContainerFactory: ${err}') }
|
||||||
|
|
||||||
container := factory.new(
|
println('=== HeroPods Refactored API Demo ===')
|
||||||
|
|
||||||
|
// Step 1: factory.new() now only creates a container definition/handle
|
||||||
|
// It does NOT create the actual container in the backend yet
|
||||||
|
mut container := factory.new(
|
||||||
name: 'myalpine'
|
name: 'myalpine'
|
||||||
image: .custom
|
image: .custom
|
||||||
custom_image_name: 'alpine_3_20'
|
custom_image_name: 'alpine_3_20'
|
||||||
docker_url: 'docker.io/library/alpine:3.20'
|
docker_url: 'docker.io/library/alpine:3.20'
|
||||||
)!
|
)!
|
||||||
|
|
||||||
|
println('✓ Container definition created: ${container.name}')
|
||||||
|
println(' (No actual container created in backend yet)')
|
||||||
|
|
||||||
|
// Step 2: container.start() handles creation and starting
|
||||||
|
// - Checks if container exists in backend
|
||||||
|
// - Creates it if it doesn't exist
|
||||||
|
// - Starts it if it exists but is stopped
|
||||||
|
println('\n--- First start() call ---')
|
||||||
|
container.start()!
|
||||||
|
println('✓ Container started successfully')
|
||||||
|
|
||||||
|
// Step 3: Multiple start() calls are now idempotent
|
||||||
|
println('\n--- Second start() call (should be idempotent) ---')
|
||||||
|
container.start()!
|
||||||
|
println('✓ Second start() call successful - no errors!')
|
||||||
|
|
||||||
|
// Step 4: Execute commands in the container and save results
|
||||||
|
println('\n--- Executing commands in container ---')
|
||||||
|
result1 := container.exec(cmd: 'ls -la /')!
|
||||||
|
println('✓ Command executed: ls -la /')
|
||||||
|
println('Result: ${result1}')
|
||||||
|
|
||||||
|
result2 := container.exec(cmd: 'echo "Hello from container!"')!
|
||||||
|
println('✓ Command executed: echo "Hello from container!"')
|
||||||
|
println('Result: ${result2}')
|
||||||
|
|
||||||
|
result3 := container.exec(cmd: 'uname -a')!
|
||||||
|
println('✓ Command executed: uname -a')
|
||||||
|
println('Result: ${result3}')
|
||||||
|
|
||||||
|
// Step 5: container.delete() works naturally on the instance
|
||||||
|
println('\n--- Deleting container ---')
|
||||||
|
container.delete()!
|
||||||
|
println('✓ Container deleted successfully')
|
||||||
|
|
||||||
|
println('\n=== Demo completed! ===')
|
||||||
|
println('The refactored API now works as expected:')
|
||||||
|
println('- factory.new() creates definition only')
|
||||||
|
println('- container.start() is idempotent')
|
||||||
|
println('- container.exec() works and returns results')
|
||||||
|
println('- container.delete() works on instances')
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub fn (mut executor ExecutorCrun) init() ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse state to ensure container is running
|
// Parse state to ensure container is running
|
||||||
if !result.output.contains('"status":"running"') {
|
if !result.output.contains('"status": "running"') {
|
||||||
return error('Container ${executor.container_id} is not running')
|
return error('Container ${executor.container_id} is not running')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ pub fn (mut node Node) exec(args ExecArgs) !string {
|
|||||||
return node.executor.exec(cmd: args.cmd, stdout: args.stdout)
|
return node.executor.exec(cmd: args.cmd, stdout: args.stdout)
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
return node.executor.exec(cmd: args.cmd, stdout: args.stdout)
|
return node.executor.exec(cmd: args.cmd, stdout: args.stdout)
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
return node.executor.exec(cmd: args.cmd, stdout: args.stdout)
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
@@ -80,6 +82,8 @@ pub fn (mut node Node) exec_silent(cmd string) !string {
|
|||||||
return node.executor.exec(cmd: cmd, stdout: false)
|
return node.executor.exec(cmd: cmd, stdout: false)
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
return node.executor.exec(cmd: cmd, stdout: false)
|
return node.executor.exec(cmd: cmd, stdout: false)
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
return node.executor.exec(cmd: cmd, stdout: false)
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
@@ -89,8 +93,11 @@ pub fn (mut node Node) exec_interactive(cmd_ string) ! {
|
|||||||
node.executor.exec_interactive(cmd: cmd_)!
|
node.executor.exec_interactive(cmd: cmd_)!
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
node.executor.exec_interactive(cmd: cmd_)!
|
node.executor.exec_interactive(cmd: cmd_)!
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
node.executor.exec_interactive(cmd: cmd_)!
|
||||||
|
} else {
|
||||||
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut node Node) file_write(path string, text string) ! {
|
pub fn (mut node Node) file_write(path string, text string) ! {
|
||||||
@@ -98,6 +105,8 @@ pub fn (mut node Node) file_write(path string, text string) ! {
|
|||||||
return node.executor.file_write(path, text)
|
return node.executor.file_write(path, text)
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
return node.executor.file_write(path, text)
|
return node.executor.file_write(path, text)
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
return node.executor.file_write(path, text)
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
@@ -107,6 +116,8 @@ pub fn (mut node Node) file_read(path string) !string {
|
|||||||
return node.executor.file_read(path)
|
return node.executor.file_read(path)
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
return node.executor.file_read(path)
|
return node.executor.file_read(path)
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
return node.executor.file_read(path)
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
@@ -116,6 +127,8 @@ pub fn (mut node Node) file_exists(path string) bool {
|
|||||||
return node.executor.file_exists(path)
|
return node.executor.file_exists(path)
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
return node.executor.file_exists(path)
|
return node.executor.file_exists(path)
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
return node.executor.file_exists(path)
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
@@ -137,6 +150,8 @@ pub fn (mut node Node) delete(path string) ! {
|
|||||||
return node.executor.delete(path)
|
return node.executor.delete(path)
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
return node.executor.delete(path)
|
return node.executor.delete(path)
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
return node.executor.delete(path)
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
@@ -179,6 +194,8 @@ pub fn (mut node Node) download(args_ SyncArgs) ! {
|
|||||||
return node.executor.download(args)
|
return node.executor.download(args)
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
return node.executor.download(args)
|
return node.executor.download(args)
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
return node.executor.download(args)
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
@@ -208,6 +225,8 @@ pub fn (mut node Node) upload(args_ SyncArgs) ! {
|
|||||||
return node.executor.upload(args)
|
return node.executor.upload(args)
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
return node.executor.upload(args)
|
return node.executor.upload(args)
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
return node.executor.upload(args)
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
@@ -224,6 +243,8 @@ pub fn (mut node Node) environ_get(args EnvGetParams) !map[string]string {
|
|||||||
return node.executor.environ_get()
|
return node.executor.environ_get()
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
return node.executor.environ_get()
|
return node.executor.environ_get()
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
return node.executor.environ_get()
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
@@ -235,6 +256,8 @@ pub fn (mut node Node) info() map[string]string {
|
|||||||
return node.executor.info()
|
return node.executor.info()
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
return node.executor.info()
|
return node.executor.info()
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
return node.executor.info()
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
@@ -244,6 +267,8 @@ pub fn (mut node Node) shell(cmd string) ! {
|
|||||||
return node.executor.shell(cmd)
|
return node.executor.shell(cmd)
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
return node.executor.shell(cmd)
|
return node.executor.shell(cmd)
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
return node.executor.shell(cmd)
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
@@ -257,6 +282,8 @@ pub fn (mut node Node) list(path string) ![]string {
|
|||||||
return node.executor.list(path)
|
return node.executor.list(path)
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
return node.executor.list(path)
|
return node.executor.list(path)
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
return node.executor.list(path)
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
@@ -266,6 +293,8 @@ pub fn (mut node Node) dir_exists(path string) bool {
|
|||||||
return node.executor.dir_exists(path)
|
return node.executor.dir_exists(path)
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
return node.executor.dir_exists(path)
|
return node.executor.dir_exists(path)
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
return node.executor.dir_exists(path)
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
@@ -275,8 +304,11 @@ pub fn (mut node Node) debug_off() {
|
|||||||
node.executor.debug_off()
|
node.executor.debug_off()
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
node.executor.debug_off()
|
node.executor.debug_off()
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
node.executor.debug_off()
|
||||||
|
} else {
|
||||||
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut node Node) debug_on() {
|
pub fn (mut node Node) debug_on() {
|
||||||
@@ -284,6 +316,9 @@ pub fn (mut node Node) debug_on() {
|
|||||||
node.executor.debug_on()
|
node.executor.debug_on()
|
||||||
} else if mut node.executor is ExecutorSSH {
|
} else if mut node.executor is ExecutorSSH {
|
||||||
node.executor.debug_on()
|
node.executor.debug_on()
|
||||||
|
} else if mut node.executor is ExecutorCrun {
|
||||||
|
node.executor.debug_on()
|
||||||
|
} else {
|
||||||
|
panic('did not find right executor')
|
||||||
}
|
}
|
||||||
panic('did not find right executor')
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
"gid": 0
|
"gid": 0
|
||||||
},
|
},
|
||||||
"args": [
|
"args": [
|
||||||
"/bin/sh"
|
"/bin/sh",
|
||||||
|
"-c",
|
||||||
|
"while true; do sleep 30; done"
|
||||||
],
|
],
|
||||||
"env": [
|
"env": [
|
||||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
|
|||||||
@@ -15,13 +15,48 @@ pub mut:
|
|||||||
factory &ContainerFactory
|
factory &ContainerFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Struct to parse JSON output of `crun state`
|
||||||
|
struct CrunState {
|
||||||
|
id string
|
||||||
|
status string
|
||||||
|
pid int
|
||||||
|
bundle string
|
||||||
|
created string
|
||||||
|
}
|
||||||
|
|
||||||
pub fn (mut self Container) start() ! {
|
pub fn (mut self Container) start() ! {
|
||||||
|
// Check if container exists in crun
|
||||||
|
container_exists := self.container_exists_in_crun()!
|
||||||
|
|
||||||
|
if !container_exists {
|
||||||
|
// Container doesn't exist, create it first
|
||||||
|
console.print_debug('Container ${self.name} does not exist, creating it...')
|
||||||
|
osal.exec(
|
||||||
|
cmd: 'crun create --bundle ${self.factory.base_dir}/configs/${self.name} ${self.name}'
|
||||||
|
stdout: true
|
||||||
|
)!
|
||||||
|
console.print_debug('Container ${self.name} created')
|
||||||
|
}
|
||||||
|
|
||||||
status := self.status()!
|
status := self.status()!
|
||||||
if status == .running {
|
if status == .running {
|
||||||
console.print_debug('Container ${self.name} is already running')
|
console.print_debug('Container ${self.name} is already running')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If container exists but is stopped, we need to delete and recreate it
|
||||||
|
// because crun doesn't allow restarting a stopped container
|
||||||
|
if container_exists && status != .running {
|
||||||
|
console.print_debug('Container ${self.name} exists but is stopped, recreating...')
|
||||||
|
osal.exec(cmd: 'crun delete ${self.name}', stdout: false) or {}
|
||||||
|
osal.exec(
|
||||||
|
cmd: 'crun create --bundle ${self.factory.base_dir}/configs/${self.name} ${self.name}'
|
||||||
|
stdout: true
|
||||||
|
)!
|
||||||
|
console.print_debug('Container ${self.name} recreated')
|
||||||
|
}
|
||||||
|
|
||||||
|
// start the container (crun start doesn't have --detach flag)
|
||||||
osal.exec(cmd: 'crun start ${self.name}', stdout: true)!
|
osal.exec(cmd: 'crun start ${self.name}', stdout: true)!
|
||||||
console.print_green('Container ${self.name} started')
|
console.print_green('Container ${self.name} started')
|
||||||
}
|
}
|
||||||
@@ -44,8 +79,20 @@ pub fn (mut self Container) stop() ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut self Container) delete() ! {
|
pub fn (mut self Container) delete() ! {
|
||||||
|
// Check if container exists before trying to delete
|
||||||
|
if !self.container_exists_in_crun()! {
|
||||||
|
console.print_debug('Container ${self.name} does not exist, nothing to delete')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
self.stop()!
|
self.stop()!
|
||||||
osal.exec(cmd: 'crun delete ${self.name}', stdout: false) or {}
|
osal.exec(cmd: 'crun delete ${self.name}', stdout: false) or {}
|
||||||
|
|
||||||
|
// Remove from factory's container cache
|
||||||
|
if self.name in self.factory.containers {
|
||||||
|
self.factory.containers.delete(self.name)
|
||||||
|
}
|
||||||
|
|
||||||
console.print_green('Container ${self.name} deleted')
|
console.print_green('Container ${self.name} deleted')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,11 +112,9 @@ pub fn (self Container) status() !ContainerStatus {
|
|||||||
result := osal.exec(cmd: 'crun state ${self.name}', stdout: false) or { return .unknown }
|
result := osal.exec(cmd: 'crun state ${self.name}', stdout: false) or { return .unknown }
|
||||||
|
|
||||||
// Parse JSON output from crun state
|
// Parse JSON output from crun state
|
||||||
state := json.decode(map[string]json.Any, result.output) or { return .unknown }
|
state := json.decode(CrunState, result.output) or { return .unknown }
|
||||||
|
|
||||||
status_str := state['status'].str()
|
return match state.status {
|
||||||
|
|
||||||
return match status_str {
|
|
||||||
'running' { .running }
|
'running' { .running }
|
||||||
'stopped' { .stopped }
|
'stopped' { .stopped }
|
||||||
'paused' { .paused }
|
'paused' { .paused }
|
||||||
@@ -77,6 +122,15 @@ pub fn (self Container) status() !ContainerStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if container exists in crun (regardless of its state)
|
||||||
|
fn (self Container) container_exists_in_crun() !bool {
|
||||||
|
// Try to get container state - if it fails, container doesn't exist
|
||||||
|
result := osal.exec(cmd: 'crun state ${self.name}', stdout: false) or { return false }
|
||||||
|
|
||||||
|
// If we get here, the container exists (even if stopped/paused)
|
||||||
|
return result.exit_code == 0
|
||||||
|
}
|
||||||
|
|
||||||
pub enum ContainerStatus {
|
pub enum ContainerStatus {
|
||||||
running
|
running
|
||||||
stopped
|
stopped
|
||||||
@@ -92,8 +146,6 @@ pub fn (self Container) cpu_usage() !f64 {
|
|||||||
stdout: false
|
stdout: false
|
||||||
) or { return 0.0 }
|
) or { return 0.0 }
|
||||||
|
|
||||||
// Parse cpu.stat file and calculate usage percentage
|
|
||||||
// This is a simplified implementation
|
|
||||||
for line in result.output.split_into_lines() {
|
for line in result.output.split_into_lines() {
|
||||||
if line.starts_with('usage_usec') {
|
if line.starts_with('usage_usec') {
|
||||||
usage := line.split(' ')[1].f64()
|
usage := line.split(' ')[1].f64()
|
||||||
@@ -166,25 +218,22 @@ pub fn (mut self Container) node() !&builder.Node {
|
|||||||
return self.node
|
return self.node
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create builder factory (so node has proper lifecycle)
|
|
||||||
mut b := builder.new()!
|
mut b := builder.new()!
|
||||||
|
|
||||||
// Create a new executor for this container
|
|
||||||
mut exec := builder.ExecutorCrun{
|
mut exec := builder.ExecutorCrun{
|
||||||
container_id: self.name
|
container_id: self.name
|
||||||
debug: false
|
debug: false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize executor (checks container is running)
|
|
||||||
exec.init() or {
|
exec.init() or {
|
||||||
return error('Failed to init ExecutorCrun for container ${self.name}: ${err}')
|
return error('Failed to init ExecutorCrun for container ${self.name}: ${err}')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a node with this executor
|
// Create node using the factory method, then override the executor
|
||||||
mut node := b.node_new(name: 'container_${self.name}')!
|
mut node := b.node_new(name: 'container_${self.name}', ipaddr: 'localhost')!
|
||||||
node.executor = exec
|
node.executor = exec
|
||||||
node.platform = .alpine // TODO: detect from ContainerImageType
|
node.platform = .alpine
|
||||||
node.cputype = .intel // TODO: detect dynamically if needed
|
node.cputype = .intel
|
||||||
node.done = map[string]string{}
|
node.done = map[string]string{}
|
||||||
node.environment = map[string]string{}
|
node.environment = map[string]string{}
|
||||||
node.hostname = self.name
|
node.hostname = self.name
|
||||||
|
|||||||
@@ -3,9 +3,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.core.pathlib
|
||||||
import freeflowuniverse.herolib.core.texttools
|
|
||||||
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 {
|
||||||
@@ -54,14 +54,10 @@ pub fn (mut self ContainerFactory) new(args ContainerNewArgs) !&Container {
|
|||||||
image_name = args.custom_image_name
|
image_name = args.custom_image_name
|
||||||
rootfs_path = '${self.base_dir}/images/${image_name}/rootfs'
|
rootfs_path = '${self.base_dir}/images/${image_name}/rootfs'
|
||||||
|
|
||||||
// Check if image exists, if not and docker_url provided, create it
|
// If image not yet extracted, pull and unpack it
|
||||||
if !os.is_dir(rootfs_path) && args.docker_url != '' {
|
if !os.is_dir(rootfs_path) && args.docker_url != '' {
|
||||||
console.print_debug('Creating new image ${image_name} from ${args.docker_url}')
|
console.print_debug('Pulling image ${args.docker_url} with podman...')
|
||||||
_ = self.image_new(
|
self.podman_pull_and_export(args.docker_url, image_name, rootfs_path)!
|
||||||
image_name: image_name
|
|
||||||
docker_url: args.docker_url
|
|
||||||
reset: args.reset
|
|
||||||
)!
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,21 +67,17 @@ 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
|
// Create container config (with terminal disabled) but don't create the container yet
|
||||||
self.create_container_config(args.name, rootfs_path)!
|
self.create_container_config(args.name, rootfs_path)!
|
||||||
|
|
||||||
// Install crun if not installed
|
// Ensure crun is installed on host
|
||||||
if !osal.cmd_exists('crun') {
|
if !osal.cmd_exists('crun') {
|
||||||
mut herorunner := herorunner_installer.new()!
|
mut herorunner := herorunner_installer.new()!
|
||||||
herorunner.install()!
|
herorunner.install()!
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create container using crun
|
// Create container struct but don't create the actual container in crun yet
|
||||||
osal.exec(
|
// The actual container creation will happen in container.start()
|
||||||
cmd: 'crun create --bundle ${self.base_dir}/configs/${args.name} ${args.name}'
|
|
||||||
stdout: true
|
|
||||||
)!
|
|
||||||
|
|
||||||
mut container := &Container{
|
mut container := &Container{
|
||||||
name: args.name
|
name: args.name
|
||||||
factory: &self
|
factory: &self
|
||||||
@@ -95,14 +87,63 @@ pub fn (mut self ContainerFactory) new(args ContainerNewArgs) !&Container {
|
|||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create OCI config.json from template
|
||||||
fn (self ContainerFactory) create_container_config(container_name string, rootfs_path string) ! {
|
fn (self ContainerFactory) create_container_config(container_name string, rootfs_path string) ! {
|
||||||
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)!
|
||||||
|
|
||||||
// Generate OCI config.json using template
|
// Load template
|
||||||
config_content := $tmpl('config_template.json')
|
mut config_content := $tmpl('config_template.json')
|
||||||
config_path := '${config_dir}/config.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)!
|
mut p := pathlib.get_file(path: config_path, create: true)!
|
||||||
p.write(config_content)!
|
p.write(json2.encode_pretty(json2.Any(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
|
||||||
|
)!
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user