Skip to content

Commit 9c30c36

Browse files
committed
Add support for generating placement with raw data
Placements can now be generated by nesting a full labelSelector or clusterSelector YAML under the `placement` key, in addition to the legacy method of listing key/value pairs under `placement`. This allows users to specify multiple clusters in the values array of a placement or placementRule, and to use matchLabels instead of matchExpressions (only supported by placement). Signed-off-by: Will Kutler <[email protected]>
1 parent e32fb03 commit 9c30c36

File tree

5 files changed

+446
-63
lines changed

5 files changed

+446
-63
lines changed

docs/policygenerator-reference.yaml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,15 @@ policyDefaults:
108108
# Optional. The placement configuration for the policies. This defaults to a placement configuration that matches all
109109
# clusters.
110110
placement:
111-
# To specify a placement rule, specify key:value pair cluster selectors. (See placementRulePath to specify an
112-
# existing file instead.)
111+
# Deprecated: use clusterSelector instead
112+
# To specify a placement rule, specify key:value pair cluster selectors or the full YAML for the desired cluster
113+
# selectors. (See placementRulePath to specify an existing file instead.)
113114
clusterSelectors: {}
114-
# To specify a placement, specify key:value pair cluster label selectors. (See placementPath to specify an existing
115-
# file instead.)
115+
# To specify a placement rule, specify key:value pair cluster selectors or the full YAML for the desired cluster
116+
# selectors. (See placementRulePath to specify an existing file instead.)
117+
clusterSelector: {}
118+
# To specify a placement, specify key:value pair cluster label selectors or the full YAML for the desired cluster
119+
# label selectors. (See placementPath to specify an existing file instead.)
116120
labelSelector: {}
117121
# Optional. Specifying a name will consolidate placement rules that contain the same cluster selectors.
118122
name: ""

internal/plugin.go

Lines changed: 91 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package internal
33

44
import (
55
"bytes"
6+
"encoding/json"
67
"errors"
78
"fmt"
89
"os"
@@ -12,7 +13,9 @@ import (
1213
"time"
1314

1415
yaml "gopkg.in/yaml.v3"
16+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1517
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
18+
"k8s.io/apimachinery/pkg/runtime"
1619
"k8s.io/apimachinery/pkg/util/validation"
1720
"open-cluster-management.io/ocm-kustomize-generator-plugins/internal/types"
1821
)
@@ -831,7 +834,7 @@ func (p *Plugin) assertValidConfig() error {
831834
}
832835

833836
// Validate default policy placement settings
834-
err := assertValidPlacement(p.PolicyDefaults.Placement, "policyDefaults", nil)
837+
err := p.assertValidPlacement(p.PolicyDefaults.Placement, "policyDefaults", nil)
835838
if err != nil {
836839
return err
837840
}
@@ -1061,14 +1064,14 @@ func (p *Plugin) assertValidConfig() error {
10611064
}
10621065
}
10631066

1064-
err := assertValidPlacement(policy.Placement, fmt.Sprintf("policy %s", policy.Name), &plCount)
1067+
err := p.assertValidPlacement(policy.Placement, fmt.Sprintf("policy %s", policy.Name), &plCount)
10651068
if err != nil {
10661069
return err
10671070
}
10681071
}
10691072

