Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add labels #6

Merged
merged 1 commit into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

operatorv1alpha1 "github.com/dash0hq/dash0-operator/api/v1alpha1"
"github.com/dash0hq/dash0-operator/internal/controller"
"github.com/dash0hq/dash0-operator/internal/k8sresources"
dash0webhook "github.com/dash0hq/dash0-operator/internal/webhook"
//+kubebuilder:scaffold:imports
)
Expand Down Expand Up @@ -137,11 +138,32 @@ func startOperatorManager(
return fmt.Errorf("unable to create the clientset client")
}

operatorVersion, isSet := os.LookupEnv("DASH0_OPERATOR_VERSION")
if !isSet {
operatorVersion = "unknown"
}
initContainerImageVersion, isSet := os.LookupEnv("DASH0_INIT_CONTAINER_IMAGE_VERSION")
if !isSet {
return fmt.Errorf("cannot start Dash0 operator, the mandatory environment variable " +
"\"DASH0_INIT_CONTAINER_IMAGE_VERSION\" is missing")
}
setupLog.Info(
"version information",
"operator version",
operatorVersion,
"init container image version",
initContainerImageVersion,
)

if err = (&controller.Dash0Reconciler{
Client: mgr.GetClient(),
ClientSet: clientSet,
Scheme: mgr.GetScheme(),
Recorder: mgr.GetEventRecorderFor("dash0-controller"),
Versions: k8sresources.Versions{
OperatorVersion: operatorVersion,
InitContainerImageVersion: initContainerImageVersion,
},
}).SetupWithManager(mgr); err != nil {
return fmt.Errorf("unable to set up the Dash0 reconciler: %w", err)
}
Expand All @@ -150,6 +172,10 @@ func startOperatorManager(
if os.Getenv("ENABLE_WEBHOOKS") != "false" {
if err = (&dash0webhook.Handler{
Recorder: mgr.GetEventRecorderFor("dash0-webhook"),
Versions: k8sresources.Versions{
OperatorVersion: operatorVersion,
InitContainerImageVersion: initContainerImageVersion,
},
}).SetupWebhookWithManager(mgr); err != nil {
return fmt.Errorf("unable to create the Dash0 webhook: %w", err)
}
Expand Down
5 changes: 5 additions & 0 deletions config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ spec:
- --leader-elect
image: dash0-operator-controller:latest
name: manager
env:
- name: DASH0_OPERATOR_VERSION
value: 1.0.0
- name: DASH0_INIT_CONTAINER_IMAGE_VERSION
value: 1.0.0

# Note: Use "imagePullPolicy: Never" when only building the image locally without pushing them anywhere. Omit
# the attribute otherwise to use the default pull policy.
Expand Down
8 changes: 7 additions & 1 deletion internal/controller/dash0_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Dash0Reconciler struct {
ClientSet *kubernetes.Clientset
Scheme *runtime.Scheme
Recorder record.EventRecorder
Versions k8sresources.Versions
}

func (r *Dash0Reconciler) SetupWithManager(mgr ctrl.Manager) error {
Expand Down Expand Up @@ -186,7 +187,12 @@ func (r *Dash0Reconciler) modifyExistingResources(ctx context.Context, dash0Cust
}, &deployment); err != nil {
return fmt.Errorf("error when fetching deployment %s/%s: %w", deployment.GetNamespace(), deployment.GetName(), err)
}
hasBeenModified := k8sresources.ModifyPodSpec(&deployment.Spec.Template.Spec, deployment.GetNamespace(), logger)
hasBeenModified := k8sresources.ModifyDeployment(
&deployment,
deployment.GetNamespace(),
r.Versions,
logger,
)
if hasBeenModified {
return r.Client.Update(ctx, &deployment)
} else {
Expand Down
7 changes: 7 additions & 0 deletions internal/controller/dash0_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,17 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"

operatorv1alpha1 "github.com/dash0hq/dash0-operator/api/v1alpha1"
"github.com/dash0hq/dash0-operator/internal/k8sresources"
)

var (
timeout = 15 * time.Second
pollingInterval = 50 * time.Millisecond

versions = k8sresources.Versions{
OperatorVersion: "1.2.3",
InitContainerImageVersion: "4.5.6",
}
)

var _ = Describe("Dash0 Controller", func() {
Expand Down Expand Up @@ -61,6 +67,7 @@ var _ = Describe("Dash0 Controller", func() {
ClientSet: clientset,
Recorder: recorder,
Scheme: k8sClient.Scheme(),
Versions: versions,
}
})

Expand Down
62 changes: 53 additions & 9 deletions internal/k8sresources/modify.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import (
"slices"

"github.com/go-logr/logr"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
initContainerName = "dash0-instrumentation"
initContainerImage = "dash0-instrumentation:1.0.0"
initContainerName = "dash0-instrumentation"
initContainerImageTemplate = "dash0-instrumentation:%s"

dash0VolumeName = "dash0-instrumentation"
dash0DirectoryEnvVarName = "DASH0_INSTRUMENTATION_FOLDER_DESTINATION"
Expand All @@ -28,6 +29,10 @@ const (
envVarNodeOptionsValue = "--require /opt/dash0/instrumentation/node.js/node_modules/@dash0/opentelemetry/src/index.js"
envVarDash0CollectorBaseUrlName = "DASH0_OTEL_COLLECTOR_BASE_URL"
envVarDash0CollectorBaseUrlNameValueTemplate = "http://dash0-opentelemetry-collector-daemonset.%s.svc.cluster.local:4318"

instrumentedLabelKey = "dash0.instrumented"
operatorVersionLabelKey = "dash0.operator.version"
initContainerImageVersionLabelKey = "dash0.initcontainer.image.version"
)

var (
Expand All @@ -38,14 +43,40 @@ var (
initContainerReadOnlyRootFilesystem = true
)

func ModifyPodSpec(podSpec *corev1.PodSpec, namespace string, logger logr.Logger) bool {
type Versions struct {
OperatorVersion string
InitContainerImageVersion string
}

func ModifyDeployment(
deployment *appsv1.Deployment,
namespace string,
versions Versions,
logger logr.Logger,
) bool {
podTemplateSpec := &deployment.Spec.Template
hasBeenModified := modifyPodSpec(
&podTemplateSpec.Spec,
namespace,
versions.InitContainerImageVersion,
logger,
)
if hasBeenModified {
addInstrumentationLabels(&deployment.ObjectMeta, versions)
addInstrumentationLabels(&podTemplateSpec.ObjectMeta, versions)
}
return hasBeenModified
}

func modifyPodSpec(podSpec *corev1.PodSpec, namespace string, initContainerImageVersion string, logger logr.Logger) bool {
originalSpec := podSpec.DeepCopy()
addInstrumentationVolume(podSpec)
addInitContainer(podSpec)
addInitContainer(podSpec, initContainerImageVersion)
for idx := range podSpec.Containers {
container := &podSpec.Containers[idx]
instrumentContainer(container, namespace, logger)
}

return !reflect.DeepEqual(originalSpec, podSpec)
}

Expand All @@ -72,7 +103,7 @@ func addInstrumentationVolume(podSpec *corev1.PodSpec) {
}
}

func addInitContainer(podSpec *corev1.PodSpec) {
func addInitContainer(podSpec *corev1.PodSpec, initContainerImageVersion string) {
// The init container has all the instrumentation packages (e.g. the Dash0 Node.js distribution etc.), stored under
// /dash0/instrumentation. Its main responsibility is to copy these files to the Kubernetes volume created and mounted in
// addInstrumentationVolume (mounted at /opt/dash0/instrumentation in the init container and also in the target containers).
Expand All @@ -83,15 +114,15 @@ func addInitContainer(podSpec *corev1.PodSpec) {
idx := slices.IndexFunc(podSpec.InitContainers, func(c corev1.Container) bool {
return c.Name == initContainerName
})
initContainer := createInitContainer(podSpec)
initContainer := createInitContainer(podSpec, initContainerImageVersion)
if idx < 0 {
podSpec.InitContainers = append(podSpec.InitContainers, *initContainer)
} else {
podSpec.InitContainers[idx] = *initContainer
}
}

func createInitContainer(podSpec *corev1.PodSpec) *corev1.Container {
func createInitContainer(podSpec *corev1.PodSpec, initContainerImageVersion string) *corev1.Container {
initContainerUser := &defaultInitContainerUser
initContainerGroup := &defaultInitContainerGroup

Expand All @@ -106,7 +137,7 @@ func createInitContainer(podSpec *corev1.PodSpec) *corev1.Container {

return &corev1.Container{
Name: initContainerName,
Image: initContainerImage,
Image: fmt.Sprintf(initContainerImageTemplate, initContainerImageVersion),
Env: []corev1.EnvVar{
{
Name: dash0DirectoryEnvVarName,
Expand Down Expand Up @@ -212,3 +243,16 @@ func addOrReplaceEnvironmentVariable(container *corev1.Container, name string, v
container.Env[idx].Value = value
}
}

func addInstrumentationLabels(meta *v1.ObjectMeta, labelInformation Versions) {
addLabel(meta, instrumentedLabelKey, "true")
addLabel(meta, operatorVersionLabelKey, labelInformation.OperatorVersion)
addLabel(meta, initContainerImageVersionLabelKey, labelInformation.InitContainerImageVersion)
}

func addLabel(meta *v1.ObjectMeta, key string, value string) {
if meta.Labels == nil {
meta.Labels = make(map[string]string, 1)
}
meta.Labels[key] = value
}
28 changes: 25 additions & 3 deletions internal/k8sresources/modify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,26 @@ import (
// intentional. However, this test should be used for more fine-grained test cases, while dash0_webhook_test.go should
// be used to verify external effects (recording events etc.) that cannot be covered in this test.

var (
versions = Versions{
OperatorVersion: "1.2.3",
InitContainerImageVersion: "4.5.6",
}
)

var _ = Describe("Dash0 Resource Modification", func() {

ctx := context.Background()

Context("when mutating new deployments", func() {
It("should inject Dash into a new basic deployment", func() {
deployment := BasicDeployment(TestNamespaceName, DeploymentName)
result := ModifyPodSpec(&deployment.Spec.Template.Spec, TestNamespaceName, log.FromContext(ctx))
result := ModifyDeployment(
deployment,
TestNamespaceName,
versions,
log.FromContext(ctx),
)

Expect(result).To(BeTrue())
VerifyModifiedDeployment(deployment, DeploymentExpectations{
Expand All @@ -46,7 +58,12 @@ var _ = Describe("Dash0 Resource Modification", func() {

It("should inject Dash into a new deployment that has multiple Containers, and already has Volumes and init Containers", func() {
deployment := DeploymentWithMoreBellsAndWhistles(TestNamespaceName, DeploymentName)
result := ModifyPodSpec(&deployment.Spec.Template.Spec, TestNamespaceName, log.FromContext(ctx))
result := ModifyDeployment(
deployment,
TestNamespaceName,
versions,
log.FromContext(ctx),
)

Expect(result).To(BeTrue())
VerifyModifiedDeployment(deployment, DeploymentExpectations{
Expand Down Expand Up @@ -77,7 +94,12 @@ var _ = Describe("Dash0 Resource Modification", func() {

It("should update existing Dash artifacts in a new deployment", func() {
deployment := DeploymentWithExistingDash0Artifacts(TestNamespaceName, DeploymentName)
result := ModifyPodSpec(&deployment.Spec.Template.Spec, TestNamespaceName, log.FromContext(ctx))
result := ModifyDeployment(
deployment,
TestNamespaceName,
versions,
log.FromContext(ctx),
)

Expect(result).To(BeTrue())
VerifyModifiedDeployment(deployment, DeploymentExpectations{
Expand Down
3 changes: 2 additions & 1 deletion internal/webhook/dash0_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var (

type Handler struct {
Recorder record.EventRecorder
Versions k8sresources.Versions
}

func (h *Handler) SetupWebhookWithManager(mgr ctrl.Manager) error {
Expand Down Expand Up @@ -59,7 +60,7 @@ func (h *Handler) Handle(_ context.Context, request admission.Request) admission
return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error while parsing the resource: %w", err))
}

hasBeenModified := k8sresources.ModifyPodSpec(&deployment.Spec.Template.Spec, request.Namespace, logger)
hasBeenModified := k8sresources.ModifyDeployment(deployment, request.Namespace, h.Versions, logger)
if !hasBeenModified {
return admission.Allowed("no changes")
}
Expand Down
7 changes: 7 additions & 0 deletions internal/webhook/webhook_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
. "github.com/onsi/gomega"

operatorv1alpha1 "github.com/dash0hq/dash0-operator/api/v1alpha1"
"github.com/dash0hq/dash0-operator/internal/k8sresources"
admissionv1 "k8s.io/api/admission/v1"
apimachineryruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
Expand All @@ -40,6 +41,11 @@ var (
testEnv *envtest.Environment
ctx context.Context
cancel context.CancelFunc

versions = k8sresources.Versions{
OperatorVersion: "1.2.3",
InitContainerImageVersion: "4.5.6",
}
)

func TestWebhook(t *testing.T) {
Expand Down Expand Up @@ -115,6 +121,7 @@ var _ = BeforeSuite(func() {

err = (&Handler{
Recorder: mgr.GetEventRecorderFor("dash0-webhook"),
Versions: versions,
}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

Expand Down
Loading