Skip to content

Commit 1360aa9

Browse files
committed
style: full-repo SKILL.md audit closed-loop
Multi-round senior-review + /simplify pass on all 190 .go files: - hypervisor.BalloonSize(memoryBytes, windows) consolidates 5 sites (CH/FC args, CH config patch, FC start, debug printer); fixed bugs: FC start.go missed Windows gate, debug.go default was 50% not 25%, printCommonCHArgs emitted --balloon unconditionally - backend.SnapshotSequence: build the http.Client once and pass it via SnapshotSpec so HTTP keep-alive reuses the API socket connection across pause/capture/resume - firecracker.DevPath exported; cmd/vm/debug.go printFCDebug uses it to fix 'a'+nLayers overflow at >=26 layers - snapshot/localfile clone-MAC + delete-multiple bugs fixed; per-id atomic Delete preserves invariants under partial failure - cmd/core/helpers.go RouteRefs returns resolved full IDs so network-recovery / lifecycle batchers never re-resolve prefixes; resolveVMOwner guards (nil, nil) Inspect contract - log tags repo-wide: every log.WithFunc("pkg.funcName") now matches the enclosing func (cmd.images.Import, cmd.images.RM, oci.import*, oci.handleCachedLayer, cloudimg.import*, storage.json.withLocked) - Comment hygiene: godoc starts with the func/type name; drop walkthrough narratives, decorative // --- foo --- dividers (5 in impl + 62 in tests), history narration ("no longer", "previously"), WHAT-restate comments - Layout: utility funcs below struct methods (BalloonSize), private methods after public (resolveRecord), CH/FC functionality grouped contiguously, types at top of file (chDebugSpec, tarImportJob) - Quality: drop chDebugArgs (parameter sprawl with chDebugSpec), collapse selfHealBootFiles to take layerJob instead of 8 positional fields, drop redundant Seek-to-0 after os.Open, drop SA4010 dead append in test, drop dead IsTarFile / IsQcow2File exports - Error wrap: %w: %w multi-wrap at extend.go:231; bdfFromSysfsPath uses CutPrefix's bool make lint dual-platform 0 issues, go vet clean, staticcheck clean (except 1 intentional tar.TypeRegA //nolint), gofumpt/goimports silent, all tests pass.
1 parent 73e832f commit 1360aa9

73 files changed

Lines changed: 879 additions & 1198 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/core/helpers.go

Lines changed: 56 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,17 @@ import (
3232
"github.com/cocoonstack/cocoon/utils"
3333
)
3434

35+
var hypervisorFactories = []hypervisorFactory{
36+
{config.HypervisorCH, func(c *config.Config) (hypervisor.Hypervisor, error) { return cloudhypervisor.New(c) }},
37+
{config.HypervisorFirecracker, func(c *config.Config) (hypervisor.Hypervisor, error) { return firecracker.New(c) }},
38+
}
39+
3540
// hypervisorFactory keeps backend lookup and iteration order together.
3641
type hypervisorFactory struct {
3742
typ config.HypervisorType
3843
ctor func(*config.Config) (hypervisor.Hypervisor, error)
3944
}
4045

