github.com/dhaiducek/policy-generator-plugin@v1.99.99/internal/plugin_test.go (about) 1 // Copyright Contributors to the Open Cluster Management project 2 package internal 3 4 import ( 5 "encoding/json" 6 "fmt" 7 "os" 8 "path" 9 "path/filepath" 10 "reflect" 11 "strings" 12 "testing" 13 14 "github.com/dhaiducek/policy-generator-plugin/internal/types" 15 "gopkg.in/yaml.v3" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 ) 18 19 type testCase struct { 20 name string 21 setupFunc func(p *Plugin) 22 expectedPolicySetConfigInPolicy [][]string 23 expectedPolicySetConfigs []types.PolicySetConfig 24 expectedErrMsg string 25 } 26 27 func TestGenerate(t *testing.T) { 28 t.Parallel() 29 tmpDir := t.TempDir() 30 createConfigMap(t, tmpDir, "configmap.yaml") 31 32 p := Plugin{} 33 var err error 34 35 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 36 if err != nil { 37 t.Fatal(err.Error()) 38 } 39 40 p.PlacementBindingDefaults.Name = "my-placement-binding" 41 p.PolicyDefaults.Placement.Name = "my-placement-rule" 42 p.PolicyDefaults.Namespace = "my-policies" 43 p.PolicyDefaults.MetadataComplianceType = "musthave" 44 p.PolicyDefaults.PruneObjectBehavior = "DeleteAll" 45 patch := map[string]interface{}{ 46 "metadata": map[string]interface{}{ 47 "labels": map[string]string{ 48 "chandler": "bing", 49 }, 50 }, 51 } 52 policyConf := types.PolicyConfig{ 53 Name: "policy-app-config", 54 ConfigurationPolicyOptions: types.ConfigurationPolicyOptions{ 55 PruneObjectBehavior: "None", 56 }, 57 Manifests: []types.Manifest{ 58 { 59 Path: path.Join(tmpDir, "configmap.yaml"), 60 Patches: []map[string]interface{}{patch}, 61 }, 62 }, 63 } 64 policyConf2 := types.PolicyConfig{ 65 Name: "policy-app-config2", 66 Manifests: []types.Manifest{ 67 { 68 ConfigurationPolicyOptions: types.ConfigurationPolicyOptions{ 69 MetadataComplianceType: "mustonlyhave", 70 }, 71 Path: path.Join(tmpDir, "configmap.yaml"), 72 }, 73 }, 74 } 75 p.Policies = append(p.Policies, policyConf, policyConf2) 76 p.applyDefaults(map[string]interface{}{}) 77 // Default all policy ConsolidateManifests flags are set to true 78 // unless explicitly set 79 assertEqual(t, p.Policies[0].ConsolidateManifests, true) 80 assertEqual(t, p.Policies[1].ConsolidateManifests, true) 81 82 if err := p.assertValidConfig(); err != nil { 83 t.Fatal(err.Error()) 84 } 85 86 expected := ` 87 --- 88 apiVersion: policy.open-cluster-management.io/v1 89 kind: Policy 90 metadata: 91 annotations: 92 policy.open-cluster-management.io/categories: CM Configuration Management 93 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 94 policy.open-cluster-management.io/standards: NIST SP 800-53 95 name: policy-app-config 96 namespace: my-policies 97 spec: 98 disabled: false 99 policy-templates: 100 - objectDefinition: 101 apiVersion: policy.open-cluster-management.io/v1 102 kind: ConfigurationPolicy 103 metadata: 104 name: policy-app-config 105 spec: 106 object-templates: 107 - complianceType: musthave 108 metadataComplianceType: musthave 109 objectDefinition: 110 apiVersion: v1 111 data: 112 game.properties: enemies=potato 113 kind: ConfigMap 114 metadata: 115 labels: 116 chandler: bing 117 name: my-configmap 118 pruneObjectBehavior: None 119 remediationAction: inform 120 severity: low 121 remediationAction: inform 122 --- 123 apiVersion: policy.open-cluster-management.io/v1 124 kind: Policy 125 metadata: 126 annotations: 127 policy.open-cluster-management.io/categories: CM Configuration Management 128 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 129 policy.open-cluster-management.io/standards: NIST SP 800-53 130 name: policy-app-config2 131 namespace: my-policies 132 spec: 133 disabled: false 134 policy-templates: 135 - objectDefinition: 136 apiVersion: policy.open-cluster-management.io/v1 137 kind: ConfigurationPolicy 138 metadata: 139 name: policy-app-config2 140 spec: 141 object-templates: 142 - complianceType: musthave 143 metadataComplianceType: mustonlyhave 144 objectDefinition: 145 apiVersion: v1 146 data: 147 game.properties: enemies=potato 148 kind: ConfigMap 149 metadata: 150 name: my-configmap 151 pruneObjectBehavior: DeleteAll 152 remediationAction: inform 153 severity: low 154 remediationAction: inform 155 --- 156 apiVersion: apps.open-cluster-management.io/v1 157 kind: PlacementRule 158 metadata: 159 name: my-placement-rule 160 namespace: my-policies 161 spec: 162 clusterSelector: 163 matchExpressions: [] 164 --- 165 apiVersion: policy.open-cluster-management.io/v1 166 kind: PlacementBinding 167 metadata: 168 name: my-placement-binding 169 namespace: my-policies 170 placementRef: 171 apiGroup: apps.open-cluster-management.io 172 kind: PlacementRule 173 name: my-placement-rule 174 subjects: 175 - apiGroup: policy.open-cluster-management.io 176 kind: Policy 177 name: policy-app-config 178 - apiGroup: policy.open-cluster-management.io 179 kind: Policy 180 name: policy-app-config2 181 ` 182 expected = strings.TrimPrefix(expected, "\n") 183 184 output, err := p.Generate() 185 if err != nil { 186 t.Fatal(err.Error()) 187 } 188 189 assertEqual(t, string(output), expected) 190 } 191 192 func TestConfigManifestKeyOverride(t *testing.T) { 193 t.Parallel() 194 tmpDir := t.TempDir() 195 createConfigMap(t, tmpDir, "configmap.yaml") 196 197 tests := map[string]struct { 198 // Individual values can't be used for compliant/noncompliant since an empty string means 199 // to not inherit from the policy defaults. 200 keyName string 201 defaultKey string 202 policyKey string 203 manifestKey string 204 }{ 205 "pruneObjectBehavior specified in manifest": { 206 "pruneObjectBehavior", 207 "None", 208 "DeleteIfCreated", 209 `"DeleteAll"`, 210 }, 211 "namespaceSelector specified in manifest": { 212 "namespaceSelector", 213 `{"matchLabels":{"name":"test"}}`, 214 `{"exclude":["test"]}`, 215 `{"include":["test"]}`, 216 }, 217 "remediationAction specified in manifest": { 218 "remediationAction", 219 "inform", 220 "inform", 221 `"enforce"`, 222 }, 223 "severity specified in manifest": { 224 "severity", 225 "low", 226 "medium", 227 `"critical"`, 228 }, 229 } 230 231 for testName, test := range tests { 232 test := test 233 234 t.Run( 235 testName, 236 func(t *testing.T) { 237 t.Parallel() 238 config := fmt.Sprintf(` 239 apiVersion: policy.open-cluster-management.io/v1 240 kind: PolicyGenerator 241 metadata: 242 name: policy-generator-name 243 policyDefaults: 244 namespace: my-policies 245 consolidateManifests: false 246 %s: %s 247 policies: 248 - name: policy-app 249 %s: %s 250 manifests: 251 - path: %s 252 %s: %s 253 `, 254 test.keyName, test.defaultKey, 255 test.keyName, test.policyKey, 256 path.Join(tmpDir, "configmap.yaml"), 257 test.keyName, test.manifestKey, 258 ) 259 260 p := Plugin{} 261 err := p.Config([]byte(config), tmpDir) 262 if err != nil { 263 t.Fatal("Unexpected error", err) 264 } 265 266 assertEqual(t, p.Policies[0].ConsolidateManifests, false) 267 268 output, err := p.Generate() 269 if err != nil { 270 t.Fatal("Failed to generate policies from PolicyGenerator manifest", err) 271 } 272 273 var policyObj map[string]interface{} 274 err = yaml.Unmarshal(output, &policyObj) 275 if err != nil { 276 t.Fatal("Failed to unmarshal object", err) 277 } 278 279 policyTemplate := policyObj["spec"].(map[string]interface{})["policy-templates"].([]interface{})[0] 280 objectDef := policyTemplate.(map[string]interface{})["objectDefinition"].(map[string]interface{}) 281 configSpec := objectDef["spec"].(map[string]interface{}) 282 283 jsonConfig, err := json.Marshal(configSpec[test.keyName]) 284 if err != nil { 285 t.Fatal("Failed to marshal policy to JSON", err) 286 } 287 288 assertEqual(t, string(jsonConfig), test.manifestKey) 289 }, 290 ) 291 } 292 } 293 294 func TestGeneratePolicyDisablePlacement(t *testing.T) { 295 t.Parallel() 296 tmpDir := t.TempDir() 297 createConfigMap(t, tmpDir, "configmap.yaml") 298 299 p := Plugin{} 300 var err error 301 302 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 303 if err != nil { 304 t.Fatal(err.Error()) 305 } 306 307 p.PolicyDefaults.Namespace = "my-policies" 308 p.PolicyDefaults.MetadataComplianceType = "musthave" 309 p.PolicyDefaults.Placement.PlacementName = "my-placement" 310 policyConf := types.PolicyConfig{ 311 Name: "policy-app-config", 312 Manifests: []types.Manifest{ 313 { 314 Path: path.Join(tmpDir, "configmap.yaml"), 315 }, 316 }, 317 } 318 p.Policies = append(p.Policies, policyConf) 319 p.applyDefaults(map[string]interface{}{ 320 "policyDefaults": map[string]interface{}{ 321 "generatePolicyPlacement": false, 322 }, 323 }) 324 assertEqual(t, p.Policies[0].GeneratePolicyPlacement, false) 325 // Default all policy ConsolidateManifests flags are set to true 326 // unless explicitly set 327 assertEqual(t, p.Policies[0].ConsolidateManifests, true) 328 329 if err := p.assertValidConfig(); err != nil { 330 t.Fatal(err.Error()) 331 } 332 333 expected := ` 334 --- 335 apiVersion: policy.open-cluster-management.io/v1 336 kind: Policy 337 metadata: 338 annotations: 339 policy.open-cluster-management.io/categories: CM Configuration Management 340 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 341 policy.open-cluster-management.io/standards: NIST SP 800-53 342 name: policy-app-config 343 namespace: my-policies 344 spec: 345 disabled: false 346 policy-templates: 347 - objectDefinition: 348 apiVersion: policy.open-cluster-management.io/v1 349 kind: ConfigurationPolicy 350 metadata: 351 name: policy-app-config 352 spec: 353 object-templates: 354 - complianceType: musthave 355 metadataComplianceType: musthave 356 objectDefinition: 357 apiVersion: v1 358 data: 359 game.properties: enemies=potato 360 kind: ConfigMap 361 metadata: 362 name: my-configmap 363 remediationAction: inform 364 severity: low 365 remediationAction: inform 366 ` 367 expected = strings.TrimPrefix(expected, "\n") 368 369 output, err := p.Generate() 370 if err != nil { 371 t.Fatal(err.Error()) 372 } 373 374 assertEqual(t, string(output), expected) 375 } 376 377 func TestGeneratePolicyDisablePlacementOverride(t *testing.T) { 378 t.Parallel() 379 tmpDir := t.TempDir() 380 createConfigMap(t, tmpDir, "configmap.yaml") 381 382 p := Plugin{} 383 var err error 384 385 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 386 if err != nil { 387 t.Fatal(err.Error()) 388 } 389 390 p.PolicyDefaults.Namespace = "my-policies" 391 p.PolicyDefaults.MetadataComplianceType = "musthave" 392 p.PolicyDefaults.Placement.PlacementName = "my-placement" 393 policyConf := types.PolicyConfig{ 394 Name: "policy-app-config", 395 Manifests: []types.Manifest{ 396 { 397 Path: path.Join(tmpDir, "configmap.yaml"), 398 }, 399 }, 400 PolicyOptions: types.PolicyOptions{ 401 GeneratePolicyPlacement: false, 402 Placement: types.PlacementConfig{ 403 PlacementName: "my-placement", 404 }, 405 }, 406 } 407 p.Policies = append(p.Policies, policyConf) 408 p.applyDefaults(map[string]interface{}{ 409 "policies": []interface{}{ 410 map[string]interface{}{ 411 "generatePolicyPlacement": false, 412 }, 413 }, 414 }) 415 assertEqual(t, p.Policies[0].GeneratePolicyPlacement, false) 416 // Default all policy ConsolidateManifests flags are set to true 417 // unless explicitly set 418 assertEqual(t, p.Policies[0].ConsolidateManifests, true) 419 420 if err := p.assertValidConfig(); err != nil { 421 t.Fatal(err.Error()) 422 } 423 424 expected := ` 425 --- 426 apiVersion: policy.open-cluster-management.io/v1 427 kind: Policy 428 metadata: 429 annotations: 430 policy.open-cluster-management.io/categories: CM Configuration Management 431 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 432 policy.open-cluster-management.io/standards: NIST SP 800-53 433 name: policy-app-config 434 namespace: my-policies 435 spec: 436 disabled: false 437 policy-templates: 438 - objectDefinition: 439 apiVersion: policy.open-cluster-management.io/v1 440 kind: ConfigurationPolicy 441 metadata: 442 name: policy-app-config 443 spec: 444 object-templates: 445 - complianceType: musthave 446 metadataComplianceType: musthave 447 objectDefinition: 448 apiVersion: v1 449 data: 450 game.properties: enemies=potato 451 kind: ConfigMap 452 metadata: 453 name: my-configmap 454 remediationAction: inform 455 severity: low 456 remediationAction: inform 457 ` 458 expected = strings.TrimPrefix(expected, "\n") 459 460 output, err := p.Generate() 461 if err != nil { 462 t.Fatal(err.Error()) 463 } 464 465 assertEqual(t, string(output), expected) 466 } 467 468 func TestGeneratePolicyExistingPlacementRuleName(t *testing.T) { 469 t.Parallel() 470 tmpDir := t.TempDir() 471 createConfigMap(t, tmpDir, "configmap.yaml") 472 473 p := Plugin{} 474 var err error 475 476 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 477 if err != nil { 478 t.Fatal(err.Error()) 479 } 480 481 p.PolicyDefaults.Placement.PlacementRuleName = "plrexistingname" 482 p.PolicyDefaults.Namespace = "my-policies" 483 p.PolicyDefaults.MetadataComplianceType = "musthave" 484 policyConf := types.PolicyConfig{ 485 Name: "policy-app-config", 486 Manifests: []types.Manifest{ 487 { 488 Path: path.Join(tmpDir, "configmap.yaml"), 489 }, 490 }, 491 } 492 p.Policies = append(p.Policies, policyConf) 493 p.applyDefaults(map[string]interface{}{}) 494 // Default all policy ConsolidateManifests flags are set to true 495 // unless explicitly set 496 assertEqual(t, p.Policies[0].ConsolidateManifests, true) 497 498 if err := p.assertValidConfig(); err != nil { 499 t.Fatal(err.Error()) 500 } 501 502 expected := ` 503 --- 504 apiVersion: policy.open-cluster-management.io/v1 505 kind: Policy 506 metadata: 507 annotations: 508 policy.open-cluster-management.io/categories: CM Configuration Management 509 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 510 policy.open-cluster-management.io/standards: NIST SP 800-53 511 name: policy-app-config 512 namespace: my-policies 513 spec: 514 disabled: false 515 policy-templates: 516 - objectDefinition: 517 apiVersion: policy.open-cluster-management.io/v1 518 kind: ConfigurationPolicy 519 metadata: 520 name: policy-app-config 521 spec: 522 object-templates: 523 - complianceType: musthave 524 metadataComplianceType: musthave 525 objectDefinition: 526 apiVersion: v1 527 data: 528 game.properties: enemies=potato 529 kind: ConfigMap 530 metadata: 531 name: my-configmap 532 remediationAction: inform 533 severity: low 534 remediationAction: inform 535 --- 536 apiVersion: policy.open-cluster-management.io/v1 537 kind: PlacementBinding 538 metadata: 539 name: binding-policy-app-config 540 namespace: my-policies 541 placementRef: 542 apiGroup: apps.open-cluster-management.io 543 kind: PlacementRule 544 name: plrexistingname 545 subjects: 546 - apiGroup: policy.open-cluster-management.io 547 kind: Policy 548 name: policy-app-config 549 ` 550 expected = strings.TrimPrefix(expected, "\n") 551 552 output, err := p.Generate() 553 if err != nil { 554 t.Fatal(err.Error()) 555 } 556 557 assertEqual(t, string(output), expected) 558 } 559 560 func TestGeneratePolicyExistingPlacementName(t *testing.T) { 561 t.Parallel() 562 tmpDir := t.TempDir() 563 createConfigMap(t, tmpDir, "configmap.yaml") 564 565 p := Plugin{} 566 var err error 567 568 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 569 if err != nil { 570 t.Fatal(err.Error()) 571 } 572 573 p.PolicyDefaults.Placement.PlacementName = "plexistingname" 574 p.PolicyDefaults.Namespace = "my-policies" 575 p.PolicyDefaults.MetadataComplianceType = "musthave" 576 policyConf := types.PolicyConfig{ 577 Name: "policy-app-config", 578 Manifests: []types.Manifest{ 579 { 580 Path: path.Join(tmpDir, "configmap.yaml"), 581 }, 582 }, 583 } 584 p.Policies = append(p.Policies, policyConf) 585 p.applyDefaults(map[string]interface{}{}) 586 // Default all policy ConsolidateManifests flags are set to true 587 // unless explicitly set 588 assertEqual(t, p.Policies[0].ConsolidateManifests, true) 589 590 if err := p.assertValidConfig(); err != nil { 591 t.Fatal(err.Error()) 592 } 593 594 expected := ` 595 --- 596 apiVersion: policy.open-cluster-management.io/v1 597 kind: Policy 598 metadata: 599 annotations: 600 policy.open-cluster-management.io/categories: CM Configuration Management 601 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 602 policy.open-cluster-management.io/standards: NIST SP 800-53 603 name: policy-app-config 604 namespace: my-policies 605 spec: 606 disabled: false 607 policy-templates: 608 - objectDefinition: 609 apiVersion: policy.open-cluster-management.io/v1 610 kind: ConfigurationPolicy 611 metadata: 612 name: policy-app-config 613 spec: 614 object-templates: 615 - complianceType: musthave 616 metadataComplianceType: musthave 617 objectDefinition: 618 apiVersion: v1 619 data: 620 game.properties: enemies=potato 621 kind: ConfigMap 622 metadata: 623 name: my-configmap 624 remediationAction: inform 625 severity: low 626 remediationAction: inform 627 --- 628 apiVersion: policy.open-cluster-management.io/v1 629 kind: PlacementBinding 630 metadata: 631 name: binding-policy-app-config 632 namespace: my-policies 633 placementRef: 634 apiGroup: cluster.open-cluster-management.io 635 kind: Placement 636 name: plexistingname 637 subjects: 638 - apiGroup: policy.open-cluster-management.io 639 kind: Policy 640 name: policy-app-config 641 ` 642 expected = strings.TrimPrefix(expected, "\n") 643 644 output, err := p.Generate() 645 if err != nil { 646 t.Fatal(err.Error()) 647 } 648 649 assertEqual(t, string(output), expected) 650 } 651 652 func TestGenerateSeparateBindings(t *testing.T) { 653 t.Parallel() 654 tmpDir := t.TempDir() 655 createConfigMap(t, tmpDir, "configmap.yaml") 656 657 p := Plugin{} 658 var err error 659 660 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 661 if err != nil { 662 t.Fatal(err.Error()) 663 } 664 665 p.PolicyDefaults.Namespace = "my-policies" 666 policyConf := types.PolicyConfig{ 667 Name: "policy-app-config", 668 Manifests: []types.Manifest{ 669 {Path: path.Join(tmpDir, "configmap.yaml")}, 670 }, 671 } 672 policyConf2 := types.PolicyConfig{ 673 Name: "policy-app-config2", 674 Manifests: []types.Manifest{ 675 {Path: path.Join(tmpDir, "configmap.yaml")}, 676 }, 677 } 678 p.Policies = append(p.Policies, policyConf, policyConf2) 679 p.applyDefaults(map[string]interface{}{}) 680 681 if err := p.assertValidConfig(); err != nil { 682 t.Fatal(err.Error()) 683 } 684 685 expected := ` 686 --- 687 apiVersion: policy.open-cluster-management.io/v1 688 kind: Policy 689 metadata: 690 annotations: 691 policy.open-cluster-management.io/categories: CM Configuration Management 692 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 693 policy.open-cluster-management.io/standards: NIST SP 800-53 694 name: policy-app-config 695 namespace: my-policies 696 spec: 697 disabled: false 698 policy-templates: 699 - objectDefinition: 700 apiVersion: policy.open-cluster-management.io/v1 701 kind: ConfigurationPolicy 702 metadata: 703 name: policy-app-config 704 spec: 705 object-templates: 706 - complianceType: musthave 707 objectDefinition: 708 apiVersion: v1 709 data: 710 game.properties: enemies=potato 711 kind: ConfigMap 712 metadata: 713 name: my-configmap 714 remediationAction: inform 715 severity: low 716 remediationAction: inform 717 --- 718 apiVersion: policy.open-cluster-management.io/v1 719 kind: Policy 720 metadata: 721 annotations: 722 policy.open-cluster-management.io/categories: CM Configuration Management 723 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 724 policy.open-cluster-management.io/standards: NIST SP 800-53 725 name: policy-app-config2 726 namespace: my-policies 727 spec: 728 disabled: false 729 policy-templates: 730 - objectDefinition: 731 apiVersion: policy.open-cluster-management.io/v1 732 kind: ConfigurationPolicy 733 metadata: 734 name: policy-app-config2 735 spec: 736 object-templates: 737 - complianceType: musthave 738 objectDefinition: 739 apiVersion: v1 740 data: 741 game.properties: enemies=potato 742 kind: ConfigMap 743 metadata: 744 name: my-configmap 745 remediationAction: inform 746 severity: low 747 remediationAction: inform 748 --- 749 apiVersion: apps.open-cluster-management.io/v1 750 kind: PlacementRule 751 metadata: 752 name: placement-policy-app-config 753 namespace: my-policies 754 spec: 755 clusterSelector: 756 matchExpressions: [] 757 --- 758 apiVersion: apps.open-cluster-management.io/v1 759 kind: PlacementRule 760 metadata: 761 name: placement-policy-app-config2 762 namespace: my-policies 763 spec: 764 clusterSelector: 765 matchExpressions: [] 766 --- 767 apiVersion: policy.open-cluster-management.io/v1 768 kind: PlacementBinding 769 metadata: 770 name: binding-policy-app-config 771 namespace: my-policies 772 placementRef: 773 apiGroup: apps.open-cluster-management.io 774 kind: PlacementRule 775 name: placement-policy-app-config 776 subjects: 777 - apiGroup: policy.open-cluster-management.io 778 kind: Policy 779 name: policy-app-config 780 --- 781 apiVersion: policy.open-cluster-management.io/v1 782 kind: PlacementBinding 783 metadata: 784 name: binding-policy-app-config2 785 namespace: my-policies 786 placementRef: 787 apiGroup: apps.open-cluster-management.io 788 kind: PlacementRule 789 name: placement-policy-app-config2 790 subjects: 791 - apiGroup: policy.open-cluster-management.io 792 kind: Policy 793 name: policy-app-config2 794 ` 795 expected = strings.TrimPrefix(expected, "\n") 796 797 output, err := p.Generate() 798 if err != nil { 799 t.Fatal(err.Error()) 800 } 801 802 assertEqual(t, string(output), expected) 803 } 804 805 func TestGenerateMissingBindingName(t *testing.T) { 806 t.Parallel() 807 tmpDir := t.TempDir() 808 createConfigMap(t, tmpDir, "configmap.yaml") 809 810 p := Plugin{} 811 var err error 812 813 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 814 if err != nil { 815 t.Fatal(err.Error()) 816 } 817 818 p.PlacementBindingDefaults.Name = "" 819 p.PolicyDefaults.Placement.Name = "my-placement-rule" 820 p.PolicyDefaults.Namespace = "my-policies" 821 policyConf := types.PolicyConfig{ 822 Name: "policy-app-config", 823 Manifests: []types.Manifest{ 824 {Path: path.Join(tmpDir, "configmap.yaml")}, 825 }, 826 } 827 policyConf2 := types.PolicyConfig{ 828 Name: "policy-app-config2", 829 Manifests: []types.Manifest{ 830 {Path: path.Join(tmpDir, "configmap.yaml")}, 831 }, 832 } 833 p.Policies = append(p.Policies, policyConf, policyConf2) 834 p.applyDefaults(map[string]interface{}{}) 835 836 if err := p.assertValidConfig(); err != nil { 837 t.Fatal(err.Error()) 838 } 839 840 _, err = p.Generate() 841 if err == nil { 842 t.Fatal("Expected an error but did not get one") 843 } 844 845 expected := fmt.Sprintf( 846 "placementBindingDefaults.name must be set but is empty (multiple policies or policy sets were found for the "+ 847 "PlacementBinding to placement %s)", 848 p.PolicyDefaults.Placement.Name, 849 ) 850 assertEqual(t, err.Error(), expected) 851 } 852 853 func TestCreatePolicy(t *testing.T) { 854 t.Parallel() 855 tmpDir := t.TempDir() 856 createConfigMap(t, tmpDir, "configmap.yaml") 857 858 p := Plugin{} 859 p.PolicyDefaults.Namespace = "my-policies" 860 policyConf := types.PolicyConfig{ 861 Name: "policy-app-config", 862 Manifests: []types.Manifest{ 863 {Path: path.Join(tmpDir, "configmap.yaml")}, 864 }, 865 } 866 p.Policies = append(p.Policies, policyConf) 867 p.applyDefaults(map[string]interface{}{}) 868 869 err := p.createPolicy(&p.Policies[0]) 870 if err != nil { 871 t.Fatal(err.Error()) 872 } 873 874 output := p.outputBuffer.String() 875 expected := ` 876 --- 877 apiVersion: policy.open-cluster-management.io/v1 878 kind: Policy 879 metadata: 880 annotations: 881 policy.open-cluster-management.io/categories: CM Configuration Management 882 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 883 policy.open-cluster-management.io/standards: NIST SP 800-53 884 name: policy-app-config 885 namespace: my-policies 886 spec: 887 disabled: false 888 policy-templates: 889 - objectDefinition: 890 apiVersion: policy.open-cluster-management.io/v1 891 kind: ConfigurationPolicy 892 metadata: 893 name: policy-app-config 894 spec: 895 object-templates: 896 - complianceType: musthave 897 objectDefinition: 898 apiVersion: v1 899 data: 900 game.properties: enemies=potato 901 kind: ConfigMap 902 metadata: 903 name: my-configmap 904 remediationAction: inform 905 severity: low 906 remediationAction: inform 907 ` 908 expected = strings.TrimPrefix(expected, "\n") 909 assertEqual(t, output, expected) 910 } 911 912 func TestCreatePolicyEmptyManifest(t *testing.T) { 913 t.Parallel() 914 tmpDir := t.TempDir() 915 createConfigMap(t, tmpDir, "configmap.yaml") 916 917 err := os.WriteFile(path.Join(tmpDir, "empty.yaml"), []byte{}, 0o666) 918 if err != nil { 919 t.Fatalf("Failed to write empty.yaml") 920 } 921 922 p := Plugin{} 923 p.PolicyDefaults.Namespace = "my-policies" 924 policyConf := types.PolicyConfig{ 925 Name: "policy-app-config", 926 Manifests: []types.Manifest{ 927 { 928 Path: path.Join(tmpDir, "empty.yaml"), 929 ConfigurationPolicyOptions: types.ConfigurationPolicyOptions{ComplianceType: "mustonlyhave"}, 930 }, { 931 Path: path.Join(tmpDir, "configmap.yaml"), 932 ConfigurationPolicyOptions: types.ConfigurationPolicyOptions{ComplianceType: "mustnothave"}, 933 }, 934 }, 935 } 936 p.Policies = append(p.Policies, policyConf) 937 p.applyDefaults(map[string]interface{}{}) 938 939 err = p.createPolicy(&p.Policies[0]) 940 expectedErr := fmt.Sprintf("found empty YAML in the manifest at %s", path.Join(tmpDir, "empty.yaml")) 941 assertEqual(t, err.Error(), expectedErr) 942 } 943 944 func TestCreatePolicyWithAnnotations(t *testing.T) { 945 t.Parallel() 946 tmpDir := t.TempDir() 947 createConfigMap(t, tmpDir, "configmap.yaml") 948 949 p := Plugin{} 950 p.PolicyDefaults.Namespace = "my-policies" 951 p.PolicyDefaults.PolicyAnnotations = map[string]string{"test-default-annotation": "default"} 952 953 policyConf := types.PolicyConfig{ 954 Name: "policy-app-config", 955 Manifests: []types.Manifest{ 956 {Path: path.Join(tmpDir, "configmap.yaml")}, 957 }, 958 } 959 p.Policies = append(p.Policies, policyConf) 960 p.applyDefaults(map[string]interface{}{}) 961 962 err := p.createPolicy(&p.Policies[0]) 963 if err != nil { 964 t.Fatal(err.Error()) 965 } 966 967 output := p.outputBuffer.String() 968 expected := ` 969 --- 970 apiVersion: policy.open-cluster-management.io/v1 971 kind: Policy 972 metadata: 973 annotations: 974 policy.open-cluster-management.io/categories: CM Configuration Management 975 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 976 policy.open-cluster-management.io/standards: NIST SP 800-53 977 test-default-annotation: default 978 name: policy-app-config 979 namespace: my-policies 980 spec: 981 disabled: false 982 policy-templates: 983 - objectDefinition: 984 apiVersion: policy.open-cluster-management.io/v1 985 kind: ConfigurationPolicy 986 metadata: 987 name: policy-app-config 988 spec: 989 object-templates: 990 - complianceType: musthave 991 objectDefinition: 992 apiVersion: v1 993 data: 994 game.properties: enemies=potato 995 kind: ConfigMap 996 metadata: 997 name: my-configmap 998 remediationAction: inform 999 severity: low 1000 remediationAction: inform 1001 ` 1002 expected = strings.TrimPrefix(expected, "\n") 1003 assertEqual(t, output, expected) 1004 1005 // Check for override default policy with empty map to skip default annotations from the policy 1006 p.outputBuffer.Reset() 1007 p.Policies[0].PolicyAnnotations = map[string]string{} 1008 p.applyDefaults(map[string]interface{}{}) 1009 1010 err = p.createPolicy(&p.Policies[0]) 1011 if err != nil { 1012 t.Fatal(err.Error()) 1013 } 1014 1015 output = p.outputBuffer.String() 1016 expected = ` 1017 --- 1018 apiVersion: policy.open-cluster-management.io/v1 1019 kind: Policy 1020 metadata: 1021 annotations: 1022 policy.open-cluster-management.io/categories: CM Configuration Management 1023 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 1024 policy.open-cluster-management.io/standards: NIST SP 800-53 1025 name: policy-app-config 1026 namespace: my-policies 1027 spec: 1028 disabled: false 1029 policy-templates: 1030 - objectDefinition: 1031 apiVersion: policy.open-cluster-management.io/v1 1032 kind: ConfigurationPolicy 1033 metadata: 1034 name: policy-app-config 1035 spec: 1036 object-templates: 1037 - complianceType: musthave 1038 objectDefinition: 1039 apiVersion: v1 1040 data: 1041 game.properties: enemies=potato 1042 kind: ConfigMap 1043 metadata: 1044 name: my-configmap 1045 remediationAction: inform 1046 severity: low 1047 remediationAction: inform 1048 ` 1049 expected = strings.TrimPrefix(expected, "\n") 1050 assertEqual(t, output, expected) 1051 1052 // Check for override default policy annotation 1053 p.outputBuffer.Reset() 1054 p.Policies[0].PolicyAnnotations = map[string]string{"test-wave-annotation": "100"} 1055 p.applyDefaults(map[string]interface{}{}) 1056 1057 err = p.createPolicy(&p.Policies[0]) 1058 if err != nil { 1059 t.Fatal(err.Error()) 1060 } 1061 1062 output = p.outputBuffer.String() 1063 expected = ` 1064 --- 1065 apiVersion: policy.open-cluster-management.io/v1 1066 kind: Policy 1067 metadata: 1068 annotations: 1069 policy.open-cluster-management.io/categories: CM Configuration Management 1070 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 1071 policy.open-cluster-management.io/standards: NIST SP 800-53 1072 test-wave-annotation: "100" 1073 name: policy-app-config 1074 namespace: my-policies 1075 spec: 1076 disabled: false 1077 policy-templates: 1078 - objectDefinition: 1079 apiVersion: policy.open-cluster-management.io/v1 1080 kind: ConfigurationPolicy 1081 metadata: 1082 name: policy-app-config 1083 spec: 1084 object-templates: 1085 - complianceType: musthave 1086 objectDefinition: 1087 apiVersion: v1 1088 data: 1089 game.properties: enemies=potato 1090 kind: ConfigMap 1091 metadata: 1092 name: my-configmap 1093 remediationAction: inform 1094 severity: low 1095 remediationAction: inform 1096 ` 1097 expected = strings.TrimPrefix(expected, "\n") 1098 assertEqual(t, output, expected) 1099 } 1100 1101 func TestCreatePolicyFromIamPolicyTypeManifest(t *testing.T) { 1102 t.Parallel() 1103 tmpDir := t.TempDir() 1104 createIamPolicyManifest(t, tmpDir, "iamKindManifestPluginTest.yaml") 1105 1106 p := Plugin{} 1107 p.PolicyDefaults.Namespace = "Iam-policies" 1108 policyConf := types.PolicyConfig{ 1109 PolicyOptions: types.PolicyOptions{ 1110 Categories: []string{"AC Access Control"}, 1111 Controls: []string{"AC-3 Access Enforcement"}, 1112 Standards: []string{"NIST SP 800-53"}, 1113 }, 1114 Name: "policy-limitclusteradmin", 1115 Manifests: []types.Manifest{ 1116 {Path: path.Join(tmpDir, "iamKindManifestPluginTest.yaml")}, 1117 }, 1118 } 1119 p.Policies = append(p.Policies, policyConf) 1120 p.applyDefaults(map[string]interface{}{}) 1121 1122 err := p.createPolicy(&p.Policies[0]) 1123 if err != nil { 1124 t.Fatal(err.Error()) 1125 } 1126 1127 output := p.outputBuffer.String() 1128 // expected Iam policy generated from 1129 // non-root IAM policy type manifest 1130 // in createIamPolicyTypeConfigMap() 1131 expected := ` 1132 --- 1133 apiVersion: policy.open-cluster-management.io/v1 1134 kind: Policy 1135 metadata: 1136 annotations: 1137 policy.open-cluster-management.io/categories: AC Access Control 1138 policy.open-cluster-management.io/controls: AC-3 Access Enforcement 1139 policy.open-cluster-management.io/standards: NIST SP 800-53 1140 name: policy-limitclusteradmin 1141 namespace: Iam-policies 1142 spec: 1143 disabled: false 1144 policy-templates: 1145 - objectDefinition: 1146 apiVersion: policy.open-cluster-management.io/v1 1147 kind: IamPolicy 1148 metadata: 1149 name: policy-limitclusteradmin-example 1150 spec: 1151 maxClusterRoleBindingUsers: 5 1152 namespaceSelector: 1153 exclude: 1154 - kube-* 1155 - openshift-* 1156 include: 1157 - '*' 1158 remediationAction: enforce 1159 severity: medium 1160 remediationAction: enforce 1161 ` 1162 expected = strings.TrimPrefix(expected, "\n") 1163 assertEqual(t, output, expected) 1164 } 1165 1166 func TestCreatePolicyWithGkConstraintTemplate(t *testing.T) { 1167 t.Parallel() 1168 tmpDir := t.TempDir() 1169 gatekeeperPath := path.Join(tmpDir, "gatekeeper.yaml") 1170 yamlContent := ` 1171 apiVersion: templates.gatekeeper.sh/v1 1172 kind: ConstraintTemplate 1173 metadata: 1174 name: myconstrainingtemplate 1175 ` 1176 1177 err := os.WriteFile(gatekeeperPath, []byte(yamlContent), 0o666) 1178 if err != nil { 1179 t.Fatalf("Failed to write %s", gatekeeperPath) 1180 } 1181 1182 p := Plugin{} 1183 1184 p.PolicyDefaults.Namespace = "gatekeeper-policies" 1185 p.PolicyDefaults.InformGatekeeperPolicies = false 1186 policyConf := types.PolicyConfig{ 1187 Name: "policy-gatekeeper", 1188 Manifests: []types.Manifest{ 1189 {Path: path.Join(tmpDir, "gatekeeper.yaml")}, 1190 }, 1191 } 1192 p.Policies = append(p.Policies, policyConf) 1193 p.applyDefaults(map[string]interface{}{ 1194 "policyDefaults": map[string]interface{}{ 1195 "informGatekeeperPolicies": false, 1196 }, 1197 }) 1198 1199 err = p.createPolicy(&p.Policies[0]) 1200 if err != nil { 1201 t.Fatal(err.Error()) 1202 } 1203 1204 output := p.outputBuffer.String() 1205 expected := ` 1206 --- 1207 apiVersion: policy.open-cluster-management.io/v1 1208 kind: Policy 1209 metadata: 1210 annotations: 1211 policy.open-cluster-management.io/categories: CM Configuration Management 1212 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 1213 policy.open-cluster-management.io/standards: NIST SP 800-53 1214 name: policy-gatekeeper 1215 namespace: gatekeeper-policies 1216 spec: 1217 disabled: false 1218 policy-templates: 1219 - objectDefinition: 1220 apiVersion: templates.gatekeeper.sh/v1 1221 kind: ConstraintTemplate 1222 metadata: 1223 name: myconstrainingtemplate 1224 ` 1225 expected = strings.TrimPrefix(expected, "\n") 1226 assertEqual(t, output, expected) 1227 } 1228 1229 func TestCreatePolicyWithGkConstraint(t *testing.T) { 1230 t.Parallel() 1231 tmpDir := t.TempDir() 1232 gatekeeperPath := path.Join(tmpDir, "gatekeeper.yaml") 1233 yamlContent := ` 1234 apiVersion: constraints.gatekeeper.sh/v1 1235 kind: MyConstrainingTemplate 1236 metadata: 1237 name: thisthingimconstraining 1238 ` 1239 1240 err := os.WriteFile(gatekeeperPath, []byte(yamlContent), 0o666) 1241 if err != nil { 1242 t.Fatalf("Failed to write %s", gatekeeperPath) 1243 } 1244 1245 p := Plugin{} 1246 1247 p.PolicyDefaults.Namespace = "gatekeeper-policies" 1248 p.PolicyDefaults.InformGatekeeperPolicies = false 1249 policyConf := types.PolicyConfig{ 1250 Name: "policy-gatekeeper", 1251 Manifests: []types.Manifest{ 1252 {Path: path.Join(tmpDir, "gatekeeper.yaml")}, 1253 }, 1254 } 1255 p.Policies = append(p.Policies, policyConf) 1256 p.applyDefaults(map[string]interface{}{ 1257 "policyDefaults": map[string]interface{}{ 1258 "informGatekeeperPolicies": false, 1259 }, 1260 }) 1261 1262 err = p.createPolicy(&p.Policies[0]) 1263 if err != nil { 1264 t.Fatal(err.Error()) 1265 } 1266 1267 output := p.outputBuffer.String() 1268 expected := ` 1269 --- 1270 apiVersion: policy.open-cluster-management.io/v1 1271 kind: Policy 1272 metadata: 1273 annotations: 1274 policy.open-cluster-management.io/categories: CM Configuration Management 1275 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 1276 policy.open-cluster-management.io/standards: NIST SP 800-53 1277 name: policy-gatekeeper 1278 namespace: gatekeeper-policies 1279 spec: 1280 disabled: false 1281 policy-templates: 1282 - objectDefinition: 1283 apiVersion: constraints.gatekeeper.sh/v1 1284 kind: MyConstrainingTemplate 1285 metadata: 1286 name: thisthingimconstraining 1287 ` 1288 expected = strings.TrimPrefix(expected, "\n") 1289 assertEqual(t, output, expected) 1290 } 1291 1292 func TestCreatePolicyWithDifferentRemediationAction(t *testing.T) { 1293 t.Parallel() 1294 tmpDir := t.TempDir() 1295 createIamPolicyManifest(t, tmpDir, "iamKindManifestPluginTest.yaml") 1296 createIamPolicyManifest(t, tmpDir, "iamKindManifestPluginTest2.yaml") 1297 1298 p := Plugin{} 1299 p.PolicyDefaults.Namespace = "Iam-policies" 1300 1301 patches := []map[string]interface{}{ 1302 { 1303 "spec": map[string]interface{}{ 1304 "remediationAction": "inform", 1305 }, 1306 }, 1307 } 1308 policyConf := types.PolicyConfig{ 1309 PolicyOptions: types.PolicyOptions{ 1310 Categories: []string{"AC Access Control"}, 1311 Controls: []string{"AC-3 Access Enforcement"}, 1312 Standards: []string{"NIST SP 800-53"}, 1313 }, 1314 Name: "policy-limitclusteradmin", 1315 Manifests: []types.Manifest{ 1316 {Path: path.Join(tmpDir, "iamKindManifestPluginTest.yaml")}, 1317 { 1318 Path: path.Join(tmpDir, "iamKindManifestPluginTest2.yaml"), 1319 Patches: patches, 1320 }, 1321 }, 1322 } 1323 p.Policies = append(p.Policies, policyConf) 1324 p.applyDefaults(map[string]interface{}{}) 1325 1326 err := p.createPolicy(&p.Policies[0]) 1327 if err != nil { 1328 t.Fatal(err.Error()) 1329 } 1330 1331 output := p.outputBuffer.String() 1332 // expected Iam policy generated from 1333 // non-root IAM policy type manifest 1334 // in createIamPolicyTypeConfigMap() 1335 expected := ` 1336 --- 1337 apiVersion: policy.open-cluster-management.io/v1 1338 kind: Policy 1339 metadata: 1340 annotations: 1341 policy.open-cluster-management.io/categories: AC Access Control 1342 policy.open-cluster-management.io/controls: AC-3 Access Enforcement 1343 policy.open-cluster-management.io/standards: NIST SP 800-53 1344 name: policy-limitclusteradmin 1345 namespace: Iam-policies 1346 spec: 1347 disabled: false 1348 policy-templates: 1349 - objectDefinition: 1350 apiVersion: policy.open-cluster-management.io/v1 1351 kind: IamPolicy 1352 metadata: 1353 name: policy-limitclusteradmin-example 1354 spec: 1355 maxClusterRoleBindingUsers: 5 1356 namespaceSelector: 1357 exclude: 1358 - kube-* 1359 - openshift-* 1360 include: 1361 - '*' 1362 remediationAction: enforce 1363 severity: medium 1364 - objectDefinition: 1365 apiVersion: policy.open-cluster-management.io/v1 1366 kind: IamPolicy 1367 metadata: 1368 name: policy-limitclusteradmin-example 1369 spec: 1370 maxClusterRoleBindingUsers: 5 1371 namespaceSelector: 1372 exclude: 1373 - kube-* 1374 - openshift-* 1375 include: 1376 - '*' 1377 remediationAction: inform 1378 severity: medium 1379 ` 1380 expected = strings.TrimPrefix(expected, "\n") 1381 assertEqual(t, output, expected) 1382 } 1383 1384 func TestCreatePolicyDir(t *testing.T) { 1385 t.Parallel() 1386 tmpDir := t.TempDir() 1387 createConfigMap(t, tmpDir, "configmap.yaml") 1388 createConfigMap(t, tmpDir, "configmap2.yaml") 1389 1390 p := Plugin{} 1391 p.PolicyDefaults.Namespace = "my-policies" 1392 policyConf := types.PolicyConfig{ 1393 Name: "policy-app-config", 1394 Manifests: []types.Manifest{{Path: tmpDir}}, 1395 ConfigurationPolicyOptions: types.ConfigurationPolicyOptions{ 1396 NamespaceSelector: types.NamespaceSelector{Include: []string{"default"}}, 1397 }, 1398 } 1399 p.Policies = append(p.Policies, policyConf) 1400 p.applyDefaults(map[string]interface{}{}) 1401 1402 err := p.createPolicy(&p.Policies[0]) 1403 if err != nil { 1404 t.Fatal(err.Error()) 1405 } 1406 1407 output := p.outputBuffer.String() 1408 expected := ` 1409 --- 1410 apiVersion: policy.open-cluster-management.io/v1 1411 kind: Policy 1412 metadata: 1413 annotations: 1414 policy.open-cluster-management.io/categories: CM Configuration Management 1415 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 1416 policy.open-cluster-management.io/standards: NIST SP 800-53 1417 name: policy-app-config 1418 namespace: my-policies 1419 spec: 1420 disabled: false 1421 policy-templates: 1422 - objectDefinition: 1423 apiVersion: policy.open-cluster-management.io/v1 1424 kind: ConfigurationPolicy 1425 metadata: 1426 name: policy-app-config 1427 spec: 1428 namespaceSelector: 1429 include: 1430 - default 1431 object-templates: 1432 - complianceType: musthave 1433 objectDefinition: 1434 apiVersion: v1 1435 data: 1436 game.properties: enemies=potato 1437 kind: ConfigMap 1438 metadata: 1439 name: my-configmap 1440 - complianceType: musthave 1441 objectDefinition: 1442 apiVersion: v1 1443 data: 1444 game.properties: enemies=potato 1445 kind: ConfigMap 1446 metadata: 1447 name: my-configmap 1448 remediationAction: inform 1449 severity: low 1450 remediationAction: inform 1451 ` 1452 expected = strings.TrimPrefix(expected, "\n") 1453 assertEqual(t, output, expected) 1454 } 1455 1456 func TestCreatePolicyInvalidYAML(t *testing.T) { 1457 t.Parallel() 1458 tmpDir := t.TempDir() 1459 manifestPath := path.Join(tmpDir, "configmap.yaml") 1460 1461 err := os.WriteFile(manifestPath, []byte("$ not Yaml!"), 0o666) 1462 if err != nil { 1463 t.Fatalf("Failed to create %s: %v", manifestPath, err) 1464 } 1465 1466 p := Plugin{} 1467 p.PolicyDefaults.Namespace = "my-policies" 1468 policyConf := types.PolicyConfig{ 1469 Name: "policy-app-config", 1470 Manifests: []types.Manifest{{Path: manifestPath}}, 1471 } 1472 p.Policies = append(p.Policies, policyConf) 1473 p.applyDefaults(map[string]interface{}{}) 1474 1475 err = p.createPolicy(&p.Policies[0]) 1476 if err == nil { 1477 t.Fatal("Expected an error but did not get one") 1478 } 1479 1480 expected := fmt.Sprintf( 1481 "failed to decode the manifest file at %s: the input manifests must be in the format of "+ 1482 "YAML objects", manifestPath, 1483 ) 1484 assertEqual(t, err.Error(), expected) 1485 } 1486 1487 func TestCreatePolicyInvalidAPIOrKind(t *testing.T) { 1488 t.Parallel() 1489 tmpDir := t.TempDir() 1490 manifestPath := path.Join(tmpDir, "invalidAPIOrKind.yaml") 1491 yamlContent := ` 1492 apiVersion: policy.open-cluster-management.io/v1 1493 kind: 1494 - IamPolicy 1495 - CertificatePolicy 1496 metadata: 1497 name: policy-limitclusteradmin-example 1498 ` 1499 1500 err := os.WriteFile(manifestPath, []byte(yamlContent), 0o666) 1501 if err != nil { 1502 t.Fatalf("Failed to create %s: %v", manifestPath, err) 1503 } 1504 1505 p := Plugin{} 1506 p.PolicyDefaults.Namespace = "my-policies" 1507 policyConf := types.PolicyConfig{ 1508 Name: "policy-limitclusteradmin", 1509 Manifests: []types.Manifest{{Path: manifestPath}}, 1510 } 1511 p.Policies = append(p.Policies, policyConf) 1512 p.applyDefaults(map[string]interface{}{}) 1513 1514 err = p.createPolicy(&p.Policies[0]) 1515 if err == nil { 1516 t.Fatal("Expected an error but did not get one") 1517 } 1518 1519 expected := fmt.Sprintf( 1520 "invalid or not found kind in manifest path: %s", manifestPath, 1521 ) 1522 assertEqual(t, err.Error(), expected) 1523 } 1524 1525 func TestCreatePlacementDefault(t *testing.T) { 1526 t.Parallel() 1527 1528 p := Plugin{} 1529 p.allPlcs = map[string]bool{} 1530 p.csToPlc = map[string]string{} 1531 p.PolicyDefaults.Namespace = "my-policies" 1532 policyConf := types.PolicyConfig{Name: "policy-app-config"} 1533 1534 name, err := p.createPolicyPlacement(policyConf.Placement, policyConf.Name) 1535 if err != nil { 1536 t.Fatal(err.Error()) 1537 } 1538 1539 assertEqual(t, name, "placement-policy-app-config") 1540 1541 output := p.outputBuffer.String() 1542 expected := ` 1543 --- 1544 apiVersion: cluster.open-cluster-management.io/v1beta1 1545 kind: Placement 1546 metadata: 1547 name: placement-policy-app-config 1548 namespace: my-policies 1549 spec: 1550 predicates: 1551 - requiredClusterSelector: 1552 labelSelector: 1553 matchExpressions: [] 1554 ` 1555 expected = strings.TrimPrefix(expected, "\n") 1556 assertEqual(t, output, expected) 1557 } 1558 1559 func TestCreatePlacementSinglePlr(t *testing.T) { 1560 t.Parallel() 1561 1562 p := Plugin{} 1563 p.allPlcs = map[string]bool{} 1564 p.csToPlc = map[string]string{} 1565 p.PolicyDefaults.Namespace = "my-policies" 1566 p.PolicyDefaults.Placement.Name = "my-placement-rule" 1567 policyConf := types.PolicyConfig{Name: "policy-app-config"} 1568 1569 name, err := p.createPolicyPlacement(policyConf.Placement, policyConf.Name) 1570 if err != nil { 1571 t.Fatal(err.Error()) 1572 } 1573 1574 name2, err := p.createPolicyPlacement(policyConf.Placement, policyConf.Name) 1575 if err != nil { 1576 t.Fatal(err.Error()) 1577 } 1578 1579 // Verify that another placement rule is not created when the same cluster selectors are used 1580 assertEqual(t, name, "my-placement-rule") 1581 assertEqual(t, name2, "my-placement-rule") 1582 1583 output := p.outputBuffer.String() 1584 expected := ` 1585 --- 1586 apiVersion: cluster.open-cluster-management.io/v1beta1 1587 kind: Placement 1588 metadata: 1589 name: my-placement-rule 1590 namespace: my-policies 1591 spec: 1592 predicates: 1593 - requiredClusterSelector: 1594 labelSelector: 1595 matchExpressions: [] 1596 ` 1597 expected = strings.TrimPrefix(expected, "\n") 1598 assertEqual(t, output, expected) 1599 } 1600 1601 func TestCreatePlacementClusterSelectors(t *testing.T) { 1602 t.Parallel() 1603 1604 p := Plugin{} 1605 p.usingPlR = true 1606 p.allPlcs = map[string]bool{} 1607 p.csToPlc = map[string]string{} 1608 p.PolicyDefaults.Namespace = "my-policies" 1609 policyConf := types.PolicyConfig{Name: "policy-app-config"} 1610 policyConf.Placement.ClusterSelectors = map[string]interface{}{ 1611 "cloud": "red hat", 1612 "doesIt": "", 1613 "game": "pacman", 1614 } 1615 1616 name, err := p.createPolicyPlacement(policyConf.Placement, policyConf.Name) 1617 if err != nil { 1618 t.Fatal(err.Error()) 1619 } 1620 1621 assertEqual(t, name, "placement-policy-app-config") 1622 1623 output := p.outputBuffer.String() 1624 expected := ` 1625 --- 1626 apiVersion: apps.open-cluster-management.io/v1 1627 kind: PlacementRule 1628 metadata: 1629 name: placement-policy-app-config 1630 namespace: my-policies 1631 spec: 1632 clusterSelector: 1633 matchExpressions: 1634 - key: cloud 1635 operator: In 1636 values: 1637 - red hat 1638 - key: doesIt 1639 operator: Exists 1640 - key: game 1641 operator: In 1642 values: 1643 - pacman 1644 ` 1645 expected = strings.TrimPrefix(expected, "\n") 1646 assertEqual(t, output, expected) 1647 } 1648 1649 func TestCreatePlacementLabelSelector(t *testing.T) { 1650 t.Parallel() 1651 1652 p := Plugin{} 1653 p.allPlcs = map[string]bool{} 1654 p.csToPlc = map[string]string{} 1655 p.PolicyDefaults.Namespace = "my-policies" 1656 policyConf := types.PolicyConfig{Name: "policy-app-config"} 1657 policyConf.Placement.LabelSelector = map[string]interface{}{ 1658 "cloud": "red hat", 1659 "doesIt": "", 1660 "game": "pacman", 1661 } 1662 1663 name, err := p.createPolicyPlacement(policyConf.Placement, policyConf.Name) 1664 if err != nil { 1665 t.Fatal(err.Error()) 1666 } 1667 1668 assertEqual(t, name, "placement-policy-app-config") 1669 1670 output := p.outputBuffer.String() 1671 expected := ` 1672 --- 1673 apiVersion: cluster.open-cluster-management.io/v1beta1 1674 kind: Placement 1675 metadata: 1676 name: placement-policy-app-config 1677 namespace: my-policies 1678 spec: 1679 predicates: 1680 - requiredClusterSelector: 1681 labelSelector: 1682 matchExpressions: 1683 - key: cloud 1684 operator: In 1685 values: 1686 - red hat 1687 - key: doesIt 1688 operator: Exists 1689 - key: game 1690 operator: In 1691 values: 1692 - pacman 1693 ` 1694 expected = strings.TrimPrefix(expected, "\n") 1695 assertEqual(t, output, expected) 1696 } 1697 1698 func TestCreatePlacementDuplicateName(t *testing.T) { 1699 t.Parallel() 1700 1701 p := Plugin{} 1702 p.allPlcs = map[string]bool{} 1703 p.csToPlc = map[string]string{} 1704 p.PolicyDefaults.Namespace = "my-policies" 1705 policyConf := types.PolicyConfig{ 1706 Name: "policy-app-config", 1707 PolicyOptions: types.PolicyOptions{ 1708 Placement: types.PlacementConfig{ 1709 Name: "my-placement", 1710 }, 1711 }, 1712 } 1713 policyConf2 := types.PolicyConfig{ 1714 Name: "policy-app-config2", 1715 PolicyOptions: types.PolicyOptions{ 1716 Placement: types.PlacementConfig{ 1717 ClusterSelectors: map[string]interface{}{"my": "app"}, 1718 Name: "my-placement", 1719 }, 1720 }, 1721 } 1722 1723 _, err := p.createPolicyPlacement(policyConf.Placement, policyConf.Name) 1724 if err != nil { 1725 t.Fatal(err.Error()) 1726 } 1727 1728 _, err = p.createPolicyPlacement(policyConf2.Placement, policyConf2.Name) 1729 if err == nil { 1730 t.Fatal("Expected an error but did not get one") 1731 } 1732 1733 assertEqual(t, err.Error(), "a duplicate placement name was detected: my-placement") 1734 } 1735 1736 func plPathHelper(t *testing.T, plrYAML string, usingPlR bool) (*Plugin, string) { 1737 t.Helper() 1738 tmpDir := t.TempDir() 1739 plrPath := path.Join(tmpDir, "pl.yaml") 1740 plrYAML = strings.TrimPrefix(plrYAML, "\n") 1741 1742 err := os.WriteFile(plrPath, []byte(plrYAML), 0o666) 1743 if err != nil { 1744 t.Fatal(err.Error()) 1745 } 1746 1747 p := Plugin{} 1748 p.usingPlR = usingPlR 1749 p.allPlcs = map[string]bool{} 1750 p.processedPlcs = map[string]bool{} 1751 p.PolicyDefaults.Namespace = "my-policies" 1752 policyConf := types.PolicyConfig{Name: "policy-app-config"} 1753 1754 if usingPlR { 1755 policyConf.Placement.PlacementRulePath = plrPath 1756 } else { 1757 policyConf.Placement.PlacementPath = plrPath 1758 } 1759 1760 p.Policies = append(p.Policies, policyConf) 1761 1762 return &p, plrPath 1763 } 1764 1765 func TestCreatePlacementPlrPath(t *testing.T) { 1766 t.Parallel() 1767 1768 plrYAML := ` 1769 --- 1770 apiVersion: apps.open-cluster-management.io/v1 1771 kind: PlacementRule 1772 metadata: 1773 name: my-plr 1774 namespace: my-policies 1775 spec: 1776 clusterSelector: 1777 matchExpressions: 1778 - key: game 1779 operator: In 1780 values: 1781 - pacman 1782 ` 1783 plrYAML = strings.TrimPrefix(plrYAML, "\n") 1784 p, _ := plPathHelper(t, plrYAML, true) 1785 1786 name, err := p.createPolicyPlacement(p.Policies[0].Placement, p.Policies[0].Name) 1787 if err != nil { 1788 t.Fatal(err.Error()) 1789 } 1790 1791 assertEqual(t, name, "my-plr") 1792 1793 output := p.outputBuffer.String() 1794 1795 assertEqual(t, output, plrYAML) 1796 } 1797 1798 func TestCreatePlacementPlrPathSkip(t *testing.T) { 1799 t.Parallel() 1800 1801 plrYAML := ` 1802 --- 1803 apiVersion: apps.open-cluster-management.io/v1 1804 kind: PlacementRule 1805 metadata: 1806 name: my-plr 1807 namespace: my-policies 1808 ` 1809 plrYAML = strings.TrimPrefix(plrYAML, "\n") 1810 p, _ := plPathHelper(t, plrYAML, true) 1811 1812 p.processedPlcs = map[string]bool{"my-plr": true} 1813 1814 name, err := p.createPolicyPlacement(p.Policies[0].Placement, p.Policies[0].Name) 1815 if err != nil { 1816 t.Fatal(err.Error()) 1817 } 1818 1819 assertEqual(t, name, "my-plr") 1820 assertEqual(t, p.outputBuffer.String(), "") 1821 } 1822 1823 func TestCreatePlacementPlrPathNoName(t *testing.T) { 1824 t.Parallel() 1825 1826 plrYAML := ` 1827 --- 1828 apiVersion: apps.open-cluster-management.io/v1 1829 kind: PlacementRule 1830 metadata: 1831 namespace: my-policies 1832 spec: 1833 clusterSelector: 1834 matchExpressions: [] 1835 ` 1836 p, plrPath := plPathHelper(t, plrYAML, true) 1837 1838 _, err := p.createPolicyPlacement(p.Policies[0].Placement, p.Policies[0].Name) 1839 if err == nil { 1840 t.Fatal("Expected an error but did not get one") 1841 } 1842 1843 expected := fmt.Sprintf("the placement %s must have a name set", plrPath) 1844 assertEqual(t, err.Error(), expected) 1845 } 1846 1847 func TestCreatePlacementPlrPathNoNamespace(t *testing.T) { 1848 t.Parallel() 1849 1850 plrYAML := ` 1851 --- 1852 apiVersion: apps.open-cluster-management.io/v1 1853 kind: PlacementRule 1854 metadata: 1855 name: my-plr 1856 spec: 1857 clusterSelector: 1858 matchExpressions: [] 1859 ` 1860 p, plrPath := plPathHelper(t, plrYAML, true) 1861 1862 _, err := p.createPolicyPlacement(p.Policies[0].Placement, p.Policies[0].Name) 1863 if err == nil { 1864 t.Fatal("Expected an error but did not get one") 1865 } 1866 1867 expected := fmt.Sprintf("the placement %s must have a namespace set", plrPath) 1868 assertEqual(t, err.Error(), expected) 1869 } 1870 1871 func TestCreatePlacementPlrPathWrongNamespace(t *testing.T) { 1872 t.Parallel() 1873 1874 plrYAML := ` 1875 --- 1876 apiVersion: apps.open-cluster-management.io/v1 1877 kind: PlacementRule 1878 metadata: 1879 name: my-plr 1880 namespace: wrong-namespace 1881 spec: 1882 clusterSelector: 1883 matchExpressions: [] 1884 ` 1885 p, plrPath := plPathHelper(t, plrYAML, true) 1886 1887 _, err := p.createPolicyPlacement(p.Policies[0].Placement, p.Policies[0].Name) 1888 if err == nil { 1889 t.Fatal("Expected an error but did not get one") 1890 } 1891 1892 expected := fmt.Sprintf( 1893 "the placement %s must have the same namespace as the policy (%s)", 1894 plrPath, 1895 p.PolicyDefaults.Namespace, 1896 ) 1897 assertEqual(t, err.Error(), expected) 1898 } 1899 1900 func TestCreatePlacementPlrPathNoPlr(t *testing.T) { 1901 t.Parallel() 1902 1903 plrYAML := ` 1904 apiVersion: v1 1905 kind: ConfigMap 1906 metadata: 1907 name: my-configmap2 1908 namespace: my-policies 1909 data: 1910 game.properties: | 1911 enemies=potato 1912 ` 1913 p, plrPath := plPathHelper(t, plrYAML, true) 1914 1915 _, err := p.createPolicyPlacement(p.Policies[0].Placement, p.Policies[0].Name) 1916 if err == nil { 1917 t.Fatal("Expected an error but did not get one") 1918 } 1919 1920 expected := fmt.Sprintf("the placement manifest %s did not have a placement", plrPath) 1921 assertEqual(t, err.Error(), expected) 1922 } 1923 1924 func TestCreatePlacementPlPath(t *testing.T) { 1925 t.Parallel() 1926 1927 plrYAML := ` 1928 --- 1929 apiVersion: cluster.open-cluster-management.io/v1beta1 1930 kind: Placement 1931 metadata: 1932 name: my-plr 1933 namespace: my-policies 1934 spec: 1935 predicates: 1936 - requiredClusterSelector: 1937 labelSelector: 1938 matchExpressions: 1939 - key: game 1940 operator: In 1941 values: 1942 - pacman 1943 ` 1944 plrYAML = strings.TrimPrefix(plrYAML, "\n") 1945 p, _ := plPathHelper(t, plrYAML, false) 1946 1947 name, err := p.createPolicyPlacement(p.Policies[0].Placement, p.Policies[0].Name) 1948 if err != nil { 1949 t.Fatal(err.Error()) 1950 } 1951 1952 assertEqual(t, name, "my-plr") 1953 1954 output := p.outputBuffer.String() 1955 1956 assertEqual(t, output, plrYAML) 1957 } 1958 1959 func TestCreatePlacementPlPathSkip(t *testing.T) { 1960 t.Parallel() 1961 1962 plrYAML := ` 1963 --- 1964 apiVersion: cluster.open-cluster-management.io/v1beta1 1965 kind: Placement 1966 metadata: 1967 name: my-plr 1968 namespace: my-policies 1969 ` 1970 plrYAML = strings.TrimPrefix(plrYAML, "\n") 1971 p, _ := plPathHelper(t, plrYAML, false) 1972 1973 p.processedPlcs = map[string]bool{"my-plr": true} 1974 1975 name, err := p.createPolicyPlacement(p.Policies[0].Placement, p.Policies[0].Name) 1976 if err != nil { 1977 t.Fatal(err.Error()) 1978 } 1979 1980 assertEqual(t, name, "my-plr") 1981 assertEqual(t, p.outputBuffer.String(), "") 1982 } 1983 1984 func TestCreatePlacementPlPathNoName(t *testing.T) { 1985 t.Parallel() 1986 1987 plrYAML := ` 1988 --- 1989 apiVersion: cluster.open-cluster-management.io/v1beta1 1990 kind: Placement 1991 metadata: 1992 namespace: my-policies 1993 spec: 1994 predicates: 1995 - requiredClusterSelector: 1996 labelSelector: 1997 matchExpressions: [] 1998 ` 1999 p, plrPath := plPathHelper(t, plrYAML, false) 2000 2001 _, err := p.createPolicyPlacement(p.Policies[0].Placement, p.Policies[0].Name) 2002 if err == nil { 2003 t.Fatal("Expected an error but did not get one") 2004 } 2005 2006 expected := fmt.Sprintf("the placement %s must have a name set", plrPath) 2007 assertEqual(t, err.Error(), expected) 2008 } 2009 2010 func TestCreatePlacementPlPathNoNamespace(t *testing.T) { 2011 t.Parallel() 2012 2013 plrYAML := ` 2014 --- 2015 apiVersion: cluster.open-cluster-management.io/v1beta1 2016 kind: Placement 2017 metadata: 2018 name: my-plr 2019 spec: 2020 predicates: 2021 - requiredClusterSelector: 2022 labelSelector: 2023 matchExpressions: [] 2024 ` 2025 p, plrPath := plPathHelper(t, plrYAML, false) 2026 2027 _, err := p.createPolicyPlacement(p.Policies[0].Placement, p.Policies[0].Name) 2028 if err == nil { 2029 t.Fatal("Expected an error but did not get one") 2030 } 2031 2032 expected := fmt.Sprintf("the placement %s must have a namespace set", plrPath) 2033 assertEqual(t, err.Error(), expected) 2034 } 2035 2036 func TestCreatePlacementPlPathWrongNamespace(t *testing.T) { 2037 t.Parallel() 2038 2039 plrYAML := ` 2040 --- 2041 apiVersion: cluster.open-cluster-management.io/v1beta1 2042 kind: Placement 2043 metadata: 2044 name: my-plr 2045 namespace: wrong-namespace 2046 spec: 2047 predicates: 2048 - requiredClusterSelector: 2049 labelSelector: 2050 matchExpressions: [] 2051 ` 2052 p, plrPath := plPathHelper(t, plrYAML, false) 2053 2054 _, err := p.createPolicyPlacement(p.Policies[0].Placement, p.Policies[0].Name) 2055 if err == nil { 2056 t.Fatal("Expected an error but did not get one") 2057 } 2058 2059 expected := fmt.Sprintf( 2060 "the placement %s must have the same namespace as the policy (%s)", 2061 plrPath, 2062 p.PolicyDefaults.Namespace, 2063 ) 2064 assertEqual(t, err.Error(), expected) 2065 } 2066 2067 func TestCreatePlacementPlPathFoundPlR(t *testing.T) { 2068 t.Parallel() 2069 2070 plrYAML := ` 2071 --- 2072 apiVersion: apps.open-cluster-management.io/v1 2073 kind: PlacementRule 2074 metadata: 2075 name: my-plr 2076 namespace: my-policies 2077 ` 2078 p, plrPath := plPathHelper(t, plrYAML, false) 2079 2080 _, err := p.createPolicyPlacement(p.Policies[0].Placement, p.Policies[0].Name) 2081 if err == nil { 2082 t.Fatal("Expected an error but did not get one") 2083 } 2084 2085 expected := fmt.Sprintf( 2086 "the placement %s specified a placementRule kind but expected a placement kind", 2087 plrPath, 2088 ) 2089 assertEqual(t, err.Error(), expected) 2090 } 2091 2092 func TestCreatePlacementPlrPathFoundPl(t *testing.T) { 2093 t.Parallel() 2094 2095 plrYAML := ` 2096 --- 2097 apiVersion: cluster.open-cluster-management.io/v1beta1 2098 kind: Placement 2099 metadata: 2100 name: my-plr 2101 namespace: my-policies 2102 ` 2103 p, plrPath := plPathHelper(t, plrYAML, true) 2104 2105 _, err := p.createPolicyPlacement(p.Policies[0].Placement, p.Policies[0].Name) 2106 if err == nil { 2107 t.Fatal("Expected an error but did not get one") 2108 } 2109 2110 expected := fmt.Sprintf( 2111 "the placement %s specified a placement kind but expected a placementRule kind", 2112 plrPath, 2113 ) 2114 assertEqual(t, err.Error(), expected) 2115 } 2116 2117 func TestCreatePlacementPlPathNoPl(t *testing.T) { 2118 t.Parallel() 2119 2120 plrYAML := ` 2121 apiVersion: v1 2122 kind: ConfigMap 2123 metadata: 2124 name: my-configmap2 2125 namespace: my-policies 2126 data: 2127 game.properties: | 2128 enemies=potato 2129 ` 2130 p, plrPath := plPathHelper(t, plrYAML, false) 2131 2132 _, err := p.createPolicyPlacement(p.Policies[0].Placement, p.Policies[0].Name) 2133 if err == nil { 2134 t.Fatal("Expected an error but did not get one") 2135 } 2136 2137 expected := fmt.Sprintf("the placement manifest %s did not have a placement", plrPath) 2138 assertEqual(t, err.Error(), expected) 2139 } 2140 2141 func TestCreatePlacementBinding(t *testing.T) { 2142 t.Parallel() 2143 2144 p := Plugin{} 2145 p.PolicyDefaults.Namespace = "my-policies" 2146 policyConf := types.PolicyConfig{Name: "policy-app-config"} 2147 p.Policies = append(p.Policies, policyConf) 2148 policyConf2 := types.PolicyConfig{Name: "policy-app-config2"} 2149 p.Policies = append(p.Policies, policyConf2) 2150 2151 bindingName := "my-placement-binding" 2152 plrName := "my-placement-rule" 2153 policyConfs := []*types.PolicyConfig{} 2154 policyConfs = append(policyConfs, &p.Policies[0], &p.Policies[1]) 2155 2156 policySetConfs := []*types.PolicySetConfig{ 2157 { 2158 Name: "my-policyset", 2159 }, 2160 } 2161 2162 err := p.createPlacementBinding(bindingName, plrName, policyConfs, policySetConfs) 2163 if err != nil { 2164 t.Fatal(err) 2165 } 2166 2167 expected := ` 2168 --- 2169 apiVersion: policy.open-cluster-management.io/v1 2170 kind: PlacementBinding 2171 metadata: 2172 name: my-placement-binding 2173 namespace: my-policies 2174 placementRef: 2175 apiGroup: cluster.open-cluster-management.io 2176 kind: Placement 2177 name: my-placement-rule 2178 subjects: 2179 - apiGroup: policy.open-cluster-management.io 2180 kind: Policy 2181 name: policy-app-config 2182 - apiGroup: policy.open-cluster-management.io 2183 kind: Policy 2184 name: policy-app-config2 2185 - apiGroup: policy.open-cluster-management.io 2186 kind: PolicySet 2187 name: my-policyset 2188 ` 2189 expected = strings.TrimPrefix(expected, "\n") 2190 assertEqual(t, p.outputBuffer.String(), expected) 2191 } 2192 2193 func TestGeneratePolicySets(t *testing.T) { 2194 t.Parallel() 2195 tmpDir := t.TempDir() 2196 createConfigMap(t, tmpDir, "configmap.yaml") 2197 2198 testCases := []testCase{ 2199 { 2200 name: "Use p.PolicyDefaults.PolicySets only", 2201 setupFunc: func(p *Plugin) { 2202 // PolicyDefaults.PolicySets should be applied to both policies 2203 p.PolicyDefaults.PolicySets = []string{"policyset-default"} 2204 }, 2205 expectedPolicySetConfigInPolicy: [][]string{ 2206 {"policyset-default"}, 2207 {"policyset-default"}, 2208 }, 2209 expectedPolicySetConfigs: []types.PolicySetConfig{ 2210 { 2211 Name: "policyset-default", 2212 Policies: []string{ 2213 "policy-app-config", 2214 "policy-app-config2", 2215 }, 2216 PolicySetOptions: types.PolicySetOptions{ 2217 GeneratePolicySetPlacement: true, 2218 }, 2219 }, 2220 }, 2221 }, 2222 { 2223 name: "Use p.Policies[0].PolicySets to override with a different policy set", 2224 setupFunc: func(p *Plugin) { 2225 // p.PolicyDefaults.PolicySets should be overridden by p.Policies[0].PolicySets 2226 p.PolicyDefaults.PolicySets = []string{"policyset-default"} 2227 p.Policies[0] = types.PolicyConfig{ 2228 Name: "policy-app-config", 2229 Manifests: []types.Manifest{ 2230 { 2231 Path: path.Join(tmpDir, "configmap.yaml"), 2232 }, 2233 }, 2234 PolicyOptions: types.PolicyOptions{ 2235 PolicySets: []string{"policyset0"}, 2236 }, 2237 } 2238 }, 2239 expectedPolicySetConfigInPolicy: [][]string{ 2240 {"policyset0"}, 2241 {"policyset-default"}, 2242 }, 2243 expectedPolicySetConfigs: []types.PolicySetConfig{ 2244 { 2245 Name: "policyset0", 2246 Policies: []string{ 2247 "policy-app-config", 2248 }, 2249 PolicySetOptions: types.PolicySetOptions{ 2250 GeneratePolicySetPlacement: true, 2251 }, 2252 }, 2253 { 2254 Name: "policyset-default", 2255 Policies: []string{ 2256 "policy-app-config2", 2257 }, 2258 PolicySetOptions: types.PolicySetOptions{ 2259 GeneratePolicySetPlacement: true, 2260 }, 2261 }, 2262 }, 2263 }, 2264 { 2265 name: "Use p.Policies[0].PolicySets to override with an empty policyset", 2266 setupFunc: func(p *Plugin) { 2267 // p.PolicyDefaults.PolicySets should be overridden by p.Policies[0].PolicySets 2268 p.PolicyDefaults.PolicySets = []string{"policyset-default"} 2269 p.Policies[0] = types.PolicyConfig{ 2270 Name: "policy-app-config", 2271 Manifests: []types.Manifest{ 2272 { 2273 Path: path.Join(tmpDir, "configmap.yaml"), 2274 }, 2275 }, 2276 PolicyOptions: types.PolicyOptions{ 2277 PolicySets: []string{}, 2278 }, 2279 } 2280 }, 2281 expectedPolicySetConfigInPolicy: [][]string{ 2282 {}, 2283 {"policyset-default"}, 2284 }, 2285 expectedPolicySetConfigs: []types.PolicySetConfig{ 2286 { 2287 Name: "policyset-default", 2288 Policies: []string{ 2289 "policy-app-config2", 2290 }, 2291 PolicySetOptions: types.PolicySetOptions{ 2292 GeneratePolicySetPlacement: true, 2293 }, 2294 }, 2295 }, 2296 }, 2297 { 2298 name: "Use p.Policies[0].PolicySets and p.PolicySets, should merge", 2299 setupFunc: func(p *Plugin) { 2300 // p.Policies[0].PolicySets and p.PolicySets should merge 2301 p.PolicySets = []types.PolicySetConfig{ 2302 { 2303 Name: "policyset-default", 2304 Description: "This is a default policyset.", 2305 Policies: []string{ 2306 "policy-app-config", 2307 "policy-app-config2", 2308 "pre-exists-policy", 2309 }, 2310 }, 2311 } 2312 }, 2313 expectedPolicySetConfigInPolicy: [][]string{ 2314 {"policyset-default"}, 2315 {"policyset-default"}, 2316 }, 2317 expectedPolicySetConfigs: []types.PolicySetConfig{ 2318 { 2319 Name: "policyset-default", 2320 Description: "This is a default policyset.", 2321 Policies: []string{ 2322 "policy-app-config", 2323 "policy-app-config2", 2324 "pre-exists-policy", 2325 }, 2326 PolicySetOptions: types.PolicySetOptions{ 2327 GeneratePolicySetPlacement: true, 2328 }, 2329 }, 2330 }, 2331 }, 2332 } 2333 for _, tc := range testCases { 2334 tc := tc // capture range variable 2335 t.Run(tc.name, func(t *testing.T) { 2336 t.Parallel() 2337 p := Plugin{} 2338 var err error 2339 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 2340 if err != nil { 2341 t.Fatal(err.Error()) 2342 } 2343 p.PlacementBindingDefaults.Name = "my-placement-binding" 2344 p.PolicyDefaults.Placement.Name = "my-placement-rule" 2345 p.PolicyDefaults.Namespace = "my-policies" 2346 policyConf := types.PolicyConfig{ 2347 Name: "policy-app-config", 2348 Manifests: []types.Manifest{ 2349 { 2350 Path: path.Join(tmpDir, "configmap.yaml"), 2351 }, 2352 }, 2353 } 2354 policyConf2 := types.PolicyConfig{ 2355 Name: "policy-app-config2", 2356 Manifests: []types.Manifest{ 2357 {Path: path.Join(tmpDir, "configmap.yaml")}, 2358 }, 2359 } 2360 p.Policies = append(p.Policies, policyConf, policyConf2) 2361 tc.setupFunc(&p) 2362 p.applyDefaults(map[string]interface{}{}) 2363 assertReflectEqual(t, p.Policies[0].PolicySets, tc.expectedPolicySetConfigInPolicy[0]) 2364 assertReflectEqual(t, p.Policies[1].PolicySets, tc.expectedPolicySetConfigInPolicy[1]) 2365 assertReflectEqual(t, p.PolicySets, tc.expectedPolicySetConfigs) 2366 }) 2367 } 2368 } 2369 2370 func TestGeneratePolicySetsWithPlacement(t *testing.T) { 2371 t.Parallel() 2372 tmpDir := t.TempDir() 2373 createConfigMap(t, tmpDir, "configmap.yaml") 2374 2375 p := Plugin{} 2376 var err error 2377 2378 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 2379 if err != nil { 2380 t.Fatal(err.Error()) 2381 } 2382 2383 p.PlacementBindingDefaults.Name = "my-placement-binding" 2384 p.PolicyDefaults.Placement.Name = "my-placement-rule" 2385 p.PolicyDefaults.Namespace = "my-policies" 2386 2387 policyConf := types.PolicyConfig{ 2388 Name: "policy-app-config", 2389 Manifests: []types.Manifest{ 2390 { 2391 Path: path.Join(tmpDir, "configmap.yaml"), 2392 }, 2393 }, 2394 PolicyOptions: types.PolicyOptions{ 2395 PolicySets: []string{"policyset"}, 2396 }, 2397 } 2398 p.Policies = append(p.Policies, policyConf) 2399 2400 p.applyDefaults(map[string]interface{}{}) 2401 2402 if err := p.assertValidConfig(); err != nil { 2403 t.Fatal(err.Error()) 2404 } 2405 2406 expected := ` 2407 --- 2408 apiVersion: policy.open-cluster-management.io/v1 2409 kind: Policy 2410 metadata: 2411 annotations: 2412 policy.open-cluster-management.io/categories: CM Configuration Management 2413 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 2414 policy.open-cluster-management.io/standards: NIST SP 800-53 2415 name: policy-app-config 2416 namespace: my-policies 2417 spec: 2418 disabled: false 2419 policy-templates: 2420 - objectDefinition: 2421 apiVersion: policy.open-cluster-management.io/v1 2422 kind: ConfigurationPolicy 2423 metadata: 2424 name: policy-app-config 2425 spec: 2426 object-templates: 2427 - complianceType: musthave 2428 objectDefinition: 2429 apiVersion: v1 2430 data: 2431 game.properties: enemies=potato 2432 kind: ConfigMap 2433 metadata: 2434 name: my-configmap 2435 remediationAction: inform 2436 severity: low 2437 remediationAction: inform 2438 --- 2439 apiVersion: policy.open-cluster-management.io/v1beta1 2440 kind: PolicySet 2441 metadata: 2442 name: policyset 2443 namespace: my-policies 2444 spec: 2445 description: "" 2446 policies: 2447 - policy-app-config 2448 --- 2449 apiVersion: apps.open-cluster-management.io/v1 2450 kind: PlacementRule 2451 metadata: 2452 name: my-placement-rule 2453 namespace: my-policies 2454 spec: 2455 clusterSelector: 2456 matchExpressions: [] 2457 --- 2458 apiVersion: policy.open-cluster-management.io/v1 2459 kind: PlacementBinding 2460 metadata: 2461 name: my-placement-binding 2462 namespace: my-policies 2463 placementRef: 2464 apiGroup: apps.open-cluster-management.io 2465 kind: PlacementRule 2466 name: my-placement-rule 2467 subjects: 2468 - apiGroup: policy.open-cluster-management.io 2469 kind: PolicySet 2470 name: policyset 2471 ` 2472 expected = strings.TrimPrefix(expected, "\n") 2473 2474 output, err := p.Generate() 2475 if err != nil { 2476 t.Fatal(err.Error()) 2477 } 2478 2479 assertEqual(t, string(output), expected) 2480 } 2481 2482 func TestGeneratePolicySetsOverridePlacement(t *testing.T) { 2483 t.Parallel() 2484 tmpDir := t.TempDir() 2485 createConfigMap(t, tmpDir, "configmap.yaml") 2486 2487 p := Plugin{} 2488 var err error 2489 2490 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 2491 if err != nil { 2492 t.Fatal(err.Error()) 2493 } 2494 2495 p.PlacementBindingDefaults.Name = "my-placement-binding" 2496 p.PolicyDefaults.Placement.Name = "my-placement" 2497 p.PolicyDefaults.Namespace = "my-policies" 2498 p.PolicySetDefaults.Placement.Name = "other-placement" 2499 2500 policyConf := types.PolicyConfig{ 2501 Name: "policy-app-config", 2502 Manifests: []types.Manifest{ 2503 { 2504 Path: path.Join(tmpDir, "configmap.yaml"), 2505 }, 2506 }, 2507 PolicyOptions: types.PolicyOptions{ 2508 PolicySets: []string{"policyset-overrides"}, 2509 }, 2510 } 2511 p.Policies = append(p.Policies, policyConf) 2512 2513 policySetConf := types.PolicySetConfig{ 2514 Name: "policyset-overrides", 2515 PolicySetOptions: types.PolicySetOptions{ 2516 Placement: types.PlacementConfig{ 2517 LabelSelector: map[string]interface{}{ 2518 "my-label": "my-cluster", 2519 }, 2520 }, 2521 }, 2522 } 2523 p.PolicySets = append(p.PolicySets, policySetConf) 2524 2525 p.applyDefaults(map[string]interface{}{}) 2526 2527 if err := p.assertValidConfig(); err != nil { 2528 t.Fatal(err.Error()) 2529 } 2530 2531 expected := ` 2532 --- 2533 apiVersion: policy.open-cluster-management.io/v1 2534 kind: Policy 2535 metadata: 2536 annotations: 2537 policy.open-cluster-management.io/categories: CM Configuration Management 2538 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 2539 policy.open-cluster-management.io/standards: NIST SP 800-53 2540 name: policy-app-config 2541 namespace: my-policies 2542 spec: 2543 disabled: false 2544 policy-templates: 2545 - objectDefinition: 2546 apiVersion: policy.open-cluster-management.io/v1 2547 kind: ConfigurationPolicy 2548 metadata: 2549 name: policy-app-config 2550 spec: 2551 object-templates: 2552 - complianceType: musthave 2553 objectDefinition: 2554 apiVersion: v1 2555 data: 2556 game.properties: enemies=potato 2557 kind: ConfigMap 2558 metadata: 2559 name: my-configmap 2560 remediationAction: inform 2561 severity: low 2562 remediationAction: inform 2563 --- 2564 apiVersion: policy.open-cluster-management.io/v1beta1 2565 kind: PolicySet 2566 metadata: 2567 name: policyset-overrides 2568 namespace: my-policies 2569 spec: 2570 description: "" 2571 policies: 2572 - policy-app-config 2573 --- 2574 apiVersion: cluster.open-cluster-management.io/v1beta1 2575 kind: Placement 2576 metadata: 2577 name: other-placement 2578 namespace: my-policies 2579 spec: 2580 predicates: 2581 - requiredClusterSelector: 2582 labelSelector: 2583 matchExpressions: 2584 - key: my-label 2585 operator: In 2586 values: 2587 - my-cluster 2588 --- 2589 apiVersion: policy.open-cluster-management.io/v1 2590 kind: PlacementBinding 2591 metadata: 2592 name: my-placement-binding 2593 namespace: my-policies 2594 placementRef: 2595 apiGroup: cluster.open-cluster-management.io 2596 kind: Placement 2597 name: other-placement 2598 subjects: 2599 - apiGroup: policy.open-cluster-management.io 2600 kind: PolicySet 2601 name: policyset-overrides 2602 ` 2603 expected = strings.TrimPrefix(expected, "\n") 2604 2605 output, err := p.Generate() 2606 if err != nil { 2607 t.Fatal(err.Error()) 2608 } 2609 2610 assertEqual(t, string(output), expected) 2611 } 2612 2613 func TestGeneratePolicySetsWithoutPlacement(t *testing.T) { 2614 t.Parallel() 2615 tmpDir := t.TempDir() 2616 createConfigMap(t, tmpDir, "configmap.yaml") 2617 2618 p := Plugin{} 2619 var err error 2620 2621 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 2622 if err != nil { 2623 t.Fatal(err.Error()) 2624 } 2625 2626 p.PlacementBindingDefaults.Name = "my-placement-binding" 2627 p.PolicyDefaults.Placement.Name = "my-placement-rule" 2628 p.PolicyDefaults.Namespace = "my-policies" 2629 2630 policyConf := types.PolicyConfig{ 2631 Name: "policy-app-config", 2632 Manifests: []types.Manifest{ 2633 { 2634 Path: path.Join(tmpDir, "configmap.yaml"), 2635 }, 2636 }, 2637 PolicyOptions: types.PolicyOptions{ 2638 PolicySets: []string{"policyset"}, 2639 }, 2640 } 2641 p.Policies = append(p.Policies, policyConf) 2642 2643 p.applyDefaults(map[string]interface{}{ 2644 "policySetDefaults": map[string]interface{}{ 2645 "generatePolicySetPlacement": false, 2646 }, 2647 }) 2648 2649 if err := p.assertValidConfig(); err != nil { 2650 t.Fatal(err.Error()) 2651 } 2652 2653 expected := ` 2654 --- 2655 apiVersion: policy.open-cluster-management.io/v1 2656 kind: Policy 2657 metadata: 2658 annotations: 2659 policy.open-cluster-management.io/categories: CM Configuration Management 2660 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 2661 policy.open-cluster-management.io/standards: NIST SP 800-53 2662 name: policy-app-config 2663 namespace: my-policies 2664 spec: 2665 disabled: false 2666 policy-templates: 2667 - objectDefinition: 2668 apiVersion: policy.open-cluster-management.io/v1 2669 kind: ConfigurationPolicy 2670 metadata: 2671 name: policy-app-config 2672 spec: 2673 object-templates: 2674 - complianceType: musthave 2675 objectDefinition: 2676 apiVersion: v1 2677 data: 2678 game.properties: enemies=potato 2679 kind: ConfigMap 2680 metadata: 2681 name: my-configmap 2682 remediationAction: inform 2683 severity: low 2684 remediationAction: inform 2685 --- 2686 apiVersion: policy.open-cluster-management.io/v1beta1 2687 kind: PolicySet 2688 metadata: 2689 name: policyset 2690 namespace: my-policies 2691 spec: 2692 description: "" 2693 policies: 2694 - policy-app-config 2695 ` 2696 expected = strings.TrimPrefix(expected, "\n") 2697 2698 output, err := p.Generate() 2699 if err != nil { 2700 t.Fatal(err.Error()) 2701 } 2702 2703 assertEqual(t, string(output), expected) 2704 } 2705 2706 func TestGeneratePolicySetsWithPolicyPlacement(t *testing.T) { 2707 t.Parallel() 2708 tmpDir := t.TempDir() 2709 createConfigMap(t, tmpDir, "configmap.yaml") 2710 2711 p := Plugin{} 2712 var err error 2713 2714 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 2715 if err != nil { 2716 t.Fatal(err.Error()) 2717 } 2718 2719 p.PlacementBindingDefaults.Name = "my-placement-binding" 2720 p.PolicyDefaults.Placement.Name = "my-placement" 2721 p.PolicyDefaults.Namespace = "my-policies" 2722 2723 policyConf := types.PolicyConfig{ 2724 Name: "policy-app-config", 2725 Manifests: []types.Manifest{ 2726 { 2727 Path: path.Join(tmpDir, "configmap.yaml"), 2728 }, 2729 }, 2730 PolicyOptions: types.PolicyOptions{ 2731 PolicySets: []string{"my-policyset"}, 2732 }, 2733 } 2734 p.Policies = append(p.Policies, policyConf) 2735 p.PolicySets = []types.PolicySetConfig{ 2736 { 2737 Name: "my-policyset", 2738 PolicySetOptions: types.PolicySetOptions{ 2739 Placement: types.PlacementConfig{ 2740 Name: "policyset-placement", 2741 ClusterSelectors: map[string]interface{}{"my": "app"}, 2742 }, 2743 }, 2744 }, 2745 } 2746 2747 p.applyDefaults(map[string]interface{}{}) 2748 2749 if err := p.assertValidConfig(); err != nil { 2750 t.Fatal(err.Error()) 2751 } 2752 2753 p.Policies[0].GeneratePlacementWhenInSet = true 2754 2755 expected := ` 2756 --- 2757 apiVersion: policy.open-cluster-management.io/v1 2758 kind: Policy 2759 metadata: 2760 annotations: 2761 policy.open-cluster-management.io/categories: CM Configuration Management 2762 policy.open-cluster-management.io/controls: CM-2 Baseline Configuration 2763 policy.open-cluster-management.io/standards: NIST SP 800-53 2764 name: policy-app-config 2765 namespace: my-policies 2766 spec: 2767 disabled: false 2768 policy-templates: 2769 - objectDefinition: 2770 apiVersion: policy.open-cluster-management.io/v1 2771 kind: ConfigurationPolicy 2772 metadata: 2773 name: policy-app-config 2774 spec: 2775 object-templates: 2776 - complianceType: musthave 2777 objectDefinition: 2778 apiVersion: v1 2779 data: 2780 game.properties: enemies=potato 2781 kind: ConfigMap 2782 metadata: 2783 name: my-configmap 2784 remediationAction: inform 2785 severity: low 2786 remediationAction: inform 2787 --- 2788 apiVersion: policy.open-cluster-management.io/v1beta1 2789 kind: PolicySet 2790 metadata: 2791 name: my-policyset 2792 namespace: my-policies 2793 spec: 2794 description: "" 2795 policies: 2796 - policy-app-config 2797 --- 2798 apiVersion: apps.open-cluster-management.io/v1 2799 kind: PlacementRule 2800 metadata: 2801 name: my-placement 2802 namespace: my-policies 2803 spec: 2804 clusterSelector: 2805 matchExpressions: [] 2806 --- 2807 apiVersion: apps.open-cluster-management.io/v1 2808 kind: PlacementRule 2809 metadata: 2810 name: policyset-placement 2811 namespace: my-policies 2812 spec: 2813 clusterSelector: 2814 matchExpressions: 2815 - key: my 2816 operator: In 2817 values: 2818 - app 2819 --- 2820 apiVersion: policy.open-cluster-management.io/v1 2821 kind: PlacementBinding 2822 metadata: 2823 name: binding-policy-app-config 2824 namespace: my-policies 2825 placementRef: 2826 apiGroup: apps.open-cluster-management.io 2827 kind: PlacementRule 2828 name: my-placement 2829 subjects: 2830 - apiGroup: policy.open-cluster-management.io 2831 kind: Policy 2832 name: policy-app-config 2833 --- 2834 apiVersion: policy.open-cluster-management.io/v1 2835 kind: PlacementBinding 2836 metadata: 2837 name: my-placement-binding 2838 namespace: my-policies 2839 placementRef: 2840 apiGroup: apps.open-cluster-management.io 2841 kind: PlacementRule 2842 name: policyset-placement 2843 subjects: 2844 - apiGroup: policy.open-cluster-management.io 2845 kind: PolicySet 2846 name: my-policyset 2847 ` 2848 expected = strings.TrimPrefix(expected, "\n") 2849 2850 output, err := p.Generate() 2851 if err != nil { 2852 t.Fatal(err.Error()) 2853 } 2854 2855 assertEqual(t, string(output), expected) 2856 } 2857 2858 func TestCreatePolicySet(t *testing.T) { 2859 t.Parallel() 2860 tmpDir := t.TempDir() 2861 createConfigMap(t, tmpDir, "configmap.yaml") 2862 2863 p := Plugin{} 2864 var err error 2865 2866 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 2867 if err != nil { 2868 t.Fatal(err.Error()) 2869 } 2870 2871 p.PlacementBindingDefaults.Name = "my-placement-binding" 2872 p.PolicyDefaults.Placement.Name = "my-placement-rule" 2873 p.PolicyDefaults.Namespace = "my-policies" 2874 patch := map[string]interface{}{ 2875 "metadata": map[string]interface{}{ 2876 "labels": map[string]string{ 2877 "chandler": "bing", 2878 }, 2879 }, 2880 } 2881 policyConf := types.PolicyConfig{ 2882 Name: "policy-app-config", 2883 Manifests: []types.Manifest{ 2884 { 2885 Path: path.Join(tmpDir, "configmap.yaml"), 2886 Patches: []map[string]interface{}{patch}, 2887 }, 2888 }, 2889 } 2890 policyConf2 := types.PolicyConfig{ 2891 Name: "policy-app-config2", 2892 Manifests: []types.Manifest{ 2893 {Path: path.Join(tmpDir, "configmap.yaml")}, 2894 }, 2895 } 2896 p.Policies = append(p.Policies, policyConf, policyConf2) 2897 2898 p.PolicyDefaults.PolicySets = []string{"policyset-default"} 2899 p.PolicySets = []types.PolicySetConfig{ 2900 { 2901 Name: "policyset-default", 2902 Description: "This is a default policyset.", 2903 Policies: []string{ 2904 "policy-app-config", 2905 "policy-app-config2", 2906 "pre-exists-policy", 2907 }, 2908 }, 2909 } 2910 p.applyDefaults(map[string]interface{}{}) 2911 2912 err = p.createPolicySet(&p.PolicySets[0]) 2913 if err != nil { 2914 t.Fatal(err.Error()) 2915 } 2916 2917 output := p.outputBuffer.String() 2918 expected := ` 2919 --- 2920 apiVersion: policy.open-cluster-management.io/v1beta1 2921 kind: PolicySet 2922 metadata: 2923 name: policyset-default 2924 namespace: my-policies 2925 spec: 2926 description: This is a default policyset. 2927 policies: 2928 - policy-app-config 2929 - policy-app-config2 2930 - pre-exists-policy 2931 ` 2932 expected = strings.TrimPrefix(expected, "\n") 2933 assertEqual(t, output, expected) 2934 } 2935 2936 func getYAMLEvaluationInterval( 2937 t *testing.T, policyTemplate interface{}, skipFinalValidation bool, 2938 ) map[string]interface{} { 2939 t.Helper() 2940 2941 plcTemplate, ok := policyTemplate.(map[string]interface{}) 2942 assertEqual(t, ok, true) 2943 2944 configPolicy, ok := plcTemplate["objectDefinition"].(map[string]interface{}) 2945 assertEqual(t, ok, true) 2946 2947 configPolicyOptions, ok := configPolicy["spec"].(map[string]interface{}) 2948 assertEqual(t, ok, true) 2949 2950 evaluationInterval, ok := configPolicyOptions["evaluationInterval"].(map[string]interface{}) 2951 2952 if !skipFinalValidation { 2953 assertEqual(t, ok, true) 2954 } 2955 2956 return evaluationInterval 2957 } 2958 2959 func TestGenerateEvaluationInterval(t *testing.T) { 2960 t.Parallel() 2961 tmpDir := t.TempDir() 2962 createConfigMap(t, tmpDir, "configmap.yaml") 2963 2964 p := Plugin{} 2965 var err error 2966 2967 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 2968 if err != nil { 2969 t.Fatal(err.Error()) 2970 } 2971 2972 p.PolicyDefaults.Namespace = "my-policies" 2973 p.PolicyDefaults.EvaluationInterval = types.EvaluationInterval{ 2974 Compliant: "never", 2975 NonCompliant: "15s", 2976 } 2977 2978 // Test that the policy evaluation interval gets inherited when not set on a manifest. 2979 policyConf := types.PolicyConfig{ 2980 PolicyOptions: types.PolicyOptions{ 2981 ConsolidateManifests: false, 2982 }, 2983 ConfigurationPolicyOptions: types.ConfigurationPolicyOptions{ 2984 EvaluationInterval: types.EvaluationInterval{ 2985 Compliant: "30m", 2986 NonCompliant: "30s", 2987 }, 2988 }, 2989 Name: "policy-app-config", 2990 Manifests: []types.Manifest{ 2991 {Path: path.Join(tmpDir, "configmap.yaml")}, 2992 { 2993 ConfigurationPolicyOptions: types.ConfigurationPolicyOptions{ 2994 EvaluationInterval: types.EvaluationInterval{ 2995 Compliant: "25m", 2996 NonCompliant: "5m", 2997 }, 2998 }, 2999 Path: path.Join(tmpDir, "configmap.yaml"), 3000 }, 3001 // Test that it does not get an inherited value when it is explicitly set to empty in the YAML below. 3002 { 3003 Path: path.Join(tmpDir, "configmap.yaml"), 3004 }, 3005 }, 3006 } 3007 // Test that the policy defaults get inherited. 3008 policyConf2 := types.PolicyConfig{ 3009 Name: "policy-app-config2", 3010 Manifests: []types.Manifest{ 3011 {Path: path.Join(tmpDir, "configmap.yaml")}, 3012 }, 3013 } 3014 // Test that explicitly setting evaluationInterval to an empty value overrides the policy default. 3015 policyConf3 := types.PolicyConfig{ 3016 ConfigurationPolicyOptions: types.ConfigurationPolicyOptions{ 3017 EvaluationInterval: types.EvaluationInterval{}, 3018 }, 3019 Name: "policy-app-config3", 3020 Manifests: []types.Manifest{ 3021 {Path: path.Join(tmpDir, "configmap.yaml")}, 3022 }, 3023 } 3024 p.Policies = append(p.Policies, policyConf, policyConf2, policyConf3) 3025 p.applyDefaults( 3026 map[string]interface{}{ 3027 "policies": []interface{}{ 3028 map[string]interface{}{ 3029 "consolidateManifests": false, 3030 "manifests": []interface{}{ 3031 map[string]interface{}{}, 3032 map[string]interface{}{}, 3033 map[string]interface{}{ 3034 "evaluationInterval": map[string]interface{}{ 3035 "compliant": "", 3036 "noncompliant": "", 3037 }, 3038 }, 3039 }, 3040 }, 3041 map[string]interface{}{}, 3042 map[string]interface{}{ 3043 "evaluationInterval": map[string]interface{}{ 3044 "compliant": "", 3045 "noncompliant": "", 3046 }, 3047 }, 3048 }, 3049 }, 3050 ) 3051 3052 if err := p.assertValidConfig(); err != nil { 3053 t.Fatal(err.Error()) 3054 } 3055 3056 output, err := p.Generate() 3057 if err != nil { 3058 t.Fatal(err.Error()) 3059 } 3060 3061 generatedManifests, err := unmarshalManifestBytes(output) 3062 if err != nil { 3063 t.Fatal(err.Error()) 3064 } 3065 3066 assertEqual(t, len(generatedManifests), 9) 3067 3068 for _, manifest := range generatedManifests { 3069 kind, _ := manifest["kind"].(string) 3070 if kind != "Policy" { 3071 continue 3072 } 3073 3074 metadata, _ := manifest["metadata"].(map[string]interface{}) 3075 3076 name, _ := metadata["name"].(string) 3077 3078 spec, _ := manifest["spec"].(map[string]interface{}) 3079 policyTemplates, _ := spec["policy-templates"].([]interface{}) 3080 3081 if name == "policy-app-config" { 3082 assertEqual(t, len(policyTemplates), 3) 3083 evaluationInterval := getYAMLEvaluationInterval(t, policyTemplates[0], false) 3084 assertEqual(t, evaluationInterval["compliant"], "30m") 3085 assertEqual(t, evaluationInterval["noncompliant"], "30s") 3086 3087 evaluationInterval = getYAMLEvaluationInterval(t, policyTemplates[1], false) 3088 assertEqual(t, evaluationInterval["compliant"], "25m") 3089 assertEqual(t, evaluationInterval["noncompliant"], "5m") 3090 3091 evaluationInterval = getYAMLEvaluationInterval(t, policyTemplates[2], true) 3092 assertEqual(t, len(evaluationInterval), 0) 3093 } else if name == "policy-app-config2" { 3094 assertEqual(t, len(policyTemplates), 1) 3095 evaluationInterval := getYAMLEvaluationInterval(t, policyTemplates[0], false) 3096 assertEqual(t, evaluationInterval["compliant"], "never") 3097 assertEqual(t, evaluationInterval["noncompliant"], "15s") 3098 } else if name == "policy-app-config3" { 3099 assertEqual(t, len(policyTemplates), 1) 3100 evaluationInterval := getYAMLEvaluationInterval(t, policyTemplates[0], true) 3101 assertEqual(t, len(evaluationInterval), 0) 3102 } 3103 } 3104 } 3105 3106 func TestCreatePolicyWithConfigPolicyAnnotations(t *testing.T) { 3107 t.Parallel() 3108 tmpDir := t.TempDir() 3109 createConfigMap(t, tmpDir, "configmap.yaml") 3110 3111 tests := []struct { 3112 name string 3113 annotations map[string]string 3114 }{ 3115 {name: "no-override", annotations: nil}, 3116 { 3117 name: "override", 3118 annotations: map[string]string{ 3119 "policy.open-cluster-management.io/disable-templates": "true", 3120 }, 3121 }, 3122 { 3123 name: "override-empty", 3124 annotations: map[string]string{}, 3125 }, 3126 } 3127 3128 for _, test := range tests { 3129 test := test 3130 t.Run(test.name, func(t *testing.T) { 3131 t.Parallel() 3132 3133 p := Plugin{} 3134 p.PolicyDefaults.Namespace = "my-policies" 3135 p.PolicyDefaults.ConfigurationPolicyAnnotations = map[string]string{"test-default-annotation": "default"} 3136 policyConf := types.PolicyConfig{ 3137 Name: "policy-app-config", Manifests: []types.Manifest{ 3138 {Path: path.Join(tmpDir, "configmap.yaml")}, 3139 }, 3140 } 3141 3142 if test.annotations != nil { 3143 policyConf.ConfigurationPolicyAnnotations = test.annotations 3144 } 3145 3146 p.Policies = append(p.Policies, policyConf) 3147 p.applyDefaults(map[string]interface{}{}) 3148 3149 err := p.createPolicy(&p.Policies[0]) 3150 if err != nil { 3151 t.Fatal(err.Error()) 3152 } 3153 3154 output := p.outputBuffer.Bytes() 3155 policyManifests, err := unmarshalManifestBytes(output) 3156 if err != nil { 3157 t.Fatal(err.Error()) 3158 } 3159 //nolint:forcetypeassert 3160 spec := policyManifests[0]["spec"].(map[string]interface{}) 3161 policyTemplates := spec["policy-templates"].([]interface{}) 3162 //nolint:forcetypeassert 3163 configPolicy := policyTemplates[0].(map[string]interface{})["objectDefinition"].(map[string]interface{}) 3164 //nolint:forcetypeassert 3165 metadata := configPolicy["metadata"].(map[string]interface{}) 3166 3167 if test.annotations != nil && len(test.annotations) == 0 { 3168 assertEqual(t, metadata["annotations"], nil) 3169 } else { 3170 annotations := map[string]string{} 3171 for key, val := range metadata["annotations"].(map[string]interface{}) { 3172 //nolint:forcetypeassert 3173 annotations[key] = val.(string) 3174 } 3175 3176 if test.annotations == nil { 3177 assertReflectEqual(t, annotations, map[string]string{"test-default-annotation": "default"}) 3178 } else { 3179 assertReflectEqual(t, annotations, test.annotations) 3180 } 3181 } 3182 }) 3183 } 3184 } 3185 3186 func TestCreatePolicyWithNamespaceSelector(t *testing.T) { 3187 t.Parallel() 3188 tmpDir := t.TempDir() 3189 createConfigMap(t, tmpDir, "configmap.yaml") 3190 3191 tests := map[string]struct { 3192 name string 3193 namespaceSelector types.NamespaceSelector 3194 }{ 3195 "nil-selector": {namespaceSelector: types.NamespaceSelector{}}, 3196 "empty-selector-values": { 3197 namespaceSelector: types.NamespaceSelector{ 3198 Include: []string{}, 3199 Exclude: []string{}, 3200 MatchLabels: &map[string]string{}, 3201 MatchExpressions: &[]metav1.LabelSelectorRequirement{}, 3202 }, 3203 }, 3204 "completely-filled-values": { 3205 namespaceSelector: types.NamespaceSelector{ 3206 Include: []string{"test-ns-1", "test-ns-2"}, 3207 Exclude: []string{"*-ns-[1]"}, 3208 MatchLabels: &map[string]string{ 3209 "testing": "is awesome", 3210 }, 3211 MatchExpressions: &[]metav1.LabelSelectorRequirement{{ 3212 Key: "door", 3213 Operator: "Exists", 3214 }}, 3215 }, 3216 }, 3217 "include-exclude-only": { 3218 namespaceSelector: types.NamespaceSelector{ 3219 Include: []string{"test-ns-1", "test-ns-2"}, 3220 Exclude: []string{"*-ns-[1]"}, 3221 }, 3222 }, 3223 "label-selectors-only": { 3224 namespaceSelector: types.NamespaceSelector{ 3225 MatchLabels: &map[string]string{ 3226 "testing": "is awesome", 3227 }, 3228 MatchExpressions: &[]metav1.LabelSelectorRequirement{{ 3229 Key: "door", 3230 Operator: "Exists", 3231 }}, 3232 }, 3233 }, 3234 } 3235 3236 for name, test := range tests { 3237 test := test 3238 3239 t.Run(name, func(t *testing.T) { 3240 t.Parallel() 3241 3242 p := Plugin{} 3243 p.PolicyDefaults.Namespace = "my-policies" 3244 p.PolicyDefaults.NamespaceSelector = types.NamespaceSelector{ 3245 MatchLabels: &map[string]string{}, 3246 } 3247 policyConf := types.PolicyConfig{ 3248 Name: "policy-app-config", Manifests: []types.Manifest{ 3249 {Path: path.Join(tmpDir, "configmap.yaml")}, 3250 }, 3251 } 3252 policyConf.NamespaceSelector = test.namespaceSelector 3253 3254 p.Policies = append(p.Policies, policyConf) 3255 p.applyDefaults(map[string]interface{}{}) 3256 3257 err := p.createPolicy(&p.Policies[0]) 3258 if err != nil { 3259 t.Fatal(err.Error()) 3260 } 3261 3262 output := p.outputBuffer.Bytes() 3263 policyManifests, err := unmarshalManifestBytes(output) 3264 if err != nil { 3265 t.Fatal(err.Error()) 3266 } 3267 //nolint:forcetypeassert 3268 spec := policyManifests[0]["spec"].(map[string]interface{}) 3269 policyTemplates := spec["policy-templates"].([]interface{}) 3270 //nolint:forcetypeassert 3271 configPolicy := policyTemplates[0].(map[string]interface{})["objectDefinition"].(map[string]interface{}) 3272 //nolint:forcetypeassert 3273 configPolicyOptions := configPolicy["spec"].(map[string]interface{}) 3274 //nolint:forcetypeassert 3275 configPolicySelector := configPolicyOptions["namespaceSelector"].(map[string]interface{}) 3276 3277 if reflect.DeepEqual(test.namespaceSelector, types.NamespaceSelector{}) { 3278 assertSelectorEqual(t, configPolicySelector, p.PolicyDefaults.NamespaceSelector) 3279 } else { 3280 assertSelectorEqual(t, configPolicySelector, test.namespaceSelector) 3281 } 3282 }) 3283 } 3284 } 3285 3286 func TestGenerateNonDNSPolicyName(t *testing.T) { 3287 t.Parallel() 3288 tmpDir := t.TempDir() 3289 createConfigMap(t, tmpDir, "configmap.yaml") 3290 3291 tests := []struct { 3292 name string 3293 policyName string 3294 }{ 3295 { 3296 name: "capitalized", 3297 policyName: "policy-APP-CONFIG", 3298 }, 3299 { 3300 name: "invalid character", 3301 policyName: "policy_app_config", 3302 }, 3303 } 3304 3305 for _, test := range tests { 3306 test := test 3307 t.Run(test.name, func(t *testing.T) { 3308 t.Parallel() 3309 3310 p := Plugin{} 3311 var err error 3312 3313 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 3314 if err != nil { 3315 t.Fatal(err.Error()) 3316 } 3317 3318 p.PlacementBindingDefaults.Name = "my-placement-binding" 3319 p.PolicyDefaults.Placement.Name = "my-placement-rule" 3320 p.PolicyDefaults.Namespace = "my-policies" 3321 policyConf := types.PolicyConfig{ 3322 Name: test.policyName, 3323 Manifests: []types.Manifest{ 3324 {Path: path.Join(tmpDir, "configmap.yaml")}, 3325 }, 3326 } 3327 basePolicies := p.Policies 3328 p.Policies = append(basePolicies, policyConf) 3329 p.applyDefaults(map[string]interface{}{}) 3330 3331 err = p.assertValidConfig() 3332 if err == nil { 3333 t.Fatal("Expected an error but did not get one") 3334 } 3335 3336 expected := fmt.Sprintf( 3337 "policy name `%s` is not DNS compliant. See "+ 3338 "https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names", 3339 test.policyName, 3340 ) 3341 assertEqual(t, err.Error(), expected) 3342 }) 3343 } 3344 } 3345 3346 func TestGenerateNonDNSPlacementName(t *testing.T) { 3347 t.Parallel() 3348 tmpDir := t.TempDir() 3349 createConfigMap(t, tmpDir, "configmap.yaml") 3350 3351 tests := []struct { 3352 name string 3353 placementName string 3354 }{ 3355 { 3356 name: "capitalized", 3357 placementName: "my-placement-RULE", 3358 }, 3359 { 3360 name: "invalid character", 3361 placementName: "my-placement?rule", 3362 }, 3363 { 3364 name: "too many characters", 3365 placementName: "placementplacementplacementplacementplacementplacementplacementplacementplacement" + 3366 "placementplacementplacementplacementplacementplacementplacementplacementplacementplacement" + 3367 "placementplacementplacementplacementplacementplacementplacementplacementplacementrule", 3368 }, 3369 } 3370 3371 for _, test := range tests { 3372 test := test 3373 t.Run(test.name, func(t *testing.T) { 3374 t.Parallel() 3375 3376 p := Plugin{} 3377 var err error 3378 3379 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 3380 if err != nil { 3381 t.Fatal(err.Error()) 3382 } 3383 3384 p.PlacementBindingDefaults.Name = "my-placement-binding" 3385 p.PolicyDefaults.Placement.Name = test.placementName 3386 p.PolicyDefaults.Namespace = "my-policies" 3387 policyConf := types.PolicyConfig{ 3388 Name: "policy-app-config", 3389 Manifests: []types.Manifest{ 3390 {Path: path.Join(tmpDir, "configmap.yaml")}, 3391 }, 3392 } 3393 p.Policies = append(p.Policies, policyConf) 3394 p.applyDefaults(map[string]interface{}{}) 3395 3396 err = p.assertValidConfig() 3397 if err == nil { 3398 t.Fatal("Expected an error but did not get one") 3399 } 3400 3401 expected := fmt.Sprintf( 3402 "policyDefaults placement.name `%s` is not DNS compliant. See "+ 3403 "https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names", 3404 test.placementName, 3405 ) 3406 assertEqual(t, err.Error(), expected) 3407 }) 3408 } 3409 } 3410 3411 func TestGenerateNonDNSBindingName(t *testing.T) { 3412 t.Parallel() 3413 tmpDir := t.TempDir() 3414 createConfigMap(t, tmpDir, "configmap.yaml") 3415 3416 tests := []struct { 3417 name string 3418 bindingName string 3419 }{ 3420 { 3421 name: "capitalized", 3422 bindingName: "my-placement-BINDING", 3423 }, 3424 { 3425 name: "invalid character", 3426 bindingName: "my-placement?binding", 3427 }, 3428 { 3429 name: "too many characters", 3430 bindingName: "placementplacementplacementplacementplacementplacementplacementplacementplacement" + 3431 "placementplacementplacementplacementplacementplacementplacementplacementplacementplacement" + 3432 "placementplacementplacementplacementplacementplacementplacementplacementplacementbinding", 3433 }, 3434 } 3435 3436 for _, test := range tests { 3437 test := test 3438 t.Run(test.name, func(t *testing.T) { 3439 t.Parallel() 3440 3441 p := Plugin{} 3442 var err error 3443 3444 p.baseDirectory, err = filepath.EvalSymlinks(tmpDir) 3445 if err != nil { 3446 t.Fatal(err.Error()) 3447 } 3448 3449 p.PlacementBindingDefaults.Name = test.bindingName 3450 p.PolicyDefaults.Placement.Name = "my-placement-rule" 3451 p.PolicyDefaults.Namespace = "my-policies" 3452 policyConf := types.PolicyConfig{ 3453 Name: "policy-app-config", 3454 Manifests: []types.Manifest{ 3455 {Path: path.Join(tmpDir, "configmap.yaml")}, 3456 }, 3457 } 3458 policyConf2 := types.PolicyConfig{ 3459 Name: "policy-app-config2", 3460 Manifests: []types.Manifest{ 3461 {Path: path.Join(tmpDir, "configmap.yaml")}, 3462 }, 3463 } 3464 p.Policies = append(p.Policies, policyConf, policyConf2) 3465 p.applyDefaults(map[string]interface{}{}) 3466 3467 err = p.assertValidConfig() 3468 if err == nil { 3469 t.Fatal("Expected an error but did not get one") 3470 } 3471 3472 expected := fmt.Sprintf( 3473 "PlacementBindingDefaults.Name `%s` is not DNS compliant. See "+ 3474 "https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names", 3475 test.bindingName, 3476 ) 3477 assertEqual(t, err.Error(), expected) 3478 }) 3479 } 3480 } 3481 3482 func TestCreatePlacementRuleFromMatchExpressions(t *testing.T) { 3483 t.Parallel() 3484 3485 p := Plugin{} 3486 p.usingPlR = true 3487 p.allPlcs = map[string]bool{} 3488 p.csToPlc = map[string]string{} 3489 p.PolicyDefaults.Namespace = "my-policies" 3490 policyConf := types.PolicyConfig{Name: "policy-app-config"} 3491 me := map[string]interface{}{ 3492 "key": "cloud", 3493 "operator": "In", 3494 "values": []string{ 3495 "red hat", 3496 "test", 3497 }, 3498 } 3499 policyConf.Placement.ClusterSelectors = map[string]interface{}{ 3500 "matchExpressions": []interface{}{me}, 3501 } 3502 3503 name, err := p.createPolicyPlacement(policyConf.Placement, policyConf.Name) 3504 if err != nil { 3505 t.Fatal(err.Error()) 3506 } 3507 3508 assertEqual(t, name, "placement-policy-app-config") 3509 3510 output := p.outputBuffer.String() 3511 expected := ` 3512 --- 3513 apiVersion: apps.open-cluster-management.io/v1 3514 kind: PlacementRule 3515 metadata: 3516 name: placement-policy-app-config 3517 namespace: my-policies 3518 spec: 3519 clusterSelector: 3520 matchExpressions: 3521 - key: cloud 3522 operator: In 3523 values: 3524 - red hat 3525 - test 3526 ` 3527 expected = strings.TrimPrefix(expected, "\n") 3528 assertEqual(t, output, expected) 3529 } 3530 3531 func TestCreatePlacementRuleWithClusterSelector(t *testing.T) { 3532 t.Parallel() 3533 3534 p := Plugin{} 3535 p.usingPlR = true 3536 p.allPlcs = map[string]bool{} 3537 p.csToPlc = map[string]string{} 3538 p.PolicyDefaults.Namespace = "my-policies" 3539 policyConf := types.PolicyConfig{Name: "policy-app-config"} 3540 me := map[string]interface{}{ 3541 "key": "cloud", 3542 "operator": "In", 3543 "values": []string{ 3544 "red hat", 3545 "test", 3546 }, 3547 } 3548 policyConf.Placement.ClusterSelector = map[string]interface{}{ 3549 "matchExpressions": []interface{}{me}, 3550 } 3551 3552 name, err := p.createPolicyPlacement(policyConf.Placement, policyConf.Name) 3553 if err != nil { 3554 t.Fatal(err.Error()) 3555 } 3556 3557 assertEqual(t, name, "placement-policy-app-config") 3558 3559 output := p.outputBuffer.String() 3560 expected := ` 3561 --- 3562 apiVersion: apps.open-cluster-management.io/v1 3563 kind: PlacementRule 3564 metadata: 3565 name: placement-policy-app-config 3566 namespace: my-policies 3567 spec: 3568 clusterSelector: 3569 matchExpressions: 3570 - key: cloud 3571 operator: In 3572 values: 3573 - red hat 3574 - test 3575 ` 3576 expected = strings.TrimPrefix(expected, "\n") 3577 assertEqual(t, output, expected) 3578 } 3579 3580 func TestCreatePlacementFromMatchLabels(t *testing.T) { 3581 t.Parallel() 3582 3583 p := Plugin{} 3584 p.allPlcs = map[string]bool{} 3585 p.csToPlc = map[string]string{} 3586 p.PolicyDefaults.Namespace = "my-policies" 3587 policyConf := types.PolicyConfig{Name: "policy-app-config"} 3588 ml := map[string]interface{}{ 3589 "cloud": "red hat", 3590 } 3591 policyConf.Placement.ClusterSelectors = map[string]interface{}{ 3592 "matchLabels": ml, 3593 } 3594 3595 name, err := p.createPolicyPlacement(policyConf.Placement, policyConf.Name) 3596 if err != nil { 3597 t.Fatal(err.Error()) 3598 } 3599 3600 assertEqual(t, name, "placement-policy-app-config") 3601 3602 output := p.outputBuffer.String() 3603 expected := ` 3604 --- 3605 apiVersion: cluster.open-cluster-management.io/v1beta1 3606 kind: Placement 3607 metadata: 3608 name: placement-policy-app-config 3609 namespace: my-policies 3610 spec: 3611 predicates: 3612 - requiredClusterSelector: 3613 labelSelector: 3614 matchLabels: 3615 cloud: red hat 3616 ` 3617 expected = strings.TrimPrefix(expected, "\n") 3618 assertEqual(t, output, expected) 3619 } 3620 3621 func TestCreatePlacementFromMatchExpressions(t *testing.T) { 3622 t.Parallel() 3623 3624 p := Plugin{} 3625 p.allPlcs = map[string]bool{} 3626 p.csToPlc = map[string]string{} 3627 p.PolicyDefaults.Namespace = "my-policies" 3628 policyConf := types.PolicyConfig{Name: "policy-app-config"} 3629 me := map[string]interface{}{ 3630 "key": "cloud", 3631 "operator": "In", 3632 "values": []string{ 3633 "red hat", 3634 "test", 3635 }, 3636 } 3637 policyConf.Placement.LabelSelector = map[string]interface{}{ 3638 "matchExpressions": []interface{}{me}, 3639 } 3640 3641 name, err := p.createPolicyPlacement(policyConf.Placement, policyConf.Name) 3642 if err != nil { 3643 t.Fatal(err.Error()) 3644 } 3645 3646 assertEqual(t, name, "placement-policy-app-config") 3647 3648 output := p.outputBuffer.String() 3649 expected := ` 3650 --- 3651 apiVersion: cluster.open-cluster-management.io/v1beta1 3652 kind: Placement 3653 metadata: 3654 name: placement-policy-app-config 3655 namespace: my-policies 3656 spec: 3657 predicates: 3658 - requiredClusterSelector: 3659 labelSelector: 3660 matchExpressions: 3661 - key: cloud 3662 operator: In 3663 values: 3664 - red hat 3665 - test 3666 ` 3667 expected = strings.TrimPrefix(expected, "\n") 3668 assertEqual(t, output, expected) 3669 } 3670 3671 func TestCreatePlacementInvalidMatchExpressions(t *testing.T) { 3672 t.Parallel() 3673 3674 p := Plugin{} 3675 p.allPlcs = map[string]bool{} 3676 p.csToPlc = map[string]string{} 3677 p.PolicyDefaults.Namespace = "my-policies" 3678 policyConf := types.PolicyConfig{Name: "policy-app-config"} 3679 nestedMap := map[string]interface{}{ 3680 "test": "invalid", 3681 } 3682 me := map[string]interface{}{ 3683 "key": "cloud", 3684 "operator": "In", 3685 "values": []interface{}{ 3686 "red hat", 3687 nestedMap, 3688 }, 3689 } 3690 policyConf.Placement.LabelSelector = map[string]interface{}{ 3691 "matchExpressions": []interface{}{me}, 3692 } 3693 3694 _, err := p.createPolicyPlacement(policyConf.Placement, policyConf.Name) 3695 if err == nil { 3696 t.Fatal("Expected an error but did not get one") 3697 } 3698 3699 expected := "the input is not a valid label selector or key-value label matching map" 3700 assertEqual(t, err.Error(), expected) 3701 } 3702 3703 func TestCreatePlacementMultipleSelectors(t *testing.T) { 3704 t.Parallel() 3705 3706 p := Plugin{} 3707 p.allPlcs = map[string]bool{} 3708 p.csToPlc = map[string]string{} 3709 p.PolicyDefaults.Namespace = "my-policies" 3710 policyConf := types.PolicyConfig{Name: "policy-app-config"} 3711 me := map[string]interface{}{ 3712 "key": "cloud", 3713 "operator": "In", 3714 "values": []string{ 3715 "red hat", 3716 }, 3717 } 3718 ml := map[string]interface{}{ 3719 "cloud": "red hat", 3720 } 3721 policyConf.Placement.LabelSelector = map[string]interface{}{ 3722 "matchExpressions": []interface{}{me}, 3723 "matchLabels": ml, 3724 } 3725 3726 _, err := p.createPolicyPlacement(policyConf.Placement, policyConf.Name) 3727 if err != nil { 3728 t.Fatal(err.Error()) 3729 } 3730 3731 output := p.outputBuffer.String() 3732 expected := ` 3733 --- 3734 apiVersion: cluster.open-cluster-management.io/v1beta1 3735 kind: Placement 3736 metadata: 3737 name: placement-policy-app-config 3738 namespace: my-policies 3739 spec: 3740 predicates: 3741 - requiredClusterSelector: 3742 labelSelector: 3743 matchExpressions: 3744 - key: cloud 3745 operator: In 3746 values: 3747 - red hat 3748 matchLabels: 3749 cloud: red hat 3750 ` 3751 expected = strings.TrimPrefix(expected, "\n") 3752 assertEqual(t, output, expected) 3753 } 3754 3755 func TestCreatePolicyWithCopyPolicyMetadata(t *testing.T) { 3756 t.Parallel() 3757 tmpDir := t.TempDir() 3758 createConfigMap(t, tmpDir, "configmap.yaml") 3759 3760 bTrue := true 3761 bFalse := false 3762 3763 tests := []struct { 3764 name string 3765 copyPolicyMetadata *bool 3766 expected *bool 3767 }{ 3768 {name: "unset", copyPolicyMetadata: nil, expected: nil}, 3769 {name: "true", copyPolicyMetadata: &bTrue, expected: nil}, 3770 {name: "false", copyPolicyMetadata: &bFalse, expected: &bFalse}, 3771 } 3772 3773 for _, mode := range []string{"policyDefault", "policy"} { 3774 mode := mode 3775 3776 for _, test := range tests { 3777 test := test 3778 t.Run(mode+" "+test.name, func(t *testing.T) { 3779 t.Parallel() 3780 3781 p := Plugin{} 3782 p.PolicyDefaults.Namespace = "my-policies" 3783 policyConf := types.PolicyConfig{ 3784 Name: "policy-app-config", Manifests: []types.Manifest{ 3785 {Path: path.Join(tmpDir, "configmap.yaml")}, 3786 }, 3787 } 3788 3789 policyDefaultsUnmarshaled := map[string]interface{}{} 3790 policyUnmarshaled := map[string]interface{}{} 3791 3792 if test.copyPolicyMetadata != nil { 3793 if mode == "policyDefault" { 3794 policyDefaultsUnmarshaled["copyPolicyMetadata"] = *test.copyPolicyMetadata 3795 } else if mode == "policy" { 3796 policyUnmarshaled["copyPolicyMetadata"] = *test.copyPolicyMetadata 3797 } 3798 } 3799 3800 p.Policies = append(p.Policies, policyConf) 3801 p.applyDefaults( 3802 map[string]interface{}{ 3803 "policyDefaults": policyDefaultsUnmarshaled, 3804 "policies": []interface{}{policyUnmarshaled}, 3805 }, 3806 ) 3807 3808 err := p.createPolicy(&p.Policies[0]) 3809 if err != nil { 3810 t.Fatal(err.Error()) 3811 } 3812 3813 output := p.outputBuffer.Bytes() 3814 policyManifests, err := unmarshalManifestBytes(output) 3815 if err != nil { 3816 t.Fatal(err.Error()) 3817 } 3818 3819 //nolint:forcetypeassert 3820 spec := policyManifests[0]["spec"].(map[string]interface{}) 3821 3822 if test.expected == nil { 3823 if _, set := spec["copyPolicyMetadata"]; set { 3824 t.Fatal("Expected the policy's spec.copyPolicyMetadata to be unset") 3825 } 3826 } else { 3827 assertEqual(t, spec["copyPolicyMetadata"], *test.expected) 3828 } 3829 }) 3830 } 3831 } 3832 }