Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 995e4f91b5 | |||
| fa62bc1ec6 | |||
| 2766ac997a | |||
| d0b4c0b9df | |||
| beabe9f041 | |||
| 3a038eeab4 | |||
| a95f903e72 | |||
| 61add23efb | |||
| b228fca371 | |||
| e5a9003ed6 | |||
| 803b039849 | |||
| 2824f8c703 | |||
| cfc00262ff |
6
.changes/v0.11.1.md
Normal file
6
.changes/v0.11.1.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
## v0.11.1 - 2025-12-08
|
||||||
|
### Added
|
||||||
|
* Started integration testing
|
||||||
|
### Changed
|
||||||
|
* inject ssh env vars by apppending them to the script/command if SSH setenv fails
|
||||||
|
* fix local command injection by running in a shell
|
||||||
3
.changes/v0.11.2.md
Normal file
3
.changes/v0.11.2.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
## v0.11.2 - 2025-12-27
|
||||||
|
### Added
|
||||||
|
* Upgraded GoCron; web ui viewer for viewing cron jobs
|
||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
- run: git fetch --force --tags
|
- run: git fetch --force --tags
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
go-version: '1.24'
|
||||||
cache: true
|
cache: true
|
||||||
# More assembly might be required: Docker logins, GPG, etc. It all depends
|
# More assembly might be required: Docker logins, GPG, etc. It all depends
|
||||||
# on your needs.
|
# on your needs.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
steps:
|
steps:
|
||||||
golang:
|
golang:
|
||||||
image: golang:1.23
|
image: golang:1.24
|
||||||
commands:
|
commands:
|
||||||
- go install github.com/goreleaser/goreleaser/v2@v2.7.0
|
- go install github.com/goreleaser/goreleaser/v2@v2.7.0
|
||||||
- goreleaser release -f .goreleaser/gitea.yml --release-notes=".changes/$(go run backy.go version -V).md"
|
- goreleaser release -f .goreleaser/gitea.yml --release-notes=".changes/$(go run backy.go version -V).md"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
steps:
|
steps:
|
||||||
build:
|
build:
|
||||||
image: golang
|
image: golang:1.24
|
||||||
commands:
|
commands:
|
||||||
- go build
|
- go build
|
||||||
- go test
|
- go test
|
||||||
19
.woodpecker/github/github.yml
Normal file
19
.woodpecker/github/github.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
steps:
|
||||||
|
golang:
|
||||||
|
image: golang:1.24
|
||||||
|
commands:
|
||||||
|
- go install github.com/goreleaser/goreleaser/v2@v2.7.0
|
||||||
|
- goreleaser release -f .goreleaser/github.yml --release-notes=".changes/$(go run backy.go version -V).md"
|
||||||
|
environment:
|
||||||
|
GITHUB_TOKEN:
|
||||||
|
from_secret: github_token
|
||||||
|
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
# release:
|
||||||
|
# image: goreleaser/goreleaser
|
||||||
|
# commands:
|
||||||
|
|
||||||
|
when:
|
||||||
|
- event: tag
|
||||||
|
branch: master
|
||||||
11
CHANGELOG.md
11
CHANGELOG.md
@@ -6,6 +6,17 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
|||||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||||
|
|
||||||
|
|
||||||
|
## v0.11.2 - 2025-12-27
|
||||||
|
### Added
|
||||||
|
* Upgraded GoCron; web ui viewer for viewing cron jobs
|
||||||
|
|
||||||
|
## v0.11.1 - 2025-12-08
|
||||||
|
### Added
|
||||||
|
* Started integration testing
|
||||||
|
### Changed
|
||||||
|
* inject ssh env vars by apppending them to the script/command if SSH setenv fails
|
||||||
|
* fix local command injection by running in a shell
|
||||||
|
|
||||||
## v0.11.0 - 2025-11-24
|
## v0.11.0 - 2025-11-24
|
||||||
### Added
|
### Added
|
||||||
* feat: Package operation `versionCheck` supports regular expressions (see [regexp](https://pkg.go.dev/regexp) package for docs)
|
* feat: Package operation `versionCheck` supports regular expressions (see [regexp](https://pkg.go.dev/regexp) package for docs)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
const versionStr = "0.11.0"
|
const versionStr = "0.11.2"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
versionCmd = &cobra.Command{
|
versionCmd = &cobra.Command{
|
||||||
|
|||||||
@@ -12,4 +12,4 @@ External directives are for including data that should not be in the config file
|
|||||||
|
|
||||||
See the docs of each command if the field is supported.
|
See the docs of each command if the field is supported.
|
||||||
|
|
||||||
If the file path does not begin with a `/`, the config file's directory will be used as the starting point.
|
If the file path does not begin with the root directory marker, usually `/`, the config file's directory will be used as the starting point.
|
||||||
49
docs/content/config/gocron.md
Normal file
49
docs/content/config/gocron.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
title: "Configuring Cron"
|
||||||
|
weight: 3
|
||||||
|
description: >
|
||||||
|
Use Cron to run lists at a specified time.
|
||||||
|
---
|
||||||
|
|
||||||
|
Backy provides an easy-to-use way to execute commands at a specified time.
|
||||||
|
|
||||||
|
Adding `cron: 0 0 1 * * *` to a `cmdLists` object will schedule the list at 1 in the morning. See [https://crontab.guru/](https://crontab.guru/) for reference.
|
||||||
|
|
||||||
|
GoCron allows one to configure a server to view the jobs in the scheduler. See [GoCron UI GitHub](https://github.com/go-co-op/gocron-ui).
|
||||||
|
GoCron can be configured or left alone for defaults.
|
||||||
|
|
||||||
|
GoCron configuration:
|
||||||
|
|
||||||
|
| key | description | type | required | default
|
||||||
|
| --- | --- | --- | --- | ---
|
||||||
|
| `bindAddress` | Interface's IP to bind to. Must not contain port. | `string` | no | `:port`
|
||||||
|
| `port` | Port to use. | `int` | no | `8888`
|
||||||
|
| `useSeconds` | Whether to parse the second cron field. | `bool` | no | `false`
|
||||||
|
|
||||||
|
|
||||||
|
```yaml {lineNos="true" wrap="true" title="yaml"}
|
||||||
|
goCron:
|
||||||
|
bindAddress: "0.0.0.0"
|
||||||
|
port: 8888
|
||||||
|
useSeconds: true
|
||||||
|
|
||||||
|
cmdLists:
|
||||||
|
docker-container-backup: # this can be any name you want
|
||||||
|
# all commands have to be defined
|
||||||
|
order:
|
||||||
|
- stop-docker-container
|
||||||
|
- backup-docker-container-script
|
||||||
|
- shell-cmd
|
||||||
|
- hostname
|
||||||
|
- start-docker-container
|
||||||
|
notifications:
|
||||||
|
- matrix.id
|
||||||
|
name: backup-some-container
|
||||||
|
cron: "0 0 1 * * *"
|
||||||
|
hostname:
|
||||||
|
name: hostname
|
||||||
|
order:
|
||||||
|
- hostname
|
||||||
|
notifications:
|
||||||
|
- mail.prod-email
|
||||||
|
```
|
||||||
14
go.mod
14
go.mod
@@ -1,13 +1,12 @@
|
|||||||
module git.andrewnw.xyz/CyberShell/backy
|
module git.andrewnw.xyz/CyberShell/backy
|
||||||
|
|
||||||
go 1.23.0
|
go 1.24.0
|
||||||
|
|
||||||
toolchain go1.23.7
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0
|
||||||
github.com/dmarkham/enumer v1.5.11
|
github.com/dmarkham/enumer v1.5.11
|
||||||
github.com/go-co-op/gocron v1.37.0
|
github.com/go-co-op/gocron-ui v0.2.0
|
||||||
|
github.com/go-co-op/gocron/v2 v2.19.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hashicorp/vault/api v1.20.0
|
github.com/hashicorp/vault/api v1.20.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
@@ -50,6 +49,8 @@ require (
|
|||||||
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
|
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
@@ -60,6 +61,7 @@ require (
|
|||||||
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
|
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
|
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
|
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
@@ -75,18 +77,18 @@ require (
|
|||||||
github.com/philhofer/fwd v1.2.0 // indirect
|
github.com/philhofer/fwd v1.2.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
|
github.com/rs/cors v1.11.1 // indirect
|
||||||
github.com/rs/xid v1.6.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/stretchr/testify v1.10.0 // indirect
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
github.com/tidwall/gjson v1.18.0 // indirect
|
github.com/tidwall/gjson v1.18.0 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/tidwall/sjson v1.2.5 // indirect
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
github.com/tinylib/msgp v1.3.0 // indirect
|
github.com/tinylib/msgp v1.3.0 // indirect
|
||||||
go.mau.fi/util v0.8.8 // indirect
|
go.mau.fi/util v0.8.8 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // indirect
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
|
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
|
||||||
golang.org/x/mod v0.26.0 // indirect
|
golang.org/x/mod v0.26.0 // indirect
|
||||||
|
|||||||
38
go.sum
38
go.sum
@@ -26,7 +26,6 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
|
|||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/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=
|
||||||
@@ -36,8 +35,10 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||||||
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=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
|
github.com/go-co-op/gocron-ui v0.2.0 h1:f4JqnIfgzeWYgJcNT5ukn86mnyewbXswsa1To1XQroc=
|
||||||
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
|
github.com/go-co-op/gocron-ui v0.2.0/go.mod h1:QvFWbaoVY2fHVzQ3DvYdfFTSz22PaKFtNQuT7rXnj4Y=
|
||||||
|
github.com/go-co-op/gocron/v2 v2.19.0 h1:OKf2y6LXPs/BgBI2fl8PxUpNAI1DA9Mg+hSeGOS38OU=
|
||||||
|
github.com/go-co-op/gocron/v2 v2.19.0/go.mod h1:5lEiCKk1oVJV39Zg7/YG10OnaVrDAV5GGR6O0663k6U=
|
||||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
|
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
|
||||||
@@ -54,9 +55,12 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
|
|||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@@ -84,6 +88,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
|
|||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||||
|
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
|
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
|
||||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||||
@@ -103,13 +109,8 @@ github.com/knadh/koanf/v2 v2.2.2 h1:ghbduIkpFui3L587wavneC9e3WIliCgiCgdxYO/wd7A=
|
|||||||
github.com/knadh/koanf/v2 v2.2.2/go.mod h1:abWQc0cBXLSF/PSOMCB/SK+T13NXDsPvOksbpi5e/9Q=
|
github.com/knadh/koanf/v2 v2.2.2/go.mod h1:abWQc0cBXLSF/PSOMCB/SK+T13NXDsPvOksbpi5e/9Q=
|
||||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
@@ -139,7 +140,6 @@ github.com/pascaldekloe/name v1.0.1 h1:9lnXOHeqeHHnWLbKfH6X98+4+ETVqFqxN09UXSjcM
|
|||||||
github.com/pascaldekloe/name v1.0.1/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM=
|
github.com/pascaldekloe/name v1.0.1/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM=
|
||||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw=
|
github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw=
|
||||||
@@ -148,10 +148,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
|
||||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
|
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||||
|
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||||
@@ -167,15 +167,12 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
|||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
@@ -191,9 +188,8 @@ github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57
|
|||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c=
|
go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c=
|
||||||
go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c=
|
go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c=
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
@@ -281,10 +277,8 @@ golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
|||||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -349,10 +349,14 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
|||||||
}
|
}
|
||||||
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
||||||
localCMD = exec.Command("/bin/sh", "-c", ArgsStr)
|
localCMD = exec.Command("/bin/sh", "-c", ArgsStr)
|
||||||
|
} else {
|
||||||
|
if command.Env != "" || command.Environment != nil {
|
||||||
|
localCMD = exec.Command("/bin/sh", "-c", ArgsStr)
|
||||||
} else {
|
} else {
|
||||||
localCMD = exec.Command(command.Cmd, command.Args...)
|
localCMD = exec.Command(command.Cmd, command.Args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if command.Type == UserCommandType {
|
if command.Type == UserCommandType {
|
||||||
if command.UserOperation == "password" {
|
if command.UserOperation == "password" {
|
||||||
|
|||||||
@@ -169,6 +169,8 @@ func (opts *ConfigOpts) ParseConfigurationFile() *ConfigOpts {
|
|||||||
logging.ExitWithMSG("No cron fields detected in any command lists", 1, nil)
|
logging.ExitWithMSG("No cron fields detected in any command lists", 1, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unmarshalConfigIntoStruct(backyKoanf, "goCron", &opts.GoCron, opts.Logger)
|
||||||
|
|
||||||
if err := processCmds(opts); err != nil {
|
if err := processCmds(opts); err != nil {
|
||||||
logging.ExitWithMSG(err.Error(), 1, &opts.Logger)
|
logging.ExitWithMSG(err.Error(), 1, &opts.Logger)
|
||||||
}
|
}
|
||||||
@@ -425,6 +427,9 @@ func generateFileFetchErrorString(file, fileType string, err error) string {
|
|||||||
func validateCommandLists(opts *ConfigOpts) {
|
func validateCommandLists(opts *ConfigOpts) {
|
||||||
var cmdNotFoundSliceErr []error
|
var cmdNotFoundSliceErr []error
|
||||||
for cmdListName, cmdList := range opts.CmdConfigLists {
|
for cmdListName, cmdList := range opts.CmdConfigLists {
|
||||||
|
if cmdList.Name == "" {
|
||||||
|
cmdList.Name = cmdListName
|
||||||
|
}
|
||||||
// if cron is enabled and cron is not set, delete the list
|
// if cron is enabled and cron is not set, delete the list
|
||||||
if opts.cronEnabled && strings.TrimSpace(cmdList.Cron) == "" {
|
if opts.cronEnabled && strings.TrimSpace(cmdList.Cron) == "" {
|
||||||
opts.Logger.Debug().Str("cron", "enabled").Str("list", cmdListName).Msg("cron not set, deleting list")
|
opts.Logger.Debug().Str("cron", "enabled").Str("list", cmdListName).Msg("cron not set, deleting list")
|
||||||
|
|||||||
@@ -6,30 +6,73 @@ package backy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.andrewnw.xyz/CyberShell/backy/pkg/logging"
|
"git.andrewnw.xyz/CyberShell/backy/pkg/logging"
|
||||||
"github.com/go-co-op/gocron"
|
"github.com/go-co-op/gocron-ui/server"
|
||||||
|
"github.com/go-co-op/gocron/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var defaultPort = 8888
|
||||||
|
|
||||||
func (opts *ConfigOpts) Cron() {
|
func (opts *ConfigOpts) Cron() {
|
||||||
s := gocron.NewScheduler(time.Local)
|
s, _ := gocron.NewScheduler(gocron.WithLocation(time.Local))
|
||||||
s.TagsUnique()
|
defer func() { _ = s.Shutdown() }()
|
||||||
|
opts.Logger.Info().Msg("Starting cron mode...")
|
||||||
|
s.Start()
|
||||||
cmdLists := opts.CmdConfigLists
|
cmdLists := opts.CmdConfigLists
|
||||||
for _, config := range cmdLists {
|
for _, config := range cmdLists {
|
||||||
|
|
||||||
cron := strings.TrimSpace(config.Cron)
|
cron := strings.TrimSpace(config.Cron)
|
||||||
if cron != "" {
|
if cron != "" {
|
||||||
opts.Logger.Info().Str("Scheduling cron list", config.Name).Str("Time", cron).Send()
|
job, err := s.NewJob(
|
||||||
_, err := s.CronWithSeconds(cron).Tag(config.Name).Do(func(cron string) {
|
gocron.CronJob(cron, opts.GoCron.UseSeconds),
|
||||||
opts.RunListConfig(cron)
|
gocron.NewTask(
|
||||||
}, cron)
|
func(cronStr string) {
|
||||||
|
opts.RunListConfig(cronStr)
|
||||||
|
},
|
||||||
|
cron,
|
||||||
|
),
|
||||||
|
gocron.WithName(config.Name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.ExitWithMSG(fmt.Sprintf("error: %v", err), 1, &opts.Logger)
|
logging.ExitWithMSG(fmt.Sprintf("error: %v", err), 1, &opts.Logger)
|
||||||
}
|
}
|
||||||
|
nextRun, _ := job.NextRun()
|
||||||
|
opts.Logger.Info().Str("Scheduling cron list", config.Name).Str("Time", cron).Str("Next run", nextRun.String()).Send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
opts.Logger.Info().Msg("Starting cron mode...")
|
|
||||||
s.StartBlocking()
|
// start the web UI server
|
||||||
|
if opts.GoCron.BindAddress == "" {
|
||||||
|
if opts.GoCron.Port == 0 {
|
||||||
|
opts.GoCron.BindAddress = ":8888"
|
||||||
|
} else {
|
||||||
|
opts.GoCron.BindAddress = fmt.Sprintf(":%d", opts.GoCron.Port)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if opts.GoCron.Port != 0 {
|
||||||
|
opts.GoCron.BindAddress = fmt.Sprintf("%s:%d", opts.GoCron.BindAddress, opts.GoCron.Port)
|
||||||
|
} else {
|
||||||
|
opts.GoCron.BindAddress = fmt.Sprintf("%s:%d", opts.GoCron.BindAddress, defaultPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// consensus := externalip.DefaultConsensus(nil, nil)
|
||||||
|
|
||||||
|
// By default Ipv4 or Ipv6 is returned,
|
||||||
|
// use the function below to limit yourself to IPv4,
|
||||||
|
// or pass in `6` instead to limit yourself to IPv6.
|
||||||
|
// consensus.UseIPProtocol(4)
|
||||||
|
|
||||||
|
// Get your IP,
|
||||||
|
// which is never <nil> when err is <nil>.
|
||||||
|
// ip, err := consensus.ExternalIP()
|
||||||
|
// if err == nil {
|
||||||
|
// fmt.Println(ip.String()) // print IPv4/IPv6 in string format
|
||||||
|
// }
|
||||||
|
srv := server.NewServer(s, opts.GoCron.Port)
|
||||||
|
// srv := server.NewServer(scheduler, 8080, server.WithTitle("My Custom Scheduler")) // with custom title if you want to customize the title of the UI (optional)
|
||||||
|
opts.Logger.Info().Msgf("GoCron UI available at http://%s", opts.GoCron.BindAddress)
|
||||||
|
opts.Logger.Fatal().Msg(http.ListenAndServe(opts.GoCron.BindAddress, srv.Router).Error())
|
||||||
|
select {} // wait forever
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +1,67 @@
|
|||||||
package backy
|
package backy
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"testing"
|
// "testing"
|
||||||
"time"
|
// "time"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func TestAddingMetricsForCommand(t *testing.T) {
|
// func TestAddingMetricsForCommand(t *testing.T) {
|
||||||
|
|
||||||
// Create a new MetricFile
|
// // Create a new MetricFile
|
||||||
metricFile := NewMetricsFromFile("test_metrics.json")
|
// metricFile := NewMetricsFromFile("test_metrics.json")
|
||||||
|
|
||||||
metricFile, err := LoadMetricsFromFile(metricFile.Filename)
|
// metricFile, err := LoadMetricsFromFile(metricFile.Filename)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
t.Errorf("Failed to load metrics from file: %v", err)
|
// t.Errorf("Failed to load metrics from file: %v", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Add metrics for a command
|
// // Add metrics for a command
|
||||||
commandName := "test_command"
|
// commandName := "test_command"
|
||||||
if _, exists := metricFile.CommandMetrics[commandName]; !exists {
|
// if _, exists := metricFile.CommandMetrics[commandName]; !exists {
|
||||||
metricFile.CommandMetrics[commandName] = NewMetrics()
|
// metricFile.CommandMetrics[commandName] = NewMetrics()
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Update the metrics for the command
|
// // Update the metrics for the command
|
||||||
executionTime := 1.8 // Example execution time in seconds
|
// executionTime := 1.8 // Example execution time in seconds
|
||||||
success := true // Example success status
|
// success := true // Example success status
|
||||||
metricFile.CommandMetrics[commandName].Update(success, executionTime, time.Now())
|
// metricFile.CommandMetrics[commandName].Update(success, executionTime, time.Now())
|
||||||
|
|
||||||
// Check if the metrics were updated correctly
|
// // Check if the metrics were updated correctly
|
||||||
if metricFile.CommandMetrics[commandName].SuccessfulExecutions > 50 {
|
// if metricFile.CommandMetrics[commandName].SuccessfulExecutions > 50 {
|
||||||
t.Errorf("Expected 1 successful execution, got %d", metricFile.CommandMetrics[commandName].SuccessfulExecutions)
|
// t.Errorf("Expected 1 successful execution, got %d", metricFile.CommandMetrics[commandName].SuccessfulExecutions)
|
||||||
}
|
// }
|
||||||
if metricFile.CommandMetrics[commandName].TotalExecutions > 50 {
|
// if metricFile.CommandMetrics[commandName].TotalExecutions > 50 {
|
||||||
t.Errorf("Expected 1 total execution, got %d", metricFile.CommandMetrics[commandName].TotalExecutions)
|
// t.Errorf("Expected 1 total execution, got %d", metricFile.CommandMetrics[commandName].TotalExecutions)
|
||||||
}
|
// }
|
||||||
// if metricFile.CommandMetrics[commandName].TotalExecutionTime != executionTime {
|
// // if metricFile.CommandMetrics[commandName].TotalExecutionTime != executionTime {
|
||||||
// t.Errorf("Expected execution time %f, got %f", executionTime, metricFile.CommandMetrics[commandName].TotalExecutionTime)
|
// // t.Errorf("Expected execution time %f, got %f", executionTime, metricFile.CommandMetrics[commandName].TotalExecutionTime)
|
||||||
// }
|
// // }
|
||||||
|
|
||||||
err = metricFile.SaveToFile()
|
// err = metricFile.SaveToFile()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
t.Errorf("Failed to save metrics to file: %v", err)
|
// t.Errorf("Failed to save metrics to file: %v", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
listName := "test_list"
|
// listName := "test_list"
|
||||||
if _, exists := metricFile.ListMetrics[listName]; !exists {
|
// if _, exists := metricFile.ListMetrics[listName]; !exists {
|
||||||
metricFile.ListMetrics[listName] = NewMetrics()
|
// metricFile.ListMetrics[listName] = NewMetrics()
|
||||||
}
|
// }
|
||||||
// Update the metrics for the list
|
// // Update the metrics for the list
|
||||||
metricFile.ListMetrics[listName].Update(success, executionTime, time.Now())
|
// metricFile.ListMetrics[listName].Update(success, executionTime, time.Now())
|
||||||
if metricFile.ListMetrics[listName].SuccessfulExecutions > 50 {
|
// if metricFile.ListMetrics[listName].SuccessfulExecutions > 50 {
|
||||||
t.Errorf("Expected 1 successful execution for list, got %d", metricFile.ListMetrics[listName].SuccessfulExecutions)
|
// t.Errorf("Expected 1 successful execution for list, got %d", metricFile.ListMetrics[listName].SuccessfulExecutions)
|
||||||
}
|
// }
|
||||||
if metricFile.ListMetrics[listName].TotalExecutions > 50 {
|
// if metricFile.ListMetrics[listName].TotalExecutions > 50 {
|
||||||
t.Errorf("Expected 1 total execution for list, got %d", metricFile.ListMetrics[listName].TotalExecutions)
|
// t.Errorf("Expected 1 total execution for list, got %d", metricFile.ListMetrics[listName].TotalExecutions)
|
||||||
}
|
// }
|
||||||
// if metricFile.ListMetrics[listName].TotalExecutionTime > executionTime {
|
// // if metricFile.ListMetrics[listName].TotalExecutionTime > executionTime {
|
||||||
// t.Errorf("Expected execution time %f for list, got %f", executionTime, metricFile.ListMetrics[listName].TotalExecutionTime)
|
// // t.Errorf("Expected execution time %f for list, got %f", executionTime, metricFile.ListMetrics[listName].TotalExecutionTime)
|
||||||
// }
|
// // }
|
||||||
|
|
||||||
// Save the metrics to a file
|
// // Save the metrics to a file
|
||||||
err = metricFile.SaveToFile()
|
// err = metricFile.SaveToFile()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
t.Errorf("Failed to save metrics to file: %v", err)
|
// t.Errorf("Failed to save metrics to file: %v", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -471,9 +471,6 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
|
|||||||
}
|
}
|
||||||
defer commandSession.Close()
|
defer commandSession.Close()
|
||||||
|
|
||||||
// Inject environment variables
|
|
||||||
injectEnvIntoSSH(envVars, commandSession, opts, cmdCtxLogger)
|
|
||||||
|
|
||||||
// Set output writers
|
// Set output writers
|
||||||
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
|
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
|
||||||
if IsCmdStdOutEnabled() {
|
if IsCmdStdOutEnabled() {
|
||||||
@@ -482,6 +479,16 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
|
|||||||
commandSession.Stdout = cmdOutWriters
|
commandSession.Stdout = cmdOutWriters
|
||||||
commandSession.Stderr = cmdOutWriters
|
commandSession.Stderr = cmdOutWriters
|
||||||
|
|
||||||
|
command.ArgStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
||||||
|
//! environment vars and SSH:
|
||||||
|
//? skip if commandType is not *script*?
|
||||||
|
//? option to use SSH setenv or add to beginning?
|
||||||
|
// Inject environment variables
|
||||||
|
err = injectEnvIntoSSH(envVars, commandSession, opts, cmdCtxLogger)
|
||||||
|
if err != nil {
|
||||||
|
cmdCtxLogger.Info().Err(fmt.Errorf("%v; appending env variables to beginning of command", err)).Send()
|
||||||
|
command.ArgStr = prependEnvVarsToCommand(envVars, opts, command.Cmd, command.Args, cmdCtxLogger)
|
||||||
|
}
|
||||||
// Handle command execution based on type
|
// Handle command execution based on type
|
||||||
switch command.Type {
|
switch command.Type {
|
||||||
case ScriptCommandType:
|
case ScriptCommandType:
|
||||||
@@ -495,11 +502,15 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
|
|||||||
return remoteHostPackageExecutor.RunCmdOnHost(command, commandSession, cmdCtxLogger, cmdOutBuf)
|
return remoteHostPackageExecutor.RunCmdOnHost(command, commandSession, cmdCtxLogger, cmdOutBuf)
|
||||||
default:
|
default:
|
||||||
if command.Shell != "" {
|
if command.Shell != "" {
|
||||||
ArgsStr = fmt.Sprintf("%s -c '%s %s'", command.Shell, command.Cmd, ArgsStr)
|
command.ArgStr = fmt.Sprintf("%s -c '%s'", command.Shell, command.ArgStr)
|
||||||
} else {
|
} else {
|
||||||
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
if command.Env == "" && command.Environment == nil {
|
||||||
|
// command.ArgStr = fmt.Sprintf("/bin/sh -c '%s'", command.ArgStr)
|
||||||
|
command.ArgStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
||||||
}
|
}
|
||||||
cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send()
|
|
||||||
|
}
|
||||||
|
// cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send()
|
||||||
|
|
||||||
if command.Type == UserCommandType && command.UserOperation == "password" {
|
if command.Type == UserCommandType && command.UserOperation == "password" {
|
||||||
// cmdCtxLogger.Debug().Msgf("adding stdin")
|
// cmdCtxLogger.Debug().Msgf("adding stdin")
|
||||||
@@ -522,6 +533,7 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
|
|||||||
}
|
}
|
||||||
|
|
||||||
ArgsStr = fmt.Sprintf("cat %s | chpasswd", passFilePath)
|
ArgsStr = fmt.Sprintf("cat %s | chpasswd", passFilePath)
|
||||||
|
command.ArgStr = ArgsStr
|
||||||
defer passFile.Close()
|
defer passFile.Close()
|
||||||
|
|
||||||
rmFileFunc := func() {
|
rmFileFunc := func() {
|
||||||
@@ -530,7 +542,7 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
|
|||||||
|
|
||||||
defer rmFileFunc()
|
defer rmFileFunc()
|
||||||
}
|
}
|
||||||
if err := commandSession.Run(ArgsStr); err != nil {
|
if err := commandSession.Run(command.ArgStr); err != nil {
|
||||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running command: %w", err)
|
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running command: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,11 +618,10 @@ func checkPackageVersion(cmdCtxLogger zerolog.Logger, command *Command, commandS
|
|||||||
for _, v := range command.Args {
|
for _, v := range command.Args {
|
||||||
ArgsStr += fmt.Sprintf(" %s", v)
|
ArgsStr += fmt.Sprintf(" %s", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var cmdOut []byte
|
var cmdOut []byte
|
||||||
|
|
||||||
if cmdOut, err = commandSession.CombinedOutput(ArgsStr); err != nil {
|
if cmdOut, err = commandSession.CombinedOutput(command.ArgStr); err != nil {
|
||||||
cmdOutBuf.Write(cmdOut)
|
cmdOutBuf.Write(cmdOut)
|
||||||
|
|
||||||
_, parseErr := parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf)
|
_, parseErr := parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf)
|
||||||
@@ -673,6 +684,11 @@ func (command *Command) runScriptFile(session *ssh.Session, cmdCtxLogger zerolog
|
|||||||
func (command *Command) prepareScriptBuffer() (*bytes.Buffer, error) {
|
func (command *Command) prepareScriptBuffer() (*bytes.Buffer, error) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
for _, envVar := range command.Environment {
|
||||||
|
buffer.WriteString(fmt.Sprintf("export %s", envVar))
|
||||||
|
buffer.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
if command.ScriptEnvFile != "" {
|
if command.ScriptEnvFile != "" {
|
||||||
envBuffer, err := readFileToBuffer(command.ScriptEnvFile)
|
envBuffer, err := readFileToBuffer(command.ScriptEnvFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -694,6 +710,11 @@ func (command *Command) prepareScriptBuffer() (*bytes.Buffer, error) {
|
|||||||
func (command *Command) prepareScriptFileBuffer() (*bytes.Buffer, error) {
|
func (command *Command) prepareScriptFileBuffer() (*bytes.Buffer, error) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
for _, envVar := range command.Environment {
|
||||||
|
buffer.WriteString(fmt.Sprintf("export %s", envVar))
|
||||||
|
buffer.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
// Handle script environment file
|
// Handle script environment file
|
||||||
if command.ScriptEnvFile != "" {
|
if command.ScriptEnvFile != "" {
|
||||||
envBuffer, err := readFileToBuffer(command.ScriptEnvFile)
|
envBuffer, err := readFileToBuffer(command.ScriptEnvFile)
|
||||||
@@ -836,7 +857,7 @@ func (r RemoteHostPackageExecutor) RunCmdOnHost(command *Command, commandSession
|
|||||||
return checkPackageVersion(cmdCtxLogger, command, commandSession, cmdOutBuf)
|
return checkPackageVersion(cmdCtxLogger, command, commandSession, cmdOutBuf)
|
||||||
}
|
}
|
||||||
if command.Shell != "" {
|
if command.Shell != "" {
|
||||||
ArgsStr = fmt.Sprintf("%s -c '%s %s'", command.Shell, command.Cmd, ArgsStr)
|
ArgsStr = fmt.Sprintf("%s -c '%s'", command.Shell, command.ArgStr)
|
||||||
} else {
|
} else {
|
||||||
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ type (
|
|||||||
RemoteHost *Host `yaml:"-"`
|
RemoteHost *Host `yaml:"-"`
|
||||||
|
|
||||||
Args []string `yaml:"args,omitempty"`
|
Args []string `yaml:"args,omitempty"`
|
||||||
|
ArgStr string
|
||||||
|
|
||||||
Dir *string `yaml:"dir,omitempty"`
|
Dir *string `yaml:"dir,omitempty"`
|
||||||
|
|
||||||
@@ -169,6 +170,12 @@ type (
|
|||||||
Type string `yaml:"type"`
|
Type string `yaml:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GoCronOpts struct {
|
||||||
|
BindAddress string `yaml:"bindAddress"`
|
||||||
|
UseSeconds bool `yaml:"useSeconds"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
}
|
||||||
|
|
||||||
ConfigOpts struct {
|
ConfigOpts struct {
|
||||||
// Cmds holds the commands for a list.
|
// Cmds holds the commands for a list.
|
||||||
// Key is the name of the command,
|
// Key is the name of the command,
|
||||||
@@ -182,6 +189,8 @@ type (
|
|||||||
// key is the host.
|
// key is the host.
|
||||||
Hosts map[string]*Host `yaml:"hosts"`
|
Hosts map[string]*Host `yaml:"hosts"`
|
||||||
|
|
||||||
|
GoCron GoCronOpts `yaml:"goCron:"`
|
||||||
|
|
||||||
Logger zerolog.Logger
|
Logger zerolog.Logger
|
||||||
|
|
||||||
// Global log level
|
// Global log level
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/knadh/koanf/v2"
|
"github.com/knadh/koanf/v2"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"mvdan.cc/sh/v3/shell"
|
"mvdan.cc/sh/v3/shell"
|
||||||
)
|
)
|
||||||
@@ -99,7 +100,7 @@ func NewConfigOptions(configFilePath string, opts ...BackyOptionFunc) *ConfigOpt
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func injectEnvIntoSSH(envVarsToInject environmentVars, process *ssh.Session, opts *ConfigOpts, log zerolog.Logger) {
|
func injectEnvIntoSSH(envVarsToInject environmentVars, session *ssh.Session, opts *ConfigOpts, log zerolog.Logger) error {
|
||||||
if envVarsToInject.file != "" {
|
if envVarsToInject.file != "" {
|
||||||
envPath, envPathErr := getFullPathWithHomeDir(envVarsToInject.file)
|
envPath, envPathErr := getFullPathWithHomeDir(envVarsToInject.file)
|
||||||
if envPathErr != nil {
|
if envPathErr != nil {
|
||||||
@@ -113,31 +114,31 @@ func injectEnvIntoSSH(envVarsToInject environmentVars, process *ssh.Session, opt
|
|||||||
|
|
||||||
envMap, err := godotenv.Parse(file)
|
envMap, err := godotenv.Parse(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Str("envFile", envPath).Err(err).Send()
|
log.Fatal().Str("envFile", envPath).Err(err).Send()
|
||||||
goto errEnvFile
|
|
||||||
}
|
}
|
||||||
for key, val := range envMap {
|
for key, val := range envMap {
|
||||||
err = process.Setenv(key, getExternalConfigDirectiveValue(val, opts, AllowedExternalDirectiveVault))
|
err = session.Setenv(key, getExternalConfigDirectiveValue(val, opts, AllowedExternalDirectiveVault))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Send()
|
log.Info().Err(err).Send()
|
||||||
|
return fmt.Errorf("failed to set environment variable %s: %w", val, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
errEnvFile:
|
|
||||||
// fmt.Printf("%v", envVarsToInject.env)
|
// fmt.Printf("%v", envVarsToInject.env)
|
||||||
for _, envVal := range envVarsToInject.env {
|
for _, envVal := range envVarsToInject.env {
|
||||||
// don't append env Vars for Backy
|
// don't append env Vars for Backy
|
||||||
if strings.Contains(envVal, "=") {
|
if strings.Contains(envVal, "=") {
|
||||||
envVarArr := strings.Split(envVal, "=")
|
envVarArr := strings.Split(envVal, "=")
|
||||||
|
|
||||||
err := process.Setenv(envVarArr[0], getExternalConfigDirectiveValue(envVarArr[1], opts, AllowedExternalDirectiveVaultFile))
|
err := session.Setenv(envVarArr[0], getExternalConfigDirectiveValue(envVarArr[1], opts, AllowedExternalDirectiveVaultFile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Send()
|
log.Info().Err(err).Send()
|
||||||
|
return fmt.Errorf("failed to set environment variable %s: %w", envVarArr[1], err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func injectEnvIntoLocalCMD(envVarsToInject environmentVars, process *exec.Cmd, log zerolog.Logger, opts *ConfigOpts) {
|
func injectEnvIntoLocalCMD(envVarsToInject environmentVars, process *exec.Cmd, log zerolog.Logger, opts *ConfigOpts) {
|
||||||
@@ -171,6 +172,35 @@ errEnvFile:
|
|||||||
process.Env = append(process.Env, os.Environ()...)
|
process.Env = append(process.Env, os.Environ()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prependEnvVarsToCommand(envVars environmentVars, opts *ConfigOpts, command string, args []string, cmdCtxLogger zerolog.Logger) string {
|
||||||
|
var envPrefix string
|
||||||
|
if envVars.file != "" {
|
||||||
|
envPath, envPathErr := getFullPathWithHomeDir(envVars.file)
|
||||||
|
if envPathErr != nil {
|
||||||
|
cmdCtxLogger.Fatal().Str("envFile", envPath).Err(envPathErr).Send()
|
||||||
|
}
|
||||||
|
file, err := os.Open(envPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Str("envFile", envPath).Err(err).Send()
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
envMap, err := godotenv.Parse(file)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Str("envFile", envPath).Err(err).Send()
|
||||||
|
}
|
||||||
|
for key, val := range envMap {
|
||||||
|
envPrefix += fmt.Sprintf("%s=%s ", key, getExternalConfigDirectiveValue(val, opts, AllowedExternalDirectiveVaultEnv))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, value := range envVars.env {
|
||||||
|
envVarArr := strings.Split(value, "=")
|
||||||
|
envPrefix += fmt.Sprintf("%s=%s ", envVarArr[0], getExternalConfigDirectiveValue(envVarArr[1], opts, AllowedExternalDirectiveVault))
|
||||||
|
envPrefix += "\n"
|
||||||
|
}
|
||||||
|
return envPrefix + command + " " + strings.Join(args, " ")
|
||||||
|
}
|
||||||
|
|
||||||
func contains(s []string, e string) bool {
|
func contains(s []string, e string) bool {
|
||||||
for _, a := range s {
|
for _, a := range s {
|
||||||
if a == e {
|
if a == e {
|
||||||
@@ -403,24 +433,22 @@ func getExternalConfigDirectiveValue(key string, opts *ConfigOpts, allowedDirect
|
|||||||
key = replaceVarInString(opts.Vars, key, opts.Logger)
|
key = replaceVarInString(opts.Vars, key, opts.Logger)
|
||||||
opts.Logger.Debug().Str("expanding external key", key).Send()
|
opts.Logger.Debug().Str("expanding external key", key).Send()
|
||||||
|
|
||||||
if strings.HasPrefix(key, envExternDirectiveStart) {
|
if newKeyStr, directiveFound := strings.CutPrefix(key, envExternDirectiveStart); directiveFound {
|
||||||
if IsExternalDirectiveEnv(allowedDirectives) {
|
if IsExternalDirectiveEnv(allowedDirectives) {
|
||||||
|
|
||||||
key = strings.TrimPrefix(key, envExternDirectiveStart)
|
key = strings.TrimSuffix(newKeyStr, externDirectiveEnd)
|
||||||
key = strings.TrimSuffix(key, externDirectiveEnd)
|
|
||||||
key = os.Getenv(key)
|
key = os.Getenv(key)
|
||||||
} else {
|
} else {
|
||||||
opts.Logger.Error().Msgf("Config key with value %s does not support env directive", key)
|
opts.Logger.Error().Msgf("Config key with value %s does not support env directive", key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(key, externFileDirectiveStart) {
|
if newKeyStr, directiveFound := strings.CutPrefix(key, externFileDirectiveStart); directiveFound {
|
||||||
if IsExternalDirectiveFile(allowedDirectives) {
|
if IsExternalDirectiveFile(allowedDirectives) {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var keyValue []byte
|
var keyValue []byte
|
||||||
key = strings.TrimPrefix(key, externFileDirectiveStart)
|
key = strings.TrimSuffix(newKeyStr, externDirectiveEnd)
|
||||||
key = strings.TrimSuffix(key, externDirectiveEnd)
|
|
||||||
key, err = getFullPathWithHomeDir(key)
|
key, err = getFullPathWithHomeDir(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
opts.Logger.Err(err).Send()
|
opts.Logger.Err(err).Send()
|
||||||
@@ -440,11 +468,10 @@ func getExternalConfigDirectiveValue(key string, opts *ConfigOpts, allowedDirect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(key, vaultExternDirectiveStart) {
|
if newKeyStr, directiveFound := strings.CutPrefix(key, vaultExternDirectiveStart); directiveFound {
|
||||||
if IsExternalDirectiveVault(allowedDirectives) {
|
if IsExternalDirectiveVault(allowedDirectives) {
|
||||||
|
|
||||||
key = strings.TrimPrefix(key, vaultExternDirectiveStart)
|
key = strings.TrimSuffix(newKeyStr, externDirectiveEnd)
|
||||||
key = strings.TrimSuffix(key, externDirectiveEnd)
|
|
||||||
key = GetVaultKey(key, opts, opts.Logger)
|
key = GetVaultKey(key, opts, opts.Logger)
|
||||||
} else {
|
} else {
|
||||||
opts.Logger.Error().Msgf("Config key with value %s does not support vault directive", key)
|
opts.Logger.Error().Msgf("Config key with value %s does not support vault directive", key)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ commands:
|
|||||||
success:
|
success:
|
||||||
- successCmd
|
- successCmd
|
||||||
|
|
||||||
errorCmd:
|
successCmd:
|
||||||
name: get docker version
|
name: get docker version
|
||||||
cmd: docker
|
cmd: docker
|
||||||
getOutput: true
|
getOutput: true
|
||||||
|
|||||||
22
tests/files_test.go
Normal file
22
tests/files_test.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunCommandFileTest(t *testing.T) {
|
||||||
|
filePath := "packageCommands.yml"
|
||||||
|
cmdLineStr := fmt.Sprintf("go run ../backy.go exec host -c checkDockerNoVersion -m localhost --cmdStdOut -f %s", filePath)
|
||||||
|
|
||||||
|
cmd := exec.Command("bash", "-c", cmdLineStr)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Command failed: %v, Output: %s", err, string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(output) == 0 {
|
||||||
|
t.Fatal("Expected command output, got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
81
tests/integration_test.go
Normal file
81
tests/integration_test.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIntegration_ExecuteCommand(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expectFail bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Version Command",
|
||||||
|
args: []string{"version"},
|
||||||
|
expectFail: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Command",
|
||||||
|
args: []string{"invalid"},
|
||||||
|
expectFail: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cmd := exec.Command("go", append([]string{"run", "../backy.go"}, tt.args...)...)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
|
if tt.expectFail && err == nil {
|
||||||
|
t.Fatalf("Expected failure but got success. Output: %s", string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.expectFail && err != nil {
|
||||||
|
t.Fatalf("Expected success but got failure. Error: %v, Output: %s", err, string(output))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntegration_ExecuteCommandWithConfig(t *testing.T) {
|
||||||
|
configFile := "./SuccessHook.yml"
|
||||||
|
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("Config file not found: %s", configFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"--config", configFile}
|
||||||
|
hosts := []string{"localhost"}
|
||||||
|
|
||||||
|
execListArgs := setupExecListRunOnHosts([]string{"echoTestSuccess"}, hosts, args)
|
||||||
|
|
||||||
|
cmd := exec.Command("go", execListArgs...)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Command execution failed. Error: %v, Output: %s", err, string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(output) == 0 {
|
||||||
|
t.Fatal("Expected command output, got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Command output:\n\n%s\n\n", string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupExecListRunOnHosts(cmds, hosts, args []string) []string {
|
||||||
|
baseArgs := []string{"run", "../backy.go", "exec", "host"}
|
||||||
|
for _, h := range hosts {
|
||||||
|
hostArg := "-m"
|
||||||
|
hostArg += h
|
||||||
|
baseArgs = append(baseArgs, hostArg)
|
||||||
|
}
|
||||||
|
for _, c := range cmds {
|
||||||
|
cmdArg := "-c"
|
||||||
|
cmdArg += c
|
||||||
|
baseArgs = append(baseArgs, cmdArg)
|
||||||
|
}
|
||||||
|
return append(baseArgs, args...)
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ commands:
|
|||||||
checkDockerNoVersion:
|
checkDockerNoVersion:
|
||||||
type: package
|
type: package
|
||||||
shell: zsh
|
shell: zsh
|
||||||
|
environment:
|
||||||
|
- TEST_ENV=production
|
||||||
packages:
|
packages:
|
||||||
- name: "docker-ce-cli"
|
- name: "docker-ce-cli"
|
||||||
- name: "docker-ce"
|
- name: "docker-ce"
|
||||||
|
|||||||
14
tests/run_tests.sh
Normal file
14
tests/run_tests.sh
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This script runs all Go test files in the tests directory.
|
||||||
|
|
||||||
|
echo "Running all tests in the tests directory..."
|
||||||
|
|
||||||
|
go test ./tests/... -v
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "All tests passed successfully."
|
||||||
|
else
|
||||||
|
echo "Some tests failed. Check the output above for details."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user