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
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.23'
|
||||
go-version: '1.24'
|
||||
cache: true
|
||||
# More assembly might be required: Docker logins, GPG, etc. It all depends
|
||||
# on your needs.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
steps:
|
||||
golang:
|
||||
image: golang:1.23
|
||||
image: golang:1.24
|
||||
commands:
|
||||
- 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"
|
||||
@@ -1,6 +1,6 @@
|
||||
steps:
|
||||
build:
|
||||
image: golang
|
||||
image: golang:1.24
|
||||
commands:
|
||||
- go build
|
||||
- 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).
|
||||
|
||||
|
||||
## 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
|
||||
### Added
|
||||
* 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"
|
||||
)
|
||||
|
||||
const versionStr = "0.11.0"
|
||||
const versionStr = "0.11.2"
|
||||
|
||||
var (
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.7
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0
|
||||
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/hashicorp/vault/api v1.20.0
|
||||
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-viper/mapstructure/v2 v2.3.0 // 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/go-cleanhttp v0.5.2 // 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/hcl v1.0.1-vault-7 // 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/klauspost/compress v1.18.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/pmezard/go-difflib v1.0.0 // 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/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // 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/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/tinylib/msgp v1.3.0 // 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
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // 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/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/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
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 v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
|
||||
github.com/go-co-op/gocron-ui v0.2.0 h1:f4JqnIfgzeWYgJcNT5ukn86mnyewbXswsa1To1XQroc=
|
||||
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/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
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.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
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/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||
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/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
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/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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/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/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/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/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
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/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.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/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.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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
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/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=
|
||||
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.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -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/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 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/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/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
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)
|
||||
localCMD = exec.Command("/bin/sh", "-c", ArgsStr)
|
||||
} else {
|
||||
if command.Env != "" || command.Environment != nil {
|
||||
localCMD = exec.Command("/bin/sh", "-c", ArgsStr)
|
||||
} else {
|
||||
localCMD = exec.Command(command.Cmd, command.Args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if command.Type == UserCommandType {
|
||||
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)
|
||||
}
|
||||
|
||||
unmarshalConfigIntoStruct(backyKoanf, "goCron", &opts.GoCron, opts.Logger)
|
||||
|
||||
if err := processCmds(opts); err != nil {
|
||||
logging.ExitWithMSG(err.Error(), 1, &opts.Logger)
|
||||
}
|
||||
@@ -425,6 +427,9 @@ func generateFileFetchErrorString(file, fileType string, err error) string {
|
||||
func validateCommandLists(opts *ConfigOpts) {
|
||||
var cmdNotFoundSliceErr []error
|
||||
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 opts.cronEnabled && strings.TrimSpace(cmdList.Cron) == "" {
|
||||
opts.Logger.Debug().Str("cron", "enabled").Str("list", cmdListName).Msg("cron not set, deleting list")
|
||||
|
||||
@@ -6,30 +6,73 @@ package backy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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() {
|
||||
s := gocron.NewScheduler(time.Local)
|
||||
s.TagsUnique()
|
||||
s, _ := gocron.NewScheduler(gocron.WithLocation(time.Local))
|
||||
defer func() { _ = s.Shutdown() }()
|
||||
opts.Logger.Info().Msg("Starting cron mode...")
|
||||
s.Start()
|
||||
cmdLists := opts.CmdConfigLists
|
||||
for _, config := range cmdLists {
|
||||
|
||||
cron := strings.TrimSpace(config.Cron)
|
||||
if cron != "" {
|
||||
opts.Logger.Info().Str("Scheduling cron list", config.Name).Str("Time", cron).Send()
|
||||
_, err := s.CronWithSeconds(cron).Tag(config.Name).Do(func(cron string) {
|
||||
opts.RunListConfig(cron)
|
||||
}, cron)
|
||||
job, err := s.NewJob(
|
||||
gocron.CronJob(cron, opts.GoCron.UseSeconds),
|
||||
gocron.NewTask(
|
||||
func(cronStr string) {
|
||||
opts.RunListConfig(cronStr)
|
||||
},
|
||||
cron,
|
||||
),
|
||||
gocron.WithName(config.Name))
|
||||
if err != nil {
|
||||
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
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
// import (
|
||||
// "testing"
|
||||
// "time"
|
||||
// )
|
||||
|
||||
func TestAddingMetricsForCommand(t *testing.T) {
|
||||
// func TestAddingMetricsForCommand(t *testing.T) {
|
||||
|
||||
// Create a new MetricFile
|
||||
metricFile := NewMetricsFromFile("test_metrics.json")
|
||||
// // Create a new MetricFile
|
||||
// metricFile := NewMetricsFromFile("test_metrics.json")
|
||||
|
||||
metricFile, err := LoadMetricsFromFile(metricFile.Filename)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to load metrics from file: %v", err)
|
||||
}
|
||||
// metricFile, err := LoadMetricsFromFile(metricFile.Filename)
|
||||
// if err != nil {
|
||||
// t.Errorf("Failed to load metrics from file: %v", err)
|
||||
// }
|
||||
|
||||
// Add metrics for a command
|
||||
commandName := "test_command"
|
||||
if _, exists := metricFile.CommandMetrics[commandName]; !exists {
|
||||
metricFile.CommandMetrics[commandName] = NewMetrics()
|
||||
}
|
||||
// // Add metrics for a command
|
||||
// commandName := "test_command"
|
||||
// if _, exists := metricFile.CommandMetrics[commandName]; !exists {
|
||||
// metricFile.CommandMetrics[commandName] = NewMetrics()
|
||||
// }
|
||||
|
||||
// Update the metrics for the command
|
||||
executionTime := 1.8 // Example execution time in seconds
|
||||
success := true // Example success status
|
||||
metricFile.CommandMetrics[commandName].Update(success, executionTime, time.Now())
|
||||
// // Update the metrics for the command
|
||||
// executionTime := 1.8 // Example execution time in seconds
|
||||
// success := true // Example success status
|
||||
// metricFile.CommandMetrics[commandName].Update(success, executionTime, time.Now())
|
||||
|
||||
// Check if the metrics were updated correctly
|
||||
if metricFile.CommandMetrics[commandName].SuccessfulExecutions > 50 {
|
||||
t.Errorf("Expected 1 successful execution, got %d", metricFile.CommandMetrics[commandName].SuccessfulExecutions)
|
||||
}
|
||||
if metricFile.CommandMetrics[commandName].TotalExecutions > 50 {
|
||||
t.Errorf("Expected 1 total execution, got %d", metricFile.CommandMetrics[commandName].TotalExecutions)
|
||||
}
|
||||
// if metricFile.CommandMetrics[commandName].TotalExecutionTime != executionTime {
|
||||
// t.Errorf("Expected execution time %f, got %f", executionTime, metricFile.CommandMetrics[commandName].TotalExecutionTime)
|
||||
// }
|
||||
// // Check if the metrics were updated correctly
|
||||
// if metricFile.CommandMetrics[commandName].SuccessfulExecutions > 50 {
|
||||
// t.Errorf("Expected 1 successful execution, got %d", metricFile.CommandMetrics[commandName].SuccessfulExecutions)
|
||||
// }
|
||||
// if metricFile.CommandMetrics[commandName].TotalExecutions > 50 {
|
||||
// t.Errorf("Expected 1 total execution, got %d", metricFile.CommandMetrics[commandName].TotalExecutions)
|
||||
// }
|
||||
// // if metricFile.CommandMetrics[commandName].TotalExecutionTime != executionTime {
|
||||
// // t.Errorf("Expected execution time %f, got %f", executionTime, metricFile.CommandMetrics[commandName].TotalExecutionTime)
|
||||
// // }
|
||||
|
||||
err = metricFile.SaveToFile()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to save metrics to file: %v", err)
|
||||
}
|
||||
// err = metricFile.SaveToFile()
|
||||
// if err != nil {
|
||||
// t.Errorf("Failed to save metrics to file: %v", err)
|
||||
// }
|
||||
|
||||
listName := "test_list"
|
||||
if _, exists := metricFile.ListMetrics[listName]; !exists {
|
||||
metricFile.ListMetrics[listName] = NewMetrics()
|
||||
}
|
||||
// Update the metrics for the list
|
||||
metricFile.ListMetrics[listName].Update(success, executionTime, time.Now())
|
||||
if metricFile.ListMetrics[listName].SuccessfulExecutions > 50 {
|
||||
t.Errorf("Expected 1 successful execution for list, got %d", metricFile.ListMetrics[listName].SuccessfulExecutions)
|
||||
}
|
||||
if metricFile.ListMetrics[listName].TotalExecutions > 50 {
|
||||
t.Errorf("Expected 1 total execution for list, got %d", metricFile.ListMetrics[listName].TotalExecutions)
|
||||
}
|
||||
// if metricFile.ListMetrics[listName].TotalExecutionTime > executionTime {
|
||||
// t.Errorf("Expected execution time %f for list, got %f", executionTime, metricFile.ListMetrics[listName].TotalExecutionTime)
|
||||
// }
|
||||
// listName := "test_list"
|
||||
// if _, exists := metricFile.ListMetrics[listName]; !exists {
|
||||
// metricFile.ListMetrics[listName] = NewMetrics()
|
||||
// }
|
||||
// // Update the metrics for the list
|
||||
// metricFile.ListMetrics[listName].Update(success, executionTime, time.Now())
|
||||
// if metricFile.ListMetrics[listName].SuccessfulExecutions > 50 {
|
||||
// t.Errorf("Expected 1 successful execution for list, got %d", metricFile.ListMetrics[listName].SuccessfulExecutions)
|
||||
// }
|
||||
// if metricFile.ListMetrics[listName].TotalExecutions > 50 {
|
||||
// t.Errorf("Expected 1 total execution for list, got %d", metricFile.ListMetrics[listName].TotalExecutions)
|
||||
// }
|
||||
// // if metricFile.ListMetrics[listName].TotalExecutionTime > executionTime {
|
||||
// // t.Errorf("Expected execution time %f for list, got %f", executionTime, metricFile.ListMetrics[listName].TotalExecutionTime)
|
||||
// // }
|
||||
|
||||
// Save the metrics to a file
|
||||
err = metricFile.SaveToFile()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to save metrics to file: %v", err)
|
||||
}
|
||||
// // Save the metrics to a file
|
||||
// err = metricFile.SaveToFile()
|
||||
// if err != nil {
|
||||
// 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()
|
||||
|
||||
// Inject environment variables
|
||||
injectEnvIntoSSH(envVars, commandSession, opts, cmdCtxLogger)
|
||||
|
||||
// Set output writers
|
||||
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
|
||||
if IsCmdStdOutEnabled() {
|
||||
@@ -482,6 +479,16 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
|
||||
commandSession.Stdout = 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
|
||||
switch command.Type {
|
||||
case ScriptCommandType:
|
||||
@@ -495,11 +502,15 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
|
||||
return remoteHostPackageExecutor.RunCmdOnHost(command, commandSession, cmdCtxLogger, cmdOutBuf)
|
||||
default:
|
||||
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 {
|
||||
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" {
|
||||
// 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)
|
||||
command.ArgStr = ArgsStr
|
||||
defer passFile.Close()
|
||||
|
||||
rmFileFunc := func() {
|
||||
@@ -530,7 +542,7 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -606,11 +618,10 @@ func checkPackageVersion(cmdCtxLogger zerolog.Logger, command *Command, commandS
|
||||
for _, v := range command.Args {
|
||||
ArgsStr += fmt.Sprintf(" %s", v)
|
||||
}
|
||||
|
||||
var err error
|
||||
var cmdOut []byte
|
||||
|
||||
if cmdOut, err = commandSession.CombinedOutput(ArgsStr); err != nil {
|
||||
if cmdOut, err = commandSession.CombinedOutput(command.ArgStr); err != nil {
|
||||
cmdOutBuf.Write(cmdOut)
|
||||
|
||||
_, 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) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
for _, envVar := range command.Environment {
|
||||
buffer.WriteString(fmt.Sprintf("export %s", envVar))
|
||||
buffer.WriteByte('\n')
|
||||
}
|
||||
|
||||
if command.ScriptEnvFile != "" {
|
||||
envBuffer, err := readFileToBuffer(command.ScriptEnvFile)
|
||||
if err != nil {
|
||||
@@ -694,6 +710,11 @@ func (command *Command) prepareScriptBuffer() (*bytes.Buffer, error) {
|
||||
func (command *Command) prepareScriptFileBuffer() (*bytes.Buffer, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
for _, envVar := range command.Environment {
|
||||
buffer.WriteString(fmt.Sprintf("export %s", envVar))
|
||||
buffer.WriteByte('\n')
|
||||
}
|
||||
|
||||
// Handle script environment file
|
||||
if command.ScriptEnvFile != "" {
|
||||
envBuffer, err := readFileToBuffer(command.ScriptEnvFile)
|
||||
@@ -836,7 +857,7 @@ func (r RemoteHostPackageExecutor) RunCmdOnHost(command *Command, commandSession
|
||||
return checkPackageVersion(cmdCtxLogger, command, commandSession, cmdOutBuf)
|
||||
}
|
||||
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 {
|
||||
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ type (
|
||||
RemoteHost *Host `yaml:"-"`
|
||||
|
||||
Args []string `yaml:"args,omitempty"`
|
||||
ArgStr string
|
||||
|
||||
Dir *string `yaml:"dir,omitempty"`
|
||||
|
||||
@@ -169,6 +170,12 @@ type (
|
||||
Type string `yaml:"type"`
|
||||
}
|
||||
|
||||
GoCronOpts struct {
|
||||
BindAddress string `yaml:"bindAddress"`
|
||||
UseSeconds bool `yaml:"useSeconds"`
|
||||
Port int `yaml:"port"`
|
||||
}
|
||||
|
||||
ConfigOpts struct {
|
||||
// Cmds holds the commands for a list.
|
||||
// Key is the name of the command,
|
||||
@@ -182,6 +189,8 @@ type (
|
||||
// key is the host.
|
||||
Hosts map[string]*Host `yaml:"hosts"`
|
||||
|
||||
GoCron GoCronOpts `yaml:"goCron:"`
|
||||
|
||||
Logger zerolog.Logger
|
||||
|
||||
// Global log level
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"mvdan.cc/sh/v3/shell"
|
||||
)
|
||||
@@ -99,7 +100,7 @@ func NewConfigOptions(configFilePath string, opts ...BackyOptionFunc) *ConfigOpt
|
||||
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 != "" {
|
||||
envPath, envPathErr := getFullPathWithHomeDir(envVarsToInject.file)
|
||||
if envPathErr != nil {
|
||||
@@ -113,31 +114,31 @@ func injectEnvIntoSSH(envVarsToInject environmentVars, process *ssh.Session, opt
|
||||
|
||||
envMap, err := godotenv.Parse(file)
|
||||
if err != nil {
|
||||
log.Error().Str("envFile", envPath).Err(err).Send()
|
||||
goto errEnvFile
|
||||
log.Fatal().Str("envFile", envPath).Err(err).Send()
|
||||
}
|
||||
for key, val := range envMap {
|
||||
err = process.Setenv(key, getExternalConfigDirectiveValue(val, opts, AllowedExternalDirectiveVault))
|
||||
err = session.Setenv(key, getExternalConfigDirectiveValue(val, opts, AllowedExternalDirectiveVault))
|
||||
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)
|
||||
for _, envVal := range envVarsToInject.env {
|
||||
// don't append env Vars for Backy
|
||||
if strings.Contains(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 {
|
||||
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) {
|
||||
@@ -171,6 +172,35 @@ errEnvFile:
|
||||
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 {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
@@ -403,24 +433,22 @@ func getExternalConfigDirectiveValue(key string, opts *ConfigOpts, allowedDirect
|
||||
key = replaceVarInString(opts.Vars, key, opts.Logger)
|
||||
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) {
|
||||
|
||||
key = strings.TrimPrefix(key, envExternDirectiveStart)
|
||||
key = strings.TrimSuffix(key, externDirectiveEnd)
|
||||
key = strings.TrimSuffix(newKeyStr, externDirectiveEnd)
|
||||
key = os.Getenv(key)
|
||||
} else {
|
||||
opts.Logger.Error().Msgf("Config key with value %s does not support env directive", key)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(key, externFileDirectiveStart) {
|
||||
if newKeyStr, directiveFound := strings.CutPrefix(key, externFileDirectiveStart); directiveFound {
|
||||
if IsExternalDirectiveFile(allowedDirectives) {
|
||||
|
||||
var err error
|
||||
var keyValue []byte
|
||||
key = strings.TrimPrefix(key, externFileDirectiveStart)
|
||||
key = strings.TrimSuffix(key, externDirectiveEnd)
|
||||
key = strings.TrimSuffix(newKeyStr, externDirectiveEnd)
|
||||
key, err = getFullPathWithHomeDir(key)
|
||||
if err != nil {
|
||||
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) {
|
||||
|
||||
key = strings.TrimPrefix(key, vaultExternDirectiveStart)
|
||||
key = strings.TrimSuffix(key, externDirectiveEnd)
|
||||
key = strings.TrimSuffix(newKeyStr, externDirectiveEnd)
|
||||
key = GetVaultKey(key, opts, opts.Logger)
|
||||
} else {
|
||||
opts.Logger.Error().Msgf("Config key with value %s does not support vault directive", key)
|
||||
|
||||
@@ -7,7 +7,7 @@ commands:
|
||||
success:
|
||||
- successCmd
|
||||
|
||||
errorCmd:
|
||||
successCmd:
|
||||
name: get docker version
|
||||
cmd: docker
|
||||
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:
|
||||
type: package
|
||||
shell: zsh
|
||||
environment:
|
||||
- TEST_ENV=production
|
||||
packages:
|
||||
- name: "docker-ce-cli"
|
||||
- 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