kubekey/pkg/web/handler/executor.go
liujian 620b7f56a3
feat: enhance connector interface and implementations (#2675)
- Updated the Connector interface to return both stdout and stderr for command execution.
- Modified implementations in local, kubernetes, and ssh connectors to support the new return values.
- Improved documentation for the Connector interface methods for clarity.
- Added error handling for stderr in command execution across connectors.
- Introduced new utility functions for IP parsing and checking localhost IPs.

Signed-off-by: joyceliu <joyceliu@yunify.com>
2025-07-30 08:02:34 +00:00

94 lines
3.1 KiB
Go

package handler
import (
"context"
"os"
"path/filepath"
"sync"
"github.com/cockroachdb/errors"
kkcorev1 "github.com/kubesphere/kubekey/api/core/v1"
"k8s.io/klog/v2"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
_const "github.com/kubesphere/kubekey/v4/pkg/const"
"github.com/kubesphere/kubekey/v4/pkg/executor"
)
var playbookManager = &manager{
manager: make(map[string]context.CancelFunc),
}
// playbookManager is responsible for managing playbook execution contexts and their cancellation.
// It uses a mutex to ensure thread-safe access to the manager map.
type manager struct {
sync.Mutex
manager map[string]context.CancelFunc // Map of playbook key to its cancel function
}
func (m *manager) executor(playbook *kkcorev1.Playbook, client ctrlclient.Client) error {
// Build the log file path for the playbook execution
filename := filepath.Join(
_const.GetWorkdirFromConfig(playbook.Spec.Config),
_const.RuntimeDir,
kkcorev1.SchemeGroupVersion.Group,
kkcorev1.SchemeGroupVersion.Version,
"playbooks",
playbook.Namespace,
playbook.Name,
playbook.Name+".log",
)
// Ensure the directory for the log file exists
if _, err := os.Stat(filepath.Dir(filename)); err != nil {
if !os.IsNotExist(err) {
return errors.Wrapf(err, "failed to stat playbook dir %q", filepath.Dir(filename))
}
// If directory does not exist, create it
if err := os.MkdirAll(filepath.Dir(filename), os.ModePerm); err != nil {
return errors.Wrapf(err, "failed to create playbook dir %q", filepath.Dir(filename))
}
}
// Open the log file for writing
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return errors.Wrapf(err, "failed to open log file", "file", filename)
}
defer file.Close()
// Create a cancellable context for playbook execution
ctx, cancel := context.WithCancel(context.Background())
// Register the playbook and its cancel function in the playbookManager
m.addPlaybook(playbook, cancel)
// Execute the playbook and write output to the log file
if err := executor.NewPlaybookExecutor(ctx, client, playbook, file).Exec(ctx); err != nil {
klog.ErrorS(err, "failed to exec playbook", "playbook", playbook.Name)
}
// Remove the playbook from the playbookManager after execution
m.deletePlaybook(playbook)
return nil
}
// addPlaybook adds a playbook and its cancel function to the manager map.
func (m *manager) addPlaybook(playbook *kkcorev1.Playbook, cancel context.CancelFunc) {
m.Lock()
defer m.Unlock()
m.manager[ctrlclient.ObjectKeyFromObject(playbook).String()] = cancel
}
// deletePlaybook removes a playbook from the manager map.
func (m *manager) deletePlaybook(playbook *kkcorev1.Playbook) {
m.Lock()
defer m.Unlock()
delete(m.manager, ctrlclient.ObjectKeyFromObject(playbook).String())
}
// stopPlaybook cancels the context for a running playbook, if it exists.
func (m *manager) stopPlaybook(playbook *kkcorev1.Playbook) {
// Attempt to cancel the playbook's context if it exists in the manager
if cancel, ok := m.manager[ctrlclient.ObjectKeyFromObject(playbook).String()]; ok {
cancel()
}
}