diff --git a/api/datadoghq/v2alpha1/datadogagent_types.go b/api/datadoghq/v2alpha1/datadogagent_types.go index 3929616e1..76b269e62 100644 --- a/api/datadoghq/v2alpha1/datadogagent_types.go +++ b/api/datadoghq/v2alpha1/datadogagent_types.go @@ -284,6 +284,13 @@ type CSIConfig struct { // Default: false // +optional Enabled *bool `json:"enabled,omitempty"` + + // CreateDatadogCSIDriver instructs the operator to create a DatadogCSIDriver custom resource + // when CSI is enabled. This requires the DatadogCSIDriver CRD to be installed and the + // DatadogCSIDriver controller to be enabled on the operator. + // Default: false + // +optional + CreateDatadogCSIDriver *bool `json:"createDatadogCSIDriver,omitempty"` } // InjectorConfig contains the configuration for the APM Injector. diff --git a/api/datadoghq/v2alpha1/zz_generated.deepcopy.go b/api/datadoghq/v2alpha1/zz_generated.deepcopy.go index 9c768f10a..f5285a201 100644 --- a/api/datadoghq/v2alpha1/zz_generated.deepcopy.go +++ b/api/datadoghq/v2alpha1/zz_generated.deepcopy.go @@ -450,6 +450,11 @@ func (in *CSIConfig) DeepCopyInto(out *CSIConfig) { *out = new(bool) **out = **in } + if in.CreateDatadogCSIDriver != nil { + in, out := &in.CreateDatadogCSIDriver, &out.CreateDatadogCSIDriver + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CSIConfig. diff --git a/config/crd/bases/v1/datadoghq.com_datadogagentinternals.yaml b/config/crd/bases/v1/datadoghq.com_datadogagentinternals.yaml index 30ad41021..8275f05f9 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagentinternals.yaml +++ b/config/crd/bases/v1/datadoghq.com_datadogagentinternals.yaml @@ -2612,6 +2612,13 @@ spec: csi: description: CSI contains configuration for Datadog CSI Driver properties: + createDatadogCSIDriver: + description: |- + CreateDatadogCSIDriver instructs the operator to create a DatadogCSIDriver custom resource + when CSI is enabled. This requires the DatadogCSIDriver CRD to be installed and the + DatadogCSIDriver controller to be enabled on the operator. + Default: false + type: boolean enabled: description: |- Enables the usage of CSI driver in Datadog Agent. diff --git a/config/crd/bases/v1/datadoghq.com_datadogagentinternals_v1alpha1.json b/config/crd/bases/v1/datadoghq.com_datadogagentinternals_v1alpha1.json index b6e0f6900..ed36e63df 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagentinternals_v1alpha1.json +++ b/config/crd/bases/v1/datadoghq.com_datadogagentinternals_v1alpha1.json @@ -2739,6 +2739,10 @@ "additionalProperties": false, "description": "CSI contains configuration for Datadog CSI Driver", "properties": { + "createDatadogCSIDriver": { + "description": "CreateDatadogCSIDriver instructs the operator to create a DatadogCSIDriver custom resource\nwhen CSI is enabled. This requires the DatadogCSIDriver CRD to be installed and the\nDatadogCSIDriver controller to be enabled on the operator.\nDefault: false", + "type": "boolean" + }, "enabled": { "description": "Enables the usage of CSI driver in Datadog Agent.\nRequires installation of Datadog CSI Driver https://github.com/DataDog/helm-charts/tree/main/charts/datadog-csi-driver\nDefault: false", "type": "boolean" diff --git a/config/crd/bases/v1/datadoghq.com_datadogagentprofiles.yaml b/config/crd/bases/v1/datadoghq.com_datadogagentprofiles.yaml index afd1b0e50..f81ec6b2d 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagentprofiles.yaml +++ b/config/crd/bases/v1/datadoghq.com_datadogagentprofiles.yaml @@ -2612,6 +2612,13 @@ spec: csi: description: CSI contains configuration for Datadog CSI Driver properties: + createDatadogCSIDriver: + description: |- + CreateDatadogCSIDriver instructs the operator to create a DatadogCSIDriver custom resource + when CSI is enabled. This requires the DatadogCSIDriver CRD to be installed and the + DatadogCSIDriver controller to be enabled on the operator. + Default: false + type: boolean enabled: description: |- Enables the usage of CSI driver in Datadog Agent. diff --git a/config/crd/bases/v1/datadoghq.com_datadogagentprofiles_v1alpha1.json b/config/crd/bases/v1/datadoghq.com_datadogagentprofiles_v1alpha1.json index 3185fb853..83bace1aa 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagentprofiles_v1alpha1.json +++ b/config/crd/bases/v1/datadoghq.com_datadogagentprofiles_v1alpha1.json @@ -2743,6 +2743,10 @@ "additionalProperties": false, "description": "CSI contains configuration for Datadog CSI Driver", "properties": { + "createDatadogCSIDriver": { + "description": "CreateDatadogCSIDriver instructs the operator to create a DatadogCSIDriver custom resource\nwhen CSI is enabled. This requires the DatadogCSIDriver CRD to be installed and the\nDatadogCSIDriver controller to be enabled on the operator.\nDefault: false", + "type": "boolean" + }, "enabled": { "description": "Enables the usage of CSI driver in Datadog Agent.\nRequires installation of Datadog CSI Driver https://github.com/DataDog/helm-charts/tree/main/charts/datadog-csi-driver\nDefault: false", "type": "boolean" diff --git a/config/crd/bases/v1/datadoghq.com_datadogagents.yaml b/config/crd/bases/v1/datadoghq.com_datadogagents.yaml index 22ecfd013..ad20f86bc 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagents.yaml +++ b/config/crd/bases/v1/datadoghq.com_datadogagents.yaml @@ -2616,6 +2616,13 @@ spec: csi: description: CSI contains configuration for Datadog CSI Driver properties: + createDatadogCSIDriver: + description: |- + CreateDatadogCSIDriver instructs the operator to create a DatadogCSIDriver custom resource + when CSI is enabled. This requires the DatadogCSIDriver CRD to be installed and the + DatadogCSIDriver controller to be enabled on the operator. + Default: false + type: boolean enabled: description: |- Enables the usage of CSI driver in Datadog Agent. diff --git a/config/crd/bases/v1/datadoghq.com_datadogagents_v2alpha1.json b/config/crd/bases/v1/datadoghq.com_datadogagents_v2alpha1.json index 84ba9c4e7..d4c45af74 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagents_v2alpha1.json +++ b/config/crd/bases/v1/datadoghq.com_datadogagents_v2alpha1.json @@ -2739,6 +2739,10 @@ "additionalProperties": false, "description": "CSI contains configuration for Datadog CSI Driver", "properties": { + "createDatadogCSIDriver": { + "description": "CreateDatadogCSIDriver instructs the operator to create a DatadogCSIDriver custom resource\nwhen CSI is enabled. This requires the DatadogCSIDriver CRD to be installed and the\nDatadogCSIDriver controller to be enabled on the operator.\nDefault: false", + "type": "boolean" + }, "enabled": { "description": "Enables the usage of CSI driver in Datadog Agent.\nRequires installation of Datadog CSI Driver https://github.com/DataDog/helm-charts/tree/main/charts/datadog-csi-driver\nDefault: false", "type": "boolean" diff --git a/docs/configuration.v2alpha1.md b/docs/configuration.v2alpha1.md index 3705d38cd..f6717197e 100644 --- a/docs/configuration.v2alpha1.md +++ b/docs/configuration.v2alpha1.md @@ -218,6 +218,7 @@ spec: | global.credentials.appSecret.keyName | KeyName is the key of the secret to use. | | global.credentials.appSecret.secretName | SecretName is the name of the secret. | | global.criSocketPath | Path to the container runtime socket (if different from Docker). | +| global.csi.createDatadogCSIDriver | CreateDatadogCSIDriver instructs the operator to create a DatadogCSIDriver custom resource when CSI is enabled. This requires the DatadogCSIDriver CRD to be installed and the DatadogCSIDriver controller to be enabled on the operator. Default: false | | global.csi.enabled | Enables the usage of CSI driver in Datadog Agent. Requires installation of Datadog CSI Driver https://github.com/DataDog/helm-charts/tree/main/charts/datadog-csi-driver Default: false | | global.disableNonResourceRules | Set DisableNonResourceRules to exclude NonResourceURLs from default ClusterRoles. Required 'true' for Google Cloud Marketplace. | | global.dockerSocketPath | Path to the docker runtime socket. | diff --git a/docs/configuration_public.md b/docs/configuration_public.md index c536cfd61..66708f109 100644 --- a/docs/configuration_public.md +++ b/docs/configuration_public.md @@ -432,6 +432,9 @@ spec: `global.criSocketPath` : Path to the container runtime socket (if different from Docker). +`global.csi.createDatadogCSIDriver` +: CreateDatadogCSIDriver instructs the operator to create a DatadogCSIDriver custom resource when CSI is enabled. This requires the DatadogCSIDriver CRD to be installed and the DatadogCSIDriver controller to be enabled on the operator. Default: false + `global.csi.enabled` : Enables the usage of CSI driver in Datadog Agent. Requires installation of Datadog CSI Driver https://github.com/DataDog/helm-charts/tree/main/charts/datadog-csi-driver Default: false diff --git a/internal/controller/datadogagent/ddcsi.go b/internal/controller/datadogagent/ddcsi.go new file mode 100644 index 000000000..8efa63ce0 --- /dev/null +++ b/internal/controller/datadogagent/ddcsi.go @@ -0,0 +1,133 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +package datadogagent + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + apiequality "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/DataDog/datadog-operator/api/datadoghq/v1alpha1" + "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" + apiutils "github.com/DataDog/datadog-operator/api/utils" + "github.com/DataDog/datadog-operator/pkg/kubernetes" +) + +const datadogCSIDriverKind = "DatadogCSIDriver" + +// reconcileDatadogCSIDriver creates or deletes a DatadogCSIDriver custom resource based on the +// DDA's spec.global.csi configuration. +// +// This uses direct create/delete via the API client rather than the dependency store because the +// store (store.Store) only supports built-in Kubernetes types via a hardcoded ObjectKind enum, +// type-specific factory functions, and type-specific equality checks. Extending it for a single +// custom resource would require changes across multiple packages (pkg/kubernetes, pkg/equality, +// store). Direct management is simpler and follows the same pattern used for DatadogAgentInternal. +func (r *Reconciler) reconcileDatadogCSIDriver(ctx context.Context, logger logr.Logger, instance *v2alpha1.DatadogAgent) error { + csiEnabled := instance.Spec.Global != nil && + instance.Spec.Global.CSI != nil && + apiutils.BoolValue(instance.Spec.Global.CSI.Enabled) && + apiutils.BoolValue(instance.Spec.Global.CSI.CreateDatadogCSIDriver) + + if csiEnabled { + return r.createOrUpdateDatadogCSIDriver(ctx, logger, instance) + } + return r.cleanupDatadogCSIDriver(ctx, logger, instance) +} + +// buildDesiredDatadogCSIDriver builds the desired DatadogCSIDriver object from the DDA spec. +// Currently the spec is empty (the DatadogCSIDriver controller applies its own defaults). +// In a follow-up, this will construct the spec based on relevant DDA fields. +func (r *Reconciler) buildDesiredDatadogCSIDriver(instance *v2alpha1.DatadogAgent) (*v1alpha1.DatadogCSIDriver, error) { + ddcsi := &v1alpha1.DatadogCSIDriver{ + ObjectMeta: metav1.ObjectMeta{ + Name: instance.Name, + Namespace: instance.Namespace, + }, + } + if err := controllerutil.SetControllerReference(instance, ddcsi, r.scheme); err != nil { + return nil, fmt.Errorf("failed to set owner reference on DatadogCSIDriver: %w", err) + } + return ddcsi, nil +} + +// createOrUpdateDatadogCSIDriver ensures a DatadogCSIDriver resource exists with the desired spec. +// Like DatadogAgentInternal, this treats the DatadogCSIDriver as a desired-state object: if someone +// modifies it, the operator will reconcile it back to the desired state on the next loop. +func (r *Reconciler) createOrUpdateDatadogCSIDriver(ctx context.Context, logger logr.Logger, instance *v2alpha1.DatadogAgent) error { + // Guard: check that the DatadogCSIDriver CRD is installed on the cluster. + if !r.platformInfo.IsResourceSupported(datadogCSIDriverKind) { + return fmt.Errorf("DatadogCSIDriver CRD is not installed on the cluster but spec.global.csi.createDatadogCSIDriver is enabled") + } + + desired, err := r.buildDesiredDatadogCSIDriver(instance) + if err != nil { + return err + } + + existing := &v1alpha1.DatadogCSIDriver{} + err = r.client.Get(ctx, types.NamespacedName{Name: desired.Name, Namespace: desired.Namespace}, existing) + if err != nil { + if !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to get DatadogCSIDriver %s/%s: %w", desired.Namespace, desired.Name, err) + } + + // Create a new DatadogCSIDriver. + logger.Info("Creating DatadogCSIDriver", "name", desired.Name, "namespace", desired.Namespace) + if err := r.client.Create(ctx, desired); err != nil { + return fmt.Errorf("failed to create DatadogCSIDriver %s/%s: %w", desired.Namespace, desired.Name, err) + } + return nil + } + + // Update if the spec has drifted from the desired state. + if !apiequality.Semantic.DeepEqual(existing.Spec, desired.Spec) { + logger.Info("Updating DatadogCSIDriver", "name", desired.Name, "namespace", desired.Namespace) + if err := kubernetes.UpdateFromObject(ctx, r.client, desired, existing.ObjectMeta); err != nil { + return fmt.Errorf("failed to update DatadogCSIDriver %s/%s: %w", desired.Namespace, desired.Name, err) + } + } + + return nil +} + +// cleanupDatadogCSIDriver deletes the DDA-owned DatadogCSIDriver if it exists. +func (r *Reconciler) cleanupDatadogCSIDriver(ctx context.Context, logger logr.Logger, instance *v2alpha1.DatadogAgent) error { + // If the CRD is not installed, there is nothing to clean up. + if !r.platformInfo.IsResourceSupported(datadogCSIDriverKind) { + return nil + } + + ddcsiName := instance.Name + ddcsiNamespace := instance.Namespace + + existing := &v1alpha1.DatadogCSIDriver{} + err := r.client.Get(ctx, types.NamespacedName{Name: ddcsiName, Namespace: ddcsiNamespace}, existing) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return fmt.Errorf("failed to get DatadogCSIDriver %s/%s for cleanup: %w", ddcsiNamespace, ddcsiName, err) + } + + // Only delete if this DDA owns it (via controller reference). + if !metav1.IsControlledBy(existing, instance) { + logger.V(1).Info("DatadogCSIDriver exists but is not owned by this DatadogAgent, skipping cleanup", "name", ddcsiName) + return nil + } + + logger.Info("Deleting DatadogCSIDriver", "name", ddcsiName, "namespace", ddcsiNamespace) + if err := r.client.Delete(ctx, existing); err != nil && !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to delete DatadogCSIDriver %s/%s: %w", ddcsiNamespace, ddcsiName, err) + } + return nil +} diff --git a/internal/controller/datadogagent/ddcsi_test.go b/internal/controller/datadogagent/ddcsi_test.go new file mode 100644 index 000000000..44558aadc --- /dev/null +++ b/internal/controller/datadogagent/ddcsi_test.go @@ -0,0 +1,226 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +package datadogagent + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/version" + "k8s.io/client-go/tools/record" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/DataDog/datadog-operator/api/datadoghq/v1alpha1" + "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" + "github.com/DataDog/datadog-operator/pkg/kubernetes" +) + +func newTestReconcilerForDDCSI(scheme *runtime.Scheme, platformInfo kubernetes.PlatformInfo) *Reconciler { + fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() + return &Reconciler{ + client: fakeClient, + scheme: scheme, + log: logf.Log.WithName("test"), + recorder: record.NewFakeRecorder(100), + platformInfo: platformInfo, + } +} + +func testScheme() *runtime.Scheme { + s := runtime.NewScheme() + _ = v1alpha1.AddToScheme(s) + _ = v2alpha1.AddToScheme(s) + return s +} + +func platformInfoWithDDCSI() kubernetes.PlatformInfo { + return kubernetes.NewPlatformInfoFromVersionMaps( + &version.Info{}, + map[string]string{datadogCSIDriverKind: "datadoghq.com/v1alpha1"}, + nil, + ) +} + +func platformInfoWithoutDDCSI() kubernetes.PlatformInfo { + return kubernetes.NewPlatformInfoFromVersionMaps( + &version.Info{}, + map[string]string{}, + nil, + ) +} + +func newDDAForDDCSI(name, namespace string, csiEnabled, createDDCSI bool) *v2alpha1.DatadogAgent { + dda := &v2alpha1.DatadogAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + UID: types.UID("test-uid"), + }, + Spec: v2alpha1.DatadogAgentSpec{ + Global: &v2alpha1.GlobalConfig{ + CSI: &v2alpha1.CSIConfig{ + Enabled: ptr.To(csiEnabled), + CreateDatadogCSIDriver: ptr.To(createDDCSI), + }, + }, + }, + } + return dda +} + +func TestReconcileDatadogCSIDriver_Disabled(t *testing.T) { + r := newTestReconcilerForDDCSI(testScheme(), platformInfoWithDDCSI()) + dda := newDDAForDDCSI("test-dda", "default", false, false) + + err := r.reconcileDatadogCSIDriver(context.Background(), r.log, dda) + assert.NoError(t, err) + + // No DatadogCSIDriver should exist + ddcsi := &v1alpha1.DatadogCSIDriver{} + err = r.client.Get(context.Background(), types.NamespacedName{Name: "test-dda", Namespace: "default"}, ddcsi) + assert.Error(t, err) +} + +func TestReconcileDatadogCSIDriver_EnabledAndCreated(t *testing.T) { + r := newTestReconcilerForDDCSI(testScheme(), platformInfoWithDDCSI()) + dda := newDDAForDDCSI("test-dda", "default", true, true) + + err := r.reconcileDatadogCSIDriver(context.Background(), r.log, dda) + require.NoError(t, err) + + // DatadogCSIDriver should exist with correct owner reference + ddcsi := &v1alpha1.DatadogCSIDriver{} + err = r.client.Get(context.Background(), types.NamespacedName{Name: "test-dda", Namespace: "default"}, ddcsi) + require.NoError(t, err) + assert.Equal(t, "test-dda", ddcsi.Name) + assert.Equal(t, "default", ddcsi.Namespace) + require.Len(t, ddcsi.OwnerReferences, 1) + assert.Equal(t, "test-dda", ddcsi.OwnerReferences[0].Name) + assert.True(t, *ddcsi.OwnerReferences[0].Controller) +} + +func TestReconcileDatadogCSIDriver_CRDNotAvailable(t *testing.T) { + r := newTestReconcilerForDDCSI(testScheme(), platformInfoWithoutDDCSI()) + dda := newDDAForDDCSI("test-dda", "default", true, true) + + err := r.reconcileDatadogCSIDriver(context.Background(), r.log, dda) + assert.Error(t, err) + assert.Contains(t, err.Error(), "CRD is not installed") +} + +func TestReconcileDatadogCSIDriver_CSIEnabledButCreateFalse(t *testing.T) { + r := newTestReconcilerForDDCSI(testScheme(), platformInfoWithDDCSI()) + dda := newDDAForDDCSI("test-dda", "default", true, false) + + err := r.reconcileDatadogCSIDriver(context.Background(), r.log, dda) + assert.NoError(t, err) + + ddcsi := &v1alpha1.DatadogCSIDriver{} + err = r.client.Get(context.Background(), types.NamespacedName{Name: "test-dda", Namespace: "default"}, ddcsi) + assert.Error(t, err) // Not found +} + +func TestReconcileDatadogCSIDriver_Idempotent(t *testing.T) { + r := newTestReconcilerForDDCSI(testScheme(), platformInfoWithDDCSI()) + dda := newDDAForDDCSI("test-dda", "default", true, true) + + // First call creates + err := r.reconcileDatadogCSIDriver(context.Background(), r.log, dda) + require.NoError(t, err) + + // Second call should not error + err = r.reconcileDatadogCSIDriver(context.Background(), r.log, dda) + assert.NoError(t, err) +} + +func TestReconcileDatadogCSIDriver_CleanupOnDisable(t *testing.T) { + scheme := testScheme() + r := newTestReconcilerForDDCSI(scheme, platformInfoWithDDCSI()) + dda := newDDAForDDCSI("test-dda", "default", true, true) + + // Create the DatadogCSIDriver first + err := r.reconcileDatadogCSIDriver(context.Background(), r.log, dda) + require.NoError(t, err) + + // Now disable + dda.Spec.Global.CSI.CreateDatadogCSIDriver = ptr.To(false) + err = r.reconcileDatadogCSIDriver(context.Background(), r.log, dda) + require.NoError(t, err) + + // Should be deleted + ddcsi := &v1alpha1.DatadogCSIDriver{} + err = r.client.Get(context.Background(), types.NamespacedName{Name: "test-dda", Namespace: "default"}, ddcsi) + assert.Error(t, err) // Not found +} + +func TestReconcileDatadogCSIDriver_CleanupSkipsNotOwned(t *testing.T) { + scheme := testScheme() + r := newTestReconcilerForDDCSI(scheme, platformInfoWithDDCSI()) + + // Pre-create a DatadogCSIDriver that is NOT owned by our DDA + ddcsi := &v1alpha1.DatadogCSIDriver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-dda", + Namespace: "default", + }, + } + err := r.client.Create(context.Background(), ddcsi) + require.NoError(t, err) + + // Reconcile with CSI disabled — should NOT delete the unowned resource + dda := newDDAForDDCSI("test-dda", "default", false, false) + err = r.reconcileDatadogCSIDriver(context.Background(), r.log, dda) + require.NoError(t, err) + + // Should still exist + existing := &v1alpha1.DatadogCSIDriver{} + err = r.client.Get(context.Background(), types.NamespacedName{Name: "test-dda", Namespace: "default"}, existing) + assert.NoError(t, err) +} + +func TestReconcileDatadogCSIDriver_UpdateOnSpecDrift(t *testing.T) { + r := newTestReconcilerForDDCSI(testScheme(), platformInfoWithDDCSI()) + dda := newDDAForDDCSI("test-dda", "default", true, true) + + // Create the DatadogCSIDriver via reconcile + err := r.reconcileDatadogCSIDriver(context.Background(), r.log, dda) + require.NoError(t, err) + + // Simulate external modification: someone sets a custom APM socket path + existing := &v1alpha1.DatadogCSIDriver{} + err = r.client.Get(context.Background(), types.NamespacedName{Name: "test-dda", Namespace: "default"}, existing) + require.NoError(t, err) + customPath := "/custom/apm.socket" + existing.Spec.APMSocketPath = &customPath + err = r.client.Update(context.Background(), existing) + require.NoError(t, err) + + // Reconcile again — should update back to the desired state (empty spec) + err = r.reconcileDatadogCSIDriver(context.Background(), r.log, dda) + require.NoError(t, err) + + // Verify the spec was reconciled back to desired state + updated := &v1alpha1.DatadogCSIDriver{} + err = r.client.Get(context.Background(), types.NamespacedName{Name: "test-dda", Namespace: "default"}, updated) + require.NoError(t, err) + assert.Nil(t, updated.Spec.APMSocketPath) +} + +func TestReconcileDatadogCSIDriver_CleanupCRDNotAvailable(t *testing.T) { + r := newTestReconcilerForDDCSI(testScheme(), platformInfoWithoutDDCSI()) + dda := newDDAForDDCSI("test-dda", "default", false, false) + + // Should not error when CRD is not available and cleanup is a no-op + err := r.reconcileDatadogCSIDriver(context.Background(), r.log, dda) + assert.NoError(t, err) +} diff --git a/internal/controller/datadogagent/dependencies.go b/internal/controller/datadogagent/dependencies.go index 8ec1bd8c4..76df86e22 100644 --- a/internal/controller/datadogagent/dependencies.go +++ b/internal/controller/datadogagent/dependencies.go @@ -83,6 +83,13 @@ func (r *Reconciler) manageDDADependenciesWithDDAI(ctx context.Context, logger l return errors.NewAggregate(err) } + // DatadogCSIDriver: create or delete based on spec.global.csi configuration. + // This is managed outside the store because the store only supports built-in Kubernetes + // resource types. See ddcsi.go for details. + if err := r.reconcileDatadogCSIDriver(ctx, logger, instance); err != nil { + return err + } + return nil }