11package experimental_e2e
22
33import (
4+ "context"
5+ "fmt"
46 "os"
57 "testing"
8+ "time"
69
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"
715 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"
821 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
22+ "k8s.io/client-go/dynamic"
923 "k8s.io/client-go/rest"
24+ "k8s.io/utils/ptr"
1025 ctrl "sigs.k8s.io/controller-runtime"
1126 "sigs.k8s.io/controller-runtime/pkg/client"
1227
28+ ocv1 "github.com/operator-framework/operator-controller/api/v1"
1329 "github.com/operator-framework/operator-controller/internal/operator-controller/scheme"
1430 "github.com/operator-framework/operator-controller/test/utils"
1531)
1632
1733const (
1834 artifactName = "operator-controller-experimental-e2e"
35+ pollDuration = time .Minute
36+ pollInterval = time .Second
1937)
2038
2139var (
22- cfg * rest.Config
23- c client.Client
40+ cfg * rest.Config
41+ c client.Client
42+ dynamicClient dynamic.Interface
2443)
2544
2645func TestMain (m * testing.M ) {
@@ -31,10 +50,203 @@ func TestMain(m *testing.M) {
3150 c , err = client .New (cfg , client.Options {Scheme : scheme .Scheme })
3251 utilruntime .Must (err )
3352
53+ dynamicClient , err = dynamic .NewForConfig (cfg )
54+ utilruntime .Must (err )
55+
3456 os .Exit (m .Run ())
3557}
3658
3759func TestNoop (t * testing.T ) {
3860 t .Log ("Running experimental-e2e tests" )
3961 defer utils .CollectTestArtifacts (t , artifactName , c , cfg )
4062}
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