mirror of
https://github.com/kubesphere/kubekey.git
synced 2025-12-29 13:33:15 +00:00
Signed-off-by: joyceliu <joyceliu@yunify.com> Co-authored-by: joyceliu <joyceliu@yunify.com>
470 lines
11 KiB
Go
470 lines
11 KiB
Go
/*
|
|
Copyright 2023 The KubeSphere 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 variable
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"reflect"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/util/json"
|
|
"k8s.io/klog/v2"
|
|
"k8s.io/utils/ptr"
|
|
|
|
kkcorev1 "github.com/kubesphere/kubekey/v4/pkg/apis/core/v1"
|
|
_const "github.com/kubesphere/kubekey/v4/pkg/const"
|
|
"github.com/kubesphere/kubekey/v4/pkg/converter/tmpl"
|
|
)
|
|
|
|
// combineVariables merge multiple variables into one variable
|
|
// v2 will override v1 if variable is repeated
|
|
func combineVariables(m1, m2 map[string]any) map[string]any {
|
|
var f func(val1, val2 any) any
|
|
f = func(val1, val2 any) any {
|
|
if val1 != nil && val2 != nil &&
|
|
reflect.TypeOf(val1).Kind() == reflect.Map && reflect.TypeOf(val2).Kind() == reflect.Map {
|
|
mergedVars := make(map[string]any)
|
|
for _, k := range reflect.ValueOf(val1).MapKeys() {
|
|
mergedVars[k.String()] = reflect.ValueOf(val1).MapIndex(k).Interface()
|
|
}
|
|
|
|
for _, k := range reflect.ValueOf(val2).MapKeys() {
|
|
mergedVars[k.String()] = f(mergedVars[k.String()], reflect.ValueOf(val2).MapIndex(k).Interface())
|
|
}
|
|
|
|
return mergedVars
|
|
}
|
|
|
|
return val2
|
|
}
|
|
mv := make(map[string]any)
|
|
|
|
for k, v := range m1 {
|
|
mv[k] = v
|
|
}
|
|
|
|
for k, v := range m2 {
|
|
mv[k] = f(mv[k], v)
|
|
}
|
|
|
|
return mv
|
|
}
|
|
|
|
func convertGroup(inv kkcorev1.Inventory) map[string]any {
|
|
groups := make(map[string]any)
|
|
all := make([]string, 0)
|
|
|
|
for hn := range inv.Spec.Hosts {
|
|
all = append(all, hn)
|
|
}
|
|
|
|
if !slices.Contains(all, _const.VariableLocalHost) { // set default localhost
|
|
all = append(all, _const.VariableLocalHost)
|
|
}
|
|
|
|
groups[_const.VariableGroupsAll] = all
|
|
|
|
for gn := range inv.Spec.Groups {
|
|
groups[gn] = hostsInGroup(inv, gn)
|
|
}
|
|
|
|
return groups
|
|
}
|
|
|
|
// hostsInGroup get a host_name slice in a given group
|
|
// if the given group contains other group. convert other group to host_name slice.
|
|
func hostsInGroup(inv kkcorev1.Inventory, groupName string) []string {
|
|
if v, ok := inv.Spec.Groups[groupName]; ok {
|
|
var hosts []string
|
|
for _, cg := range v.Groups {
|
|
hosts = mergeSlice(hostsInGroup(inv, cg), hosts)
|
|
}
|
|
|
|
return mergeSlice(hosts, v.Hosts)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// mergeSlice with skip repeat value
|
|
func mergeSlice(g1, g2 []string) []string {
|
|
uniqueValues := make(map[string]bool)
|
|
mg := make([]string, 0)
|
|
|
|
// Add values from the first slice
|
|
for _, v := range g1 {
|
|
if !uniqueValues[v] {
|
|
uniqueValues[v] = true
|
|
|
|
mg = append(mg, v)
|
|
}
|
|
}
|
|
|
|
// Add values from the second slice
|
|
for _, v := range g2 {
|
|
if !uniqueValues[v] {
|
|
uniqueValues[v] = true
|
|
|
|
mg = append(mg, v)
|
|
}
|
|
}
|
|
|
|
return mg
|
|
}
|
|
|
|
// parseVariable parse all string values to the actual value.
|
|
func parseVariable(v any, parseTmplFunc func(string) (string, error)) error {
|
|
switch reflect.ValueOf(v).Kind() {
|
|
case reflect.Map:
|
|
if err := parseVariableFromMap(v, parseTmplFunc); err != nil {
|
|
return err
|
|
}
|
|
case reflect.Slice, reflect.Array:
|
|
if err := parseVariableFromArray(v, parseTmplFunc); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseVariableFromMap parse to variable when the v is map.
|
|
func parseVariableFromMap(v any, parseTmplFunc func(string) (string, error)) error {
|
|
for _, kv := range reflect.ValueOf(v).MapKeys() {
|
|
val := reflect.ValueOf(v).MapIndex(kv)
|
|
if vv, ok := val.Interface().(string); ok {
|
|
if !tmpl.IsTmplSyntax(vv) {
|
|
continue
|
|
}
|
|
|
|
newValue, err := parseTmplFunc(vv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch {
|
|
case strings.EqualFold(newValue, "TRUE"):
|
|
reflect.ValueOf(v).SetMapIndex(kv, reflect.ValueOf(true))
|
|
case strings.EqualFold(newValue, "FALSE"):
|
|
reflect.ValueOf(v).SetMapIndex(kv, reflect.ValueOf(false))
|
|
default:
|
|
reflect.ValueOf(v).SetMapIndex(kv, reflect.ValueOf(newValue))
|
|
}
|
|
} else {
|
|
if err := parseVariable(val.Interface(), parseTmplFunc); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseVariableFromArray parse to variable when the v is slice.
|
|
func parseVariableFromArray(v any, parseTmplFunc func(string) (string, error)) error {
|
|
for i := range reflect.ValueOf(v).Len() {
|
|
val := reflect.ValueOf(v).Index(i)
|
|
if vv, ok := val.Interface().(string); ok {
|
|
if !tmpl.IsTmplSyntax(vv) {
|
|
continue
|
|
}
|
|
|
|
newValue, err := parseTmplFunc(vv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch {
|
|
case strings.EqualFold(newValue, "TRUE"):
|
|
val.Set(reflect.ValueOf(true))
|
|
case strings.EqualFold(newValue, "FALSE"):
|
|
val.Set(reflect.ValueOf(false))
|
|
default:
|
|
val.Set(reflect.ValueOf(newValue))
|
|
}
|
|
} else {
|
|
if err := parseVariable(val.Interface(), parseTmplFunc); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// setlocalhostVarialbe set default vars when hostname is "localhost"
|
|
func setlocalhostVarialbe(hostname string, v value, hostVars map[string]any) {
|
|
if hostname == _const.VariableLocalHost {
|
|
if os, ok := v.Hosts[hostname].RemoteVars[_const.VariableOS]; ok {
|
|
// try to set hostname by current actual hostname.
|
|
if osd, ok := os.(map[string]any); ok {
|
|
hostVars[_const.VariableHostName] = osd[_const.VariableOSHostName]
|
|
}
|
|
}
|
|
|
|
if _, ok := hostVars[_const.VariableIPv4]; !ok {
|
|
hostVars[_const.VariableIPv4] = getLocalIP(_const.VariableIPv4)
|
|
}
|
|
|
|
if _, ok := hostVars[_const.VariableIPv6]; !ok {
|
|
hostVars[_const.VariableIPv6] = getLocalIP(_const.VariableIPv6)
|
|
}
|
|
}
|
|
}
|
|
|
|
// getLocalIP get the ipv4 or ipv6 for localhost machine
|
|
func getLocalIP(ipType string) string {
|
|
addrs, err := net.InterfaceAddrs()
|
|
if err != nil {
|
|
klog.ErrorS(err, "get network address error")
|
|
}
|
|
|
|
for _, addr := range addrs {
|
|
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
|
|
if ipType == _const.VariableIPv4 && ipNet.IP.To4() != nil {
|
|
return ipNet.IP.String()
|
|
}
|
|
|
|
if ipType == _const.VariableIPv6 && ipNet.IP.To16() != nil && ipNet.IP.To4() == nil {
|
|
return ipNet.IP.String()
|
|
}
|
|
}
|
|
}
|
|
|
|
klog.V(4).Infof("connot get local %s address", ipType)
|
|
|
|
return ""
|
|
}
|
|
|
|
// StringVar get string value by key
|
|
func StringVar(d map[string]any, args map[string]any, key string) (string, error) {
|
|
val, ok := args[key]
|
|
if !ok {
|
|
klog.V(4).InfoS("cannot find variable", "key", key)
|
|
|
|
return "", fmt.Errorf("cannot find variable \"%s\"", key)
|
|
}
|
|
// convert to string
|
|
sv, ok := val.(string)
|
|
if !ok {
|
|
klog.V(4).ErrorS(nil, "variable is not string", "key", key)
|
|
|
|
return "", fmt.Errorf("variable \"%s\" is not string", key)
|
|
}
|
|
|
|
return tmpl.ParseString(d, sv)
|
|
}
|
|
|
|
// StringSliceVar get string slice value by key
|
|
func StringSliceVar(d map[string]any, vars map[string]any, key string) ([]string, error) {
|
|
val, ok := vars[key]
|
|
if !ok {
|
|
klog.V(4).InfoS("cannot find variable", "key", key)
|
|
|
|
return nil, fmt.Errorf("cannot find variable \"%s\"", key)
|
|
}
|
|
|
|
switch valv := val.(type) {
|
|
case []any:
|
|
var ss []string
|
|
|
|
for _, a := range valv {
|
|
av, ok := a.(string)
|
|
if !ok {
|
|
klog.V(6).InfoS("variable is not string", "key", key)
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
as, err := tmpl.ParseString(d, av)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ss = append(ss, as)
|
|
}
|
|
|
|
return ss, nil
|
|
case string:
|
|
as, err := tmpl.ParseString(d, valv)
|
|
if err != nil {
|
|
klog.V(4).ErrorS(err, "parse variable error", "key", key)
|
|
|
|
return nil, err
|
|
}
|
|
|
|
var ss []string
|
|
if err := json.Unmarshal([]byte(as), &ss); err == nil {
|
|
return ss, nil
|
|
}
|
|
|
|
return []string{as}, nil
|
|
default:
|
|
klog.V(4).ErrorS(nil, "unsupported variable type", "key", key)
|
|
|
|
return nil, fmt.Errorf("unsupported variable \"%s\" type", key)
|
|
}
|
|
}
|
|
|
|
// IntVar get int value by key
|
|
func IntVar(d map[string]any, vars map[string]any, key string) (*int, error) {
|
|
val, ok := vars[key]
|
|
if !ok {
|
|
klog.V(4).InfoS("cannot find variable", "key", key)
|
|
|
|
return nil, fmt.Errorf("cannot find variable \"%s\"", key)
|
|
}
|
|
// default convert to int
|
|
v := reflect.ValueOf(val)
|
|
switch v.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return ptr.To(int(v.Int())), nil
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
return ptr.To(int(v.Uint())), nil
|
|
case reflect.Float32, reflect.Float64:
|
|
return ptr.To(int(v.Float())), nil
|
|
case reflect.String:
|
|
vs, err := tmpl.ParseString(d, v.String())
|
|
if err != nil {
|
|
klog.V(4).ErrorS(err, "parse string variable error", "key", key)
|
|
|
|
return nil, err
|
|
}
|
|
|
|
atoi, err := strconv.Atoi(vs)
|
|
if err != nil {
|
|
klog.V(4).ErrorS(err, "parse convert string to int error", "key", key)
|
|
|
|
return nil, err
|
|
}
|
|
|
|
return ptr.To(atoi), nil
|
|
default:
|
|
klog.V(4).ErrorS(nil, "unsupported variable type", "key", key)
|
|
|
|
return nil, fmt.Errorf("unsupported variable \"%s\" type", key)
|
|
}
|
|
}
|
|
|
|
// BoolVar get bool value by key
|
|
func BoolVar(d map[string]any, args map[string]any, key string) (*bool, error) {
|
|
val, ok := args[key]
|
|
if !ok {
|
|
klog.V(4).InfoS("cannot find variable", "key", key)
|
|
|
|
return nil, fmt.Errorf("cannot find variable \"%s\"", key)
|
|
}
|
|
// default convert to int
|
|
v := reflect.ValueOf(val)
|
|
switch v.Kind() {
|
|
case reflect.Bool:
|
|
return ptr.To(v.Bool()), nil
|
|
case reflect.String:
|
|
vs, err := tmpl.ParseString(d, v.String())
|
|
if err != nil {
|
|
klog.V(4).ErrorS(err, "parse string variable error", "key", key)
|
|
|
|
return nil, err
|
|
}
|
|
|
|
if strings.EqualFold(vs, "TRUE") {
|
|
return ptr.To(true), nil
|
|
}
|
|
|
|
if strings.EqualFold(vs, "FALSE") {
|
|
return ptr.To(false), nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("unsupported variable \"%s\" type", key)
|
|
}
|
|
|
|
// DurationVar get time.Duration value by key
|
|
func DurationVar(d map[string]any, args map[string]any, key string) (time.Duration, error) {
|
|
stringVar, err := StringVar(d, args, key)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return time.ParseDuration(stringVar)
|
|
}
|
|
|
|
// Extension2Variables convert runtime.RawExtension to variables
|
|
func Extension2Variables(ext runtime.RawExtension) map[string]any {
|
|
if len(ext.Raw) == 0 {
|
|
return make(map[string]any)
|
|
}
|
|
|
|
var data map[string]any
|
|
if err := json.Unmarshal(ext.Raw, &data); err != nil {
|
|
klog.V(4).ErrorS(err, "failed to unmarshal extension to variables")
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
// Extension2Slice convert runtime.RawExtension to slice
|
|
// if runtime.RawExtension contains tmpl syntax, parse it.
|
|
func Extension2Slice(d map[string]any, ext runtime.RawExtension) []any {
|
|
if len(ext.Raw) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var data []any
|
|
// try parse yaml string which defined by single value or multi value
|
|
if err := json.Unmarshal(ext.Raw, &data); err == nil {
|
|
return data
|
|
}
|
|
// try converter template string
|
|
val, err := Extension2String(d, ext)
|
|
if err != nil {
|
|
klog.ErrorS(err, "extension2string error", "input", string(ext.Raw))
|
|
}
|
|
|
|
if err := json.Unmarshal([]byte(val), &data); err == nil {
|
|
return data
|
|
}
|
|
|
|
return []any{val}
|
|
}
|
|
|
|
// Extension2String convert runtime.RawExtension to string.
|
|
// if runtime.RawExtension contains tmpl syntax, parse it.
|
|
func Extension2String(d map[string]any, ext runtime.RawExtension) (string, error) {
|
|
if len(ext.Raw) == 0 {
|
|
return "", nil
|
|
}
|
|
|
|
var input = string(ext.Raw)
|
|
// try to escape string
|
|
if ns, err := strconv.Unquote(string(ext.Raw)); err == nil {
|
|
input = ns
|
|
}
|
|
|
|
result, err := tmpl.ParseString(d, input)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return result, nil
|
|
}
|