41-
var hypervisorFactories = []hypervisorFactory{
42-
{config.HypervisorCH, func(c *config.Config) (hypervisor.Hypervisor, error) { return cloudhypervisor.New(c) }},
43-
{config.HypervisorFirecracker, func(c *config.Config) (hypervisor.Hypervisor, error) { return firecracker.New(c) }},
44-
}
45-
4646
// BaseHandler provides shared config access for all command handlers.
4747
type BaseHandler struct {
4848
ConfProvider func() *config.Config
@@ -52,7 +52,6 @@ func NewBaseHandler(conf *config.Config) BaseHandler {
5252
return BaseHandler{ConfProvider: func() *config.Config { return conf }}
5353
}
5454

55-
// Init returns command context and validated config.
5655
func (h BaseHandler) Init(cmd *cobra.Command) (context.Context, *config.Config, error) {
5756
conf, err := h.Conf()
5857
if err != nil {
@@ -61,7 +60,6 @@ func (h BaseHandler) Init(cmd *cobra.Command) (context.Context, *config.Config,
6160
return CommandContext(cmd), conf, nil
6261
}
6362

64-
// Conf validates and returns the config.
6563
func (h BaseHandler) Conf() (*config.Config, error) {
6664
if h.ConfProvider == nil {
6765
return nil, fmt.Errorf("config provider is nil")
@@ -73,7 +71,7 @@ func (h BaseHandler) Conf() (*config.Config, error) {
7371
return conf, nil
7472
}
7573

76-
// CommandContext returns command context, fallback to Background.
74+
// CommandContext returns ctx from cmd or Background as fallback.
7775
func CommandContext(cmd *cobra.Command) context.Context {
7876
if cmd != nil && cmd.Context() != nil {
7977
return cmd.Context()
@@ -142,7 +140,8 @@ func FindHypervisor(ctx context.Context, conf *config.Config, ref string) (hyper
142140
if err != nil {
143141
return nil, err
144142
}
145-
return resolveVMOwner(ctx, hypers, ref)
143+
owner, _, err := resolveVMOwner(ctx, hypers, ref)
144+
return owner, err
146145
}
147146

148147
func ListAllVMs(ctx context.Context, hypers []hypervisor.Hypervisor) ([]*types.VM, error) {
@@ -157,14 +156,18 @@ func ListAllVMs(ctx context.Context, hypers []hypervisor.Hypervisor) ([]*types.V
157156
return all, nil
158157
}
159158

159+
// RouteRefs resolves each user ref (full ID, name, or ID prefix) to its
160+
// owning hypervisor and returns a map of owner → resolved full VM IDs.
161+
// Returning full IDs (not raw refs) ensures downstream code — including
162+
// network recovery and lifecycle batchers — never has to re-resolve prefixes.
160163
func RouteRefs(ctx context.Context, hypers []hypervisor.Hypervisor, refs []string) (map[hypervisor.Hypervisor][]string, error) {
161164
result := map[hypervisor.Hypervisor][]string{}
162165
for _, ref := range refs {
163-
owner, err := resolveVMOwner(ctx, hypers, ref)
166+
owner, vm, err := resolveVMOwner(ctx, hypers, ref)
164167
if err != nil {
165168
return nil, err
166169
}
167-
result[owner] = append(result[owner], ref)
170+
result[owner] = append(result[owner], vm.ID)
168171
}
169172
return result, nil
170173
}
@@ -219,13 +222,9 @@ func ResolveImage(ctx context.Context, backends []imagebackend.Images, vmCfg *ty
219222
return storageConfigs, bootCfg, nil
220223
}
221224

222-
// EnsureImage checks whether the exact base image (by digest) required by
223-
// vmCfg exists locally and pulls it if missing. Inspect uses ImageDigest
224-
// to match the exact version recorded at snapshot time; Pull uses Image
225-
// (tag/URL) as the registry reference, then verifies the pulled digest.
226-
//
227-
// For imported images (not pullable from a registry), a warning is logged
228-
// and the caller proceeds — VerifyBaseFiles will catch the actual error.
225+
// EnsureImage pulls the digest-pinned base image if missing locally; warns
226+
// (rather than fails) for imported images so VerifyBaseFiles surfaces the
227+
// real error.
229228
func EnsureImage(ctx context.Context, backends []imagebackend.Images, vmCfg *types.VMConfig) {
230229
if vmCfg.Image == "" || vmCfg.ImageType == "" {
231230
return
@@ -270,27 +269,41 @@ func EnsureImage(ctx context.Context, backends []imagebackend.Images, vmCfg *typ
270269
}
271270

272271
func ResolveImageOwner(ctx context.Context, backends []imagebackend.Images, ref string) (imagebackend.Images, error) {
273-
var matches []imagebackend.Images
274-
for _, b := range backends {
272+
return resolveOwner(backends, ref, func(b imagebackend.Images) (bool, error) {
275273
img, err := b.Inspect(ctx, ref)
274+
return img != nil, err
275+
},
276+
fmt.Errorf("image %q: not found in any backend", ref),
277+
fmt.Errorf("image %s: %w", ref, imagebackend.ErrAmbiguous),
278+
)
279+
}
280+
281+
// resolveOwner returns the unique backend among backends for which found
282+
// reports true. notFound is returned when zero match; ambiguous wraps the
283+
// caller-supplied error and lists the matched backend types.
284+
func resolveOwner[T interface{ Type() string }](backends []T, ref string, found func(T) (bool, error), notFound, ambiguous error) (T, error) {
285+
var matches []T
286+
var zero T
287+
for _, b := range backends {
288+
ok, err := found(b)
276289
if err != nil {
277-
return nil, fmt.Errorf("inspect %s in %s: %w", ref, b.Type(), err)
290+
return zero, fmt.Errorf("inspect %s in %s: %w", ref, b.Type(), err)
278291
}
279-
if img != nil {
292+
if ok {
280293
matches = append(matches, b)
281294
}
282295
}
283296
switch len(matches) {
284297
case 0:
285-
return nil, fmt.Errorf("image %q: not found in any backend", ref)
298+
return zero, notFound
286299
case 1:
287300
return matches[0], nil
288301
default:
289302
names := make([]string, len(matches))
290303
for i, b := range matches {
291304
names[i] = b.Type()
292305
}
293-
return nil, fmt.Errorf("image %s: %w (backends: %s)", ref, imagebackend.ErrAmbiguous, strings.Join(names, ", "))
306+
return zero, fmt.Errorf("%w (backends: %s)", ambiguous, strings.Join(names, ", "))
294307
}
295308
}
296309

@@ -502,31 +515,27 @@ func findHypervisorFactory(typ config.HypervisorType) func(*config.Config) (hype
502515
return nil
503516
}
504517

505-
func resolveVMOwner(ctx context.Context, hypers []hypervisor.Hypervisor, ref string) (hypervisor.Hypervisor, error) {
506-
var matches []hypervisor.Hypervisor
507-
for _, h := range hypers {
508-
_, resolveErr := h.Inspect(ctx, ref)
509-
if resolveErr == nil {
510-
matches = append(matches, h)
511-
continue
518+
// resolveVMOwner returns the owning hypervisor and the resolved *types.VM.
519+
// Reusing the Inspect we already pay for fixes prefix-resolution downstream:
520+
// callers can use vm.ID instead of the user's raw ref (which may be a prefix
521+
// or name).
522+
func resolveVMOwner(ctx context.Context, hypers []hypervisor.Hypervisor, ref string) (hypervisor.Hypervisor, *types.VM, error) {
523+
var resolved *types.VM
524+
owner, err := resolveOwner(hypers, ref, func(h hypervisor.Hypervisor) (bool, error) {
525+
vm, err := h.Inspect(ctx, ref)
526+
if err == nil && vm != nil {
527+
resolved = vm
528+
return true, nil
512529
}
513-
if errors.Is(resolveErr, hypervisor.ErrNotFound) {
514-
continue
530+
if err != nil && !errors.Is(err, hypervisor.ErrNotFound) {
531+
return false, err
515532
}
516-
return nil, fmt.Errorf("inspect %s in %s: %w", ref, h.Type(), resolveErr)
517-
}
518-
switch len(matches) {
519-
case 0:
520-
return nil, fmt.Errorf("vm %s: %w", ref, hypervisor.ErrNotFound)
521-
case 1:
522-
return matches[0], nil
523-
default:
524-
names := make([]string, len(matches))
525-
for i, h := range matches {
526-
names[i] = h.Type()
527-
}
528-
return nil, fmt.Errorf("vm %s: %w (backends: %s)", ref, hypervisor.ErrAmbiguous, strings.Join(names, ", "))
529-
}
533+
return false, nil
534+
},
535+
fmt.Errorf("vm %s: %w", ref, hypervisor.ErrNotFound),
536+
fmt.Errorf("vm %s: %w", ref, hypervisor.ErrAmbiguous),
537+
)
538+
return owner, resolved, err
530539
}
531540

532541
// sanitizeVMName derives a safe VM name from an image reference.

cmd/images/import.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"bytes"
77
"compress/gzip"
88
"context"
9+
"errors"
910
"fmt"
1011
"io"
1112
"os"
@@ -26,7 +27,7 @@ func (h Handler) Import(cmd *cobra.Command, args []string) error {
2627
if err != nil {
2728
return err
2829
}
29-
logger := log.WithFunc("cmd.image.import")
30+
logger := log.WithFunc("cmd.images.Import")
3031

3132
name := args[0]
3233
files := args[1:]
@@ -40,7 +41,7 @@ func (h Handler) Import(cmd *cobra.Command, args []string) error {
4041
}
4142

4243
func (h Handler) importLocalFiles(ctx context.Context, conf *config.Config, name string, files ...string) error {
43-
logger := log.WithFunc("cmd.image.import")
44+
logger := log.WithFunc("cmd.images.importLocalFiles")
4445

4546
plan, err := planLocalImport(files)
4647
if err != nil {
@@ -76,9 +77,6 @@ func (h Handler) importLocalStream(ctx context.Context, conf *config.Config, nam
7677
}
7778
defer f.Close() //nolint:errcheck
7879

79-
if _, err := f.Seek(0, io.SeekStart); err != nil {
80-
return fmt.Errorf("seek %s: %w", filePath, err)
81-
}
8280
logger := log.WithFunc("cmd.images.importLocalStream")
8381
logger.Infof(ctx, "importing from %s ...", filePath)
8482
return h.importFromReader(ctx, conf, name, f)
@@ -224,7 +222,7 @@ func detectLocalImportSource(filePath string) (importSourceKind, error) {
224222

225223
var magic [4]byte
226224
n, readErr := io.ReadFull(f, magic[:])
227-
if readErr != nil && readErr != io.EOF && readErr != io.ErrUnexpectedEOF {
225+
if readErr != nil && !errors.Is(readErr, io.EOF) && !errors.Is(readErr, io.ErrUnexpectedEOF) {
228226
return 0, fmt.Errorf("peek %s: %w", filePath, readErr)
229227
}
230228

cmd/images/manage.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func (h Handler) RM(cmd *cobra.Command, args []string) error {
5656
if err != nil {
5757
return err
5858
}
59-
logger := log.WithFunc("cmd.image.rm")
59+
logger := log.WithFunc("cmd.images.RM")
6060
backends, err := cmdcore.InitImageBackends(ctx, conf)
6161
if err != nil {
6262
return err

cmd/root.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ var (
8282
}()
8383
)
8484

85-
// Execute is the main entry point called from main.go.
8685
func Execute(ctx context.Context) error {
8786
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
8887
defer cancel()

cmd/vm/attach.go

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package vm
22

33
import (
4+
"context"
45
"errors"
56
"fmt"
67

@@ -14,18 +15,10 @@ import (
1415
)
1516

1617
func (h Handler) FsAttach(cmd *cobra.Command, args []string) error {
17-
ctx, conf, err := h.Init(cmd)
18+
ctx, a, err := resolveAttacher[fs.Attacher](h, cmd, args, "fs attach", fs.ErrUnsupportedBackend)
1819
if err != nil {
1920
return err
2021
}
21-
hyper, err := cmdcore.FindHypervisor(ctx, conf, args[0])
22-
if err != nil {
23-
return fmt.Errorf("fs attach: %w", err)
24-
}
25-
a, ok := hyper.(fs.Attacher)
26-
if !ok {
27-
return fmt.Errorf("fs attach: backend %s: %w", hyper.Type(), fs.ErrUnsupportedBackend)
28-
}
2922
socket, _ := cmd.Flags().GetString("socket")
3023
tag, _ := cmd.Flags().GetString("tag")
3124
numQ, _ := cmd.Flags().GetInt("num-queues")
@@ -42,18 +35,10 @@ func (h Handler) FsAttach(cmd *cobra.Command, args []string) error {
4235
}
4336

4437
func (h Handler) FsDetach(cmd *cobra.Command, args []string) error {
45-
ctx, conf, err := h.Init(cmd)
38+
ctx, a, err := resolveAttacher[fs.Attacher](h, cmd, args, "fs detach", fs.ErrUnsupportedBackend)
4639
if err != nil {
4740
return err
4841
}
49-
hyper, err := cmdcore.FindHypervisor(ctx, conf, args[0])
50-
if err != nil {
51-
return fmt.Errorf("fs detach: %w", err)
52-
}
53-
a, ok := hyper.(fs.Attacher)
54-
if !ok {
55-
return fmt.Errorf("fs detach: backend %s: %w", hyper.Type(), fs.ErrUnsupportedBackend)
56-
}
5742
tag, _ := cmd.Flags().GetString("tag")
5843
if err := a.FsDetach(ctx, args[0], tag); err != nil {
5944
return classifyAttachErr(err)
@@ -66,18 +51,10 @@ func (h Handler) FsDetach(cmd *cobra.Command, args []string) error {
6651
}
6752

6853
func (h Handler) DeviceAttach(cmd *cobra.Command, args []string) error {
69-
ctx, conf, err := h.Init(cmd)
54+
ctx, a, err := resolveAttacher[vfio.Attacher](h, cmd, args, "device attach", vfio.ErrUnsupportedBackend)
7055
if err != nil {
7156
return err
7257
}
73-
hyper, err := cmdcore.FindHypervisor(ctx, conf, args[0])
74-
if err != nil {
75-
return fmt.Errorf("device attach: %w", err)
76-
}
77-
a, ok := hyper.(vfio.Attacher)
78-
if !ok {
79-
return fmt.Errorf("device attach: backend %s: %w", hyper.Type(), vfio.ErrUnsupportedBackend)
80-
}
8158
pci, _ := cmd.Flags().GetString("pci")
8259
id, _ := cmd.Flags().GetString("id")
8360
deviceID, err := a.DeviceAttach(ctx, args[0], vfio.Spec{PCI: pci, ID: id})
@@ -92,18 +69,10 @@ func (h Handler) DeviceAttach(cmd *cobra.Command, args []string) error {
9269
}
9370

9471
func (h Handler) DeviceDetach(cmd *cobra.Command, args []string) error {
95-
ctx, conf, err := h.Init(cmd)
72+
ctx, a, err := resolveAttacher[vfio.Attacher](h, cmd, args, "device detach", vfio.ErrUnsupportedBackend)
9673
if err != nil {
9774
return err
9875
}
99-
hyper, err := cmdcore.FindHypervisor(ctx, conf, args[0])
100-
if err != nil {
101-
return fmt.Errorf("device detach: %w", err)
102-
}
103-
a, ok := hyper.(vfio.Attacher)
104-
if !ok {
105-
return fmt.Errorf("device detach: backend %s: %w", hyper.Type(), vfio.ErrUnsupportedBackend)
106-
}
10776
id, _ := cmd.Flags().GetString("id")
10877
if err := a.DeviceDetach(ctx, args[0], id); err != nil {
10978
return classifyAttachErr(err)
@@ -115,6 +84,26 @@ func (h Handler) DeviceDetach(cmd *cobra.Command, args []string) error {
11584
return nil
11685
}
11786

87+
// resolveAttacher resolves args[0] to a hypervisor implementing A
88+
// (fs.Attacher / vfio.Attacher). op ("fs attach", "device detach") prefixes
89+
// both error wraps so callers see the operation, not just the type-assert.
90+
func resolveAttacher[A any](h Handler, cmd *cobra.Command, args []string, op string, errUnsupported error) (context.Context, A, error) {
91+
var zero A
92+
ctx, conf, err := h.Init(cmd)
93+
if err != nil {
94+
return ctx, zero, err
95+
}
96+
hyper, err := cmdcore.FindHypervisor(ctx, conf, args[0])
97+
if err != nil {
98+
return ctx, zero, fmt.Errorf("%s: %w", op, err)
99+
}
100+
a, ok := hyper.(A)
101+
if !ok {
102+
return ctx, zero, fmt.Errorf("%s: backend %s: %w", op, hyper.Type(), errUnsupported)
103+
}
104+
return ctx, a, nil
105+
}
106+
118107
// classifyAttachErr surfaces ErrNotRunning more clearly than the generic wrap.
119108
func classifyAttachErr(err error) error {
120109
if errors.Is(err, hypervisor.ErrNotRunning) {

0 commit comments

Comments
 (0)