github.com/dhaiducek/policy-generator-plugin@v1.99.99/internal/plugin.go (about)

     1  // Copyright Contributors to the Open Cluster Management project
     2  package internal
     3  
     4  import (
     5  	"bytes"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"sort"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/dhaiducek/policy-generator-plugin/internal/types"
    16  	yaml "gopkg.in/yaml.v3"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    19  	"k8s.io/apimachinery/pkg/runtime"
    20  	"k8s.io/apimachinery/pkg/util/validation"
    21  )
    22  
    23  const (
    24  	configPolicyKind           = "ConfigurationPolicy"
    25  	policyAPIGroup             = "policy.open-cluster-management.io"
    26  	policyAPIVersion           = policyAPIGroup + "/v1"
    27  	policyKind                 = "Policy"
    28  	policySetAPIVersion        = policyAPIGroup + "/v1beta1"
    29  	policySetKind              = "PolicySet"
    30  	placementBindingAPIVersion = policyAPIGroup + "/v1"
    31  	placementBindingKind       = "PlacementBinding"
    32  	placementRuleAPIVersion    = "apps.open-cluster-management.io/v1"
    33  	placementRuleKind          = "PlacementRule"
    34  	placementAPIVersion        = "cluster.open-cluster-management.io/v1beta1"
    35  	placementKind              = "Placement"
    36  	maxObjectNameLength        = 63
    37  	dnsReference               = "https://kubernetes.io/docs/concepts/overview/working-with-objects/names/" +
    38  		"#dns-subdomain-names"
    39  )
    40  
    41  // Plugin is used to store the PolicyGenerator configuration and the methods to generate the
    42  // desired policies.
    43  type Plugin struct {
    44  	APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
    45  	Kind       string `json:"kind,omitempty" yaml:"kind,omitempty"`
    46  	Metadata   struct {
    47  		Name string `json:"name,omitempty" yaml:"name,omitempty"`
    48  	} `json:"metadata,omitempty" yaml:"metadata,omitempty"`
    49  	PlacementBindingDefaults struct {
    50  		Name string `json:"name,omitempty" yaml:"name,omitempty"`
    51  	} `json:"placementBindingDefaults,omitempty" yaml:"placementBindingDefaults,omitempty"`
    52  	PolicyDefaults    types.PolicyDefaults    `json:"policyDefaults,omitempty" yaml:"policyDefaults,omitempty"`
    53  	PolicySetDefaults types.PolicySetDefaults `json:"policySetDefaults,omitempty" yaml:"policySetDefaults,omitempty"`
    54  	Policies          []types.PolicyConfig    `json:"policies" yaml:"policies"`
    55  	PolicySets        []types.PolicySetConfig `json:"policySets" yaml:"policySets"`
    56  	// A set of all placement names that have been processed or generated
    57  	allPlcs map[string]bool
    58  	// The base of the directory tree to restrict all manifest files to be within
    59  	baseDirectory string
    60  	// This is a mapping of cluster/label selectors formatted as the return value of getCsKey to
    61  	// placement names. This is used to find common cluster/label selectors that can be consolidated
    62  	// to a single placement.
    63  	csToPlc      map[string]string
    64  	outputBuffer bytes.Buffer
    65  	// Track placement kind (we only expect to have one kind)
    66  	usingPlR bool
    67  	// A set of processed placements from external placements (either Placement.PlacementRulePath or
    68  	// Placement.PlacementPath)
    69  	processedPlcs map[string]bool
    70  	// Track previous policy name for use if policies are being ordered
    71  	previousPolicyName string
    72  }
    73  
    74  var defaults = types.PolicyDefaults{
    75  	PolicyOptions: types.PolicyOptions{
    76  		Categories: []string{"CM Configuration Management"},
    77  		Controls:   []string{"CM-2 Baseline Configuration"},
    78  		Standards:  []string{"NIST SP 800-53"},
    79  	},
    80  	ConfigurationPolicyOptions: types.ConfigurationPolicyOptions{
    81  		ComplianceType:    "musthave",
    82  		RemediationAction: "inform",
    83  		Severity:          "low",
    84  	},
    85  }
    86  
    87  // Config validates the input PolicyGenerator configuration, applies any missing defaults, and
    88  // configures the Policy object.
    89  func (p *Plugin) Config(config []byte, baseDirectory string) error {
    90  	dec := yaml.NewDecoder(bytes.NewReader(config))
    91  	dec.KnownFields(true) // emit an error on unknown fields in the input
    92  
    93  	err := dec.Decode(p)
    94  	const errTemplate = "the PolicyGenerator configuration file is invalid: %w"
    95  
    96  	if err != nil {
    97  		return fmt.Errorf(errTemplate, addFieldNotFoundHelp(err))
    98  	}
    99  
   100  	var unmarshaledConfig map[string]interface{}
   101  
   102  	err = yaml.Unmarshal(config, &unmarshaledConfig)
   103  	if err != nil {
   104  		return fmt.Errorf(errTemplate, err)
   105  	}
   106  
   107  	p.applyDefaults(unmarshaledConfig)
   108  
   109  	baseDirectory, err = filepath.EvalSymlinks(baseDirectory)
   110  	if err != nil {
   111  		return fmt.Errorf("failed to evaluate symlinks for the base directory: %w", err)
   112  	}
   113  
   114  	p.baseDirectory = baseDirectory
   115  
   116  	return p.assertValidConfig()
   117  }
   118  
   119  // Generate generates the policies, placements, and placement bindings and returns them as
   120  // a single YAML file as a byte array. An error is returned if they cannot be created.
   121  func (p *Plugin) Generate() ([]byte, error) {
   122  	// Set the default empty values to the fields that track state
   123  	p.allPlcs = map[string]bool{}
   124  	p.csToPlc = map[string]string{}
   125  	p.outputBuffer = bytes.Buffer{}
   126  	p.processedPlcs = map[string]bool{}
   127  
   128  	for i := range p.Policies {
   129  		err := p.createPolicy(&p.Policies[i])
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  	}
   134  
   135  	for i := range p.PolicySets {
   136  		err := p.createPolicySet(&p.PolicySets[i])
   137  		if err != nil {
   138  			return nil, err
   139  		}
   140  	}
   141  
   142  	// Keep track of which placement maps to which policy and policySet. This will be used to determine
   143  	// how many placement bindings are required since one binding per placement is required.
   144  	// plcNameToPolicyAndSetIdxs[plcName]["policy"] stores the index of policy
   145  	// plcNameToPolicyAndSetIdxs[plcName]["policyset"] stores the index of policyset
   146  	plcNameToPolicyAndSetIdxs := map[string]map[string][]int{}
   147  
   148  	for i := range p.Policies {
   149  		// only generate placement when GeneratePlacementWhenInSet equals to true, GeneratePlacement is true,
   150  		// or policy is not part of any policy sets
   151  		if p.Policies[i].GeneratePlacementWhenInSet ||
   152  			(p.Policies[i].GeneratePolicyPlacement && len(p.Policies[i].PolicySets) == 0) {
   153  			plcName, err := p.createPolicyPlacement(p.Policies[i].Placement, p.Policies[i].Name)
   154  			if err != nil {
   155  				return nil, err
   156  			}
   157  
   158  			if plcNameToPolicyAndSetIdxs[plcName] == nil {
   159  				plcNameToPolicyAndSetIdxs[plcName] = map[string][]int{}
   160  			}
   161  
   162  			plcNameToPolicyAndSetIdxs[plcName]["policy"] = append(plcNameToPolicyAndSetIdxs[plcName]["policy"], i)
   163  		}
   164  	}
   165  
   166  	for i := range p.PolicySets {
   167  		// only generate placement when GeneratePolicySetPlacement equals to true
   168  		if p.PolicySets[i].GeneratePolicySetPlacement {
   169  			plcName, err := p.createPolicySetPlacement(p.PolicySets[i].Placement, p.PolicySets[i].Name)
   170  			if err != nil {
   171  				return nil, err
   172  			}
   173  
   174  			if plcNameToPolicyAndSetIdxs[plcName] == nil {
   175  				plcNameToPolicyAndSetIdxs[plcName] = map[string][]int{}
   176  			}
   177  
   178  			plcNameToPolicyAndSetIdxs[plcName]["policyset"] = append(plcNameToPolicyAndSetIdxs[plcName]["policyset"], i)
   179  		}
   180  	}
   181  
   182  	// Sort the keys of plcNameToPolicyseetsIdxs so that the policy bindings are generated in a
   183  	// consistent order.
   184  	plcNames := make([]string, len(plcNameToPolicyAndSetIdxs))
   185  	i := 0
   186  
   187  	for k := range plcNameToPolicyAndSetIdxs {
   188  		plcNames[i] = k
   189  		i++
   190  	}
   191  
   192  	sort.Strings(plcNames)
   193  
   194  	plcBindingCount := 0
   195  
   196  	for _, plcName := range plcNames {
   197  		// Determine which policies and policy sets to be included in the placement binding.
   198  		policyConfs := []*types.PolicyConfig{}
   199  		for _, i := range plcNameToPolicyAndSetIdxs[plcName]["policy"] {
   200  			policyConfs = append(policyConfs, &p.Policies[i])
   201  		}
   202  
   203  		policySetConfs := []*types.PolicySetConfig{}
   204  		for _, i := range plcNameToPolicyAndSetIdxs[plcName]["policyset"] {
   205  			policySetConfs = append(policySetConfs, &p.PolicySets[i])
   206  		}
   207  
   208  		// If there is more than one policy associated with a placement but no default binding name
   209  		// specified, throw an error
   210  		if (len(policyConfs) > 1 || len(policySetConfs) > 1) && p.PlacementBindingDefaults.Name == "" {
   211  			return nil, fmt.Errorf(
   212  				"placementBindingDefaults.name must be set but is empty (multiple policies or policy sets were found "+
   213  					"for the PlacementBinding to placement %s)",
   214  				plcName,
   215  			)
   216  		}
   217  
   218  		var bindingName string
   219  
   220  		existMultiple := false
   221  
   222  		// If there is only one policy or one policy set, use the policy or policy set name if there is no default
   223  		// binding name specified
   224  		if len(policyConfs) == 1 && len(policySetConfs) == 0 {
   225  			bindingName = "binding-" + policyConfs[0].Name
   226  		} else if len(policyConfs) == 0 && len(policySetConfs) == 0 {
   227  			bindingName = "binding-" + policySetConfs[0].Name
   228  		} else {
   229  			existMultiple = true
   230  		}
   231  		// If there are multiple policies or policy sets, use the default placement binding name
   232  		// but append a number to it so it's a unique name.
   233  		if p.PlacementBindingDefaults.Name != "" && existMultiple {
   234  			plcBindingCount++
   235  			if plcBindingCount == 1 {
   236  				bindingName = p.PlacementBindingDefaults.Name
   237  			} else {
   238  				bindingName = fmt.Sprintf("%s%d", p.PlacementBindingDefaults.Name, plcBindingCount)
   239  			}
   240  		}
   241  
   242  		err := p.createPlacementBinding(bindingName, plcName, policyConfs, policySetConfs)
   243  		if err != nil {
   244  			return nil, fmt.Errorf("failed to create a placement binding: %w", err)
   245  		}
   246  	}
   247  
   248  	return p.outputBuffer.Bytes(), nil
   249  }
   250  
   251  func getPolicyDefaultBool(config map[string]interface{}, key string) (value bool, set bool) {
   252  	return getDefaultBool(config, "policyDefaults", key)
   253  }
   254  
   255  func getPolicySetDefaultBool(config map[string]interface{}, key string) (value bool, set bool) {
   256  	return getDefaultBool(config, "policySetDefaults", key)
   257  }
   258  
   259  func getDefaultBool(config map[string]interface{}, defaultKey string, key string) (value bool, set bool) {
   260  	defaults, ok := config[defaultKey].(map[string]interface{})
   261  	if ok {
   262  		value, set = defaults[key].(bool)
   263  
   264  		return
   265  	}
   266  
   267  	return false, false
   268  }
   269  
   270  func getPolicyBool(
   271  	config map[string]interface{}, policyIndex int, key string,
   272  ) (value bool, set bool) {
   273  	policy := getPolicy(config, policyIndex)
   274  	if policy == nil {
   275  		return false, false
   276  	}
   277  
   278  	value, set = policy[key].(bool)
   279  
   280  	return
   281  }
   282  
   283  func getPolicySetBool(
   284  	config map[string]interface{}, policySetIndex int, key string,
   285  ) (value bool, set bool) {
   286  	policySet := getPolicySet(config, policySetIndex)
   287  	if policySet == nil {
   288  		return false, false
   289  	}
   290  
   291  	value, set = policySet[key].(bool)
   292  
   293  	return
   294  }
   295  
   296  func getArrayObject(config map[string]interface{}, key string, idx int) map[string]interface{} {
   297  	array, ok := config[key].([]interface{})
   298  	if !ok {
   299  		return nil
   300  	}
   301  
   302  	if len(array)-1 < idx {
   303  		return nil
   304  	}
   305  
   306  	object, ok := array[idx].(map[string]interface{})
   307  	if !ok {
   308  		return nil
   309  	}
   310  
   311  	return object
   312  }
   313  
   314  // getPolicy will return a policy at the specified index in the Policy Generator configuration YAML.
   315  func getPolicy(config map[string]interface{}, policyIndex int) map[string]interface{} {
   316  	return getArrayObject(config, "policies", policyIndex)
   317  }
   318  
   319  // getPolicySet will return a policy at the specified index in the Policy Generator configuration YAML.
   320  func getPolicySet(config map[string]interface{}, policySetIndex int) map[string]interface{} {
   321  	return getArrayObject(config, "policySets", policySetIndex)
   322  }
   323  
   324  // getEvaluationInterval will return the evaluation interval of specified policy in the Policy Generator configuration
   325  // YAML.
   326  func isEvaluationIntervalSet(config map[string]interface{}, policyIndex int, complianceType string) bool {
   327  	policy := getPolicy(config, policyIndex)
   328  	if policy == nil {
   329  		return false
   330  	}
   331  
   332  	evaluationInterval, ok := policy["evaluationInterval"].(map[string]interface{})
   333  	if !ok {
   334  		return false
   335  	}
   336  
   337  	_, set := evaluationInterval[complianceType].(string)
   338  
   339  	return set
   340  }
   341  
   342  // isEvaluationIntervalSetManifest will return whether the evaluation interval of the specified manifest
   343  // of the specified policy is set in the Policy Generator configuration YAML.
   344  func isEvaluationIntervalSetManifest(
   345  	config map[string]interface{}, policyIndex int, manifestIndex int, complianceType string,
   346  ) bool {
   347  	policy := getPolicy(config, policyIndex)
   348  	if policy == nil {
   349  		return false
   350  	}
   351  
   352  	manifests, ok := policy["manifests"].([]interface{})
   353  	if !ok {
   354  		return false
   355  	}
   356  
   357  	if len(manifests)-1 < manifestIndex {
   358  		return false
   359  	}
   360  
   361  	manifest, ok := manifests[manifestIndex].(map[string]interface{})
   362  	if !ok {
   363  		return false
   364  	}
   365  
   366  	evaluationInterval, ok := manifest["evaluationInterval"].(map[string]interface{})
   367  	if !ok {
   368  		return false
   369  	}
   370  
   371  	_, set := evaluationInterval[complianceType].(string)
   372  
   373  	return set
   374  }
   375  
   376  func isPolicyFieldSet(config map[string]interface{}, policyIndex int, field string) bool {
   377  	policy := getPolicy(config, policyIndex)
   378  	if policy == nil {
   379  		return false
   380  	}
   381  
   382  	_, set := policy[field]
   383  
   384  	return set
   385  }
   386  
   387  func isManifestFieldSet(config map[string]interface{}, policyIdx, manifestIdx int, field string) bool {
   388  	policy := getPolicy(config, policyIdx)
   389  	if policy == nil {
   390  		return false
   391  	}
   392  
   393  	manifests, ok := policy["manifests"].([]interface{})
   394  	if !ok {
   395  		return false
   396  	}
   397  
   398  	if len(manifests)-1 < manifestIdx {
   399  		return false
   400  	}
   401  
   402  	manifest, ok := manifests[manifestIdx].(map[string]interface{})
   403  	if !ok {
   404  		return false
   405  	}
   406  
   407  	_, set := manifest[field]
   408  
   409  	return set
   410  }
   411  
   412  // applyDefaults applies any missing defaults under Policy.PlacementBindingDefaults,
   413  // Policy.PolicyDefaults and PolicySets. It then applies the defaults and user provided
   414  // defaults on each policy and policyset entry if they are not overridden by the user. The
   415  // input unmarshaledConfig is used in situations where it is necessary to know if an explicit
   416  // false is provided rather than rely on the default Go value on the Plugin struct.
   417  func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
   418  	if len(p.Policies) == 0 {
   419  		return
   420  	}
   421  
   422  	// Set defaults to the defaults that aren't overridden
   423  	if p.PolicyDefaults.Categories == nil {
   424  		p.PolicyDefaults.Categories = defaults.Categories
   425  	}
   426  
   427  	if p.PolicyDefaults.ComplianceType == "" {
   428  		p.PolicyDefaults.ComplianceType = defaults.ComplianceType
   429  	}
   430  
   431  	if p.PolicyDefaults.Controls == nil {
   432  		p.PolicyDefaults.Controls = defaults.Controls
   433  	}
   434  
   435  	cpmValue, setCPM := getPolicyDefaultBool(unmarshaledConfig, "copyPolicyMetadata")
   436  	if setCPM {
   437  		p.PolicyDefaults.CopyPolicyMetadata = cpmValue
   438  	} else {
   439  		p.PolicyDefaults.CopyPolicyMetadata = true
   440  	}
   441  
   442  	// Policy expanders default to true unless explicitly set in the config.
   443  	// Gatekeeper policy expander policyDefault
   444  	igvValue, setIgv := getPolicyDefaultBool(unmarshaledConfig, "informGatekeeperPolicies")
   445  	if setIgv {
   446  		p.PolicyDefaults.InformGatekeeperPolicies = igvValue
   447  	} else {
   448  		p.PolicyDefaults.InformGatekeeperPolicies = true
   449  	}
   450  	// Kyverno policy expander policyDefault
   451  	ikvValue, setIkv := getPolicyDefaultBool(unmarshaledConfig, "informKyvernoPolicies")
   452  	if setIkv {
   453  		p.PolicyDefaults.InformKyvernoPolicies = ikvValue
   454  	} else {
   455  		p.PolicyDefaults.InformKyvernoPolicies = true
   456  	}
   457  
   458  	consolidatedValue, setConsolidated := getPolicyDefaultBool(unmarshaledConfig, "consolidateManifests")
   459  	if setConsolidated {
   460  		p.PolicyDefaults.ConsolidateManifests = consolidatedValue
   461  	} else {
   462  		p.PolicyDefaults.ConsolidateManifests = true
   463  	}
   464  
   465  	if p.PolicyDefaults.RemediationAction == "" {
   466  		p.PolicyDefaults.RemediationAction = defaults.RemediationAction
   467  	}
   468  
   469  	if p.PolicyDefaults.Severity == "" {
   470  		p.PolicyDefaults.Severity = defaults.Severity
   471  	}
   472  
   473  	if p.PolicyDefaults.Standards == nil {
   474  		p.PolicyDefaults.Standards = defaults.Standards
   475  	}
   476  
   477  	// GeneratePolicyPlacement defaults to true unless explicitly set in the config.
   478  	gppValue, setGpp := getPolicyDefaultBool(unmarshaledConfig, "generatePolicyPlacement")
   479  	if setGpp {
   480  		p.PolicyDefaults.GeneratePolicyPlacement = gppValue
   481  	} else {
   482  		p.PolicyDefaults.GeneratePolicyPlacement = true
   483  	}
   484  
   485  	// Generate temporary sets to later merge the policy sets declared in p.Policies[*] and p.PolicySets
   486  	plcsetToPlc := make(map[string]map[string]bool)
   487  	plcToPlcset := make(map[string]map[string]bool)
   488  
   489  	for _, plcset := range p.PolicySets {
   490  		if plcsetToPlc[plcset.Name] == nil {
   491  			plcsetToPlc[plcset.Name] = make(map[string]bool)
   492  		}
   493  
   494  		for _, plc := range plcset.Policies {
   495  			plcsetToPlc[plcset.Name][plc] = true
   496  
   497  			if plcToPlcset[plc] == nil {
   498  				plcToPlcset[plc] = make(map[string]bool)
   499  			}
   500  
   501  			plcToPlcset[plc][plcset.Name] = true
   502  		}
   503  	}
   504  
   505  	applyDefaultDependencyFields(p.PolicyDefaults.Dependencies, p.PolicyDefaults.Namespace)
   506  	applyDefaultDependencyFields(p.PolicyDefaults.ExtraDependencies, p.PolicyDefaults.Namespace)
   507  
   508  	for i := range p.Policies {
   509  		policy := &p.Policies[i]
   510  
   511  		if policy.PolicyAnnotations == nil {
   512  			annotations := map[string]string{}
   513  			for k, v := range p.PolicyDefaults.PolicyAnnotations {
   514  				annotations[k] = v
   515  			}
   516  
   517  			policy.PolicyAnnotations = annotations
   518  		}
   519  
   520  		if policy.Categories == nil {
   521  			policy.Categories = p.PolicyDefaults.Categories
   522  		}
   523  
   524  		if policy.ConfigurationPolicyAnnotations == nil {
   525  			annotations := map[string]string{}
   526  			for k, v := range p.PolicyDefaults.ConfigurationPolicyAnnotations {
   527  				annotations[k] = v
   528  			}
   529  
   530  			policy.ConfigurationPolicyAnnotations = annotations
   531  		}
   532  
   533  		cpmValue, setCpm := getPolicyBool(unmarshaledConfig, i, "copyPolicyMetadata")
   534  		if setCpm {
   535  			policy.CopyPolicyMetadata = cpmValue
   536  		} else {
   537  			policy.CopyPolicyMetadata = p.PolicyDefaults.CopyPolicyMetadata
   538  		}
   539  
   540  		if policy.Standards == nil {
   541  			policy.Standards = p.PolicyDefaults.Standards
   542  		}
   543  
   544  		if policy.Controls == nil {
   545  			policy.Controls = p.PolicyDefaults.Controls
   546  		}
   547  
   548  		if policy.ComplianceType == "" {
   549  			policy.ComplianceType = p.PolicyDefaults.ComplianceType
   550  		}
   551  
   552  		if policy.MetadataComplianceType == "" && p.PolicyDefaults.MetadataComplianceType != "" {
   553  			policy.MetadataComplianceType = p.PolicyDefaults.MetadataComplianceType
   554  		}
   555  
   556  		// Only use the policyDefault evaluationInterval value when it's not explicitly set on the policy.
   557  		if policy.EvaluationInterval.Compliant == "" {
   558  			set := isEvaluationIntervalSet(unmarshaledConfig, i, "compliant")
   559  			if !set {
   560  				policy.EvaluationInterval.Compliant = p.PolicyDefaults.EvaluationInterval.Compliant
   561  			}
   562  		}
   563  
   564  		if policy.EvaluationInterval.NonCompliant == "" {
   565  			set := isEvaluationIntervalSet(unmarshaledConfig, i, "noncompliant")
   566  			if !set {
   567  				policy.EvaluationInterval.NonCompliant = p.PolicyDefaults.EvaluationInterval.NonCompliant
   568  			}
   569  		}
   570  
   571  		if policy.PruneObjectBehavior == "" {
   572  			policy.PruneObjectBehavior = p.PolicyDefaults.PruneObjectBehavior
   573  		}
   574  
   575  		if policy.PolicySets == nil {
   576  			policy.PolicySets = p.PolicyDefaults.PolicySets
   577  		}
   578  
   579  		// GeneratePolicyPlacement defaults to true unless explicitly set in the config.
   580  		gppValue, setGpp := getPolicyBool(unmarshaledConfig, i, "generatePolicyPlacement")
   581  		if setGpp {
   582  			policy.GeneratePolicyPlacement = gppValue
   583  		} else {
   584  			policy.GeneratePolicyPlacement = p.PolicyDefaults.GeneratePolicyPlacement
   585  		}
   586  
   587  		// GeneratePlacementWhenInSet defaults to false unless explicitly set in the config.
   588  		gpsetValue, setGpset := getPolicyBool(unmarshaledConfig, i, "generatePlacementWhenInSet")
   589  		if setGpset {
   590  			policy.GeneratePlacementWhenInSet = gpsetValue
   591  		} else {
   592  			policy.GeneratePlacementWhenInSet = p.PolicyDefaults.GeneratePlacementWhenInSet
   593  		}
   594  
   595  		// Policy expanders default to the policy default unless explicitly set.
   596  		// Gatekeeper policy expander policy override
   597  		igvValue, setIgv := getPolicyBool(unmarshaledConfig, i, "informGatekeeperPolicies")
   598  		if setIgv {
   599  			policy.InformGatekeeperPolicies = igvValue
   600  		} else {
   601  			policy.InformGatekeeperPolicies = p.PolicyDefaults.InformGatekeeperPolicies
   602  		}
   603  		// Kyverno policy expander policy override
   604  		ikvValue, setIkv := getPolicyBool(unmarshaledConfig, i, "informKyvernoPolicies")
   605  		if setIkv {
   606  			policy.InformKyvernoPolicies = ikvValue
   607  		} else {
   608  			policy.InformKyvernoPolicies = p.PolicyDefaults.InformKyvernoPolicies
   609  		}
   610  
   611  		consolidatedValue, setConsolidated := getPolicyBool(unmarshaledConfig, i, "consolidateManifests")
   612  		if setConsolidated {
   613  			policy.ConsolidateManifests = consolidatedValue
   614  		} else {
   615  			policy.ConsolidateManifests = p.PolicyDefaults.ConsolidateManifests
   616  		}
   617  
   618  		disabledValue, setDisabled := getPolicyBool(unmarshaledConfig, i, "disabled")
   619  		if setDisabled {
   620  			policy.Disabled = disabledValue
   621  		} else {
   622  			policy.Disabled = p.PolicyDefaults.Disabled
   623  		}
   624  
   625  		ignorePending, ignorePendingIsSet := getPolicyBool(unmarshaledConfig, i, "ignorePending")
   626  		if ignorePendingIsSet {
   627  			policy.IgnorePending = ignorePending
   628  		} else {
   629  			policy.IgnorePending = p.PolicyDefaults.IgnorePending
   630  		}
   631  
   632  		if isPolicyFieldSet(unmarshaledConfig, i, "dependencies") {
   633  			applyDefaultDependencyFields(policy.Dependencies, p.PolicyDefaults.Namespace)
   634  		} else {
   635  			policy.Dependencies = p.PolicyDefaults.Dependencies
   636  		}
   637  
   638  		if isPolicyFieldSet(unmarshaledConfig, i, "extraDependencies") {
   639  			applyDefaultDependencyFields(policy.ExtraDependencies, p.PolicyDefaults.Namespace)
   640  		} else {
   641  			policy.ExtraDependencies = p.PolicyDefaults.ExtraDependencies
   642  		}
   643  
   644  		if !isPolicyFieldSet(unmarshaledConfig, i, "orderManifests") {
   645  			policy.OrderManifests = p.PolicyDefaults.OrderManifests
   646  		}
   647  
   648  		applyDefaultPlacementFields(&policy.Placement, p.PolicyDefaults.Placement)
   649  
   650  		// Only use defaults when the namespaceSelector is not set on the policy
   651  		nsSelector := policy.NamespaceSelector
   652  		defNsSelector := p.PolicyDefaults.NamespaceSelector
   653  
   654  		if nsSelector.Exclude == nil && nsSelector.Include == nil &&
   655  			nsSelector.MatchLabels == nil && nsSelector.MatchExpressions == nil {
   656  			policy.NamespaceSelector = defNsSelector
   657  		}
   658  
   659  		if policy.RemediationAction == "" {
   660  			policy.RemediationAction = p.PolicyDefaults.RemediationAction
   661  		}
   662  
   663  		if policy.Severity == "" {
   664  			policy.Severity = p.PolicyDefaults.Severity
   665  		}
   666  
   667  		for j := range policy.Manifests {
   668  			manifest := &policy.Manifests[j]
   669  
   670  			if manifest.ComplianceType == "" {
   671  				manifest.ComplianceType = policy.ComplianceType
   672  			}
   673  
   674  			if manifest.MetadataComplianceType == "" && policy.MetadataComplianceType != "" {
   675  				manifest.MetadataComplianceType = policy.MetadataComplianceType
   676  			}
   677  
   678  			// If the manifests are consolidated to a single ConfigurationPolicy object, don't set
   679  			// ConfigurationPolicy options per manifest.
   680  			if policy.ConsolidateManifests {
   681  				continue
   682  			}
   683  
   684  			// Only use the policy's ConfigurationPolicyOptions values when they're not explicitly set in the manifest.
   685  			if manifest.EvaluationInterval.Compliant == "" {
   686  				set := isEvaluationIntervalSetManifest(unmarshaledConfig, i, j, "compliant")
   687  				if !set {
   688  					manifest.EvaluationInterval.Compliant = policy.EvaluationInterval.Compliant
   689  				}
   690  			}
   691  
   692  			if manifest.EvaluationInterval.NonCompliant == "" {
   693  				set := isEvaluationIntervalSetManifest(unmarshaledConfig, i, j, "noncompliant")
   694  				if !set {
   695  					manifest.EvaluationInterval.NonCompliant = policy.EvaluationInterval.NonCompliant
   696  				}
   697  			}
   698  
   699  			selector := manifest.NamespaceSelector
   700  			if selector.Exclude == nil && selector.Include == nil &&
   701  				selector.MatchLabels == nil && selector.MatchExpressions == nil {
   702  				manifest.NamespaceSelector = policy.NamespaceSelector
   703  			}
   704  
   705  			if manifest.RemediationAction == "" && policy.RemediationAction != "" {
   706  				manifest.RemediationAction = policy.RemediationAction
   707  			}
   708  
   709  			if manifest.PruneObjectBehavior == "" && policy.PruneObjectBehavior != "" {
   710  				manifest.PruneObjectBehavior = policy.PruneObjectBehavior
   711  			}
   712  
   713  			if manifest.Severity == "" && manifest.Severity != "" {
   714  				manifest.Severity = policy.Severity
   715  			}
   716  
   717  			if isManifestFieldSet(unmarshaledConfig, i, j, "extraDependencies") {
   718  				applyDefaultDependencyFields(manifest.ExtraDependencies, p.PolicyDefaults.Namespace)
   719  			} else {
   720  				manifest.ExtraDependencies = policy.ExtraDependencies
   721  			}
   722  
   723  			if !isManifestFieldSet(unmarshaledConfig, i, j, "ignorePending") {
   724  				manifest.IgnorePending = policy.IgnorePending
   725  			}
   726  		}
   727  
   728  		for _, plcsetInPlc := range policy.PolicySets {
   729  			if _, ok := plcsetToPlc[plcsetInPlc]; !ok {
   730  				newPlcset := types.PolicySetConfig{
   731  					Name: plcsetInPlc,
   732  				}
   733  				p.PolicySets = append(p.PolicySets, newPlcset)
   734  				plcsetToPlc[plcsetInPlc] = make(map[string]bool)
   735  			}
   736  
   737  			if plcToPlcset[policy.Name] == nil {
   738  				plcToPlcset[policy.Name] = make(map[string]bool)
   739  			}
   740  
   741  			plcToPlcset[policy.Name][plcsetInPlc] = true
   742  
   743  			plcsetToPlc[plcsetInPlc][policy.Name] = true
   744  		}
   745  
   746  		policy.PolicySets = make([]string, 0, len(plcToPlcset[policy.Name]))
   747  
   748  		for plcset := range plcToPlcset[policy.Name] {
   749  			policy.PolicySets = append(policy.PolicySets, plcset)
   750  		}
   751  	}
   752  
   753  	gpspValue, setGpsp := getPolicySetDefaultBool(unmarshaledConfig, "generatePolicySetPlacement")
   754  	if setGpsp {
   755  		p.PolicySetDefaults.GeneratePolicySetPlacement = gpspValue
   756  	} else {
   757  		p.PolicySetDefaults.GeneratePolicySetPlacement = true
   758  	}
   759  
   760  	// Sync up the declared policy sets in p.Policies[*]
   761  	for i := range p.PolicySets {
   762  		plcset := &p.PolicySets[i]
   763  		plcset.Policies = make([]string, 0, len(plcsetToPlc[plcset.Name]))
   764  
   765  		for plc := range plcsetToPlc[plcset.Name] {
   766  			plcset.Policies = append(plcset.Policies, plc)
   767  		}
   768  
   769  		// GeneratePolicySetPlacement defaults to true unless explicitly set in the config.
   770  		gpspValue, setGpsp := getPolicySetBool(unmarshaledConfig, i, "generatePolicySetPlacement")
   771  		if setGpsp {
   772  			plcset.GeneratePolicySetPlacement = gpspValue
   773  		} else {
   774  			plcset.GeneratePolicySetPlacement = p.PolicySetDefaults.GeneratePolicySetPlacement
   775  		}
   776  
   777  		applyDefaultPlacementFields(&plcset.Placement, p.PolicySetDefaults.Placement)
   778  
   779  		// Sort alphabetically to make it deterministic
   780  		sort.Strings(plcset.Policies)
   781  	}
   782  }
   783  
   784  func applyDefaultDependencyFields(deps []types.PolicyDependency, namespace string) {
   785  	for i, dep := range deps {
   786  		if dep.Namespace == "" {
   787  			deps[i].Namespace = namespace
   788  		}
   789  
   790  		if dep.Compliance == "" {
   791  			deps[i].Compliance = "Compliant"
   792  		}
   793  
   794  		if dep.Kind == "" {
   795  			deps[i].Kind = policyKind
   796  		}
   797  
   798  		if dep.APIVersion == "" {
   799  			deps[i].APIVersion = policyAPIVersion
   800  		}
   801  	}
   802  }
   803  
   804  // applyDefaultPlacementFields is a helper for applyDefaults that handles default Placement configuration
   805  func applyDefaultPlacementFields(placement *types.PlacementConfig, defaultPlacement types.PlacementConfig) {
   806  	// Determine whether defaults are set for placement
   807  	plcDefaultSet := len(defaultPlacement.LabelSelector) != 0 ||
   808  		defaultPlacement.PlacementPath != "" ||
   809  		defaultPlacement.PlacementName != ""
   810  	plrDefaultSet := len(defaultPlacement.ClusterSelectors) != 0 ||
   811  		len(defaultPlacement.ClusterSelector) != 0 ||
   812  		defaultPlacement.PlacementRulePath != "" ||
   813  		defaultPlacement.PlacementRuleName != ""
   814  
   815  	// If both cluster label selectors and placement path/name aren't set, then use the defaults with a
   816  	// priority on placement path followed by placement name.
   817  	if len(placement.LabelSelector) == 0 &&
   818  		placement.PlacementPath == "" &&
   819  		placement.PlacementName == "" &&
   820  		plcDefaultSet {
   821  		if defaultPlacement.PlacementPath != "" {
   822  			placement.PlacementPath = defaultPlacement.PlacementPath
   823  		} else if defaultPlacement.PlacementName != "" {
   824  			placement.PlacementName = defaultPlacement.PlacementName
   825  		} else if len(defaultPlacement.LabelSelector) > 0 {
   826  			placement.LabelSelector = defaultPlacement.LabelSelector
   827  		}
   828  	} else if len(placement.ClusterSelectors) == 0 &&
   829  		// Else if both cluster selectors and placement rule path/name aren't set, then use the defaults with a
   830  		// priority on placement rule path followed by placement rule name.
   831  		len(placement.ClusterSelector) == 0 &&
   832  		placement.PlacementRulePath == "" &&
   833  		placement.PlacementRuleName == "" &&
   834  		plrDefaultSet {
   835  		if defaultPlacement.PlacementRulePath != "" {
   836  			placement.PlacementRulePath = defaultPlacement.PlacementRulePath
   837  		} else if defaultPlacement.PlacementRuleName != "" {
   838  			placement.PlacementRuleName = defaultPlacement.PlacementRuleName
   839  		} else if len(defaultPlacement.ClusterSelectors) > 0 {
   840  			placement.ClusterSelectors = defaultPlacement.ClusterSelectors
   841  		} else if len(defaultPlacement.ClusterSelector) > 0 {
   842  			placement.ClusterSelector = defaultPlacement.ClusterSelector
   843  		}
   844  	}
   845  }
   846  
   847  // assertValidConfig verifies that the user provided configuration has all the
   848  // required fields. Note that this should be run only after applyDefaults is run.
   849  func (p *Plugin) assertValidConfig() error {
   850  	if p.PolicyDefaults.Namespace == "" {
   851  		return errors.New("policyDefaults.namespace is empty but it must be set")
   852  	}
   853  
   854  	// Validate default policy placement settings
   855  	err := p.assertValidPlacement(p.PolicyDefaults.Placement, "policyDefaults", nil)
   856  	if err != nil {
   857  		return err
   858  	}
   859  
   860  	// validate placement binding names are DNS compliant
   861  	if p.PlacementBindingDefaults.Name != "" &&
   862  		len(validation.IsDNS1123Subdomain(p.PlacementBindingDefaults.Name)) > 0 {
   863  		return fmt.Errorf(
   864  			"PlacementBindingDefaults.Name `%s` is not DNS compliant. See %s",
   865  			p.PlacementBindingDefaults.Name,
   866  			dnsReference,
   867  		)
   868  	}
   869  
   870  	if len(p.Policies) == 0 {
   871  		return errors.New("policies is empty but it must be set")
   872  	}
   873  
   874  	if p.PolicyDefaults.OrderPolicies && len(p.PolicyDefaults.Dependencies) != 0 {
   875  		return errors.New("policyDefaults must specify only one of dependencies or orderPolicies")
   876  	}
   877  
   878  	for i, dep := range p.PolicyDefaults.Dependencies {
   879  		if dep.Name == "" {
   880  			return fmt.Errorf("dependency name must be set in policyDefaults dependency %v", i)
   881  		}
   882  	}
   883  
   884  	if p.PolicyDefaults.OrderManifests && p.PolicyDefaults.ConsolidateManifests {
   885  		return errors.New("policyDefaults may not specify both consolidateManifests and orderManifests")
   886  	}
   887  
   888  	if len(p.PolicyDefaults.ExtraDependencies) > 0 && p.PolicyDefaults.OrderManifests {
   889  		return errors.New("policyDefaults may not specify both extraDependencies and orderManifests")
   890  	}
   891  
   892  	for i, dep := range p.PolicyDefaults.ExtraDependencies {
   893  		if dep.Name == "" {
   894  			return fmt.Errorf("extraDependency name must be set in policyDefaults extraDependency %v", i)
   895  		}
   896  	}
   897  
   898  	seenPlc := map[string]bool{}
   899  	plCount := struct {
   900  		plc int
   901  		plr int
   902  	}{}
   903  
   904  	for i := range p.Policies {
   905  		policy := &p.Policies[i]
   906  		if policy.Name == "" {
   907  			return fmt.Errorf(
   908  				"each policy must have a name set, but did not find a name at policy array index %d", i,
   909  			)
   910  		}
   911  
   912  		if len(validation.IsDNS1123Subdomain(policy.Name)) > 0 {
   913  			return fmt.Errorf(
   914  				"policy name `%s` is not DNS compliant. See %s", policy.Name, dnsReference,
   915  			)
   916  		}
   917  
   918  		if seenPlc[policy.Name] {
   919  			return fmt.Errorf(
   920  				"each policy must have a unique name set, but found a duplicate name: %s", policy.Name,
   921  			)
   922  		}
   923  
   924  		seenPlc[policy.Name] = true
   925  
   926  		if len(p.PolicyDefaults.Namespace+"."+policy.Name) > maxObjectNameLength {
   927  			return fmt.Errorf("the policy namespace and name cannot be more than 63 characters: %s.%s",
   928  				p.PolicyDefaults.Namespace, policy.Name)
   929  		}
   930  
   931  		if policy.EvaluationInterval.Compliant != "" && policy.EvaluationInterval.Compliant != "never" {
   932  			_, err := time.ParseDuration(policy.EvaluationInterval.Compliant)
   933  			if err != nil {
   934  				return fmt.Errorf(
   935  					"the policy %s has an invalid policy.evaluationInterval.compliant value: %w", policy.Name, err,
   936  				)
   937  			}
   938  		}
   939  
   940  		if policy.EvaluationInterval.NonCompliant != "" && policy.EvaluationInterval.NonCompliant != "never" {
   941  			_, err := time.ParseDuration(policy.EvaluationInterval.NonCompliant)
   942  			if err != nil {
   943  				return fmt.Errorf(
   944  					"the policy %s has an invalid policy.evaluationInterval.noncompliant value: %w", policy.Name, err,
   945  				)
   946  			}
   947  		}
   948  
   949  		if len(policy.Manifests) == 0 {
   950  			return fmt.Errorf(
   951  				"each policy must have at least one manifest, but found none in policy %s", policy.Name,
   952  			)
   953  		}
   954  
   955  		if len(policy.Dependencies) > 0 && p.PolicyDefaults.OrderPolicies {
   956  			return fmt.Errorf(
   957  				"dependencies may not be set in policy %v when policyDefaults.orderPolicies is true", policy.Name,
   958  			)
   959  		}
   960  
   961  		for x, dep := range policy.Dependencies {
   962  			if dep.Name == "" {
   963  				return fmt.Errorf("dependency name must be set in policy %v dependency %v", policy.Name, x)
   964  			}
   965  		}
   966  
   967  		if policy.ConsolidateManifests && policy.OrderManifests {
   968  			return fmt.Errorf("policy %v may not set orderManifests when consolidateManifests is true", policy.Name)
   969  		}
   970  
   971  		if len(policy.ExtraDependencies) > 0 && policy.OrderManifests {
   972  			return fmt.Errorf("extraDependencies may not be set in policy %v when orderManifests is true", policy.Name)
   973  		}
   974  
   975  		for x, dep := range policy.ExtraDependencies {
   976  			if dep.Name == "" {
   977  				return fmt.Errorf("extraDependency name must be set in policy %v extraDependency %v", policy.Name, x)
   978  			}
   979  		}
   980  
   981  		for j := range policy.Manifests {
   982  			manifest := &policy.Manifests[j]
   983  
   984  			if manifest.Path == "" {
   985  				return fmt.Errorf(
   986  					"each policy manifest entry must have path set, but did not find a path in policy %s",
   987  					policy.Name,
   988  				)
   989  			}
   990  
   991  			_, err := os.Stat(manifest.Path)
   992  			if err != nil {
   993  				return fmt.Errorf(
   994  					"could not read the manifest path %s in policy %s", manifest.Path, policy.Name,
   995  				)
   996  			}
   997  
   998  			err = verifyManifestPath(p.baseDirectory, manifest.Path)
   999  			if err != nil {
  1000  				return err
  1001  			}
  1002  
  1003  			evalInterval := manifest.EvaluationInterval
  1004  
  1005  			// Verify that consolidated manifests don't specify fields
  1006  			// that can't be overridden at the objectTemplate level
  1007  			if policy.ConsolidateManifests {
  1008  				errorMsgFmt := fmt.Sprintf(
  1009  					"the policy %s has the %%s value set on manifest[%d] but consolidateManifests is true",
  1010  					policy.Name, j,
  1011  				)
  1012  
  1013  				if evalInterval.Compliant != "" || evalInterval.NonCompliant != "" {
  1014  					return fmt.Errorf(errorMsgFmt, "evaluationInterval")
  1015  				}
  1016  
  1017  				selector := manifest.NamespaceSelector
  1018  				if selector.Exclude != nil || selector.Include != nil ||
  1019  					selector.MatchLabels != nil || selector.MatchExpressions != nil {
  1020  					return fmt.Errorf(errorMsgFmt, "namespaceSelector")
  1021  				}
  1022  
  1023  				if manifest.PruneObjectBehavior != "" {
  1024  					return fmt.Errorf(errorMsgFmt, "pruneObjectBehavior")
  1025  				}
  1026  
  1027  				if manifest.RemediationAction != "" {
  1028  					return fmt.Errorf(errorMsgFmt, "remediationAction")
  1029  				}
  1030  
  1031  				if manifest.Severity != "" {
  1032  					return fmt.Errorf(errorMsgFmt, "severity")
  1033  				}
  1034  
  1035  				if len(manifest.ExtraDependencies) != 0 {
  1036  					return fmt.Errorf(errorMsgFmt, "extraDependencies")
  1037  				}
  1038  
  1039  				if manifest.IgnorePending {
  1040  					return fmt.Errorf(errorMsgFmt, "ignorePending")
  1041  				}
  1042  			}
  1043  
  1044  			if evalInterval.Compliant != "" && evalInterval.Compliant != "never" {
  1045  				_, err := time.ParseDuration(evalInterval.Compliant)
  1046  				if err != nil {
  1047  					return fmt.Errorf(
  1048  						"the policy %s has an invalid policy.evaluationInterval.manifest[%d].compliant value: %w",
  1049  						policy.Name,
  1050  						j,
  1051  						err,
  1052  					)
  1053  				}
  1054  			}
  1055  
  1056  			if evalInterval.NonCompliant != "" && evalInterval.NonCompliant != "never" {
  1057  				_, err := time.ParseDuration(evalInterval.NonCompliant)
  1058  				if err != nil {
  1059  					return fmt.Errorf(
  1060  						"the policy %s has an invalid policy.evaluationInterval.manifest[%d].noncompliant value: %w",
  1061  						policy.Name,
  1062  						j,
  1063  						err,
  1064  					)
  1065  				}
  1066  			}
  1067  
  1068  			if len(manifest.ExtraDependencies) > 0 && policy.OrderManifests {
  1069  				return fmt.Errorf(
  1070  					"extraDependencies may not be set in policy %v manifest[%d] because orderManifests is set",
  1071  					policy.Name,
  1072  					j,
  1073  				)
  1074  			}
  1075  
  1076  			for x, dep := range manifest.ExtraDependencies {
  1077  				if dep.Name == "" {
  1078  					return fmt.Errorf(
  1079  						"extraDependency name must be set in policy %v manifest[%d] extraDependency %v",
  1080  						policy.Name, j, x)
  1081  				}
  1082  			}
  1083  		}
  1084  
  1085  		err := p.assertValidPlacement(policy.Placement, fmt.Sprintf("policy %s", policy.Name), &plCount)
  1086  		if err != nil {
  1087  			return err
  1088  		}
  1089  	}
  1090  
  1091  	// Validate default policy set placement settings
  1092  	err = p.assertValidPlacement(p.PolicySetDefaults.Placement, "policySetDefaults", nil)
  1093  	if err != nil {
  1094  		return err
  1095  	}
  1096  
  1097  	seenPlcset := map[string]bool{}
  1098  
  1099  	for i := range p.PolicySets {
  1100  		plcset := &p.PolicySets[i]
  1101  
  1102  		if plcset.Name == "" {
  1103  			return fmt.Errorf(
  1104  				"each policySet must have a name set, but did not find a name at policySet array index %d", i,
  1105  			)
  1106  		}
  1107  
  1108  		if len(validation.IsDNS1123Subdomain(plcset.Name)) > 0 {
  1109  			return fmt.Errorf(
  1110  				"policy set name `%s` is not DNS compliant. See %s", plcset.Name, dnsReference,
  1111  			)
  1112  		}
  1113  
  1114  		if seenPlcset[plcset.Name] {
  1115  			return fmt.Errorf(
  1116  				"each policySet must have a unique name set, but found a duplicate name: %s", plcset.Name,
  1117  			)
  1118  		}
  1119  
  1120  		seenPlcset[plcset.Name] = true
  1121  
  1122  		// Validate policy set Placement settings
  1123  		err := p.assertValidPlacement(plcset.Placement, fmt.Sprintf("policySet %s", plcset.Name), &plCount)
  1124  		if err != nil {
  1125  			return err
  1126  		}
  1127  	}
  1128  
  1129  	// Validate only one type of placement kind is in use
  1130  	if plCount.plc != 0 && plCount.plr != 0 {
  1131  		return fmt.Errorf(
  1132  			"may not use a mix of Placement and PlacementRule for policies and policysets; found %d Placement and "+
  1133  				"%d PlacementRule",
  1134  			plCount.plc, plCount.plr,
  1135  		)
  1136  	}
  1137  
  1138  	p.usingPlR = plCount.plc == 0
  1139  
  1140  	return nil
  1141  }
  1142  
  1143  // assertValidPlacement is a helper for assertValidConfig to verify placement configurations
  1144  func (p *Plugin) assertValidPlacement(
  1145  	placement types.PlacementConfig,
  1146  	path string,
  1147  	plCount *struct {
  1148  		plc int
  1149  		plr int
  1150  	},
  1151  ) error {
  1152  	if placement.PlacementRulePath != "" && placement.PlacementPath != "" {
  1153  		return fmt.Errorf(
  1154  			"%s must provide only one of placement.placementPath or placement.placementRulePath", path,
  1155  		)
  1156  	}
  1157  
  1158  	if (len(placement.ClusterSelectors) > 0 || len(placement.ClusterSelector) > 0) &&
  1159  		len(placement.LabelSelector) > 0 {
  1160  		return fmt.Errorf(
  1161  			"%s must provide only one of placement.labelSelector or placement.clusterSelectors", path,
  1162  		)
  1163  	}
  1164  
  1165  	if placement.PlacementRuleName != "" && placement.PlacementName != "" {
  1166  		return fmt.Errorf(
  1167  			"%s must provide only one of placement.placementName or placement.placementRuleName", path,
  1168  		)
  1169  	}
  1170  
  1171  	placementOptionCount := 0
  1172  	if len(placement.LabelSelector) != 0 || len(placement.ClusterSelectors) != 0 ||
  1173  		len(placement.ClusterSelector) != 0 {
  1174  		placementOptionCount++
  1175  	}
  1176  
  1177  	if placement.PlacementRulePath != "" || placement.PlacementPath != "" {
  1178  		placementOptionCount++
  1179  	}
  1180  
  1181  	if placement.PlacementRuleName != "" || placement.PlacementName != "" {
  1182  		placementOptionCount++
  1183  	}
  1184  
  1185  	if placementOptionCount > 1 {
  1186  		return fmt.Errorf(
  1187  			"%s must specify only one of placement selector, placement path, or placement name", path,
  1188  		)
  1189  	}
  1190  
  1191  	// validate placement names are DNS compliant
  1192  	defPlrName := placement.PlacementRuleName
  1193  	if defPlrName != "" && len(validation.IsDNS1123Subdomain(defPlrName)) > 0 {
  1194  		return fmt.Errorf(
  1195  			"%s placement.placementRuleName placement name `%s` is not DNS compliant. See %s",
  1196  			path,
  1197  			defPlrName,
  1198  			dnsReference,
  1199  		)
  1200  	}
  1201  
  1202  	defPlcmtPlName := placement.PlacementName
  1203  	if defPlcmtPlName != "" && len(validation.IsDNS1123Subdomain(defPlcmtPlName)) > 0 {
  1204  		return fmt.Errorf(
  1205  			"%s placement.placementName `%s` is not DNS compliant. See %s",
  1206  			path,
  1207  			defPlcmtPlName,
  1208  			dnsReference,
  1209  		)
  1210  	}
  1211  
  1212  	defPlName := placement.Name
  1213  	if defPlName != "" && len(validation.IsDNS1123Subdomain(defPlName)) > 0 {
  1214  		return fmt.Errorf(
  1215  			"%s placement.name `%s` is not DNS compliant. See %s", path, defPlName, dnsReference,
  1216  		)
  1217  	}
  1218  
  1219  	if placement.PlacementRulePath != "" {
  1220  		_, err := os.Stat(placement.PlacementRulePath)
  1221  		if err != nil {
  1222  			return fmt.Errorf(
  1223  				"%s placement.placementRulePath could not read the path %s",
  1224  				path, placement.PlacementRulePath,
  1225  			)
  1226  		}
  1227  	}
  1228  
  1229  	if placement.PlacementPath != "" {
  1230  		_, err := os.Stat(placement.PlacementPath)
  1231  		if err != nil {
  1232  			return fmt.Errorf(
  1233  				"%s placement.placementPath could not read the path %s",
  1234  				path, placement.PlacementPath,
  1235  			)
  1236  		}
  1237  	}
  1238  
  1239  	if plCount != nil {
  1240  		foundPl := false
  1241  
  1242  		if len(placement.LabelSelector) != 0 ||
  1243  			placement.PlacementPath != "" ||
  1244  			placement.PlacementName != "" {
  1245  			plCount.plc++
  1246  
  1247  			foundPl = true
  1248  		}
  1249  
  1250  		if len(placement.ClusterSelectors) != 0 ||
  1251  			len(placement.ClusterSelector) != 0 ||
  1252  			placement.PlacementRulePath != "" ||
  1253  			placement.PlacementRuleName != "" {
  1254  			plCount.plr++
  1255  
  1256  			if foundPl {
  1257  				return fmt.Errorf(
  1258  					"%s may not use both Placement and PlacementRule kinds", path,
  1259  				)
  1260  			}
  1261  		}
  1262  	}
  1263  
  1264  	if len(placement.ClusterSelectors) > 0 && len(placement.ClusterSelector) > 0 {
  1265  		return fmt.Errorf("cannot use both clusterSelector and clusterSelectors in %s placement config "+
  1266  			"(clusterSelector is recommended since it matches the actual placement field)", path)
  1267  	}
  1268  
  1269  	// Determine which selectors to use
  1270  	var resolvedSelectors map[string]interface{}
  1271  	if len(placement.ClusterSelectors) > 0 {
  1272  		resolvedSelectors = placement.ClusterSelectors
  1273  	} else if len(placement.ClusterSelector) > 0 {
  1274  		resolvedSelectors = placement.ClusterSelector
  1275  	} else if len(placement.LabelSelector) > 0 {
  1276  		resolvedSelectors = placement.LabelSelector
  1277  	}
  1278  
  1279  	_, err := p.generateSelector(resolvedSelectors)
  1280  	if err != nil {
  1281  		return fmt.Errorf("%s placement has invalid selectors: %w", path, err)
  1282  	}
  1283  
  1284  	return nil
  1285  }
  1286  
  1287  // createPolicy will generate the root policy based on the PolicyGenerator configuration.
  1288  // The generated policy is written to the plugin's output buffer. An error is returned if the
  1289  // manifests specified in the configuration are invalid or can't be read.
  1290  func (p *Plugin) createPolicy(policyConf *types.PolicyConfig) error {
  1291  	policyTemplates, err := getPolicyTemplates(policyConf)
  1292  	if err != nil {
  1293  		return err
  1294  	}
  1295  
  1296  	if policyConf.PolicyAnnotations == nil {
  1297  		policyConf.PolicyAnnotations = map[string]string{}
  1298  	}
  1299  
  1300  	policyConf.PolicyAnnotations["policy.open-cluster-management.io/categories"] = strings.Join(
  1301  		policyConf.Categories, ",",
  1302  	)
  1303  	policyConf.PolicyAnnotations["policy.open-cluster-management.io/controls"] = strings.Join(
  1304  		policyConf.Controls, ",",
  1305  	)
  1306  	policyConf.PolicyAnnotations["policy.open-cluster-management.io/standards"] = strings.Join(
  1307  		policyConf.Standards, ",",
  1308  	)
  1309  
  1310  	spec := map[string]interface{}{
  1311  		"disabled":         policyConf.Disabled,
  1312  		"policy-templates": policyTemplates,
  1313  	}
  1314  
  1315  	if p.PolicyDefaults.OrderPolicies && p.previousPolicyName != "" {
  1316  		policyConf.Dependencies = []types.PolicyDependency{{
  1317  			Name:       p.previousPolicyName,
  1318  			Namespace:  p.PolicyDefaults.Namespace,
  1319  			Compliance: "Compliant",
  1320  			Kind:       policyKind,
  1321  			APIVersion: policyAPIVersion,
  1322  		}}
  1323  	}
  1324  
  1325  	p.previousPolicyName = policyConf.Name
  1326  
  1327  	if len(policyConf.Dependencies) != 0 {
  1328  		spec["dependencies"] = policyConf.Dependencies
  1329  	}
  1330  
  1331  	// When copyPolicyMetadata is unset, it defaults to the behavior of true, so this leaves it out entirely when set to
  1332  	// true to avoid unnecessarily including it in the Policy YAML.
  1333  	if !policyConf.CopyPolicyMetadata {
  1334  		spec["copyPolicyMetadata"] = false
  1335  	}
  1336  
  1337  	policy := map[string]interface{}{
  1338  		"apiVersion": policyAPIVersion,
  1339  		"kind":       policyKind,
  1340  		"metadata": map[string]interface{}{
  1341  			"annotations": policyConf.PolicyAnnotations,
  1342  			"name":        policyConf.Name,
  1343  			"namespace":   p.PolicyDefaults.Namespace,
  1344  		},
  1345  		"spec": spec,
  1346  	}
  1347  
  1348  	// set the root policy remediation action if all the remediation actions match
  1349  	if rootRemediationAction := getRootRemediationAction(policyTemplates); rootRemediationAction != "" {
  1350  		policy["spec"].(map[string]interface{})["remediationAction"] = rootRemediationAction
  1351  	}
  1352  
  1353  	policyYAML, err := yaml.Marshal(policy)
  1354  	if err != nil {
  1355  		return fmt.Errorf(
  1356  			"an unexpected error occurred when converting the policy to YAML: %w", err,
  1357  		)
  1358  	}
  1359  
  1360  	p.outputBuffer.Write([]byte("---\n"))
  1361  	p.outputBuffer.Write(policyYAML)
  1362  
  1363  	return nil
  1364  }
  1365  
  1366  // createPolicySet will generate the policyset based on the Policy Generator configuration.
  1367  // The generated policyset is written to the plugin's output buffer. An error is returned if the
  1368  // manifests specified in the configuration are invalid or can't be read.
  1369  func (p *Plugin) createPolicySet(policySetConf *types.PolicySetConfig) error {
  1370  	policyset := map[string]interface{}{
  1371  		"apiVersion": policySetAPIVersion,
  1372  		"kind":       policySetKind,
  1373  		"metadata": map[string]interface{}{
  1374  			"name":      policySetConf.Name,
  1375  			"namespace": p.PolicyDefaults.Namespace, // policyset should be generated in the same namespace of policy
  1376  		},
  1377  		"spec": map[string]interface{}{
  1378  			"description": policySetConf.Description,
  1379  			"policies":    policySetConf.Policies,
  1380  		},
  1381  	}
  1382  
  1383  	policysetYAML, err := yaml.Marshal(policyset)
  1384  	if err != nil {
  1385  		return fmt.Errorf(
  1386  			"an unexpected error occurred when converting the policyset to YAML: %w", err,
  1387  		)
  1388  	}
  1389  
  1390  	p.outputBuffer.Write([]byte("---\n"))
  1391  	p.outputBuffer.Write(policysetYAML)
  1392  
  1393  	return nil
  1394  }
  1395  
  1396  // getPlcFromPath finds the placement manifest in the input manifest file. It will return the name
  1397  // of the placement, the unmarshaled placement manifest, and an error. An error is returned if the
  1398  // placement manifest cannot be found or is invalid.
  1399  func (p *Plugin) getPlcFromPath(plcPath string) (string, map[string]interface{}, error) {
  1400  	manifests, err := unmarshalManifestFile(plcPath)
  1401  	if err != nil {
  1402  		return "", nil, fmt.Errorf("failed to read the placement: %w", err)
  1403  	}
  1404  
  1405  	var name string
  1406  	var placement map[string]interface{}
  1407  
  1408  	for _, manifest := range manifests {
  1409  		kind, _, _ := unstructured.NestedString(manifest, "kind")
  1410  		if kind != placementRuleKind && kind != placementKind {
  1411  			continue
  1412  		}
  1413  
  1414  		// Validate PlacementRule Kind given in manifest
  1415  		if kind == placementRuleKind {
  1416  			if !p.usingPlR {
  1417  				return "", nil, fmt.Errorf(
  1418  					"the placement %s specified a placementRule kind but expected a placement kind", plcPath,
  1419  				)
  1420  			}
  1421  		}
  1422  
  1423  		// Validate Placement Kind given in manifest
  1424  		if kind == placementKind {
  1425  			if p.usingPlR {
  1426  				return "", nil, fmt.Errorf(
  1427  					"the placement %s specified a placement kind but expected a placementRule kind", plcPath,
  1428  				)
  1429  			}
  1430  		}
  1431  
  1432  		var found bool
  1433  		name, found, err = unstructured.NestedString(manifest, "metadata", "name")
  1434  
  1435  		if !found || err != nil {
  1436  			return "", nil, fmt.Errorf("the placement %s must have a name set", plcPath)
  1437  		}
  1438  
  1439  		var namespace string
  1440  		namespace, found, err = unstructured.NestedString(manifest, "metadata", "namespace")
  1441  
  1442  		if !found || err != nil {
  1443  			return "", nil, fmt.Errorf("the placement %s must have a namespace set", plcPath)
  1444  		}
  1445  
  1446  		if namespace != p.PolicyDefaults.Namespace {
  1447  			err = fmt.Errorf(
  1448  				"the placement %s must have the same namespace as the policy (%s)",
  1449  				plcPath,
  1450  				p.PolicyDefaults.Namespace,
  1451  			)
  1452  
  1453  			return "", nil, err
  1454  		}
  1455  
  1456  		placement = manifest
  1457  
  1458  		break
  1459  	}
  1460  
  1461  	if name == "" {
  1462  		err = fmt.Errorf(
  1463  			"the placement manifest %s did not have a placement", plcPath,
  1464  		)
  1465  
  1466  		return "", nil, err
  1467  	}
  1468  
  1469  	return name, placement, nil
  1470  }
  1471  
  1472  // getCsKey generates the key for the policy's cluster/label selectors to be used in
  1473  // Policies.csToPlc.
  1474  func getCsKey(placementConfig types.PlacementConfig) string {
  1475  	return fmt.Sprintf("%#v", placementConfig.ClusterSelectors)
  1476  }
  1477  
  1478  // getPlcName will generate a placement name for the policy. If the placement has
  1479  // previously been generated, skip will be true.
  1480  func (p *Plugin) getPlcName(
  1481  	defaultPlacementConfig types.PlacementConfig,
  1482  	placementConfig types.PlacementConfig,
  1483  	nameDefault string,
  1484  ) (string, bool) {
  1485  	if placementConfig.Name != "" {
  1486  		// If the policy explicitly specifies a placement name, use it
  1487  		return placementConfig.Name, false
  1488  	} else if defaultPlacementConfig.Name != "" || p.PolicyDefaults.Placement.Name != "" {
  1489  		// Prioritize the provided default but fall back to policyDefaults
  1490  		defaultPlacementName := p.PolicyDefaults.Placement.Name
  1491  		if defaultPlacementConfig.Name != "" {
  1492  			defaultPlacementName = defaultPlacementConfig.Name
  1493  		}
  1494  		// If the policy doesn't explicitly specify a placement name, and there is a
  1495  		// default placement name set, check if one has already been generated for these
  1496  		// cluster/label selectors
  1497  		csKey := getCsKey(placementConfig)
  1498  		if _, ok := p.csToPlc[csKey]; ok {
  1499  			// Just reuse the previously created placement with the same cluster/label selectors
  1500  			return p.csToPlc[csKey], true
  1501  		}
  1502  		// If the policy doesn't explicitly specify a placement name, and there is a
  1503  		// default placement name, use that
  1504  		if len(p.csToPlc) == 0 {
  1505  			// If this is the first generated placement, just use it as is
  1506  			return defaultPlacementName, false
  1507  		}
  1508  		// If there is already one or more generated placements, increment the name
  1509  		return fmt.Sprintf("%s%d", defaultPlacementName, len(p.csToPlc)+1), false
  1510  	}
  1511  	// Default to a placement per policy
  1512  	return "placement-" + nameDefault, false
  1513  }
  1514  
  1515  func (p *Plugin) createPolicyPlacement(placementConfig types.PlacementConfig, nameDefault string) (
  1516  	name string, err error,
  1517  ) {
  1518  	return p.createPlacement(p.PolicyDefaults.Placement, placementConfig, nameDefault)
  1519  }
  1520  
  1521  func (p *Plugin) createPolicySetPlacement(placementConfig types.PlacementConfig, nameDefault string) (
  1522  	name string, err error,
  1523  ) {
  1524  	return p.createPlacement(p.PolicySetDefaults.Placement, placementConfig, nameDefault)
  1525  }
  1526  
  1527  // createPlacement creates a placement for the input placement config and default name by writing it to
  1528  // the policy generator's output buffer. The name of the placement or an error is returned.
  1529  // If the placement has already been generated, it will be reused and not added to the
  1530  // policy generator's output buffer. An error is returned if the placement cannot be created.
  1531  func (p *Plugin) createPlacement(
  1532  	defaultPlacementConfig types.PlacementConfig,
  1533  	placementConfig types.PlacementConfig,
  1534  	nameDefault string) (
  1535  	name string, err error,
  1536  ) {
  1537  	// If a placementName or placementRuleName is defined just return it
  1538  	if placementConfig.PlacementName != "" {
  1539  		name = placementConfig.PlacementName
  1540  
  1541  		return
  1542  	}
  1543  
  1544  	if placementConfig.PlacementRuleName != "" {
  1545  		name = placementConfig.PlacementRuleName
  1546  
  1547  		return
  1548  	}
  1549  
  1550  	plrPath := placementConfig.PlacementRulePath
  1551  	plcPath := placementConfig.PlacementPath
  1552  	var placement map[string]interface{}
  1553  	// If a path to a placement is provided, find the placement and reuse it.
  1554  	if plrPath != "" || plcPath != "" {
  1555  		var resolvedPlPath string
  1556  		if plrPath != "" {
  1557  			resolvedPlPath = plrPath
  1558  		} else {
  1559  			resolvedPlPath = plcPath
  1560  		}
  1561  
  1562  		name, placement, err = p.getPlcFromPath(resolvedPlPath)
  1563  		if err != nil {
  1564  			return
  1565  		}
  1566  
  1567  		// processedPlcs keeps track of which placements have been seen by name. This is so
  1568  		// that if the same placement path is provided for multiple policies, it's not re-included
  1569  		// in the generated output of the plugin.
  1570  		if p.processedPlcs[name] {
  1571  			return
  1572  		}
  1573  
  1574  		p.processedPlcs[name] = true
  1575  	} else {
  1576  		var skip bool
  1577  		name, skip = p.getPlcName(defaultPlacementConfig, placementConfig, nameDefault)
  1578  		if skip {
  1579  			return
  1580  		}
  1581  
  1582  		// Determine which selectors to use
  1583  		var resolvedSelectors map[string]interface{}
  1584  		if len(placementConfig.ClusterSelectors) > 0 {
  1585  			resolvedSelectors = placementConfig.ClusterSelectors
  1586  		} else if len(placementConfig.ClusterSelector) > 0 {
  1587  			resolvedSelectors = placementConfig.ClusterSelector
  1588  		} else if len(placementConfig.LabelSelector) > 0 {
  1589  			resolvedSelectors = placementConfig.LabelSelector
  1590  		}
  1591  
  1592  		// Build cluster selector object
  1593  		selectorObj, err := p.generateSelector(resolvedSelectors)
  1594  		if err != nil {
  1595  			return "", err
  1596  		}
  1597  
  1598  		if p.usingPlR {
  1599  			placement = map[string]interface{}{
  1600  				"apiVersion": placementRuleAPIVersion,
  1601  				"kind":       placementRuleKind,
  1602  				"metadata": map[string]interface{}{
  1603  					"name":      name,
  1604  					"namespace": p.PolicyDefaults.Namespace,
  1605  				},
  1606  				"spec": map[string]interface{}{
  1607  					"clusterSelector": selectorObj,
  1608  				},
  1609  			}
  1610  		} else {
  1611  			placement = map[string]interface{}{
  1612  				"apiVersion": placementAPIVersion,
  1613  				"kind":       placementKind,
  1614  				"metadata": map[string]interface{}{
  1615  					"name":      name,
  1616  					"namespace": p.PolicyDefaults.Namespace,
  1617  				},
  1618  				"spec": map[string]interface{}{
  1619  					"predicates": []map[string]interface{}{
  1620  						{
  1621  							"requiredClusterSelector": map[string]interface{}{
  1622  								"labelSelector": selectorObj,
  1623  							},
  1624  						},
  1625  					},
  1626  				},
  1627  			}
  1628  		}
  1629  
  1630  		csKey := getCsKey(placementConfig)
  1631  		p.csToPlc[csKey] = name
  1632  	}
  1633  
  1634  	if p.allPlcs[name] {
  1635  		return "", fmt.Errorf("a duplicate placement name was detected: %s", name)
  1636  	}
  1637  
  1638  	p.allPlcs[name] = true
  1639  
  1640  	var placementYAML []byte
  1641  
  1642  	placementYAML, err = yaml.Marshal(placement)
  1643  	if err != nil {
  1644  		err = fmt.Errorf(
  1645  			"an unexpected error occurred when converting the placement to YAML: %w", err,
  1646  		)
  1647  
  1648  		return
  1649  	}
  1650  
  1651  	p.outputBuffer.Write([]byte("---\n"))
  1652  	p.outputBuffer.Write(placementYAML)
  1653  
  1654  	return
  1655  }
  1656  
  1657  // generateSelector determines the type of input and creates a map of selectors to be used in either the
  1658  // clusterSelector or labelSelector field
  1659  func (p *Plugin) generateSelector(
  1660  	resolvedSelectors map[string]interface{},
  1661  ) (map[string]interface{}, error) {
  1662  	if resolvedSelectors == nil {
  1663  		return map[string]interface{}{"matchExpressions": []interface{}{}}, nil
  1664  	}
  1665  
  1666  	resolvedSelectorsJSON, err := json.Marshal(resolvedSelectors)
  1667  	if err != nil {
  1668  		return nil, err
  1669  	}
  1670  
  1671  	resolvedSelectorsLS := metav1.LabelSelector{}
  1672  	decoder := json.NewDecoder(bytes.NewReader(resolvedSelectorsJSON))
  1673  	decoder.DisallowUnknownFields()
  1674  
  1675  	err = decoder.Decode(&resolvedSelectorsLS)
  1676  	if err != nil {
  1677  		resolvedSelectorsLS = metav1.LabelSelector{}
  1678  
  1679  		// Check if it's a legacy selector
  1680  		for label, value := range resolvedSelectors {
  1681  			valueStr, ok := value.(string)
  1682  			if !ok {
  1683  				return nil, fmt.Errorf(
  1684  					"the input is not a valid label selector or key-value label matching map",
  1685  				)
  1686  			}
  1687  
  1688  			lsReq := metav1.LabelSelectorRequirement{Key: label}
  1689  
  1690  			if valueStr == "" {
  1691  				lsReq.Operator = metav1.LabelSelectorOpExists
  1692  			} else {
  1693  				lsReq.Operator = metav1.LabelSelectorOpIn
  1694  				lsReq.Values = []string{valueStr}
  1695  			}
  1696  
  1697  			resolvedSelectorsLS.MatchExpressions = append(resolvedSelectorsLS.MatchExpressions, lsReq)
  1698  		}
  1699  
  1700  		resolved, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&resolvedSelectorsLS)
  1701  		if err != nil {
  1702  			panic(err)
  1703  		}
  1704  
  1705  		return resolved, nil
  1706  	}
  1707  
  1708  	return resolvedSelectors, nil
  1709  }
  1710  
  1711  // createPlacementBinding creates a placement binding for the input placement, policies and policy sets by
  1712  // writing it to the policy generator's output buffer. An error is returned if the placement binding
  1713  // cannot be created.
  1714  func (p *Plugin) createPlacementBinding(
  1715  	bindingName, plcName string, policyConfs []*types.PolicyConfig, policySetConfs []*types.PolicySetConfig,
  1716  ) error {
  1717  	subjects := make([]map[string]string, 0, len(policyConfs)+len(policySetConfs))
  1718  
  1719  	for _, policyConf := range policyConfs {
  1720  		subject := map[string]string{
  1721  			"apiGroup": policyAPIGroup,
  1722  			"kind":     policyKind,
  1723  			"name":     policyConf.Name,
  1724  		}
  1725  		subjects = append(subjects, subject)
  1726  	}
  1727  
  1728  	for _, policySetConf := range policySetConfs {
  1729  		subject := map[string]string{
  1730  			"apiGroup": policyAPIGroup,
  1731  			"kind":     policySetKind,
  1732  			"name":     policySetConf.Name,
  1733  		}
  1734  		subjects = append(subjects, subject)
  1735  	}
  1736  
  1737  	var resolvedPlcKind string
  1738  	var resolvedPlcAPIVersion string
  1739  
  1740  	if p.usingPlR {
  1741  		resolvedPlcKind = placementRuleKind
  1742  		resolvedPlcAPIVersion = placementRuleAPIVersion
  1743  	} else {
  1744  		resolvedPlcKind = placementKind
  1745  		resolvedPlcAPIVersion = placementAPIVersion
  1746  	}
  1747  
  1748  	binding := map[string]interface{}{
  1749  		"apiVersion": placementBindingAPIVersion,
  1750  		"kind":       placementBindingKind,
  1751  		"metadata": map[string]interface{}{
  1752  			"name":      bindingName,
  1753  			"namespace": p.PolicyDefaults.Namespace,
  1754  		},
  1755  		"placementRef": map[string]string{
  1756  			// Remove the version at the end
  1757  			"apiGroup": strings.Split(resolvedPlcAPIVersion, "/")[0],
  1758  			"name":     plcName,
  1759  			"kind":     resolvedPlcKind,
  1760  		},
  1761  		"subjects": subjects,
  1762  	}
  1763  
  1764  	bindingYAML, err := yaml.Marshal(binding)
  1765  	if err != nil {
  1766  		return fmt.Errorf(
  1767  			"an unexpected error occurred when converting the placement binding to YAML: %w", err,
  1768  		)
  1769  	}
  1770  
  1771  	p.outputBuffer.Write([]byte("---\n"))
  1772  	p.outputBuffer.Write(bindingYAML)
  1773  
  1774  	return nil
  1775  }