From f0cef338d86581bae3bbd240c04a63f994be2e60 Mon Sep 17 00:00:00 2001 From: Bartek Plotka Date: Wed, 5 Aug 2026 12:39:21 +0000 Subject: [PATCH] docs(e2e): add GMP sidecar injection example script and test Adds a bash script example for injecting GMP sidecars (prometheus and config-reloader) into an existing Kubernetes Deployment via strategic merge patch, alongside an E2E test verifying the injection. The naming uses 'example-deployment' and 'example-service'. TAG=agy CONV=bbab9ab7-4203-4ea3-a69b-2c40577629c2 --- e2e/examples_test.go | 124 +++++++++++++++++++++++++++++ examples/inject-gmp-sidecar.sh | 140 +++++++++++++++++++++++++++++++++ hack/Dockerfile | 3 + 3 files changed, 267 insertions(+) create mode 100644 e2e/examples_test.go create mode 100755 examples/inject-gmp-sidecar.sh diff --git a/e2e/examples_test.go b/e2e/examples_test.go new file mode 100644 index 0000000000..fef0004609 --- /dev/null +++ b/e2e/examples_test.go @@ -0,0 +1,124 @@ +// Copyright 2024 Google LLC +// +// Licensed 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 +// +// https://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 e2e + +import ( + "context" + "os/exec" + "testing" + "time" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestInjectGMPSidecarExample(t *testing.T) { + ctx := contextWithDeadline(t) + + kubeClient, _, err := setupCluster(ctx, t) + if err != nil { + t.Fatalf("error setting up cluster: %s", err) + } + + // 1. Create example-deployment in default namespace. + deployment := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example-deployment", + Namespace: "default", + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "example-service", + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app": "example-service", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "example-service", + Image: "nginx:latest", // Just a dummy image. + }, + }, + }, + }, + }, + } + + if err := kubeClient.Create(ctx, deployment); err != nil { + t.Fatalf("error creating example-deployment: %s", err) + } + defer func() { + _ = kubeClient.Delete(ctx, deployment) + _ = kubeClient.Delete(ctx, &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example-deployment", + Namespace: deployment.Namespace, + }, + }) + }() + + // 2. Run the bash script. + cmd := exec.CommandContext(ctx, "../examples/inject-gmp-sidecar.sh") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("error running inject-gmp-sidecar.sh: %s\n%s", err, string(out)) + } + t.Logf("script output: %s", string(out)) + + // 3. Verify sidecars are injected and ConfigMap is created. + if err := wait.PollUntilContextCancel(ctx, 2*time.Second, true, func(ctx context.Context) (bool, error) { + cm := &corev1.ConfigMap{} + if getErr := kubeClient.Get(ctx, client.ObjectKey{Name: "example-deployment", Namespace: "default"}, cm); getErr != nil { + return false, nil //nolint:nilerr + } + if cm.Data["config.yaml"] == "" { + return false, nil //nolint:nilerr + } + + dep := &appsv1.Deployment{} + if getErr := kubeClient.Get(ctx, client.ObjectKey{Name: "example-deployment", Namespace: "default"}, dep); getErr != nil { + return false, nil //nolint:nilerr + } + + containers := dep.Spec.Template.Spec.Containers + if len(containers) != 3 { + return false, nil //nolint:nilerr + } + + hasProm := false + hasReloader := false + for _, c := range containers { + if c.Name == "prometheus" { + hasProm = true + } + if c.Name == "config-reloader" { + hasReloader = true + } + } + + return hasProm && hasReloader, nil + }); err != nil { + t.Fatalf("failed to verify injected sidecars or config map: %s", err) + } +} diff --git a/examples/inject-gmp-sidecar.sh b/examples/inject-gmp-sidecar.sh new file mode 100755 index 0000000000..5ce04a2e17 --- /dev/null +++ b/examples/inject-gmp-sidecar.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash +# Copyright 2024 Google LLC +# +# Licensed 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. + +set -exuo pipefail + +KIND="deployment" +NAMESPACE="default" +NAME="example-deployment" +CONTAINER="example-service" +METRICS_PATH="/metrics" +PORT=80 + +if ! command -v yq &> /dev/null; then + echo "Error: yq is not installed. Please install it (e.g., via 'go install github.com/mikefarah/yq/v4@latest' or from https://github.com/mikefarah/yq)." >&2 + exit 1 +fi + +# Extract labels. +CLUSTER_NAME=$(kubectl -n gmp-system get configmap/collector -o jsonpath='{.data.config\.yaml}' | yq '.global.external_labels.cluster') +LOCATION=$(kubectl -n gmp-system get configmap/collector -o jsonpath='{.data.config\.yaml}' | yq '.global.external_labels.location') +PROJECT_ID=$(kubectl -n gmp-system get configmap/collector -o jsonpath='{.data.config\.yaml}' | yq '.global.external_labels.project_id') + + +if [[ -z "${CLUSTER_NAME}" || -z "${LOCATION}" || -z "${PROJECT_ID}" ]]; then + echo "Error: Failed to extract cluster, location, or project_id from gmp-system/collector configmap." >&2 + exit 1 +fi + +# Images need to be updated periodically. +DISTROLESS_IMAGE=gke.gcr.io/gke-distroless/bash:gke_distroless_20240220.00_p0@sha256:828371616edc2c38e36868e2f8c992df37e484df72670f148de59867dfdd2490 +PROMETHEUS_IMAGE=gke.gcr.io/prometheus-engine/prometheus:v2.53.5-gmp.4-gke.0@sha256:6f349dc0be36c8a61be183254f1126c9935f5332daa96c481f7e0e1b20fe0513 +CONFIG_RELOADER_IMAGE=gke.gcr.io/prometheus-engine/config-reloader:v0.18.0-gke.2@sha256:b41862ee7ee3e9f24112ccdb0e53060085af1a8347054a7dbcff04467d3e1e9c + +# Define the scrape configuration. +CONFIG_MAP=$( + cat < /prometheus/config_out/config.yaml"] + volumeMounts: + - name: config-out + mountPath: /prometheus/config_out + containers: + - name: prometheus + image: ${PROMETHEUS_IMAGE} + args: + - --config.file=/prometheus/config_out/config.yaml + - --storage.tsdb.path=/prometheus/data + - --storage.tsdb.retention.time=24h + - --web.enable-lifecycle + - --storage.tsdb.no-lockfile + - --web.route-prefix=/ + - --log.level=debug + ports: + - containerPort: 9090 + volumeMounts: + - name: config-out + mountPath: /prometheus/config_out + readOnly: true + - name: prometheus-db + mountPath: /prometheus/data + - name: config-reloader + image: ${CONFIG_RELOADER_IMAGE} + args: + - --config-file=/prometheus/config/config.yaml + - --config-file-output=/prometheus/config_out/config.yaml + - --reload-url=http://localhost:9090/-/reload + - --ready-url=http://localhost:9090/-/ready + - --listen-address=:19091 + env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + volumeMounts: + - name: config + mountPath: /prometheus/config + - name: config-out + mountPath: /prometheus/config_out +INNER_EOF +) + +# Apply the Patch +kubectl -n "${NAMESPACE}" patch "${KIND}" "${NAME}" --patch "${STRATEGIC_PATCH}" diff --git a/hack/Dockerfile b/hack/Dockerfile index 6b979e7d43..70f3aacdf5 100644 --- a/hack/Dockerfile +++ b/hack/Dockerfile @@ -88,6 +88,9 @@ RUN ARCH=$(dpkg --print-architecture) && \ curl -Lo ./jq "https://github.com/jqlang/jq/releases/download/jq-1.7/jq-linux-${ARCH}" && \ install -o root -g root -m 0755 jq /usr/local/bin/jq && \ rm jq && \ + curl -Lo ./yq "https://github.com/mikefarah/yq/releases/download/v4.44.3/yq_linux_${ARCH}" && \ + install -o root -g root -m 0755 yq /usr/local/bin/yq && \ + rm yq && \ curl -Lo ./kind "https://kind.sigs.k8s.io/dl/v0.26.0/kind-linux-${ARCH}" && \ install -o root -g root -m 0755 kind /usr/local/bin/kind && \ rm kind