Skip to content

Commit

Permalink
Devops 831 fix env var cleanup upon change (#23)
Browse files Browse the repository at this point in the history
* redo patch env var function
introduce annotations to save agent args for future cleanup

* fix updating same container 2 times within the same apply to kube api
add tests

* remove comment

* add test cases
#minor

* bump github actions deps

* replace append with proper Delete func
rename test
Leonid Podolinskiy authored May 2, 2024
1 parent 605151d commit 04d4b21
Showing 8 changed files with 595 additions and 166 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
@@ -20,10 +20,10 @@ jobs:
ports:
- 5000:5000
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Setup Go environment
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: "1.22"

@@ -83,7 +83,7 @@ jobs:
kubectl wait --timeout=120s --for=condition=Ready node/$(echo $HOSTNAME| awk '{print tolower($0)}')
- name: Build and push to local repo
uses: docker/build-push-action@v3
uses: docker/build-push-action@v5
with:
context: .
push: true
176 changes: 92 additions & 84 deletions .github/workflows/init_container.yaml
Original file line number Diff line number Diff line change
@@ -4,35 +4,46 @@ on:
workflow_dispatch:
inputs:
release_tag:
description: 'Release tag of the agent'
description: "Release tag of the agent"
required: true
init_image_tag:
description: 'Image tag'
description: "Image tag"
required: true
default: "0"
force:
description: 'Force build'
description: "Force build"
required: false
default: "false"



jobs:
jobs:
set_image_tag_variable:
strategy:
matrix:
agents: [
{name: "linux", file: "agent.zip", platform: "linux/amd64"},
{name: "alpine", file: "agent-alpine.zip", platform: "linux/amd64"},
{name: "linux-arm64", file: "agent-arm64.zip", platform: "linux/arm64"},
{name: "alpine-arm64", file: "agent-alpine-arm64.zip", platform: "linux/arm64"}
]
agents:
[
{ name: "linux", file: "agent.zip", platform: "linux/amd64" },
{
name: "alpine",
file: "agent-alpine.zip",
platform: "linux/amd64",
},
{
name: "linux-arm64",
file: "agent-arm64.zip",
platform: "linux/arm64",
},
{
name: "alpine-arm64",
file: "agent-alpine-arm64.zip",
platform: "linux/arm64",
},
]
runs-on: ubuntu-latest
name: Build and push Docker image
steps:
- name: Set release tag
shell: bash
run: |
- name: Set release tag
shell: bash
run: |
# check that tag is matching regex x.y.x-release.<commit hash> or force flag is enabled
if [[ ! ${{ inputs.release_tag }} =~ ^[0-9]+\.[0-9]+\.[0-9]+-release\.[0-9a-f]+$ ]] ; then
echo "Tag ${{ inputs.release_tag }} is not matching regex x.y.x-release.<commithash>"
@@ -43,81 +54,78 @@ jobs:
fi
fi
echo "TAG_NAME=$(echo ${{ inputs.release_tag }} | sed -E 's/^([0-9]*\.[0-9]*\.[0-9]*).*/\1/')-init.${{ inputs.init_image_tag }}" >> "$GITHUB_OUTPUT"
id: set_tag
id: set_tag

- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to DockerHub
if: ${{ success() }}
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_PASS }}

- name: Login to DockerHub
if: ${{ success() }}
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_PASS }}

- name: Configure AWS credentials for artifacts bucket
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.RELEASE_ARTIFACTS_MANAGER_KEY }}
aws-secret-access-key: ${{ secrets.RELEASE_ARTIFACTS_MANAGER_SECRET }}
aws-region: us-east-1
- name: Configure AWS credentials for artifacts bucket
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.RELEASE_ARTIFACTS_MANAGER_KEY }}
aws-secret-access-key: ${{ secrets.RELEASE_ARTIFACTS_MANAGER_SECRET }}
aws-region: us-east-1

