mirror of https://github.com/linkerd/linkerd2.git
160 lines
4.5 KiB
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)
|
|
}
|
|
}
|