Files
herolib/lib/virt/podman/factory.v
Mahmoud-Emad 9d79b6f2e2 feat: introduce consolidated Podman module with dual APIs
- Restructure Podman module into sub-files
- Introduce Simple API for quick Podman operations
- Add Podman machine management (init, start, list)
- Enhance Buildah integration with structured errors
- Define specific error types for Podman operations
- Update documentation and add comprehensive demo script
2025-08-27 13:10:53 +03:00

412 lines
11 KiB
V

module podman
import os
import freeflowuniverse.herolib.osal.core as osal { exec }
import freeflowuniverse.herolib.core
import freeflowuniverse.herolib.installers.virt.podman as podman_installer
import freeflowuniverse.herolib.installers.lang.herolib
import freeflowuniverse.herolib.ui.console
import json
import rand
@[heap]
pub struct PodmanFactory {
pub mut:
// sshkeys_allowed []string // all keys here have access over ssh into the machine, when ssh enabled
images []Image
containers []Container
builders []Builder
buildpath string
// cache bool = true
// push bool
// platform []BuildPlatformType // used to build
// registries []BAHRegistry // one or more supported BAHRegistries
prefix string
}
// BuildPlatformType represents different build platforms
pub enum BuildPlatformType {
linux_amd64
linux_arm64
darwin_amd64
darwin_arm64
}
// ContainerRuntimeConfig represents container runtime configuration
pub struct ContainerRuntimeConfig {
pub mut:
name string
image string
command []string
env map[string]string
ports []string
volumes []string
detach bool = true
remove bool
interactive bool
tty bool
working_dir string
entrypoint string
}
@[params]
pub struct NewArgs {
pub mut:
install bool = true
reset bool
herocompile bool
}
pub fn new(args_ NewArgs) !PodmanFactory {
mut args := args_
// Support both Linux and macOS
if !core.is_linux()! && !core.is_osx()! {
return error('only linux and macOS supported as host for now')
}
if args.install {
mut podman_installer0 := podman_installer.get()!
podman_installer0.install()!
}
// Ensure podman machine is available (macOS/Windows)
ensure_machine_available() or {
console.print_debug('Warning: Failed to ensure podman machine availability: ${err}')
console.print_debug('Continuing anyway - podman operations may fail if machine is not running')
}
if args.herocompile {
herolib.check()! // will check if install, if not will do
herolib.hero_compile(reset: true)!
}
mut factory := PodmanFactory{}
factory.init()!
if args.reset {
factory.reset_all()!
}
return factory
}
fn (mut e PodmanFactory) init() ! {
if e.buildpath == '' {
e.buildpath = '/tmp/builder'
exec(cmd: 'mkdir -p ${e.buildpath}', stdout: false)!
}
e.load()!
}
// reload the state from system
pub fn (mut e PodmanFactory) load() ! {
e.builders_load()!
e.images_load()!
e.containers_load()!
}
// reset all images & containers, CAREFUL!
pub fn (mut e PodmanFactory) reset_all() ! {
e.load()!
for mut container in e.containers.clone() {
container.delete()!
}
for mut image in e.images.clone() {
image.delete(true)!
}
exec(cmd: 'podman rm -a -f', stdout: false)!
exec(cmd: 'podman rmi -a -f', stdout: false)!
e.builders_delete_all()!
osal.done_reset()!
// Only check systemctl on Linux
if core.is_linux()! && core.platform()! == core.PlatformType.arch {
exec(cmd: 'systemctl status podman.socket', stdout: false)!
}
e.load()!
}
// Get free port - simple implementation
pub fn (mut e PodmanFactory) get_free_port() ?int {
// Simple implementation - return a random port in the range
// In a real implementation, you'd check for port availability
return 20000 + (rand.int() % 20000)
}
// create_from_buildah_image creates a podman container from a buildah image
pub fn (mut e PodmanFactory) create_from_buildah_image(image_name string, config ContainerRuntimeConfig) !string {
// Check if image exists in podman
image_exists := e.image_exists(repo: image_name) or { false }
if !image_exists {
// Try to transfer from buildah to podman
exec(cmd: 'buildah push ${image_name} containers-storage:${image_name}') or {
return new_image_error('create_from_buildah', image_name, 1, 'Failed to transfer image from buildah',
err.msg())
}
// Reload images after transfer
e.images_load()!
}
// Create container using the image
args := ContainerCreateArgs{
name: config.name
image_repo: image_name
command: config.command.join(' ')
env: config.env
forwarded_ports: config.ports
mounted_volumes: config.volumes
detach: config.detach
remove_when_done: config.remove
interactive: config.interactive
}
container := e.container_create(args)!
return container.id
}
// build_and_run_workflow performs a complete buildah build to podman run workflow
pub fn (mut e PodmanFactory) build_and_run_workflow(build_config ContainerRuntimeConfig, run_config ContainerRuntimeConfig, image_name string) !string {
// Simple implementation - just create a container from the image
// In a full implementation, this would coordinate with buildah
return e.create_from_buildah_image(image_name, run_config)
}
// Simple API functions (from client.v) - these use a default factory instance
// run_container runs a container with the specified image and options.
// Returns the container ID of the created container.
pub fn run_container(image string, options RunOptions) !string {
mut factory := new(install: false)!
// Convert RunOptions to ContainerCreateArgs
args := ContainerCreateArgs{
name: options.name
image_repo: image
command: options.command.join(' ')
env: options.env
forwarded_ports: options.ports
mounted_volumes: options.volumes
detach: options.detach
interactive: options.interactive
remove_when_done: options.remove
// Map other options as needed
}
container := factory.container_create(args)!
return container.id
}
// exec_podman executes a podman command with the given arguments
fn exec_podman(args []string) !string {
cmd := 'podman ' + args.join(' ')
result := exec(cmd: cmd, stdout: false)!
return result.output
}
// parse_json_output parses JSON output into the specified type
fn parse_json_output[T](output string) ![]T {
if output.trim_space() == '' {
return []T{}
}
return json.decode([]T, output)!
}
// list_containers lists running containers, or all containers if all=true.
pub fn list_containers(all bool) ![]PodmanContainer {
mut args := ['ps', '--format', 'json']
if all {
args << '--all'
}
output := exec_podman(args)!
return parse_json_output[PodmanContainer](output) or {
return new_container_error('list', 'containers', 1, err.msg(), err.msg())
}
}
// list_images lists all available images.
pub fn list_images() ![]PodmanImage {
output := exec_podman(['images', '--format', 'json'])!
return parse_json_output[PodmanImage](output) or {
return new_image_error('list', 'images', 1, err.msg(), err.msg())
}
}
// inspect_container returns detailed information about a container.
pub fn inspect_container(id string) !PodmanContainer {
output := exec_podman(['inspect', '--format', 'json', id])!
containers := parse_json_output[PodmanContainer](output) or {
return new_container_error('inspect', id, 1, err.msg(), err.msg())
}
if containers.len == 0 {
return new_container_error('inspect', id, 1, 'Container not found', 'Container ${id} not found')
}
return containers[0]
}
// stop_container stops a running container.
pub fn stop_container(id string) ! {
exec_podman(['stop', id]) or { return new_container_error('stop', id, 1, err.msg(), err.msg()) }
}
// remove_container removes a container.
// If force=true, the container will be forcefully removed even if running.
pub fn remove_container(id string, force bool) ! {
mut args := ['rm']
if force {
args << '-f'
}
args << id
exec_podman(args) or { return new_container_error('remove', id, 1, err.msg(), err.msg()) }
}
// remove_image removes an image by ID or name.
// If force=true, the image will be forcefully removed even if in use.
pub fn remove_image(id string, force bool) ! {
mut args := ['rmi']
if force {
args << '-f'
}
args << id
exec_podman(args) or { return new_image_error('remove', id, 1, err.msg(), err.msg()) }
}
// =============================================================================
// MACHINE MANAGEMENT (macOS/Windows support)
// =============================================================================
// Machine represents a podman machine (VM)
pub struct Machine {
pub:
name string
vm_type string
created string
last_up string
cpus string
memory string
disk string
running bool
}
// ensure_machine_available ensures a podman machine is available and running
// This is required on macOS and Windows where podman runs in a VM
pub fn ensure_machine_available() ! {
// Only needed on macOS and Windows
if os.user_os() !in ['macos', 'windows'] {
return
}
// Check if any machine exists
machines := list_machines() or { []Machine{} }
if machines.len == 0 {
console.print_debug('No podman machine found, initializing...')
machine_init() or { return error('Failed to initialize podman machine: ${err}') }
}
// Check if a machine is running
if !is_any_machine_running() {
console.print_debug('Starting podman machine...')
machine_start() or { return error('Failed to start podman machine: ${err}') }
}
}
// list_machines returns all available podman machines
pub fn list_machines() ![]Machine {
return parse_machine_list_text()!
}
// parse_machine_list_text parses text format output as fallback
fn parse_machine_list_text() ![]Machine {
job := exec(cmd: 'podman machine list', stdout: false) or {
return error('Failed to list podman machines: ${err}')
}
lines := job.output.split_into_lines()
if lines.len <= 1 {
return []Machine{} // No machines or only header
}
mut machines := []Machine{}
for i in 1 .. lines.len {
line := lines[i].trim_space()
if line == '' {
continue
}
fields := line.split_any(' \t').filter(it.trim_space() != '')
if fields.len >= 6 {
machine := Machine{
name: fields[0]
vm_type: fields[1]
created: fields[2]
last_up: fields[3]
cpus: fields[4]
memory: fields[5]
disk: if fields.len > 6 { fields[6] } else { '' }
running: line.contains('Currently running') || line.contains('Running')
}
machines << machine
}
}
return machines
}
// is_any_machine_running checks if any podman machine is currently running
pub fn is_any_machine_running() bool {
machines := list_machines() or { return false }
return machines.any(it.running)
}
// machine_init initializes a new podman machine with default settings
pub fn machine_init() ! {
machine_init_named('podman-machine-default')!
}
// machine_init_named initializes a new podman machine with specified name
pub fn machine_init_named(name string) ! {
console.print_debug('Initializing podman machine: ${name}')
exec(cmd: 'podman machine init ${name}', stdout: false) or {
return error('Failed to initialize podman machine: ${err}')
}
console.print_debug(' Podman machine initialized: ${name}')
}
// machine_start starts the default podman machine
pub fn machine_start() ! {
machine_start_named('')!
}
// machine_start_named starts a specific podman machine
pub fn machine_start_named(name string) ! {
mut cmd := 'podman machine start'
if name != '' {
cmd += ' ${name}'
}
console.print_debug('Starting podman machine...')
exec(cmd: cmd, stdout: false) or { return error('Failed to start podman machine: ${err}') }
console.print_debug(' Podman machine started')
}
// machine_stop stops the default podman machine
pub fn machine_stop() ! {
machine_stop_named('')!
}
// machine_stop_named stops a specific podman machine
pub fn machine_stop_named(name string) ! {
mut cmd := 'podman machine stop'
if name != '' {
cmd += ' ${name}'
}
exec(cmd: cmd, stdout: false) or { return error('Failed to stop podman machine: ${err}') }
}