From af4f99570b49e00c78d248253a5f55a74ba22ca6 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Sat, 14 Mar 2026 09:46:12 +0100 Subject: [PATCH 1/2] fix: PR metadata temp file missing .json extension Add wildcard to os.CreateTemp pattern so the random suffix is inserted before the extension instead of appended after it. Closes #2845 Signed-off-by: Miguel Martinez Trivino --- pkg/attestation/crafter/crafter.go | 2 +- pkg/attestation/crafter/prmetadata_test.go | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/pkg/attestation/crafter/crafter.go b/pkg/attestation/crafter/crafter.go index 5918e0648..9646001da 100644 --- a/pkg/attestation/crafter/crafter.go +++ b/pkg/attestation/crafter/crafter.go @@ -546,7 +546,7 @@ func (c *Crafter) AutoCollectPRMetadata(ctx context.Context, attestationID strin // Create a temporary file for the metadata materialName := fmt.Sprintf("pr-metadata-%s", metadata.Number) - tmpFile, err := os.CreateTemp("", fmt.Sprintf("%s.json", materialName)) + tmpFile, err := os.CreateTemp("", fmt.Sprintf("%s*.json", materialName)) if err != nil { return fmt.Errorf("failed to create temp file: %w", err) } diff --git a/pkg/attestation/crafter/prmetadata_test.go b/pkg/attestation/crafter/prmetadata_test.go index 472234b2f..0031b64cd 100644 --- a/pkg/attestation/crafter/prmetadata_test.go +++ b/pkg/attestation/crafter/prmetadata_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. @@ -16,6 +16,8 @@ package crafter import ( + "fmt" + "os" "path/filepath" "testing" @@ -23,6 +25,17 @@ import ( "github.com/stretchr/testify/require" ) +func TestPRMetadataTempFileHasJSONExtension(t *testing.T) { + materialName := fmt.Sprintf("pr-metadata-%s", "123") + tmpFile, err := os.CreateTemp("", fmt.Sprintf("%s*.json", materialName)) + require.NoError(t, err) + defer os.Remove(tmpFile.Name()) + tmpFile.Close() + + assert.Equal(t, ".json", filepath.Ext(tmpFile.Name()), + "temp file should have .json extension, got: %s", tmpFile.Name()) +} + func TestExtractGitHubPRMetadata(t *testing.T) { testCases := []struct { name string From ba34d517814e02933b9ff6b96b3059225a906add Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Sat, 14 Mar 2026 10:06:40 +0100 Subject: [PATCH 2/2] 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 }