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  }