mirror of
https://github.com/kubesphere/kubekey.git
synced 2025-12-25 17:12:50 +00:00
feat: add gather_facts cache (#2558)
Signed-off-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
parent
a876b3c9d7
commit
13df73e0ea
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 ===
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue