feat: Add Buildah builder API and refactor module

- Introduce `Builder` struct for image creation
- Implement `PodmanFactory` methods for builder lifecycle
- Rename `herocontainers` module to `podman`
- Update `PodmanFactory.new` with platform checks
- Revise documentation for `podman` and Buildah usage
This commit is contained in:
Mahmoud-Emad
2025-08-26 20:28:38 +03:00
parent ae5ab3133f
commit 1228441fd6
9 changed files with 294 additions and 52 deletions

View File

@@ -23,7 +23,6 @@ develop-eggs/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/

217
lib/virt/podman/builder.v Normal file
View File

@@ -0,0 +1,217 @@
module podman
import time
import freeflowuniverse.herolib.osal.core as osal { exec }
import freeflowuniverse.herolib.ui.console
import json
@[heap]
pub struct Builder {
pub mut:
id string
containername string
imageid string
imagename string
created time.Time
engine &PodmanFactory @[skip; str: skip]
}
pub struct BuilderInfo {
pub:
id string
containername string
imageid string
imagename string
created string
}
// load all buildah containers/builders
pub fn (mut self PodmanFactory) builders_load() ! {
self.builders = []Builder{}
cmd := 'buildah containers --json'
out := osal.execute_silent(cmd) or {
// If buildah is not installed or no containers, return empty list
console.print_debug('buildah containers command failed: ${err}')
return
}
if out.trim_space() == '' || out.trim_space() == '[]' {
return
}
mut r := json.decode([]BuilderInfo, out) or {
console.print_debug('Failed to decode buildah JSON: ${err}')
return
}
for item in r {
mut builder := Builder{
engine: &self
id: item.id
containername: item.containername
imageid: item.imageid
imagename: item.imagename
}
// Parse created time if needed
builder.created = time.now() // TODO: parse from item.created
self.builders << builder
}
}
// delete all buildah containers/builders
pub fn (mut self PodmanFactory) builders_delete_all() ! {
for mut builder in self.builders.clone() {
builder.delete()!
}
self.builders = []Builder{}
}
@[params]
pub struct BuilderNewArgs {
pub mut:
name string = 'default'
from string = 'docker.io/ubuntu:latest'
delete bool = true
}
pub fn (mut e PodmanFactory) builder_new(args_ BuilderNewArgs) !Builder {
mut args := args_
if args.delete {
e.builder_delete(args.name)!
}
exec(cmd: 'buildah --name ${args.name} from ${args.from}')!
e.builders_load()!
return e.builder_get(args.name)!
}
// get buildah containers
pub fn (mut e PodmanFactory) builders_get() ![]Builder {
if e.builders.len == 0 {
e.builders_load()!
}
return e.builders
}
pub fn (mut e PodmanFactory) builder_exists(name string) !bool {
r := e.builders_get()!
res := r.filter(it.containername == name)
if res.len == 1 {
return true
}
if res.len > 1 {
panic('bug: multiple builders with same name')
}
return false
}
pub fn (mut e PodmanFactory) builder_get(name string) !Builder {
r := e.builders_get()!
res := r.filter(it.containername == name)
if res.len == 0 {
return error('builder with name ${name} not found')
}
if res.len > 1 {
return error('multiple builders found with name ${name}')
}
return res[0]
}
pub fn (mut e PodmanFactory) builder_delete(name string) ! {
if e.builder_exists(name)! {
exec(cmd: 'buildah rm ${name}', stdout: false) or {
console.print_debug('Failed to delete builder ${name}: ${err}')
}
}
e.builders_load()!
}
// Builder methods
pub fn (mut self Builder) delete() ! {
self.engine.builder_delete(self.containername)!
}
pub fn (mut self Builder) run(cmd string) ! {
cmd_str := 'buildah run ${self.id} ${cmd}'
exec(cmd: cmd_str)!
}
pub fn (mut self Builder) copy(src string, dest string) ! {
cmd := 'buildah copy ${self.id} ${src} ${dest}'
exec(cmd: cmd, stdout: false)!
}
pub fn (mut self Builder) shell() ! {
cmd := 'buildah run --terminal --env TERM=xterm ${self.id} /bin/bash'
osal.execute_interactive(cmd)!
}
pub fn (mut self Builder) commit(image_name string) ! {
cmd := 'buildah commit ${self.containername} ${image_name}'
exec(cmd: cmd)!
}
pub fn (self Builder) set_entrypoint(entrypoint string) ! {
cmd := 'buildah config --entrypoint \'${entrypoint}\' ${self.containername}'
exec(cmd: cmd)!
}
pub fn (self Builder) set_workingdir(workdir string) ! {
cmd := 'buildah config --workingdir ${workdir} ${self.containername}'
exec(cmd: cmd)!
}
pub fn (self Builder) set_cmd(command string) ! {
cmd := 'buildah config --cmd ${command} ${self.containername}'
exec(cmd: cmd)!
}
// mount the build container to a path and return the path where its mounted
pub fn (mut self Builder) mount_to_path() !string {
cmd := 'buildah mount ${self.containername}'
out := osal.execute_silent(cmd)!
return out.trim_space()
}
// Builder solution methods for common setups
@[params]
pub struct GetArgs {
pub mut:
reset bool
}
// builder machine based on arch and install vlang
pub fn (mut e PodmanFactory) builder_base(args GetArgs) !Builder {
name := 'base'
if !args.reset && e.builder_exists(name)! {
return e.builder_get(name)!
}
console.print_header('buildah base build')
mut builder := e.builder_new(name: name, from: 'scratch', delete: true)!
mount_path := builder.mount_to_path()!
if mount_path.len < 4 {
return error('mount_path needs to be +4 chars')
}
// TODO: Add base system setup here
console.print_header('buildah base build done')
return builder
}
// builder with hero tools
pub fn (mut e PodmanFactory) builder_hero(args GetArgs) !Builder {
name := 'hero'
if !args.reset && e.builder_exists(name)! {
return e.builder_get(name)!
}
console.print_header('buildah hero build')
mut builder := e.builder_new(name: name, from: 'docker.io/ubuntu:latest', delete: true)!
// Install basic tools and hero
builder.run('apt-get update && apt-get install -y curl git build-essential')!
// TODO: Add hero installation steps
console.print_header('buildah hero build done')
return builder
}

