| /* |
| Copyright 2015 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package fieldpath |
| |
| import ( |
| "fmt" |
| "sort" |
| "strconv" |
| "strings" |
| |
| "k8s.io/apimachinery/pkg/api/meta" |
| "k8s.io/apimachinery/pkg/util/validation" |
| ) |
| |
| // FormatMap formats map[string]string to a string. |
| func FormatMap(m map[string]string) (fmtStr string) { |
| // output with keys in sorted order to provide stable output |
| keys := make([]string, 0, len(m)) |
| var grow int |
| for k, v := range m { |
| keys = append(keys, k) |
| // why add 4: (for =, \n, " and ") |
| grow += len(k) + len(v) + 4 |
| } |
| sort.Strings(keys) |
| // allocate space to avoid expansion |
| dst := make([]byte, 0, grow) |
| for _, key := range keys { |
| if len(dst) > 0 { |
| dst = append(dst, '\n') |
| } |
| dst = append(dst, key...) |
| dst = append(dst, '=') |
| dst = strconv.AppendQuote(dst, m[key]) |
| } |
| return string(dst) |
| } |
| |
| // ExtractFieldPathAsString extracts the field from the given object |
| // and returns it as a string. The object must be a pointer to an |
| // API type. |
| func ExtractFieldPathAsString(obj interface{}, fieldPath string) (string, error) { |
| accessor, err := meta.Accessor(obj) |
| if err != nil { |
| return "", err |
| } |
| |
| if path, subscript, ok := SplitMaybeSubscriptedPath(fieldPath); ok { |
| switch path { |
| case "metadata.annotations": |
| if errs := validation.IsQualifiedName(strings.ToLower(subscript)); len(errs) != 0 { |
| return "", fmt.Errorf("invalid key subscript in %s: %s", fieldPath, strings.Join(errs, ";")) |
| } |
| return accessor.GetAnnotations()[subscript], nil |
| case "metadata.labels": |
| if errs := validation.IsQualifiedName(subscript); len(errs) != 0 { |
| return "", fmt.Errorf("invalid key subscript in %s: %s", fieldPath, strings.Join(errs, ";")) |
| } |
| return accessor.GetLabels()[subscript], nil |
| default: |
| return "", fmt.Errorf("fieldPath %q does not support subscript", fieldPath) |
| } |
| } |
| |
| switch fieldPath { |
| case "metadata.annotations": |
| return FormatMap(accessor.GetAnnotations()), nil |
| case "metadata.labels": |
| return FormatMap(accessor.GetLabels()), nil |
| case "metadata.name": |
| return accessor.GetName(), nil |
| case "metadata.namespace": |
| return accessor.GetNamespace(), nil |
| case "metadata.uid": |
| return string(accessor.GetUID()), nil |
| } |
| |
| return "", fmt.Errorf("unsupported fieldPath: %v", fieldPath) |
| } |
| |
| // SplitMaybeSubscriptedPath checks whether the specified fieldPath is |
| // subscripted, and |
| // - if yes, this function splits the fieldPath into path and subscript, and |
| // returns (path, subscript, true). |
| // - if no, this function returns (fieldPath, "", false). |
| // |
| // Example inputs and outputs: |
| // |
| // "metadata.annotations['myKey']" --> ("metadata.annotations", "myKey", true) |
| // "metadata.annotations['a[b]c']" --> ("metadata.annotations", "a[b]c", true) |
| // "metadata.labels['']" --> ("metadata.labels", "", true) |
| // "metadata.labels" --> ("metadata.labels", "", false) |
| func SplitMaybeSubscriptedPath(fieldPath string) (string, string, bool) { |
| if !strings.HasSuffix(fieldPath, "']") { |
| return fieldPath, "", false |
| } |
| s := strings.TrimSuffix(fieldPath, "']") |
| parts := strings.SplitN(s, "['", 2) |
| if len(parts) < 2 { |
| return fieldPath, "", false |
| } |
| if len(parts[0]) == 0 { |
| return fieldPath, "", false |
| } |
| return parts[0], parts[1], true |
| } |