diff --git a/cmd/backup.go b/cmd/backup.go index a1fc11d..dafe91a 100644 --- a/cmd/backup.go +++ b/cmd/backup.go @@ -31,8 +31,10 @@ func init() { func Backup(cmd *cobra.Command, args []string) { backyConfOpts := backy.NewOpts(cfgFile, backy.AddCommandLists(cmdLists)) backyConfOpts.InitConfig() + config := backy.ReadConfig(backyConfOpts) - config.RunBackyConfig("") + + config.RunBackyConfig("", backyConfOpts) for _, host := range config.Hosts { if host.SshClient != nil { host.SshClient.Close() diff --git a/cmd/version.go b/cmd/version.go index a7a1d3a..b782cbb 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -16,15 +16,20 @@ var ( Run: version, } numOnly bool + vPre bool ) func version(cmd *cobra.Command, args []string) { cmd.PersistentFlags().BoolVarP(&numOnly, "num", "n", true, "Output the version number only.") - if numOnly { + cmd.PersistentFlags().BoolVarP(&vPre, "vpre", "V", false, "Output the version with v prefixed.") + + if numOnly && !vPre { fmt.Printf("%s\n", versionStr) + } else if vPre { + fmt.Printf("v%s", versionStr) } else { - fmt.Printf("Version: %s", versionStr) + fmt.Printf("Backy version: %s", versionStr) } os.Exit(0) diff --git a/go.mod b/go.mod index c6418df..e48517c 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/go-co-op/gocron v1.18.0 + github.com/hashicorp/vault/api v1.9.0 github.com/joho/godotenv v1.4.0 github.com/kevinburke/ssh_config v1.2.0 github.com/mattn/go-isatty v0.0.17 @@ -20,21 +21,32 @@ require ( ) require ( + github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/golang/snappy v0.0.1 // 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.6.6 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -54,7 +66,9 @@ require ( golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.4.0 // indirect golang.org/x/text v0.6.0 // indirect + golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4ba7623..3e9958b 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,10 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +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.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -58,6 +62,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= @@ -66,6 +72,7 @@ github.com/go-co-op/gocron v1.18.0/go.mod h1:sD/a0Aadtw5CpflUJ/lpP9Vfdk979Wl1Sg3 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -124,10 +131,34 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +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.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +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 v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +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.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +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.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/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/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 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 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.9.0 h1:ab7dI6W8DuCY7yCU8blo0UCYl2oHre/dloCmzMWg9w8= +github.com/hashicorp/vault/api v1.9.0/go.mod h1:lloELQP4EyhjnCQhF8agKvWIVTmxbpEJj70b98959sM= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -152,13 +183,20 @@ 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/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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/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-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +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/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= @@ -174,6 +212,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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= @@ -183,6 +222,9 @@ github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= 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/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= @@ -339,6 +381,7 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -395,6 +438,8 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -542,6 +587,8 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/pkg/backy/backy.go b/pkg/backy/backy.go index 1c626a2..a9d775b 100644 --- a/pkg/backy/backy.go +++ b/pkg/backy/backy.go @@ -11,6 +11,7 @@ import ( "io" "os" "os/exec" + "strings" "text/template" "embed" @@ -29,7 +30,7 @@ var Sprintf = fmt.Sprintf // The environment of local commands will be the machine's environment plus any extra // variables specified in the Env file or Environment. // Dir can also be specified for local commands. -func (command *Command) RunCmd(log *zerolog.Logger, backyConf *BackyConfigFile) ([]string, error) { +func (command *Command) RunCmd(log *zerolog.Logger, backyConf *BackyConfigFile, opts *BackyConfigOpts) ([]string, error) { var ( outputArr []string @@ -48,7 +49,13 @@ func (command *Command) RunCmd(log *zerolog.Logger, backyConf *BackyConfigFile) } if command.Host != nil { - log.Info().Str("Command", fmt.Sprintf("Running command %s %s on host %s", command.Cmd, ArgsStr, *command.Host)).Send() + command.Type = strings.TrimSpace(command.Type) + + if command.Type != "" { + log.Info().Str("Command", fmt.Sprintf("Running script %s on host %s", command.Cmd, *command.Host)).Send() + } else { + log.Info().Str("Command", fmt.Sprintf("Running command %s %s on host %s", command.Cmd, ArgsStr, *command.Host)).Send() + } if command.RemoteHost.SshClient == nil { err := command.RemoteHost.ConnectToSSHHost(log, backyConf) @@ -58,12 +65,11 @@ func (command *Command) RunCmd(log *zerolog.Logger, backyConf *BackyConfigFile) } commandSession, err := command.RemoteHost.SshClient.NewSession() if err != nil { - log.Err(fmt.Errorf("new ssh session: %w", err)).Send() return nil, err } defer commandSession.Close() - injectEnvIntoSSH(envVars, commandSession, log) + injectEnvIntoSSH(envVars, commandSession, opts) cmd := command.Cmd for _, a := range command.Args { cmd += " " + a @@ -76,6 +82,94 @@ func (command *Command) RunCmd(log *zerolog.Logger, backyConf *BackyConfigFile) commandSession.Stdout = cmdOutWriters commandSession.Stderr = cmdOutWriters + if command.Type != "" { + + if command.Type == "script" { + script := bytes.NewBufferString(cmd + "\n") + + commandSession.Stdin = script + if err := commandSession.Shell(); err != nil { + return nil, err + } + + if err := commandSession.Wait(); err != nil { + outScanner := bufio.NewScanner(&cmdOutBuf) + for outScanner.Scan() { + outMap := make(map[string]interface{}) + outMap["cmd"] = cmd + outMap["output"] = outScanner.Text() + if str, ok := outMap["output"].(string); ok { + outputArr = append(outputArr, str) + } + log.Info().Fields(outMap).Send() + } + return outputArr, err + } + + outScanner := bufio.NewScanner(&cmdOutBuf) + for outScanner.Scan() { + outMap := make(map[string]interface{}) + outMap["cmd"] = cmd + outMap["output"] = outScanner.Text() + if str, ok := outMap["output"].(string); ok { + outputArr = append(outputArr, str) + } + log.Info().Fields(outMap).Send() + } + return outputArr, nil + } + if command.Type == "scriptFile" { + var buffer bytes.Buffer + var dirErr error + command.Cmd, dirErr = resolveDir(command.Cmd) + if dirErr != nil { + return nil, dirErr + } + file, err := os.Open(command.Cmd) + if err != nil { + return nil, err + } + defer file.Close() + _, err = io.Copy(&buffer, file) + if err != nil { + return nil, err + } + + script := &buffer + + commandSession.Stdin = script + if err := commandSession.Shell(); err != nil { + return nil, err + } + if err := commandSession.Wait(); err != nil { + outScanner := bufio.NewScanner(&cmdOutBuf) + for outScanner.Scan() { + outMap := make(map[string]interface{}) + outMap["cmd"] = cmd + outMap["output"] = outScanner.Text() + if str, ok := outMap["output"].(string); ok { + outputArr = append(outputArr, str) + } + log.Info().Fields(outMap).Send() + } + return outputArr, err + } + + outScanner := bufio.NewScanner(&cmdOutBuf) + for outScanner.Scan() { + outMap := make(map[string]interface{}) + outMap["cmd"] = cmd + outMap["output"] = outScanner.Text() + if str, ok := outMap["output"].(string); ok { + outputArr = append(outputArr, str) + } + log.Info().Fields(outMap).Send() + } + return outputArr, nil + } + return nil, fmt.Errorf("command type not recognized") + } + err = commandSession.Run(cmd) outScanner := bufio.NewScanner(&cmdOutBuf) for outScanner.Scan() { @@ -93,10 +187,6 @@ func (command *Command) RunCmd(log *zerolog.Logger, backyConf *BackyConfigFile) return outputArr, err } } else { - cmdExists := command.checkCmdExists() - if !cmdExists { - log.Info().Str(command.Cmd, "not found").Send() - } var err error if command.Shell != "" { @@ -177,10 +267,9 @@ func (command *Command) RunCmd(log *zerolog.Logger, backyConf *BackyConfigFile) return outputArr, nil } -func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, config *BackyConfigFile, results chan<- string) { +func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, config *BackyConfigFile, results chan<- string, opts *BackyConfigOpts) { for list := range jobs { - var currentCmd string fieldsMap := make(map[string]interface{}) fieldsMap["list"] = list.Name @@ -188,13 +277,14 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, config *BackyCo var count int var cmdsRan []string + var outStructArr []outStruct for _, cmd := range list.Order { - currentCmd = config.Cmds[cmd].Cmd + currentCmd := config.Cmds[cmd].Cmd fieldsMap["cmd"] = config.Cmds[cmd].Cmd - cmdLog.Fields(fieldsMap).Send() cmdToRun := config.Cmds[cmd] + cmdLog.Fields(fieldsMap).Send() cmdLogger := config.Logger.With(). Str("backy-cmd", cmd).Str("Host", "local machine"). @@ -206,7 +296,17 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, config *BackyCo Logger() } - outputArr, runOutErr := cmdToRun.RunCmd(&cmdLogger, config) + outputArr, runOutErr := cmdToRun.RunCmd(&cmdLogger, config, opts) + if cmdToRun.Output { + outputStruct := outStruct{ + CmdName: cmd, + CmdExecuted: currentCmd, + Output: outputArr, + } + + outStructArr = append(outStructArr, outputStruct) + + } count++ if runOutErr != nil { var errMsg bytes.Buffer @@ -215,10 +315,14 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, config *BackyCo errStruct["listName"] = list.Name errStruct["Command"] = currentCmd + errStruct["Cmd"] = cmd + errStruct["Args"] = config.Cmds[cmd].Args errStruct["Err"] = runOutErr errStruct["CmdsRan"] = cmdsRan errStruct["Output"] = outputArr + errStruct["CmdOutput"] = outStructArr + tmpErr := msgTemps.err.Execute(&errMsg, errStruct) if tmpErr != nil { @@ -243,9 +347,12 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, config *BackyCo if list.NotifyConfig != nil { successStruct := make(map[string]interface{}) + successStruct["listName"] = list.Name successStruct["CmdsRan"] = cmdsRan + successStruct["CmdOutput"] = outStructArr + tmpErr := msgTemps.success.Execute(&successMsg, successStruct) if tmpErr != nil { @@ -271,7 +378,7 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, config *BackyCo } // RunBackyConfig runs a command list from the BackyConfigFile. -func (config *BackyConfigFile) RunBackyConfig(cron string) { +func (config *BackyConfigFile) RunBackyConfig(cron string, opts *BackyConfigOpts) { 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")), @@ -283,7 +390,7 @@ func (config *BackyConfigFile) RunBackyConfig(cron string) { // This starts up 3 workers, initially blocked // because there are no jobs yet. for w := 1; w <= configListsLen; w++ { - go cmdListWorker(mTemps, listChan, config, results) + go cmdListWorker(mTemps, listChan, config, results, opts) } // Here we send 5 `jobs` and then `close` that @@ -316,7 +423,7 @@ func (config *BackyConfigFile) ExecuteCmds(opts *BackyConfigOpts) { cmdLogger := config.Logger.With(). Str("backy-cmd", cmd). Logger() - _, runErr := cmdToRun.RunCmd(&cmdLogger, config) + _, runErr := cmdToRun.RunCmd(&cmdLogger, config, opts) if runErr != nil { config.Logger.Err(runErr).Send() } diff --git a/pkg/backy/config.go b/pkg/backy/config.go index 840cc02..4ee3902 100644 --- a/pkg/backy/config.go +++ b/pkg/backy/config.go @@ -1,6 +1,7 @@ package backy import ( + "context" "errors" "fmt" "os" @@ -8,12 +9,39 @@ import ( "strings" "git.andrewnw.xyz/CyberShell/backy/pkg/logging" - "github.com/joho/godotenv" + vault "github.com/hashicorp/vault/api" "github.com/mattn/go-isatty" "github.com/rs/zerolog" "github.com/spf13/viper" ) +func (opts *BackyConfigOpts) InitConfig() { + if opts.viper != nil { + return + } + backyViper := viper.New() + + if strings.TrimSpace(opts.ConfigFilePath) != "" { + err := testFile(opts.ConfigFilePath) + if err != nil { + logging.ExitWithMSG(fmt.Sprintf("Could not open config file %s: %v", opts.ConfigFilePath, err), 1, nil) + } + backyViper.SetConfigFile(opts.ConfigFilePath) + } else { + backyViper.SetConfigName("backy.yml") // name of config file (with extension) + backyViper.SetConfigName("backy.yaml") // name of config file (with extension) + backyViper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name + backyViper.AddConfigPath(".") // optionally look for config in the working directory + backyViper.AddConfigPath("$HOME/.config/backy") // call multiple times to add many search paths + } + err := backyViper.ReadInConfig() // Find and read the config file + if err != nil { // Handle errors reading the config file + msg := fmt.Sprintf("fatal error reading config file %s: %v", backyViper.ConfigFileUsed(), err) + logging.ExitWithMSG(msg, 1, nil) + } + opts.viper = backyViper +} + // ReadConfig validates and reads the config file. func ReadConfig(opts *BackyConfigOpts) *BackyConfigFile { @@ -28,10 +56,10 @@ func ReadConfig(opts *BackyConfigOpts) *BackyConfigFile { backyConfigFile := NewConfig() backyViper := opts.viper opts.loadEnv() - envFileInConfigDir := fmt.Sprintf("%s/.env", path.Dir(backyViper.ConfigFileUsed())) + // envFileInConfigDir := fmt.Sprintf("%s/.env", path.Dir(backyViper.ConfigFileUsed())) // load the .env file in config file directory - _ = godotenv.Load(envFileInConfigDir) + // _ = godotenv.Load(envFileInConfigDir) if backyViper.GetBool(getNestedConfig("logging", "cmd-std-out")) { os.Setenv("BACKY_STDOUT", "enabled") @@ -239,6 +267,11 @@ func ReadConfig(opts *BackyConfigOpts) *BackyConfigFile { } backyConfigFile.SetupNotify() opts.ConfigFile = backyConfigFile + if err := opts.setupVault(); err != nil { + log.Err(err).Send() + } + opts.ConfigFile = backyConfigFile + return backyConfigFile } @@ -261,29 +294,101 @@ func getCmdListFromConfig(list string) string { return fmt.Sprintf("cmd-configs.%s", list) } -func (opts *BackyConfigOpts) InitConfig() { - if opts.viper != nil { - return +func (opts *BackyConfigOpts) setupVault() error { + if !opts.viper.GetBool("vault.enabled") { + return nil } - backyViper := viper.New() + config := vault.DefaultConfig() - if strings.TrimSpace(opts.ConfigFilePath) != "" { - err := testFile(opts.ConfigFilePath) - if err != nil { - logging.ExitWithMSG(fmt.Sprintf("Could not open config file %s: %v", opts.ConfigFilePath, err), 1, nil) - } - backyViper.SetConfigFile(opts.ConfigFilePath) + config.Address = opts.viper.GetString("vault.address") + if strings.TrimSpace(config.Address) == "" { + config.Address = os.Getenv("VAULT_ADDR") + } + + client, err := vault.NewClient(config) + if err != nil { + return err + } + + token := opts.viper.GetString("vault.token") + if strings.TrimSpace(token) == "" { + token = os.Getenv("VAULT_TOKEN") + } + + client.SetToken(token) + + cmdListCfg := opts.viper.Sub("viper.keys") + unmarshalErr := cmdListCfg.Unmarshal(&opts.VaultKeys) + if unmarshalErr != nil { + panic(fmt.Errorf("error unmarshalling viper.keys into struct: %w", unmarshalErr)) + } + + opts.vaultClient = client + + return nil +} + +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 { - backyViper.SetConfigName("backy.yml") // name of config file (with extension) - backyViper.SetConfigName("backy.yaml") // name of config file (with extension) - backyViper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name - backyViper.AddConfigPath(".") // optionally look for config in the working directory - backyViper.AddConfigPath("$HOME/.config/backy") // call multiple times to add many search paths + return "", fmt.Errorf("type for key %s must be specified. Valid types are KVv1 or KVv2", key.Name) + } - err := backyViper.ReadInConfig() // Find and read the config file - if err != nil { // Handle errors reading the config file - msg := fmt.Sprintf("fatal error reading config file %s: %v", backyViper.ConfigFileUsed(), err) - logging.ExitWithMSG(msg, 1, nil) + if err != nil { + return "", fmt.Errorf("unable to read secret: %v", err) } - opts.viper = backyViper + + value, ok := secret.Data[key.Name].(string) + if !ok { + return "", fmt.Errorf("value type assertion failed: %T %#v", secret.Data[key.Name], secret.Data[key.Name]) + } + + return value, nil +} + +func isVaultKey(str string) (string, bool) { + str = strings.TrimSpace(str) + return strings.TrimPrefix(str, "vault:"), strings.HasPrefix(str, "vault:") +} + +func parseVaultKey(str string, keys []*VaultKey) (*VaultKey, error) { + keyName, isKey := isVaultKey(str) + if !isKey { + return nil, nil + } + + 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 *BackyConfigOpts) string { + key, err := parseVaultKey(str, opts.VaultKeys) + if key == nil && err == nil { + return str + } + if err != nil && key == nil { + opts.ConfigFile.Logger.Err(err).Send() + return "" + } + + value, secretErr := getVaultSecret(opts.vaultClient, key) + if secretErr != nil { + opts.ConfigFile.Logger.Err(secretErr).Send() + return value + } + return value } diff --git a/pkg/backy/templates/error.txt b/pkg/backy/templates/error.txt index 9982dee..026f2a5 100644 --- a/pkg/backy/templates/error.txt +++ b/pkg/backy/templates/error.txt @@ -1,4 +1,8 @@ -Command list {{.listName }} failed on running {{.Command}}. +Command list {{.listName }} failed. + +The command run was {{.Cmd}}. + +The command executed was {{.Command}} {{ if .Args }} {{- range .Args}} {{.}} {{end}} {{end}} {{ if .Err }} The error was {{ .Err }}{{ end }} @@ -9,4 +13,10 @@ The following commands ran: {{- range .CmdsRan}} - {{. -}} {{end}} +{{ end }} + +{{ if .CmdOutput }}{{- range .CmdOutput }}Commad output for {{ .CmdName }}: +{{- range .Output}} + {{ . }} +{{ end }}{{ end }} {{ end }} \ No newline at end of file diff --git a/pkg/backy/templates/success.txt b/pkg/backy/templates/success.txt index 0212c86..4d5731b 100644 --- a/pkg/backy/templates/success.txt +++ b/pkg/backy/templates/success.txt @@ -1,7 +1,12 @@ -Command list {{ .listName }} was completed successfully. - +Command list {{ .listName }} completed successfully. The following commands ran: {{- range .CmdsRan}} - {{. -}} {{end}} + +{{ if .CmdOutput }}{{- range .CmdOutput }}Commad output for {{ .CmdName }}: +{{- range .Output}} + {{ . }} +{{ end }}{{ end }} +{{ end }} \ No newline at end of file diff --git a/pkg/backy/types.go b/pkg/backy/types.go index 6d6c73f..5a68b9e 100644 --- a/pkg/backy/types.go +++ b/pkg/backy/types.go @@ -4,6 +4,7 @@ import ( "bytes" "text/template" + vaultapi "github.com/hashicorp/vault/api" "github.com/kevinburke/ssh_config" "github.com/nikoksr/notify" "github.com/rs/zerolog" @@ -66,6 +67,10 @@ type ( // command to run Cmd string `yaml:"cmd"` + // Possible values: script, scriptFile + // If blank, it is regualar command. + Type string `yaml:"type"` + // host on which to run cmd Host *string `yaml:"host,omitempty"` @@ -91,6 +96,10 @@ type ( // 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. + Output bool `yaml:"output,omitempty"` } BackyOptionFunc func(*BackyConfigOpts) @@ -146,9 +155,33 @@ type ( // Holds env vars from .env file backyEnv map[string]string + vaultClient *vaultapi.Client + + VaultKeys []*VaultKey `yaml:"keys"` + viper *viper.Viper } + outStruct struct { + CmdName string + CmdExecuted string + Output []string + } + + VaultKey struct { + Name string `yaml:"name"` + Path string `yaml:"path"` + ValueType string `yaml:"type"` + MountPath string `yaml:"mountpath"` + } + + VaultConfig struct { + Token string `yaml:"token"` + Address string `yaml:"address"` + Enabled string `yaml:"enabled"` + Keys []*VaultKey `yaml:"keys"` + } + NotificationsConfig struct { Config *viper.Viper Enabled bool diff --git a/pkg/backy/utils.go b/pkg/backy/utils.go index fe7c44c..23b8df9 100644 --- a/pkg/backy/utils.go +++ b/pkg/backy/utils.go @@ -21,25 +21,25 @@ import ( "mvdan.cc/sh/v3/shell" ) -func injectEnvIntoSSH(envVarsToInject environmentVars, process *ssh.Session, log *zerolog.Logger) { +func injectEnvIntoSSH(envVarsToInject environmentVars, process *ssh.Session, opts *BackyConfigOpts) { if envVarsToInject.file != "" { envPath, envPathErr := resolveDir(envVarsToInject.file) if envPathErr != nil { - log.Fatal().Str("envFile", envPath).Err(envPathErr).Send() + opts.ConfigFile.Logger.Fatal().Str("envFile", envPath).Err(envPathErr).Send() } file, err := os.Open(envPath) if err != nil { - log.Fatal().Str("envFile", envPath).Err(err).Send() + opts.ConfigFile.Logger.Fatal().Str("envFile", envPath).Err(err).Send() } defer file.Close() envMap, err := godotenv.Parse(file) if err != nil { - log.Error().Str("envFile", envPath).Err(err).Send() + opts.ConfigFile.Logger.Error().Str("envFile", envPath).Err(err).Send() goto errEnvFile } for key, val := range envMap { - process.Setenv(key, val) + process.Setenv(key, GetVaultKey(val, opts)) } } @@ -49,7 +49,8 @@ errEnvFile: // don't append env Vars for Backy if strings.Contains(envVal, "=") { envVarArr := strings.Split(envVal, "=") - process.Setenv(envVarArr[0], envVarArr[1]) + + process.Setenv(envVarArr[0], GetVaultKey(envVarArr[1], opts)) } } } @@ -58,10 +59,11 @@ func injectEnvIntoLocalCMD(envVarsToInject environmentVars, process *exec.Cmd, l if envVarsToInject.file != "" { envPath, _ := resolveDir(envVarsToInject.file) - file, _ := os.Open(envPath) - // if err != nil { - // log.Fatal().Str("envFile", envPath).Err(err).Send() - // } + file, fileErr := os.Open(envPath) + if fileErr != nil { + log.Error().Str("envFile", envPath).Err(fileErr).Send() + goto errEnvFile + } defer file.Close() envMap, err := godotenv.Parse(file) if err != nil { @@ -69,6 +71,7 @@ func injectEnvIntoLocalCMD(envVarsToInject environmentVars, process *exec.Cmd, l goto errEnvFile } for key, val := range envMap { + process.Env = append(process.Env, fmt.Sprintf("%s=%s", key, val)) } @@ -83,11 +86,6 @@ errEnvFile: process.Env = append(process.Env, os.Environ()...) } -func (cmd *Command) checkCmdExists() bool { - _, err := exec.LookPath(cmd.Cmd) - return err == nil -} - func contains(s []string, e string) bool { for _, a := range s { if a == e {