Skip to content

Commit 348da47

Browse files
committed
MCO-2120: Rework OS image stream label classification
Replace the single ostree.linux discriminator with configurable label matchers that explicitly classify OS images (ostree.bootable, containers.bootc) and extensions images (io.openshift.os.extensions). Images that match no discriminator now return nil instead of defaulting to extensions. Signed-off-by: Pablo Rodriguez Nava <git@amail.pablintino.com>
1 parent 6d48b64 commit 348da47

File tree

5 files changed

+196
-91
lines changed

5 files changed

+196
-91
lines changed

pkg/osimagestream/image_data.go

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package osimagestream
33
import (
44
"maps"
55
"slices"
6+
"strings"
67

78
"github.com/openshift/api/machineconfiguration/v1alpha1"
89
"k8s.io/klog/v2"
@@ -11,8 +12,15 @@ import (
1112
const (
1213
// coreOSLabelStreamClass is the container image label that identifies the OS stream name.
1314
coreOSLabelStreamClass = "io.openshift.os.streamclass"
14-
// coreOSLabelDiscriminator is the container image label that distinguishes OS images from Extensions images.
15-
coreOSLabelDiscriminator = "ostree.linux"
15+
16+
// coreOSLabelExtension is the container image label that identifies an extensions image.
17+
coreOSLabelExtension = "io.openshift.os.extensions"
18+
coreOSLabelExtensionValue = "true"
19+
20+
// coreOSLabelBootc is the container image label present on bootc-based OS images.
21+
coreOSLabelBootc = "containers.bootc"
22+
coreOSLabelBootcValueBool = "true"
23+
coreOSLabelBootcValueInteger = "1"
1624
)
1725

1826
// ImageType indicates whether a container image is an OS image or an extensions image.
@@ -40,12 +48,16 @@ type ImageData struct {
4048
Stream string // OS stream name
4149
}
4250

51+
// ImageLabelMatcher is a function that checks whether a set of container image
52+
// labels matches a specific criterion.
53+
type ImageLabelMatcher func(labels map[string]string) bool
54+
4355
// ImageStreamExtractorImpl implements ImageDataExtractor using container image labels
4456
// to identify and classify OS and extensions images.
4557
type ImageStreamExtractorImpl struct {
46-
imagesInspector ImagesInspector
47-
streamLabels []string
48-
osImageDiscriminator string
58+
streamLabels []string
59+
osImageMatchers []ImageLabelMatcher
60+
extensionsImageMatchers []ImageLabelMatcher
4961
}
5062

5163
// NewImageStreamExtractor creates a new ImageDataExtractor configured to recognize
@@ -54,14 +66,19 @@ func NewImageStreamExtractor() ImageDataExtractor {
5466
// The type is thought to allow future extra label addition for
5567
// i.e. Allow a customer to bring their own images with their own labels (defining a selector)
5668
return &ImageStreamExtractorImpl{
57-
streamLabels: []string{coreOSLabelStreamClass},
58-
osImageDiscriminator: coreOSLabelDiscriminator,
69+
streamLabels: []string{coreOSLabelStreamClass},
70+
osImageMatchers: []ImageLabelMatcher{
71+
labelEquals(coreOSLabelBootc, coreOSLabelBootcValueBool, coreOSLabelBootcValueInteger),
72+
},
73+
extensionsImageMatchers: []ImageLabelMatcher{
74+
labelEquals(coreOSLabelExtension, coreOSLabelExtensionValue),
75+
},
5976
}
6077
}
6178

6279
// GetImageData analyzes container image labels to extract OS stream metadata.
63-
// Returns nil if the image does not have the required stream label.
64-
// Distinguishes between OS and extensions images based on the presence of the ostree discriminator label.
80+
// Returns nil if the image does not have the required stream label or if it
81+
// cannot be classified as either an OS or extensions image.
6582
func (e *ImageStreamExtractorImpl) GetImageData(image string, labels map[string]string) *ImageData {
6683
imageData := &ImageData{
6784
Image: image,
@@ -72,10 +89,14 @@ func (e *ImageStreamExtractorImpl) GetImageData(image string, labels map[string]
7289
return nil
7390
}
7491

75-
if findLabelValue(labels, e.osImageDiscriminator) != "" {
92+
// Determine the type of image. OS or extensions.
93+
switch {
94+
case matchesAny(labels, e.osImageMatchers):
7695
imageData.Type = ImageTypeOS
77-
} else {
96+
case matchesAny(labels, e.extensionsImageMatchers):
7897
imageData.Type = ImageTypeExtensions
98+
default:
99+
return nil
79100
}
80101

81102
return imageData
@@ -150,3 +171,29 @@ func NewOSImageStreamURLSetFromImageMetadata(imageMetadata *ImageData) *v1alpha1
150171
}
151172
return urlSet
152173
}
174+
175+
// labelEquals returns a matcher that checks whether a label has the expected value (case-insensitive).
176+
func labelEquals(key string, values ...string) ImageLabelMatcher {
177+
return func(labels map[string]string) bool {
178+
v, ok := labels[key]
179+
if !ok {
180+
return false
181+
}
182+
for _, value := range values {
183+
if strings.EqualFold(v, value) {
184+
return true
185+
}
186+
}
187+
return false
188+
}
189+
}
190+
191+
// matchesAny returns true if any of the matchers match the given labels.
192+
func matchesAny(labels map[string]string, matchers []ImageLabelMatcher) bool {
193+
for _, m := range matchers {
194+
if m(labels) {
195+
return true
196+
}
197+
}
198+
return false
199+
}

pkg/osimagestream/image_data_test.go

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,81 @@ import (
99
"github.com/stretchr/testify/require"
1010
)
1111

12-
func TestImageStreamExtractorImpl_GetImageData_OSImage(t *testing.T) {
13-
extractor := NewImageStreamExtractor()
14-
15-
labels := map[string]string{
16-
"io.openshift.os.streamclass": "rhel-9",
17-
"ostree.linux": "present",
18-
}
19-
20-
result := extractor.GetImageData("quay.io/openshift/os@sha256:abc123", labels)
21-
22-
require.NotNil(t, result)
23-
assert.Equal(t, "quay.io/openshift/os@sha256:abc123", result.Image)
24-
assert.EqualValues(t, ImageTypeOS, result.Type)
25-
assert.Equal(t, "rhel-9", result.Stream)
26-
}
12+
const (
13+
testCoreOSLabelStreamClass = "io.openshift.os.streamclass"
14+
testCoreOSLabelExtension = "io.openshift.os.extensions"
15+
testCoreOSLabelExtensionValue = "true"
16+
testCoreOSLabelBootc = "containers.bootc"
17+
testCoreOSLabelBootcValue1 = "1"
18+
testCoreOSLabelBootcValueTrue = "true"
19+
)
2720

28-
func TestImageStreamExtractorImpl_GetImageData_ExtensionsImage(t *testing.T) {
21+
func TestImageStreamExtractorImpl_GetImageData_ImageType(t *testing.T) {
2922
extractor := NewImageStreamExtractor()
3023

31-
labels := map[string]string{
32-
"io.openshift.os.streamclass": "rhel-9",
24+
tests := []struct {
25+
label string
26+
labelValue string
27+
imageType ImageType
28+
shouldMatch bool
29+
}{
30+
{
31+
label: testCoreOSLabelBootc,
32+
labelValue: testCoreOSLabelBootcValueTrue,
33+
shouldMatch: true,
34+
imageType: ImageTypeOS,
35+
},
36+
{
37+
label: testCoreOSLabelBootc,
38+
labelValue: testCoreOSLabelBootcValue1,
39+
shouldMatch: true,
40+
imageType: ImageTypeOS,
41+
},
42+
{
43+
label: testCoreOSLabelBootc,
44+
labelValue: "2",
45+
shouldMatch: false,
46+
},
47+
{
48+
label: testCoreOSLabelBootc,
49+
labelValue: "nope",
50+
shouldMatch: false,
51+
},
52+
{
53+
label: testCoreOSLabelExtension,
54+
labelValue: testCoreOSLabelExtensionValue,
55+
shouldMatch: true,
56+
imageType: ImageTypeExtensions,
57+
},
58+
{
59+
label: testCoreOSLabelExtension,
60+
shouldMatch: false,
61+
},
62+
{
63+
label: "do.not.match",
64+
shouldMatch: false,
65+
},
3366
}
3467

35-
result := extractor.GetImageData("quay.io/openshift/ext@sha256:def456", labels)
36-
37-
require.NotNil(t, result)
38-
assert.Equal(t, "quay.io/openshift/ext@sha256:def456", result.Image)
39-
assert.EqualValues(t, ImageTypeExtensions, result.Type)
40-
assert.Equal(t, "rhel-9", result.Stream)
68+
for _, tt := range tests {
69+
t.Run(tt.label, func(t *testing.T) {
70+
71+
result := extractor.GetImageData(
72+
"quay.io/openshift/os@sha256:abc123",
73+
map[string]string{
74+
testCoreOSLabelStreamClass: "rhel-9",
75+
tt.label: tt.labelValue,
76+
})
77+
if tt.shouldMatch {
78+
require.NotNil(t, result)
79+
assert.Equal(t, "quay.io/openshift/os@sha256:abc123", result.Image)
80+
assert.EqualValues(t, tt.imageType, result.Type)
81+
assert.Equal(t, "rhel-9", result.Stream)
82+
} else {
83+
assert.Nil(t, result)
84+
}
85+
})
86+
}
4187
}
4288

4389
func TestImageStreamExtractorImpl_GetImageData_MissingStreamLabel(t *testing.T) {

pkg/osimagestream/imagestream_source_test.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ const (
2525
testRHELCoreosTagName = "rhel-coreos-9.4"
2626
testRHELCoreosExtTagName = "rhel-coreos-extensions-9.4"
2727

28-
labelStreamClass = "io.openshift.os.streamclass"
29-
labelOSTreeLinux = "ostree.linux"
30-
31-
labelValuePresent = "present"
32-
3328
streamNameRHELCoreos = "rhel-coreos"
3429
)
3530

@@ -87,16 +82,17 @@ func TestImageStreamStreamSource_FetchStreams(t *testing.T) {
8782
Image: testRHELCoreosImage,
8883
InspectInfo: &types.ImageInspectInfo{
8984
Labels: map[string]string{
90-
labelStreamClass: streamNameRHELCoreos,
91-
labelOSTreeLinux: labelValuePresent,
85+
testCoreOSLabelStreamClass: streamNameRHELCoreos,
86+
testCoreOSLabelBootc: testCoreOSLabelBootcValue1,
9287
},
9388
},
9489
},
9590
{
9691
Image: testRHELCoreosExtensionsImage,
9792
InspectInfo: &types.ImageInspectInfo{
9893
Labels: map[string]string{
99-
labelStreamClass: streamNameRHELCoreos,
94+
testCoreOSLabelStreamClass: streamNameRHELCoreos,
95+
testCoreOSLabelExtension: testCoreOSLabelExtensionValue,
10096
},
10197
},
10298
},
@@ -151,16 +147,17 @@ func TestImageStreamStreamSource_FetchStreams(t *testing.T) {
151147
Image: "quay.io/openshift/custom-os:latest",
152148
InspectInfo: &types.ImageInspectInfo{
153149
Labels: map[string]string{
154-
labelStreamClass: "custom-stream",
150+
testCoreOSLabelStreamClass: "custom-stream",
151+
testCoreOSLabelBootc: testCoreOSLabelBootcValue1,
155152
},
156153
},
157154
},
158155
{
159156
Image: "quay.io/openshift/custom-os-extensions:latest",
160157
InspectInfo: &types.ImageInspectInfo{
161158
Labels: map[string]string{
162-
labelStreamClass: "custom-stream",
163-
labelOSTreeLinux: labelValuePresent,
159+
testCoreOSLabelStreamClass: "custom-stream",
160+
testCoreOSLabelExtension: testCoreOSLabelExtensionValue,
164161
},
165162
},
166163
},
@@ -205,16 +202,17 @@ func TestImageStreamStreamSource_FetchStreams(t *testing.T) {
205202
Image: testRHELCoreosImage,
206203
InspectInfo: &types.ImageInspectInfo{
207204
Labels: map[string]string{
208-
labelStreamClass: streamNameRHELCoreos,
205+
testCoreOSLabelStreamClass: streamNameRHELCoreos,
206+
testCoreOSLabelBootc: testCoreOSLabelBootcValue1,
209207
},
210208
},
211209
},
212210
{
213211
Image: testRHELCoreosExtensionsImage,
214212
InspectInfo: &types.ImageInspectInfo{
215213
Labels: map[string]string{
216-
labelStreamClass: streamNameRHELCoreos,
217-
labelOSTreeLinux: labelValuePresent,
214+
testCoreOSLabelStreamClass: streamNameRHELCoreos,
215+
testCoreOSLabelExtension: testCoreOSLabelExtensionValue,
218216
},
219217
},
220218
},
@@ -266,33 +264,35 @@ func TestImageStreamStreamSource_FetchStreams(t *testing.T) {
266264
Image: testRHELCoreosImage,
267265
InspectInfo: &types.ImageInspectInfo{
268266
Labels: map[string]string{
269-
labelStreamClass: streamNameRHELCoreos,
267+
testCoreOSLabelStreamClass: streamNameRHELCoreos,
268+
testCoreOSLabelBootc: testCoreOSLabelBootcValue1,
270269
},
271270
},
272271
},
273272
{
274273
Image: testRHELCoreosExtensionsImage,
275274
InspectInfo: &types.ImageInspectInfo{
276275
Labels: map[string]string{
277-
labelStreamClass: streamNameRHELCoreos,
278-
labelOSTreeLinux: labelValuePresent,
276+
testCoreOSLabelStreamClass: streamNameRHELCoreos,
277+
testCoreOSLabelExtension: testCoreOSLabelExtensionValue,
279278
},
280279
},
281280
},
282281
{
283282
Image: "quay.io/openshift/stream-coreos:10.0",
284283
InspectInfo: &types.ImageInspectInfo{
285284
Labels: map[string]string{
286-
labelStreamClass: "stream-coreos",
285+
testCoreOSLabelStreamClass: "stream-coreos",
286+
testCoreOSLabelBootc: testCoreOSLabelBootcValue1,
287287
},
288288
},
289289
},
290290
{
291291
Image: "quay.io/openshift/stream-coreos-extensions:10.0",
292292
InspectInfo: &types.ImageInspectInfo{
293293
Labels: map[string]string{
294-
labelStreamClass: "stream-coreos",
295-
labelOSTreeLinux: labelValuePresent,
294+
testCoreOSLabelStreamClass: "stream-coreos",
295+
testCoreOSLabelExtension: testCoreOSLabelExtensionValue,
296296
},
297297
},
298298
},

0 commit comments

Comments
 (0)