-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementing workload webhook for auto-annotation (#73)
- Loading branch information
1 parent
b1767cc
commit cf477d2
Showing
7 changed files
with
301 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ rules: | |
- namespaces | ||
verbs: | ||
- list | ||
- update | ||
- watch | ||
- apiGroups: | ||
- apps | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
// Package workloadmutation contains the webhook that injects annotations into daemon-sets, deployments and stateful-sets. | ||
package workloadmutation | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"net/http" | ||
|
||
"github.com/go-logr/logr" | ||
appsv1 "k8s.io/api/apps/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission" | ||
|
||
"github.com/aws/amazon-cloudwatch-agent-operator/internal/config" | ||
"github.com/aws/amazon-cloudwatch-agent-operator/pkg/instrumentation/auto" | ||
) | ||
|
||
// +kubebuilder:webhook:path=/mutate-v1-workload,mutating=true,failurePolicy=ignore,groups="apps",resources=daemonsets;deployments;statefulsets,verbs=create;update,versions=v1,name=mworkload.kb.io,sideEffects=none,admissionReviewVersions=v1 | ||
// +kubebuilder:rbac:groups="apps",resources=daemonsets;deployments;statefulsets,verbs=get;list;watch | ||
|
||
var _ WebhookHandler = (*workloadMutationWebhook)(nil) | ||
|
||
// WebhookHandler is a webhook handler that analyzes new daemon-sets and injects appropriate annotations into it. | ||
type WebhookHandler interface { | ||
admission.Handler | ||
} | ||
|
||
// the implementation. | ||
type workloadMutationWebhook struct { | ||
client client.Client | ||
decoder *admission.Decoder | ||
logger logr.Logger | ||
config config.Config | ||
annotationMutator *auto.AnnotationMutators | ||
} | ||
|
||
// NewWebhookHandler creates a new WorkloadWebhookHandler. | ||
func NewWebhookHandler(cfg config.Config, logger logr.Logger, decoder *admission.Decoder, cl client.Client, annotationMutation *auto.AnnotationMutators) WebhookHandler { | ||
return &workloadMutationWebhook{ | ||
config: cfg, | ||
decoder: decoder, | ||
logger: logger, | ||
client: cl, | ||
annotationMutator: annotationMutation, | ||
} | ||
} | ||
|
||
func (p *workloadMutationWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { | ||
var err error | ||
var marshaledObject []byte | ||
var object runtime.Object | ||
switch objectKind := req.Kind.Kind; objectKind { | ||
case "DaemonSet": | ||
ds := appsv1.DaemonSet{} | ||
err = p.decoder.Decode(req, &ds) | ||
if err != nil { | ||
return admission.Errored(http.StatusBadRequest, err) | ||
} | ||
object = &ds | ||
case "Deployment": | ||
d := appsv1.Deployment{} | ||
err = p.decoder.Decode(req, &d) | ||
if err != nil { | ||
return admission.Errored(http.StatusBadRequest, err) | ||
} | ||
object = &d | ||
case "StatefulSet": | ||
ss := appsv1.StatefulSet{} | ||
err = p.decoder.Decode(req, &ss) | ||
if err != nil { | ||
return admission.Errored(http.StatusBadRequest, err) | ||
} | ||
object = &ss | ||
default: | ||
return admission.Errored(http.StatusBadRequest, errors.New("failed to unmarshal request object")) | ||
} | ||
|
||
p.annotationMutator.Mutate(object) | ||
marshaledObject, err = json.Marshal(object) | ||
if err != nil { | ||
res := admission.Errored(http.StatusInternalServerError, err) | ||
res.Allowed = true | ||
return res | ||
} | ||
|
||
return admission.PatchResponseFromRaw(req.Object.Raw, marshaledObject) | ||
} |
149 changes: 149 additions & 0 deletions
149
internal/webhook/workloadmutation/webhookhandler_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package workloadmutation | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"github.com/stretchr/testify/require" | ||
admv1 "k8s.io/api/admission/v1" | ||
appsv1 "k8s.io/api/apps/v1" | ||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/go-logr/logr" | ||
"github.com/stretchr/testify/assert" | ||
"k8s.io/kubectl/pkg/scheme" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
logf "sigs.k8s.io/controller-runtime/pkg/log" | ||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission" | ||
|
||
"github.com/aws/amazon-cloudwatch-agent-operator/internal/config" | ||
"github.com/aws/amazon-cloudwatch-agent-operator/pkg/instrumentation" | ||
"github.com/aws/amazon-cloudwatch-agent-operator/pkg/instrumentation/auto" | ||
) | ||
|
||
var ( | ||
k8sClient client.Client | ||
logger = logf.Log.WithName("unit-tests") | ||
) | ||
|
||
func TestHandle(t *testing.T) { | ||
for _, tt := range []struct { | ||
req admission.Request | ||
name string | ||
expected int32 | ||
allowed bool | ||
}{ | ||
{ | ||
name: "empty payload", | ||
req: admission.Request{}, | ||
expected: http.StatusBadRequest, | ||
allowed: false, | ||
}, | ||
{ | ||
name: "invalid empty daemon-set payload", | ||
req: func() admission.Request { | ||
ds := appsv1.DaemonSet{} | ||
encoded, err := json.Marshal(ds) | ||
require.NoError(t, err) | ||
|
||
return admission.Request{ | ||
AdmissionRequest: admv1.AdmissionRequest{ | ||
Namespace: "testing", | ||
Object: runtime.RawExtension{ | ||
Raw: encoded, | ||
}, | ||
}, | ||
} | ||
}(), | ||
expected: http.StatusBadRequest, | ||
allowed: false, | ||
}, | ||
{ | ||
name: "invalid pod payload", | ||
req: func() admission.Request { | ||
pod := corev1.Pod{} | ||
encoded, err := json.Marshal(pod) | ||
require.NoError(t, err) | ||
|
||
return admission.Request{ | ||
AdmissionRequest: admv1.AdmissionRequest{ | ||
Namespace: "testing", | ||
Object: runtime.RawExtension{ | ||
Raw: encoded, | ||
}, | ||
}, | ||
} | ||
}(), | ||
expected: http.StatusBadRequest, | ||
allowed: false, | ||
}, | ||
{ | ||
name: "valid workload payload", | ||
req: func() admission.Request { | ||
ds := appsv1.DaemonSet{ | ||
TypeMeta: metav1.TypeMeta{ | ||
Kind: "DaemonSet", | ||
}, | ||
Spec: appsv1.DaemonSetSpec{ | ||
Template: corev1.PodTemplateSpec{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Annotations: map[string]string{}, | ||
}, | ||
}, | ||
}, | ||
} | ||
encoded, err := json.Marshal(ds) | ||
require.NoError(t, err) | ||
|
||
return admission.Request{ | ||
AdmissionRequest: admv1.AdmissionRequest{ | ||
Kind: metav1.GroupVersionKind{ | ||
Kind: "DaemonSet", | ||
}, | ||
Namespace: "testing", | ||
Object: runtime.RawExtension{ | ||
Raw: encoded, | ||
}, | ||
}, | ||
} | ||
}(), | ||
expected: http.StatusOK, | ||
allowed: true, | ||
}, | ||
} { | ||
t.Run(tt.name, func(t *testing.T) { | ||
// prepare | ||
cfg := config.New() | ||
decoder := admission.NewDecoder(scheme.Scheme) | ||
autoAnnotationConfig := auto.AnnotationConfig{ | ||
Java: auto.AnnotationResources{ | ||
Namespaces: []string{"keep-auto-java"}, | ||
}, | ||
} | ||
mutators := auto.NewAnnotationMutators( | ||
k8sClient, | ||
k8sClient, | ||
logr.Logger{}, | ||
autoAnnotationConfig, | ||
instrumentation.NewTypeSet(instrumentation.TypeJava), | ||
) | ||
injector := NewWebhookHandler(cfg, logger, decoder, k8sClient, mutators) | ||
|
||
// test | ||
res := injector.Handle(context.Background(), tt.req) | ||
|
||
// verify | ||
assert.Equal(t, tt.allowed, res.Allowed) | ||
if !tt.allowed { | ||
assert.NotNil(t, res.AdmissionResponse.Result) | ||
assert.Equal(t, tt.expected, res.AdmissionResponse.Result.Code) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.