Skip to content

Commit 80ba173

Browse files
authored
Merge pull request #3272 from simonbaird/multi-arch-component-criteria-fix
Fix per-component criteria for multi-arch
2 parents 68bb316 + 3cc2555 commit 80ba173

2 files changed

Lines changed: 111 additions & 0 deletions

File tree

internal/evaluator/criteria.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package evaluator
1818

1919
import (
2020
"fmt"
21+
"regexp"
2122
"time"
2223

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

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

131+
// multiArchSuffixRe matches the suffix appended to component names during multi-arch
132+
// image index expansion (see applicationsnapshot/input.go imageIndexWorker). The format
133+
// is "-sha256:<hex digest>-<architecture>", e.g. "-sha256:abc123...-arm64".
134+
var multiArchSuffixRe = regexp.MustCompile(`-sha256:[0-9a-f]{64}-\S+$`)
135+
136+
// originalComponentName returns the component name before multi-arch image index
137+
// expansion. For example, component "foo" is expanded into per-arch components like
138+
// "foo-sha256:<digest>-arm64". This allows component-scoped volatile config
139+
// (includes/excludes) to apply to per-arch components as well as the original.
140+
func originalComponentName(componentName string) string {
141+
return multiArchSuffixRe.ReplaceAllString(componentName, "")
142+
}
143+
122144
func computeIncludeExclude(src ecc.Source, p ConfigProvider) (*Criteria, *Criteria) {
123145
include := &Criteria{}
124146
exclude := &Criteria{}

internal/evaluator/criteria_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,45 @@ func TestCriteriaGetWithComponentName(t *testing.T) {
977977
componentName: "my-component",
978978
expected: []string{"test.image_check", "test.component_check", "*"},
979979
},
980+
{
981+
name: "Multi-arch expanded component matches original component name",
982+
criteria: &Criteria{
983+
digestItems: map[string][]string{},
984+
componentItems: map[string][]string{
985+
"my-component": {"test.component_check"},
986+
},
987+
defaultItems: []string{"*"},
988+
},
989+
imageRef: "quay.io/repo/img@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
990+
componentName: "my-component-sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890-arm64",
991+
expected: []string{"test.component_check", "*"},
992+
},
993+
{
994+
name: "Multi-arch expanded component with noarch suffix",
995+
criteria: &Criteria{
996+
digestItems: map[string][]string{},
997+
componentItems: map[string][]string{
998+
"my-component": {"test.component_check"},
999+
},
1000+
defaultItems: []string{"*"},
1001+
},
1002+
imageRef: "quay.io/repo/img@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
1003+
componentName: "my-component-sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890-noarch-2",
1004+
expected: []string{"test.component_check", "*"},
1005+
},
1006+
{
1007+
name: "Similar component name not incorrectly matched",
1008+
criteria: &Criteria{
1009+
digestItems: map[string][]string{},
1010+
componentItems: map[string][]string{
1011+
"my-component": {"test.component_check"},
1012+
},
1013+
defaultItems: []string{"*"},
1014+
},
1015+
imageRef: "quay.io/repo/img@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
1016+
componentName: "my-component-libs",
1017+
expected: []string{"*"},
1018+
},
9801019
}
9811020

9821021
for _, tt := range tests {
@@ -986,3 +1025,53 @@ func TestCriteriaGetWithComponentName(t *testing.T) {
9861025
})
9871026
}
9881027
}
1028+
1029+
func TestOriginalComponentName(t *testing.T) {
1030+
tests := []struct {
1031+
name string
1032+
input string
1033+
expected string
1034+
}{
1035+
{
1036+
name: "plain component name unchanged",
1037+
input: "my-component",
1038+
expected: "my-component",
1039+
},
1040+
{
1041+
name: "multi-arch arm64 suffix stripped",
1042+
input: "my-component-sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890-arm64",
1043+
expected: "my-component",
1044+
},
1045+
{
1046+
name: "multi-arch amd64 suffix stripped",
1047+
input: "my-component-sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890-amd64",
1048+
expected: "my-component",
1049+
},
1050+
{
1051+
name: "multi-arch noarch suffix stripped",
1052+
input: "my-component-sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890-noarch-2",
1053+
expected: "my-component",
1054+
},
1055+
{
1056+
name: "similar name without digest not stripped",
1057+
input: "my-component-libs",
1058+
expected: "my-component-libs",
1059+
},
1060+
{
1061+
name: "similar name with sha prefix but no digest not stripped",
1062+
input: "my-component-sha256",
1063+
expected: "my-component-sha256",
1064+
},
1065+
{
1066+
name: "empty string unchanged",
1067+
input: "",
1068+
expected: "",
1069+
},
1070+
}
1071+
1072+
for _, tt := range tests {
1073+
t.Run(tt.name, func(t *testing.T) {
1074+
require.Equal(t, tt.expected, originalComponentName(tt.input))
1075+
})
1076+
}
1077+
}

0 commit comments

Comments
 (0)