From 128a9b49e93c5dbf2d52e693d4e7505820240192 Mon Sep 17 00:00:00 2001 From: Nicolas Carlier Date: Tue, 11 Feb 2020 15:45:21 +0000 Subject: [PATCH] feat(config): configuration refactoring --- README.md | 22 ++---- etc/default/readflow.env | 39 ++++++++++ main.go | 53 ++++++++------ pkg/api/image-proxy.go | 25 +++---- pkg/api/index.go | 2 +- pkg/api/router.go | 2 +- pkg/config/config.go | 78 +++----------------- pkg/config/expvars.go | 33 +++------ pkg/config/flag/bind.go | 117 ++++++++++++++++++++++++++++++ pkg/config/flag/snake.go | 94 ++++++++++++++++++++++++ pkg/config/flag/test/bind_test.go | 36 +++++++++ pkg/config/flag/types.go | 40 ++++++++++ pkg/event-broker/broker.go | 2 +- pkg/event-broker/types.go | 4 +- pkg/version/version.go | 8 +- 15 files changed, 402 insertions(+), 153 deletions(-) create mode 100644 etc/default/readflow.env create mode 100644 pkg/config/flag/bind.go create mode 100644 pkg/config/flag/snake.go create mode 100644 pkg/config/flag/test/bind_test.go create mode 100644 pkg/config/flag/types.go diff --git a/README.md b/README.md index 2b388ee08..aff6e0bde 100644 --- a/README.md +++ b/README.md @@ -40,23 +40,11 @@ $ docker run -d --name=readflow ncarlier/readflow ## Configuration -You can configure the server by setting environment variables: - -| Variable | Default | Description | -|----------|---------|-------------| -| `APP_LISTEN` | `:8080` | Service listen address | -| `APP_LISTEN_METRICS` | none | Metrics listen address | -| `APP_DB` | `postgres://postgres:testpwd@localhost/reader_test` | Database connection string | -| `APP_AUTHN` | `https://login.nunux.org/auth/realms/readflow` | Authentication method ("mock", "proxy" or OIDC if URL) | -| `APP_BROKER` | none | External event broker URI for outgoing events | -| `APP_PUBLIC_URL` | `https://api.readflow.app` | Public URL | -| `APP_SENTRY_DSN` | none | Sentry DSN URL for error reporting | -| `APP_LOG_LEVEL` | `info` | Logging level (`debug`, `info`, `warn` or `error`) | -| `APP_LOG_PRETTY` | `false` | Plain text log output format if true (JSON otherwise) | -| `APP_LOG_OUTPUT` | `stdout` | Log output target (`stdout` or `file://sample.log`) | - -You can also override these settings using program parameters. -Type `readflow --help` to see options. +Readflow can be configured by using command line parameters or by setting environment variables. + +Type `readflow -h` to display all parameters and related environment variables. + +All configuration variables are described in [etc/default/readflow.env](./etc/default/readflow.env) file. ## UI diff --git a/etc/default/readflow.env b/etc/default/readflow.env new file mode 100644 index 000000000..b5ae817b8 --- /dev/null +++ b/etc/default/readflow.env @@ -0,0 +1,39 @@ +### +# readflow configuration +### + +# Authentication method +# - `mock`: Generic user use for testing +# - `proxy`: Use "X-WEBAUTH-USER" or "X-Auth-Username" HTTP header to extract username +# - `https://...`: Use OpenID Connect provider +READFLOW_AUTHN="https://login.nunux.org/auth/realms/readflow" + +# External event broker URI for outgoing events, deactivated by default +# Example: "https://example.com/event" +READFLOW_BROKER= + +# Database connection string +READFLOW_DB="postgres://postgres:testpwd@localhost/readflow_test?sslmode=disable") + +# Image proxy service, deactivated by default +READFLOW_IMAGE_PROXY= + +# HTTP listen address +# Examples: "localhost:8080" or ":8080" for all interfaces +READFLOW_LISTEN_ADDR=":8080" + +# Metrics listen address (aka: Prometheus metrics endpoint), deactivated by default +# Example: ":9090" +READFLOW_LISTEN_METRICS= + +# Log level (debug, info, warn, error), default is "info" +READFLOW_LOG_LEVEL="info" + +# Output human readable logs, default is "false" +READFLOW_LOG_PRETTY=flase + +# Public URL +READFLOW_PUBLIC_URL="https://api.readflow.app" + +# Sentry DSN URL, deactivated by default +READFLOW_SENTRY_DSN= diff --git a/main.go b/main.go index ea448b6c6..527838852 100644 --- a/main.go +++ b/main.go @@ -13,18 +13,16 @@ import ( "syscall" "time" - "github.com/ncarlier/readflow/pkg/job" - "github.com/ncarlier/readflow/pkg/metric" - - eventbroker "github.com/ncarlier/readflow/pkg/event-broker" - _ "github.com/ncarlier/readflow/pkg/event-listener" - "github.com/ncarlier/readflow/pkg/service" - - "github.com/ncarlier/readflow/pkg/db" - "github.com/ncarlier/readflow/pkg/api" "github.com/ncarlier/readflow/pkg/config" + configflag "github.com/ncarlier/readflow/pkg/config/flag" + "github.com/ncarlier/readflow/pkg/db" + eventbroker "github.com/ncarlier/readflow/pkg/event-broker" + _ "github.com/ncarlier/readflow/pkg/event-listener" + "github.com/ncarlier/readflow/pkg/job" "github.com/ncarlier/readflow/pkg/logger" + "github.com/ncarlier/readflow/pkg/metric" + "github.com/ncarlier/readflow/pkg/service" "github.com/ncarlier/readflow/pkg/version" "github.com/rs/zerolog/log" ) @@ -38,28 +36,35 @@ func init() { } func main() { - flag.Parse() + // Get global configuration + conf := config.Config{} + configflag.Bind(&conf, "READFLOW") - conf := config.Get() + // Parse command line (and environment variables) + flag.Parse() - if *conf.Version { + // Show version if asked + if *version.ShowVersion { version.Print() - return + os.Exit(0) } + // Export configurations vars + config.ExportVars(conf) + // Configure the logger - logger.Configure(*conf.LogLevel, *conf.LogPretty, *conf.SentryDSN) + logger.Configure(conf.LogLevel, conf.LogPretty, conf.SentryDSN) log.Debug().Msg("starting readflow server...") // Configure the DB - _db, err := db.Configure(*conf.DB) + _db, err := db.Configure(conf.DB) if err != nil { log.Fatal().Err(err).Msg("could not configure database") } // Configure Event Broker - _, err = eventbroker.Configure(*conf.Broker) + _, err = eventbroker.Configure(conf.Broker) if err != nil { log.Fatal().Err(err).Msg("could not configure event broker") } @@ -74,21 +79,21 @@ func main() { scheduler := job.StartNewScheduler(_db) server := &http.Server{ - Addr: *conf.ListenAddr, - Handler: api.NewRouter(conf), + Addr: conf.ListenAddr, + Handler: api.NewRouter(&conf), } var metricsServer *http.Server - if *conf.ListenMetricsAddr != "" { + if conf.ListenMetricsAddr != "" { metricsServer = &http.Server{ - Addr: *conf.ListenMetricsAddr, + Addr: conf.ListenMetricsAddr, Handler: metric.NewRouter(), } metric.StartCollectors(_db) go func() { - log.Info().Str("listen", *conf.ListenMetricsAddr).Msg("metrics server started") + log.Info().Str("listen", conf.ListenMetricsAddr).Msg("metrics server started") if err := metricsServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatal().Err(err).Str("listen", *conf.ListenMetricsAddr).Msg("could not start metrics server") + log.Fatal().Err(err).Str("listen", conf.ListenMetricsAddr).Msg("could not start metrics server") } }() } @@ -122,10 +127,10 @@ func main() { api.Start() - log.Info().Str("listen", *conf.ListenAddr).Msg("server started") + log.Info().Str("listen", conf.ListenAddr).Msg("server started") if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatal().Err(err).Str("listen", *conf.ListenAddr).Msg("could not start the server") + log.Fatal().Err(err).Str("listen", conf.ListenAddr).Msg("could not start the server") } <-done diff --git a/pkg/api/image-proxy.go b/pkg/api/image-proxy.go index b4959730e..b90f885bd 100644 --- a/pkg/api/image-proxy.go +++ b/pkg/api/image-proxy.go @@ -1,11 +1,10 @@ - package api import ( + "io" + "net" "net/http" "strings" - "net" - "io" "github.com/ncarlier/readflow/pkg/config" ) @@ -60,33 +59,33 @@ func imgProxyHandler(conf *config.Config) http.Handler { } // Redirect if image proxy service not configured - if conf.ImageProxy == nil || *conf.ImageProxy == "" { + if conf.ImageProxy == "" { http.Redirect(w, r, img, 301) return } - + // Build image proxy client client := &http.Client{} - req, err := http.NewRequest("GET", *conf.ImageProxy + "/resize?" + q.Encode(), nil) - if err != nil { + req, err := http.NewRequest("GET", conf.ImageProxy+"/resize?"+q.Encode(), nil) + if err != nil { http.Error(w, err.Error(), http.StatusBadGateway) return - } + } // Manage request headers - req.Header.Set("User-Agent", userAgent) + req.Header.Set("User-Agent", userAgent) if clientIP, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { appendHostToXForwardHeader(req.Header, clientIP) } delHopHeaders(r.Header) // Do proxy request - resp, err := client.Do(req) - if err != nil { + resp, err := client.Do(req) + if err != nil { http.Error(w, err.Error(), http.StatusBadGateway) return - } - defer resp.Body.Close() + } + defer resp.Body.Close() // Create proxy response delHopHeaders(resp.Header) diff --git a/pkg/api/index.go b/pkg/api/index.go index edf862bf9..05b6e111c 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -20,7 +20,7 @@ type Info struct { func index(conf *config.Config) http.Handler { v := Info{ Version: version.Version, - Authority: *config.Get().AuthN, + Authority: conf.AuthN, VAPID: service.Lookup().GetProperties().VAPIDPublicKey, } diff --git a/pkg/api/router.go b/pkg/api/router.go index ab381213f..68afb5660 100644 --- a/pkg/api/router.go +++ b/pkg/api/router.go @@ -23,7 +23,7 @@ func NewRouter(conf *config.Config) *http.ServeMux { return fmt.Sprintf("%d", time.Now().UnixNano()) } - authMiddleware := middleware.Auth(*conf.AuthN) + authMiddleware := middleware.Auth(conf.AuthN) for _, route := range routes { var handler http.Handler diff --git a/pkg/config/config.go b/pkg/config/config.go index b19b7bd5c..400eec523 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,73 +1,15 @@ package config -import ( - "flag" - "os" - "strconv" -) - // Config contain global configuration type Config struct { - ListenAddr *string - ListenMetricsAddr *string - DB *string - Broker *string - AuthN *string - PublicURL *string - Version *bool - LogPretty *bool - LogLevel *string - SentryDSN *string - ImageProxy *string -} - -var config = &Config{ - ListenAddr: flag.String("listen", getEnv("LISTEN", ":8080"), "Service listen address"), - ListenMetricsAddr: flag.String("listen-metrics", getEnv("LISTEN_METRICS", ""), "Metrics listen address"), - DB: flag.String("db", getEnv("DB", "postgres://postgres:testpwd@localhost/readflow_test?sslmode=disable"), "Database connection string"), - Broker: flag.String("broker", getEnv("BROKER", ""), "External event broker URI for outgoing events"), - AuthN: flag.String("authn", getEnv("AUTHN", "https://login.nunux.org/auth/realms/readflow"), "Authentication method (\"mock\", \"proxy\" or OIDC if URL)"), - PublicURL: flag.String("public-url", getEnv("PUBLIC_URL", "https://api.readflow.app"), "Public URL"), - Version: flag.Bool("version", false, "Print version"), - LogPretty: flag.Bool("log-pretty", getBoolEnv("LOG_PRETTY", false), "Output human readable logs"), - LogLevel: flag.String("log-level", getEnv("LOG_LEVEL", "info"), "Log level (debug, info, warn, error)"), - SentryDSN: flag.String("sentry-dsn", getEnv("SENTRY_DSN", ""), "Sentry DSN URL"), - ImageProxy: flag.String("image-proxy", getEnv("IMAGE_PROXY", ""), "Image proxy service (passthrough if empty)"), -} - -func init() { - // set shorthand parameters - const shorthand = " (shorthand)" - usage := flag.Lookup("listen").Usage + shorthand - flag.StringVar(config.ListenAddr, "l", *config.ListenAddr, usage) - usage = flag.Lookup("version").Usage + shorthand - flag.BoolVar(config.Version, "v", *config.Version, usage) -} - -// Get global configuration -func Get() *Config { - return config -} - -func getEnv(key, fallback string) string { - if value, ok := os.LookupEnv("APP_" + key); ok { - return value - } - return fallback -} - -func getIntEnv(key string, fallback int) int { - strValue := getEnv(key, strconv.Itoa(fallback)) - if value, err := strconv.Atoi(strValue); err == nil { - return value - } - return fallback -} - -func getBoolEnv(key string, fallback bool) bool { - strValue := getEnv(key, strconv.FormatBool(fallback)) - if value, err := strconv.ParseBool(strValue); err == nil { - return value - } - return fallback + ListenAddr string `flag:"listen-addr" desc:"HTTP listen address" default:":8080"` + ListenMetricsAddr string `flag:"listen-metrics" desc:"Metrics listen address"` + DB string `flag:"db" desc:"Database connection string" default:"postgres://postgres:testpwd@localhost/readflow_test?sslmode=disable"` + Broker string `flag:"broker" desc:"External event broker URI for outgoing events"` + AuthN string `flag:"authn" desc:"Authentication method (\"mock\", \"proxy\" or OIDC if URL)" default:"https://login.nunux.org/auth/realms/readflow"` + PublicURL string `flag:"public-url" desc:"Public URL" default:"https://api.readflow.app"` + LogPretty bool `flag:"log-pretty" desc:"Output human readable logs" default:"false"` + LogLevel string `flag:"log-level" desc:"Log level (debug, info, warn, error)" default:"info"` + SentryDSN string `flag:"sentry-dsn" desc:"Sentry DSN URL"` + ImageProxy string `flag:"image-proxy" desc:"Image proxy service (passthrough if empty)"` } diff --git a/pkg/config/expvars.go b/pkg/config/expvars.go index e92d4d0bf..03e4d822e 100644 --- a/pkg/config/expvars.go +++ b/pkg/config/expvars.go @@ -2,32 +2,19 @@ package config import ( "expvar" - "time" ) -var conf = expvar.NewMap("config") +var configMap = expvar.NewMap("config") -func getConfString(val *string) func() interface{} { - return func() interface{} { - if val == nil { - return nil - } - return *val - } +func exportConfigVar(key, value string) { + configMap.Set(key, new(expvar.String)) + configMap.Get(key).(*expvar.String).Set(value) } -func getConfDur(val *time.Duration) func() interface{} { - return func() interface{} { - if val == nil { - return nil - } - return val.String() - } -} - -func init() { - conf.Set("addr", expvar.Func(getConfString(config.ListenAddr))) - conf.Set("authn", expvar.Func(getConfString(config.AuthN))) - conf.Set("public-url", expvar.Func(getConfString(config.PublicURL))) - conf.Set("image-proxy", expvar.Func(getConfString(config.ImageProxy))) +// ExportVars export some configuration variables to expvar +func ExportVars(conf Config) { + exportConfigVar("addr", conf.ListenAddr) + exportConfigVar("authn", conf.ListenAddr) + exportConfigVar("public-url", conf.PublicURL) + exportConfigVar("image-proxy", conf.ImageProxy) } diff --git a/pkg/config/flag/bind.go b/pkg/config/flag/bind.go new file mode 100644 index 000000000..3f031b93c --- /dev/null +++ b/pkg/config/flag/bind.go @@ -0,0 +1,117 @@ +package configflag + +import ( + "flag" + "fmt" + "os" + "reflect" + "strconv" + "strings" + "time" +) + +// Bind conf struct tags with flags +func Bind(conf interface{}, prefix string) error { + rv := reflect.ValueOf(conf) + for rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface { + rv = rv.Elem() + } + typ := rv.Type() + + for i := 0; i < typ.NumField(); i++ { + fieldType := typ.Field(i) + field := rv.Field(i) + + var key, desc, val string + // Get field key from struct tag + if tag, ok := fieldType.Tag.Lookup("flag"); ok { + key = tag + } else { + continue + } + // Get field description from struct tag + if tag, ok := fieldType.Tag.Lookup("desc"); ok { + desc = tag + } + // Get field value from struct tag + if tag, ok := fieldType.Tag.Lookup("default"); ok { + val = tag + } + + // Get field value and description from environment variable + if fieldType.Type.Kind() == reflect.Slice { + val = getEnvValue(prefix, key+"s", val) + desc = getEnvDesc(prefix, key+"s", desc) + } else { + val = getEnvValue(prefix, key, val) + desc = getEnvDesc(prefix, key, desc) + } + + // Get field value by reflection from struct definition + // And bind value to command line flag + switch fieldType.Type.Kind() { + case reflect.String: + field.SetString(val) + ptr, _ := field.Addr().Interface().(*string) + flag.StringVar(ptr, key, val, desc) + case reflect.Bool: + bVal, err := strconv.ParseBool(val) + if err != nil { + return fmt.Errorf("Invalid boolean value for %s: %v", key, err) + } + field.SetBool(bVal) + ptr, _ := field.Addr().Interface().(*bool) + flag.BoolVar(ptr, key, bVal, desc) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if field.Kind() == reflect.Int64 && field.Type().PkgPath() == "time" && field.Type().Name() == "Duration" { + d, err := time.ParseDuration(val) + if err != nil { + return fmt.Errorf("Invalid duration value for %s: %v", key, err) + } + field.SetInt(int64(d)) + ptr, _ := field.Addr().Interface().(*time.Duration) + flag.DurationVar(ptr, key, d, desc) + } else { + i64Val, err := strconv.ParseInt(val, 0, fieldType.Type.Bits()) + if err != nil { + return fmt.Errorf("Invalid number value for %s: %v", key, err) + } + field.SetInt(i64Val) + ptr, _ := field.Addr().Interface().(*int) + flag.IntVar(ptr, key, int(i64Val), desc) + } + case reflect.Slice: + sliceType := field.Type().Elem() + if sliceType.Kind() == reflect.String { + var sl []string + if len(strings.TrimSpace(val)) != 0 { + vals := strings.Split(val, ",") + sl = make([]string, len(vals), len(vals)) + for i, v := range vals { + sl[i] = v + } + } + field.Set(reflect.ValueOf(sl)) + ptr, _ := field.Addr().Interface().(*[]string) + af := newArrayFlags(ptr) + flag.Var(af, key, desc) + } + } + } + return nil +} + +func getEnvKey(prefix, key string) string { + return ToScreamingSnake(prefix + "_" + key) +} + +func getEnvValue(prefix, key, fallback string) string { + if value, ok := os.LookupEnv(getEnvKey(prefix, key)); ok { + return value + } + return fallback +} + +func getEnvDesc(prefix, key, desc string) string { + return fmt.Sprintf("%s (env: %s)", desc, getEnvKey(prefix, key)) +} diff --git a/pkg/config/flag/snake.go b/pkg/config/flag/snake.go new file mode 100644 index 000000000..136d73a37 --- /dev/null +++ b/pkg/config/flag/snake.go @@ -0,0 +1,94 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Ian Coleman + * Copyright (c) 2018 Ma_124, + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, Subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or Substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// Package configflag converts strings to snake_case or CamelCase +package configflag + +import ( + "strings" +) + +// ToSnake converts a string to snake_case +func ToSnake(s string) string { + return ToDelimited(s, '_') +} + +// ToScreamingSnake converts a string to SCREAMING_SNAKE_CASE +func ToScreamingSnake(s string) string { + return ToScreamingDelimited(s, '_', true) +} + +// ToKebab converts a string to kebab-case +func ToKebab(s string) string { + return ToDelimited(s, '-') +} + +// ToScreamingKebab converts a string to SCREAMING-KEBAB-CASE +func ToScreamingKebab(s string) string { + return ToScreamingDelimited(s, '-', true) +} + +// ToDelimited converts a string to delimited.snake.case (in this case `del = '.'`) +func ToDelimited(s string, del uint8) string { + return ToScreamingDelimited(s, del, false) +} + +// ToScreamingDelimited converts a string to SCREAMING.DELIMITED.SNAKE.CASE (in this case `del = '.'; screaming = true`) or delimited.snake.case (in this case `del = '.'; screaming = false`) +func ToScreamingDelimited(s string, del uint8, screaming bool) string { + // s = addWordBoundariesToNumbers(s) + s = strings.Trim(s, " ") + n := "" + for i, v := range s { + // treat acronyms as words, eg for JSONData -> JSON is a whole word + nextCaseIsChanged := false + if i+1 < len(s) { + next := s[i+1] + if (v >= 'A' && v <= 'Z' && next >= 'a' && next <= 'z') || (v >= 'a' && v <= 'z' && next >= 'A' && next <= 'Z') { + nextCaseIsChanged = true + } + } + + if i > 0 && n[len(n)-1] != del && nextCaseIsChanged { + // add underscore if next letter case type is changed + if v >= 'A' && v <= 'Z' { + n += string(del) + string(v) + } else if v >= 'a' && v <= 'z' { + n += string(v) + string(del) + } + } else if v == ' ' || v == '_' || v == '-' || v == '/' { + // replace spaces/underscores with delimiters + n += string(del) + } else { + n = n + string(v) + } + } + + if screaming { + n = strings.ToUpper(n) + } else { + n = strings.ToLower(n) + } + return n +} diff --git a/pkg/config/flag/test/bind_test.go b/pkg/config/flag/test/bind_test.go new file mode 100644 index 000000000..4c249422c --- /dev/null +++ b/pkg/config/flag/test/bind_test.go @@ -0,0 +1,36 @@ +package test + +import ( + "flag" + "testing" + "time" + + "github.com/ncarlier/readflow/pkg/assert" + configflag "github.com/ncarlier/readflow/pkg/config/flag" +) + +type sampleConfig struct { + Label string `flag:"label" desc:"String parameter" default:"foo"` + Override string `flag:"override" desc:"String parameter to override" default:"bar"` + Count int `flag:"count" desc:"Number parameter" default:"2"` + Debug bool `flag:"debug" desc:"Boolean parameter" default:"false"` + Timer time.Duration `flag:"timer" desc:"Duration parameter" default:"30s"` + Array []string `flag:"array" desc:"Array parameter" default:"foo,bar"` + OverrideArray []string `flag:"override-array" desc:"Array parameter to override" default:"foo"` +} + +func TestFlagBinding(t *testing.T) { + conf := &sampleConfig{} + err := configflag.Bind(conf, "FOO") + flag.CommandLine.Parse([]string{"-override", "test", "-override-array", "a", "-override-array", "b"}) + assert.Nil(t, err, "error should be nil") + assert.Equal(t, "foo", conf.Label, "") + assert.Equal(t, "test", conf.Override, "") + assert.Equal(t, 2, conf.Count, "") + assert.Equal(t, false, conf.Debug, "") + assert.Equal(t, time.Second*30, conf.Timer, "") + assert.Equal(t, 2, len(conf.Array), "") + assert.Equal(t, "foo", conf.Array[0], "") + assert.Equal(t, 2, len(conf.OverrideArray), "") + assert.Equal(t, "a", conf.OverrideArray[0], "") +} diff --git a/pkg/config/flag/types.go b/pkg/config/flag/types.go new file mode 100644 index 000000000..237454807 --- /dev/null +++ b/pkg/config/flag/types.go @@ -0,0 +1,40 @@ +package configflag + +import "strings" + +// arrayFlags contains an array of command flags +type arrayFlags struct { + items *[]string + reset bool +} + +func newArrayFlags(items *[]string) *arrayFlags { + return &arrayFlags{ + items: items, + reset: true, + } +} + +// Values return the values of a flag array +func (i *arrayFlags) Values() []string { + if i.items == nil { + return []string{} + } + return *i.items +} + +// String return the string value of a flag array +func (i *arrayFlags) String() string { + return strings.Join(i.Values(), ",") +} + +// Set is used to add a value to the flag array +func (i *arrayFlags) Set(value string) error { + if i.reset { + i.reset = false + *i.items = []string{value} + } else { + *i.items = append(*i.items, value) + } + return nil +} diff --git a/pkg/event-broker/broker.go b/pkg/event-broker/broker.go index faaae6c17..bd4f41246 100644 --- a/pkg/event-broker/broker.go +++ b/pkg/event-broker/broker.go @@ -33,7 +33,7 @@ func Configure(uri string) (Broker, error) { } log.Info().Str("component", "broker").Str("uri", u.String()).Msg("using HTTP event broker") default: - return nil, fmt.Errorf("unsuported event broker: %s", u.Scheme) + return nil, fmt.Errorf("unsupported event broker: %s", u.Scheme) } return instance, nil } diff --git a/pkg/event-broker/types.go b/pkg/event-broker/types.go index 96e3947d2..5f0cc3d26 100644 --- a/pkg/event-broker/types.go +++ b/pkg/event-broker/types.go @@ -5,7 +5,6 @@ import ( "encoding/json" "time" - "github.com/ncarlier/readflow/pkg/config" "github.com/ncarlier/readflow/pkg/event" "github.com/ncarlier/readflow/pkg/model" ) @@ -43,7 +42,8 @@ func NewUserEvent(user model.User) *UserEvent { evt.Action = event.CreateUser evt.Issue = Issue{ Date: time.Now(), - URL: config.Get().PublicURL, + // TODO: set proper issuer URL + // URL: conf.PublicURL, } return evt } diff --git a/pkg/version/version.go b/pkg/version/version.go index 9d8e6b1a3..eedb31597 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -1,6 +1,7 @@ package version import ( + "flag" "fmt" ) @@ -13,16 +14,17 @@ var GitCommit = "n/a" // Built is the built date var Built = "n/a" +// ShowVersion is the flag used to print version +var ShowVersion = flag.Bool("version", false, "Print version") + // Print version to stdout func Print() { fmt.Printf(`Version: %s Git commit: %s Built: %s -Copyright (C) 2019 Nunux, Org. +Copyright (C) 2020 Nicolas Carlier This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. - -Written by Nicolas Carlier. `, Version, GitCommit, Built) }