View File

@@ -1,4 +1,4 @@
module herocontainers
module podman
import time
import freeflowuniverse.herolib.osal.core as osal { exec }
@@ -22,7 +22,7 @@ pub mut:
networks []string
labels map[string]string @[str: skip]
image &Image @[str: skip]
engine &PodmanFactory @[skip; str: skip]
engine &PodmanFactory @[skip; str: skip]
status utils.ContainerStatus
memsize int // in MB
command string

View File

@@ -1,11 +1,9 @@
module herocontainers
module podman
import time
import freeflowuniverse.herolib.osal.core as osal { exec }
import freeflowuniverse.herolib.data.ipaddress
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.virt.utils
import freeflowuniverse.herolib.ui.console
// info see https://docs.podman.io/en/latest/markdown/podman-run.1.html

View File

@@ -1,16 +1,18 @@
module herocontainers
module podman
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
@[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
buildpath string
images []Image
containers []Container
builders []Builder
buildpath string
// cache bool = true
// push bool
// platform []BuildPlatformType // used to build
@@ -31,12 +33,32 @@ pub mut:
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()!
}
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 == '' {
@@ -66,7 +88,8 @@ pub fn (mut e PodmanFactory) reset_all() ! {
exec(cmd: 'podman rmi -a -f', stdout: false)!
e.builders_delete_all()!
osal.done_reset()!
if core.platform()! == core.PlatformType.arch {
// Only check systemctl on Linux
if core.is_linux()! && core.platform()! == core.PlatformType.arch {
exec(cmd: 'systemctl status podman.socket', stdout: false)!
}
e.load()!

View File

@@ -1,4 +1,4 @@
module herocontainers
module podman
import freeflowuniverse.herolib.osal.core as osal { exec }
import time

View File

@@ -1,4 +1,4 @@
module herocontainers
module podman
import freeflowuniverse.herolib.osal.core as osal { exec }
// import freeflowuniverse.herolib.data.ipaddress { IPAddress }
@@ -8,7 +8,7 @@ import freeflowuniverse.herolib.virt.utils
// load all containers, they can be consulted in self.containers
// see obj: Container as result in self.containers
fn (mut self PodmanFactory) containers_load() ! {
pub fn (mut self PodmanFactory) containers_load() ! {
self.containers = []Container{}
mut ljob := exec(
// we used || because sometimes the command has | in it and this will ruin all subsequent columns

View File

@@ -1,11 +1,11 @@
module herocontainers
module podman
import freeflowuniverse.herolib.virt.utils
import freeflowuniverse.herolib.osal.core as osal { exec }
import time
import freeflowuniverse.herolib.ui.console
fn (mut self PodmanFactory) images_load() ! {
pub fn (mut self PodmanFactory) images_load() ! {
self.images = []Image{}
mut lines := osal.execute_silent("podman images --format '{{.ID}}||{{.Id}}||{{.Repository}}||{{.Tag}}||{{.Digest}}||{{.Size}}||{{.CreatedAt}}'")!
for line in lines.split_into_lines() {

View File

@@ -1,32 +1,37 @@
# Herocontainers
# Podman Module
Tools to work with containers
Tools to work with containers using Podman and Buildah.
```go
#!/usr/bin/env -S v -n -cg -w -enable-globals run
## Platform Support
import freeflowuniverse.herolib.virt.herocontainers
- **Linux**: Full support
- **macOS**: Full support (requires podman installation)
- **Windows**: Not supported
## Basic Usage
```v
#!/usr/bin/env -S v -n -w -enable-globals run
import freeflowuniverse.herolib.virt.podman
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.builder
//interative means will ask for login/passwd
console.print_header("PODMAN Demo")
console.print_header("BUILDAH Demo.")
// Create a new podman factory
// install: true will install podman if not present
// herocompile: true will compile hero for use in containers
mut factory := podman.new(install: false, herocompile: false)!
//if herocompile on, then will forced compile hero, which might be needed in debug mode for hero
// to execute hero scripts inside build container
mut factory:=herocontainers.new(herocompile=true)!
//mut b:=factory.builder_new(name:"test")!
//create
factory.builderv_create()!
//get the container
//mut b2:=factory.builder_get("builderv")!
//b2.shell()!
// Create a new builder
mut builder := factory.builder_new(name: 'test', from: 'docker.io/ubuntu:latest')!
// Run commands in the builder
builder.run('apt-get update && apt-get install -y curl')!
// Open interactive shell
builder.shell()!
```
## buildah tricks
@@ -40,7 +45,6 @@ buildah images
result is something like
```bash
CONTAINER ID BUILDER IMAGE ID IMAGE NAME CONTAINER NAME
a9946633d4e7 * scratch base
@@ -66,25 +70,22 @@ apt install ncdu
ncdu
```
## create container
## Create Container
```go
import freeflowuniverse.herolib.virt.herocontainers
```v
import freeflowuniverse.herolib.virt.podman
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.builder
//interative means will ask for login/passwd
console.print_header("Create a container")
console.print_header("Get a container.")
mut factory := podman.new()!
mut e:=herocontainers.new()!
//info see https://docs.podman.io/en/latest/markdown/podman-run.1.html
mut c:=e.container_create(
// Create a container with advanced options
// See https://docs.podman.io/en/latest/markdown/podman-run.1.html
mut container := factory.container_create(
name: 'mycontainer'
image_repo: 'ubuntu'
image_tag: 'latest'
// Resource limits
memory: '1g'
cpus: 0.5
@@ -103,15 +104,19 @@ mut c:=e.container_create(
]
published_ports: [
'127.0.0.1:8080:80'
]
]
)!
// Start the container
container.start()!
// Execute commands in the container
container.execute('apt-get update', false)!
// Open interactive shell
container.shell()!
```
## future
should make this module compatible with https://github.com/containerd/nerdctl
should make this module compatible with <https://github.com/containerd/nerdctl>