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:
@@ -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
360
lib/core/herocmds/pods.v
Normal 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
138
lib/virt/heropods/CLI.md
Normal 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
|
||||||
Reference in New Issue
Block a user