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
10 changes: 5 additions & 5 deletions app/cli/pkg/action/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ func LoadFileOrURL(fileRef string) ([]byte, error) {

// ValidateAndExtractName validates and extracts a name from either
// an explicit name parameter OR from metadata.name in the file content.
// Ensures exactly one source is provided. Returns error when:
// Returns error when:
// - Neither explicit name nor metadata.name is provided
// - Both explicit name and metadata.name are provided (ambiguous)
// - Both are provided and they differ (providing both with the same value is allowed)
func ValidateAndExtractName(explicitName, filePath string) (string, error) {
// Load file content if provided
var content []byte
Expand All @@ -74,9 +74,9 @@ func ValidateAndExtractName(explicitName, filePath string) (string, error) {
return "", fmt.Errorf("parse content: %w", err)
}

// Both provided - ambiguous
if explicitName != "" && metadataName != "" {
return "", fmt.Errorf("conflicting names: explicit name (%q) and metadata.name (%q) both provided", explicitName, metadataName)
// Both provided - only an error if they differ
if explicitName != "" && metadataName != "" && explicitName != metadataName {
return "", fmt.Errorf("--name %q and metadata.name %q differ: pass only one, or set them to the same value", explicitName, metadataName)
}

// Neither provided - missing required name
Expand Down
108 changes: 108 additions & 0 deletions app/cli/pkg/action/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//
// 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 action

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

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

func TestValidateAndExtractName(t *testing.T) {
contractWithName := `apiVersion: chainloop.dev/v1
kind: Contract
metadata:
name: container-image-contract
spec:
materials:
- type: CONTAINER_IMAGE
name: image
`

contractWithoutName := `schemaVersion: v1
materials:
- type: CONTAINER_IMAGE
name: image
`

tests := []struct {
name string
explicitName string
fileContent string
wantName string
wantErr string
}{
{
name: "explicit name only, no file",
explicitName: "my-contract",
wantName: "my-contract",
},
{
name: "metadata.name only",
fileContent: contractWithName,
wantName: "container-image-contract",
},
{
name: "explicit name matching metadata.name is accepted",
explicitName: "container-image-contract",
fileContent: contractWithName,
wantName: "container-image-contract",
},
{
name: "explicit name differing from metadata.name errors with actionable message",
explicitName: "another-name",
fileContent: contractWithName,
wantErr: `--name "another-name" and metadata.name "container-image-contract" differ: pass only one, or set them to the same value`,
},
{
name: "neither name nor file",
wantErr: "name is required when no file is provided",
},
{
name: "file without metadata.name and no explicit name",
fileContent: contractWithoutName,
wantErr: "name is required: either provide explicit name or include metadata.name in the schema",
},
{
name: "explicit name with file without metadata.name",
explicitName: "my-contract",
fileContent: contractWithoutName,
wantName: "my-contract",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var filePath string
if tc.fileContent != "" {
filePath = filepath.Join(t.TempDir(), "contract.yaml")
require.NoError(t, os.WriteFile(filePath, []byte(tc.fileContent), 0o600))
}

got, err := ValidateAndExtractName(tc.explicitName, filePath)
if tc.wantErr != "" {
assert.ErrorContains(t, err, tc.wantErr)
return
}

require.NoError(t, err)
assert.Equal(t, tc.wantName, got)
})
}
}
5 changes: 3 additions & 2 deletions docs/examples/contracts/sbom/sbom-quality.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#release-contract
# DO NOT CHANGE metadata.name, it's used in the SSDF and CRA guides
# https://docs.chainloop.dev/guides/ssdf and https://docs.chainloop.dev/guides/cra
apiVersion: chainloop.dev/v1
kind: Contract
metadata:
name: sbom-quality
name: release-contract
description: Contract for SBOM quality checks
spec:
materials:
Expand Down
6 changes: 4 additions & 2 deletions docs/examples/contracts/slsa/github.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Require a container image reference and include SLSA complicance verification
# Require a container image reference and include SLSA compliance verification
# DO NOT CHANGE metadata.name, it's used in the SLSA guide
# https://docs.chainloop.dev/guides/slsa
apiVersion: chainloop.dev/v1
kind: Contract
metadata:
name: github
name: skynet-build
description: Require a container image reference and include SLSA compliance verification
spec:
materials:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#vuln-scan-contract
# DO NOT CHANGE metadata.name, it's used in the SSDF and CRA guides
# https://docs.chainloop.dev/guides/ssdf and https://docs.chainloop.dev/guides/cra
apiVersion: chainloop.dev/v1
kind: Contract
metadata:
name: vulnerability-management
name: vuln-scan-contract
description: Contract for vulnerability scanning and management
spec:
materials:
Expand Down
Loading