Compare commits

..

34 Commits

Author SHA1 Message Date
7fe07f86a9 CLI: added exec hosts subcommand list 2025-07-23 22:05:44 -05:00
14c81a00a7 CLI: added exec hosts subcommand list 2025-07-23 22:04:48 -05:00
3eced64453 CLI: Exec subcommand host 2025-07-15 20:24:30 -05:00
c284d928fd CLI: Exec subcommand host 2025-07-15 20:24:06 -05:00
dd9da9452b CLI: Exec subcommand host 2025-07-15 20:23:58 -05:00
11d5121954 update deps 2025-07-14 20:48:53 -05:00
66d622b474 Improved error message for remote version package output 2025-07-09 23:21:46 -05:00
47b2aabd9f Improved error message for remote version package output 2025-07-09 23:20:11 -05:00
b91cf18b04 change file 2025-07-04 10:22:37 -05:00
305b504ca1 tests: beginning of tests using Docker 2025-07-04 09:02:27 -05:00
7be2679b91 change: Commands: host can now be localhost or 127.0.0.1 to run commands locally
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-21 09:09:56 -05:00
3c6e3ed914 v0.10.2
All checks were successful
ci/woodpecker/push/publish-docs Pipeline was successful
ci/woodpecker/tag/gitea Pipeline was successful
ci/woodpecker/tag/publish-docs Pipeline was successful
ci/woodpecker/release/publish-docs Pipeline was successful
2025-03-19 22:42:49 -05:00
02bc040e2a v0.10.2
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-19 22:41:18 -05:00
9f1f36215a v0.10.2 2025-03-19 22:40:06 -05:00
ff75f4bbcd feat: add variable support
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-16 23:43:12 -05:00
5f40713e98 feat: add variable support 2025-03-16 23:42:54 -05:00
cd5f7611a9 notifications: add http service
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-13 23:35:00 -05:00
b542711078 notifications: add http service 2025-03-13 23:34:37 -05:00
52dbc353e5 change: update go toolchain
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-12 23:22:52 -05:00
6bef0c3e5b notifications: add http config
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-12 10:10:45 -05:00
4d705d78fb fix: golang-ci-lint version
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-03-11 22:22:39 -05:00
62d47ecfa7 fix: pipeline errors
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
ci/woodpecker/tag/gitea Pipeline was successful
ci/woodpecker/tag/publish-docs Pipeline was successful
ci/woodpecker/release/publish-docs Pipeline was successful
2025-03-11 22:08:59 -05:00
32444ff82e fix: docs and pipeline errors
Some checks failed
ci/woodpecker/tag/gitea Pipeline failed
ci/woodpecker/tag/publish-docs Pipeline was successful
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline failed
2025-03-11 21:55:51 -05:00
a5a7c05640 v0.10.1
Some checks failed
ci/woodpecker/push/publish-docs Pipeline was successful
ci/woodpecker/tag/gitea Pipeline failed
ci/woodpecker/tag/publish-docs Pipeline was successful
2025-03-11 21:37:58 -05:00
bfb81e11b2 version bump
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
2025-03-11 21:36:53 -05:00
fd4c83f9c0 Vault: keys are now referenced by name, and the actual data by data
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline failed
2025-03-11 21:33:06 -05:00
fe27c6396a LinuxUserManager: correct parameters for AddUser()
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-03-11 16:13:29 -05:00
c89dde186a UserCommands: change field name
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
2025-03-11 15:37:12 -05:00
18a64de0de UserCommands: change field name 2025-03-11 15:36:43 -05:00
99c622b69f UserCommands: add field CreateUserHome 2025-03-11 15:30:07 -05:00
95e85e8b45 UserCommands: add ssh public keys when running locally
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-03-11 15:21:02 -05:00
1a48c7bca5 change: create temp file when modifing password over SSH 2025-03-11 14:55:02 -05:00
5d21764ef1 fix: don't test empty env files
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-03-11 13:42:40 -05:00
c7302f0e17 update docs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
2025-03-10 12:34:33 -05:00
75 changed files with 2389 additions and 653 deletions

View File

@ -0,0 +1,3 @@
kind: Added
body: 'feat: Package operation `versionCheck` supports regular expressions (see [regexp](https://pkg.go.dev/regexp) package for docs)'
time: 2025-04-09T17:45:28.836497149-05:00

View File

@ -0,0 +1,3 @@
kind: Added
body: 'Command lists: added `cmdLists.[name].notify` object'
time: 2025-05-01T11:07:45.96164753-05:00

View File

@ -0,0 +1,3 @@
kind: Added
body: Testing setup with Docker
time: 2025-07-04T08:59:17.430373451-05:00

View File

@ -0,0 +1,3 @@
kind: Added
body: 'CLI: add global flag --hostsConfig that allows hosts to be dynamic in relation to the main config'
time: 2025-07-04T10:21:26.864635558-05:00

View 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

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

@ -0,0 +1,3 @@
kind: Changed
body: 'Commands: `host` can now be `localhost` or `127.0.0.1` to run commands locally'
time: 2025-03-21T09:08:49.871021144-05:00

View File

@ -0,0 +1,3 @@
kind: Changed
body: lists loaded from external files only if no list config present in current file
time: 2025-03-25T00:33:57.039431409-05:00

View File

@ -0,0 +1,3 @@
kind: Changed
body: "`PackageManager.Parse` renamed to `ParseRemotePackageManagerVersionOutput`. This now returns arrays of PackageManagerCommon.Package and errors."
time: 2025-04-07T22:30:20.342177323-05:00

View File

@ -0,0 +1,3 @@
kind: Changed
body: 'Internal: refactoring and renaming functions'
time: 2025-04-18T13:34:40.842541658-05:00

View File

@ -0,0 +1,3 @@
kind: Changed
body: 'Commands: moved output-prefixed keys to the `commands.[name].output` object'
time: 2025-05-01T11:05:34.90130087-05:00

View File

@ -0,0 +1,3 @@
kind: Changed
body: Change internal method name for better understanding
time: 2025-06-09T07:26:01.819927627-05:00

View File

@ -0,0 +1,3 @@
kind: Changed
body: Improved error message for remote version package output
time: 2025-07-09T23:19:19.431960446-05:00

View File

@ -0,0 +1,3 @@
kind: Fixed
body: 'Command Lists: hooks now run correctly when commands finish'
time: 2025-04-18T09:57:47.39035092-05:00

View File

@ -0,0 +1,3 @@
kind: Fixed
body: Log file passed using `--log-file` correctly used
time: 2025-04-24T22:57:11.592829277-05:00

8
.changes/v0.10.1.md Normal file
View File

@ -0,0 +1,8 @@
## v0.10.1 - 2025-03-11
### Added
* UserCommands: add ssh public keys when running locally
* UserCommands: add field CreateUserHome
### Changed
* UserCommands: create temp file when modifing password over SSH
* UserCommands: change field name
* Vault: keys are now referenced by `name`, and the actual data by `data`

6
.changes/v0.10.2.md Normal file
View File

@ -0,0 +1,6 @@
## v0.10.2 - 2025-03-19
### Added
* Notifications: http service added
* Variable support. Can be referenced with `%{var:nameOfVar}%` in select string fields.
### Changed
* vault: initialize vault before validating config

2
.gitignore vendored
View File