10701073
// Validate default policy set placement settings
1071-
err = assertValidPlacement(p.PolicySetDefaults.Placement, "policySetDefaults", nil)
1074+
err = p.assertValidPlacement(p.PolicySetDefaults.Placement, "policySetDefaults", nil)
10721075
if err != nil {
10731076
return err
10741077
}
@@ -1099,7 +1102,7 @@ func (p *Plugin) assertValidConfig() error {
10991102
seenPlcset[plcset.Name] = true
11001103

11011104
// Validate policy set Placement settings
1102-
err := assertValidPlacement(plcset.Placement, fmt.Sprintf("policySet %s", plcset.Name), &plCount)
1105+
err := p.assertValidPlacement(plcset.Placement, fmt.Sprintf("policySet %s", plcset.Name), &plCount)
11031106
if err != nil {
11041107
return err
11051108
}
@@ -1120,7 +1123,7 @@ func (p *Plugin) assertValidConfig() error {
11201123
}
11211124

11221125
// assertValidPlacement is a helper for assertValidConfig to verify placement configurations
1123-
func assertValidPlacement(
1126+
func (p *Plugin) assertValidPlacement(
11241127
placement types.PlacementConfig,
11251128
path string,
11261129
plCount *struct {
@@ -1237,6 +1240,26 @@ func assertValidPlacement(
12371240
}
12381241
}
12391242

1243+
if len(placement.ClusterSelectors) > 0 && len(placement.ClusterSelector) > 0 {
1244+
return fmt.Errorf("cannot use both clusterSelector and clusterSelectors in %s placement config "+
1245+
"(clusterSelector is recommended since it matches the actual placement field)", path)
1246+
}
1247+
1248+
// Determine which selectors to use
1249+
var resolvedSelectors map[string]interface{}
1250+
if len(placement.ClusterSelectors) > 0 {
1251+
resolvedSelectors = placement.ClusterSelectors
1252+
} else if len(placement.ClusterSelector) > 0 {
1253+
resolvedSelectors = placement.ClusterSelector
1254+
} else if len(placement.LabelSelector) > 0 {
1255+
resolvedSelectors = placement.LabelSelector
1256+
}
1257+
1258+
_, err := p.generateSelector(resolvedSelectors)
1259+
if err != nil {
1260+
return fmt.Errorf("%s placement has invalid selectors: %w", path, err)
1261+
}
1262+
12401263
return nil
12411264
}
12421265

@@ -1530,32 +1553,19 @@ func (p *Plugin) createPlacement(
15301553
}
15311554

15321555
// Determine which selectors to use
1533-
var resolvedSelectors map[string]string
1556+
var resolvedSelectors map[string]interface{}
15341557
if len(placementConfig.ClusterSelectors) > 0 {
15351558
resolvedSelectors = placementConfig.ClusterSelectors
1559+
} else if len(placementConfig.ClusterSelector) > 0 {
1560+
resolvedSelectors = placementConfig.ClusterSelector
15361561
} else if len(placementConfig.LabelSelector) > 0 {
15371562
resolvedSelectors = placementConfig.LabelSelector
15381563
}
15391564

1540-
// Sort the keys so that the match expressions can be ordered based on the label name
1541-
keys := make([]string, 0, len(resolvedSelectors))
1542-
for key := range resolvedSelectors {
1543-
keys = append(keys, key)
1544-
}
1545-
sort.Strings(keys)
1546-
1547-
matchExpressions := []map[string]interface{}{}
1548-
for _, label := range keys {
1549-
matchExpression := map[string]interface{}{
1550-
"key": label,
1551-
}
1552-
if resolvedSelectors[label] == "" {
1553-
matchExpression["operator"] = "Exists"
1554-
} else {
1555-
matchExpression["operator"] = "In"
1556-
matchExpression["values"] = []string{resolvedSelectors[label]}
1557-
}
1558-
matchExpressions = append(matchExpressions, matchExpression)
1565+
// Build cluster selector object
1566+
selectorObj, err := p.generateSelector(resolvedSelectors)
1567+
if err != nil {
1568+
return "", err
15591569
}
15601570

15611571
if p.usingPlR {
@@ -1567,9 +1577,7 @@ func (p *Plugin) createPlacement(
15671577
"namespace": p.PolicyDefaults.Namespace,
15681578
},
15691579
"spec": map[string]interface{}{
1570-
"clusterSelector": map[string]interface{}{
1571-
"matchExpressions": matchExpressions,
1572-
},
1580+
"clusterSelector": selectorObj,
15731581
},
15741582
}
15751583
} else {
@@ -1584,9 +1592,7 @@ func (p *Plugin) createPlacement(
15841592
"predicates": []map[string]interface{}{
15851593
{
15861594
"requiredClusterSelector": map[string]interface{}{
1587-
"labelSelector": map[string]interface{}{
1588-
"matchExpressions": matchExpressions,
1589-
},
1595+
"labelSelector": selectorObj,
15901596
},
15911597
},
15921598
},
@@ -1621,6 +1627,60 @@ func (p *Plugin) createPlacement(
16211627
return
16221628
}
16231629

1630+
// generateSelector determines the type of input and creates a map of selectors to be used in either the
1631+
// clusterSelector or labelSelector field
1632+
func (p *Plugin) generateSelector(
1633+
resolvedSelectors map[string]interface{},
1634+
) (map[string]interface{}, error) {
1635+
if resolvedSelectors == nil {
1636+
return map[string]interface{}{"matchExpressions": []interface{}{}}, nil
1637+
}
1638+
1639+
resolvedSelectorsJSON, err := json.Marshal(resolvedSelectors)
1640+
if err != nil {
1641+
return nil, err
1642+
}
1643+
1644+
resolvedSelectorsLS := metav1.LabelSelector{}
1645+
decoder := json.NewDecoder(bytes.NewReader(resolvedSelectorsJSON))
1646+
decoder.DisallowUnknownFields()
1647+
1648+
err = decoder.Decode(&resolvedSelectorsLS)
1649+
if err != nil {
1650+
resolvedSelectorsLS = metav1.LabelSelector{}
1651+
1652+
// Check if it's a legacy selector
1653+
for label, value := range resolvedSelectors {
1654+
valueStr, ok := value.(string)
1655+
if !ok {
1656+
return nil, fmt.Errorf(
1657+
"the input is not a valid label selector or key-value label matching map",
1658+
)
1659+
}
1660+
1661+
lsReq := metav1.LabelSelectorRequirement{Key: label}
1662+
1663+
if valueStr == "" {
1664+
lsReq.Operator = metav1.LabelSelectorOpExists
1665+
} else {
1666+
lsReq.Operator = metav1.LabelSelectorOpIn
1667+
lsReq.Values = []string{valueStr}
1668+
}
1669+
1670+
resolvedSelectorsLS.MatchExpressions = append(resolvedSelectorsLS.MatchExpressions, lsReq)
1671+
}
1672+
1673+
resolved, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&resolvedSelectorsLS)
1674+
if err != nil {
1675+
panic(err)
1676+
}
1677+
1678+
return resolved, nil
1679+
}
1680+
1681+
return resolvedSelectors, nil
1682+
}
1683+
16241684
// createPlacementBinding creates a placement binding for the input placement, policies and policy sets by
16251685
// writing it to the policy generator's output buffer. An error is returned if the placement binding
16261686
// cannot be created.

0 commit comments

Comments
 (0)