Skip to content
Open
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
22 changes: 22 additions & 0 deletions internal/evaluator/criteria.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package evaluator

import (
"fmt"
"regexp"
"time"

ecc "github.com/conforma/crds/api/v1alpha1"
Expand Down Expand Up @@ -106,6 +107,14 @@ func (c *Criteria) get(imageRef string, componentName string) []string {
if componentItems, ok := c.componentItems[componentName]; ok {
items = append(items, componentItems...)
}
// For multi-arch image index components, also match by the original component
// name. During expansion, "foo" becomes "foo-sha256:<digest>-arm64" etc., but
// volatile config references the original name.
if origName := originalComponentName(componentName); origName != componentName {
if componentItems, ok := c.componentItems[origName]; ok {
items = append(items, componentItems...)
}
}
}

// Add any exceptions that pertain to all images.
Expand All @@ -119,6 +128,19 @@ func (c *Criteria) getWithKey(key string) []string {
return []string{}
}

// multiArchSuffixRe matches the suffix appended to component names during multi-arch
// image index expansion (see applicationsnapshot/input.go imageIndexWorker). The format
// is "-sha256:<hex digest>-<architecture>", e.g. "-sha256:abc123...-arm64".
var multiArchSuffixRe = regexp.MustCompile(`-sha256:[0-9a-f]{64}-\S+$`)

// originalComponentName returns the component name before multi-arch image index
// expansion. For example, component "foo" is expanded into per-arch components like
// "foo-sha256:<digest>-arm64". This allows component-scoped volatile config
// (includes/excludes) to apply to per-arch components as well as the original.
func originalComponentName(componentName string) string {
return multiArchSuffixRe.ReplaceAllString(componentName, "")
}

func computeIncludeExclude(src ecc.Source, p ConfigProvider) (*Criteria, *Criteria) {
include := &Criteria{}
exclude := &Criteria{}
Expand Down
89 changes: 89 additions & 0 deletions internal/evaluator/criteria_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,45 @@ func TestCriteriaGetWithComponentName(t *testing.T) {
componentName: "my-component",
expected: []string{"test.image_check", "test.component_check", "*"},
},
{
name: "Multi-arch expanded component matches original component name",
criteria: &Criteria{
digestItems: map[string][]string{},
componentItems: map[string][]string{
"my-component": {"test.component_check"},
},
defaultItems: []string{"*"},
},
imageRef: "quay.io/repo/img@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
componentName: "my-component-sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890-arm64",
expected: []string{"test.component_check", "*"},
},
{
name: "Multi-arch expanded component with noarch suffix",
criteria: &Criteria{
digestItems: map[string][]string{},
componentItems: map[string][]string{
"my-component": {"test.component_check"},
},
defaultItems: []string{"*"},
},
imageRef: "quay.io/repo/img@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
componentName: "my-component-sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890-noarch-2",
expected: []string{"test.component_check", "*"},
},
{
name: "Similar component name not incorrectly matched",
criteria: &Criteria{
digestItems: map[string][]string{},
componentItems: map[string][]string{
"my-component": {"test.component_check"},
},
defaultItems: []string{"*"},
},
imageRef: "quay.io/repo/img@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
componentName: "my-component-libs",
expected: []string{"*"},
},
}

for _, tt := range tests {
Expand All @@ -986,3 +1025,53 @@ func TestCriteriaGetWithComponentName(t *testing.T) {
})
}
}

func TestOriginalComponentName(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "plain component name unchanged",
input: "my-component",
expected: "my-component",
},
{
name: "multi-arch arm64 suffix stripped",
input: "my-component-sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890-arm64",
expected: "my-component",
},
{
name: "multi-arch amd64 suffix stripped",
input: "my-component-sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890-amd64",
expected: "my-component",
},
{
name: "multi-arch noarch suffix stripped",
input: "my-component-sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890-noarch-2",
expected: "my-component",
},
{
name: "similar name without digest not stripped",
input: "my-component-libs",
expected: "my-component-libs",
},
{
name: "similar name with sha prefix but no digest not stripped",
input: "my-component-sha256",
expected: "my-component-sha256",
},
{
name: "empty string unchanged",
input: "",
expected: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expected, originalComponentName(tt.input))
})
}
}
Loading