| // Licensed to Apache Software Foundation (ASF) under one or more contributor |
| // license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright |
| // ownership. Apache Software Foundation (ASF) licenses this file to you 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 injector |
| |
| import ( |
| "context" |
| "encoding/json" |
| "net/http" |
| "strings" |
| |
| "github.com/go-logr/logr" |
| corev1 "k8s.io/api/core/v1" |
| "sigs.k8s.io/controller-runtime/pkg/client" |
| "sigs.k8s.io/controller-runtime/pkg/webhook/admission" |
| ) |
| |
| // Injection is each step of the injection process |
| type Injection interface { |
| execute(*InjectProcessData) admission.Response |
| // set next step |
| setNext(Injection) |
| } |
| |
| // InjectProcessData defines data that needs to be processed in the injection process |
| // Divide the entire injection process into 6 steps |
| // 1.Get injection strategy |
| // 2.Overlay the sidecar info |
| // 3.Overlay the agent by setting jvm string |
| // 4.Overlay the plugins by setting jvm string and set the optional plugins |
| // 5.Get the ConfigMap |
| // 6.Inject fields into Pod |
| // After all steps are completed, return fully injected Pod, Or there is an error |
| // in a certain step, inject error info into annotations and return incompletely injected Pod |
| type InjectProcessData struct { |
| injectFileds *SidecarInjectField |
| annotation *Annotations |
| annotationOverlay *AnnotationOverlay |
| pod *corev1.Pod |
| req admission.Request |
| log logr.Logger |
| kubeclient client.Client |
| ctx context.Context |
| } |
| |
| // PatchReq is to fill the injected pod into the request and return the Response |
| func PatchReq(pod *corev1.Pod, req admission.Request) admission.Response { |
| marshaledPod, err := json.Marshal(pod) |
| if err != nil { |
| return admission.Errored(http.StatusInternalServerError, err) |
| } |
| |
| return admission.PatchResponseFromRaw(req.Object.Raw, marshaledPod) |
| } |
| |
| // GetStrategy is the first step of injection process |
| type GetStrategy struct { |
| next Injection |
| } |
| |
| // get injection strategy from pod's labels and annotations |
| // if don't need to inject, then return the original pod, otherwise go to the next step |
| func (gs *GetStrategy) execute(ipd *InjectProcessData) admission.Response { |
| ipd.injectFileds.GetInjectStrategy(*ipd.annotation, &ipd.pod.ObjectMeta.Labels, &ipd.pod.ObjectMeta.Annotations) |
| if !ipd.injectFileds.NeedInject { |
| log.Info("don't inject agent") |
| return admission.Allowed("ok") |
| } |
| return gs.next.execute(ipd) |
| } |
| |
| func (gs *GetStrategy) setNext(next Injection) { |
| gs.next = next |
| } |
| |
| // OverlaySidecar is the second step of injection process |
| type OverlaySidecar struct { |
| next Injection |
| } |
| |
| // OverlaySidecar will inject sidecar information such as image, command, args etc. |
| // Since we did not set the validation function for these fields, it usually goes to the next step |
| func (os *OverlaySidecar) execute(ipd *InjectProcessData) admission.Response { |
| if !ipd.injectFileds.OverlaySidecar(*ipd.annotation, ipd.annotationOverlay, &ipd.pod.ObjectMeta.Annotations) { |
| return PatchReq(ipd.pod, ipd.req) |
| } |
| return os.next.execute(ipd) |
| } |
| |
| func (os *OverlaySidecar) setNext(next Injection) { |
| os.next = next |
| } |
| |
| // OverlayAgent is the third step of injection process |
| type OverlayAgent struct { |
| next Injection |
| } |
| |
| // OverlayAgent overlays the agent by getting the pod's annotations |
| // If the agent overlay option is not set, go directly to the next step |
| // If set the wrong value in the annotation , inject the error info and return |
| func (oa *OverlayAgent) execute(ipd *InjectProcessData) admission.Response { |
| if !ipd.injectFileds.AgentOverlay { |
| return oa.next.execute(ipd) |
| } |
| if !ipd.injectFileds.OverlayAgent(*ipd.annotation, ipd.annotationOverlay, &ipd.pod.ObjectMeta.Annotations) { |
| ipd.log.Info("overlay agent config error!please look the error annotation!") |
| return PatchReq(ipd.pod, ipd.req) |
| } |
| return oa.next.execute(ipd) |
| } |
| |
| func (oa *OverlayAgent) setNext(next Injection) { |
| oa.next = next |
| } |
| |
| // OverlayPlugins is the fourth step of injection process |
| type OverlayPlugins struct { |
| next Injection |
| } |
| |
| // OverlayPlugins contains two step , the first is to set jvm string , the second is to set optional plugins |
| // during the step , we need to add jvm string to the Env of injected container |
| func (op *OverlayPlugins) execute(ipd *InjectProcessData) admission.Response { |
| if !ipd.injectFileds.AgentOverlay { |
| return op.next.execute(ipd) |
| } |
| ipd.injectFileds.OverlayPlugins(&ipd.pod.ObjectMeta.Annotations) |
| if ipd.injectFileds.JvmAgentConfigStr != "" { |
| ipd.injectFileds.Env.Value = strings.Join([]string{ipd.injectFileds.Env.Value, ipd.injectFileds.JvmAgentConfigStr}, "=") |
| } |
| ipd.injectFileds.OverlayOptional(&ipd.pod.ObjectMeta.Annotations) |
| return op.next.execute(ipd) |
| } |
| func (op *OverlayPlugins) setNext(next Injection) { |
| op.next = next |
| } |
| |
| // GetConfigmap is the fifth step of injection process |
| type GetConfigmap struct { |
| next Injection |
| } |
| |
| // GetConfigmap will get the configmap specified in the annotation. if not exist , |
| // we will get data from default configmap and create configmap in the req's namespace |
| func (gc *GetConfigmap) execute(ipd *InjectProcessData) admission.Response { |
| if !ipd.injectFileds.CreateConfigmap(ipd.ctx, ipd.kubeclient, ipd.req.Namespace, &ipd.pod.ObjectMeta.Annotations) { |
| return PatchReq(ipd.pod, ipd.req) |
| } |
| return gc.next.execute(ipd) |
| } |
| func (gc *GetConfigmap) setNext(next Injection) { |
| gc.next = next |
| } |
| |
| // PodInject is the sixth step of injection process |
| type PodInject struct { |
| next Injection |
| } |
| |
| // PodInject will inject all fields to the pod |
| func (pi *PodInject) execute(ipd *InjectProcessData) admission.Response { |
| ipd.injectFileds.Inject(ipd.pod) |
| ipd.injectFileds.injectSucceedAnnotation(&ipd.pod.Annotations) |
| log.Info("inject successfully!") |
| return PatchReq(ipd.pod, ipd.req) |
| } |
| func (pi *PodInject) setNext(next Injection) { |
| pi.next = next |
| } |
| |
| // NewInjectProcess create a new InjectProcess |
| func NewInjectProcess(ctx context.Context, injectFileds *SidecarInjectField, annotation *Annotations, |
| annotationOverlay *AnnotationOverlay, pod *corev1.Pod, req admission.Request, log logr.Logger, |
| kubeclient client.Client) *InjectProcessData { |
| return &InjectProcessData{ |
| ctx: ctx, |
| injectFileds: injectFileds, |
| annotation: annotation, |
| annotationOverlay: annotationOverlay, |
| pod: pod, |
| req: req, |
| log: log, |
| kubeclient: kubeclient, |
| } |
| } |
| |
| // Run will connect the above six steps into a chain and start to execute the first step |
| func (ipd *InjectProcessData) Run() admission.Response { |
| // set final step |
| podInject := &PodInject{} |
| |
| // set next step is PodInject |
| getConfigmap := &GetConfigmap{} |
| getConfigmap.setNext(podInject) |
| |
| // set next step is GetConfigmap |
| overlayPlugins := &OverlayPlugins{} |
| overlayPlugins.setNext(getConfigmap) |
| |
| // set next step is OverlayPlugins |
| overlayAgent := &OverlayAgent{} |
| overlayAgent.setNext(overlayPlugins) |
| |
| // set next step is OverlayAgent |
| overlaysidecar := &OverlaySidecar{} |
| overlaysidecar.setNext(overlayAgent) |
| |
| // set next step is OverlaySidecar |
| getStrategy := &GetStrategy{} |
| getStrategy.setNext(overlaysidecar) |
| |
| // this is first step and do real injection |
| return getStrategy.execute(ipd) |
| } |