@@ -25,6 +25,7 @@ import (
25
25
corev1 "k8s.io/api/core/v1"
26
26
apierrs "k8s.io/apimachinery/pkg/api/errors"
27
27
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28
+
28
29
"sigs.k8s.io/controller-runtime/pkg/client"
29
30
30
31
. "github.com/onsi/ginkgo"
@@ -48,20 +49,70 @@ var _ = Describe("The Openshift Notebook webhook", func() {
48
49
}
49
50
})
50
51
52
+ var newImageStream = func (name , namespace , tag , dockerImageReference string ) * imagev1.ImageStream {
53
+ return & imagev1.ImageStream {
54
+ TypeMeta : metav1.TypeMeta {
55
+ Kind : "ImageStream" ,
56
+ APIVersion : "image.openshift.io/v1" ,
57
+ },
58
+ ObjectMeta : metav1.ObjectMeta {
59
+ Name : name ,
60
+ Namespace : namespace ,
61
+ },
62
+ Spec : imagev1.ImageStreamSpec {
63
+ LookupPolicy : imagev1.ImageLookupPolicy {
64
+ Local : true ,
65
+ },
66
+ },
67
+ Status : imagev1.ImageStreamStatus {
68
+ Tags : []imagev1.NamedTagEventList {{
69
+ Tag : tag ,
70
+ Items : []imagev1.TagEvent {{
71
+ DockerImageReference : dockerImageReference ,
72
+ }},
73
+ }},
74
+ },
75
+ }
76
+ }
77
+
78
+ var newNotebook = func (annotations map [string ]string , initialImage string ) * nbv1.Notebook {
79
+ return & nbv1.Notebook {
80
+ ObjectMeta : metav1.ObjectMeta {
81
+ Name : Name ,
82
+ Namespace : Namespace ,
83
+ Annotations : annotations ,
84
+ },
85
+ Spec : nbv1.NotebookSpec {
86
+ Template : nbv1.NotebookTemplateSpec {
87
+ Spec : corev1.PodSpec {Containers : []corev1.Container {{
88
+ Name : Name ,
89
+ Image : initialImage ,
90
+ }}},
91
+ },
92
+ },
93
+ }
94
+ }
95
+
96
+ // https://go.dev/wiki/TableDrivenTests
51
97
testCases := []struct {
52
- name string
53
- imageStream * imagev1.ImageStream
54
- notebook * nbv1.Notebook
98
+ name string
99
+
100
+ imageStreams []* imagev1.ImageStream
101
+ notebook * nbv1.Notebook
102
+
55
103
// currently we expect that Notebook CR is always created,
56
- // and when unable to resolve imagestream , image: is left alone
104
+ // and when unable to resolve ImageStream , image: is left alone
57
105
expectedImage string
58
106
// see https://www.youtube.com/watch?v=prLRI3VEVq4 for Observability Driven Development intro
59
107
expectedEvents []string
60
108
unexpectedEvents []string
61
109
}{
62
110
{
63
- name : "ImageStream with all that is needful" ,
64
- imageStream : & imagev1.ImageStream {
111
+ name : "ImageStream with all that is needful is correctly resolved" ,
112
+
113
+ // The first test case does not use the CR creation helpers
114
+ // so that everything going in is fully visible
115
+ imageStreams : []* imagev1.ImageStream {{
65
116
TypeMeta : metav1.TypeMeta {
66
117
Kind : "ImageStream" ,
67
118
APIVersion : "image.openshift.io/v1" ,
@@ -92,7 +143,7 @@ var _ = Describe("The Openshift Notebook webhook", func() {
92
143
}},
93
144
}},
94
145
},
95
- },
146
+ }} ,
96
147
notebook : & nbv1.Notebook {
97
148
ObjectMeta : metav1.ObjectMeta {
98
149
Name : Name ,
@@ -111,14 +162,16 @@ var _ = Describe("The Openshift Notebook webhook", func() {
111
162
}}}},
112
163
},
113
164
},
165
+
114
166
expectedImage : "quay.io/modh/odh-generic-data-science-notebook@sha256:76e6af79c601a323f75a58e7005de0beac66b8cccc3d2b67efb6d11d85f0cfa1" ,
115
167
unexpectedEvents : []string {
116
168
IMAGE_STREAM_NOT_FOUND_EVENT ,
117
169
},
118
170
},
119
171
{
120
172
name : "ImageStream with a tag without items (RHOAIENG-13916)" ,
121
- imageStream : & imagev1.ImageStream {
173
+
174
+ imageStreams : []* imagev1.ImageStream {{
122
175
TypeMeta : metav1.TypeMeta {
123
176
Kind : "ImageStream" ,
124
177
APIVersion : "image.openshift.io/v1" ,
@@ -144,86 +197,96 @@ var _ = Describe("The Openshift Notebook webhook", func() {
144
197
}},
145
198
}},
146
199
},
147
- },
148
- notebook : & nbv1.Notebook {
149
- ObjectMeta : metav1.ObjectMeta {
150
- Name : Name ,
151
- Namespace : Namespace ,
152
- Annotations : map [string ]string {
153
- "notebooks.opendatahub.io/last-image-selection" : "some-image:some-tag" ,
154
- },
155
- },
156
- Spec : nbv1.NotebookSpec {
157
- Template : nbv1.NotebookTemplateSpec {
158
- Spec : corev1.PodSpec {Containers : []corev1.Container {{
159
- Name : Name ,
160
- Image : ":some-tag" ,
161
- }}}},
162
- },
163
- },
200
+ }},
201
+ notebook : newNotebook (map [string ]string {
202
+ "notebooks.opendatahub.io/last-image-selection" : "some-image:some-tag" ,
203
+ }, ":some-tag" ),
204
+
164
205
// there is no update to the Notebook
165
206
expectedImage : ":some-tag" ,
166
207
expectedEvents : []string {
167
208
IMAGE_STREAM_TAG_NOT_FOUND_EVENT ,
168
209
},
169
210
},
211
+ //
212
+ // Tests for RHOAIENG-23609 project-scoped workbench imagestreams feature,
213
+ // tasks RHOAIENG-23765, and RHOAIENG-25707
214
+ //
170
215
{
171
216
name : "ImageStream in the same namespace as the Notebook" ,
172
- imageStream : & imagev1.ImageStream {
173
- TypeMeta : metav1.TypeMeta {
174
- Kind : "ImageStream" ,
175
- APIVersion : "image.openshift.io/v1" ,
176
- },
177
- ObjectMeta : metav1.ObjectMeta {
178
- Name : "some-image" ,
179
- Namespace : Namespace ,
180
- },
181
- Spec : imagev1.ImageStreamSpec {
182
- LookupPolicy : imagev1.ImageLookupPolicy {
183
- Local : true ,
184
- },
185
- },
186
- Status : imagev1.ImageStreamStatus {
187
- Tags : []imagev1.NamedTagEventList {{
188
- Tag : "some-tag" ,
189
- Items : []imagev1.TagEvent {{
190
- Created : toMetav1Time ("2025-05-14T02:36:21Z" ),
191
- DockerImageReference : "quay.io/modh/odh-generic-data-science-notebook@sha256:5999547f847ca841fe067ff84e2972d2cbae598066c2418e236448e115c1728e" ,
192
- Image : "sha256:5999547f847ca841fe067ff84e2972d2cbae598066c2418e236448e115c1728e" ,
193
- Generation : 2 ,
194
- }},
195
- Conditions : []imagev1.TagEventCondition {{
196
- Type : "ImportSuccess" ,
197
- Status : "False" ,
198
- LastTransitionTime : toMetav1Time ("2025-05-14T02:36:21Z" ),
199
- Reason : "NotFound" ,
200
- }},
201
- }},
202
- },
203
- },
204
- notebook : & nbv1.Notebook {
205
- ObjectMeta : metav1.ObjectMeta {
206
- Name : Name ,
207
- Namespace : Namespace ,
208
- Annotations : map [string ]string {
209
- "notebooks.opendatahub.io/last-image-selection" : "some-image:some-tag" ,
210
- "opendatahub.io/workbench-image-namespace" : Namespace ,
211
- },
212
- },
213
- Spec : nbv1.NotebookSpec {
214
- Template : nbv1.NotebookTemplateSpec {
215
- Spec : corev1.PodSpec {Containers : []corev1.Container {{
216
- Name : Name ,
217
- Image : ":some-tag" ,
218
- }}}},
219
- },
217
+
218
+ imageStreams : []* imagev1.ImageStream {
219
+ newImageStream ("some-image" , Namespace , "some-tag" , "quay.io/modh/odh-generic-data-science-notebook@sha256:5999547f847ca841fe067ff84e2972d2cbae598066c2418e236448e115c1728e" ),
220
220
},
221
- // valid new image is picked from user namespace.
221
+ notebook : newNotebook (map [string ]string {
222
+ "notebooks.opendatahub.io/last-image-selection" : "some-image:some-tag" ,
223
+ "opendatahub.io/workbench-image-namespace" : Namespace ,
224
+ }, ":some-tag" ),
225
+
222
226
expectedImage : "quay.io/modh/odh-generic-data-science-notebook@sha256:5999547f847ca841fe067ff84e2972d2cbae598066c2418e236448e115c1728e" ,
223
227
unexpectedEvents : []string {
224
228
IMAGE_STREAM_NOT_FOUND_EVENT ,
225
229
},
226
230
},
231
+ //
232
+ // The following test cases require
233
+ // multiple ImageStreams to be created.
234
+ //
235
+ {
236
+ name : "last-image-selection is specified, workbench-image-namespace is also specified" ,
237
+
238
+ imageStreams : []* imagev1.ImageStream {
239
+ newImageStream ("some-image" , Namespace , "some-tag" , "quay.io/workbench-namespace-imagestream:latest" ),
240
+ newImageStream ("some-image" , odhNotebookControllerTestNamespace , "some-tag" , "quay.io/operator-namespace-imagestream:latest" ),
241
+ },
242
+ notebook : newNotebook (map [string ]string {
243
+ "notebooks.opendatahub.io/last-image-selection" : "some-image:some-tag" ,
244
+ "opendatahub.io/workbench-image-namespace" : Namespace ,
245
+ }, ":some-tag" ),
246
+
247
+ // image is picked from notebook namespace
248
+ expectedImage : "quay.io/workbench-namespace-imagestream:latest" ,
249
+ unexpectedEvents : []string {
250
+ IMAGE_STREAM_NOT_FOUND_EVENT ,
251
+ },
252
+ },
253
+ {
254
+ name : "last-image-selection is specified, workbench-image-namespace is not specified" ,
255
+
256
+ imageStreams : []* imagev1.ImageStream {
257
+ newImageStream ("some-image" , Namespace , "some-tag" , "quay.io/workbench-namespace-imagestream:latest" ),
258
+ newImageStream ("some-image" , odhNotebookControllerTestNamespace , "some-tag" , "quay.io/operator-namespace-imagestream:latest" ),
259
+ },
260
+ notebook : newNotebook (map [string ]string {
261
+ "notebooks.opendatahub.io/last-image-selection" : "some-image:some-tag" ,
262
+ "opendatahub.io/workbench-image-namespace" : "" ,
263
+ }, ":some-tag" ),
264
+
265
+ // image is picked from operator namespace
266
+ expectedImage : "quay.io/operator-namespace-imagestream:latest" ,
267
+ unexpectedEvents : []string {
268
+ IMAGE_STREAM_NOT_FOUND_EVENT ,
269
+ },
270
+ },
271
+ //
272
+ // Something that Dashboard should never do, but we want to check it nevertheless
273
+ //
274
+ {
275
+ name : "last-image-selection is not specified" ,
276
+
277
+ imageStreams : []* imagev1.ImageStream {
278
+ newImageStream ("some-image" , Namespace , "some-tag" , "quay.io/modh/odh-generic-data-science-notebook@sha256:5999547f847ca841fe067ff84e2972d2cbae598066c2418e236448e115c1728e" ),
279
+ },
280
+ notebook : newNotebook (map [string ]string {
281
+ "opendatahub.io/workbench-image-namespace" : Namespace ,
282
+ }, ":some-tag" ),
283
+
284
+ // there is no update to the Notebook
285
+ expectedImage : ":some-tag" ,
286
+ unexpectedEvents : []string {
287
+ IMAGE_STREAM_NOT_FOUND_EVENT ,
288
+ },
289
+ },
227
290
}
228
291
229
292
BeforeEach (func () {
@@ -233,10 +296,15 @@ var _ = Describe("The Openshift Notebook webhook", func() {
233
296
234
297
for _ , testCase := range testCases {
235
298
Context (fmt .Sprintf ("The Notebook webhook test case: %s" , testCase .name ), func () {
299
+ BeforeEach (func () {
300
+ By ("Creating the requisite ImageStream resources successfully" )
301
+ for _ , imageStream := range testCase .imageStreams {
302
+ Expect (cli .Create (ctx , imageStream , & client.CreateOptions {})).To (Succeed ())
303
+ }
304
+ })
236
305
It ("Should create a Notebook resource successfully" , func () {
237
- By ("Creating a Notebook resource successfully" )
238
- Expect (cli .Create (ctx , testCase .imageStream , & client.CreateOptions {})).To (Succeed ())
239
306
// if our webhook panics, then cli.Create will err
307
+ By ("Creating a Notebook resource successfully" )
240
308
Expect (cli .Create (ctx , testCase .notebook , & client.CreateOptions {})).To (Succeed ())
241
309
242
310
By ("Checking that the webhook modified the notebook CR with the expected image" )
@@ -259,7 +327,9 @@ var _ = Describe("The Openshift Notebook webhook", func() {
259
327
AfterEach (func () {
260
328
By ("Deleting the created resources" )
261
329
Expect (cli .Delete (ctx , testCase .notebook , & client.DeleteOptions {})).To (Succeed ())
262
- Expect (cli .Delete (ctx , testCase .imageStream , & client.DeleteOptions {})).To (Succeed ())
330
+ for _ , imageStream := range testCase .imageStreams {
331
+ Expect (cli .Delete (ctx , imageStream , & client.DeleteOptions {})).To (Succeed ())
332
+ }
263
333
})
264
334
})
265
335
}
@@ -268,8 +338,6 @@ var _ = Describe("The Openshift Notebook webhook", func() {
268
338
269
339
func toMetav1Time (timeString string ) metav1.Time {
270
340
parsedTime , err := time .Parse (time .RFC3339 , timeString )
271
- if err != nil {
272
- return metav1.Time {}
273
- }
341
+ Expect (err ).ToNot (HaveOccurred ())
274
342
return metav1 .NewTime (parsedTime )
275
343
}
0 commit comments