blob: 7ba77d9ba648b3b68b18aabc6e3f79e1e84b18b0 [file] [log] [blame]
// 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)
}