Phase 3: CLI banger image pull

newImagePullCommand mirrors newImageRegisterCommand with a positional
<oci-ref> arg, the same kernel-ref / direct-paths flag set + mutual
exclusion, plus --size that parses human-friendly values via
model.ParseSize before crossing the RPC boundary.

Calls "image.pull" RPC, prints the resulting image summary on success.
Long help warns about the Phase A bootability gap (ownership not
preserved; suitable as `image build` base, not yet directly bootable).

CLI test confirms image pull is registered with the expected flags.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Thales Maciel 2026-04-16 17:29:06 -03:00
parent a8c9983542
commit d5f72dfad9
No known key found for this signature in database
GPG key ID: 33112E6833C34679
2 changed files with 84 additions and 0 deletions

View file

@ -1427,6 +1427,7 @@ func newImageCommand() *cobra.Command {
cmd.AddCommand(
newImageBuildCommand(),
newImageRegisterCommand(),
newImagePullCommand(),
newImagePromoteCommand(),
newImageListCommand(),
newImageShowCommand(),
@ -1507,6 +1508,60 @@ func newImageRegisterCommand() *cobra.Command {
return cmd
}
func newImagePullCommand() *cobra.Command {
var (
params api.ImagePullParams
sizeRaw string
)
cmd := &cobra.Command{
Use: "pull <oci-ref>",
Short: "Pull an OCI image and register it as a managed banger image",
Long: "Download an OCI image (e.g. docker.io/library/debian:bookworm), " +
"flatten its layers into an ext4 rootfs, and register the result as a " +
"managed image. Kernel info is required (via --kernel-ref or direct paths). " +
"\n\nNote: Phase A primitive — file ownership in the produced ext4 reflects " +
"the runner's uid/gid, not the OCI tar headers, so the resulting image is " +
"suitable as a base for `image build` but is not directly bootable until a " +
"future ownership-fixup pass lands.",
Args: exactArgsUsage(1, "usage: banger image pull <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(&params.KernelPath, &params.InitrdPath, &params.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
}
result, err := rpc.Call[api.ImageShowResult](cmd.Context(), layout.SocketPath, "image.pull", params)
if err != nil {
return err
}
return printImageSummary(cmd.OutOrStdout(), result.Image)
},
}
cmd.Flags().StringVar(&params.Name, "name", "", "image name (defaults to the ref's repo+tag, sanitised)")
cmd.Flags().StringVar(&params.KernelPath, "kernel", "", "kernel path")
cmd.Flags().StringVar(&params.InitrdPath, "initrd", "", "initrd path")
cmd.Flags().StringVar(&params.ModulesDir, "modules", "", "modules dir")
cmd.Flags().StringVar(&params.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")
return cmd
}
func newImagePromoteCommand() *cobra.Command {
return &cobra.Command{
Use: "promote <id-or-name>",