Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions pkg/webhook/bus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2018 The Knative Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package webhook

import (
"context"
"errors"
"fmt"
"strings"

"github.com/golang/glog"
"github.com/knative/eventing/pkg/apis/channels/v1alpha1"
"github.com/mattbaird/jsonpatch"
"k8s.io/apimachinery/pkg/util/validation"
)

var (
errInvalidBusInput = errors.New("failed to convert input into Bus or ClusterBus")
errInternalNilBus = errors.New("unexpected internal error: nil Bus or ClusterBus")
)

// ValidateBus is Bus resource specific validation and mutation handler
func ValidateBus(ctx context.Context) ResourceCallback {
return func(patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error {
oldBus, newBus, err := unmarshalBuses(ctx, old, new, "ValidateBus")
if err != nil {
return err
}

return validateBus(oldBus, newBus)
}
}

func validateBus(old, new v1alpha1.GenericBus) error {
if new.GetSpec().Parameters != nil {
if new.GetSpec().Parameters.Channel != nil {
for _, p := range *new.GetSpec().Parameters.Channel {
errs := validation.IsConfigMapKey(p.Name)
if len(errs) > 0 {
return fmt.Errorf("invalid parameter name Spec.Parameters.Channel.%s: %s", p.Name,
strings.Join(errs, ", "))
}
}
}
if new.GetSpec().Parameters.Subscription != nil {
for _, p := range *new.GetSpec().Parameters.Subscription {
errs := validation.IsConfigMapKey(p.Name)
if len(errs) > 0 {
return fmt.Errorf("invalid parameter name Spec.Parameters.Subscription.%s: %s", p.Name,
strings.Join(errs, ", "))
}
}
}
}
return nil
}

func unmarshalBuses(
ctx context.Context, old, new GenericCRD, fnName string) (v1alpha1.GenericBus, v1alpha1.GenericBus, error) {
var oldBus v1alpha1.GenericBus
if old != nil {
var ok bool
oldBus, ok = old.(v1alpha1.GenericBus)
if !ok {
return nil, nil, errInvalidBusInput
}
}
glog.Infof("%s: OLD Bus is\n%+v", fnName, oldBus)

if new == nil {
return nil, nil, errInternalNilBus
}
newBus, ok := new.(v1alpha1.GenericBus)
if !ok {
return nil, nil, errInvalidBusInput
}
glog.Infof("%s: NEW Bus is\n%+v", fnName, newBus)

return oldBus, newBus, nil
}
12 changes: 10 additions & 2 deletions pkg/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import (
admissionv1beta1 "k8s.io/api/admission/v1beta1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
corev1 "k8s.io/api/core/v1"
v1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/api/extensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -202,6 +202,14 @@ func NewAdmissionController(client kubernetes.Interface, options ControllerOptio
client: client,
options: options,
handlers: map[string]GenericCRDHandler{
"Bus": {
Factory: &v1alpha1.Bus{},
Validator: ValidateBus(ctx),
},
"ClusterBus": {
Factory: &v1alpha1.ClusterBus{},
Validator: ValidateBus(ctx),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ValidateBus looks like it will only work for Bus and not ClusterBus. There is a GenericBus interface that is implemented by both bus types.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, correct.

BTW, why isn't ClusterBus a type alias of Bus?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bus and ClusterBus structs share a common spec, but are different types. The GenericBus interface has a method to get the spec, which you can then validate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize they're different (conceptual) types. But we leveraged type aliases for some of the parts that they share (Spec) but not the rest, hence my question

},
"Channel": {
Factory: &v1alpha1.Channel{},
Validator: ValidateChannel(ctx),
Expand Down Expand Up @@ -290,7 +298,7 @@ func (ac *AdmissionController) unregister(

func (ac *AdmissionController) register(
ctx context.Context, client clientadmissionregistrationv1beta1.MutatingWebhookConfigurationInterface, caCert []byte) error { // nolint: lll
resources := []string{"channels", "subscriptions"}
resources := []string{"buses", "clusterbuses", "channels", "subscriptions"}

webhook := &admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Expand Down
118 changes: 118 additions & 0 deletions pkg/webhook/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/mattbaird/jsonpatch"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
"k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
fakekubeclientset "k8s.io/client-go/kubernetes/fake"
Expand Down Expand Up @@ -127,6 +128,87 @@ func TestUnknownKindFails(t *testing.T) {
expectFailsWith(t, ac.admit(testCtx, &req), "unhandled kind")
}

func TestValidBusParameterNamePasses(t *testing.T) {
_, ac := newNonRunningTestAdmissionController(t, newDefaultOptions())
req := &admissionv1beta1.AdmissionRequest{
Operation: admissionv1beta1.Create,
Kind: metav1.GroupVersionKind{Kind: "Bus"},
}
validName := "ok_param.name"
bus := createBus(testBusName, "foobar/dispatcher")
bus.Spec.Parameters.Subscription = &[]v1alpha1.Parameter{{Name: validName}}
marshaled, err := json.Marshal(bus)
if err != nil {
t.Fatalf("Failed to marshal bus: %s", err)
}
req.Object.Raw = marshaled
expectAllowed(t, ac.admit(testCtx, req))

validName = "simple-name"
bus = createBus(testBusName, "foobar/dispatcher")
bus.Spec.Parameters.Channel = &[]v1alpha1.Parameter{{Name: validName}}
marshaled, err = json.Marshal(bus)
if err != nil {
t.Fatalf("Failed to marshal bus: %s", err)
}
req.Object.Raw = marshaled
expectAllowed(t, ac.admit(testCtx, req))
}

func TestInvalidBusParameterNameFails(t *testing.T) {
_, ac := newNonRunningTestAdmissionController(t, newDefaultOptions())
req := &admissionv1beta1.AdmissionRequest{
Operation: admissionv1beta1.Create,
Kind: metav1.GroupVersionKind{Kind: "Bus"},
}
invalidName := "paramètre"
bus := createBus(testBusName, "foobar/dispatcher")
bus.Spec.Parameters.Subscription = &[]v1alpha1.Parameter{{Name: invalidName}}
marshaled, err := json.Marshal(bus)
if err != nil {
t.Fatalf("Failed to marshal bus: %s", err)
}
req.Object.Raw = marshaled
expectFailsWith(t, ac.admit(testCtx, req), "invalid parameter name Spec.Parameters.Subscription.paramètre")

invalidName = "param/name"
bus = createBus(testBusName, "foobar/dispatcher")
bus.Spec.Parameters.Channel = &[]v1alpha1.Parameter{{Name: invalidName}}
marshaled, err = json.Marshal(bus)
if err != nil {
t.Fatalf("Failed to marshal bus: %s", err)
}
req.Object.Raw = marshaled
expectFailsWith(t, ac.admit(testCtx, req), "invalid parameter name Spec.Parameters.Channel.param/name")
}

func TestInvalidClusterBusParameterNameFails(t *testing.T) {
_, ac := newNonRunningTestAdmissionController(t, newDefaultOptions())
req := &admissionv1beta1.AdmissionRequest{
Operation: admissionv1beta1.Create,
Kind: metav1.GroupVersionKind{Kind: "ClusterBus"},
}
invalidName := "paramètre"
bus := createClusterBus(testBusName, "foobar/dispatcher")
bus.Spec.Parameters.Subscription = &[]v1alpha1.Parameter{{Name: invalidName}}
marshaled, err := json.Marshal(bus)
if err != nil {
t.Fatalf("Failed to marshal bus: %s", err)
}
req.Object.Raw = marshaled
expectFailsWith(t, ac.admit(testCtx, req), "invalid parameter name Spec.Parameters.Subscription.paramètre")

invalidName = "param/name"
bus = createClusterBus(testBusName, "foobar/dispatcher")
bus.Spec.Parameters.Channel = &[]v1alpha1.Parameter{{Name: invalidName}}
marshaled, err = json.Marshal(bus)
if err != nil {
t.Fatalf("Failed to marshal bus: %s", err)
}
req.Object.Raw = marshaled
expectFailsWith(t, ac.admit(testCtx, req), "invalid parameter name Spec.Parameters.Channel.param/name")
}

func TestInvalidNewChannelNameFails(t *testing.T) {
_, ac := newNonRunningTestAdmissionController(t, newDefaultOptions())
req := &admissionv1beta1.AdmissionRequest{
Expand Down Expand Up @@ -319,6 +401,42 @@ func expectPatches(t *testing.T, a []byte, e []jsonpatch.JsonPatchOperation) {
}
}

func createBus(busName string, dispatcherImage string) v1alpha1.Bus {
return v1alpha1.Bus{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: busName,
},
Spec: v1alpha1.BusSpec{
Dispatcher: v1.Container{
Image: dispatcherImage,
},
Parameters: &v1alpha1.BusParameters{
Channel: &[]v1alpha1.Parameter{},
Subscription: &[]v1alpha1.Parameter{},
},
},
}
}

func createClusterBus(busName string, dispatcherImage string) v1alpha1.ClusterBus {
return v1alpha1.ClusterBus{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: busName,
},
Spec: v1alpha1.BusSpec{
Dispatcher: v1.Container{
Image: dispatcherImage,
},
Parameters: &v1alpha1.BusParameters{
Channel: &[]v1alpha1.Parameter{},
Subscription: &[]v1alpha1.Parameter{},
},
},
}
}

func createBaseUpdateChannel() *admissionv1beta1.AdmissionRequest {
return &admissionv1beta1.AdmissionRequest{
Operation: admissionv1beta1.Update,
Expand Down