feat: Support watch for file.

This commit is contained in:
Fernandez Ludovic 2019-04-22 04:00:28 +02:00
parent c05755948d
commit 5b6b959074
15 changed files with 175 additions and 42 deletions

View File

@ -1,7 +1,6 @@
package cmd
import (
"strconv"
"time"
"github.com/abronan/valkeyrie/store"
@ -24,7 +23,6 @@ func init() {
kvCmd.PersistentFlags().String("prefix", "traefik", "Prefix used for KV store.")
kvCmd.PersistentFlags().String("password", "", "Password for connection.")
kvCmd.PersistentFlags().String("username", "", "Username for connection.")
kvCmd.PersistentFlags().Bool("watch", false, "Enable watching changes.")
// FIXME review TLS parts
// kvCmd.PersistentFlags().Bool("tls.enable", false, "Enable TLS encryption.")
@ -43,8 +41,6 @@ func getKvConfig(cmd *cobra.Command) (*kv.Config, error) {
return nil, err
}
watch, _ := strconv.ParseBool(cmd.Flag("watch").Value.String())
return &kv.Config{
Endpoints: endpoints,
Prefix: cmd.Flag("prefix").Value.String(),
@ -53,6 +49,5 @@ func getKvConfig(cmd *cobra.Command) (*kv.Config, error) {
Username: cmd.Flag("password").Value.String(),
Password: cmd.Flag("username").Value.String(),
},
Watch: watch,
}, 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().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.")
}
// 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 {
return func(cmd *cobra.Command, _ []string) error {
baseConfig, err := getBaseConfig(cmd)
@ -169,3 +144,35 @@ func tree(root, indent string) error {
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
```
--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")
@ -17,6 +18,7 @@ Dump Let's Encrypt certificates from Traefik.
-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.
```
### 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 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
```
--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")
@ -27,10 +28,11 @@ traefik-certs-dumper file [flags]
--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.
```
### SEE ALSO
* [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.
--prefix string Prefix used for KV store. (default "traefik")
--username string Username for connection.
--watch Enable watching changes.
```
### 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")
@ -28,6 +28,7 @@ Dump the content of a KV store.
--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.
```
### 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 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
```
--clean Clean destination folder before dumping content. (default true)
--config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--connection-timeout int Connection timeout in seconds.
--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.
###### 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
```
--clean Clean destination folder before dumping content. (default true)
--config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--connection-timeout int Connection timeout in seconds.
--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.
###### 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
```
--clean Clean destination folder before dumping content. (default true)
--config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--connection-timeout int Connection timeout in seconds.
--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.
###### 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
```
--clean Clean destination folder before dumping content. (default true)
--config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--connection-timeout int Connection timeout in seconds.
--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.
###### 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
```
--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")
@ -26,10 +27,11 @@ traefik-certs-dumper version [flags]
--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.
```
### SEE ALSO
* [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
DomainSubDir bool
Clean bool
Watch bool
}

View File

@ -1,14 +1,32 @@
package file
import (
"bytes"
"crypto/md5"
"encoding/json"
"io"
"log"
"os"
"strings"
"github.com/fsnotify/fsnotify"
"github.com/ldez/traefik-certs-dumper/v2/dumper"
)
// Dump Dumps "acme.json" file to certificates.
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)
if err != nil {
return err
@ -30,3 +48,104 @@ func readFile(acmeFile string) (*dumper.StoredData, error) {
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)
}
switch {
case event.Op&fsnotify.Rename == fsnotify.Rename:
errW := watcher.Remove(acmeFile)
if errW != nil {
log.Println("error:", errW)
done <- true
return
}
errW = watcher.Add(acmeFile)
if errW != nil {
log.Println("error:", errW)
done <- true
return
}
fallthrough
case event.Op&fsnotify.Write == fsnotify.Write:
hash, errH := calculateHash(acmeFile)
if err != nil {
log.Println("error:", errH)
done <- true
return
}
if !bytes.Equal(previousHash, hash) {
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 {
log.Println("error:", errD)
done <- true
return
}
log.Println("Dumped new certificate data.")
}
}
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 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
Prefix string
Endpoints []string
Watch bool
Options *store.Config
}

View File

@ -24,7 +24,7 @@ func Dump(config *Config, baseConfig *dumper.BaseConfig) error {
storeKey := config.Prefix + storeKeySuffix
if config.Watch {
if baseConfig.Watch {
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/cpuguy83/go-md2man v1.0.10 // 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/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
github.com/google/btree v1.0.0 // indirect