github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd/commands/admin/settings_test.go (about) 1 package admin 2 3 import ( 4 "context" 5 "io" 6 "os" 7 "testing" 8 9 "github.com/argoproj/argo-cd/v3/common" 10 utilio "github.com/argoproj/argo-cd/v3/util/io" 11 "github.com/argoproj/argo-cd/v3/util/settings" 12 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 corev1 "k8s.io/api/core/v1" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/client-go/kubernetes/fake" 18 ) 19 20 func captureStdout(callback func()) (string, error) { 21 oldStdout := os.Stdout 22 oldStderr := os.Stderr 23 r, w, err := os.Pipe() 24 if err != nil { 25 return "", err 26 } 27 os.Stdout = w 28 defer func() { 29 os.Stdout = oldStdout 30 os.Stderr = oldStderr 31 }() 32 33 callback() 34 utilio.Close(w) 35 36 data, err := io.ReadAll(r) 37 if err != nil { 38 return "", err 39 } 40 return string(data), err 41 } 42 43 func newSettingsManager(data map[string]string) *settings.SettingsManager { 44 ctx := context.Background() 45 46 clientset := fake.NewClientset(&corev1.ConfigMap{ 47 ObjectMeta: metav1.ObjectMeta{ 48 Namespace: "default", 49 Name: common.ArgoCDConfigMapName, 50 Labels: map[string]string{ 51 "app.kubernetes.io/part-of": "argocd", 52 }, 53 }, 54 Data: data, 55 }, &corev1.Secret{ 56 ObjectMeta: metav1.ObjectMeta{ 57 Namespace: "default", 58 Name: common.ArgoCDSecretName, 59 }, 60 Data: map[string][]byte{ 61 "admin.password": []byte("test"), 62 "server.secretkey": []byte("test"), 63 }, 64 }) 65 return settings.NewSettingsManager(ctx, clientset, "default") 66 } 67 68 type fakeCmdContext struct { 69 mgr *settings.SettingsManager 70 } 71 72 func newCmdContext(data map[string]string) *fakeCmdContext { 73 return &fakeCmdContext{mgr: newSettingsManager(data)} 74 } 75 76 func (ctx *fakeCmdContext) createSettingsManager(context.Context) (*settings.SettingsManager, error) { 77 return ctx.mgr, nil 78 } 79 80 type validatorTestCase struct { 81 validator string 82 data map[string]string 83 containsSummary string 84 containsError string 85 } 86 87 func TestCreateSettingsManager(t *testing.T) { 88 ctx := t.Context() 89 90 f, closer, err := tempFile(`apiVersion: v1 91 kind: ConfigMap 92 metadata: 93 name: argocd-cm 94 data: 95 url: https://myargocd.com`) 96 require.NoError(t, err) 97 defer utilio.Close(closer) 98 99 opts := settingsOpts{argocdCMPath: f} 100 settingsManager, err := opts.createSettingsManager(ctx) 101 102 require.NoError(t, err) 103 104 argoCDSettings, err := settingsManager.GetSettings() 105 require.NoError(t, err) 106 107 assert.Equal(t, "https://myargocd.com", argoCDSettings.URL) 108 } 109 110 func TestValidator(t *testing.T) { 111 testCases := map[string]validatorTestCase{ 112 "General_SSOIsNotConfigured": { 113 validator: "general", containsSummary: "SSO is not configured", 114 }, 115 "General_DexInvalidConfig": { 116 validator: "general", 117 data: map[string]string{"dex.config": "abcdefg"}, 118 containsError: "invalid dex.config", 119 }, 120 "General_OIDCConfigured": { 121 validator: "general", 122 data: map[string]string{ 123 "url": "https://myargocd.com", 124 "oidc.config": ` 125 name: Okta 126 issuer: https://dev-123456.oktapreview.com 127 clientID: aaaabbbbccccddddeee 128 clientSecret: aaaabbbbccccddddeee`, 129 }, 130 containsSummary: "OIDC is configured", 131 }, 132 "General_DexConfiguredMissingURL": { 133 validator: "general", 134 data: map[string]string{ 135 "dex.config": `connectors: 136 - type: github 137 name: GitHub 138 config: 139 clientID: aabbccddeeff00112233 140 clientSecret: aabbccddeeff00112233`, 141 }, 142 containsSummary: "Dex is configured ('url' field is missing)", 143 }, 144 "Kustomize_ModifiedOptions": { 145 validator: "kustomize", 146 containsSummary: "default options", 147 }, 148 "Kustomize_DefaultOptions": { 149 validator: "kustomize", 150 data: map[string]string{ 151 "kustomize.buildOptions": "updated-options (2 versions)", 152 "kustomize.versions.v123": "binary-123", 153 "kustomize.versions.v321": "binary-321", 154 }, 155 containsSummary: "updated-options", 156 }, 157 "Accounts": { 158 validator: "accounts", 159 data: map[string]string{ 160 "accounts.user1": "apiKey, login", 161 "accounts.user2": "login", 162 "accounts.user3": "apiKey", 163 }, 164 containsSummary: "4 accounts", 165 }, 166 "ResourceOverrides": { 167 validator: "resource-overrides", 168 data: map[string]string{ 169 "resource.customizations": ` 170 admissionregistration.k8s.io/MutatingWebhookConfiguration: 171 ignoreDifferences: | 172 jsonPointers: 173 - /webhooks/0/clientConfig/caBundle`, 174 }, 175 containsSummary: "2 resource overrides", 176 }, 177 } 178 for name := range testCases { 179 tc := testCases[name] 180 t.Run(name, func(t *testing.T) { 181 validator, ok := validatorsByGroup[tc.validator] 182 if !assert.True(t, ok) { 183 return 184 } 185 summary, err := validator(newSettingsManager(tc.data)) 186 if tc.containsSummary != "" { 187 require.NoError(t, err) 188 assert.Contains(t, summary, tc.containsSummary) 189 } else if tc.containsError != "" { 190 assert.ErrorContains(t, err, tc.containsError) 191 } 192 }) 193 } 194 } 195 196 const ( 197 testDeploymentYAML = `apiVersion: v1 198 apiVersion: apps/v1 199 kind: Deployment 200 metadata: 201 name: nginx-deployment 202 labels: 203 app: nginx 204 spec: 205 replicas: 0` 206 ) 207 208 const ( 209 testCustomResourceYAML = `apiVersion: v1 210 apiVersion: example.com/v1alpha1 211 kind: ExampleResource 212 metadata: 213 name: example-resource 214 labels: 215 app: example 216 spec: 217 replicas: 0` 218 ) 219 220 const ( 221 testCronJobYAML = `apiVersion: batch/v1 222 kind: CronJob 223 metadata: 224 name: hello 225 namespace: test-ns 226 uid: "123" 227 spec: 228 schedule: "* * * * *"` 229 ) 230 231 func tempFile(content string) (string, io.Closer, error) { 232 f, err := os.CreateTemp("", "*.yaml") 233 if err != nil { 234 return "", nil, err 235 } 236 _, err = f.WriteString(content) 237 if err != nil { 238 _ = os.Remove(f.Name()) 239 return "", nil, err 240 } 241 defer func() { 242 if err = f.Close(); err != nil { 243 panic(err) 244 } 245 }() 246 return f.Name(), utilio.NewCloser(func() error { 247 return os.Remove(f.Name()) 248 }), nil 249 } 250 251 func TestValidateSettingsCommand_NoErrors(t *testing.T) { 252 cmd := NewValidateSettingsCommand(newCmdContext(map[string]string{})) 253 out, err := captureStdout(func() { 254 err := cmd.Execute() 255 require.NoError(t, err) 256 }) 257 258 require.NoError(t, err) 259 for k := range validatorsByGroup { 260 assert.Contains(t, out, "✅ "+k) 261 } 262 } 263 264 func TestResourceOverrideIgnoreDifferences(t *testing.T) { 265 f, closer, err := tempFile(testDeploymentYAML) 266 require.NoError(t, err) 267 defer utilio.Close(closer) 268 269 t.Run("NoOverridesConfigured", func(t *testing.T) { 270 cmd := NewResourceOverridesCommand(newCmdContext(map[string]string{})) 271 out, err := captureStdout(func() { 272 cmd.SetArgs([]string{"ignore-differences", f}) 273 err := cmd.Execute() 274 require.NoError(t, err) 275 }) 276 require.NoError(t, err) 277 assert.Contains(t, out, "Ignore differences are not configured for 'apps/Deployment'\n") 278 }) 279 280 t.Run("DataIgnored", func(t *testing.T) { 281 cmd := NewResourceOverridesCommand(newCmdContext(map[string]string{ 282 "resource.customizations": `apps/Deployment: 283 ignoreDifferences: | 284 jsonPointers: 285 - /spec`, 286 })) 287 out, err := captureStdout(func() { 288 cmd.SetArgs([]string{"ignore-differences", f}) 289 err := cmd.Execute() 290 require.NoError(t, err) 291 }) 292 require.NoError(t, err) 293 assert.Contains(t, out, "< spec:") 294 }) 295 } 296 297 func TestResourceOverrideHealth(t *testing.T) { 298 f, closer, err := tempFile(testCustomResourceYAML) 299 require.NoError(t, err) 300 defer utilio.Close(closer) 301 302 t.Run("NoHealthAssessment", func(t *testing.T) { 303 cmd := NewResourceOverridesCommand(newCmdContext(map[string]string{ 304 "resource.customizations": `example.com/ExampleResource: {}`, 305 })) 306 out, err := captureStdout(func() { 307 cmd.SetArgs([]string{"health", f}) 308 err := cmd.Execute() 309 require.NoError(t, err) 310 }) 311 require.NoError(t, err) 312 assert.Contains(t, out, "Health script is not configured for 'example.com/ExampleResource'\n") 313 }) 314 315 t.Run("HealthAssessmentConfigured", func(t *testing.T) { 316 cmd := NewResourceOverridesCommand(newCmdContext(map[string]string{ 317 "resource.customizations": `example.com/ExampleResource: 318 health.lua: | 319 return { status = "Progressing" } 320 `, 321 })) 322 out, err := captureStdout(func() { 323 cmd.SetArgs([]string{"health", f}) 324 err := cmd.Execute() 325 require.NoError(t, err) 326 }) 327 require.NoError(t, err) 328 assert.Contains(t, out, "Progressing") 329 }) 330 331 t.Run("HealthAssessmentConfiguredWildcard", func(t *testing.T) { 332 cmd := NewResourceOverridesCommand(newCmdContext(map[string]string{ 333 "resource.customizations": `example.com/*: 334 health.lua: | 335 return { status = "Progressing" } 336 `, 337 })) 338 out, err := captureStdout(func() { 339 cmd.SetArgs([]string{"health", f}) 340 err := cmd.Execute() 341 require.NoError(t, err) 342 }) 343 require.NoError(t, err) 344 assert.Contains(t, out, "Progressing") 345 }) 346 } 347 348 func TestResourceOverrideAction(t *testing.T) { 349 f, closer, err := tempFile(testDeploymentYAML) 350 require.NoError(t, err) 351 defer utilio.Close(closer) 352 353 cronJobFile, closer, err := tempFile(testCronJobYAML) 354 require.NoError(t, err) 355 defer utilio.Close(closer) 356 357 t.Run("NoActions", func(t *testing.T) { 358 cmd := NewResourceOverridesCommand(newCmdContext(map[string]string{ 359 "resource.customizations": `apps/Deployment: {}`, 360 })) 361 out, err := captureStdout(func() { 362 cmd.SetArgs([]string{"run-action", f, "test"}) 363 err := cmd.Execute() 364 require.NoError(t, err) 365 }) 366 require.NoError(t, err) 367 assert.Contains(t, out, "Actions are not configured") 368 }) 369 370 t.Run("OldStyleActionConfigured", func(t *testing.T) { 371 cmd := NewResourceOverridesCommand(newCmdContext(map[string]string{ 372 "resource.customizations": `apps/Deployment: 373 actions: | 374 discovery.lua: | 375 actions = {} 376 actions["resume"] = {["disabled"] = false} 377 actions["restart"] = {["disabled"] = false} 378 return actions 379 definitions: 380 - name: test 381 action.lua: | 382 obj.metadata.labels["test"] = 'updated' 383 return obj 384 `, 385 })) 386 out, err := captureStdout(func() { 387 cmd.SetArgs([]string{"run-action", f, "test"}) 388 err := cmd.Execute() 389 require.NoError(t, err) 390 }) 391 require.NoError(t, err) 392 assert.Contains(t, out, "test: updated") 393 394 out, err = captureStdout(func() { 395 cmd.SetArgs([]string{"list-actions", f}) 396 err := cmd.Execute() 397 require.NoError(t, err) 398 }) 399 require.NoError(t, err) 400 assert.Contains(t, out, `NAME DISABLED 401 restart false 402 resume false 403 `) 404 }) 405 406 t.Run("NewStyleActionConfigured", func(t *testing.T) { 407 cmd := NewResourceOverridesCommand(newCmdContext(map[string]string{ 408 "resource.customizations": `batch/CronJob: 409 actions: | 410 discovery.lua: | 411 actions = {} 412 actions["create-a-job"] = {["disabled"] = false} 413 return actions 414 definitions: 415 - name: test 416 action.lua: | 417 job1 = {} 418 job1.apiVersion = "batch/v1" 419 job1.kind = "Job" 420 job1.metadata = {} 421 job1.metadata.name = "hello-1" 422 job1.metadata.namespace = "obj.metadata.namespace" 423 impactedResource1 = {} 424 impactedResource1.operation = "create" 425 impactedResource1.resource = job1 426 result = {} 427 result[1] = impactedResource1 428 return result 429 `, 430 })) 431 out, err := captureStdout(func() { 432 cmd.SetArgs([]string{"run-action", cronJobFile, "test"}) 433 err := cmd.Execute() 434 require.NoError(t, err) 435 }) 436 require.NoError(t, err) 437 assert.Contains(t, out, "resource was created:") 438 assert.Contains(t, out, "hello-1") 439 440 out, err = captureStdout(func() { 441 cmd.SetArgs([]string{"list-actions", cronJobFile}) 442 err := cmd.Execute() 443 require.NoError(t, err) 444 }) 445 446 require.NoError(t, err) 447 assert.Contains(t, out, "NAME") 448 assert.Contains(t, out, "DISABLED") 449 assert.Contains(t, out, "create-a-job") 450 assert.Contains(t, out, "false") 451 }) 452 }