Cleaner CLI options definition (env keys as tags)

This commit is contained in:
Denis Arh
2019-05-30 02:08:01 +02:00
parent 8592aa8d35
commit a8ae581e8f
27 changed files with 183 additions and 1397 deletions
+6 -4
View File
@@ -2,16 +2,18 @@ package options
type (
DBOpt struct {
DSN string
Profiler string
DSN string `env:"DB_DSN"`
Profiler string `env:"DB_PROFILER"`
}
)
func DB(pfix string) (o *DBOpt) {
o = &DBOpt{
DSN: EnvString(pfix, "DB_DSN", "corteza:corteza@tcp(db:3306)/corteza?collation=utf8mb4_general_ci"),
Profiler: EnvString(pfix, "DB_PROFILER", "none"),
DSN: "corteza:corteza@tcp(db:3306)/corteza?collation=utf8mb4_general_ci",
Profiler: "none",
}
fill(o, pfix)
return
}
+48
View File
@@ -2,12 +2,60 @@ package options
import (
"os"
"reflect"
"strings"
"time"
"github.com/spf13/cast"
)
func fill(opt interface{}, pfix string) {
v := reflect.ValueOf(opt)
if v.Kind() != reflect.Ptr {
panic("expecting a pointer, not a value")
}
if v.IsNil() {
panic("nil pointer passed")
}
v = v.Elem()
length := v.NumField()
for i := 0; i < length; i++ {
f := v.Field(i)
t := v.Type().Field(i)
if tag := t.Tag.Get("env"); tag != "" {
if !f.CanSet() {
panic("unexpected pointer for field " + t.Name)
}
if f.Type() == reflect.TypeOf(time.Duration(1)) {
v.FieldByName(t.Name).SetInt(int64(EnvDuration(pfix, tag, time.Duration(f.Int()))))
continue
}
if f.Kind() == reflect.String {
v.FieldByName(t.Name).SetString(EnvString(pfix, tag, f.String()))
continue
}
if f.Kind() == reflect.Bool {
v.FieldByName(t.Name).SetBool(EnvBool(pfix, tag, f.Bool()))
continue
}
if f.Kind() == reflect.Int {
v.FieldByName(t.Name).SetInt(int64(EnvInt(pfix, tag, int(f.Int()))))
continue
}
panic("unsupported type/kind for field " + t.Name)
}
}
}
func makeEnvKeys(pfix, name string) []string {
return []string{
strings.ToUpper(strings.Trim(pfix, "_") + "_" + name),
+20 -18
View File
@@ -6,34 +6,36 @@ import (
type (
HTTPOpt struct {
Addr string
Logging bool
Tracing bool
Addr string `env:"HTTP_ADDR"`
Logging bool `env:"HTTP_LOG_REQUESTS"`
Tracing bool `env:"HTTP_ERROR_TRACING"`
EnableVersionRoute bool
EnableDebugRoute bool
EnableVersionRoute bool `env:"HTTP_ENABLE_VERSION_ROUTE"`
EnableDebugRoute bool `env:"HTTP_ENABLE_DEBUG_ROUTE"`
EnableMetrics bool
MetricsServiceLabel string
MetricsUsername string
MetricsPassword string
EnableMetrics bool `env:"HTTP_METRICS"`
MetricsServiceLabel string `env:"HTTP_METRICS_NAME"`
MetricsUsername string `env:"HTTP_METRICS_USERNAME"`
MetricsPassword string `env:"HTTP_METRICS_PASSWORD"`
}
)
func HTTP(pfix string) (o *HTTPOpt) {
o = &HTTPOpt{
Addr: EnvString(pfix, "HTTP_ADDR", ":80"),
Logging: EnvBool(pfix, "HTTP_LOG_REQUESTS", true),
Tracing: EnvBool(pfix, "HTTP_ERROR_TRACING", false),
EnableVersionRoute: EnvBool(pfix, "HTTP_ENABLE_VERSION_ROUTE", true),
EnableDebugRoute: EnvBool(pfix, "HTTP_ENABLE_DEBUG_ROUTE", false),
EnableMetrics: EnvBool(pfix, "HTTP_METRICS", false),
MetricsServiceLabel: EnvString(pfix, "HTTP_METRICS_NAME", "corteza"),
MetricsUsername: EnvString(pfix, "HTTP_METRICS_USERNAME", "metrics"),
Addr: ":80",
Logging: true,
Tracing: false,
EnableVersionRoute: true,
EnableDebugRoute: false,
EnableMetrics: false,
MetricsServiceLabel: "corteza",
MetricsUsername: "metrics",
// Setting metrics password to random string to prevent security accidents...
MetricsPassword: EnvString(pfix, "HTTP_METRICS_PASSWORD", string(rand.Bytes(5))),
MetricsPassword: string(rand.Bytes(5)),
}
fill(o, pfix)
return
}
+6 -4
View File
@@ -6,16 +6,18 @@ import (
type (
HttpClientOpt struct {
ClientTSLInsecure bool
HttpClientTimeout time.Duration
ClientTSLInsecure bool `env:"HTTP_CLIENT_TSL_INSECURE"`
HttpClientTimeout time.Duration `env:"HTTP_CLIENT_TIMEOUT"`
}
)
func HttpClient(pfix string) (o *HttpClientOpt) {
o = &HttpClientOpt{
ClientTSLInsecure: EnvBool(pfix, "HTTP_CLIENT_TSL_INSECURE", false),
HttpClientTimeout: EnvDuration(pfix, "HTTP_CLIENT_TIMEOUT", 30*time.Second),
ClientTSLInsecure: false,
HttpClientTimeout: 30 * time.Second,
}
fill(o, pfix)
return
}
+7 -4
View File
@@ -8,17 +8,20 @@ import (
type (
JWTOpt struct {
Secret string
Expiry time.Duration
Secret string `env:"AUTH_JWT_SECRET"`
Expiry time.Duration `env:"AUTH_JWT_EXPIRY"`
}
)
func JWT(pfix string) (o *JWTOpt) {
o = &JWTOpt{
Secret: EnvString(pfix, "AUTH_JWT_SECRET", string(rand.Bytes(32))),
// Setting JWT secret to random string to prevent security accidents...
Expiry: EnvDuration(pfix, "AUTH_JWT_EXPIRY", time.Hour*24*30),
Secret: string(rand.Bytes(32)),
Expiry: time.Hour * 24 * 30,
}
fill(o, pfix)
return
}
+4 -2
View File
@@ -4,14 +4,16 @@ type (
// Logger's output leve is configured here, but
// dev/prod configuration happens earlier
LogOpt struct {
Level string
Level string `env:"LOG_LEVEL"`
}
)
func Log(pfix string) (o *LogOpt) {
o = &LogOpt{
Level: EnvString(pfix, "LOG_LEVEL", "info"),
Level: "info",
}
fill(o, pfix)
return
}
+3 -2
View File
@@ -6,14 +6,15 @@ import (
type (
MonitorOpt struct {
Interval time.Duration
Interval time.Duration `env:"MONITOR_INTERVAL"`
}
)
func Monitor(pfix string) (o *MonitorOpt) {
o = &MonitorOpt{
Interval: EnvDuration(pfix, "MONITOR_INTERVAL", 300*time.Second),
Interval: 300 * time.Second,
}
fill(o, pfix)
return
}
+6 -5
View File
@@ -2,17 +2,18 @@ package options
type (
ProvisionOpt struct {
MigrateDatabase bool
AutoSetup bool
MigrateDatabase bool `env:"PROVISION_MIGRATE_DATABASE"`
AutoSetup bool `env:"PROVISION_AUTO_SETUP"`
}
)
func Provision(pfix string) (o *ProvisionOpt) {
o = &ProvisionOpt{
MigrateDatabase: EnvBool(pfix, "PROVISION_MIGRATE_DATABASE", true),
AutoSetup: EnvBool(pfix, "PROVISION_AUTO_SETUP", true),
MigrateDatabase: true,
AutoSetup: true,
}
fill(o, pfix)
return
}
+14 -12
View File
@@ -6,16 +6,16 @@ import (
type (
PubSubOpt struct {
Mode string
Mode string `env:"PUBSUB_MODE"`
// Mode
PollingInterval time.Duration
PollingInterval time.Duration `env:"PUBSUB_POLLING_INTERVAL"`
// Redis
RedisAddr string
RedisTimeout time.Duration
RedisPingTimeout time.Duration
RedisPingPeriod time.Duration
RedisAddr string `env:"PUBSUB_REDIS_ADDR"`
RedisTimeout time.Duration `env:"PUBSUB_REDIS_TIMEOUT"`
RedisPingTimeout time.Duration `env:"PUBSUB_REDIS_PING_TIMEOUT"`
RedisPingPeriod time.Duration `env:"PUBSUB_REDIS_PING_PERIOD"`
}
)
@@ -27,13 +27,15 @@ func PubSub(pfix string) (o *PubSubOpt) {
)
o = &PubSubOpt{
Mode: EnvString(pfix, "PUBSUB_MODE", "poll"),
PollingInterval: EnvDuration(pfix, "PUBSUB_POLLING_INTERVAL", timeout),
RedisAddr: EnvString(pfix, "PUBSUB_REDIS_ADDR", "redis:6379"),
RedisTimeout: EnvDuration(pfix, "PUBSUB_REDIS_TIMEOUT", timeout),
RedisPingTimeout: EnvDuration(pfix, "PUBSUB_REDIS_PING_TIMEOUT", pingTimeout),
RedisPingPeriod: EnvDuration(pfix, "PUBSUB_REDIS_PING_PERIOD", pingPeriod),
Mode: "poll",
PollingInterval: timeout,
RedisAddr: "redis:6379",
RedisTimeout: timeout,
RedisPingTimeout: pingTimeout,
RedisPingPeriod: pingPeriod,
}
fill(o, pfix)
return
}
+12 -10
View File
@@ -2,22 +2,24 @@ package options
type (
SMTPOpt struct {
Host string
Port int
User string
Pass string
From string
Host string `env:"SMTP_HOST"`
Port int `env:"SMTP_PORT"`
User string `env:"SMTP_USERNAM"`
Pass string `env:"SMTP_PASS"`
From string `env:"SMTP_FROM"`
}
)
func SMTP(pfix string) (o *SMTPOpt) {
o = &SMTPOpt{
Host: EnvString(pfix, "SMTP_HOST", "localhost:25"),
Port: EnvInt(pfix, "SMTP_PORT", 25),
User: EnvString(pfix, "SMTP_USERNAME", ""),
Pass: EnvString(pfix, "SMTP_PASS", ""),
From: EnvString(pfix, "SMTP_FROM", ""),
Host: "localhost:25",
Port: 25,
User: "",
Pass: "",
From: "",
}
fill(o, pfix)
return
}
+8 -6
View File
@@ -6,9 +6,9 @@ import (
type (
WebsocketOpt struct {
Timeout time.Duration
PingTimeout time.Duration
PingPeriod time.Duration
Timeout time.Duration `env:"WEBSOCKET_TIMEOUT"`
PingTimeout time.Duration `env:"WEBSOCKET_PING_TIMEOUT"`
PingPeriod time.Duration `env:"WEBSOCKET_PING_PERIOD"`
}
)
@@ -20,10 +20,12 @@ func Websocket(pfix string) (o *WebsocketOpt) {
)
o = &WebsocketOpt{
Timeout: EnvDuration(pfix, "WEBSOCKET_TIMEOUT", timeout),
PingTimeout: EnvDuration(pfix, "WEBSOCKET_PING_TIMEOUT", pingTimeout),
PingPeriod: EnvDuration(pfix, "WEBSOCKET_PING_PERIOD", pingPeriod),
Timeout: timeout,
PingTimeout: pingTimeout,
PingPeriod: pingPeriod,
}
fill(o, pfix)
return
}
+12 -11
View File
@@ -173,11 +173,20 @@ func (c *Config) Init() {
c.DatabaseName = c.ServiceName
}
c.LogOpt = options.Log(c.EnvPrefix)
c.SmtpOpt = options.SMTP(c.EnvPrefix)
c.JwtOpt = options.JWT(c.EnvPrefix)
c.HttpClientOpt = options.HttpClient(c.EnvPrefix)
c.DbOpt = options.DB(c.ServiceName)
c.ProvisionOpt = options.Provision(c.ServiceName)
if c.RootCommandDBSetup == nil {
c.RootCommandDBSetup = Runners{func(ctx context.Context, cmd *cobra.Command, c *Config) (err error) {
_, err = db.TryToConnect(ctx, c.Log, c.DatabaseName, c.DbOpt.DSN, c.DbOpt.Profiler)
if err != nil {
return errors.Wrap(err, "could not connect to database")
if c.DbOpt != nil {
_, err = db.TryToConnect(ctx, c.Log, c.DatabaseName, c.DbOpt.DSN, c.DbOpt.Profiler)
if err != nil {
return errors.Wrap(err, "could not connect to database")
}
}
return
@@ -204,16 +213,8 @@ func (c *Config) MakeCLI(ctx context.Context) (cmd *cobra.Command) {
Use: c.RootCommandName,
TraverseChildren: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {
c.LogOpt = options.Log(c.EnvPrefix)
c.SmtpOpt = options.SMTP(c.EnvPrefix)
c.JwtOpt = options.JWT(c.EnvPrefix)
c.HttpClientOpt = options.HttpClient(c.EnvPrefix)
InitGeneralServices(c.LogOpt, c.SmtpOpt, c.JwtOpt, c.HttpClientOpt)
c.DbOpt = options.DB(c.ServiceName)
c.ProvisionOpt = options.Provision(c.ServiceName)
err = c.RootCommandDBSetup.Run(ctx, cmd, c)
if err != nil {
c.Log.Error("Failed to connect to the database", zap.Error(err))