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,202 @@ 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+ defer utils .CollectTestArtifacts (t , artifactName , c , cfg )
66+
67+ t .Log ("By creating install namespace, and necessary rbac resources" )
68+ namespace := corev1.Namespace {
69+ ObjectMeta : metav1.ObjectMeta {
70+ Name : "webhook-operator" ,
71+ },
72+ }
73+ require .NoError (t , c .Create (t .Context (), & namespace ))
74+ t .Cleanup (func () {
75+ require .NoError (t , c .Delete (context .Background (), & namespace ))
76+ })
77+
78+ serviceAccount := corev1.ServiceAccount {
79+ ObjectMeta : metav1.ObjectMeta {
80+ Name : "webhook-operator-installer" ,
81+ Namespace : namespace .GetName (),
82+ },
83+ }
84+ require .NoError (t , c .Create (t .Context (), & serviceAccount ))
85+ t .Cleanup (func () {
86+ require .NoError (t , c .Delete (context .Background (), & serviceAccount ))
87+ })
88+
89+ clusterRoleBinding := & rbacv1.ClusterRoleBinding {
90+ ObjectMeta : metav1.ObjectMeta {
91+ Name : "webhook-operator-installer" ,
92+ },
93+ Subjects : []rbacv1.Subject {
94+ {
95+ Kind : "ServiceAccount" ,
96+ APIGroup : corev1 .GroupName ,
97+ Name : serviceAccount .GetName (),
98+ Namespace : serviceAccount .GetNamespace (),
99+ },
100+ },
101+ RoleRef : rbacv1.RoleRef {
102+ APIGroup : rbacv1 .GroupName ,
103+ Kind : "ClusterRole" ,
104+ Name : "cluster-admin" ,
105+ },
106+ }
107+ require .NoError (t , c .Create (t .Context (), clusterRoleBinding ))
108+ t .Cleanup (func () {
109+ require .NoError (t , c .Delete (context .Background (), clusterRoleBinding ))
110+ })
111+
112+ t .Log ("By creating the webhook-operator ClusterCatalog" )
113+ extensionCatalog := & ocv1.ClusterCatalog {
114+ ObjectMeta : metav1.ObjectMeta {
115+ Name : "webhook-operator-catalog" ,
116+ },
117+ Spec : ocv1.ClusterCatalogSpec {
118+ Source : ocv1.CatalogSource {
119+ Type : ocv1 .SourceTypeImage ,
120+ Image : & ocv1.ImageSource {
121+ Ref : fmt .Sprintf ("%s/e2e/webhook-operator-catalog:v1" , os .Getenv ("LOCAL_REGISTRY_HOST" )),
122+ PollIntervalMinutes : ptr .To (1 ),
123+ },
124+ },
125+ },
126+ }
127+ require .NoError (t , c .Create (t .Context (), extensionCatalog ))
128+ t .Cleanup (func () {
129+ require .NoError (t , c .Delete (context .Background (), extensionCatalog ))
130+ })
131+
132+ t .Log ("By waiting for the catalog to serve its metadata" )
133+ require .EventuallyWithT (t , func (ct * assert.CollectT ) {
134+ assert .NoError (t , c .Get (context .Background (), types.NamespacedName {Name : extensionCatalog .GetName ()}, extensionCatalog ))
135+ cond := apimeta .FindStatusCondition (extensionCatalog .Status .Conditions , ocv1 .TypeServing )
136+ assert .NotNil (t , cond )
137+ assert .Equal (t , metav1 .ConditionTrue , cond .Status )
138+ assert .Equal (t , ocv1 .ReasonAvailable , cond .Reason )
139+ }, pollDuration , pollInterval )
140+
141+ t .Log ("By installing the webhook-operator ClusterExtension" )
142+ clusterExtension := & ocv1.ClusterExtension {
143+ ObjectMeta : metav1.ObjectMeta {
144+ Name : "webhook-operator-extension" ,
145+ },
146+ Spec : ocv1.ClusterExtensionSpec {
147+ Source : ocv1.SourceConfig {
148+ SourceType : "Catalog" ,
149+ Catalog : & ocv1.CatalogFilter {
150+ PackageName : "webhook-operator" ,
151+ Selector : & metav1.LabelSelector {
152+ MatchLabels : map [string ]string {"olm.operatorframework.io/metadata.name" : extensionCatalog .Name },
153+ },
154+ },
155+ },
156+ Namespace : namespace .GetName (),
157+ ServiceAccount : ocv1.ServiceAccountReference {
158+ Name : serviceAccount .GetName (),
159+ },
160+ },
161+ }
162+ require .NoError (t , c .Create (t .Context (), clusterExtension ))
163+ t .Cleanup (func () {
164+ require .NoError (t , c .Delete (context .Background (), clusterExtension ))
165+ })
166+
167+ t .Log ("By waiting for webhook-operator extension to be installed successfully" )
168+ require .EventuallyWithT (t , func (ct * assert.CollectT ) {
169+ assert .NoError (ct , c .Get (t .Context (), types.NamespacedName {Name : clusterExtension .Name }, clusterExtension ))
170+ cond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeInstalled )
171+ if assert .NotNil (ct , cond ) {
172+ assert .Equal (ct , metav1 .ConditionTrue , cond .Status )
173+ assert .Equal (ct , ocv1 .ReasonSucceeded , cond .Reason )
174+ assert .Contains (ct , cond .Message , "Installed bundle" )
175+ assert .NotEmpty (ct , clusterExtension .Status .Install .Bundle )
176+ }
177+ }, pollDuration , pollInterval )
178+
179+ t .Log ("By waiting for webhook-operator deployment to be available" )
180+ require .EventuallyWithT (t , func (ct * assert.CollectT ) {
181+ deployment := & appsv1.Deployment {}
182+ assert .NoError (ct , c .Get (t .Context (), types.NamespacedName {Namespace : namespace .GetName (), Name : "webhook-operator-webhook" }, deployment ))
183+ available := false
184+ for _ , cond := range deployment .Status .Conditions {
185+ if cond .Type == appsv1 .DeploymentAvailable {
186+ available = cond .Status == corev1 .ConditionTrue
187+ }
188+ }
189+ assert .True (ct , available )
190+ }, pollDuration , pollInterval )
191+
192+ v1Gvr := schema.GroupVersionResource {
193+ Group : "webhook.operators.coreos.io" ,
194+ Version : "v1" ,
195+ Resource : "webhooktests" ,
196+ }
197+ v1Client := dynamicClient .Resource (v1Gvr ).Namespace (namespace .GetName ())
198+
199+ t .Log ("By checking an invalid CR is rejected by the validating webhook" )
200+ obj := getWebhookOperatorResource ("invalid-test-cr" , namespace .GetName (), false )
201+ _ , err := v1Client .Create (t .Context (), obj , metav1.CreateOptions {})
202+ require .Error (t , err )
203+ require .Contains (t , err .Error (), "Invalid value: false: Spec.Valid must be true" )
204+
205+ t .Log ("By checking a valid CR is mutated by the mutating webhook" )
206+ obj = getWebhookOperatorResource ("valid-test-cr" , namespace .GetName (), true )
207+ _ , err = v1Client .Create (t .Context (), obj , metav1.CreateOptions {})
208+ require .NoError (t , err )
209+ t .Cleanup (func () {
210+ require .NoError (t , dynamicClient .Resource (v1Gvr ).Namespace (namespace .GetName ()).Delete (context .Background (), obj .GetName (), metav1.DeleteOptions {}))
211+ })
212+ res , err := v1Client .Get (t .Context (), obj .GetName (), metav1.GetOptions {})
213+ require .NoError (t , err )
214+ require .Equal (t , map [string ]interface {}{
215+ "valid" : true ,
216+ "mutate" : true ,
217+ }, res .Object ["spec" ])
218+
219+ t .Log ("By checking a valid CR is converted to v2 by the conversion webhook" )
220+ v2Gvr := schema.GroupVersionResource {
221+ Group : "webhook.operators.coreos.io" ,
222+ Version : "v2" ,
223+ Resource : "webhooktests" ,
224+ }
225+ v2Client := dynamicClient .Resource (v2Gvr ).Namespace (namespace .GetName ())
226+
227+ res , err = v2Client .Get (t .Context (), obj .GetName (), metav1.GetOptions {})
228+ require .NoError (t , err )
229+ require .Equal (t , map [string ]interface {}{
230+ "conversion" : map [string ]interface {}{
231+ "valid" : true ,
232+ "mutate" : true ,
233+ },
234+ }, res .Object ["spec" ])
235+ }
236+
237+ func getWebhookOperatorResource (name string , namespace string , valid bool ) * unstructured.Unstructured {
238+ return & unstructured.Unstructured {
239+ Object : map [string ]interface {}{
240+ "apiVersion" : "webhook.operators.coreos.io/v1" ,
241+ "kind" : "webhooktests" ,
242+ "metadata" : map [string ]interface {}{
243+ "name" : name ,
244+ "namespace" : namespace ,
245+ },
246+ "spec" : map [string ]interface {}{
247+ "valid" : valid ,
248+ },
249+ },
250+ }
251+ }
0 commit comments