mirror of
https://github.com/kubesphere/kubekey.git
synced 2025-12-25 17:12:50 +00:00
178 lines
5.7 KiB
Go
178 lines
5.7 KiB
Go
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
|
|
}
|