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
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ Code reviews are required for all submissions via GitHub pull requests.
- I do not want you to be in the co-author signoff
- when the schema is changed, run make generate, do not create a migration explicitly
- If you are writing go code, adhere to best practices such as the ones in effective-go, or others. This could include, error handling patterns, interface design, package organization, concurrency patterns, etc.
- When writing tests, use table-driven tests whenever possible
- When implementing new functionality, follow TDD: write failing tests first, then implement the code to make them pass
- do not change previous migrations, they are immutable
- if you add any new dependency to a constructor, remember to run wire ./...
- when adding new inedexes, make sure to update the generated sql migraiton files and make them CREATE INDEX CONCURRENTLY and set -- atlas:txmode none at the top
Expand All @@ -272,3 +274,4 @@ Code reviews are required for all submissions via GitHub pull requests.
- any call to authorization Enforce done from the biz or svc layer must be done using biz.AuthzUseCase
- if you modify a schema, remember to run `make migration_sync`
- after changing Helm chart source code (`deployment/chainloop/`), bump the **patch** version (not minor, not major) in the chart's `Chart.yaml`
- when asked to create a GitHub issue, create it in the `chainloop-dev/chainloop` repository
38 changes: 27 additions & 11 deletions pkg/attestation/crafter/collector_prmetadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,24 +68,40 @@ func (c *PRMetadataCollector) Collect(ctx context.Context, cr *Crafter, attestat
return fmt.Errorf("marshaling PR/MR metadata: %w", err)
}

materialName := fmt.Sprintf("pr-metadata-%s", metadata.Number)
tmpFile, err := os.CreateTemp("", fmt.Sprintf("%s-*.json", materialName))
materialName, tmpFile, err := createPRMetadataTempFile(metadata.Number, jsonData)
if err != nil {
return fmt.Errorf("creating temp file: %w", err)
}
defer os.Remove(tmpFile.Name())

if _, err := tmpFile.Write(jsonData); err != nil {
tmpFile.Close()
return fmt.Errorf("writing temp file: %w", err)
return fmt.Errorf("creating PR metadata temp file: %w", err)
}
tmpFile.Close()
defer os.Remove(tmpFile)

if _, err := cr.AddMaterialContractFree(ctx, attestationID, schemaapi.CraftingSchema_Material_CHAINLOOP_PR_INFO.String(), materialName, tmpFile.Name(), casBackend, nil); err != nil {
if _, err := cr.AddMaterialContractFree(ctx, attestationID, schemaapi.CraftingSchema_Material_CHAINLOOP_PR_INFO.String(), materialName, tmpFile, casBackend, nil); err != nil {
return fmt.Errorf("adding PR/MR metadata material: %w", err)
}

cr.Logger.Info().Msg("successfully collected and attested PR/MR metadata")

return nil
}

// createPRMetadataTempFile creates a temp file with the PR metadata JSON content
// and returns the material name and the temp file path.
func createPRMetadataTempFile(prNumber string, data []byte) (materialName string, filePath string, err error) {
materialName = fmt.Sprintf("pr-metadata-%s", prNumber)

tmpFile, err := os.CreateTemp("", fmt.Sprintf("%s-*.json", materialName))
if err != nil {
return "", "", fmt.Errorf("creating temp file: %w", err)
}

if _, err := tmpFile.Write(data); err != nil {
tmpFile.Close()
Comment thread
migmartri marked this conversation as resolved.
os.Remove(tmpFile.Name())
return "", "", fmt.Errorf("writing temp file: %w", err)
}
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
if err := tmpFile.Close(); err != nil {
os.Remove(tmpFile.Name())
return "", "", fmt.Errorf("closing temp file: %w", err)
}

return materialName, tmpFile.Name(), nil
}
66 changes: 66 additions & 0 deletions pkg/attestation/crafter/collector_prmetadata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// Copyright 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.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package crafter

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCreatePRMetadataTempFile(t *testing.T) {
tests := []struct {
name string
prNumber string
wantMaterial string
wantFilePrefix string
}{
{
name: "numeric PR number",
prNumber: "123",
wantMaterial: "pr-metadata-123",
wantFilePrefix: "pr-metadata-123-",
},
{
name: "large PR number",
prNumber: "99999",
wantMaterial: "pr-metadata-99999",
wantFilePrefix: "pr-metadata-99999-",
},
{
name: "single digit PR number",
prNumber: "1",
wantMaterial: "pr-metadata-1",
wantFilePrefix: "pr-metadata-1-",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
materialName, filePath, err := createPRMetadataTempFile(tc.prNumber, []byte(`{"test": true}`))
require.NoError(t, err)
defer os.Remove(filePath)

assert.Equal(t, tc.wantMaterial, materialName)
assert.Equal(t, ".json", filepath.Ext(filePath))
assert.True(t, strings.HasPrefix(filepath.Base(filePath), tc.wantFilePrefix))
})
}
}
2 changes: 1 addition & 1 deletion pkg/attestation/crafter/prmetadata_test.go
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Loading