Skip to content

Commit

Permalink
Add editable operations
Browse files Browse the repository at this point in the history
  • Loading branch information
yiraeChristineKim authored and openshift-merge-bot[bot] committed Dec 13, 2023
1 parent 660bc20 commit 8ca4720
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 0 deletions.
5 changes: 5 additions & 0 deletions api/v1alpha1/gatekeeper_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ type WebhookConfig struct {
// +optional
Resources *corev1.ResourceRequirements `json:"resources,omitempty"`
// +optional
Operations []OperationType `json:"operations,omitempty"`
// +optional
DisabledBuiltins []string `json:"disabledBuiltins,omitempty"`
// +optional
// Sets the --log-mutations flag which enables logging of mutation events and errors. This defaults to Disabled.
Expand All @@ -155,6 +157,9 @@ type WebhookConfig struct {
MutationAnnotations *Mode `json:"mutationAnnotations,omitempty"`
}

// +kubebuilder:validation:Enum:=CONNECT;CREATE;UPDATE;DELETE;*
type OperationType admregv1.OperationType

// +kubebuilder:validation:Enum:=DEBUG;INFO;WARNING;ERROR
type LogLevelMode string

Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions bundle/manifests/operator.gatekeeper.sh_gatekeepers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,17 @@ spec:
"value". The requirements are ANDed.
type: object
type: object
operations:
items:
description: OperationType specifies an operation for a request.
enum:
- CONNECT
- CREATE
- UPDATE
- DELETE
- '*'
type: string
type: array
replicas:
format: int32
minimum: 0
Expand Down
11 changes: 11 additions & 0 deletions config/crd/bases/operator.gatekeeper.sh_gatekeepers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,17 @@ spec:
"value". The requirements are ANDed.
type: object
type: object
operations:
items:
description: OperationType specifies an operation for a request.
enum:
- CONNECT
- CREATE
- UPDATE
- DELETE
- '*'
type: string
type: array
replicas:
format: int32
minimum: 0
Expand Down
43 changes: 43 additions & 0 deletions controllers/gatekeeper_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/source"

"github.com/gatekeeper/gatekeeper-operator/api/v1alpha1"
operatorv1alpha1 "github.com/gatekeeper/gatekeeper-operator/api/v1alpha1"
"github.com/gatekeeper/gatekeeper-operator/controllers/merge"
"github.com/gatekeeper/gatekeeper-operator/pkg/platform"
Expand Down Expand Up @@ -670,6 +671,7 @@ func webhookOverrides(obj *unstructured.Unstructured, webhook *operatorv1alpha1.
return nil
}

// override common properties
func webhookConfigurationOverrides(
obj *unstructured.Unstructured,
webhook *operatorv1alpha1.WebhookConfig,
Expand All @@ -694,9 +696,15 @@ func webhookConfigurationOverrides(
return err
}
}

if err := setOperations(obj, webhook.Operations, webhookName); err != nil {
return err
}

if err := setNamespaceSelector(obj, webhook.NamespaceSelector, gatekeeperNamespace, webhookName); err != nil {
return err
}

} else if err := setNamespaceSelector(obj, nil, gatekeeperNamespace, webhookName); err != nil {
return err
}
Expand Down Expand Up @@ -1046,6 +1054,41 @@ func setNamespaceSelector(
return setWebhookConfigurationWithFn(obj, webhookName, setNamespaceSelectorFn)
}

func setOperations(
obj *unstructured.Unstructured, operations []v1alpha1.OperationType, webhookName string,
) error {
// If no operations is provided, no override for operations
if operations == nil {
return nil
}

setOperationsFn := func(webhook map[string]interface{}) error {
rules := webhook["rules"].([]interface{})
if len(rules) == 0 {
return nil
}

converted := make([]interface{}, 0, len(operations))
for _, op := range operations {
converted = append(converted, string(op))
}

for i, r := range rules {
firstRuleObj := r.(map[string]interface{})
firstRuleObj["operations"] = converted
rules[i] = firstRuleObj
}

if err := unstructured.SetNestedSlice(webhook, rules, "rules"); err != nil {
return errors.Wrapf(err, "Failed to set webhook operations")
}

return nil
}

return setWebhookConfigurationWithFn(obj, webhookName, setOperationsFn)
}

// Generic setters

