From eca35ecb921660c6f0ed163f0141d171c2a467ff Mon Sep 17 00:00:00 2001 From: Adam Martin Date: Wed, 22 Apr 2026 12:57:51 -0400 Subject: [PATCH] adjust logging for extracting oci artifacts with cosign bits (#575) Signed-off-by: Adam Martin --- cmd/hauler/cli/store/extract.go | 15 ++++++ cmd/hauler/cli/store/extract_test.go | 74 ++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/cmd/hauler/cli/store/extract.go b/cmd/hauler/cli/store/extract.go index a1c79b4..c712e57 100644 --- a/cmd/hauler/cli/store/extract.go +++ b/cmd/hauler/cli/store/extract.go @@ -99,6 +99,21 @@ func ExtractCmd(ctx context.Context, o *flags.ExtractOpts, s *store.Layout, ref if !strings.Contains(reference, repo) { return nil } + + // Cosign sig/att/sbom/referrer descriptors are registry-only metadata — + // they are never extractable to disk. Skip them silently at debug level, + // mirroring the same guard in copy.go (directory-target path). + kind := desc.Annotations[consts.KindAnnotationName] + switch kind { + case consts.KindAnnotationSigs, consts.KindAnnotationAtts, consts.KindAnnotationSboms: + l.Debugf("skipping cosign artifact [%s] for extract", reference) + return nil + } + if strings.HasPrefix(kind, consts.KindAnnotationReferrers) { + l.Debugf("skipping OCI referrer [%s] for extract", reference) + return nil + } + found = true rc, err := s.Fetch(ctx, desc) diff --git a/cmd/hauler/cli/store/extract_test.go b/cmd/hauler/cli/store/extract_test.go index 6d951e0..498a190 100644 --- a/cmd/hauler/cli/store/extract_test.go +++ b/cmd/hauler/cli/store/extract_test.go @@ -1,6 +1,8 @@ package store import ( + "bytes" + "context" "os" "path/filepath" "strings" @@ -14,6 +16,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1/static" gvtypes "github.com/google/go-containerregistry/pkg/v1/types" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/rs/zerolog" "hauler.dev/go/hauler/internal/flags" v1 "hauler.dev/go/hauler/pkg/apis/hauler.cattle.io/v1" @@ -554,3 +557,74 @@ func TestExtractCmd_SubstringMatch(t *testing.T) { t.Errorf("content mismatch: got %q, want %q", string(data), fileContent) } } + +// newLogCaptureContext returns a context backed by a zerolog logger that writes +// JSON log lines to buf. Use buf.String() after the call to inspect log output. +func newLogCaptureContext(buf *bytes.Buffer) context.Context { + zl := zerolog.New(buf).With().Timestamp().Logger() + return zl.WithContext(context.Background()) +} + +func TestExtractCmd_CosignArtifactsProduceNoContainerImageWarning(t *testing.T) { + // Regression test: extracting a file ref whose store also contains cosign + // sig/att/sbom/referrer descriptors with the same repo prefix must not emit + // the "container images cannot be extracted" warning for those cosign descriptors. + // + // Before the fix, cosign manifests tripped isContainerImageManifest() and caused + // a misleading WRN line. The fix is a kind-annotation early-return in the Walk + // callback that mirrors the pattern in copy.go:49-58. + + var logBuf bytes.Buffer + ctx := newLogCaptureContext(&logBuf) + s := newTestStore(t) + + // Seed a real file artifact so ExtractCmd finds something to extract. + fileContent := "cosign-filter test file content" + url := seedFileInHTTPServer(t, "sigtest.txt", fileContent) + if err := storeFile(ctx, s, v1.File{Path: url}); err != nil { + t.Fatalf("storeFile: %v", err) + } + + // The file is stored under "hauler/sigtest.txt:latest". Inject fake cosign + // sig/att/sbom/referrer descriptors sharing the same AnnotationRefName so the + // Walk callback's strings.Contains(reference, repo) filter matches them too. + baseRef := "hauler/sigtest.txt:latest" + for _, kind := range []string{ + consts.KindAnnotationSigs, + consts.KindAnnotationAtts, + consts.KindAnnotationSboms, + consts.KindAnnotationReferrers + "/sha256" + strings.Repeat("a", 64), + } { + seedStoreDescriptor(t, s, map[string]string{ + ocispec.AnnotationRefName: baseRef, + consts.KindAnnotationName: kind, + consts.ContainerdImageNameKey: "registry.example.com/" + baseRef, + }) + } + + destDir := t.TempDir() + eo := &flags.ExtractOpts{ + StoreRootOpts: defaultRootOpts(s.Root), + DestinationDir: destDir, + } + + if err := ExtractCmd(ctx, eo, s, baseRef); err != nil { + t.Fatalf("ExtractCmd: %v", err) + } + + // The "container images cannot be extracted" warning must NOT appear for + // the cosign descriptors — they must be silently skipped at debug level. + if strings.Contains(logBuf.String(), "container images cannot be extracted") { + t.Errorf("unexpected warning in log output for cosign descriptors:\n%s", logBuf.String()) + } + + // The file artifact must still be extracted to the destination directory. + outPath := filepath.Join(destDir, "sigtest.txt") + data, err := os.ReadFile(outPath) + if err != nil { + t.Fatalf("expected extracted file at %s: %v", outPath, err) + } + if string(data) != fileContent { + t.Errorf("content mismatch: got %q, want %q", string(data), fileContent) + } +}