diff --git a/src/instrumentation/dotnet.go b/src/apm/dotnet.go similarity index 98% rename from src/instrumentation/dotnet.go rename to src/apm/dotnet.go index ba7993ec..72b2c999 100644 --- a/src/instrumentation/dotnet.go +++ b/src/apm/dotnet.go @@ -13,7 +13,7 @@ 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 instrumentation +package apm import ( "errors" @@ -37,7 +37,7 @@ const ( dotnetInitContainerName = initContainerName + "-dotnet" ) -func injectDotNetSDK(dotNetSpec v1alpha1.DotNet, pod corev1.Pod, index int) (corev1.Pod, error) { +func InjectDotNetSDK(dotNetSpec v1alpha1.DotNet, pod corev1.Pod, index int) (corev1.Pod, error) { // caller checks if there is at least one container. container := &pod.Spec.Containers[index] diff --git a/src/instrumentation/golang.go b/src/apm/golang.go similarity index 96% rename from src/instrumentation/golang.go rename to src/apm/golang.go index 9dd97476..6d8b1659 100644 --- a/src/instrumentation/golang.go +++ b/src/apm/golang.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package instrumentation +package apm import ( "fmt" @@ -30,7 +30,7 @@ const ( kernelDebugVolumePath = "/sys/kernel/debug" ) -func injectGoSDK(goSpec v1alpha1.Go, pod corev1.Pod) (corev1.Pod, error) { +func InjectGoSDK(goSpec v1alpha1.Go, pod corev1.Pod) (corev1.Pod, error) { // skip instrumentation if share process namespaces is explicitly disabled if pod.Spec.ShareProcessNamespace != nil && !*pod.Spec.ShareProcessNamespace { return pod, fmt.Errorf("shared process namespace has been explicitly disabled") diff --git a/src/apm/helper.go b/src/apm/helper.go new file mode 100644 index 00000000..253a2229 --- /dev/null +++ b/src/apm/helper.go @@ -0,0 +1,79 @@ +/* +Copyright 2024. + +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. +*/ +package apm + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" +) + +const ( + volumeName = "newrelic-instrumentation" + initContainerName = "newrelic-instrumentation" + sideCarName = "opentelemetry-auto-instrumentation" + + // indicates whether newrelic agents should be injected or not. + // Possible values are "true", "false" or "" name. + annotationInjectJava = "instrumentation.newrelic.com/inject-java" + annotationInjectJavaContainersName = "instrumentation.newrelic.com/java-container-names" + annotationInjectNodeJS = "instrumentation.newrelic.com/inject-nodejs" + annotationInjectNodeJSContainersName = "instrumentation.newrelic.com/nodejs-container-names" + annotationInjectPython = "instrumentation.newrelic.com/inject-python" + annotationInjectPythonContainersName = "instrumentation.newrelic.com/python-container-names" + annotationInjectDotNet = "instrumentation.newrelic.com/inject-dotnet" + annotationInjectDotnetContainersName = "instrumentation.newrelic.com/dotnet-container-names" + annotationInjectPhp = "instrumentation.newrelic.com/inject-php" + annotationInjectPhpContainersName = "instrumentation.newrelic.com/php-container-names" + annotationPhpExecCmd = "instrumentation.newrelic.com/php-exec-command" + annotationInjectContainerName = "instrumentation.newrelic.com/container-name" + annotationInjectGo = "instrumentation.opentelemetry.io/inject-go" + annotationGoExecPath = "instrumentation.opentelemetry.io/otel-go-auto-target-exe" + annotationInjectGoContainerName = "instrumentation.opentelemetry.io/go-container-name" +) + +// Calculate if we already inject InitContainers. +func isInitContainerMissing(pod corev1.Pod) bool { + for _, initContainer := range pod.Spec.InitContainers { + if initContainer.Name == initContainerName { + return false + } + } + return true +} + +func getIndexOfEnv(envs []corev1.EnvVar, name string) int { + for i := range envs { + if envs[i].Name == name { + return i + } + } + return -1 +} + +func validateContainerEnv(envs []corev1.EnvVar, envsToBeValidated ...string) error { + for _, envToBeValidated := range envsToBeValidated { + for _, containerEnv := range envs { + if containerEnv.Name == envToBeValidated { + if containerEnv.ValueFrom != nil { + return fmt.Errorf("the container defines env var value via ValueFrom, envVar: %s", containerEnv.Name) + } + break + } + } + } + return nil +} diff --git a/src/instrumentation/javaagent.go b/src/apm/javaagent.go similarity index 96% rename from src/instrumentation/javaagent.go rename to src/apm/javaagent.go index bec096f7..8b07bf9b 100644 --- a/src/instrumentation/javaagent.go +++ b/src/apm/javaagent.go @@ -13,7 +13,7 @@ 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 instrumentation +package apm import ( corev1 "k8s.io/api/core/v1" @@ -28,7 +28,7 @@ const ( javaVolumeName = volumeName + "-java" ) -func injectJavaagent(javaSpec v1alpha1.Java, pod corev1.Pod, index int) (corev1.Pod, error) { +func InjectJavaagent(javaSpec v1alpha1.Java, pod corev1.Pod, index int) (corev1.Pod, error) { // caller checks if there is at least one container. container := &pod.Spec.Containers[index] diff --git a/src/instrumentation/nodejs.go b/src/apm/nodejs.go similarity index 96% rename from src/instrumentation/nodejs.go rename to src/apm/nodejs.go index b37604aa..fd4ef0f7 100644 --- a/src/instrumentation/nodejs.go +++ b/src/apm/nodejs.go @@ -13,7 +13,7 @@ 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 instrumentation +package apm import ( corev1 "k8s.io/api/core/v1" @@ -28,7 +28,7 @@ const ( nodejsVolumeName = volumeName + "-nodejs" ) -func injectNodeJSSDK(nodeJSSpec v1alpha1.NodeJS, pod corev1.Pod, index int) (corev1.Pod, error) { +func InjectNodeJSSDK(nodeJSSpec v1alpha1.NodeJS, pod corev1.Pod, index int) (corev1.Pod, error) { // caller checks if there is at least one container. container := &pod.Spec.Containers[index] diff --git a/src/instrumentation/php.go b/src/apm/php.go similarity index 97% rename from src/instrumentation/php.go rename to src/apm/php.go index b66278fe..70984aeb 100644 --- a/src/instrumentation/php.go +++ b/src/apm/php.go @@ -13,7 +13,7 @@ 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 instrumentation +package apm import ( "fmt" @@ -33,7 +33,7 @@ const ( phpInstallArgument = "/newrelic-instrumentation/newrelic-install install && sed -i -e \"s/PHP Application/$NEW_RELIC_APP_NAME/g; s/REPLACE_WITH_REAL_KEY/$NEW_RELIC_LICENSE_KEY/g\" /usr/local/etc/php/conf.d/newrelic.ini" ) -func injectPhpagent(phpSpec v1alpha1.Php, pod corev1.Pod, index int) (corev1.Pod, error) { +func InjectPhpagent(phpSpec v1alpha1.Php, pod corev1.Pod, index int) (corev1.Pod, error) { // caller checks if there is at least one container. container := &pod.Spec.Containers[index] diff --git a/src/instrumentation/python.go b/src/apm/python.go similarity index 96% rename from src/instrumentation/python.go rename to src/apm/python.go index 2ae38b20..0a11c08c 100644 --- a/src/instrumentation/python.go +++ b/src/apm/python.go @@ -13,7 +13,7 @@ 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 instrumentation +package apm import ( "fmt" @@ -31,7 +31,7 @@ const ( pythonInitContainerName = initContainerName + "-python" ) -func injectPythonSDK(pythonSpec v1alpha1.Python, pod corev1.Pod, index int) (corev1.Pod, error) { +func InjectPythonSDK(pythonSpec v1alpha1.Python, pod corev1.Pod, index int) (corev1.Pod, error) { // caller checks if there is at least one container. container := &pod.Spec.Containers[index] diff --git a/src/instrumentation/helper.go b/src/instrumentation/helper.go deleted file mode 100644 index cda5fbd9..00000000 --- a/src/instrumentation/helper.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright 2024. - -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. -*/ -package instrumentation - -import ( - corev1 "k8s.io/api/core/v1" -) - -// Calculate if we already inject InitContainers. -func isInitContainerMissing(pod corev1.Pod) bool { - for _, initContainer := range pod.Spec.InitContainers { - if initContainer.Name == initContainerName { - return false - } - } - return true -} diff --git a/src/instrumentation/sdk.go b/src/instrumentation/sdk.go index cdaa7151..3c931551 100644 --- a/src/instrumentation/sdk.go +++ b/src/instrumentation/sdk.go @@ -37,15 +37,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/newrelic/k8s-agents-operator/src/api/v1alpha1" + apm "github.com/newrelic/k8s-agents-operator/src/apm" "github.com/newrelic/k8s-agents-operator/src/constants" ) -const ( - volumeName = "newrelic-instrumentation" - initContainerName = "newrelic-instrumentation" - sideCarName = "opentelemetry-auto-instrumentation" -) - type sdkInjector struct { client client.Client logger logr.Logger @@ -69,7 +64,7 @@ func (i *sdkInjector) inject(ctx context.Context, insts languageInstrumentations newrelic := *insts.Java var err error i.logger.V(1).Info("injecting Java instrumentation into pod", "newrelic-namespace", newrelic.Namespace, "newrelic-name", newrelic.Name) - pod, err = injectJavaagent(newrelic.Spec.Java, pod, index) + pod, err = apm.InjectJavaagent(newrelic.Spec.Java, pod, index) if err != nil { i.logger.Info("Skipping Java agent injection", "reason", err.Error(), "container", pod.Spec.Containers[index].Name) } else { @@ -80,7 +75,7 @@ func (i *sdkInjector) inject(ctx context.Context, insts languageInstrumentations newrelic := *insts.NodeJS var err error i.logger.V(1).Info("injecting NodeJS instrumentation into pod", "newrelic-namespace", newrelic.Namespace, "newrelic-name", newrelic.Name) - pod, err = injectNodeJSSDK(newrelic.Spec.NodeJS, pod, index) + pod, err = apm.InjectNodeJSSDK(newrelic.Spec.NodeJS, pod, index) if err != nil { i.logger.Info("Skipping NodeJS agent injection", "reason", err.Error(), "container", pod.Spec.Containers[index].Name) } else { @@ -91,7 +86,7 @@ func (i *sdkInjector) inject(ctx context.Context, insts languageInstrumentations newrelic := *insts.Python var err error i.logger.V(1).Info("injecting Python instrumentation into pod", "newrelic-namespace", newrelic.Namespace, "newrelic-name", newrelic.Name) - pod, err = injectPythonSDK(newrelic.Spec.Python, pod, index) + pod, err = apm.InjectPythonSDK(newrelic.Spec.Python, pod, index) if err != nil { i.logger.Info("Skipping Python agent injection", "reason", err.Error(), "container", pod.Spec.Containers[index].Name) } else { @@ -102,7 +97,7 @@ func (i *sdkInjector) inject(ctx context.Context, insts languageInstrumentations newrelic := *insts.DotNet var err error i.logger.V(1).Info("injecting DotNet instrumentation into pod", "newrelic-namespace", newrelic.Namespace, "newrelic-name", newrelic.Name) - pod, err = injectDotNetSDK(newrelic.Spec.DotNet, pod, index) + pod, err = apm.InjectDotNetSDK(newrelic.Spec.DotNet, pod, index) if err != nil { i.logger.Info("Skipping DotNet agent injection", "reason", err.Error(), "container", pod.Spec.Containers[index].Name) } else { @@ -113,7 +108,7 @@ func (i *sdkInjector) inject(ctx context.Context, insts languageInstrumentations newrelic := *insts.Php var err error i.logger.V(1).Info("injecting Php instrumentation into pod", "newrelic-namespace", newrelic.Namespace, "newrelic-name", newrelic.Name) - pod, err = injectPhpagent(newrelic.Spec.Php, pod, index) + pod, err = apm.InjectPhpagent(newrelic.Spec.Php, pod, index) if err != nil { i.logger.Info("Skipping Php agent injection", "reason", err.Error(), "container", pod.Spec.Containers[index].Name) } else { @@ -121,7 +116,6 @@ func (i *sdkInjector) inject(ctx context.Context, insts languageInstrumentations } } if insts.Go != nil { - origPod := pod newrelic := *insts.Go var err error i.logger.V(1).Info("injecting Go instrumentation into pod", "newrelic-namespace", newrelic.Namespace, "newrelic-name", newrelic.Name) @@ -130,20 +124,13 @@ func (i *sdkInjector) inject(ctx context.Context, insts languageInstrumentations index := getContainerIndex(goContainers, pod) // Go instrumentation supports only single container instrumentation. - pod, err = injectGoSDK(newrelic.Spec.Go, pod) + pod, err = apm.InjectGoSDK(newrelic.Spec.Go, pod) if err != nil { i.logger.Info("Skipping Go SDK injection", "reason", err.Error(), "container", pod.Spec.Containers[index].Name) } else { // Common env vars and config need to be applied to the agent container. pod = i.injectCommonEnvVar(newrelic, pod, len(pod.Spec.Containers)-1) pod = i.injectCommonSDKConfig(ctx, newrelic, ns, pod, len(pod.Spec.Containers)-1, 0) - - // Ensure that after all the env var coalescing we have a value for OTEL_GO_AUTO_TARGET_EXE - idx := getIndexOfEnv(pod.Spec.Containers[len(pod.Spec.Containers)-1].Env, envOtelTargetExe) - if idx == -1 { - i.logger.Info("Skipping Go SDK injection", "reason", "OTEL_GO_AUTO_TARGET_EXE not set", "container", pod.Spec.Containers[index].Name) - pod = origPod - } } } return pod @@ -507,17 +494,3 @@ func moveEnvToListEnd(envs []corev1.EnvVar, idx int) []corev1.EnvVar { return envs } - -func validateContainerEnv(envs []corev1.EnvVar, envsToBeValidated ...string) error { - for _, envToBeValidated := range envsToBeValidated { - for _, containerEnv := range envs { - if containerEnv.Name == envToBeValidated { - if containerEnv.ValueFrom != nil { - return fmt.Errorf("the container defines env var value via ValueFrom, envVar: %s", containerEnv.Name) - } - break - } - } - } - return nil -}