@ -5,6 +5,6 @@ dist/
.codegpt .codegpt
*.log *.log
*.sh /*.sh
/*.yaml /*.yaml
/*.yml /*.yml

View File

@ -9,5 +9,6 @@
"mautrix", "mautrix",
"nikoksr", "nikoksr",
"Strs" "Strs"
] ],
"CodeGPT.apiKey": "CodeGPT Plus Beta"
} }

View File

@ -1,9 +1,7 @@
name: goreleaser release
steps: steps:
golang: golang:
image: golang:1.23 image: golang:1.23
commands: commands:
- go mod tidy
- go install github.com/goreleaser/goreleaser/v2@v2.7.0 - go install github.com/goreleaser/goreleaser/v2@v2.7.0
- goreleaser release -f .goreleaser/gitea.yml --release-notes=".changes/$(go run backy.go version -V).md" - goreleaser release -f .goreleaser/gitea.yml --release-notes=".changes/$(go run backy.go version -V).md"
environment: environment:

View File

@ -5,7 +5,7 @@ steps:
- go build - go build
- go test - go test
release: release:
image: golangci/golangci-lint:v1.53.3 image: golangci/golangci-lint:v1.64.7
commands: commands:
- golangci-lint run -v --timeout 5m - golangci-lint run -v --timeout 5m

View File

@ -6,6 +6,22 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie). and is generated by [Changie](https://github.com/miniscruff/changie).
## v0.10.2 - 2025-03-19
### Added
* Notifications: http service added
* Variable support. Can be referenced with `%{var:nameOfVar}%` in select string fields.
### Changed
* vault: initialize vault before validating config
## v0.10.1 - 2025-03-11
### Added
* UserCommands: add ssh public keys when running locally
* UserCommands: add field CreateUserHome
### Changed
* UserCommands: create temp file when modifing password over SSH
* UserCommands: change field name
* Vault: keys are now referenced by `name`, and the actual data by `data`
## v0.10.0 - 2025-03-08 ## v0.10.0 - 2025-03-08
### Added ### Added
* Hooks: improved logging when executing * Hooks: improved logging when executing

View File

@ -18,6 +18,7 @@
"maunium", "maunium",
"mautrix", "mautrix",
"nikoksr", "nikoksr",
"packagemanagercommon",
"rawbytes", "rawbytes",
"remotefetcher", "remotefetcher",
"Strs" "Strs"

View File

@ -30,9 +30,14 @@ func init() {
} }
func Backup(cmd *cobra.Command, args []string) { func Backup(cmd *cobra.Command, args []string) {
backyConfOpts := backy.NewOpts(cfgFile, backy.AddCommandLists(cmdLists), backy.SetLogFile(logFile), backy.SetCmdStdOut(cmdStdOut)) backyConfOpts := backy.NewConfigOptions(configFile,
backy.AddCommandLists(cmdLists),
backy.SetLogFile(logFile),
backy.EnableCommandStdOut(cmdStdOut),
backy.SetHostsConfigFile(hostsConfigFile))
backyConfOpts.InitConfig() backyConfOpts.InitConfig()
backyConfOpts.ReadConfig() backyConfOpts.ParseConfigurationFile()
backyConfOpts.RunListConfig("") backyConfOpts.RunListConfig("")
for _, host := range backyConfOpts.Hosts { for _, host := range backyConfOpts.Hosts {

54
cmd/backup_test.go Normal file
View File

@ -0,0 +1,54 @@
package cmd
// import (
// "bufio"
// "encoding/json"
// "os"
// "os/exec"
// "strings"
// "testing"
// "github.com/stretchr/testify/assert"
// )
// // TestConfigOptions tests the configuration options for the backy package.
// func Test_ErrorHook(t *testing.T) {
// configFile := "-f ../../tests/ErrorHook.yml"
// logFile := "--log-file=ErrorHook.log"
// backyCommand := exec.Command("go", "run", "../../backy.go", configFile, logFile, "backup")
// backyCommand.Stderr = os.Stdout
// backyCommand.Stdout = os.Stdout
// err := backyCommand.Run()
// assert.Nil(t, err)
// os.Remove("ErrorHook.log")
// logFileData, logFileErr := os.ReadFile("ErrorHook.log")
// if logFileErr != nil {
// assert.FailNow(t, logFileErr.Error())
// }
// var JsonData []map[string]interface{}
// jsonScanner := bufio.NewScanner(strings.NewReader(string(logFileData)))
// for jsonScanner.Scan() {
// var jsonDataLine map[string]interface{}
// err = json.Unmarshal(jsonScanner.Bytes(), &jsonDataLine)
// assert.Nil(t, err)
// JsonData = append(JsonData, jsonDataLine)
// }
// for _, v := range JsonData {
// _, ok := v["error"]
// if !ok {
// assert.FailNow(t, "error does not exist\n")
// // return
// }
// }
// // t.Logf("%s", logFileData)
// // t.Logf("%v", JsonData)
// }
// func TestBackupErrorHook(t *testing.T) {
// logFile = "ErrorHook.log"
// configFile = "../tests/ErrorHook.yml"
// }

View File

@ -20,7 +20,7 @@ package cmd
// func config(cmd *cobra.Command, args []string) { // func config(cmd *cobra.Command, args []string) {
// opts := backy.NewOpts(cfgFile, backy.cronEnabled()) // opts := backy.NewConfigOptions(configFile, backy.cronEnabled())
// opts.InitConfig() // opts.InitConfig()
// } // }

View File

@ -18,13 +18,14 @@ var (
func cron(cmd *cobra.Command, args []string) { func cron(cmd *cobra.Command, args []string) {
parseS3Config() parseS3Config()
opts := backy.NewOpts(cfgFile, opts := backy.NewConfigOptions(configFile,
backy.EnableCron(), backy.EnableCron(),
backy.SetLogFile(logFile), backy.SetLogFile(logFile),
backy.SetCmdStdOut(cmdStdOut)) backy.EnableCommandStdOut(cmdStdOut),
backy.SetHostsConfigFile(hostsConfigFile))
opts.InitConfig() opts.InitConfig()
opts.ReadConfig() opts.ParseConfigurationFile()
opts.Cron() opts.Cron()
} }

View File

@ -21,7 +21,7 @@ var (
) )
func init() { func init() {
execCmd.AddCommand(hostExecCommand) execCmd.AddCommand(hostExecCommand, hostsExecCommand)
} }
@ -32,8 +32,12 @@ func execute(cmd *cobra.Command, args []string) {
logging.ExitWithMSG("Please provide a command to run. Pass --help to see options.", 1, nil) logging.ExitWithMSG("Please provide a command to run. Pass --help to see options.", 1, nil)
} }
opts := backy.NewOpts(cfgFile, backy.AddCommands(args), backy.SetLogFile(logFile), backy.SetCmdStdOut(cmdStdOut)) opts := backy.NewConfigOptions(configFile,
backy.AddCommands(args),
backy.SetLogFile(logFile),
backy.EnableCommandStdOut(cmdStdOut),
backy.SetHostsConfigFile(hostsConfigFile))
opts.InitConfig() opts.InitConfig()
opts.ReadConfig() opts.ParseConfigurationFile()
opts.ExecuteCmds() opts.ExecuteCmds()
} }

View File

@ -35,10 +35,13 @@ func init() {
// 2. stdin (on command line) (TODO) // 2. stdin (on command line) (TODO)
func Host(cmd *cobra.Command, args []string) { func Host(cmd *cobra.Command, args []string) {
backyConfOpts := backy.NewOpts(cfgFile, backy.SetLogFile(logFile), backy.SetCmdStdOut(cmdStdOut)) backyConfOpts := backy.NewConfigOptions(configFile,
backy.SetLogFile(logFile),
backy.EnableCommandStdOut(cmdStdOut),
backy.SetHostsConfigFile(hostsConfigFile))
backyConfOpts.InitConfig() backyConfOpts.InitConfig()
backyConfOpts.ReadConfig() backyConfOpts.ParseConfigurationFile()
// check CLI input // check CLI input
if hostsList == nil { if hostsList == nil {
@ -46,14 +49,20 @@ func Host(cmd *cobra.Command, args []string) {
} }
for _, h := range hostsList { for _, h := range hostsList {
if backy.IsHostLocal(h) {
continue
}
// check if h exists in the config file // check if h exists in the config file
_, hostFound := backyConfOpts.Hosts[h] _, hostFound := backyConfOpts.Hosts[h]
if !hostFound { if !hostFound {
// check if h exists in the SSH config file // check if h exists in the SSH config file
hostFoundInConfig, s := backy.CheckIfHostHasHostName(h) hostFoundInConfig, s := backy.DoesHostHaveHostName(h)
if !hostFoundInConfig { if !hostFoundInConfig {
logging.ExitWithMSG("host "+h+" not found", 1, &backyConfOpts.Logger) logging.ExitWithMSG("host "+h+" not found", 1, &backyConfOpts.Logger)
} }
if backyConfOpts.Hosts == nil {
backyConfOpts.Hosts = make(map[string]*backy.Host)
}
// create host with hostname and host // create host with hostname and host
backyConfOpts.Hosts[h] = &backy.Host{Host: h, HostName: s} backyConfOpts.Hosts[h] = &backy.Host{Host: h, HostName: s}
} }
@ -68,5 +77,5 @@ func Host(cmd *cobra.Command, args []string) {
} }
} }
backyConfOpts.ExecCmdsSSH(cmdList, hostsList) backyConfOpts.ExecCmdsOnHosts(cmdList, hostsList)
} }

91
cmd/hosts.go Normal file
View 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)
}

View File

@ -22,13 +22,13 @@ var (
Use: "cmds [cmd1 cmd2 cmd3...]", Use: "cmds [cmd1 cmd2 cmd3...]",
Short: "List commands defined in config file.", Short: "List commands defined in config file.",
Long: "List commands defined in config file", Long: "List commands defined in config file",
Run: ListCmds, Run: ListCommands,
} }
listCmdLists = &cobra.Command{ listCmdLists = &cobra.Command{
Use: "lists [list1 list2 ...]", Use: "lists [list1 list2 ...]",
Short: "List lists defined in config file.", Short: "List lists defined in config file.",
Long: "List lists defined in config file", Long: "List lists defined in config file",
Run: ListCmdLists, Run: ListCommandLists,
} }
) )
@ -40,7 +40,7 @@ func init() {
} }
func ListCmds(cmd *cobra.Command, args []string) { func ListCommands(cmd *cobra.Command, args []string) {
// setup based on whats passed in: // setup based on whats passed in:
// - cmds // - cmds
@ -54,17 +54,19 @@ func ListCmds(cmd *cobra.Command, args []string) {
parseS3Config() parseS3Config()
opts := backy.NewOpts(cfgFile, backy.SetLogFile(logFile)) opts := backy.NewConfigOptions(configFile,
backy.SetLogFile(logFile),
backy.SetHostsConfigFile(hostsConfigFile))
opts.InitConfig() opts.InitConfig()
opts.ReadConfig() opts.ParseConfigurationFile()
for _, v := range cmdsToList { for _, v := range cmdsToList {
opts.ListCommand(v) opts.ListCommand(v)
} }
} }
func ListCmdLists(cmd *cobra.Command, args []string) { func ListCommandLists(cmd *cobra.Command, args []string) {
parseS3Config() parseS3Config()
@ -74,10 +76,12 @@ func ListCmdLists(cmd *cobra.Command, args []string) {
logging.ExitWithMSG("Error: lists subcommand needs lists", 1, nil) logging.ExitWithMSG("Error: lists subcommand needs lists", 1, nil)
} }
opts := backy.NewOpts(cfgFile, backy.SetLogFile(logFile)) opts := backy.NewConfigOptions(configFile,
backy.SetLogFile(logFile),
backy.SetHostsConfigFile(hostsConfigFile))
opts.InitConfig() opts.InitConfig()
opts.ReadConfig() opts.ParseConfigurationFile()
for _, v := range listsToList { for _, v := range listsToList {
opts.ListCommandList(v) opts.ListCommandList(v)

View File

@ -13,11 +13,12 @@ import (
var ( var (
// Used for flags. // Used for flags.
cfgFile string configFile string
verbose bool hostsConfigFile string
cmdStdOut bool verbose bool
logFile string cmdStdOut bool
s3Endpoint string logFile string
s3Endpoint string
rootCmd = &cobra.Command{ rootCmd = &cobra.Command{
Use: "backy", Use: "backy",
@ -35,12 +36,13 @@ func Execute() {
} }
func init() { 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().BoolVar(&cmdStdOut, "cmdStdOut", false, "Pass to print command output to stdout")
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "f", "", "config file to read from") 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().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) rootCmd.AddCommand(backupCmd, execCmd, cronCmd, versionCmd, listCmd)
} }

View File

@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const versionStr = "0.10.0" const versionStr = "0.10.2"
var ( var (
versionCmd = &cobra.Command{ versionCmd = &cobra.Command{

View File

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

View File

@ -35,12 +35,12 @@ If a remote config file is specified (on the command-line using `-f`) and the li
``` ```
| key | description | type | required | key | description | type | required
| --- | --- | --- | --- | | --- | --- | --- | ---
| `order` | Defines the sequence of commands to execute | `[]string` | yes | | `order` | Defines the sequence of commands to execute | `[]string` | yes
| `getOutput` | Command(s) output is in the notification(s) | `bool` | no | | `sendNotificationOnSuccess` | Whether to send notification on list success with the commands' output | `bool` | no
| `notifications` | The notification service(s) and ID(s) to use on success and failure. Must be *`service.id`*. See the [notifications documentation page](/config/notifications/) for more | `[]string` | no | | `notifications` | The notification service(s) and ID(s) to use on success and failure. Must be *`service.id`*. See the [notifications documentation page](/config/notifications/) for more | `[]string` | no
| `name` | Optional name of the list | `string` | no | | `name` | Optional name of the list | `string` | no
| `cron` | Time at which to schedule the list. Only has affect when cron subcommand is run. | `string` | no | | `cron` | Time at which to schedule the list. Only has affect when cron subcommand is run. | `string` | no
### Order ### Order

View File

@ -16,7 +16,7 @@ Values available for this section **(case-sensitive)**:
| ----------------| ------------------------------------------------------------------------------------------------------- | --------------------- | -------- |----------------------------| | ----------------| ------------------------------------------------------------------------------------------------------- | --------------------- | -------- |----------------------------|
| `cmd` | Defines the command to execute | `string` | yes | No | | `cmd` | Defines the command to execute | `string` | yes | No |
| `Args` | Defines the arguments to the command | `[]string` | no | No | | `Args` | Defines the arguments to the command | `[]string` | no | No |
| `environment` | Defines environment variables for the command | `[]string` | no | No | | `environment` | Defines environment variables for the command | `[]string` | no | Partial |
| `type` | See documentation further down the page. Additional fields may be required. | `string` | no | No | | `type` | See documentation further down the page. Additional fields may be required. | `string` | no | No |
| `getOutput` | Command(s) output is in the notification(s) | `bool` | no | No | | `getOutput` | Command(s) output is in the notification(s) | `bool` | no | No |
| `host` | If not specified, the command will execute locally. | `string` | no | No | | `host` | If not specified, the command will execute locally. | `string` | no | No |
@ -95,8 +95,9 @@ The following options are available:
The environment variables support expansion: The environment variables support expansion:
- using escaped values `$VAR` or `${VAR}` - using escaped values `$VAR` or `${VAR}`
- using any external directive, and if using the env directive, the variable will be read from a `.env` file
For now, the variables have to be defined in an `.env` file in the same directory that the program is run from. <!-- For now, the variables expanded have to be defined in an `.env` file in the same directory that the program is run from. -->
If using it with host specified, the SSH server has to be configured to accept those env variables. If using it with host specified, the SSH server has to be configured to accept those env variables.

View File

@ -6,16 +6,18 @@ description: This is dedicated to user commands.
This is dedicated to `user` commands. The command `type` field must be `user`. User is a type that allows one to perform user operations. There are several additional options available when `type` is `user`: This is dedicated to `user` commands. The command `type` field must be `user`. User is a type that allows one to perform user operations. There are several additional options available when `type` is `user`:
| name | notes | type | required | | name | notes | type | required | External directive support
| --- | --- | --- | --- | | ----------------| -------------------------------------------------------------| ---------- | ---------| --------------------------|
| `userName` | The name of a user to be configured. | `string` | yes | | `userName` | The name of a user to be configured. | `string` | yes | no |
| `userOperation` | The type of operation to perform. | `string` | yes | | `userOperation` | The type of operation to perform. | `string` | yes | no |
| `userID` | The user ID to use. | `string` | no | | `userID` | The user ID to use. | `string` | no | no |
| `userGroups` | The groups the user should be added to. | `[]string` | no | | `userGroups` | The groups the user should be added to. | `[]string` | no | no |
| `userSshPubKeys` | The keys to add to the user's authorized keys. | `[]string` | no | | `systemUser` | Create a system user. | `bool` | no | no |
| `userShell` | The shell for the user. | `string` | no | | `userCreateHome`| Create the home directory. | `bool` | no | no |
| `userHome` | The user's home directory. | `string` | no | | `userSshPubKeys`| The keys to add to the user's authorized keys. | `[]string` | no | yes |
| `userPassword` | The new password value when using the `password` operation. Can be specified by using external directive. | `string` | no | | `userShell` | The shell for the user. | `string` | no | no |
| `userHome` | The user's home directory. | `string` | no | no |
| `userPassword` | The new password value when using the `password` operation. | `string` | no | yes |
#### example #### example

View File

@ -21,4 +21,4 @@ description: >
## exec host subcommand ## exec host subcommand
Backy has a subcommand `exec host`. This subcommand takes the flags of `-m host1 -m host2`. For now these hosts need to be defined in the config file. Backy has a subcommand `exec host`. This subcommand takes the flags of `-m host1 -m host2`. The commands can also be specified by `-c command1 -c command2`.

View File

@ -6,7 +6,7 @@ description: Set up and configure vault.
[Vault](https://www.vaultproject.io/) is a tool for storing secrets and other data securely. [Vault](https://www.vaultproject.io/) is a tool for storing secrets and other data securely.
Vault config can be used by prefixing `vault:` in front of a password or ENV var. A Vault key can be used by prefixing `%{vault:vault.keys.name}%` in a field that supports external directives.
This is the object in the config file: This is the object in the config file:
@ -18,10 +18,12 @@ vault:
keys: keys:
- name: mongourl - name: mongourl
mountpath: secret mountpath: secret
key: data
path: mongo/url path: mongo/url
type: # KVv1 or KVv2 type: KVv2 # KVv1 or KVv2
- name: - name: someKeyName
path: mountpath: secret
type: key: keyData
mountpath: type: KVv2
path: some/path
``` ```

View File

@ -124,13 +124,13 @@ notifications:
### Logging ### Logging
cmd-std-out controls whether commands output is echoed to StdOut. `cmd-std-out` controls whether commands output is echoed to StdOut.
If logfile is not defined, the log file will be written to the config directory in the file `backy.log`. If `logfile` is not defined, the log file will be written to the config directory in the file `backy.log`.
`console-disabled` controls whether the logging messages are echoed to StdOut. Default is false. `console-disabled` controls whether the logging messages are echoed to StdOut. Default is false.
`verbose` basically does nothing as all necessary info is already output. `verbose` prints out debugging messages.
```yaml ```yaml
logging: logging:
@ -144,7 +144,7 @@ logging:
[Vault](https://www.vaultproject.io/) can be used to get some configuration values and ENV variables securely. [Vault](https://www.vaultproject.io/) can be used to get some configuration values and ENV variables securely.
``` ```yaml
vault: vault:
token: hvs.tXqcASvTP8wg92f7riyvGyuf token: hvs.tXqcASvTP8wg92f7riyvGyuf
address: http://127.0.0.1:8200 address: http://127.0.0.1:8200

137
go.mod
View File

@ -1,97 +1,142 @@
module git.andrewnw.xyz/CyberShell/backy module git.andrewnw.xyz/CyberShell/backy
go 1.23 go 1.23.0
toolchain go1.23.6 toolchain go1.23.7
replace git.andrewnw.xyz/CyberShell/backy => /home/andrew/Projects/backy
require ( require (
github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0
github.com/dmarkham/enumer v1.5.11 github.com/dmarkham/enumer v1.5.11
github.com/go-co-op/gocron v1.37.0 github.com/go-co-op/gocron v1.37.0
github.com/hashicorp/vault/api v1.15.0 github.com/google/uuid v1.6.0
github.com/hashicorp/vault/api v1.20.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/kevinburke/ssh_config v1.2.0 github.com/kevinburke/ssh_config v1.2.0
github.com/knadh/koanf/parsers/yaml v0.1.0 github.com/knadh/koanf/parsers/yaml v1.1.0
github.com/knadh/koanf/providers/rawbytes v0.1.0 github.com/knadh/koanf/providers/rawbytes v1.0.0
github.com/knadh/koanf/v2 v2.1.2 github.com/knadh/koanf/v2 v2.2.2
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
github.com/minio/minio-go/v7 v7.0.84 github.com/minio/minio-go/v7 v7.0.94
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/nikoksr/notify v1.3.0 github.com/nikoksr/notify v1.3.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pkg/sftp v1.13.7 github.com/pkg/sftp v1.13.9
github.com/rs/zerolog v1.33.0 github.com/rs/zerolog v1.34.0
github.com/sethvargo/go-password v0.3.1 github.com/sethvargo/go-password v0.3.1
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.9.1
golang.org/x/crypto v0.33.0 github.com/stretchr/testify v1.10.0
github.com/testcontainers/testcontainers-go v0.37.0
golang.org/x/crypto v0.40.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
maunium.net/go/mautrix v0.23.0 maunium.net/go/mautrix v0.24.1
mvdan.cc/sh/v3 v3.10.0 mvdan.cc/sh/v3 v3.12.0
) )
require ( require (
dario.cat/mergo v1.0.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 // indirect github.com/aws/aws-sdk-go-v2 v1.36.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.6 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 // indirect
github.com/aws/smithy-go v1.22.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 // indirect
github.com/aws/smithy-go v1.22.4 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/docker v28.0.1+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.8.2 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-ini/ini v1.67.0 // indirect github.com/go-ini/ini v1.67.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/go-jose/go-jose/v4 v4.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/kr/fs v0.1.0 // indirect github.com/kr/fs v0.1.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/minio/crc64nvme v1.0.2 // indirect
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pascaldekloe/name v1.0.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/user v0.1.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pascaldekloe/name v1.0.1 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rs/xid v1.6.0 // indirect github.com/rs/xid v1.6.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.6 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect github.com/tidwall/sjson v1.2.5 // indirect
go.mau.fi/util v0.8.4 // indirect github.com/tinylib/msgp v1.3.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.mau.fi/util v0.8.8 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/atomic v1.11.0 // indirect
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.23.0 // indirect golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
golang.org/x/net v0.35.0 // indirect golang.org/x/mod v0.26.0 // indirect
golang.org/x/sync v0.11.0 // indirect golang.org/x/net v0.42.0 // indirect
golang.org/x/sys v0.30.0 // indirect golang.org/x/sync v0.16.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/sys v0.34.0 // indirect
golang.org/x/time v0.10.0 // indirect golang.org/x/text v0.27.0 // indirect
golang.org/x/tools v0.30.0 // indirect golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.35.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
) )

276
go.sum
View File

@ -1,61 +1,130 @@
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E= github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E=
github.com/aws/aws-sdk-go-v2 v1.36.1/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= github.com/aws/aws-sdk-go-v2 v1.36.1/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM=
github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0=
github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 h1:zAxi9p3wsZMIaVCdoiQp2uZ9k1LsZvmAnoTBeZPXom0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 h1:zAxi9p3wsZMIaVCdoiQp2uZ9k1LsZvmAnoTBeZPXom0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8/go.mod h1:3XkePX5dSaxveLAYY7nsbsZZrKxCyEuE5pM4ziFxyGg= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8/go.mod h1:3XkePX5dSaxveLAYY7nsbsZZrKxCyEuE5pM4ziFxyGg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 h1:BjUcr3X3K0wZPGFg2bxOWW3VPN8rkE3/61zhP+IHviA= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 h1:BjUcr3X3K0wZPGFg2bxOWW3VPN8rkE3/61zhP+IHviA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32/go.mod h1:80+OGC/bgzzFFTUmcuwD0lb4YutwQeKLFpmt6hoWapU= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32/go.mod h1:80+OGC/bgzzFFTUmcuwD0lb4YutwQeKLFpmt6hoWapU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 h1:m1GeXHVMJsRsUAqG6HjZWx9dj7F5TR+cF1bjyfYyBd4= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 h1:m1GeXHVMJsRsUAqG6HjZWx9dj7F5TR+cF1bjyfYyBd4=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32/go.mod h1:IitoQxGfaKdVLNg0hD8/DXmAqNy0H4K2H2Sf91ti8sI= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32/go.mod h1:IitoQxGfaKdVLNg0hD8/DXmAqNy0H4K2H2Sf91ti8sI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32 h1:OIHj/nAhVzIXGzbAE+4XmZ8FPvro3THr6NlqErJc3wY= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32 h1:OIHj/nAhVzIXGzbAE+4XmZ8FPvro3THr6NlqErJc3wY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32/go.mod h1:LiBEsDo34OJXqdDlRGsilhlIiXR7DL+6Cx2f4p1EgzI= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32/go.mod h1:LiBEsDo34OJXqdDlRGsilhlIiXR7DL+6Cx2f4p1EgzI=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36/go.mod h1:gDhdAV6wL3PmPqBhiPbnlS447GoWs8HTTOYef9/9Inw=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.6 h1:cCBJaT7EeEojpJ4s7wTDbhZlHVJOgNHN7iw6qVurGaw= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.6 h1:cCBJaT7EeEojpJ4s7wTDbhZlHVJOgNHN7iw6qVurGaw=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.6/go.mod h1:WYH1ABybY7JK9TITPnk6ZlP7gQB8psI4c9qDmMsnLSA= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.6/go.mod h1:WYH1ABybY7JK9TITPnk6ZlP7gQB8psI4c9qDmMsnLSA=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 h1:SYVGSFQHlchIcy6e7x12bsrxClCXSP5et8cqVhL8cuw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 h1:SYVGSFQHlchIcy6e7x12bsrxClCXSP5et8cqVhL8cuw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13/go.mod h1:kizuDaLX37bG5WZaoxGPQR/LNFXpxp0vsUnqfkWXfNE= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13/go.mod h1:kizuDaLX37bG5WZaoxGPQR/LNFXpxp0vsUnqfkWXfNE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13 h1:OBsrtam3rk8NfBEq7OLOMm5HtQ9Yyw32X4UQMya/wjw= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13 h1:OBsrtam3rk8NfBEq7OLOMm5HtQ9Yyw32X4UQMya/wjw=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13/go.mod h1:3U4gFA5pmoCOja7aq4nSaIAGbaOHv2Yl2ug018cmC+Q= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13/go.mod h1:3U4gFA5pmoCOja7aq4nSaIAGbaOHv2Yl2ug018cmC+Q=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=
github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0 h1:ehvUZNVrGA1Usa6yYo8A8pUqrigRelWXSbcCqYpRLeI= github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0 h1:ehvUZNVrGA1Usa6yYo8A8pUqrigRelWXSbcCqYpRLeI=
github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0/go.mod h1:KuLNrwYJFaC2AVZ+CVVc12k9NyqwgWsoNNHjwqF6QNk= github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0/go.mod h1:KuLNrwYJFaC2AVZ+CVVc12k9NyqwgWsoNNHjwqF6QNk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0 h1:5Y75q0RPQoAbieyOuGLhjV9P3txvYgXv2lg0UwJOfmE=
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw=
github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dmarkham/enumer v1.5.11 h1:quorLCaEfzjJ23Pf7PB9lyyaHseh91YfTM/sAD/4Mbo= github.com/dmarkham/enumer v1.5.11 h1:quorLCaEfzjJ23Pf7PB9lyyaHseh91YfTM/sAD/4Mbo=
github.com/dmarkham/enumer v1.5.11/go.mod h1:yixql+kDDQRYqcuBM2n9Vlt7NoT9ixgXhaXry8vmRg8= github.com/dmarkham/enumer v1.5.11/go.mod h1:yixql+kDDQRYqcuBM2n9Vlt7NoT9ixgXhaXry8vmRg8=
github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -67,18 +136,26 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9 h1:FW0YttEnUNDJ2WL9XcrrfteS1xW8u+sh4ggM8pN5isQ= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9 h1:FW0YttEnUNDJ2WL9XcrrfteS1xW8u+sh4ggM8pN5isQ=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I=
github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA= github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA=
github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8= github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8=
github.com/hashicorp/vault/api v1.20.0 h1:KQMHElgudOsr+IbJgmbjHnCTxEpKs9LnozA1D3nozU4=
github.com/hashicorp/vault/api v1.20.0/go.mod h1:GZ4pcjfzoOWpkJ3ijHNpEoAxKEsBJnVljyTe3jM2Sms=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
@ -87,19 +164,33 @@ github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w= github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY= github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
github.com/knadh/koanf/parsers/yaml v1.1.0 h1:3ltfm9ljprAHt4jxgeYLlFPmUaunuCgu1yILuTXRdM4=
github.com/knadh/koanf/parsers/yaml v1.1.0/go.mod h1:HHmcHXUrp9cOPcuC+2wrr44GTUB0EC+PyfN3HZD9tFg=
github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME= github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME=
github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c= github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c=
github.com/knadh/koanf/providers/rawbytes v1.0.0 h1:MrKDh/HksJlKJmaZjgs4r8aVBb/zsJyc/8qaSnzcdNI=
github.com/knadh/koanf/providers/rawbytes v1.0.0/go.mod h1:KxwYJf1uezTKy6PBtfE+m725NGp4GPVA7XoNTJ/PtLo=
github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ= github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo= github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
github.com/knadh/koanf/v2 v2.2.2 h1:ghbduIkpFui3L587wavneC9e3WIliCgiCgdxYO/wd7A=
github.com/knadh/koanf/v2 v2.2.2/go.mod h1:abWQc0cBXLSF/PSOMCB/SK+T13NXDsPvOksbpi5e/9Q=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -111,6 +202,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
@ -118,10 +213,14 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg=
github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.84 h1:D1HVmAF8JF8Bpi6IU4V9vIEj+8pc+xU88EWMs2yed0E= github.com/minio/minio-go/v7 v7.0.84 h1:D1HVmAF8JF8Bpi6IU4V9vIEj+8pc+xU88EWMs2yed0E=
github.com/minio/minio-go/v7 v7.0.84/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY= github.com/minio/minio-go/v7 v7.0.84/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY=
github.com/minio/minio-go/v7 v7.0.94 h1:1ZoksIKPyaSt64AVOyaQvhDOgVC3MfZsWM6mZXRUGtM=
github.com/minio/minio-go/v7 v7.0.94/go.mod h1:71t2CqDt3ThzESgZUlU1rBN54mksGGlkLcFgguDnnAc=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@ -130,17 +229,43 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/nikoksr/notify v1.3.0 h1:UxzfxzAYGQD9a5JYLBTVx0lFMxeHCke3rPCkfWdPgLs= github.com/nikoksr/notify v1.3.0 h1:UxzfxzAYGQD9a5JYLBTVx0lFMxeHCke3rPCkfWdPgLs=
github.com/nikoksr/notify v1.3.0/go.mod h1:Xor2hMmkvrCfkCKvXGbcrESez4brac2zQjhd6U2BbeM= github.com/nikoksr/notify v1.3.0/go.mod h1:Xor2hMmkvrCfkCKvXGbcrESez4brac2zQjhd6U2BbeM=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pascaldekloe/name v1.0.0 h1:n7LKFgHixETzxpRv2R77YgPUFo85QHGZKrdaYm7eY5U= github.com/pascaldekloe/name v1.0.0 h1:n7LKFgHixETzxpRv2R77YgPUFo85QHGZKrdaYm7eY5U=
github.com/pascaldekloe/name v1.0.0/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM= github.com/pascaldekloe/name v1.0.0/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM=
github.com/pascaldekloe/name v1.0.1 h1:9lnXOHeqeHHnWLbKfH6X98+4+ETVqFqxN09UXSjcMb0=
github.com/pascaldekloe/name v1.0.1/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM= github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY= github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY=
github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw=
github.com/pkg/sftp v1.13.9/go.mod h1:OBN7bVXdstkFFN/gdnHPUb5TE8eb8G1Rp9wCItqjkkA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
@ -152,13 +277,21 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU= github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU=
github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs= github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs=
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@ -168,11 +301,14 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg=
github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@ -183,72 +319,180 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ= go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ=
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY= go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c=
go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34= golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@ -259,7 +503,13 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
maunium.net/go/mautrix v0.23.0 h1:HNlR19eew5lvrNSL2muhExaGhYdaGk5FfEiA82QqUP4= maunium.net/go/mautrix v0.23.0 h1:HNlR19eew5lvrNSL2muhExaGhYdaGk5FfEiA82QqUP4=
maunium.net/go/mautrix v0.23.0/go.mod h1:AGnnaz3ylGikUo1I1MJVn9QLsl2No1/ZNnGDyO0QD5s= maunium.net/go/mautrix v0.23.0/go.mod h1:AGnnaz3ylGikUo1I1MJVn9QLsl2No1/ZNnGDyO0QD5s=
maunium.net/go/mautrix v0.24.1 h1:09/xi4qTeA03g1n/DPmmqAlT8Cx4QrgwiPlmLVzA9AU=
maunium.net/go/mautrix v0.24.1/go.mod h1:Xy6o+pXmbqmgWsUWh15EQ1eozjC+k/VT/7kloByv9PI=
mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4= mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4=
mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY= mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=

View File

@ -0,0 +1,149 @@
// Code generated by "enumer -linecomment -yaml -text -json -type=AllowedExternalDirectives"; DO NOT EDIT.
package backy
import (
"encoding/json"
"fmt"
"strings"
)
const _AllowedExternalDirectivesName = "DefaultExternalDirvaultvault-envvault-filevault-file-envfile-envfileenv"
var _AllowedExternalDirectivesIndex = [...]uint8{0, 18, 23, 32, 42, 56, 64, 68, 71}
const _AllowedExternalDirectivesLowerName = "defaultexternaldirvaultvault-envvault-filevault-file-envfile-envfileenv"
func (i AllowedExternalDirectives) String() string {
if i < 0 || i >= AllowedExternalDirectives(len(_AllowedExternalDirectivesIndex)-1) {
return fmt.Sprintf("AllowedExternalDirectives(%d)", i)
}
return _AllowedExternalDirectivesName[_AllowedExternalDirectivesIndex[i]:_AllowedExternalDirectivesIndex[i+1]]
}
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
func _AllowedExternalDirectivesNoOp() {
var x [1]struct{}
_ = x[DefaultExternalDir-(0)]
_ = x[AllowedExternalDirectiveVault-(1)]
_ = x[AllowedExternalDirectiveVaultEnv-(2)]
_ = x[AllowedExternalDirectiveVaultFile-(3)]
_ = x[AllowedExternalDirectiveAll-(4)]
_ = x[AllowedExternalDirectiveFileEnv-(5)]
_ = x[AllowedExternalDirectiveFile-(6)]
_ = x[AllowedExternalDirectiveEnv-(7)]
}
var _AllowedExternalDirectivesValues = []AllowedExternalDirectives{DefaultExternalDir, AllowedExternalDirectiveVault, AllowedExternalDirectiveVaultEnv, AllowedExternalDirectiveVaultFile, AllowedExternalDirectiveAll, AllowedExternalDirectiveFileEnv, AllowedExternalDirectiveFile, AllowedExternalDirectiveEnv}
var _AllowedExternalDirectivesNameToValueMap = map[string]AllowedExternalDirectives{
_AllowedExternalDirectivesName[0:18]: DefaultExternalDir,
_AllowedExternalDirectivesLowerName[0:18]: DefaultExternalDir,
_AllowedExternalDirectivesName[18:23]: AllowedExternalDirectiveVault,
_AllowedExternalDirectivesLowerName[18:23]: AllowedExternalDirectiveVault,
_AllowedExternalDirectivesName[23:32]: AllowedExternalDirectiveVaultEnv,
_AllowedExternalDirectivesLowerName[23:32]: AllowedExternalDirectiveVaultEnv,
_AllowedExternalDirectivesName[32:42]: AllowedExternalDirectiveVaultFile,
_AllowedExternalDirectivesLowerName[32:42]: AllowedExternalDirectiveVaultFile,
_AllowedExternalDirectivesName[42:56]: AllowedExternalDirectiveAll,
_AllowedExternalDirectivesLowerName[42:56]: AllowedExternalDirectiveAll,
_AllowedExternalDirectivesName[56:64]: AllowedExternalDirectiveFileEnv,
_AllowedExternalDirectivesLowerName[56:64]: AllowedExternalDirectiveFileEnv,
_AllowedExternalDirectivesName[64:68]: AllowedExternalDirectiveFile,
_AllowedExternalDirectivesLowerName[64:68]: AllowedExternalDirectiveFile,
_AllowedExternalDirectivesName[68:71]: AllowedExternalDirectiveEnv,
_AllowedExternalDirectivesLowerName[68:71]: AllowedExternalDirectiveEnv,
}
var _AllowedExternalDirectivesNames = []string{
_AllowedExternalDirectivesName[0:18],
_AllowedExternalDirectivesName[18:23],
_AllowedExternalDirectivesName[23:32],
_AllowedExternalDirectivesName[32:42],
_AllowedExternalDirectivesName[42:56],
_AllowedExternalDirectivesName[56:64],
_AllowedExternalDirectivesName[64:68],
_AllowedExternalDirectivesName[68:71],
}
// AllowedExternalDirectivesString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func AllowedExternalDirectivesString(s string) (AllowedExternalDirectives, error) {
if val, ok := _AllowedExternalDirectivesNameToValueMap[s]; ok {
return val, nil
}
if val, ok := _AllowedExternalDirectivesNameToValueMap[strings.ToLower(s)]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to AllowedExternalDirectives values", s)
}
// AllowedExternalDirectivesValues returns all values of the enum
func AllowedExternalDirectivesValues() []AllowedExternalDirectives {
return _AllowedExternalDirectivesValues
}
// AllowedExternalDirectivesStrings returns a slice of all String values of the enum
func AllowedExternalDirectivesStrings() []string {
strs := make([]string, len(_AllowedExternalDirectivesNames))
copy(strs, _AllowedExternalDirectivesNames)
return strs
}
// IsAAllowedExternalDirectives returns "true" if the value is listed in the enum definition. "false" otherwise
func (i AllowedExternalDirectives) IsAAllowedExternalDirectives() bool {
for _, v := range _AllowedExternalDirectivesValues {
if i == v {
return true
}
}
return false
}
// MarshalJSON implements the json.Marshaler interface for AllowedExternalDirectives
func (i AllowedExternalDirectives) MarshalJSON() ([]byte, error) {
return json.Marshal(i.String())
}
// UnmarshalJSON implements the json.Unmarshaler interface for AllowedExternalDirectives
func (i *AllowedExternalDirectives) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return fmt.Errorf("AllowedExternalDirectives should be a string, got %s", data)
}
var err error
*i, err = AllowedExternalDirectivesString(s)
return err
}
// MarshalText implements the encoding.TextMarshaler interface for AllowedExternalDirectives
func (i AllowedExternalDirectives) MarshalText() ([]byte, error) {
return []byte(i.String()), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface for AllowedExternalDirectives
func (i *AllowedExternalDirectives) UnmarshalText(text []byte) error {
var err error
*i, err = AllowedExternalDirectivesString(string(text))
return err
}
// MarshalYAML implements a YAML Marshaler for AllowedExternalDirectives
func (i AllowedExternalDirectives) MarshalYAML() (interface{}, error) {
return i.String(), nil
}
// UnmarshalYAML implements a YAML Unmarshaler for AllowedExternalDirectives
func (i *AllowedExternalDirectives) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
var err error
*i, err = AllowedExternalDirectivesString(s)
return err
}

View File

@ -11,6 +11,8 @@ import (
"io" "io"
"os" "os"
"os/exec" "os/exec"
"slices"
"strings"
"text/template" "text/template"
"embed" "embed"
@ -34,7 +36,7 @@ var Sprintf = fmt.Sprintf
func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([]string, error) { func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([]string, error) {
var ( var (
ArgsStr string // concatenating the arguments ArgsStr string
cmdOutBuf bytes.Buffer cmdOutBuf bytes.Buffer
cmdOutWriters io.Writer cmdOutWriters io.Writer
errSSH error errSSH error
@ -54,26 +56,35 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
ArgsStr += fmt.Sprintf(" %s", v) ArgsStr += fmt.Sprintf(" %s", v)
} }
if command.Type == UserCT { if command.Type == UserCommandType {
if command.UserOperation == "password" { if command.UserOperation == "password" {
cmdCtxLogger.Info().Str("password", command.UserPassword).Msg("user password to be updated") cmdCtxLogger.Info().Str("password", command.UserPassword).Msg("user password to be updated")
} }
} }
if command.Host != nil { if !IsHostLocal(command.Host) {
outputArr, errSSH = command.RunCmdSSH(cmdCtxLogger, opts)
outputArr, errSSH = command.RunCmdOnHost(cmdCtxLogger, opts)
if errSSH != nil { if errSSH != nil {
return outputArr, errSSH return outputArr, errSSH
} }
} else { } else {
// Handle package operations // Handle package operations
if command.Type == PackageCT && command.PackageOperation == PackOpCheckVersion { if command.Type == PackageCommandType && command.PackageOperation == PackageOperationCheckVersion {
cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Checking package versions") 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 // Execute the package version command
cmd := exec.Command(command.Cmd, command.Args...) cmd := exec.Command(command.Cmd, command.Args...)
cmdOutWriters = io.MultiWriter(&cmdOutBuf) cmdOutWriters = io.MultiWriter(&cmdOutBuf)
if IsCmdStdOutEnabled() {
cmdOutWriters = io.MultiWriter(os.Stdout, &cmdOutBuf)
}
cmd.Stdout = cmdOutWriters cmd.Stdout = cmdOutWriters
cmd.Stderr = cmdOutWriters cmd.Stderr = cmdOutWriters
@ -85,7 +96,8 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
} }
var localCMD *exec.Cmd var localCMD *exec.Cmd
if command.Type == RemoteScriptCT {
if command.Type == RemoteScriptCommandType {
script, err := command.Fetcher.Fetch(command.Cmd) script, err := command.Fetcher.Fetch(command.Cmd)
if err != nil { if err != nil {
return nil, err return nil, err
@ -95,15 +107,15 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
command.Shell = "sh" command.Shell = "sh"
} }
localCMD = exec.Command(command.Shell, command.Args...) localCMD = exec.Command(command.Shell, command.Args...)
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger) injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger, opts)
cmdOutWriters = io.MultiWriter(&cmdOutBuf) cmdOutWriters = io.MultiWriter(&cmdOutBuf)
if IsCmdStdOutEnabled() { if IsCmdStdOutEnabled() {
cmdOutWriters = io.MultiWriter(os.Stdout, &cmdOutBuf) cmdOutWriters = io.MultiWriter(os.Stdout, &cmdOutBuf)
} }
if command.OutputFile != "" { if command.Output.File != "" {
file, err := os.Create(command.OutputFile) file, err := os.Create(command.Output.File)
if err != nil { if err != nil {
return nil, fmt.Errorf("error creating output file: %w", err) return nil, fmt.Errorf("error creating output file: %w", err)
} }
@ -136,7 +148,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
if str, ok := outMap["output"].(string); ok { if str, ok := outMap["output"].(string); ok {
outputArr = append(outputArr, str) outputArr = append(outputArr, str)
} }
if command.OutputToLog { if command.Output.ToLog {
cmdCtxLogger.Info().Fields(outMap).Send() cmdCtxLogger.Info().Fields(outMap).Send()
} }
} }
@ -157,8 +169,10 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine", command.Name)).Send() cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine", command.Name)).Send()
// execute package commands in a shell // execute package commands in a shell
if command.Type == PackageCT { if command.Type == PackageCommandType {
cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Executing package command") for _, p := range command.Packages {
cmdCtxLogger.Info().Str("packages", p.Name).Msg("Executing package command")
}
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr) ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
localCMD = exec.Command("/bin/sh", "-c", ArgsStr) localCMD = exec.Command("/bin/sh", "-c", ArgsStr)
} else { } else {
@ -166,7 +180,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
} }
} }
if command.Type == UserCT { if command.Type == UserCommandType {
if command.UserOperation == "password" { if command.UserOperation == "password" {
localCMD.Stdin = command.stdin localCMD.Stdin = command.stdin
cmdCtxLogger.Info().Str("password", command.UserPassword).Msg("user password to be updated") cmdCtxLogger.Info().Str("password", command.UserPassword).Msg("user password to be updated")
@ -176,7 +190,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
localCMD.Dir = *command.Dir localCMD.Dir = *command.Dir
} }
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger) injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger, opts)
cmdOutWriters = io.MultiWriter(&cmdOutBuf) cmdOutWriters = io.MultiWriter(&cmdOutBuf)
@ -194,20 +208,79 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd %s: %w", command.Name, err)).Send() cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd %s: %w", command.Name, err)).Send()
return outputArr, err return outputArr, err
} }
if command.Type == UserCommandType {
if command.UserOperation == "add" {
if command.UserSshPubKeys != nil {
var (
authorizedKeysFile *os.File
err error
userHome []byte
)
cmdCtxLogger.Info().Msg("adding SSH Keys")
localCMD := exec.Command(fmt.Sprintf("grep \"%s\" /etc/passwd | cut -d: -f6", command.Username))
userHome, err = localCMD.CombinedOutput()
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error finding user home from /etc/passwd: %v", err)
}
command.UserHome = strings.TrimSpace(string(userHome))
userSshDir := fmt.Sprintf("%s/.ssh", command.UserHome)
if _, err := os.Stat(userSshDir); os.IsNotExist(err) {
err := os.MkdirAll(userSshDir, 0700)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating directory %s %v", userSshDir, err)
}
}
if _, err := os.Stat(fmt.Sprintf("%s/authorized_keys", userSshDir)); os.IsNotExist(err) {
_, err := os.Create(fmt.Sprintf("%s/authorized_keys", userSshDir))
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating file %s/authorized_keys: %v", userSshDir, err)
}
}
authorizedKeysFile, err = os.OpenFile(fmt.Sprintf("%s/authorized_keys", userSshDir), 0700, os.ModeAppend)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)
}
defer authorizedKeysFile.Close()
for _, k := range command.UserSshPubKeys {
buf := bytes.NewBufferString(k)
cmdCtxLogger.Info().Str("key", k).Msg("adding SSH key")
if _, err := authorizedKeysFile.ReadFrom(buf); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error adding to authorized keys: %v", err)
}
}
localCMD = exec.Command(fmt.Sprintf("chown -R %s:%s %s", command.Username, command.Username, userHome))
_, err = localCMD.CombinedOutput()
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), err
}
}
}
}
} }
return outputArr, nil return outputArr, nil
} }
func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<- CmdResult, opts *ConfigOpts) { func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<- string, opts *ConfigOpts) {
for list := range jobs { for list := range jobs {
fieldsMap := map[string]interface{}{"list": list.Name} fieldsMap := map[string]interface{}{"list": list.Name}
var cmdLogger zerolog.Logger var cmdLogger zerolog.Logger
var commandExecuted *Command
var cmdsRan []string var cmdsRan []string
var outStructArr []outStruct var outStructArr []outStruct
var hasError bool // Tracks if any command in the list failed var hasError bool // Tracks if any command in the list failed
for _, cmd := range list.Order { for _, cmd := range list.Order {
cmdToRun := opts.Cmds[cmd] cmdToRun := opts.Cmds[cmd]
commandExecuted = cmdToRun
currentCmd := cmdToRun.Name currentCmd := cmdToRun.Name
fieldsMap["cmd"] = currentCmd fieldsMap["cmd"] = currentCmd
cmdLogger = cmdToRun.GenerateLogger(opts) cmdLogger = cmdToRun.GenerateLogger(opts)
@ -218,23 +291,21 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<-
if runErr != nil { if runErr != nil {
// Log the error and send a failed result
cmdLogger.Err(runErr).Send() cmdLogger.Err(runErr).Send()
results <- CmdResult{CmdName: cmd, ListName: list.Name, Error: runErr}
// Execute error hooks for the failed command
cmdToRun.ExecuteHooks("error", opts) cmdToRun.ExecuteHooks("error", opts)
// Notify failure // Notify failure
if list.NotifyConfig != nil { if list.NotifyConfig != nil {
notifyError(cmdLogger, msgTemps, list, cmdsRan, outStructArr, runErr, cmdToRun) notifyError(cmdLogger, msgTemps, list, cmdsRan, outStructArr, runErr, cmdToRun)
} }
// Execute error hooks for the failed command
hasError = true hasError = true
break break
} }
// Collect output if required if list.GetCommandOutputInNotificationsOnSuccess || cmdToRun.Output.InList {
if list.GetOutput || cmdToRun.GetOutputInList {
outStructArr = append(outStructArr, outStruct{ outStructArr = append(outStructArr, outStruct{
CmdName: currentCmd, CmdName: currentCmd,
CmdExecuted: currentCmd, CmdExecuted: currentCmd,
@ -243,27 +314,82 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<-
} }
} }
if !hasError && list.NotifyConfig != nil && (list.NotifyOnSuccess || list.GetOutput) { if !hasError && list.NotifyConfig != nil && list.Notify.OnFailure {
notifySuccess(cmdLogger, msgTemps, list, cmdsRan, outStructArr) notifySuccess(cmdLogger, msgTemps, list, cmdsRan, outStructArr)
} }
for _, cmd := range list.Order { if !hasError {
cmdToRun := opts.Cmds[cmd] commandExecuted.ExecuteHooks("success", opts)
}
if !hasError { commandExecuted.ExecuteHooks("final", opts)
cmdToRun.ExecuteHooks("success", opts)
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,
})
}
} }
// Execute final hooks for every command if !hasError && list.NotifyConfig != nil && list.Notify.OnFailure {
cmdToRun.ExecuteHooks("final", opts) notifySuccess(cmdLogger, msgTemps, list, cmdsRan, outStructArr)
} }
if !hasError {
commandExecuted.ExecuteHooks("success", opts)
}
commandExecuted.ExecuteHooks("final", opts)
// Send the final result for the list
if hasError {
results <- CmdResult{CmdName: cmdsRan[len(cmdsRan)-1], ListName: list.Name, Error: fmt.Errorf("list execution failed")}
} else {
results <- CmdResult{CmdName: cmdsRan[len(cmdsRan)-1], ListName: list.Name, Error: nil}
} }
results <- "done"
} }
} }
@ -312,7 +438,7 @@ func (opts *ConfigOpts) RunListConfig(cron string) {
} }
configListsLen := len(opts.CmdConfigLists) configListsLen := len(opts.CmdConfigLists)
listChan := make(chan *CmdList, configListsLen) listChan := make(chan *CmdList, configListsLen)
results := make(chan CmdResult, configListsLen) results := make(chan string, configListsLen)
// Start workers // Start workers
for w := 1; w <= configListsLen; w++ { for w := 1; w <= configListsLen; w++ {
@ -332,13 +458,62 @@ func (opts *ConfigOpts) RunListConfig(cron string) {
// Process results // Process results
for a := 1; a <= configListsLen; a++ { for a := 1; a <= configListsLen; a++ {
result := <-results <-results
opts.Logger.Debug().Msgf("Processing result for list %s, command %s", result.ListName, result.CmdName)
} }
opts.closeHostConnections() 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() { func (opts *ConfigOpts) ExecuteCmds() {
for _, cmd := range opts.executeCmds { for _, cmd := range opts.executeCmds {
cmdToRun := opts.Cmds[cmd] cmdToRun := opts.Cmds[cmd]
@ -401,30 +576,32 @@ func (cmd *Command) ExecuteHooks(hookType string, opts *ConfigOpts) {
case "error": case "error":
for _, v := range cmd.Hooks.Error { for _, v := range cmd.Hooks.Error {
errCmd := opts.Cmds[v] errCmd := opts.Cmds[v]
opts.Logger.Info().Msgf("Running error hook command %s", v)
cmdLogger := opts.Logger.With(). cmdLogger := opts.Logger.With().
Str("backy-cmd", v).Str("hookType", "error"). Str("backy-cmd", v).Str("hookType", "error").
Logger() Logger()
errCmd.RunCmd(cmdLogger, opts) cmdLogger.Info().Msgf("Running error hook command %s", v)
// URGENT: Never returns
_, _ = errCmd.RunCmd(cmdLogger, opts)
return
} }
case "success": case "success":
for _, v := range cmd.Hooks.Success { for _, v := range cmd.Hooks.Success {
successCmd := opts.Cmds[v] successCmd := opts.Cmds[v]
opts.Logger.Info().Msgf("Running success hook command %s", v)
cmdLogger := opts.Logger.With(). cmdLogger := opts.Logger.With().
Str("backy-cmd", v).Str("hookType", "success"). Str("backy-cmd", v).Str("hookType", "success").
Logger() Logger()
successCmd.RunCmd(cmdLogger, opts) cmdLogger.Info().Msgf("Running success hook command %s", v)
_, _ = successCmd.RunCmd(cmdLogger, opts)
} }
case "final": case "final":
for _, v := range cmd.Hooks.Final { for _, v := range cmd.Hooks.Final {
finalCmd := opts.Cmds[v] finalCmd := opts.Cmds[v]
opts.Logger.Info().Msgf("Running final hook command %s", v)
cmdLogger := opts.Logger.With(). cmdLogger := opts.Logger.With().
Str("backy-cmd", v).Str("hookType", "final"). Str("backy-cmd", v).Str("hookType", "final").
Logger() Logger()
finalCmd.RunCmd(cmdLogger, opts) cmdLogger.Info().Msgf("Running final hook command %s", v)
_, _ = finalCmd.RunCmd(cmdLogger, opts)
} }
} }
} }
@ -434,27 +611,35 @@ func (cmd *Command) GenerateLogger(opts *ConfigOpts) zerolog.Logger {
Str("Backy-cmd", cmd.Name).Str("Host", "local machine"). Str("Backy-cmd", cmd.Name).Str("Host", "local machine").
Logger() Logger()
if cmd.Host != nil { if !IsHostLocal(cmd.Host) {
cmdLogger = opts.Logger.With(). cmdLogger = opts.Logger.With().
Str("Backy-cmd", cmd.Name).Str("Host", *cmd.Host). Str("Backy-cmd", cmd.Name).Str("Host", cmd.Host).
Logger() Logger()
} }
return cmdLogger return cmdLogger
} }
func (opts *ConfigOpts) ExecCmdsSSH(cmdList []string, hostsList []string) { func (opts *ConfigOpts) ExecCmdsOnHosts(cmdList []string, hostsList []string) {
// Iterate over hosts and exec commands // Iterate over hosts and exec commands
for _, h := range hostsList { for _, h := range hostsList {
host := opts.Hosts[h] host := opts.Hosts[h]
for _, c := range cmdList { for _, c := range cmdList {
cmd := opts.Cmds[c] cmd := opts.Cmds[c]
cmd.RemoteHost = host cmd.RemoteHost = host
cmd.Host = &host.Host cmd.Host = h
opts.Logger.Info().Str("host", h).Str("cmd", c).Send() if IsHostLocal(h) {
_, err := cmd.RunCmdSSH(cmd.GenerateLogger(opts), opts) _, err := cmd.RunCmd(cmd.GenerateLogger(opts), opts)
if err != nil { if err != nil {
opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send() 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()
}
} }
} }
} }
@ -472,7 +657,7 @@ func logCommandOutput(command *Command, cmdOutBuf bytes.Buffer, cmdCtxLogger zer
if str, ok := outMap["output"].(string); ok { if str, ok := outMap["output"].(string); ok {
outputArr = append(outputArr, str) outputArr = append(outputArr, str)
} }
if command.OutputToLog { if command.Output.ToLog {
cmdCtxLogger.Info().Fields(outMap).Send() cmdCtxLogger.Info().Fields(outMap).Send()
} }
} }

83
pkg/backy/backy_test.go Normal file
View File

@ -0,0 +1,83 @@
package backy
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"
"github.com/testcontainers/testcontainers-go"
)
// 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("")
}
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.pkgMan = dnfPackage
PackageCommand := getCommandTypeAndSetCommandInfo(packagecommand)
assert.Equal(t, "dnf", PackageCommand.Cmd)
}
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.
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
}
logs, err := io.ReadAll(r)
if err != nil {
log.Printf("failed to read logs: %v", err)
return
}
fmt.Println(string(logs))
require.NoError(t, err)
}

View File

@ -25,29 +25,29 @@ func (i CommandType) String() string {
// Re-run the stringer command to generate them again. // Re-run the stringer command to generate them again.
func _CommandTypeNoOp() { func _CommandTypeNoOp() {
var x [1]struct{} var x [1]struct{}
_ = x[DefaultCT-(0)] _ = x[DefaultCommandType-(0)]
_ = x[ScriptCT-(1)] _ = x[ScriptCommandType-(1)]
_ = x[ScriptFileCT-(2)] _ = x[ScriptFileCommandType-(2)]
_ = x[RemoteScriptCT-(3)] _ = x[RemoteScriptCommandType-(3)]
_ = x[PackageCT-(4)] _ = x[PackageCommandType-(4)]
_ = x[UserCT-(5)] _ = x[UserCommandType-(5)]
} }
var _CommandTypeValues = []CommandType{DefaultCT, ScriptCT, ScriptFileCT, RemoteScriptCT, PackageCT, UserCT} var _CommandTypeValues = []CommandType{DefaultCommandType, ScriptCommandType, ScriptFileCommandType, RemoteScriptCommandType, PackageCommandType, UserCommandType}
var _CommandTypeNameToValueMap = map[string]CommandType{ var _CommandTypeNameToValueMap = map[string]CommandType{
_CommandTypeName[0:0]: DefaultCT, _CommandTypeName[0:0]: DefaultCommandType,
_CommandTypeLowerName[0:0]: DefaultCT, _CommandTypeLowerName[0:0]: DefaultCommandType,
_CommandTypeName[0:6]: ScriptCT, _CommandTypeName[0:6]: ScriptCommandType,
_CommandTypeLowerName[0:6]: ScriptCT, _CommandTypeLowerName[0:6]: ScriptCommandType,
_CommandTypeName[6:16]: ScriptFileCT, _CommandTypeName[6:16]: ScriptFileCommandType,
_CommandTypeLowerName[6:16]: ScriptFileCT, _CommandTypeLowerName[6:16]: ScriptFileCommandType,
_CommandTypeName[16:28]: RemoteScriptCT, _CommandTypeName[16:28]: RemoteScriptCommandType,
_CommandTypeLowerName[16:28]: RemoteScriptCT, _CommandTypeLowerName[16:28]: RemoteScriptCommandType,
_CommandTypeName[28:35]: PackageCT, _CommandTypeName[28:35]: PackageCommandType,
_CommandTypeLowerName[28:35]: PackageCT, _CommandTypeLowerName[28:35]: PackageCommandType,
_CommandTypeName[35:39]: UserCT, _CommandTypeName[35:39]: UserCommandType,
_CommandTypeLowerName[35:39]: UserCT, _CommandTypeLowerName[35:39]: UserCommandType,
} }
var _CommandTypeNames = []string{ var _CommandTypeNames = []string{

View File

@ -1,7 +1,6 @@
package backy package backy
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
@ -96,14 +95,21 @@ func (opts *ConfigOpts) InitConfig() {
} else { } else {
loadDefaultConfigFiles(fetcher, configFiles, backyKoanf, opts) loadDefaultConfigFiles(fetcher, configFiles, backyKoanf, opts)
} }
opts.koanf = backyKoanf opts.koanf = backyKoanf
} }
func (opts *ConfigOpts) ReadConfig() *ConfigOpts { func (opts *ConfigOpts) ParseConfigurationFile() *ConfigOpts {
setTerminalEnv() setTerminalEnv()
backyKoanf := opts.koanf backyKoanf := opts.koanf
if backyKoanf.Exists("variables") {
unmarshalConfigIntoStruct(backyKoanf, "variables", &opts.Vars, opts.Logger)
}
getConfigDir(opts)
opts.loadEnv() opts.loadEnv()
if backyKoanf.Bool(getNestedConfig("logging", "cmd-std-out")) { if backyKoanf.Bool(getNestedConfig("logging", "cmd-std-out")) {
@ -124,15 +130,36 @@ func (opts *ConfigOpts) ReadConfig() *ConfigOpts {
log := setupLogger(opts) log := setupLogger(opts)
opts.Logger = log opts.Logger = log
hostsFetcher, err := remotefetcher.NewRemoteFetcher(opts.HostsFilePath, opts.Cache)
opts.Logger.Info().Str("hosts file", opts.HostsFilePath).Send()
if err != nil {
logging.ExitWithMSG(fmt.Sprintf("error initializing config fetcher: %v", err), 1, nil)
}
var hostKoanf = koanf.New(".")
if opts.HostsFilePath != "" {
loadConfigFile(hostsFetcher, opts.HostsFilePath, hostKoanf, opts)
unmarshalConfigIntoStruct(hostKoanf, "hosts", &opts.Hosts, opts.Logger)
} else {
unmarshalConfigIntoStruct(backyKoanf, "hosts", &opts.Hosts, opts.Logger)
}
log.Info().Str("config file", opts.ConfigFilePath).Send() log.Info().Str("config file", opts.ConfigFilePath).Send()
unmarshalConfig(backyKoanf, "commands", &opts.Cmds, opts.Logger) if err := opts.initializeVault(); err != nil {
log.Err(err).Send()
}
unmarshalConfigIntoStruct(backyKoanf, "commands", &opts.Cmds, opts.Logger)
getCommandEnvironments(opts) getCommandEnvironments(opts)
unmarshalConfig(backyKoanf, "hosts", &opts.Hosts, opts.Logger) getHostConfigs(opts)
resolveHostConfigs(opts) for k, v := range opts.Vars {
v = getExternalConfigDirectiveValue(v, opts, AllowedExternalDirectiveAll)
opts.Vars[k] = v
}
loadCommandLists(opts, backyKoanf) loadCommandLists(opts, backyKoanf)
@ -149,25 +176,21 @@ func (opts *ConfigOpts) ReadConfig() *ConfigOpts {
filterExecuteLists(opts) filterExecuteLists(opts)
if backyKoanf.Exists("notifications") { if backyKoanf.Exists("notifications") {
unmarshalConfig(backyKoanf, "notifications", &opts.NotificationConf, opts.Logger) unmarshalConfigIntoStruct(backyKoanf, "notifications", &opts.NotificationConf, opts.Logger)
} }
opts.SetupNotify() opts.SetupNotify()
if err := opts.setupVault(); err != nil {
log.Err(err).Send()
}
return opts return opts
} }
func loadConfigFile(fetcher remotefetcher.RemoteFetcher, filePath string, k *koanf.Koanf, opts *ConfigOpts) { func loadConfigFile(fetcher remotefetcher.RemoteFetcher, filePath string, koanfConfigParser *koanf.Koanf, opts *ConfigOpts) {
data, err := fetcher.Fetch(filePath) data, err := fetcher.Fetch(filePath)
if err != nil { if err != nil {
logging.ExitWithMSG(generateFileFetchErrorString(filePath, "config", err), 1, nil) logging.ExitWithMSG(generateFileFetchErrorString(filePath, "config", err), 1, nil)
} }
if err := k.Load(rawbytes.Provider(data), yaml.Parser()); err != nil { if err := koanfConfigParser.Load(rawbytes.Provider(data), yaml.Parser()); err != nil {
logging.ExitWithMSG(fmt.Sprintf("error loading config: %v", err), 1, &opts.Logger) logging.ExitWithMSG(fmt.Sprintf("error loading config: %v", err), 1, &opts.Logger)
} }
} }
@ -212,23 +235,23 @@ func validateExecCommandsFromCLI(k *koanf.Koanf, opts *ConfigOpts) {
} }
} }
func setLoggingOptions(k *koanf.Koanf, opts *ConfigOpts) { func setLoggingOptions(backyKoanf *koanf.Koanf, opts *ConfigOpts) {
isLoggingVerbose := k.Bool(getLoggingKeyFromConfig("verbose")) isVerboseLoggingSetInConfig := backyKoanf.Bool(getLoggingKeyFromConfig("verbose"))
// if log file is set in config file and not set on command line, use "./backy.log" // if log file is set in config file and not set on command line, use "./backy.log"
logFile := "./backy.log" logFile := "./backy.log"
if opts.LogFilePath == "" && k.Exists(getLoggingKeyFromConfig("file")) { if opts.LogFilePath == "" && backyKoanf.Exists(getLoggingKeyFromConfig("file")) {
logFile = k.String(getLoggingKeyFromConfig("file")) logFile = backyKoanf.String(getLoggingKeyFromConfig("file"))
opts.LogFilePath = logFile opts.LogFilePath = logFile
} }
zerolog.SetGlobalLevel(zerolog.InfoLevel) zerolog.SetGlobalLevel(zerolog.InfoLevel)
if isLoggingVerbose { if isVerboseLoggingSetInConfig {
zerolog.SetGlobalLevel(zerolog.DebugLevel) zerolog.SetGlobalLevel(zerolog.DebugLevel)
os.Setenv("BACKY_LOGLEVEL", fmt.Sprintf("%v", zerolog.GlobalLevel())) os.Setenv("BACKY_LOGLEVEL", fmt.Sprintf("%v", zerolog.GlobalLevel()))
} }
if k.Bool(getLoggingKeyFromConfig("console-disabled")) { if backyKoanf.Bool(getLoggingKeyFromConfig("console-disabled")) {
os.Setenv("BACKY_CONSOLE_LOGGING", "") os.Setenv("BACKY_CONSOLE_LOGGING", "")
} else { } else {
os.Setenv("BACKY_CONSOLE_LOGGING", "enabled") os.Setenv("BACKY_CONSOLE_LOGGING", "enabled")
@ -240,7 +263,7 @@ func setupLogger(opts *ConfigOpts) zerolog.Logger {
return zerolog.New(writers).With().Timestamp().Logger() return zerolog.New(writers).With().Timestamp().Logger()
} }
func unmarshalConfig(k *koanf.Koanf, key string, target interface{}, log zerolog.Logger) { func unmarshalConfigIntoStruct(k *koanf.Koanf, key string, target interface{}, log zerolog.Logger) {
if err := k.UnmarshalWithConf(key, target, koanf.UnmarshalConf{Tag: "yaml"}); err != nil { if err := k.UnmarshalWithConf(key, target, koanf.UnmarshalConf{Tag: "yaml"}); err != nil {
logging.ExitWithMSG(fmt.Sprintf("error unmarshaling key %s into struct: %v", key, err), 1, &log) logging.ExitWithMSG(fmt.Sprintf("error unmarshaling key %s into struct: %v", key, err), 1, &log)
} }
@ -248,6 +271,9 @@ func unmarshalConfig(k *koanf.Koanf, key string, target interface{}, log zerolog
func getCommandEnvironments(opts *ConfigOpts) { func getCommandEnvironments(opts *ConfigOpts) {
for cmdName, cmdConf := range opts.Cmds { for cmdName, cmdConf := range opts.Cmds {
if cmdConf.Env == "" {
continue
}
opts.Logger.Debug().Str("env file", cmdConf.Env).Str("cmd", cmdName).Send() opts.Logger.Debug().Str("env file", cmdConf.Env).Str("cmd", cmdName).Send()
if err := testFile(cmdConf.Env); err != nil { if err := testFile(cmdConf.Env); err != nil {
logging.ExitWithMSG("Could not open file"+cmdConf.Env+": "+err.Error(), 1, &opts.Logger) logging.ExitWithMSG("Could not open file"+cmdConf.Env+": "+err.Error(), 1, &opts.Logger)
@ -256,18 +282,18 @@ func getCommandEnvironments(opts *ConfigOpts) {
} }
} }
func resolveHostConfigs(opts *ConfigOpts) { func getHostConfigs(opts *ConfigOpts) {
for hostConfigName, host := range opts.Hosts { for hostConfigName, host := range opts.Hosts {
if host.Host == "" { if host.Host == "" {
host.Host = hostConfigName host.Host = hostConfigName
} }
if host.ProxyJump != "" { if host.ProxyJump != "" {
resolveProxyHosts(host, opts) getProxyHosts(host, opts)
} }
} }
} }
func resolveProxyHosts(host *Host, opts *ConfigOpts) { func getProxyHosts(host *Host, opts *ConfigOpts) {
proxyHosts := strings.Split(host.ProxyJump, ",") proxyHosts := strings.Split(host.ProxyJump, ",")
for _, h := range proxyHosts { for _, h := range proxyHosts {
proxyHost, defined := opts.Hosts[h] proxyHost, defined := opts.Hosts[h]
@ -279,16 +305,22 @@ func resolveProxyHosts(host *Host, opts *ConfigOpts) {
} }
} }
func getConfigDir(opts *ConfigOpts) {
if isRemoteURL(opts.ConfigFilePath) {
p, _ := getRemoteDir(opts.ConfigFilePath)
opts.ConfigDir = p
} else {
opts.ConfigDir = path.Dir(opts.ConfigFilePath)
}
}
func loadCommandLists(opts *ConfigOpts, backyKoanf *koanf.Koanf) { func loadCommandLists(opts *ConfigOpts, backyKoanf *koanf.Koanf) {
var listConfigFiles []string var listConfigFiles []string
var u *url.URL var u *url.URL
var p string var p string
// if config file is remote, use the directory of the remote file
if isRemoteURL(opts.ConfigFilePath) { if isRemoteURL(opts.ConfigFilePath) {
p, u = getRemoteDir(opts.ConfigFilePath) p, u = getRemoteDir(opts.ConfigFilePath)
opts.ConfigDir = p opts.ConfigDir = p
println(p)
// // Still use local list files if a remote config file is used, but use them last
listConfigFiles = []string{u.JoinPath("lists.yml").String(), u.JoinPath("lists.yaml").String()} listConfigFiles = []string{u.JoinPath("lists.yml").String(), u.JoinPath("lists.yaml").String()}
} else { } else {
opts.ConfigDir = path.Dir(opts.ConfigFilePath) opts.ConfigDir = path.Dir(opts.ConfigFilePath)
@ -301,17 +333,19 @@ func loadCommandLists(opts *ConfigOpts, backyKoanf *koanf.Koanf) {
listsConfig := koanf.New(".") listsConfig := koanf.New(".")
for _, l := range listConfigFiles {
if loadListConfigFile(l, listsConfig, opts) {
break
}
}
if backyKoanf.Exists("cmdLists") { if backyKoanf.Exists("cmdLists") {
if backyKoanf.Exists("cmdLists.file") { if backyKoanf.Exists("cmdLists.file") {
loadCmdListsFile(backyKoanf, listsConfig, opts) loadCmdListsFile(backyKoanf, listsConfig, opts)
} else { } else {
unmarshalConfig(backyKoanf, "cmdLists", &opts.CmdConfigLists, opts.Logger) unmarshalConfigIntoStruct(backyKoanf, "cmdLists", &opts.CmdConfigLists, opts.Logger)
}
}
if opts.CmdConfigLists == nil {
for _, l := range listConfigFiles {
if loadListConfigFile(l, listsConfig, opts) {
break
}
} }
} }
} }
@ -351,7 +385,7 @@ func loadListConfigFile(filePath string, k *koanf.Koanf, opts *ConfigOpts) bool
return false return false
} }
unmarshalConfig(k, "cmdLists", &opts.CmdConfigLists, opts.Logger) unmarshalConfigIntoStruct(k, "cmdLists", &opts.CmdConfigLists, opts.Logger)
keyNotSupported("cmd-lists", "cmdLists", k, opts, true) keyNotSupported("cmd-lists", "cmdLists", k, opts, true)
opts.CmdListFile = filePath opts.CmdListFile = filePath
return true return true
@ -360,6 +394,7 @@ func loadListConfigFile(filePath string, k *koanf.Koanf, opts *ConfigOpts) bool
func loadCmdListsFile(backyKoanf *koanf.Koanf, listsConfig *koanf.Koanf, opts *ConfigOpts) { func loadCmdListsFile(backyKoanf *koanf.Koanf, listsConfig *koanf.Koanf, opts *ConfigOpts) {
opts.CmdListFile = strings.TrimSpace(backyKoanf.String("cmdLists.file")) opts.CmdListFile = strings.TrimSpace(backyKoanf.String("cmdLists.file"))
if !path.IsAbs(opts.CmdListFile) { if !path.IsAbs(opts.CmdListFile) {
// TODO: Needs testing - might cause undefined/unexpected behavior if remote config path is used
opts.CmdListFile = path.Join(path.Dir(opts.ConfigFilePath), opts.CmdListFile) opts.CmdListFile = path.Join(path.Dir(opts.ConfigFilePath), opts.CmdListFile)
} }
@ -379,7 +414,7 @@ func loadCmdListsFile(backyKoanf *koanf.Koanf, listsConfig *koanf.Koanf, opts *C
} }
keyNotSupported("cmd-lists", "cmdLists", listsConfig, opts, true) keyNotSupported("cmd-lists", "cmdLists", listsConfig, opts, true)
unmarshalConfig(listsConfig, "cmdLists", &opts.CmdConfigLists, opts.Logger) unmarshalConfigIntoStruct(listsConfig, "cmdLists", &opts.CmdConfigLists, opts.Logger)
opts.Logger.Info().Str("using lists config file", opts.CmdListFile).Send() opts.Logger.Info().Str("using lists config file", opts.CmdListFile).Send()
} }
@ -436,7 +471,7 @@ func getLoggingKeyFromConfig(key string) string {
// return fmt.Sprintf("cmdLists.%s", list) // return fmt.Sprintf("cmdLists.%s", list)
// } // }
func (opts *ConfigOpts) setupVault() error { func (opts *ConfigOpts) initializeVault() error {
if !opts.koanf.Bool("vault.enabled") { if !opts.koanf.Bool("vault.enabled") {
return nil return nil
} }
@ -457,7 +492,7 @@ func (opts *ConfigOpts) setupVault() error {
token = os.Getenv("VAULT_TOKEN") token = os.Getenv("VAULT_TOKEN")
} }
if strings.TrimSpace(token) == "" { if strings.TrimSpace(token) == "" {
return fmt.Errorf("no token found, but one was required. \n\nSet the config key vault.token or the environment variable VAULT_TOKEN") return fmt.Errorf("no token found. One is required. \n\nSet the config key vault.token or the environment variable VAULT_TOKEN")
} }
client.SetToken(token) client.SetToken(token)
@ -469,76 +504,29 @@ func (opts *ConfigOpts) setupVault() error {
opts.vaultClient = client opts.vaultClient = client
for _, v := range opts.VaultKeys {
v.Name = replaceVarInString(opts.Vars, v.Key, opts.Logger)
v.MountPath = replaceVarInString(opts.Vars, v.MountPath, opts.Logger)
}
return nil return nil
} }
func getVaultSecret(vaultClient *vault.Client, key *VaultKey) (string, error) {
var (
secret *vault.KVSecret
err error
)
if key.ValueType == "KVv2" {
secret, err = vaultClient.KVv2(key.MountPath).Get(context.Background(), key.Path)
} else if key.ValueType == "KVv1" {
secret, err = vaultClient.KVv1(key.MountPath).Get(context.Background(), key.Path)
} else if key.ValueType != "" {
return "", fmt.Errorf("type %s for key %s not known. Valid types are KVv1 or KVv2", key.ValueType, key.Name)
} else {
return "", fmt.Errorf("type for key %s must be specified. Valid types are KVv1 or KVv2", key.Name)
}
if err != nil {
return "", fmt.Errorf("unable to read secret: %v", err)
}
value, ok := secret.Data[key.Name].(string)
if !ok {
return "", fmt.Errorf("value type assertion failed: %T %#v", secret.Data[key.Name], secret.Data[key.Name])
}
return value, nil
}
func parseVaultKey(keyName string, keys []*VaultKey) (*VaultKey, error) {
for _, k := range keys {
if k.Name == keyName {
return k, nil
}
}
return nil, fmt.Errorf("key %s not found in vault keys", keyName)
}
func GetVaultKey(str string, opts *ConfigOpts, log zerolog.Logger) string {
key, err := parseVaultKey(str, opts.VaultKeys)
if key == nil && err == nil {
return str
}
if err != nil && key == nil {
log.Err(err).Send()
return ""
}
value, secretErr := getVaultSecret(opts.vaultClient, key)
if secretErr != nil {
log.Err(secretErr).Send()
return value
}
return value
}
func processCmds(opts *ConfigOpts) error { func processCmds(opts *ConfigOpts) error {
// process commands // process commands
for cmdName, cmd := range opts.Cmds { for cmdName, cmd := range opts.Cmds {
cmd.GetVariablesFromConf(opts)
cmd.Cmd = replaceVarInString(opts.Vars, cmd.Cmd, opts.Logger)
for i, v := range cmd.Args {
v = replaceVarInString(opts.Vars, v, opts.Logger)
cmd.Args[i] = v
}
if cmd.Name == "" { if cmd.Name == "" {
cmd.Name = cmdName cmd.Name = cmdName
} }
// println("Cmd.Name = " + cmd.Name)
hooks := cmd.Hooks hooks := cmd.Hooks
// resolve hooks
if hooks != nil { if hooks != nil {
processHookSuccess := processHooks(cmd, hooks.Error, opts, "error") processHookSuccess := processHooks(cmd, hooks.Error, opts, "error")
@ -555,9 +543,13 @@ func processCmds(opts *ConfigOpts) error {
} }
} }
// resolve hosts if !IsHostLocal(cmd.Host) {
if cmd.Host != nil {
host, hostFound := opts.Hosts[*cmd.Host] cmdHost := replaceVarInString(opts.Vars, cmd.Host, opts.Logger)
if cmdHost != cmd.Host {
cmd.Host = cmdHost
}
host, hostFound := opts.Hosts[cmd.Host]
if hostFound { if hostFound {
cmd.RemoteHost = host cmd.RemoteHost = host
cmd.RemoteHost.Host = host.Host cmd.RemoteHost.Host = host.Host
@ -565,12 +557,12 @@ func processCmds(opts *ConfigOpts) error {
cmd.RemoteHost.HostName = host.HostName cmd.RemoteHost.HostName = host.HostName
} }
} else { } else {
opts.Logger.Info().Msgf("adding host %s to host list", *cmd.Host) opts.Logger.Info().Msgf("adding host %s to host list", cmd.Host)
if opts.Hosts == nil { if opts.Hosts == nil {
opts.Hosts = make(map[string]*Host) opts.Hosts = make(map[string]*Host)
} }
opts.Hosts[*cmd.Host] = &Host{Host: *cmd.Host} opts.Hosts[cmd.Host] = &Host{Host: cmd.Host}
cmd.RemoteHost = &Host{Host: *cmd.Host} cmd.RemoteHost = &Host{Host: cmd.Host}
} }
} else { } else {
@ -586,15 +578,15 @@ func processCmds(opts *ConfigOpts) error {
} }
} }
if cmd.Type == PackageCT { if cmd.Type == PackageCommandType {
if cmd.PackageManager == "" { if cmd.PackageManager == "" {
return fmt.Errorf("package manager is required for package command %s", cmd.PackageName) return fmt.Errorf("package manager is required for package command %s", cmd.Name)
} }
if cmd.PackageOperation.String() == "" { if cmd.PackageOperation.String() == "" {
return fmt.Errorf("package operation is required for package command %s", cmd.PackageName) return fmt.Errorf("package operation is required for package command %s", cmd.Name)
} }
if cmd.PackageName == "" { if cmd.Packages == nil {
return fmt.Errorf("package name is required for package command %s", cmd.PackageName) return fmt.Errorf("package name is required for package command %s", cmd.Name)
} }
var err error var err error
@ -612,11 +604,11 @@ func processCmds(opts *ConfigOpts) error {
} }
// Parse user commands // Parse user commands
if cmd.Type == UserCT { if cmd.Type == UserCommandType {
if cmd.Username == "" { if cmd.Username == "" {
return fmt.Errorf("username is required for user command %s", cmd.Name) return fmt.Errorf("username is required for user command %s", cmd.Name)
} }
cmd.Username = replaceVarInString(opts.Vars, cmd.Username, opts.Logger)
err := detectOSType(cmd, opts) err := detectOSType(cmd, opts)
if err != nil { if err != nil {
opts.Logger.Info().Err(err).Str("command", cmdName).Send() opts.Logger.Info().Err(err).Str("command", cmdName).Send()
@ -629,17 +621,19 @@ func processCmds(opts *ConfigOpts) error {
if cmd.UserOperation == "password" { if cmd.UserOperation == "password" {
opts.Logger.Debug().Msg("changing password for user: " + cmd.Username) opts.Logger.Debug().Msg("changing password for user: " + cmd.Username)
cmd.UserPassword = getExternalConfigDirectiveValue(cmd.UserPassword, opts) cmd.UserPassword = getExternalConfigDirectiveValue(cmd.UserPassword, opts, AllowedExternalDirectiveAll)
} }
if cmd.Host != nil {
host, ok := opts.Hosts[*cmd.Host] if !IsHostLocal(cmd.Host) {
host, ok := opts.Hosts[cmd.Host]
if ok { if ok {
cmd.userMan, err = usermanager.NewUserManager(host.OS) cmd.userMan, err = usermanager.NewUserManager(host.OS)
} }
} }
for indx, key := range cmd.UserSshPubKeys { for indx, key := range cmd.UserSshPubKeys {
opts.Logger.Debug().Msg("adding SSH Keys") opts.Logger.Debug().Msg("adding SSH Keys")
key = getExternalConfigDirectiveValue(key, opts) key = getExternalConfigDirectiveValue(key, opts, AllowedExternalDirectiveAll)
cmd.UserSshPubKeys[indx] = key cmd.UserSshPubKeys[indx] = key
} }
if err != nil { if err != nil {
@ -651,7 +645,7 @@ func processCmds(opts *ConfigOpts) error {
} }
if cmd.Type == RemoteScriptCT { if cmd.Type == RemoteScriptCommandType {
var fetchErr error var fetchErr error
if !isRemoteURL(cmd.Cmd) { if !isRemoteURL(cmd.Cmd) {
return fmt.Errorf("remoteScript command %s must be a remote resource", cmdName) return fmt.Errorf("remoteScript command %s must be a remote resource", cmdName)
@ -662,9 +656,9 @@ func processCmds(opts *ConfigOpts) error {
} }
} }
if cmd.OutputFile != "" { if cmd.Output.File != "" {
var err error var err error
cmd.OutputFile, err = getFullPathWithHomeDir(cmd.OutputFile) cmd.Output.File, err = getFullPathWithHomeDir(cmd.Output.File)
if err != nil { if err != nil {
return err return err
} }
@ -701,7 +695,9 @@ func processHooks(cmd *Command, hooks []string, opts *ConfigOpts, hookType strin
} }
func detectOSType(cmd *Command, opts *ConfigOpts) error { func detectOSType(cmd *Command, opts *ConfigOpts) error {
if cmd.Host == nil {
if IsHostLocal(cmd.Host) {
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
cmd.OS = "linux" cmd.OS = "linux"
opts.Logger.Info().Msg("Unix/Linux type OS detected") opts.Logger.Info().Msg("Unix/Linux type OS detected")
@ -710,7 +706,7 @@ func detectOSType(cmd *Command, opts *ConfigOpts) error {
return fmt.Errorf("using an os that is not yet supported for user commands") return fmt.Errorf("using an os that is not yet supported for user commands")
} }
host, ok := opts.Hosts[*cmd.Host] host, ok := opts.Hosts[cmd.Host]
if ok { if ok {
if host.OS != "" { if host.OS != "" {
return nil return nil
@ -742,3 +738,25 @@ func keyNotSupported(oldKey, newKey string, koanf *koanf.Koanf, opts *ConfigOpts
} }
} }
} }
func replaceVarInString(vars map[string]string, str string, logger zerolog.Logger) string {
if strings.Contains(str, "%{var:") && strings.Contains(str, "}%") {
logger.Debug().Msgf("replacing vars in string %s", str)
for k, v := range vars {
if strings.Contains(str, "%{var:"+k+"}%") {
str = strings.ReplaceAll(str, "%{var:"+k+"}%", v)
}
}
if strings.Contains(str, "%{var:") && strings.Contains(str, "}%") {
logger.Warn().Msg("could not replace all vars in string")
}
}
return str
}
func (c *Command) GetVariablesFromConf(opts *ConfigOpts) {
c.ScriptEnvFile = replaceVarInString(opts.Vars, c.ScriptEnvFile, opts.Logger)
c.Name = replaceVarInString(opts.Vars, c.Name, opts.Logger)
c.Output.File = replaceVarInString(opts.Vars, c.Output.File, opts.Logger)
c.Host = replaceVarInString(opts.Vars, c.Host, opts.Logger)
}

133
pkg/backy/lineinfile.go Normal file
View File

@ -0,0 +1,133 @@
package backy
import (
"bufio"
"bytes"
"fmt"
"io"
"regexp"
"strings"
"golang.org/x/crypto/ssh"
)
func sshConnect(user, password, host string, port int) (*ssh.Client, error) {
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.Password(password),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
addr := fmt.Sprintf("%s:%d", host, port)
return ssh.Dial("tcp", addr, config)
}
func sshReadFile(client *ssh.Client, remotePath string) (string, error) {
session, err := client.NewSession()
if err != nil {
return "", err
}
defer session.Close()
var b bytes.Buffer
session.Stdout = &b
if err := session.Run(fmt.Sprintf("cat %s", remotePath)); err != nil {
return "", err
}
return b.String(), nil
}
func sshWriteFile(client *ssh.Client, remotePath, content string) error {
session, err := client.NewSession()
if err != nil {
return err
}
defer session.Close()
stdin, err := session.StdinPipe()
if err != nil {
return err
}
go func() {
defer stdin.Close()
io.WriteString(stdin, content)
}()
cmd := fmt.Sprintf("cat > %s", remotePath)
return session.Run(cmd)
}
func lineInString(content, regexpPattern, line string) string {
scanner := bufio.NewScanner(strings.NewReader(content))
var lines []string
found := false
re := regexp.MustCompile(regexpPattern)
for scanner.Scan() {
l := scanner.Text()
if re.MatchString(l) {
found = true
lines = append(lines, line)
} else {
lines = append(lines, l)
}
}
if !found {
lines = append(lines, line)
}
return strings.Join(lines, "\n") + "\n"
}
func Call() {
user := "youruser"
password := "yourpassword"
host := "yourhost"
port := 22
remotePath := "/path/to/remote/file"
client, err := sshConnect(user, password, host, port)
if err != nil {
fmt.Println("SSH connection error:", err)
return
}
defer client.Close()
content, err := sshReadFile(client, remotePath)
if err != nil {
fmt.Println("Read error:", err)
return
}
newContent := lineInString(content, "^foo=", "foo=bar")
if err := sshWriteFile(client, remotePath, newContent); err != nil {
fmt.Println("Write error:", err)
return
}
fmt.Println("Line updated successfully over SSH.")
}
type LineInFile struct {
RemotePath string // Path to the remote file
Pattern string // Regex pattern to match lines
Line string // Line to insert or replace
InsertAfter bool // If true, insert after matched line; else replace
User string // SSH username
Password string // SSH password (use key for production)
Host string // SSH host
Port int // SSH port
regexCompiled *regexp.Regexp // Compiled regex (internal use)
}
// CompileRegex compiles the regex pattern for later use
func (l *LineInFile) CompileRegex() error {
re, err := regexp.Compile(l.Pattern)
if err != nil {
return err
}
l.regexCompiled = re
return nil
}

View File

@ -46,9 +46,9 @@ func (opts *ConfigOpts) ListCommand(cmd string) {
} }
// is it remote or local // is it remote or local
if cmdInfo.Host != nil { if !IsHostLocal(cmdInfo.Host) {
println() println()
print("Host: ", *cmdInfo.Host) print("Host: ", cmdInfo.Host)
println() println()
} else { } else {

57
pkg/backy/metrics.go Normal file
View File

@ -0,0 +1,57 @@
package backy
import (
"encoding/json"
"os"
)
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
}
func NewMetrics() *Metrics {
return &Metrics{
SuccessfulExecutions: 0,
FailedExecutions: 0,
TotalExecutions: 0,
ExecutionTime: 0.0,
AverageExecutionTime: 0.0,
SuccessRate: 0.0,
FailureRate: 0.0,
}
}
func (m *Metrics) Update(success bool, executionTime float64) {
m.TotalExecutions++
if success {
m.SuccessfulExecutions++
} else {
m.FailedExecutions++
}
m.ExecutionTime += executionTime
m.AverageExecutionTime = m.ExecutionTime / float64(m.TotalExecutions)
if m.TotalExecutions > 0 {
m.SuccessRate = float64(m.SuccessfulExecutions) / float64(m.TotalExecutions) * 100
m.FailureRate = float64(m.FailedExecutions) / float64(m.TotalExecutions) * 100
}
}
func SaveToFile(metrics *Metrics, filename string) error {
data, err := json.MarshalIndent(metrics, "", " ")
if err != nil {
return err
}
return os.WriteFile(filename, data, 0644)
}
func LoadFromFile(filename string) (*Metrics, error) {
return nil, nil
}

View File

@ -0,0 +1,7 @@
package backy
import "testing"
func TestAddingMetricsForCommand(t *testing.T) {
}

View File

@ -9,6 +9,7 @@ import (
"git.andrewnw.xyz/CyberShell/backy/pkg/logging" "git.andrewnw.xyz/CyberShell/backy/pkg/logging"
"github.com/nikoksr/notify" "github.com/nikoksr/notify"
"github.com/nikoksr/notify/service/http"
"github.com/nikoksr/notify/service/mail" "github.com/nikoksr/notify/service/mail"
"github.com/nikoksr/notify/service/matrix" "github.com/nikoksr/notify/service/matrix"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
@ -30,6 +31,12 @@ type MailConfig struct {
Password string `yaml:"password"` Password string `yaml:"password"`
} }
type HttpConfig struct {
URL string `yaml:"url"`
Method string `yaml:"method"`
Headers map[string][]string `yaml:"headers"`
}
// SetupNotify sets up notify instances for each command list. // SetupNotify sets up notify instances for each command list.
func (opts *ConfigOpts) SetupNotify() { func (opts *ConfigOpts) SetupNotify() {
@ -58,7 +65,8 @@ func (opts *ConfigOpts) SetupNotify() {
opts.Logger.Info().Err(fmt.Errorf("error: ID %s not found in mail object", confId)).Str("list", confName).Send() opts.Logger.Info().Err(fmt.Errorf("error: ID %s not found in mail object", confId)).Str("list", confName).Send()
continue continue
} }
conf.Password = getExternalConfigDirectiveValue(conf.Password, opts) conf.Password = getExternalConfigDirectiveValue(conf.Password, opts, AllowedExternalDirectiveAll)
opts.Logger.Debug().Str("list", confName).Str("id", confId).Msg("adding mail notification service")
mailConf := setupMail(conf) mailConf := setupMail(conf)
services = append(services, mailConf) services = append(services, mailConf)
case "matrix": case "matrix":
@ -67,14 +75,23 @@ func (opts *ConfigOpts) SetupNotify() {
opts.Logger.Info().Err(fmt.Errorf("error: ID %s not found in matrix object", confId)).Str("list", confName).Send() opts.Logger.Info().Err(fmt.Errorf("error: ID %s not found in matrix object", confId)).Str("list", confName).Send()
continue continue
} }
conf.AccessToken = getExternalConfigDirectiveValue(conf.AccessToken, opts) conf.AccessToken = getExternalConfigDirectiveValue(conf.AccessToken, opts, AllowedExternalDirectiveAll)
opts.Logger.Debug().Str("list", confName).Str("id", confId).Msg("adding matrix notification service")
mtrxConf, mtrxErr := setupMatrix(conf) mtrxConf, mtrxErr := setupMatrix(conf)
if mtrxErr != nil { if mtrxErr != nil {
opts.Logger.Info().Str("list", confName).Err(fmt.Errorf("error: configuring matrix id %s failed during setup: %w", id, mtrxErr)) opts.Logger.Info().Str("list", confName).Err(fmt.Errorf("error: configuring matrix id %s failed during setup: %w", id, mtrxErr))
continue continue
} }
services = append(services, mtrxConf) services = append(services, mtrxConf)
case "http":
conf, ok := opts.NotificationConf.HttpConfig[confId]
if !ok {
opts.Logger.Info().Err(fmt.Errorf("error: ID %s not found in http object", confId)).Str("list", confName).Send()
continue
}
opts.Logger.Debug().Str("list", confName).Str("id", confId).Msg("adding http notification service")
httpConf := setupHttp(conf)
services = append(services, httpConf)
default: default:
opts.Logger.Info().Err(fmt.Errorf("id %s not found", id)).Str("list", confName).Send() opts.Logger.Info().Err(fmt.Errorf("id %s not found", id)).Str("list", confName).Send()
} }
@ -100,3 +117,19 @@ func setupMail(config MailConfig) *mail.Mail {
mailClient.BodyFormat(mail.PlainText) mailClient.BodyFormat(mail.PlainText)
return mailClient return mailClient
} }
func setupHttp(httpConf HttpConfig) *http.Service {
httpService := http.New()
httpService.AddReceivers(&http.Webhook{
URL: httpConf.URL,
Header: httpConf.Headers,
ContentType: "text/plain",
Method: httpConf.Method,
BuildPayload: func(subject, message string) (payload any) {
return subject + "\n\n" + message
},
})
return httpService
}

View File

@ -26,31 +26,31 @@ func (i PackageOperation) String() string {
func _PackageOperationNoOp() { func _PackageOperationNoOp() {
var x [1]struct{} var x [1]struct{}
_ = x[DefaultPO-(0)] _ = x[DefaultPO-(0)]
_ = x[PackOpInstall-(1)] _ = x[PackageOperationInstall-(1)]
_ = x[PackOpUpgrade-(2)] _ = x[PackageOperationUpgrade-(2)]
_ = x[PackOpPurge-(3)] _ = x[PackageOperationPurge-(3)]
_ = x[PackOpRemove-(4)] _ = x[PackageOperationRemove-(4)]
_ = x[PackOpCheckVersion-(5)] _ = x[PackageOperationCheckVersion-(5)]
_ = x[PackOpIsInstalled-(6)] _ = x[PackageOperationIsInstalled-(6)]
} }
var _PackageOperationValues = []PackageOperation{DefaultPO, PackOpInstall, PackOpUpgrade, PackOpPurge, PackOpRemove, PackOpCheckVersion, PackOpIsInstalled} var _PackageOperationValues = []PackageOperation{DefaultPO, PackageOperationInstall, PackageOperationUpgrade, PackageOperationPurge, PackageOperationRemove, PackageOperationCheckVersion, PackageOperationIsInstalled}
var _PackageOperationNameToValueMap = map[string]PackageOperation{ var _PackageOperationNameToValueMap = map[string]PackageOperation{
_PackageOperationName[0:0]: DefaultPO, _PackageOperationName[0:0]: DefaultPO,
_PackageOperationLowerName[0:0]: DefaultPO, _PackageOperationLowerName[0:0]: DefaultPO,
_PackageOperationName[0:7]: PackOpInstall, _PackageOperationName[0:7]: PackageOperationInstall,
_PackageOperationLowerName[0:7]: PackOpInstall, _PackageOperationLowerName[0:7]: PackageOperationInstall,
_PackageOperationName[7:14]: PackOpUpgrade, _PackageOperationName[7:14]: PackageOperationUpgrade,
_PackageOperationLowerName[7:14]: PackOpUpgrade, _PackageOperationLowerName[7:14]: PackageOperationUpgrade,
_PackageOperationName[14:19]: PackOpPurge, _PackageOperationName[14:19]: PackageOperationPurge,
_PackageOperationLowerName[14:19]: PackOpPurge, _PackageOperationLowerName[14:19]: PackageOperationPurge,
_PackageOperationName[19:25]: PackOpRemove, _PackageOperationName[19:25]: PackageOperationRemove,
_PackageOperationLowerName[19:25]: PackOpRemove, _PackageOperationLowerName[19:25]: PackageOperationRemove,
_PackageOperationName[25:37]: PackOpCheckVersion, _PackageOperationName[25:37]: PackageOperationCheckVersion,
_PackageOperationLowerName[25:37]: PackOpCheckVersion, _PackageOperationLowerName[25:37]: PackageOperationCheckVersion,
_PackageOperationName[37:48]: PackOpIsInstalled, _PackageOperationName[37:48]: PackageOperationIsInstalled,
_PackageOperationLowerName[37:48]: PackOpIsInstalled, _PackageOperationLowerName[37:48]: PackageOperationIsInstalled,
} }
var _PackageOperationNames = []string{ var _PackageOperationNames = []string{

10
pkg/backy/planForHosts.md Normal file
View 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.

View File

@ -15,6 +15,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/google/uuid"
"github.com/kevinburke/ssh_config" "github.com/kevinburke/ssh_config"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/pkg/sftp" "github.com/pkg/sftp"
@ -28,38 +29,38 @@ var TS = strings.TrimSpace
// ConnectToHost connects to a host by looking up the config values in the file ~/.ssh/config // 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 // It uses any set values and looks up an unset values in the config files
// remoteConfig is modified directly. The *ssh.Client is returned as part of remoteConfig, // remoteHost is modified directly. The *ssh.Client is returned as part of remoteHost,
// If configFile is empty, any required configuration is looked up in the default config files // If configFile is empty, any required configuration is looked up in the default config files
// If any value is not found, defaults are used // If any value is not found, defaults are used
func (remoteConfig *Host) ConnectToHost(opts *ConfigOpts) error { func (remoteHost *Host) ConnectToHost(opts *ConfigOpts) error {
var connectErr error var connectErr error
if TS(remoteConfig.ConfigFilePath) == "" { if TS(remoteHost.ConfigFilePath) == "" {
remoteConfig.useDefaultConfig = true remoteHost.useDefaultConfig = true
} }
khPathErr := remoteConfig.GetKnownHosts() khPathErr := remoteHost.GetKnownHosts()
if khPathErr != nil { if khPathErr != nil {
return khPathErr return khPathErr
} }
if remoteConfig.ClientConfig == nil { if remoteHost.ClientConfig == nil {
remoteConfig.ClientConfig = &ssh.ClientConfig{} remoteHost.ClientConfig = &ssh.ClientConfig{}
} }
var configFile *os.File var configFile *os.File
var sshConfigFileOpenErr error var sshConfigFileOpenErr error
if !remoteConfig.useDefaultConfig { if !remoteHost.useDefaultConfig {
var err error var err error
remoteConfig.ConfigFilePath, err = getFullPathWithHomeDir(remoteConfig.ConfigFilePath) remoteHost.ConfigFilePath, err = getFullPathWithHomeDir(remoteHost.ConfigFilePath)
if err != nil { if err != nil {
return err return err
} }
configFile, sshConfigFileOpenErr = os.Open(remoteConfig.ConfigFilePath) configFile, sshConfigFileOpenErr = os.Open(remoteHost.ConfigFilePath)
if sshConfigFileOpenErr != nil { if sshConfigFileOpenErr != nil {
return sshConfigFileOpenErr return sshConfigFileOpenErr
} }
@ -70,22 +71,22 @@ func (remoteConfig *Host) ConnectToHost(opts *ConfigOpts) error {
return sshConfigFileOpenErr return sshConfigFileOpenErr
} }
} }
remoteConfig.SSHConfigFile = &sshConfigFile{} remoteHost.SSHConfigFile = &sshConfigFile{}
remoteConfig.SSHConfigFile.DefaultUserSettings = ssh_config.DefaultUserSettings remoteHost.SSHConfigFile.DefaultUserSettings = ssh_config.DefaultUserSettings
var decodeErr error var decodeErr error
remoteConfig.SSHConfigFile.SshConfigFile, decodeErr = ssh_config.Decode(configFile) remoteHost.SSHConfigFile.SshConfigFile, decodeErr = ssh_config.Decode(configFile)
if decodeErr != nil { if decodeErr != nil {
return decodeErr return decodeErr
} }
err := remoteConfig.GetProxyJumpFromConfig(opts.Hosts) err := remoteHost.GetProxyJumpFromConfig(opts.Hosts)
if err != nil { if err != nil {
return err return err
} }
if remoteConfig.ProxyHost != nil { if remoteHost.ProxyHost != nil {
for _, proxyHost := range remoteConfig.ProxyHost { for _, proxyHost := range remoteHost.ProxyHost {
err := proxyHost.GetProxyJumpConfig(opts.Hosts, opts) err := proxyHost.GetProxyJumpConfig(opts.Hosts, opts)
opts.Logger.Info().Msgf("Proxy host: %s", proxyHost.Host) opts.Logger.Info().Msgf("Proxy host: %s", proxyHost.Host)
if err != nil { if err != nil {
@ -94,49 +95,49 @@ func (remoteConfig *Host) ConnectToHost(opts *ConfigOpts) error {
} }
} }
remoteConfig.ClientConfig.Timeout = time.Second * 30 remoteHost.ClientConfig.Timeout = time.Second * 30
remoteConfig.GetPrivateKeyFileFromConfig() remoteHost.GetPrivateKeyFileFromConfig()
remoteConfig.GetPort() remoteHost.GetPort()
remoteConfig.GetHostName() remoteHost.GetHostName()
remoteConfig.CombineHostNameWithPort() remoteHost.CombineHostNameWithPort()
remoteConfig.GetSshUserFromConfig() remoteHost.GetSshUserFromConfig()
if remoteConfig.HostName == "" { if remoteHost.HostName == "" {
return errors.Errorf("No hostname found or specified for host %s", remoteConfig.Host) return errors.Errorf("No hostname found or specified for host %s", remoteHost.Host)
} }
err = remoteConfig.GetAuthMethods(opts) err = remoteHost.GetAuthMethods(opts)
if err != nil { if err != nil {
return err return err
} }
hostKeyCallback, err := knownhosts.New(remoteConfig.KnownHostsFile) hostKeyCallback, err := knownhosts.New(remoteHost.KnownHostsFile)
if err != nil { if err != nil {
return errors.Wrap(err, "could not create hostkeycallback function") return errors.Wrap(err, "could not create hostkeycallback function")
} }
remoteConfig.ClientConfig.HostKeyCallback = hostKeyCallback remoteHost.ClientConfig.HostKeyCallback = hostKeyCallback
remoteConfig.SshClient, connectErr = remoteConfig.ConnectThroughBastion(opts.Logger) remoteHost.SshClient, connectErr = remoteHost.ConnectThroughBastion(opts.Logger)
if connectErr != nil { if connectErr != nil {
return connectErr return connectErr
} }
if remoteConfig.SshClient != nil { if remoteHost.SshClient != nil {
opts.Hosts[remoteConfig.Host] = remoteConfig opts.Hosts[remoteHost.Host] = remoteHost
return nil return nil
} }
opts.Logger.Info().Msgf("Connecting to host %s", remoteConfig.HostName) opts.Logger.Info().Msgf("Connecting to host %s", remoteHost.HostName)
remoteConfig.SshClient, connectErr = ssh.Dial("tcp", remoteConfig.HostName, remoteConfig.ClientConfig) remoteHost.SshClient, connectErr = ssh.Dial("tcp", remoteHost.HostName, remoteHost.ClientConfig)
if connectErr != nil { if connectErr != nil {
return connectErr return connectErr
} }
opts.Hosts[remoteConfig.Host] = remoteConfig opts.Hosts[remoteHost.Host] = remoteHost
return nil return nil
} }
@ -226,6 +227,8 @@ func (remoteHost *Host) GetPrivateKeyFileFromConfig() {
var identityFile string var identityFile string
if remoteHost.PrivateKeyPath == "" { if remoteHost.PrivateKeyPath == "" {
identityFile, _ = remoteHost.SSHConfigFile.SshConfigFile.Get(remoteHost.Host, "IdentityFile") identityFile, _ = remoteHost.SSHConfigFile.SshConfigFile.Get(remoteHost.Host, "IdentityFile")
// println("Identity file:", identityFile)
// println("Host:", remoteHost.Host)
if identityFile == "" { if identityFile == "" {
identityFile, _ = remoteHost.SSHConfigFile.DefaultUserSettings.GetStrict(remoteHost.Host, "IdentityFile") identityFile, _ = remoteHost.SSHConfigFile.DefaultUserSettings.GetStrict(remoteHost.Host, "IdentityFile")
if identityFile == "" { if identityFile == "" {
@ -237,6 +240,7 @@ func (remoteHost *Host) GetPrivateKeyFileFromConfig() {
identityFile = remoteHost.PrivateKeyPath identityFile = remoteHost.PrivateKeyPath
} }
// println("Identity file:", identityFile)
remoteHost.PrivateKeyPath, _ = getFullPathWithHomeDir(identityFile) remoteHost.PrivateKeyPath, _ = getFullPathWithHomeDir(identityFile)
} }
@ -325,33 +329,33 @@ func (remoteHost *Host) GetKnownHosts() error {
} }
func GetPrivateKeyPassword(key string, opts *ConfigOpts) string { func GetPrivateKeyPassword(key string, opts *ConfigOpts) string {
return getExternalConfigDirectiveValue(key, opts) return getExternalConfigDirectiveValue(key, opts, AllowedExternalDirectiveAll)
} }
// GetPassword gets any password // GetPassword gets any password
func GetPassword(pass string, opts *ConfigOpts) string { func GetPassword(pass string, opts *ConfigOpts) string {
return getExternalConfigDirectiveValue(pass, opts) return getExternalConfigDirectiveValue(pass, opts, AllowedExternalDirectiveAll)
} }
func (remoteConfig *Host) GetProxyJumpFromConfig(hosts map[string]*Host) error { func (remoteHost *Host) GetProxyJumpFromConfig(hosts map[string]*Host) error {
proxyJump, _ := remoteConfig.SSHConfigFile.SshConfigFile.Get(remoteConfig.Host, "ProxyJump") proxyJump, _ := remoteHost.SSHConfigFile.SshConfigFile.Get(remoteHost.Host, "ProxyJump")
if proxyJump == "" { if proxyJump == "" {
proxyJump = remoteConfig.SSHConfigFile.DefaultUserSettings.Get(remoteConfig.Host, "ProxyJump") proxyJump = remoteHost.SSHConfigFile.DefaultUserSettings.Get(remoteHost.Host, "ProxyJump")
} }
if remoteConfig.ProxyJump == "" && proxyJump != "" { if remoteHost.ProxyJump == "" && proxyJump != "" {
remoteConfig.ProxyJump = proxyJump remoteHost.ProxyJump = proxyJump
} }
proxyJumpHosts := strings.Split(remoteConfig.ProxyJump, ",") proxyJumpHosts := strings.Split(remoteHost.ProxyJump, ",")
if remoteConfig.ProxyHost == nil && len(proxyJumpHosts) == 1 { if remoteHost.ProxyHost == nil && len(proxyJumpHosts) == 1 {
remoteConfig.ProxyJump = proxyJump remoteHost.ProxyJump = proxyJump
proxyHost, proxyHostFound := hosts[proxyJump] proxyHost, proxyHostFound := hosts[proxyJump]
if proxyHostFound { if proxyHostFound {
remoteConfig.ProxyHost = append(remoteConfig.ProxyHost, proxyHost) remoteHost.ProxyHost = append(remoteHost.ProxyHost, proxyHost)
} else { } else {
if proxyJump != "" { if proxyJump != "" {
newProxy := &Host{Host: proxyJump} newProxy := &Host{Host: proxyJump}
remoteConfig.ProxyHost = append(remoteConfig.ProxyHost, newProxy) remoteHost.ProxyHost = append(remoteHost.ProxyHost, newProxy)
} }
} }
} }
@ -359,25 +363,25 @@ func (remoteConfig *Host) GetProxyJumpFromConfig(hosts map[string]*Host) error {
return nil return nil
} }
func (remoteConfig *Host) GetProxyJumpConfig(hosts map[string]*Host, opts *ConfigOpts) error { func (remoteHost *Host) GetProxyJumpConfig(hosts map[string]*Host, opts *ConfigOpts) error {
if TS(remoteConfig.ConfigFilePath) == "" { if TS(remoteHost.ConfigFilePath) == "" {
remoteConfig.useDefaultConfig = true remoteHost.useDefaultConfig = true
} }
khPathErr := remoteConfig.GetKnownHosts() khPathErr := remoteHost.GetKnownHosts()
if khPathErr != nil { if khPathErr != nil {
return khPathErr return khPathErr
} }
if remoteConfig.ClientConfig == nil { if remoteHost.ClientConfig == nil {
remoteConfig.ClientConfig = &ssh.ClientConfig{} remoteHost.ClientConfig = &ssh.ClientConfig{}
} }
var configFile *os.File var configFile *os.File
var sshConfigFileOpenErr error var sshConfigFileOpenErr error
if !remoteConfig.useDefaultConfig { if !remoteHost.useDefaultConfig {
configFile, sshConfigFileOpenErr = os.Open(remoteConfig.ConfigFilePath) configFile, sshConfigFileOpenErr = os.Open(remoteHost.ConfigFilePath)
if sshConfigFileOpenErr != nil { if sshConfigFileOpenErr != nil {
return sshConfigFileOpenErr return sshConfigFileOpenErr
} }
@ -388,39 +392,39 @@ func (remoteConfig *Host) GetProxyJumpConfig(hosts map[string]*Host, opts *Confi
return sshConfigFileOpenErr return sshConfigFileOpenErr
} }
} }
remoteConfig.SSHConfigFile = &sshConfigFile{} remoteHost.SSHConfigFile = &sshConfigFile{}
remoteConfig.SSHConfigFile.DefaultUserSettings = ssh_config.DefaultUserSettings remoteHost.SSHConfigFile.DefaultUserSettings = ssh_config.DefaultUserSettings
var decodeErr error var decodeErr error
remoteConfig.SSHConfigFile.SshConfigFile, decodeErr = ssh_config.Decode(configFile) remoteHost.SSHConfigFile.SshConfigFile, decodeErr = ssh_config.Decode(configFile)
if decodeErr != nil { if decodeErr != nil {
return decodeErr return decodeErr
} }
remoteConfig.GetPrivateKeyFileFromConfig() remoteHost.GetPrivateKeyFileFromConfig()
remoteConfig.GetPort() remoteHost.GetPort()
remoteConfig.GetHostName() remoteHost.GetHostName()
remoteConfig.CombineHostNameWithPort() remoteHost.CombineHostNameWithPort()
remoteConfig.GetSshUserFromConfig() remoteHost.GetSshUserFromConfig()
remoteConfig.isProxyHost = true remoteHost.isProxyHost = true
if remoteConfig.HostName == "" { if remoteHost.HostName == "" {
return errors.Errorf("No hostname found or specified for host %s", remoteConfig.Host) return errors.Errorf("No hostname found or specified for host %s", remoteHost.Host)
} }
err := remoteConfig.GetAuthMethods(opts) err := remoteHost.GetAuthMethods(opts)
if err != nil { if err != nil {
return err return err
} }
// TODO: Add value/option to config for host key and add bool to check for host key // TODO: Add value/option to config for host key and add bool to check for host key
hostKeyCallback, err := knownhosts.New(remoteConfig.KnownHostsFile) hostKeyCallback, err := knownhosts.New(remoteHost.KnownHostsFile)
if err != nil { if err != nil {
return fmt.Errorf("could not create hostkeycallback function: %v", err) return fmt.Errorf("could not create hostkeycallback function: %v", err)
} }
remoteConfig.ClientConfig.HostKeyCallback = hostKeyCallback remoteHost.ClientConfig.HostKeyCallback = hostKeyCallback
hosts[remoteConfig.Host] = remoteConfig hosts[remoteHost.Host] = remoteHost
return nil return nil
} }
func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([]string, error) { func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([]string, error) {
var ( var (
ArgsStr string ArgsStr string
cmdOutBuf bytes.Buffer cmdOutBuf bytes.Buffer
@ -440,8 +444,8 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
cmdCtxLogger.Info(). cmdCtxLogger.Info().
Str("Command", command.Name). Str("Command", command.Name).
Str("Host", *command.Host). Str("Host", command.Host).
Msgf("Running %s on host %s", getCommandTypeAndSetCommandInfoLabel(command.Type), *command.Host) Msgf("Running %s on host %s", getCommandTypeAndSetCommandInfoLabel(command.Type), command.Host)
// cmdCtxLogger.Debug().Str("cmd", command.Cmd).Strs("args", command.Args).Send() // cmdCtxLogger.Debug().Str("cmd", command.Cmd).Strs("args", command.Args).Send()
@ -472,14 +476,14 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
// Handle command execution based on type // Handle command execution based on type
switch command.Type { switch command.Type {
case ScriptCT: case ScriptCommandType:
return command.runScript(commandSession, cmdCtxLogger, &cmdOutBuf) return command.runScript(commandSession, cmdCtxLogger, &cmdOutBuf)
case RemoteScriptCT: case RemoteScriptCommandType:
return command.runRemoteScript(commandSession, cmdCtxLogger, &cmdOutBuf) return command.runRemoteScript(commandSession, cmdCtxLogger, &cmdOutBuf)
case ScriptFileCT: case ScriptFileCommandType:
return command.runScriptFile(commandSession, cmdCtxLogger, &cmdOutBuf) return command.runScriptFile(commandSession, cmdCtxLogger, &cmdOutBuf)
case PackageCT: case PackageCommandType:
if command.PackageOperation == PackOpCheckVersion { if command.PackageOperation == PackageOperationCheckVersion {
commandSession.Stderr = nil commandSession.Stderr = nil
// Execute the package version command remotely // Execute the package version command remotely
// Parse the output of package version command // Parse the output of package version command
@ -496,7 +500,7 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send() cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send()
// Run simple command // Run simple command
if err := commandSession.Run(ArgsStr); err != nil { if err := commandSession.Run(ArgsStr); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error running command: %w", err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running command: %w", err)
} }
} }
default: default:
@ -507,18 +511,43 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
} }
cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send() cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send()
if command.Type == UserCT && command.UserOperation == "password" { if command.Type == UserCommandType && command.UserOperation == "password" {
// cmdCtxLogger.Debug().Msgf("adding stdin") // cmdCtxLogger.Debug().Msgf("adding stdin")
userNamePass := fmt.Sprintf("%s:%s", command.Username, command.UserPassword)
ArgsStr = fmt.Sprintf("echo %s | chpasswd", userNamePass)
userNamePass := fmt.Sprintf("%s:%s", command.Username, command.UserPassword)
client, err := sftp.NewClient(command.RemoteHost.SshClient)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating sftp client: %v", err)
}
uuidFile := uuid.New()
passFilePath := fmt.Sprintf("/tmp/%s", uuidFile.String())
passFile, passFileErr := client.Create(passFilePath)
if passFileErr != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating file /tmp/%s: %v", uuidFile.String(), passFileErr)
}
_, err = passFile.Write([]byte(userNamePass))
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error writing to file /tmp/%s: %v", uuidFile.String(), err)
}
ArgsStr = fmt.Sprintf("cat %s | chpasswd", passFilePath)
defer passFile.Close()
rmFileFunc := func() {
_ = client.Remove(passFilePath)
}
defer rmFileFunc()
// commandSession.Stdin = command.stdin // commandSession.Stdin = command.stdin
} }
if err := commandSession.Run(ArgsStr); err != nil { if err := commandSession.Run(ArgsStr); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error running command: %w", err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running command: %w", err)
} }
if command.Type == UserCT { if command.Type == UserCommandType {
// REFACTOR IF/WHEN WINDOWS SUPPORT IS ADDED
if command.UserOperation == "add" { if command.UserOperation == "add" {
if command.UserSshPubKeys != nil { if command.UserSshPubKeys != nil {
@ -534,38 +563,41 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
commandSession, _ = command.RemoteHost.createSSHSession(opts) commandSession, _ = command.RemoteHost.createSSHSession(opts)
userHome, err = commandSession.CombinedOutput(fmt.Sprintf("grep \"%s\" /etc/passwd | cut -d: -f6", command.Username)) userHome, err = commandSession.CombinedOutput(fmt.Sprintf("grep \"%s\" /etc/passwd | cut -d: -f6", command.Username))
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error finding user home from /etc/passwd: %v", err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error finding user home from /etc/passwd: %v", err)
} }
command.UserHome = strings.TrimSpace(string(userHome)) command.UserHome = strings.TrimSpace(string(userHome))
userSshDir := fmt.Sprintf("%s/.ssh", command.UserHome) userSshDir := fmt.Sprintf("%s/.ssh", command.UserHome)
client, err = sftp.NewClient(command.RemoteHost.SshClient) client, err = sftp.NewClient(command.RemoteHost.SshClient)
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error creating sftp client: %v", err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating sftp client: %v", err)
} }
client.MkdirAll(userSshDir) err = client.MkdirAll(userSshDir)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating directory %s: %v", userSshDir, err)
}
_, err = client.Create(fmt.Sprintf("%s/authorized_keys", userSshDir)) _, err = client.Create(fmt.Sprintf("%s/authorized_keys", userSshDir))
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)
} }
f, err = client.OpenFile(fmt.Sprintf("%s/authorized_keys", userSshDir), os.O_APPEND|os.O_CREATE|os.O_WRONLY) f, err = client.OpenFile(fmt.Sprintf("%s/authorized_keys", userSshDir), os.O_APPEND|os.O_CREATE|os.O_WRONLY)
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)
} }
defer f.Close() defer f.Close()
for _, k := range command.UserSshPubKeys { for _, k := range command.UserSshPubKeys {
buf := bytes.NewBufferString(k) buf := bytes.NewBufferString(k)
cmdCtxLogger.Info().Str("key", k).Msg("adding SSH key") cmdCtxLogger.Info().Str("key", k).Msg("adding SSH key")
if _, err := f.ReadFrom(buf); err != nil { if _, err := f.ReadFrom(buf); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error adding to authorized keys: %v", err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error adding to authorized keys: %v", err)
} }
} }
commandSession, _ = command.RemoteHost.createSSHSession(opts) commandSession, _ = command.RemoteHost.createSSHSession(opts)
_, err = commandSession.CombinedOutput(fmt.Sprintf("chown -R %s:%s %s", command.Username, command.Username, userHome)) _, err = commandSession.CombinedOutput(fmt.Sprintf("chown -R %s:%s %s", command.Username, command.Username, userHome))
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), err return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), err
} }
} }
@ -573,11 +605,13 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
} }
} }
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), nil return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil
} }
func checkPackageVersion(cmdCtxLogger zerolog.Logger, command *Command, commandSession *ssh.Session, cmdOutBuf bytes.Buffer) ([]string, error) { func checkPackageVersion(cmdCtxLogger zerolog.Logger, command *Command, commandSession *ssh.Session, cmdOutBuf bytes.Buffer) ([]string, error) {
cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Checking package versions") for _, p := range command.Packages {
cmdCtxLogger.Info().Str("package", p.Name).Msg("Checking package versions")
}
// Prepare command arguments // Prepare command arguments
ArgsStr := command.Cmd ArgsStr := command.Cmd
for _, v := range command.Args { for _, v := range command.Args {
@ -592,9 +626,9 @@ func checkPackageVersion(cmdCtxLogger zerolog.Logger, command *Command, commandS
_, parseErr := parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf) _, parseErr := parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf)
if parseErr != nil { if parseErr != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error: package %s not listed: %w", command.PackageName, err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error: packages %v not listed: %w", command.Packages, err)
} }
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error running %s: %w", ArgsStr, err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running %s: %w", ArgsStr, err)
} }
return parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf) return parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf)
@ -624,7 +658,7 @@ func (command *Command) runScript(session *ssh.Session, cmdCtxLogger zerolog.Log
return collectOutput(outputBuf, command.Name, cmdCtxLogger, true), fmt.Errorf("error waiting for shell: %w", err) return collectOutput(outputBuf, command.Name, cmdCtxLogger, true), fmt.Errorf("error waiting for shell: %w", err)
} }
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.OutputToLog), nil return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil
} }
// runScriptFile handles the execution of script files. // runScriptFile handles the execution of script files.
@ -643,7 +677,7 @@ func (command *Command) runScriptFile(session *ssh.Session, cmdCtxLogger zerolog
return collectOutput(outputBuf, command.Name, cmdCtxLogger, true), fmt.Errorf("error waiting for shell: %w", err) return collectOutput(outputBuf, command.Name, cmdCtxLogger, true), fmt.Errorf("error waiting for shell: %w", err)
} }
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.OutputToLog), nil return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil
} }
// prepareScriptBuffer prepares a buffer for inline scripts. // prepareScriptBuffer prepares a buffer for inline scripts.
@ -700,10 +734,10 @@ func (command *Command) runRemoteScript(session *ssh.Session, cmdCtxLogger zerol
err = session.Run(command.Shell) err = session.Run(command.Shell)
if err != nil { if err != nil {
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error running remote script: %w", err) return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running remote script: %w", err)
} }
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.OutputToLog), nil return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil
} }
// readFileToBuffer reads a file into a buffer. // readFileToBuffer reads a file into a buffer.
@ -776,7 +810,7 @@ func (h *Host) DetectOS(opts *ConfigOpts) (string, error) {
return osName, nil return osName, nil
} }
func CheckIfHostHasHostName(host string) (bool, string) { func DoesHostHaveHostName(host string) (bool, string) {
HostName, err := ssh_config.DefaultUserSettings.GetStrict(host, "HostName") HostName, err := ssh_config.DefaultUserSettings.GetStrict(host, "HostName")
if err != nil { if err != nil {
return false, "" return false, ""
@ -784,3 +818,8 @@ func CheckIfHostHasHostName(host string) (bool, string) {
println(HostName) println(HostName)
return HostName != "", HostName return HostName != "", HostName
} }
func IsHostLocal(host string) bool {
host = strings.ToLower(host)
return host == "127.0.0.1" || host == "localhost" || host == ""
}

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman" "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman"
packagemanagercommon "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/common"
"git.andrewnw.xyz/CyberShell/backy/pkg/remotefetcher" "git.andrewnw.xyz/CyberShell/backy/pkg/remotefetcher"
"git.andrewnw.xyz/CyberShell/backy/pkg/usermanager" "git.andrewnw.xyz/CyberShell/backy/pkg/usermanager"
vaultapi "github.com/hashicorp/vault/api" vaultapi "github.com/hashicorp/vault/api"
@ -33,7 +34,7 @@ type (
Port uint16 `yaml:"port,omitempty"` Port uint16 `yaml:"port,omitempty"`
ProxyJump string `yaml:"proxyjump,omitempty"` ProxyJump string `yaml:"proxyjump,omitempty"`
Password string `yaml:"password,omitempty"` Password string `yaml:"password,omitempty"`
PrivateKeyPath string `yaml:"privateKeyPath,omitempty"` PrivateKeyPath string `yaml:"IdentityFile,omitempty"`
PrivateKeyPassword string `yaml:"privateKeyPassword,omitempty"` PrivateKeyPassword string `yaml:"privateKeyPassword,omitempty"`
useDefaultConfig bool useDefaultConfig bool
User string `yaml:"user,omitempty"` User string `yaml:"user,omitempty"`
@ -56,7 +57,7 @@ type (
// See CommandType enum further down the page for acceptable values // See CommandType enum further down the page for acceptable values
Type CommandType `yaml:"type,omitempty"` Type CommandType `yaml:"type,omitempty"`
Host *string `yaml:"host,omitempty"` Host string `yaml:"host,omitempty"`
Hooks *Hooks `yaml:"hooks,omitempty"` Hooks *Hooks `yaml:"hooks,omitempty"`
@ -74,19 +75,19 @@ type (
Environment []string `yaml:"environment,omitempty"` Environment []string `yaml:"environment,omitempty"`
GetOutputInList bool `yaml:"getOutputInList,omitempty"`
ScriptEnvFile string `yaml:"scriptEnvFile"` ScriptEnvFile string `yaml:"scriptEnvFile"`
OutputToLog bool `yaml:"outputToLog,omitempty"` Output struct {
File string `yaml:"file,omitempty"`
OutputFile string `yaml:"outputFile,omitempty"` ToLog bool `yaml:"toLog,omitempty"`
InList bool `yaml:"inList,omitempty"`
} `yaml:"output"`
// BEGIN PACKAGE COMMAND FIELDS // BEGIN PACKAGE COMMAND FIELDS
PackageManager string `yaml:"packageManager,omitempty"` PackageManager string `yaml:"packageManager,omitempty"`
PackageName string `yaml:"packageName,omitempty"` Packages []packagemanagercommon.Package `yaml:"packages,omitempty"`
PackageVersion string `yaml:"packageVersion,omitempty"` PackageVersion string `yaml:"packageVersion,omitempty"`
@ -115,7 +116,9 @@ type (
UserShell string `yaml:"userShell,omitempty"` UserShell string `yaml:"userShell,omitempty"`
SystemUser bool `yaml:"systemUser,omitempty"` UserCreateHome bool `yaml:"userCreateHome,omitempty"`
UserIsSystem bool `yaml:"userIsSystem,omitempty"`
UserPassword string `yaml:"userPassword,omitempty"` UserPassword string `yaml:"userPassword,omitempty"`
@ -133,7 +136,7 @@ type (
// stdin only for userOperation = password (for now) // stdin only for userOperation = password (for now)
stdin *strings.Reader stdin *strings.Reader
// END USER STRUCT FIELDS // END USER STRUCommandType FIELDS
} }
RemoteSource struct { RemoteSource struct {
@ -148,13 +151,17 @@ type (
BackyOptionFunc func(*ConfigOpts) BackyOptionFunc func(*ConfigOpts)
CmdList struct { CmdList struct {
Name string `yaml:"name,omitempty"` Name string `yaml:"name,omitempty"`
Cron string `yaml:"cron,omitempty"` Cron string `yaml:"cron,omitempty"`
RunCmdOnFailure string `yaml:"runCmdOnFailure,omitempty"` RunCmdOnFailure string `yaml:"runCmdOnFailure,omitempty"`
Order []string `yaml:"order,omitempty"` Order []string `yaml:"order,omitempty"`
Notifications []string `yaml:"notifications,omitempty"` Notifications []string `yaml:"notifications,omitempty"`
GetOutput bool `yaml:"getOutput,omitempty"` GetCommandOutputInNotificationsOnSuccess bool `yaml:"sendNotificationOnSuccess,omitempty"`
NotifyOnSuccess bool `yaml:"notifyOnSuccess,omitempty"`
Notify struct {
OnFailure bool `yaml:"onFailure,omitempty"`
OnSuccess bool `yaml:"onSuccess,omitempty"`
} `yaml:"notify,omitempty"`
NotifyConfig *notify.Notify NotifyConfig *notify.Notify
Source string `yaml:"source"` // URL to fetch remote commands Source string `yaml:"source"` // URL to fetch remote commands
@ -183,6 +190,8 @@ type (
ConfigFilePath string ConfigFilePath string
HostsFilePath string
ConfigDir string ConfigDir string
LogFilePath string LogFilePath string
@ -203,6 +212,8 @@ type (
List ListConfig List ListConfig
Vars map[string]string `yaml:"variables"`
VaultKeys []*VaultKey `yaml:"keys"` VaultKeys []*VaultKey `yaml:"keys"`
koanf *koanf.Koanf koanf *koanf.Koanf
@ -221,6 +232,7 @@ type (
VaultKey struct { VaultKey struct {
Name string `yaml:"name"` Name string `yaml:"name"`
Key string `yaml:"key"`
Path string `yaml:"path"` Path string `yaml:"path"`
ValueType string `yaml:"type"` ValueType string `yaml:"type"`
MountPath string `yaml:"mountpath"` MountPath string `yaml:"mountpath"`
@ -236,6 +248,7 @@ type (
Notifications struct { Notifications struct {
MailConfig map[string]MailConfig `yaml:"mail,omitempty"` MailConfig map[string]MailConfig `yaml:"mail,omitempty"`
MatrixConfig map[string]MatrixStruct `yaml:"matrix,omitempty"` MatrixConfig map[string]MatrixStruct `yaml:"matrix,omitempty"`
HttpConfig map[string]HttpConfig `yaml:"http,omitempty"`
} }
CmdOutput struct { CmdOutput struct {
@ -271,28 +284,55 @@ type (
Error error // Error encountered, if any Error error // Error encountered, if any
} }
ListMetrics struct {
Name string
SuccessfulExecutions uint64
FailedExecutions uint64
TotalExecutions uint64
}
CommandMetrics struct {
Name string
SuccessfulExecutions uint64
FailedExecutions uint64
TotalExecutions uint64
}
// use ints so we can use enums // use ints so we can use enums
CommandType int CommandType int
PackageOperation int PackageOperation int
AllowedExternalDirectives int
) )
//go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=CommandType //go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=CommandType
const ( const (
DefaultCT CommandType = iota // DefaultCommandType CommandType = iota //
ScriptCT // script ScriptCommandType // script
ScriptFileCT // scriptFile ScriptFileCommandType // scriptFile
RemoteScriptCT // remoteScript RemoteScriptCommandType // remoteScript
PackageCT // package PackageCommandType // package
UserCT // user UserCommandType // user
) )
//go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=PackageOperation //go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=PackageOperation
const ( const (
DefaultPO PackageOperation = iota // DefaultPO PackageOperation = iota //
PackOpInstall // install PackageOperationInstall // install
PackOpUpgrade // upgrade PackageOperationUpgrade // upgrade
PackOpPurge // purge PackageOperationPurge // purge
PackOpRemove // remove PackageOperationRemove // remove
PackOpCheckVersion // checkVersion PackageOperationCheckVersion // checkVersion
PackOpIsInstalled // isInstalled PackageOperationIsInstalled // isInstalled
)
//go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=AllowedExternalDirectives
const (
DefaultExternalDir AllowedExternalDirectives = iota
AllowedExternalDirectiveVault // vault
AllowedExternalDirectiveVaultEnv // vault-env
AllowedExternalDirectiveVaultFile // vault-file
AllowedExternalDirectiveAll // vault-file-env
AllowedExternalDirectiveFileEnv // file-env
AllowedExternalDirectiveFile // file
AllowedExternalDirectiveEnv // env
) )

View File

@ -6,16 +6,19 @@ package backy
import ( import (
"bytes" "bytes"
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"git.andrewnw.xyz/CyberShell/backy/pkg/logging" "git.andrewnw.xyz/CyberShell/backy/pkg/logging"
"git.andrewnw.xyz/CyberShell/backy/pkg/remotefetcher" "git.andrewnw.xyz/CyberShell/backy/pkg/remotefetcher"
vault "github.com/hashicorp/vault/api"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/knadh/koanf/v2" "github.com/knadh/koanf/v2"
"github.com/rs/zerolog" "github.com/rs/zerolog"
@ -65,8 +68,14 @@ func SetLogFile(logFile string) BackyOptionFunc {
} }
} }
// SetCmdStdOut forces the command output to stdout func SetHostsConfigFile(hostsConfigFile string) BackyOptionFunc {
func SetCmdStdOut(setStdOut bool) BackyOptionFunc { return func(bco *ConfigOpts) {
bco.HostsFilePath = hostsConfigFile
}
}
// EnableCommandStdOut forces the command output to stdout
func EnableCommandStdOut(setStdOut bool) BackyOptionFunc {
return func(bco *ConfigOpts) { return func(bco *ConfigOpts) {
bco.CmdStdOut = setStdOut bco.CmdStdOut = setStdOut
} }
@ -79,7 +88,7 @@ func EnableCron() BackyOptionFunc {
} }
} }
func NewOpts(configFilePath string, opts ...BackyOptionFunc) *ConfigOpts { func NewConfigOptions(configFilePath string, opts ...BackyOptionFunc) *ConfigOpts {
b := &ConfigOpts{} b := &ConfigOpts{}
b.ConfigFilePath = configFilePath b.ConfigFilePath = configFilePath
for _, opt := range opts { for _, opt := range opts {
@ -108,7 +117,11 @@ func injectEnvIntoSSH(envVarsToInject environmentVars, process *ssh.Session, opt
goto errEnvFile goto errEnvFile
} }
for key, val := range envMap { for key, val := range envMap {
process.Setenv(key, GetVaultKey(val, opts, log)) err = process.Setenv(key, getExternalConfigDirectiveValue(val, opts, AllowedExternalDirectiveVault))
if err != nil {
log.Error().Err(err).Send()
}
} }
} }
@ -119,12 +132,15 @@ errEnvFile:
if strings.Contains(envVal, "=") { if strings.Contains(envVal, "=") {
envVarArr := strings.Split(envVal, "=") envVarArr := strings.Split(envVal, "=")
process.Setenv(envVarArr[0], GetVaultKey(envVarArr[1], opts, log)) err := process.Setenv(envVarArr[0], getExternalConfigDirectiveValue(envVarArr[1], opts, AllowedExternalDirectiveVaultFile))
if err != nil {
log.Error().Err(err).Send()
}
} }
} }
} }
func injectEnvIntoLocalCMD(envVarsToInject environmentVars, process *exec.Cmd, log zerolog.Logger) { func injectEnvIntoLocalCMD(envVarsToInject environmentVars, process *exec.Cmd, log zerolog.Logger, opts *ConfigOpts) {
if envVarsToInject.file != "" { if envVarsToInject.file != "" {
envPath, _ := getFullPathWithHomeDir(envVarsToInject.file) envPath, _ := getFullPathWithHomeDir(envVarsToInject.file)
@ -148,7 +164,8 @@ errEnvFile:
for _, envVal := range envVarsToInject.env { for _, envVal := range envVarsToInject.env {
if strings.Contains(envVal, "=") { if strings.Contains(envVal, "=") {
process.Env = append(process.Env, envVal) envVarArr := strings.Split(envVal, "=")
process.Env = append(process.Env, fmt.Sprintf("%s=%s", envVarArr[0], getExternalConfigDirectiveValue(envVarArr[1], opts, AllowedExternalDirectiveVault)))
} }
} }
process.Env = append(process.Env, os.Environ()...) process.Env = append(process.Env, os.Environ()...)
@ -249,7 +266,6 @@ func (opts *ConfigOpts) loadEnv() {
func expandEnvVars(backyEnv map[string]string, envVars []string) { func expandEnvVars(backyEnv map[string]string, envVars []string) {
env := func(name string) string { env := func(name string) string {
name = strings.ToUpper(name)
envVar, found := backyEnv[name] envVar, found := backyEnv[name]
if found { if found {
return envVar return envVar
@ -258,34 +274,34 @@ func expandEnvVars(backyEnv map[string]string, envVars []string) {
} }
for indx, v := range envVars { for indx, v := range envVars {
if strings.HasPrefix(v, externDirectiveStart) && strings.HasSuffix(v, externDirectiveEnd) {
if strings.HasPrefix(v, envExternDirectiveStart) { if strings.HasPrefix(v, envExternDirectiveStart) && strings.HasSuffix(v, externDirectiveEnd) {
v = strings.TrimPrefix(v, envExternDirectiveStart) v = strings.TrimPrefix(v, envExternDirectiveStart)
v = strings.TrimRight(v, externDirectiveEnd) v = strings.TrimRight(v, externDirectiveEnd)
out, _ := shell.Expand(v, env) out, _ := shell.Expand(v, env)
envVars[indx] = out envVars[indx] = out
}
} }
} }
} }
func getCommandTypeAndSetCommandInfo(command *Command) *Command { func getCommandTypeAndSetCommandInfo(command *Command) *Command {
if command.Type == PackageCT && !command.packageCmdSet { if command.Type == PackageCommandType && !command.packageCmdSet {
command.packageCmdSet = true command.packageCmdSet = true
switch command.PackageOperation { switch command.PackageOperation {
case PackOpInstall: case PackageOperationInstall:
command.Cmd, command.Args = command.pkgMan.Install(command.PackageName, command.PackageVersion, command.Args) command.Cmd, command.Args = command.pkgMan.Install(command.Packages, command.Args)
case PackOpRemove: case PackageOperationRemove:
command.Cmd, command.Args = command.pkgMan.Remove(command.PackageName, command.Args) command.Cmd, command.Args = command.pkgMan.Remove(command.Packages, command.Args)
case PackOpUpgrade: case PackageOperationUpgrade:
command.Cmd, command.Args = command.pkgMan.Upgrade(command.PackageName, command.PackageVersion) command.Cmd, command.Args = command.pkgMan.Upgrade(command.Packages)
case PackOpCheckVersion: case PackageOperationCheckVersion:
command.Cmd, command.Args = command.pkgMan.CheckVersion(command.PackageName, command.PackageVersion) command.Cmd, command.Args = command.pkgMan.CheckVersion(command.Packages)
} }
} }
if command.Type == UserCT && !command.userCmdSet { if command.Type == UserCommandType && !command.userCmdSet {
command.userCmdSet = true command.userCmdSet = true
switch command.UserOperation { switch command.UserOperation {
case "add": case "add":
@ -293,7 +309,8 @@ func getCommandTypeAndSetCommandInfo(command *Command) *Command {
command.Username, command.Username,
command.UserHome, command.UserHome,
command.UserShell, command.UserShell,
command.SystemUser, command.UserIsSystem,
command.UserCreateHome,
command.UserGroups, command.UserGroups,
command.Args) command.Args)
case "modify": case "modify":
@ -317,71 +334,188 @@ func getCommandTypeAndSetCommandInfo(command *Command) *Command {
func parsePackageVersion(output string, cmdCtxLogger zerolog.Logger, command *Command, cmdOutBuf bytes.Buffer) ([]string, error) { func parsePackageVersion(output string, cmdCtxLogger zerolog.Logger, command *Command, cmdOutBuf bytes.Buffer) ([]string, error) {
var err error var err error
pkgVersion, err := command.pkgMan.Parse(output) var errs []error
// println(output) pkgVersionOnSystem, err := command.pkgMan.ParseRemotePackageManagerVersionOutput(output)
if err != nil { if err != nil {
cmdCtxLogger.Error().Err(err).Str("package", command.PackageName).Msg("Error parsing package version output") cmdCtxLogger.Error().AnErr("Error parsing package version output", err).Send()
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), err return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error parsing package version output: %v", err)
} }
cmdCtxLogger.Info(). for _, p := range pkgVersionOnSystem {
Str("Installed", pkgVersion.Installed). packageIndex := getPackageIndexFromCommand(command, p.Name)
Str("Candidate", pkgVersion.Candidate). if packageIndex == -1 {
Msg("Package version comparison") cmdCtxLogger.Error().Str("package", p.Name).Msg("Package not found in command")
continue
if command.PackageVersion != "" {
if pkgVersion.Installed == command.PackageVersion {
cmdCtxLogger.Info().Msgf("Installed version matches specified version: %s", command.PackageVersion)
} else {
cmdCtxLogger.Info().Msgf("Installed version does not match specified version: %s", command.PackageVersion)
err = fmt.Errorf("Installed version does not match specified version: %s", command.PackageVersion)
} }
} else { command.Packages[packageIndex].VersionCheck = p.VersionCheck
if pkgVersion.Installed == pkgVersion.Candidate { packageFromCommand := command.Packages[packageIndex]
cmdCtxLogger.Info().Msg("Installed and Candidate versions match") cmdCtxLogger.Info().
Str("Installed", packageFromCommand.VersionCheck.Installed).
Msg("Package version comparison")
versionLogger := cmdCtxLogger.With().Str("package", packageFromCommand.Name).Logger()
if packageFromCommand.Version != "" {
versionLogger := cmdCtxLogger.With().Str("package", packageFromCommand.Name).Str("Specified Version", packageFromCommand.Version).Logger()
packageVersionRegex, PkgRegexErr := regexp.Compile(packageFromCommand.Version)
if PkgRegexErr != nil {
versionLogger.Error().Err(PkgRegexErr).Msg("Error compiling package version regex")
errs = append(errs, PkgRegexErr)
continue
}
if p.Version == packageFromCommand.Version {
versionLogger.Info().Msgf("Installed version matches specified version: %s", packageFromCommand.Version)
} else if packageVersionRegex.MatchString(p.VersionCheck.Installed) {
versionLogger.Info().Msgf("Installed version contains specified version: %s", packageFromCommand.Version)
} else {
versionLogger.Info().Msg("Installed version does not match specified version")
errs = append(errs, fmt.Errorf("installed version of %s does not match specified version: %s", packageFromCommand.Name, packageFromCommand.Version))
}
} else { } else {
cmdCtxLogger.Info().Msg("Installed and Candidate versions differ") if p.VersionCheck.Installed == p.VersionCheck.Candidate {
err = errors.New("Installed and Candidate versions differ") versionLogger.Info().Msg("Installed and Candidate versions match")
} else {
cmdCtxLogger.Info().Msg("Installed and Candidate versions differ")
errs = append(errs, errors.New("installed and Candidate versions differ"))
}
} }
} }
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, false), err if errs == nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil
}
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error parsing package version output: %v", errs)
} }
func getExternalConfigDirectiveValue(key string, opts *ConfigOpts) string { func getPackageIndexFromCommand(command *Command, name string) int {
for i, v := range command.Packages {
if name == v.Name {
return i
}
}
return -1
}
func getExternalConfigDirectiveValue(key string, opts *ConfigOpts, allowedDirectives AllowedExternalDirectives) string {
if !(strings.HasPrefix(key, externDirectiveStart) && strings.HasSuffix(key, externDirectiveEnd)) { if !(strings.HasPrefix(key, externDirectiveStart) && strings.HasSuffix(key, externDirectiveEnd)) {
return key return key
} }
key = replaceVarInString(opts.Vars, key, opts.Logger)
opts.Logger.Debug().Str("expanding external key", key).Send() opts.Logger.Debug().Str("expanding external key", key).Send()
if strings.HasPrefix(key, envExternDirectiveStart) { if strings.HasPrefix(key, envExternDirectiveStart) {
key = strings.TrimPrefix(key, envExternDirectiveStart) if IsExternalDirectiveEnv(allowedDirectives) {
key = strings.TrimSuffix(key, externDirectiveEnd)
key = os.Getenv(key) key = strings.TrimPrefix(key, envExternDirectiveStart)
key = strings.TrimSuffix(key, externDirectiveEnd)
key = os.Getenv(key)
} else {
opts.Logger.Error().Msgf("Config key with value %s does not support env directive", key)
}
} }
if strings.HasPrefix(key, externFileDirectiveStart) { if strings.HasPrefix(key, externFileDirectiveStart) {
var err error if IsExternalDirectiveFile(allowedDirectives) {
var keyValue []byte
key = strings.TrimPrefix(key, externFileDirectiveStart) var err error
key = strings.TrimSuffix(key, externDirectiveEnd) var keyValue []byte
key, err = getFullPathWithHomeDir(key) key = strings.TrimPrefix(key, externFileDirectiveStart)
if err != nil { key = strings.TrimSuffix(key, externDirectiveEnd)
opts.Logger.Err(err).Send() key, err = getFullPathWithHomeDir(key)
return "" if err != nil {
opts.Logger.Err(err).Send()
return ""
}
if !path.IsAbs(key) {
key = path.Join(opts.ConfigDir, key)
}
keyValue, err = os.ReadFile(key)
if err != nil {
opts.Logger.Err(err).Send()
return ""
}
key = string(keyValue)
} else {
opts.Logger.Error().Msgf("Config key with value %s does not support file directive", key)
} }
if !path.IsAbs(key) {
key = path.Join(opts.ConfigDir, key)
}
keyValue, err = os.ReadFile(key)
if err != nil {
opts.Logger.Err(err).Send()
return ""
}
key = string(keyValue)
} }
if strings.HasPrefix(key, vaultExternDirectiveStart) { if strings.HasPrefix(key, vaultExternDirectiveStart) {
key = strings.TrimPrefix(key, vaultExternDirectiveStart) if IsExternalDirectiveVault(allowedDirectives) {
key = strings.TrimSuffix(key, externDirectiveEnd)
key = GetVaultKey(key, opts, opts.Logger) key = strings.TrimPrefix(key, vaultExternDirectiveStart)
key = strings.TrimSuffix(key, externDirectiveEnd)
key = GetVaultKey(key, opts, opts.Logger)
} else {
opts.Logger.Error().Msgf("Config key with value %s does not support vault directive", key)
}
} }
return key return key
} }
func getVaultSecret(vaultClient *vault.Client, key *VaultKey) (string, error) {
var (
secret *vault.KVSecret
err error
)
if key.ValueType == "KVv2" {
secret, err = vaultClient.KVv2(key.MountPath).Get(context.Background(), key.Path)
} else if key.ValueType == "KVv1" {
secret, err = vaultClient.KVv1(key.MountPath).Get(context.Background(), key.Path)
} else if key.ValueType != "" {
return "", fmt.Errorf("type %s for key %s not known. Valid types are KVv1 or KVv2", key.ValueType, key.Name)
} else {
return "", fmt.Errorf("type for key %s must be specified. Valid types are KVv1 or KVv2", key.Name)
}
if err != nil {
return "", fmt.Errorf("unable to read secret: %v", err)
}
value, ok := secret.Data[key.Key].(string)
if !ok {
return "", fmt.Errorf("value type assertion failed for vault key %s: %T %#v", key.Name, secret.Data[key.Name], secret.Data[key.Name])
}
return value, nil
}
func getVaultKeyData(keyName string, keys []*VaultKey) (*VaultKey, error) {
for _, k := range keys {
if k.Name == keyName {
return k, nil
}
}
return nil, fmt.Errorf("key %s not found in vault keys", keyName)
}
func GetVaultKey(str string, opts *ConfigOpts, log zerolog.Logger) string {
key, err := getVaultKeyData(str, opts.VaultKeys)
if key == nil && err == nil {
return str
}
if err != nil && key == nil {
log.Err(err).Send()
return ""
}
value, secretErr := getVaultSecret(opts.vaultClient, key)
if secretErr != nil {
log.Err(secretErr).Send()
return value
}
return value
}
func IsExternalDirectiveFile(allowedExternalDirectives AllowedExternalDirectives) bool {
return strings.Contains(allowedExternalDirectives.String(), "file")
}
func IsExternalDirectiveEnv(allowedExternalDirectives AllowedExternalDirectives) bool {
return strings.Contains(allowedExternalDirectives.String(), "env")
}
func IsExternalDirectiveVault(allowedExternalDirectives AllowedExternalDirectives) bool {
return strings.Contains(allowedExternalDirectives.String(), "vault")
}

View File

@ -1,18 +1,19 @@
package apt package apt
import ( import (
"bufio"
"bytes"
"fmt" "fmt"
"regexp"
"strings" "strings"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/pkgcommon" packagemanagercommon "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/common"
) )
// AptManager implements PackageManager for systems using APT. // AptManager implements PackageManager for systems using APT.
type AptManager struct { type AptManager struct {
useAuth bool // Whether to use an authentication command useAuth bool // Whether to use an authentication command
authCommand string // The authentication command, e.g., "sudo" authCommand string // The authentication command, e.g., "sudo"
Parser pkgcommon.PackageParser Parser packagemanagercommon.PackageParser
} }
// DefaultAuthCommand is the default command used for authentication. // DefaultAuthCommand is the default command used for authentication.
@ -29,14 +30,13 @@ func NewAptManager() *AptManager {
} }
// Install returns the command and arguments for installing a package. // Install returns the command and arguments for installing a package.
func (a *AptManager) Install(pkg, version string, args []string) (string, []string) { func (a *AptManager) Install(pkgs []packagemanagercommon.Package, args []string) (string, []string) {
baseCmd := a.prependAuthCommand(DefaultPackageCommand) baseCmd := a.prependAuthCommand(DefaultPackageCommand)
baseArgs := []string{"update", "&&", baseCmd, "install", "-y"} baseArgs := []string{"update", "&&", baseCmd, "install", "-y"}
if version != "" { for _, p := range pkgs {
baseArgs = append(baseArgs, fmt.Sprintf("%s=%s", pkg, version)) baseArgs = append(baseArgs, p.Name)
} else {
baseArgs = append(baseArgs, pkg)
} }
if args != nil { if args != nil {
baseArgs = append(baseArgs, args...) baseArgs = append(baseArgs, args...)
} }
@ -44,31 +44,34 @@ func (a *AptManager) Install(pkg, version string, args []string) (string, []stri
} }
// Remove returns the command and arguments for removing a package. // Remove returns the command and arguments for removing a package.
func (a *AptManager) Remove(pkg string, args []string) (string, []string) { func (a *AptManager) Remove(pkgs []packagemanagercommon.Package, args []string) (string, []string) {
baseCmd := a.prependAuthCommand(DefaultPackageCommand) baseCmd := a.prependAuthCommand(DefaultPackageCommand)
baseArgs := []string{"remove", "-y", pkg} baseArgs := []string{"remove", "-y"}
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
if args != nil { if args != nil {
baseArgs = append(baseArgs, args...) baseArgs = append(baseArgs, args...)
} }
return baseCmd, baseArgs return baseCmd, baseArgs
} }
// Upgrade returns the command and arguments for upgrading a specific package. func (a *AptManager) Upgrade(pkgs []packagemanagercommon.Package) (string, []string) {
func (a *AptManager) Upgrade(pkg, version string) (string, []string) {
baseCmd := a.prependAuthCommand(DefaultPackageCommand) baseCmd := a.prependAuthCommand(DefaultPackageCommand)
baseArgs := []string{"update", "&&", baseCmd, "install", "--only-upgrade", "-y"} baseArgs := []string{"update", "&&", baseCmd, "install", "--only-upgrade", "-y"}
if version != "" { for _, p := range pkgs {
baseArgs = append(baseArgs, fmt.Sprintf("%s=%s", pkg, version)) baseArgs = append(baseArgs, p.Name)
} else {
baseArgs = append(baseArgs, pkg)
} }
return baseCmd, baseArgs return baseCmd, baseArgs
} }
// CheckVersion returns the command and arguments for checking the info of a specific package. func (a *AptManager) CheckVersion(pkgs []packagemanagercommon.Package) (string, []string) {
func (a *AptManager) CheckVersion(pkg, version string) (string, []string) {
baseCmd := a.prependAuthCommand("apt-cache") baseCmd := a.prependAuthCommand("apt-cache")
baseArgs := []string{"policy", pkg} baseArgs := []string{"policy"}
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
return baseCmd, baseArgs return baseCmd, baseArgs
} }
@ -81,7 +84,7 @@ func (a *AptManager) UpgradeAll() (string, []string) {
} }
// Configure applies functional options to customize the package manager. // Configure applies functional options to customize the package manager.
func (a *AptManager) Configure(options ...pkgcommon.PackageManagerOption) { func (a *AptManager) Configure(options ...packagemanagercommon.PackageManagerOption) {
for _, opt := range options { for _, opt := range options {
opt(a) opt(a)
} }
@ -106,25 +109,64 @@ func (a *AptManager) SetAuthCommand(authCommand string) {
} }
// Parse parses the apt-cache policy output to extract Installed and Candidate versions. // Parse parses the apt-cache policy output to extract Installed and Candidate versions.
func (a *AptManager) Parse(output string) (*pkgcommon.PackageVersion, error) { func (a *AptManager) ParseRemotePackageManagerVersionOutput(output string) ([]packagemanagercommon.Package, error) {
var (
packageName string
installedString string
candidateString string
countRelevantLines int
)
// Check for error message in the output // Check for error message in the output
if strings.Contains(output, "Unable to locate package") { if strings.Contains(output, "Unable to locate package") {
return nil, fmt.Errorf("error: %s", strings.TrimSpace(output)) return nil, fmt.Errorf("error: %s", strings.TrimSpace(output))
} }
packages := []packagemanagercommon.Package{}
outputBuf := bytes.NewBufferString(output)
outputScan := bufio.NewScanner(outputBuf)
var packageCount uint
for outputScan.Scan() {
line := outputScan.Text()
if !strings.HasPrefix(line, " ") && strings.HasSuffix(line, ":") {
// count++
packageName = strings.TrimSpace(strings.TrimSuffix(line, ":"))
}
if strings.Contains(line, "Installed:") {
countRelevantLines++
installedString = strings.TrimPrefix(strings.TrimSpace(line), "Installed:")
}
reInstalled := regexp.MustCompile(`Installed:\s*([^\s]+)`) if strings.Contains(line, "Candidate:") {
reCandidate := regexp.MustCompile(`Candidate:\s*([^\s]+)`) countRelevantLines++
candidateString = strings.TrimPrefix(strings.TrimSpace(line), "Candidate:")
}
installedMatch := reInstalled.FindStringSubmatch(output) if countRelevantLines == 2 {
candidateMatch := reCandidate.FindStringSubmatch(output) countRelevantLines = 0
if !strings.Contains(installedString, " (none)") {
if len(installedMatch) < 2 || len(candidateMatch) < 2 { packageCount++
return nil, fmt.Errorf("failed to parse Installed or Candidate versions from apt output. check package name") packages = append(packages, packagemanagercommon.Package{
Name: packageName,
VersionCheck: packagemanagercommon.PackageVersion{
Installed: strings.TrimSpace(installedString),
Candidate: strings.TrimSpace(candidateString),
Match: installedString == candidateString,
}},
)
}
}
} }
return &pkgcommon.PackageVersion{ if packageCount == 0 {
Installed: strings.TrimSpace(installedMatch[1]), return nil, fmt.Errorf("no packages found")
Candidate: strings.TrimSpace(candidateMatch[1]), }
Match: installedMatch[1] == candidateMatch[1],
}, nil return packages, nil
}
func SearchPackages(pkgs []string, version string) (string, []string) {
baseCommand := "dpkg-query"
baseArgs := []string{"-W", "-f='${Package}\t${Architecture}\t${db:Status-Status}\t${Version}\t${Installed-Size}\t${Binary:summary}\n'"}
baseArgs = append(baseArgs, pkgs...)
return baseCommand, baseArgs
} }

View File

@ -1,4 +1,4 @@
package pkgcommon package packagemanagercommon
// PackageManagerOption defines a functional option for configuring a PackageManager. // PackageManagerOption defines a functional option for configuring a PackageManager.
type PackageManagerOption func(interface{}) type PackageManagerOption func(interface{})
@ -15,3 +15,9 @@ type PackageVersion struct {
Match bool Match bool
Message string Message string
} }
type Package struct {
Name string `yaml:"name"`
Version string `yaml:"version,omitempty"`
VersionCheck PackageVersion
}

View File

@ -5,7 +5,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/pkgcommon" packagemanagercommon "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/common"
) )
// DnfManager implements PackageManager for systems using YUM. // DnfManager implements PackageManager for systems using YUM.
@ -26,21 +26,21 @@ func NewDnfManager() *DnfManager {
} }
// Configure applies functional options to customize the package manager. // Configure applies functional options to customize the package manager.
func (y *DnfManager) Configure(options ...pkgcommon.PackageManagerOption) { func (y *DnfManager) Configure(options ...packagemanagercommon.PackageManagerOption) {
for _, opt := range options { for _, opt := range options {
opt(y) opt(y)
} }
} }
// Install returns the command and arguments for installing a package. // Install returns the command and arguments for installing a package.
func (y *DnfManager) Install(pkg, version string, args []string) (string, []string) { func (y *DnfManager) Install(pkgs []packagemanagercommon.Package, args []string) (string, []string) {
baseCmd := y.prependAuthCommand("dnf") baseCmd := y.prependAuthCommand("dnf")
baseArgs := []string{"install", "-y"} baseArgs := []string{"install", "-y"}
if version != "" {
baseArgs = append(baseArgs, fmt.Sprintf("%s-%s", pkg, version)) for _, p := range pkgs {
} else { baseArgs = append(baseArgs, p.Name)
baseArgs = append(baseArgs, pkg)
} }
if args != nil { if args != nil {
baseArgs = append(baseArgs, args...) baseArgs = append(baseArgs, args...)
} }
@ -48,9 +48,13 @@ func (y *DnfManager) Install(pkg, version string, args []string) (string, []stri
} }
// Remove returns the command and arguments for removing a package. // Remove returns the command and arguments for removing a package.
func (y *DnfManager) Remove(pkg string, args []string) (string, []string) { func (y *DnfManager) Remove(pkgs []packagemanagercommon.Package, args []string) (string, []string) {
baseCmd := y.prependAuthCommand("dnf") baseCmd := y.prependAuthCommand("dnf")
baseArgs := []string{"remove", "-y", pkg} baseArgs := []string{"remove", "-y"}
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
if args != nil { if args != nil {
baseArgs = append(baseArgs, args...) baseArgs = append(baseArgs, args...)
} }
@ -58,34 +62,37 @@ func (y *DnfManager) Remove(pkg string, args []string) (string, []string) {
} }
// Upgrade returns the command and arguments for upgrading a specific package. // Upgrade returns the command and arguments for upgrading a specific package.
func (y *DnfManager) Upgrade(pkg, version string) (string, []string) { func (y *DnfManager) Upgrade(pkgs []packagemanagercommon.Package) (string, []string) {
baseCmd := y.prependAuthCommand("dnf") baseCmd := y.prependAuthCommand("dnf")
baseArgs := []string{"update", "-y"} baseArgs := []string{"update", "-y"}
if version != "" {
baseArgs = append(baseArgs, fmt.Sprintf("%s-%s", pkg, version)) for _, p := range pkgs {
} else { baseArgs = append(baseArgs, p.Name)
baseArgs = append(baseArgs, pkg)
} }
return baseCmd, baseArgs return baseCmd, baseArgs
} }
// UpgradeAll returns the command and arguments for upgrading all packages. // UpgradeAll returns the command and arguments for upgrading all packages.
func (y *DnfManager) UpgradeAll() (string, []string) { func (y *DnfManager) UpgradeAll() (string, []string) {
baseCmd := y.prependAuthCommand("dnf") baseCmd := y.prependAuthCommand("dnf")
baseArgs := []string{"update", "-y"} baseArgs := []string{"upgrade", "-y"}
return baseCmd, baseArgs return baseCmd, baseArgs
} }
// CheckVersion returns the command and arguments for checking the info of a specific package. // CheckVersion returns the command and arguments for checking the info of a specific package.
func (d *DnfManager) CheckVersion(pkg, version string) (string, []string) { func (d *DnfManager) CheckVersion(pkgs []packagemanagercommon.Package) (string, []string) {
baseCmd := d.prependAuthCommand("dnf") baseCmd := d.prependAuthCommand("dnf")
baseArgs := []string{"info", pkg} baseArgs := []string{"info"}
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
return baseCmd, baseArgs return baseCmd, baseArgs
} }
// Parse parses the dnf info output to extract Installed and Candidate versions. // Parse parses the dnf info output to extract Installed and Candidate versions.
func (d DnfManager) Parse(output string) (*pkgcommon.PackageVersion, error) { func (d DnfManager) ParseRemotePackageManagerVersionOutput(output string) ([]packagemanagercommon.Package, error) {
// Check for error message in the output // Check for error message in the output
if strings.Contains(output, "No matching packages to list") { if strings.Contains(output, "No matching packages to list") {
@ -114,10 +121,7 @@ func (d DnfManager) Parse(output string) (*pkgcommon.PackageVersion, error) {
return nil, fmt.Errorf("failed to parse versions from dnf output") return nil, fmt.Errorf("failed to parse versions from dnf output")
} }
return &pkgcommon.PackageVersion{ return nil, nil
Installed: installedVersion,
Candidate: candidateVersion,
}, nil
} }
// prependAuthCommand prepends the authentication command if UseAuth is true. // prependAuthCommand prepends the authentication command if UseAuth is true.

View File

@ -4,26 +4,26 @@ import (
"fmt" "fmt"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/apt" "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/apt"
packagemanagercommon "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/common"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/dnf" "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/dnf"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/pkgcommon"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/yum" "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/yum"
) )
// PackageManager is an interface used to define common package commands. This shall be implemented by every package. // PackageManager is an interface used to define common package commands. This shall be implemented by every package.
type PackageManager interface { type PackageManager interface {
Install(pkg, version string, args []string) (string, []string) Install(pkgs []packagemanagercommon.Package, args []string) (string, []string)
Remove(pkg string, args []string) (string, []string) Remove(pkgs []packagemanagercommon.Package, args []string) (string, []string)
Upgrade(pkg, version string) (string, []string) // Upgrade a specific package Upgrade(pkgs []packagemanagercommon.Package) (string, []string) // Upgrade a specific package
UpgradeAll() (string, []string) UpgradeAll() (string, []string)
CheckVersion(pkg, version string) (string, []string) CheckVersion(pkgs []packagemanagercommon.Package) (string, []string)
Parse(output string) (*pkgcommon.PackageVersion, error) ParseRemotePackageManagerVersionOutput(output string) ([]packagemanagercommon.Package, error)
// Configure applies functional options to customize the package manager. // Configure applies functional options to customize the package manager.
Configure(options ...pkgcommon.PackageManagerOption) Configure(options ...packagemanagercommon.PackageManagerOption)
} }
// PackageManagerFactory returns the appropriate PackageManager based on the package tool. // PackageManagerFactory returns the appropriate PackageManager based on the package tool.
// Takes variable number of options. // Takes variable number of options.
func PackageManagerFactory(managerType string, options ...pkgcommon.PackageManagerOption) (PackageManager, error) { func PackageManagerFactory(managerType string, options ...packagemanagercommon.PackageManagerOption) (PackageManager, error) {
var manager PackageManager var manager PackageManager
switch managerType { switch managerType {
@ -43,7 +43,7 @@ func PackageManagerFactory(managerType string, options ...pkgcommon.PackageManag
} }
// WithAuth enables authentication and sets the authentication command. // WithAuth enables authentication and sets the authentication command.
func WithAuth(authCommand string) pkgcommon.PackageManagerOption { func WithAuth(authCommand string) packagemanagercommon.PackageManagerOption {
return func(manager interface{}) { return func(manager interface{}) {
if configurable, ok := manager.(interface { if configurable, ok := manager.(interface {
SetUseAuth(bool) SetUseAuth(bool)
@ -56,7 +56,7 @@ func WithAuth(authCommand string) pkgcommon.PackageManagerOption {
} }
// WithoutAuth disables authentication. // WithoutAuth disables authentication.
func WithoutAuth() pkgcommon.PackageManagerOption { func WithoutAuth() packagemanagercommon.PackageManagerOption {
return func(manager interface{}) { return func(manager interface{}) {
if configurable, ok := manager.(interface { if configurable, ok := manager.(interface {
SetUseAuth(bool) SetUseAuth(bool)
@ -68,8 +68,8 @@ func WithoutAuth() pkgcommon.PackageManagerOption {
// ConfigurablePackageManager defines methods for setting configuration options. // ConfigurablePackageManager defines methods for setting configuration options.
type ConfigurablePackageManager interface { type ConfigurablePackageManager interface {
pkgcommon.PackageParser packagemanagercommon.PackageParser
SetUseAuth(useAuth bool) SetUseAuth(useAuth bool)
SetAuthCommand(authCommand string) SetAuthCommand(authCommand string)
SetPackageParser(parser pkgcommon.PackageParser) SetPackageParser(parser packagemanagercommon.PackageParser)
} }

View File

@ -3,8 +3,9 @@ package yum
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strings"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/pkgcommon" packagemanagercommon "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/common"
) )
// YumManager implements PackageManager for systems using YUM. // YumManager implements PackageManager for systems using YUM.
@ -25,21 +26,20 @@ func NewYumManager() *YumManager {
} }
// Configure applies functional options to customize the package manager. // Configure applies functional options to customize the package manager.
func (y *YumManager) Configure(options ...pkgcommon.PackageManagerOption) { func (y *YumManager) Configure(options ...packagemanagercommon.PackageManagerOption) {
for _, opt := range options { for _, opt := range options {
opt(y) opt(y)
} }
} }
// Install returns the command and arguments for installing a package. // Install returns the command and arguments for installing a package.
func (y *YumManager) Install(pkg, version string, args []string) (string, []string) { func (y *YumManager) Install(pkgs []packagemanagercommon.Package, args []string) (string, []string) {
baseCmd := y.prependAuthCommand("yum") baseCmd := y.prependAuthCommand("yum")
baseArgs := []string{"install", "-y"} baseArgs := []string{"install", "-y"}
if version != "" { for _, p := range pkgs {
baseArgs = append(baseArgs, fmt.Sprintf("%s-%s", pkg, version)) baseArgs = append(baseArgs, p.Name)
} else {
baseArgs = append(baseArgs, pkg)
} }
if args != nil { if args != nil {
baseArgs = append(baseArgs, args...) baseArgs = append(baseArgs, args...)
} }
@ -47,9 +47,13 @@ func (y *YumManager) Install(pkg, version string, args []string) (string, []stri
} }
// Remove returns the command and arguments for removing a package. // Remove returns the command and arguments for removing a package.
func (y *YumManager) Remove(pkg string, args []string) (string, []string) { func (y *YumManager) Remove(pkgs []packagemanagercommon.Package, args []string) (string, []string) {
baseCmd := y.prependAuthCommand("yum") baseCmd := y.prependAuthCommand("yum")
baseArgs := []string{"remove", "-y", pkg} baseArgs := []string{"remove", "-y"}
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
if args != nil { if args != nil {
baseArgs = append(baseArgs, args...) baseArgs = append(baseArgs, args...)
} }
@ -57,14 +61,13 @@ func (y *YumManager) Remove(pkg string, args []string) (string, []string) {
} }
// Upgrade returns the command and arguments for upgrading a specific package. // Upgrade returns the command and arguments for upgrading a specific package.
func (y *YumManager) Upgrade(pkg, version string) (string, []string) { func (y *YumManager) Upgrade(pkgs []packagemanagercommon.Package) (string, []string) {
baseCmd := y.prependAuthCommand("yum") baseCmd := y.prependAuthCommand("yum")
baseArgs := []string{"update", "-y"} baseArgs := []string{"update", "-y"}
if version != "" { for _, p := range pkgs {
baseArgs = append(baseArgs, fmt.Sprintf("%s-%s", pkg, version)) baseArgs = append(baseArgs, p.Name)
} else {
baseArgs = append(baseArgs, pkg)
} }
return baseCmd, baseArgs return baseCmd, baseArgs
} }
@ -76,17 +79,27 @@ func (y *YumManager) UpgradeAll() (string, []string) {
} }
// CheckVersion returns the command and arguments for checking the info of a specific package. // CheckVersion returns the command and arguments for checking the info of a specific package.
func (y *YumManager) CheckVersion(pkg, version string) (string, []string) { func (y *YumManager) CheckVersion(pkgs []packagemanagercommon.Package) (string, []string) {
baseCmd := y.prependAuthCommand("yum") baseCmd := y.prependAuthCommand("yum")
baseArgs := []string{"info", pkg} baseArgs := []string{"info"}
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
return baseCmd, baseArgs return baseCmd, baseArgs
} }
// Parse parses the dnf info output to extract Installed and Candidate versions. // Parse parses the dnf info output to extract Installed and Candidate versions.
func (y YumManager) Parse(output string) (*pkgcommon.PackageVersion, error) { func (y YumManager) ParseRemotePackageManagerVersionOutput(output string) ([]packagemanagercommon.Package, error) {
reInstalled := regexp.MustCompile(`(?m)^Installed Packages\s*Name\s*:\s*\S+\s*Version\s*:\s*([^\s]+)\s*Release\s*:\s*([^\s]+)`)
reAvailable := regexp.MustCompile(`(?m)^Available Packages\s*Name\s*:\s*\S+\s*Version\s*:\s*([^\s]+)\s*Release\s*:\s*([^\s]+)`) // Check for error message in the output
if strings.Contains(output, "No matching packages to list") {
return nil, fmt.Errorf("error: package not listed")
}
// Define regular expressions to capture installed and available versions
reInstalled := regexp.MustCompile(`(?m)^Installed packages\s*Name\s*:\s*\S+\s*Epoch\s*:\s*\S+\s*Version\s*:\s*([^\s]+)\s*Release\s*:\s*([^\s]+)`)
reAvailable := regexp.MustCompile(`(?m)^Available packages\s*Name\s*:\s*\S+\s*Epoch\s*:\s*\S+\s*Version\s*:\s*([^\s]+)\s*Release\s*:\s*([^\s]+)`)
installedMatch := reInstalled.FindStringSubmatch(output) installedMatch := reInstalled.FindStringSubmatch(output)
candidateMatch := reAvailable.FindStringSubmatch(output) candidateMatch := reAvailable.FindStringSubmatch(output)
@ -106,10 +119,7 @@ func (y YumManager) Parse(output string) (*pkgcommon.PackageVersion, error) {
return nil, fmt.Errorf("failed to parse versions from dnf output") return nil, fmt.Errorf("failed to parse versions from dnf output")
} }
return &pkgcommon.PackageVersion{ return nil, nil
Installed: installedVersion,
Candidate: candidateVersion,
}, nil
} }
// prependAuthCommand prepends the authentication command if UseAuth is true. // prependAuthCommand prepends the authentication command if UseAuth is true.

View File

@ -116,7 +116,7 @@ func (c *Cache) Set(source, hash string, data []byte, dataType string) (CacheDat
path := filepath.Join(c.dir, fmt.Sprintf("%s-%s", fileName, sourceHash)) path := filepath.Join(c.dir, fmt.Sprintf("%s-%s", fileName, sourceHash))
if _, err := os.Stat(path); os.IsNotExist(err) { if _, err := os.Stat(path); os.IsNotExist(err) {
os.MkdirAll(c.dir, 0700) _ = os.MkdirAll(c.dir, 0700)
} }
err := os.WriteFile(path, data, 0644) err := os.WriteFile(path, data, 0644)
@ -171,7 +171,7 @@ func (cf *CachedFetcher) Hash(data []byte) string {
func LoadMetadataFromFile(filePath string) ([]*CacheData, error) { func LoadMetadataFromFile(filePath string) ([]*CacheData, error) {
if _, err := os.Stat(filePath); os.IsNotExist(err) { if _, err := os.Stat(filePath); os.IsNotExist(err) {
// Create the file if it does not exist // Create the file if it does not exist
os.MkdirAll(path.Dir(filePath), 0700) _ = os.MkdirAll(path.Dir(filePath), 0700)
emptyData := []byte("[]") emptyData := []byte("[]")
err := os.WriteFile(filePath, emptyData, 0644) err := os.WriteFile(filePath, emptyData, 0644)
if err != nil { if err != nil {

View File

@ -15,7 +15,7 @@ func (l LinuxUserManager) NewLinuxManager() *LinuxUserManager {
} }
// AddUser adds a new user to the system. // AddUser adds a new user to the system.
func (l LinuxUserManager) AddUser(username, homeDir, shell string, isSystem bool, groups, args []string) (string, []string) { func (l LinuxUserManager) AddUser(username, homeDir, shell string, isSystem, createHome bool, groups, args []string) (string, []string) {
baseArgs := []string{} baseArgs := []string{}
if isSystem { if isSystem {
@ -38,6 +38,10 @@ func (l LinuxUserManager) AddUser(username, homeDir, shell string, isSystem bool
baseArgs = append(baseArgs, args...) baseArgs = append(baseArgs, args...)
} }
if createHome {
baseArgs = append(baseArgs, "-m")
}
args = append(baseArgs, username) args = append(baseArgs, username)
cmd := "useradd" cmd := "useradd"

View File

@ -10,7 +10,7 @@ import (
// UserManager defines the interface for user management operations. // UserManager defines the interface for user management operations.
// All functions but one return a string for the command and any args. // All functions but one return a string for the command and any args.
type UserManager interface { type UserManager interface {
AddUser(username, homeDir, shell string, isSystem bool, groups, args []string) (string, []string) AddUser(username, homeDir, shell string, createHome, isSystem bool, groups, args []string) (string, []string)
RemoveUser(username string) (string, []string) RemoveUser(username string) (string, []string)
ModifyUser(username, homeDir, shell string, groups []string) (string, []string) ModifyUser(username, homeDir, shell string, groups []string) (string, []string)
// Modify password uses chpasswd for Linux systems to build the command to change the password // Modify password uses chpasswd for Linux systems to build the command to change the password

View File

@ -1,5 +1,6 @@
#!/bin/bash #!/bin/bash
set -eou pipefail set -eou pipefail
go mod tidy
go generate ./... go generate ./...
CURRENT_TAG="$(go run backy.go version -V)" CURRENT_TAG="$(go run backy.go version -V)"
goreleaser -f .goreleaser/github.yml check goreleaser -f .goreleaser/github.yml check

View File

@ -2,10 +2,18 @@ commands:
echoTestFail: echoTestFail:
cmd: ech cmd: ech
shell: bash shell: bash
Args: hello world Args:
- hello world
hooks: hooks:
error: error:
- errorCmd - errorCmd
final:
- finalCmd
finalCmd:
cmd: echo
Args:
- "echo test fail"
errorCmd: errorCmd:
name: get docker version name: get docker version
@ -14,4 +22,10 @@ commands:
outputToLog: true outputToLog: true
Args: Args:
- "-v" - "-v"
host: email-svr
cmdLists:
TestHooks:
output:
onFailure: true
order:
- echoTestFail

27
tests/VaultTest.yml Normal file
View File

@ -0,0 +1,27 @@
commands:
vaultEnvVar:
cmd: echo
shell: /bin/zsh
Args:
- ${VAULT_VAR}
environment:
"VAULT_VAR=%{vault:vaultTestSecret}%"
logging:
verbose: true
vault:
token: root
address: http://127.0.0.1:8200
enabled: true
keys:
- name: vaultTestSecret
key: data
mountpath: secret
path: test/var
type: KVv2 # KVv1 or KVv2
cmdLists:
addUsers:
order:
- vaultEnvVar

26
tests/docker/Dockerfile Normal file
View File

@ -0,0 +1,26 @@
FROM ubuntu:latest
# Install SSH server
RUN apt-get update && \
apt-get install -y openssh-server && \
apt-get clean
RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config
RUN useradd -m -s /bin/bash backy
RUN echo "backy:backy" | chpasswd
RUN echo "root:test" | chpasswd
COPY --chown=backy:backy backytest.pub /home/backy/.ssh/authorized_keys
COPY --chown=root:root backytest.pub /root/.ssh/authorized_keys
EXPOSE 22
RUN mkdir /var/run/sshd
RUN chmod 0755 /var/run/sshd
RUN apt-get update && apt-get install -y curl
# Start SSH service
CMD ["/usr/sbin/sshd", "-D"]
# ENTRYPOINT service ssh start && bash

7
tests/docker/backytest Normal file
View File

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBtBASN+HMx/EVUIs5ThK9Nlw0wPFVt2rXsiNZlDN+CswAAAKAfFc5AHxXO
QAAAAAtzc2gtZWQyNTUxOQAAACBtBASN+HMx/EVUIs5ThK9Nlw0wPFVt2rXsiNZlDN+Csw
AAAEAxs6uRkenVbXPrjgbIv/1THXL6dUdgr5KaCd7uBVm0PW0EBI34czH8RVQizlOEr02X
DTA8VW3ateyI1mUM34KzAAAAGU1lZGlhIHVzZXIgc3RvcmFnZSBzZXJ2ZXIBAgME
-----END OPENSSH PRIVATE KEY-----

View File

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG0EBI34czH8RVQizlOEr02XDTA8VW3ateyI1mUM34Kz Backy test

4
tests/docker/buildDocker.sh Executable file
View File

@ -0,0 +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

6
tests/hosts.yml Normal file
View File

@ -0,0 +1,6 @@
hosts:
docker:
port: 2222
Hostname: localhost
user: root
IdentityFile: ./docker/backytest

89
tests/packageCommands.yml Normal file
View File

@ -0,0 +1,89 @@
commands:
checkDockerNoVersion:
type: package
shell: zsh
packages:
- name: "docker-ce-cli"
- name: "docker-ce"
packageManager: apt
packageOperation: checkVersion
checkAptNoVersion:
type: package
shell: zsh
packages:
- name: "apt"
packageManager: apt
packageOperation: checkVersion
checkDockerPartialVersionWithoutRegex:
type: package
shell: zsh
packages:
- name: "docker-ce-cli"
version: "5:28.0.4-1"
- name: "docker-ce"
version: "5:28.0.4-1"
packageManager: apt
packageOperation: checkVersion
checkDockerPartialVersionWithRegex:
type: package
shell: zsh
packages:
- name: "docker-ce-cli"
version: "5:28.0-*"
- name: "docker-ce"
version: '5:28\.0\.4-1~([A-Za-z0-9]+(\.[A-Za-z0-9]+)+)*'
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
packages:
- name: "docker-ce-cli"
version: "5:28.0-**"
- name: "docker-ce"
version: '5:28\.0\K.4-1~([A-Za-z0-9]+(\.[A-Za-z0-9]+)+)*'
packageManager: apt
packageOperation: checkVersion
cmdLists:
packageCommands:
output:
onFailure: true
order:
- checkDockerPartialVersionWithoutRegex
- checkDockerPartialVersionWithRegex
- checkDockerVersionWithInvalidRegex
- checkDockerNoVersion
aptCommands:
output:
onFailure: true
order:
- installJq
logging:
verbose: true
cmd-std-out: true

View File

@ -0,0 +1,52 @@
commands:
checkDockerNoVersion:
type: package
shell: zsh
packages:
- name: "docker-ce-cli"
- name: "docker-ce"
packageManager: dnf
packageOperation: checkVersion
checkDockerPartialVersionWithoutRegex:
type: package
shell: zsh
packages:
- name: "docker-ce-cli"
version: "5:28.0.4-1"
- name: "docker-ce"
version: "5:28.0.4-1"
packageManager: dnf
packageOperation: checkVersion
checkDockerPartialVersionWithRegex:
type: package
shell: zsh
packages:
- name: "docker-ce-cli"
version: "5:28.0-*"
- name: "docker-ce"
version: '5:28\.0\.4-1~([A-Za-z0-9]+(\.[A-Za-z0-9]+)+)*'
packageManager: dnf
packageOperation: checkVersion
checkDockerVersionWithInvalidRegex:
type: package
shell: zsh
packages:
- name: "docker-ce-cli"
version: "5:28.0-**"
- name: "docker-ce"
version: '5:28\.0\K.4-1~([A-Za-z0-9]+(\.[A-Za-z0-9]+)+)*'
packageManager: dnf
packageOperation: checkVersion
cmdLists:
packageCommands:
output:
onFailure: true
order:
- checkDockerPartialVersionWithoutRegex
- checkDockerPartialVersionWithRegex
- checkDockerVersionWithInvalidRegex
- checkDockerNoVersion