Phase 2: image register --kernel-ref resolves through the catalog
`banger image register --kernel-ref <name>` now substitutes for the --kernel/--initrd/--modules triple. The daemon looks the name up via kernelcat.ReadLocal under d.layout.KernelsDir, populates the three paths from the resolved entry, then continues through the existing validate/persist flow unchanged. Passing both --kernel-ref and any of --kernel/--initrd/--modules is rejected — at the CLI layer (before starting the daemon) and defensively at the RPC layer. A missing catalog entry produces a clear "run 'banger kernel list'" message. Once registered, the image stores the resolved absolute paths, so deleting the catalog entry later does not invalidate already-registered images — managed image build still copies the kernel into its artifact dir per imagemgr.StageBootArtifacts. Tests cover: resolution success (absolute KernelPath populated from catalog), mutual-exclusion rejection, and missing-entry error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
83cc3aee15
commit
48e3a938cf
4 changed files with 98 additions and 4 deletions
|
|
@ -259,6 +259,7 @@ type ImageRegisterParams struct {
|
|||
KernelPath string `json:"kernel_path,omitempty"`
|
||||
InitrdPath string `json:"initrd_path,omitempty"`
|
||||
ModulesDir string `json:"modules_dir,omitempty"`
|
||||
KernelRef string `json:"kernel_ref,omitempty"`
|
||||
Docker bool `json:"docker,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1474,8 +1474,11 @@ func newImageRegisterCommand() *cobra.Command {
|
|||
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>]"),
|
||||
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
|
||||
}
|
||||
|
|
@ -1499,6 +1502,7 @@ func newImageRegisterCommand() *cobra.Command {
|
|||
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")
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"banger/internal/api"
|
||||
"banger/internal/daemon/imagemgr"
|
||||
"banger/internal/imagepreset"
|
||||
"banger/internal/kernelcat"
|
||||
"banger/internal/model"
|
||||
"banger/internal/system"
|
||||
)
|
||||
|
|
@ -179,11 +180,29 @@ func (d *Daemon) RegisterImage(ctx context.Context, params api.ImageRegisterPara
|
|||
}
|
||||
}
|
||||
kernelPath := strings.TrimSpace(params.KernelPath)
|
||||
if kernelPath == "" {
|
||||
return model.Image{}, fmt.Errorf("kernel path is required")
|
||||
}
|
||||
initrdPath := strings.TrimSpace(params.InitrdPath)
|
||||
modulesDir := strings.TrimSpace(params.ModulesDir)
|
||||
kernelRef := strings.TrimSpace(params.KernelRef)
|
||||
|
||||
if kernelRef != "" {
|
||||
if kernelPath != "" || initrdPath != "" || modulesDir != "" {
|
||||
return model.Image{}, fmt.Errorf("--kernel-ref is mutually exclusive with --kernel/--initrd/--modules")
|
||||
}
|
||||
entry, err := kernelcat.ReadLocal(d.layout.KernelsDir, kernelRef)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return model.Image{}, fmt.Errorf("kernel %q not found in catalog; run 'banger kernel list' to see available entries", kernelRef)
|
||||
}
|
||||
return model.Image{}, fmt.Errorf("resolve kernel %q: %w", kernelRef, err)
|
||||
}
|
||||
kernelPath = entry.KernelPath
|
||||
initrdPath = entry.InitrdPath
|
||||
modulesDir = entry.ModulesDir
|
||||
}
|
||||
|
||||
if kernelPath == "" {
|
||||
return model.Image{}, fmt.Errorf("kernel path is required (pass --kernel <path> or --kernel-ref <name>)")
|
||||
}
|
||||
|
||||
if err := imagemgr.ValidateRegisterPaths(rootfsPath, workSeedPath, kernelPath, initrdPath, modulesDir); err != nil {
|
||||
return model.Image{}, err
|
||||
|
|
|
|||
|
|
@ -97,3 +97,73 @@ func TestKernelDeleteRejectsInvalidName(t *testing.T) {
|
|||
t.Fatalf("KernelDelete should reject traversal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterImageResolvesKernelRef(t *testing.T) {
|
||||
kernelsDir := t.TempDir()
|
||||
seedKernelEntry(t, kernelsDir, "void-6.12")
|
||||
|
||||
rootfs := filepath.Join(t.TempDir(), "rootfs.ext4")
|
||||
if err := os.WriteFile(rootfs, []byte("rootfs"), 0o644); err != nil {
|
||||
t.Fatalf("write rootfs: %v", err)
|
||||
}
|
||||
|
||||
d := &Daemon{
|
||||
layout: paths.Layout{KernelsDir: kernelsDir},
|
||||
store: openDaemonStore(t),
|
||||
}
|
||||
|
||||
image, err := d.RegisterImage(context.Background(), api.ImageRegisterParams{
|
||||
Name: "testbox",
|
||||
RootfsPath: rootfs,
|
||||
KernelRef: "void-6.12",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("RegisterImage: %v", err)
|
||||
}
|
||||
want := filepath.Join(kernelsDir, "void-6.12", "vmlinux")
|
||||
if image.KernelPath != want {
|
||||
t.Fatalf("image.KernelPath = %q, want %q", image.KernelPath, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterImageRejectsKernelRefAndPath(t *testing.T) {
|
||||
kernelsDir := t.TempDir()
|
||||
seedKernelEntry(t, kernelsDir, "void-6.12")
|
||||
rootfs := filepath.Join(t.TempDir(), "rootfs.ext4")
|
||||
if err := os.WriteFile(rootfs, []byte("rootfs"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
d := &Daemon{
|
||||
layout: paths.Layout{KernelsDir: kernelsDir},
|
||||
store: openDaemonStore(t),
|
||||
}
|
||||
_, err := d.RegisterImage(context.Background(), api.ImageRegisterParams{
|
||||
Name: "testbox",
|
||||
RootfsPath: rootfs,
|
||||
KernelRef: "void-6.12",
|
||||
KernelPath: "/some/other/vmlinux",
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "mutually exclusive") {
|
||||
t.Fatalf("RegisterImage kernel-ref+kernel: err=%v, want mutually-exclusive error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterImageMissingKernelRef(t *testing.T) {
|
||||
rootfs := filepath.Join(t.TempDir(), "rootfs.ext4")
|
||||
if err := os.WriteFile(rootfs, []byte("rootfs"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
d := &Daemon{
|
||||
layout: paths.Layout{KernelsDir: t.TempDir()},
|
||||
store: openDaemonStore(t),
|
||||
}
|
||||
_, err := d.RegisterImage(context.Background(), api.ImageRegisterParams{
|
||||
Name: "testbox",
|
||||
RootfsPath: rootfs,
|
||||
KernelRef: "never-imported",
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "not found in catalog") {
|
||||
t.Fatalf("RegisterImage missing kernel-ref: err=%v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue