1
1
package experimental_e2e
2
2
3
3
import (
4
+ "context"
5
+ "fmt"
4
6
"os"
5
7
"testing"
8
+ "time"
6
9
10
+ "github.com/stretchr/testify/assert"
11
+ "github.com/stretchr/testify/require"
12
+ appsv1 "k8s.io/api/apps/v1"
13
+ corev1 "k8s.io/api/core/v1"
14
+ rbacv1 "k8s.io/api/rbac/v1"
7
15
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
16
+ apimeta "k8s.io/apimachinery/pkg/api/meta"
17
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
19
+ "k8s.io/apimachinery/pkg/runtime/schema"
20
+ "k8s.io/apimachinery/pkg/types"
8
21
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
22
+ "k8s.io/client-go/dynamic"
9
23
"k8s.io/client-go/rest"
24
+ "k8s.io/utils/ptr"
10
25
ctrl "sigs.k8s.io/controller-runtime"
11
26
"sigs.k8s.io/controller-runtime/pkg/client"
12
27
28
+ ocv1 "github.com/operator-framework/operator-controller/api/v1"
13
29
"github.com/operator-framework/operator-controller/internal/operator-controller/scheme"
14
30
"github.com/operator-framework/operator-controller/test/utils"
15
31
)
16
32
17
33
const (
18
34
artifactName = "operator-controller-experimental-e2e"
35
+ pollDuration = time .Minute
36
+ pollInterval = time .Second
19
37
)
20
38
21
39
var (
22
- cfg * rest.Config
23
- c client.Client
40
+ cfg * rest.Config
41
+ c client.Client
42
+ dynamicClient dynamic.Interface
24
43
)
25
44
26
45
func TestMain (m * testing.M ) {
@@ -31,10 +50,203 @@ func TestMain(m *testing.M) {
31
50
c , err = client .New (cfg , client.Options {Scheme : scheme .Scheme })
32
51
utilruntime .Must (err )
33
52
53
+ dynamicClient , err = dynamic .NewForConfig (cfg )
54
+ utilruntime .Must (err )
55
+
34
56
os .Exit (m .Run ())
35
57
}
36
58
37
59
func TestNoop (t * testing.T ) {
38
60
t .Log ("Running experimental-e2e tests" )
39
61
defer utils .CollectTestArtifacts (t , artifactName , c , cfg )
40
62
}
63
+
64
+ func TestWebhookSupport (t * testing.T ) {
65
+ t .Log ("Test support for bundles with webhooks" )
66
+ defer utils .CollectTestArtifacts (t , artifactName , c , cfg )
67
+
68
+ t .Log ("By creating install namespace, and necessary rbac resources" )
69
+ namespace := corev1.Namespace {
70
+ ObjectMeta : metav1.ObjectMeta {
71
+ Name : "webhook-operator" ,
72
+ },
73
+ }
74
+ require .NoError (t , c .Create (t .Context (), & namespace ))
75
+ t .Cleanup (func () {
76
+ require .NoError (t , c .Delete (context .Background (), & namespace ))
77
+ })
78
+
79
+ serviceAccount := corev1.ServiceAccount {
80
+ ObjectMeta : metav1.ObjectMeta {
81
+ Name : "webhook-operator-installer" ,
82
+ Namespace : namespace .GetName (),
83
+ },
84
+ }
85
+ require .NoError (t , c .Create (t .Context (), & serviceAccount ))
86
+ t .Cleanup (func () {
87
+ require .NoError (t , c .Delete (context .Background (), & serviceAccount ))
88
+ })
89
+
90
+ clusterRoleBinding := & rbacv1.ClusterRoleBinding {
91
+ ObjectMeta : metav1.ObjectMeta {
92
+ Name : "webhook-operator-installer" ,
93
+ },
94
+ Subjects : []rbacv1.Subject {
95
+ {
96
+ Kind : "ServiceAccount" ,
97
+ APIGroup : corev1 .GroupName ,
98
+ Name : serviceAccount .GetName (),
99
+ Namespace : serviceAccount .GetNamespace (),
100
+ },
101
+ },
102
+ RoleRef : rbacv1.RoleRef {
103
+ APIGroup : rbacv1 .GroupName ,
104
+ Kind : "ClusterRole" ,
105
+ Name : "cluster-admin" ,
106
+ },
107
+ }
108
+ require .NoError (t , c .Create (t .Context (), clusterRoleBinding ))
109
+ t .Cleanup (func () {
110
+ require .NoError (t , c .Delete (context .Background (), clusterRoleBinding ))
111
+ })
112
+
113
+ t .Log ("By creating the webhook-operator ClusterCatalog" )
114
+ extensionCatalog := & ocv1.ClusterCatalog {
115
+ ObjectMeta : metav1.ObjectMeta {
116
+ Name : "webhook-operator-catalog" ,
117
+ },
118
+ Spec : ocv1.ClusterCatalogSpec {
119
+ Source : ocv1.CatalogSource {
120
+ Type : ocv1 .SourceTypeImage ,
121
+ Image : & ocv1.ImageSource {
122
+ Ref : fmt .Sprintf ("%s/e2e/test-catalog:v1" , os .Getenv ("LOCAL_REGISTRY_HOST" )),
123
+ PollIntervalMinutes : ptr .To (1 ),
124
+ },
125
+ },
126
+ },
127
+ }
128
+ require .NoError (t , c .Create (t .Context (), extensionCatalog ))
129
+ t .Cleanup (func () {
130
+ require .NoError (t , c .Delete (context .Background (), extensionCatalog ))
131
+ })
132
+
133
+ t .Log ("By waiting for the catalog to serve its metadata" )
134
+ require .EventuallyWithT (t , func (ct * assert.CollectT ) {
135
+ assert .NoError (t , c .Get (context .Background (), types.NamespacedName {Name : extensionCatalog .GetName ()}, extensionCatalog ))
136
+ cond := apimeta .FindStatusCondition (extensionCatalog .Status .Conditions , ocv1 .TypeServing )
137
+ assert .NotNil (t , cond )
138
+ assert .Equal (t , metav1 .ConditionTrue , cond .Status )
139
+ assert .Equal (t , ocv1 .ReasonAvailable , cond .Reason )
140
+ }, pollDuration , pollInterval )
141
+
142
+ t .Log ("By installing the webhook-operator ClusterExtension" )
143
+ clusterExtension := & ocv1.ClusterExtension {
144
+ ObjectMeta : metav1.ObjectMeta {
145
+ Name : "webhook-operator-extension" ,
146
+ },
147
+ Spec : ocv1.ClusterExtensionSpec {
148
+ Source : ocv1.SourceConfig {
149
+ SourceType : "Catalog" ,
150
+ Catalog : & ocv1.CatalogFilter {
151
+ PackageName : "webhook-operator" ,
152
+ Selector : & metav1.LabelSelector {
153
+ MatchLabels : map [string ]string {"olm.operatorframework.io/metadata.name" : extensionCatalog .Name },
154
+ },
155
+ },
156
+ },
157
+ Namespace : namespace .GetName (),
158
+ ServiceAccount : ocv1.ServiceAccountReference {
159
+ Name : serviceAccount .GetName (),
160
+ },
161
+ },
162
+ }
163
+ require .NoError (t , c .Create (t .Context (), clusterExtension ))
164
+ t .Cleanup (func () {
165
+ require .NoError (t , c .Delete (context .Background (), clusterExtension ))
166
+ })
167
+
168
+ t .Log ("By waiting for webhook-operator extension to be installed successfully" )
169
+ require .EventuallyWithT (t , func (ct * assert.CollectT ) {
170
+ assert .NoError (ct , c .Get (t .Context (), types.NamespacedName {Name : clusterExtension .Name }, clusterExtension ))
171
+ cond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeInstalled )
172
+ if assert .NotNil (ct , cond ) {
173
+ assert .Equal (ct , metav1 .ConditionTrue , cond .Status )
174
+ assert .Equal (ct , ocv1 .ReasonSucceeded , cond .Reason )
175
+ assert .Contains (ct , cond .Message , "Installed bundle" )
176
+ assert .NotEmpty (ct , clusterExtension .Status .Install .Bundle )
177
+ }
178
+ }, pollDuration , pollInterval )
179
+
180
+ t .Log ("By waiting for webhook-operator deployment to be available" )
181
+ require .EventuallyWithT (t , func (ct * assert.CollectT ) {
182
+ deployment := & appsv1.Deployment {}
183
+ assert .NoError (ct , c .Get (t .Context (), types.NamespacedName {Namespace : namespace .GetName (), Name : "webhook-operator-webhook" }, deployment ))
184
+ available := false
185
+ for _ , cond := range deployment .Status .Conditions {
186
+ if cond .Type == appsv1 .DeploymentAvailable {
187
+ available = cond .Status == corev1 .ConditionTrue
188
+ }
189
+ }
190
+ assert .True (ct , available )
191
+ }, pollDuration , pollInterval )
192
+
193
+ v1Gvr := schema.GroupVersionResource {
194
+ Group : "webhook.operators.coreos.io" ,
195
+ Version : "v1" ,
196
+ Resource : "webhooktests" ,
197
+ }
198
+ v1Client := dynamicClient .Resource (v1Gvr ).Namespace (namespace .GetName ())
199
+
200
+ t .Log ("By checking an invalid CR is rejected by the validating webhook" )
201
+ obj := getWebhookOperatorResource ("invalid-test-cr" , namespace .GetName (), false )
202
+ _ , err := v1Client .Create (t .Context (), obj , metav1.CreateOptions {})
203
+ require .Error (t , err )
204
+ require .Contains (t , err .Error (), "Invalid value: false: Spec.Valid must be true" )
205
+
206
+ t .Log ("By checking a valid CR is mutated by the mutating webhook" )
207
+ obj = getWebhookOperatorResource ("valid-test-cr" , namespace .GetName (), true )
208
+ _ , err = v1Client .Create (t .Context (), obj , metav1.CreateOptions {})
209
+ require .NoError (t , err )
210
+ t .Cleanup (func () {
211
+ require .NoError (t , dynamicClient .Resource (v1Gvr ).Namespace (namespace .GetName ()).Delete (context .Background (), obj .GetName (), metav1.DeleteOptions {}))
212
+ })
213
+ res , err := v1Client .Get (t .Context (), obj .GetName (), metav1.GetOptions {})
214
+ require .NoError (t , err )
215
+ require .Equal (t , map [string ]interface {}{
216
+ "valid" : true ,
217
+ "mutate" : true ,
218
+ }, res .Object ["spec" ])
219
+
220
+ t .Log ("By checking a valid CR is converted to v2 by the conversion webhook" )
221
+ v2Gvr := schema.GroupVersionResource {
222
+ Group : "webhook.operators.coreos.io" ,
223
+ Version : "v2" ,
224
+ Resource : "webhooktests" ,
225
+ }
226
+ v2Client := dynamicClient .Resource (v2Gvr ).Namespace (namespace .GetName ())
227
+
228
+ res , err = v2Client .Get (t .Context (), obj .GetName (), metav1.GetOptions {})
229
+ require .NoError (t , err )
230
+ require .Equal (t , map [string ]interface {}{
231
+ "conversion" : map [string ]interface {}{
232
+ "valid" : true ,
233
+ "mutate" : true ,
234
+ },
235
+ }, res .Object ["spec" ])
236
+ }
237
+
238
+ func getWebhookOperatorResource (name string , namespace string , valid bool ) * unstructured.Unstructured {
239
+ return & unstructured.Unstructured {
240
+ Object : map [string ]interface {}{
241
+ "apiVersion" : "webhook.operators.coreos.io/v1" ,
242
+ "kind" : "webhooktests" ,
243
+ "metadata" : map [string ]interface {}{
244
+ "name" : name ,
245
+ "namespace" : namespace ,
246
+ },
247
+ "spec" : map [string ]interface {}{
248
+ "valid" : valid ,
249
+ },
250
+ },
251
+ }
252
+ }
0 commit comments