initial commit
This commit is contained in:
109
internal/config/config.go
Normal file
109
internal/config/config.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/logutils"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// time configuration
|
||||
TimeFormat string `env:"TIME_FORMAT" default:"2006-01-02 15:04:05"`
|
||||
TimeZoneLocal string `env:"TIME_ZONE" default:"America/Chicago"`
|
||||
TZoneLocal *time.Location `ignored:"true"`
|
||||
TZoneUTC *time.Location `ignored:"true"`
|
||||
|
||||
// logging
|
||||
LogLevel int `env:"LOG_LEVEL" default:"0"`
|
||||
Log *logutils.LevelFilter `ignored:"true"`
|
||||
|
||||
// configuration file
|
||||
ConfigFile string `ignored:"true" env:"CONFIG_FILE" required:"false"`
|
||||
ConfigFileExample string `ignored:"true"`
|
||||
ConfigFileData ConfigFileStruct `ignored:"true"`
|
||||
|
||||
// misc
|
||||
VaultUser string `env:"VAULT_USER" required:"false" default:""`
|
||||
VaultPass string `env:"VAULT_PASS" required:"false" default:""`
|
||||
SecretID string `env:"VAULT_SECRET_ID" required:"false" default:""`
|
||||
VaultInstance string `env:"VAULT_INSTANCE" required:"false" default:"dev"`
|
||||
}
|
||||
|
||||
// DefaultConfig initializes the config variable for use with a prepared set of defaults.
|
||||
func DefaultConfig() *Config {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Fatalf("[FATAL] Unable to determine user home directory for config file: %v", err)
|
||||
}
|
||||
|
||||
return &Config{
|
||||
Log: &logutils.LevelFilter{
|
||||
Levels: []logutils.LogLevel{"TRACE", "DEBUG", "INFO", "WARNING", "ERROR"},
|
||||
Writer: os.Stderr,
|
||||
},
|
||||
ConfigFile: home + "/.getvaultpw.yml",
|
||||
ConfigFileExample: `---
|
||||
salt: exampleSalt
|
||||
|
||||
vaultEnvironment:
|
||||
dev:
|
||||
host: vault.dev.example.com
|
||||
user: userName
|
||||
test:
|
||||
host: vault.test.example.com
|
||||
user: userName
|
||||
stage:
|
||||
host: vault.stage.example.com
|
||||
user: userName
|
||||
prod:
|
||||
host: vault.prod.example.com
|
||||
user: userName
|
||||
|
||||
credentials:
|
||||
serviceAccountNumberOne:
|
||||
vaultPath: /secrets/serviceAccountNumberOne/credentials
|
||||
vaultID: password
|
||||
vaultInstance: dev
|
||||
serviceAccountNumberTwo:
|
||||
vaultPath: /secrets/serviceAccountNumberTwo/credentials
|
||||
vaultID: password
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *Config) setLogLevel() {
|
||||
switch {
|
||||
case cfg.LogLevel <= 20:
|
||||
cfg.Log.SetMinLevel(logutils.LogLevel("ERROR"))
|
||||
case cfg.LogLevel > 20 && cfg.LogLevel <= 40:
|
||||
cfg.Log.SetMinLevel(logutils.LogLevel("WARNING"))
|
||||
case cfg.LogLevel > 40 && cfg.LogLevel <= 60:
|
||||
cfg.Log.SetMinLevel(logutils.LogLevel("INFO"))
|
||||
case cfg.LogLevel > 60 && cfg.LogLevel <= 80:
|
||||
cfg.Log.SetMinLevel(logutils.LogLevel("DEBUG"))
|
||||
case cfg.LogLevel > 80:
|
||||
cfg.Log.SetMinLevel(logutils.LogLevel("TRACE"))
|
||||
}
|
||||
log.SetOutput(cfg.Log)
|
||||
}
|
||||
|
||||
func (cfg *Config) printRunningConfig(cfgInfo []StructInfo) {
|
||||
log.Printf("[DEBUG] Current Running Configuration Values:")
|
||||
for _, info := range cfgInfo {
|
||||
switch info.Type.String() {
|
||||
case "string":
|
||||
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*string)
|
||||
log.Printf("[DEBUG]\t%s\t\t= %s\n", info.Alt, *p)
|
||||
case "bool":
|
||||
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*bool)
|
||||
log.Printf("[DEBUG]\t%s\t\t= %s\n", info.Alt, strconv.FormatBool(*p))
|
||||
case "int":
|
||||
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*int)
|
||||
log.Printf("[DEBUG]\t%s\t\t= %s\n", info.Alt, strconv.FormatInt(int64(*p), 10))
|
||||
}
|
||||
}
|
||||
}
|
43
internal/config/configFile.go
Normal file
43
internal/config/configFile.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type ConfigFileStruct struct {
|
||||
Salt string `yaml:"salt"`
|
||||
VaultEnvironment map[string]*VaultEnvStruct `yaml:"vaultEnvironment"`
|
||||
Credential map[string]*CredentialStruct `yaml:"credentials"`
|
||||
}
|
||||
|
||||
type VaultEnvStruct struct {
|
||||
Host string `yaml:"host"`
|
||||
User string `yaml:"user"`
|
||||
EPass string `yaml:"encryptedPassword"`
|
||||
}
|
||||
|
||||
type CredentialStruct struct {
|
||||
Path string `yaml:"vaultPath"`
|
||||
ID string `yaml:"vaultID"`
|
||||
TimeStamp int64 `yaml:"timestamp"`
|
||||
Cache string `yaml:"cachedValue"`
|
||||
VaultEnv string `yaml:"vaultInstance"`
|
||||
}
|
||||
|
||||
func (cfg *Config) WriteConfig(dest string) error {
|
||||
file, err := os.OpenFile(dest, os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
log.Fatalf("[ERROR] Unable to open the config file %s: %v", dest, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, _ := yaml.Marshal(cfg.ConfigFileData)
|
||||
if _, err = file.Write(data); err != nil {
|
||||
log.Fatalf("[ERROR] Unable to update config file %s: %v", cfg.ConfigFile, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
111
internal/config/envconfig.go
Normal file
111
internal/config/envconfig.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type StructInfo struct {
|
||||
Name string
|
||||
Alt string
|
||||
Key string
|
||||
Field reflect.Value
|
||||
Tags reflect.StructTag
|
||||
Type reflect.Type
|
||||
DefaultValue interface{}
|
||||
}
|
||||
|
||||
func getStructInfo(spec interface{}) ([]StructInfo, error) {
|
||||
s := reflect.ValueOf(spec)
|
||||
|
||||
if s.Kind() != reflect.Pointer {
|
||||
return []StructInfo{}, fmt.Errorf("getStructInfo() was sent a %s instead of a pointer to a struct.\n", s.Kind())
|
||||
}
|
||||
|
||||
s = s.Elem()
|
||||
if s.Kind() != reflect.Struct {
|
||||
return []StructInfo{}, fmt.Errorf("getStructInfo() was sent a %s instead of a struct.\n", s.Kind())
|
||||
}
|
||||
typeOfSpec := s.Type()
|
||||
|
||||
infos := make([]StructInfo, 0, s.NumField())
|
||||
for i := 0; i < s.NumField(); i++ {
|
||||
f := s.Field(i)
|
||||
ftype := typeOfSpec.Field(i)
|
||||
|
||||
ignored, _ := strconv.ParseBool(ftype.Tag.Get("ignored"))
|
||||
if !f.CanSet() || ignored {
|
||||
continue
|
||||
}
|
||||
|
||||
for f.Kind() == reflect.Pointer {
|
||||
if f.IsNil() {
|
||||
if f.Type().Elem().Kind() != reflect.Struct {
|
||||
break
|
||||
}
|
||||
f.Set(reflect.New(f.Type().Elem()))
|
||||
}
|
||||
f = f.Elem()
|
||||
}
|
||||
|
||||
info := StructInfo{
|
||||
Name: ftype.Name,
|
||||
Alt: strings.ToUpper(ftype.Tag.Get("env")),
|
||||
Key: ftype.Name,
|
||||
Field: f,
|
||||
Tags: ftype.Tag,
|
||||
Type: ftype.Type,
|
||||
}
|
||||
if info.Alt != "" {
|
||||
info.Key = info.Alt
|
||||
}
|
||||
info.Key = strings.ToUpper(info.Key)
|
||||
if ftype.Tag.Get("default") != "" {
|
||||
v, err := typeConversion(ftype.Type.String(), ftype.Tag.Get("default"))
|
||||
if err != nil {
|
||||
return []StructInfo{}, err
|
||||
}
|
||||
info.DefaultValue = v
|
||||
}
|
||||
infos = append(infos, info)
|
||||
}
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func typeConversion(t, v string) (interface{}, error) {
|
||||
switch t {
|
||||
case "string":
|
||||
return v, nil
|
||||
case "int":
|
||||
return strconv.ParseInt(v, 10, 0)
|
||||
case "int8":
|
||||
return strconv.ParseInt(v, 10, 8)
|
||||
case "int16":
|
||||
return strconv.ParseInt(v, 10, 16)
|
||||
case "int32":
|
||||
return strconv.ParseInt(v, 10, 32)
|
||||
case "int64":
|
||||
return strconv.ParseInt(v, 10, 64)
|
||||
case "uint":
|
||||
return strconv.ParseUint(v, 10, 0)
|
||||
case "uint16":
|
||||
return strconv.ParseUint(v, 10, 16)
|
||||
case "uint32":
|
||||
return strconv.ParseUint(v, 10, 32)
|
||||
case "uint64":
|
||||
return strconv.ParseUint(v, 10, 64)
|
||||
case "float32":
|
||||
return strconv.ParseFloat(v, 32)
|
||||
case "float64":
|
||||
return strconv.ParseFloat(v, 64)
|
||||
case "complex64":
|
||||
return strconv.ParseComplex(v, 64)
|
||||
case "complex128":
|
||||
return strconv.ParseComplex(v, 128)
|
||||
case "bool":
|
||||
return strconv.ParseBool(v)
|
||||
}
|
||||
return nil, fmt.Errorf("Unable to identify type.")
|
||||
}
|
161
internal/config/initialize.go
Normal file
161
internal/config/initialize.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// getEnvString returns string from environment variable
|
||||
func getEnvString(env, def string) (val string) { //nolint:deadcode
|
||||
val = os.Getenv(env)
|
||||
|
||||
if val == "" {
|
||||
return def
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// getEnvInt returns int from environment variable
|
||||
func getEnvInt(env string, def int) (ret int) {
|
||||
val := os.Getenv(env)
|
||||
|
||||
if val == "" {
|
||||
return def
|
||||
}
|
||||
|
||||
ret, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
log.Fatalf("[ERROR] Environment variable is not numeric: %v\n", env)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// getEnvBool returns boolean from environment variable
|
||||
func getEnvBool(env string, def bool) bool {
|
||||
var (
|
||||
err error
|
||||
retVal bool
|
||||
val = os.Getenv(env)
|
||||
)
|
||||
|
||||
if len(val) == 0 {
|
||||
return def
|
||||
} else {
|
||||
retVal, err = strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
log.Fatalf("[ERROR] Environment variable is not boolean: %v\n", env)
|
||||
}
|
||||
}
|
||||
|
||||
return retVal
|
||||
}
|
||||
|
||||
// Init initializes the application configuration by reading default values from the struct's tags
|
||||
// and environment variables. Tags processed by this process are as follows:
|
||||
// `ignored:"true" env:"ENVIRONMENT_VARIABLE" default:"default value"`
|
||||
func Init() *Config {
|
||||
var cryptovault string
|
||||
cfg := DefaultConfig()
|
||||
|
||||
cfgInfo, err := getStructInfo(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("[FATAL] %v", err)
|
||||
}
|
||||
|
||||
for _, info := range cfgInfo {
|
||||
switch info.Type.String() {
|
||||
case "string":
|
||||
var dv string
|
||||
|
||||
if info.DefaultValue != nil {
|
||||
dv = info.DefaultValue.(string)
|
||||
}
|
||||
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*string)
|
||||
flag.StringVar(p, info.Name, getEnvString(info.Name, dv), "("+info.Key+")")
|
||||
case "bool":
|
||||
var dv bool
|
||||
|
||||
if info.DefaultValue != nil {
|
||||
dv = info.DefaultValue.(bool)
|
||||
}
|
||||
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*bool)
|
||||
flag.BoolVar(p, info.Name, getEnvBool(info.Name, dv), "("+info.Key+")")
|
||||
case "int":
|
||||
var dv int
|
||||
|
||||
if info.DefaultValue != nil {
|
||||
dv = int(info.DefaultValue.(int64))
|
||||
}
|
||||
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*int)
|
||||
flag.IntVar(p, info.Name, getEnvInt(info.Name, dv), "("+info.Key+")")
|
||||
}
|
||||
}
|
||||
|
||||
flag.StringVar(&cryptovault, "label", "", "Deprecated support feature. Please use -SecretID.\nCryptovault compatibility feature used to identify the ID of the secret")
|
||||
flag.Parse()
|
||||
|
||||
if len(cryptovault) != 0 {
|
||||
cfg.SecretID = cryptovault
|
||||
}
|
||||
|
||||
// set logging level
|
||||
cfg.setLogLevel()
|
||||
|
||||
// check if configuration file is present, if not create the example config file
|
||||
if _, err := os.Stat(cfg.ConfigFile); os.IsNotExist(err) {
|
||||
log.Printf("[WARNING] No configuration file present. Creating example configuration file at %s", cfg.ConfigFile)
|
||||
file, err := os.Create(cfg.ConfigFile)
|
||||
if err != nil {
|
||||
log.Fatalf("[ERROR] Unable to create the config file %s: %v", cfg.ConfigFile, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
file.Write([]byte(cfg.ConfigFileExample))
|
||||
}
|
||||
|
||||
// readConfiguration File
|
||||
fileData, err := ioutil.ReadFile(cfg.ConfigFile)
|
||||
if err != nil {
|
||||
log.Fatalf("[ERROR] Unable to read the config file %s: %v", cfg.ConfigFile, err)
|
||||
}
|
||||
err = yaml.Unmarshal(fileData, &cfg.ConfigFileData)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Unable to process the config file %s: %v", cfg.ConfigFile, err)
|
||||
}
|
||||
|
||||
// make sure there is a secure salt
|
||||
if cfg.ConfigFileData.Salt == "" || cfg.ConfigFileData.Salt == "exampleSalt" {
|
||||
s := make([]byte, 32)
|
||||
rand.Read(s)
|
||||
cfg.ConfigFileData.Salt = hex.EncodeToString(s)
|
||||
}
|
||||
|
||||
// timezone & format configuration
|
||||
cfg.TZoneUTC, _ = time.LoadLocation("UTC")
|
||||
if err != nil {
|
||||
log.Fatalf("[ERROR] Unable to parse timezone string. Please use one of the timezone database values listed here: %s", "https://en.wikipedia.org/wiki/List_of_tz_database_time_zones")
|
||||
}
|
||||
cfg.TZoneLocal, err = time.LoadLocation(cfg.TimeZoneLocal)
|
||||
if err != nil {
|
||||
log.Fatalf("[ERROR] Unable to parse timezone string. Please use one of the timezone database values listed here: %s", "https://en.wikipedia.org/wiki/List_of_tz_database_time_zones")
|
||||
}
|
||||
time.Now().Format(cfg.TimeFormat)
|
||||
|
||||
// print running config
|
||||
cfg.printRunningConfig(cfgInfo)
|
||||
|
||||
log.Println("[INFO] initialization complete")
|
||||
return cfg
|
||||
}
|
Reference in New Issue
Block a user