Skip to content

Commit d26c5a6

Browse files
author
Per Goncalves da Silva
committed
Add basic webhook support e2e
Signed-off-by: Per Goncalves da Silva <[email protected]>
1 parent 5f5142d commit d26c5a6

File tree

1 file changed

+213
-2
lines changed

1 file changed

+213
-2
lines changed

test/experimental-e2e/experimental_e2e_test.go

Lines changed: 213 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,45 @@
11
package experimental_e2e
22

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

1733
const (
1834
artifactName = "operator-controller-experimental-e2e"
35+
pollDuration = time.Minute
36+
pollInterval = time.Second
1937
)
2038

2139
var (
22-
cfg *rest.Config
23-
c client.Client
40+
cfg *rest.Config
41+
c client.Client
42+
dynamicClient dynamic.Interface
2443
)
2544

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

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

Comments
 (0)