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