feat: Add pods command for container management

- Implement `hero pods` CLI command
- Add subcommands for ps, images, create, start, stop, rm, exec, inspect
- Add flags for container creation and removal
This commit is contained in:
Mahmoud-Emad
2025-11-18 16:09:20 +02:00
parent e7a38e555b
commit 11c3ea9ca5
3 changed files with 499 additions and 0 deletions

View File

@@ -91,6 +91,7 @@ fn do() ! {
herocmds.cmd_web(mut cmd) herocmds.cmd_web(mut cmd)
herocmds.cmd_sshagent(mut cmd) herocmds.cmd_sshagent(mut cmd)
herocmds.cmd_atlas(mut cmd) herocmds.cmd_atlas(mut cmd)
herocmds.cmd_pods(mut cmd)
cmd.setup() cmd.setup()
cmd.parse(os.args) cmd.parse(os.args)

360
lib/core/herocmds/pods.v Normal file
View File

@@ -0,0 +1,360 @@
module herocmds
import cli { Command, Flag }
import incubaid.herolib.virt.heropods
import incubaid.herolib.ui.console
pub fn cmd_pods(mut cmdroot Command) {
mut cmd_pods := Command{
name: 'pods'
description: 'Manage lightweight OCI containers with HeroPods'
usage: '
HeroPods Container Management
USAGE:
hero pods <subcommand> [options]
EXAMPLES:
hero pods ps # List all containers
hero pods images ls # List available images
hero pods create alpine_3_20 # Create container from image
hero pods start mycontainer # Start a container
hero pods stop mycontainer # Stop a container
hero pods rm mycontainer # Remove a container
hero pods exec mycontainer ls -la # Execute command in container
hero pods inspect mycontainer # Show container details
'
execute: cmd_pods_execute
sort_commands: true
}
// ps - list containers
mut cmd_ps := Command{
name: 'ps'
description: 'List all containers (running and stopped)'
execute: cmd_pods_execute
}
// images - image management
mut cmd_images := Command{
name: 'images'
description: 'Manage container images'
execute: cmd_pods_execute
}
mut cmd_images_ls := Command{
name: 'ls'
description: 'List all available images'
execute: cmd_pods_execute
}
cmd_images.add_command(cmd_images_ls)
// create - create container
mut cmd_create := Command{
name: 'create'
description: 'Create a new container from an image'
execute: cmd_pods_execute
}
cmd_create.add_flag(Flag{
flag: .string
name: 'name'
abbrev: 'n'
description: 'Container name (required)'
})
cmd_create.add_flag(Flag{
flag: .string
name: 'docker-url'
abbrev: 'd'
description: 'Docker image URL (e.g., docker.io/library/alpine:3.20)'
})
// start - start container
mut cmd_start := Command{
name: 'start'
description: 'Start a stopped container'
execute: cmd_pods_execute
}
// stop - stop container
mut cmd_stop := Command{
name: 'stop'
description: 'Stop a running container'
execute: cmd_pods_execute
}
// rm - remove container
mut cmd_rm := Command{
name: 'rm'
description: 'Remove/delete a container'
execute: cmd_pods_execute
}
cmd_rm.add_flag(Flag{
flag: .bool
name: 'force'
abbrev: 'f'
description: 'Force remove running container'
})
// exec - execute command
mut cmd_exec := Command{
name: 'exec'
description: 'Execute a command inside a running container'
execute: cmd_pods_execute
}
// inspect - show container details
mut cmd_inspect := Command{
name: 'inspect'
description: 'Show detailed information about a container'
execute: cmd_pods_execute
}
// Add all subcommands
cmd_pods.add_command(cmd_ps)
cmd_pods.add_command(cmd_images)
cmd_pods.add_command(cmd_create)
cmd_pods.add_command(cmd_start)
cmd_pods.add_command(cmd_stop)
cmd_pods.add_command(cmd_rm)
cmd_pods.add_command(cmd_exec)
cmd_pods.add_command(cmd_inspect)
cmdroot.add_command(cmd_pods)
}
fn cmd_pods_execute(cmd Command) ! {
// Get or create HeroPods instance
mut hp := heropods.get() or { heropods.new(reset: false, use_podman: true)! }
match cmd.name {
'ps' {
cmd_ps_execute(mut hp)!
}
'ls' {
// This is images ls
cmd_images_ls_execute(mut hp)!
}
'create' {
cmd_create_execute(mut hp, cmd)!
}
'start' {
cmd_start_execute(mut hp, cmd)!
}
'stop' {
cmd_stop_execute(mut hp, cmd)!
}
'rm' {
cmd_rm_execute(mut hp, cmd)!
}
'exec' {
cmd_exec_execute(mut hp, cmd)!
}
'inspect' {
cmd_inspect_execute(mut hp, cmd)!
}
else {
return error(cmd.help_message())
}
}
}
// List all containers
fn cmd_ps_execute(mut hp heropods.HeroPods) ! {
containers := hp.list()!
if containers.len == 0 {
console.print_header('No containers found')
return
}
// Print header
println('CONTAINER NAME STATUS PID')
println('─'.repeat(50))
// Print each container
for container in containers {
status := container.status() or { heropods.ContainerStatus.stopped }
pid := container.pid() or { 0 }
status_str := match status {
.running { 'running' }
.stopped { 'stopped' }
.paused { 'paused' }
.unknown { 'unknown' }
}
pid_str := if pid > 0 { pid.str() } else { '-' }
println('${container.name:-20} ${status_str:-11} ${pid_str}')
}
}
// List all images
fn cmd_images_ls_execute(mut hp heropods.HeroPods) ! {
images := hp.images_list()!
if images.len == 0 {
console.print_header('No images found')
return
}
// Print header
println('IMAGE NAME SIZE CREATED')
println('─'.repeat(60))
// Print each image
for image in images {
size_str := if image.size_mb > 0 {
'${image.size_mb:.1f}MB'
} else {
'-'
}
created_str := if image.created_at != '' {
image.created_at
} else {
'-'
}
println('${image.image_name:-20} ${size_str:-11} ${created_str}')
}
}
// Create a new container
fn cmd_create_execute(mut hp heropods.HeroPods, cmd Command) ! {
container_name := cmd.flags.get_string('name') or {
if cmd.args.len > 0 {
cmd.args[0]
} else {
return error('Container name is required. Use --name or provide as argument')
}
}
docker_url := cmd.flags.get_string('docker-url') or { '' }
image_name := if cmd.args.len > 1 { cmd.args[1] } else { container_name }
console.print_header('Creating container: ${container_name}')
hp.container_new(
name: container_name
image: .custom
custom_image_name: image_name
docker_url: docker_url
)!
console.print_green(' Container ${container_name} created successfully')
}
// Start a container
fn cmd_start_execute(mut hp heropods.HeroPods, cmd Command) ! {
if cmd.args.len == 0 {
return error('Container name is required')
}
container_name := cmd.args[0]
console.print_header('Starting container: ${container_name}')
mut container := hp.get(name: container_name)!
container.start()!
console.print_green(' Container ${container_name} started successfully')
}
// Stop a container
fn cmd_stop_execute(mut hp heropods.HeroPods, cmd Command) ! {
if cmd.args.len == 0 {
return error('Container name is required')
}
container_name := cmd.args[0]
console.print_header('Stopping container: ${container_name}')
mut container := hp.get(name: container_name)!
container.stop()!
console.print_green(' Container ${container_name} stopped successfully')
}
// Remove a container
fn cmd_rm_execute(mut hp heropods.HeroPods, cmd Command) ! {
if cmd.args.len == 0 {
return error('Container name is required')
}
container_name := cmd.args[0]
force := cmd.flags.get_bool('force') or { false }
console.print_header('Removing container: ${container_name}')
mut container := hp.get(name: container_name)!
// Stop if running and force flag is set
if force {
status := container.status() or { heropods.ContainerStatus.stopped }
if status == .running {
console.print_debug('Force stopping container...')
container.stop()!
}
}
container.delete()!
console.print_green(' Container ${container_name} removed successfully')
}
// Execute command in container
fn cmd_exec_execute(mut hp heropods.HeroPods, cmd Command) ! {
if cmd.args.len < 2 {
return error('Usage: hero pods exec <container_name> <command> [args...]')
}
container_name := cmd.args[0]
command := cmd.args[1..].join(' ')
mut container := hp.get(name: container_name)!
result := container.exec(cmd: command, stdout: true)!
println(result)
}
// Inspect container
fn cmd_inspect_execute(mut hp heropods.HeroPods, cmd Command) ! {
if cmd.args.len == 0 {
return error('Container name is required')
}
container_name := cmd.args[0]
mut container := hp.get(name: container_name)!
console.print_header('Container: ${container_name}')
// Get status
status := container.status() or { heropods.ContainerStatus.unknown }
println('Status: ${status}')
// Get PID if running
if status == .running {
pid := container.pid() or { 0 }
if pid > 0 {
println('PID: ${pid}')
// Show network namespace info
println('\nNetwork:')
println(' To access container network:')
println(' nsenter -t ${pid} -n <command>')
println(' To get shell in container:')
println(' nsenter -t ${pid} -n -m -p /bin/sh')
}
}
// Show crun command
println('\nManagement:')
crun_root := '${hp.base_dir}/runtime'
println(' Crun root: ${crun_root}')
println(' Execute command: crun --root ${crun_root} exec ${container_name} <command>')
println(' View state: crun --root ${crun_root} state ${container_name}')
}

138
lib/virt/heropods/CLI.md Normal file
View File

@@ -0,0 +1,138 @@
# HeroPods CLI
The `hero pods` command provides a Docker/Podman-like CLI interface for managing HeroPods containers.
## Installation
Build the hero CLI:
```bash
v -o ~/hero cli/hero.v
```
## Commands
### List Containers
```bash
hero pods ps
```
Lists all containers (running and stopped) with their status and PID.
### List Images
```bash
hero pods images ls
```
Lists all available container images with size and creation time.
### Create Container
```bash
hero pods create --name mycontainer alpine_3_20
# or with Docker URL
hero pods create --name mycontainer --docker-url docker.io/library/alpine:3.20
```
Creates a new container from an image.
### Start Container
```bash
hero pods start mycontainer
```
Starts a stopped container.
### Stop Container
```bash
hero pods stop mycontainer
```
Stops a running container.
### Remove Container
```bash
hero pods rm mycontainer
# Force remove running container
hero pods rm --force mycontainer
```
Removes/deletes a container.
### Execute Command
```bash
hero pods exec mycontainer ls -la
hero pods exec mycontainer /bin/sh
```
Executes a command inside a running container.
### Inspect Container
```bash
hero pods inspect mycontainer
```
Shows detailed information about a container including:
- Status (running/stopped)
- PID (if running)
- Network namespace access commands
- Crun management commands
## Example Workflow
```bash
# List available images
hero pods images ls
# Create a container
hero pods create --name test1 alpine_3_20
# Start the container
hero pods start test1
# Execute commands in the container
hero pods exec test1 ip addr show
hero pods exec test1 ping -c 2 8.8.8.8
# Inspect the container
hero pods inspect test1
# Stop the container
hero pods stop test1
# Remove the container
hero pods rm test1
```
## Manual Container Access
For manual testing and debugging, you can access containers directly using the information from `hero pods inspect`:
```bash
# Get container PID
hero pods inspect mycontainer
# Access container network namespace
nsenter -t <PID> -n ip addr show
# Get shell in container
nsenter -t <PID> -n -m -p /bin/sh
# Use crun directly
crun --root /Users/user/.heropods/default/runtime exec mycontainer /bin/sh
```
## Notes
- All containers are managed by the default HeroPods instance
- Container networking uses bridge mode with automatic IP allocation
- Images are pulled and extracted using Podman
- The OCI runtime used is crun