Skip to content

Commit 150b05c

Browse files
feat: support arbitrary key on the cluster as the value of override (#1074)
1 parent d7e219a commit 150b05c

File tree

3 files changed

+432
-5
lines changed

3 files changed

+432
-5
lines changed

apis/placement/v1alpha1/common.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,13 @@ const (
1717

1818
// OverrideClusterNameVariable is the reserved variable in the override value that will be replaced by the actual cluster name.
1919
OverrideClusterNameVariable = "${MEMBER-CLUSTER-NAME}"
20+
21+
// OverrideClusterLabelKeyVariablePrefix is a reserved variable in the override expression.
22+
// We use this variable to find the associated the key following the prefix.
23+
// The key name ends with a "}" character (but not include it).
24+
// The key name must be a valid Kubernetes label name and case-sensitive.
25+
// The content of the string containing this variable will be replaced by the actual label value on the member cluster.
26+
// For example, if the string is "${MEMBER-CLUSTER-LABEL-KEY-kube-fleet.io/region}" then the key name is "kube-fleet.io/region".
27+
// If there is a label "kube-fleet.io/region": "us-west-1" on the member cluster, this string will be replaced by "us-west-1".
28+
OverrideClusterLabelKeyVariablePrefix = "${MEMBER-CLUSTER-LABEL-KEY-"
2029
)

pkg/controllers/workgenerator/override.go

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ func applyOverrideRules(resource *placementv1beta1.ResourceContent, cluster *clu
185185
return nil
186186
}
187187
// Apply JSONPatchOverrides by default
188-
if err := applyJSONPatchOverride(resource, cluster, rule.JSONPatchOverrides); err != nil {
188+
if err = applyJSONPatchOverride(resource, cluster, rule.JSONPatchOverrides); err != nil {
189189
klog.ErrorS(err, "Failed to apply JSON patch override")
190190
return controller.NewUserError(err)
191191
}
@@ -195,16 +195,24 @@ func applyOverrideRules(resource *placementv1beta1.ResourceContent, cluster *clu
195195

196196
// applyJSONPatchOverride applies a JSON patch on the selected resources following [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902).
197197
func applyJSONPatchOverride(resourceContent *placementv1beta1.ResourceContent, cluster *clusterv1beta1.MemberCluster, overrides []placementv1alpha1.JSONPatchOverride) error {
198+
var err error
198199
if len(overrides) == 0 { // do nothing
199200
return nil
200201
}
201202
// go through the JSON patch overrides to replace the built-in variables before json Marshal
202203
// as it may contain the built-in variables that cannot be marshaled directly
203204
for i := range overrides {
204-
// find and replace a few special built-in variables
205-
// replace the built-in variable with the actual cluster name
206-
processedJSONStr := []byte(strings.ReplaceAll(string(overrides[i].Value.Raw), placementv1alpha1.OverrideClusterNameVariable, cluster.Name))
207-
overrides[i].Value.Raw = processedJSONStr
205+
// Process the JSON string to replace variables
206+
jsonStr := string(overrides[i].Value.Raw)
207+
// Replace the built-in ${MEMBER-CLUSTER-NAME} variable with the actual cluster name
208+
jsonStr = strings.ReplaceAll(jsonStr, placementv1alpha1.OverrideClusterNameVariable, cluster.Name)
209+
// Replace label key variables with actual label values
210+
jsonStr, err = replaceClusterLabelKeyVariables(jsonStr, cluster)
211+
if err != nil {
212+
klog.ErrorS(err, "Failed to replace cluster label key variables in JSON patch override")
213+
return err
214+
}
215+
overrides[i].Value.Raw = []byte(jsonStr)
208216
}
209217

210218
jsonPatchBytes, err := json.Marshal(overrides)
@@ -227,3 +235,38 @@ func applyJSONPatchOverride(resourceContent *placementv1beta1.ResourceContent, c
227235
resourceContent.Raw = patchedObjectJSONBytes
228236
return nil
229237
}
238+
239+
// replaceClusterLabelKeyVariables finds all occurrences of the OverrideClusterLabelKeyVariablePrefix pattern
240+
// (e.g. ${MEMBER-CLUSTER-LABEL-KEY-region}) in the input string and replaces them with
241+
// the corresponding label values from the cluster.
242+
// If a label with the specified key doesn't exist, it returns an error.
243+
func replaceClusterLabelKeyVariables(input string, cluster *clusterv1beta1.MemberCluster) (string, error) {
244+
prefixLen := len(placementv1alpha1.OverrideClusterLabelKeyVariablePrefix)
245+
result := input
246+
247+
for {
248+
startIdx := strings.Index(result, placementv1alpha1.OverrideClusterLabelKeyVariablePrefix)
249+
if startIdx == -1 {
250+
break
251+
}
252+
// extract the key value user wants to replace
253+
endIdx := strings.Index(result[startIdx+prefixLen:], "}")
254+
if endIdx == -1 {
255+
klog.V(2).InfoS("malformed key ${MEMBER-CLUSTER-LABEL-KEY without the closing `}`", "input", input)
256+
return "", fmt.Errorf("input %s is missing the closing bracket `}`", input)
257+
}
258+
endIdx += startIdx + prefixLen
259+
// extract the key name
260+
keyName := result[startIdx+prefixLen : endIdx]
261+
// check if the key exists in the cluster labels
262+
labelValue, exists := cluster.ObjectMeta.Labels[keyName]
263+
if !exists {
264+
klog.V(2).InfoS("Label key not found on cluster", "key", keyName, "cluster", cluster.Name)
265+
return "", fmt.Errorf("label key %s not found on cluster %s", keyName, cluster.Name)
266+
}
267+
// replace this instance of the variable with the actual label value
268+
fullVariable := result[startIdx : endIdx+1]
269+
result = strings.Replace(result, fullVariable, labelValue, 1)
270+
}
271+
return result, nil
272+
}

0 commit comments

Comments
 (0)