Pure code motion — banger.go 3508→240 LOC, same-package decomposition keeps all identifiers visible without export changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
231 lines
8 KiB
Go
231 lines
8 KiB
Go
package cli
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"banger/internal/api"
|
|
"banger/internal/model"
|
|
"banger/internal/rpc"
|
|
"banger/internal/system"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func newImageCommand() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "image",
|
|
Short: "Manage images",
|
|
RunE: helpNoArgs,
|
|
}
|
|
cmd.AddCommand(
|
|
newImageRegisterCommand(),
|
|
newImagePullCommand(),
|
|
newImagePromoteCommand(),
|
|
newImageListCommand(),
|
|
newImageShowCommand(),
|
|
newImageDeleteCommand(),
|
|
)
|
|
return cmd
|
|
}
|
|
|
|
func newImageRegisterCommand() *cobra.Command {
|
|
var params api.ImageRegisterParams
|
|
cmd := &cobra.Command{
|
|
Use: "register",
|
|
Short: "Register or update an unmanaged image",
|
|
Args: noArgsUsage("usage: banger image register --name <name> --rootfs <path> [--work-seed <path>] (--kernel <path> [--initrd <path>] [--modules <dir>] | --kernel-ref <name>)"),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if strings.TrimSpace(params.KernelRef) != "" && (params.KernelPath != "" || params.InitrdPath != "" || params.ModulesDir != "") {
|
|
return errors.New("--kernel-ref is mutually exclusive with --kernel/--initrd/--modules")
|
|
}
|
|
if err := absolutizeImageRegisterPaths(¶ms); err != nil {
|
|
return err
|
|
}
|
|
if err := system.EnsureSudo(cmd.Context()); err != nil {
|
|
return err
|
|
}
|
|
layout, _, err := ensureDaemon(cmd.Context())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result, err := rpc.Call[api.ImageShowResult](cmd.Context(), layout.SocketPath, "image.register", params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return printImageSummary(cmd.OutOrStdout(), result.Image)
|
|
},
|
|
}
|
|
cmd.Flags().StringVar(¶ms.Name, "name", "", "image name")
|
|
cmd.Flags().StringVar(¶ms.RootfsPath, "rootfs", "", "rootfs path")
|
|
cmd.Flags().StringVar(¶ms.WorkSeedPath, "work-seed", "", "work-seed path")
|
|
cmd.Flags().StringVar(¶ms.KernelPath, "kernel", "", "kernel path")
|
|
cmd.Flags().StringVar(¶ms.InitrdPath, "initrd", "", "initrd path")
|
|
cmd.Flags().StringVar(¶ms.ModulesDir, "modules", "", "modules dir")
|
|
cmd.Flags().StringVar(¶ms.KernelRef, "kernel-ref", "", "name of a cataloged kernel (see 'banger kernel list')")
|
|
cmd.Flags().BoolVar(¶ms.Docker, "docker", false, "mark image as docker-prepared")
|
|
_ = cmd.RegisterFlagCompletionFunc("kernel-ref", completeKernelNames)
|
|
return cmd
|
|
}
|
|
|
|
func newImagePullCommand() *cobra.Command {
|
|
var (
|
|
params api.ImagePullParams
|
|
sizeRaw string
|
|
)
|
|
cmd := &cobra.Command{
|
|
Use: "pull <name-or-oci-ref>",
|
|
Short: "Pull an image bundle (catalog name) or OCI image and register it",
|
|
Long: strings.TrimSpace(`
|
|
Pull an image into banger. Two paths:
|
|
|
|
• Catalog name (e.g. 'debian-bookworm')
|
|
Fetches a pre-built bundle from the embedded imagecat catalog.
|
|
Kernel-ref comes from the catalog entry; --kernel-ref still
|
|
overrides.
|
|
|
|
• OCI reference (e.g. 'docker.io/library/debian:bookworm')
|
|
Pulls the image, flattens its layers, fixes ownership, injects
|
|
banger's guest agents. --kernel-ref or direct --kernel/--initrd/
|
|
--modules are required.
|
|
|
|
Use 'banger image catalog' to see available catalog names (once that
|
|
subcommand lands).
|
|
`),
|
|
Example: strings.TrimSpace(`
|
|
banger image pull debian-bookworm
|
|
banger image pull debian-bookworm --name sandbox
|
|
banger image pull docker.io/library/debian:bookworm --kernel-ref generic-6.12
|
|
`),
|
|
Args: exactArgsUsage(1, "usage: banger image pull <name-or-oci-ref> [--name <name>] [--kernel-ref <name>] [--kernel <path>] [--initrd <path>] [--modules <dir>] [--size <human>]"),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
params.Ref = args[0]
|
|
if strings.TrimSpace(params.KernelRef) != "" && (params.KernelPath != "" || params.InitrdPath != "" || params.ModulesDir != "") {
|
|
return errors.New("--kernel-ref is mutually exclusive with --kernel/--initrd/--modules")
|
|
}
|
|
if strings.TrimSpace(sizeRaw) != "" {
|
|
size, err := model.ParseSize(sizeRaw)
|
|
if err != nil {
|
|
return fmt.Errorf("--size: %w", err)
|
|
}
|
|
params.SizeBytes = size
|
|
}
|
|
if err := absolutizePaths(¶ms.KernelPath, ¶ms.InitrdPath, ¶ms.ModulesDir); err != nil {
|
|
return err
|
|
}
|
|
if err := system.EnsureSudo(cmd.Context()); err != nil {
|
|
return err
|
|
}
|
|
layout, _, err := ensureDaemon(cmd.Context())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var result api.ImageShowResult
|
|
err = withHeartbeat(cmd.ErrOrStderr(), "image pull", func() error {
|
|
var callErr error
|
|
result, callErr = rpc.Call[api.ImageShowResult](cmd.Context(), layout.SocketPath, "image.pull", params)
|
|
return callErr
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return printImageSummary(cmd.OutOrStdout(), result.Image)
|
|
},
|
|
}
|
|
cmd.Flags().StringVar(¶ms.Name, "name", "", "image name (defaults to the ref's repo+tag, sanitised)")
|
|
cmd.Flags().StringVar(¶ms.KernelPath, "kernel", "", "kernel path")
|
|
cmd.Flags().StringVar(¶ms.InitrdPath, "initrd", "", "initrd path")
|
|
cmd.Flags().StringVar(¶ms.ModulesDir, "modules", "", "modules dir")
|
|
cmd.Flags().StringVar(¶ms.KernelRef, "kernel-ref", "", "name of a cataloged kernel (see 'banger kernel list')")
|
|
cmd.Flags().StringVar(&sizeRaw, "size", "", "ext4 image size (e.g. 4GiB); defaults to content + 25%, min 1GiB")
|
|
_ = cmd.RegisterFlagCompletionFunc("kernel-ref", completeKernelNames)
|
|
return cmd
|
|
}
|
|
|
|
func newImagePromoteCommand() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "promote <id-or-name>",
|
|
Short: "Promote an unmanaged image to a managed artifact",
|
|
Args: exactArgsUsage(1, "usage: banger image promote <id-or-name>"),
|
|
ValidArgsFunction: completeImageNameOnlyAtPos0,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if err := system.EnsureSudo(cmd.Context()); err != nil {
|
|
return err
|
|
}
|
|
layout, _, err := ensureDaemon(cmd.Context())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result, err := rpc.Call[api.ImageShowResult](cmd.Context(), layout.SocketPath, "image.promote", api.ImageRefParams{IDOrName: args[0]})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return printImageSummary(cmd.OutOrStdout(), result.Image)
|
|
},
|
|
}
|
|
}
|
|
|
|
func newImageListCommand() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "list",
|
|
Aliases: []string{"ls"},
|
|
Short: "List images",
|
|
Args: noArgsUsage("usage: banger image list"),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
layout, _, err := ensureDaemon(cmd.Context())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result, err := rpc.Call[api.ImageListResult](cmd.Context(), layout.SocketPath, "image.list", api.Empty{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return printImageListTable(cmd.OutOrStdout(), result.Images)
|
|
},
|
|
}
|
|
}
|
|
|
|
func newImageShowCommand() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "show <id-or-name>",
|
|
Short: "Show image details",
|
|
Args: exactArgsUsage(1, "usage: banger image show <id-or-name>"),
|
|
ValidArgsFunction: completeImageNameOnlyAtPos0,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
layout, _, err := ensureDaemon(cmd.Context())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result, err := rpc.Call[api.ImageShowResult](cmd.Context(), layout.SocketPath, "image.show", api.ImageRefParams{IDOrName: args[0]})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return printJSON(cmd.OutOrStdout(), result.Image)
|
|
},
|
|
}
|
|
}
|
|
|
|
func newImageDeleteCommand() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "delete <id-or-name>",
|
|
Aliases: []string{"rm"},
|
|
Short: "Delete an image",
|
|
Args: exactArgsUsage(1, "usage: banger image delete <id-or-name>"),
|
|
ValidArgsFunction: completeImageNameOnlyAtPos0,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if err := system.EnsureSudo(cmd.Context()); err != nil {
|
|
return err
|
|
}
|
|
layout, _, err := ensureDaemon(cmd.Context())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result, err := rpc.Call[api.ImageShowResult](cmd.Context(), layout.SocketPath, "image.delete", api.ImageRefParams{IDOrName: args[0]})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return printImageSummary(cmd.OutOrStdout(), result.Image)
|
|
},
|
|
}
|
|
}
|