From 61c2b65a0c7bc7d5285ce6dcfcbf37d44233d11c Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Sat, 14 Mar 2026 10:06:40 +0100 Subject: [PATCH] feat: add material size annotation to attestations Closes #2847 Signed-off-by: Miguel Martinez Trivino --- .../crafter/materials/artifact_test.go | 7 ++++++- .../crafter/materials/evidence_test.go | 21 ++++++++++++------- .../crafter/materials/materials.go | 8 ++++++- .../crafter/materials/materials_test.go | 4 ++-- pkg/attestation/crafter/materials/string.go | 6 +++++- 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/pkg/attestation/crafter/materials/artifact_test.go b/pkg/attestation/crafter/materials/artifact_test.go index c4b62b0b1..c16b97c23 100644 --- a/pkg/attestation/crafter/materials/artifact_test.go +++ b/pkg/attestation/crafter/materials/artifact_test.go @@ -1,5 +1,5 @@ // -// Copyright 2023-2025 The Chainloop Authors. +// Copyright 2023-2026 The Chainloop Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -96,6 +96,9 @@ func TestArtifactCraft(t *testing.T) { assert.Equal(got.GetArtifact(), &attestationApi.Attestation_Material_Artifact{ Id: "test", Digest: "sha256:54181dfe59340b318253e59f7695f547c5c10d071cb75001170a389061349918", Name: "simple.txt", }) + + // The result includes the size annotation + assert.Equal("8", got.Annotations[materials.AnnotationMaterialSize]) } func TestArtifactCraftInline(t *testing.T) { @@ -153,4 +156,6 @@ func assertMaterial(t *testing.T, got *attestationApi.Attestation_Material) { // Inline content Content: []byte("txt file"), }) + // The result includes the size annotation + assert.Equal("8", got.Annotations[materials.AnnotationMaterialSize]) } diff --git a/pkg/attestation/crafter/materials/evidence_test.go b/pkg/attestation/crafter/materials/evidence_test.go index 57577f489..cac91232b 100644 --- a/pkg/attestation/crafter/materials/evidence_test.go +++ b/pkg/attestation/crafter/materials/evidence_test.go @@ -1,5 +1,5 @@ // -// Copyright 2024-2025 The Chainloop Authors. +// Copyright 2024-2026 The Chainloop Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -171,6 +171,7 @@ func TestEvidenceCraftWithJSONAnnotations(t *testing.T) { expectedAnnotations: map[string]string{ "chainloop.material.evidence.id": "custom-evidence-123", "chainloop.material.evidence.schema": "https://example.com/schema/v1", + materials.AnnotationMaterialSize: "297", }, }, { @@ -178,6 +179,7 @@ func TestEvidenceCraftWithJSONAnnotations(t *testing.T) { filePath: "./testdata/evidence-with-id-data-no-schema.json", expectedAnnotations: map[string]string{ "chainloop.material.evidence.id": "custom-evidence-456", + materials.AnnotationMaterialSize: "158", }, }, { @@ -186,17 +188,22 @@ func TestEvidenceCraftWithJSONAnnotations(t *testing.T) { expectedAnnotations: map[string]string{ "chainloop.material.evidence.id": "custom-evidence-123", "chainloop.material.evidence.schema": "https://example.com/schema/v1", + materials.AnnotationMaterialSize: "325", }, }, { - name: "JSON without required structure does not extract annotations", - filePath: "./testdata/evidence-invalid-structure.json", - expectedAnnotations: nil, + name: "JSON without required structure does not extract annotations", + filePath: "./testdata/evidence-invalid-structure.json", + expectedAnnotations: map[string]string{ + materials.AnnotationMaterialSize: "90", + }, }, { - name: "Non-JSON file does not extract annotations", - filePath: "./testdata/simple.txt", - expectedAnnotations: nil, + name: "Non-JSON file does not extract annotations", + filePath: "./testdata/simple.txt", + expectedAnnotations: map[string]string{ + materials.AnnotationMaterialSize: "8", + }, }, } diff --git a/pkg/attestation/crafter/materials/materials.go b/pkg/attestation/crafter/materials/materials.go index a8c92a090..a30e80bbb 100644 --- a/pkg/attestation/crafter/materials/materials.go +++ b/pkg/attestation/crafter/materials/materials.go @@ -1,5 +1,5 @@ // -// Copyright 2024-2025 The Chainloop Authors. +// Copyright 2024-2026 The Chainloop Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import ( "fmt" "io" "os" + "strconv" "time" "buf.build/go/protovalidate" @@ -39,6 +40,7 @@ const ( AnnotationToolNameKey = "chainloop.material.tool.name" AnnotationToolVersionKey = "chainloop.material.tool.version" AnnotationToolsKey = "chainloop.material.tools" + AnnotationMaterialSize = "chainloop.material.size" ) // IsLegacyAnnotation returns true if the annotation key is a legacy annotation @@ -165,6 +167,10 @@ func uploadAndCraft(ctx context.Context, input *schemaapi.CraftingSchema_Materia material.GetArtifact().Content = content } + material.Annotations = map[string]string{ + AnnotationMaterialSize: strconv.FormatInt(result.size, 10), + } + return material, nil } diff --git a/pkg/attestation/crafter/materials/materials_test.go b/pkg/attestation/crafter/materials/materials_test.go index f7aaafdab..7b09f7e17 100644 --- a/pkg/attestation/crafter/materials/materials_test.go +++ b/pkg/attestation/crafter/materials/materials_test.go @@ -1,5 +1,5 @@ // -// Copyright 2023 The Chainloop Authors. +// Copyright 2023-2026 The Chainloop Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -51,5 +51,5 @@ func TestCraft(t *testing.T) { // Timestamp assert.WithinDuration(time.Now(), got.AddedAt.AsTime(), 5*time.Second) // Annotations - assert.Equal(map[string]string{"test": "test"}, got.Annotations) + assert.Equal(map[string]string{"test": "test", "chainloop.material.size": "10"}, got.Annotations) } diff --git a/pkg/attestation/crafter/materials/string.go b/pkg/attestation/crafter/materials/string.go index 08f949cb0..17eefd11e 100644 --- a/pkg/attestation/crafter/materials/string.go +++ b/pkg/attestation/crafter/materials/string.go @@ -1,5 +1,5 @@ // -// Copyright 2023 The Chainloop Authors. +// Copyright 2023-2026 The Chainloop Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package materials import ( "context" "fmt" + "strconv" "strings" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" @@ -53,5 +54,8 @@ func (i *StringCrafter) Craft(_ context.Context, value string) (*api.Attestation Value: value, Digest: hash.String(), }, }, + Annotations: map[string]string{ + AnnotationMaterialSize: strconv.Itoa(len(value)), + }, }, nil }