v0.8.1 WIP

This commit is contained in:
Andrew Woodlee 2025-02-23 15:33:17 -06:00
parent 1ad50ebcf8
commit 98d8b8e8f2
14 changed files with 200 additions and 61 deletions

View File

@ -14,20 +14,20 @@ import (
var ( var (
listCmd = &cobra.Command{ listCmd = &cobra.Command{
Use: "list [command]", Use: "list [command]",
Short: "Lists commands, lists, or hosts defined in config file.", Short: "List commands, lists, or hosts defined in config file.",
Long: "Backup lists commands or groups defined in config file.\nUse the --lists or -l flag to list the specified lists. If not flag is not given, all lists will be executed.", Long: "List commands, lists, or hosts defined in config file",
} }
listCmds = &cobra.Command{ listCmds = &cobra.Command{
Use: "cmds [cmd1 cmd2 cmd3...]", Use: "cmds [cmd1 cmd2 cmd3...]",
Short: "Lists commands, lists, or hosts defined in config file.", Short: "List commands defined in config file.",
Long: "Backup lists commands or groups defined in config file.\nUse the --lists or -l flag to list the specified lists. If not flag is not given, all lists will be executed.", Long: "List commands defined in config file",
Run: ListCmds, Run: ListCmds,
} }
listCmdLists = &cobra.Command{ listCmdLists = &cobra.Command{
Use: "lists [list1 list2 ...]", Use: "lists [list1 list2 ...]",
Short: "Lists commands, lists, or hosts defined in config file.", Short: "List lists defined in config file.",
Long: "Backup lists commands or groups defined in config file.\nUse the --lists or -l flag to list the specified lists. If not flag is not given, all lists will be executed.", Long: "List lists defined in config file",
Run: ListCmdLists, Run: ListCmdLists,
} }
) )

View File

@ -19,10 +19,11 @@ Available Commands:
cron Starts a scheduler that runs lists defined in config file. cron Starts a scheduler that runs lists defined in config file.
exec Runs commands defined in config file in order given. exec Runs commands defined in config file in order given.
help Help about any command help Help about any command
list Lists commands, lists, or hosts defined in config file. list List commands, lists, or hosts defined in config file.
version Prints the version and exits version Prints the version and exits
Flags: Flags:
--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 --log-file string log file to write to
@ -48,6 +49,7 @@ Flags:
-l, --lists stringArray Accepts comma-separated names of command lists to execute. -l, --lists stringArray Accepts comma-separated names of command lists to execute.
Global Flags: Global Flags:
--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 --log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable. --s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
@ -66,6 +68,7 @@ Flags:
-h, --help help for cron -h, --help help for cron
Global Flags: Global Flags:
--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 --log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable. --s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
@ -88,6 +91,7 @@ Flags:
-h, --help help for exec -h, --help help for exec
Global Flags: Global Flags:
--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 --log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable. --s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
@ -111,6 +115,7 @@ Flags:
-m, --hosts stringArray Accepts space-separated names of hosts. Specify multiple times for multiple hosts. -m, --hosts stringArray Accepts space-separated names of hosts. Specify multiple times for multiple hosts.
Global Flags: Global Flags:
--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 --log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable. --s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
@ -131,6 +136,7 @@ Flags:
-V, --vpre Output the version with v prefixed. -V, --vpre Output the version with v prefixed.
Global Flags: Global Flags:
--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 --log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable. --s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
@ -140,20 +146,24 @@ Global Flags:
## list ## list
``` ```
Backup lists commands or groups defined in config file. List commands, lists, or hosts defined in config file
Use the --lists or -l flag to list the specified lists. If not flag is not given, all lists will be executed.
Usage: Usage:
backy list [--list=list1,list2,... | -l list1, list2,...] [ -cmd cmd1 cmd2 cmd3...] [flags] backy list [command]
Available Commands:
cmds List commands defined in config file.
lists List lists defined in config file.
Flags: Flags:
-c, --cmds strings Accepts comma-separated names of commands to list. -h, --help help for list
-h, --help help for list
-l, --lists strings Accepts comma-separated names of command lists to list.
Global Flags: Global Flags:
--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 --log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable. --s3-endpoint 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.
``` ```

View File

@ -7,13 +7,13 @@ The `exec` subcommand can do some things that the configuration file can't do ye
`exec host` takes the following arguments: `exec host` takes the following arguments:
```sh ```sh
-c, --commands strings Accepts comma-separated names of commands. -c, --commands strings Accepts space-separated names of commands.
-h, --help help for host -h, --help help for host
-m, --hosts strings Accepts comma-separated names of hosts. -m, --hosts strings Accepts space-separated names of hosts.
``` ```
The commands have to be defined in the config file. The hosts need to at least be in the ssh_config(5) file. The commands have to be defined in the config file. The hosts need to at least be in the ssh_config(5) file.
```sh ```sh
backy exec host [--commands=command1,command2, ... | -c command1,command2, ...] [--hosts=host1,hosts2, ... | -m host1,host2, ...] [flags] backy exec host [--commands command1 -commands command2 ... | -c command1 -c command2 ...] [--hosts host1 --hosts hosts2 ... | -m host1 -c host2 ...] [flags]
``` ```

6
go.mod
View File

@ -8,6 +8,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.76.0
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/hashicorp/vault/api v1.15.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
@ -69,6 +70,7 @@ require (
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/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // 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
@ -83,9 +85,11 @@ require (
go.mau.fi/util v0.8.4 // indirect go.mau.fi/util v0.8.4 // 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 golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 // indirect
golang.org/x/net v0.34.0 // indirect golang.org/x/mod v0.23.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sync v0.11.0 // indirect golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.10.0 // indirect golang.org/x/time v0.10.0 // indirect
golang.org/x/tools v0.30.0 // indirect
) )

12
go.sum
View File

@ -30,6 +30,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
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/dmarkham/enumer v1.5.11 h1:quorLCaEfzjJ23Pf7PB9lyyaHseh91YfTM/sAD/4Mbo=
github.com/dmarkham/enumer v1.5.11/go.mod h1:yixql+kDDQRYqcuBM2n9Vlt7NoT9ixgXhaXry8vmRg8=
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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
@ -128,6 +130,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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/pascaldekloe/name v1.0.0 h1:n7LKFgHixETzxpRv2R77YgPUFo85QHGZKrdaYm7eY5U=
github.com/pascaldekloe/name v1.0.0/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM=
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=
@ -184,8 +188,10 @@ golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
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/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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=
@ -199,6 +205,8 @@ golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
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/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
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=

View File

@ -47,22 +47,19 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
outputArr []string // holds the output strings returned by processes outputArr []string // holds the output strings returned by processes
) )
// Get the command type // Getting the command type must be done before concatenating the arguments
// This must be done before concatenating the arguments command = getCommandTypeAndSetCommandInfo(command)
command = getCommandType(command)
for _, v := range command.Args { for _, v := range command.Args {
ArgsStr += fmt.Sprintf(" %s", v) ArgsStr += fmt.Sprintf(" %s", v)
} }
// print the user's password if it is updated if command.Type == User {
if command.Type == "user" {
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")
} }
} }
// is host defined
if command.Host != nil { if command.Host != nil {
outputArr, errSSH = command.RunCmdSSH(cmdCtxLogger, opts) outputArr, errSSH = command.RunCmdSSH(cmdCtxLogger, opts)
if errSSH != nil { if errSSH != nil {
@ -71,7 +68,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
} else { } else {
// Handle package operations // Handle package operations
if command.Type == "package" && command.PackageOperation == "checkVersion" { if command.Type == Package && command.PackageOperation == "checkVersion" {
cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Checking package versions") cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Checking package versions")
// Execute the package version command // Execute the package version command
@ -88,6 +85,64 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
} }
var localCMD *exec.Cmd var localCMD *exec.Cmd
if command.Type == RemoteScript {
script, err := command.Fetcher.Fetch(command.Cmd)
if err != nil {
return nil, err
}
if command.Shell == "" {
command.Shell = "sh"
}
localCMD = exec.Command(command.Shell, command.Args...)
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger)
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
if IsCmdStdOutEnabled() {
cmdOutWriters = io.MultiWriter(os.Stdout, &cmdOutBuf)
}
if command.OutputFile != "" {
file, err := os.Create(command.OutputFile)
if err != nil {
return nil, fmt.Errorf("error creating output file: %w", err)
}
defer file.Close()
cmdOutWriters = io.MultiWriter(file, &cmdOutBuf)
if IsCmdStdOutEnabled() {
cmdOutWriters = io.MultiWriter(os.Stdout, file, &cmdOutBuf)
}
}
localCMD.Stdin = bytes.NewReader(script)
localCMD.Stdout = cmdOutWriters
localCMD.Stderr = cmdOutWriters
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running remoteScript %s on local machine in %s", command.Cmd, command.Shell)).Send()
err = localCMD.Run()
if err != nil {
return nil, fmt.Errorf("error running remote script: %w", err)
}
outScanner := bufio.NewScanner(&cmdOutBuf)
for outScanner.Scan() {
outMap := make(map[string]interface{})
outMap["cmd"] = command.Cmd
outMap["output"] = outScanner.Text()
if str, ok := outMap["output"].(string); ok {
outputArr = append(outputArr, str)
}
if command.OutputToLog {
cmdCtxLogger.Info().Fields(outMap).Send()
}
}
return outputArr, nil
}
var err error var err error
if command.Shell != "" { if command.Shell != "" {
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine in %s", command.Name, command.Shell)).Send() cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine in %s", command.Name, command.Shell)).Send()
@ -101,7 +156,7 @@ 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 == "package" { if command.Type == Package {
cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Executing package command") cmdCtxLogger.Info().Str("package", command.PackageName).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)

View File

@ -578,7 +578,7 @@ func processCmds(opts *ConfigOpts) error {
} }
// Parse package commands // Parse package commands
if cmd.Type == "package" { if cmd.Type == Package {
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.PackageName)
} }
@ -603,7 +603,7 @@ func processCmds(opts *ConfigOpts) error {
} }
// Parse user commands // Parse user commands
if cmd.Type == "user" { if cmd.Type == User {
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)
} }
@ -630,12 +630,24 @@ func processCmds(opts *ConfigOpts) error {
} }
if cmd.Type == "remoteScript" { if cmd.Type == RemoteScript {
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)
} }
cmd.Fetcher, fetchErr = remotefetcher.NewRemoteFetcher(cmd.Cmd, opts.Cache, remotefetcher.WithFileType("script"))
if fetchErr != nil {
return fmt.Errorf("error initializing remote fetcher for remoteScript: %v", fetchErr)
}
} }
if cmd.OutputFile != "" {
var err error
cmd.OutputFile, err = resolveDir(cmd.OutputFile)
if err != nil {
return err
}
}
} }
return nil return nil
} }

