-
Notifications
You must be signed in to change notification settings - Fork 156
Expand file tree
/
Copy pathdatadogagentprofile_validation.go
More file actions
204 lines (173 loc) · 5.54 KB
/
Copy pathdatadogagentprofile_validation.go
File metadata and controls
204 lines (173 loc) · 5.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// 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 v1alpha1
import (
"fmt"
"reflect"
"strings"
"unicode"
"github.com/DataDog/datadog-operator/api/datadoghq/common"
"github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1"
)
var datadogAgentProfileFeatureAllowlist = map[string]struct{}{
"gpu": {},
"apm": {},
}
var datadogAgentProfileComponentOverrideAllowlist = map[string]struct{}{
"containers": {},
"priorityClassName": {},
"runtimeClassName": {},
"updateStrategy": {},
"labels": {},
}
var datadogAgentProfileContainerOverrideAllowlist = map[string]struct{}{
"resources": {},
"env": {},
}
// ValidateDatadogAgentProfileSpec is used to check if a DatadogAgentProfileSpec is valid
func ValidateDatadogAgentProfileSpec(spec *DatadogAgentProfileSpec) error {
if err := validateProfileAffinity(spec.ProfileAffinity); err != nil {
return err
}
if err := validateConfig(spec.Config); err != nil {
return err
}
return nil
}
func validateProfileAffinity(profileAffinity *ProfileAffinity) error {
if profileAffinity == nil {
return undefinedError("profileAffinity")
}
if profileAffinity.ProfileNodeAffinity == nil {
return undefinedError("profileNodeAffinity")
}
if len(profileAffinity.ProfileNodeAffinity) < 1 {
return fmt.Errorf("profileNodeAffinity must have at least 1 requirement")
}
return nil
}
func validateConfig(spec *v2alpha1.DatadogAgentSpec) error {
if spec == nil {
return undefinedError("config")
}
if err := validateFeatures(spec.Features); err != nil {
return err
}
// global is not supported
if spec.Global != nil {
return unsupportedError("global")
}
for component, override := range spec.Override {
if err := validateOverride(component, override); err != nil {
return err
}
}
return nil
}
func validateFeatures(features *v2alpha1.DatadogFeatures) error {
if features == nil {
return nil
}
return validateAllowlistedFields(features, datadogAgentProfileFeatureAllowlist, jsonFieldName)
}
func validateOverride(component v2alpha1.ComponentName, override *v2alpha1.DatadogAgentComponentOverride) error {
if component != v2alpha1.NodeAgentComponentName {
return fmt.Errorf("only node agent componentoverrides are supported")
}
if override == nil {
return undefinedError("component override")
}
if err := validateAllowlistedFields(override, datadogAgentProfileComponentOverrideAllowlist, prefixedJSONFieldName("component")); err != nil {
return err
}
for name, override := range override.Containers {
if err := validateContainerOverride(name, override); err != nil {
return err
}
}
return nil
}
func validateContainerOverride(name common.AgentContainerName, override *v2alpha1.DatadogAgentGenericContainer) error {
supportedContainers := map[common.AgentContainerName]struct{}{
common.CoreAgentContainerName: {},
common.TraceAgentContainerName: {},
common.ProcessAgentContainerName: {},
common.SecurityAgentContainerName: {},
common.SystemProbeContainerName: {},
common.OtelAgent: {},
common.AgentDataPlaneContainerName: {},
}
if _, ok := supportedContainers[name]; !ok {
return unsupportedError(fmt.Sprintf("container %s", name))
}
if override == nil {
return undefinedError(fmt.Sprintf("container %s", name))
}
return validateAllowlistedFields(override, datadogAgentProfileContainerOverrideAllowlist, prefixedJSONFieldName("container"))
}
// For every set field in a struct/pointer, read the JSON name
// (nodeSelector from json:"nodeSelector,omitempty") and check
// the name is in the allowlist. If not in the list, error
func validateAllowlistedFields(value any, allowlist map[string]struct{}, unsupportedFieldName func(reflect.StructField) string) error {
structValue := reflect.ValueOf(value)
if structValue.Kind() == reflect.Ptr {
if structValue.IsNil() {
return nil
}
structValue = structValue.Elem()
}
structType := structValue.Type()
for i := 0; i < structValue.NumField(); i++ {
if isEmptyOverrideValue(structValue.Field(i)) {
continue
}
field := structType.Field(i)
if _, ok := allowlist[jsonFieldName(field)]; !ok {
return unsupportedError(unsupportedFieldName(field))
}
}
return nil
}
func isEmptyOverrideValue(value reflect.Value) bool {
switch value.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return value.IsNil()
default:
return value.IsZero()
}
}
func jsonFieldName(field reflect.StructField) string {
name, _, _ := strings.Cut(field.Tag.Get("json"), ",")
if name == "" || name == "-" {
return field.Name
}
return name
}
func prefixedJSONFieldName(prefix string) func(reflect.StructField) string {
return func(field reflect.StructField) string {
return fmt.Sprintf("%s %s", prefix, splitJSONFieldName(field))
}
}
func splitJSONFieldName(field reflect.StructField) string {
name := []rune(jsonFieldName(field))
var words []rune
for i, r := range name {
if i > 0 && unicode.IsUpper(r) {
previous := name[i-1]
hasNext := i+1 < len(name)
if unicode.IsLower(previous) || hasNext && unicode.IsLower(name[i+1]) {
words = append(words, ' ')
}
}
words = append(words, unicode.ToLower(r))
}
return string(words)
}
func unsupportedError(config string) error {
return fmt.Errorf("%s override is not supported", config)
}
func undefinedError(config string) error {
return fmt.Errorf("%s must be defined", config)
}