- name: Set docker image tags
id: set_docker_tags
run: |
python3 -m pip install semver
existing_tags=()
dockerhub_tags=$(curl -s "https://hub.docker.com/v2/namespaces/lightruncom/repositories/k8s-operator-init-java-agent-${{ matrix.agents.name }}/tags?page_size=50" | jq -r ".results[].name")
if [[ $? -ne 0 ]] ; then
echo "Failed to fetch existing tags"
exit 1
fi
while IFS= read -r line; do
existing_tags+=("$line")
done < <(echo $dockerhub_tags)
for tag in $existing_tags
do
if [[ "$tag" == "latest" ]] ; then
continue
- name: Set docker image tags
id: set_docker_tags
run: |
python3 -m pip install semver
existing_tags=()
dockerhub_tags=$(curl -s "https://hub.docker.com/v2/namespaces/lightruncom/repositories/k8s-operator-init-java-agent-${{ matrix.agents.name }}/tags?page_size=50" | jq -r ".results[].name")
if [[ $? -ne 0 ]] ; then
echo "Failed to fetch existing tags"
exit 1
fi
echo "Comparing existing tag: $tag with new: ${{steps.set_tag.outputs.TAG_NAME}}"
if [[ $(pysemver compare $tag ${{steps.set_tag.outputs.TAG_NAME}}) -ge 0 ]] ; then
echo "Existing tag: $tag is greater or equal than new: ${{ inputs.release_tag }}. Skip adding latest tag"
echo "DOCKER_TAGS=lightruncom/k8s-operator-init-java-agent-${{ matrix.agents.name }}:${{steps.set_tag.outputs.TAG_NAME}}" >> "$GITHUB_OUTPUT"
exit 0
fi
done
echo "Adding latest tag to ${{steps.set_tag.outputs.TAG_NAME}}"
echo "DOCKER_TAGS=lightruncom/k8s-operator-init-java-agent-${{ matrix.agents.name }}:${{steps.set_tag.outputs.TAG_NAME}},lightruncom/k8s-operator-init-java-agent-${{ matrix.agents.name }}:latest" >> "$GITHUB_OUTPUT"
- name: Download agent artifacts
run: |
aws s3 cp s3://${{ secrets.RELEASE_ARTIFACTS_BUCKET }}/artifacts/${{ inputs.release_tag }}/${{ matrix.agents.file }} ./lightrun-init-agent/
while IFS= read -r line; do
existing_tags+=("$line")
done < <(echo $dockerhub_tags)
for tag in $existing_tags
do
if [[ "$tag" == "latest" ]] ; then
continue
fi
echo "Comparing existing tag: $tag with new: ${{steps.set_tag.outputs.TAG_NAME}}"
if [[ $(pysemver compare $tag ${{steps.set_tag.outputs.TAG_NAME}}) -ge 0 ]] ; then
echo "Existing tag: $tag is greater or equal than new: ${{ inputs.release_tag }}. Skip adding latest tag"
echo "DOCKER_TAGS=lightruncom/k8s-operator-init-java-agent-${{ matrix.agents.name }}:${{steps.set_tag.outputs.TAG_NAME}}" >> "$GITHUB_OUTPUT"
exit 0
fi
done
echo "Adding latest tag to ${{steps.set_tag.outputs.TAG_NAME}}"
echo "DOCKER_TAGS=lightruncom/k8s-operator-init-java-agent-${{ matrix.agents.name }}:${{steps.set_tag.outputs.TAG_NAME}},lightruncom/k8s-operator-init-java-agent-${{ matrix.agents.name }}:latest" >> "$GITHUB_OUTPUT"
- name: Build and push ${{ matrix.agents.name }} container
uses: docker/build-push-action@v4
with:
context: .
file: ./lightrun-init-agent/Dockerfile
push: true
platforms: ${{ matrix.agents.platform }}
tags: ${{steps.set_docker_tags.outputs.DOCKER_TAGS}}
build-args: |
FILE=${{ matrix.agents.file }}
- name: Download agent artifacts
run: |
aws s3 cp s3://${{ secrets.RELEASE_ARTIFACTS_BUCKET }}/artifacts/${{ inputs.release_tag }}/${{ matrix.agents.file }} ./lightrun-init-agent/
- name: Build and push ${{ matrix.agents.name }} container
uses: docker/build-push-action@v4
with:
context: .
file: ./lightrun-init-agent/Dockerfile
push: true
platforms: ${{ matrix.agents.platform }}
tags: ${{steps.set_docker_tags.outputs.DOCKER_TAGS}}
build-args: |
FILE=${{ matrix.agents.file }}
- name: Slack Notification
if: always()
uses: rtCamp/action-slack-notify@v2.2.0
env:
SLACK_CHANNEL: devops-alerts
SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff'
SLACK_MESSAGE: "Tag ${{ inputs.release_tag }} | Platform ${{ matrix.agents.name }}"
SLACK_TITLE: Init contianer build status - ${{ job.status }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
- name: Slack Notification
if: always()
uses: rtCamp/action-slack-notify@v2.2.0
env:
SLACK_CHANNEL: devops-alerts
SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff'
SLACK_MESSAGE: "Tag ${{ inputs.release_tag }} | Platform ${{ matrix.agents.name }}"
SLACK_TITLE: Init contianer build status - ${{ job.status }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
12 changes: 6 additions & 6 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -21,12 +21,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
path: "helm-repo"
ref: "helm-repo"
@@ -42,7 +42,7 @@ jobs:
WITH_V: false

- name: Setup Go environment
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: "1.22"

@@ -63,7 +63,7 @@ jobs:
- name: Login to DockerHub
if: ${{ success() }}
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_PASS }}
@@ -74,7 +74,7 @@ jobs:
uses: docker/setup-buildx-action@v3