View File

@ -64,6 +64,11 @@ func (opts *ConfigOpts) ListCommand(cmd string) {
println() println()
} }
if cmdInfo.Type.String() != "" {
print("Type: ", cmdInfo.Type.String())
println()
}
} else { } else {
fmt.Printf("Command %s not found. Check spelling.\n", cmd) fmt.Printf("Command %s not found. Check spelling.\n", cmd)

View File

@ -252,7 +252,6 @@ func (remoteHost *Host) GetPort() {
// port specifed? // port specifed?
// port will be 0 if missing from backy config // port will be 0 if missing from backy config
if port == "0" { if port == "0" {
// get port from specified SSH config file
port, _ = remoteHost.SSHConfigFile.SshConfigFile.Get(remoteHost.Host, "Port") port, _ = remoteHost.SSHConfigFile.SshConfigFile.Get(remoteHost.Host, "Port")
if port == "" { if port == "" {
@ -315,7 +314,6 @@ func (remoteHost *Host) ConnectThroughBastion(log zerolog.Logger) (*ssh.Client,
return nil, err return nil, err
} }
// sClient is an ssh client connected to the service host, through the bastion host.
sClient := ssh.NewClient(ncc, chans, reqs) sClient := ssh.NewClient(ncc, chans, reqs)
return sClient, nil return sClient, nil
@ -480,7 +478,6 @@ func (remoteConfig *Host) GetProxyJumpConfig(hosts map[string]*Host, opts *Confi
return nil return nil
} }
// RunCmdSSH runs commands over SSH.
func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([]string, error) { func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([]string, error) {
var ( var (
ArgsStr string ArgsStr string
@ -492,10 +489,8 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
env: command.Environment, env: command.Environment,
} }
) )
// Get the command type // Getting the command type must be done before concatenating the arguments
// This must be done before concatenating the arguments command = getCommandTypeAndSetCommandInfo(command)
command.Type = strings.TrimSpace(command.Type)
command = getCommandType(command)
// Prepare command arguments // Prepare command arguments
for _, v := range command.Args { for _, v := range command.Args {
@ -505,7 +500,7 @@ 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", getCommandTypeLabel(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()
@ -536,11 +531,13 @@ 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 "script": case Script:
return command.runScript(commandSession, cmdCtxLogger, &cmdOutBuf) return command.runScript(commandSession, cmdCtxLogger, &cmdOutBuf)
case "scriptFile": case RemoteScript:
return command.runRemoteScript(commandSession, cmdCtxLogger, &cmdOutBuf)
case ScriptFile:
return command.runScriptFile(commandSession, cmdCtxLogger, &cmdOutBuf) return command.runScriptFile(commandSession, cmdCtxLogger, &cmdOutBuf)
case "package": case Package:
if command.PackageOperation == "checkVersion" { if command.PackageOperation == "checkVersion" {
commandSession.Stderr = nil commandSession.Stderr = nil
// Execute the package version command remotely // Execute the package version command remotely
@ -558,7 +555,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, true), fmt.Errorf("error running command: %w", err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error running command: %w", err)
} }
} }
default: default:
@ -570,11 +567,11 @@ 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.GetOutput), fmt.Errorf("error running command: %w", err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error running command: %w", err)
} }
} }
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, true), nil return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), 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) {
@ -593,17 +590,17 @@ 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.GetOutput), fmt.Errorf("error: package %s not listed: %w", command.PackageName, err) 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.GetOutput), fmt.Errorf("error running %s: %w", ArgsStr, err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error running %s: %w", ArgsStr, err)
} }
return parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf) return parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf)
} }
// getCommandTypeLabel returns a human-readable label for the command type. // getCommandTypeAndSetCommandInfoLabel returns a human-readable label for the command type.
func getCommandTypeLabel(commandType string) string { func getCommandTypeAndSetCommandInfoLabel(commandType CommandType) string {
if commandType == "" { if !commandType.IsACommandType() {
return "command" return "command"
} }
return fmt.Sprintf("%s command", commandType) return fmt.Sprintf("%s command", commandType)
@ -644,7 +641,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, true), nil return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.OutputToLog), nil
} }
// prepareScriptBuffer prepares a buffer for inline scripts. // prepareScriptBuffer prepares a buffer for inline scripts.
@ -688,6 +685,25 @@ func (command *Command) prepareScriptFileBuffer() (*bytes.Buffer, error) {
return &buffer, nil return &buffer, nil
} }
// runRemoteScript handles the execution of remote scripts
func (command *Command) runRemoteScript(session *ssh.Session, cmdCtxLogger zerolog.Logger, outputBuf *bytes.Buffer) ([]string, error) {
script, err := command.Fetcher.Fetch(command.Cmd)
if err != nil {
return nil, err
}
if command.Shell == "" {
command.Shell = "sh"
}
session.Stdin = bytes.NewReader(script)
err = session.Run(command.Shell)
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.OutputToLog), nil
}
// readFileToBuffer reads a file into a buffer. // readFileToBuffer reads a file into a buffer.
func readFileToBuffer(filePath string) (*bytes.Buffer, error) { func readFileToBuffer(filePath string) (*bytes.Buffer, error) {
resolvedPath, err := resolveDir(filePath) resolvedPath, err := resolveDir(filePath)

View File

@ -54,9 +54,8 @@ type (
// command to run // command to run
Cmd string `yaml:"cmd"` Cmd string `yaml:"cmd"`
// Possible values: script, scriptFile // See CommandType enum further down the page for acceptable values
// If blank, it is regular command. Type CommandType `yaml:"type,omitempty"`
Type string `yaml:"type,omitempty"`
// host on which to run cmd // host on which to run cmd
Host *string `yaml:"host,omitempty"` Host *string `yaml:"host,omitempty"`
@ -93,6 +92,10 @@ type (
ScriptEnvFile string `yaml:"scriptEnvFile"` ScriptEnvFile string `yaml:"scriptEnvFile"`
OutputToLog bool `yaml:"outputToLog,omitempty"`
OutputFile string `yaml:"outputFile,omitempty"`
// BEGIN PACKAGE COMMAND FIELDS // BEGIN PACKAGE COMMAND FIELDS
PackageManager string `yaml:"packageManager,omitempty"` PackageManager string `yaml:"packageManager,omitempty"`
@ -116,6 +119,8 @@ type (
// FetchBeforeExecution determines if the remoteSource should be fetched before running // FetchBeforeExecution determines if the remoteSource should be fetched before running
FetchBeforeExecution bool `yaml:"fetchBeforeExecution,omitempty"` FetchBeforeExecution bool `yaml:"fetchBeforeExecution,omitempty"`
Fetcher remotefetcher.RemoteFetcher
// BEGIN USER COMMAND FIELDS // BEGIN USER COMMAND FIELDS
// Username specifies the username for user creation or related operations // Username specifies the username for user creation or related operations
@ -289,4 +294,15 @@ type (
ListName string // Name of the command list ListName string // Name of the command list
Error error // Error encountered, if any Error error // Error encountered, if any
} }
CommandType int
)
//go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=CommandType
const (
Default CommandType = iota //
Script // script
ScriptFile // scriptFile
RemoteScript // remoteScript
Package // package
User // user
) )

View File

@ -251,12 +251,12 @@ func expandEnvVars(backyEnv map[string]string, envVars []string) {
} }
} }
// getCommandType checks for command type and if the command has already been set // getCommandTypeAndSetCommandInfo checks for command type and if the command has already been set
// Checks for types package and user // Checks for types package and user
// Returns the modified Command with the package- or userManager command as Cmd and the package- or userOperation as args, plus any additional Args // Returns the modified Command with the package- or userManager command as Cmd and the package- or userOperation as args, plus any additional Args
func getCommandType(command *Command) *Command { func getCommandTypeAndSetCommandInfo(command *Command) *Command {
if command.Type == "package" && !command.packageCmdSet { if command.Type == Package && !command.packageCmdSet {
command.packageCmdSet = true command.packageCmdSet = true
switch command.PackageOperation { switch command.PackageOperation {
case "install": case "install":
@ -270,7 +270,7 @@ func getCommandType(command *Command) *Command {
} }
} }
if command.Type == "user" && !command.userCmdSet { if command.Type == User && !command.userCmdSet {
command.userCmdSet = true command.userCmdSet = true
switch command.UserOperation { switch command.UserOperation {
case "add": case "add":

View File

@ -16,6 +16,7 @@ type CacheData struct {
Hash string `yaml:"hash"` Hash string `yaml:"hash"`
Path string `yaml:"path"` Path string `yaml:"path"`
Type string `yaml:"type"` Type string `yaml:"type"`
URL string `yaml:"url"`
} }
type Cache struct { type Cache struct {
@ -101,13 +102,17 @@ func (c *Cache) AddDataToStore(hash string, cacheData CacheData) error {
return c.saveToFile() return c.saveToFile()
} }
// Set stores data on disk and in the cache file and returns the cache data
// The filepath of the data is the file name + a SHA256 hash of the URL
func (c *Cache) Set(source, hash string, data []byte, dataType string) (CacheData, error) { func (c *Cache) Set(source, hash string, data []byte, dataType string) (CacheData, error) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
sourceHash := HashURL(source)
fileName := filepath.Base(source) fileName := filepath.Base(source)
path := filepath.Join(c.dir, fmt.Sprintf("%s-%s", fileName, hash)) 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)
@ -122,9 +127,10 @@ func (c *Cache) Set(source, hash string, data []byte, dataType string) (CacheDat
Hash: hash, Hash: hash,
Path: path, Path: path,
Type: dataType, Type: dataType,
URL: sourceHash,
} }
c.store[hash] = cacheData c.store[sourceHash] = cacheData
// Unlock before calling saveToFile to avoid double-locking // Unlock before calling saveToFile to avoid double-locking
c.mu.Unlock() c.mu.Unlock()
@ -184,3 +190,8 @@ func LoadMetadataFromFile(filePath string) ([]*CacheData, error) {
return cacheData, nil return cacheData, nil
} }
func HashURL(url string) string {
hash := sha256.Sum256([]byte(url))
return hex.EncodeToString(hash[:])
}

View File

@ -57,11 +57,12 @@ func NewRemoteFetcher(source string, cache *Cache, options ...FetcherOption) (Re
return nil, err return nil, err
} }
hash := fetcher.Hash(data) URLHash := HashURL(source)
if cachedData, cacheMeta, exists := cache.Get(hash); exists { if cachedData, cacheMeta, exists := cache.Get(URLHash); exists {
return &CachedFetcher{data: cachedData, path: cacheMeta.Path, dataType: cacheMeta.Type}, nil return &CachedFetcher{data: cachedData, path: cacheMeta.Path, dataType: cacheMeta.Type}, nil
} }
hash := fetcher.Hash(data)
cacheData, err := cache.Set(source, hash, data, config.FileType) cacheData, err := cache.Set(source, hash, data, config.FileType)
if err != nil { if err != nil {
return nil, err return nil, err

View File

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