kubectl/pkg/explain/v2/funcs.go

238 lines
5.6 KiB
Go

/*
Copyright 2022 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 v2
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
"text/template"
"github.com/go-openapi/jsonreference"
"k8s.io/kubectl/pkg/util/term"
)
type explainError string
func (e explainError) Error() string {
return string(e)
}
func WithBuiltinTemplateFuncs(tmpl *template.Template) *template.Template {
return tmpl.Funcs(map[string]interface{}{
"throw": func(e string, args ...any) (string, error) {
errString := fmt.Sprintf(e, args...)
return "", explainError(errString)
},
"toJson": func(obj any) (string, error) {
res, err := json.Marshal(obj)
return string(res), err
},
"toPrettyJson": func(obj any) (string, error) {
res, err := json.MarshalIndent(obj, "", " ")
if err != nil {
return "", err
}
return string(res), err
},
"fail": func(message string) (string, error) {
return "", errors.New(message)
},
"wrap": func(l int, s string) (string, error) {
buf := bytes.NewBuffer(nil)
writer := term.NewWordWrapWriter(buf, uint(l))
_, err := writer.Write([]byte(s))
if err != nil {
return "", err
}
return buf.String(), nil
},
"split": func(s string, sep string) []string {
return strings.Split(s, sep)
},
"join": func(sep string, strs ...string) string {
return strings.Join(strs, sep)
},
"include": func(name string, data interface{}) (string, error) {
buf := bytes.NewBuffer(nil)
if err := tmpl.ExecuteTemplate(buf, name, data); err != nil {
return "", err
}
return buf.String(), nil
},
"ternary": func(a, b any, condition bool) any {
if condition {
return a
}
return b
},
"first": func(list any) (any, error) {
if list == nil {
return nil, errors.New("list is empty")
}
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
if l == 0 {
return nil, errors.New("list is empty")
}
return l2.Index(0).Interface(), nil
default:
return nil, fmt.Errorf("first cannot be used on type: %T", list)
}
},
"last": func(list any) (any, error) {
if list == nil {
return nil, errors.New("list is empty")
}
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
if l == 0 {
return nil, errors.New("list is empty")
}
return l2.Index(l - 1).Interface(), nil
default:
return nil, fmt.Errorf("last cannot be used on type: %T", list)
}
},
"indent": func(amount int, str string) string {
pad := strings.Repeat(" ", amount)
return pad + strings.Replace(str, "\n", "\n"+pad, -1)
},
"dict": func(keysAndValues ...any) (map[string]any, error) {
if len(keysAndValues)%2 != 0 {
return nil, errors.New("expected even # of arguments")
}
res := map[string]any{}
for i := 0; i+1 < len(keysAndValues); i = i + 2 {
if key, ok := keysAndValues[i].(string); ok {
res[key] = keysAndValues[i+1]
} else {
return nil, fmt.Errorf("key of type %T is not a string as expected", key)
}
}
return res, nil
},
"contains": func(list any, value any) bool {
if list == nil {
return false
}
val := reflect.ValueOf(list)
switch val.Kind() {
case reflect.Array:
case reflect.Slice:
for i := 0; i < val.Len(); i++ {
cur := val.Index(i)
if cur.CanInterface() && reflect.DeepEqual(cur.Interface(), value) {
return true
}
}
return false
default:
return false
}
return false
},
"set": func(dict map[string]any, keysAndValues ...any) (any, error) {
if len(keysAndValues)%2 != 0 {
return nil, errors.New("expected even number of arguments")
}
copyDict := make(map[string]any, len(dict))
for k, v := range dict {
copyDict[k] = v
}
for i := 0; i < len(keysAndValues); i += 2 {
key, ok := keysAndValues[i].(string)
if !ok {
return nil, errors.New("keys must be strings")
}
copyDict[key] = keysAndValues[i+1]
}
return copyDict, nil
},
"add": func(value, operand int) int {
return value + operand
},
"sub": func(value, operand int) int {
return value - operand
},
"mul": func(value, operand int) int {
return value * operand
},
"resolveRef": func(refAny any, document map[string]any) map[string]any {
refString, ok := refAny.(string)
if !ok {
// if passed nil, or wrong type just treat the same
// way as unresolved reference (makes for easier templates)
return nil
}
// Resolve field path encoded by the ref
ref, err := jsonreference.New(refString)
if err != nil {
// Unrecognized ref format.
return nil
}
if !ref.HasFragmentOnly {
// Downloading is not supported. Treat as not found
return nil
}
fragment := ref.GetURL().Fragment
components := strings.Split(fragment, "/")
cur := document
for _, k := range components {
if len(k) == 0 {
// first component is usually empty (#/components/) , etc
continue
}
next, ok := cur[k].(map[string]any)
if !ok {
return nil
}
cur = next
}
return cur
},
})
}