pkg/sidecar/podmutator.go (147 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package sidecar
import (
"context"
"errors"
"strings"
"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/aws/amazon-cloudwatch-agent-operator/apis/v1alpha1"
"github.com/aws/amazon-cloudwatch-agent-operator/internal/config"
"github.com/aws/amazon-cloudwatch-agent-operator/internal/webhook/podmutation"
)
var (
errMultipleInstancesPossible = errors.New("multiple OpenTelemetry Collector instances available, cannot determine which one to select")
errNoInstancesAvailable = errors.New("no OpenTelemetry Collector instances available")
errInstanceNotSidecar = errors.New("the OpenTelemetry Collector's mode is not set to sidecar")
)
type sidecarPodMutator struct {
client client.Client
logger logr.Logger
config config.Config
}
var _ podmutation.PodMutator = (*sidecarPodMutator)(nil)
func NewMutator(logger logr.Logger, config config.Config, client client.Client) *sidecarPodMutator {
return &sidecarPodMutator{
config: config,
logger: logger,
client: client,
}
}
func (p *sidecarPodMutator) Mutate(ctx context.Context, ns corev1.Namespace, pod corev1.Pod) (corev1.Pod, error) {
logger := p.logger.WithValues("namespace", pod.Namespace, "name", pod.Name)
// if no annotations are found at all, just return the same pod
annValue := annotationValue(ns, pod)
if len(annValue) == 0 {
logger.V(1).Info("annotation not present in deployment, skipping sidecar injection")
return pod, nil
}
// is the annotation value 'false'? if so, we need a pod without the sidecar (ie, remove if exists)
if strings.EqualFold(annValue, "false") {
logger.V(1).Info("pod explicitly refuses sidecar injection, attempting to remove sidecar if it exists")
return remove(pod)
}
// from this point and on, a sidecar is wanted
// check whether there's a sidecar already -- return the same pod if that's the case.
if existsIn(pod) {
logger.V(1).Info("pod already has sidecar in it, skipping injection")
return pod, nil
}
// which instance should it talk to?
otelcol, err := p.getCollectorInstance(ctx, ns, annValue)
if err != nil {
if errors.Is(err, errMultipleInstancesPossible) || errors.Is(err, errNoInstancesAvailable) || errors.Is(err, errInstanceNotSidecar) {
// we still allow the pod to be created, but we log a message to the operator's logs
logger.Error(err, "failed to select an OpenTelemetry Collector instance for this pod's sidecar")
return pod, nil
}
// something else happened, better fail here
return pod, err
}
// getting pod references, if any
references := p.podReferences(ctx, pod.OwnerReferences, ns)
attributes := getResourceAttributesEnv(ns, references)
// once it's been determined that a sidecar is desired, none exists yet, and we know which instance it should talk to,
// we should add the sidecar.
logger.V(1).Info("injecting sidecar into pod", "otelcol-namespace", otelcol.Namespace, "otelcol-name", otelcol.Name)
return add(p.config, p.logger, otelcol, pod, attributes)
}
func (p *sidecarPodMutator) getCollectorInstance(ctx context.Context, ns corev1.Namespace, ann string) (v1alpha1.AmazonCloudWatchAgent, error) {
if strings.EqualFold(ann, "true") {
return p.selectCollectorInstance(ctx, ns)
}
otelcol := v1alpha1.AmazonCloudWatchAgent{}
var nsnOtelcol types.NamespacedName
instNamespace, instName, namespaced := strings.Cut(ann, "/")
if namespaced {
nsnOtelcol = types.NamespacedName{Name: instName, Namespace: instNamespace}
} else {
nsnOtelcol = types.NamespacedName{Name: ann, Namespace: ns.Name}
}
err := p.client.Get(ctx, nsnOtelcol, &otelcol)
if err != nil {
return otelcol, err
}
if otelcol.Spec.Mode != v1alpha1.ModeSidecar {
return v1alpha1.AmazonCloudWatchAgent{}, errInstanceNotSidecar
}
return otelcol, nil
}
func (p *sidecarPodMutator) selectCollectorInstance(ctx context.Context, ns corev1.Namespace) (v1alpha1.AmazonCloudWatchAgent, error) {
var (
otelcols = v1alpha1.AmazonCloudWatchAgentList{}
sidecars []v1alpha1.AmazonCloudWatchAgent
)
if err := p.client.List(ctx, &otelcols, client.InNamespace(ns.Name)); err != nil {
return v1alpha1.AmazonCloudWatchAgent{}, err
}
for i := range otelcols.Items {
coll := otelcols.Items[i]
if coll.Spec.Mode == v1alpha1.ModeSidecar {
sidecars = append(sidecars, coll)
}
}
switch {
case len(sidecars) == 0:
return v1alpha1.AmazonCloudWatchAgent{}, errNoInstancesAvailable
case len(sidecars) > 1:
return v1alpha1.AmazonCloudWatchAgent{}, errMultipleInstancesPossible
default:
return sidecars[0], nil
}
}
func (p *sidecarPodMutator) podReferences(ctx context.Context, ownerReferences []metav1.OwnerReference, ns corev1.Namespace) podReferences {
references := &podReferences{}
replicaSet := p.getReplicaSetReference(ctx, ownerReferences, ns)
if replicaSet != nil {
references.replicaset = replicaSet
deployment := p.getDeploymentReference(ctx, replicaSet)
if deployment != nil {
references.deployment = deployment
}
}
return *references
}
func (p *sidecarPodMutator) getReplicaSetReference(ctx context.Context, ownerReferences []metav1.OwnerReference, ns corev1.Namespace) *appsv1.ReplicaSet {
replicaSetName := findOwnerReferenceKind(ownerReferences, "ReplicaSet")
if replicaSetName != "" {
replicaSet := &appsv1.ReplicaSet{}
err := p.client.Get(ctx, types.NamespacedName{Name: replicaSetName, Namespace: ns.Name}, replicaSet)
if err == nil {
return replicaSet
}
}
return nil
}
func (p *sidecarPodMutator) getDeploymentReference(ctx context.Context, replicaSet *appsv1.ReplicaSet) *appsv1.Deployment {
deploymentName := findOwnerReferenceKind(replicaSet.OwnerReferences, "Deployment")
if deploymentName != "" {
deployment := &appsv1.Deployment{}
err := p.client.Get(ctx, types.NamespacedName{Name: deploymentName, Namespace: replicaSet.Namespace}, deployment)
if err == nil {
return deployment
}
}
return nil
}
func findOwnerReferenceKind(references []metav1.OwnerReference, kind string) string {
for _, reference := range references {
if reference.Kind == kind {
return reference.Name
}
}
return ""
}