Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 27 additions & 10 deletions packages/shared/pkg/storage/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,18 +125,35 @@ func seekableObjectType(path string) (SeekableObjectType, CompressionType) {
}
}

// blobType derives the metric file_type from a blob's last path segment,
// stripping .header so read.blob shares read.read's file_type vocabulary.
// Blob file_type values. The read.blob* metrics cover whole-object (WriteTo)
// reads only — a small, fixed set. The seekable data files (memfile,
// rootfs.ext4) are NOT blobs: they are range-read and recorded under read.read
// with their own vocabulary, so they never appear here. Everything that isn't a
// known type collapses to "other": content-addressed cache blobs are keyed by
// hash, and letting those (or any per-build path) into the label is what blew
// file_type up to ~10^5 distinct values.
const (
blobTypeHeader = "header" // memfile/rootfs header sidecar (*.header)
blobTypeSnapfile = "snapfile" // VM snapshot file
blobTypeMetadata = "metadata" // metadata.json
blobTypeOther = "other" // anything else — never a raw hash/ID
)

// blobType classifies a whole-object blob read into the fixed file_type set
// above; anything unrecognized collapses to "other" to keep the label bounded.
func blobType(path string) string {
name := path
if i := strings.LastIndex(name, "/"); i >= 0 {
name = name[i+1:]
}
if base, ok := strings.CutSuffix(name, HeaderSuffix); ok {
return base
}
name := StripCompression(path[strings.LastIndex(path, "/")+1:])

return name
switch {
case strings.HasSuffix(name, HeaderSuffix):
return blobTypeHeader
case name == SnapfileName:
return blobTypeSnapfile
case name == MetadataName:
return blobTypeMetadata
default:
return blobTypeOther
}
}

func compressionType(name string) CompressionType {
Expand Down
37 changes: 37 additions & 0 deletions packages/shared/pkg/storage/paths_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,40 @@ func TestSeekableKindFromPath(t *testing.T) {
})
}
}

// TestBlobType pins blobType to its fixed vocabulary: the known whole-object
// blobs (header, snapfile, metadata) and "other" for everything else. The
// seekable data files memfile/rootfs.ext4 are NOT blobs and must never be
// returned, and no per-hash/per-build path may leak — the cardinality blowup
// from #3063.
func TestBlobType(t *testing.T) {
t.Parallel()

p := Paths{BuildID: "11111111-1111-1111-1111-111111111111"}
const hash = "deadbeefcafef00ddeadbeefcafef00ddeadbeefcafef00ddeadbeefcafef00d"

cases := []struct {
name string
path string
want string
}{
{"memfile header", p.MemfileHeader(), blobTypeHeader},
{"rootfs header", p.RootfsHeader(), blobTypeHeader},
{"snapfile", p.Snapfile(), blobTypeSnapfile},
{"metadata", p.Metadata(), blobTypeMetadata},
// Not known blobs — bounded "other", never the raw name or hash.
{"memfile data file is not a blob", p.Memfile(), blobTypeOther},
{"rootfs data file is not a blob", p.RootfsCompressed(CompressionZstd), blobTypeOther},
{"layer files keyed by hash", "scope-abc/files/" + hash + ".tar", blobTypeOther},
{"unknown collapses to other", p.BuildID + "/something-else", blobTypeOther},
{"bare hash never leaks", hash, blobTypeOther},
}

for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
t.Parallel()

require.Equal(t, c.want, blobType(c.path))
})
}
}
Loading