From acd3b487f4631e6b450658c4394169a757897858 Mon Sep 17 00:00:00 2001 From: Frank Spano Date: Fri, 5 Jun 2026 10:58:21 -0400 Subject: [PATCH 1/3] Add kubernetes-actions feature + required rbac --- api/datadoghq/v2alpha1/datadogagent_types.go | 13 +++ .../v2alpha1/zz_generated.deepcopy.go | 25 +++++ .../v2alpha1/zz_generated.openapi.go | 31 +++++- .../datadoghq.com_datadogagentinternals.yaml | 18 ++++ ...hq.com_datadogagentinternals_v1alpha1.json | 22 +++++ .../datadoghq.com_datadogagentprofiles.yaml | 9 ++ ...ghq.com_datadogagentprofiles_v1alpha1.json | 11 +++ .../bases/v1/datadoghq.com_datadogagents.yaml | 18 ++++ .../datadoghq.com_datadogagents_v2alpha1.json | 22 +++++ docs/configuration.v2alpha1.md | 1 + docs/configuration_public.md | 3 + .../controller/datadogagent/controller.go | 1 + .../controller/datadogagent/feature/ids.go | 2 + .../datadogagent/feature/kubeactions/const.go | 24 +++++ .../feature/kubeactions/feature.go | 95 +++++++++++++++++++ .../feature/kubeactions/feature_test.go | 63 ++++++++++++ .../datadogagent/feature/kubeactions/rbac.go | 32 +++++++ .../feature/kubeactions/rbac_test.go | 38 ++++++++ .../datadogagentinternal/controller.go | 1 + pkg/testutils/builder.go | 12 +++ pkg/testutils/builder_test.go | 31 ++++++ 21 files changed, 470 insertions(+), 2 deletions(-) create mode 100644 internal/controller/datadogagent/feature/kubeactions/const.go create mode 100644 internal/controller/datadogagent/feature/kubeactions/feature.go create mode 100644 internal/controller/datadogagent/feature/kubeactions/feature_test.go create mode 100644 internal/controller/datadogagent/feature/kubeactions/rbac.go create mode 100644 internal/controller/datadogagent/feature/kubeactions/rbac_test.go create mode 100644 pkg/testutils/builder_test.go diff --git a/api/datadoghq/v2alpha1/datadogagent_types.go b/api/datadoghq/v2alpha1/datadogagent_types.go index 9606de3b56..d750fa18cf 100644 --- a/api/datadoghq/v2alpha1/datadogagent_types.go +++ b/api/datadoghq/v2alpha1/datadogagent_types.go @@ -119,6 +119,19 @@ type DatadogFeatures struct { HelmCheck *HelmCheckFeatureConfig `json:"helmCheck,omitempty"` // ControlPlaneMonitoring configuration. ControlPlaneMonitoring *ControlPlaneMonitoringFeatureConfig `json:"controlPlaneMonitoring,omitempty"` + // KubeActions configuration. + KubeActions *KubeActionsFeatureConfig `json:"kubeActions,omitempty"` +} + +// KubeActionsFeatureConfig allows configuration of the Kubernetes Actions feature. +// When enabled, the Cluster Agent is granted RBAC to perform remediation actions +// (deleting pods, restarting deployments) driven by the Datadog Kubernetes Actions product. +// +k8s:openapi-gen=true +type KubeActionsFeatureConfig struct { + // Enabled enables the Kubernetes Actions feature on the Cluster Agent. + // Default: false + // +optional + Enabled *bool `json:"enabled,omitempty"` } // Configuration structs for each feature in DatadogFeatures. All parameters are optional and have default values when necessary. diff --git a/api/datadoghq/v2alpha1/zz_generated.deepcopy.go b/api/datadoghq/v2alpha1/zz_generated.deepcopy.go index 3ce3956403..a7bd9e4561 100644 --- a/api/datadoghq/v2alpha1/zz_generated.deepcopy.go +++ b/api/datadoghq/v2alpha1/zz_generated.deepcopy.go @@ -1636,6 +1636,11 @@ func (in *DatadogFeatures) DeepCopyInto(out *DatadogFeatures) { *out = new(ControlPlaneMonitoringFeatureConfig) (*in).DeepCopyInto(*out) } + if in.KubeActions != nil { + in, out := &in.KubeActions, &out.KubeActions + *out = new(KubeActionsFeatureConfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatadogFeatures. @@ -2315,6 +2320,26 @@ func (in *InjectorConfig) DeepCopy() *InjectorConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeActionsFeatureConfig) DeepCopyInto(out *KubeActionsFeatureConfig) { + *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeActionsFeatureConfig. +func (in *KubeActionsFeatureConfig) DeepCopy() *KubeActionsFeatureConfig { + if in == nil { + return nil + } + out := new(KubeActionsFeatureConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubeStateMetricsCoreFeatureConfig) DeepCopyInto(out *KubeStateMetricsCoreFeatureConfig) { *out = *in diff --git a/api/datadoghq/v2alpha1/zz_generated.openapi.go b/api/datadoghq/v2alpha1/zz_generated.openapi.go index ba169b2555..0d829952cb 100644 --- a/api/datadoghq/v2alpha1/zz_generated.openapi.go +++ b/api/datadoghq/v2alpha1/zz_generated.openapi.go @@ -37,6 +37,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.ExperimentStatus": schema_datadog_operator_api_datadoghq_v2alpha1_ExperimentStatus(ref), "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.FIPSConfig": schema_datadog_operator_api_datadoghq_v2alpha1_FIPSConfig(ref), "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.HelmCheckFeatureConfig": schema_datadog_operator_api_datadoghq_v2alpha1_HelmCheckFeatureConfig(ref), + "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.KubeActionsFeatureConfig": schema_datadog_operator_api_datadoghq_v2alpha1_KubeActionsFeatureConfig(ref), "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.KubeStateMetricsCoreFeatureConfig": schema_datadog_operator_api_datadoghq_v2alpha1_KubeStateMetricsCoreFeatureConfig(ref), "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.LocalService": schema_datadog_operator_api_datadoghq_v2alpha1_LocalService(ref), "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.MultiCustomConfig": schema_datadog_operator_api_datadoghq_v2alpha1_MultiCustomConfig(ref), @@ -900,11 +901,17 @@ func schema_datadog_operator_api_datadoghq_v2alpha1_DatadogFeatures(ref common.R Ref: ref("github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.ControlPlaneMonitoringFeatureConfig"), }, }, + "kubeActions": { + SchemaProps: spec.SchemaProps{ + Description: "KubeActions configuration.", + Ref: ref("github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.KubeActionsFeatureConfig"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.APMFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.ASMFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.AdmissionControllerFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.AutoscalingFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.CSPMFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.CWSFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.ClusterChecksFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.ControlPlaneMonitoringFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.DataPlaneFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.DogstatsdFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.EBPFCheckFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.EventCollectionFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.ExternalMetricsServerFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.GPUFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.HelmCheckFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.KubeStateMetricsCoreFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.LiveContainerCollectionFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.LiveProcessCollectionFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.LogCollectionFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.NPMFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.OOMKillFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.OTLPFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.OrchestratorExplorerFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.OtelAgentGatewayFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.OtelCollectorFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.ProcessDiscoveryFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.PrometheusScrapeFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.RemoteConfigurationFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.SBOMFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.ServiceDiscoveryFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.TCPQueueLengthFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.USMFeatureConfig"}, + "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.APMFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.ASMFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.AdmissionControllerFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.AutoscalingFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.CSPMFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.CWSFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.ClusterChecksFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.ControlPlaneMonitoringFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.DataPlaneFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.DogstatsdFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.EBPFCheckFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.EventCollectionFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.ExternalMetricsServerFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.GPUFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.HelmCheckFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.KubeActionsFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.KubeStateMetricsCoreFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.LiveContainerCollectionFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.LiveProcessCollectionFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.LogCollectionFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.NPMFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.OOMKillFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.OTLPFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.OrchestratorExplorerFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.OtelAgentGatewayFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.OtelCollectorFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.ProcessDiscoveryFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.PrometheusScrapeFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.RemoteConfigurationFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.SBOMFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.ServiceDiscoveryFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.TCPQueueLengthFeatureConfig", "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1.USMFeatureConfig"}, } } @@ -1281,6 +1288,26 @@ func schema_datadog_operator_api_datadoghq_v2alpha1_HelmCheckFeatureConfig(ref c } } +func schema_datadog_operator_api_datadoghq_v2alpha1_KubeActionsFeatureConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "KubeActionsFeatureConfig allows configuration of the Kubernetes Actions feature. When enabled, the Cluster Agent is granted RBAC to perform remediation actions (deleting pods, restarting deployments) driven by the Datadog Kubernetes Actions product.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "enabled": { + SchemaProps: spec.SchemaProps{ + Description: "Enabled enables the Kubernetes Actions feature on the Cluster Agent. Default: false", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_datadog_operator_api_datadoghq_v2alpha1_KubeStateMetricsCoreFeatureConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -1420,7 +1447,7 @@ func schema_datadog_operator_api_datadoghq_v2alpha1_NetworkPolicyConfig(ref comm }, }, SchemaProps: spec.SchemaProps{ - Description: "DNSSelectorEndpoints defines the cilium selector of the DNS\u202fserver entity.", + Description: "DNSSelectorEndpoints defines the cilium selector of the DNS server entity.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ diff --git a/config/crd/bases/v1/datadoghq.com_datadogagentinternals.yaml b/config/crd/bases/v1/datadoghq.com_datadogagentinternals.yaml index 0fd41618bc..b036556572 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagentinternals.yaml +++ b/config/crd/bases/v1/datadoghq.com_datadogagentinternals.yaml @@ -1721,6 +1721,15 @@ spec: Default: {} type: object type: object + kubeActions: + description: KubeActions configuration. + properties: + enabled: + description: |- + Enabled enables the Kubernetes Actions feature on the Cluster Agent. + Default: false + type: boolean + type: object kubeStateMetricsCore: description: KubeStateMetricsCore check configuration. properties: @@ -10428,6 +10437,15 @@ spec: Default: {} type: object type: object + kubeActions: + description: KubeActions configuration. + properties: + enabled: + description: |- + Enabled enables the Kubernetes Actions feature on the Cluster Agent. + Default: false + type: boolean + type: object kubeStateMetricsCore: description: KubeStateMetricsCore check configuration. properties: diff --git a/config/crd/bases/v1/datadoghq.com_datadogagentinternals_v1alpha1.json b/config/crd/bases/v1/datadoghq.com_datadogagentinternals_v1alpha1.json index 3c61a4a54a..8b0167eb0d 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagentinternals_v1alpha1.json +++ b/config/crd/bases/v1/datadoghq.com_datadogagentinternals_v1alpha1.json @@ -1723,6 +1723,17 @@ }, "type": "object" }, + "kubeActions": { + "additionalProperties": false, + "description": "KubeActions configuration.", + "properties": { + "enabled": { + "description": "Enabled enables the Kubernetes Actions feature on the Cluster Agent.\nDefault: false", + "type": "boolean" + } + }, + "type": "object" + }, "kubeStateMetricsCore": { "additionalProperties": false, "description": "KubeStateMetricsCore check configuration.", @@ -10149,6 +10160,17 @@ }, "type": "object" }, + "kubeActions": { + "additionalProperties": false, + "description": "KubeActions configuration.", + "properties": { + "enabled": { + "description": "Enabled enables the Kubernetes Actions feature on the Cluster Agent.\nDefault: false", + "type": "boolean" + } + }, + "type": "object" + }, "kubeStateMetricsCore": { "additionalProperties": false, "description": "KubeStateMetricsCore check configuration.", diff --git a/config/crd/bases/v1/datadoghq.com_datadogagentprofiles.yaml b/config/crd/bases/v1/datadoghq.com_datadogagentprofiles.yaml index 5bc20fa1e9..a062a6c7ea 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagentprofiles.yaml +++ b/config/crd/bases/v1/datadoghq.com_datadogagentprofiles.yaml @@ -1721,6 +1721,15 @@ spec: Default: {} type: object type: object + kubeActions: + description: KubeActions configuration. + properties: + enabled: + description: |- + Enabled enables the Kubernetes Actions feature on the Cluster Agent. + Default: false + type: boolean + type: object kubeStateMetricsCore: description: KubeStateMetricsCore check configuration. properties: diff --git a/config/crd/bases/v1/datadoghq.com_datadogagentprofiles_v1alpha1.json b/config/crd/bases/v1/datadoghq.com_datadogagentprofiles_v1alpha1.json index a7488a18ca..749edbfa21 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagentprofiles_v1alpha1.json +++ b/config/crd/bases/v1/datadoghq.com_datadogagentprofiles_v1alpha1.json @@ -1727,6 +1727,17 @@ }, "type": "object" }, + "kubeActions": { + "additionalProperties": false, + "description": "KubeActions configuration.", + "properties": { + "enabled": { + "description": "Enabled enables the Kubernetes Actions feature on the Cluster Agent.\nDefault: false", + "type": "boolean" + } + }, + "type": "object" + }, "kubeStateMetricsCore": { "additionalProperties": false, "description": "KubeStateMetricsCore check configuration.", diff --git a/config/crd/bases/v1/datadoghq.com_datadogagents.yaml b/config/crd/bases/v1/datadoghq.com_datadogagents.yaml index 61b96259f8..11108539ac 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagents.yaml +++ b/config/crd/bases/v1/datadoghq.com_datadogagents.yaml @@ -1725,6 +1725,15 @@ spec: Default: {} type: object type: object + kubeActions: + description: KubeActions configuration. + properties: + enabled: + description: |- + Enabled enables the Kubernetes Actions feature on the Cluster Agent. + Default: false + type: boolean + type: object kubeStateMetricsCore: description: KubeStateMetricsCore check configuration. properties: @@ -10521,6 +10530,15 @@ spec: Default: {} type: object type: object + kubeActions: + description: KubeActions configuration. + properties: + enabled: + description: |- + Enabled enables the Kubernetes Actions feature on the Cluster Agent. + Default: false + type: boolean + type: object kubeStateMetricsCore: description: KubeStateMetricsCore check configuration. properties: diff --git a/config/crd/bases/v1/datadoghq.com_datadogagents_v2alpha1.json b/config/crd/bases/v1/datadoghq.com_datadogagents_v2alpha1.json index 0f72aabd93..3811b425cf 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagents_v2alpha1.json +++ b/config/crd/bases/v1/datadoghq.com_datadogagents_v2alpha1.json @@ -1723,6 +1723,17 @@ }, "type": "object" }, + "kubeActions": { + "additionalProperties": false, + "description": "KubeActions configuration.", + "properties": { + "enabled": { + "description": "Enabled enables the Kubernetes Actions feature on the Cluster Agent.\nDefault: false", + "type": "boolean" + } + }, + "type": "object" + }, "kubeStateMetricsCore": { "additionalProperties": false, "description": "KubeStateMetricsCore check configuration.", @@ -10248,6 +10259,17 @@ }, "type": "object" }, + "kubeActions": { + "additionalProperties": false, + "description": "KubeActions configuration.", + "properties": { + "enabled": { + "description": "Enabled enables the Kubernetes Actions feature on the Cluster Agent.\nDefault: false", + "type": "boolean" + } + }, + "type": "object" + }, "kubeStateMetricsCore": { "additionalProperties": false, "description": "KubeStateMetricsCore check configuration.", diff --git a/docs/configuration.v2alpha1.md b/docs/configuration.v2alpha1.md index 2bc0e20346..3d0ad518e4 100644 --- a/docs/configuration.v2alpha1.md +++ b/docs/configuration.v2alpha1.md @@ -137,6 +137,7 @@ spec: | features.helmCheck.collectEvents | CollectEvents set to `true` enables event collection in the Helm check (Requires Agent 7.36.0+ and Cluster Agent 1.20.0+) Default: false | | features.helmCheck.enabled | Enables the Helm check. Default: false | | features.helmCheck.valuesAsTags | ValuesAsTags collects Helm values from a release and uses them as tags (Requires Agent and Cluster Agent 7.40.0+). Default: {} | +| features.kubeActions.enabled | Enables the Kubernetes Actions feature on the Cluster Agent. Default: false | | features.kubeStateMetricsCore.collectCrMetrics | `CollectCrMetrics` defines custom resources for the kube-state-metrics core check to collect. The datadog agent uses the same logic as upstream `kube-state-metrics`. So is its configuration. The exact structure and existing fields of each item in this list can be found in: https://github.com/kubernetes/kube-state-metrics/blob/main/docs/metrics/extend/customresourcestate-metrics.md | | features.kubeStateMetricsCore.conf.configData | ConfigData corresponds to the configuration file content. | | features.kubeStateMetricsCore.conf.configMap.items | Maps a ConfigMap data `key` to a file `path` mount. | diff --git a/docs/configuration_public.md b/docs/configuration_public.md index c141925584..bdb13a2832 100644 --- a/docs/configuration_public.md +++ b/docs/configuration_public.md @@ -243,6 +243,9 @@ spec: `features.helmCheck.valuesAsTags` : ValuesAsTags collects Helm values from a release and uses them as tags (Requires Agent and Cluster Agent 7.40.0+). Default: {} +`features.kubeActions.enabled` +: Enables the Kubernetes Actions feature on the Cluster Agent. Default: false + `features.kubeStateMetricsCore.collectCrMetrics` : `CollectCrMetrics` defines custom resources for the kube-state-metrics core check to collect. The datadog agent uses the same logic as upstream `kube-state-metrics`. So is its configuration. The exact structure and existing fields of each item in this list can be found in: https://github.com/kubernetes/kube-state-metrics/blob/main/docs/metrics/extend/customresourcestate-metrics.md diff --git a/internal/controller/datadogagent/controller.go b/internal/controller/datadogagent/controller.go index 15449b6930..648443ff43 100644 --- a/internal/controller/datadogagent/controller.go +++ b/internal/controller/datadogagent/controller.go @@ -43,6 +43,7 @@ import ( _ "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/helmcheck" _ "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/hostprofiler" _ "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/instrumentationcrd" + _ "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/kubeactions" _ "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/kubernetesstatecore" _ "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/livecontainer" _ "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/liveprocess" diff --git a/internal/controller/datadogagent/feature/ids.go b/internal/controller/datadogagent/feature/ids.go index dc968983bc..604cd2a7be 100644 --- a/internal/controller/datadogagent/feature/ids.go +++ b/internal/controller/datadogagent/feature/ids.go @@ -89,4 +89,6 @@ const ( DataPlaneIDType = "data_plane" // FlightRecorderIDType Flight Recorder feature. FlightRecorderIDType = "flightrecorder" + // KubeActionsIDType Kubernetes Actions feature. + KubeActionsIDType = "kube_actions" ) diff --git a/internal/controller/datadogagent/feature/kubeactions/const.go b/internal/controller/datadogagent/feature/kubeactions/const.go new file mode 100644 index 0000000000..b566fd1420 --- /dev/null +++ b/internal/controller/datadogagent/feature/kubeactions/const.go @@ -0,0 +1,24 @@ +// 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 kubeactions + +import ( + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + kubeActionsRBACPrefix = "kube-actions" + + // DDKubeActionsEnabled is the env var that turns on the Kubernetes Actions + // product in the Cluster Agent. + DDKubeActionsEnabled = "DD_KUBEACTIONS_ENABLED" +) + +func getRBACResourceName(owner metav1.Object, suffix string) string { + return fmt.Sprintf("%s-%s-%s-%s", owner.GetNamespace(), owner.GetName(), kubeActionsRBACPrefix, suffix) +} diff --git a/internal/controller/datadogagent/feature/kubeactions/feature.go b/internal/controller/datadogagent/feature/kubeactions/feature.go new file mode 100644 index 0000000000..47eddb8bc2 --- /dev/null +++ b/internal/controller/datadogagent/feature/kubeactions/feature.go @@ -0,0 +1,95 @@ +// 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 kubeactions + +import ( + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + apicommon "github.com/DataDog/datadog-operator/api/datadoghq/common" + "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" + apiutils "github.com/DataDog/datadog-operator/api/utils" + "github.com/DataDog/datadog-operator/internal/controller/datadogagent/common" + "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature" + "github.com/DataDog/datadog-operator/pkg/constants" +) + +func init() { + err := feature.Register(feature.KubeActionsIDType, buildKubeActionsFeature) + if err != nil { + panic(err) + } +} + +func buildKubeActionsFeature(options *feature.Options) feature.Feature { + f := &kubeActionsFeature{ + rbacSuffix: common.ClusterAgentSuffix, + } + if options != nil { + f.logger = options.Logger + } + return f +} + +type kubeActionsFeature struct { + owner metav1.Object + serviceAccountName string + rbacSuffix string + logger logr.Logger +} + +func (f *kubeActionsFeature) ID() feature.IDType { + return feature.KubeActionsIDType +} + +func (f *kubeActionsFeature) Configure(dda metav1.Object, ddaSpec *v2alpha1.DatadogAgentSpec, _ *v2alpha1.RemoteConfigConfiguration) (reqComp feature.RequiredComponents) { + f.owner = dda + + if ddaSpec.Features == nil || ddaSpec.Features.KubeActions == nil || !apiutils.BoolValue(ddaSpec.Features.KubeActions.Enabled) { + return reqComp + } + + f.serviceAccountName = constants.GetClusterAgentServiceAccount(dda.GetName(), ddaSpec) + + reqComp = feature.RequiredComponents{ + ClusterAgent: feature.RequiredComponent{ + IsRequired: ptr.To(true), + Containers: []apicommon.AgentContainerName{apicommon.ClusterAgentContainerName}, + }, + } + return reqComp +} + +func (f *kubeActionsFeature) ManageDependencies(managers feature.ResourceManagers) error { + rbacName := getRBACResourceName(f.owner, f.rbacSuffix) + return managers.RBACManager().AddClusterPolicyRules(f.owner.GetNamespace(), rbacName, f.serviceAccountName, kubeActionsRBACPolicyRules) +} + +func (f *kubeActionsFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error { + managers.EnvVar().AddEnvVarToContainer(apicommon.ClusterAgentContainerName, &corev1.EnvVar{ + Name: DDKubeActionsEnabled, + Value: "true", + }) + return nil +} + +func (f *kubeActionsFeature) ManageSingleContainerNodeAgent(_ feature.PodTemplateManagers) error { + return nil +} + +func (f *kubeActionsFeature) ManageNodeAgent(_ feature.PodTemplateManagers) error { + return nil +} + +func (f *kubeActionsFeature) ManageClusterChecksRunner(_ feature.PodTemplateManagers) error { + return nil +} + +func (f *kubeActionsFeature) ManageOtelAgentGateway(_ feature.PodTemplateManagers) error { + return nil +} diff --git a/internal/controller/datadogagent/feature/kubeactions/feature_test.go b/internal/controller/datadogagent/feature/kubeactions/feature_test.go new file mode 100644 index 0000000000..d540c097ff --- /dev/null +++ b/internal/controller/datadogagent/feature/kubeactions/feature_test.go @@ -0,0 +1,63 @@ +// 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 kubeactions + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + + apicommon "github.com/DataDog/datadog-operator/api/datadoghq/common" + apiutils "github.com/DataDog/datadog-operator/api/utils" + "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature" + "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/fake" + "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/test" + "github.com/DataDog/datadog-operator/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" +) + +func Test_kubeActionsFeature_Configure(t *testing.T) { + tests := test.FeatureTestSuite{ + { + Name: "KubeActions not configured", + DDA: testutils.NewDatadogAgentBuilder().Build(), + WantConfigure: false, + }, + { + Name: "KubeActions explicitly disabled", + DDA: testutils.NewDatadogAgentBuilder(). + WithKubeActionsEnabled(false). + Build(), + WantConfigure: false, + }, + { + Name: "KubeActions enabled", + DDA: testutils.NewDatadogAgentBuilder(). + WithName("ddaDCA"). + WithKubeActionsEnabled(true). + Build(), + WantConfigure: true, + ClusterAgent: test.NewDefaultComponentTest().WithWantFunc(kubeActionsClusterAgentWantFunc), + }, + } + + tests.Run(t, buildKubeActionsFeature) +} + +func kubeActionsClusterAgentWantFunc(t testing.TB, mgrInterface feature.PodTemplateManagers) { + mgr := mgrInterface.(*fake.PodTemplateManagers) + dcaEnvVars := mgr.EnvVarMgr.EnvVarsByC[apicommon.ClusterAgentContainerName] + + want := []*corev1.EnvVar{ + { + Name: DDKubeActionsEnabled, + Value: "true", + }, + } + assert.True(t, apiutils.IsEqualStruct(dcaEnvVars, want), "DCA envvars \ndiff = %s", cmp.Diff(dcaEnvVars, want)) +} diff --git a/internal/controller/datadogagent/feature/kubeactions/rbac.go b/internal/controller/datadogagent/feature/kubeactions/rbac.go new file mode 100644 index 0000000000..d39ae0f127 --- /dev/null +++ b/internal/controller/datadogagent/feature/kubeactions/rbac.go @@ -0,0 +1,32 @@ +// 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 kubeactions + +import ( + rbacv1 "k8s.io/api/rbac/v1" + + "github.com/DataDog/datadog-operator/pkg/kubernetes/rbac" +) + +// kubeActionsRBACPolicyRules are the cluster-scoped rules the Cluster Agent +// needs to remediate workloads on behalf of the Kubernetes Actions product. +var kubeActionsRBACPolicyRules = []rbacv1.PolicyRule{ + { + APIGroups: []string{rbac.CoreAPIGroup}, + Resources: []string{rbac.PodsResource}, + Verbs: []string{rbac.GetVerb, rbac.ListVerb, rbac.DeleteVerb}, + }, + { + APIGroups: []string{rbac.AppsAPIGroup}, + Resources: []string{rbac.DeploymentsResource}, + Verbs: []string{rbac.GetVerb, rbac.ListVerb, rbac.PatchVerb, rbac.UpdateVerb}, + }, + { + APIGroups: []string{rbac.AppsAPIGroup}, + Resources: []string{"deployments/status"}, + Verbs: []string{rbac.GetVerb}, + }, +} diff --git a/internal/controller/datadogagent/feature/kubeactions/rbac_test.go b/internal/controller/datadogagent/feature/kubeactions/rbac_test.go new file mode 100644 index 0000000000..2e0b232e07 --- /dev/null +++ b/internal/controller/datadogagent/feature/kubeactions/rbac_test.go @@ -0,0 +1,38 @@ +// 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 kubeactions + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/DataDog/datadog-operator/pkg/kubernetes/rbac" +) + +func TestKubeActionsRBACPolicyRules(t *testing.T) { + rules := kubeActionsRBACPolicyRules + assert.Len(t, rules, 3, "expected pods, deployments, deployments/status rules") + + byResource := map[string]int{} + for i, r := range rules { + for _, res := range r.Resources { + byResource[res] = i + } + } + + podRule := rules[byResource[rbac.PodsResource]] + assert.Equal(t, []string{rbac.CoreAPIGroup}, podRule.APIGroups) + assert.ElementsMatch(t, []string{rbac.GetVerb, rbac.ListVerb, rbac.DeleteVerb}, podRule.Verbs) + + deployRule := rules[byResource[rbac.DeploymentsResource]] + assert.Equal(t, []string{rbac.AppsAPIGroup}, deployRule.APIGroups) + assert.ElementsMatch(t, []string{rbac.GetVerb, rbac.ListVerb, rbac.PatchVerb, rbac.UpdateVerb}, deployRule.Verbs) + + statusRule := rules[byResource["deployments/status"]] + assert.Equal(t, []string{rbac.AppsAPIGroup}, statusRule.APIGroups) + assert.ElementsMatch(t, []string{rbac.GetVerb}, statusRule.Verbs) +} diff --git a/internal/controller/datadogagentinternal/controller.go b/internal/controller/datadogagentinternal/controller.go index ff192c0d7e..6e0ea5915c 100644 --- a/internal/controller/datadogagentinternal/controller.go +++ b/internal/controller/datadogagentinternal/controller.go @@ -39,6 +39,7 @@ import ( _ "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/externalmetrics" _ "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/gpu" _ "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/helmcheck" + _ "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/kubeactions" _ "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/kubernetesstatecore" _ "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/livecontainer" _ "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/liveprocess" diff --git a/pkg/testutils/builder.go b/pkg/testutils/builder.go index e7a4f94f60..da8a9b5d7f 100644 --- a/pkg/testutils/builder.go +++ b/pkg/testutils/builder.go @@ -1007,6 +1007,18 @@ func (builder *DatadogAgentBuilder) WithHelmCheckValuesAsTags(valuesAsTags map[s return builder } +func (builder *DatadogAgentBuilder) initKubeActions() { + if builder.datadogAgent.Spec.Features.KubeActions == nil { + builder.datadogAgent.Spec.Features.KubeActions = &v2alpha1.KubeActionsFeatureConfig{} + } +} + +func (builder *DatadogAgentBuilder) WithKubeActionsEnabled(enabled bool) *DatadogAgentBuilder { + builder.initKubeActions() + builder.datadogAgent.Spec.Features.KubeActions.Enabled = ptr.To(enabled) + return builder +} + // Global Kubelet func (builder *DatadogAgentBuilder) WithGlobalKubeletConfig(hostCAPath, agentCAPath string, tlsVerify bool, podResourcesSocketDir string) *DatadogAgentBuilder { diff --git a/pkg/testutils/builder_test.go b/pkg/testutils/builder_test.go new file mode 100644 index 0000000000..dc3cec5ac5 --- /dev/null +++ b/pkg/testutils/builder_test.go @@ -0,0 +1,31 @@ +// 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 testutils + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDatadogAgentBuilder_WithKubeActionsEnabled(t *testing.T) { + builder := NewDatadogAgentBuilder() + require.Nil(t, builder.datadogAgent.Spec.Features.KubeActions) + + returnedBuilder := builder.WithKubeActionsEnabled(true) + require.Same(t, builder, returnedBuilder) + require.NotNil(t, builder.datadogAgent.Spec.Features.KubeActions) + require.NotNil(t, builder.datadogAgent.Spec.Features.KubeActions.Enabled) + assert.True(t, *builder.datadogAgent.Spec.Features.KubeActions.Enabled) + + initialKubeActionsConfig := builder.datadogAgent.Spec.Features.KubeActions + builder.WithKubeActionsEnabled(false) + + assert.Same(t, initialKubeActionsConfig, builder.datadogAgent.Spec.Features.KubeActions) + require.NotNil(t, builder.datadogAgent.Spec.Features.KubeActions.Enabled) + assert.False(t, *builder.datadogAgent.Spec.Features.KubeActions.Enabled) +} From c64bfa07ee0d576c21b16aac7e95d17872731bda Mon Sep 17 00:00:00 2001 From: Frank Spano Date: Fri, 5 Jun 2026 14:24:18 -0400 Subject: [PATCH 2/3] Gate to version 7.79 and above --- .../datadogagent/feature/kubeactions/const.go | 4 +++ .../feature/kubeactions/feature.go | 19 +++++++++++++ .../feature/kubeactions/feature_test.go | 28 +++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/internal/controller/datadogagent/feature/kubeactions/const.go b/internal/controller/datadogagent/feature/kubeactions/const.go index b566fd1420..55eb33ed76 100644 --- a/internal/controller/datadogagent/feature/kubeactions/const.go +++ b/internal/controller/datadogagent/feature/kubeactions/const.go @@ -17,6 +17,10 @@ const ( // DDKubeActionsEnabled is the env var that turns on the Kubernetes Actions // product in the Cluster Agent. DDKubeActionsEnabled = "DD_KUBEACTIONS_ENABLED" + + // ClusterAgentMinVersion is the minimum Cluster Agent version that supports + // the Kubernetes Actions product. + ClusterAgentMinVersion = "7.79.0" ) func getRBACResourceName(owner metav1.Object, suffix string) string { diff --git a/internal/controller/datadogagent/feature/kubeactions/feature.go b/internal/controller/datadogagent/feature/kubeactions/feature.go index 47eddb8bc2..ba8212a116 100644 --- a/internal/controller/datadogagent/feature/kubeactions/feature.go +++ b/internal/controller/datadogagent/feature/kubeactions/feature.go @@ -17,8 +17,22 @@ import ( "github.com/DataDog/datadog-operator/internal/controller/datadogagent/common" "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature" "github.com/DataDog/datadog-operator/pkg/constants" + "github.com/DataDog/datadog-operator/pkg/images" + "github.com/DataDog/datadog-operator/pkg/utils" ) +func clusterAgentVersion(ddaSpec *v2alpha1.DatadogAgentSpec) string { + if ddaSpec == nil { + return images.AgentLatestVersion + } + if clusterAgent, ok := ddaSpec.Override[v2alpha1.ClusterAgentComponentName]; ok { + if clusterAgent.Image != nil { + return common.GetAgentVersionFromImage(*clusterAgent.Image) + } + } + return images.AgentLatestVersion +} + func init() { err := feature.Register(feature.KubeActionsIDType, buildKubeActionsFeature) if err != nil { @@ -54,6 +68,11 @@ func (f *kubeActionsFeature) Configure(dda metav1.Object, ddaSpec *v2alpha1.Data return reqComp } + if !utils.IsAboveMinVersion(clusterAgentVersion(ddaSpec), ClusterAgentMinVersion, nil) { + f.logger.V(1).Info("cluster agent version is too low for Kubernetes Actions", "min", ClusterAgentMinVersion) + return reqComp + } + f.serviceAccountName = constants.GetClusterAgentServiceAccount(dda.GetName(), ddaSpec) reqComp = feature.RequiredComponents{ diff --git a/internal/controller/datadogagent/feature/kubeactions/feature_test.go b/internal/controller/datadogagent/feature/kubeactions/feature_test.go index d540c097ff..a8bf518574 100644 --- a/internal/controller/datadogagent/feature/kubeactions/feature_test.go +++ b/internal/controller/datadogagent/feature/kubeactions/feature_test.go @@ -35,6 +35,25 @@ func Test_kubeActionsFeature_Configure(t *testing.T) { Build(), WantConfigure: false, }, + { + Name: "KubeActions enabled but cluster agent version too low", + DDA: testutils.NewDatadogAgentBuilder(). + WithName("ddaDCA"). + WithKubeActionsEnabled(true). + WithClusterAgentTag("7.78.0"). + Build(), + WantConfigure: false, + }, + { + Name: "KubeActions enabled at minimum cluster agent version", + DDA: testutils.NewDatadogAgentBuilder(). + WithName("ddaDCA"). + WithKubeActionsEnabled(true). + WithClusterAgentTag("7.79.0"). + Build(), + WantConfigure: true, + ClusterAgent: test.NewDefaultComponentTest().WithWantFunc(kubeActionsClusterAgentWantFunc), + }, { Name: "KubeActions enabled", DDA: testutils.NewDatadogAgentBuilder(). @@ -61,3 +80,12 @@ func kubeActionsClusterAgentWantFunc(t testing.TB, mgrInterface feature.PodTempl } assert.True(t, apiutils.IsEqualStruct(dcaEnvVars, want), "DCA envvars \ndiff = %s", cmp.Diff(dcaEnvVars, want)) } + +func Test_kubeActionsFeature_NoOpManagers(t *testing.T) { + f := buildKubeActionsFeature(nil) + + assert.NoError(t, f.ManageSingleContainerNodeAgent(nil)) + assert.NoError(t, f.ManageNodeAgent(nil)) + assert.NoError(t, f.ManageClusterChecksRunner(nil)) + assert.NoError(t, f.ManageOtelAgentGateway(nil)) +} From b5d05743236cf193777a191bf25a3fb4b5154f4a Mon Sep 17 00:00:00 2001 From: Frank Spano Date: Fri, 5 Jun 2026 15:58:44 -0400 Subject: [PATCH 3/3] regen --- api/datadoghq/v2alpha1/zz_generated.openapi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/datadoghq/v2alpha1/zz_generated.openapi.go b/api/datadoghq/v2alpha1/zz_generated.openapi.go index 0d829952cb..dbaa208103 100644 --- a/api/datadoghq/v2alpha1/zz_generated.openapi.go +++ b/api/datadoghq/v2alpha1/zz_generated.openapi.go @@ -1447,7 +1447,7 @@ func schema_datadog_operator_api_datadoghq_v2alpha1_NetworkPolicyConfig(ref comm }, }, SchemaProps: spec.SchemaProps{ - Description: "DNSSelectorEndpoints defines the cilium selector of the DNS server entity.", + Description: "DNSSelectorEndpoints defines the cilium selector of the DNS\u202fserver entity.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{