mirror of
https://github.com/Luzifer/yaml-vault.git
synced 2024-12-20 20:11:16 +00:00
Improve code quality
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
1540ea328d
commit
1cd015ad67
1 changed files with 44 additions and 57 deletions
101
main.go
101
main.go
|
@ -4,29 +4,33 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"github.com/Luzifer/rconfig"
|
|
||||||
"github.com/hashicorp/vault/api"
|
"github.com/hashicorp/vault/api"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
korvike "github.com/Luzifer/korvike/functions"
|
||||||
|
"github.com/Luzifer/rconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfg = struct {
|
cfg = struct {
|
||||||
File string `flag:"file,f" default:"vault.yaml" description:"File to import from / export to"`
|
File string `flag:"file,f" default:"vault.yaml" description:"File to import from / export to" validate:"non-zero"`
|
||||||
Import bool `flag:"import" default:"false" description:"Enable importing data into Vault"`
|
Import bool `flag:"import" default:"false" description:"Enable importing data into Vault"`
|
||||||
Export bool `flag:"export" default:"false" description:"Enable exporting data from Vault"`
|
Export bool `flag:"export" default:"false" description:"Enable exporting data from Vault"`
|
||||||
ExportPaths []string `flag:"export-paths" default:"secret" description:"Which paths to export"`
|
ExportPaths []string `flag:"export-paths" default:"secret" description:"Which paths to export"`
|
||||||
IgnoreErrors bool `flag:"ignore-errors" default:"false" description:"Do not exit on read/write errors"`
|
IgnoreErrors bool `flag:"ignore-errors" default:"false" description:"Do not exit on read/write errors"`
|
||||||
|
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
|
||||||
VaultAddress string `flag:"vault-addr" env:"VAULT_ADDR" default:"https://127.0.0.1:8200" description:"Vault API address"`
|
VaultAddress string `flag:"vault-addr" env:"VAULT_ADDR" default:"https://127.0.0.1:8200" description:"Vault API address"`
|
||||||
VaultToken string `flag:"vault-token" env:"VAULT_TOKEN" vardefault:"vault-token" description:"Specify a token to use instead of app-id auth"`
|
VaultToken string `flag:"vault-token" env:"VAULT_TOKEN" vardefault:"vault-token" description:"Specify a token to use instead of app-id auth" validate:"non-zero"`
|
||||||
VersionAndExit bool `flag:"version" default:"false" description:"Print program version and exit"`
|
VersionAndExit bool `flag:"version" default:"false" description:"Print program version and exit"`
|
||||||
Verbose bool `flag:"verbose,v" default:"false" description:"Print verbose output"`
|
Verbose bool `flag:"verbose,v" default:"false" description:"Print verbose output [DEPRECATED]"`
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
version = "dev"
|
version = "dev"
|
||||||
|
@ -44,16 +48,6 @@ type importField struct {
|
||||||
|
|
||||||
type execFunction func(*api.Client) error
|
type execFunction func(*api.Client) error
|
||||||
|
|
||||||
func debug(format string, v ...interface{}) {
|
|
||||||
if cfg.Verbose {
|
|
||||||
log.Printf(format, v...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func info(format string, v ...interface{}) {
|
|
||||||
log.Printf(format, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func vaultTokenFromDisk() string {
|
func vaultTokenFromDisk() string {
|
||||||
vf, err := homedir.Expand("~/.vault-token")
|
vf, err := homedir.Expand("~/.vault-token")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -72,30 +66,35 @@ func init() {
|
||||||
rconfig.SetVariableDefaults(map[string]string{
|
rconfig.SetVariableDefaults(map[string]string{
|
||||||
"vault-token": vaultTokenFromDisk(),
|
"vault-token": vaultTokenFromDisk(),
|
||||||
})
|
})
|
||||||
rconfig.Parse(&cfg)
|
if err := rconfig.ParseAndValidate(&cfg); err != nil {
|
||||||
|
log.WithError(err).Fatal("Unable to parse commandline options")
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.VersionAndExit {
|
if cfg.VersionAndExit {
|
||||||
fmt.Printf("vault2env %s\n", version)
|
fmt.Printf("vault2env %s\n", version)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.VaultToken == "" {
|
if l, err := log.ParseLevel(cfg.LogLevel); err != nil {
|
||||||
log.Fatalf("[ERR] You need to set vault-token")
|
log.WithError(err).Fatal("Unable to parse log level")
|
||||||
|
} else {
|
||||||
|
log.SetLevel(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.File == "" {
|
if cfg.Verbose {
|
||||||
log.Fatalf("[ERR] You need to specify a file")
|
// Backwards compatibility
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Export == cfg.Import {
|
if cfg.Export == cfg.Import {
|
||||||
log.Fatalf("[ERR] You need to either import or export")
|
log.Fatal("You need to either import or export")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(cfg.File); (err == nil && cfg.Export) || (err != nil && cfg.Import) {
|
if _, err := os.Stat(cfg.File); (err == nil && cfg.Export) || (err != nil && cfg.Import) {
|
||||||
if cfg.Export {
|
if cfg.Export {
|
||||||
log.Fatalf("[ERR] Output file exists, stopping now.")
|
log.Fatal("Output file exists, stopping now.")
|
||||||
}
|
}
|
||||||
log.Fatalf("[ERR] Input file does not exist, stopping now.")
|
log.Fatal("Input file does not exist, stopping now.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +103,7 @@ func main() {
|
||||||
Address: cfg.VaultAddress,
|
Address: cfg.VaultAddress,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Unable to create client: %s", err)
|
log.WithError(err).Fatal("Unable to create client")
|
||||||
}
|
}
|
||||||
|
|
||||||
client.SetToken(cfg.VaultToken)
|
client.SetToken(cfg.VaultToken)
|
||||||
|
@ -118,7 +117,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ex(client); err != nil {
|
if err = ex(client); err != nil {
|
||||||
log.Fatalf("[ERR] %s", err)
|
log.WithError(err).Fatal("Unable to execute requested action")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,13 +134,13 @@ func exportFromVault(client *api.Client) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := readRecurse(client, path, &out); err != nil {
|
if err := readRecurse(client, path, &out); err != nil {
|
||||||
return err
|
return errors.Wrap(err, "Unable to read from Vault")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := yaml.Marshal(out)
|
data, err := yaml.Marshal(out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "Unable to marshal yaml")
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(cfg.File, data, 0600)
|
return ioutil.WriteFile(cfg.File, data, 0600)
|
||||||
|
@ -151,29 +150,29 @@ func readRecurse(client *api.Client, path string, out *importFile) error {
|
||||||
if !strings.HasSuffix(path, "/") {
|
if !strings.HasSuffix(path, "/") {
|
||||||
secret, err := client.Logical().Read(path)
|
secret, err := client.Logical().Read(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrapf(err, "Unable to read path %q", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
if secret == nil {
|
if secret == nil {
|
||||||
if cfg.IgnoreErrors {
|
if cfg.IgnoreErrors {
|
||||||
info("Unable to read %s: %#v", path, secret)
|
log.WithField("path", path).Info("Unable to read nil secret")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("Unable to read %s: %#v", path, secret)
|
return errors.Errorf("Unable to read non-existent path %s", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
out.Keys = append(out.Keys, importField{Key: path, Values: secret.Data})
|
out.Keys = append(out.Keys, importField{Key: path, Values: secret.Data})
|
||||||
debug("Successfully read data from key '%s'", path)
|
log.WithField("path", path).Debug("Successfully read data from key")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
secret, err := client.Logical().List(path)
|
secret, err := client.Logical().List(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if cfg.IgnoreErrors {
|
if cfg.IgnoreErrors {
|
||||||
info("Error reading %s: %s", path, err)
|
log.WithError(err).WithField("path", path).Error("Error reading secret")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("Error reading %s: %s", path, err)
|
return errors.Wrapf(err, "Error reading %s", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
if secret != nil && secret.Data["keys"] != nil {
|
if secret != nil && secret.Data["keys"] != nil {
|
||||||
|
@ -191,37 +190,38 @@ func readRecurse(client *api.Client, path string, out *importFile) error {
|
||||||
func importToVault(client *api.Client) error {
|
func importToVault(client *api.Client) error {
|
||||||
keysRaw, err := ioutil.ReadFile(cfg.File)
|
keysRaw, err := ioutil.ReadFile(cfg.File)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "Unable to read input file")
|
||||||
}
|
}
|
||||||
|
|
||||||
keysRaw, err = parseImportFile(keysRaw)
|
keysRaw, err = parseImportFile(keysRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "Unable to parse input file")
|
||||||
}
|
}
|
||||||
|
|
||||||
var keys importFile
|
var keys importFile
|
||||||
if err := yaml.Unmarshal(keysRaw, &keys); err != nil {
|
if err := yaml.Unmarshal(keysRaw, &keys); err != nil {
|
||||||
return err
|
return errors.Wrap(err, "Unable to unmarshal input file")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, field := range keys.Keys {
|
for _, field := range keys.Keys {
|
||||||
if field.State == "absent" {
|
if field.State == "absent" {
|
||||||
if _, err := client.Logical().Delete(field.Key); err != nil {
|
if _, err := client.Logical().Delete(field.Key); err != nil {
|
||||||
if cfg.IgnoreErrors {
|
if cfg.IgnoreErrors {
|
||||||
info("Error while deleting key '%s': %s", field.Key, err)
|
log.WithError(err).WithField("path", field.Key).Error("Error while deleting key")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return err
|
return errors.Wrapf(err, "Unable to delete path %q", field.Key)
|
||||||
}
|
}
|
||||||
|
log.WithField("path", field.Key).Info("Successfully deleted key")
|
||||||
} else {
|
} else {
|
||||||
if _, err := client.Logical().Write(field.Key, field.Values); err != nil {
|
if _, err := client.Logical().Write(field.Key, field.Values); err != nil {
|
||||||
if cfg.IgnoreErrors {
|
if cfg.IgnoreErrors {
|
||||||
info("Error while writing data to key '%s': %s", field.Key, err)
|
log.WithError(err).WithField("path", field.Key).Error("Error while writing data to key")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return err
|
return errors.Wrapf(err, "Unable to write path %q", field.Key)
|
||||||
}
|
}
|
||||||
debug("Successfully wrote data to key '%s'", field.Key)
|
log.WithField("path", field.Key).Debug("Successfully wrote data to key")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,25 +229,12 @@ func importToVault(client *api.Client) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseImportFile(in []byte) (out []byte, err error) {
|
func parseImportFile(in []byte) (out []byte, err error) {
|
||||||
funcMap := template.FuncMap{
|
t, err := template.New("input file").Funcs(korvike.GetFunctionMap()).Parse(string(in))
|
||||||
"env": func(name string, v ...string) string {
|
|
||||||
defaultValue := ""
|
|
||||||
if len(v) > 0 {
|
|
||||||
defaultValue = v[0]
|
|
||||||
}
|
|
||||||
if value, ok := os.LookupEnv(name); ok {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return defaultValue
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := template.New("input file").Funcs(funcMap).Parse(string(in))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, errors.Wrap(err, "Unable to parse template")
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
err = t.Execute(buf, nil)
|
err = t.Execute(buf, nil)
|
||||||
return buf.Bytes(), err
|
return buf.Bytes(), errors.Wrap(err, "Unable to execute template")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue