From c8703f88a90bc7d118e8400ed7390b6e2809bb17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Insaurralde?= Date: Wed, 20 May 2026 23:23:27 -0300 Subject: [PATCH] perf(crafter): avoid redundant SHA256 pass when uploading materials to CAS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit uploadAndCraft already computes the artifact's SHA256 via fileStats, then called Uploader.UploadFile which re-opened the file and re-hashed it before streaming. Switch to Uploader.Upload with the reader and digest from fileStats so the file is opened once and hashed once per material. Also fix a file-handle leak in fileStats when SHA256 or Seek fails. Chainloop-Trace-Sessions: 70c6c792-4598-426b-9e75-482bcb10498f Signed-off-by: Matías Insaurralde --- pkg/attestation/crafter/crafter_test.go | 5 +++-- pkg/attestation/crafter/materials/artifact_test.go | 3 ++- pkg/attestation/crafter/materials/asyncapi_test.go | 5 +++-- pkg/attestation/crafter/materials/attestation_test.go | 4 ++-- pkg/attestation/crafter/materials/blackduck_test.go | 5 +++-- .../crafter/materials/chainloop_ai_agent_config_test.go | 3 ++- .../materials/chainloop_ai_coding_session_test.go | 3 ++- pkg/attestation/crafter/materials/csaf_test.go | 3 ++- pkg/attestation/crafter/materials/cyclonedxjson_test.go | 7 ++++--- pkg/attestation/crafter/materials/evidence_test.go | 5 +++-- pkg/attestation/crafter/materials/ghas_test.go | 9 +++++---- pkg/attestation/crafter/materials/gitlab_test.go | 5 +++-- pkg/attestation/crafter/materials/gitleaks_test.go | 3 ++- pkg/attestation/crafter/materials/graphql_test.go | 3 ++- pkg/attestation/crafter/materials/helmchart_test.go | 5 +++-- pkg/attestation/crafter/materials/jacoco_test.go | 5 +++-- pkg/attestation/crafter/materials/junit_xml_test.go | 5 +++-- pkg/attestation/crafter/materials/materials.go | 6 +++++- pkg/attestation/crafter/materials/openapi_test.go | 7 ++++--- pkg/attestation/crafter/materials/openvex_test.go | 5 +++-- pkg/attestation/crafter/materials/runnercontext_test.go | 5 +++-- pkg/attestation/crafter/materials/sarif_test.go | 7 ++++--- pkg/attestation/crafter/materials/slsaprovenance_test.go | 4 ++-- pkg/attestation/crafter/materials/spdxjson_test.go | 3 ++- pkg/attestation/crafter/materials/twistcli_scan_test.go | 5 +++-- pkg/attestation/crafter/materials/zap_test.go | 5 +++-- 26 files changed, 76 insertions(+), 49 deletions(-) diff --git a/pkg/attestation/crafter/crafter_test.go b/pkg/attestation/crafter/crafter_test.go index 80c70c390..6786c92c3 100644 --- a/pkg/attestation/crafter/crafter_test.go +++ b/pkg/attestation/crafter/crafter_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. @@ -40,6 +40,7 @@ import ( "github.com/go-git/go-git/v6/config" "github.com/go-git/go-git/v6/plumbing/object" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "google.golang.org/protobuf/encoding/protojson" @@ -609,7 +610,7 @@ func (s *crafterSuite) TestAddMaterialsAutomatic() { uploader := mUploader.NewUploader(s.T()) if !tc.uploadArtifact { - uploader.On("UploadFile", context.Background(), tc.materialPath). + uploader.On("Upload", context.Background(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "simple.txt", diff --git a/pkg/attestation/crafter/materials/artifact_test.go b/pkg/attestation/crafter/materials/artifact_test.go index c16b97c23..e42509a19 100644 --- a/pkg/attestation/crafter/materials/artifact_test.go +++ b/pkg/attestation/crafter/materials/artifact_test.go @@ -28,6 +28,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -76,7 +77,7 @@ func TestArtifactCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) - uploader.On("UploadFile", context.TODO(), "./testdata/simple.txt"). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "simple.txt", diff --git a/pkg/attestation/crafter/materials/asyncapi_test.go b/pkg/attestation/crafter/materials/asyncapi_test.go index 888f260bd..c5b1daa95 100644 --- a/pkg/attestation/crafter/materials/asyncapi_test.go +++ b/pkg/attestation/crafter/materials/asyncapi_test.go @@ -27,6 +27,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -143,7 +144,7 @@ func TestAsyncAPICraft(t *testing.T) { t.Run(tc.name, func(t *testing.T) { uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "spec.json", @@ -178,7 +179,7 @@ func TestAsyncAPICraft(t *testing.T) { func TestAsyncAPICraftNoStrictValidation(t *testing.T) { l := zerolog.Nop() uploader := mUploader.NewUploader(t) - uploader.On("UploadFile", context.TODO(), "./testdata/asyncapi-invalid.json"). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "spec.json", diff --git a/pkg/attestation/crafter/materials/attestation_test.go b/pkg/attestation/crafter/materials/attestation_test.go index 345e68f13..c70497788 100644 --- a/pkg/attestation/crafter/materials/attestation_test.go +++ b/pkg/attestation/crafter/materials/attestation_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. @@ -136,7 +136,7 @@ func TestAttestationCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if !tc.expectErr { - uploader.On("UploadFile", context.Background(), mock.Anything). + uploader.On("Upload", context.Background(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "attestation.json", diff --git a/pkg/attestation/crafter/materials/blackduck_test.go b/pkg/attestation/crafter/materials/blackduck_test.go index 8ab26b444..07e3731fa 100644 --- a/pkg/attestation/crafter/materials/blackduck_test.go +++ b/pkg/attestation/crafter/materials/blackduck_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. @@ -26,6 +26,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -77,7 +78,7 @@ func TestBlackduckJSONCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{}, nil) } diff --git a/pkg/attestation/crafter/materials/chainloop_ai_agent_config_test.go b/pkg/attestation/crafter/materials/chainloop_ai_agent_config_test.go index 877fdd9db..cf425f6d3 100644 --- a/pkg/attestation/crafter/materials/chainloop_ai_agent_config_test.go +++ b/pkg/attestation/crafter/materials/chainloop_ai_agent_config_test.go @@ -29,6 +29,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -288,7 +289,7 @@ func TestChainloopAIAgentConfigCrafter_AgentNameAnnotation(t *testing.T) { } uploader := mUploader.NewUploader(t) - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: tc.filePath, diff --git a/pkg/attestation/crafter/materials/chainloop_ai_coding_session_test.go b/pkg/attestation/crafter/materials/chainloop_ai_coding_session_test.go index 92eb9386c..82ce7fd9c 100644 --- a/pkg/attestation/crafter/materials/chainloop_ai_coding_session_test.go +++ b/pkg/attestation/crafter/materials/chainloop_ai_coding_session_test.go @@ -29,6 +29,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -327,7 +328,7 @@ func TestChainloopAICodingSessionCrafter_Annotations(t *testing.T) { } uploader := mUploader.NewUploader(t) - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: tc.filePath, diff --git a/pkg/attestation/crafter/materials/csaf_test.go b/pkg/attestation/crafter/materials/csaf_test.go index cb7126adc..46a9b6e3b 100644 --- a/pkg/attestation/crafter/materials/csaf_test.go +++ b/pkg/attestation/crafter/materials/csaf_test.go @@ -28,6 +28,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -220,7 +221,7 @@ func TestCSAFCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "vex.json", diff --git a/pkg/attestation/crafter/materials/cyclonedxjson_test.go b/pkg/attestation/crafter/materials/cyclonedxjson_test.go index 63223663e..437f6268a 100644 --- a/pkg/attestation/crafter/materials/cyclonedxjson_test.go +++ b/pkg/attestation/crafter/materials/cyclonedxjson_test.go @@ -26,6 +26,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -212,7 +213,7 @@ func TestCyclonedxJSONCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{}, nil) } @@ -304,7 +305,7 @@ func TestCycloneDXJSONCraftNoStrictValidation(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{}, nil) } @@ -358,7 +359,7 @@ func TestCycloneDXJSONCraft_SkipUpload(t *testing.T) { // Mock uploader - only expect upload call if not skipped uploader := mUploader.NewUploader(t) if tc.wantUpload { - uploader.On("UploadFile", context.TODO(), filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "sbom.cyclonedx.json", diff --git a/pkg/attestation/crafter/materials/evidence_test.go b/pkg/attestation/crafter/materials/evidence_test.go index cac91232b..b73e9d517 100644 --- a/pkg/attestation/crafter/materials/evidence_test.go +++ b/pkg/attestation/crafter/materials/evidence_test.go @@ -28,6 +28,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -76,7 +77,7 @@ func TestEvidenceCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) - uploader.On("UploadFile", context.TODO(), "./testdata/simple.txt"). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "simple.txt", @@ -213,7 +214,7 @@ func TestEvidenceCraftWithJSONAnnotations(t *testing.T) { // Create a new mock uploader for each test case uploader := mUploader.NewUploader(t) - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: tc.filePath, diff --git a/pkg/attestation/crafter/materials/ghas_test.go b/pkg/attestation/crafter/materials/ghas_test.go index 46c240554..2a99d3a63 100644 --- a/pkg/attestation/crafter/materials/ghas_test.go +++ b/pkg/attestation/crafter/materials/ghas_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. @@ -27,6 +27,7 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -105,7 +106,7 @@ func TestGHASCodeScanCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "ghas-code-scan.json", @@ -177,7 +178,7 @@ func TestGHASSecretScanCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "ghas-secret-scan.json", @@ -249,7 +250,7 @@ func TestGHASDependencyScanCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "ghas-dependency-scan.json", diff --git a/pkg/attestation/crafter/materials/gitlab_test.go b/pkg/attestation/crafter/materials/gitlab_test.go index 6a9879614..301eee73c 100644 --- a/pkg/attestation/crafter/materials/gitlab_test.go +++ b/pkg/attestation/crafter/materials/gitlab_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. @@ -25,6 +25,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -129,7 +130,7 @@ func TestGitlabCrafter_Craft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{}, nil) } diff --git a/pkg/attestation/crafter/materials/gitleaks_test.go b/pkg/attestation/crafter/materials/gitleaks_test.go index 63ac12ffe..e28d241e3 100644 --- a/pkg/attestation/crafter/materials/gitleaks_test.go +++ b/pkg/attestation/crafter/materials/gitleaks_test.go @@ -25,6 +25,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -111,7 +112,7 @@ func TestGitleaksReportCrafter_Craft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{}, nil) } diff --git a/pkg/attestation/crafter/materials/graphql_test.go b/pkg/attestation/crafter/materials/graphql_test.go index 52d4e1c16..c2e1ec3fc 100644 --- a/pkg/attestation/crafter/materials/graphql_test.go +++ b/pkg/attestation/crafter/materials/graphql_test.go @@ -27,6 +27,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -118,7 +119,7 @@ func TestGraphQLCraft(t *testing.T) { t.Run(tc.name, func(t *testing.T) { uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "schema.graphql", diff --git a/pkg/attestation/crafter/materials/helmchart_test.go b/pkg/attestation/crafter/materials/helmchart_test.go index 9799806a5..1fc4bceba 100644 --- a/pkg/attestation/crafter/materials/helmchart_test.go +++ b/pkg/attestation/crafter/materials/helmchart_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. @@ -26,6 +26,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -121,7 +122,7 @@ func TestHelmChartCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{}, nil) } diff --git a/pkg/attestation/crafter/materials/jacoco_test.go b/pkg/attestation/crafter/materials/jacoco_test.go index f32a6ae0f..d0856fc34 100644 --- a/pkg/attestation/crafter/materials/jacoco_test.go +++ b/pkg/attestation/crafter/materials/jacoco_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. @@ -27,6 +27,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -68,7 +69,7 @@ func TestJacocoCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "jacoco.xml", diff --git a/pkg/attestation/crafter/materials/junit_xml_test.go b/pkg/attestation/crafter/materials/junit_xml_test.go index 8a8870ecb..e10cb840b 100644 --- a/pkg/attestation/crafter/materials/junit_xml_test.go +++ b/pkg/attestation/crafter/materials/junit_xml_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. @@ -28,6 +28,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -111,7 +112,7 @@ func TestJUnitXMLCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "test.xml", diff --git a/pkg/attestation/crafter/materials/materials.go b/pkg/attestation/crafter/materials/materials.go index a29df88e1..1bc1ce7ad 100644 --- a/pkg/attestation/crafter/materials/materials.go +++ b/pkg/attestation/crafter/materials/materials.go @@ -148,7 +148,9 @@ func uploadAndCraft(ctx context.Context, input *schemaapi.CraftingSchema_Materia case backend.Uploader != nil: l.Debug().Str("backend", backend.Name).Msg("uploading") - _, err = backend.Uploader.UploadFile(ctx, artifactPath) + // Reuse the already-open, already-hashed reader from fileStats + // to avoid a redundant SHA256 pass inside Uploader.UploadFile. + _, err = backend.Uploader.Upload(ctx, result.r, result.filename, result.digest) if err != nil { return nil, fmt.Errorf("%w: %w", ErrBaseUploadAndCraft, fmt.Errorf("uploading material: %w", err)) } @@ -193,6 +195,7 @@ func fileStats(filepath string) (*fileInfo, error) { hash, _, err := cr_v1.SHA256(f) if err != nil { + f.Close() return nil, fmt.Errorf("generating digest: %w", err) } @@ -200,6 +203,7 @@ func fileStats(filepath string) (*fileInfo, error) { // we need to rewind the file pointer _, err = f.Seek(0, io.SeekStart) if err != nil { + f.Close() return nil, fmt.Errorf("rewinding file pointer: %w", err) } diff --git a/pkg/attestation/crafter/materials/openapi_test.go b/pkg/attestation/crafter/materials/openapi_test.go index 87f6be501..742c1c3fc 100644 --- a/pkg/attestation/crafter/materials/openapi_test.go +++ b/pkg/attestation/crafter/materials/openapi_test.go @@ -27,6 +27,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -177,7 +178,7 @@ func TestOpenAPICraft(t *testing.T) { t.Run(tc.name, func(t *testing.T) { uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "spec.json", @@ -212,7 +213,7 @@ func TestOpenAPICraft(t *testing.T) { func TestOpenAPICraftNoStrictValidationSwagger2(t *testing.T) { l := zerolog.Nop() uploader := mUploader.NewUploader(t) - uploader.On("UploadFile", context.TODO(), "./testdata/swagger-2.0-invalid.json"). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "spec.json", @@ -236,7 +237,7 @@ func TestOpenAPICraftNoStrictValidationSwagger2(t *testing.T) { func TestOpenAPICraftNoStrictValidation(t *testing.T) { l := zerolog.Nop() uploader := mUploader.NewUploader(t) - uploader.On("UploadFile", context.TODO(), "./testdata/openapi-invalid.json"). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "spec.json", diff --git a/pkg/attestation/crafter/materials/openvex_test.go b/pkg/attestation/crafter/materials/openvex_test.go index 509e63bfc..162155501 100644 --- a/pkg/attestation/crafter/materials/openvex_test.go +++ b/pkg/attestation/crafter/materials/openvex_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. @@ -27,6 +27,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -102,7 +103,7 @@ func TestOpenVEXCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "openvex.json", diff --git a/pkg/attestation/crafter/materials/runnercontext_test.go b/pkg/attestation/crafter/materials/runnercontext_test.go index 5f5ceccd3..e90503a62 100644 --- a/pkg/attestation/crafter/materials/runnercontext_test.go +++ b/pkg/attestation/crafter/materials/runnercontext_test.go @@ -1,5 +1,5 @@ // -// Copyright 2025 The Chainloop Authors. +// Copyright 2025-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. @@ -27,6 +27,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -105,7 +106,7 @@ func TestChainloopRunnerContextCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "test.xml", diff --git a/pkg/attestation/crafter/materials/sarif_test.go b/pkg/attestation/crafter/materials/sarif_test.go index 4797a0d82..b0c602a95 100644 --- a/pkg/attestation/crafter/materials/sarif_test.go +++ b/pkg/attestation/crafter/materials/sarif_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. @@ -27,6 +27,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -100,7 +101,7 @@ func TestSARIFCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "report.sarif", @@ -163,7 +164,7 @@ func TestSARIFCraft_SkipUpload(t *testing.T) { // Mock uploader - only expect upload call if not skipped uploader := mUploader.NewUploader(t) if tc.wantUpload { - uploader.On("UploadFile", context.TODO(), filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "report.sarif", diff --git a/pkg/attestation/crafter/materials/slsaprovenance_test.go b/pkg/attestation/crafter/materials/slsaprovenance_test.go index ce5986dd6..ca8db590c 100644 --- a/pkg/attestation/crafter/materials/slsaprovenance_test.go +++ b/pkg/attestation/crafter/materials/slsaprovenance_test.go @@ -1,5 +1,5 @@ // -// Copyright 2025 The Chainloop Authors. +// Copyright 2025-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. @@ -74,7 +74,7 @@ func TestSLSAProvenanceCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) - uploader.On("UploadFile", context.TODO(), mock.Anything). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "attestation.json", diff --git a/pkg/attestation/crafter/materials/spdxjson_test.go b/pkg/attestation/crafter/materials/spdxjson_test.go index fc8a9966e..35f4491b3 100644 --- a/pkg/attestation/crafter/materials/spdxjson_test.go +++ b/pkg/attestation/crafter/materials/spdxjson_test.go @@ -26,6 +26,7 @@ import ( mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -191,7 +192,7 @@ func TestSPDXJSONCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "sbom-spdx.json", diff --git a/pkg/attestation/crafter/materials/twistcli_scan_test.go b/pkg/attestation/crafter/materials/twistcli_scan_test.go index 32be56372..ee272cfda 100644 --- a/pkg/attestation/crafter/materials/twistcli_scan_test.go +++ b/pkg/attestation/crafter/materials/twistcli_scan_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. @@ -27,6 +27,7 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -100,7 +101,7 @@ func TestTwistCLIScanCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "twistcli_scan", diff --git a/pkg/attestation/crafter/materials/zap_test.go b/pkg/attestation/crafter/materials/zap_test.go index dfe009d0e..eb89c289e 100644 --- a/pkg/attestation/crafter/materials/zap_test.go +++ b/pkg/attestation/crafter/materials/zap_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. @@ -27,6 +27,7 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -105,7 +106,7 @@ func TestNewZAPCraft(t *testing.T) { // Mock uploader uploader := mUploader.NewUploader(t) if tc.wantErr == "" { - uploader.On("UploadFile", context.TODO(), tc.filePath). + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). Return(&casclient.UpDownStatus{ Digest: "deadbeef", Filename: "zap_scan.zip",