13 Commits

Author SHA1 Message Date
995e4f91b5 update docs
All checks were successful
ci/woodpecker/push/publish-docs Pipeline was successful
ci/woodpecker/tag/gitea Pipeline was successful
ci/woodpecker/tag/publish-docs Pipeline was successful
ci/woodpecker/release/publish-docs Pipeline was successful
2025-12-27 00:59:57 -06:00
fa62bc1ec6 v0.11.2
Some checks failed
ci/woodpecker/push/publish-docs Pipeline failed
2025-12-27 00:45:54 -06:00
2766ac997a update CI Configs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-12-27 00:03:39 -06:00
d0b4c0b9df update CI Configs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-12-26 23:55:13 -06:00
beabe9f041 Upgraded GoCron; web ui viewer for viewing cron jobs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
2025-12-26 23:04:25 -06:00
3a038eeab4 Upgraded GoCron; web ui viewer for viewing cron jobs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
2025-12-26 22:58:08 -06:00
a95f903e72 more work on integration testing
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-12-15 13:50:20 -06:00
61add23efb v0.11.1
Some checks failed
ci/woodpecker/push/publish-docs Pipeline was successful
ci/woodpecker/tag/gitea Pipeline was successful
ci/woodpecker/tag/publish-docs Pipeline was successful
ci/woodpecker/release/publish-docs Pipeline was successful
ci/woodpecker/push/go-lint Pipeline failed
2025-12-08 18:17:48 -06:00
b228fca371 update docs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
2025-12-08 18:15:40 -06:00
e5a9003ed6 start integration testing
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-12-08 18:13:58 -06:00
803b039849 start integration testing
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline failed
2025-12-08 18:12:31 -06:00
2824f8c703 inject ssh env vars by apppending them to the script
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-12-08 10:08:44 -06:00
cfc00262ff inject ssh env vars by apppending them to the script
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-11-29 20:55:11 -06:00
25 changed files with 441 additions and 129 deletions

6
.changes/v0.11.1.md Normal file
View 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
View File

@@ -0,0 +1,3 @@
## v0.11.2 - 2025-12-27
### Added
* Upgraded GoCron; web ui viewer for viewing cron jobs

View File

@@ -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.

View File

@@ -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"

View File

@@ -1,6 +1,6 @@
steps: steps:
build: build:
image: golang image: golang:1.24
commands: commands:
- go build - go build
- go test - go test

View 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

View File

@@ -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)

View File

@@ -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{

View File

@@ -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.

View 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
View File

@@ -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
View File

@@ -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=

View File

@@ -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" {

View File

@@ -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")

View File

@@ -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
} }

View File

@@ -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)
} // }
} // }

View File

@@ -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)
} }

View File

@@ -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

View File

@@ -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)

View File

@@ -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
View 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
View 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...)
}

View File

@@ -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
View 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