feat: add gather_facts cache (#2558)

Signed-off-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
liujian 2025-05-12 14:20:01 +08:00 committed by GitHub
parent a876b3c9d7
commit 13df73e0ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 322 additions and 101 deletions

View File

@ -72,7 +72,7 @@ func (c *Config) MarshalJSON() ([]byte, error) {
// This provides direct access to the config values stored in Spec.Object.
func (c *Config) Value() map[string]any {
if c.Spec.Object == nil {
return make(map[string]any)
c.Spec.Object = &unstructured.Unstructured{Object: make(map[string]any)}
}
return c.Spec.Object.(*unstructured.Unstructured).Object

View File

@ -2,7 +2,7 @@
- hosts:
- all
vars:
work_dir: /root/kubekey
# work_dir: default is <current_dir>/kubekey
binary_dir: |
{{ .work_dir }}/kubekey
scripts_dir: |

View File

@ -15,6 +15,7 @@
gather_facts: true
tags: ["always"]
roles:
- precheck/env_check
- role: precheck/env_check
tags: ["always"]
- import_playbook: hook/post_install.yaml

View File

@ -177,7 +177,7 @@ func (o *CommonOptions) Complete(playbook *kkcorev1.Playbook) error {
o.Workdir = filepath.Join(wd, o.Workdir)
}
// Generate and complete the configuration.
if err := o.completeConfig(o.Config); err != nil {
if err := o.completeConfig(); err != nil {
return err
}
playbook.Spec.Config = ptr.Deref(o.Config, kkcorev1.Config{})
@ -196,7 +196,7 @@ func (o *CommonOptions) Complete(playbook *kkcorev1.Playbook) error {
}
// genConfig generate config by ConfigFile and set value by command args.
func (o *CommonOptions) completeConfig(config *kkcorev1.Config) error {
func (o *CommonOptions) completeConfig() error {
// set value by command args
if o.Workdir != "" {
if err := unstructured.SetNestedField(o.Config.Value(), o.Workdir, _const.Workdir); err != nil {
@ -215,7 +215,7 @@ func (o *CommonOptions) completeConfig(config *kkcorev1.Config) error {
if i == 0 || i == -1 {
return errors.New("--set value should be k=v")
}
if err := setValue(config, setVal[:i], setVal[i+1:]); err != nil {
if err := setValue(o.Config, setVal[:i], setVal[i+1:]); err != nil {
return err
}
}

View File

@ -59,55 +59,48 @@ type Connector interface {
// if connector is not set. when host is localhost, use local connector, else use ssh connector
// vars contains all inventory for host. It's best to define the connector info in inventory file.
func NewConnector(host string, v variable.Variable) (Connector, error) {
vars, err := v.Get(variable.GetAllVariable(host))
ha, err := v.Get(variable.GetAllVariable(host))
if err != nil {
return nil, err
}
connectorVars := make(map[string]any)
if c1, ok := vars.(map[string]any)[_const.VariableConnector]; ok {
if c2, ok := c1.(map[string]any); ok {
connectorVars = c2
}
vd, ok := ha.(map[string]any)
if !ok {
return nil, errors.Errorf("host: %s variable is not a map", host)
}
connectedType, _ := variable.StringVar(nil, connectorVars, _const.VariableConnectorType)
workdir, err := v.Get(variable.GetWorkDir())
if err != nil {
return nil, err
}
wd, ok := workdir.(string)
if !ok {
return nil, errors.New("workdir in variable should be string")
}
connectedType, _ := variable.StringVar(nil, vd, _const.VariableConnector, _const.VariableConnectorType)
switch connectedType {
case connectedLocal:
return newLocalConnector(connectorVars), nil
return newLocalConnector(wd, vd), nil
case connectedSSH:
return newSSHConnector(host, connectorVars), nil
return newSSHConnector(wd, host, vd), nil
case connectedKubernetes:
workdir, err := v.Get(variable.GetWorkDir())
if err != nil {
return nil, err
}
wd, ok := workdir.(string)
if !ok {
return nil, errors.New("workdir in variable should be string")
}
return newKubernetesConnector(host, wd, connectorVars)
return newKubernetesConnector(host, wd, vd)
default:
localHost, _ := os.Hostname()
// get host in connector variable. if empty, set default host: host_name.
hostParam, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorHost)
hostParam, err := variable.StringVar(nil, vd, _const.VariableConnector, _const.VariableConnectorHost)
if err != nil {
klog.V(4).Infof("connector host is empty use: %s", host)
hostParam = host
}
if host == _const.VariableLocalHost || host == localHost || isLocalIP(hostParam) {
return newLocalConnector(connectorVars), nil
return newLocalConnector(wd, vd), nil
}
return newSSHConnector(host, connectorVars), nil
return newSSHConnector(wd, host, vd), nil
}
}
// GatherFacts get host info.
type GatherFacts interface {
HostInfo(ctx context.Context) (map[string]any, error)
}
// isLocalIP check if given ipAddr is local network ip
func isLocalIP(ipAddr string) bool {
addrs, err := net.InterfaceAddrs()

View File

@ -0,0 +1,177 @@
package connector
import (
"context"
"encoding/json"
"os"
"path/filepath"
"sync"
"github.com/cockroachdb/errors"
"gopkg.in/yaml.v3"
"k8s.io/klog/v2"
_const "github.com/kubesphere/kubekey/v4/pkg/const"
)
const (
// gatherFactsCacheJSON indicates that facts should be cached in JSON format
gatherFactsCacheJSON = "jsonfile"
// gatherFactsCacheYAML indicates that facts should be cached in YAML format
gatherFactsCacheYAML = "yamlfile"
// gatherFactsCacheMemory indicates that facts should be cached in memory
gatherFactsCacheMemory = "memory"
)
var cache = &memoryCache{
cache: make(map[string]map[string]any),
}
type memoryCache struct {
cache map[string]map[string]any
cacheMutex sync.RWMutex
}
// Get retrieves cached data for a host (thread-safe).
func (m *memoryCache) Get(hostname string) (map[string]any, bool) {
m.cacheMutex.RLock()
defer m.cacheMutex.RUnlock()
data, exists := m.cache[hostname]
return data, exists
}
// Set stores data for a host (thread-safe).
func (m *memoryCache) Set(hostname string, data map[string]any) {
m.cacheMutex.Lock()
defer m.cacheMutex.Unlock()
m.cache[hostname] = data
}
// Delete removes the cached data for a specific hostname (thread-safe).
func (m *memoryCache) Delete(hostname string) {
m.cacheMutex.Lock()
defer m.cacheMutex.Unlock()
delete(m.cache, hostname)
}
// GatherFacts defines an interface for retrieving host information
type GatherFacts interface {
// HostInfo returns a map of host facts gathered from the system
HostInfo(ctx context.Context) (map[string]any, error)
}
// cacheGatherFact implements GatherFacts with caching capabilities
type cacheGatherFact struct {
// inventoryName is the name of the host in the inventory
inventoryName string
// cacheType specifies the format to cache facts (json, yaml, or memory)
cacheType string
// cacheDir is the cache dir in local
cacheDir string
// getHostInfoFn is the function that actually gathers host information
getHostInfoFn func(context.Context) (map[string]any, error)
}
// newCacheGatherFact creates a new cacheGatherFact instance
func newCacheGatherFact(inventoryName, cacheType, workdir string, getHostInfoFn func(context.Context) (map[string]any, error)) *cacheGatherFact {
return &cacheGatherFact{
inventoryName: inventoryName,
cacheType: cacheType,
cacheDir: filepath.Join(workdir, _const.RuntimeDir, _const.RuntimeGatherFactsCacheDir),
getHostInfoFn: getHostInfoFn,
}
}
// HostInfo returns host information from cache or fetches it remotely if not cached.
// The caching behavior depends on the configured cache type (JSON, YAML, or memory).
func (c *cacheGatherFact) HostInfo(ctx context.Context) (map[string]any, error) {
switch c.cacheType {
case gatherFactsCacheJSON:
return c.handleJSONCache(ctx)
case gatherFactsCacheYAML:
return c.handleYAMLCache(ctx)
case gatherFactsCacheMemory:
return c.handleMemoryCache(ctx)
default:
// fallback: delete possible cache and fetch directly
_ = os.Remove(filepath.Join(c.cacheDir, c.inventoryName+".json"))
_ = os.Remove(filepath.Join(c.cacheDir, c.inventoryName+".yaml"))
cache.Delete(c.inventoryName)
return c.getHostInfoFn(ctx)
}
}
// ensureCacheDir ensures the cache directory exists, creating it if necessary
func (c *cacheGatherFact) ensureCacheDir() error {
if _, err := os.Stat(c.cacheDir); err != nil {
if os.IsNotExist(err) {
return os.MkdirAll(c.cacheDir, os.ModePerm)
}
return err
}
return nil
}
// handleJSONCache handles caching host information in JSON format.
// It attempts to read from the cache file first, falling back to remote fetch if needed.
func (c *cacheGatherFact) handleJSONCache(ctx context.Context) (map[string]any, error) {
if err := c.ensureCacheDir(); err != nil {
return nil, errors.Wrapf(err, "json cache dir error for host %q", c.inventoryName)
}
filename := filepath.Join(c.cacheDir, c.inventoryName+".json")
data, err := os.ReadFile(filename)
if err != nil {
klog.V(4).Infof("JSON cache miss for %q. Fetching remotely.", filename)
return c.fetchAndCache(ctx, filename, json.Marshal)
}
var result map[string]any
return result, json.Unmarshal(data, &result)
}
// handleYAMLCache handles caching host information in YAML format.
// It attempts to read from the cache file first, falling back to remote fetch if needed.
func (c *cacheGatherFact) handleYAMLCache(ctx context.Context) (map[string]any, error) {
if err := c.ensureCacheDir(); err != nil {
return nil, errors.Wrapf(err, "yaml cache dir error for host %q", c.inventoryName)
}
filename := filepath.Join(c.cacheDir, c.inventoryName+".yaml")
data, err := os.ReadFile(filename)
if err != nil {
klog.V(4).Infof("YAML cache miss for %q. Fetching remotely.", filename)
return c.fetchAndCache(ctx, filename, yaml.Marshal)
}
var result map[string]any
return result, yaml.Unmarshal(data, &result)
}
// fetchAndCache fetches host information remotely and caches it to a file.
// marshalFn specifies how to marshal the data (JSON or YAML).
func (c *cacheGatherFact) fetchAndCache(
ctx context.Context,
filename string,
marshalFn func(any) ([]byte, error),
) (map[string]any, error) {
hostInfo, err := c.getHostInfoFn(ctx)
if err != nil {
return nil, err
}
data, err := marshalFn(hostInfo)
if err != nil {
return nil, err
}
return hostInfo, os.WriteFile(filename, data, os.ModePerm)
}
// handleMemoryCache handles caching host information in memory.
// It checks the in-memory cache first, falling back to remote fetch if needed.
func (c *cacheGatherFact) handleMemoryCache(ctx context.Context) (map[string]any, error) {
if cached, exists := cache.Get(c.inventoryName); exists {
return cached, nil
}
hostInfo, err := c.getHostInfoFn(ctx)
if err != nil {
return nil, err
}
cache.Set(c.inventoryName, hostInfo)
return hostInfo, nil
}

View File

@ -35,8 +35,8 @@ const kubeconfigRelPath = ".kube/config"
var _ Connector = &kubernetesConnector{}
func newKubernetesConnector(host string, workdir string, connectorVars map[string]any) (*kubernetesConnector, error) {
kubeconfig, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorKubeconfig)
func newKubernetesConnector(host string, workdir string, hostVars map[string]any) (*kubernetesConnector, error) {
kubeconfig, err := variable.StringVar(nil, hostVars, _const.VariableConnector, _const.VariableConnectorKubeconfig)
if err != nil && host != _const.VariableLocalHost {
return nil, err
}

View File

@ -36,13 +36,21 @@ import (
var _ Connector = &localConnector{}
var _ GatherFacts = &localConnector{}
func newLocalConnector(connectorVars map[string]any) *localConnector {
password, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorPassword)
func newLocalConnector(workdir string, hostVars map[string]any) *localConnector {
password, err := variable.StringVar(nil, hostVars, _const.VariableConnector, _const.VariableConnectorPassword)
if err != nil { // password is not necessary when execute with root user.
klog.V(4).Info("Warning: Failed to obtain local connector password when executing command with sudo. Please ensure the 'kk' process is run by a root-privileged user.")
}
cacheType, _ := variable.StringVar(nil, hostVars, _const.VariableGatherFactsCache)
connector := &localConnector{
Password: password,
Cmd: exec.New(),
shell: defaultSHELL,
}
// Initialize the cacheGatherFact with a function that will call getHostInfoFromRemote
connector.gatherFacts = newCacheGatherFact(_const.VariableLocalHost, cacheType, workdir, connector.getHostInfo)
return &localConnector{Password: password, Cmd: exec.New(), shell: defaultSHELL}
return connector
}
type localConnector struct {
@ -50,6 +58,8 @@ type localConnector struct {
Cmd exec.Interface
// shell to execute command
shell string
gatherFacts *cacheGatherFact
}
// Init initializes the local connector. This method does nothing for localConnector.
@ -116,8 +126,13 @@ func (c *localConnector) ExecuteCommand(ctx context.Context, cmd string) ([]byte
return output, errors.Wrapf(err, "failed to execute command")
}
// HostInfo gathers and returns host information for the local host.
// HostInfo from gatherFacts cache
func (c *localConnector) HostInfo(ctx context.Context) (map[string]any, error) {
return c.gatherFacts.HostInfo(ctx)
}
// getHostInfo from remote
func (c *localConnector) getHostInfo(ctx context.Context) (map[string]any, error) {
switch runtime.GOOS {
case "linux":
// os information

View File

@ -56,38 +56,38 @@ func init() {
var _ Connector = &sshConnector{}
var _ GatherFacts = &sshConnector{}
func newSSHConnector(host string, connectorVars map[string]any) *sshConnector {
func newSSHConnector(workdir, host string, hostVars map[string]any) *sshConnector {
// get host in connector variable. if empty, set default host: host_name.
hostParam, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorHost)
hostParam, err := variable.StringVar(nil, hostVars, _const.VariableConnector, _const.VariableConnectorHost)
if err != nil {
klog.V(4).InfoS("get connector host failed use current hostname", "error", err)
hostParam = host
}
// get port in connector variable. if empty, set default port: 22.
portParam, err := variable.IntVar(nil, connectorVars, _const.VariableConnectorPort)
portParam, err := variable.IntVar(nil, hostVars, _const.VariableConnector, _const.VariableConnectorPort)
if err != nil {
klog.V(4).Infof("connector port is empty use: %v", defaultSSHPort)
portParam = ptr.To(defaultSSHPort)
}
// get user in connector variable. if empty, set default user: root.
userParam, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorUser)
userParam, err := variable.StringVar(nil, hostVars, _const.VariableConnector, _const.VariableConnectorUser)
if err != nil {
klog.V(4).Infof("connector user is empty use: %s", defaultSSHUser)
userParam = defaultSSHUser
}
// get password in connector variable. if empty, should connector by private key.
passwdParam, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorPassword)
passwdParam, err := variable.StringVar(nil, hostVars, _const.VariableConnector, _const.VariableConnectorPassword)
if err != nil {
klog.V(4).InfoS("connector password is empty use public key")
}
// get private key path in connector variable. if empty, set default path: /root/.ssh/id_rsa.
keyParam, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorPrivateKey)
keyParam, err := variable.StringVar(nil, hostVars, _const.VariableConnector, _const.VariableConnectorPrivateKey)
if err != nil {
klog.V(4).Infof("ssh public key is empty, use: %s", defaultSSHPrivateKey)
keyParam = defaultSSHPrivateKey
}
return &sshConnector{
cacheType, _ := variable.StringVar(nil, hostVars, _const.VariableGatherFactsCache)
connector := &sshConnector{
Host: hostParam,
Port: *portParam,
User: userParam,
@ -95,6 +95,11 @@ func newSSHConnector(host string, connectorVars map[string]any) *sshConnector {
PrivateKey: keyParam,
shell: defaultSHELL,
}
// Initialize the cacheGatherFact with a function that will call getHostInfoFromRemote
connector.gatherFacts = newCacheGatherFact(_const.VariableLocalHost, cacheType, workdir, connector.getHostInfo)
return connector
}
type sshConnector struct {
@ -107,6 +112,8 @@ type sshConnector struct {
client *ssh.Client
// shell to execute command
shell string
gatherFacts *cacheGatherFact
}
// Init connector, get ssh.Client
@ -291,8 +298,13 @@ func (c *sshConnector) ExecuteCommand(_ context.Context, cmd string) ([]byte, er
return output, errors.Wrap(err, "failed to execute ssh command")
}
// HostInfo for GatherFacts
// HostInfo from gatherFacts cache
func (c *sshConnector) HostInfo(ctx context.Context) (map[string]any, error) {
return c.gatherFacts.HostInfo(ctx)
}
// getHostInfo from remote
func (c *sshConnector) getHostInfo(ctx context.Context) (map[string]any, error) {
// os information
osVars := make(map[string]any)
var osRelease bytes.Buffer
@ -329,6 +341,8 @@ func (c *sshConnector) HostInfo(ctx context.Context) (map[string]any, error) {
}
procVars[_const.VariableProcessMemory] = convertBytesToMap(mem.Bytes(), ":")
// persistence the hostInfo
return map[string]any{
_const.VariableOS: osVars,
_const.VariableProcess: procVars,

View File

@ -17,7 +17,7 @@ limitations under the License.
package _const
// variable specific key in system
const ( // === From inventory ===
const ( // === From Global Parameter ===
// VariableLocalHost is the default local host name in inventory.
VariableLocalHost = "localhost"
// VariableIPv4 is the ipv4 in inventory.
@ -42,6 +42,8 @@ const ( // === From inventory ===
VariableConnectorPrivateKey = "private_key"
// VariableConnectorKubeconfig is connected auth key for VariableConnector.
VariableConnectorKubeconfig = "kubeconfig"
// VariableGatherFactsCache type in runtimedir. support jsonfile, yamlfile, memory.
VariableGatherFactsCache = "fact_caching"
)
const ( // === From system generate ===

View File

@ -41,19 +41,24 @@ work_dir/
|-- scripts_dir/
|
|-- runtime/
|-- group/version/
| | |-- playbooks/
| | | |-- namespace/
| | | | |-- playbook.yaml
| | | | |-- /playbookName/variable/
| | | | | |-- location.json
| | | | | |-- hostname.json
| | |-- tasks/
| | | |-- namespace/
| | | | |-- task.yaml
| | |-- inventories/
| | | |-- namespace/
| | | | |-- inventory.yaml
|-- | -- gather_facts_caches
|-- | -- | -- inventory
| |-- group/version/
| | | |-- playbooks/
| | | | |-- namespace/
| | | | | |-- playbook.yaml
| | | | | |-- /playbookName/variable/
| | | | | | |-- location.yaml
| | | | | | |-- inventory_name1.yaml
| | | | | | |-- inventory_name2.yaml
|
| | | |-- inventories/
| | | | |-- namespace/
| | | | | |-- inventory.yaml
| |-- group/version/
| | | |-- tasks/
| | | | |-- namespace/
| | | | | |-- task.yaml
|
|-- kubernetes/
@ -103,6 +108,9 @@ const BinaryImagesDir = "images"
// RuntimeDir used to store runtime data for the current task execution. By default, its path is set to {{ .work_dir/runtime }}.
const RuntimeDir = "runtime"
// RuntimeGatherFactsCacheDir is a fixed directory name under runtime, used to store cached host facts gathered during execution.
const RuntimeGatherFactsCacheDir = "gather_facts_caches"
// RuntimePlaybookDir stores playbook resources created during playbook execution.
const RuntimePlaybookDir = "playbooks"

View File

@ -39,7 +39,7 @@ func TestCommand(t *testing.T) {
Variable: &testVariable{},
},
ctxFunc: context.Background,
exceptStderr: "failed to connector of \"\" error: host is not set",
exceptStderr: "failed to connector of \"\" error: workdir in variable should be string",
},
{
name: "exec command success",

View File

@ -20,11 +20,13 @@ import (
"reflect"
"slices"
"strconv"
"strings"
"time"
"github.com/cockroachdb/errors"
kkcorev1 "github.com/kubesphere/kubekey/api/core/v1"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/klog/v2"
@ -203,32 +205,34 @@ func HostsInGroup(inv kkcorev1.Inventory, groupName string) []string {
}
// 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 {
return "", errors.Errorf("cannot find variable %q", key)
}
func StringVar(ctx map[string]any, args map[string]any, keys ...string) (string, error) {
// convert to string
sv, ok := val.(string)
if !ok {
return "", errors.Errorf("variable %q is not string", key)
sv, found, err := unstructured.NestedString(args, keys...)
if err != nil {
return "", errors.WithStack(err)
}
if !found {
return "", errors.Errorf("cannot find variable %q", strings.Join(keys, "."))
}
return tmpl.ParseFunc(d, sv, func(b []byte) string { return string(b) })
return tmpl.ParseFunc(ctx, sv, func(b []byte) string { return string(b) })
}
// 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 {
return nil, errors.Errorf("cannot find variable %q", key)
func StringSliceVar(ctx map[string]any, args map[string]any, keys ...string) ([]string, error) {
val, found, err := unstructured.NestedFieldNoCopy(args, keys...)
if err != nil {
return nil, errors.WithStack(err)
}
if !found {
return nil, errors.Errorf("cannot find variable %q", strings.Join(keys, "."))
}
switch valv := val.(type) {
case []string:
var ss []string
for _, a := range valv {
as, err := tmpl.ParseFunc(d, a, func(b []byte) string { return string(b) })
as, err := tmpl.ParseFunc(ctx, a, func(b []byte) string { return string(b) })
if err != nil {
return nil, err
}
@ -242,12 +246,12 @@ func StringSliceVar(d map[string]any, vars map[string]any, key string) ([]string
for _, a := range valv {
av, ok := a.(string)
if !ok {
klog.V(6).InfoS("variable is not string", "key", key)
klog.V(6).InfoS("variable is not string", "key", keys)
return nil, nil
}
as, err := tmpl.ParseFunc(d, av, func(b []byte) string { return string(b) })
as, err := tmpl.ParseFunc(ctx, av, func(b []byte) string { return string(b) })
if err != nil {
return nil, err
}
@ -257,7 +261,7 @@ func StringSliceVar(d map[string]any, vars map[string]any, key string) ([]string
return ss, nil
case string:
as, err := tmpl.Parse(d, valv)
as, err := tmpl.Parse(ctx, valv)
if err != nil {
return nil, err
}
@ -269,16 +273,20 @@ func StringSliceVar(d map[string]any, vars map[string]any, key string) ([]string
return []string{string(as)}, nil
default:
return nil, errors.Errorf("unsupported variable %q type", key)
return nil, errors.Errorf("unsupported variable %q type", strings.Join(keys, "."))
}
}
// 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 {
return nil, errors.Errorf("cannot find variable %q", key)
func IntVar(ctx map[string]any, args map[string]any, keys ...string) (*int, error) {
val, found, err := unstructured.NestedFieldNoCopy(args, keys...)
if err != nil {
return nil, errors.WithStack(err)
}
if !found {
return nil, errors.Errorf("cannot find variable %q", strings.Join(keys, "."))
}
// default convert to int
v := reflect.ValueOf(val)
switch v.Kind() {
@ -287,34 +295,37 @@ func IntVar(d map[string]any, vars map[string]any, key string) (*int, error) {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
u := v.Uint()
if u > uint64(^uint(0)>>1) {
return nil, errors.Errorf("variable %q value %d overflows int", key, u)
return nil, errors.Errorf("variable %q value %d overflows int", strings.Join(keys, "."), u)
}
return ptr.To(int(u)), nil
case reflect.Float32, reflect.Float64:
return ptr.To(int(v.Float())), nil
case reflect.String:
vs, err := tmpl.ParseFunc(d, v.String(), func(b []byte) string { return string(b) })
vs, err := tmpl.ParseFunc(ctx, v.String(), func(b []byte) string { return string(b) })
if err != nil {
return nil, err
}
atoi, err := strconv.Atoi(vs)
if err != nil {
return nil, errors.Wrapf(err, "failed to convert string %q to int of key %q", vs, key)
return nil, errors.Wrapf(err, "failed to convert string %q to int of key %q", vs, strings.Join(keys, "."))
}
return ptr.To(atoi), nil
default:
return nil, errors.Errorf("unsupported variable %q type", key)
return nil, errors.Errorf("unsupported variable %q type", strings.Join(keys, "."))
}
}
// 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 {
return nil, errors.Errorf("failed to find variable of key %q", key)
func BoolVar(ctx map[string]any, args map[string]any, keys ...string) (*bool, error) {
val, found, err := unstructured.NestedFieldNoCopy(args, keys...)
if err != nil {
return nil, errors.WithStack(err)
}
if !found {
return nil, errors.Errorf("cannot find variable %q", strings.Join(keys, "."))
}
// default convert to int
v := reflect.ValueOf(val)
@ -322,7 +333,7 @@ func BoolVar(d map[string]any, args map[string]any, key string) (*bool, error) {
case reflect.Bool:
return ptr.To(v.Bool()), nil
case reflect.String:
vs, err := tmpl.ParseBool(d, v.String())
vs, err := tmpl.ParseBool(ctx, v.String())
if err != nil {
return nil, err
}
@ -330,12 +341,12 @@ func BoolVar(d map[string]any, args map[string]any, key string) (*bool, error) {
return ptr.To(vs), nil
}
return nil, errors.Errorf("unsupported variable %q type", key)
return nil, errors.Errorf("unsupported variable %q type", strings.Join(keys, "."))
}
// 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)
func DurationVar(ctx map[string]any, args map[string]any, key string) (time.Duration, error) {
stringVar, err := StringVar(ctx, args, key)
if err != nil {
return 0, err
}
@ -359,7 +370,7 @@ func Extension2Variables(ext runtime.RawExtension) map[string]any {
// Extension2Slice convert runtime.RawExtension to slice
// if runtime.RawExtension contains tmpl syntax, parse it.
func Extension2Slice(d map[string]any, ext runtime.RawExtension) []any {
func Extension2Slice(ctx map[string]any, ext runtime.RawExtension) []any {
if len(ext.Raw) == 0 {
return nil
}
@ -370,7 +381,7 @@ func Extension2Slice(d map[string]any, ext runtime.RawExtension) []any {
return data
}
// try converter template string
val, err := Extension2String(d, ext)
val, err := Extension2String(ctx, ext)
if err != nil {
klog.ErrorS(err, "extension2string error", "input", string(ext.Raw))
}
@ -384,7 +395,7 @@ func Extension2Slice(d map[string]any, ext runtime.RawExtension) []any {
// 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) {
func Extension2String(ctx map[string]any, ext runtime.RawExtension) (string, error) {
if len(ext.Raw) == 0 {
return "", nil
}
@ -395,7 +406,7 @@ func Extension2String(d map[string]any, ext runtime.RawExtension) (string, error
input = ns
}
result, err := tmpl.Parse(d, input)
result, err := tmpl.Parse(ctx, input)
if err != nil {
return "", err
}