started refactoring into executors using interfaces

This commit is contained in:
2025-09-07 20:44:48 -05:00
parent 7fe07f86a9
commit d45b1562fc
10 changed files with 547 additions and 403 deletions

View File

@ -11,8 +11,8 @@ import (
"io"
"os"
"os/exec"
"slices"
"strings"
"sync"
"text/template"
"embed"
@ -27,6 +27,124 @@ var requiredKeys = []string{"commands"}
var Sprintf = fmt.Sprintf
type CommandExecutor interface {
Run(cmd *Command, opts *ConfigOpts, logger zerolog.Logger) ([]string, error)
}
type OutputHandler interface {
CollectOutput(buf *bytes.Buffer, commandName string, logger zerolog.Logger, wantOutput bool) []string
}
type EnvInjector interface {
Inject(cmd *Command, opts *ConfigOpts)
}
type PackageCommandExecutor struct{}
func (e *PackageCommandExecutor) Run(cmd *Command, opts *ConfigOpts, logger zerolog.Logger) ([]string, error) {
var (
ArgsStr string
cmdOutBuf bytes.Buffer
outputArr []string
cmdOutWriters io.Writer
)
for _, v := range cmd.Args {
ArgsStr += fmt.Sprintf(" %s", v)
}
// Example: Check version operation
if cmd.PackageOperation == PackageOperationCheckVersion {
logger.Info().Msg("Checking package versions")
logger.Info().Msg("")
for _, p := range cmd.Packages {
logger.Info().Str("package", p.Name).Msg("Checking installed and remote package versions")
}
opts.Logger.Info().Msg("")
// Execute the package version command
execCmd := exec.Command(cmd.Cmd, cmd.Args...)
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
if IsCmdStdOutEnabled() {
cmdOutWriters = io.MultiWriter(os.Stdout, &cmdOutBuf)
}
execCmd.Stdout = cmdOutWriters
execCmd.Stderr = cmdOutWriters
if err := execCmd.Run(); err != nil {
return nil, fmt.Errorf("error running command %s: %w", ArgsStr, err)
}
return parsePackageVersion(cmdOutBuf.String(), logger, cmd, cmdOutBuf)
}
// Other package operations (install, upgrade, etc.) can be handled here
// Default: run as a shell command
execCmd := exec.Command(cmd.Cmd, cmd.Args...)
execCmd.Stdout = &cmdOutBuf
execCmd.Stderr = &cmdOutBuf
err := execCmd.Run()
outputArr = logCommandOutput(cmd, cmdOutBuf, logger, outputArr)
if err != nil {
logger.Error().Err(fmt.Errorf("error running package command %s: %w", cmd.Name, err)).Send()
return outputArr, err
}
return outputArr, nil
}
type LocalCommandExecutor struct{}
func (e *LocalCommandExecutor) Run(cmd *Command, opts *ConfigOpts, logger zerolog.Logger) ([]string, error) {
var (
ArgsStr string
cmdOutBuf bytes.Buffer
outputArr []string
)
for _, v := range cmd.Args {
ArgsStr += fmt.Sprintf(" %s", v)
}
// Build the command
var localCMD *exec.Cmd
if cmd.Shell != "" {
logger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine in %s", cmd.Name, cmd.Shell)).Send()
ArgsStr = fmt.Sprintf("%s %s", cmd.Cmd, ArgsStr)
localCMD = exec.Command(cmd.Shell, "-c", ArgsStr)
} else {
localCMD = exec.Command(cmd.Cmd, cmd.Args...)
}
// Set working directory
if cmd.Dir != nil {
localCMD.Dir = *cmd.Dir
}
// Inject environment variables (extract this to an EnvInjector if desired)
// injectEnvIntoLocalCMD(...)
// Set output writers
cmdOutWriters := io.MultiWriter(&cmdOutBuf)
if IsCmdStdOutEnabled() {
cmdOutWriters = io.MultiWriter(os.Stdout, &cmdOutBuf)
}
localCMD.Stdout = cmdOutWriters
localCMD.Stderr = cmdOutWriters
// Run the command
err := localCMD.Run()
outputArr = logCommandOutput(cmd, cmdOutBuf, logger, outputArr)
if err != nil {
logger.Error().Err(fmt.Errorf("error when running cmd %s: %w", cmd.Name, err)).Send()
return outputArr, err
}
return outputArr, nil
}
// RunCmd runs a Command.
// The environment of local commands will be the machine's environment plus any extra
// variables specified in the Env file or Environment.
@ -70,29 +188,10 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
}
} else {
// Handle package operations
if command.Type == PackageCommandType && command.PackageOperation == PackageOperationCheckVersion {
opts.Logger.Info().Msg("")
for _, p := range command.Packages {
cmdCtxLogger.Info().Str("package", p.Name).Msg("Checking installed and remote package versions")
}
opts.Logger.Info().Msg("")
// Execute the package version command
cmd := exec.Command(command.Cmd, command.Args...)
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
if IsCmdStdOutEnabled() {
cmdOutWriters = io.MultiWriter(os.Stdout, &cmdOutBuf)
}
cmd.Stdout = cmdOutWriters
cmd.Stderr = cmdOutWriters
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("error running command %s: %w", ArgsStr, err)
}
return parsePackageVersion(cmdOutBuf.String(), cmdCtxLogger, command, cmdOutBuf)
switch command.Type {
case PackageCommandType:
var executor PackageCommandExecutor
return executor.Run(command, opts, cmdCtxLogger)
}
var localCMD *exec.Cmd
@ -393,6 +492,163 @@ func cmdListWorkerWithHosts(msgTemps *msgTemplates, jobs <-chan *CmdList, hosts
}
}
// func cmdListWorkerExecuteCommandsInParallel(msgTemps *msgTemplates, jobs <-chan *CmdList, hosts <-chan *Host, results chan<- string, opts *ConfigOpts) {
// opts.Logger.Info().Msg("Running commands in parallel")
// for list := range jobs {
// fieldsMap := map[string]interface{}{"list": list.Name}
// var cmdLogger zerolog.Logger
// var commandExecuted *Command
// var cmdsRan []string
// var outStructArr []outStruct
// var hasError bool // Tracks if any command in the list failed
// for _, cmd := range list.Order {
// for host := range hosts {
// cmdToRun := opts.Cmds[cmd]
// if cmdToRun.Host != host.Host {
// cmdToRun.Host = host.Host
// cmdToRun.RemoteHost = host
// }
// commandExecuted = cmdToRun
// currentCmd := cmdToRun.Name
// fieldsMap["cmd"] = currentCmd
// cmdLogger = cmdToRun.GenerateLogger(opts)
// cmdLogger.Info().Fields(fieldsMap).Send()
// outputArr, runErr := cmdToRun.RunCmd(cmdLogger, opts)
// cmdsRan = append(cmdsRan, cmd)
// if runErr != nil {
// cmdLogger.Err(runErr).Send()
// cmdToRun.ExecuteHooks("error", opts)
// // Notify failure
// if list.NotifyConfig != nil {
// notifyError(cmdLogger, msgTemps, list, cmdsRan, outStructArr, runErr, cmdToRun)
// }
// // Execute error hooks for the failed command
// hasError = true
// break
// }
// if list.GetCommandOutputInNotificationsOnSuccess || cmdToRun.Output.InList {
// outStructArr = append(outStructArr, outStruct{
// CmdName: currentCmd,
// CmdExecuted: currentCmd,
// Output: outputArr,
// })
// }
// }
// if !hasError && list.NotifyConfig != nil && list.Notify.OnFailure {
// notifySuccess(cmdLogger, msgTemps, list, cmdsRan, outStructArr)
// }
// if !hasError {
// commandExecuted.ExecuteHooks("success", opts)
// }
// commandExecuted.ExecuteHooks("final", opts)
// }
// results <- "done"
// }
// }
func cmdListWorkerExecuteCommandsInParallel(msgTemps *msgTemplates, jobs <-chan *CmdList, hosts <-chan *Host, results chan<- string, opts *ConfigOpts) {
opts.Logger.Info().Msg("Running commands in parallel")
for list := range jobs {
fieldsMap := map[string]interface{}{"list": list.Name}
var cmdLogger zerolog.Logger
var commandExecuted *Command
var cmdsRan []string
var outStructArr []outStruct
var hasError bool // Tracks if any command in the list failed
var wg sync.WaitGroup
hostList := []*Host{}
for host := range hosts {
hostList = append(hostList, host)
}
println("Total hosts to run commands on:", len(hostList))
println("Total commands to run:", len(list.Order))
for _, cmd := range list.Order {
cmdsRan = append(cmdsRan, cmd)
println("Running cmd:", cmd, "on", len(hostList), "hosts")
outputChan := make(chan outStruct, len(hostList))
errorChan := make(chan error, len(hostList))
// cmdToRun := opts.Cmds[cmd]
origCmd := opts.Cmds[cmd]
for _, host := range hostList {
wg.Add(1)
cmdToRun := *origCmd // shallow copy
commandExecuted = origCmd
if cmdToRun.Host != host.Host {
cmdToRun.Host = host.Host
cmdToRun.RemoteHost = host
}
cmdLogger = cmdToRun.GenerateLogger(opts)
cmdLogger.Info().Fields(fieldsMap).Send()
print("Running cmd on: ", host.Host, "\n")
go func(cmd string, host *Host) {
defer wg.Done()
currentCmd := cmdToRun.Name
fieldsMap["cmd"] = currentCmd
outputArr, runErr := cmdToRun.RunCmd(cmdLogger, opts)
if runErr != nil {
cmdLogger.Err(runErr).Send()
cmdToRun.ExecuteHooks("error", opts)
errorChan <- runErr
return
}
if list.GetCommandOutputInNotificationsOnSuccess || cmdToRun.Output.InList {
outputChan <- outStruct{
CmdName: currentCmd,
CmdExecuted: currentCmd,
Output: outputArr,
}
}
}(cmd, host)
}
wg.Wait()
close(outputChan)
close(errorChan)
for out := range outputChan {
outStructArr = append(outStructArr, out)
}
if len(errorChan) > 0 {
hasError = true
runErr := <-errorChan
if list.NotifyConfig != nil {
notifyError(cmdLogger, msgTemps, list, cmdsRan, outStructArr, runErr, commandExecuted)
}
break
}
if !hasError && list.NotifyConfig != nil && list.Notify.OnFailure {
notifySuccess(cmdLogger, msgTemps, list, cmdsRan, outStructArr)
}
if !hasError {
commandExecuted.ExecuteHooks("success", opts)
}
}
commandExecuted.ExecuteHooks("final", opts)
results <- "done"
}
}
func notifyError(logger zerolog.Logger, templates *msgTemplates, list *CmdList, cmdsRan []string, outStructArr []outStruct, err error, cmd *Command) {
errStruct := map[string]interface{}{
"listName": list.Name,
@ -463,17 +719,17 @@ func (opts *ConfigOpts) RunListConfig(cron string) {
opts.closeHostConnections()
}
func (opts *ConfigOpts) ExecuteListOnHosts(lists []string) {
func (opts *ConfigOpts) ExecuteListOnHosts(lists []string, parallel bool) {
mTemps := &msgTemplates{
err: template.Must(template.New("error.txt").ParseFS(templates, "templates/error.txt")),
success: template.Must(template.New("success.txt").ParseFS(templates, "templates/success.txt")),
}
for _, l := range opts.CmdConfigLists {
if !slices.Contains(lists, l.Name) {
delete(opts.CmdConfigLists, l.Name)
}
}
// for _, l := range opts.CmdConfigLists {
// if !slices.Contains(lists, l.Name) {
// delete(opts.CmdConfigLists, l.Name)
// }
// }
configListsLen := len(opts.CmdConfigLists)
listChan := make(chan *CmdList, configListsLen)
hostChan := make(chan *Host, len(opts.Hosts))
@ -481,7 +737,11 @@ func (opts *ConfigOpts) ExecuteListOnHosts(lists []string) {
// Start workers
for w := 1; w <= configListsLen; w++ {
go cmdListWorkerWithHosts(mTemps, listChan, hostChan, results, opts)
if parallel {
go cmdListWorkerExecuteCommandsInParallel(mTemps, listChan, hostChan, results, opts)
} else {
go cmdListWorkerWithHosts(mTemps, listChan, hostChan, results, opts)
}
}
// Enqueue jobs
@ -645,6 +905,33 @@ func (opts *ConfigOpts) ExecCmdsOnHosts(cmdList []string, hostsList []string) {
}
}
func (opts *ConfigOpts) ExecCmdsOnHostsInParallel(cmdList []string, hostsList []string) {
opts.Logger.Info().Msg("Executing commands in parallel on hosts")
// Iterate over hosts and exec commands
for _, c := range cmdList {
for _, h := range hostsList {
host := opts.Hosts[h]
cmd := opts.Cmds[c]
cmd.RemoteHost = host
cmd.Host = h
if IsHostLocal(h) {
_, err := cmd.RunCmd(cmd.GenerateLogger(opts), opts)
if err != nil {
opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send()
}
} else {
cmd.Host = host.Host
opts.Logger.Info().Str("host", h).Str("cmd", c).Send()
_, err := cmd.RunCmdOnHost(cmd.GenerateLogger(opts), opts)
if err != nil {
opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send()
}
}
}
}
}
func logCommandOutput(command *Command, cmdOutBuf bytes.Buffer, cmdCtxLogger zerolog.Logger, outputArr []string) []string {
outScanner := bufio.NewScanner(&cmdOutBuf)

View File

@ -1,83 +1,83 @@
package backy
import (
"context"
"fmt"
"io"
"log"
"testing"
// import (
// "context"
// "fmt"
// "io"
// "log"
// "testing"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman"
packagemanagercommon "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
// "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman"
// packagemanagercommon "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/common"
// "github.com/stretchr/testify/assert"
// "github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
)
// "github.com/testcontainers/testcontainers-go"
// )
// TestConfigOptions tests the configuration options for the backy package.
func Test_ErrorHook(t *testing.T) {
// // TestConfigOptions tests the configuration options for the backy package.
// func Test_ErrorHook(t *testing.T) {
configFile := "../../tests/ErrorHook.yml"
logFile := "ErrorHook.log"
backyConfigOptions := NewConfigOptions(configFile, SetLogFile(logFile))
backyConfigOptions.InitConfig()
backyConfigOptions.ParseConfigurationFile()
backyConfigOptions.RunListConfig("")
// configFile := "../../tests/ErrorHook.yml"
// logFile := "ErrorHook.log"
// backyConfigOptions := NewConfigOptions(configFile, SetLogFile(logFile))
// backyConfigOptions.InitConfig()
// backyConfigOptions.ParseConfigurationFile()
// backyConfigOptions.RunListConfig("")
}
// }
func TestSettingCommandInfoPackageCommandDnf(t *testing.T) {
// func TestSettingCommandInfoPackageCommandDnf(t *testing.T) {
packagecommand := &Command{
Type: PackageCommandType,
PackageManager: "dnf",
Shell: "zsh",
PackageOperation: PackageOperationCheckVersion,
Packages: []packagemanagercommon.Package{{Name: "docker-ce"}},
}
dnfPackage, _ := pkgman.PackageManagerFactory("dnf", pkgman.WithoutAuth())
// packagecommand := &Command{
// Type: PackageCommandType,
// PackageManager: "dnf",
// Shell: "zsh",
// PackageOperation: PackageOperationCheckVersion,
// Packages: []packagemanagercommon.Package{{Name: "docker-ce"}},
// }
// dnfPackage, _ := pkgman.PackageManagerFactory("dnf", pkgman.WithoutAuth())
packagecommand.pkgMan = dnfPackage
PackageCommand := getCommandTypeAndSetCommandInfo(packagecommand)
// packagecommand.pkgMan = dnfPackage
// PackageCommand := getCommandTypeAndSetCommandInfo(packagecommand)
assert.Equal(t, "dnf", PackageCommand.Cmd)
// assert.Equal(t, "dnf", PackageCommand.Cmd)
}
// }
func TestWithDockerFile(t *testing.T) {
ctx := context.Background()
// func TestWithDockerFile(t *testing.T) {
// ctx := context.Background()
docker, err := testcontainers.Run(ctx, "",
testcontainers.WithDockerfile(testcontainers.FromDockerfile{
Context: "../../tests/docker",
Dockerfile: "Dockerfile",
KeepImage: false,
// BuildOptionsModifier: func(buildOptions *types.ImageBuildOptions) {
// buildOptions.Target = "target2"
// },
}),
)
// docker.
// docker, err := testcontainers.Run(ctx, "",
// testcontainers.WithDockerfile(testcontainers.FromDockerfile{
// Context: "../../tests/docker",
// Dockerfile: "Dockerfile",
// KeepImage: false,
// // BuildOptionsModifier: func(buildOptions *types.ImageBuildOptions) {
// // buildOptions.Target = "target2"
// // },
// }),
// )
// // docker.
if err != nil {
log.Printf("failed to start container: %v", err)
return
}
// if err != nil {
// log.Printf("failed to start container: %v", err)
// return
// }
r, err := docker.Logs(ctx)
if err != nil {
log.Printf("failed to get logs: %v", err)
return
}
// r, err := docker.Logs(ctx)
// if err != nil {
// log.Printf("failed to get logs: %v", err)
// return
// }
logs, err := io.ReadAll(r)
if err != nil {
log.Printf("failed to read logs: %v", err)
return
}
// logs, err := io.ReadAll(r)
// if err != nil {
// log.Printf("failed to read logs: %v", err)
// return
// }
fmt.Println(string(logs))
// fmt.Println(string(logs))
require.NoError(t, err)
}
// require.NoError(t, err)
// }

View File

@ -3,31 +3,51 @@ package backy
import (
"encoding/json"
"os"
"time"
)
type MetricFile struct {
Filename string `json:"filename"`
CommandMetrics map[string]*Metrics `json:"commandMetrics"`
ListMetrics map[string]*Metrics `json:"listMetrics"`
}
type Metrics struct {
SuccessfulExecutions uint64 `json:"successful_executions"`
FailedExecutions uint64 `json:"failed_executions"`
TotalExecutions uint64 `json:"total_executions"`
ExecutionTime float64 `json:"execution_time"` // in seconds
AverageExecutionTime float64 `json:"average_execution_time"` // in seconds
SuccessRate float64 `json:"success_rate"` // percentage of successful executions
FailureRate float64 `json:"failure_rate"` // percentage of failed executions
DateStartedLast string `json:"dateStartedLast"`
DateLastFinished string `json:"dateLastFinished"`
DateLastFinishedSuccessfully string `json:"dateLastFinishedSuccessfully"`
SuccessfulExecutions uint64 `json:"successfulExecutions"`
FailedExecutions uint64 `json:"failedExecutions"`
TotalExecutions uint64 `json:"totalExecutions"`
TotalExecutionTime float64 `json:"lastExecutionTime"` // in seconds
AverageExecutionTime float64 `json:"totalExecutionTime"` // in seconds
SuccessRate float64 `json:"successRate"` // percentage of successful executions
FailureRate float64 `json:"failureRate"` // percentage of failed executions
}
func NewMetrics() *Metrics {
return &Metrics{
DateStartedLast: time.Now().Format(time.RFC3339),
SuccessfulExecutions: 0,
FailedExecutions: 0,
TotalExecutions: 0,
ExecutionTime: 0.0,
TotalExecutionTime: 0.0,
AverageExecutionTime: 0.0,
SuccessRate: 0.0,
FailureRate: 0.0,
}
}
func (m *Metrics) Update(success bool, executionTime float64) {
func NewMetricsFromFile(filename string) *MetricFile {
return &MetricFile{
Filename: filename,
CommandMetrics: make(map[string]*Metrics),
ListMetrics: make(map[string]*Metrics),
}
}
func (m *Metrics) Update(success bool, executionTime float64, dateLastFinished time.Time) {
m.TotalExecutions++
if success {
m.SuccessfulExecutions++
@ -35,8 +55,10 @@ func (m *Metrics) Update(success bool, executionTime float64) {
m.FailedExecutions++
}
m.ExecutionTime += executionTime
m.AverageExecutionTime = m.ExecutionTime / float64(m.TotalExecutions)
m.DateLastFinished = dateLastFinished.Format(time.RFC3339)
m.TotalExecutionTime += executionTime
m.AverageExecutionTime = m.TotalExecutionTime / float64(m.TotalExecutions)
if m.TotalExecutions > 0 {
m.SuccessRate = float64(m.SuccessfulExecutions) / float64(m.TotalExecutions) * 100
@ -44,14 +66,23 @@ func (m *Metrics) Update(success bool, executionTime float64) {
}
}
func SaveToFile(metrics *Metrics, filename string) error {
data, err := json.MarshalIndent(metrics, "", " ")
func (metricFile *MetricFile) SaveToFile() error {
data, err := json.MarshalIndent(metricFile, "", " ")
if err != nil {
return err
}
return os.WriteFile(filename, data, 0644)
return os.WriteFile(metricFile.Filename, data, 0644)
}
func LoadFromFile(filename string) (*Metrics, error) {
return nil, nil
func LoadMetricsFromFile(filename string) (*MetricFile, error) {
jsonData, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
var metrics MetricFile
err = json.Unmarshal(jsonData, &metrics)
if err != nil {
return nil, err
}
return &metrics, nil
}

View File

@ -1,7 +1,67 @@
package backy
import "testing"
import (
"testing"
"time"
)
func TestAddingMetricsForCommand(t *testing.T) {
// Create a new MetricFile
metricFile := NewMetricsFromFile("test_metrics.json")
metricFile, err := LoadMetricsFromFile(metricFile.Filename)
if err != nil {
t.Errorf("Failed to load metrics from file: %v", err)
}
// Add metrics for a command
commandName := "test_command"
if _, exists := metricFile.CommandMetrics[commandName]; !exists {
metricFile.CommandMetrics[commandName] = NewMetrics()
}
// Update the metrics for the command
executionTime := 1.8 // Example execution time in seconds
success := true // Example success status
metricFile.CommandMetrics[commandName].Update(success, executionTime, time.Now())
// Check if the metrics were updated correctly
if metricFile.CommandMetrics[commandName].SuccessfulExecutions > 50 {
t.Errorf("Expected 1 successful execution, got %d", metricFile.CommandMetrics[commandName].SuccessfulExecutions)
}
if metricFile.CommandMetrics[commandName].TotalExecutions > 50 {
t.Errorf("Expected 1 total execution, got %d", metricFile.CommandMetrics[commandName].TotalExecutions)
}
// if metricFile.CommandMetrics[commandName].TotalExecutionTime != executionTime {
// t.Errorf("Expected execution time %f, got %f", executionTime, metricFile.CommandMetrics[commandName].TotalExecutionTime)
// }
err = metricFile.SaveToFile()
if err != nil {
t.Errorf("Failed to save metrics to file: %v", err)
}
listName := "test_list"
if _, exists := metricFile.ListMetrics[listName]; !exists {
metricFile.ListMetrics[listName] = NewMetrics()
}
// Update the metrics for the list
metricFile.ListMetrics[listName].Update(success, executionTime, time.Now())
if metricFile.ListMetrics[listName].SuccessfulExecutions > 50 {
t.Errorf("Expected 1 successful execution for list, got %d", metricFile.ListMetrics[listName].SuccessfulExecutions)
}
if metricFile.ListMetrics[listName].TotalExecutions > 50 {
t.Errorf("Expected 1 total execution for list, got %d", metricFile.ListMetrics[listName].TotalExecutions)
}
// if metricFile.ListMetrics[listName].TotalExecutionTime > executionTime {
// t.Errorf("Expected execution time %f for list, got %f", executionTime, metricFile.ListMetrics[listName].TotalExecutionTime)
// }
// Save the metrics to a file
err = metricFile.SaveToFile()
if err != nil {
t.Errorf("Failed to save metrics to file: %v", err)
}
}

View File

@ -27,6 +27,10 @@ import (
var PrivateKeyExtraInfoErr = errors.New("Private key may be encrypted. \nIf encrypted, make sure the password is specified correctly in the correct section. This may be done in one of two ways: \n Using external directives - see docs \n privatekeypassword: password (not recommended). \n ")
var TS = strings.TrimSpace
type RemoteHostCommandExecutor interface {
RunCmdOnHost(command *Command, commandSession *ssh.Session, cmdCtxLogger zerolog.Logger, cmdOutBuf bytes.Buffer) ([]string, error)
}
// ConnectToHost connects to a host by looking up the config values in the file ~/.ssh/config
// It uses any set values and looks up an unset values in the config files
// remoteHost is modified directly. The *ssh.Client is returned as part of remoteHost,
@ -483,26 +487,8 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
case ScriptFileCommandType:
return command.runScriptFile(commandSession, cmdCtxLogger, &cmdOutBuf)
case PackageCommandType:
if command.PackageOperation == PackageOperationCheckVersion {
commandSession.Stderr = nil
// Execute the package version command remotely
// Parse the output of package version command
// Compare versions
// Check if a specific version is specified
commandSession.Stdout = nil
return checkPackageVersion(cmdCtxLogger, command, commandSession, cmdOutBuf)
} else {
if command.Shell != "" {
ArgsStr = fmt.Sprintf("%s -c '%s %s'", command.Shell, command.Cmd, ArgsStr)
} else {
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
}
cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send()
// Run simple command
if err := commandSession.Run(ArgsStr); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running command: %w", err)
}
}
var remoteHostPackageExecutor RemoteHostPackageExecutor
return remoteHostPackageExecutor.RunCmdOnHost(command, commandSession, cmdCtxLogger, cmdOutBuf)
default:
if command.Shell != "" {
ArgsStr = fmt.Sprintf("%s -c '%s %s'", command.Shell, command.Cmd, ArgsStr)
@ -539,7 +525,6 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
}
defer rmFileFunc()
// commandSession.Stdin = command.stdin
}
if err := commandSession.Run(ArgsStr); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running command: %w", err)
@ -823,3 +808,34 @@ func IsHostLocal(host string) bool {
host = strings.ToLower(host)
return host == "127.0.0.1" || host == "localhost" || host == ""
}
type RemoteHostPackageExecutor struct{}
func (r RemoteHostPackageExecutor) RunCmdOnHost(command *Command, commandSession *ssh.Session, cmdCtxLogger zerolog.Logger, cmdOutBuf bytes.Buffer) ([]string, error) {
var ArgsStr string
// Prepare command arguments
for _, v := range command.Args {
ArgsStr += fmt.Sprintf(" %s", v)
}
if command.PackageOperation == PackageOperationCheckVersion {
commandSession.Stderr = nil
// Execute the package version command remotely
// Parse the output of package version command
// Compare versions
// Check if a specific version is specified
commandSession.Stdout = nil
return checkPackageVersion(cmdCtxLogger, command, commandSession, cmdOutBuf)
}
if command.Shell != "" {
ArgsStr = fmt.Sprintf("%s -c '%s %s'", command.Shell, command.Cmd, ArgsStr)
} else {
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
}
cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send()
// Run simple command
if err := commandSession.Run(ArgsStr); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running command: %w", err)
}
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil
}

View File

@ -12,7 +12,7 @@ type RemoteFetcher interface {
// Parse decodes the raw data into a Go structure (e.g., Commands, CommandLists)
// Takes the raw data as input and populates the target interface
Parse(data []byte, target interface{}) error
Parse(data []byte, target any) error
// Hash returns the hash of the configuration data
Hash(data []byte) string