Skip to content

Commit 12d6f38

Browse files
author
Per Goncalves da Silva
committed
Add basic webhook support e2e
Signed-off-by: Per Goncalves da Silva <[email protected]>
1 parent 850e4a1 commit 12d6f38

File tree

1 file changed

+215
-5
lines changed

1 file changed

+215
-5
lines changed
Lines changed: 215 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,44 @@
11
package experimental_e2e
22

33
import (
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

1732
const (
1833
artifactName = "operator-controller-experimental-e2e"
34+
pollDuration = time.Minute
35+
pollInterval = time.Second
1936
)
2037

2138
var (
22-
cfg *rest.Config
23-
c client.Client
39+
cfg *rest.Config
40+
c client.Client
41+
dynamicClient dynamic.Interface
2442
)
2543

2644
func 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

3758
func 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

Comments
 (0)