Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/icingadb: if --config-from-env given, load config from env vars #756

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/icinga/icingadb
go 1.22

require (
github.com/caarlos0/env/v11 v11.0.1
github.com/creasty/defaults v1.7.0
github.com/go-sql-driver/mysql v1.8.1
github.com/goccy/go-yaml v1.11.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/caarlos0/env/v11 v11.0.1 h1:A8dDt9Ub9ybqRSUF3fQc/TA/gTam2bKT4Pit+cwrsPs=
github.com/caarlos0/env/v11 v11.0.1/go.mod h1:2RC3HQu8BQqtEK3V4iHPxj0jOdWdbPpWJ6pOueeU1xM=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
Expand Down
8 changes: 7 additions & 1 deletion internal/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ func New() *Command {
os.Exit(0)
}

cfg, err := config.FromYAMLFile(flags.Config)
var cfg *config.Config
if flags.ConfigFromEnv {
cfg, err = config.FromEnv()
} else {
cfg, err = config.FromYAMLFile(flags.Config)
}

if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
Expand Down
40 changes: 31 additions & 9 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"crypto/tls"
"crypto/x509"
"github.com/caarlos0/env/v11"
"github.com/creasty/defaults"
"github.com/goccy/go-yaml"
"github.com/jessevdk/go-flags"
Expand All @@ -12,10 +13,10 @@ import (

// Config defines Icinga DB config.
type Config struct {
Database Database `yaml:"database"`
Redis Redis `yaml:"redis"`
Logging Logging `yaml:"logging"`
Retention Retention `yaml:"retention"`
Database Database `yaml:"database" envPrefix:"DATABASE_"`
Redis Redis `yaml:"redis" envPrefix:"REDIS_"`
Logging Logging `yaml:"logging" envPrefix:"LOGGING_"`
Retention Retention `yaml:"retention" envPrefix:"RETENTION_"`
}

// Validate checks constraints in the supplied configuration and returns an error if they are violated.
Expand All @@ -40,6 +41,8 @@ func (c *Config) Validate() error {
type Flags struct {
// Version decides whether to just print the version and exit.
Version bool `long:"version" description:"print version and exit"`
// ConfigFromEnv decides whether to load the config from environment variables.
ConfigFromEnv bool `long:"config-from-env" description:"load config from env vars"`
// Config is the path to the config file
Config string `short:"c" long:"config" description:"path to config file" required:"true" default:"/etc/icingadb/config.yml"`
}
Expand Down Expand Up @@ -70,6 +73,25 @@ func FromYAMLFile(name string) (*Config, error) {
return c, nil
}

// FromEnv returns a new Config value created from environment variables.
func FromEnv() (*Config, error) {
c := &Config{}

if err := defaults.Set(c); err != nil {
return nil, errors.Wrap(err, "can't set config defaults")
}

if err := env.ParseWithOptions(c, env.Options{Prefix: "ICINGADB_"}); err != nil {
return nil, errors.Wrap(err, "can't parse env vars")
}

if err := c.Validate(); err != nil {
return nil, errors.Wrap(err, "invalid configuration")
}

return c, nil
}

// ParseFlags parses CLI flags and
// returns a Flags value created from them.
func ParseFlags() (*Flags, error) {
Expand All @@ -85,11 +107,11 @@ func ParseFlags() (*Flags, error) {

// TLS provides TLS configuration options for Redis and Database.
type TLS struct {
Enable bool `yaml:"tls"`
Cert string `yaml:"cert"`
Key string `yaml:"key"`
Ca string `yaml:"ca"`
Insecure bool `yaml:"insecure"`
Enable bool `yaml:"tls" env:"TLS"`
Cert string `yaml:"cert" env:"CERT"`
Key string `yaml:"key" env:"KEY"`
Ca string `yaml:"ca" env:"CA"`
Insecure bool `yaml:"insecure" env:"INSECURE"`
}

// MakeConfig assembles a tls.Config from t and serverName.
Expand Down
14 changes: 7 additions & 7 deletions pkg/config/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import (

// Database defines database client configuration.
type Database struct {
Type string `yaml:"type" default:"mysql"`
Host string `yaml:"host"`
Port int `yaml:"port"`
Database string `yaml:"database"`
User string `yaml:"user"`
Password string `yaml:"password"`
Type string `yaml:"type" env:"TYPE" default:"mysql"`
Host string `yaml:"host" env:"HOST"`
Port int `yaml:"port" env:"PORT"`
Database string `yaml:"database" env:"DATABASE"`
User string `yaml:"user" env:"USER"`
Password string `yaml:"password" env:"PASSWORD"`
TlsOptions TLS `yaml:",inline"`
Options icingadb.Options `yaml:"options"`
Options icingadb.Options `yaml:"options" envPrefix:"OPTIONS_"`
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lippserd Can you live with all those struct tags?

}

// Open prepares the DSN string and driver configuration,
Expand Down
10 changes: 5 additions & 5 deletions pkg/config/history_retention.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (

// Retention defines configuration for history retention.
type Retention struct {
HistoryDays uint64 `yaml:"history-days"`
SlaDays uint64 `yaml:"sla-days"`
Interval time.Duration `yaml:"interval" default:"1h"`
Count uint64 `yaml:"count" default:"5000"`
Options history.RetentionOptions `yaml:"options"`
HistoryDays uint64 `yaml:"history-days" env:"HISTORY_DAYS"`
SlaDays uint64 `yaml:"sla-days" env:"SLA_DAYS"`
Interval time.Duration `yaml:"interval" env:"INTERVAL" default:"1h"`
Count uint64 `yaml:"count" env:"COUNT" default:"5000"`
Options history.RetentionOptions `yaml:"options" env:"OPTIONS"`
}

// Validate checks constraints in the supplied retention configuration and
Expand Down
8 changes: 4 additions & 4 deletions pkg/config/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import (
// Logging defines Logger configuration.
type Logging struct {
// zapcore.Level at 0 is for info level.
Level zapcore.Level `yaml:"level" default:"0"`
Output string `yaml:"output"`
Level zapcore.Level `yaml:"level" env:"LEVEL" default:"0"`
Output string `yaml:"output" env:"OUTPUT"`
// Interval for periodic logging.
Interval time.Duration `yaml:"interval" default:"20s"`
Interval time.Duration `yaml:"interval" env:"INTERVAL" default:"20s"`

logging.Options `yaml:"options"`
logging.Options `yaml:"options" env:"OPTIONS"`
}

// Validate checks constraints in the supplied Logging configuration and returns an error if they are violated.
Expand Down
8 changes: 4 additions & 4 deletions pkg/config/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import (

// Redis defines Redis client configuration.
type Redis struct {
Host string `yaml:"host"`
Port int `yaml:"port" default:"6380"`
Password string `yaml:"password"`
Host string `yaml:"host" env:"HOST"`
Port int `yaml:"port" env:"PORT" default:"6380"`
Password string `yaml:"password" env:"PASSWORD"`
TlsOptions TLS `yaml:",inline"`
Options icingaredis.Options `yaml:"options"`
Options icingaredis.Options `yaml:"options" envPrefix:"OPTIONS_"`
}

type ctxDialerFunc = func(ctx context.Context, network, addr string) (net.Conn, error)
Expand Down
10 changes: 5 additions & 5 deletions pkg/icingadb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,29 @@ type DB struct {
// Options define user configurable database options.
type Options struct {
// Maximum number of open connections to the database.
MaxConnections int `yaml:"max_connections" default:"16"`
MaxConnections int `yaml:"max_connections" env:"MAX_CONNECTIONS" default:"16"`

// Maximum number of connections per table,
// regardless of what the connection is actually doing,
// e.g. INSERT, UPDATE, DELETE.
MaxConnectionsPerTable int `yaml:"max_connections_per_table" default:"8"`
MaxConnectionsPerTable int `yaml:"max_connections_per_table" env:"MAX_CONNECTIONS_PER_TABLE" default:"8"`

// MaxPlaceholdersPerStatement defines the maximum number of placeholders in an
// INSERT, UPDATE or DELETE statement. Theoretically, MySQL can handle up to 2^16-1 placeholders,
// but this increases the execution time of queries and thus reduces the number of queries
// that can be executed in parallel in a given time.
// The default is 2^13, which in our tests showed the best performance in terms of execution time and parallelism.
MaxPlaceholdersPerStatement int `yaml:"max_placeholders_per_statement" default:"8192"`
MaxPlaceholdersPerStatement int `yaml:"max_placeholders_per_statement" env:"MAX_PLACEHOLDERS_PER_STATEMENT" default:"8192"`

// MaxRowsPerTransaction defines the maximum number of rows per transaction.
// The default is 2^13, which in our tests showed the best performance in terms of execution time and parallelism.
MaxRowsPerTransaction int `yaml:"max_rows_per_transaction" default:"8192"`
MaxRowsPerTransaction int `yaml:"max_rows_per_transaction" env:"MAX_ROWS_PER_TRANSACTION" default:"8192"`

// WsrepSyncWait enforces Galera cluster nodes to perform strict cluster-wide causality checks
// before executing specific SQL queries determined by the number you provided.
// Please refer to the below link for a detailed description.
// https://icinga.com/docs/icinga-db/latest/doc/03-Configuration/#galera-cluster
WsrepSyncWait int `yaml:"wsrep_sync_wait" default:"7"`
WsrepSyncWait int `yaml:"wsrep_sync_wait" env:"WSREP_SYNC_WAIT" default:"7"`
}

// Validate checks constraints in the supplied database options and returns an error if they are violated.
Expand Down
12 changes: 6 additions & 6 deletions pkg/icingaredis/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ type Client struct {

// Options define user configurable Redis options.
type Options struct {
BlockTimeout time.Duration `yaml:"block_timeout" default:"1s"`
HMGetCount int `yaml:"hmget_count" default:"4096"`
HScanCount int `yaml:"hscan_count" default:"4096"`
MaxHMGetConnections int `yaml:"max_hmget_connections" default:"8"`
Timeout time.Duration `yaml:"timeout" default:"30s"`
XReadCount int `yaml:"xread_count" default:"4096"`
BlockTimeout time.Duration `yaml:"block_timeout" env:"BLOCK_TIMEOUT" default:"1s"`
HMGetCount int `yaml:"hmget_count" env:"HMGET_COUNT" default:"4096"`
HScanCount int `yaml:"hscan_count" env:"HSCAN_COUNT" default:"4096"`
MaxHMGetConnections int `yaml:"max_hmget_connections" env:"MAX_HMGET_CONNECTIONS" default:"8"`
Timeout time.Duration `yaml:"timeout" env:"TIMEOUT" default:"30s"`
XReadCount int `yaml:"xread_count" env:"XREAD_COUNT" default:"4096"`
}

// Validate checks constraints in the supplied Redis options and returns an error if they are violated.
Expand Down
Loading