Compare commits
5 Commits
11d5121954
...
develop
Author | SHA1 | Date | |
---|---|---|---|
7fe07f86a9 | |||
14c81a00a7 | |||
3eced64453 | |||
c284d928fd | |||
dd9da9452b |
3
.changes/unreleased/Added-20250715-202303.yaml
Normal file
3
.changes/unreleased/Added-20250715-202303.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Added
|
||||
body: 'CLI: Exec subcommand `hosts`. See documentation for more details.'
|
||||
time: 2025-07-15T20:23:03.647128713-05:00
|
3
.changes/unreleased/Added-20250723-220340.yaml
Normal file
3
.changes/unreleased/Added-20250723-220340.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Added
|
||||
body: 'CLI: added `exec hosts` subcommand `list`'
|
||||
time: 2025-07-23T22:03:40.24191927-05:00
|
@ -21,7 +21,7 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
execCmd.AddCommand(hostExecCommand)
|
||||
execCmd.AddCommand(hostExecCommand, hostsExecCommand)
|
||||
|
||||
}
|
||||
|
||||
|
91
cmd/hosts.go
Normal file
91
cmd/hosts.go
Normal file
@ -0,0 +1,91 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/backy"
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/logging"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
hostsExecCommand = &cobra.Command{
|
||||
Use: "hosts [--command=command1 --command=command2 ... | -c command1 -c command2 ...]",
|
||||
Short: "Runs command defined in config file on the hosts in order specified.",
|
||||
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.AddCommand(hostsListExecCommand)
|
||||
parseS3Config()
|
||||
|
||||
}
|
||||
|
||||
// cli input should be hosts and commands. Hosts are defined in config files.
|
||||
// commands can be passed by the following mutually exclusive options:
|
||||
// 1. as a list of commands defined in the config file
|
||||
// 2. stdin (on command line) (TODO)
|
||||
|
||||
func Hosts(cmd *cobra.Command, args []string) {
|
||||
backyConfOpts := backy.NewConfigOptions(configFile,
|
||||
backy.SetLogFile(logFile),
|
||||
backy.EnableCommandStdOut(cmdStdOut),
|
||||
backy.SetHostsConfigFile(hostsConfigFile))
|
||||
backyConfOpts.InitConfig()
|
||||
|
||||
backyConfOpts.ParseConfigurationFile()
|
||||
|
||||
for _, h := range backyConfOpts.Hosts {
|
||||
|
||||
hostsList = append(hostsList, h.Host)
|
||||
}
|
||||
|
||||
if cmdList == nil {
|
||||
logging.ExitWithMSG("error: commands must be specified", 1, &backyConfOpts.Logger)
|
||||
}
|
||||
for _, c := range cmdList {
|
||||
_, cmdFound := backyConfOpts.Cmds[c]
|
||||
if !cmdFound {
|
||||
logging.ExitWithMSG("cmd "+c+" not found", 1, &backyConfOpts.Logger)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
```
|
||||
|
@ -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]
|
||||
|
10
pkg/backy/planForHosts.md
Normal file
10
pkg/backy/planForHosts.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Running commands on hosts
|
||||
|
||||
I want all commands in a list to be able to be run on all hosts. The underlying solution will be using a function to run the list on a host, and therefore change the host on the commands. This can be done in several ways:
|
||||
|
||||
1. The commands can have a `Hosts` field that will be a []string. This array can be populated several ways:
|
||||
- From the config file
|
||||
- using CLI options and commands
|
||||
The commands can be run in succession on all hosts using functions
|
||||
|
||||
2. The existing `Host` field can be modified in a function. The commands need to be added to a `[]*Command` slice so that all hosts can be run.
|
@ -22,7 +22,6 @@ commands:
|
||||
outputToLog: true
|
||||
Args:
|
||||
- "-v"
|
||||
host: email-svr
|
||||
|
||||
cmdLists:
|
||||
TestHooks:
|
||||
|
@ -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"]
|
||||
|
@ -1,3 +1,4 @@
|
||||
cd ~/Projects/backy/tests/docker
|
||||
docker container rm -f ssh_server_container
|
||||
docker build -t ssh_server_image .
|
||||
docker run -d -p 2222:22 --name ssh_server_container ssh_server_image
|
@ -2,5 +2,5 @@ hosts:
|
||||
docker:
|
||||
port: 2222
|
||||
Hostname: localhost
|
||||
user: backy
|
||||
user: root
|
||||
IdentityFile: ./docker/backytest
|
@ -38,6 +38,24 @@ commands:
|
||||
packageManager: apt
|
||||
packageOperation: checkVersion
|
||||
|
||||
installJq:
|
||||
type: package
|
||||
shell: bash
|
||||
packages:
|
||||
- name: "jq"
|
||||
packageManager: apt
|
||||
packageOperation: install
|
||||
output:
|
||||
toLog: true
|
||||
|
||||
testJq:
|
||||
cmd: jq
|
||||
shell: bash
|
||||
Args:
|
||||
- '--version'
|
||||
output:
|
||||
toLog: true
|
||||
|
||||
checkDockerVersionWithInvalidRegex:
|
||||
type: package
|
||||
shell: zsh
|
||||
@ -48,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:
|
||||
@ -60,5 +78,12 @@ cmdLists:
|
||||
- checkDockerVersionWithInvalidRegex
|
||||
- checkDockerNoVersion
|
||||
|
||||
aptCommands:
|
||||
output:
|
||||
onFailure: true
|
||||
order:
|
||||
- installJq
|
||||
|
||||
logging:
|
||||
verbose: true
|
||||
cmd-std-out: true
|
Reference in New Issue
Block a user