feat: Support watch for file. (#15)

This commit is contained in:
Ludovic Fernandez 2019-04-22 04:28:18 +02:00 committed by GitHub
parent c05755948d
commit 5639decf82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 186 additions and 42 deletions

View File

@ -1,7 +1,6 @@
package cmd package cmd
import ( import (
"strconv"
"time" "time"
"github.com/abronan/valkeyrie/store" "github.com/abronan/valkeyrie/store"
@ -24,7 +23,6 @@ func init() {
kvCmd.PersistentFlags().String("prefix", "traefik", "Prefix used for KV store.") kvCmd.PersistentFlags().String("prefix", "traefik", "Prefix used for KV store.")
kvCmd.PersistentFlags().String("password", "", "Password for connection.") kvCmd.PersistentFlags().String("password", "", "Password for connection.")
kvCmd.PersistentFlags().String("username", "", "Username for connection.") kvCmd.PersistentFlags().String("username", "", "Username for connection.")
kvCmd.PersistentFlags().Bool("watch", false, "Enable watching changes.")
// FIXME review TLS parts // FIXME review TLS parts
// kvCmd.PersistentFlags().Bool("tls.enable", false, "Enable TLS encryption.") // kvCmd.PersistentFlags().Bool("tls.enable", false, "Enable TLS encryption.")
@ -43,8 +41,6 @@ func getKvConfig(cmd *cobra.Command) (*kv.Config, error) {
return nil, err return nil, err
} }
watch, _ := strconv.ParseBool(cmd.Flag("watch").Value.String())
return &kv.Config{ return &kv.Config{
Endpoints: endpoints, Endpoints: endpoints,
Prefix: cmd.Flag("prefix").Value.String(), Prefix: cmd.Flag("prefix").Value.String(),
@ -53,6 +49,5 @@ func getKvConfig(cmd *cobra.Command) (*kv.Config, error) {
Username: cmd.Flag("password").Value.String(), Username: cmd.Flag("password").Value.String(),
Password: cmd.Flag("username").Value.String(), Password: cmd.Flag("username").Value.String(),
}, },
Watch: watch,
}, nil }, nil
} }

View File