- name: Build and push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v5
with:
context: .
push: true
@@ -85,7 +85,7 @@ jobs:
- name: Create Release
if: ${{ success() }}
uses: ncipollo/release-action@v1.10.0
uses: ncipollo/release-action@v1.14.0
with:
artifacts: helm-repo/lightrun-k8s-operator-${{steps.release_tag.outputs.new_tag}}.tgz
tag: ${{steps.release_tag.outputs.new_tag}}
29 changes: 29 additions & 0 deletions internal/controller/helpers.go
Original file line number Diff line number Diff line change
@@ -2,8 +2,10 @@ package controller

import (
"context"
"errors"
"hash/fnv"
"sort"
"strings"
"time"

agentv1beta "github.com/lightrun-platform/lightrun-k8s-operator/api/v1beta"
@@ -226,3 +228,30 @@ func SetStatusCondition(conditions *[]metav1.Condition, newCondition metav1.Cond
existingCondition.Message = newCondition.Message
existingCondition.ObservedGeneration = newCondition.ObservedGeneration
}

func agentEnvVarArgument(mountPath string, agentCliFlags string) (string, error) {
agentArg := " -agentpath:" + mountPath + "/agent/lightrun_agent.so"
if agentCliFlags != "" {
agentArg += "=" + agentCliFlags
if len(agentArg) > 1024 {
return "", errors.New("agentpath with agentCliFlags has more than 1024 chars. This is a limitation of Java")
}
}
return agentArg, nil
}

// Removes from env var value. Removes env var from the list if value is empty after the update
func unpatchEnvVarValue(origValue string, removalValue string) string {
value := strings.ReplaceAll(origValue, removalValue, "")
return value
}

// Return index if the env var in the []corev1.EnvVar, otherwise -1
func findEnvVarIndex(envVarName string, envVarList []corev1.EnvVar) int {
for i, envVar := range envVarList {
if envVar.Name == envVarName {
return i
}
}
return -1
}
159 changes: 159 additions & 0 deletions internal/controller/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package controller

import (
"strings"
"testing"

corev1 "k8s.io/api/core/v1"
)

func Test_findEnvVarIndex(t *testing.T) {
type args struct {
envVarName string
envVarList []corev1.EnvVar
}
tests := []struct {
name string
args args
want int
}{
{
name: "correctly finds the index of the env var",
args: args{
envVarName: "test",
envVarList: []corev1.EnvVar{
{
Name: "test",
Value: "test",
},
},
},
want: 0,
},
{
name: "env var not found",
args: args{
envVarName: "test",
envVarList: []corev1.EnvVar{
{
Name: "test1",
Value: "test",
},
},
},
want: -1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := findEnvVarIndex(tt.args.envVarName, tt.args.envVarList); got != tt.want {
t.Errorf("findEnvVarIndex() = %v, want %v", got, tt.want)
}
})
}
}

func Test_unpatchEnvVarValue(t *testing.T) {
type args struct {
origValue string
removalValue string
}
tests := []struct {
name string
args args
want string
}{
{
name: "correctly removes the value from the env var",
args: args{
origValue: "test",
removalValue: "test",
},
want: "",
},
{
name: "not found substring",
args: args{
origValue: "test",
removalValue: "test1",
},
want: "test",
},
{
name: "with space",
args: args{
origValue: "test this string",
removalValue: " this",
},
want: "test string",
},
{
name: "unpatch empty value",
args: args{
origValue: "test this string",
removalValue: "",
},
want: "test this string",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := unpatchEnvVarValue(tt.args.origValue, tt.args.removalValue); got != tt.want {
t.Errorf("unpatchEnvVarValue() = %v, want %v", got, tt.want)
}
})
}
}

