CLI: added exec hosts subcommand list

This commit is contained in:
Andrew Woodlee 2025-07-23 22:04:48 -05:00
parent 3eced64453
commit 14c81a00a7
7 changed files with 209 additions and 26 deletions

View File

@ -0,0 +1,3 @@
kind: Added
body: 'CLI: added `exec hosts` subcommand `list`'
time: 2025-07-23T22:03:40.24191927-05:00

View File

@ -1,6 +1,9 @@
package cmd
import (
"maps"
"slices"
"git.andrewnw.xyz/CyberShell/backy/pkg/backy"
"git.andrewnw.xyz/CyberShell/backy/pkg/logging"
"github.com/spf13/cobra"
@ -13,11 +16,17 @@ var (
Long: "Hosts executes specified commands on all the hosts defined in config file.\nUse the --commands or -c flag to choose the commands.",
Run: Hosts,
}
hostsListExecCommand = &cobra.Command{
Use: "list list1 list2 ...",
Short: "Runs lists in order specified defined in config file on all hosts.",
Long: "Lists executes specified lists on all the hosts defined in hosts config.\nPass the names of lists as arguments after command.",
Run: HostsList,
}
)
func init() {
hostsExecCommand.Flags().StringArrayVarP(&cmdList, "command", "c", nil, "Accepts space-separated names of commands. Specify multiple times for multiple commands.")
hostsExecCommand.AddCommand(hostsListExecCommand)
parseS3Config()
}
@ -53,3 +62,30 @@ func Hosts(cmd *cobra.Command, args []string) {
backyConfOpts.ExecCmdsOnHosts(cmdList, hostsList)
}
func HostsList(cmd *cobra.Command, args []string) {
backyConfOpts := backy.NewConfigOptions(configFile,
backy.SetLogFile(logFile),
backy.EnableCommandStdOut(cmdStdOut),
backy.SetHostsConfigFile(hostsConfigFile))
backyConfOpts.InitConfig()
backyConfOpts.ParseConfigurationFile()
if len(args) == 0 {
logging.ExitWithMSG("error: no lists specified", 1, &backyConfOpts.Logger)
}
for _, l := range args {
_, listFound := backyConfOpts.CmdConfigLists[l]
if !listFound {
logging.ExitWithMSG("list "+l+" not found", 1, &backyConfOpts.Logger)
}
}
maps.DeleteFunc(backyConfOpts.CmdConfigLists, func(k string, v *backy.CmdList) bool {
return !slices.Contains(args, k)
})
backyConfOpts.ExecuteListOnHosts(args)
}

View File

@ -36,13 +36,13 @@ func Execute() {
}
func init() {
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", "", "log file to write to")
rootCmd.PersistentFlags().StringVar(&logFile, "logFile", "", "log file to write to")
rootCmd.PersistentFlags().BoolVar(&cmdStdOut, "cmdStdOut", false, "Pass to print command output to stdout")
rootCmd.PersistentFlags().StringVarP(&configFile, "config", "f", "", "config file to read from")
rootCmd.PersistentFlags().StringVar(&hostsConfigFile, "hostsConfig", "", "yaml hosts file to read from")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Sets verbose level")
rootCmd.PersistentFlags().StringVar(&s3Endpoint, "s3-endpoint", "", "Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.")
rootCmd.PersistentFlags().StringVar(&s3Endpoint, "s3Endpoint", "", "Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.")
rootCmd.AddCommand(backupCmd, execCmd, cronCmd, versionCmd, listCmd)
}

View File

@ -26,8 +26,9 @@ Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
-h, --help help for backy
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
Use "backy [command] --help" for more information about a command.
@ -51,8 +52,9 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
```
@ -70,8 +72,9 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
```
@ -86,6 +89,7 @@ Usage:
Available Commands:
host Runs command defined in config file on the hosts in order specified.
hosts Runs command defined in config file on the hosts in order specified.
Flags:
-h, --help help for exec
@ -93,8 +97,9 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
Use "backy exec [command] --help" for more information about a command.
@ -117,8 +122,9 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
```
@ -138,8 +144,9 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
```
@ -161,8 +168,9 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
Use "backy list [command] --help" for more information about a command.
@ -181,8 +189,9 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
```
## list lists
@ -199,7 +208,8 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
```

View File

@ -11,6 +11,7 @@ import (
"io"
"os"
"os/exec"
"slices"
"strings"
"text/template"
@ -326,6 +327,71 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<-
results <- "done"
}
}
func cmdListWorkerWithHosts(msgTemps *msgTemplates, jobs <-chan *CmdList, hosts <-chan *Host, results chan<- string, opts *ConfigOpts) {
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 host := range hosts {
for _, cmd := range list.Order {
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 notifyError(logger zerolog.Logger, templates *msgTemplates, list *CmdList, cmdsRan []string, outStructArr []outStruct, err error, cmd *Command) {
errStruct := map[string]interface{}{
@ -397,6 +463,57 @@ func (opts *ConfigOpts) RunListConfig(cron string) {
opts.closeHostConnections()
}
func (opts *ConfigOpts) ExecuteListOnHosts(lists []string) {
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)
}
}
configListsLen := len(opts.CmdConfigLists)
listChan := make(chan *CmdList, configListsLen)
hostChan := make(chan *Host, len(opts.Hosts))
results := make(chan string, configListsLen)
// Start workers
for w := 1; w <= configListsLen; w++ {
go cmdListWorkerWithHosts(mTemps, listChan, hostChan, results, opts)
}
// Enqueue jobs
for listName, cmdConfig := range opts.CmdConfigLists {
if cmdConfig.Name == "" {
cmdConfig.Name = listName
}
listChan <- cmdConfig
}
for _, h := range opts.Hosts {
if h.isProxyHost {
continue
}
hostChan <- h
// for _, proxyHost := range h.ProxyHost {
// if proxyHost.isProxyHost {
// continue
// }
// hostChan <- proxyHost
// }
}
close(listChan)
close(hostChan)
// Process results
for a := 1; a <= configListsLen; a++ {
<-results
}
opts.closeHostConnections()
}
func (opts *ConfigOpts) ExecuteCmds() {
for _, cmd := range opts.executeCmds {
cmdToRun := opts.Cmds[cmd]

View File

@ -1,4 +1,4 @@
FROM debian:buster
FROM ubuntu:latest
# Install SSH server
RUN apt-get update && \
@ -19,7 +19,7 @@ EXPOSE 22
RUN mkdir /var/run/sshd
RUN chmod 0755 /var/run/sshd
# RUN apt-get update && apt-get install -y
RUN apt-get update && apt-get install -y curl
# Start SSH service
CMD ["/usr/sbin/sshd", "-D"]

View File

@ -45,6 +45,16 @@ commands:
- name: "jq"
packageManager: apt
packageOperation: install
output:
toLog: true
testJq:
cmd: jq
shell: bash
Args:
- '--version'
output:
toLog: true
checkDockerVersionWithInvalidRegex:
type: package
@ -56,7 +66,7 @@ commands:
version: '5:28\.0\K.4-1~([A-Za-z0-9]+(\.[A-Za-z0-9]+)+)*'
packageManager: apt
packageOperation: checkVersion
# host: mail.andrewnw.com
cmdLists:
packageCommands:
@ -67,6 +77,13 @@ cmdLists:
- checkDockerPartialVersionWithRegex
- checkDockerVersionWithInvalidRegex
- checkDockerNoVersion
aptCommands:
output:
onFailure: true
order:
- installJq
logging:
verbose: true
verbose: true
cmd-std-out: true