@ -60,6 +60,7 @@ func init() {
rootCmd.PersistentFlags().String("key-name", "privatekey", "The file name (without extension) of the generated private keys.") rootCmd.PersistentFlags().String("key-name", "privatekey", "The file name (without extension) of the generated private keys.")
rootCmd.PersistentFlags().Bool("domain-subdir", false, "Use domain as sub-directory.") 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("clean", true, "Clean destination folder before dumping content.")
rootCmd.PersistentFlags().Bool("watch", false, "Enable watching changes.")
} }
// initConfig reads in config file and ENV variables if set. // initConfig reads in config file and ENV variables if set.
@ -88,32 +89,6 @@ func initConfig() {
} }
} }
func getBaseConfig(cmd *cobra.Command) (*dumper.BaseConfig, error) {
subDir, err := strconv.ParseBool(cmd.Flag("domain-subdir").Value.String())
if err != nil {
return nil, err
}
clean, err := strconv.ParseBool(cmd.Flag("clean").Value.String())
if err != nil {
return nil, err
}
return &dumper.BaseConfig{
DumpPath: cmd.Flag("dest").Value.String(),
CrtInfo: dumper.FileInfo{
Name: cmd.Flag("crt-name").Value.String(),
Ext: cmd.Flag("crt-ext").Value.String(),
},
KeyInfo: dumper.FileInfo{
Name: cmd.Flag("key-name").Value.String(),
Ext: cmd.Flag("key-ext").Value.String(),
},
DomainSubDir: subDir,
Clean: clean,
}, nil
}
func runE(apply func(*dumper.BaseConfig, *cobra.Command) error) func(*cobra.Command, []string) error { func runE(apply func(*dumper.BaseConfig, *cobra.Command) error) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, _ []string) error { return func(cmd *cobra.Command, _ []string) error {
baseConfig, err := getBaseConfig(cmd) baseConfig, err := getBaseConfig(cmd)
@ -169,3 +144,35 @@ func tree(root, indent string) error {
return nil return nil
} }
func getBaseConfig(cmd *cobra.Command) (*dumper.BaseConfig, error) {
subDir, err := strconv.ParseBool(cmd.Flag("domain-subdir").Value.String())
if err != nil {
return nil, err
}
clean, err := strconv.ParseBool(cmd.Flag("clean").Value.String())
if err != nil {
return nil, err
}
watch, err := strconv.ParseBool(cmd.Flag("watch").Value.String())
if err != nil {
return nil, err
}
return &dumper.BaseConfig{
DumpPath: cmd.Flag("dest").Value.String(),
CrtInfo: dumper.FileInfo{
Name: cmd.Flag("crt-name").Value.String(),
Ext: cmd.Flag("crt-ext").Value.String(),
},
KeyInfo: dumper.FileInfo{
Name: cmd.Flag("key-name").Value.String(),
Ext: cmd.Flag("key-ext").Value.String(),
},
DomainSubDir: subDir,
Clean: clean,
Watch: watch,
}, nil
}

View File

@ -9,6 +9,7 @@ Dump Let's Encrypt certificates from Traefik.
### Options ### Options
``` ```
--clean Clean destination folder before dumping content. (default true)
--config string config file (default is $HOME/.traefik-certs-dumper.yaml) --config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--crt-ext string The file extension of the generated certificates. (default ".crt") --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") --crt-name string The file name (without extension) of the generated certificates. (default "certificate")
@ -17,6 +18,7 @@ Dump Let's Encrypt certificates from Traefik.
-h, --help help for traefik-certs-dumper -h, --help help for traefik-certs-dumper
--key-ext string The file extension of the generated private keys. (default ".key") --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") --key-name string The file name (without extension) of the generated private keys. (default "privatekey")
--watch Enable watching changes.
``` ```
### SEE ALSO ### SEE ALSO
@ -25,4 +27,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 kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
* [traefik-certs-dumper version](traefik-certs-dumper_version.md) - Display version * [traefik-certs-dumper version](traefik-certs-dumper_version.md) - Display version
###### Auto generated by spf13/cobra on 20-Apr-2019 ###### Auto generated by spf13/cobra on 22-Apr-2019

View File

@ -20,6 +20,7 @@ traefik-certs-dumper file [flags]
### Options inherited from parent commands ### 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) --config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--crt-ext string The file extension of the generated certificates. (default ".crt") --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") --crt-name string The file name (without extension) of the generated certificates. (default "certificate")
@ -27,10 +28,11 @@ traefik-certs-dumper file [flags]
--domain-subdir Use domain as sub-directory. --domain-subdir Use domain as sub-directory.
--key-ext string The file extension of the generated private keys. (default ".key") --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") --key-name string The file name (without extension) of the generated private keys. (default "privatekey")
--watch Enable watching changes.
``` ```
### SEE ALSO ### SEE ALSO
* [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik. * [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik.
###### Auto generated by spf13/cobra on 20-Apr-2019 ###### Auto generated by spf13/cobra on 22-Apr-2019

View File

@ -15,12 +15,12 @@ Dump the content of a KV store.
--password string Password for connection. --password string Password for connection.
--prefix string Prefix used for KV store. (default "traefik") --prefix string Prefix used for KV store. (default "traefik")
--username string Username for connection. --username string Username for connection.
--watch Enable watching changes.
``` ```
### Options inherited from parent commands ### 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) --config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--crt-ext string The file extension of the generated certificates. (default ".crt") --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") --crt-name string The file name (without extension) of the generated certificates. (default "certificate")
@ -28,6 +28,7 @@ Dump the content of a KV store.
--domain-subdir Use domain as sub-directory. --domain-subdir Use domain as sub-directory.
--key-ext string The file extension of the generated private keys. (default ".key") --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") --key-name string The file name (without extension) of the generated private keys. (default "privatekey")
--watch Enable watching changes.
``` ```
### SEE ALSO ### SEE ALSO
@ -38,4 +39,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 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. * [traefik-certs-dumper kv zookeeper](traefik-certs-dumper_kv_zookeeper.md) - Dump the content of zookeeper.
###### Auto generated by spf13/cobra on 20-Apr-2019 ###### Auto generated by spf13/cobra on 22-Apr-2019

View File

@ -21,6 +21,7 @@ traefik-certs-dumper kv boltdb [flags]
### Options inherited from parent commands ### 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) --config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--connection-timeout int Connection timeout in seconds. --connection-timeout int Connection timeout in seconds.
--crt-ext string The file extension of the generated certificates. (default ".crt") --crt-ext string The file extension of the generated certificates. (default ".crt")
@ -40,4 +41,4 @@ traefik-certs-dumper kv boltdb [flags]
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. * [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
###### Auto generated by spf13/cobra on 20-Apr-2019 ###### Auto generated by spf13/cobra on 22-Apr-2019

View File

@ -20,6 +20,7 @@ traefik-certs-dumper kv consul [flags]
### Options inherited from parent commands ### 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) --config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--connection-timeout int Connection timeout in seconds. --connection-timeout int Connection timeout in seconds.
--crt-ext string The file extension of the generated certificates. (default ".crt") --crt-ext string The file extension of the generated certificates. (default ".crt")
@ -39,4 +40,4 @@ traefik-certs-dumper kv consul [flags]
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. * [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
###### Auto generated by spf13/cobra on 20-Apr-2019 ###### Auto generated by spf13/cobra on 22-Apr-2019

View File

@ -20,6 +20,7 @@ traefik-certs-dumper kv etcd [flags]
### Options inherited from parent commands ### 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) --config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--connection-timeout int Connection timeout in seconds. --connection-timeout int Connection timeout in seconds.
--crt-ext string The file extension of the generated certificates. (default ".crt") --crt-ext string The file extension of the generated certificates. (default ".crt")
@ -39,4 +40,4 @@ traefik-certs-dumper kv etcd [flags]
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. * [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
###### Auto generated by spf13/cobra on 20-Apr-2019 ###### Auto generated by spf13/cobra on 22-Apr-2019

View File

@ -19,6 +19,7 @@ traefik-certs-dumper kv zookeeper [flags]
### Options inherited from parent commands ### 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) --config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--connection-timeout int Connection timeout in seconds. --connection-timeout int Connection timeout in seconds.
--crt-ext string The file extension of the generated certificates. (default ".crt") --crt-ext string The file extension of the generated certificates. (default ".crt")
@ -38,4 +39,4 @@ traefik-certs-dumper kv zookeeper [flags]
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. * [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
###### Auto generated by spf13/cobra on 20-Apr-2019 ###### Auto generated by spf13/cobra on 22-Apr-2019

View File

@ -19,6 +19,7 @@ traefik-certs-dumper version [flags]
### Options inherited from parent commands ### 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) --config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--crt-ext string The file extension of the generated certificates. (default ".crt") --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") --crt-name string The file name (without extension) of the generated certificates. (default "certificate")
@ -26,10 +27,11 @@ traefik-certs-dumper version [flags]
--domain-subdir Use domain as sub-directory. --domain-subdir Use domain as sub-directory.
--key-ext string The file extension of the generated private keys. (default ".key") --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") --key-name string The file name (without extension) of the generated private keys. (default "privatekey")
--watch Enable watching changes.
``` ```
### SEE ALSO ### SEE ALSO
* [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik. * [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik.
###### Auto generated by spf13/cobra on 20-Apr-2019 ###### Auto generated by spf13/cobra on 22-Apr-2019

View File

@ -7,4 +7,5 @@ type BaseConfig struct {
KeyInfo FileInfo KeyInfo FileInfo
DomainSubDir bool DomainSubDir bool
Clean bool Clean bool
Watch bool
} }

View File

@ -1,14 +1,32 @@
package file package file
import ( import (
"bytes"
"crypto/md5"
"encoding/json" "encoding/json"
"io"
"log"
"os" "os"
"strings"
"github.com/fsnotify/fsnotify"
"github.com/ldez/traefik-certs-dumper/v2/dumper" "github.com/ldez/traefik-certs-dumper/v2/dumper"
) )
// Dump Dumps "acme.json" file to certificates. // Dump Dumps "acme.json" file to certificates.
func Dump(acmeFile string, baseConfig *dumper.BaseConfig) error { func Dump(acmeFile string, baseConfig *dumper.BaseConfig) error {
err := dump(acmeFile, baseConfig)
if err != nil {
return err
}
if baseConfig.Watch {
return watch(acmeFile, baseConfig)
}
return nil
}
func dump(acmeFile string, baseConfig *dumper.BaseConfig) error {
data, err := readFile(acmeFile) data, err := readFile(acmeFile)
if err != nil { if err != nil {
return err return err
@ -30,3 +48,115 @@ func readFile(acmeFile string) (*dumper.StoredData, error) {
return data, nil return data, nil
} }
func watch(acmeFile string, baseConfig *dumper.BaseConfig) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
defer func() { _ = watcher.Close() }()
done := make(chan bool)
go func() {
var previousHash []byte
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if strings.EqualFold(os.Getenv("TCD_DEBUG"), "true") {
log.Println("event:", event)
}
hash, errW := manageEvent(watcher, event, acmeFile, previousHash, baseConfig)
if errW != nil {
log.Println("error:", errW)
done <- true
return
}
previousHash = hash
case errW, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", errW)
done <- true
return
}
}
}()
err = watcher.Add(acmeFile)
if err != nil {
return err
}
<-done
return nil
}
func manageEvent(watcher *fsnotify.Watcher, event fsnotify.Event, acmeFile string, previousHash []byte, baseConfig *dumper.BaseConfig) ([]byte, error) {
err := manageRename(watcher, event, acmeFile)
if err != nil {
return nil, err
}
hash, err := calculateHash(acmeFile)
if err != nil {
return nil, err
}
if !bytes.Equal(previousHash, hash) {
if strings.EqualFold(os.Getenv("TCD_DEBUG"), "true") {
log.Println("detected changes on file:", event.Name)
}
if errD := dump(acmeFile, baseConfig); errD != nil {
return nil, errD
}
log.Println("Dumped new certificate data.")
}
return hash, nil
}
func manageRename(watcher *fsnotify.Watcher, event fsnotify.Event, acmeFile string) error {
if event.Op&fsnotify.Rename == fsnotify.Rename {
err := watcher.Remove(acmeFile)
if err != nil {
return err
}
err = watcher.Add(acmeFile)
if err != nil {
return err
}
}
return nil
}
func calculateHash(acmeFile string) ([]byte, error) {
file, err := os.Open(acmeFile)
if err != nil {
return nil, err
}
defer func() { _ = file.Close() }()
h := md5.New()
_, err = io.Copy(h, file)
if err != nil {
return nil, err
}
return h.Sum(nil), nil
}

View File

@ -7,6 +7,5 @@ type Config struct {
Backend store.Backend Backend store.Backend
Prefix string Prefix string
Endpoints []string Endpoints []string
Watch bool
Options *store.Config Options *store.Config
} }

View File

@ -24,7 +24,7 @@ func Dump(config *Config, baseConfig *dumper.BaseConfig) error {
storeKey := config.Prefix + storeKeySuffix storeKey := config.Prefix + storeKeySuffix
if config.Watch { if baseConfig.Watch {
return watch(kvStore, storeKey, baseConfig) return watch(kvStore, storeKey, baseConfig)
} }

1
go.mod
View File

@ -10,6 +10,7 @@ require (
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
github.com/cpuguy83/go-md2man v1.0.10 // indirect github.com/cpuguy83/go-md2man v1.0.10 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/fsnotify/fsnotify v1.4.7
github.com/go-acme/lego v2.5.0+incompatible github.com/go-acme/lego v2.5.0+incompatible
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
github.com/google/btree v1.0.0 // indirect github.com/google/btree v1.0.0 // indirect