diff --git a/cmd/root.go b/cmd/root.go index a67b5f5..f281016 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -61,6 +61,7 @@ func init() { rootCmd.PersistentFlags().Bool("domain-subdir", false, "Use domain as sub-directory.") rootCmd.PersistentFlags().Bool("clean", true, "Clean destination folder before dumping content.") rootCmd.PersistentFlags().Bool("watch", false, "Enable watching changes.") + rootCmd.PersistentFlags().String("post-hook", "", "Execute a command only if changes occurs on the data source. (works only with the watch mode)") } // initConfig reads in config file and ENV variables if set. @@ -174,5 +175,6 @@ func getBaseConfig(cmd *cobra.Command) (*dumper.BaseConfig, error) { DomainSubDir: subDir, Clean: clean, Watch: watch, + Hook: cmd.Flag("post-hook").Value.String(), }, nil } diff --git a/docs/traefik-certs-dumper.md b/docs/traefik-certs-dumper.md index da0c103..f2a73aa 100644 --- a/docs/traefik-certs-dumper.md +++ b/docs/traefik-certs-dumper.md @@ -9,16 +9,17 @@ Dump Let's Encrypt certificates from Traefik. ### Options ``` - --clean Clean destination folder before dumping content. (default true) - --config string config file (default is $HOME/.traefik-certs-dumper.yaml) - --crt-ext string The file extension of the generated certificates. (default ".crt") - --crt-name string The file name (without extension) of the generated certificates. (default "certificate") - --dest string Path to store the dump content. (default "./dump") - --domain-subdir Use domain as sub-directory. - -h, --help help for traefik-certs-dumper - --key-ext string The file extension of the generated private keys. (default ".key") - --key-name string The file name (without extension) of the generated private keys. (default "privatekey") - --watch Enable watching changes. + --clean Clean destination folder before dumping content. (default true) + --config string config file (default is $HOME/.traefik-certs-dumper.yaml) + --crt-ext string The file extension of the generated certificates. (default ".crt") + --crt-name string The file name (without extension) of the generated certificates. (default "certificate") + --dest string Path to store the dump content. (default "./dump") + --domain-subdir Use domain as sub-directory. + -h, --help help for traefik-certs-dumper + --key-ext string The file extension of the generated private keys. (default ".key") + --key-name string The file name (without extension) of the generated private keys. (default "privatekey") + --post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode) + --watch Enable watching changes. ``` ### SEE ALSO @@ -27,4 +28,4 @@ Dump Let's Encrypt certificates from Traefik. * [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. * [traefik-certs-dumper version](traefik-certs-dumper_version.md) - Display version -###### Auto generated by spf13/cobra on 22-Apr-2019 +###### Auto generated by spf13/cobra on 25-Apr-2019 diff --git a/docs/traefik-certs-dumper_file.md b/docs/traefik-certs-dumper_file.md index a4a7f85..d7be5a4 100644 --- a/docs/traefik-certs-dumper_file.md +++ b/docs/traefik-certs-dumper_file.md @@ -20,19 +20,20 @@ traefik-certs-dumper file [flags] ### Options inherited from parent commands ``` - --clean Clean destination folder before dumping content. (default true) - --config string config file (default is $HOME/.traefik-certs-dumper.yaml) - --crt-ext string The file extension of the generated certificates. (default ".crt") - --crt-name string The file name (without extension) of the generated certificates. (default "certificate") - --dest string Path to store the dump content. (default "./dump") - --domain-subdir Use domain as sub-directory. - --key-ext string The file extension of the generated private keys. (default ".key") - --key-name string The file name (without extension) of the generated private keys. (default "privatekey") - --watch Enable watching changes. + --clean Clean destination folder before dumping content. (default true) + --config string config file (default is $HOME/.traefik-certs-dumper.yaml) + --crt-ext string The file extension of the generated certificates. (default ".crt") + --crt-name string The file name (without extension) of the generated certificates. (default "certificate") + --dest string Path to store the dump content. (default "./dump") + --domain-subdir Use domain as sub-directory. + --key-ext string The file extension of the generated private keys. (default ".key") + --key-name string The file name (without extension) of the generated private keys. (default "privatekey") + --post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode) + --watch Enable watching changes. ``` ### SEE ALSO * [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik. -###### Auto generated by spf13/cobra on 22-Apr-2019 +###### Auto generated by spf13/cobra on 25-Apr-2019 diff --git a/docs/traefik-certs-dumper_kv.md b/docs/traefik-certs-dumper_kv.md index edf097b..ba510f0 100644 --- a/docs/traefik-certs-dumper_kv.md +++ b/docs/traefik-certs-dumper_kv.md @@ -26,15 +26,16 @@ Dump the content of a KV store. ### Options inherited from parent commands ``` - --clean Clean destination folder before dumping content. (default true) - --config string config file (default is $HOME/.traefik-certs-dumper.yaml) - --crt-ext string The file extension of the generated certificates. (default ".crt") - --crt-name string The file name (without extension) of the generated certificates. (default "certificate") - --dest string Path to store the dump content. (default "./dump") - --domain-subdir Use domain as sub-directory. - --key-ext string The file extension of the generated private keys. (default ".key") - --key-name string The file name (without extension) of the generated private keys. (default "privatekey") - --watch Enable watching changes. + --clean Clean destination folder before dumping content. (default true) + --config string config file (default is $HOME/.traefik-certs-dumper.yaml) + --crt-ext string The file extension of the generated certificates. (default ".crt") + --crt-name string The file name (without extension) of the generated certificates. (default "certificate") + --dest string Path to store the dump content. (default "./dump") + --domain-subdir Use domain as sub-directory. + --key-ext string The file extension of the generated private keys. (default ".key") + --key-name string The file name (without extension) of the generated private keys. (default "privatekey") + --post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode) + --watch Enable watching changes. ``` ### SEE ALSO @@ -45,4 +46,4 @@ Dump the content of a KV store. * [traefik-certs-dumper kv etcd](traefik-certs-dumper_kv_etcd.md) - Dump the content of etcd. * [traefik-certs-dumper kv zookeeper](traefik-certs-dumper_kv_zookeeper.md) - Dump the content of zookeeper. -###### Auto generated by spf13/cobra on 22-Apr-2019 +###### Auto generated by spf13/cobra on 25-Apr-2019 diff --git a/docs/traefik-certs-dumper_kv_boltdb.md b/docs/traefik-certs-dumper_kv_boltdb.md index 43a9506..f2e774b 100644 --- a/docs/traefik-certs-dumper_kv_boltdb.md +++ b/docs/traefik-certs-dumper_kv_boltdb.md @@ -32,6 +32,7 @@ traefik-certs-dumper kv boltdb [flags] --key-ext string The file extension of the generated private keys. (default ".key") --key-name string The file name (without extension) of the generated private keys. (default "privatekey") --password string Password for connection. + --post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode) --prefix string Prefix used for KV store. (default "traefik") --tls Enable TLS encryption. --tls.ca string Root CA for certificate verification if TLS is enabled @@ -47,4 +48,4 @@ traefik-certs-dumper kv boltdb [flags] * [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. -###### Auto generated by spf13/cobra on 22-Apr-2019 +###### Auto generated by spf13/cobra on 25-Apr-2019 diff --git a/docs/traefik-certs-dumper_kv_consul.md b/docs/traefik-certs-dumper_kv_consul.md index 4072d2c..6fb0df0 100644 --- a/docs/traefik-certs-dumper_kv_consul.md +++ b/docs/traefik-certs-dumper_kv_consul.md @@ -31,6 +31,7 @@ traefik-certs-dumper kv consul [flags] --key-ext string The file extension of the generated private keys. (default ".key") --key-name string The file name (without extension) of the generated private keys. (default "privatekey") --password string Password for connection. + --post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode) --prefix string Prefix used for KV store. (default "traefik") --tls Enable TLS encryption. --tls.ca string Root CA for certificate verification if TLS is enabled @@ -46,4 +47,4 @@ traefik-certs-dumper kv consul [flags] * [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. -###### Auto generated by spf13/cobra on 22-Apr-2019 +###### Auto generated by spf13/cobra on 25-Apr-2019 diff --git a/docs/traefik-certs-dumper_kv_etcd.md b/docs/traefik-certs-dumper_kv_etcd.md index 7c16cf0..878c3b8 100644 --- a/docs/traefik-certs-dumper_kv_etcd.md +++ b/docs/traefik-certs-dumper_kv_etcd.md @@ -31,6 +31,7 @@ traefik-certs-dumper kv etcd [flags] --key-ext string The file extension of the generated private keys. (default ".key") --key-name string The file name (without extension) of the generated private keys. (default "privatekey") --password string Password for connection. + --post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode) --prefix string Prefix used for KV store. (default "traefik") --tls Enable TLS encryption. --tls.ca string Root CA for certificate verification if TLS is enabled @@ -46,4 +47,4 @@ traefik-certs-dumper kv etcd [flags] * [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. -###### Auto generated by spf13/cobra on 22-Apr-2019 +###### Auto generated by spf13/cobra on 25-Apr-2019 diff --git a/docs/traefik-certs-dumper_kv_zookeeper.md b/docs/traefik-certs-dumper_kv_zookeeper.md index 3326a85..1ffe529 100644 --- a/docs/traefik-certs-dumper_kv_zookeeper.md +++ b/docs/traefik-certs-dumper_kv_zookeeper.md @@ -30,6 +30,7 @@ traefik-certs-dumper kv zookeeper [flags] --key-ext string The file extension of the generated private keys. (default ".key") --key-name string The file name (without extension) of the generated private keys. (default "privatekey") --password string Password for connection. + --post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode) --prefix string Prefix used for KV store. (default "traefik") --tls Enable TLS encryption. --tls.ca string Root CA for certificate verification if TLS is enabled @@ -45,4 +46,4 @@ traefik-certs-dumper kv zookeeper [flags] * [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. -###### Auto generated by spf13/cobra on 22-Apr-2019 +###### Auto generated by spf13/cobra on 25-Apr-2019 diff --git a/docs/traefik-certs-dumper_version.md b/docs/traefik-certs-dumper_version.md index caa7e2c..4d76473 100644 --- a/docs/traefik-certs-dumper_version.md +++ b/docs/traefik-certs-dumper_version.md @@ -19,19 +19,20 @@ traefik-certs-dumper version [flags] ### Options inherited from parent commands ``` - --clean Clean destination folder before dumping content. (default true) - --config string config file (default is $HOME/.traefik-certs-dumper.yaml) - --crt-ext string The file extension of the generated certificates. (default ".crt") - --crt-name string The file name (without extension) of the generated certificates. (default "certificate") - --dest string Path to store the dump content. (default "./dump") - --domain-subdir Use domain as sub-directory. - --key-ext string The file extension of the generated private keys. (default ".key") - --key-name string The file name (without extension) of the generated private keys. (default "privatekey") - --watch Enable watching changes. + --clean Clean destination folder before dumping content. (default true) + --config string config file (default is $HOME/.traefik-certs-dumper.yaml) + --crt-ext string The file extension of the generated certificates. (default ".crt") + --crt-name string The file name (without extension) of the generated certificates. (default "certificate") + --dest string Path to store the dump content. (default "./dump") + --domain-subdir Use domain as sub-directory. + --key-ext string The file extension of the generated private keys. (default ".key") + --key-name string The file name (without extension) of the generated private keys. (default "privatekey") + --post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode) + --watch Enable watching changes. ``` ### SEE ALSO * [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik. -###### Auto generated by spf13/cobra on 22-Apr-2019 +###### Auto generated by spf13/cobra on 25-Apr-2019 diff --git a/dumper/config.go b/dumper/config.go index 1eb1669..7c189c0 100644 --- a/dumper/config.go +++ b/dumper/config.go @@ -8,4 +8,5 @@ type BaseConfig struct { DomainSubDir bool Clean bool Watch bool + Hook string } diff --git a/dumper/file/file.go b/dumper/file/file.go index 91228df..33bbacc 100644 --- a/dumper/file/file.go +++ b/dumper/file/file.go @@ -11,6 +11,7 @@ import ( "github.com/fsnotify/fsnotify" "github.com/ldez/traefik-certs-dumper/v2/dumper" + "github.com/ldez/traefik-certs-dumper/v2/hook" ) // Dump Dumps "acme.json" file to certificates. @@ -68,7 +69,7 @@ func watch(acmeFile string, baseConfig *dumper.BaseConfig) error { return } - if strings.EqualFold(os.Getenv("TCD_DEBUG"), "true") { + if isDebug() { log.Println("event:", event) } @@ -115,7 +116,7 @@ func manageEvent(watcher *fsnotify.Watcher, event fsnotify.Event, acmeFile strin } if !bytes.Equal(previousHash, hash) { - if strings.EqualFold(os.Getenv("TCD_DEBUG"), "true") { + if isDebug() { log.Println("detected changes on file:", event.Name) } @@ -123,7 +124,11 @@ func manageEvent(watcher *fsnotify.Watcher, event fsnotify.Event, acmeFile strin return nil, errD } - log.Println("Dumped new certificate data.") + if isDebug() { + log.Println("Dumped new certificate data.") + } + + hook.Exec(baseConfig.Hook) } return hash, nil @@ -156,3 +161,7 @@ func calculateHash(acmeFile string) ([]byte, error) { return h.Sum(nil), nil } + +func isDebug() bool { + return strings.EqualFold(os.Getenv("TCD_DEBUG"), "true") +} diff --git a/dumper/kv/kv.go b/dumper/kv/kv.go index d9d7cf6..3714a60 100644 --- a/dumper/kv/kv.go +++ b/dumper/kv/kv.go @@ -7,10 +7,13 @@ import ( "fmt" "io/ioutil" "log" + "os" + "strings" "github.com/abronan/valkeyrie" "github.com/abronan/valkeyrie/store" "github.com/ldez/traefik-certs-dumper/v2/dumper" + "github.com/ldez/traefik-certs-dumper/v2/hook" ) const storeKeySuffix = "/acme/account/object" @@ -55,7 +58,11 @@ func watch(kvStore store.Store, storeKey string, baseConfig *dumper.BaseConfig) return err } - log.Println("Dumped new certificate data.") + if isDebug() { + log.Println("Dumped new certificate data.") + } + + hook.Exec(baseConfig.Hook) } } @@ -86,3 +93,7 @@ func getStoredDataFromGzip(pair *store.KVPair) (*dumper.StoredData, error) { return convertAccountV1ToV2(account), nil } + +func isDebug() bool { + return strings.EqualFold(os.Getenv("TCD_DEBUG"), "true") +} diff --git a/hook/hook.go b/hook/hook.go new file mode 100644 index 0000000..72b0471 --- /dev/null +++ b/hook/hook.go @@ -0,0 +1,41 @@ +package hook + +import ( + "context" + "errors" + "fmt" + "os/exec" + "strings" + "time" +) + +// Exec Execute a command on a go routine. +func Exec(command string) { + if command == "" { + return + } + + go func() { + errH := execute(command) + if errH != nil { + panic(errH) + } + }() +} + +func execute(command string) error { + ctxCmd, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + parts := strings.Fields(command) + output, err := exec.CommandContext(ctxCmd, parts[0], parts[1:]...).CombinedOutput() + if len(output) > 0 { + fmt.Println(string(output)) + } + + if ctxCmd.Err() == context.DeadlineExceeded { + return errors.New("hook timed out") + } + + return err +} diff --git a/readme.md b/readme.md index f0bff6d..f744bb5 100644 --- a/readme.md +++ b/readme.md @@ -18,6 +18,7 @@ - Output formats: - use domain as sub-directory (allow custom names and extensions) - flat (domain as filename) +- Hook (only with watch mode and if the data source changes) ## Installation @@ -71,7 +72,6 @@ dump └──private ├──my.domain.com.crt └──letsencrypt.key - ``` ### Change source and destination @@ -84,7 +84,6 @@ test └──private ├──my.domain.com.crt └──letsencrypt.key - ``` ### Use domain as sub-directory @@ -102,7 +101,7 @@ dump #### Change file extension ```console -$ traefik-certs-dumper file --domain-subdir=true --crt-ext=.pem --key-ext=.pem +$ traefik-certs-dumper file --domain-subdir --crt-ext=.pem --key-ext=.pem dump ├──my.domain.com │ ├──certificate.pem @@ -114,7 +113,7 @@ dump #### Change file name ```console -$ traefik-certs-dumper file --domain-subdir=true --crt-name=fullchain --key-name=privkey +$ traefik-certs-dumper file --domain-subdir --crt-name=fullchain --key-name=privkey dump ├──my.domain.com │ ├──fullchain.crt