func setAffinity(obj *unstructured.Unstructured, spec operatorv1alpha1.GatekeeperSpec) error {
Expand Down
46 changes: 46 additions & 0 deletions controllers/gatekeeper_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package controllers

import (
"os"
"reflect"
"testing"
"time"

Expand Down Expand Up @@ -1373,6 +1374,51 @@ func expectObjContainerArgument(g *WithT, containerName string, obj *unstructure
return g.Expect(args)
}

func TestWebhookOperations(t *testing.T) {
g := NewWithT(t)

operations := []operatorv1alpha1.OperationType{"CREATE", "UPDATE", "DELETE"}

gatekeeper := &operatorv1alpha1.Gatekeeper{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: operatorv1alpha1.GatekeeperSpec{
Webhook: &operatorv1alpha1.WebhookConfig{
Operations: operations,
},
},
}

// test WebhookOperations override
valObj, err := util.GetManifestObject(ValidatingWebhookConfiguration)
g.Expect(err).ToNot(HaveOccurred())
mutObj, err := util.GetManifestObject(MutatingWebhookConfiguration)
g.Expect(err).ToNot(HaveOccurred())

err = crOverrides(gatekeeper, ValidatingWebhookConfiguration, valObj, namespace, false, false)
g.Expect(err).ToNot(HaveOccurred())
overrideWebhookOperations(g, valObj, ValidatingWebhookConfiguration, operations)
err = crOverrides(gatekeeper, MutatingWebhookConfiguration, mutObj, namespace, false, false)
g.Expect(err).ToNot(HaveOccurred())
overrideWebhookOperations(g, mutObj, MutatingWebhookConfiguration, operations)
}

func overrideWebhookOperations(g *WithT, obj *unstructured.Unstructured, webhookName string, expected []operatorv1alpha1.OperationType) {
assertWebhooksWithFn(g, obj, func(webhook map[string]interface{}) {
if webhook["name"] == webhookName {
current, found, err := unstructured.NestedSlice(webhook, "rules")
g.Expect(err).ToNot(HaveOccurred())
g.Expect(found).To(BeTrue())

for _, rule := range current {
operations := rule.(map[string]interface{})["operations"].([]string)
g.Expect(reflect.DeepEqual(expected, operations)).To(BeTrue())
}
}
})
}

func getContainerArgumentsMap(g *WithT, containerName string, obj *unstructured.Unstructured) map[string]string {
argsMap := make(map[string]string)
args := getContainerArgumentsSlice(g, containerName, obj)
Expand Down
58 changes: 58 additions & 0 deletions test/e2e/gatekeeper_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,64 @@ var _ = Describe("Gatekeeper", func() {
auditDeployment, webhookDeployment = gatekeeperDeployments()
byCheckingMutationDisabled(auditDeployment, webhookDeployment)
})

It("Override Webhook operations with Create, Update, Delete, Connect", func() {
gatekeeper := &v1alpha1.Gatekeeper{
ObjectMeta: metav1.ObjectMeta{
Namespace: gatekeeperNamespace,
Name: "gatekeeper",
},
Spec: v1alpha1.GatekeeperSpec{
Webhook: &v1alpha1.WebhookConfig{
Operations: []v1alpha1.OperationType{
"CREATE", "UPDATE", "CONNECT", "DELETE",
},
},
},
}
Expect(K8sClient.Create(ctx, gatekeeper)).Should(Succeed())

By("Wait until new Deployments loaded")
gatekeeperDeployments()

By("ValidatingWebhookConfiguration Rules should have 4 operations")
validatingWebhookConfiguration := &admregv1.ValidatingWebhookConfiguration{}
Eventually(func(g Gomega) {
err := K8sClient.Get(ctx, validatingWebhookName, validatingWebhookConfiguration)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(validatingWebhookConfiguration.Webhooks[0].Rules[0].Operations).Should(HaveLen(4))
g.Expect(validatingWebhookConfiguration.Webhooks[1].Rules[0].Operations).Should(HaveLen(4))
}, timeout, pollInterval).Should(Succeed())

By("MutatingWebhookConfiguration Rules should have 4 operations")
mutatingWebhookConfiguration := &admregv1.MutatingWebhookConfiguration{}
Eventually(func(g Gomega) {
err := K8sClient.Get(ctx, mutatingWebhookName, mutatingWebhookConfiguration)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(mutatingWebhookConfiguration.Webhooks[0].Rules[0].Operations).Should(HaveLen(4))
}, timeout, pollInterval).Should(Succeed())

gatekeeper.Spec.Webhook.Operations = []v1alpha1.OperationType{"*"}
Expect(K8sClient.Update(ctx, gatekeeper)).Should(Succeed())

By("ValidatingWebhookConfiguration Rules should have 1 operations")
Eventually(func(g Gomega) {
err := K8sClient.Get(ctx, validatingWebhookName, validatingWebhookConfiguration)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(validatingWebhookConfiguration.Webhooks[0].Rules[0].Operations).Should(HaveLen(1))
g.Expect(validatingWebhookConfiguration.Webhooks[0].Rules[0].Operations[0]).Should(BeEquivalentTo("*"))
g.Expect(validatingWebhookConfiguration.Webhooks[1].Rules[0].Operations).Should(HaveLen(1))
g.Expect(validatingWebhookConfiguration.Webhooks[1].Rules[0].Operations[0]).Should(BeEquivalentTo("*"))
}, timeout*2, pollInterval).Should(Succeed())

By("MutatingWebhookConfiguration Rules should have 1 operations")
Eventually(func(g Gomega) {
err := K8sClient.Get(ctx, mutatingWebhookName, mutatingWebhookConfiguration)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(mutatingWebhookConfiguration.Webhooks[0].Rules[0].Operations).Should(HaveLen(1))
g.Expect(mutatingWebhookConfiguration.Webhooks[0].Rules[0].Operations[0]).Should(BeEquivalentTo("*"))
}, timeout, pollInterval).Should(Succeed())
})
})
})

Expand Down

0 comments on commit 8ca4720

Please sign in to comment.