@@ -19,6 +19,7 @@ const (
1919 configPolicyKind = "ConfigurationPolicy"
2020 policyAPIVersion = "policy.open-cluster-management.io/v1"
2121 policyKind = "Policy"
22+ policySetKind = "PolicySet"
2223 placementBindingAPIVersion = "policy.open-cluster-management.io/v1"
2324 placementBindingKind = "PlacementBinding"
2425 placementRuleAPIVersion = "apps.open-cluster-management.io/v1"
@@ -37,8 +38,9 @@ type Plugin struct {
3738 PlacementBindingDefaults struct {
3839 Name string `json:"name,omitempty" yaml:"name,omitempty"`
3940 } `json:"placementBindingDefaults,omitempty" yaml:"placementBindingDefaults,omitempty"`
40- PolicyDefaults types.PolicyDefaults `json:"policyDefaults,omitempty" yaml:"policyDefaults,omitempty"`
41- Policies []types.PolicyConfig `json:"policies" yaml:"policies"`
41+ PolicyDefaults types.PolicyDefaults `json:"policyDefaults,omitempty" yaml:"policyDefaults,omitempty"`
42+ Policies []types.PolicyConfig `json:"policies" yaml:"policies"`
43+ PolicySets []types.PolicySetConfig `json:"policySets" yaml:"policySets"`
4244 // A set of all placement names that have been processed or generated
4345 allPlcs map [string ]bool
4446 // The base of the directory tree to restrict all manifest files to be within
@@ -105,15 +107,26 @@ func (p *Plugin) Generate() ([]byte, error) {
105107 }
106108 }
107109
110+ for i := range p .PolicySets {
111+ err := p .createPolicySet (& p .PolicySets [i ])
112+ if err != nil {
113+ return nil , err
114+ }
115+ }
116+
108117 // Keep track of which placement maps to which policy. This will be used to determine
109118 // how many placement bindings are required since one binding per placement is required.
110119 plcNameToPolicyIdxs := map [string ][]int {}
111120 for i := range p .Policies {
112- plcName , err := p .createPlacement (& p .Policies [i ])
113- if err != nil {
114- return nil , err
121+ // only generate placement when GeneratePlacementWhenInSet equals to true or policy is not
122+ // part of any policy sets
123+ if p .Policies [i ].GeneratePlacementWhenInSet || len (p .Policies [i ].PolicySets ) == 0 {
124+ plcName , err := p .createPlacement (& p .Policies [i ])
125+ if err != nil {
126+ return nil , err
127+ }
128+ plcNameToPolicyIdxs [plcName ] = append (plcNameToPolicyIdxs [plcName ], i )
115129 }
116- plcNameToPolicyIdxs [plcName ] = append (plcNameToPolicyIdxs [plcName ], i )
117130 }
118131
119132 // Sort the keys of plcNameToPolicyIdxs so that the policy bindings are generated in a
@@ -201,11 +214,11 @@ func getPolicyBool(
201214 return
202215}
203216
204- // applyDefaults applies any missing defaults under Policy.PlacementBindingDefaults and
205- // Policy.PolicyDefaults. It then applies the defaults and user provided defaults on each
206- // policy entry if they are not overridden by the user. The input unmarshaledConfig is used
207- // in situations where it is necessary to know if an explicit false is provided rather than
208- // rely on the default Go value on the Plugin struct.
217+ // applyDefaults applies any missing defaults under Policy.PlacementBindingDefaults,
218+ // Policy.PolicyDefaults and PolicySets . It then applies the defaults and user provided
219+ // defaults on each policy and policyset entry if they are not overridden by the user. The
220+ // input unmarshaledConfig is used in situations where it is necessary to know if an explicit
221+ // false is provided rather than rely on the default Go value on the Plugin struct.
209222func (p * Plugin ) applyDefaults (unmarshaledConfig map [string ]interface {}) {
210223 if len (p .Policies ) == 0 {
211224 return
@@ -258,6 +271,25 @@ func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
258271 if p .PolicyDefaults .Standards == nil {
259272 p .PolicyDefaults .Standards = defaults .Standards
260273 }
274+ // Generate temporary sets to later merge the policy sets declared in p.Policies[*] and p.PolicySets
275+ plcsetToPlc := make (map [string ]map [string ]bool )
276+ plcToPlcset := make (map [string ]map [string ]bool )
277+
278+ for _ , plcset := range p .PolicySets {
279+ if plcsetToPlc [plcset .Name ] == nil {
280+ plcsetToPlc [plcset .Name ] = make (map [string ]bool )
281+ }
282+
283+ for _ , plc := range plcset .Policies {
284+ plcsetToPlc [plcset.Name ][plc ] = true
285+
286+ if plcToPlcset [plc ] == nil {
287+ plcToPlcset [plc ] = make (map [string ]bool )
288+ }
289+
290+ plcToPlcset [plc ][plcset.Name ] = true
291+ }
292+ }
261293
262294 for i := range p .Policies {
263295 policy := & p .Policies [i ]
@@ -273,6 +305,18 @@ func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
273305 policy .Controls = p .PolicyDefaults .Controls
274306 }
275307
308+ if policy .PolicySets == nil {
309+ policy .PolicySets = p .PolicyDefaults .PolicySets
310+ }
311+
312+ // GeneratePlacementWhenInSet default to false unless explicitly set in the config.
313+ gpValue , setGp := getPolicyBool (unmarshaledConfig , i , "generatePlacementWhenInSet" )
314+ if setGp {
315+ policy .GeneratePlacementWhenInSet = gpValue
316+ } else {
317+ policy .GeneratePlacementWhenInSet = p .PolicyDefaults .GeneratePlacementWhenInSet
318+ }
319+
276320 // Policy expanders default to the policy default unless explicitly set.
277321 // Gatekeeper policy expander policy override
278322 igvValue , setIgv := getPolicyBool (unmarshaledConfig , i , "informGatekeeperPolicies" )
@@ -342,6 +386,42 @@ func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
342386 policy .Manifests [i ].ComplianceType = policy .ComplianceType
343387 }
344388 }
389+
390+ for _ , plcsetInPlc := range policy .PolicySets {
391+ if _ , ok := plcsetToPlc [plcsetInPlc ]; ! ok {
392+ newPlcset := types.PolicySetConfig {
393+ Name : plcsetInPlc ,
394+ }
395+ p .PolicySets = append (p .PolicySets , newPlcset )
396+ plcsetToPlc [plcsetInPlc ] = make (map [string ]bool )
397+ }
398+ if plcToPlcset [policy .Name ] == nil {
399+ plcToPlcset [policy .Name ] = make (map [string ]bool )
400+ }
401+
402+ plcToPlcset [policy.Name ][plcsetInPlc ] = true
403+
404+ plcsetToPlc [plcsetInPlc ][policy.Name ] = true
405+ }
406+
407+ policy .PolicySets = make ([]string , 0 , len (plcToPlcset [policy .Name ]))
408+
409+ for plcset := range plcToPlcset [policy .Name ] {
410+ policy .PolicySets = append (policy .PolicySets , plcset )
411+ }
412+ }
413+
414+ // Sync up the declared policy sets in p.Policies[*]
415+ for i := range p .PolicySets {
416+ plcset := & p .PolicySets [i ]
417+ plcset .Policies = make ([]string , 0 , len (plcsetToPlc [plcset .Name ]))
418+
419+ for plc := range plcsetToPlc [plcset .Name ] {
420+ plcset .Policies = append (plcset .Policies , plc )
421+ }
422+
423+ // Sort alphabetically to make it deterministic
424+ sort .Strings (plcset .Policies )
345425 }
346426}
347427
@@ -532,6 +612,36 @@ func (p *Plugin) createPolicy(policyConf *types.PolicyConfig) error {
532612 return nil
533613}
534614
615+ // createPolicySet will generate the policyset based on the Policy Generator configuration.
616+ // The generated policyset is written to the plugin's output buffer. An error is returned if the
617+ // manifests specified in the configuration are invalid or can't be read.
618+ func (p * Plugin ) createPolicySet (policySetConf * types.PolicySetConfig ) error {
619+ policyset := map [string ]interface {}{
620+ "apiVersion" : policyAPIVersion ,
621+ "kind" : policySetKind ,
622+ "metadata" : map [string ]interface {}{
623+ "name" : policySetConf .Name ,
624+ "namespace" : p .PolicyDefaults .Namespace , // policyset should be generated in the same namespace of policy
625+ },
626+ "spec" : map [string ]interface {}{
627+ "description" : policySetConf .Description ,
628+ "policies" : policySetConf .Policies ,
629+ },
630+ }
631+
632+ policysetYAML , err := yaml .Marshal (policyset )
633+ if err != nil {
634+ return fmt .Errorf (
635+ "an unexpected error occurred when converting the policyset to YAML: %w" , err ,
636+ )
637+ }
638+
639+ p .outputBuffer .Write ([]byte ("---\n " ))
640+ p .outputBuffer .Write (policysetYAML )
641+
642+ return nil
643+ }
644+
535645// getPlcFromPath finds the placement manifest in the input manifest file. It will return the name
536646// of the placement, the unmarshaled placement manifest, and an error. An error is returned if the
537647// placement manifest cannot be found or is invalid.
0 commit comments