Compare commits
115 Commits
Author | SHA1 | Date | |
---|---|---|---|
7fe07f86a9 | |||
14c81a00a7 | |||
3eced64453 | |||
c284d928fd | |||
dd9da9452b | |||
11d5121954 | |||
66d622b474 | |||
47b2aabd9f | |||
b91cf18b04 | |||
305b504ca1 | |||
7be2679b91 | |||
3c6e3ed914 | |||
02bc040e2a | |||
9f1f36215a | |||
ff75f4bbcd | |||
5f40713e98 | |||
cd5f7611a9 | |||
b542711078 | |||
52dbc353e5 | |||
6bef0c3e5b | |||
4d705d78fb | |||
62d47ecfa7 | |||
32444ff82e | |||
a5a7c05640 | |||
bfb81e11b2 | |||
fd4c83f9c0 | |||
fe27c6396a | |||
c89dde186a | |||
18a64de0de | |||
99c622b69f | |||
95e85e8b45 | |||
1a48c7bca5 | |||
5d21764ef1 | |||
c7302f0e17 | |||
fb1c8ec4fb | |||
fe9462dac0 | |||
d8453d1fb0 | |||
65c46a1e26 | |||
f859b5961f | |||
25ddd65f25 | |||
bcba6b2086 | |||
753b03861f | |||
80a45cd595 | |||
551c8ad441 | |||
3823b1bf44 | |||
f777c78aad | |||
bb693dbb97 | |||
7beda281e0 | |||
1143d2850b | |||
8900bd70a4 | |||
6db5f73bc0 | |||
a163c11129 | |||
2b4d191271 | |||
417088c32b | |||
4fa5efa5b6 | |||
a0bf51636c | |||
684edd7985 | |||
3acb20a40f | |||
0007c8696a | |||
cf2baf3601 | |||
e6b9f8e6e6 | |||
2eefc59cf7 | |||
98d8b8e8f2 | |||
1ad50ebcf8 | |||
c483a1056f | |||
3b9f569310 | |||
843be7968b | |||
d477d850ac | |||
8eb3229af7 | |||
d89a208bbd | |||
0d28d6afcf | |||
7c42a9a7cd | |||
31339fb4d8 | |||
c642e827f5 | |||
a328239021 | |||
4ee60184bf | |||
161ad31577 | |||
7c5f4a95da | |||
4981acbf9d | |||
932d5c380f | |||
f84d76badf | |||
6ee6e10621 | |||
127d38c076 | |||
0218dee76d | |||
67a1eab908 | |||
c618ca33f8 | |||
6e7d912fa2 | |||
b90d1958b2 | |||
c187fbb735 | |||
c3de4386ab | |||
e20141043c | |||
11ec1a98d8 | |||
8788d473a5 | |||
edc669b340 | |||
086835453b | |||
5d3c265ce9 | |||
8c633fd4b2 | |||
a664edaed7 | |||
e88773e289 | |||
5c2bfcc940 | |||
aee513f786 | |||
6b99cfa196 | |||
c24e8086e9 | |||
c12bbe3bee | |||
33febd662e | |||
5635c1edd0 | |||
e169778c82 | |||
c838bfe815 | |||
e81a5def47 | |||
18884c640d | |||
ee2256bfb2 | |||
82d79c520a | |||
c30ae2ac3e | |||
fc738597ff | |||
aebef21eb4 |
@ -0,0 +1 @@
|
||||
*.yaml
|
3
.changes/unreleased/Added-20250409-174528.yaml
Normal file
3
.changes/unreleased/Added-20250409-174528.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Added
|
||||
body: 'feat: Package operation `versionCheck` supports regular expressions (see [regexp](https://pkg.go.dev/regexp) package for docs)'
|
||||
time: 2025-04-09T17:45:28.836497149-05:00
|
3
.changes/unreleased/Added-20250501-110745.yaml
Normal file
3
.changes/unreleased/Added-20250501-110745.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Added
|
||||
body: 'Command lists: added `cmdLists.[name].notify` object'
|
||||
time: 2025-05-01T11:07:45.96164753-05:00
|
3
.changes/unreleased/Added-20250704-085917.yaml
Normal file
3
.changes/unreleased/Added-20250704-085917.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Added
|
||||
body: Testing setup with Docker
|
||||
time: 2025-07-04T08:59:17.430373451-05:00
|
3
.changes/unreleased/Added-20250704-102126.yaml
Normal file
3
.changes/unreleased/Added-20250704-102126.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Added
|
||||
body: 'CLI: add global flag --hostsConfig that allows hosts to be dynamic in relation to the main config'
|
||||
time: 2025-07-04T10:21:26.864635558-05:00
|
3
.changes/unreleased/Added-20250715-202303.yaml
Normal file
3
.changes/unreleased/Added-20250715-202303.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Added
|
||||
body: 'CLI: Exec subcommand `hosts`. See documentation for more details.'
|
||||
time: 2025-07-15T20:23:03.647128713-05:00
|
3
.changes/unreleased/Added-20250723-220340.yaml
Normal file
3
.changes/unreleased/Added-20250723-220340.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Added
|
||||
body: 'CLI: added `exec hosts` subcommand `list`'
|
||||
time: 2025-07-23T22:03:40.24191927-05:00
|
3
.changes/unreleased/Changed-20250321-090849.yaml
Normal file
3
.changes/unreleased/Changed-20250321-090849.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Changed
|
||||
body: 'Commands: `host` can now be `localhost` or `127.0.0.1` to run commands locally'
|
||||
time: 2025-03-21T09:08:49.871021144-05:00
|
3
.changes/unreleased/Changed-20250325-003357.yaml
Normal file
3
.changes/unreleased/Changed-20250325-003357.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Changed
|
||||
body: lists loaded from external files only if no list config present in current file
|
||||
time: 2025-03-25T00:33:57.039431409-05:00
|
3
.changes/unreleased/Changed-20250407-223020.yaml
Normal file
3
.changes/unreleased/Changed-20250407-223020.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Changed
|
||||
body: "`PackageManager.Parse` renamed to `ParseRemotePackageManagerVersionOutput`. This now returns arrays of PackageManagerCommon.Package and errors."
|
||||
time: 2025-04-07T22:30:20.342177323-05:00
|
3
.changes/unreleased/Changed-20250418-133440.yaml
Normal file
3
.changes/unreleased/Changed-20250418-133440.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Changed
|
||||
body: 'Internal: refactoring and renaming functions'
|
||||
time: 2025-04-18T13:34:40.842541658-05:00
|
3
.changes/unreleased/Changed-20250501-110534.yaml
Normal file
3
.changes/unreleased/Changed-20250501-110534.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Changed
|
||||
body: 'Commands: moved output-prefixed keys to the `commands.[name].output` object'
|
||||
time: 2025-05-01T11:05:34.90130087-05:00
|
3
.changes/unreleased/Changed-20250609-072601.yaml
Normal file
3
.changes/unreleased/Changed-20250609-072601.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Changed
|
||||
body: Change internal method name for better understanding
|
||||
time: 2025-06-09T07:26:01.819927627-05:00
|
3
.changes/unreleased/Changed-20250709-231919.yaml
Normal file
3
.changes/unreleased/Changed-20250709-231919.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Changed
|
||||
body: Improved error message for remote version package output
|
||||
time: 2025-07-09T23:19:19.431960446-05:00
|
3
.changes/unreleased/Fixed-20250418-095747.yaml
Normal file
3
.changes/unreleased/Fixed-20250418-095747.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Fixed
|
||||
body: 'Command Lists: hooks now run correctly when commands finish'
|
||||
time: 2025-04-18T09:57:47.39035092-05:00
|
3
.changes/unreleased/Fixed-20250424-225711.yaml
Normal file
3
.changes/unreleased/Fixed-20250424-225711.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
kind: Fixed
|
||||
body: Log file passed using `--log-file` correctly used
|
||||
time: 2025-04-24T22:57:11.592829277-05:00
|
16
.changes/v0.10.0.md
Normal file
16
.changes/v0.10.0.md
Normal file
@ -0,0 +1,16 @@
|
||||
## v0.10.0 - 2025-03-08
|
||||
### Added
|
||||
* Hooks: improved logging when executing
|
||||
* User commands: adding SSH keys using config key `userSshPubKeys`
|
||||
* directives: added support for fetching values using directive `%{externalSource:key}%`
|
||||
### Changed
|
||||
* Commands: if dir is not specified, run in config dir
|
||||
* FileDirective: use the config directory if path is not absolute
|
||||
* Host: changes to case of some keys
|
||||
* Notifications: added external directive to sensitive keys
|
||||
### Fixed
|
||||
* LocalFetcher: return fetch error
|
||||
* Lists: load file key before attempting to load from current file
|
||||
* fix: host not in config file, but in ssh config, properly added to hosts struct
|
||||
* SSH: password authentication bugs
|
||||
* User commands: change user password works
|
8
.changes/v0.10.1.md
Normal file
8
.changes/v0.10.1.md
Normal file
@ -0,0 +1,8 @@
|
||||
## v0.10.1 - 2025-03-11
|
||||
### Added
|
||||
* UserCommands: add ssh public keys when running locally
|
||||
* UserCommands: add field CreateUserHome
|
||||
### Changed
|
||||
* UserCommands: create temp file when modifing password over SSH
|
||||
* UserCommands: change field name
|
||||
* Vault: keys are now referenced by `name`, and the actual data by `data`
|
6
.changes/v0.10.2.md
Normal file
6
.changes/v0.10.2.md
Normal file
@ -0,0 +1,6 @@
|
||||
## v0.10.2 - 2025-03-19
|
||||
### Added
|
||||
* Notifications: http service added
|
||||
* Variable support. Can be referenced with `%{var:nameOfVar}%` in select string fields.
|
||||
### Changed
|
||||
* vault: initialize vault before validating config
|
3
.changes/v0.6.1.md
Normal file
3
.changes/v0.6.1.md
Normal file
@ -0,0 +1,3 @@
|
||||
## v0.6.1 - 2025-01-04
|
||||
### Fixed
|
||||
* When running a list, hooks now run explicitly after the command executes. Fixed panic due to improper logic.
|
16
.changes/v0.7.0.md
Normal file
16
.changes/v0.7.0.md
Normal file
@ -0,0 +1,16 @@
|
||||
## v0.7.0 - 2025-02-11
|
||||
### Added
|
||||
* [feat]: package `packageOperation` option `checkVersion` implemented
|
||||
* user management added - see docs
|
||||
* Support for remote config sources. Only config file and list can be used for now.
|
||||
* Cache functionality - still a WIP
|
||||
* Flag `--s3-endpoint` for config file fetching from S3
|
||||
### Changed
|
||||
* Internal refactoring of config setup
|
||||
* Formatting and sending for notifications
|
||||
* name of `configfetcher` to `remotefetcher`
|
||||
* Flags that took comma-separated lists now have to be passed multiple times for each argument.
|
||||
* Hosts passed to `exec host` now checked against default SSH config files
|
||||
### Fixed
|
||||
* Parsing of remote URLs when determining list config file path
|
||||
* Incorrect error notification template value
|
3
.changes/v0.7.1.md
Normal file
3
.changes/v0.7.1.md
Normal file
@ -0,0 +1,3 @@
|
||||
## v0.7.1 - 2025-02-14
|
||||
### Fixed
|
||||
* Incorrect local config file loading logic caused files to not be detected
|
3
.changes/v0.7.2.md
Normal file
3
.changes/v0.7.2.md
Normal file
@ -0,0 +1,3 @@
|
||||
## v0.7.2 - 2025-02-14
|
||||
### Fixed
|
||||
* CI configs
|
3
.changes/v0.7.3.md
Normal file
3
.changes/v0.7.3.md
Normal file
@ -0,0 +1,3 @@
|
||||
## v0.7.3 - 2025-02-14
|
||||
### Changed
|
||||
* GoReleaser configs
|
5
.changes/v0.7.4.md
Normal file
5
.changes/v0.7.4.md
Normal file
@ -0,0 +1,5 @@
|
||||
## v0.7.4 - 2025-02-14
|
||||
### Changed
|
||||
* CI configs
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
5
.changes/v0.7.5.md
Normal file
5
.changes/v0.7.5.md
Normal file
@ -0,0 +1,5 @@
|
||||
## v0.7.5 - 2025-02-14
|
||||
### Changed
|
||||
* CI configs
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
4
.changes/v0.7.6.md
Normal file
4
.changes/v0.7.6.md
Normal file
@ -0,0 +1,4 @@
|
||||
## v0.7.6 - 2025-02-14
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
||||
* CI configs
|
4
.changes/v0.7.7.md
Normal file
4
.changes/v0.7.7.md
Normal file
@ -0,0 +1,4 @@
|
||||
## v0.7.7 - 2025-02-14
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
||||
* CI configs
|
4
.changes/v0.7.8.md
Normal file
4
.changes/v0.7.8.md
Normal file
@ -0,0 +1,4 @@
|
||||
## v0.7.8 - 2025-02-14
|
||||
### Fixed
|
||||
* Github CI config
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
6
.changes/v0.8.0.md
Normal file
6
.changes/v0.8.0.md
Normal file
@ -0,0 +1,6 @@
|
||||
## v0.8.0 - 2025-02-15
|
||||
### Changed
|
||||
* Breaking: `cmd-lists` key changed to `cmdLists`
|
||||
* Properly load list config
|
||||
* Config file loading properly errors
|
||||
* CI Configs
|
12
.changes/v0.9.0.md
Normal file
12
.changes/v0.9.0.md
Normal file
@ -0,0 +1,12 @@
|
||||
## v0.9.0 - 2025-02-28
|
||||
### Added
|
||||
* `list` command with subcommands `cmds` and `lists`
|
||||
* Deprecation and unsupported warnings for old config keys
|
||||
* CLI flag `--cmdStdOut` to output command's stdout/stderr to stdout
|
||||
* Command type `remoteScript`. See docs for more info.
|
||||
### Changed
|
||||
* change to enums for Command type
|
||||
* Cache now stores resources by URL hash for ease-of-lookup
|
||||
* Changed PackageOperation to enums
|
||||
### Fixed
|
||||
* Local command's `dir` full path is now found with home directory
|
3
.changes/v0.9.1.md
Normal file
3
.changes/v0.9.1.md
Normal file
@ -0,0 +1,3 @@
|
||||
## v0.9.1 - 2025-03-01
|
||||
### Changed
|
||||
* Use EnvVar AWS_PROFILE to get S3 profile
|
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
@ -15,26 +15,26 @@ jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: git fetch --force --tags
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.20'
|
||||
go-version: '1.23'
|
||||
cache: true
|
||||
# More assembly might be required: Docker logins, GPG, etc. It all depends
|
||||
# on your needs.
|
||||
- name: Get tag
|
||||
id: tag
|
||||
uses: dawidd6/action-get-tag@v1
|
||||
- uses: olegtarasov/get-tag@v2.1.4
|
||||
id: tagName
|
||||
with:
|
||||
# Optionally strip `v` prefix
|
||||
strip_v: false
|
||||
- uses: goreleaser/goreleaser-action@v4
|
||||
# tagRegex: "foobar-(.*)" # Optional. Returns specified group text as tag name. Full tag string is returned if regex is not defined.
|
||||
tagRegexGroup: 1 # Optional. Default is 1.
|
||||
- uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --release-notes=".changes/${{steps.tag.outputs.tag}}.md" -f .goreleaser/github.yml --clean
|
||||
version: 2.7.0
|
||||
args: release --release-notes=".changes/${{ env.GIT_TAG_NAME }}.md" -f .goreleaser/github.yml --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GORELEASER_TOKEN }}
|
||||
GIT_TAG_NAME: ${{ steps.tagName.outputs.tag }}
|
||||
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,2 +1,10 @@
|
||||
!.changie.yaml
|
||||
!.changes/**
|
||||
|
||||
dist/
|
||||
.codegpt
|
||||
|
||||
*.log
|
||||
/*.sh
|
||||
/*.yaml
|
||||
/*.yml
|
||||
|
@ -6,7 +6,6 @@ before:
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
- GOPROXY=https://goproxy.io
|
||||
goos:
|
||||
- freebsd
|
||||
- linux
|
||||
@ -16,7 +15,7 @@ builds:
|
||||
- arm64
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
- formats: tar.gz
|
||||
# this name template makes the OS and Arch compatible with the results of uname.
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_{{ .Version }}_
|
||||
@ -28,7 +27,7 @@ archives:
|
||||
# use zip for windows archives
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats: [zip]
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
|
@ -1,4 +1,3 @@
|
||||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
version: 2
|
||||
before:
|
||||
@ -17,7 +16,7 @@ builds:
|
||||
- arm64
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
- formats: tar.gz
|
||||
# this name template makes the OS and Arch compatible with the results of uname.
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_{{ .Version }}_
|
||||
@ -29,7 +28,7 @@ archives:
|
||||
# use zip for windows archives
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats: [zip]
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -1,6 +1,7 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"Cmds",
|
||||
"remotefetcher",
|
||||
"knadh",
|
||||
"koanf",
|
||||
"mattn",
|
||||
@ -8,5 +9,6 @@
|
||||
"mautrix",
|
||||
"nikoksr",
|
||||
"Strs"
|
||||
]
|
||||
],
|
||||
"CodeGPT.apiKey": "CodeGPT Plus Beta"
|
||||
}
|
@ -1,11 +1,18 @@
|
||||
steps:
|
||||
release:
|
||||
image: goreleaser/goreleaser
|
||||
golang:
|
||||
image: golang:1.23
|
||||
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"
|
||||
secrets: [ gitea_token ]
|
||||
environment:
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
|
||||
when:
|
||||
event: tag
|
||||
# release:
|
||||
# image: goreleaser/goreleaser
|
||||
# commands:
|
||||
|
||||
when:
|
||||
- event: tag
|
||||
|
@ -5,7 +5,7 @@ steps:
|
||||
- go build
|
||||
- go test
|
||||
release:
|
||||
image: golangci/golangci-lint:v1.53.3
|
||||
image: golangci/golangci-lint:v1.64.7
|
||||
commands:
|
||||
- golangci-lint run -v --timeout 5m
|
||||
|
||||
|
@ -1,15 +1,12 @@
|
||||
steps:
|
||||
build:
|
||||
image: klakegg/hugo:ext-debian-ci
|
||||
image: hugomods/hugo:ci
|
||||
commands:
|
||||
- git submodule foreach 'git fetch origin; git checkout $(git describe --tags `git rev-list --tags --max-count=1`);'
|
||||
- cd docs
|
||||
- hugo mod get -u ./...
|
||||
- hugo mod get -u github.com/divinerites/plausible-hugo
|
||||
- hugo mod get -u github.com/McShelby/hugo-theme-relearn@7.3.1
|
||||
- hugo
|
||||
when:
|
||||
- event: push
|
||||
branch: master
|
||||
path: "docs/*"
|
||||
|
||||
deploy:
|
||||
image: codingkoopa/git-rsync-openssh
|
||||
@ -26,9 +23,14 @@ steps:
|
||||
- echo "$SSH_DEPLOY_KEY" | tr -d '\r' | DISPLAY=":0.0" SSH_ASKPASS=~/.ssh/.print_ssh_password setsid ssh-add -
|
||||
- rsync -atv --delete --progress public/ backy@backy.cybershell.xyz:docs
|
||||
- rsync -atv --delete --progress vangen/ backy@backy.cybershell.xyz:vangen-go
|
||||
secrets: [ ssh_host_key, ssh_deploy_key, ssh_passphrase ]
|
||||
environment:
|
||||
SSH_HOST_KEY:
|
||||
from_secret: ssh_host_key
|
||||
SSH_DEPLOY_KEY:
|
||||
from_secret: ssh_deploy_key
|
||||
SSH_PASSPHRASE:
|
||||
from_secret: ssh_passphrase
|
||||
|
||||
when:
|
||||
- event: push
|
||||
branch: master
|
||||
path: "docs/*"
|
||||
when:
|
||||
- branch: master
|
||||
- path: 'docs/**'
|
121
CHANGELOG.md
121
CHANGELOG.md
@ -6,6 +6,127 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||
|
||||
|
||||
## v0.10.2 - 2025-03-19
|
||||
### Added
|
||||
* Notifications: http service added
|
||||
* Variable support. Can be referenced with `%{var:nameOfVar}%` in select string fields.
|
||||
### Changed
|
||||
* vault: initialize vault before validating config
|
||||
|
||||
## v0.10.1 - 2025-03-11
|
||||
### Added
|
||||
* UserCommands: add ssh public keys when running locally
|
||||
* UserCommands: add field CreateUserHome
|
||||
### Changed
|
||||
* UserCommands: create temp file when modifing password over SSH
|
||||
* UserCommands: change field name
|
||||
* Vault: keys are now referenced by `name`, and the actual data by `data`
|
||||
|
||||
## v0.10.0 - 2025-03-08
|
||||
### Added
|
||||
* Hooks: improved logging when executing
|
||||
* User commands: adding SSH keys using config key `userSshPubKeys`
|
||||
* directives: added support for fetching values using directive `%{externalSource:key}%`
|
||||
### Changed
|
||||
* Commands: if dir is not specified, run in config dir
|
||||
* FileDirective: use the config directory if path is not absolute
|
||||
* Host: changes to case of some keys
|
||||
* Notifications: added external directive to sensitive keys
|
||||
### Fixed
|
||||
* LocalFetcher: return fetch error
|
||||
* Lists: load file key before attempting to load from current file
|
||||
* fix: host not in config file, but in ssh config, properly added to hosts struct
|
||||
* SSH: password authentication bugs
|
||||
* User commands: change user password works
|
||||
|
||||
## v0.9.1 - 2025-03-01
|
||||
### Changed
|
||||
* Use EnvVar AWS_PROFILE to get S3 profile
|
||||
|
||||
## v0.9.0 - 2025-02-28
|
||||
### Added
|
||||
* `list` command with subcommands `cmds` and `lists`
|
||||
* Deprecation and unsupported warnings for old config keys
|
||||
* CLI flag `--cmdStdOut` to output command's stdout/stderr to stdout
|
||||
* Command type `remoteScript`. See docs for more info.
|
||||
### Changed
|
||||
* change to enums for Command type
|
||||
* Cache now stores resources by URL hash for ease-of-lookup
|
||||
* Changed PackageOperation to enums
|
||||
### Fixed
|
||||
* Local command's `dir` full path is now found with home directory
|
||||
|
||||
## v0.8.0 - 2025-02-15
|
||||
### Changed
|
||||
* Breaking: `cmd-lists` key changed to `cmdLists`
|
||||
* Properly load list config
|
||||
* Config file loading properly errors
|
||||
* CI Configs
|
||||
|
||||
## v0.7.8 - 2025-02-14
|
||||
### Fixed
|
||||
* Github CI config
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
||||
|
||||
## v0.7.7 - 2025-02-14
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
||||
* CI configs
|
||||
|
||||
## v0.7.6 - 2025-02-14
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
||||
* CI configs
|
||||
|
||||
## v0.7.5 - 2025-02-14
|
||||
### Changed
|
||||
* CI configs
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
||||
|
||||
## v0.7.4 - 2025-02-14
|
||||
### Changed
|
||||
* CI configs
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
||||
|
||||
## v0.7.3 - 2025-02-14
|
||||
### Changed
|
||||
* GoReleaser configs
|
||||
|
||||
## v0.7.2 - 2025-02-14
|
||||
### Fixed
|
||||
* CI configs
|
||||
|
||||
## v0.7.1 - 2025-02-14
|
||||
### Fixed
|
||||
* Incorrect local config file loading logic caused files to not be detected
|
||||
|
||||
## v0.7.0 - 2025-02-11
|
||||
### Added
|
||||
* [feat]: package `packageOperation` option `checkVersion` implemented
|
||||
* user management added - see docs
|
||||
* Support for remote config sources. Only config file and list can be used for now.
|
||||
* Cache functionality - still a WIP
|
||||
* Flag `--s3-endpoint` for config file fetching from S3
|
||||
### Changed
|
||||
* Internal refactoring of config setup
|
||||
* Formatting and sending for notifications
|
||||
* name of `configfetcher` to `remotefetcher`
|
||||
* Flags that took comma-separated lists now have to be passed multiple times for each argument.
|
||||
* Hosts passed to `exec host` now checked against default SSH config files
|
||||
### Fixed
|
||||
* Parsing of remote URLs when determining list config file path
|
||||
* Incorrect error notification template value
|
||||
|
||||
## v0.6.1 - 2025-01-04
|
||||
### Fixed
|
||||
* When running a list, hooks now run explicitly after the command executes. Fixed panic due to improper logic.
|
||||
## v0.6.0 - 2025-01-04
|
||||
### Added
|
||||
* Command Type Package - allows one to perform package operations [docs](https://backy.cybershell.xyz/config/packages/)
|
||||
* Exec subcommand `host` allows for parallel execution of commands on hosts. [See docs](https://backy.cybershell.xyz/cli/exec)
|
||||
|
||||
## v0.5.0 - 2024-11-19
|
||||
### Added
|
||||
* Lists can now go in a file. See docs for more information.
|
||||
|
27
backy.code-workspace
Normal file
27
backy.code-workspace
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"name": "backy",
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"cSpell.words": [
|
||||
"Autorestic",
|
||||
"changie",
|
||||
"Cmds",
|
||||
"CMDSTDOUT",
|
||||
"goreleaser",
|
||||
"knadh",
|
||||
"koanf",
|
||||
"mattn",
|
||||
"maunium",
|
||||
"mautrix",
|
||||
"nikoksr",
|
||||
"packagemanagercommon",
|
||||
"rawbytes",
|
||||
"remotefetcher",
|
||||
"Strs"
|
||||
]
|
||||
}
|
||||
}
|
@ -12,8 +12,8 @@ import (
|
||||
|
||||
var (
|
||||
backupCmd = &cobra.Command{
|
||||
Use: "backup [--lists=list1,list2,... | -l list1, list2,...]",
|
||||
Short: "Runs commands defined in config file.",
|
||||
Use: "backup [--lists=list1 --lists list2 ... | -l list1 -l list2 ...]",
|
||||
Short: "Runs commands defined in config file. Use -l flag multiple times to run multiple lists.",
|
||||
Long: "Backup executes commands defined in config file.\nUse the --lists or -l flag to execute the specified lists. If not flag is not given, all lists will be executed.",
|
||||
Run: Backup,
|
||||
}
|
||||
@ -23,16 +23,21 @@ var (
|
||||
var cmdLists []string
|
||||
|
||||
func init() {
|
||||
parseS3Config()
|
||||
|
||||
backupCmd.Flags().StringSliceVarP(&cmdLists, "lists", "l", nil, "Accepts comma-separated names of command lists to execute.")
|
||||
backupCmd.Flags().StringArrayVarP(&cmdLists, "lists", "l", nil, "Accepts comma-separated names of command lists to execute.")
|
||||
|
||||
}
|
||||
|
||||
func Backup(cmd *cobra.Command, args []string) {
|
||||
backyConfOpts := backy.NewOpts(cfgFile, backy.AddCommandLists(cmdLists))
|
||||
backyConfOpts.InitConfig()
|
||||
backyConfOpts := backy.NewConfigOptions(configFile,
|
||||
backy.AddCommandLists(cmdLists),
|
||||
backy.SetLogFile(logFile),
|
||||
backy.EnableCommandStdOut(cmdStdOut),
|
||||
backy.SetHostsConfigFile(hostsConfigFile))
|
||||
|
||||
backy.ReadConfig(backyConfOpts)
|
||||
backyConfOpts.InitConfig()
|
||||
backyConfOpts.ParseConfigurationFile()
|
||||
|
||||
backyConfOpts.RunListConfig("")
|
||||
for _, host := range backyConfOpts.Hosts {
|
||||
|
54
cmd/backup_test.go
Normal file
54
cmd/backup_test.go
Normal file
@ -0,0 +1,54 @@
|
||||
package cmd
|
||||
|
||||
// import (
|
||||
// "bufio"
|
||||
// "encoding/json"
|
||||
// "os"
|
||||
// "os/exec"
|
||||
// "strings"
|
||||
// "testing"
|
||||
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
|
||||
// // TestConfigOptions tests the configuration options for the backy package.
|
||||
// func Test_ErrorHook(t *testing.T) {
|
||||
// configFile := "-f ../../tests/ErrorHook.yml"
|
||||
// logFile := "--log-file=ErrorHook.log"
|
||||
// backyCommand := exec.Command("go", "run", "../../backy.go", configFile, logFile, "backup")
|
||||
// backyCommand.Stderr = os.Stdout
|
||||
// backyCommand.Stdout = os.Stdout
|
||||
// err := backyCommand.Run()
|
||||
// assert.Nil(t, err)
|
||||
// os.Remove("ErrorHook.log")
|
||||
// logFileData, logFileErr := os.ReadFile("ErrorHook.log")
|
||||
// if logFileErr != nil {
|
||||
// assert.FailNow(t, logFileErr.Error())
|
||||
|
||||
// }
|
||||
// var JsonData []map[string]interface{}
|
||||
// jsonScanner := bufio.NewScanner(strings.NewReader(string(logFileData)))
|
||||
|
||||
// for jsonScanner.Scan() {
|
||||
// var jsonDataLine map[string]interface{}
|
||||
// err = json.Unmarshal(jsonScanner.Bytes(), &jsonDataLine)
|
||||
// assert.Nil(t, err)
|
||||
// JsonData = append(JsonData, jsonDataLine)
|
||||
// }
|
||||
// for _, v := range JsonData {
|
||||
// _, ok := v["error"]
|
||||
// if !ok {
|
||||
// assert.FailNow(t, "error does not exist\n")
|
||||
// // return
|
||||
// }
|
||||
// }
|
||||
// // t.Logf("%s", logFileData)
|
||||
// // t.Logf("%v", JsonData)
|
||||
// }
|
||||
|
||||
// func TestBackupErrorHook(t *testing.T) {
|
||||
// logFile = "ErrorHook.log"
|
||||
|
||||
// configFile = "../tests/ErrorHook.yml"
|
||||
|
||||
// }
|
@ -20,7 +20,7 @@ package cmd
|
||||
|
||||
// func config(cmd *cobra.Command, args []string) {
|
||||
|
||||
// opts := backy.NewOpts(cfgFile, backy.cronEnabled())
|
||||
// opts := backy.NewConfigOptions(configFile, backy.cronEnabled())
|
||||
// opts.InitConfig()
|
||||
|
||||
// }
|
||||
|
11
cmd/cron.go
11
cmd/cron.go
@ -16,9 +16,16 @@ var (
|
||||
)
|
||||
|
||||
func cron(cmd *cobra.Command, args []string) {
|
||||
parseS3Config()
|
||||
|
||||
opts := backy.NewConfigOptions(configFile,
|
||||
backy.EnableCron(),
|
||||
backy.SetLogFile(logFile),
|
||||
backy.EnableCommandStdOut(cmdStdOut),
|
||||
backy.SetHostsConfigFile(hostsConfigFile))
|
||||
|
||||
opts := backy.NewOpts(cfgFile, backy.CronEnabled())
|
||||
opts.InitConfig()
|
||||
backy.ReadConfig(opts)
|
||||
opts.ParseConfigurationFile()
|
||||
|
||||
opts.Cron()
|
||||
}
|
||||
|
17
cmd/exec.go
17
cmd/exec.go
@ -21,20 +21,23 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
execCmd.AddCommand(hostExecCommand)
|
||||
|
||||
hostExecCommand.Flags().StringSliceVarP(&hostsList, "hosts", "m", nil, "Accepts comma-separated names of hosts.")
|
||||
hostExecCommand.Flags().StringSliceVarP(&cmdList, "commands", "c", nil, "Accepts comma-separated names of commands.")
|
||||
execCmd.AddCommand(hostExecCommand, hostsExecCommand)
|
||||
|
||||
}
|
||||
|
||||
func execute(cmd *cobra.Command, args []string) {
|
||||
parseS3Config()
|
||||
|
||||
if len(args) < 1 {
|
||||
logging.ExitWithMSG("Please provide a command to run. Pass --help to see options.", 1, nil)
|
||||
}
|
||||
|
||||
opts := backy.NewOpts(cfgFile, backy.AddCommands(args))
|
||||
opts := backy.NewConfigOptions(configFile,
|
||||
backy.AddCommands(args),
|
||||
backy.SetLogFile(logFile),
|
||||
backy.EnableCommandStdOut(cmdStdOut),
|
||||
backy.SetHostsConfigFile(hostsConfigFile))
|
||||
opts.InitConfig()
|
||||
// opts.InitMongo()
|
||||
backy.ReadConfig(opts).ExecuteCmds(opts)
|
||||
opts.ParseConfigurationFile()
|
||||
opts.ExecuteCmds()
|
||||
}
|
||||
|
37
cmd/host.go
37
cmd/host.go
@ -8,19 +8,25 @@ import (
|
||||
|
||||
var (
|
||||
hostExecCommand = &cobra.Command{
|
||||
Use: "host [--commands=command1,command2, ... | -c command1,command2, ...] [--hosts=host1,hosts2, ... | -m host1,host2, ...] ",
|
||||
Use: "host [--command=command1 --command=command2 ... | -c command1 -c command2 ...] [--hosts=host1 --hosts=hosts2 ... | -m host1 -m host2 ...] ",
|
||||
Short: "Runs command defined in config file on the hosts in order specified.",
|
||||
Long: "Host executes specified commands on the hosts defined in config file.\nUse the --commands or -c flag to choose the commands.",
|
||||
Run: Host,
|
||||
}
|
||||
)
|
||||
|
||||
// Holds command list to run
|
||||
// Holds list of hosts to run commands on
|
||||
var hostsList []string
|
||||
|
||||
// Holds command list to run
|
||||
var cmdList []string
|
||||
|
||||
func init() {
|
||||
|
||||
hostExecCommand.Flags().StringArrayVarP(&hostsList, "hosts", "m", nil, "Accepts space-separated names of hosts. Specify multiple times for multiple hosts.")
|
||||
hostExecCommand.Flags().StringArrayVarP(&cmdList, "command", "c", nil, "Accepts space-separated names of commands. Specify multiple times for multiple commands.")
|
||||
parseS3Config()
|
||||
|
||||
}
|
||||
|
||||
// cli input should be hosts and commands. Hosts are defined in config files.
|
||||
@ -29,21 +35,36 @@ func init() {
|
||||
// 2. stdin (on command line) (TODO)
|
||||
|
||||
func Host(cmd *cobra.Command, args []string) {
|
||||
backyConfOpts := backy.NewOpts(cfgFile)
|
||||
backyConfOpts := backy.NewConfigOptions(configFile,
|
||||
backy.SetLogFile(logFile),
|
||||
backy.EnableCommandStdOut(cmdStdOut),
|
||||
backy.SetHostsConfigFile(hostsConfigFile))
|
||||
backyConfOpts.InitConfig()
|
||||
|
||||
backy.ReadConfig(backyConfOpts)
|
||||
backyConfOpts.ParseConfigurationFile()
|
||||
|
||||
// check CLI input
|
||||
if hostsList == nil {
|
||||
logging.ExitWithMSG("error: hosts must be specified", 1, &backyConfOpts.Logger)
|
||||
}
|
||||
// host is only checked when we read the SSH File
|
||||
// so a check may not be needed here
|
||||
|
||||
for _, h := range hostsList {
|
||||
if backy.IsHostLocal(h) {
|
||||
continue
|
||||
}
|
||||
// check if h exists in the config file
|
||||
_, hostFound := backyConfOpts.Hosts[h]
|
||||
if !hostFound {
|
||||
logging.ExitWithMSG("host "+h+" not found", 1, &backyConfOpts.Logger)
|
||||
// check if h exists in the SSH config file
|
||||
hostFoundInConfig, s := backy.DoesHostHaveHostName(h)
|
||||
if !hostFoundInConfig {
|
||||
logging.ExitWithMSG("host "+h+" not found", 1, &backyConfOpts.Logger)
|
||||
}
|
||||
if backyConfOpts.Hosts == nil {
|
||||
backyConfOpts.Hosts = make(map[string]*backy.Host)
|
||||
}
|
||||
// create host with hostname and host
|
||||
backyConfOpts.Hosts[h] = &backy.Host{Host: h, HostName: s}
|
||||
}
|
||||
}
|
||||
if cmdList == nil {
|
||||
@ -56,5 +77,5 @@ func Host(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
backyConfOpts.ExecCmdsSSH(cmdList, hostsList)
|
||||
backyConfOpts.ExecCmdsOnHosts(cmdList, hostsList)
|
||||
}
|
||||
|
91
cmd/hosts.go
Normal file
91
cmd/hosts.go
Normal file
@ -0,0 +1,91 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/backy"
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/logging"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
hostsExecCommand = &cobra.Command{
|
||||
Use: "hosts [--command=command1 --command=command2 ... | -c command1 -c command2 ...]",
|
||||
Short: "Runs command defined in config file on the hosts in order specified.",
|
||||
Long: "Hosts executes specified commands on all the hosts defined in config file.\nUse the --commands or -c flag to choose the commands.",
|
||||
Run: Hosts,
|
||||
}
|
||||
|
||||
hostsListExecCommand = &cobra.Command{
|
||||
Use: "list list1 list2 ...",
|
||||
Short: "Runs lists in order specified defined in config file on all hosts.",
|
||||
Long: "Lists executes specified lists on all the hosts defined in hosts config.\nPass the names of lists as arguments after command.",
|
||||
Run: HostsList,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
hostsExecCommand.AddCommand(hostsListExecCommand)
|
||||
parseS3Config()
|
||||
|
||||
}
|
||||
|
||||
// cli input should be hosts and commands. Hosts are defined in config files.
|
||||
// commands can be passed by the following mutually exclusive options:
|
||||
// 1. as a list of commands defined in the config file
|
||||
// 2. stdin (on command line) (TODO)
|
||||
|
||||
func Hosts(cmd *cobra.Command, args []string) {
|
||||
backyConfOpts := backy.NewConfigOptions(configFile,
|
||||
backy.SetLogFile(logFile),
|
||||
backy.EnableCommandStdOut(cmdStdOut),
|
||||
backy.SetHostsConfigFile(hostsConfigFile))
|
||||
backyConfOpts.InitConfig()
|
||||
|
||||
backyConfOpts.ParseConfigurationFile()
|
||||
|
||||
for _, h := range backyConfOpts.Hosts {
|
||||
|
||||
hostsList = append(hostsList, h.Host)
|
||||
}
|
||||
|
||||
if cmdList == nil {
|
||||
logging.ExitWithMSG("error: commands must be specified", 1, &backyConfOpts.Logger)
|
||||
}
|
||||
for _, c := range cmdList {
|
||||
_, cmdFound := backyConfOpts.Cmds[c]
|
||||
if !cmdFound {
|
||||
logging.ExitWithMSG("cmd "+c+" not found", 1, &backyConfOpts.Logger)
|
||||
}
|
||||
}
|
||||
|
||||
backyConfOpts.ExecCmdsOnHosts(cmdList, hostsList)
|
||||
}
|
||||
|
||||
func HostsList(cmd *cobra.Command, args []string) {
|
||||
backyConfOpts := backy.NewConfigOptions(configFile,
|
||||
backy.SetLogFile(logFile),
|
||||
backy.EnableCommandStdOut(cmdStdOut),
|
||||
backy.SetHostsConfigFile(hostsConfigFile))
|
||||
backyConfOpts.InitConfig()
|
||||
|
||||
backyConfOpts.ParseConfigurationFile()
|
||||
|
||||
if len(args) == 0 {
|
||||
logging.ExitWithMSG("error: no lists specified", 1, &backyConfOpts.Logger)
|
||||
}
|
||||
|
||||
for _, l := range args {
|
||||
_, listFound := backyConfOpts.CmdConfigLists[l]
|
||||
if !listFound {
|
||||
logging.ExitWithMSG("list "+l+" not found", 1, &backyConfOpts.Logger)
|
||||
}
|
||||
}
|
||||
|
||||
maps.DeleteFunc(backyConfOpts.CmdConfigLists, func(k string, v *backy.CmdList) bool {
|
||||
return !slices.Contains(args, k)
|
||||
})
|
||||
|
||||
backyConfOpts.ExecuteListOnHosts(args)
|
||||
}
|
71
cmd/list.go
71
cmd/list.go
@ -6,16 +6,29 @@ package cmd
|
||||
|
||||
import (
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/backy"
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/logging"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
listCmd = &cobra.Command{
|
||||
Use: "list [--list=list1,list2,... | -l list1, list2,...] [ -cmd cmd1 cmd2 cmd3...]",
|
||||
Short: "Lists commands, lists, or hosts defined in config file.",
|
||||
Long: "Backup lists commands or groups defined in config file.\nUse the --lists or -l flag to list the specified lists. If not flag is not given, all lists will be executed.",
|
||||
Run: List,
|
||||
Use: "list [command]",
|
||||
Short: "List commands, lists, or hosts defined in config file.",
|
||||
Long: "List commands, lists, or hosts defined in config file",
|
||||
}
|
||||
|
||||
listCmds = &cobra.Command{
|
||||
Use: "cmds [cmd1 cmd2 cmd3...]",
|
||||
Short: "List commands defined in config file.",
|
||||
Long: "List commands defined in config file",
|
||||
Run: ListCommands,
|
||||
}
|
||||
listCmdLists = &cobra.Command{
|
||||
Use: "lists [list1 list2 ...]",
|
||||
Short: "List lists defined in config file.",
|
||||
Long: "List lists defined in config file",
|
||||
Run: ListCommandLists,
|
||||
}
|
||||
)
|
||||
|
||||
@ -23,27 +36,55 @@ var listsToList []string
|
||||
var cmdsToList []string
|
||||
|
||||
func init() {
|
||||
|
||||
listCmd.Flags().StringSliceVarP(&listsToList, "lists", "l", nil, "Accepts comma-separated names of command lists to list.")
|
||||
listCmd.Flags().StringSliceVarP(&cmdsToList, "cmds", "c", nil, "Accepts comma-separated names of commands to list.")
|
||||
listCmd.AddCommand(listCmds, listCmdLists)
|
||||
|
||||
}
|
||||
|
||||
func List(cmd *cobra.Command, args []string) {
|
||||
func ListCommands(cmd *cobra.Command, args []string) {
|
||||
|
||||
// settup based on whats passed in:
|
||||
// setup based on whats passed in:
|
||||
// - cmds
|
||||
// - lists
|
||||
// - if none, list all commands
|
||||
if cmdLists != nil {
|
||||
|
||||
if len(args) > 0 {
|
||||
cmdsToList = args
|
||||
} else {
|
||||
logging.ExitWithMSG("Error: list cmds subcommand needs commands to list", 1, nil)
|
||||
}
|
||||
|
||||
opts := backy.NewOpts(cfgFile)
|
||||
parseS3Config()
|
||||
|
||||
opts := backy.NewConfigOptions(configFile,
|
||||
backy.SetLogFile(logFile),
|
||||
backy.SetHostsConfigFile(hostsConfigFile))
|
||||
|
||||
opts.InitConfig()
|
||||
opts.ParseConfigurationFile()
|
||||
|
||||
for _, v := range cmdsToList {
|
||||
opts.ListCommand(v)
|
||||
}
|
||||
}
|
||||
|
||||
func ListCommandLists(cmd *cobra.Command, args []string) {
|
||||
|
||||
parseS3Config()
|
||||
|
||||
if len(args) > 0 {
|
||||
listsToList = args
|
||||
} else {
|
||||
logging.ExitWithMSG("Error: lists subcommand needs lists", 1, nil)
|
||||
}
|
||||
|
||||
opts := backy.NewConfigOptions(configFile,
|
||||
backy.SetLogFile(logFile),
|
||||
backy.SetHostsConfigFile(hostsConfigFile))
|
||||
|
||||
opts.InitConfig()
|
||||
opts.ParseConfigurationFile()
|
||||
|
||||
for _, v := range listsToList {
|
||||
opts.ListCommandList(v)
|
||||
}
|
||||
|
||||
opts = backy.ReadConfig(opts)
|
||||
|
||||
opts.ListCommand("rm-sn-db")
|
||||
}
|
||||
|
21
cmd/root.go
21
cmd/root.go
@ -13,8 +13,12 @@ import (
|
||||
|
||||
var (
|
||||
// Used for flags.
|
||||
cfgFile string
|
||||
verbose bool
|
||||
configFile string
|
||||
hostsConfigFile string
|
||||
verbose bool
|
||||
cmdStdOut bool
|
||||
logFile string
|
||||
s3Endpoint string
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "backy",
|
||||
@ -32,9 +36,18 @@ func Execute() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVar(&logFile, "logFile", "", "log file to write to")
|
||||
rootCmd.PersistentFlags().BoolVar(&cmdStdOut, "cmdStdOut", false, "Pass to print command output to stdout")
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "f", "", "config file to read from")
|
||||
rootCmd.PersistentFlags().StringVarP(&configFile, "config", "f", "", "config file to read from")
|
||||
rootCmd.PersistentFlags().StringVar(&hostsConfigFile, "hostsConfig", "", "yaml hosts file to read from")
|
||||
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Sets verbose level")
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&s3Endpoint, "s3Endpoint", "", "Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.")
|
||||
rootCmd.AddCommand(backupCmd, execCmd, cronCmd, versionCmd, listCmd)
|
||||
}
|
||||
|
||||
func parseS3Config() {
|
||||
if s3Endpoint != "" {
|
||||
os.Setenv("S3_ENDPOINT", s3Endpoint)
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const versionStr = "0.6.0"
|
||||
const versionStr = "0.10.2"
|
||||
|
||||
var (
|
||||
versionCmd = &cobra.Command{
|
||||
|
137
docs/config.toml
137
docs/config.toml
@ -1,137 +0,0 @@
|
||||
baseURL = 'http://backy.cybershell.xyz/'
|
||||
languageCode = 'en-us'
|
||||
title = 'A tool for commands'
|
||||
|
||||
# Change the default theme to be use when building the site with Hugo
|
||||
theme = [ "hugo-theme-relearn", "plausible-hugo"] # Add this theme to your already existing other themes
|
||||
|
||||
# For search functionality
|
||||
[outputs]
|
||||
home = [ "HTML", "RSS", "PRINT"]
|
||||
|
||||
[module]
|
||||
[[module.imports]]
|
||||
path = "github.com/divinerites/plausible-hugo"
|
||||
|
||||
[params]
|
||||
# This controls whether submenus will be expanded (true), or collapsed (false) in the
|
||||
# menu; if no setting is given, the first menu level is set to false, all others to true;
|
||||
# this can be overridden in the pages frontmatter
|
||||
alwaysopen = false
|
||||
# Prefix URL to edit current page. Will display an "Edit" button on top right hand corner of every page.
|
||||
# Useful to give opportunity to people to create merge request for your doc.
|
||||
# See the config.toml file from this documentation site to have an example.
|
||||
editURL = ""
|
||||
# Description of the site, will be used in meta information
|
||||
description = ""
|
||||
# Shows a checkmark for visited pages on the menu
|
||||
showVisitedLinks = false
|
||||
# Disable search function. It will hide search bar
|
||||
disableSearch = false
|
||||
# Disable search in hidden pages, otherwise they will be shown in search box
|
||||
disableSearchHiddenPages = false
|
||||
# Disables hidden pages from showing up in the sitemap and on Google (et all), otherwise they may be indexed by search engines
|
||||
disableSeoHiddenPages = false
|
||||
# Disables hidden pages from showing up on the tags page although the tag term will be displayed even if all pages are hidden
|
||||
disableTagHiddenPages = false
|
||||
# Javascript and CSS cache are automatically busted when new version of site is generated.
|
||||
# Set this to true to disable this behavior (some proxies don't handle well this optimization)
|
||||
disableAssetsBusting = false
|
||||
# Set this to true if you want to disable generation for generator version meta tags of hugo and the theme;
|
||||
# don't forget to also set Hugo's disableHugoGeneratorInject=true, otherwise it will generate a meta tag into your home page
|
||||
disableGeneratorVersion = false
|
||||
# Set this to true to disable copy-to-clipboard button for inline code.
|
||||
disableInlineCopyToClipBoard = false
|
||||
# A title for shortcuts in menu is set by default. Set this to true to disable it.
|
||||
disableShortcutsTitle = false
|
||||
# If set to false, a Home button will appear below the search bar on the menu.
|
||||
# It is redirecting to the landing page of the current language if specified. (Default is "/")
|
||||
disableLandingPageButton = true
|
||||
# When using mulitlingual website, disable the switch language button.
|
||||
disableLanguageSwitchingButton = false
|
||||
# Hide breadcrumbs in the header and only show the current page title
|
||||
disableBreadcrumb = true
|
||||
# If set to true, hide table of contents menu in the header of all pages
|
||||
disableToc = false
|
||||
# If set to false, load the MathJax module on every page regardless if a MathJax shortcode is present
|
||||
math = true
|
||||
# Specifies the remote location of the MathJax js
|
||||
customMathJaxURL = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"
|
||||
# Initialization parameter for MathJax, see MathJax documentation
|
||||
mathJaxInitialize = "{}"
|
||||
# If set to false, load the Mermaid module on every page regardless if a Mermaid shortcode or Mermaid codefence is present
|
||||
mermaid = true
|
||||
# Specifies the remote location of the Mermaid js
|
||||
customMermaidURL = "https://unpkg.com/mermaid/dist/mermaid.min.js"
|
||||
# Initialization parameter for Mermaid, see Mermaid documentation
|
||||
mermaidInitialize = "{ \"theme\": \"default\" }"
|
||||
# If set to false, load the Swagger module on every page regardless if a Swagger shortcode is present
|
||||
disableSwagger = false
|
||||
# Specifies the remote location of the RapiDoc js
|
||||
customSwaggerURL = "https://unpkg.com/rapidoc/dist/rapidoc-min.js"
|
||||
# Initialization parameter for Swagger, see RapiDoc documentation
|
||||
swaggerInitialize = "{ \"theme\": \"light\" }"
|
||||
# Hide Next and Previous page buttons normally displayed full height beside content
|
||||
disableNextPrev = true
|
||||
# Order sections in menu by "weight" or "title". Default to "weight";
|
||||
# this can be overridden in the pages frontmatter
|
||||
ordersectionsby = "weight"
|
||||
|
||||
[[params.themeVariant]]
|
||||
auto = []
|
||||
identifier = 'relearn-auto'
|
||||
name = 'Relearn Light/Dark'
|
||||
|
||||
[[params.themeVariant]]
|
||||
identifier = 'relearn-light'
|
||||
|
||||
[[params.themeVariant]]
|
||||
identifier = 'relearn-dark'
|
||||
|
||||
[[params.themeVariant]]
|
||||
identifier = 'relearn-bright'
|
||||
|
||||
[[params.themeVariant]]
|
||||
auto = ['zen-light', 'zen-dark']
|
||||
identifier = 'zen-auto'
|
||||
name = 'Zen Light/Dark'
|
||||
|
||||
[[params.themeVariant]]
|
||||
identifier = 'zen-light'
|
||||
|
||||
[[params.themeVariant]]
|
||||
identifier = 'zen-dark'
|
||||
|
||||
[[params.themeVariant]]
|
||||
auto = ['learn', 'neon']
|
||||
identifier = 'retro-auto'
|
||||
name = 'Retro Learn/Neon'
|
||||
|
||||
[[params.themeVariant]]
|
||||
identifier = 'neon'
|
||||
|
||||
[[params.themeVariant]]
|
||||
identifier = 'learn'
|
||||
# Change the title separator. Default to "::".
|
||||
titleSeparator = "-"
|
||||
# If set to true, the menu in the sidebar will be displayed in a collapsible tree view. Although the functionality works with old browsers (IE11), the display of the expander icons is limited to modern browsers
|
||||
collapsibleMenu = true
|
||||
# If a single page can contain content in multiple languages, add those here
|
||||
additionalContentLanguage = [ "en" ]
|
||||
# If set to true, no index.html will be appended to prettyURLs; this will cause pages not
|
||||
# to be servable from the file system
|
||||
disableExplicitIndexURLs = false
|
||||
# For external links you can define how they are opened in your browser; this setting will only be applied to the content area but not the shortcut menu
|
||||
externalLinkTarget = "_blank"
|
||||
# Author of the site, will be used in meta information
|
||||
[params.author]
|
||||
name = "Andrew Woodlee"
|
||||
|
||||
|
||||
[params.plausible]
|
||||
enable = true # Whether to enable plausible tracking
|
||||
domain = "backy.cybershell.xyz" # Plausible "domain" name/id in your dashboard
|
||||
outbound_link = true
|
||||
gitstar = false
|
||||
selfhosted_domain = "stats.andrewnw.com" # Self-hosted plausible domain
|
||||
|
78
docs/config.yaml
Normal file
78
docs/config.yaml
Normal file
@ -0,0 +1,78 @@
|
||||
baseURL: https://backy.cybershell.xyz/
|
||||
languageCode: en-us
|
||||
title: A tool for commands
|
||||
theme:
|
||||
- hugo-theme-relearn
|
||||
- plausible-hugo
|
||||
outputs:
|
||||
home:
|
||||
- HTML
|
||||
- RSS
|
||||
- PRINT
|
||||
module:
|
||||
imports:
|
||||
- path: github.com/divinerites/plausible-hugo
|
||||
- path: github.com/McShelby/hugo-theme-relearn
|
||||
params:
|
||||
themeVariant:
|
||||
- auto: []
|
||||
identifier: relearn-auto
|
||||
name: Relearn Light/Dark
|
||||
- identifier: relearn-light
|
||||
- identifier: relearn-dark
|
||||
- identifier: relearn-bright
|
||||
- auto:
|
||||
- zen-light
|
||||
- zen-dark
|
||||
identifier: zen-auto
|
||||
name: Zen Light/Dark
|
||||
- identifier: zen-light
|
||||
- identifier: zen-dark
|
||||
- auto:
|
||||
- learn
|
||||
- neon
|
||||
identifier: retro-auto
|
||||
name: Retro Learn/Neon
|
||||
- identifier: neon
|
||||
- identifier: learn
|
||||
plausible:
|
||||
enable: true
|
||||
domain: backy.cybershell.xyz
|
||||
outbound_link: true
|
||||
gitstar: false
|
||||
selfhosted_domain: stats.andrewnw.com
|
||||
author:
|
||||
name: Andrew Woodlee
|
||||
alwaysopen: false
|
||||
editURL: ""
|
||||
description: ""
|
||||
showVisitedLinks: false
|
||||
disableSearch: false
|
||||
disableSearchHiddenPages: false
|
||||
disableSeoHiddenPages: false
|
||||
disableTagHiddenPages: false
|
||||
disableAssetsBusting: false
|
||||
disableGeneratorVersion: false
|
||||
disableInlineCopyToClipBoard: false
|
||||
disableShortcutsTitle: false
|
||||
disableLandingPageButton: true
|
||||
disableLanguageSwitchingButton: false
|
||||
disableBreadcrumb: true
|
||||
disableToc: false
|
||||
math: true
|
||||
customMathJaxURL: https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
|
||||
mathJaxInitialize: "{}"
|
||||
mermaid: true
|
||||
customMermaidURL: https://unpkg.com/mermaid/dist/mermaid.min.js
|
||||
mermaidInitialize: '{ "theme": "default" }'
|
||||
disableSwagger: false
|
||||
customSwaggerURL: https://unpkg.com/rapidoc/dist/rapidoc-min.js
|
||||
swaggerInitialize: '{ "theme": "light" }'
|
||||
disableNextPrev: true
|
||||
ordersectionsby: weight
|
||||
titleSeparator: "-"
|
||||
collapsibleMenu: true
|
||||
additionalContentLanguage:
|
||||
- en
|
||||
disableExplicitIndexURLs: false
|
||||
externalLinkTarget: _blank
|
@ -17,6 +17,10 @@ Feel free to open a [PR](https://git.andrewnw.xyz/CyberShell/backy/pulls), raise
|
||||
|
||||
- Allows easy configuration of executable commands
|
||||
|
||||
- Allows for running package operations
|
||||
|
||||
- Allows configuring failure, success, and final hooks
|
||||
|
||||
- Allows for commands to be run on many hosts over SSH
|
||||
|
||||
- Commands can be grouped in list to run in specific order
|
||||
|
@ -14,18 +14,22 @@ Usage:
|
||||
backy [command]
|
||||
|
||||
Available Commands:
|
||||
backup Runs commands defined in config file.
|
||||
backup Runs commands defined in config file. Use -l flag multiple times to run multiple lists.
|
||||
completion Generate the autocompletion script for the specified shell
|
||||
cron Starts a scheduler that runs lists defined in config file.
|
||||
exec Runs commands defined in config file in order given.
|
||||
help Help about any command
|
||||
list Lists commands, lists, or hosts defined in config file.
|
||||
list List commands, lists, or hosts defined in config file.
|
||||
version Prints the version and exits
|
||||
|
||||
Flags:
|
||||
-f, --config string config file to read from
|
||||
-h, --help help for backy
|
||||
-v, --verbose Sets verbose level
|
||||
--cmdStdOut Pass to print command output to stdout
|
||||
-f, --config string config file to read from
|
||||
-h, --help help for backy
|
||||
--hostsConfig string yaml hosts file to read from
|
||||
--logFile string log file to write to
|
||||
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
|
||||
-v, --verbose Sets verbose level
|
||||
|
||||
Use "backy [command] --help" for more information about a command.
|
||||
```
|
||||
@ -39,15 +43,19 @@ Backup executes commands defined in config file.
|
||||
Use the --lists or -l flag to execute the specified lists. If not flag is not given, all lists will be executed.
|
||||
|
||||
Usage:
|
||||
backy backup [--lists=list1,list2,... | -l list1, list2,...] [flags]
|
||||
backy backup [--lists=list1 --lists list2 ... | -l list1 -l list2 ...] [flags]
|
||||
|
||||
Flags:
|
||||
-h, --help help for backup
|
||||
-l, --lists strings Accepts comma-separated names of command lists to execute.
|
||||
-h, --help help for backup
|
||||
-l, --lists stringArray Accepts comma-separated names of command lists to execute.
|
||||
|
||||
Global Flags:
|
||||
-f, --config string config file to read from
|
||||
-v, --verbose Sets verbose level
|
||||
--cmdStdOut Pass to print command output to stdout
|
||||
-f, --config string config file to read from
|
||||
--hostsConfig string yaml hosts file to read from
|
||||
--logFile string log file to write to
|
||||
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
|
||||
-v, --verbose Sets verbose level
|
||||
```
|
||||
|
||||
## cron
|
||||
@ -62,8 +70,12 @@ Flags:
|
||||
-h, --help help for cron
|
||||
|
||||
Global Flags:
|
||||
-f, --config string config file to read from
|
||||
-v, --verbose Sets verbose level
|
||||
--cmdStdOut Pass to print command output to stdout
|
||||
-f, --config string config file to read from
|
||||
--hostsConfig string yaml hosts file to read from
|
||||
--logFile string log file to write to
|
||||
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
|
||||
-v, --verbose Sets verbose level
|
||||
```
|
||||
|
||||
## exec
|
||||
@ -77,13 +89,18 @@ Usage:
|
||||
|
||||
Available Commands:
|
||||
host Runs command defined in config file on the hosts in order specified.
|
||||
hosts Runs command defined in config file on the hosts in order specified.
|
||||
|
||||
Flags:
|
||||
-h, --help help for exec
|
||||
|
||||
Global Flags:
|
||||
-f, --config string config file to read from
|
||||
-v, --verbose Sets verbose level
|
||||
--cmdStdOut Pass to print command output to stdout
|
||||
-f, --config string config file to read from
|
||||
--hostsConfig string yaml hosts file to read from
|
||||
--logFile string log file to write to
|
||||
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
|
||||
-v, --verbose Sets verbose level
|
||||
|
||||
Use "backy exec [command] --help" for more information about a command.
|
||||
```
|
||||
@ -95,16 +112,20 @@ Host executes specified commands on the hosts defined in config file.
|
||||
Use the --commands or -c flag to choose the commands.
|
||||
|
||||
Usage:
|
||||
backy exec host [--commands=command1,command2, ... | -c command1,command2, ...] [--hosts=host1,hosts2, ... | -m host1,host2, ...] [flags]
|
||||
backy exec host [--command=command1 --command=command2 ... | -c command1 -c command2 ...] [--hosts=host1 --hosts=hosts2 ... | -m host1 -m host2 ...] [flags]
|
||||
|
||||
Flags:
|
||||
-c, --commands strings Accepts comma-separated names of commands.
|
||||
-h, --help help for host
|
||||
-m, --hosts strings Accepts comma-separated names of hosts.
|
||||
-c, --command stringArray Accepts space-separated names of commands. Specify multiple times for multiple commands.
|
||||
-h, --help help for host
|
||||
-m, --hosts stringArray Accepts space-separated names of hosts. Specify multiple times for multiple hosts.
|
||||
|
||||
Global Flags:
|
||||
-f, --config string config file to read from
|
||||
-v, --verbose Sets verbose level
|
||||
--cmdStdOut Pass to print command output to stdout
|
||||
-f, --config string config file to read from
|
||||
--hostsConfig string yaml hosts file to read from
|
||||
--logFile string log file to write to
|
||||
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
|
||||
-v, --verbose Sets verbose level
|
||||
```
|
||||
|
||||
## version
|
||||
@ -121,25 +142,74 @@ Flags:
|
||||
-V, --vpre Output the version with v prefixed.
|
||||
|
||||
Global Flags:
|
||||
-f, --config string config file to read from
|
||||
-v, --verbose Sets verbose level
|
||||
--cmdStdOut Pass to print command output to stdout
|
||||
-f, --config string config file to read from
|
||||
--hostsConfig string yaml hosts file to read from
|
||||
--logFile string log file to write to
|
||||
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
|
||||
-v, --verbose Sets verbose level
|
||||
```
|
||||
|
||||
## list
|
||||
|
||||
```
|
||||
Backup lists commands or groups defined in config file.
|
||||
Use the --lists or -l flag to list the specified lists. If not flag is not given, all lists will be executed.
|
||||
List commands, lists, or hosts defined in config file
|
||||
|
||||
Usage:
|
||||
backy list [--list=list1,list2,... | -l list1, list2,...] [ -cmd cmd1 cmd2 cmd3...] [flags]
|
||||
backy list [command]
|
||||
|
||||
Available Commands:
|
||||
cmds List commands defined in config file.
|
||||
lists List lists defined in config file.
|
||||
|
||||
Flags:
|
||||
-c, --cmds strings Accepts comma-separated names of commands to list.
|
||||
-h, --help help for list
|
||||
-l, --lists strings Accepts comma-separated names of command lists to list.
|
||||
-h, --help help for list
|
||||
|
||||
Global Flags:
|
||||
-f, --config string config file to read from
|
||||
-v, --verbose Sets verbose level
|
||||
--cmdStdOut Pass to print command output to stdout
|
||||
-f, --config string config file to read from
|
||||
--hostsConfig string yaml hosts file to read from
|
||||
--logFile string log file to write to
|
||||
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
|
||||
-v, --verbose Sets verbose level
|
||||
|
||||
Use "backy list [command] --help" for more information about a command.
|
||||
```
|
||||
## list cmds
|
||||
|
||||
```
|
||||
List commands defined in config file
|
||||
|
||||
Usage:
|
||||
backy list cmds [cmd1 cmd2 cmd3...] [flags]
|
||||
|
||||
Flags:
|
||||
-h, --help help for cmds
|
||||
|
||||
Global Flags:
|
||||
--cmdStdOut Pass to print command output to stdout
|
||||
-f, --config string config file to read from
|
||||
--hostsConfig string yaml hosts file to read from
|
||||
--logFile string log file to write to
|
||||
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
|
||||
-v, --verbose Sets verbose level
|
||||
```
|
||||
## list lists
|
||||
|
||||
```
|
||||
List lists defined in config file
|
||||
|
||||
Usage:
|
||||
backy list lists [list1 list2 ...] [flags]
|
||||
|
||||
Flags:
|
||||
-h, --help help for lists
|
||||
|
||||
Global Flags:
|
||||
--cmdStdOut Pass to print command output to stdout
|
||||
-f, --config string config file to read from
|
||||
--hostsConfig string yaml hosts file to read from
|
||||
--logFile string log file to write to
|
||||
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
|
||||
-v, --verbose Sets verbose level
|
||||
```
|
||||
|
@ -2,18 +2,18 @@
|
||||
title: Exec
|
||||
---
|
||||
|
||||
The `exec` subcommand can do somethings that the configuration file can't do yet. The command `exec host` can execute commands on many hosts.
|
||||
The `exec` subcommand can do some things that the configuration file can't do yet. The command `exec host` can execute commands on many hosts.
|
||||
|
||||
`exec host` takes the following arguments:
|
||||
|
||||
```sh
|
||||
-c, --commands strings Accepts comma-separated names of commands.
|
||||
-c, --commands strings Accepts space-separated names of commands.
|
||||
-h, --help help for host
|
||||
-m, --hosts strings Accepts comma-separated names of hosts.
|
||||
-m, --hosts strings Accepts space-separated names of hosts.
|
||||
```
|
||||
|
||||
The commands have to be defined in the config file. The hosts need to at least be in the ssh_config(5) file.
|
||||
|
||||
```sh
|
||||
backy exec host [--commands=command1,command2, ... | -c command1,command2, ...] [--hosts=host1,hosts2, ... | -m host1,host2, ...] [flags]
|
||||
backy exec host [--commands=command1 -commands=command2 ... | -c command1 -c command2 ...] [--hosts=host1 --hosts=hosts2 ... | -m host1 -m host2 ...] [flags]
|
||||
```
|
||||
|
29
docs/content/cli/list.md
Normal file
29
docs/content/cli/list.md
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
title: List
|
||||
---
|
||||
|
||||
|
||||
List commands, lists, or hosts defined in config file
|
||||
|
||||
Usage:
|
||||
```
|
||||
backy list [command]
|
||||
```
|
||||
|
||||
Available Commands:
|
||||
cmds List commands defined in config file.
|
||||
lists List lists defined in config file.
|
||||
|
||||
Flags:
|
||||
```
|
||||
-h, --help help for list
|
||||
```
|
||||
|
||||
Global Flags:
|
||||
```
|
||||
--cmdStdOut Pass to print command output to stdout
|
||||
-f, --config string config file to read from
|
||||
--log-file string log file to write to
|
||||
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
|
||||
-v, --verbose Sets verbose level
|
||||
```
|
@ -10,13 +10,31 @@ This is the section on the config file.
|
||||
To use a specific file:
|
||||
```backy [command] -f /path/to/file```
|
||||
|
||||
You can also use a remote file:
|
||||
```
|
||||
backy [command] -f `s3/http source`
|
||||
```
|
||||
|
||||
See remote resources docs for specific info.
|
||||
|
||||
If you leave the config path blank, the following paths will be searched in order:
|
||||
|
||||
1. `./backy.yml`
|
||||
2. `./backy.yaml`
|
||||
3. `~/.config/backy.yml`
|
||||
4. `~/.config/backy.yaml`
|
||||
3. The same two files above contained in a `backy` subdirectory under in what is returned by Go's `os` package function `UserConfigDir()`.
|
||||
|
||||
Create a file at `~/.config/backy.yml`.
|
||||
{{% expand title="`UserConfigDir()` documentation:" %}}
|
||||
|
||||
See the rest of the documentation in this section to configure it.
|
||||
Up-to date documentation for this function may be found on [GoDoc](https://pkg.go.dev/os#UserConfigDir).
|
||||
|
||||
>UserConfigDir returns the default root directory to use for user-specific configuration data. Users should create their own application-specific subdirectory within this one and use that.
|
||||
|
||||
>On Unix systems, it returns $XDG_CONFIG_HOME as specified by https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html if non-empty, else $HOME/.config. On Darwin, it returns $HOME/Library/Application Support. On Windows, it returns %AppData%. On Plan 9, it returns $home/lib.
|
||||
|
||||
>If the location cannot be determined (for example, $HOME is not defined), then it will return an error.
|
||||
|
||||
{{% /expand %}}
|
||||
|
||||
See the rest of the documentation, titles included below, in this section to configure it.
|
||||
|
||||
{{% children description="true" %}}
|
@ -2,7 +2,7 @@
|
||||
title: "Command Lists"
|
||||
weight: 2
|
||||
description: >
|
||||
This page tells you how to get started with Backy.
|
||||
This page tells you how to use command lists.
|
||||
---
|
||||
|
||||
Command lists are for executing commands in sequence and getting notifications from them.
|
||||
@ -11,10 +11,18 @@ The top-level object key can be anything you want but not the same as another.
|
||||
|
||||
Lists can go in a separate file. Command lists should be in a separate file if:
|
||||
|
||||
1. key 'cmd-lists.file' is found
|
||||
2. hosts.yml or hosts.yaml is found in the same directory as the backy config file
|
||||
1. key 'cmdLists.file' is specified
|
||||
2. lists.yml or lists.yaml is found in the same directory as the backy config file (this includes remote config files as of v0.7.0)
|
||||
|
||||
```yaml
|
||||
{{% notice info %}}
|
||||
The lists file is also checked in remote resources.
|
||||
|
||||
The lists file is ignored under the following condition:
|
||||
|
||||
If a remote config file is specified (on the command-line using `-f`) and the lists file is not found in the same directory, the lists file is assumed to not exist.
|
||||
{{% /notice %}}
|
||||
|
||||
```yaml {lineNos="true" wrap="true" title="yaml"}
|
||||
test2:
|
||||
name: test2
|
||||
order:
|
||||
@ -27,12 +35,12 @@ Lists can go in a separate file. Command lists should be in a separate file if:
|
||||
```
|
||||
|
||||
| key | description | type | required
|
||||
| --- | --- | --- | --- |
|
||||
| `order` | Defines the sequence of commands to execute | `[]string` | yes |
|
||||
| `getOutput` | Command(s) output is in the notification(s) | `bool` | no |
|
||||
| `notifications` | The notification service(s) and ID(s) to use on success and failure. Must be *`service.id`*. See the [notifications documentation page](/config/notifications/) for more | `[]string` | no |
|
||||
| `name` | Optional name of the list | `string` | no |
|
||||
| `cron` | Time at which to schedule the list. Only has affect when cron subcommand is run. | `string` | no |
|
||||
| --- | --- | --- | ---
|
||||
| `order` | Defines the sequence of commands to execute | `[]string` | yes
|
||||
| `sendNotificationOnSuccess` | Whether to send notification on list success with the commands' output | `bool` | no
|
||||
| `notifications` | The notification service(s) and ID(s) to use on success and failure. Must be *`service.id`*. See the [notifications documentation page](/config/notifications/) for more | `[]string` | no
|
||||
| `name` | Optional name of the list | `string` | no
|
||||
| `cron` | Time at which to schedule the list. Only has affect when cron subcommand is run. | `string` | no
|
||||
|
||||
### Order
|
||||
|
||||
@ -62,14 +70,14 @@ Name is optional. If name is not defined, name will be the object's map key.
|
||||
|
||||
Backy also has a cron mode, so one can run `backy cron` and start a process that schedules jobs to run at times defined in the configuration file.
|
||||
|
||||
Adding `cron: 0 0 1 * * *` to a `cmd-lists` object will schedule the list at 1 in the morning. See [https://crontab.guru/](https://crontab.guru/) for reference.
|
||||
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.
|
||||
|
||||
{{% notice tip %}}
|
||||
Note: Backy uses the second field of cron, so add anything except * to the beginning of a regular cron expression.
|
||||
Note: Backy uses the second field of cron, so add anything except `*` to the beginning of a regular cron expression.
|
||||
{{% /notice %}}
|
||||
|
||||
```yaml
|
||||
cmd-lists:
|
||||
```yaml {lineNos="true" wrap="true" title="yaml"}
|
||||
cmdLists:
|
||||
docker-container-backup: # this can be any name you want
|
||||
# all commands have to be defined
|
||||
order:
|
||||
|
@ -1,152 +0,0 @@
|
||||
---
|
||||
title: "Commands"
|
||||
weight: 1
|
||||
---
|
||||
|
||||
The yaml top-level map can be any string.
|
||||
|
||||
The top-level name must be unique.
|
||||
|
||||
### Example Config
|
||||
|
||||
```yaml
|
||||
commands:
|
||||
stop-docker-container:
|
||||
cmd: docker
|
||||
Args:
|
||||
- compose
|
||||
- -f /some/path/to/docker-compose.yaml
|
||||
- down
|
||||
# if host is not defined, command will be run locally
|
||||
# The host has to be defined in either the config file or the SSH Config files
|
||||
host: some-host
|
||||
hooks
|
||||
error:
|
||||
- some-other-command-when-failing
|
||||
success:
|
||||
- success-command
|
||||
final:
|
||||
- final-command
|
||||
backup-docker-container-script:
|
||||
cmd: /path/to/local/script
|
||||
# script file is input as stdin to SSH
|
||||
type: scriptFile # also can be script
|
||||
environment:
|
||||
- FOO=BAR
|
||||
- APP=$VAR
|
||||
```
|
||||
|
||||
Values available for this section **(case-sensitive)**:
|
||||
|
||||
| name | notes | type | required
|
||||
| --- | --- | --- | --- |
|
||||
| `cmd` | Defines the command to execute | `string` | yes |
|
||||
| `Args` | Defines the arguments to the command | `[]string` | no |
|
||||
| `environment` | Defines evironment variables for the command | `[]string` | no |
|
||||
| `type` | May be `scriptFile`, `script`, or `package`. Runs script from local machine on remote. `Package` is the only one that can be run on local and remote hosts. | `string` | no |
|
||||
| `getOutput` | Command(s) output is in the notification(s) | `bool` | no |
|
||||
| `host` | If not specified, the command will execute locally. | `string` | no |
|
||||
| `scriptEnvFile` | When type is `scriptFile` or `script`, this file is prepended to the input. | `string` | no |
|
||||
| `shell` | Run the command in the shell | `string` | no |
|
||||
| `hooks` | Hooks are used at the end of the individual command. Must have at least `error`, `success`, or `final`. | `map[string][]string` | no |
|
||||
|
||||
#### cmd
|
||||
|
||||
cmd must be a valid command or script to execute.
|
||||
|
||||
#### Args
|
||||
|
||||
args must be arguments to cmd as they would be passed on the command-line:
|
||||
|
||||
```sh
|
||||
cmd [arg1 arg2 ...]
|
||||
```
|
||||
|
||||
Define them in an array:
|
||||
|
||||
```yaml
|
||||
Args:
|
||||
- arg1
|
||||
- arg2
|
||||
- arg3
|
||||
```
|
||||
|
||||
### getOutput
|
||||
|
||||
Get command output when a notification is sent.
|
||||
|
||||
Is not required. Can be `true` or `false`.
|
||||
|
||||
#### host
|
||||
|
||||
{{% notice info %}}
|
||||
If any `host` is not defined or left blank, the command will run on the local machine.
|
||||
{{% /notice %}}
|
||||
|
||||
Host may or may not be defined in the `hosts` section.
|
||||
|
||||
{{% notice info %}}
|
||||
If any `host` from the commands section does not match any object in the `hosts` section, the `Host` is assumed to be this value. This value will be used to search in the default SSH config files.
|
||||
|
||||
For example, say that I have a host defined in my SSH config with the `Host` defined as `web-prod`.
|
||||
If I assign a value to host as `host: web-prod` and don't specify this value in the `hosts` object, web-prod will be used as the `Host` in searching the SSH config files.
|
||||
{{% /notice %}}
|
||||
|
||||
### shell
|
||||
|
||||
If shell is defined, the command will run in the specified shell.
|
||||
Make sure to escape any shell input.
|
||||
|
||||
### scriptEnvFile
|
||||
|
||||
Path to a file.
|
||||
|
||||
When type is `script` or `scriptFile` , the script is appended to this file.
|
||||
|
||||
This is useful for specifying environment variables or other things so they don't have to be included in the script.
|
||||
|
||||
### type
|
||||
|
||||
May be `scriptFile` or `script`. Runs script from local machine on remote host passed to the SSH session as standard input.
|
||||
|
||||
If `type` is `script`, `cmd` is used as the script.
|
||||
|
||||
If `type` is `scriptFile`, cmd must be a script file.
|
||||
|
||||
If `type` is `package`, there are additional fields that must be specified.
|
||||
|
||||
### environment
|
||||
|
||||
The environment variables support expansion:
|
||||
|
||||
- using escaped values `$VAR` or `${VAR}`
|
||||
|
||||
For now, the variables have to be defined in an `.env` file in the same directory as the config file.
|
||||
|
||||
If using it with host specified, the SSH server has to be configured to accept those env variables.
|
||||
|
||||
If the command is run locally, the OS's environment is added.
|
||||
|
||||
### hooks
|
||||
|
||||
Hooks are run after the command is run.
|
||||
|
||||
Errors are run if the command errors, success if it returns no error. Final hooks are run regardless of error condition.
|
||||
|
||||
Values for hooks are as follows:
|
||||
|
||||
```yaml
|
||||
command:
|
||||
hook:
|
||||
# these commands are defined elsewhere in the file
|
||||
error:
|
||||
- errcommand
|
||||
success:
|
||||
- successcommand
|
||||
final:
|
||||
- donecommand
|
||||
```
|
||||
|
||||
### packages
|
||||
|
||||
See the [dedicated page](/config/packages) for package configuration.
|
128
docs/content/config/commands/_index.md
Normal file
128
docs/content/config/commands/_index.md
Normal file
@ -0,0 +1,128 @@
|
||||
---
|
||||
title: "Commands"
|
||||
description: Commands are just that, commands
|
||||
weight: 1
|
||||
---
|
||||
|
||||
|
||||
|
||||
### Example Config
|
||||
|
||||
{{% code file="/examples/example.yml" language="yaml" %}}
|
||||
|
||||
Values available for this section **(case-sensitive)**:
|
||||
|
||||
| name | notes | type | required | External directive support |
|
||||
| ----------------| ------------------------------------------------------------------------------------------------------- | --------------------- | -------- |----------------------------|
|
||||
| `cmd` | Defines the command to execute | `string` | yes | No |
|
||||
| `Args` | Defines the arguments to the command | `[]string` | no | No |
|
||||
| `environment` | Defines environment variables for the command | `[]string` | no | Partial |
|
||||
| `type` | See documentation further down the page. Additional fields may be required. | `string` | no | No |
|
||||
| `getOutput` | Command(s) output is in the notification(s) | `bool` | no | No |
|
||||
| `host` | If not specified, the command will execute locally. | `string` | no | No |
|
||||
| `scriptEnvFile` | When type is `scriptFile` or `script`, this file is prepended to the input. | `string` | no | No |
|
||||
| `shell` | Run the command in the shell | `string` | no | No |
|
||||
| `hooks` | Hooks are used at the end of the individual command. Must have at least `error`, `success`, or `final`. | `map[string][]string` | no | No |
|
||||
|
||||
#### cmd
|
||||
|
||||
cmd must be a valid command or script to execute.
|
||||
|
||||
#### Args
|
||||
|
||||
args must be arguments to cmd as they would be passed on the command-line:
|
||||
|
||||
```sh
|
||||
cmd [arg1 arg2 ...]
|
||||
```
|
||||
|
||||
Define them in an array:
|
||||
|
||||
```yaml
|
||||
Args:
|
||||
- arg1
|
||||
- arg2
|
||||
- arg3
|
||||
```
|
||||
|
||||
### getOutput
|
||||
|
||||
Get command output when a notification is sent.
|
||||
|
||||
Is not required. Can be `true` or `false`.
|
||||
|
||||
#### host
|
||||
|
||||
{{% notice info %}}
|
||||
If any `host` is not defined or left blank, the command will run on the local machine.
|
||||
{{% /notice %}}
|
||||
|
||||
Host may or may not be defined in the `hosts` section.
|
||||
|
||||
{{% notice info %}}
|
||||
If any `host` from the commands section does not match any object in the `hosts` section, the `Host` is assumed to be this value. This value will be used to search in the default SSH config files.
|
||||
|
||||
For example, say that I have a host defined in my SSH config with the `Host` defined as `web-prod`.
|
||||
If I assign a value to host as `host: web-prod` and don't specify this value in the `hosts` object, web-prod will be used as the `Host` in searching the SSH config files.
|
||||
{{% /notice %}}
|
||||
|
||||
### shell
|
||||
|
||||
If shell is defined, the command will run in the specified shell.
|
||||
Make sure to escape any shell input.
|
||||
|
||||
### scriptEnvFile
|
||||
|
||||
Path to a file.
|
||||
|
||||
When type is `script` or `scriptFile` , the script is appended to this file.
|
||||
|
||||
This is useful for specifying environment variables or other things so they don't have to be included in the script.
|
||||
|
||||
### type
|
||||
|
||||
The following options are available:
|
||||
|
||||
| name | description |
|
||||
| --- | --- |
|
||||
| script | `cmd` is used as the script |
|
||||
| scriptFile | Can only be run on a host. `cmd` is read and used as the script, and `scriptEnvFile` can be used to add env variables |
|
||||
| package | Run package operations. See [dedicated page](/config/packages) for configuring package commands |
|
||||
| user | Run user operations. See [dedicated page](/config/user-commands) for configuring package commands |
|
||||
|
||||
### environment
|
||||
|
||||
The environment variables support expansion:
|
||||
|
||||
- using escaped values `$VAR` or `${VAR}`
|
||||
- using any external directive, and if using the env directive, the variable will be read from a `.env` file
|
||||
|
||||
<!-- For now, the variables expanded have to be defined in an `.env` file in the same directory that the program is run from. -->
|
||||
|
||||
If using it with host specified, the SSH server has to be configured to accept those env variables.
|
||||
|
||||
If the command is run locally, the OS's environment is added.
|
||||
|
||||
### hooks
|
||||
|
||||
Hooks are run after the command is run.
|
||||
|
||||
Errors are run if the command errors, success if it returns no error. Final hooks are run regardless of error condition.
|
||||
|
||||
Values for hooks are as follows:
|
||||
|
||||
```yaml
|
||||
command:
|
||||
hook:
|
||||
# these commands are defined elsewhere in the file
|
||||
error:
|
||||
- errcommand
|
||||
success:
|
||||
- successcommand
|
||||
final:
|
||||
- donecommand
|
||||
```
|
||||
|
||||
### packages
|
||||
|
||||
See the [dedicated page](/config/packages) for package configuration.
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
title: "Packages"
|
||||
weight: 2
|
||||
description: This is dedicated to package commands.
|
||||
---
|
||||
|
||||
This is dedicated to `package` commands. The command `type` field must be `package`. Package is a type that allows one to perform package operations. There are several additional options available when `type` is `package`:
|
||||
@ -9,8 +10,8 @@ This is dedicated to `package` commands. The command `type` field must be `packa
|
||||
| --- | --- | --- | --- |
|
||||
| `packageName` | The name of a package to be modified. | `string` | yes |
|
||||
| `packageManager` | The name of the package manger to be used. | `string` | yes |
|
||||
| `packageOperation` | The type of operation to be perform. | `string` | yes |
|
||||
| `packageVersion` | The version of a package to be modified. | `string` | no |
|
||||
| `packageOperation` | The type of operation to perform. | `string` | yes |
|
||||
| `packageVersion` | The version of a package. | `string` | no |
|
||||
|
||||
|
||||
#### example
|
||||
@ -34,6 +35,7 @@ The following package operations are supported:
|
||||
- `install`
|
||||
- `remove`
|
||||
- `upgrade`
|
||||
- `checkVersion`
|
||||
|
||||
#### packageManager
|
||||
|
||||
@ -45,11 +47,11 @@ The following package managers are recognized:
|
||||
|
||||
#### package command args
|
||||
|
||||
You can add additional arguments using the standard `Args` key. This is useful for adding more packages.
|
||||
You can add additional arguments using the standard `Args` key. This is useful for adding more packages, yet it does not work with `checkVersion`.
|
||||
|
||||
### Development
|
||||
|
||||
The PackageManager interface provides an easy to enforce functions and options. There are two interfaces, `PackageManager` and `ConfigurablePackageManager` in the directory `pkg/pkgman`. Go's import-cycle "feature" caused me to implement functional options using a third interface. `PackageManagerOption`is a function that takes an interface.
|
||||
The PackageManager interface provides an easy way to enforce functions and options. There are two interfaces, `PackageManager` and `ConfigurablePackageManager` in the directory `pkg/pkgman`. Go's import-cycle "feature" caused me to implement functional options using a third interface. `PackageManagerOption`is a function that takes an interface.
|
||||
|
||||
#### PackageManager
|
||||
|
66
docs/content/config/commands/user-commands.md
Normal file
66
docs/content/config/commands/user-commands.md
Normal file
@ -0,0 +1,66 @@
|
||||
---
|
||||
title: "User commands"
|
||||
weight: 2
|
||||
description: This is dedicated to user commands.
|
||||
---
|
||||
|
||||
This is dedicated to `user` commands. The command `type` field must be `user`. User is a type that allows one to perform user operations. There are several additional options available when `type` is `user`:
|
||||
|
||||
| name | notes | type | required | External directive support
|
||||
| ----------------| -------------------------------------------------------------| ---------- | ---------| --------------------------|
|
||||
| `userName` | The name of a user to be configured. | `string` | yes | no |
|
||||
| `userOperation` | The type of operation to perform. | `string` | yes | no |
|
||||
| `userID` | The user ID to use. | `string` | no | no |
|
||||
| `userGroups` | The groups the user should be added to. | `[]string` | no | no |
|
||||
| `systemUser` | Create a system user. | `bool` | no | no |
|
||||
| `userCreateHome`| Create the home directory. | `bool` | no | no |
|
||||
| `userSshPubKeys`| The keys to add to the user's authorized keys. | `[]string` | no | yes |
|
||||
| `userShell` | The shell for the user. | `string` | no | no |
|
||||
| `userHome` | The user's home directory. | `string` | no | no |
|
||||
| `userPassword` | The new password value when using the `password` operation. | `string` | no | yes |
|
||||
|
||||
|
||||
#### example
|
||||
|
||||
The following is an example of a package command:
|
||||
|
||||
```yaml
|
||||
addUser:
|
||||
name: add user backy with custom home dir
|
||||
type: user
|
||||
userName: backy
|
||||
userHome: /opt/backy
|
||||
userOperation: add
|
||||
host: some-host
|
||||
```
|
||||
|
||||
#### userOperation
|
||||
|
||||
The following package operations are supported:
|
||||
|
||||
- `add`
|
||||
- `remove`
|
||||
- `modify`
|
||||
- `password`
|
||||
- `checkIfExists`
|
||||
|
||||
### Development
|
||||
|
||||
The UserManager interface provides an way easy to add new commands. There is one interface `Usermanager` in directory `pkg/usermanager`.
|
||||
|
||||
#### UserManager
|
||||
|
||||
```go
|
||||
// UserManager defines the interface for user management operations.
|
||||
// All functions but one return a string for the command and any args.
|
||||
type UserManager interface {
|
||||
AddUser(username, homeDir, shell string, isSystem bool, groups, args []string) (string, []string)
|
||||
RemoveUser(username string) (string, []string)
|
||||
ModifyUser(username, homeDir, shell string, groups []string) (string, []string)
|
||||
// Modify password uses chpasswd for Linux systems to build the command to change the password
|
||||
// Should return a password as the last argument
|
||||
// TODO: refactor when adding more systems instead of Linux
|
||||
ModifyPassword(username, password string) (string, *strings.Reader, string)
|
||||
UserExists(username string) (string, []string)
|
||||
}
|
||||
```
|
15
docs/content/config/directives.md
Normal file
15
docs/content/config/directives.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "External Directives"
|
||||
weight: 2
|
||||
description: How to set up external directives.
|
||||
---
|
||||
|
||||
External directives are for including data that should not be in the config file. The following directives are supported:
|
||||
|
||||
- `%{file:path/to/file}%`
|
||||
- `%{env:ENV_VAR}%`
|
||||
- `%{vault:vault-key}%`
|
||||
|
||||
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.
|
24
docs/content/config/hosts.md
Normal file
24
docs/content/config/hosts.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: "Hosts"
|
||||
weight: 2
|
||||
description: >
|
||||
This page tells you how to use hosts.
|
||||
---
|
||||
|
||||
| Key | Description | Type | Required | External directive support |
|
||||
|----------------------|---------------------------------------------------------------|----------|----------|----------------------------|
|
||||
| `OS` | Operating system of the host (used for package commands) | `string` | no | No |
|
||||
| `config` | Path to the SSH config file | `string` | no | No |
|
||||
| `host` | Specifies the `Host` ssh_config(5) directive | `string` | yes | No |
|
||||
| `hostname` | Hostname of the host | `string` | no | No |
|
||||
| `knownHostsFile` | Path to the known hosts file | `string` | no | No |
|
||||
| `port` | Port number to connect to | `uint16` | no | No |
|
||||
| `proxyjump` | Proxy jump hosts, comma-separated | `string` | no | No |
|
||||
| `password` | Password for SSH authentication | `string` | no | No |
|
||||
| `privateKeyPath` | Path to the private key file | `string` | no | No |
|
||||
| `privateKeyPassword` | Password for the private key file | `string` | no | Yes |
|
||||
| `user` | Username for SSH authentication | `string` | no | No |
|
||||
|
||||
## exec host subcommand
|
||||
|
||||
Backy has a subcommand `exec host`. This subcommand takes the flags of `-m host1 -m host2`. The commands can also be specified by `-c command1 -c command2`.
|
@ -5,6 +5,7 @@ description: >
|
||||
This page tells you how to get set up Backy notifications.
|
||||
---
|
||||
|
||||
Notifications are only configurable for command lists, as of right now.
|
||||
|
||||
Notifications can be sent on command list completion and failure.
|
||||
|
||||
@ -38,23 +39,23 @@ There must be a section with an id (eg. `mail.test-svr`) following one of these
|
||||
|
||||
### mail
|
||||
|
||||
| key | description | type
|
||||
| --- | --- | ---
|
||||
| `host` | Specifies the SMTP host to connect to | `string`
|
||||
| `port` | Specifies the SMTP port | `uint16`
|
||||
| `senderaddress` | Address from which to send mail | `string`
|
||||
| `to` | Recipients to send emails to | `[]string`
|
||||
| `username` | SMTP username | `string`
|
||||
| `password` | SMTP password | `string`
|
||||
| key | description | type | External directive support |
|
||||
| --- | --- | --- | --- |
|
||||
| `host` | Specifies the SMTP host to connect to | `string` | no
|
||||
| `port` | Specifies the SMTP port | `uint16` | no
|
||||
| `senderaddress` | Address from which to send mail | `string` | no
|
||||
| `to` | Recipients to send emails to | `[]string` | no
|
||||
| `username` | SMTP username | `string` | no
|
||||
| `password` | SMTP password | `string` | yes
|
||||
|
||||
### matrix
|
||||
|
||||
| key | description | type
|
||||
| --- | --- | ---
|
||||
| `home-server` | Specifies the Matrix server connect to | `string`
|
||||
| `room-id` | Specifies the room ID of the room to send messages to | `string`
|
||||
| `access-token` | Matrix access token | `string`
|
||||
| `user-id` | Matrix user ID | `string`
|
||||
| key | description | type | External directive support |
|
||||
| --- | --- | ---| ---- |
|
||||
| `home-server` | Specifies the Matrix server connect to | `string` | no
|
||||
| `room-id` | Specifies the room ID of the room to send messages to | `string` | no
|
||||
| `access-token` | Matrix access token | `string` | yes
|
||||
| `user-id` | Matrix user ID | `string` | no
|
||||
|
||||
To get your access token (assumes you are using [Element](https://element.io/)) :
|
||||
|
||||
|
21
docs/content/config/remote-resources.md
Normal file
21
docs/content/config/remote-resources.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
title: "Remote resources"
|
||||
weight: 2
|
||||
description: This is dedicated to configuring remote resources.
|
||||
---
|
||||
|
||||
Remote resources can be used for a lot of things, including config files and scripts.
|
||||
|
||||
## Config file
|
||||
|
||||
For the main config file to be fetched remotely, pass the URL using `-f [url]`.
|
||||
|
||||
If using S3, you should use the s3 protocol URI: `s3://bucketName/key/path`. You will also need to set the env variable `S3_ENDPOINT` to the appropriate value. The flag `--s3-endpoint` can be used to override this value or to set this value, if not already set.
|
||||
|
||||
## Authentication
|
||||
|
||||
Currently, only the AWS authentication credentials file `~/.aws/credentials` is supported. For now, the environment variable `AWS_PROFILE` is used to lookup the profile.
|
||||
|
||||
## Scripts
|
||||
|
||||
Remote script support is currently limited to http/https endpoints.
|
@ -1,11 +1,12 @@
|
||||
---
|
||||
title: "Vault"
|
||||
weight: 4
|
||||
description: Set up and configure vault.
|
||||
---
|
||||
|
||||
[Vault](https://www.vaultproject.io/) is a tool for storing secrets and other data securely.
|
||||
|
||||
Vault config can be used by prefixing `vault:` in front of a password or ENV var.
|
||||
A Vault key can be used by prefixing `%{vault:vault.keys.name}%` in a field that supports external directives.
|
||||
|
||||
This is the object in the config file:
|
||||
|
||||
@ -17,10 +18,12 @@ vault:
|
||||
keys:
|
||||
- name: mongourl
|
||||
mountpath: secret
|
||||
key: data
|
||||
path: mongo/url
|
||||
type: # KVv1 or KVv2
|
||||
- name:
|
||||
path:
|
||||
type:
|
||||
mountpath:
|
||||
type: KVv2 # KVv1 or KVv2
|
||||
- name: someKeyName
|
||||
mountpath: secret
|
||||
key: keyData
|
||||
type: KVv2
|
||||
path: some/path
|
||||
```
|
||||
|
108
docs/content/examples/backy.yaml
Normal file
108
docs/content/examples/backy.yaml
Normal file
@ -0,0 +1,108 @@
|
||||
commands:
|
||||
stop-docker-container:
|
||||
cmd: docker
|
||||
Args:
|
||||
- compose
|
||||
- -f /some/path/to/docker-compose.yaml
|
||||
- down
|
||||
# if host is not defined, cmd will be run locally
|
||||
host: some-host
|
||||
hooks:
|
||||
final:
|
||||
- hostname
|
||||
error:
|
||||
- hostname
|
||||
backup-docker-container-script:
|
||||
cmd: /path/to/script
|
||||
# The host has to be defined in the config file
|
||||
host: some-host
|
||||
environment:
|
||||
- FOO=BAR
|
||||
- APP=$VAR
|
||||
shell-cmd:
|
||||
cmd: rsync
|
||||
shell: bash
|
||||
Args:
|
||||
- -av some-host:/path/to/data ~/Docker/Backups/docker-data
|
||||
hostname:
|
||||
cmd: hostname
|
||||
update-docker:
|
||||
type: package
|
||||
shell: zsh # best to run package commands in a shell
|
||||
packageName: docker-ce
|
||||
Args:
|
||||
- docker-ce-cli
|
||||
packageManager: apt
|
||||
packageOperation: install
|
||||
update-dockerApt:
|
||||
# type: package
|
||||
shell: zsh
|
||||
cmd: apt
|
||||
Args:
|
||||
- update
|
||||
- "&&"
|
||||
- apt install -y docker-ce
|
||||
- docker-ce-cli
|
||||
packageManager: apt
|
||||
packageOperation: install
|
||||
|
||||
cmd-lists:
|
||||
cmds-to-run: # this can be any name you want
|
||||
# all commands have to be defined
|
||||
order:
|
||||
- stop-docker-container
|
||||
- backup-docker-container-script
|
||||
- shell-cmd
|
||||
- hostname
|
||||
notifications:
|
||||
- matrix.matrix
|
||||
name: backup-some-server
|
||||
cron: "0 0 1 * * *"
|
||||
hostname:
|
||||
name: hostname
|
||||
order:
|
||||
- hostname
|
||||
notifications:
|
||||
- mail.prod-email
|
||||
|
||||
hosts:
|
||||
# any ssh_config(5) keys/values not listed here will be looked up in the config file or the default config file
|
||||
some-host:
|
||||
hostname: some-hostname
|
||||
config: ~/.ssh/config
|
||||
user: user
|
||||
privateKeyPath: /path/to/private/key
|
||||
port: 22
|
||||
# can also be env:VAR
|
||||
password: file:/path/to/file
|
||||
# only one is supported for now
|
||||
proxyjump: some-proxy-host
|
||||
|
||||
# optional
|
||||
logging:
|
||||
verbose: true
|
||||
file: ./backy.log
|
||||
console: false
|
||||
cmd-std-out: false
|
||||
|
||||
|
||||
notifications:
|
||||
mail:
|
||||
prod-email:
|
||||
id: prod-email
|
||||
type: mail
|
||||
host: yourhost.tld
|
||||
port: 587
|
||||
senderAddress: email@domain.tld
|
||||
to:
|
||||
- admin@domain.tld
|
||||
username: smtp-username@domain.tld
|
||||
password: your-password-here
|
||||
matrix:
|
||||
matrix:
|
||||
id: matrix
|
||||
type: matrix
|
||||
home-server: your-home-server.tld
|
||||
room-id: room-id
|
||||
access-token: your-access-token
|
||||
user-id: your-user-id
|
24
docs/content/examples/example.yml
Normal file
24
docs/content/examples/example.yml
Normal file
@ -0,0 +1,24 @@
|
||||
commands:
|
||||
stop-docker-container:
|
||||
cmd: docker
|
||||
Args:
|
||||
- compose
|
||||
- -f /some/path/to/docker-compose.yaml
|
||||
- down
|
||||
# if host is not defined, command will be run locally
|
||||
# The host has to be defined in either the config file or the SSH Config files
|
||||
host: some-host
|
||||
hooks:
|
||||
error:
|
||||
- some-other-command-when-failing
|
||||
success:
|
||||
- success-command
|
||||
final:
|
||||
- final-command
|
||||
backup-docker-container-script:
|
||||
cmd: /path/to/local/script
|
||||
# script file is input as stdin to SSH
|
||||
type: scriptFile # also can be script
|
||||
environment:
|
||||
- FOO=BAR
|
||||
- APP=$VAR
|
@ -48,7 +48,7 @@ commands:
|
||||
To execute groups of commands in sequence, use a list configuration.
|
||||
|
||||
```yaml
|
||||
cmd-lists:
|
||||
cmdLists:
|
||||
cmds-to-run: # this can be any name you want
|
||||
# all commands have to be defined in the commands section
|
||||
order:
|
||||
@ -97,7 +97,7 @@ hosts:
|
||||
|
||||
The notifications object can have two forms.
|
||||
|
||||
For more, [see the notification object documentation](/config/notifications). The top-level map key is id that has to be referenced by the `cmd-lists` key `notifications`.
|
||||
For more, [see the notification object documentation](/config/notifications). The top-level map key is id that has to be referenced by the `cmdLists` key `notifications`.
|
||||
|
||||
```yaml
|
||||
notifications:
|
||||
@ -124,13 +124,13 @@ notifications:
|
||||
|
||||
### Logging
|
||||
|
||||
cmd-std-out controls whether commands output is echoed to StdOut.
|
||||
`cmd-std-out` controls whether commands output is echoed to StdOut.
|
||||
|
||||
If logfile is not defined, the log file will be written to the config directory in the file `backy.log`.
|
||||
If `logfile` is not defined, the log file will be written to the config directory in the file `backy.log`.
|
||||
|
||||
`console-disabled` controls whether the logging messages are echoed to StdOut. Default is false.
|
||||
|
||||
`verbose` basically does nothing as all necessary info is already output.
|
||||
`verbose` prints out debugging messages.
|
||||
|
||||
```yaml
|
||||
logging:
|
||||
@ -144,7 +144,7 @@ logging:
|
||||
|
||||
[Vault](https://www.vaultproject.io/) can be used to get some configuration values and ENV variables securely.
|
||||
|
||||
```
|
||||
```yaml
|
||||
vault:
|
||||
token: hvs.tXqcASvTP8wg92f7riyvGyuf
|
||||
address: http://127.0.0.1:8200
|
||||
|
@ -3,6 +3,6 @@ module git.andrewnw.xyz/CyberShell/backy/docs
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/McShelby/hugo-theme-relearn v0.0.0-20230209073138-890d12ea922d // indirect
|
||||
github.com/McShelby/hugo-theme-relearn v0.0.0-20250103114405-80e448e5bdaa // indirect
|
||||
github.com/divinerites/plausible-hugo v1.21.1 // indirect
|
||||
)
|
||||
|
@ -1,4 +1,10 @@
|
||||
github.com/McShelby/hugo-theme-relearn v0.0.0-20230209073138-890d12ea922d h1:weq1mrQ/qNAvGrNgvZVL1K8adbT3bswZf2ABLr/LCIA=
|
||||
github.com/McShelby/hugo-theme-relearn v0.0.0-20230209073138-890d12ea922d/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM=
|
||||
github.com/McShelby/hugo-theme-relearn v0.0.0-20241210183303-16d4de84becf h1:bMx4kwM7Q+dAzvSOWs3XWZ25o+n4mI0GPHqzbzeWb3M=
|
||||
github.com/McShelby/hugo-theme-relearn v0.0.0-20241210183303-16d4de84becf/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM=
|
||||
github.com/McShelby/hugo-theme-relearn v0.0.0-20250102210630-dd0597ffa4b2 h1:sWaC1/dL65v3iRvblEAaBLpKC5TIT0R9JASk1hZNET8=
|
||||
github.com/McShelby/hugo-theme-relearn v0.0.0-20250102210630-dd0597ffa4b2/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM=
|
||||
github.com/McShelby/hugo-theme-relearn v0.0.0-20250103114405-80e448e5bdaa h1:G+OnMEzK4XOzbbcf1SmaGyOYJ0h5idp/IJdguWs8ioU=
|
||||
github.com/McShelby/hugo-theme-relearn v0.0.0-20250103114405-80e448e5bdaa/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM=
|
||||
github.com/divinerites/plausible-hugo v1.21.1 h1:ZTWwjhZ0PmLMacCVGlcGiYFEZW7VaYE767tchDskOug=
|
||||
github.com/divinerites/plausible-hugo v1.21.1/go.mod h1:cxr+YB3FUwbLon8KCs4pV4Ankbkq6lJxTQUpNb5KqPo=
|
||||
|
3
docs/layouts/shortcodes/code.html
Normal file
3
docs/layouts/shortcodes/code.html
Normal file
@ -0,0 +1,3 @@
|
||||
{{ $file := .Get "file" | readFile }}
|
||||
{{ $lang := .Get "language" }}
|
||||
{{ (print "```" $lang "\n" $file "\n```") }}
|
1
docs/themes/hugo-theme-relearn
vendored
Submodule
1
docs/themes/hugo-theme-relearn
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 80e448e5bdaa92c87ee0d0d86f1125c8606ebf5f
|
@ -28,9 +28,23 @@ commands:
|
||||
cmd: hostname
|
||||
update-docker:
|
||||
type: package
|
||||
packageManager: apt
|
||||
shell: zsh # best to run package commands in a shell
|
||||
packageName: docker-ce
|
||||
packageVersion: "5:27.4.1-1~debian.12~bookworm"
|
||||
Args:
|
||||
- docker-ce-cli
|
||||
packageManager: apt
|
||||
packageOperation: install
|
||||
update-dockerApt:
|
||||
# type: package
|
||||
shell: zsh
|
||||
cmd: apt
|
||||
Args:
|
||||
- update
|
||||
- "&&"
|
||||
- apt install -y docker-ce
|
||||
- docker-ce-cli
|
||||
packageManager: apt
|
||||
packageOperation: install
|
||||
|
||||
cmd-lists:
|
||||
cmds-to-run: # this can be any name you want
|
||||
@ -67,7 +81,7 @@ hosts:
|
||||
# optional
|
||||
logging:
|
||||
verbose: true
|
||||
file: /path/to/logs/commands.log
|
||||
file: ./backy.log
|
||||
console: false
|
||||
cmd-std-out: false
|
||||
|
||||
|
24
examples/example.yml
Normal file
24
examples/example.yml
Normal file
@ -0,0 +1,24 @@
|
||||
commands:
|
||||
stop-docker-container:
|
||||
cmd: docker
|
||||
Args:
|
||||
- compose
|
||||
- -f /some/path/to/docker-compose.yaml
|
||||
- down
|
||||
# if host is not defined, command will be run locally
|
||||
# The host has to be defined in either the config file or the SSH Config files
|
||||
host: some-host
|
||||
hooks:
|
||||
error:
|
||||
- some-other-command-when-failing
|
||||
success:
|
||||
- success-command
|
||||
final:
|
||||
- final-command
|
||||
backup-docker-container-script:
|
||||
cmd: /path/to/local/script
|
||||
# script file is input as stdin to SSH
|
||||
type: scriptFile # also can be script
|
||||
environment:
|
||||
- FOO=BAR
|
||||
- APP=$VAR
|
110
getCommandHelp
110
getCommandHelp
@ -1,67 +1,83 @@
|
||||
#!/bin/bash
|
||||
CLI_PAGE="docs/content/cli/_index.md"
|
||||
|
||||
BACKYCOMMAND="go run backy.go"
|
||||
|
||||
{
|
||||
echo "---
|
||||
title: "CLI"
|
||||
title: CLI
|
||||
weight: 4
|
||||
---
|
||||
|
||||
This page lists documentation for the CLI.
|
||||
" > _index.md
|
||||
"
|
||||
|
||||
BACKYCOMMAND="go run backy.go"
|
||||
|
||||
echo "## Backy " >> _index.md
|
||||
echo " " >> _index.md
|
||||
echo "\`\`\`" >> _index.md
|
||||
eval "${BACKYCOMMAND} -h >> _index.md"
|
||||
echo "\`\`\`" >> _index.md
|
||||
echo " " >> _index.md
|
||||
echo "## Backy "
|
||||
echo " "
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} -h"
|
||||
echo "\`\`\`"
|
||||
echo " "
|
||||
|
||||
|
||||
echo "# Subcommands" >> _index.md
|
||||
echo "" >> _index.md
|
||||
|
||||
echo "## backup" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "\`\`\`" >> _index.md
|
||||
eval "${BACKYCOMMAND} backup -h >> _index.md"
|
||||
echo "\`\`\`" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "# Subcommands"
|
||||
echo ""
|
||||
|
||||
echo "## cron" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "\`\`\`" >> _index.md
|
||||
eval "${BACKYCOMMAND} cron -h >> _index.md"
|
||||
echo "\`\`\`" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "## backup"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} backup -h"
|
||||
echo "\`\`\`"
|
||||
echo ""
|
||||
|
||||
echo "## exec" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "\`\`\`" >> _index.md
|
||||
eval "${BACKYCOMMAND} exec -h >> _index.md"
|
||||
echo "\`\`\`" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "## cron"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} cron -h"
|
||||
echo "\`\`\`"
|
||||
echo ""
|
||||
|
||||
echo "### exec host" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "\`\`\`" >> _index.md
|
||||
eval "${BACKYCOMMAND} exec host -h >> _index.md"
|
||||
echo "\`\`\`" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "## exec"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} exec -h"
|
||||
echo "\`\`\`"
|
||||
echo ""
|
||||
|
||||
echo "### exec host"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} exec host -h"
|
||||
echo "\`\`\`"
|
||||
echo ""
|
||||
|
||||
|
||||
echo "## version" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "\`\`\`" >> _index.md
|
||||
eval "${BACKYCOMMAND} version -h >> _index.md"
|
||||
echo "\`\`\`" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "## version"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} version -h"
|
||||
echo "\`\`\`"
|
||||
echo ""
|
||||
|
||||
echo "## list" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "\`\`\`" >> _index.md
|
||||
eval "${BACKYCOMMAND} list -h >> _index.md"
|
||||
echo "\`\`\`" >> _index.md
|
||||
echo "## list"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} list -h"
|
||||
echo "\`\`\`"
|
||||
|
||||
echo "## list cmds"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} list cmds -h"
|
||||
echo "\`\`\`"
|
||||
|
||||
echo "## list lists"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} list lists -h"
|
||||
echo "\`\`\`"
|
||||
} >> _index.md
|
||||
|
||||
|
||||
mv _index.md "$CLI_PAGE"
|
149
go.mod
149
go.mod
@ -1,69 +1,142 @@
|
||||
module git.andrewnw.xyz/CyberShell/backy
|
||||
|
||||
go 1.20
|
||||
go 1.23.0
|
||||
|
||||
replace git.andrewnw.xyz/CyberShell/backy => /home/andrew/Projects/backy
|
||||
toolchain go1.23.7
|
||||
|
||||
require (
|
||||
github.com/go-co-op/gocron v1.33.1
|
||||
github.com/hashicorp/vault/api v1.10.0
|
||||
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/google/uuid v1.6.0
|
||||
github.com/hashicorp/vault/api v1.20.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/kevinburke/ssh_config v1.2.0
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0
|
||||
github.com/knadh/koanf/providers/file v0.1.0
|
||||
github.com/knadh/koanf/v2 v2.0.1
|
||||
github.com/mattn/go-isatty v0.0.19
|
||||
github.com/nikoksr/notify v0.41.0
|
||||
github.com/knadh/koanf/parsers/yaml v1.1.0
|
||||
github.com/knadh/koanf/providers/rawbytes v1.0.0
|
||||
github.com/knadh/koanf/v2 v2.2.2
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/minio/minio-go/v7 v7.0.94
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/nikoksr/notify v1.3.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rs/zerolog v1.30.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
golang.org/x/crypto v0.13.0
|
||||
github.com/pkg/sftp v1.13.9
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/sethvargo/go-password v0.3.1
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/testcontainers/testcontainers-go v0.37.0
|
||||
golang.org/x/crypto v0.40.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
maunium.net/go/mautrix v0.16.0
|
||||
mvdan.cc/sh/v3 v3.7.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
maunium.net/go/mautrix v0.24.1
|
||||
mvdan.cc/sh/v3 v3.12.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 // indirect
|
||||
github.com/aws/smithy-go v1.22.4 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/docker v28.0.1+incompatible // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // 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
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.5 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
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/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
|
||||
github.com/knadh/koanf/maps v0.1.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.2 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/minio/crc64nvme v1.0.2 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/user v0.1.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pascaldekloe/name v1.0.1 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/robfig/cron/v3 v3.0.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.5 // indirect
|
||||
github.com/stretchr/objx v0.5.1 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/tidwall/gjson v1.16.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // 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
|
||||
go.mau.fi/util v0.0.0-20230906155759-14bad39a8718 // indirect
|
||||
github.com/tinylib/msgp v1.3.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.mau.fi/util v0.8.8 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/net v0.15.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
maunium.net/go/maulogger/v2 v2.4.1 // 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
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
)
|
||||
|
493
go.sum
493
go.sum
@ -1,54 +1,161 @@
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
|
||||
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.1/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 h1:zAxi9p3wsZMIaVCdoiQp2uZ9k1LsZvmAnoTBeZPXom0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8/go.mod h1:3XkePX5dSaxveLAYY7nsbsZZrKxCyEuE5pM4ziFxyGg=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 h1:BjUcr3X3K0wZPGFg2bxOWW3VPN8rkE3/61zhP+IHviA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32/go.mod h1:80+OGC/bgzzFFTUmcuwD0lb4YutwQeKLFpmt6hoWapU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 h1:m1GeXHVMJsRsUAqG6HjZWx9dj7F5TR+cF1bjyfYyBd4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32/go.mod h1:IitoQxGfaKdVLNg0hD8/DXmAqNy0H4K2H2Sf91ti8sI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32 h1:OIHj/nAhVzIXGzbAE+4XmZ8FPvro3THr6NlqErJc3wY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32/go.mod h1:LiBEsDo34OJXqdDlRGsilhlIiXR7DL+6Cx2f4p1EgzI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36/go.mod h1:gDhdAV6wL3PmPqBhiPbnlS447GoWs8HTTOYef9/9Inw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.6 h1:cCBJaT7EeEojpJ4s7wTDbhZlHVJOgNHN7iw6qVurGaw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.6/go.mod h1:WYH1ABybY7JK9TITPnk6ZlP7gQB8psI4c9qDmMsnLSA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 h1:SYVGSFQHlchIcy6e7x12bsrxClCXSP5et8cqVhL8cuw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13/go.mod h1:kizuDaLX37bG5WZaoxGPQR/LNFXpxp0vsUnqfkWXfNE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13 h1:OBsrtam3rk8NfBEq7OLOMm5HtQ9Yyw32X4UQMya/wjw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13/go.mod h1:3U4gFA5pmoCOja7aq4nSaIAGbaOHv2Yl2ug018cmC+Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0 h1:ehvUZNVrGA1Usa6yYo8A8pUqrigRelWXSbcCqYpRLeI=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0/go.mod h1:KuLNrwYJFaC2AVZ+CVVc12k9NyqwgWsoNNHjwqF6QNk=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0 h1:5Y75q0RPQoAbieyOuGLhjV9P3txvYgXv2lg0UwJOfmE=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
|
||||
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
|
||||
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw=
|
||||
github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
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/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
|
||||
github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
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=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/go-co-op/gocron v1.33.1 h1:wjX+Dg6Ae29a/f9BSQjY1Rl+jflTpW9aDyMqseCj78c=
|
||||
github.com/go-co-op/gocron v1.33.1/go.mod h1:NLi+bkm4rRSy1F8U7iacZOz0xPseMoIOnvabGoSe/no=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dmarkham/enumer v1.5.11 h1:quorLCaEfzjJ23Pf7PB9lyyaHseh91YfTM/sAD/4Mbo=
|
||||
github.com/dmarkham/enumer v1.5.11/go.mod h1:yixql+kDDQRYqcuBM2n9Vlt7NoT9ixgXhaXry8vmRg8=
|
||||
github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
|
||||
github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
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-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.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
|
||||
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
|
||||
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
|
||||
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=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9 h1:FW0YttEnUNDJ2WL9XcrrfteS1xW8u+sh4ggM8pN5isQ=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||
github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
|
||||
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
|
||||
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ=
|
||||
github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8=
|
||||
github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I=
|
||||
github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
|
||||
github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA=
|
||||
github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8=
|
||||
github.com/hashicorp/vault/api v1.20.0 h1:KQMHElgudOsr+IbJgmbjHnCTxEpKs9LnozA1D3nozU4=
|
||||
github.com/hashicorp/vault/api v1.20.0/go.mod h1:GZ4pcjfzoOWpkJ3ijHNpEoAxKEsBJnVljyTe3jM2Sms=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
@ -57,83 +164,154 @@ github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
|
||||
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
|
||||
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
|
||||
github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c=
|
||||
github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA=
|
||||
github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g=
|
||||
github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus=
|
||||
github.com/knadh/koanf/parsers/yaml v1.1.0 h1:3ltfm9ljprAHt4jxgeYLlFPmUaunuCgu1yILuTXRdM4=
|
||||
github.com/knadh/koanf/parsers/yaml v1.1.0/go.mod h1:HHmcHXUrp9cOPcuC+2wrr44GTUB0EC+PyfN3HZD9tFg=
|
||||
github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME=
|
||||
github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c=
|
||||
github.com/knadh/koanf/providers/rawbytes v1.0.0 h1:MrKDh/HksJlKJmaZjgs4r8aVBb/zsJyc/8qaSnzcdNI=
|
||||
github.com/knadh/koanf/providers/rawbytes v1.0.0/go.mod h1:KxwYJf1uezTKy6PBtfE+m725NGp4GPVA7XoNTJ/PtLo=
|
||||
github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
|
||||
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
|
||||
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.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg=
|
||||
github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.84 h1:D1HVmAF8JF8Bpi6IU4V9vIEj+8pc+xU88EWMs2yed0E=
|
||||
github.com/minio/minio-go/v7 v7.0.84/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY=
|
||||
github.com/minio/minio-go/v7 v7.0.94 h1:1ZoksIKPyaSt64AVOyaQvhDOgVC3MfZsWM6mZXRUGtM=
|
||||
github.com/minio/minio-go/v7 v7.0.94/go.mod h1:71t2CqDt3ThzESgZUlU1rBN54mksGGlkLcFgguDnnAc=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/nikoksr/notify v0.41.0 h1:4LGE41GpWdHX5M3Xo6DlWRwS2WLDbOq1Rk7IzY4vjmQ=
|
||||
github.com/nikoksr/notify v0.41.0/go.mod h1:FoE0UVPeopz1Vy5nm9vQZ+JVmYjEIjQgbFstbkw+cRE=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
|
||||
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/nikoksr/notify v1.3.0 h1:UxzfxzAYGQD9a5JYLBTVx0lFMxeHCke3rPCkfWdPgLs=
|
||||
github.com/nikoksr/notify v1.3.0/go.mod h1:Xor2hMmkvrCfkCKvXGbcrESez4brac2zQjhd6U2BbeM=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/pascaldekloe/name v1.0.0 h1:n7LKFgHixETzxpRv2R77YgPUFo85QHGZKrdaYm7eY5U=
|
||||
github.com/pascaldekloe/name v1.0.0/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM=
|
||||
github.com/pascaldekloe/name v1.0.1 h1:9lnXOHeqeHHnWLbKfH6X98+4+ETVqFqxN09UXSjcMb0=
|
||||
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.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
|
||||
github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY=
|
||||
github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw=
|
||||
github.com/pkg/sftp v1.13.9/go.mod h1:OBN7bVXdstkFFN/gdnHPUb5TE8eb8G1Rp9wCItqjkkA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
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.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
|
||||
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
|
||||
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.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU=
|
||||
github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
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.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0=
|
||||
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
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/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg=
|
||||
github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg=
|
||||
github.com/tidwall/gjson v1.16.0/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=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
@ -141,39 +319,180 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
go.mau.fi/util v0.0.0-20230906155759-14bad39a8718 h1:hmm5bZqE0M8+Uvys0HJPCSbAIZIwYtTkBKYPjAWHuMM=
|
||||
go.mau.fi/util v0.0.0-20230906155759-14bad39a8718/go.mod h1:AxuJUMCxpzgJ5eV9JbPWKRH8aAJJidxetNdUj7qcb84=
|
||||
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
||||
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ=
|
||||
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
||||
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.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
|
||||
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
|
||||
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.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=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34=
|
||||
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
||||
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
|
||||
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||
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=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
|
||||
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
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=
|
||||
@ -184,9 +503,13 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
maunium.net/go/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8=
|
||||
maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho=
|
||||
maunium.net/go/mautrix v0.16.0 h1:iUqCzJE2yqBC1ddAK6eAn159My8rLb4X8g4SFtQh2Dk=
|
||||
maunium.net/go/mautrix v0.16.0/go.mod h1:XAjE9pTSGcr6vXaiNgQGiip7tddJ8FQV1a29u2QdBG4=
|
||||
mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
|
||||
mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
maunium.net/go/mautrix v0.23.0 h1:HNlR19eew5lvrNSL2muhExaGhYdaGk5FfEiA82QqUP4=
|
||||
maunium.net/go/mautrix v0.23.0/go.mod h1:AGnnaz3ylGikUo1I1MJVn9QLsl2No1/ZNnGDyO0QD5s=
|
||||
maunium.net/go/mautrix v0.24.1 h1:09/xi4qTeA03g1n/DPmmqAlT8Cx4QrgwiPlmLVzA9AU=
|
||||
maunium.net/go/mautrix v0.24.1/go.mod h1:Xy6o+pXmbqmgWsUWh15EQ1eozjC+k/VT/7kloByv9PI=
|
||||
mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4=
|
||||
mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
|
||||
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
|
||||
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
|
||||
|
149
pkg/backy/allowedexternaldirectives_enumer.go
Normal file
149
pkg/backy/allowedexternaldirectives_enumer.go
Normal file
@ -0,0 +1,149 @@
|
||||
// Code generated by "enumer -linecomment -yaml -text -json -type=AllowedExternalDirectives"; DO NOT EDIT.
|
||||
|
||||
package backy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _AllowedExternalDirectivesName = "DefaultExternalDirvaultvault-envvault-filevault-file-envfile-envfileenv"
|
||||
|
||||
var _AllowedExternalDirectivesIndex = [...]uint8{0, 18, 23, 32, 42, 56, 64, 68, 71}
|
||||
|
||||
const _AllowedExternalDirectivesLowerName = "defaultexternaldirvaultvault-envvault-filevault-file-envfile-envfileenv"
|
||||
|
||||
func (i AllowedExternalDirectives) String() string {
|
||||
if i < 0 || i >= AllowedExternalDirectives(len(_AllowedExternalDirectivesIndex)-1) {
|
||||
return fmt.Sprintf("AllowedExternalDirectives(%d)", i)
|
||||
}
|
||||
return _AllowedExternalDirectivesName[_AllowedExternalDirectivesIndex[i]:_AllowedExternalDirectivesIndex[i+1]]
|
||||
}
|
||||
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
func _AllowedExternalDirectivesNoOp() {
|
||||
var x [1]struct{}
|
||||
_ = x[DefaultExternalDir-(0)]
|
||||
_ = x[AllowedExternalDirectiveVault-(1)]
|
||||
_ = x[AllowedExternalDirectiveVaultEnv-(2)]
|
||||
_ = x[AllowedExternalDirectiveVaultFile-(3)]
|
||||
_ = x[AllowedExternalDirectiveAll-(4)]
|
||||
_ = x[AllowedExternalDirectiveFileEnv-(5)]
|
||||
_ = x[AllowedExternalDirectiveFile-(6)]
|
||||
_ = x[AllowedExternalDirectiveEnv-(7)]
|
||||
}
|
||||
|
||||
var _AllowedExternalDirectivesValues = []AllowedExternalDirectives{DefaultExternalDir, AllowedExternalDirectiveVault, AllowedExternalDirectiveVaultEnv, AllowedExternalDirectiveVaultFile, AllowedExternalDirectiveAll, AllowedExternalDirectiveFileEnv, AllowedExternalDirectiveFile, AllowedExternalDirectiveEnv}
|
||||
|
||||
var _AllowedExternalDirectivesNameToValueMap = map[string]AllowedExternalDirectives{
|
||||
_AllowedExternalDirectivesName[0:18]: DefaultExternalDir,
|
||||
_AllowedExternalDirectivesLowerName[0:18]: DefaultExternalDir,
|
||||
_AllowedExternalDirectivesName[18:23]: AllowedExternalDirectiveVault,
|
||||
_AllowedExternalDirectivesLowerName[18:23]: AllowedExternalDirectiveVault,
|
||||
_AllowedExternalDirectivesName[23:32]: AllowedExternalDirectiveVaultEnv,
|
||||
_AllowedExternalDirectivesLowerName[23:32]: AllowedExternalDirectiveVaultEnv,
|
||||
_AllowedExternalDirectivesName[32:42]: AllowedExternalDirectiveVaultFile,
|
||||
_AllowedExternalDirectivesLowerName[32:42]: AllowedExternalDirectiveVaultFile,
|
||||
_AllowedExternalDirectivesName[42:56]: AllowedExternalDirectiveAll,
|
||||
_AllowedExternalDirectivesLowerName[42:56]: AllowedExternalDirectiveAll,
|
||||
_AllowedExternalDirectivesName[56:64]: AllowedExternalDirectiveFileEnv,
|
||||
_AllowedExternalDirectivesLowerName[56:64]: AllowedExternalDirectiveFileEnv,
|
||||
_AllowedExternalDirectivesName[64:68]: AllowedExternalDirectiveFile,
|
||||
_AllowedExternalDirectivesLowerName[64:68]: AllowedExternalDirectiveFile,
|
||||
_AllowedExternalDirectivesName[68:71]: AllowedExternalDirectiveEnv,
|
||||
_AllowedExternalDirectivesLowerName[68:71]: AllowedExternalDirectiveEnv,
|
||||
}
|
||||
|
||||
var _AllowedExternalDirectivesNames = []string{
|
||||
_AllowedExternalDirectivesName[0:18],
|
||||
_AllowedExternalDirectivesName[18:23],
|
||||
_AllowedExternalDirectivesName[23:32],
|
||||
_AllowedExternalDirectivesName[32:42],
|
||||
_AllowedExternalDirectivesName[42:56],
|
||||
_AllowedExternalDirectivesName[56:64],
|
||||
_AllowedExternalDirectivesName[64:68],
|
||||
_AllowedExternalDirectivesName[68:71],
|
||||
}
|
||||
|
||||
// AllowedExternalDirectivesString retrieves an enum value from the enum constants string name.
|
||||
// Throws an error if the param is not part of the enum.
|
||||
func AllowedExternalDirectivesString(s string) (AllowedExternalDirectives, error) {
|
||||
if val, ok := _AllowedExternalDirectivesNameToValueMap[s]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if val, ok := _AllowedExternalDirectivesNameToValueMap[strings.ToLower(s)]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return 0, fmt.Errorf("%s does not belong to AllowedExternalDirectives values", s)
|
||||
}
|
||||
|
||||
// AllowedExternalDirectivesValues returns all values of the enum
|
||||
func AllowedExternalDirectivesValues() []AllowedExternalDirectives {
|
||||
return _AllowedExternalDirectivesValues
|
||||
}
|
||||
|
||||
// AllowedExternalDirectivesStrings returns a slice of all String values of the enum
|
||||
func AllowedExternalDirectivesStrings() []string {
|
||||
strs := make([]string, len(_AllowedExternalDirectivesNames))
|
||||
copy(strs, _AllowedExternalDirectivesNames)
|
||||
return strs
|
||||
}
|
||||
|
||||
// IsAAllowedExternalDirectives returns "true" if the value is listed in the enum definition. "false" otherwise
|
||||
func (i AllowedExternalDirectives) IsAAllowedExternalDirectives() bool {
|
||||
for _, v := range _AllowedExternalDirectivesValues {
|
||||
if i == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface for AllowedExternalDirectives
|
||||
func (i AllowedExternalDirectives) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface for AllowedExternalDirectives
|
||||
func (i *AllowedExternalDirectives) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return fmt.Errorf("AllowedExternalDirectives should be a string, got %s", data)
|
||||
}
|
||||
|
||||
var err error
|
||||
*i, err = AllowedExternalDirectivesString(s)
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface for AllowedExternalDirectives
|
||||
func (i AllowedExternalDirectives) MarshalText() ([]byte, error) {
|
||||
return []byte(i.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface for AllowedExternalDirectives
|
||||
func (i *AllowedExternalDirectives) UnmarshalText(text []byte) error {
|
||||
var err error
|
||||
*i, err = AllowedExternalDirectivesString(string(text))
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalYAML implements a YAML Marshaler for AllowedExternalDirectives
|
||||
func (i AllowedExternalDirectives) MarshalYAML() (interface{}, error) {
|
||||
return i.String(), nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements a YAML Unmarshaler for AllowedExternalDirectives
|
||||
func (i *AllowedExternalDirectives) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var s string
|
||||
if err := unmarshal(&s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
*i, err = AllowedExternalDirectivesString(s)
|
||||
return err
|
||||
}
|
@ -11,6 +11,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"embed"
|
||||
@ -34,84 +36,161 @@ var Sprintf = fmt.Sprintf
|
||||
func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([]string, error) {
|
||||
|
||||
var (
|
||||
outputArr []string
|
||||
ArgsStr string
|
||||
cmdOutBuf bytes.Buffer
|
||||
cmdOutWriters io.Writer
|
||||
errSSH error
|
||||
|
||||
envVars = environmentVars{
|
||||
file: command.Env,
|
||||
env: command.Environment,
|
||||
}
|
||||
|
||||
outputArr []string // holds the output strings returned by processes
|
||||
)
|
||||
|
||||
// Getting the command type must be done before concatenating the arguments
|
||||
command = getCommandTypeAndSetCommandInfo(command)
|
||||
|
||||
for _, v := range command.Args {
|
||||
ArgsStr += fmt.Sprintf(" %s", v)
|
||||
}
|
||||
|
||||
command = getPackageCommand(command)
|
||||
if command.Type == UserCommandType {
|
||||
if command.UserOperation == "password" {
|
||||
cmdCtxLogger.Info().Str("password", command.UserPassword).Msg("user password to be updated")
|
||||
}
|
||||
}
|
||||
|
||||
var errSSH error
|
||||
// is host defined
|
||||
if command.Host != nil {
|
||||
outputArr, errSSH = command.RunCmdSSH(cmdCtxLogger, opts)
|
||||
if !IsHostLocal(command.Host) {
|
||||
|
||||
outputArr, errSSH = command.RunCmdOnHost(cmdCtxLogger, opts)
|
||||
if errSSH != nil {
|
||||
return outputArr, errSSH
|
||||
}
|
||||
} else {
|
||||
|
||||
var err error
|
||||
if command.Shell != "" {
|
||||
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine in %s", command.Name, command.Shell)).Send()
|
||||
|
||||
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
||||
|
||||
localCMD := exec.Command(command.Shell, "-c", ArgsStr)
|
||||
|
||||
if command.Dir != nil {
|
||||
localCMD.Dir = *command.Dir
|
||||
// Handle package operations
|
||||
if command.Type == PackageCommandType && command.PackageOperation == PackageOperationCheckVersion {
|
||||
opts.Logger.Info().Msg("")
|
||||
for _, p := range command.Packages {
|
||||
cmdCtxLogger.Info().Str("package", p.Name).Msg("Checking installed and remote package versions")
|
||||
}
|
||||
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger)
|
||||
opts.Logger.Info().Msg("")
|
||||
|
||||
// Execute the package version command
|
||||
cmd := exec.Command(command.Cmd, command.Args...)
|
||||
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
|
||||
|
||||
if IsCmdStdOutEnabled() {
|
||||
cmdOutWriters = io.MultiWriter(os.Stdout, &cmdOutBuf)
|
||||
}
|
||||
cmd.Stdout = cmdOutWriters
|
||||
cmd.Stderr = cmdOutWriters
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("error running command %s: %w", ArgsStr, err)
|
||||
}
|
||||
|
||||
return parsePackageVersion(cmdOutBuf.String(), cmdCtxLogger, command, cmdOutBuf)
|
||||
}
|
||||
|
||||
var localCMD *exec.Cmd
|
||||
|
||||
if command.Type == RemoteScriptCommandType {
|
||||
script, err := command.Fetcher.Fetch(command.Cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if command.Shell == "" {
|
||||
command.Shell = "sh"
|
||||
}
|
||||
localCMD = exec.Command(command.Shell, command.Args...)
|
||||
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger, opts)
|
||||
|
||||
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
|
||||
|
||||
if IsCmdStdOutEnabled() {
|
||||
cmdOutWriters = io.MultiWriter(os.Stdout, &cmdOutBuf)
|
||||
}
|
||||
if command.Output.File != "" {
|
||||
file, err := os.Create(command.Output.File)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating output file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
cmdOutWriters = io.MultiWriter(file, &cmdOutBuf)
|
||||
|
||||
if IsCmdStdOutEnabled() {
|
||||
cmdOutWriters = io.MultiWriter(os.Stdout, file, &cmdOutBuf)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
localCMD.Stdin = bytes.NewReader(script)
|
||||
localCMD.Stdout = cmdOutWriters
|
||||
localCMD.Stderr = cmdOutWriters
|
||||
|
||||
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running remoteScript %s on local machine in %s", command.Cmd, command.Shell)).Send()
|
||||
err = localCMD.Run()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error running remote script: %w", err)
|
||||
}
|
||||
|
||||
outScanner := bufio.NewScanner(&cmdOutBuf)
|
||||
|
||||
for outScanner.Scan() {
|
||||
outMap := make(map[string]interface{})
|
||||
outMap["cmd"] = command.Name
|
||||
outMap["cmd"] = command.Cmd
|
||||
outMap["output"] = outScanner.Text()
|
||||
|
||||
if str, ok := outMap["output"].(string); ok {
|
||||
outputArr = append(outputArr, str)
|
||||
}
|
||||
cmdCtxLogger.Info().Fields(outMap).Send()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd %s: %w", command.Name, err)).Send()
|
||||
return outputArr, err
|
||||
if command.Output.ToLog {
|
||||
cmdCtxLogger.Info().Fields(outMap).Send()
|
||||
}
|
||||
}
|
||||
return outputArr, nil
|
||||
}
|
||||
|
||||
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine", command.Name)).Send()
|
||||
var err error
|
||||
|
||||
localCMD := exec.Command(command.Cmd, command.Args...)
|
||||
if command.Shell != "" {
|
||||
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine in %s", command.Name, command.Shell)).Send()
|
||||
|
||||
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
||||
|
||||
localCMD = exec.Command(command.Shell, "-c", ArgsStr)
|
||||
|
||||
} else {
|
||||
|
||||
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine", command.Name)).Send()
|
||||
|
||||
// execute package commands in a shell
|
||||
if command.Type == PackageCommandType {
|
||||
for _, p := range command.Packages {
|
||||
cmdCtxLogger.Info().Str("packages", p.Name).Msg("Executing package command")
|
||||
}
|
||||
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
||||
localCMD = exec.Command("/bin/sh", "-c", ArgsStr)
|
||||
} else {
|
||||
localCMD = exec.Command(command.Cmd, command.Args...)
|
||||
}
|
||||
}
|
||||
|
||||
if command.Type == UserCommandType {
|
||||
if command.UserOperation == "password" {
|
||||
localCMD.Stdin = command.stdin
|
||||
cmdCtxLogger.Info().Str("password", command.UserPassword).Msg("user password to be updated")
|
||||
}
|
||||
}
|
||||
if command.Dir != nil {
|
||||
localCMD.Dir = *command.Dir
|
||||
}
|
||||
|
||||
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger)
|
||||
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger, opts)
|
||||
|
||||
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
|
||||
|
||||
@ -124,135 +203,231 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
|
||||
err = localCMD.Run()
|
||||
|
||||
outScanner := bufio.NewScanner(&cmdOutBuf)
|
||||
|
||||
for outScanner.Scan() {
|
||||
outMap := make(map[string]interface{})
|
||||
outMap["cmd"] = command.Cmd
|
||||
outMap["output"] = outScanner.Text()
|
||||
|
||||
if str, ok := outMap["output"].(string); ok {
|
||||
outputArr = append(outputArr, str)
|
||||
}
|
||||
cmdCtxLogger.Info().Fields(outMap).Send()
|
||||
}
|
||||
outputArr = logCommandOutput(command, cmdOutBuf, cmdCtxLogger, outputArr)
|
||||
if err != nil {
|
||||
cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd %s: %w", command.Name, err)).Send()
|
||||
return outputArr, err
|
||||
}
|
||||
|
||||
if command.Type == UserCommandType {
|
||||
|
||||
if command.UserOperation == "add" {
|
||||
if command.UserSshPubKeys != nil {
|
||||
var (
|
||||
authorizedKeysFile *os.File
|
||||
err error
|
||||
userHome []byte
|
||||
)
|
||||
|
||||
cmdCtxLogger.Info().Msg("adding SSH Keys")
|
||||
|
||||
localCMD := exec.Command(fmt.Sprintf("grep \"%s\" /etc/passwd | cut -d: -f6", command.Username))
|
||||
userHome, err = localCMD.CombinedOutput()
|
||||
if err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error finding user home from /etc/passwd: %v", err)
|
||||
}
|
||||
|
||||
command.UserHome = strings.TrimSpace(string(userHome))
|
||||
userSshDir := fmt.Sprintf("%s/.ssh", command.UserHome)
|
||||
|
||||
if _, err := os.Stat(userSshDir); os.IsNotExist(err) {
|
||||
err := os.MkdirAll(userSshDir, 0700)
|
||||
if err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating directory %s %v", userSshDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(fmt.Sprintf("%s/authorized_keys", userSshDir)); os.IsNotExist(err) {
|
||||
_, err := os.Create(fmt.Sprintf("%s/authorized_keys", userSshDir))
|
||||
if err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating file %s/authorized_keys: %v", userSshDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
authorizedKeysFile, err = os.OpenFile(fmt.Sprintf("%s/authorized_keys", userSshDir), 0700, os.ModeAppend)
|
||||
if err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)
|
||||
}
|
||||
defer authorizedKeysFile.Close()
|
||||
for _, k := range command.UserSshPubKeys {
|
||||
buf := bytes.NewBufferString(k)
|
||||
cmdCtxLogger.Info().Str("key", k).Msg("adding SSH key")
|
||||
if _, err := authorizedKeysFile.ReadFrom(buf); err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error adding to authorized keys: %v", err)
|
||||
}
|
||||
}
|
||||
localCMD = exec.Command(fmt.Sprintf("chown -R %s:%s %s", command.Username, command.Username, userHome))
|
||||
_, err = localCMD.CombinedOutput()
|
||||
if err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return outputArr, nil
|
||||
}
|
||||
|
||||
// cmdListWorker
|
||||
func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<- string, opts *ConfigOpts) {
|
||||
// iterate over list to run
|
||||
res := CmdListResults{}
|
||||
for list := range jobs {
|
||||
fieldsMap := make(map[string]interface{})
|
||||
fieldsMap["list"] = list.Name
|
||||
fieldsMap := map[string]interface{}{"list": list.Name}
|
||||
var cmdLogger zerolog.Logger
|
||||
|
||||
var count int // count of how many commands have been executed
|
||||
var cmdsRan []string // store the commands that have been executed
|
||||
var outStructArr []outStruct // stores output messages
|
||||
var commandExecuted *Command
|
||||
var cmdsRan []string
|
||||
var outStructArr []outStruct
|
||||
var hasError bool // Tracks if any command in the list failed
|
||||
|
||||
for _, cmd := range list.Order {
|
||||
|
||||
currentCmd := opts.Cmds[cmd].Name
|
||||
|
||||
fieldsMap["cmd"] = opts.Cmds[cmd].Name
|
||||
cmdToRun := opts.Cmds[cmd]
|
||||
|
||||
commandExecuted = cmdToRun
|
||||
currentCmd := cmdToRun.Name
|
||||
fieldsMap["cmd"] = currentCmd
|
||||
cmdLogger = cmdToRun.GenerateLogger(opts)
|
||||
cmdLogger.Info().Fields(fieldsMap).Send()
|
||||
|
||||
outputArr, runOutErr := cmdToRun.RunCmd(cmdLogger, opts)
|
||||
outputArr, runErr := cmdToRun.RunCmd(cmdLogger, opts)
|
||||
cmdsRan = append(cmdsRan, cmd)
|
||||
|
||||
if list.NotifyConfig != nil {
|
||||
if runErr != nil {
|
||||
|
||||
// check if the command output should be included
|
||||
if cmdToRun.GetOutput || list.GetOutput {
|
||||
outputStruct := outStruct{
|
||||
CmdName: cmdToRun.Name,
|
||||
CmdExecuted: currentCmd,
|
||||
Output: outputArr,
|
||||
}
|
||||
cmdLogger.Err(runErr).Send()
|
||||
|
||||
outStructArr = append(outStructArr, outputStruct)
|
||||
cmdToRun.ExecuteHooks("error", opts)
|
||||
|
||||
}
|
||||
}
|
||||
count++
|
||||
if runOutErr != nil {
|
||||
res.ErrCmd = cmd
|
||||
// Notify failure
|
||||
if list.NotifyConfig != nil {
|
||||
var errMsg bytes.Buffer
|
||||
errStruct := make(map[string]interface{})
|
||||
|
||||
errStruct["listName"] = list.Name
|
||||
errStruct["Command"] = currentCmd
|
||||
errStruct["Cmd"] = cmd
|
||||
errStruct["Args"] = opts.Cmds[cmd].Args
|
||||
errStruct["Err"] = runOutErr
|
||||
errStruct["CmdsRan"] = cmdsRan
|
||||
errStruct["Output"] = outputArr
|
||||
|
||||
errStruct["CmdOutput"] = outStructArr
|
||||
|
||||
tmpErr := msgTemps.err.Execute(&errMsg, errStruct)
|
||||
|
||||
if tmpErr != nil {
|
||||
cmdLogger.Err(tmpErr).Send()
|
||||
}
|
||||
|
||||
notifySendErr := list.NotifyConfig.Send(context.Background(), fmt.Sprintf("List %s failed", list.Name), errMsg.String())
|
||||
|
||||
if notifySendErr != nil {
|
||||
cmdLogger.Err(notifySendErr).Send()
|
||||
}
|
||||
notifyError(cmdLogger, msgTemps, list, cmdsRan, outStructArr, runErr, cmdToRun)
|
||||
}
|
||||
|
||||
cmdLogger.Err(runOutErr).Send()
|
||||
|
||||
// Execute error hooks for the failed command
|
||||
hasError = true
|
||||
break
|
||||
} else {
|
||||
}
|
||||
|
||||
cmdsRan = append(cmdsRan, cmd)
|
||||
|
||||
if count == len(list.Order) {
|
||||
var successMsg bytes.Buffer
|
||||
|
||||
// if notification config is not nil, and NotifyOnSuccess is true or GetOuput is true,
|
||||
// then send notification
|
||||
if list.NotifyConfig != nil && (list.NotifyOnSuccess || list.GetOutput) {
|
||||
successStruct := make(map[string]interface{})
|
||||
|
||||
successStruct["listName"] = list.Name
|
||||
successStruct["CmdsRan"] = cmdsRan
|
||||
|
||||
successStruct["CmdOutput"] = outStructArr
|
||||
|
||||
tmpErr := msgTemps.success.Execute(&successMsg, successStruct)
|
||||
|
||||
if tmpErr != nil {
|
||||
cmdLogger.Err(tmpErr).Send()
|
||||
break
|
||||
}
|
||||
|
||||
err := list.NotifyConfig.Send(context.Background(), fmt.Sprintf("List %s succeeded", list.Name), successMsg.String())
|
||||
|
||||
if err != nil {
|
||||
cmdLogger.Err(err).Send()
|
||||
}
|
||||
}
|
||||
}
|
||||
if list.GetCommandOutputInNotificationsOnSuccess || cmdToRun.Output.InList {
|
||||
outStructArr = append(outStructArr, outStruct{
|
||||
CmdName: currentCmd,
|
||||
CmdExecuted: currentCmd,
|
||||
Output: outputArr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
results <- res.ErrCmd
|
||||
}
|
||||
if !hasError && list.NotifyConfig != nil && list.Notify.OnFailure {
|
||||
notifySuccess(cmdLogger, msgTemps, list, cmdsRan, outStructArr)
|
||||
}
|
||||
|
||||
if !hasError {
|
||||
commandExecuted.ExecuteHooks("success", opts)
|
||||
}
|
||||
|
||||
commandExecuted.ExecuteHooks("final", opts)
|
||||
|
||||
results <- "done"
|
||||
}
|
||||
}
|
||||
func cmdListWorkerWithHosts(msgTemps *msgTemplates, jobs <-chan *CmdList, hosts <-chan *Host, results chan<- string, opts *ConfigOpts) {
|
||||
for list := range jobs {
|
||||
fieldsMap := map[string]interface{}{"list": list.Name}
|
||||
var cmdLogger zerolog.Logger
|
||||
var commandExecuted *Command
|
||||
var cmdsRan []string
|
||||
var outStructArr []outStruct
|
||||
var hasError bool // Tracks if any command in the list failed
|
||||
|
||||
for host := range hosts {
|
||||
|
||||
for _, cmd := range list.Order {
|
||||
cmdToRun := opts.Cmds[cmd]
|
||||
if cmdToRun.Host != host.Host {
|
||||
cmdToRun.Host = host.Host
|
||||
cmdToRun.RemoteHost = host
|
||||
}
|
||||
commandExecuted = cmdToRun
|
||||
currentCmd := cmdToRun.Name
|
||||
fieldsMap["cmd"] = currentCmd
|
||||
cmdLogger = cmdToRun.GenerateLogger(opts)
|
||||
cmdLogger.Info().Fields(fieldsMap).Send()
|
||||
|
||||
outputArr, runErr := cmdToRun.RunCmd(cmdLogger, opts)
|
||||
cmdsRan = append(cmdsRan, cmd)
|
||||
|
||||
if runErr != nil {
|
||||
|
||||
cmdLogger.Err(runErr).Send()
|
||||
|
||||
cmdToRun.ExecuteHooks("error", opts)
|
||||
|
||||
// Notify failure
|
||||
if list.NotifyConfig != nil {
|
||||
notifyError(cmdLogger, msgTemps, list, cmdsRan, outStructArr, runErr, cmdToRun)
|
||||
}
|
||||
|
||||
// Execute error hooks for the failed command
|
||||
hasError = true
|
||||
break
|
||||
}
|
||||
|
||||
if list.GetCommandOutputInNotificationsOnSuccess || cmdToRun.Output.InList {
|
||||
outStructArr = append(outStructArr, outStruct{
|
||||
CmdName: currentCmd,
|
||||
CmdExecuted: currentCmd,
|
||||
Output: outputArr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if !hasError && list.NotifyConfig != nil && list.Notify.OnFailure {
|
||||
notifySuccess(cmdLogger, msgTemps, list, cmdsRan, outStructArr)
|
||||
}
|
||||
|
||||
if !hasError {
|
||||
commandExecuted.ExecuteHooks("success", opts)
|
||||
}
|
||||
|
||||
commandExecuted.ExecuteHooks("final", opts)
|
||||
|
||||
}
|
||||
results <- "done"
|
||||
}
|
||||
}
|
||||
|
||||
func notifyError(logger zerolog.Logger, templates *msgTemplates, list *CmdList, cmdsRan []string, outStructArr []outStruct, err error, cmd *Command) {
|
||||
errStruct := map[string]interface{}{
|
||||
"listName": list.Name,
|
||||
"CmdsRan": cmdsRan,
|
||||
"CmdOutput": outStructArr,
|
||||
"Err": err,
|
||||
"CmdName": cmd.Name,
|
||||
"Command": cmd.Cmd,
|
||||
"Args": cmd.Args,
|
||||
}
|
||||
var errMsg bytes.Buffer
|
||||
if e := templates.err.Execute(&errMsg, errStruct); e != nil {
|
||||
logger.Err(e).Send()
|
||||
return
|
||||
}
|
||||
if e := list.NotifyConfig.Send(context.Background(), fmt.Sprintf("List %s failed", list.Name), errMsg.String()); e != nil {
|
||||
logger.Err(e).Send()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to notify success
|
||||
func notifySuccess(logger zerolog.Logger, templates *msgTemplates, list *CmdList, cmdsRan []string, outStructArr []outStruct) {
|
||||
successStruct := map[string]interface{}{
|
||||
"listName": list.Name,
|
||||
"CmdsRan": cmdsRan,
|
||||
"CmdOutput": outStructArr,
|
||||
}
|
||||
var successMsg bytes.Buffer
|
||||
if e := templates.success.Execute(&successMsg, successStruct); e != nil {
|
||||
logger.Err(e).Send()
|
||||
return
|
||||
}
|
||||
if e := list.NotifyConfig.Send(context.Background(), fmt.Sprintf("List %s succeeded", list.Name), successMsg.String()); e != nil {
|
||||
logger.Err(e).Send()
|
||||
}
|
||||
}
|
||||
|
||||
// RunListConfig runs a command list from the ConfigFile.
|
||||
@ -263,62 +438,91 @@ func (opts *ConfigOpts) RunListConfig(cron string) {
|
||||
}
|
||||
configListsLen := len(opts.CmdConfigLists)
|
||||
listChan := make(chan *CmdList, configListsLen)
|
||||
results := make(chan string)
|
||||
results := make(chan string, configListsLen)
|
||||
|
||||
// This starts up list workers, initially blocked
|
||||
// because there are no jobs yet.
|
||||
// Start workers
|
||||
for w := 1; w <= configListsLen; w++ {
|
||||
go cmdListWorker(mTemps, listChan, results, opts)
|
||||
}
|
||||
|
||||
// Enqueue jobs
|
||||
for listName, cmdConfig := range opts.CmdConfigLists {
|
||||
if cmdConfig.Name == "" {
|
||||
cmdConfig.Name = listName
|
||||
}
|
||||
if cron != "" {
|
||||
if cron == cmdConfig.Cron {
|
||||
listChan <- cmdConfig
|
||||
}
|
||||
} else {
|
||||
if cron == "" || cron == cmdConfig.Cron {
|
||||
listChan <- cmdConfig
|
||||
}
|
||||
}
|
||||
close(listChan)
|
||||
|
||||
// Process results
|
||||
for a := 1; a <= configListsLen; a++ {
|
||||
l := <-results
|
||||
|
||||
opts.Logger.Debug().Msg(l)
|
||||
|
||||
if l != "" {
|
||||
// execute error hooks
|
||||
opts.Logger.Debug().Msg("hooks are working")
|
||||
opts.Cmds[l].ExecuteHooks("error", opts)
|
||||
} else {
|
||||
// execute success hooks
|
||||
opts.Cmds[l].ExecuteHooks("success", opts)
|
||||
|
||||
}
|
||||
|
||||
// execute final hooks
|
||||
opts.Cmds[l].ExecuteHooks("final", opts)
|
||||
|
||||
<-results
|
||||
}
|
||||
|
||||
opts.closeHostConnections()
|
||||
}
|
||||
|
||||
func (config *ConfigOpts) ExecuteCmds(opts *ConfigOpts) {
|
||||
func (opts *ConfigOpts) ExecuteListOnHosts(lists []string) {
|
||||
|
||||
mTemps := &msgTemplates{
|
||||
err: template.Must(template.New("error.txt").ParseFS(templates, "templates/error.txt")),
|
||||
success: template.Must(template.New("success.txt").ParseFS(templates, "templates/success.txt")),
|
||||
}
|
||||
for _, l := range opts.CmdConfigLists {
|
||||
if !slices.Contains(lists, l.Name) {
|
||||
delete(opts.CmdConfigLists, l.Name)
|
||||
}
|
||||
}
|
||||
configListsLen := len(opts.CmdConfigLists)
|
||||
listChan := make(chan *CmdList, configListsLen)
|
||||
hostChan := make(chan *Host, len(opts.Hosts))
|
||||
results := make(chan string, configListsLen)
|
||||
|
||||
// Start workers
|
||||
for w := 1; w <= configListsLen; w++ {
|
||||
go cmdListWorkerWithHosts(mTemps, listChan, hostChan, results, opts)
|
||||
}
|
||||
|
||||
// Enqueue jobs
|
||||
for listName, cmdConfig := range opts.CmdConfigLists {
|
||||
if cmdConfig.Name == "" {
|
||||
cmdConfig.Name = listName
|
||||
}
|
||||
listChan <- cmdConfig
|
||||
}
|
||||
for _, h := range opts.Hosts {
|
||||
if h.isProxyHost {
|
||||
continue
|
||||
}
|
||||
hostChan <- h
|
||||
// for _, proxyHost := range h.ProxyHost {
|
||||
// if proxyHost.isProxyHost {
|
||||
// continue
|
||||
// }
|
||||
// hostChan <- proxyHost
|
||||
// }
|
||||
}
|
||||
close(listChan)
|
||||
close(hostChan)
|
||||
|
||||
// Process results
|
||||
for a := 1; a <= configListsLen; a++ {
|
||||
<-results
|
||||
}
|
||||
opts.closeHostConnections()
|
||||
|
||||
}
|
||||
|
||||
func (opts *ConfigOpts) ExecuteCmds() {
|
||||
for _, cmd := range opts.executeCmds {
|
||||
cmdToRun := opts.Cmds[cmd]
|
||||
cmdLogger := cmdToRun.GenerateLogger(opts)
|
||||
_, runErr := cmdToRun.RunCmd(cmdLogger, opts)
|
||||
if runErr != nil {
|
||||
opts.Logger.Err(runErr).Send()
|
||||
|
||||
cmdToRun.ExecuteHooks("error", opts)
|
||||
} else {
|
||||
|
||||
cmdToRun.ExecuteHooks("success", opts)
|
||||
}
|
||||
|
||||
@ -326,7 +530,6 @@ func (config *ConfigOpts) ExecuteCmds(opts *ConfigOpts) {
|
||||
}
|
||||
|
||||
opts.closeHostConnections()
|
||||
|
||||
}
|
||||
|
||||
func (c *ConfigOpts) closeHostConnections() {
|
||||
@ -374,26 +577,31 @@ func (cmd *Command) ExecuteHooks(hookType string, opts *ConfigOpts) {
|
||||
for _, v := range cmd.Hooks.Error {
|
||||
errCmd := opts.Cmds[v]
|
||||
cmdLogger := opts.Logger.With().
|
||||
Str("backy-cmd", v).
|
||||
Str("backy-cmd", v).Str("hookType", "error").
|
||||
Logger()
|
||||
errCmd.RunCmd(cmdLogger, opts)
|
||||
cmdLogger.Info().Msgf("Running error hook command %s", v)
|
||||
// URGENT: Never returns
|
||||
_, _ = errCmd.RunCmd(cmdLogger, opts)
|
||||
return
|
||||
}
|
||||
|
||||
case "success":
|
||||
for _, v := range cmd.Hooks.Success {
|
||||
successCmd := opts.Cmds[v]
|
||||
cmdLogger := opts.Logger.With().
|
||||
Str("backy-cmd", v).
|
||||
Str("backy-cmd", v).Str("hookType", "success").
|
||||
Logger()
|
||||
successCmd.RunCmd(cmdLogger, opts)
|
||||
cmdLogger.Info().Msgf("Running success hook command %s", v)
|
||||
_, _ = successCmd.RunCmd(cmdLogger, opts)
|
||||
}
|
||||
case "final":
|
||||
for _, v := range cmd.Hooks.Final {
|
||||
finalCmd := opts.Cmds[v]
|
||||
cmdLogger := opts.Logger.With().
|
||||
Str("backy-cmd", v).
|
||||
Str("backy-cmd", v).Str("hookType", "final").
|
||||
Logger()
|
||||
finalCmd.RunCmd(cmdLogger, opts)
|
||||
cmdLogger.Info().Msgf("Running final hook command %s", v)
|
||||
_, _ = finalCmd.RunCmd(cmdLogger, opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -403,28 +611,66 @@ func (cmd *Command) GenerateLogger(opts *ConfigOpts) zerolog.Logger {
|
||||
Str("Backy-cmd", cmd.Name).Str("Host", "local machine").
|
||||
Logger()
|
||||
|
||||
if cmd.Host != nil {
|
||||
if !IsHostLocal(cmd.Host) {
|
||||
cmdLogger = opts.Logger.With().
|
||||
Str("Backy-cmd", cmd.Name).Str("Host", *cmd.Host).
|
||||
Str("Backy-cmd", cmd.Name).Str("Host", cmd.Host).
|
||||
Logger()
|
||||
|
||||
}
|
||||
return cmdLogger
|
||||
}
|
||||
|
||||
func (opts *ConfigOpts) ExecCmdsSSH(cmdList []string, hostsList []string) {
|
||||
func (opts *ConfigOpts) ExecCmdsOnHosts(cmdList []string, hostsList []string) {
|
||||
// Iterate over hosts and exec commands
|
||||
for _, h := range hostsList {
|
||||
host := opts.Hosts[h]
|
||||
for _, c := range cmdList {
|
||||
cmd := opts.Cmds[c]
|
||||
cmd.RemoteHost = host
|
||||
cmd.Host = &host.Host
|
||||
opts.Logger.Info().Str("host", h).Str("cmd", c).Send()
|
||||
_, err := cmd.RunCmdSSH(cmd.GenerateLogger(opts), opts)
|
||||
if err != nil {
|
||||
opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send()
|
||||
cmd.Host = h
|
||||
if IsHostLocal(h) {
|
||||
_, err := cmd.RunCmd(cmd.GenerateLogger(opts), opts)
|
||||
if err != nil {
|
||||
opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send()
|
||||
}
|
||||
} else {
|
||||
|
||||
cmd.Host = host.Host
|
||||
opts.Logger.Info().Str("host", h).Str("cmd", c).Send()
|
||||
_, err := cmd.RunCmdOnHost(cmd.GenerateLogger(opts), opts)
|
||||
if err != nil {
|
||||
opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func logCommandOutput(command *Command, cmdOutBuf bytes.Buffer, cmdCtxLogger zerolog.Logger, outputArr []string) []string {
|
||||
|
||||
outScanner := bufio.NewScanner(&cmdOutBuf)
|
||||
|
||||
for outScanner.Scan() {
|
||||
outMap := make(map[string]interface{})
|
||||
outMap["cmd"] = command.Name
|
||||
outMap["output"] = outScanner.Text()
|
||||
|
||||
if str, ok := outMap["output"].(string); ok {
|
||||
outputArr = append(outputArr, str)
|
||||
}
|
||||
if command.Output.ToLog {
|
||||
cmdCtxLogger.Info().Fields(outMap).Send()
|
||||
}
|
||||
}
|
||||
return outputArr
|
||||
}
|
||||
|
||||
// func executeUserCommands() []string {
|
||||
|
||||
// }
|
||||
|
||||
// // parseRemoteSources parses source and validates fields using sourceType
|
||||
// func (c *Command) parseRemoteSources(source, sourceType string) {
|
||||
// switch sourceType {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
83
pkg/backy/backy_test.go
Normal file
83
pkg/backy/backy_test.go
Normal file
@ -0,0 +1,83 @@
|
||||
package backy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman"
|
||||
packagemanagercommon "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
)
|
||||
|
||||
// TestConfigOptions tests the configuration options for the backy package.
|
||||
func Test_ErrorHook(t *testing.T) {
|
||||
|
||||
configFile := "../../tests/ErrorHook.yml"
|
||||
logFile := "ErrorHook.log"
|
||||
backyConfigOptions := NewConfigOptions(configFile, SetLogFile(logFile))
|
||||
backyConfigOptions.InitConfig()
|
||||
backyConfigOptions.ParseConfigurationFile()
|
||||
backyConfigOptions.RunListConfig("")
|
||||
|
||||
}
|
||||
|
||||
func TestSettingCommandInfoPackageCommandDnf(t *testing.T) {
|
||||
|
||||
packagecommand := &Command{
|
||||
Type: PackageCommandType,
|
||||
PackageManager: "dnf",
|
||||
Shell: "zsh",
|
||||
PackageOperation: PackageOperationCheckVersion,
|
||||
Packages: []packagemanagercommon.Package{{Name: "docker-ce"}},
|
||||
}
|
||||
dnfPackage, _ := pkgman.PackageManagerFactory("dnf", pkgman.WithoutAuth())
|
||||
|
||||
packagecommand.pkgMan = dnfPackage
|
||||
PackageCommand := getCommandTypeAndSetCommandInfo(packagecommand)
|
||||
|
||||
assert.Equal(t, "dnf", PackageCommand.Cmd)
|
||||
|
||||
}
|
||||
|
||||
func TestWithDockerFile(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
docker, err := testcontainers.Run(ctx, "",
|
||||
testcontainers.WithDockerfile(testcontainers.FromDockerfile{
|
||||
Context: "../../tests/docker",
|
||||
Dockerfile: "Dockerfile",
|
||||
KeepImage: false,
|
||||
// BuildOptionsModifier: func(buildOptions *types.ImageBuildOptions) {
|
||||
// buildOptions.Target = "target2"
|
||||
// },
|
||||
}),
|
||||
)
|
||||
// docker.
|
||||
|
||||
if err != nil {
|
||||
log.Printf("failed to start container: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
r, err := docker.Logs(ctx)
|
||||
if err != nil {
|
||||
log.Printf("failed to get logs: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
logs, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
log.Printf("failed to read logs: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(string(logs))
|
||||
|
||||
require.NoError(t, err)
|
||||
}
|
141
pkg/backy/commandtype_enumer.go
Normal file
141
pkg/backy/commandtype_enumer.go
Normal file
@ -0,0 +1,141 @@
|
||||
// Code generated by "enumer -linecomment -yaml -text -json -type=CommandType"; DO NOT EDIT.
|
||||
|
||||
package backy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _CommandTypeName = "scriptscriptFileremoteScriptpackageuser"
|
||||
|
||||
var _CommandTypeIndex = [...]uint8{0, 0, 6, 16, 28, 35, 39}
|
||||
|
||||
const _CommandTypeLowerName = "scriptscriptfileremotescriptpackageuser"
|
||||
|
||||
func (i CommandType) String() string {
|
||||
if i < 0 || i >= CommandType(len(_CommandTypeIndex)-1) {
|
||||
return fmt.Sprintf("CommandType(%d)", i)
|
||||
}
|
||||
return _CommandTypeName[_CommandTypeIndex[i]:_CommandTypeIndex[i+1]]
|
||||
}
|
||||
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
func _CommandTypeNoOp() {
|
||||
var x [1]struct{}
|
||||
_ = x[DefaultCommandType-(0)]
|
||||
_ = x[ScriptCommandType-(1)]
|
||||
_ = x[ScriptFileCommandType-(2)]
|
||||
_ = x[RemoteScriptCommandType-(3)]
|
||||
_ = x[PackageCommandType-(4)]
|
||||
_ = x[UserCommandType-(5)]
|
||||
}
|
||||
|
||||
var _CommandTypeValues = []CommandType{DefaultCommandType, ScriptCommandType, ScriptFileCommandType, RemoteScriptCommandType, PackageCommandType, UserCommandType}
|
||||
|
||||
var _CommandTypeNameToValueMap = map[string]CommandType{
|
||||
_CommandTypeName[0:0]: DefaultCommandType,
|
||||
_CommandTypeLowerName[0:0]: DefaultCommandType,
|
||||
_CommandTypeName[0:6]: ScriptCommandType,
|
||||
_CommandTypeLowerName[0:6]: ScriptCommandType,
|
||||
_CommandTypeName[6:16]: ScriptFileCommandType,
|
||||
_CommandTypeLowerName[6:16]: ScriptFileCommandType,
|
||||
_CommandTypeName[16:28]: RemoteScriptCommandType,
|
||||
_CommandTypeLowerName[16:28]: RemoteScriptCommandType,
|
||||
_CommandTypeName[28:35]: PackageCommandType,
|
||||
_CommandTypeLowerName[28:35]: PackageCommandType,
|
||||
_CommandTypeName[35:39]: UserCommandType,
|
||||
_CommandTypeLowerName[35:39]: UserCommandType,
|
||||
}
|
||||
|
||||
var _CommandTypeNames = []string{
|
||||
_CommandTypeName[0:0],
|
||||
_CommandTypeName[0:6],
|
||||
_CommandTypeName[6:16],
|
||||
_CommandTypeName[16:28],
|
||||
_CommandTypeName[28:35],
|
||||
_CommandTypeName[35:39],
|
||||
}
|
||||
|
||||
// CommandTypeString retrieves an enum value from the enum constants string name.
|
||||
// Throws an error if the param is not part of the enum.
|
||||
func CommandTypeString(s string) (CommandType, error) {
|
||||
if val, ok := _CommandTypeNameToValueMap[s]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if val, ok := _CommandTypeNameToValueMap[strings.ToLower(s)]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return 0, fmt.Errorf("%s does not belong to CommandType values", s)
|
||||
}
|
||||
|
||||
// CommandTypeValues returns all values of the enum
|
||||
func CommandTypeValues() []CommandType {
|
||||
return _CommandTypeValues
|
||||
}
|
||||
|
||||
// CommandTypeStrings returns a slice of all String values of the enum
|
||||
func CommandTypeStrings() []string {
|
||||
strs := make([]string, len(_CommandTypeNames))
|
||||
copy(strs, _CommandTypeNames)
|
||||
return strs
|
||||
}
|
||||
|
||||
// IsACommandType returns "true" if the value is listed in the enum definition. "false" otherwise
|
||||
func (i CommandType) IsACommandType() bool {
|
||||
for _, v := range _CommandTypeValues {
|
||||
if i == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface for CommandType
|
||||
func (i CommandType) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface for CommandType
|
||||
func (i *CommandType) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return fmt.Errorf("CommandType should be a string, got %s", data)
|
||||
}
|
||||
|
||||
var err error
|
||||
*i, err = CommandTypeString(s)
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface for CommandType
|
||||
func (i CommandType) MarshalText() ([]byte, error) {
|
||||
return []byte(i.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface for CommandType
|
||||
func (i *CommandType) UnmarshalText(text []byte) error {
|
||||
var err error
|
||||
*i, err = CommandTypeString(string(text))
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalYAML implements a YAML Marshaler for CommandType
|
||||
func (i CommandType) MarshalYAML() (interface{}, error) {
|
||||
return i.String(), nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements a YAML Unmarshaler for CommandType
|
||||
func (i *CommandType) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var s string
|
||||
if err := unmarshal(&s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
*i, err = CommandTypeString(s)
|
||||
return err
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -17,10 +17,7 @@ func (opts *ConfigOpts) Cron() {
|
||||
s := gocron.NewScheduler(time.Local)
|
||||
s.TagsUnique()
|
||||
cmdLists := opts.CmdConfigLists
|
||||
for listName, config := range cmdLists {
|
||||
if config.Name == "" {
|
||||
config.Name = listName
|
||||
}
|
||||
for _, config := range cmdLists {
|
||||
|
||||
cron := strings.TrimSpace(config.Cron)
|
||||
if cron != "" {
|
||||
|
133
pkg/backy/lineinfile.go
Normal file
133
pkg/backy/lineinfile.go
Normal file
@ -0,0 +1,133 @@
|
||||
package backy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func sshConnect(user, password, host string, port int) (*ssh.Client, error) {
|
||||
config := &ssh.ClientConfig{
|
||||
User: user,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password(password),
|
||||
},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
return ssh.Dial("tcp", addr, config)
|
||||
}
|
||||
|
||||
func sshReadFile(client *ssh.Client, remotePath string) (string, error) {
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
var b bytes.Buffer
|
||||
session.Stdout = &b
|
||||
if err := session.Run(fmt.Sprintf("cat %s", remotePath)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func sshWriteFile(client *ssh.Client, remotePath, content string) error {
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
stdin, err := session.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer stdin.Close()
|
||||
io.WriteString(stdin, content)
|
||||
}()
|
||||
|
||||
cmd := fmt.Sprintf("cat > %s", remotePath)
|
||||
return session.Run(cmd)
|
||||
}
|
||||
|
||||
func lineInString(content, regexpPattern, line string) string {
|
||||
scanner := bufio.NewScanner(strings.NewReader(content))
|
||||
var lines []string
|
||||
found := false
|
||||
re := regexp.MustCompile(regexpPattern)
|
||||
|
||||
for scanner.Scan() {
|
||||
l := scanner.Text()
|
||||
if re.MatchString(l) {
|
||||
found = true
|
||||
lines = append(lines, line)
|
||||
} else {
|
||||
lines = append(lines, l)
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
return strings.Join(lines, "\n") + "\n"
|
||||
}
|
||||
|
||||
func Call() {
|
||||
user := "youruser"
|
||||
password := "yourpassword"
|
||||
host := "yourhost"
|
||||
port := 22
|
||||
remotePath := "/path/to/remote/file"
|
||||
|
||||
client, err := sshConnect(user, password, host, port)
|
||||
if err != nil {
|
||||
fmt.Println("SSH connection error:", err)
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
content, err := sshReadFile(client, remotePath)
|
||||
if err != nil {
|
||||
fmt.Println("Read error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
newContent := lineInString(content, "^foo=", "foo=bar")
|
||||
|
||||
if err := sshWriteFile(client, remotePath, newContent); err != nil {
|
||||
fmt.Println("Write error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Line updated successfully over SSH.")
|
||||
}
|
||||
|
||||
type LineInFile struct {
|
||||
RemotePath string // Path to the remote file
|
||||
Pattern string // Regex pattern to match lines
|
||||
Line string // Line to insert or replace
|
||||
InsertAfter bool // If true, insert after matched line; else replace
|
||||
User string // SSH username
|
||||
Password string // SSH password (use key for production)
|
||||
Host string // SSH host
|
||||
Port int // SSH port
|
||||
regexCompiled *regexp.Regexp // Compiled regex (internal use)
|
||||
}
|
||||
|
||||
// CompileRegex compiles the regex pattern for later use
|
||||
func (l *LineInFile) CompileRegex() error {
|
||||
re, err := regexp.Compile(l.Pattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.regexCompiled = re
|
||||
return nil
|
||||
}
|
@ -23,8 +23,7 @@ func (opts *ConfigOpts) ListCommand(cmd string) {
|
||||
var cmdFound bool = false
|
||||
var cmdInfo *Command
|
||||
// check commands in file against cmd
|
||||
for _, cmdInFile := range opts.executeCmds {
|
||||
print(cmdInFile)
|
||||
for cmdInFile := range opts.Cmds {
|
||||
cmdFound = false
|
||||
|
||||
if cmd == cmdInFile {
|
||||
@ -37,28 +36,39 @@ func (opts *ConfigOpts) ListCommand(cmd string) {
|
||||
// print the command's information
|
||||
if cmdFound {
|
||||
|
||||
print("Command: ")
|
||||
println("Command: ")
|
||||
|
||||
print(cmdInfo.Cmd)
|
||||
if len(cmdInfo.Args) >= 0 {
|
||||
|
||||
for _, v := range cmdInfo.Args {
|
||||
print(" ") // print space between command and args
|
||||
print(v) // print command arg
|
||||
}
|
||||
for _, v := range cmdInfo.Args {
|
||||
print(" ") // print space between command and args
|
||||
print(v) // print command arg
|
||||
}
|
||||
|
||||
// is is remote or local
|
||||
if cmdInfo.Host != nil {
|
||||
|
||||
// is it remote or local
|
||||
if !IsHostLocal(cmdInfo.Host) {
|
||||
println()
|
||||
print("Host: ", cmdInfo.Host)
|
||||
println()
|
||||
|
||||
} else {
|
||||
|
||||
println()
|
||||
print("Host: Runs on Local Machine\n\n")
|
||||
|
||||
}
|
||||
|
||||
if cmdInfo.Dir != nil {
|
||||
println()
|
||||
print("Directory: ", *cmdInfo.Dir)
|
||||
println()
|
||||
}
|
||||
|
||||
if cmdInfo.Type.String() != "" {
|
||||
print("Type: ", cmdInfo.Type.String())
|
||||
println()
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
fmt.Printf("Command %s not found. Check spelling.\n", cmd)
|
||||
@ -66,3 +76,38 @@ func (opts *ConfigOpts) ListCommand(cmd string) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (opts *ConfigOpts) ListCommandList(list string) {
|
||||
// bool for commands not found
|
||||
// gets set to false if a command is not found
|
||||
// set to true if the command is found
|
||||
var listFound bool = false
|
||||
var listInfo *CmdList
|
||||
// check commands in file against cmd
|
||||
for listInFile, l := range opts.CmdConfigLists {
|
||||
listFound = false
|
||||
|
||||
if list == listInFile {
|
||||
listFound = true
|
||||
listInfo = l
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// print the command's information
|
||||
if listFound {
|
||||
|
||||
println("List: ", list)
|
||||
println()
|
||||
|
||||
for _, v := range listInfo.Order {
|
||||
println()
|
||||
opts.ListCommand(v)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
fmt.Printf("List %s not found. Check spelling.\n", list)
|
||||
|
||||
}
|
||||
}
|
||||
|
57
pkg/backy/metrics.go
Normal file
57
pkg/backy/metrics.go
Normal file
@ -0,0 +1,57 @@
|
||||
package backy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Metrics struct {
|
||||
SuccessfulExecutions uint64 `json:"successful_executions"`
|
||||
FailedExecutions uint64 `json:"failed_executions"`
|
||||
TotalExecutions uint64 `json:"total_executions"`
|
||||
ExecutionTime float64 `json:"execution_time"` // in seconds
|
||||
AverageExecutionTime float64 `json:"average_execution_time"` // in seconds
|
||||
SuccessRate float64 `json:"success_rate"` // percentage of successful executions
|
||||
FailureRate float64 `json:"failure_rate"` // percentage of failed executions
|
||||
}
|
||||
|
||||
func NewMetrics() *Metrics {
|
||||
return &Metrics{
|
||||
SuccessfulExecutions: 0,
|
||||
FailedExecutions: 0,
|
||||
TotalExecutions: 0,
|
||||
ExecutionTime: 0.0,
|
||||
AverageExecutionTime: 0.0,
|
||||
SuccessRate: 0.0,
|
||||
FailureRate: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Metrics) Update(success bool, executionTime float64) {
|
||||
m.TotalExecutions++
|
||||
if success {
|
||||
m.SuccessfulExecutions++
|
||||
} else {
|
||||
m.FailedExecutions++
|
||||
}
|
||||
|
||||
m.ExecutionTime += executionTime
|
||||
m.AverageExecutionTime = m.ExecutionTime / float64(m.TotalExecutions)
|
||||
|
||||
if m.TotalExecutions > 0 {
|
||||
m.SuccessRate = float64(m.SuccessfulExecutions) / float64(m.TotalExecutions) * 100
|
||||
m.FailureRate = float64(m.FailedExecutions) / float64(m.TotalExecutions) * 100
|
||||
}
|
||||
}
|
||||
|
||||
func SaveToFile(metrics *Metrics, filename string) error {
|
||||
data, err := json.MarshalIndent(metrics, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(filename, data, 0644)
|
||||
}
|
||||
|
||||
func LoadFromFile(filename string) (*Metrics, error) {
|
||||
return nil, nil
|
||||
}
|
7
pkg/backy/metrics_test.go
Normal file
7
pkg/backy/metrics_test.go
Normal file
@ -0,0 +1,7 @@
|
||||
package backy
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAddingMetricsForCommand(t *testing.T) {
|
||||
|
||||
}
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/logging"
|
||||
"github.com/nikoksr/notify"
|
||||
"github.com/nikoksr/notify/service/http"
|
||||
"github.com/nikoksr/notify/service/mail"
|
||||
"github.com/nikoksr/notify/service/matrix"
|
||||
"maunium.net/go/mautrix/id"
|
||||
@ -30,6 +31,12 @@ type MailConfig struct {
|
||||
Password string `yaml:"password"`
|
||||
}
|
||||
|
||||
type HttpConfig struct {
|
||||
URL string `yaml:"url"`
|
||||
Method string `yaml:"method"`
|
||||
Headers map[string][]string `yaml:"headers"`
|
||||
}
|
||||
|
||||
// SetupNotify sets up notify instances for each command list.
|
||||
func (opts *ConfigOpts) SetupNotify() {
|
||||
|
||||
@ -58,6 +65,8 @@ func (opts *ConfigOpts) SetupNotify() {
|
||||
opts.Logger.Info().Err(fmt.Errorf("error: ID %s not found in mail object", confId)).Str("list", confName).Send()
|
||||
continue
|
||||
}
|
||||
conf.Password = getExternalConfigDirectiveValue(conf.Password, opts, AllowedExternalDirectiveAll)
|
||||
opts.Logger.Debug().Str("list", confName).Str("id", confId).Msg("adding mail notification service")
|
||||
mailConf := setupMail(conf)
|
||||
services = append(services, mailConf)
|
||||
case "matrix":
|
||||
@ -66,14 +75,23 @@ func (opts *ConfigOpts) SetupNotify() {
|
||||
opts.Logger.Info().Err(fmt.Errorf("error: ID %s not found in matrix object", confId)).Str("list", confName).Send()
|
||||
continue
|
||||
}
|
||||
conf.AccessToken = getExternalConfigDirectiveValue(conf.AccessToken, opts, AllowedExternalDirectiveAll)
|
||||
opts.Logger.Debug().Str("list", confName).Str("id", confId).Msg("adding matrix notification service")
|
||||
mtrxConf, mtrxErr := setupMatrix(conf)
|
||||
if mtrxErr != nil {
|
||||
opts.Logger.Info().Str("list", confName).Err(fmt.Errorf("error: configuring matrix id %s failed during setup: %w", id, mtrxErr))
|
||||
continue
|
||||
}
|
||||
// append the services
|
||||
services = append(services, mtrxConf)
|
||||
// service is not recognized
|
||||
case "http":
|
||||
conf, ok := opts.NotificationConf.HttpConfig[confId]
|
||||
if !ok {
|
||||
opts.Logger.Info().Err(fmt.Errorf("error: ID %s not found in http object", confId)).Str("list", confName).Send()
|
||||
continue
|
||||
}
|
||||
opts.Logger.Debug().Str("list", confName).Str("id", confId).Msg("adding http notification service")
|
||||
httpConf := setupHttp(conf)
|
||||
services = append(services, httpConf)
|
||||
default:
|
||||
opts.Logger.Info().Err(fmt.Errorf("id %s not found", id)).Str("list", confName).Send()
|
||||
}
|
||||
@ -99,3 +117,19 @@ func setupMail(config MailConfig) *mail.Mail {
|
||||
mailClient.BodyFormat(mail.PlainText)
|
||||
return mailClient
|
||||
}
|
||||
|
||||
func setupHttp(httpConf HttpConfig) *http.Service {
|
||||
|
||||
httpService := http.New()
|
||||
httpService.AddReceivers(&http.Webhook{
|
||||
URL: httpConf.URL,
|
||||
Header: httpConf.Headers,
|
||||
ContentType: "text/plain",
|
||||
Method: httpConf.Method,
|
||||
BuildPayload: func(subject, message string) (payload any) {
|
||||
return subject + "\n\n" + message
|
||||
},
|
||||
})
|
||||
|
||||
return httpService
|
||||
}
|
||||
|
145
pkg/backy/packageoperation_enumer.go
Normal file
145
pkg/backy/packageoperation_enumer.go
Normal file
@ -0,0 +1,145 @@
|
||||
// Code generated by "enumer -linecomment -yaml -text -json -type=PackageOperation"; DO NOT EDIT.
|
||||
|
||||
package backy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _PackageOperationName = "installupgradepurgeremovecheckVersionisInstalled"
|
||||
|
||||
var _PackageOperationIndex = [...]uint8{0, 0, 7, 14, 19, 25, 37, 48}
|
||||
|
||||
const _PackageOperationLowerName = "installupgradepurgeremovecheckversionisinstalled"
|
||||
|
||||
func (i PackageOperation) String() string {
|
||||
if i < 0 || i >= PackageOperation(len(_PackageOperationIndex)-1) {
|
||||
return fmt.Sprintf("PackageOperation(%d)", i)
|
||||
}
|
||||
return _PackageOperationName[_PackageOperationIndex[i]:_PackageOperationIndex[i+1]]
|
||||
}
|
||||
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
func _PackageOperationNoOp() {
|
||||
var x [1]struct{}
|
||||
_ = x[DefaultPO-(0)]
|
||||
_ = x[PackageOperationInstall-(1)]
|
||||
_ = x[PackageOperationUpgrade-(2)]
|
||||
_ = x[PackageOperationPurge-(3)]
|
||||
_ = x[PackageOperationRemove-(4)]
|
||||
_ = x[PackageOperationCheckVersion-(5)]
|
||||
_ = x[PackageOperationIsInstalled-(6)]
|
||||
}
|
||||
|
||||
var _PackageOperationValues = []PackageOperation{DefaultPO, PackageOperationInstall, PackageOperationUpgrade, PackageOperationPurge, PackageOperationRemove, PackageOperationCheckVersion, PackageOperationIsInstalled}
|
||||
|
||||
var _PackageOperationNameToValueMap = map[string]PackageOperation{
|
||||
_PackageOperationName[0:0]: DefaultPO,
|
||||
_PackageOperationLowerName[0:0]: DefaultPO,
|
||||
_PackageOperationName[0:7]: PackageOperationInstall,
|
||||
_PackageOperationLowerName[0:7]: PackageOperationInstall,
|
||||
_PackageOperationName[7:14]: PackageOperationUpgrade,
|
||||
_PackageOperationLowerName[7:14]: PackageOperationUpgrade,
|
||||
_PackageOperationName[14:19]: PackageOperationPurge,
|
||||
_PackageOperationLowerName[14:19]: PackageOperationPurge,
|
||||
_PackageOperationName[19:25]: PackageOperationRemove,
|
||||
_PackageOperationLowerName[19:25]: PackageOperationRemove,
|
||||
_PackageOperationName[25:37]: PackageOperationCheckVersion,
|
||||
_PackageOperationLowerName[25:37]: PackageOperationCheckVersion,
|
||||
_PackageOperationName[37:48]: PackageOperationIsInstalled,
|
||||
_PackageOperationLowerName[37:48]: PackageOperationIsInstalled,
|
||||
}
|
||||
|
||||
var _PackageOperationNames = []string{
|
||||
_PackageOperationName[0:0],
|
||||
_PackageOperationName[0:7],
|
||||
_PackageOperationName[7:14],
|
||||
_PackageOperationName[14:19],
|
||||
_PackageOperationName[19:25],
|
||||
_PackageOperationName[25:37],
|
||||
_PackageOperationName[37:48],
|
||||
}
|
||||
|
||||
// PackageOperationString retrieves an enum value from the enum constants string name.
|
||||
// Throws an error if the param is not part of the enum.
|
||||
func PackageOperationString(s string) (PackageOperation, error) {
|
||||
if val, ok := _PackageOperationNameToValueMap[s]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if val, ok := _PackageOperationNameToValueMap[strings.ToLower(s)]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return 0, fmt.Errorf("%s does not belong to PackageOperation values", s)
|
||||
}
|
||||
|
||||
// PackageOperationValues returns all values of the enum
|
||||
func PackageOperationValues() []PackageOperation {
|
||||
return _PackageOperationValues
|
||||
}
|
||||
|
||||
// PackageOperationStrings returns a slice of all String values of the enum
|
||||
func PackageOperationStrings() []string {
|
||||
strs := make([]string, len(_PackageOperationNames))
|
||||
copy(strs, _PackageOperationNames)
|
||||
return strs
|
||||
}
|
||||
|
||||
// IsAPackageOperation returns "true" if the value is listed in the enum definition. "false" otherwise
|
||||
func (i PackageOperation) IsAPackageOperation() bool {
|
||||
for _, v := range _PackageOperationValues {
|
||||
if i == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface for PackageOperation
|
||||
func (i PackageOperation) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface for PackageOperation
|
||||
func (i *PackageOperation) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return fmt.Errorf("PackageOperation should be a string, got %s", data)
|
||||
}
|
||||
|
||||
var err error
|
||||
*i, err = PackageOperationString(s)
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface for PackageOperation
|
||||
func (i PackageOperation) MarshalText() ([]byte, error) {
|
||||
return []byte(i.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface for PackageOperation
|
||||
func (i *PackageOperation) UnmarshalText(text []byte) error {
|
||||
var err error
|
||||
*i, err = PackageOperationString(string(text))
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalYAML implements a YAML Marshaler for PackageOperation
|
||||
func (i PackageOperation) MarshalYAML() (interface{}, error) {
|
||||
return i.String(), nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements a YAML Unmarshaler for PackageOperation
|
||||
func (i *PackageOperation) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var s string
|
||||
if err := unmarshal(&s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
*i, err = PackageOperationString(s)
|
||||
return err
|
||||
}
|
10
pkg/backy/planForHosts.md
Normal file
10
pkg/backy/planForHosts.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Running commands on hosts
|
||||
|
||||
I want all commands in a list to be able to be run on all hosts. The underlying solution will be using a function to run the list on a host, and therefore change the host on the commands. This can be done in several ways:
|
||||
|
||||
1. The commands can have a `Hosts` field that will be a []string. This array can be populated several ways:
|
||||
- From the config file
|
||||
- using CLI options and commands
|
||||
The commands can be run in succession on all hosts using functions
|
||||
|
||||
2. The existing `Host` field can be modified in a function. The commands need to be added to a `[]*Command` slice so that all hosts can be run.
|
502
pkg/backy/ssh.go
502
pkg/backy/ssh.go
@ -15,76 +15,78 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/kevinburke/ssh_config"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pkg/sftp"
|
||||
"github.com/rs/zerolog"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/knownhosts"
|
||||
)
|
||||
|
||||
var PrivateKeyExtraInfoErr = errors.New("Private key may be encrypted. \nIf encrypted, make sure the password is specified correctly in the correct section. This may be done in one of three ways: \n privatekeypassword: env:PR_KEY_PASS \n privatekeypassword: file:/path/to/password-file \n privatekeypassword: password (not recommended). \n ")
|
||||
var PrivateKeyExtraInfoErr = errors.New("Private key may be encrypted. \nIf encrypted, make sure the password is specified correctly in the correct section. This may be done in one of two ways: \n Using external directives - see docs \n privatekeypassword: password (not recommended). \n ")
|
||||
var TS = strings.TrimSpace
|
||||
|
||||
// ConnectToHost connects to a host by looking up the config values in the file ~/.ssh/config
|
||||
// It uses any set values and looks up an unset values in the config files
|
||||
// remoteConfig is modified directly. The *ssh.Client is returned as part of remoteConfig,
|
||||
// remoteHost is modified directly. The *ssh.Client is returned as part of remoteHost,
|
||||
// If configFile is empty, any required configuration is looked up in the default config files
|
||||
// If any value is not found, defaults are used
|
||||
func (remoteConfig *Host) ConnectToHost(opts *ConfigOpts) error {
|
||||
func (remoteHost *Host) ConnectToHost(opts *ConfigOpts) error {
|
||||
|
||||
var connectErr error
|
||||
|
||||
if TS(remoteConfig.ConfigFilePath) == "" {
|
||||
remoteConfig.useDefaultConfig = true
|
||||
if TS(remoteHost.ConfigFilePath) == "" {
|
||||
remoteHost.useDefaultConfig = true
|
||||
}
|
||||
|
||||
khPathErr := remoteConfig.GetKnownHosts()
|
||||
khPathErr := remoteHost.GetKnownHosts()
|
||||
|
||||
if khPathErr != nil {
|
||||
return khPathErr
|
||||
}
|
||||
|
||||
if remoteConfig.ClientConfig == nil {
|
||||
remoteConfig.ClientConfig = &ssh.ClientConfig{}
|
||||
if remoteHost.ClientConfig == nil {
|
||||
remoteHost.ClientConfig = &ssh.ClientConfig{}
|
||||
}
|
||||
|
||||
var configFile *os.File
|
||||
|
||||
var sshConfigFileOpenErr error
|
||||
|
||||
if !remoteConfig.useDefaultConfig {
|
||||
if !remoteHost.useDefaultConfig {
|
||||
var err error
|
||||
remoteConfig.ConfigFilePath, err = resolveDir(remoteConfig.ConfigFilePath)
|
||||
remoteHost.ConfigFilePath, err = getFullPathWithHomeDir(remoteHost.ConfigFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configFile, sshConfigFileOpenErr = os.Open(remoteConfig.ConfigFilePath)
|
||||
configFile, sshConfigFileOpenErr = os.Open(remoteHost.ConfigFilePath)
|
||||
if sshConfigFileOpenErr != nil {
|
||||
return sshConfigFileOpenErr
|
||||
}
|
||||
} else {
|
||||
defaultConfig, _ := resolveDir("~/.ssh/config")
|
||||
defaultConfig, _ := getFullPathWithHomeDir("~/.ssh/config")
|
||||
configFile, sshConfigFileOpenErr = os.Open(defaultConfig)
|
||||
if sshConfigFileOpenErr != nil {
|
||||
return sshConfigFileOpenErr
|
||||
}
|
||||
}
|
||||
remoteConfig.SSHConfigFile = &sshConfigFile{}
|
||||
remoteConfig.SSHConfigFile.DefaultUserSettings = ssh_config.DefaultUserSettings
|
||||
remoteHost.SSHConfigFile = &sshConfigFile{}
|
||||
remoteHost.SSHConfigFile.DefaultUserSettings = ssh_config.DefaultUserSettings
|
||||
var decodeErr error
|
||||
remoteConfig.SSHConfigFile.SshConfigFile, decodeErr = ssh_config.Decode(configFile)
|
||||
remoteHost.SSHConfigFile.SshConfigFile, decodeErr = ssh_config.Decode(configFile)
|
||||
if decodeErr != nil {
|
||||
return decodeErr
|
||||
}
|
||||
|
||||
err := remoteConfig.GetProxyJumpFromConfig(opts.Hosts)
|
||||
err := remoteHost.GetProxyJumpFromConfig(opts.Hosts)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if remoteConfig.ProxyHost != nil {
|
||||
for _, proxyHost := range remoteConfig.ProxyHost {
|
||||
if remoteHost.ProxyHost != nil {
|
||||
for _, proxyHost := range remoteHost.ProxyHost {
|
||||
err := proxyHost.GetProxyJumpConfig(opts.Hosts, opts)
|
||||
opts.Logger.Info().Msgf("Proxy host: %s", proxyHost.Host)
|
||||
if err != nil {
|
||||
@ -93,50 +95,49 @@ func (remoteConfig *Host) ConnectToHost(opts *ConfigOpts) error {
|
||||
}
|
||||
}
|
||||
|
||||
remoteConfig.ClientConfig.Timeout = time.Second * 30
|
||||
remoteHost.ClientConfig.Timeout = time.Second * 30
|
||||
|
||||
remoteConfig.GetPrivateKeyFileFromConfig()
|
||||
remoteHost.GetPrivateKeyFileFromConfig()
|
||||
|
||||
remoteConfig.GetPort()
|
||||
remoteHost.GetPort()
|
||||
|
||||
remoteConfig.GetHostName()
|
||||
remoteHost.GetHostName()
|
||||
|
||||
remoteConfig.CombineHostNameWithPort()
|
||||
remoteHost.CombineHostNameWithPort()
|
||||
|
||||
remoteConfig.GetSshUserFromConfig()
|
||||
remoteHost.GetSshUserFromConfig()
|
||||
|
||||
if remoteConfig.HostName == "" {
|
||||
return errors.Errorf("No hostname found or specified for host %s", remoteConfig.Host)
|
||||
if remoteHost.HostName == "" {
|
||||
return errors.Errorf("No hostname found or specified for host %s", remoteHost.Host)
|
||||
}
|
||||
|
||||
err = remoteConfig.GetAuthMethods(opts)
|
||||
err = remoteHost.GetAuthMethods(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hostKeyCallback, err := knownhosts.New(remoteConfig.KnownHostsFile)
|
||||
hostKeyCallback, err := knownhosts.New(remoteHost.KnownHostsFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create hostkeycallback function")
|
||||
}
|
||||
remoteConfig.ClientConfig.HostKeyCallback = hostKeyCallback
|
||||
opts.Logger.Info().Str("user", remoteConfig.ClientConfig.User).Send()
|
||||
remoteHost.ClientConfig.HostKeyCallback = hostKeyCallback
|
||||
|
||||
remoteConfig.SshClient, connectErr = remoteConfig.ConnectThroughBastion(opts.Logger)
|
||||
remoteHost.SshClient, connectErr = remoteHost.ConnectThroughBastion(opts.Logger)
|
||||
if connectErr != nil {
|
||||
return connectErr
|
||||
}
|
||||
if remoteConfig.SshClient != nil {
|
||||
opts.Hosts[remoteConfig.Host] = remoteConfig
|
||||
if remoteHost.SshClient != nil {
|
||||
opts.Hosts[remoteHost.Host] = remoteHost
|
||||
return nil
|
||||
}
|
||||
|
||||
opts.Logger.Info().Msgf("Connecting to host %s", remoteConfig.HostName)
|
||||
remoteConfig.SshClient, connectErr = ssh.Dial("tcp", remoteConfig.HostName, remoteConfig.ClientConfig)
|
||||
opts.Logger.Info().Msgf("Connecting to host %s", remoteHost.HostName)
|
||||
remoteHost.SshClient, connectErr = ssh.Dial("tcp", remoteHost.HostName, remoteHost.ClientConfig)
|
||||
if connectErr != nil {
|
||||
return connectErr
|
||||
}
|
||||
|
||||
opts.Hosts[remoteConfig.Host] = remoteConfig
|
||||
opts.Hosts[remoteHost.Host] = remoteHost
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -180,11 +181,7 @@ func (remoteHost *Host) GetAuthMethods(opts *ConfigOpts) error {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteHost.PrivateKeyPassword, err = GetPrivateKeyPassword(remoteHost.PrivateKeyPassword, opts, opts.Logger)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remoteHost.PrivateKeyPassword = GetPrivateKeyPassword(remoteHost.PrivateKeyPassword, opts)
|
||||
|
||||
if remoteHost.PrivateKeyPassword == "" {
|
||||
|
||||
@ -207,14 +204,13 @@ func (remoteHost *Host) GetAuthMethods(opts *ConfigOpts) error {
|
||||
}
|
||||
}
|
||||
|
||||
if remoteHost.Password == "" {
|
||||
if remoteHost.Password != "" {
|
||||
|
||||
remoteHost.Password, err = GetPassword(remoteHost.Password, opts, opts.Logger)
|
||||
opts.Logger.Debug().Str("password", remoteHost.Password).Str("Host", remoteHost.Host).Send()
|
||||
|
||||
if err != nil {
|
||||
remoteHost.Password = GetPassword(remoteHost.Password, opts)
|
||||
|
||||
return err
|
||||
}
|
||||
// opts.Logger.Debug().Str("actual password", remoteHost.Password).Str("Host", remoteHost.Host).Send()
|
||||
|
||||
remoteHost.ClientConfig.Auth = append(remoteHost.ClientConfig.Auth, ssh.Password(remoteHost.Password))
|
||||
}
|
||||
@ -231,6 +227,8 @@ func (remoteHost *Host) GetPrivateKeyFileFromConfig() {
|
||||
var identityFile string
|
||||
if remoteHost.PrivateKeyPath == "" {
|
||||
identityFile, _ = remoteHost.SSHConfigFile.SshConfigFile.Get(remoteHost.Host, "IdentityFile")
|
||||
// println("Identity file:", identityFile)
|
||||
// println("Host:", remoteHost.Host)
|
||||
if identityFile == "" {
|
||||
identityFile, _ = remoteHost.SSHConfigFile.DefaultUserSettings.GetStrict(remoteHost.Host, "IdentityFile")
|
||||
if identityFile == "" {
|
||||
@ -242,22 +240,21 @@ func (remoteHost *Host) GetPrivateKeyFileFromConfig() {
|
||||
identityFile = remoteHost.PrivateKeyPath
|
||||
}
|
||||
|
||||
remoteHost.PrivateKeyPath, _ = resolveDir(identityFile)
|
||||
// println("Identity file:", identityFile)
|
||||
remoteHost.PrivateKeyPath, _ = getFullPathWithHomeDir(identityFile)
|
||||
}
|
||||
|
||||
// GetPort checks if the port from the config file is 0
|
||||
// If it is the port is searched in the SSH config file(s)
|
||||
func (remoteHost *Host) GetPort() {
|
||||
port := fmt.Sprintf("%d", remoteHost.Port)
|
||||
// port specifed?
|
||||
// port specified?
|
||||
// port will be 0 if missing from backy config
|
||||
if port == "0" {
|
||||
// get port from specified SSH config file
|
||||
port, _ = remoteHost.SSHConfigFile.SshConfigFile.Get(remoteHost.Host, "Port")
|
||||
|
||||
if port == "" {
|
||||
|
||||
// get port from default SSH config file
|
||||
port = remoteHost.SSHConfigFile.DefaultUserSettings.Get(remoteHost.Host, "Port")
|
||||
|
||||
// set port to be default
|
||||
@ -272,7 +269,6 @@ func (remoteHost *Host) GetPort() {
|
||||
|
||||
func (remoteHost *Host) CombineHostNameWithPort() {
|
||||
|
||||
// if the port is already in the HostName, leave it
|
||||
if strings.HasSuffix(remoteHost.HostName, fmt.Sprintf(":%d", remoteHost.Port)) {
|
||||
return
|
||||
}
|
||||
@ -315,7 +311,6 @@ func (remoteHost *Host) ConnectThroughBastion(log zerolog.Logger) (*ssh.Client,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// sClient is an ssh client connected to the service host, through the bastion host.
|
||||
sClient := ssh.NewClient(ncc, chans, reqs)
|
||||
|
||||
return sClient, nil
|
||||
@ -323,95 +318,44 @@ func (remoteHost *Host) ConnectThroughBastion(log zerolog.Logger) (*ssh.Client,
|
||||
|
||||
// GetKnownHosts resolves the host's KnownHosts file if it is defined
|
||||
// if not defined, the default location for this file is used
|
||||
func (remotehHost *Host) GetKnownHosts() error {
|
||||
func (remoteHost *Host) GetKnownHosts() error {
|
||||
var knownHostsFileErr error
|
||||
if TS(remotehHost.KnownHostsFile) != "" {
|
||||
remotehHost.KnownHostsFile, knownHostsFileErr = resolveDir(remotehHost.KnownHostsFile)
|
||||
if TS(remoteHost.KnownHostsFile) != "" {
|
||||
remoteHost.KnownHostsFile, knownHostsFileErr = getFullPathWithHomeDir(remoteHost.KnownHostsFile)
|
||||
return knownHostsFileErr
|
||||
}
|
||||
remotehHost.KnownHostsFile, knownHostsFileErr = resolveDir("~/.ssh/known_hosts")
|
||||
remoteHost.KnownHostsFile, knownHostsFileErr = getFullPathWithHomeDir("~/.ssh/known_hosts")
|
||||
return knownHostsFileErr
|
||||
}
|
||||
|
||||
func GetPrivateKeyPassword(key string, opts *ConfigOpts, log zerolog.Logger) (string, error) {
|
||||
|
||||
var prKeyPassword string
|
||||
if strings.HasPrefix(key, "file:") {
|
||||
privKeyPassFilePath := strings.TrimPrefix(key, "file:")
|
||||
privKeyPassFilePath, _ = resolveDir(privKeyPassFilePath)
|
||||
keyFile, keyFileErr := os.Open(privKeyPassFilePath)
|
||||
if keyFileErr != nil {
|
||||
return "", errors.Errorf("Private key password file %s failed to open. \n Make sure it is accessible and correct.", privKeyPassFilePath)
|
||||
}
|
||||
passwordScanner := bufio.NewScanner(keyFile)
|
||||
for passwordScanner.Scan() {
|
||||
prKeyPassword = passwordScanner.Text()
|
||||
}
|
||||
} else if strings.HasPrefix(key, "env:") {
|
||||
privKey := strings.TrimPrefix(key, "env:")
|
||||
privKey = strings.TrimPrefix(privKey, "${")
|
||||
privKey = strings.TrimSuffix(privKey, "}")
|
||||
privKey = strings.TrimPrefix(privKey, "$")
|
||||
prKeyPassword = os.Getenv(privKey)
|
||||
} else {
|
||||
prKeyPassword = key
|
||||
}
|
||||
prKeyPassword = GetVaultKey(prKeyPassword, opts, opts.Logger)
|
||||
return prKeyPassword, nil
|
||||
func GetPrivateKeyPassword(key string, opts *ConfigOpts) string {
|
||||
return getExternalConfigDirectiveValue(key, opts, AllowedExternalDirectiveAll)
|
||||
}
|
||||
|
||||
// GetPassword gets any password
|
||||
func GetPassword(pass string, opts *ConfigOpts, log zerolog.Logger) (string, error) {
|
||||
|
||||
pass = strings.TrimSpace(pass)
|
||||
if pass == "" {
|
||||
return "", nil
|
||||
}
|
||||
var password string
|
||||
if strings.HasPrefix(pass, "file:") {
|
||||
passFilePath := strings.TrimPrefix(pass, "file:")
|
||||
passFilePath, _ = resolveDir(passFilePath)
|
||||
keyFile, keyFileErr := os.Open(passFilePath)
|
||||
if keyFileErr != nil {
|
||||
return "", errors.New("Password file failed to open")
|
||||
}
|
||||
passwordScanner := bufio.NewScanner(keyFile)
|
||||
for passwordScanner.Scan() {
|
||||
password = passwordScanner.Text()
|
||||
}
|
||||
} else if strings.HasPrefix(pass, "env:") {
|
||||
passEnv := strings.TrimPrefix(pass, "env:")
|
||||
passEnv = strings.TrimPrefix(passEnv, "${")
|
||||
passEnv = strings.TrimSuffix(passEnv, "}")
|
||||
passEnv = strings.TrimPrefix(passEnv, "$")
|
||||
password = os.Getenv(passEnv)
|
||||
} else {
|
||||
password = pass
|
||||
}
|
||||
password = GetVaultKey(password, opts, opts.Logger)
|
||||
|
||||
return password, nil
|
||||
func GetPassword(pass string, opts *ConfigOpts) string {
|
||||
return getExternalConfigDirectiveValue(pass, opts, AllowedExternalDirectiveAll)
|
||||
}
|
||||
|
||||
func (remoteConfig *Host) GetProxyJumpFromConfig(hosts map[string]*Host) error {
|
||||
func (remoteHost *Host) GetProxyJumpFromConfig(hosts map[string]*Host) error {
|
||||
|
||||
proxyJump, _ := remoteConfig.SSHConfigFile.SshConfigFile.Get(remoteConfig.Host, "ProxyJump")
|
||||
proxyJump, _ := remoteHost.SSHConfigFile.SshConfigFile.Get(remoteHost.Host, "ProxyJump")
|
||||
if proxyJump == "" {
|
||||
proxyJump = remoteConfig.SSHConfigFile.DefaultUserSettings.Get(remoteConfig.Host, "ProxyJump")
|
||||
proxyJump = remoteHost.SSHConfigFile.DefaultUserSettings.Get(remoteHost.Host, "ProxyJump")
|
||||
}
|
||||
if remoteConfig.ProxyJump == "" && proxyJump != "" {
|
||||
remoteConfig.ProxyJump = proxyJump
|
||||
if remoteHost.ProxyJump == "" && proxyJump != "" {
|
||||
remoteHost.ProxyJump = proxyJump
|
||||
}
|
||||
proxyJumpHosts := strings.Split(remoteConfig.ProxyJump, ",")
|
||||
if remoteConfig.ProxyHost == nil && len(proxyJumpHosts) == 1 {
|
||||
remoteConfig.ProxyJump = proxyJump
|
||||
proxyJumpHosts := strings.Split(remoteHost.ProxyJump, ",")
|
||||
if remoteHost.ProxyHost == nil && len(proxyJumpHosts) == 1 {
|
||||
remoteHost.ProxyJump = proxyJump
|
||||
proxyHost, proxyHostFound := hosts[proxyJump]
|
||||
if proxyHostFound {
|
||||
remoteConfig.ProxyHost = append(remoteConfig.ProxyHost, proxyHost)
|
||||
remoteHost.ProxyHost = append(remoteHost.ProxyHost, proxyHost)
|
||||
} else {
|
||||
if proxyJump != "" {
|
||||
newProxy := &Host{Host: proxyJump}
|
||||
remoteConfig.ProxyHost = append(remoteConfig.ProxyHost, newProxy)
|
||||
remoteHost.ProxyHost = append(remoteHost.ProxyHost, newProxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -419,69 +363,68 @@ func (remoteConfig *Host) GetProxyJumpFromConfig(hosts map[string]*Host) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (remoteConfig *Host) GetProxyJumpConfig(hosts map[string]*Host, opts *ConfigOpts) error {
|
||||
func (remoteHost *Host) GetProxyJumpConfig(hosts map[string]*Host, opts *ConfigOpts) error {
|
||||
|
||||
if TS(remoteConfig.ConfigFilePath) == "" {
|
||||
remoteConfig.useDefaultConfig = true
|
||||
if TS(remoteHost.ConfigFilePath) == "" {
|
||||
remoteHost.useDefaultConfig = true
|
||||
}
|
||||
|
||||
khPathErr := remoteConfig.GetKnownHosts()
|
||||
khPathErr := remoteHost.GetKnownHosts()
|
||||
|
||||
if khPathErr != nil {
|
||||
return khPathErr
|
||||
}
|
||||
if remoteConfig.ClientConfig == nil {
|
||||
remoteConfig.ClientConfig = &ssh.ClientConfig{}
|
||||
if remoteHost.ClientConfig == nil {
|
||||
remoteHost.ClientConfig = &ssh.ClientConfig{}
|
||||
}
|
||||
var configFile *os.File
|
||||
var sshConfigFileOpenErr error
|
||||
if !remoteConfig.useDefaultConfig {
|
||||
if !remoteHost.useDefaultConfig {
|
||||
|
||||
configFile, sshConfigFileOpenErr = os.Open(remoteConfig.ConfigFilePath)
|
||||
configFile, sshConfigFileOpenErr = os.Open(remoteHost.ConfigFilePath)
|
||||
if sshConfigFileOpenErr != nil {
|
||||
return sshConfigFileOpenErr
|
||||
}
|
||||
} else {
|
||||
defaultConfig, _ := resolveDir("~/.ssh/config")
|
||||
defaultConfig, _ := getFullPathWithHomeDir("~/.ssh/config")
|
||||
configFile, sshConfigFileOpenErr = os.Open(defaultConfig)
|
||||
if sshConfigFileOpenErr != nil {
|
||||
return sshConfigFileOpenErr
|
||||
}
|
||||
}
|
||||
remoteConfig.SSHConfigFile = &sshConfigFile{}
|
||||
remoteConfig.SSHConfigFile.DefaultUserSettings = ssh_config.DefaultUserSettings
|
||||
remoteHost.SSHConfigFile = &sshConfigFile{}
|
||||
remoteHost.SSHConfigFile.DefaultUserSettings = ssh_config.DefaultUserSettings
|
||||
var decodeErr error
|
||||
remoteConfig.SSHConfigFile.SshConfigFile, decodeErr = ssh_config.Decode(configFile)
|
||||
remoteHost.SSHConfigFile.SshConfigFile, decodeErr = ssh_config.Decode(configFile)
|
||||
if decodeErr != nil {
|
||||
return decodeErr
|
||||
}
|
||||
remoteConfig.GetPrivateKeyFileFromConfig()
|
||||
remoteConfig.GetPort()
|
||||
remoteConfig.GetHostName()
|
||||
remoteConfig.CombineHostNameWithPort()
|
||||
remoteConfig.GetSshUserFromConfig()
|
||||
remoteConfig.isProxyHost = true
|
||||
if remoteConfig.HostName == "" {
|
||||
return errors.Errorf("No hostname found or specified for host %s", remoteConfig.Host)
|
||||
remoteHost.GetPrivateKeyFileFromConfig()
|
||||
remoteHost.GetPort()
|
||||
remoteHost.GetHostName()
|
||||
remoteHost.CombineHostNameWithPort()
|
||||
remoteHost.GetSshUserFromConfig()
|
||||
remoteHost.isProxyHost = true
|
||||
if remoteHost.HostName == "" {
|
||||
return errors.Errorf("No hostname found or specified for host %s", remoteHost.Host)
|
||||
}
|
||||
err := remoteConfig.GetAuthMethods(opts)
|
||||
err := remoteHost.GetAuthMethods(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Add value/option to config for host key and add bool to check for host key
|
||||
hostKeyCallback, err := knownhosts.New(remoteConfig.KnownHostsFile)
|
||||
hostKeyCallback, err := knownhosts.New(remoteHost.KnownHostsFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create hostkeycallback function: %v", err)
|
||||
}
|
||||
remoteConfig.ClientConfig.HostKeyCallback = hostKeyCallback
|
||||
hosts[remoteConfig.Host] = remoteConfig
|
||||
remoteHost.ClientConfig.HostKeyCallback = hostKeyCallback
|
||||
hosts[remoteHost.Host] = remoteHost
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunCmdSSH runs commands over SSH.
|
||||
func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([]string, error) {
|
||||
func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([]string, error) {
|
||||
var (
|
||||
ArgsStr string
|
||||
cmdOutBuf bytes.Buffer
|
||||
@ -492,9 +435,7 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
|
||||
env: command.Environment,
|
||||
}
|
||||
)
|
||||
|
||||
command.Type = strings.TrimSpace(command.Type)
|
||||
command = getPackageCommand(command)
|
||||
command = getCommandTypeAndSetCommandInfo(command)
|
||||
|
||||
// Prepare command arguments
|
||||
for _, v := range command.Args {
|
||||
@ -503,10 +444,10 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
|
||||
|
||||
cmdCtxLogger.Info().
|
||||
Str("Command", command.Name).
|
||||
Str("Host", *command.Host).
|
||||
Msgf("Running %s on host %s", getCommandTypeLabel(command.Type), *command.Host)
|
||||
Str("Host", command.Host).
|
||||
Msgf("Running %s on host %s", getCommandTypeAndSetCommandInfoLabel(command.Type), command.Host)
|
||||
|
||||
cmdCtxLogger.Debug().Str("cmd", command.Cmd).Strs("args", command.Args).Send()
|
||||
// cmdCtxLogger.Debug().Str("cmd", command.Cmd).Strs("args", command.Args).Send()
|
||||
|
||||
// Ensure SSH client is connected
|
||||
if command.RemoteHost.SshClient == nil {
|
||||
@ -516,7 +457,7 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
|
||||
}
|
||||
|
||||
// Create new SSH session
|
||||
commandSession, err := command.createSSHSession(opts)
|
||||
commandSession, err := command.RemoteHost.createSSHSession(opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create SSH session: %w", err)
|
||||
}
|
||||
@ -535,10 +476,33 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
|
||||
|
||||
// Handle command execution based on type
|
||||
switch command.Type {
|
||||
case "script":
|
||||
case ScriptCommandType:
|
||||
return command.runScript(commandSession, cmdCtxLogger, &cmdOutBuf)
|
||||
case "scriptFile":
|
||||
case RemoteScriptCommandType:
|
||||
return command.runRemoteScript(commandSession, cmdCtxLogger, &cmdOutBuf)
|
||||
case ScriptFileCommandType:
|
||||
return command.runScriptFile(commandSession, cmdCtxLogger, &cmdOutBuf)
|
||||
case PackageCommandType:
|
||||
if command.PackageOperation == PackageOperationCheckVersion {
|
||||
commandSession.Stderr = nil
|
||||
// Execute the package version command remotely
|
||||
// Parse the output of package version command
|
||||
// Compare versions
|
||||
// Check if a specific version is specified
|
||||
commandSession.Stdout = nil
|
||||
return checkPackageVersion(cmdCtxLogger, command, commandSession, cmdOutBuf)
|
||||
} else {
|
||||
if command.Shell != "" {
|
||||
ArgsStr = fmt.Sprintf("%s -c '%s %s'", command.Shell, command.Cmd, ArgsStr)
|
||||
} else {
|
||||
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
||||
}
|
||||
cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send()
|
||||
// Run simple command
|
||||
if err := commandSession.Run(ArgsStr); err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running command: %w", err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
if command.Shell != "" {
|
||||
ArgsStr = fmt.Sprintf("%s -c '%s %s'", command.Shell, command.Cmd, ArgsStr)
|
||||
@ -546,37 +510,138 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
|
||||
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
||||
}
|
||||
cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send()
|
||||
// Run simple command
|
||||
|
||||
if command.Type == UserCommandType && command.UserOperation == "password" {
|
||||
// cmdCtxLogger.Debug().Msgf("adding stdin")
|
||||
|
||||
userNamePass := fmt.Sprintf("%s:%s", command.Username, command.UserPassword)
|
||||
client, err := sftp.NewClient(command.RemoteHost.SshClient)
|
||||
if err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating sftp client: %v", err)
|
||||
}
|
||||
uuidFile := uuid.New()
|
||||
passFilePath := fmt.Sprintf("/tmp/%s", uuidFile.String())
|
||||
passFile, passFileErr := client.Create(passFilePath)
|
||||
if passFileErr != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating file /tmp/%s: %v", uuidFile.String(), passFileErr)
|
||||
}
|
||||
|
||||
_, err = passFile.Write([]byte(userNamePass))
|
||||
if err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error writing to file /tmp/%s: %v", uuidFile.String(), err)
|
||||
}
|
||||
|
||||
ArgsStr = fmt.Sprintf("cat %s | chpasswd", passFilePath)
|
||||
defer passFile.Close()
|
||||
|
||||
rmFileFunc := func() {
|
||||
_ = client.Remove(passFilePath)
|
||||
}
|
||||
|
||||
defer rmFileFunc()
|
||||
// commandSession.Stdin = command.stdin
|
||||
}
|
||||
if err := commandSession.Run(ArgsStr); err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger), fmt.Errorf("error running command: %w", err)
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running command: %w", err)
|
||||
}
|
||||
|
||||
if command.Type == UserCommandType {
|
||||
|
||||
// REFACTOR IF/WHEN WINDOWS SUPPORT IS ADDED
|
||||
|
||||
if command.UserOperation == "add" {
|
||||
if command.UserSshPubKeys != nil {
|
||||
var (
|
||||
f *sftp.File
|
||||
err error
|
||||
userHome []byte
|
||||
client *sftp.Client
|
||||
)
|
||||
|
||||
cmdCtxLogger.Info().Msg("adding SSH Keys")
|
||||
|
||||
commandSession, _ = command.RemoteHost.createSSHSession(opts)
|
||||
userHome, err = commandSession.CombinedOutput(fmt.Sprintf("grep \"%s\" /etc/passwd | cut -d: -f6", command.Username))
|
||||
if err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error finding user home from /etc/passwd: %v", err)
|
||||
}
|
||||
|
||||
command.UserHome = strings.TrimSpace(string(userHome))
|
||||
userSshDir := fmt.Sprintf("%s/.ssh", command.UserHome)
|
||||
client, err = sftp.NewClient(command.RemoteHost.SshClient)
|
||||
if err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating sftp client: %v", err)
|
||||
}
|
||||
|
||||
err = client.MkdirAll(userSshDir)
|
||||
if err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating directory %s: %v", userSshDir, err)
|
||||
}
|
||||
_, err = client.Create(fmt.Sprintf("%s/authorized_keys", userSshDir))
|
||||
if err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)
|
||||
}
|
||||
f, err = client.OpenFile(fmt.Sprintf("%s/authorized_keys", userSshDir), os.O_APPEND|os.O_CREATE|os.O_WRONLY)
|
||||
if err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)
|
||||
}
|
||||
defer f.Close()
|
||||
for _, k := range command.UserSshPubKeys {
|
||||
buf := bytes.NewBufferString(k)
|
||||
cmdCtxLogger.Info().Str("key", k).Msg("adding SSH key")
|
||||
if _, err := f.ReadFrom(buf); err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error adding to authorized keys: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
commandSession, _ = command.RemoteHost.createSSHSession(opts)
|
||||
_, err = commandSession.CombinedOutput(fmt.Sprintf("chown -R %s:%s %s", command.Username, command.Username, userHome))
|
||||
if err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger), nil
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil
|
||||
}
|
||||
|
||||
// getCommandTypeLabel returns a human-readable label for the command type.
|
||||
func getCommandTypeLabel(commandType string) string {
|
||||
if commandType == "" {
|
||||
func checkPackageVersion(cmdCtxLogger zerolog.Logger, command *Command, commandSession *ssh.Session, cmdOutBuf bytes.Buffer) ([]string, error) {
|
||||
for _, p := range command.Packages {
|
||||
cmdCtxLogger.Info().Str("package", p.Name).Msg("Checking package versions")
|
||||
}
|
||||
// Prepare command arguments
|
||||
ArgsStr := command.Cmd
|
||||
for _, v := range command.Args {
|
||||
ArgsStr += fmt.Sprintf(" %s", v)
|
||||
}
|
||||
|
||||
var err error
|
||||
var cmdOut []byte
|
||||
|
||||
if cmdOut, err = commandSession.CombinedOutput(ArgsStr); err != nil {
|
||||
cmdOutBuf.Write(cmdOut)
|
||||
|
||||
_, parseErr := parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf)
|
||||
if parseErr != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error: packages %v not listed: %w", command.Packages, err)
|
||||
}
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running %s: %w", ArgsStr, err)
|
||||
}
|
||||
|
||||
return parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf)
|
||||
}
|
||||
|
||||
// getCommandTypeAndSetCommandInfoLabel returns a human-readable label for the command type.
|
||||
func getCommandTypeAndSetCommandInfoLabel(commandType CommandType) string {
|
||||
if !commandType.IsACommandType() {
|
||||
return "command"
|
||||
}
|
||||
return fmt.Sprintf("%s command", commandType)
|
||||
}
|
||||
|
||||
// createSSHSession attempts to create a new SSH session and retries on failure.
|
||||
func (command *Command) createSSHSession(opts *ConfigOpts) (*ssh.Session, error) {
|
||||
session, err := command.RemoteHost.SshClient.NewSession()
|
||||
if err == nil {
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// Retry connection and session creation
|
||||
if connErr := command.RemoteHost.ConnectToHost(opts); connErr != nil {
|
||||
return nil, fmt.Errorf("session creation failed: %v, connection retry failed: %v", err, connErr)
|
||||
}
|
||||
return command.RemoteHost.SshClient.NewSession()
|
||||
}
|
||||
|
||||
// runScript handles the execution of inline scripts.
|
||||
func (command *Command) runScript(session *ssh.Session, cmdCtxLogger zerolog.Logger, outputBuf *bytes.Buffer) ([]string, error) {
|
||||
script, err := command.prepareScriptBuffer()
|
||||
@ -590,10 +655,10 @@ func (command *Command) runScript(session *ssh.Session, cmdCtxLogger zerolog.Log
|
||||
}
|
||||
|
||||
if err := session.Wait(); err != nil {
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger), fmt.Errorf("error waiting for shell: %w", err)
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger, true), fmt.Errorf("error waiting for shell: %w", err)
|
||||
}
|
||||
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger), nil
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil
|
||||
}
|
||||
|
||||
// runScriptFile handles the execution of script files.
|
||||
@ -609,10 +674,10 @@ func (command *Command) runScriptFile(session *ssh.Session, cmdCtxLogger zerolog
|
||||
}
|
||||
|
||||
if err := session.Wait(); err != nil {
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger), fmt.Errorf("error waiting for shell: %w", err)
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger, true), fmt.Errorf("error waiting for shell: %w", err)
|
||||
}
|
||||
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger), nil
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil
|
||||
}
|
||||
|
||||
// prepareScriptBuffer prepares a buffer for inline scripts.
|
||||
@ -656,9 +721,28 @@ func (command *Command) prepareScriptFileBuffer() (*bytes.Buffer, error) {
|
||||
return &buffer, nil
|
||||
}
|
||||
|
||||
// runRemoteScript handles the execution of remote scripts
|
||||
func (command *Command) runRemoteScript(session *ssh.Session, cmdCtxLogger zerolog.Logger, outputBuf *bytes.Buffer) ([]string, error) {
|
||||
script, err := command.Fetcher.Fetch(command.Cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if command.Shell == "" {
|
||||
command.Shell = "sh"
|
||||
}
|
||||
session.Stdin = bytes.NewReader(script)
|
||||
err = session.Run(command.Shell)
|
||||
|
||||
if err != nil {
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running remote script: %w", err)
|
||||
}
|
||||
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil
|
||||
}
|
||||
|
||||
// readFileToBuffer reads a file into a buffer.
|
||||
func readFileToBuffer(filePath string) (*bytes.Buffer, error) {
|
||||
resolvedPath, err := resolveDir(filePath)
|
||||
resolvedPath, err := getFullPathWithHomeDir(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -677,13 +761,65 @@ func readFileToBuffer(filePath string) (*bytes.Buffer, error) {
|
||||
}
|
||||
|
||||
// collectOutput collects output from a buffer and logs it.
|
||||
func collectOutput(buf *bytes.Buffer, commandName string, logger zerolog.Logger) []string {
|
||||
func collectOutput(buf *bytes.Buffer, commandName string, logger zerolog.Logger, wantOutput bool) []string {
|
||||
var outputArr []string
|
||||
scanner := bufio.NewScanner(buf)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
outputArr = append(outputArr, line)
|
||||
logger.Info().Str("cmd", commandName).Str("output", line).Send()
|
||||
if wantOutput {
|
||||
logger.Info().Str("cmd", commandName).Str("output", line).Send()
|
||||
}
|
||||
}
|
||||
return outputArr
|
||||
}
|
||||
|
||||
// createSSHSession attempts to create a new SSH session and retries on failure.
|
||||
func (h *Host) createSSHSession(opts *ConfigOpts) (*ssh.Session, error) {
|
||||
session, err := h.SshClient.NewSession()
|
||||
if err == nil {
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// Retry connection and session creation
|
||||
if connErr := h.ConnectToHost(opts); connErr != nil {
|
||||
return nil, fmt.Errorf("session creation failed: %v, connection retry failed: %v", err, connErr)
|
||||
}
|
||||
return h.SshClient.NewSession()
|
||||
}
|
||||
|
||||
func (h *Host) DetectOS(opts *ConfigOpts) (string, error) {
|
||||
err := h.ConnectToHost(opts)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var session *ssh.Session
|
||||
session, err = h.createSSHSession(opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Execute the "uname -a" command on the remote machine
|
||||
output, err := session.CombinedOutput("uname")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to execute OS detection command: %v", err)
|
||||
}
|
||||
|
||||
// Parse the output to determine the OS
|
||||
osName := string(output)
|
||||
return osName, nil
|
||||
}
|
||||
|
||||
func DoesHostHaveHostName(host string) (bool, string) {
|
||||
HostName, err := ssh_config.DefaultUserSettings.GetStrict(host, "HostName")
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
println(HostName)
|
||||
return HostName != "", HostName
|
||||
}
|
||||
|
||||
func IsHostLocal(host string) bool {
|
||||
host = strings.ToLower(host)
|
||||
return host == "127.0.0.1" || host == "localhost" || host == ""
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
Command list {{.listName }} failed.
|
||||
|
||||
The command run was {{.Cmd}}.
|
||||
The command run was {{.CmdName}}.
|
||||
|
||||
The command executed was {{.Command}} {{ if .Args }} {{- range .Args}} {{.}} {{end}} {{end}}
|
||||
|
||||
{{ if .Err }} The error was {{ .Err }}{{ end }}
|
||||
|
||||
{{ if .Output }} The output was {{- range .Output}} {{.}} {{end}} {{end}}
|
||||
{{ if .Output }} The output was: {{- range .Output}} {{.}} {{end}} {{end}}
|
||||
|
||||
{{ if .CmdsRan }}
|
||||
The following commands ran:
|
||||
@ -15,7 +15,7 @@ The following commands ran:
|
||||
{{end}}
|
||||
{{ end }}
|
||||
|
||||
{{ if .CmdOutput }}{{- range .CmdOutput }}Command output for {{ .CmdName }}:
|
||||
{{ if .CmdOutput }}{{- range .CmdOutput }}{{ printf "\n"}}Command output for {{ .CmdName }}:
|
||||
{{- range .Output}}
|
||||
{{ . }}
|
||||
{{ end }}{{ end }}
|
||||
|
@ -5,7 +5,7 @@ The following commands ran:
|
||||
- {{. -}}
|
||||
{{end}}
|
||||
|
||||
{{ if .CmdOutput }}{{- range .CmdOutput }}Command output for {{ .CmdName }}:
|
||||
{{ if .CmdOutput }}{{- range .CmdOutput }}{{ printf "\n"}}Command output for {{ .CmdName }}:
|
||||
{{- range .Output}}
|
||||
{{ . }}
|
||||
{{ end }}{{ end }}
|
||||
|
8
pkg/backy/tools.go
Normal file
8
pkg/backy/tools.go
Normal file
@ -0,0 +1,8 @@
|
||||
//go:build tools
|
||||
|
||||
package backy
|
||||
|
||||
import (
|
||||
// Protect this entry in go.mod from being removed by go mod tidy.
|
||||
_ "github.com/dmarkham/enumer"
|
||||
)
|
@ -4,7 +4,12 @@ import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
|
||||
"strings"
|
||||
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman"
|
||||
packagemanagercommon "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/common"
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/remotefetcher"
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/usermanager"
|
||||
vaultapi "github.com/hashicorp/vault/api"
|
||||
"github.com/kevinburke/ssh_config"
|
||||
"github.com/knadh/koanf/v2"
|
||||
@ -18,18 +23,19 @@ type (
|
||||
// Host defines a host to which to connect.
|
||||
// If not provided, the values will be looked up in the default ssh config files
|
||||
Host struct {
|
||||
OS string `yaml:"OS,omitempty"`
|
||||
ConfigFilePath string `yaml:"config,omitempty"`
|
||||
Host string `yaml:"host,omitempty"`
|
||||
HostName string `yaml:"hostname,omitempty"`
|
||||
KnownHostsFile string `yaml:"knownhostsfile,omitempty"`
|
||||
KnownHostsFile string `yaml:"knownHostsFile,omitempty"`
|
||||
ClientConfig *ssh.ClientConfig
|
||||
SSHConfigFile *sshConfigFile
|
||||
SshClient *ssh.Client
|
||||
Port uint16 `yaml:"port,omitempty"`
|
||||
ProxyJump string `yaml:"proxyjump,omitempty"`
|
||||
Password string `yaml:"password,omitempty"`
|
||||
PrivateKeyPath string `yaml:"privatekeypath,omitempty"`
|
||||
PrivateKeyPassword string `yaml:"privatekeypassword,omitempty"`
|
||||
PrivateKeyPath string `yaml:"IdentityFile,omitempty"`
|
||||
PrivateKeyPassword string `yaml:"privateKeyPassword,omitempty"`
|
||||
useDefaultConfig bool
|
||||
User string `yaml:"user,omitempty"`
|
||||
isProxyHost bool
|
||||
@ -46,93 +52,96 @@ type (
|
||||
Command struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
|
||||
// command to run
|
||||
Cmd string `yaml:"cmd"`
|
||||
|
||||
// Possible values: script, scriptFile
|
||||
// If blank, it is regular command.
|
||||
Type string `yaml:"type,omitempty"`
|
||||
// See CommandType enum further down the page for acceptable values
|
||||
Type CommandType `yaml:"type,omitempty"`
|
||||
|
||||
// host on which to run cmd
|
||||
Host *string `yaml:"host,omitempty"`
|
||||
Host string `yaml:"host,omitempty"`
|
||||
|
||||
// Hooks are for running commands on certain events
|
||||
Hooks *Hooks `yaml:"hooks,omitempty"`
|
||||
|
||||
// hook refs are internal references of commands for each hook type
|
||||
hookRefs map[string]map[string]*Command
|
||||
|
||||
/*
|
||||
Shell specifies which shell to run the command in, if any.
|
||||
Not applicable when host is defined.
|
||||
*/
|
||||
Shell string `yaml:"shell,omitempty"`
|
||||
|
||||
RemoteHost *Host `yaml:"-"`
|
||||
|
||||
// Args is an array that holds the arguments to cmd
|
||||
Args []string `yaml:"args,omitempty"`
|
||||
|
||||
/*
|
||||
Dir specifies a directory in which to run the command.
|
||||
Ignored if Host is set.
|
||||
*/
|
||||
Dir *string `yaml:"dir,omitempty"`
|
||||
|
||||
// Env points to a file containing env variables to be used with the command
|
||||
Env string `yaml:"env,omitempty"`
|
||||
|
||||
// Environment holds env variables to be used with the command
|
||||
Environment []string `yaml:"environment,omitempty"`
|
||||
|
||||
// Output determines if output is requested.
|
||||
// Only works if command is in a list.
|
||||
GetOutput bool `yaml:"getOutput,omitempty"`
|
||||
|
||||
ScriptEnvFile string `yaml:"scriptEnvFile"`
|
||||
|
||||
Output struct {
|
||||
File string `yaml:"file,omitempty"`
|
||||
ToLog bool `yaml:"toLog,omitempty"`
|
||||
InList bool `yaml:"inList,omitempty"`
|
||||
} `yaml:"output"`
|
||||
|
||||
// BEGIN PACKAGE COMMAND FIELDS
|
||||
|
||||
PackageManager string `yaml:"packageManager,omitempty"`
|
||||
|
||||
PackageName string `yaml:"packageName,omitempty"`
|
||||
Packages []packagemanagercommon.Package `yaml:"packages,omitempty"`
|
||||
|
||||
// Version specifies the desired version for package execution
|
||||
PackageVersion string `yaml:"packageVersion,omitempty"`
|
||||
|
||||
// PackageOperation specifies the action for package-related commands (e.g., "install" or "remove")
|
||||
PackageOperation string `yaml:"packageOperation,omitempty"`
|
||||
PackageOperation PackageOperation `yaml:"packageOperation,omitempty"`
|
||||
|
||||
pkgMan pkgman.PackageManager
|
||||
|
||||
packageCmdSet bool
|
||||
// END PACKAGE COMMAND FIELDS
|
||||
|
||||
// RemoteSource specifies a URL to fetch the command or configuration remotely
|
||||
RemoteSource string `yaml:"remoteSource,omitempty"`
|
||||
|
||||
// FetchBeforeExecution determines if the remoteSource should be fetched before running
|
||||
FetchBeforeExecution bool `yaml:"fetchBeforeExecution,omitempty"`
|
||||
|
||||
// Username specifies the username for user creation or related operations
|
||||
Username string `yaml:"username,omitempty"`
|
||||
Fetcher remotefetcher.RemoteFetcher
|
||||
|
||||
// Groups specifies the groups to add the user to
|
||||
Groups []string `yaml:"groups,omitempty"`
|
||||
// BEGIN USER COMMAND FIELDS
|
||||
|
||||
// Home specifies the home directory for the user
|
||||
Home string `yaml:"home,omitempty"`
|
||||
Username string `yaml:"userName,omitempty"`
|
||||
|
||||
// System specifies whether the user is a system account
|
||||
System bool `yaml:"system,omitempty"`
|
||||
UserID string `yaml:"userID,omitempty"`
|
||||
|
||||
// Password specifies the password for the user (can be file: or plain text)
|
||||
Password string `yaml:"password,omitempty"`
|
||||
UserGroups []string `yaml:"userGroups,omitempty"`
|
||||
|
||||
// Operation specifies the action for user-related commands (e.g., "create" or "remove")
|
||||
Operation string `yaml:"operation,omitempty"`
|
||||
UserHome string `yaml:"userHome,omitempty"`
|
||||
|
||||
UserShell string `yaml:"userShell,omitempty"`
|
||||
|
||||
UserCreateHome bool `yaml:"userCreateHome,omitempty"`
|
||||
|
||||
UserIsSystem bool `yaml:"userIsSystem,omitempty"`
|
||||
|
||||
UserPassword string `yaml:"userPassword,omitempty"`
|
||||
|
||||
UserSshPubKeys []string `yaml:"userSshPubKeys,omitempty"`
|
||||
|
||||
userMan usermanager.UserManager
|
||||
|
||||
// OS for the command, only used when type is user
|
||||
OS string `yaml:"OS,omitempty"`
|
||||
|
||||
UserOperation string `yaml:"userOperation,omitempty"`
|
||||
|
||||
userCmdSet bool
|
||||
|
||||
// stdin only for userOperation = password (for now)
|
||||
stdin *strings.Reader
|
||||
|
||||
// END USER STRUCommandType FIELDS
|
||||
}
|
||||
|
||||
RemoteSource struct {
|
||||
URL string `yaml:"url"`
|
||||
Type string `yaml:"type"` // e.g., yaml
|
||||
Type string `yaml:"type"` // e.g., s3, http
|
||||
Auth struct {
|
||||
AccessKey string `yaml:"accessKey"`
|
||||
SecretKey string `yaml:"secretKey"`
|
||||
@ -142,13 +151,17 @@ type (
|
||||
BackyOptionFunc func(*ConfigOpts)
|
||||
|
||||
CmdList struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Cron string `yaml:"cron,omitempty"`
|
||||
RunCmdOnFailure string `yaml:"runCmdOnFailure,omitempty"`
|
||||
Order []string `yaml:"order,omitempty"`
|
||||
Notifications []string `yaml:"notifications,omitempty"`
|
||||
GetOutput bool `yaml:"getOutput,omitempty"`
|
||||
NotifyOnSuccess bool `yaml:"notifyOnSuccess,omitempty"`
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Cron string `yaml:"cron,omitempty"`
|
||||
RunCmdOnFailure string `yaml:"runCmdOnFailure,omitempty"`
|
||||
Order []string `yaml:"order,omitempty"`
|
||||
Notifications []string `yaml:"notifications,omitempty"`
|
||||
GetCommandOutputInNotificationsOnSuccess bool `yaml:"sendNotificationOnSuccess,omitempty"`
|
||||
|
||||
Notify struct {
|
||||
OnFailure bool `yaml:"onFailure,omitempty"`
|
||||
OnSuccess bool `yaml:"onSuccess,omitempty"`
|
||||
} `yaml:"notify,omitempty"`
|
||||
|
||||
NotifyConfig *notify.Notify
|
||||
Source string `yaml:"source"` // URL to fetch remote commands
|
||||
@ -162,7 +175,7 @@ type (
|
||||
|
||||
// CmdConfigLists holds the lists of commands to be run in order.
|
||||
// Key is the command list name.
|
||||
CmdConfigLists map[string]*CmdList `yaml:"cmd-lists"`
|
||||
CmdConfigLists map[string]*CmdList `yaml:"cmdLists"`
|
||||
|
||||
// Hosts holds the Host config.
|
||||
// key is the host.
|
||||
@ -173,10 +186,16 @@ type (
|
||||
// Global log level
|
||||
BackyLogLvl *string
|
||||
|
||||
// Holds config file
|
||||
CmdStdOut bool
|
||||
|
||||
ConfigFilePath string
|
||||
|
||||
// for command list file
|
||||
HostsFilePath string
|
||||
|
||||
ConfigDir string
|
||||
|
||||
LogFilePath string
|
||||
|
||||
CmdListFile string
|
||||
|
||||
// use command lists using cron
|
||||
@ -193,11 +212,16 @@ type (
|
||||
|
||||
List ListConfig
|
||||
|
||||
Vars map[string]string `yaml:"variables"`
|
||||
|
||||
VaultKeys []*VaultKey `yaml:"keys"`
|
||||
|
||||
koanf *koanf.Koanf
|
||||
|
||||
NotificationConf *Notifications `yaml:"notifications"`
|
||||
|
||||
Cache *remotefetcher.Cache
|
||||
CachedData []*remotefetcher.CacheData
|
||||
}
|
||||
|
||||
outStruct struct {
|
||||
@ -208,6 +232,7 @@ type (
|
||||
|
||||
VaultKey struct {
|
||||
Name string `yaml:"name"`
|
||||
Key string `yaml:"key"`
|
||||
Path string `yaml:"path"`
|
||||
ValueType string `yaml:"type"`
|
||||
MountPath string `yaml:"mountpath"`
|
||||
@ -223,6 +248,7 @@ type (
|
||||
Notifications struct {
|
||||
MailConfig map[string]MailConfig `yaml:"mail,omitempty"`
|
||||
MatrixConfig map[string]MatrixStruct `yaml:"matrix,omitempty"`
|
||||
HttpConfig map[string]HttpConfig `yaml:"http,omitempty"`
|
||||
}
|
||||
|
||||
CmdOutput struct {
|
||||
@ -252,10 +278,61 @@ type (
|
||||
Final []string `yaml:"final,omitempty"`
|
||||
}
|
||||
|
||||
CmdListResults struct {
|
||||
// name of the list
|
||||
ListName string
|
||||
// command that caused the list to fail
|
||||
ErrCmd string
|
||||
CmdResult struct {
|
||||
CmdName string // Name of the command executed
|
||||
ListName string // Name of the command list
|
||||
Error error // Error encountered, if any
|
||||
}
|
||||
|
||||
ListMetrics struct {
|
||||
Name string
|
||||
SuccessfulExecutions uint64
|
||||
FailedExecutions uint64
|
||||
TotalExecutions uint64
|
||||
}
|
||||
|
||||
CommandMetrics struct {
|
||||
Name string
|
||||
SuccessfulExecutions uint64
|
||||
FailedExecutions uint64
|
||||
TotalExecutions uint64
|
||||
}
|
||||
|
||||
// use ints so we can use enums
|
||||
CommandType int
|
||||
PackageOperation int
|
||||
AllowedExternalDirectives int
|
||||
)
|
||||
|
||||
//go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=CommandType
|
||||
const (
|
||||
DefaultCommandType CommandType = iota //
|
||||
ScriptCommandType // script
|
||||
ScriptFileCommandType // scriptFile
|
||||
RemoteScriptCommandType // remoteScript
|
||||
PackageCommandType // package
|
||||
UserCommandType // user
|
||||
)
|
||||
|
||||
//go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=PackageOperation
|
||||
const (
|
||||
DefaultPO PackageOperation = iota //
|
||||
PackageOperationInstall // install
|
||||
PackageOperationUpgrade // upgrade
|
||||
PackageOperationPurge // purge
|
||||
PackageOperationRemove // remove
|
||||
PackageOperationCheckVersion // checkVersion
|
||||
PackageOperationIsInstalled // isInstalled
|
||||
)
|
||||
|
||||
//go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=AllowedExternalDirectives
|
||||
const (
|
||||
DefaultExternalDir AllowedExternalDirectives = iota
|
||||
AllowedExternalDirectiveVault // vault
|
||||
AllowedExternalDirectiveVaultEnv // vault-env
|
||||
AllowedExternalDirectiveVaultFile // vault-file
|
||||
AllowedExternalDirectiveAll // vault-file-env
|
||||
AllowedExternalDirectiveFileEnv // file-env
|
||||
AllowedExternalDirectiveFile // file
|
||||
AllowedExternalDirectiveEnv // env
|
||||
)
|
||||
|
@ -5,15 +5,20 @@
|
||||
package backy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/logging"
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/remotefetcher"
|
||||
vault "github.com/hashicorp/vault/api"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/rs/zerolog"
|
||||
@ -42,7 +47,7 @@ func AddCommandLists(lists []string) BackyOptionFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// AddPrintLists adds lists to print out
|
||||
// SetListsToSearch adds lists to search
|
||||
func SetListsToSearch(lists []string) BackyOptionFunc {
|
||||
return func(bco *ConfigOpts) {
|
||||
bco.List.Lists = append(bco.List.Lists, lists...)
|
||||
@ -56,14 +61,34 @@ func SetCmdsToSearch(cmds []string) BackyOptionFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// cronEnabled enables the execution of command lists at specified times
|
||||
func CronEnabled() BackyOptionFunc {
|
||||
// SetLogFile sets the path to the log file
|
||||
func SetLogFile(logFile string) BackyOptionFunc {
|
||||
return func(bco *ConfigOpts) {
|
||||
bco.LogFilePath = logFile
|
||||
}
|
||||
}
|
||||
|
||||
func SetHostsConfigFile(hostsConfigFile string) BackyOptionFunc {
|
||||
return func(bco *ConfigOpts) {
|
||||
bco.HostsFilePath = hostsConfigFile
|
||||
}
|
||||
}
|
||||
|
||||
// EnableCommandStdOut forces the command output to stdout
|
||||
func EnableCommandStdOut(setStdOut bool) BackyOptionFunc {
|
||||
return func(bco *ConfigOpts) {
|
||||
bco.CmdStdOut = setStdOut
|
||||
}
|
||||
}
|
||||
|
||||
// EnableCron enables the execution of command lists at specified times
|
||||
func EnableCron() BackyOptionFunc {
|
||||
return func(bco *ConfigOpts) {
|
||||
bco.cronEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
func NewOpts(configFilePath string, opts ...BackyOptionFunc) *ConfigOpts {
|
||||
func NewConfigOptions(configFilePath string, opts ...BackyOptionFunc) *ConfigOpts {
|
||||
b := &ConfigOpts{}
|
||||
b.ConfigFilePath = configFilePath
|
||||
for _, opt := range opts {
|
||||
@ -76,7 +101,7 @@ func NewOpts(configFilePath string, opts ...BackyOptionFunc) *ConfigOpts {
|
||||
|
||||
func injectEnvIntoSSH(envVarsToInject environmentVars, process *ssh.Session, opts *ConfigOpts, log zerolog.Logger) {
|
||||
if envVarsToInject.file != "" {
|
||||
envPath, envPathErr := resolveDir(envVarsToInject.file)
|
||||
envPath, envPathErr := getFullPathWithHomeDir(envVarsToInject.file)
|
||||
if envPathErr != nil {
|
||||
log.Fatal().Str("envFile", envPath).Err(envPathErr).Send()
|
||||
}
|
||||
@ -92,7 +117,11 @@ func injectEnvIntoSSH(envVarsToInject environmentVars, process *ssh.Session, opt
|
||||
goto errEnvFile
|
||||
}
|
||||
for key, val := range envMap {
|
||||
process.Setenv(key, GetVaultKey(val, opts, log))
|
||||
err = process.Setenv(key, getExternalConfigDirectiveValue(val, opts, AllowedExternalDirectiveVault))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Send()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,14 +132,17 @@ errEnvFile:
|
||||
if strings.Contains(envVal, "=") {
|
||||
envVarArr := strings.Split(envVal, "=")
|
||||
|
||||
process.Setenv(envVarArr[0], GetVaultKey(envVarArr[1], opts, log))
|
||||
err := process.Setenv(envVarArr[0], getExternalConfigDirectiveValue(envVarArr[1], opts, AllowedExternalDirectiveVaultFile))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Send()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func injectEnvIntoLocalCMD(envVarsToInject environmentVars, process *exec.Cmd, log zerolog.Logger) {
|
||||
func injectEnvIntoLocalCMD(envVarsToInject environmentVars, process *exec.Cmd, log zerolog.Logger, opts *ConfigOpts) {
|
||||
if envVarsToInject.file != "" {
|
||||
envPath, _ := resolveDir(envVarsToInject.file)
|
||||
envPath, _ := getFullPathWithHomeDir(envVarsToInject.file)
|
||||
|
||||
file, fileErr := os.Open(envPath)
|
||||
if fileErr != nil {
|
||||
@ -132,7 +164,8 @@ errEnvFile:
|
||||
|
||||
for _, envVal := range envVarsToInject.env {
|
||||
if strings.Contains(envVal, "=") {
|
||||
process.Env = append(process.Env, envVal)
|
||||
envVarArr := strings.Split(envVal, "=")
|
||||
process.Env = append(process.Env, fmt.Sprintf("%s=%s", envVarArr[0], getExternalConfigDirectiveValue(envVarArr[1], opts, AllowedExternalDirectiveVault)))
|
||||
}
|
||||
}
|
||||
process.Env = append(process.Env, os.Environ()...)
|
||||
@ -165,7 +198,6 @@ func testFile(c string) error {
|
||||
return fileOpenErr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -174,10 +206,12 @@ func IsTerminalActive() bool {
|
||||
}
|
||||
|
||||
func IsCmdStdOutEnabled() bool {
|
||||
return os.Getenv("BACKY_STDOUT") == "enabled"
|
||||
return os.Getenv("BACKY_CMDSTDOUT") == "enabled"
|
||||
}
|
||||
|
||||
func resolveDir(path string) (string, error) {
|
||||
func getFullPathWithHomeDir(path string) (string, error) {
|
||||
path = strings.TrimSpace(path)
|
||||
|
||||
if path == "~" {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
@ -199,21 +233,39 @@ func resolveDir(path string) (string, error) {
|
||||
|
||||
// loadEnv loads a .env file from the config file directory
|
||||
func (opts *ConfigOpts) loadEnv() {
|
||||
envFileInConfigDir := fmt.Sprintf("%s/.env", path.Dir(opts.ConfigFilePath))
|
||||
var backyEnv map[string]string
|
||||
backyEnv, envFileErr := godotenv.Read(envFileInConfigDir)
|
||||
if envFileErr != nil {
|
||||
return
|
||||
var envFileInConfigDir string
|
||||
var envFileErr error
|
||||
if isRemoteURL(opts.ConfigFilePath) {
|
||||
_, u := getRemoteDir(opts.ConfigFilePath)
|
||||
envFileInConfigDir = u.JoinPath(".env").String()
|
||||
envFetcher, err := remotefetcher.NewRemoteFetcher(envFileInConfigDir, opts.Cache)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err := envFetcher.Fetch(envFileInConfigDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
backyEnv, envFileErr = godotenv.UnmarshalBytes(data)
|
||||
if envFileErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
envFileInConfigDir = fmt.Sprintf("%s/.env", path.Dir(opts.ConfigFilePath))
|
||||
backyEnv, envFileErr = godotenv.Read(envFileInConfigDir)
|
||||
if envFileErr != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
opts.backyEnv = backyEnv
|
||||
}
|
||||
|
||||
// expandEnvVars expands environment variables with the env used in the config
|
||||
func expandEnvVars(backyEnv map[string]string, envVars []string) {
|
||||
|
||||
env := func(name string) string {
|
||||
name = strings.ToUpper(name)
|
||||
envVar, found := backyEnv[name]
|
||||
if found {
|
||||
return envVar
|
||||
@ -221,35 +273,249 @@ func expandEnvVars(backyEnv map[string]string, envVars []string) {
|
||||
return ""
|
||||
}
|
||||
|
||||
// parse env variables using new macros
|
||||
for indx, v := range envVars {
|
||||
if strings.HasPrefix(v, macroStart) && strings.HasSuffix(v, macroEnd) {
|
||||
if strings.HasPrefix(v, envMacroStart) {
|
||||
v = strings.TrimPrefix(v, envMacroStart)
|
||||
v = strings.TrimRight(v, macroEnd)
|
||||
out, _ := shell.Expand(v, env)
|
||||
envVars[indx] = out
|
||||
|
||||
if strings.HasPrefix(v, envExternDirectiveStart) && strings.HasSuffix(v, externDirectiveEnd) {
|
||||
v = strings.TrimPrefix(v, envExternDirectiveStart)
|
||||
v = strings.TrimRight(v, externDirectiveEnd)
|
||||
out, _ := shell.Expand(v, env)
|
||||
envVars[indx] = out
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func getCommandTypeAndSetCommandInfo(command *Command) *Command {
|
||||
|
||||
if command.Type == PackageCommandType && !command.packageCmdSet {
|
||||
command.packageCmdSet = true
|
||||
switch command.PackageOperation {
|
||||
case PackageOperationInstall:
|
||||
command.Cmd, command.Args = command.pkgMan.Install(command.Packages, command.Args)
|
||||
case PackageOperationRemove:
|
||||
command.Cmd, command.Args = command.pkgMan.Remove(command.Packages, command.Args)
|
||||
case PackageOperationUpgrade:
|
||||
command.Cmd, command.Args = command.pkgMan.Upgrade(command.Packages)
|
||||
case PackageOperationCheckVersion:
|
||||
command.Cmd, command.Args = command.pkgMan.CheckVersion(command.Packages)
|
||||
}
|
||||
}
|
||||
|
||||
if command.Type == UserCommandType && !command.userCmdSet {
|
||||
command.userCmdSet = true
|
||||
switch command.UserOperation {
|
||||
case "add":
|
||||
command.Cmd, command.Args = command.userMan.AddUser(
|
||||
command.Username,
|
||||
command.UserHome,
|
||||
command.UserShell,
|
||||
command.UserIsSystem,
|
||||
command.UserCreateHome,
|
||||
command.UserGroups,
|
||||
command.Args)
|
||||
case "modify":
|
||||
command.Cmd, command.Args = command.userMan.ModifyUser(
|
||||
command.Username,
|
||||
command.UserHome,
|
||||
command.UserShell,
|
||||
command.UserGroups)
|
||||
case "checkIfExists":
|
||||
command.Cmd, command.Args = command.userMan.UserExists(command.Username)
|
||||
case "delete":
|
||||
command.Cmd, command.Args = command.userMan.RemoveUser(command.Username)
|
||||
case "password":
|
||||
command.Cmd, command.stdin, command.UserPassword = command.userMan.ModifyPassword(command.Username, command.UserPassword)
|
||||
}
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
func parsePackageVersion(output string, cmdCtxLogger zerolog.Logger, command *Command, cmdOutBuf bytes.Buffer) ([]string, error) {
|
||||
|
||||
var err error
|
||||
var errs []error
|
||||
pkgVersionOnSystem, err := command.pkgMan.ParseRemotePackageManagerVersionOutput(output)
|
||||
if err != nil {
|
||||
cmdCtxLogger.Error().AnErr("Error parsing package version output", err).Send()
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error parsing package version output: %v", err)
|
||||
}
|
||||
|
||||
for _, p := range pkgVersionOnSystem {
|
||||
packageIndex := getPackageIndexFromCommand(command, p.Name)
|
||||
if packageIndex == -1 {
|
||||
cmdCtxLogger.Error().Str("package", p.Name).Msg("Package not found in command")
|
||||
continue
|
||||
}
|
||||
command.Packages[packageIndex].VersionCheck = p.VersionCheck
|
||||
packageFromCommand := command.Packages[packageIndex]
|
||||
cmdCtxLogger.Info().
|
||||
Str("Installed", packageFromCommand.VersionCheck.Installed).
|
||||
Msg("Package version comparison")
|
||||
|
||||
versionLogger := cmdCtxLogger.With().Str("package", packageFromCommand.Name).Logger()
|
||||
|
||||
if packageFromCommand.Version != "" {
|
||||
versionLogger := cmdCtxLogger.With().Str("package", packageFromCommand.Name).Str("Specified Version", packageFromCommand.Version).Logger()
|
||||
packageVersionRegex, PkgRegexErr := regexp.Compile(packageFromCommand.Version)
|
||||
if PkgRegexErr != nil {
|
||||
versionLogger.Error().Err(PkgRegexErr).Msg("Error compiling package version regex")
|
||||
errs = append(errs, PkgRegexErr)
|
||||
continue
|
||||
}
|
||||
if p.Version == packageFromCommand.Version {
|
||||
versionLogger.Info().Msgf("Installed version matches specified version: %s", packageFromCommand.Version)
|
||||
} else if packageVersionRegex.MatchString(p.VersionCheck.Installed) {
|
||||
versionLogger.Info().Msgf("Installed version contains specified version: %s", packageFromCommand.Version)
|
||||
} else {
|
||||
versionLogger.Info().Msg("Installed version does not match specified version")
|
||||
errs = append(errs, fmt.Errorf("installed version of %s does not match specified version: %s", packageFromCommand.Name, packageFromCommand.Version))
|
||||
}
|
||||
} else {
|
||||
if p.VersionCheck.Installed == p.VersionCheck.Candidate {
|
||||
versionLogger.Info().Msg("Installed and Candidate versions match")
|
||||
} else {
|
||||
cmdCtxLogger.Info().Msg("Installed and Candidate versions differ")
|
||||
errs = append(errs, errors.New("installed and Candidate versions differ"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getPackageCommand checks for command type of package and if the command has already been set
|
||||
// Returns the modified Command with the packageManager command as Cmd and the packageOperation as args, plus any additional Args
|
||||
func getPackageCommand(command *Command) *Command {
|
||||
|
||||
if command.Type == "package" && !command.packageCmdSet {
|
||||
command.packageCmdSet = true
|
||||
switch command.PackageOperation {
|
||||
case "install":
|
||||
command.Cmd, command.Args = command.pkgMan.Install(command.PackageName, command.PackageVersion, command.Args)
|
||||
case "remove":
|
||||
command.Cmd, command.Args = command.pkgMan.Remove(command.PackageName, command.Args)
|
||||
case "upgrade":
|
||||
command.Cmd, command.Args = command.pkgMan.Upgrade(command.PackageName, command.PackageVersion)
|
||||
}
|
||||
} else if command.Type != "package" {
|
||||
command.packageCmdSet = false
|
||||
if errs == nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil
|
||||
}
|
||||
return command
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error parsing package version output: %v", errs)
|
||||
}
|
||||
|
||||
func getPackageIndexFromCommand(command *Command, name string) int {
|
||||
for i, v := range command.Packages {
|
||||
if name == v.Name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func getExternalConfigDirectiveValue(key string, opts *ConfigOpts, allowedDirectives AllowedExternalDirectives) string {
|
||||
if !(strings.HasPrefix(key, externDirectiveStart) && strings.HasSuffix(key, externDirectiveEnd)) {
|
||||
return key
|
||||
}
|
||||
key = replaceVarInString(opts.Vars, key, opts.Logger)
|
||||
opts.Logger.Debug().Str("expanding external key", key).Send()
|
||||
|
||||
if strings.HasPrefix(key, envExternDirectiveStart) {
|
||||
if IsExternalDirectiveEnv(allowedDirectives) {
|
||||
|
||||
key = strings.TrimPrefix(key, envExternDirectiveStart)
|
||||
key = strings.TrimSuffix(key, 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 IsExternalDirectiveFile(allowedDirectives) {
|
||||
|
||||
var err error
|
||||
var keyValue []byte
|
||||
key = strings.TrimPrefix(key, externFileDirectiveStart)
|
||||
key = strings.TrimSuffix(key, externDirectiveEnd)
|
||||
key, err = getFullPathWithHomeDir(key)
|
||||
if err != nil {
|
||||
opts.Logger.Err(err).Send()
|
||||
return ""
|
||||
}
|
||||
if !path.IsAbs(key) {
|
||||
key = path.Join(opts.ConfigDir, key)
|
||||
}
|
||||
keyValue, err = os.ReadFile(key)
|
||||
if err != nil {
|
||||
opts.Logger.Err(err).Send()
|
||||
return ""
|
||||
}
|
||||
key = string(keyValue)
|
||||
} else {
|
||||
opts.Logger.Error().Msgf("Config key with value %s does not support file directive", key)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(key, vaultExternDirectiveStart) {
|
||||
if IsExternalDirectiveVault(allowedDirectives) {
|
||||
|
||||
key = strings.TrimPrefix(key, vaultExternDirectiveStart)
|
||||
key = strings.TrimSuffix(key, externDirectiveEnd)
|
||||
key = GetVaultKey(key, opts, opts.Logger)
|
||||
} else {
|
||||
opts.Logger.Error().Msgf("Config key with value %s does not support vault directive", key)
|
||||
}
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
func getVaultSecret(vaultClient *vault.Client, key *VaultKey) (string, error) {
|
||||
var (
|
||||
secret *vault.KVSecret
|
||||
err error
|
||||
)
|
||||
|
||||
if key.ValueType == "KVv2" {
|
||||
secret, err = vaultClient.KVv2(key.MountPath).Get(context.Background(), key.Path)
|
||||
} else if key.ValueType == "KVv1" {
|
||||
secret, err = vaultClient.KVv1(key.MountPath).Get(context.Background(), key.Path)
|
||||
} else if key.ValueType != "" {
|
||||
return "", fmt.Errorf("type %s for key %s not known. Valid types are KVv1 or KVv2", key.ValueType, key.Name)
|
||||
} else {
|
||||
return "", fmt.Errorf("type for key %s must be specified. Valid types are KVv1 or KVv2", key.Name)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to read secret: %v", err)
|
||||
}
|
||||
|
||||
value, ok := secret.Data[key.Key].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("value type assertion failed for vault key %s: %T %#v", key.Name, secret.Data[key.Name], secret.Data[key.Name])
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func getVaultKeyData(keyName string, keys []*VaultKey) (*VaultKey, error) {
|
||||
for _, k := range keys {
|
||||
if k.Name == keyName {
|
||||
return k, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("key %s not found in vault keys", keyName)
|
||||
}
|
||||
|
||||
func GetVaultKey(str string, opts *ConfigOpts, log zerolog.Logger) string {
|
||||
key, err := getVaultKeyData(str, opts.VaultKeys)
|
||||
if key == nil && err == nil {
|
||||
return str
|
||||
}
|
||||
if err != nil && key == nil {
|
||||
log.Err(err).Send()
|
||||
return ""
|
||||
}
|
||||
|
||||
value, secretErr := getVaultSecret(opts.vaultClient, key)
|
||||
if secretErr != nil {
|
||||
log.Err(secretErr).Send()
|
||||
return value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func IsExternalDirectiveFile(allowedExternalDirectives AllowedExternalDirectives) bool {
|
||||
return strings.Contains(allowedExternalDirectives.String(), "file")
|
||||
}
|
||||
|
||||
func IsExternalDirectiveEnv(allowedExternalDirectives AllowedExternalDirectives) bool {
|
||||
return strings.Contains(allowedExternalDirectives.String(), "env")
|
||||
}
|
||||
|
||||
func IsExternalDirectiveVault(allowedExternalDirectives AllowedExternalDirectives) bool {
|
||||
return strings.Contains(allowedExternalDirectives.String(), "vault")
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user