Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
01efeab13f | |||
9a663f4260 | |||
7c635c36e0 | |||
fff33849da | |||
3391ffa4e6 | |||
b7d1be495e | |||
2daf2f130d | |||
d120c2ca8f | |||
02fd04930d | |||
10b0abe707 | |||
291a954e9c | |||
e43eecf383 | |||
ea676fe0da | |||
e73bd9ff3b | |||
fd9426181f | |||
c25edc5d78 | |||
aebfbda64e | |||
5fe0629b0f | |||
7d6acd77b5 | |||
9d646297c7 | |||
bf8d261cf3 | |||
686cd0019a | |||
b7b002bd72 | |||
b8a63f39f5 | |||
feacb83274 | |||
2aeb435167 | |||
51f5084dd0 | |||
cf04e4456a | |||
25dc6225b3 | |||
a300f696d3 | |||
8161aaa0a9 | |||
a6bfabe22f | |||
a5466fc121 | |||
fbf2d9cbbc |
9
.changes/v0.5.0.md
Normal file
9
.changes/v0.5.0.md
Normal file
@ -0,0 +1,9 @@
|
||||
## v0.5.0 - 2024-11-19
|
||||
### Added
|
||||
* Lists can now go in a file. See docs for more information.
|
||||
* commands.[name].type: script now opens `scriptEnvFile`.
|
||||
* Hooks for Commands.[name]. Error, success, and final. [#12]
|
||||
### Changed
|
||||
* GetKnownHosts is now a method of Host
|
||||
### Fixed
|
||||
* make command logger be used for errors, not just when running the command
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
- run: git fetch --force --tags
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.26'
|
||||
go-version: '1.20'
|
||||
cache: true
|
||||
# More assembly might be required: Docker logins, GPG, etc. It all depends
|
||||
# on your needs.
|
||||
|
@ -1,3 +1,4 @@
|
||||
version: 2
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
@ -31,9 +32,9 @@ archives:
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}-next"
|
||||
version_template: "{{ incpatch .Version }}-next"
|
||||
changelog:
|
||||
skip: false
|
||||
disable: false
|
||||
|
||||
gitea_urls:
|
||||
api: https://git.andrewnw.xyz/api/v1
|
||||
|
@ -1,5 +1,6 @@
|
||||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
version: 2
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
@ -32,7 +33,7 @@ archives:
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}-next"
|
||||
version_template: "{{ incpatch .Version }}-next"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
|
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@ -1,5 +1,12 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"Cmds"
|
||||
"Cmds",
|
||||
"knadh",
|
||||
"koanf",
|
||||
"mattn",
|
||||
"maunium",
|
||||
"mautrix",
|
||||
"nikoksr",
|
||||
"Strs"
|
||||
]
|
||||
}
|
@ -7,4 +7,6 @@ steps:
|
||||
when:
|
||||
event: tag
|
||||
|
||||
branches: master
|
||||
when:
|
||||
- event: tag
|
||||
branch: master
|
@ -7,4 +7,8 @@ steps:
|
||||
release:
|
||||
image: golangci/golangci-lint:v1.53.3
|
||||
commands:
|
||||
- golangci-lint run -v
|
||||
- golangci-lint run -v --timeout 5m
|
||||
|
||||
when:
|
||||
- event: push
|
||||
branch: develop
|
@ -5,12 +5,15 @@ steps:
|
||||
- git submodule foreach 'git fetch origin; git checkout $(git describe --tags `git rev-list --tags --max-count=1`);'
|
||||
- cd docs
|
||||
- hugo
|
||||
when:
|
||||
- event: push
|
||||
branch: master
|
||||
path: "docs/*"
|
||||
|
||||
deploy:
|
||||
image: codingkoopa/git-rsync-openssh
|
||||
commands:
|
||||
- cd docs
|
||||
- echo "151.101.210.132 deb.debian.org" >> /etc/hosts
|
||||
- echo "nameserver 1.1.1.1" > /etc/resolv.conf
|
||||
- mkdir ~/.ssh && chmod -R 700 ~/.ssh
|
||||
# - apt update -y && apt install openssh-client rsync -y
|
||||
@ -24,7 +27,7 @@ steps:
|
||||
- rsync -atv --delete --progress vangen/ backy@backy.cybershell.xyz:vangen-go
|
||||
secrets: [ ssh_host_key, ssh_deploy_key, ssh_passphrase ]
|
||||
|
||||
|
||||
branches: master
|
||||
when:
|
||||
- event: push
|
||||
branch: master
|
||||
path: "docs/*"
|
24
CHANGELOG.md
24
CHANGELOG.md
@ -6,6 +6,30 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||
|
||||
|
||||
## v0.5.0 - 2024-11-19
|
||||
### Added
|
||||
* Lists can now go in a file. See docs for more information.
|
||||
* commands.[name].type: script now opens `scriptEnvFile`.
|
||||
* Hooks for Commands.[name]. Error, success, and final. [#12]
|
||||
### Changed
|
||||
* GetKnownHosts is now a method of Host
|
||||
### Fixed
|
||||
* make command logger be used for errors, not just when running the command
|
||||
|
||||
## v0.4.0 - 2023-09-08
|
||||
|
||||
### Added
|
||||
|
||||
* Added `scriptEnvFile` to command object that allows one to specify an environment file (or any file really) when a `scriptFile` is run. Inspired by the practice of keeping environment variables and scripts or commands seperate.
|
||||
* Basis for listing commands
|
||||
|
||||
### Changed
|
||||
|
||||
* BREAKING: Notifications object now takes the form of service.id, where service can be "mail" or "matrix" and id is a unique id for the service.
|
||||
* BREAKING: Since the change to the notifications object, cmd-lists' inner map key 'notifications' must be of the form service.id. id must be defined for that service. See notifications docs for aviliable services.
|
||||
* Config parser is now the simpler Koanf - Keys are now case-sensitive
|
||||
* Log size limited to 50 Mb
|
||||
|
||||
## v0.3.1 - 2023-07-20
|
||||
### Changed
|
||||
* If an SSH session failed to be created, the command would fail. This would be caused when restarting the SSH host. The SSH connection is attempted to be created again. If successful, the command is executed normally.
|
||||
|
@ -20,7 +20,7 @@ package cmd
|
||||
|
||||
// func config(cmd *cobra.Command, args []string) {
|
||||
|
||||
// opts := backy.NewOpts(cfgFile, backy.UseCron())
|
||||
// opts := backy.NewOpts(cfgFile, backy.cronEnabled())
|
||||
// opts.InitConfig()
|
||||
|
||||
// }
|
||||
|
@ -17,7 +17,7 @@ var (
|
||||
|
||||
func cron(cmd *cobra.Command, args []string) {
|
||||
|
||||
opts := backy.NewOpts(cfgFile, backy.UseCron())
|
||||
opts := backy.NewOpts(cfgFile, backy.CronEnabled())
|
||||
opts.InitConfig()
|
||||
backy.ReadConfig(opts)
|
||||
opts.Cron()
|
||||
|
14
cmd/list.go
14
cmd/list.go
@ -20,20 +20,30 @@ var (
|
||||
)
|
||||
|
||||
var listsToList []string
|
||||
var cmdsToList []string
|
||||
|
||||
func init() {
|
||||
|
||||
listCmd.Flags().StringSliceVarP(&listsToList, "lists", "l", nil, "Accepts comma-separated names of command lists to list.")
|
||||
listCmd.Flags().StringSliceVarP(&cmdsToList, "cmds", "c", nil, "Accepts comma-separated names of commands to list.")
|
||||
|
||||
}
|
||||
|
||||
func List(cmd *cobra.Command, args []string) {
|
||||
|
||||
opts := backy.NewOpts(cfgFile, backy.SetListsToSearch(cmdLists))
|
||||
// settup based on whats passed in:
|
||||
// - cmds
|
||||
// - lists
|
||||
// - if none, list all commands
|
||||
if cmdLists != nil {
|
||||
|
||||
}
|
||||
|
||||
opts := backy.NewOpts(cfgFile)
|
||||
|
||||
opts.InitConfig()
|
||||
|
||||
opts = backy.ReadConfig(opts)
|
||||
|
||||
opts.ListConfiguration()
|
||||
opts.ListCommand("rm-sn-db")
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const versionStr = "0.4.0"
|
||||
const versionStr = "0.5.0"
|
||||
|
||||
var (
|
||||
versionCmd = &cobra.Command{
|
||||
|
2
docs/.gitignore
vendored
Normal file
2
docs/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
public/
|
||||
resources/_gen
|
@ -7,6 +7,8 @@ Backy is a tool for automating data backup and remote command execution. It can
|
||||
|
||||
Why the name Backy? Because I wanted an app for backups.
|
||||
|
||||
View the [changelog here](https://git.andrewnw.xyz/CyberShell/backy/src/branch/master/CHANGELOG.md).
|
||||
|
||||
{{% notice tip %}}
|
||||
Feel free to open a [PR](https://git.andrewnw.xyz/CyberShell/backy/pulls), raise an [issue](https://git.andrewnw.xyz/CyberShell/backy/issues "Open a Gitea Issue")(s), or request new feature(s).
|
||||
{{% /notice %}}
|
||||
|
@ -110,6 +110,7 @@ Usage:
|
||||
backy list [--list=list1,list2,... | -l list1, list2,...] [ -cmd cmd1 cmd2 cmd3...] [flags]
|
||||
|
||||
Flags:
|
||||
-c, --cmds strings Accepts comma-separated names of commands to list.
|
||||
-h, --help help for list
|
||||
-l, --lists strings Accepts comma-separated names of command lists to list.
|
||||
|
||||
|
@ -12,10 +12,10 @@ To use a specific file:
|
||||
|
||||
If you leave the config path blank, the following paths will be searched in order:
|
||||
|
||||
- `./backy.yml`
|
||||
- `./backy.yaml`
|
||||
- `~/.config/backy.yml`
|
||||
- `~/.config/backy.yaml`
|
||||
1. `./backy.yml`
|
||||
2. `./backy.yaml`
|
||||
3. `~/.config/backy.yml`
|
||||
4. `~/.config/backy.yaml`
|
||||
|
||||
Create a file at `~/.config/backy.yml`.
|
||||
|
||||
|
@ -9,6 +9,11 @@ Command lists are for executing commands in sequence and getting notifications f
|
||||
|
||||
The top-level object key can be anything you want but not the same as another.
|
||||
|
||||
Lists can go in a separate file. Command lists should be in a separate file if:
|
||||
|
||||
1. key 'cmd-lists.file' is found
|
||||
2. hosts.yml or hosts.yaml is found in the same directory as the backy config file
|
||||
|
||||
```yaml
|
||||
test2:
|
||||
name: test2
|
||||
@ -51,7 +56,7 @@ An array of notification IDs to use on success and failure. Must match any of th
|
||||
|
||||
### Name
|
||||
|
||||
Name is optional for logging. If name is not defined, name will be the object's map key.
|
||||
Name is optional. If name is not defined, name will be the object's map key.
|
||||
|
||||
### Cron mode
|
||||
|
||||
|
@ -20,6 +20,13 @@ commands:
|
||||
# if host is not defined, command will be run locally
|
||||
# The host has to be defined in either the config file or the SSH Config files
|
||||
host: some-host
|
||||
hooks
|
||||
error:
|
||||
- some-other-command-when-failing
|
||||
success:
|
||||
- success-command
|
||||
final:
|
||||
- final-command
|
||||
backup-docker-container-script:
|
||||
cmd: /path/to/local/script
|
||||
# script file is input as stdin to SSH
|
||||
@ -41,6 +48,7 @@ Values available for this section:
|
||||
| `host` | If not specified, the command will execute locally. | `string` | no |
|
||||
| `scriptEnvFile` | When type is `scriptFile`, the script is appended to this file. | `string` | no |
|
||||
| `shell` | Only applicable when host is not specified | `string` | no |
|
||||
| `hooks` | Hooks are used at the end of the individual command. Must be another command. | `[]string` | no |
|
||||
|
||||
#### cmd
|
||||
|
||||
@ -93,9 +101,9 @@ Make sure to escape any shell input.
|
||||
|
||||
Path to a file.
|
||||
|
||||
When type is `scriptFile`, the script is appended to this file.
|
||||
When type is specified, the script is appended to this file.
|
||||
|
||||
This is useful for specifiing environment variables or other things so they don't have to be included in the script.
|
||||
This is useful for specifying environment variables or other things so they don't have to be included in the script.
|
||||
|
||||
### type
|
||||
|
||||
@ -103,7 +111,7 @@ May be `scriptFile` or `script`. Runs script from local machine on remote host p
|
||||
|
||||
If `type` is `script`, `cmd` is used as the script.
|
||||
|
||||
If `type` is `scriptFile`, cmd must be a file path.
|
||||
If `type` is `scriptFile`, cmd must be a script file.
|
||||
|
||||
### environment
|
||||
|
||||
@ -116,3 +124,23 @@ For now, the variables have to be defined in an `.env` file in the same director
|
||||
If using it with host specified, the SSH server has to be configured to accept those env variables.
|
||||
|
||||
If the command is run locally, the OS's environment is added.
|
||||
|
||||
### hooks
|
||||
|
||||
Hooks are run after the command is run.
|
||||
|
||||
Errors are run if the command errors, success if it returns no error. Final hooks are run regardless of error condition.
|
||||
|
||||
Values for hooks are as follows:
|
||||
|
||||
```yaml
|
||||
command:
|
||||
hook:
|
||||
# these commands are defined elsewhere in the file
|
||||
error:
|
||||
- errcommand
|
||||
success:
|
||||
- successcommand
|
||||
final:
|
||||
- donecommand
|
||||
```
|
@ -48,7 +48,7 @@ commands:
|
||||
To execute groups of commands in sequence, use a list configuration.
|
||||
|
||||
```yaml
|
||||
cmd-configs:
|
||||
cmd-lists:
|
||||
cmds-to-run: # this can be any name you want
|
||||
# all commands have to be defined in the commands section
|
||||
order:
|
||||
@ -97,7 +97,7 @@ hosts:
|
||||
|
||||
The notifications object can have two forms.
|
||||
|
||||
For more, [see the notification object documentation](/config/notifications). The top-level map key is id that has to be referenced by the `cmd-configs` key `notifications`.
|
||||
For more, [see the notification object documentation](/config/notifications). The top-level map key is id that has to be referenced by the `cmd-lists` key `notifications`.
|
||||
|
||||
```yaml
|
||||
notifications:
|
||||
|
@ -22,7 +22,7 @@ commands:
|
||||
hostname:
|
||||
cmd: hostname
|
||||
|
||||
cmd-configs:
|
||||
cmd-lists:
|
||||
cmds-to-run: # this can be any name you want
|
||||
# all commands have to be defined
|
||||
order:
|
||||
|
17
go.mod
17
go.mod
@ -15,7 +15,6 @@ require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rs/zerolog v1.30.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
go.mongodb.org/mongo-driver v1.12.1
|
||||
golang.org/x/crypto v0.13.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
maunium.net/go/mautrix v0.16.0
|
||||
@ -25,10 +24,8 @@ require (
|
||||
require (
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
@ -41,35 +38,22 @@ require (
|
||||
github.com/hashicorp/hcl v1.0.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/klauspost/compress v1.16.7 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.1 // indirect
|
||||
github.com/knadh/koanf/providers/structs v0.1.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.1 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tidwall/gjson v1.16.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||
go.mau.fi/util v0.0.0-20230906155759-14bad39a8718 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
@ -78,7 +62,6 @@ require (
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
maunium.net/go/maulogger/v2 v2.4.1 // indirect
|
||||
)
|
||||
|
48
go.sum
48
go.sum
@ -19,11 +19,7 @@ github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyM
|
||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@ -61,9 +57,6 @@ 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/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/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
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/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
|
||||
@ -100,9 +93,6 @@ 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/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/nikoksr/notify v0.41.0 h1:4LGE41GpWdHX5M3Xo6DlWRwS2WLDbOq1Rk7IzY4vjmQ=
|
||||
github.com/nikoksr/notify v0.41.0/go.mod h1:FoE0UVPeopz1Vy5nm9vQZ+JVmYjEIjQgbFstbkw+cRE=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
@ -151,76 +141,38 @@ 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/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mau.fi/util v0.0.0-20230906155759-14bad39a8718 h1:hmm5bZqE0M8+Uvys0HJPCSbAIZIwYtTkBKYPjAWHuMM=
|
||||
go.mau.fi/util v0.0.0-20230906155759-14bad39a8718/go.mod h1:AxuJUMCxpzgJ5eV9JbPWKRH8aAJJidxetNdUj7qcb84=
|
||||
go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE=
|
||||
go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
|
||||
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/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/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-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/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-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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
|
||||
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.6/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.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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=
|
||||
|
@ -17,7 +17,6 @@ import (
|
||||
"embed"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
//go:embed templates/*.txt
|
||||
@ -31,6 +30,8 @@ var Sprintf = fmt.Sprintf
|
||||
// The environment of local commands will be the machine's environment plus any extra
|
||||
// variables specified in the Env file or Environment.
|
||||
// Dir can also be specified for local commands.
|
||||
//
|
||||
// Returns the output as a slice and an error, if any
|
||||
func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([]string, error) {
|
||||
|
||||
var (
|
||||
@ -49,13 +50,14 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
ArgsStr += fmt.Sprintf(" %s", v)
|
||||
}
|
||||
|
||||
// is host defined
|
||||
if command.Host != nil {
|
||||
command.Type = strings.TrimSpace(command.Type)
|
||||
|
||||
if command.Type != "" {
|
||||
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running script %s on host %s", command.Cmd, *command.Host)).Send()
|
||||
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running script %s on host %s", command.Name, *command.Host)).Send()
|
||||
} else {
|
||||
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s %s on host %s", command.Cmd, ArgsStr, *command.Host)).Send()
|
||||
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s on host %s", command.Name, *command.Host)).Send()
|
||||
}
|
||||
|
||||
if command.RemoteHost.SshClient == nil {
|
||||
@ -64,6 +66,8 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// create new ssh session
|
||||
commandSession, err := command.RemoteHost.SshClient.NewSession()
|
||||
|
||||
// Retry connecting to host; if that fails, error. If it does not fail, try to create new session
|
||||
@ -86,6 +90,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
for _, a := range command.Args {
|
||||
cmd += " " + a
|
||||
}
|
||||
|
||||
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
|
||||
|
||||
if IsCmdStdOutEnabled() {
|
||||
@ -94,17 +99,64 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
|
||||
commandSession.Stdout = cmdOutWriters
|
||||
commandSession.Stderr = cmdOutWriters
|
||||
// Is command type defined. That is, is it local or not
|
||||
if command.Type != "" {
|
||||
|
||||
// did the program panic while writing to the buffer?
|
||||
var (
|
||||
script *bytes.Buffer
|
||||
buffer bytes.Buffer
|
||||
scriptEnvFileBuffer bytes.Buffer
|
||||
scriptFileBuffer bytes.Buffer
|
||||
dirErr error
|
||||
scriptEnvFilePresent bool
|
||||
)
|
||||
|
||||
defer func() {
|
||||
// did the program panic while writing to the buffer?
|
||||
if err := recover(); err != nil {
|
||||
cmdCtxLogger.Info().Msg(fmt.Sprintf("panic occured writing to buffer: %x", err))
|
||||
// cmdCtxLogger.Debug().Msg(fmt.Sprintf("script buffer: %v", script))
|
||||
cmdCtxLogger.Info().Msg(fmt.Sprintf("panic occurred writing to buffer: %v", err))
|
||||
}
|
||||
}()
|
||||
|
||||
if command.Type == "script" {
|
||||
script := bytes.NewBufferString(cmd + "\n")
|
||||
|
||||
if command.ScriptEnvFile != "" {
|
||||
|
||||
command.ScriptEnvFile, dirErr = resolveDir(command.ScriptEnvFile)
|
||||
if dirErr != nil {
|
||||
return nil, dirErr
|
||||
}
|
||||
file, err := os.Open(command.ScriptEnvFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = io.Copy(&scriptEnvFileBuffer, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Bug: writing to buffer triggers panic
|
||||
// why?
|
||||
// use bytes.Buffer instead of pointer to memory (*bytes.Buffer)
|
||||
|
||||
_, err = buffer.WriteString(scriptEnvFileBuffer.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// write newline
|
||||
buffer.WriteByte(0x0A)
|
||||
|
||||
_, err = buffer.WriteString(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
script = &buffer
|
||||
|
||||
} else {
|
||||
script = bytes.NewBufferString(cmd + "\n")
|
||||
}
|
||||
|
||||
commandSession.Stdin = script
|
||||
if err := commandSession.Shell(); err != nil {
|
||||
@ -115,7 +167,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
outScanner := bufio.NewScanner(&cmdOutBuf)
|
||||
for outScanner.Scan() {
|
||||
outMap := make(map[string]interface{})
|
||||
outMap["cmd"] = cmd
|
||||
outMap["cmd"] = command.Name
|
||||
outMap["output"] = outScanner.Text()
|
||||
if str, ok := outMap["output"].(string); ok {
|
||||
outputArr = append(outputArr, str)
|
||||
@ -128,25 +180,20 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
outScanner := bufio.NewScanner(&cmdOutBuf)
|
||||
for outScanner.Scan() {
|
||||
outMap := make(map[string]interface{})
|
||||
outMap["cmd"] = cmd
|
||||
outMap["cmd"] = command.Name
|
||||
outMap["output"] = outScanner.Text()
|
||||
if str, ok := outMap["output"].(string); ok {
|
||||
outputArr = append(outputArr, str)
|
||||
}
|
||||
log.Info().Fields(outMap).Send()
|
||||
cmdCtxLogger.Info().Fields(outMap).Send()
|
||||
}
|
||||
return outputArr, nil
|
||||
}
|
||||
|
||||
if command.Type == "scriptFile" {
|
||||
var (
|
||||
buffer bytes.Buffer
|
||||
scriptEnvFileBuffer bytes.Buffer
|
||||
scriptFileBuffer bytes.Buffer
|
||||
dirErr error
|
||||
scriptEnvFilePresent bool
|
||||
)
|
||||
|
||||
if command.ScriptEnvFile != "" {
|
||||
|
||||
command.ScriptEnvFile, dirErr = resolveDir(command.ScriptEnvFile)
|
||||
if dirErr != nil {
|
||||
return nil, dirErr
|
||||
@ -164,20 +211,27 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
}
|
||||
|
||||
command.Cmd, dirErr = resolveDir(command.Cmd)
|
||||
|
||||
if dirErr != nil {
|
||||
return nil, dirErr
|
||||
}
|
||||
|
||||
// treat command.Cmd as a file
|
||||
file, err := os.Open(command.Cmd)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = io.Copy(&scriptFileBuffer, file)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
// append scriptEnvFile to scriptFileBuffer
|
||||
if scriptEnvFilePresent {
|
||||
_, err := buffer.WriteString(scriptEnvFileBuffer.String())
|
||||
if err != nil {
|
||||
@ -236,7 +290,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
outScanner := bufio.NewScanner(&cmdOutBuf)
|
||||
for outScanner.Scan() {
|
||||
outMap := make(map[string]interface{})
|
||||
outMap["cmd"] = cmd
|
||||
outMap["cmd"] = command.Name
|
||||
outMap["output"] = outScanner.Text()
|
||||
if str, ok := outMap["output"].(string); ok {
|
||||
outputArr = append(outputArr, str)
|
||||
@ -245,14 +299,14 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd: %s: %w", command.Cmd, err)).Send()
|
||||
cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd: %s: %w", command.Name, err)).Send()
|
||||
return outputArr, err
|
||||
}
|
||||
} else {
|
||||
|
||||
var err error
|
||||
if command.Shell != "" {
|
||||
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s %s on local machine in %s", command.Cmd, ArgsStr, command.Shell)).Send()
|
||||
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine in %s", command.Name, command.Shell)).Send()
|
||||
|
||||
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
||||
|
||||
@ -278,39 +332,44 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
|
||||
for outScanner.Scan() {
|
||||
outMap := make(map[string]interface{})
|
||||
outMap["cmd"] = command.Cmd
|
||||
outMap["cmd"] = command.Name
|
||||
outMap["output"] = outScanner.Text()
|
||||
if str, ok := outMap["output"].(string); ok {
|
||||
outputArr = append(outputArr, str)
|
||||
}
|
||||
log.Info().Fields(outMap).Send()
|
||||
cmdCtxLogger.Info().Fields(outMap).Send()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd %s: %w", command.Cmd, err)).Send()
|
||||
cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd %s: %w", command.Name, err)).Send()
|
||||
return outputArr, err
|
||||
}
|
||||
return outputArr, nil
|
||||
}
|
||||
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s %s on local machine", command.Cmd, ArgsStr)).Send()
|
||||
|
||||
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine", command.Name)).Send()
|
||||
|
||||
localCMD := exec.Command(command.Cmd, command.Args...)
|
||||
|
||||
if command.Dir != nil {
|
||||
localCMD.Dir = *command.Dir
|
||||
}
|
||||
// fmt.Printf("%v\n", envVars.env)
|
||||
|
||||
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger)
|
||||
|
||||
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
|
||||
// fmt.Printf("%v\n", localCMD.Environ())
|
||||
|
||||
if IsCmdStdOutEnabled() {
|
||||
cmdOutWriters = io.MultiWriter(os.Stdout, &cmdOutBuf)
|
||||
}
|
||||
|
||||
localCMD.Stdout = cmdOutWriters
|
||||
localCMD.Stderr = cmdOutWriters
|
||||
|
||||
err = localCMD.Run()
|
||||
|
||||
outScanner := bufio.NewScanner(&cmdOutBuf)
|
||||
|
||||
for outScanner.Scan() {
|
||||
outMap := make(map[string]interface{})
|
||||
outMap["cmd"] = command.Cmd
|
||||
@ -322,48 +381,44 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
cmdCtxLogger.Info().Fields(outMap).Send()
|
||||
}
|
||||
if err != nil {
|
||||
cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd %s: %w", command.Cmd, err)).Send()
|
||||
cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd %s: %w", command.Name, err)).Send()
|
||||
return outputArr, err
|
||||
}
|
||||
}
|
||||
return outputArr, nil
|
||||
}
|
||||
|
||||
// cmdListWorker
|
||||
func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<- string, opts *ConfigOpts) {
|
||||
|
||||
// iterate over list to run
|
||||
res := CmdListResults{}
|
||||
for list := range jobs {
|
||||
fieldsMap := make(map[string]interface{})
|
||||
fieldsMap["list"] = list.Name
|
||||
var cmdLogger zerolog.Logger
|
||||
|
||||
cmdLog := opts.Logger.Info()
|
||||
|
||||
var count int
|
||||
var cmdsRan []string
|
||||
var outStructArr []outStruct
|
||||
var count int // count of how many commands have been executed
|
||||
var cmdsRan []string // store the commands that have been executed
|
||||
var outStructArr []outStruct // stores output messages
|
||||
|
||||
for _, cmd := range list.Order {
|
||||
currentCmd := opts.Cmds[cmd].Cmd
|
||||
|
||||
fieldsMap["cmd"] = opts.Cmds[cmd].Cmd
|
||||
currentCmd := opts.Cmds[cmd].Name
|
||||
|
||||
fieldsMap["cmd"] = opts.Cmds[cmd].Name
|
||||
cmdToRun := opts.Cmds[cmd]
|
||||
cmdLog.Fields(fieldsMap).Send()
|
||||
|
||||
cmdLogger := opts.Logger.With().
|
||||
Str("backy-cmd", cmd).Str("Host", "local machine").
|
||||
Logger()
|
||||
|
||||
if cmdToRun.Host != nil {
|
||||
cmdLogger = opts.Logger.With().
|
||||
Str("backy-cmd", cmd).Str("Host", *cmdToRun.Host).
|
||||
Logger()
|
||||
}
|
||||
cmdLogger = cmdToRun.generateLogger(opts)
|
||||
cmdLogger.Info().Fields(fieldsMap).Send()
|
||||
|
||||
outputArr, runOutErr := cmdToRun.RunCmd(cmdLogger, opts)
|
||||
|
||||
if list.NotifyConfig != nil {
|
||||
|
||||
// check if the command output should be included
|
||||
if cmdToRun.GetOutput || list.GetOutput {
|
||||
outputStruct := outStruct{
|
||||
CmdName: cmd,
|
||||
CmdName: cmdToRun.Name,
|
||||
CmdExecuted: currentCmd,
|
||||
Output: outputArr,
|
||||
}
|
||||
@ -374,8 +429,9 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<-
|
||||
}
|
||||
count++
|
||||
if runOutErr != nil {
|
||||
var errMsg bytes.Buffer
|
||||
res.ErrCmd = cmd
|
||||
if list.NotifyConfig != nil {
|
||||
var errMsg bytes.Buffer
|
||||
errStruct := make(map[string]interface{})
|
||||
|
||||
errStruct["listName"] = list.Name
|
||||
@ -391,26 +447,29 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<-
|
||||
tmpErr := msgTemps.err.Execute(&errMsg, errStruct)
|
||||
|
||||
if tmpErr != nil {
|
||||
opts.Logger.Err(tmpErr).Send()
|
||||
cmdLogger.Err(tmpErr).Send()
|
||||
}
|
||||
|
||||
notifySendErr := list.NotifyConfig.Send(context.Background(), fmt.Sprintf("List %s failed", list.Name), errMsg.String())
|
||||
|
||||
if notifySendErr != nil {
|
||||
opts.Logger.Err(notifySendErr).Send()
|
||||
cmdLogger.Err(notifySendErr).Send()
|
||||
}
|
||||
}
|
||||
|
||||
opts.Logger.Err(runOutErr).Send()
|
||||
cmdLogger.Err(runOutErr).Send()
|
||||
|
||||
break
|
||||
} else {
|
||||
|
||||
if count == len(list.Order) {
|
||||
cmdsRan = append(cmdsRan, cmd)
|
||||
|
||||
if count == len(list.Order) {
|
||||
var successMsg bytes.Buffer
|
||||
|
||||
if list.NotifyConfig != nil {
|
||||
// if notification config is not nil, and NotifyOnSuccess is true or GetOuput is true,
|
||||
// then send notification
|
||||
if list.NotifyConfig != nil && (list.NotifyOnSuccess || list.GetOutput) {
|
||||
successStruct := make(map[string]interface{})
|
||||
|
||||
successStruct["listName"] = list.Name
|
||||
@ -421,23 +480,21 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<-
|
||||
tmpErr := msgTemps.success.Execute(&successMsg, successStruct)
|
||||
|
||||
if tmpErr != nil {
|
||||
opts.Logger.Err(tmpErr).Send()
|
||||
cmdLogger.Err(tmpErr).Send()
|
||||
break
|
||||
}
|
||||
|
||||
err := list.NotifyConfig.Send(context.Background(), fmt.Sprintf("List %s succeded", list.Name), successMsg.String())
|
||||
err := list.NotifyConfig.Send(context.Background(), fmt.Sprintf("List %s succeeded", list.Name), successMsg.String())
|
||||
|
||||
if err != nil {
|
||||
opts.Logger.Err(err).Send()
|
||||
cmdLogger.Err(err).Send()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cmdsRan = append(cmdsRan, cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results <- "done"
|
||||
results <- res.ErrCmd
|
||||
}
|
||||
|
||||
}
|
||||
@ -473,7 +530,23 @@ func (opts *ConfigOpts) RunListConfig(cron string) {
|
||||
close(listChan)
|
||||
|
||||
for a := 1; a <= configListsLen; a++ {
|
||||
<-results
|
||||
l := <-results
|
||||
|
||||
opts.Logger.Debug().Msg(l)
|
||||
|
||||
if l != "" {
|
||||
// execute error hooks
|
||||
opts.Logger.Debug().Msg("hooks are working")
|
||||
opts.Cmds[l].ExecuteHooks("error", opts)
|
||||
} else {
|
||||
// execute success hooks
|
||||
opts.Cmds[l].ExecuteHooks("success", opts)
|
||||
|
||||
}
|
||||
|
||||
// execute final hooks
|
||||
opts.Cmds[l].ExecuteHooks("final", opts)
|
||||
|
||||
}
|
||||
|
||||
opts.closeHostConnections()
|
||||
@ -482,13 +555,18 @@ func (opts *ConfigOpts) RunListConfig(cron string) {
|
||||
func (config *ConfigOpts) ExecuteCmds(opts *ConfigOpts) {
|
||||
for _, cmd := range opts.executeCmds {
|
||||
cmdToRun := opts.Cmds[cmd]
|
||||
cmdLogger := opts.Logger.With().
|
||||
Str("backy-cmd", cmd).
|
||||
Logger()
|
||||
cmdLogger := cmdToRun.generateLogger(opts)
|
||||
_, runErr := cmdToRun.RunCmd(cmdLogger, opts)
|
||||
if runErr != nil {
|
||||
opts.Logger.Err(runErr).Send()
|
||||
|
||||
cmdToRun.ExecuteHooks("error", opts)
|
||||
} else {
|
||||
|
||||
cmdToRun.ExecuteHooks("success", opts)
|
||||
}
|
||||
|
||||
cmdToRun.ExecuteHooks("final", opts)
|
||||
}
|
||||
|
||||
opts.closeHostConnections()
|
||||
@ -531,3 +609,50 @@ func (c *ConfigOpts) closeHostConnections() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Command) ExecuteHooks(hookType string, opts *ConfigOpts) {
|
||||
if cmd.Hooks == nil {
|
||||
return
|
||||
}
|
||||
switch hookType {
|
||||
case "error":
|
||||
for _, v := range cmd.Hooks.Error {
|
||||
errCmd := opts.Cmds[v]
|
||||
cmdLogger := opts.Logger.With().
|
||||
Str("backy-cmd", v).
|
||||
Logger()
|
||||
errCmd.RunCmd(cmdLogger, opts)
|
||||
}
|
||||
|
||||
case "success":
|
||||
for _, v := range cmd.Hooks.Success {
|
||||
successCmd := opts.Cmds[v]
|
||||
cmdLogger := opts.Logger.With().
|
||||
Str("backy-cmd", v).
|
||||
Logger()
|
||||
successCmd.RunCmd(cmdLogger, opts)
|
||||
}
|
||||
case "final":
|
||||
for _, v := range cmd.Hooks.Final {
|
||||
finalCmd := opts.Cmds[v]
|
||||
cmdLogger := opts.Logger.With().
|
||||
Str("backy-cmd", v).
|
||||
Logger()
|
||||
finalCmd.RunCmd(cmdLogger, opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Command) generateLogger(opts *ConfigOpts) zerolog.Logger {
|
||||
cmdLogger := opts.Logger.With().
|
||||
Str("backy-cmd", cmd.Name).Str("Host", "local machine").
|
||||
Logger()
|
||||
|
||||
if cmd.Host != nil {
|
||||
cmdLogger = opts.Logger.With().
|
||||
Str("backy-cmd", cmd.Name).Str("Host", *cmd.Host).
|
||||
Logger()
|
||||
|
||||
}
|
||||
return cmdLogger
|
||||
}
|
||||
|
@ -22,16 +22,28 @@ var homeDirErr error
|
||||
var backyHomeConfDir string
|
||||
var configFiles []string
|
||||
|
||||
const macroStart string = "%{"
|
||||
const macroEnd string = "}%"
|
||||
const envMacroStart string = "%{env:"
|
||||
const vaultMacroStart string = "%{env:"
|
||||
|
||||
func (opts *ConfigOpts) InitConfig() {
|
||||
|
||||
homeDir, homeDirErr = os.UserHomeDir()
|
||||
|
||||
if homeDirErr != nil {
|
||||
fmt.Println(homeDirErr)
|
||||
logging.ExitWithMSG(homeDirErr.Error(), 1, nil)
|
||||
}
|
||||
|
||||
backyHomeConfDir = homeDir + "/.config/backy/"
|
||||
|
||||
configFiles = []string{"./backy.yml", "./backy.yaml", backyHomeConfDir + "backy.yml", backyHomeConfDir + "backy.yaml"}
|
||||
|
||||
backyKoanf := koanf.New(".")
|
||||
|
||||
opts.ConfigFilePath = strings.TrimSpace(opts.ConfigFilePath)
|
||||
|
||||
if opts.ConfigFilePath != "" {
|
||||
err := testFile(opts.ConfigFilePath)
|
||||
if err != nil {
|
||||
@ -43,16 +55,16 @@ func (opts *ConfigOpts) InitConfig() {
|
||||
}
|
||||
} else {
|
||||
|
||||
cFileFalures := 0
|
||||
cFileFailures := 0
|
||||
for _, c := range configFiles {
|
||||
if err := backyKoanf.Load(file.Provider(c), yaml.Parser()); err != nil {
|
||||
cFileFalures++
|
||||
cFileFailures++
|
||||
} else {
|
||||
opts.ConfigFilePath = c
|
||||
break
|
||||
}
|
||||
}
|
||||
if cFileFalures == len(configFiles) {
|
||||
if cFileFailures == len(configFiles) {
|
||||
logging.ExitWithMSG(fmt.Sprintf("could not find a config file. Put one in the following paths: %v", configFiles), 1, &opts.Logger)
|
||||
}
|
||||
}
|
||||
@ -80,32 +92,43 @@ func ReadConfig(opts *ConfigOpts) *ConfigOpts {
|
||||
}
|
||||
|
||||
CheckConfigValues(backyKoanf, opts.ConfigFilePath)
|
||||
|
||||
// check for commands in file
|
||||
for _, c := range opts.executeCmds {
|
||||
if !backyKoanf.Exists(getCmdFromConfig(c)) {
|
||||
logging.ExitWithMSG(Sprintf("command %s is not in config file %s", c, opts.ConfigFilePath), 1, nil)
|
||||
}
|
||||
}
|
||||
|
||||
for _, l := range opts.executeLists {
|
||||
if !backyKoanf.Exists(getCmdListFromConfig(l)) {
|
||||
logging.ExitWithMSG(Sprintf("list %s not found", l), 1, nil)
|
||||
}
|
||||
}
|
||||
// TODO: refactor this further down the line
|
||||
|
||||
// for _, l := range opts.executeLists {
|
||||
// if !backyKoanf.Exists(getCmdListFromConfig(l)) {
|
||||
// logging.ExitWithMSG(Sprintf("list %s not found", l), 1, nil)
|
||||
// }
|
||||
// }
|
||||
|
||||
// check for verbosity, via
|
||||
// 1. config file
|
||||
// 2. TODO: CLI flag
|
||||
// 3. TODO: ENV var
|
||||
|
||||
var (
|
||||
verbose bool
|
||||
isLoggingVerbose bool
|
||||
logFile string
|
||||
)
|
||||
|
||||
verbose = backyKoanf.Bool(getLoggingKeyFromConfig("verbose"))
|
||||
isLoggingVerbose = backyKoanf.Bool(getLoggingKeyFromConfig("verbose"))
|
||||
|
||||
logFile = fmt.Sprintf("%s/backy.log", path.Dir(opts.ConfigFilePath)) // get full path to logfile
|
||||
|
||||
logFile = fmt.Sprintf("%s/backy.log", path.Dir(opts.ConfigFilePath))
|
||||
if backyKoanf.Exists(getLoggingKeyFromConfig("file")) {
|
||||
logFile = backyKoanf.String(getLoggingKeyFromConfig("file"))
|
||||
}
|
||||
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
|
||||
if verbose {
|
||||
if isLoggingVerbose {
|
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||
globalLvl := zerolog.GlobalLevel()
|
||||
os.Setenv("BACKY_LOGLEVEL", Sprintf("%v", globalLvl))
|
||||
@ -128,8 +151,11 @@ func ReadConfig(opts *ConfigOpts) *ConfigOpts {
|
||||
log.Info().Str("config file", opts.ConfigFilePath).Send()
|
||||
|
||||
unmarshalErr := backyKoanf.UnmarshalWithConf("commands", &opts.Cmds, koanf.UnmarshalConf{Tag: "yaml"})
|
||||
|
||||
if unmarshalErr != nil {
|
||||
panic(fmt.Errorf("error unmarshalling cmds struct: %w", unmarshalErr))
|
||||
|
||||
panic(fmt.Errorf("error unmarshaling cmds struct: %w", unmarshalErr))
|
||||
|
||||
}
|
||||
|
||||
for cmdName, cmdConf := range opts.Cmds {
|
||||
@ -142,6 +168,8 @@ func ReadConfig(opts *ConfigOpts) *ConfigOpts {
|
||||
expandEnvVars(opts.backyEnv, cmdConf.Environment)
|
||||
}
|
||||
|
||||
// Get host configurations from config file
|
||||
|
||||
unmarshalErr = backyKoanf.UnmarshalWithConf("hosts", &opts.Hosts, koanf.UnmarshalConf{Tag: "yaml"})
|
||||
if unmarshalErr != nil {
|
||||
panic(fmt.Errorf("error unmarshalling hosts struct: %w", unmarshalErr))
|
||||
@ -152,7 +180,6 @@ func ReadConfig(opts *ConfigOpts) *ConfigOpts {
|
||||
}
|
||||
if host.ProxyJump != "" {
|
||||
proxyHosts := strings.Split(host.ProxyJump, ",")
|
||||
if len(proxyHosts) > 1 {
|
||||
for hostNum, h := range proxyHosts {
|
||||
if hostNum > 1 {
|
||||
proxyHost, defined := opts.Hosts[h]
|
||||
@ -172,28 +199,78 @@ func ReadConfig(opts *ConfigOpts) *ConfigOpts {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
proxyHost, defined := opts.Hosts[proxyHosts[0]]
|
||||
if defined {
|
||||
host.ProxyHost = append(host.ProxyHost, proxyHost)
|
||||
} else {
|
||||
newProxy := &Host{Host: proxyHosts[0]}
|
||||
host.ProxyHost = append(host.ProxyHost, newProxy)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if backyKoanf.Exists("cmd-lists") {
|
||||
unmarshalErr = backyKoanf.UnmarshalWithConf("cmd-lists", &opts.CmdConfigLists, koanf.UnmarshalConf{Tag: "yaml"})
|
||||
if unmarshalErr != nil {
|
||||
logging.ExitWithMSG((fmt.Sprintf("error unmarshalling cmd list struct: %v", unmarshalErr)), 1, &opts.Logger)
|
||||
// get command lists
|
||||
// command lists should still be in the same file if no:
|
||||
// 1. key 'cmd-lists.file' is found
|
||||
// 2. hosts.yml or hosts.yaml is found in the same directory as the backy config file
|
||||
backyConfigFileDir := path.Dir(opts.ConfigFilePath)
|
||||
|
||||
listsConfig := koanf.New(".")
|
||||
|
||||
listConfigFiles := []string{path.Join(backyConfigFileDir, "lists.yml"), path.Join(backyConfigFileDir, "lists.yaml")}
|
||||
|
||||
log.Info().Strs("list config files", listConfigFiles).Send()
|
||||
for _, l := range listConfigFiles {
|
||||
cFileFailures := 0
|
||||
if err := listsConfig.Load(file.Provider(l), yaml.Parser()); err != nil {
|
||||
cFileFailures++
|
||||
} else {
|
||||
opts.ConfigFilePath = l
|
||||
break
|
||||
}
|
||||
|
||||
if cFileFailures == len(configFiles) {
|
||||
|
||||
logging.ExitWithMSG(fmt.Sprintf("could not find a config file. Put one in the following paths: %v", listConfigFiles), 1, &opts.Logger)
|
||||
|
||||
// logging.ExitWithMSG((fmt.Sprintf("error unmarshalling cmd list struct: %v", unmarshalErr)), 1, &opts.Logger)
|
||||
}
|
||||
|
||||
}
|
||||
_ = listsConfig.UnmarshalWithConf("cmd-lists", &opts.CmdConfigLists, koanf.UnmarshalConf{Tag: "yaml"})
|
||||
|
||||
if backyKoanf.Exists("cmd-lists") {
|
||||
|
||||
unmarshalErr = backyKoanf.UnmarshalWithConf("cmd-lists", &opts.CmdConfigLists, koanf.UnmarshalConf{Tag: "yaml"})
|
||||
// if unmarshalErr is not nil, look for a cmd-lists.file key
|
||||
if unmarshalErr != nil {
|
||||
|
||||
// if file key exists, resolve file path and try to read and unmarshal file into command lists config
|
||||
if backyKoanf.Exists("cmd-lists.file") {
|
||||
opts.CmdListFile = strings.TrimSpace(backyKoanf.String("cmd-lists.file"))
|
||||
|
||||
cmdListFilePath := path.Clean(opts.CmdListFile)
|
||||
|
||||
// if path is not absolute, check config directory
|
||||
if !strings.HasPrefix(cmdListFilePath, "/") {
|
||||
opts.CmdListFile = path.Join(backyConfigFileDir, cmdListFilePath)
|
||||
}
|
||||
|
||||
err := testFile(opts.CmdListFile)
|
||||
|
||||
if err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("Could not open config file %s: %v. \n\nThe cmd-lists config should be in the main config file or should be in a lists.yml or lists.yaml file.", opts.CmdListFile, err), 1, nil)
|
||||
}
|
||||
|
||||
if err := listsConfig.Load(file.Provider(opts.CmdListFile), yaml.Parser()); err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("error loading config: %v", err), 1, &opts.Logger)
|
||||
}
|
||||
|
||||
log.Info().Str("using lists config file", opts.CmdListFile).Send()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var cmdNotFoundSliceErr []error
|
||||
for cmdListName, cmdList := range opts.CmdConfigLists {
|
||||
if opts.useCron {
|
||||
if opts.cronEnabled {
|
||||
cron := strings.TrimSpace(cmdList.Cron)
|
||||
if cron == "" {
|
||||
delete(opts.CmdConfigLists, cmdListName)
|
||||
@ -209,19 +286,19 @@ func ReadConfig(opts *ConfigOpts) *ConfigOpts {
|
||||
}
|
||||
}
|
||||
|
||||
// Exit program if command is not found from list
|
||||
if len(cmdNotFoundSliceErr) > 0 {
|
||||
var cmdNotFoundErrorLog = log.Fatal()
|
||||
cmdNotFoundErrorLog.Errs("commands not found", cmdNotFoundSliceErr).Send()
|
||||
}
|
||||
|
||||
if opts.useCron && (len(opts.CmdConfigLists) == 0) {
|
||||
if opts.cronEnabled && (len(opts.CmdConfigLists) == 0) {
|
||||
logging.ExitWithMSG("No cron fields detected in any command lists", 1, nil)
|
||||
}
|
||||
|
||||
for c := range opts.Cmds {
|
||||
if opts.executeCmds != nil && !contains(opts.executeCmds, c) {
|
||||
delete(opts.Cmds, c)
|
||||
}
|
||||
// process commands
|
||||
if err := processCmds(opts); err != nil {
|
||||
logging.ExitWithMSG(err.Error(), 1, &opts.Logger)
|
||||
}
|
||||
|
||||
if len(opts.executeLists) > 0 {
|
||||
@ -240,23 +317,8 @@ func ReadConfig(opts *ConfigOpts) *ConfigOpts {
|
||||
}
|
||||
}
|
||||
|
||||
for _, cmd := range opts.Cmds {
|
||||
if cmd.Host != nil {
|
||||
host, hostFound := opts.Hosts[*cmd.Host]
|
||||
if hostFound {
|
||||
cmd.RemoteHost = host
|
||||
cmd.RemoteHost.Host = host.Host
|
||||
if host.HostName != "" {
|
||||
cmd.RemoteHost.HostName = host.HostName
|
||||
}
|
||||
} else {
|
||||
opts.Hosts[*cmd.Host] = &Host{Host: *cmd.Host}
|
||||
cmd.RemoteHost = &Host{Host: *cmd.Host}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
opts.SetupNotify()
|
||||
|
||||
if err := opts.setupVault(); err != nil {
|
||||
log.Err(err).Send()
|
||||
}
|
||||
@ -383,3 +445,88 @@ func GetVaultKey(str string, opts *ConfigOpts, log zerolog.Logger) string {
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func processCmds(opts *ConfigOpts) error {
|
||||
// process commands
|
||||
for cmdName, cmd := range opts.Cmds {
|
||||
|
||||
if cmd.Name == "" {
|
||||
cmd.Name = cmdName
|
||||
}
|
||||
// println("Cmd.Name = " + cmd.Name)
|
||||
hooks := cmd.Hooks
|
||||
// resolve hooks
|
||||
if hooks != nil {
|
||||
|
||||
processHookSuccess := processHooks(cmd, hooks.Error, opts, "error")
|
||||
if processHookSuccess != nil {
|
||||
return processHookSuccess
|
||||
}
|
||||
processHookSuccess = processHooks(cmd, hooks.Success, opts, "success")
|
||||
if processHookSuccess != nil {
|
||||
return processHookSuccess
|
||||
}
|
||||
processHookSuccess = processHooks(cmd, hooks.Final, opts, "final")
|
||||
if processHookSuccess != nil {
|
||||
return processHookSuccess
|
||||
}
|
||||
}
|
||||
|
||||
// resolve hosts
|
||||
if cmd.Host != nil {
|
||||
host, hostFound := opts.Hosts[*cmd.Host]
|
||||
if hostFound {
|
||||
cmd.RemoteHost = host
|
||||
cmd.RemoteHost.Host = host.Host
|
||||
if host.HostName != "" {
|
||||
cmd.RemoteHost.HostName = host.HostName
|
||||
}
|
||||
} else {
|
||||
opts.Hosts[*cmd.Host] = &Host{Host: *cmd.Host}
|
||||
cmd.RemoteHost = &Host{Host: *cmd.Host}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// processHooks evaluates if hooks are valid Commands
|
||||
//
|
||||
// Takes the following arguments:
|
||||
//
|
||||
// 1. a []string of hooks
|
||||
// 2. a map of Commands as arguments
|
||||
// 3. a string hookType, must be the hook type
|
||||
//
|
||||
// The cmds.hookRef is modified in this function.
|
||||
//
|
||||
// Returns the following:
|
||||
//
|
||||
// An error, if any, if the command is not found
|
||||
func processHooks(cmd *Command, hooks []string, opts *ConfigOpts, hookType string) error {
|
||||
|
||||
// initialize hook type
|
||||
var hookCmdFound bool
|
||||
cmd.hookRefs = map[string]map[string]*Command{}
|
||||
cmd.hookRefs[hookType] = map[string]*Command{}
|
||||
|
||||
for _, hook := range hooks {
|
||||
|
||||
var hookCmd *Command
|
||||
// TODO: match by Command.Name
|
||||
|
||||
hookCmd, hookCmdFound = opts.Cmds[hook]
|
||||
|
||||
if !hookCmdFound {
|
||||
return fmt.Errorf("error in command %s hook %s list: command %s not found", cmd.Name, hookType, hook)
|
||||
}
|
||||
|
||||
cmd.hookRefs[hookType][hook] = hookCmd
|
||||
|
||||
// Recursive, decide if this is good
|
||||
// if hookCmd.hookRefs == nil {
|
||||
// }
|
||||
// hookRef[hookType][h] = hookCmd
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1,5 +1,68 @@
|
||||
package backy
|
||||
|
||||
func (opts *ConfigOpts) ListConfiguration() {
|
||||
import "fmt"
|
||||
|
||||
/*
|
||||
Command: command [args...]
|
||||
Host: Local or remote (list the name)
|
||||
|
||||
List: name
|
||||
Commands:
|
||||
flags: list commands
|
||||
if listcommands: (use list command)
|
||||
Command: command [args...]
|
||||
Host: Local or remote (list the name)
|
||||
|
||||
*/
|
||||
|
||||
// ListCommand searches the commands in the file to find one
|
||||
func (opts *ConfigOpts) ListCommand(cmd string) {
|
||||
// bool for commands not found
|
||||
// gets set to false if a command is not found
|
||||
// set to true if the command is found
|
||||
var cmdFound bool = false
|
||||
var cmdInfo *Command
|
||||
// check commands in file against cmd
|
||||
for _, cmdInFile := range opts.executeCmds {
|
||||
print(cmdInFile)
|
||||
cmdFound = false
|
||||
|
||||
if cmd == cmdInFile {
|
||||
cmdFound = true
|
||||
cmdInfo = opts.Cmds[cmd]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// print the command's information
|
||||
if cmdFound {
|
||||
|
||||
print("Command: ")
|
||||
|
||||
print(cmdInfo.Cmd)
|
||||
if len(cmdInfo.Args) >= 0 {
|
||||
|
||||
for _, v := range cmdInfo.Args {
|
||||
print(" ") // print space between command and args
|
||||
print(v) // print command arg
|
||||
}
|
||||
}
|
||||
|
||||
// is is remote or local
|
||||
if cmdInfo.Host != nil {
|
||||
|
||||
print("Host: ", cmdInfo.Host)
|
||||
|
||||
} else {
|
||||
|
||||
print("Host: Runs on Local Machine\n\n")
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
fmt.Printf("Command %s not found. Check spelling.\n", cmd)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,95 +0,0 @@
|
||||
package backy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||
)
|
||||
|
||||
const mongoConfigKey = "global.mongo"
|
||||
|
||||
func (opts *ConfigOpts) InitMongo() {
|
||||
|
||||
if !opts.koanf.Bool(getMongoConfigKey("enabled")) {
|
||||
return
|
||||
}
|
||||
var (
|
||||
err error
|
||||
client *mongo.Client
|
||||
)
|
||||
|
||||
// TODO: Get uri and creditials from config
|
||||
host := opts.koanf.String(getMongoConfigKey("host"))
|
||||
port := opts.koanf.Int64(getMongoConfigKey("port"))
|
||||
|
||||
ctx, ctxCancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer ctxCancel()
|
||||
client, err = mongo.Connect(ctx, options.Client().ApplyURI(fmt.Sprintf("mongo://%s:%d", host, port)))
|
||||
if opts.koanf.Bool(getMongoConfigKey("prod")) {
|
||||
mongoEnvFileSet := opts.koanf.Exists(getMongoConfigKey("env"))
|
||||
if mongoEnvFileSet {
|
||||
getMongoConfigFromEnv(opts)
|
||||
}
|
||||
auth := options.Credential{}
|
||||
auth.Password = opts.koanf.String("global.mongo.password")
|
||||
auth.Username = opts.koanf.String("global.mongo.username")
|
||||
client, err = mongo.Connect(ctx, options.Client().SetAuth(auth).ApplyURI("mongodb://localhost:27017"))
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
opts.Logger.Fatal().Err(err).Send()
|
||||
}
|
||||
if err != nil {
|
||||
opts.Logger.Fatal().Err(err).Send()
|
||||
}
|
||||
defer client.Disconnect(ctx)
|
||||
err = client.Ping(ctx, readpref.Primary())
|
||||
if err != nil {
|
||||
opts.Logger.Fatal().Err(err).Send()
|
||||
}
|
||||
databases, err := client.ListDatabaseNames(ctx, bson.M{})
|
||||
if err != nil {
|
||||
opts.Logger.Fatal().Err(err).Send()
|
||||
}
|
||||
fmt.Println(databases)
|
||||
backyDB := client.Database("backy")
|
||||
backyDB.CreateCollection(context.Background(), "cmds")
|
||||
backyDB.CreateCollection(context.Background(), "cmd-lists")
|
||||
backyDB.CreateCollection(context.Background(), "logs")
|
||||
opts.DB = backyDB
|
||||
}
|
||||
|
||||
func getMongoConfigFromEnv(opts *ConfigOpts) error {
|
||||
mongoEnvFile, err := os.Open(opts.koanf.String("global.mongo.env"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mongoMap, mongoErr := godotenv.Parse(mongoEnvFile)
|
||||
if mongoErr != nil {
|
||||
return err
|
||||
}
|
||||
mongoPW, mongoPWFound := mongoMap["MONGO_PASSWORD"]
|
||||
if !mongoPWFound {
|
||||
return errors.New("MONGO_PASSWORD not set in " + mongoEnvFile.Name())
|
||||
}
|
||||
mongoUser, mongoUserFound := mongoMap["MONGO_USER"]
|
||||
if !mongoUserFound {
|
||||
return errors.New("MONGO_PASSWORD not set in " + mongoEnvFile.Name())
|
||||
}
|
||||
opts.koanf.Set(mongoConfigKey+".password", mongoPW)
|
||||
opts.koanf.Set(mongoConfigKey+".username", mongoUser)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMongoConfigKey(key string) string {
|
||||
return fmt.Sprintf("global.mongo.%s", key)
|
||||
}
|
@ -39,6 +39,7 @@ func (opts *ConfigOpts) SetupNotify() {
|
||||
}
|
||||
|
||||
for confName, cmdConfig := range opts.CmdConfigLists {
|
||||
|
||||
var services []notify.Notifier
|
||||
for _, id := range cmdConfig.Notifications {
|
||||
if !strings.Contains(id, ".") {
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
"golang.org/x/crypto/ssh/knownhosts"
|
||||
)
|
||||
|
||||
var PrivateKeyExtraInfoErr = errors.New("Private key may be encrypted. \nIf encrypted, make sure the password is specified correctly in the correct section: \n privatekeypassword: env:PR_KEY_PASS \n privatekeypassword: file:/path/to/password-file \n privatekeypassword: password (not recommended). \n ")
|
||||
var PrivateKeyExtraInfoErr = errors.New("Private key may be encrypted. \nIf encrypted, make sure the password is specified correctly in the correct section. This may be done in one of three ways: \n privatekeypassword: env:PR_KEY_PASS \n privatekeypassword: file:/path/to/password-file \n privatekeypassword: password (not recommended). \n ")
|
||||
var TS = strings.TrimSpace
|
||||
|
||||
// ConnectToSSHHost connects to a host by looking up the config values in the directory ~/.ssh/config
|
||||
@ -30,22 +30,26 @@ var TS = strings.TrimSpace
|
||||
// If any value is not found, defaults are used
|
||||
func (remoteConfig *Host) ConnectToSSHHost(opts *ConfigOpts) error {
|
||||
|
||||
// var sshClient *ssh.Client
|
||||
var connectErr error
|
||||
|
||||
if TS(remoteConfig.ConfigFilePath) == "" {
|
||||
remoteConfig.useDefaultConfig = true
|
||||
}
|
||||
khPath, khPathErr := GetKnownHosts(remoteConfig.KnownHostsFile)
|
||||
|
||||
khPathErr := remoteConfig.GetKnownHosts()
|
||||
|
||||
if khPathErr != nil {
|
||||
return khPathErr
|
||||
}
|
||||
|
||||
if remoteConfig.ClientConfig == nil {
|
||||
remoteConfig.ClientConfig = &ssh.ClientConfig{}
|
||||
}
|
||||
|
||||
var configFile *os.File
|
||||
|
||||
var sshConfigFileOpenErr error
|
||||
|
||||
if !remoteConfig.useDefaultConfig {
|
||||
var err error
|
||||
remoteConfig.ConfigFilePath, err = resolveDir(remoteConfig.ConfigFilePath)
|
||||
@ -72,9 +76,11 @@ func (remoteConfig *Host) ConnectToSSHHost(opts *ConfigOpts) error {
|
||||
}
|
||||
|
||||
err := remoteConfig.GetProxyJumpFromConfig(opts.Hosts)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if remoteConfig.ProxyHost != nil {
|
||||
for _, proxyHost := range remoteConfig.ProxyHost {
|
||||
err := proxyHost.GetProxyJumpConfig(opts.Hosts, opts)
|
||||
@ -86,10 +92,15 @@ func (remoteConfig *Host) ConnectToSSHHost(opts *ConfigOpts) error {
|
||||
}
|
||||
|
||||
remoteConfig.ClientConfig.Timeout = time.Second * 30
|
||||
|
||||
remoteConfig.GetPrivateKeyFileFromConfig()
|
||||
|
||||
remoteConfig.GetPort()
|
||||
|
||||
remoteConfig.GetHostName()
|
||||
|
||||
remoteConfig.CombineHostNameWithPort()
|
||||
|
||||
remoteConfig.GetSshUserFromConfig()
|
||||
|
||||
if remoteConfig.HostName == "" {
|
||||
@ -101,7 +112,7 @@ func (remoteConfig *Host) ConnectToSSHHost(opts *ConfigOpts) error {
|
||||
return err
|
||||
}
|
||||
|
||||
hostKeyCallback, err := knownhosts.New(khPath)
|
||||
hostKeyCallback, err := knownhosts.New(remoteConfig.KnownHostsFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create hostkeycallback function")
|
||||
}
|
||||
@ -128,12 +139,19 @@ func (remoteConfig *Host) ConnectToSSHHost(opts *ConfigOpts) error {
|
||||
}
|
||||
|
||||
func (remoteHost *Host) GetSshUserFromConfig() {
|
||||
|
||||
if TS(remoteHost.User) == "" {
|
||||
|
||||
remoteHost.User, _ = remoteHost.SSHConfigFile.SshConfigFile.Get(remoteHost.Host, "User")
|
||||
|
||||
if TS(remoteHost.User) == "" {
|
||||
|
||||
remoteHost.User = remoteHost.SSHConfigFile.DefaultUserSettings.Get(remoteHost.Host, "User")
|
||||
|
||||
if TS(remoteHost.User) == "" {
|
||||
|
||||
currentUser, _ := user.Current()
|
||||
|
||||
remoteHost.User = currentUser.Username
|
||||
}
|
||||
}
|
||||
@ -145,39 +163,60 @@ func (remoteHost *Host) GetAuthMethods(opts *ConfigOpts) error {
|
||||
var signer ssh.Signer
|
||||
var err error
|
||||
var privateKey []byte
|
||||
|
||||
remoteHost.Password = strings.TrimSpace(remoteHost.Password)
|
||||
|
||||
remoteHost.PrivateKeyPassword = strings.TrimSpace(remoteHost.PrivateKeyPassword)
|
||||
|
||||
remoteHost.PrivateKeyPath = strings.TrimSpace(remoteHost.PrivateKeyPath)
|
||||
|
||||
if remoteHost.PrivateKeyPath != "" {
|
||||
|
||||
privateKey, err = os.ReadFile(remoteHost.PrivateKeyPath)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteHost.PrivateKeyPassword, err = GetPrivateKeyPassword(remoteHost.PrivateKeyPassword, opts, opts.Logger)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if remoteHost.PrivateKeyPassword == "" {
|
||||
|
||||
signer, err = ssh.ParsePrivateKey(privateKey)
|
||||
|
||||
if err != nil {
|
||||
return errors.Errorf("Failed to open private key file %s: %v \n\n %v", remoteHost.PrivateKeyPath, err, PrivateKeyExtraInfoErr)
|
||||
}
|
||||
|
||||
remoteHost.ClientConfig.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
|
||||
} else {
|
||||
|
||||
signer, err = ssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(remoteHost.PrivateKeyPassword))
|
||||
|
||||
if err != nil {
|
||||
return errors.Errorf("Failed to open private key file %s: %v \n\n %v", remoteHost.PrivateKeyPath, err, PrivateKeyExtraInfoErr)
|
||||
}
|
||||
|
||||
remoteHost.ClientConfig.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
|
||||
}
|
||||
}
|
||||
|
||||
if remoteHost.Password == "" {
|
||||
|
||||
remoteHost.Password, err = GetPassword(remoteHost.Password, opts, opts.Logger)
|
||||
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
remoteHost.ClientConfig.Auth = append(remoteHost.ClientConfig.Auth, ssh.Password(remoteHost.Password))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -209,10 +248,17 @@ func (remoteHost *Host) GetPrivateKeyFileFromConfig() {
|
||||
func (remoteHost *Host) GetPort() {
|
||||
port := fmt.Sprintf("%d", remoteHost.Port)
|
||||
// port specifed?
|
||||
// port will be 0 if missing from backy config
|
||||
if port == "0" {
|
||||
// get port from specified SSH config file
|
||||
port, _ = remoteHost.SSHConfigFile.SshConfigFile.Get(remoteHost.Host, "Port")
|
||||
|
||||
if port == "" {
|
||||
|
||||
// get port from default SSH config file
|
||||
port = remoteHost.SSHConfigFile.DefaultUserSettings.Get(remoteHost.Host, "Port")
|
||||
|
||||
// set port to be default
|
||||
if port == "" {
|
||||
port = "22"
|
||||
}
|
||||
@ -223,10 +269,12 @@ func (remoteHost *Host) GetPort() {
|
||||
}
|
||||
|
||||
func (remoteHost *Host) CombineHostNameWithPort() {
|
||||
port := fmt.Sprintf(":%d", remoteHost.Port)
|
||||
if strings.HasSuffix(remoteHost.HostName, port) {
|
||||
|
||||
// if the port is already in the HostName, leave it
|
||||
if strings.HasSuffix(remoteHost.HostName, fmt.Sprintf(":%d", remoteHost.Port)) {
|
||||
return
|
||||
}
|
||||
|
||||
remoteHost.HostName = fmt.Sprintf("%s:%d", remoteHost.HostName, remoteHost.Port)
|
||||
}
|
||||
|
||||
@ -265,18 +313,22 @@ func (remoteHost *Host) ConnectThroughBastion(log zerolog.Logger) (*ssh.Client,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sClient := ssh.NewClient(ncc, chans, reqs)
|
||||
// sClient is an ssh client connected to the service host, through the bastion host.
|
||||
sClient := ssh.NewClient(ncc, chans, reqs)
|
||||
|
||||
return sClient, nil
|
||||
}
|
||||
|
||||
func GetKnownHosts(khPath string) (string, error) {
|
||||
|
||||
if TS(khPath) != "" {
|
||||
return resolveDir(khPath)
|
||||
// GetKnownHosts resolves the host's KnownHosts file if it is defined
|
||||
// if not defined, the default location for this file is used
|
||||
func (remotehHost *Host) GetKnownHosts() error {
|
||||
var knownHostsFileErr error
|
||||
if TS(remotehHost.KnownHostsFile) != "" {
|
||||
remotehHost.KnownHostsFile, knownHostsFileErr = resolveDir(remotehHost.KnownHostsFile)
|
||||
return knownHostsFileErr
|
||||
}
|
||||
return resolveDir("~/.ssh/known_hosts")
|
||||
remotehHost.KnownHostsFile, knownHostsFileErr = resolveDir("~/.ssh/known_hosts")
|
||||
return knownHostsFileErr
|
||||
}
|
||||
|
||||
func GetPrivateKeyPassword(key string, opts *ConfigOpts, log zerolog.Logger) (string, error) {
|
||||
@ -306,6 +358,7 @@ func GetPrivateKeyPassword(key string, opts *ConfigOpts, log zerolog.Logger) (st
|
||||
return prKeyPassword, nil
|
||||
}
|
||||
|
||||
// GetPassword gets any password
|
||||
func GetPassword(pass string, opts *ConfigOpts, log zerolog.Logger) (string, error) {
|
||||
|
||||
pass = strings.TrimSpace(pass)
|
||||
@ -370,7 +423,7 @@ func (remoteConfig *Host) GetProxyJumpConfig(hosts map[string]*Host, opts *Confi
|
||||
remoteConfig.useDefaultConfig = true
|
||||
}
|
||||
|
||||
khPath, khPathErr := GetKnownHosts(remoteConfig.KnownHostsFile)
|
||||
khPathErr := remoteConfig.GetKnownHosts()
|
||||
|
||||
if khPathErr != nil {
|
||||
return khPathErr
|
||||
@ -415,7 +468,7 @@ func (remoteConfig *Host) GetProxyJumpConfig(hosts map[string]*Host, opts *Confi
|
||||
}
|
||||
|
||||
// TODO: Add value/option to config for host key and add bool to check for host key
|
||||
hostKeyCallback, err := knownhosts.New(khPath)
|
||||
hostKeyCallback, err := knownhosts.New(remoteConfig.KnownHostsFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create hostkeycallback function: %v", err)
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ The following commands ran:
|
||||
{{end}}
|
||||
{{ end }}
|
||||
|
||||
{{ if .CmdOutput }}{{- range .CmdOutput }}Commad output for {{ .CmdName }}:
|
||||
{{ if .CmdOutput }}{{- range .CmdOutput }}Command output for {{ .CmdName }}:
|
||||
{{- range .Output}}
|
||||
{{ . }}
|
||||
{{ end }}{{ end }}
|
||||
|
@ -5,7 +5,7 @@ The following commands ran:
|
||||
- {{. -}}
|
||||
{{end}}
|
||||
|
||||
{{ if .CmdOutput }}{{- range .CmdOutput }}Commad output for {{ .CmdName }}:
|
||||
{{ if .CmdOutput }}{{- range .CmdOutput }}Command output for {{ .CmdName }}:
|
||||
{{- range .Output}}
|
||||
{{ . }}
|
||||
{{ end }}{{ end }}
|
||||
|
@ -9,30 +9,10 @@ import (
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/nikoksr/notify"
|
||||
"github.com/rs/zerolog"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type (
|
||||
CmdConfigSchema struct {
|
||||
ID primitive.ObjectID `bson:"_id,omitempty"`
|
||||
CmdList []string `bson:"command-list,omitempty"`
|
||||
Name string `bson:"name,omitempty"`
|
||||
}
|
||||
|
||||
CmdSchema struct {
|
||||
ID primitive.ObjectID `bson:"_id,omitempty"`
|
||||
Cmd string `bson:"cmd,omitempty"`
|
||||
Args []string `bson:"args,omitempty"`
|
||||
Host string `bson:"host,omitempty"`
|
||||
Dir string `bson:"dir,omitempty"`
|
||||
}
|
||||
|
||||
Schemas struct {
|
||||
CmdConfigSchema
|
||||
CmdSchema
|
||||
}
|
||||
|
||||
// Host defines a host to which to connect.
|
||||
// If not provided, the values will be looked up in the default ssh config files
|
||||
@ -63,17 +43,24 @@ type (
|
||||
}
|
||||
|
||||
Command struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
|
||||
// command to run
|
||||
Cmd string `yaml:"cmd"`
|
||||
|
||||
// Possible values: script, scriptFile
|
||||
// If blank, it is regualar command.
|
||||
Type string `yaml:"type"`
|
||||
// If blank, it is regular command.
|
||||
Type string `yaml:"type,omitempty"`
|
||||
|
||||
// host on which to run cmd
|
||||
Host *string `yaml:"host,omitempty"`
|
||||
|
||||
// Hooks are for running commands on certain events
|
||||
Hooks *Hooks `yaml:"hooks,omitempty"`
|
||||
|
||||
// hook refs are internal references of commands for each hook type
|
||||
hookRefs map[string]map[string]*Command
|
||||
|
||||
/*
|
||||
Shell specifies which shell to run the command in, if any.
|
||||
Not applicable when host is defined.
|
||||
@ -109,9 +96,12 @@ type (
|
||||
CmdList struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Cron string `yaml:"cron,omitempty"`
|
||||
RunCmdOnFailure string `yaml:"runCmdOnFailure,omitempty"`
|
||||
Order []string `yaml:"order,omitempty"`
|
||||
Notifications []string `yaml:"notifications,omitempty"`
|
||||
GetOutput bool `yaml:"getOutput,omitempty"`
|
||||
NotifyOnSuccess bool `yaml:"notifyOnSuccess,omitempty"`
|
||||
|
||||
NotifyConfig *notify.Notify
|
||||
}
|
||||
|
||||
@ -136,11 +126,11 @@ type (
|
||||
// Holds config file
|
||||
ConfigFilePath string
|
||||
|
||||
Schemas
|
||||
// for command list file
|
||||
CmdListFile string
|
||||
|
||||
DB *mongo.Database
|
||||
// use command lists using cron
|
||||
useCron bool
|
||||
cronEnabled bool
|
||||
// Holds commands to execute for the exec command
|
||||
executeCmds []string
|
||||
// Holds lists to execute for the backup command
|
||||
@ -205,4 +195,17 @@ type (
|
||||
Commands []string
|
||||
Hosts []string
|
||||
}
|
||||
|
||||
Hooks struct {
|
||||
Error []string `yaml:"error,omitempty"`
|
||||
Success []string `yaml:"success,omitempty"`
|
||||
Final []string `yaml:"final,omitempty"`
|
||||
}
|
||||
|
||||
CmdListResults struct {
|
||||
// name of the list
|
||||
ListName string
|
||||
// command that caused the list to fail
|
||||
ErrCmd string
|
||||
}
|
||||
)
|
||||
|
@ -56,10 +56,10 @@ func SetCmdsToSearch(cmds []string) BackyOptionFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// UseCron enables the execution of command lists at specified times
|
||||
func UseCron() BackyOptionFunc {
|
||||
// cronEnabled enables the execution of command lists at specified times
|
||||
func CronEnabled() BackyOptionFunc {
|
||||
return func(bco *ConfigOpts) {
|
||||
bco.useCron = true
|
||||
bco.cronEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,6 +197,7 @@ func resolveDir(path string) (string, error) {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// loadEnv loads a .env file from the config file directory
|
||||
func (opts *ConfigOpts) loadEnv() {
|
||||
envFileInConfigDir := fmt.Sprintf("%s/.env", path.Dir(opts.ConfigFilePath))
|
||||
var backyEnv map[string]string
|
||||
@ -208,6 +209,7 @@ func (opts *ConfigOpts) loadEnv() {
|
||||
opts.backyEnv = backyEnv
|
||||
}
|
||||
|
||||
// expandEnvVars expands environment variables with the env used in the config
|
||||
func expandEnvVars(backyEnv map[string]string, envVars []string) {
|
||||
|
||||
env := func(name string) string {
|
||||
@ -219,10 +221,15 @@ func expandEnvVars(backyEnv map[string]string, envVars []string) {
|
||||
return ""
|
||||
}
|
||||
|
||||
// parse env variables using new macros
|
||||
for indx, v := range envVars {
|
||||
if strings.Contains(v, "$") || (strings.Contains(v, "${") && strings.Contains(v, "}")) {
|
||||
if strings.HasPrefix(v, macroStart) && strings.HasSuffix(v, macroEnd) {
|
||||
if strings.HasPrefix(v, envMacroStart) {
|
||||
v = strings.TrimPrefix(v, envMacroStart)
|
||||
v = strings.TrimRight(v, macroEnd)
|
||||
out, _ := shell.Expand(v, env)
|
||||
envVars[indx] = out
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user