func Test_agentEnvVarArgument(t *testing.T) {
type args struct {
mountPath string
agentCliFlags string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "correctly returns the agent env var argument",
args: args{
mountPath: "test",
agentCliFlags: "test",
},
want: " -agentpath:test/agent/lightrun_agent.so=test",
wantErr: false,
},
{
name: "correctly returns the agent env var argument",
args: args{
mountPath: "test",
agentCliFlags: "",
},
want: " -agentpath:test/agent/lightrun_agent.so",
wantErr: false,
},
{
name: "return error when agentpath with agentCliFlags has more than 1024 chars",
args: args{
mountPath: "test",
agentCliFlags: strings.Repeat("a", 1024),
},
want: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := agentEnvVarArgument(tt.args.mountPath, tt.args.agentCliFlags)
if (err != nil) != tt.wantErr {
t.Errorf("agentEnvVarArgument() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("agentEnvVarArgument() = %v, want %v", got, tt.want)
}
})
}
}
30 changes: 18 additions & 12 deletions internal/controller/lightrunjavaagent_controller.go
Original file line number Diff line number Diff line change
@@ -134,17 +134,17 @@ func (r *LightrunJavaAgentReconciler) Reconcile(ctx context.Context, req ctrl.Re
log.Info("Unpatching deployment", "Deployment", originalDeployment.Name)

// Remove agent from JAVA_TOOL_OPTIONS. Client side patch
conIndex := -1
clientSidePatch := client.MergeFrom(originalDeployment.DeepCopy())
for i, container := range originalDeployment.Spec.Template.Spec.Containers {
for _, targetContainer := range lightrunJavaAgent.Spec.ContainerSelector {
if targetContainer == container.Name {
conIndex = i
break
r.unpatchJavaToolEnv(originalDeployment.Annotations, &originalDeployment.Spec.Template.Spec.Containers[i])
}
}
r.unpatchJavaToolEnv(&originalDeployment.Spec.Template.Spec.Containers[conIndex], lightrunJavaAgent.Spec.AgentEnvVarName, lightrunJavaAgent.Spec.InitContainer.SharedVolumeMountPath, lightrunJavaAgent.Spec.AgentCliFlags)

}
delete(originalDeployment.Annotations, "lightrun.com/patched-env-name")
delete(originalDeployment.Annotations, "lightrun.com/patched-env-value")
err = r.Patch(ctx, originalDeployment, clientSidePatch)
if err != nil {
log.Error(err, "unable to unpatch "+lightrunJavaAgent.Spec.AgentEnvVarName)
@@ -185,6 +185,13 @@ func (r *LightrunJavaAgentReconciler) Reconcile(ctx context.Context, req ctrl.Re
}
}

// Verify that env var won't exceed 1024 chars
agentArg, err := agentEnvVarArgument(lightrunJavaAgent.Spec.InitContainer.SharedVolumeMountPath, lightrunJavaAgent.Spec.AgentCliFlags)
if err != nil {
log.Error(err, "agentEnvVarArgument exceeds 1024 chars")
return r.errorStatus(ctx, lightrunJavaAgent, err)
}

// Create config map
log.V(2).Info("Reconciling config map with agent configuration")
configMap, err := r.createAgentConfig(lightrunJavaAgent)
@@ -258,20 +265,19 @@ func (r *LightrunJavaAgentReconciler) Reconcile(ctx context.Context, req ctrl.Re
return r.errorStatus(ctx, lightrunJavaAgent, err)
}
clientSidePatch := client.MergeFrom(originalDeployment.DeepCopy())
conIndex := -1
for i, container := range originalDeployment.Spec.Template.Spec.Containers {
for _, targetContainer := range lightrunJavaAgent.Spec.ContainerSelector {
if targetContainer == container.Name {
conIndex = i
break
err = r.patchJavaToolEnv(originalDeployment.Annotations, &originalDeployment.Spec.Template.Spec.Containers[i], lightrunJavaAgent.Spec.AgentEnvVarName, agentArg)
if err != nil {
log.Error(err, "failed to patch "+lightrunJavaAgent.Spec.AgentEnvVarName)
return r.errorStatus(ctx, lightrunJavaAgent, err)
}
}
}
err = r.patchJavaToolEnv(&originalDeployment.Spec.Template.Spec.Containers[conIndex], lightrunJavaAgent.Spec.AgentEnvVarName, lightrunJavaAgent.Spec.InitContainer.SharedVolumeMountPath, lightrunJavaAgent.Spec.AgentCliFlags)
if err != nil {
log.Error(err, "failed to patch "+lightrunJavaAgent.Spec.AgentEnvVarName)
return r.errorStatus(ctx, lightrunJavaAgent, err)
}
}
originalDeployment.Annotations["lightrun.com/patched-env-name"] = lightrunJavaAgent.Spec.AgentEnvVarName
originalDeployment.Annotations["lightrun.com/patched-env-value"] = agentArg
err = r.Patch(ctx, originalDeployment, clientSidePatch)
if err != nil {
log.Error(err, "failed to patch "+lightrunJavaAgent.Spec.AgentEnvVarName)
278 changes: 254 additions & 24 deletions internal/controller/lightrunjavaagent_controller_test.go
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ package controller
import (
"context"
"fmt"
"strings"
"time"

agentsv1beta "github.com/lightrun-platform/lightrun-k8s-operator/api/v1beta"
@@ -18,21 +19,22 @@ var _ = Describe("LightrunJavaAgent controller", func() {

// Define utility constants for object names and testing timeouts/durations and intervals.
const (
lragent1Name = "lragent"
deployment = "app-deployment"
secret = "agent-secret"
server = "example.lightrun.com"
agentName = "coolio-agent"
timeout = time.Second * 10
duration = time.Second * 10
interval = time.Millisecond * 250
wrongNamespace = "wrong-namespace"
initContainerImage = "lightruncom/lightrun-init-agent:latest"
agentPlatform = "linux"
initVolumeName = "lightrun-agent-init"
javaEnv = "JAVA_TOOL_OPTIONS"
defaultAgentPath = "-agentpath:/lightrun/agent/lightrun_agent.so"
agentCliFlags = "--lightrun_extra_class_path=<PATH_TO_JAR>"
lragent1Name = "lragent"
deployment = "app-deployment"
secret = "agent-secret"
server = "example.lightrun.com"
agentName = "coolio-agent"
timeout = time.Second * 10
duration = time.Second * 10
interval = time.Millisecond * 250
wrongNamespace = "wrong-namespace"
initContainerImage = "lightruncom/lightrun-init-agent:latest"
agentPlatform = "linux"
initVolumeName = "lightrun-agent-init"
javaEnv = "JAVA_TOOL_OPTIONS"
defaultAgentPath = " -agentpath:/lightrun/agent/lightrun_agent.so"
agentCliFlags = "--lightrun_extra_class_path=<PATH_TO_JAR>"
javaEnvNonEmptyValue = "-Djava.net.preferIPv4Stack=true"
)
var containerSelector = []string{"app", "app2"}
var agentConfig map[string]string = map[string]string{
@@ -65,6 +67,12 @@ var _ = Describe("LightrunJavaAgent controller", func() {
Namespace: wrongNamespace,
}

var patchedDepl4 appsv1.Deployment
deplRequest4 := types.NamespacedName{
Name: deployment + "-4",
Namespace: testNamespace,
}

var cm corev1.ConfigMap
cmRequest := types.NamespacedName{
Name: cmNamePrefix + lragent1Name,
@@ -95,6 +103,12 @@ var _ = Describe("LightrunJavaAgent controller", func() {
Namespace: wrongNamespace,
}

var lrAgent5 agentsv1beta.LightrunJavaAgent
lrAgentRequest5 := types.NamespacedName{
Name: "change-env-name",
Namespace: testNamespace,
}

ctx := context.Background()
Context("When setting up the test environment", func() {
It("Should create a test Namespace", func() {
@@ -208,7 +222,7 @@ var _ = Describe("LightrunJavaAgent controller", func() {
Env: []corev1.EnvVar{
{
Name: javaEnv,
Value: "-Djava.net.preferIPv4Stack=true",
Value: javaEnvNonEmptyValue,
},
},
},
@@ -237,7 +251,7 @@ var _ = Describe("LightrunJavaAgent controller", func() {
}).Should(BeTrue())
})

It("Should patch Env Vars of containers with agentCliFlags value", func() {
It("Should patch Env Vars of containers with agentCliFlags value", func() {
Eventually(func() bool {
if err := k8sClient.Get(ctx, deplRequest, &patchedDepl); err != nil {
return false
@@ -250,7 +264,7 @@ var _ = Describe("LightrunJavaAgent controller", func() {
return false
}
} else if container.Name == "app2" {
if envVar.Value != "-Djava.net.preferIPv4Stack=true "+defaultAgentPath+"="+agentCliFlags {
if envVar.Value != javaEnvNonEmptyValue+defaultAgentPath+"="+agentCliFlags {
return false
}
}
@@ -392,6 +406,17 @@ var _ = Describe("LightrunJavaAgent controller", func() {
}).Should(BeTrue())
})

It("Should remove annotations from deployment", func() {
Eventually(func() bool {
for k := range patchedDepl.ObjectMeta.Annotations {
if strings.Contains(k, "lightrun.com") {
return false
}
}
return true
}).Should(BeTrue())
})

It("Should remove init container from the deployment", func() {
Eventually(func() bool {
if err := k8sClient.Get(ctx, deplRequest, &patchedDepl); err != nil {
@@ -417,8 +442,8 @@ var _ = Describe("LightrunJavaAgent controller", func() {
}
} else if container.Name == "app2" {
if envVar.Name == javaEnv {
if envVar.Value != "-Djava.net.preferIPv4Stack=true" {
//logger.Info("second container", envVar.Name, envVar.Value)
if envVar.Value != javaEnvNonEmptyValue {
// logger.Info("second container", envVar.Name, envVar.Value)
return false
}
}
@@ -498,7 +523,7 @@ var _ = Describe("LightrunJavaAgent controller", func() {
Env: []corev1.EnvVar{
{
Name: javaEnv,
Value: "-Djava.net.preferIPv4Stack=true",
Value: javaEnvNonEmptyValue,
},
},
},
@@ -536,7 +561,7 @@ var _ = Describe("LightrunJavaAgent controller", func() {
return false
}
} else if container.Name == "app2" {
if envVar.Value != "-Djava.net.preferIPv4Stack=true "+defaultAgentPath {
if envVar.Value != javaEnvNonEmptyValue+defaultAgentPath {
return false
}
}
@@ -597,7 +622,7 @@ var _ = Describe("LightrunJavaAgent controller", func() {
Env: []corev1.EnvVar{
{
Name: javaEnv,
Value: "-Djava.net.preferIPv4Stack=true",
Value: javaEnvNonEmptyValue,
},
},
},
@@ -704,7 +729,7 @@ var _ = Describe("LightrunJavaAgent controller", func() {
Env: []corev1.EnvVar{
{
Name: javaEnv,
Value: "-Djava.net.preferIPv4Stack=true",
Value: javaEnvNonEmptyValue,
},
},
},
@@ -764,4 +789,209 @@ var _ = Describe("LightrunJavaAgent controller", func() {
}).Should(BeTrue())
})
})
Context("When changing Env Var name", func() {
It("Should create Deployment", func() {
By("Creating deployment")
depl := appsv1.Deployment{
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String(), Kind: "Deployment"},
ObjectMeta: metav1.ObjectMeta{
Name: deployment + "-4",
Namespace: testNamespace,
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": "app"},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"app": "app"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "app",
Image: "busybox",
},
{
Name: "app2",
Image: "busybox",
Env: []corev1.EnvVar{
{
Name: javaEnv,
Value: javaEnvNonEmptyValue,
},
},
},
{
Name: "no-patch",
Image: "busybox",
},
},
},
},
},
}
Expect(k8sClient.Create(ctx, &depl)).Should(Succeed())
})
It("Should create CR with changed Env Var name", func() {
By("Creating new CR")
lrAgent5 := agentsv1beta.LightrunJavaAgent{
ObjectMeta: metav1.ObjectMeta{
Name: "change-env-name",
Namespace: testNamespace,
},
Spec: agentsv1beta.LightrunJavaAgentSpec{
DeploymentName: deployment + "-4",
SecretName: secret,
ServerHostname: server,
AgentName: agentName,
AgentTags: agentTags,
AgentConfig: agentConfig,
AgentEnvVarName: javaEnv,
ContainerSelector: containerSelector,
InitContainer: agentsv1beta.InitContainer{
Image: initContainerImage,
SharedVolumeName: initVolumeName,
SharedVolumeMountPath: "/lightrun",
},
},
}
Expect(k8sClient.Create(ctx, &lrAgent5)).Should(Succeed())
})
It("Should patch Env Vars of containers with default agent path", func() {
Eventually(func() bool {
if err := k8sClient.Get(ctx, deplRequest4, &patchedDepl4); err != nil {
return false
}
for _, container := range patchedDepl4.Spec.Template.Spec.Containers {
for _, envVar := range container.Env {
if envVar.Name == javaEnv {
if container.Name == "app" {
if envVar.Value != defaultAgentPath {
return false
}
} else if container.Name == "app2" {
if envVar.Value != javaEnvNonEmptyValue+defaultAgentPath {
return false
}
}
}
}
}
return true
}).Should(BeTrue())
})
It("Should add annotations to deployment", func() {
Eventually(func() bool {
if err := k8sClient.Get(ctx, lrAgentRequest5, &lrAgent5); err != nil {
return false
}
if patchedDepl4.Annotations["lightrun.com/lightrunjavaagent"] != lrAgent5.Name {
// logger.Info("annotations", "lightrun.com/lightrunjavaagent", patchedDepl4.Annotations["lightrun.com/lightrunjavaagent"])
return false
}
if patchedDepl4.Annotations["lightrun.com/patched-env-name"] != javaEnv {
// logger.Info("annotations", "lightrun.com/patched-env-name", patchedDepl4.Annotations["lightrun.com/patched-env-name"])
return false
}
if patchedDepl4.Annotations["lightrun.com/patched-env-value"] != defaultAgentPath {
// logger.Info("annotations", "lightrun.com/patched-env-value", patchedDepl4.Annotations["lightrun.com/patched-env-value"])
return false
}
return true
}).Should(BeTrue())
})
It("Should change env var name in the CR", func() {
By("Changing env var name")
if err := k8sClient.Get(ctx, lrAgentRequest5, &lrAgent5); err != nil {
Expect(err).ShouldNot(HaveOccurred())
}
lrAgent5.Spec.AgentEnvVarName = "NEW_ENV_NAME"
Expect(k8sClient.Update(ctx, &lrAgent5)).Should(Succeed())
})
It("Should patch new Env Var of containers with agent path", func() {
Eventually(func() bool {
if err := k8sClient.Get(ctx, deplRequest4, &patchedDepl4); err != nil {
return false
}
for _, container := range patchedDepl4.Spec.Template.Spec.Containers {
for _, envVar := range container.Env {
if container.Name == "app" {
if envVar.Name == "NEW_ENV_NAME" {
if envVar.Value != defaultAgentPath {
return false
}
} else if envVar.Name == javaEnv {
return false
}
}
if container.Name == "app2" {
if envVar.Name == "NEW_ENV_NAME" {
if envVar.Value != defaultAgentPath {
return false
}
} else if envVar.Name == javaEnv {
if javaEnvNonEmptyValue != envVar.Value {
return false
}
}
}
}
}
if patchedDepl4.Annotations["lightrun.com/patched-env-name"] != "NEW_ENV_NAME" {
return false
}
if patchedDepl4.Annotations["lightrun.com/patched-env-value"] != defaultAgentPath {
return false
}
return true
}).Should(BeTrue())
})
Context("When changing CLI flags", func() {
It("Should change CLI flags in the CR", func() {
Eventually(func() bool {
By("Changing CLI flags")
if err := k8sClient.Get(ctx, lrAgentRequest5, &lrAgent5); err != nil {
Expect(err).ShouldNot(HaveOccurred())
}
lrAgent5.Spec.AgentCliFlags = "--new-flags"
err = k8sClient.Update(ctx, &lrAgent5)
return err == nil
}).Should(BeTrue())

})
It("Should patch new CLI flags of containers with agent path", func() {
Eventually(func() bool {
if err := k8sClient.Get(ctx, deplRequest4, &patchedDepl4); err != nil {
return false
}
if patchedDepl4.Annotations["lightrun.com/patched-env-value"] != defaultAgentPath+"=--new-flags" {
logger.Info("annotations", "lightrun.com/patched-env-value", patchedDepl4.Annotations["lightrun.com/patched-env-value"])
return false
}
for _, container := range patchedDepl4.Spec.Template.Spec.Containers {
for _, envVar := range container.Env {
if container.Name == "app" {
if envVar.Name == "NEW_ENV_NAME" {
if envVar.Value != defaultAgentPath+"=--new-flags" {
logger.Info("first container", envVar.Name, envVar.Value)
return false
}
}
}
if container.Name == "app2" {
if envVar.Name == "NEW_ENV_NAME" {
if envVar.Value != defaultAgentPath+"=--new-flags" {
logger.Info("first container", envVar.Name, envVar.Value)
return false
}
}
}
}
}
return true
}).Should(BeTrue())
})
})
})
})
71 changes: 34 additions & 37 deletions internal/controller/patch_funcs.go
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"slices"
"strings"

agentv1beta "github.com/lightrun-platform/lightrun-k8s-operator/api/v1beta"
@@ -169,61 +170,57 @@ func (r *LightrunJavaAgentReconciler) patchAppContainers(lightrunJavaAgent *agen
}

// Client side patch, as we can't update value from 2 sources
func (r *LightrunJavaAgentReconciler) patchJavaToolEnv(container *corev1.Container, targetEnvVar string, mountPath string, agentCliFlags string) error {
agentArg := "-agentpath:" + mountPath + "/agent/lightrun_agent.so"
if agentCliFlags != "" {
agentArg += "=" + agentCliFlags
}

javaToolOptionsIndex := -1

for index, envVar := range container.Env {
if envVar.Name == targetEnvVar {
javaToolOptionsIndex = index
break
func (r *LightrunJavaAgentReconciler) patchJavaToolEnv(deplAnnotations map[string]string, container *corev1.Container, targetEnvVar string, agentArg string) error {
// Check if some env was already patched before
patchedEnv := deplAnnotations["lightrun.com/patched-env-name"]
patchedEnvValue := deplAnnotations["lightrun.com/patched-env-value"]

if patchedEnv != targetEnvVar || patchedEnvValue != agentArg {
// If different env was patched before - unpatch it
patchedEnvVarIndex := findEnvVarIndex(patchedEnv, container.Env)
if patchedEnvVarIndex != -1 {
unpatchedEnvValue := unpatchEnvVarValue(container.Env[patchedEnvVarIndex].Value, patchedEnvValue)
if unpatchedEnvValue == "" {
container.Env = slices.Delete(container.Env, patchedEnvVarIndex, patchedEnvVarIndex+1)
} else {
container.Env[patchedEnvVarIndex].Value = unpatchedEnvValue
}
}
}

if javaToolOptionsIndex == -1 {
targetEnvVarIndex := findEnvVarIndex(targetEnvVar, container.Env)
if targetEnvVarIndex == -1 {
// No such env - add new
container.Env = append(container.Env, corev1.EnvVar{
Name: targetEnvVar,
Value: agentArg,
})
} else {
if !strings.Contains(container.Env[javaToolOptionsIndex].Value, agentArg) {
if len(container.Env[javaToolOptionsIndex].Value+" "+agentArg) > 1024 {
return errors.New(targetEnvVar + " has more that 1024 chars")
if !strings.Contains(container.Env[targetEnvVarIndex].Value, agentArg) {
container.Env[targetEnvVarIndex].Value = container.Env[targetEnvVarIndex].Value + agentArg
if len(container.Env[targetEnvVarIndex].Value) > 1024 {
return errors.New(targetEnvVar + " has more that 1024 chars. This is a limitation of Java")
}
container.Env[javaToolOptionsIndex].Value = container.Env[javaToolOptionsIndex].Value + " " + agentArg
}
}
return nil
}

func (r *LightrunJavaAgentReconciler) unpatchJavaToolEnv(container *corev1.Container, targetEnvVar string, mountPath string, agentCliFlags string) *corev1.Container {
agentArg := "-agentpath:" + mountPath + "/agent/lightrun_agent.so"
if agentCliFlags != "" {
agentArg += "=" + agentCliFlags
func (r *LightrunJavaAgentReconciler) unpatchJavaToolEnv(deplAnnotations map[string]string, container *corev1.Container) *corev1.Container {
patchedEnv := deplAnnotations["lightrun.com/patched-env-name"]
patchedEnvValue := deplAnnotations["lightrun.com/patched-env-value"]
if patchedEnv == "" && patchedEnvValue == "" {
return container
}

var updatedSlice []corev1.EnvVar
for _, envVar := range container.Env {
if envVar.Name == targetEnvVar {
var updatedEnv []string
optArray := strings.Split(envVar.Value, " ")
for _, opt := range optArray {
if opt != agentArg {
updatedEnv = append(updatedEnv, opt)
}
}
if len(updatedEnv) > 0 {
envVar.Value = strings.Join(updatedEnv, " ")
updatedSlice = append(updatedSlice, envVar)
}
envVarIndex := findEnvVarIndex(patchedEnv, container.Env)
if envVarIndex != -1 {
value := strings.ReplaceAll(container.Env[envVarIndex].Value, patchedEnvValue, "")
if value == "" {
container.Env = slices.Delete(container.Env, envVarIndex, envVarIndex+1)
} else {
updatedSlice = append(updatedSlice, envVar)
container.Env[envVarIndex].Value = value
}
}
container.Env = updatedSlice
return container
}

0 comments on commit 04d4b21

Please sign in to comment.