linkerd2/controller/proxy-injector/webhook.go

160 lines
4.5 KiB
Go

package injector
import (
"fmt"
pb "github.com/linkerd/linkerd2/controller/gen/config"
"github.com/linkerd/linkerd2/controller/k8s"
"github.com/linkerd/linkerd2/pkg/config"
"github.com/linkerd/linkerd2/pkg/inject"
pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/version"
log "github.com/sirupsen/logrus"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"sigs.k8s.io/yaml"
)
// Webhook is a Kubernetes mutating admission webhook that mutates pods admission
// requests by injecting sidecar container spec into the pod spec during pod
// creation.
type Webhook struct {
k8sAPI *k8s.API
deserializer runtime.Decoder
controllerNamespace string
noInitContainer bool
}
// NewWebhook returns a new instance of Webhook.
func NewWebhook(api *k8s.API, controllerNamespace string, noInitContainer bool) (*Webhook, error) {
var (
scheme = runtime.NewScheme()
codecs = serializer.NewCodecFactory(scheme)
)
return &Webhook{
k8sAPI: api,
deserializer: codecs.UniversalDeserializer(),
controllerNamespace: controllerNamespace,
noInitContainer: noInitContainer,
}, nil
}
// Mutate changes the given pod spec by injecting the proxy sidecar container
// into the spec. The admission review object returns contains the original
// request and the response with the mutated pod spec.
func (w *Webhook) Mutate(data []byte) *admissionv1beta1.AdmissionReview {
admissionReview, err := w.decode(data)
if err != nil {
log.Error("failed to decode data. Reason: ", err)
admissionReview.Response = &admissionv1beta1.AdmissionResponse{
UID: admissionReview.Request.UID,
Allowed: false,
Result: &metav1.Status{
Message: err.Error(),
},
}
return admissionReview
}
log.Infof("received admission review request %s", admissionReview.Request.UID)
log.Debugf("admission request: %+v", admissionReview.Request)
admissionResponse, err := w.inject(admissionReview.Request)
if err != nil {
log.Error("failed to inject sidecar. Reason: ", err)
admissionReview.Response = &admissionv1beta1.AdmissionResponse{
UID: admissionReview.Request.UID,
Allowed: false,
Result: &metav1.Status{
Message: err.Error(),
},
}
return admissionReview
}
admissionReview.Response = admissionResponse
return admissionReview
}
func (w *Webhook) decode(data []byte) (*admissionv1beta1.AdmissionReview, error) {
var admissionReview admissionv1beta1.AdmissionReview
err := yaml.Unmarshal(data, &admissionReview)
return &admissionReview, err
}
func (w *Webhook) inject(request *admissionv1beta1.AdmissionRequest) (*admissionv1beta1.AdmissionResponse, error) {
log.Debugf("request object bytes: %s", request.Object.Raw)
globalConfig, err := config.Global(pkgK8s.MountPathGlobalConfig)
if err != nil {
return nil, err
}
proxyConfig, err := config.Proxy(pkgK8s.MountPathProxyConfig)
if err != nil {
return nil, err
}
namespace, err := w.k8sAPI.NS().Lister().Get(request.Namespace)
if err != nil {
return nil, err
}
nsAnnotations := namespace.GetAnnotations()
configs := &pb.All{Global: globalConfig, Proxy: proxyConfig}
conf := inject.NewResourceConfig(configs).
WithOwnerRetriever(w.ownerRetriever(request.Namespace)).
WithNsAnnotations(nsAnnotations).
WithKind(request.Kind.Kind)
nonEmpty, err := conf.ParseMeta(request.Object.Raw)
if err != nil {
return nil, err
}
admissionResponse := &admissionv1beta1.AdmissionResponse{
UID: request.UID,
Allowed: true,
}
if !nonEmpty {
return admissionResponse, nil
}
p, reports, err := conf.GetPatch(request.Object.Raw, inject.ShouldInjectWebhook)
if err != nil {
return nil, err
}
if p.IsEmpty() {
return admissionResponse, nil
}
p.AddCreatedByPodAnnotation(fmt.Sprintf("linkerd/proxy-injector %s", version.Version))
patchJSON, err := p.Marshal()
if err != nil {
return nil, err
}
// TODO: refactor GetPatch() so it only returns one report item
if len(reports) > 0 {
r := reports[0]
log.Infof("patch generated for: %s", r.ResName())
}
log.Debugf("patch: %s", patchJSON)
patchType := admissionv1beta1.PatchTypeJSONPatch
admissionResponse.Patch = patchJSON
admissionResponse.PatchType = &patchType
return admissionResponse, nil
}
func (w *Webhook) ownerRetriever(ns string) inject.OwnerRetrieverFunc {
return func(p *v1.Pod) (string, string) {
p.SetNamespace(ns)
return w.k8sAPI.GetOwnerKindAndName(p)
}
}