From a57ab31b71173c65d77c704eff06b3f1e8586e83 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Mon, 13 Jul 2020 21:39:24 -0700 Subject: [PATCH] Initial commit --- LICENSE.md | 9 +++++ README.md | 80 +++++++++++++++++++++++++++++++++++++++++ example/defaults.yml | 23 ++++++++++++ example/main.go | 54 ++++++++++++++++++++++++++++ example/settings.yml | 11 ++++++ go.mod | 5 +++ go.sum | 3 ++ yamlsettings.go | 50 ++++++++++++++++++++++++++ yamlsettings_test.go | 84 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 319 insertions(+) create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 example/defaults.yml create mode 100644 example/main.go create mode 100644 example/settings.yml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 yamlsettings.go create mode 100644 yamlsettings_test.go diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..dca7e6a --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +# MIT License + +Copyright 2020 Noah Petherbridge + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c6fe08 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# YamlSettings for Go + +This is a config file manager for Go, inspired by Python +[yamlsettings](https://github.com/KyleJamesWalker/yamlsettings). + +## Example + +Your Go code: + +```go +import "git.kirsle.net/go/yamlsettings" + +// Typedef for your config object. +type Config struct { + Database struct { + Host string `yaml:"host"` + Port string `yaml:"port"` + User string `yaml:"user"` + Password string `yaml:"passwd"` + } `yaml:"database"` + + Redis struct { + Host string `yaml:"redis_host"` + Port int `yaml:"redis_port"` + } `yaml:"redis"` + + Debug bool `yaml:"DEBUG"` + SecretKey string `yaml:"SECRET_KEY"` +} + +func main() { + // Marshal your app config from the defaults + settings files. + // Only the defaults.yml should exist on disk; the settings.yml is + // optional and will be ignored if not found. + var setting Config + err := yamlsettings.LoadFiles( + "defaults.yml", "settings.yml", + &setting, + ) + if err != nil { + panic(err) + } + + fmt.Printf("Redis host: %s\n", settings.Redis.Host) +} +``` + +Your defaults.yml file can be committed to version control. It uses sensible +defaults and has dummy text in place of passwords and needed configuration: + +```yaml +--- +database: + host: "localhost" + port: 5432 + user: "postgres" + passwd: "postgres" +redis: + host: "localhost" + port: 6379 +DEBUG: false +SECRET_KEY: "!! CHANGE ME !!" +``` + +The settings.yml file can override values from the defaults file. This +file goes in your `.gitignore` to avoid accidentally committing secrets +to version control! + +```yaml +--- +database: + host: "pgprod.example.com" + user: "pgprod_admin" + passwd: "big secret long password" +SECRET_KEY: "fd09s0sfv0dhgf098sasd" +``` + +## License + +MIT. © 2020 Noah Petherbridge. diff --git a/example/defaults.yml b/example/defaults.yml new file mode 100644 index 0000000..5e79cdb --- /dev/null +++ b/example/defaults.yml @@ -0,0 +1,23 @@ +--- +# Program Defaults, do not edit this file!!! +# All values should be overridden in the following ways: +# 1. In the 'settings.yaml' file. +# 2. With environment variables. Example myproj.databases.primary_sql.user can +# be overridden with MYPROJ_DATABASES_PRIMARY_SQL_USER.: +databases: + extra_setting: "not specified" + primary_sql: + user: my_user + passwd: password_here + host: db-bouncer-01.postgres.com:5432 + db: postgres + compress: true + engine: postgresql2 + redis: + redis_host: 127.0.0.1 + redis_port: 6379 +app_config: + DEBUG: false + SECRET_KEY: hard key to guess and keep values secret +debug_sql: false +debug_profiler: false diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..eeb3922 --- /dev/null +++ b/example/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "encoding/json" + "fmt" + + "git.kirsle.net/go/yamlsettings" +) + +type MyProj struct { + Databases struct { + PrimarySQL struct { + User string `yaml:"user"` + Passwd string `yaml:"passwd"` + Host string `yaml:"host"` + DB string `yaml:"db"` + Compress bool `yaml:"compress"` + Engine string `yaml:"engine"` + } `yaml:"primary_sql"` + + Redis struct { + Host string `yaml:"redis_host"` + Port int `yaml:"redis_port"` + } `yaml:"redis"` + } `yaml:"databases"` + + AppConfig struct { + Debug bool `yaml:"DEBUG"` + SecretKey string `yaml:"SECRET_KEY"` + } `yaml:"app_config"` + + DebugSQL bool `yaml:"debug_sql"` + DebugProfiler bool `yaml:"debug_profiler"` +} + +func (c MyProj) String() string { + output, _ := json.MarshalIndent(c, "", "\t") + return string(output) +} + +func main() { + var config MyProj + err := yamlsettings.LoadFiles( + "defaults.yml", + "settings.yml2", + &config, + ) + if err != nil { + panic(err) + } + + // Print out the final settings to terminal as JSON. + fmt.Println(config) +} diff --git a/example/settings.yml b/example/settings.yml new file mode 100644 index 0000000..67a9a01 --- /dev/null +++ b/example/settings.yml @@ -0,0 +1,11 @@ +--- +databases: + primary_sql: + user: root + passwd: god + redis: + redis_host: redis.example.com +app_config: + DEBUG: true + SECRET_KEY: sdfasjksdfASFAS23423f@#$%!$#VR@%UQ% +debug_sql: true diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fd8e861 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.kirsle.net/go/yamlsettings + +go 1.14 + +require gopkg.in/yaml.v2 v2.3.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8fabe8d --- /dev/null +++ b/go.sum @@ -0,0 +1,3 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/yamlsettings.go b/yamlsettings.go new file mode 100644 index 0000000..fb1e553 --- /dev/null +++ b/yamlsettings.go @@ -0,0 +1,50 @@ +package yamlsettings + +import ( + "io" + "os" + + "gopkg.in/yaml.v2" +) + +// LoadFiles marshals your app settings from the default.yml overridden +// by the settings.yml file. +func LoadFiles(defaultsFile, settingsFile string, v interface{}) error { + // Read the defaults file. + if err := LoadFile(defaultsFile, v); err != nil { + return err + } + + // Read the settings.yml. It's OK if it doesn't exist. + if err := LoadFile(settingsFile, v); err != nil && !os.IsNotExist(err) { + return err + } + + return nil +} + +// LoadFile opens and marshals YAML settings from a single config file. +func LoadFile(filename string, v interface{}) error { + fh, err := os.Open(filename) + if err != nil { + return err + } + + dec := yaml.NewDecoder(fh) + return dec.Decode(v) +} + +// LoadReaders marshals your app settings from the defaults and settings +// readers, so you can feed in YAML data from sources other than files +// on disk. +func LoadReaders(defaults, settings io.Reader, v interface{}) error { + if defaults != nil { + if err := yaml.NewDecoder(defaults).Decode(v); err != nil { + return err + } + } + if settings != nil { + return yaml.NewDecoder(settings).Decode(v) + } + return nil +} diff --git a/yamlsettings_test.go b/yamlsettings_test.go new file mode 100644 index 0000000..d71676b --- /dev/null +++ b/yamlsettings_test.go @@ -0,0 +1,84 @@ +package yamlsettings_test + +import ( + "bytes" + "fmt" + "testing" + + "git.kirsle.net/go/yamlsettings" +) + +func TestLoadReaders(t *testing.T) { + type Settings struct { + Databases struct { + PrimarySQL struct { + User string `yaml:"user"` + Passwd string `yaml:"passwd"` + Host string `yaml:"host"` + DB string `yaml:"db"` + Compress bool `yaml:"compress"` + Engine string `yaml:"engine"` + } `yaml:"primary_sql"` + + Redis struct { + Host string `yaml:"redis_host"` + Port int `yaml:"redis_port"` + } `yaml:"redis"` + } `yaml:"databases"` + + AppConfig struct { + Debug bool `yaml:"DEBUG"` + SecretKey string `yaml:"SECRET_KEY"` + } `yaml:"app_config"` + + DebugSQL bool `yaml:"debug_sql"` + DebugProfiler bool `yaml:"debug_profiler"` + } + + // The defaults.yml file you can commit to version control, + // it's used as the template config that contains all options, + // has sensible defaults where possible or "CHANGE_ME" where not. + var ( + defaultsYaml = bytes.NewBuffer([]byte(`--- +# Program Defaults, do not edit this file!!! +# All values should be overridden in the settings.yml file. +databases: + primary_sql: + user: my_user + passwd: password_here + host: db-bouncer-01.postgres.com:5432 + db: postgres + compress: true + engine: postgresql2 + redis: + redis_host: 127.0.0.1 + redis_port: 6379 +app_config: + DEBUG: false + SECRET_KEY: hard key to guess and keep values secret +debug_sql: false +debug_profiler: false`)) + settingsYaml = bytes.NewBuffer([]byte(`--- +databases: + primary_sql: + user: root + passwd: god + redis: + redis_host: redis.service.com +app_config: + DEBUG: true + SECRET_KEY: sdfasjksdfASFAS23423f@#$%!$#VR@%UQ% +debug_sql: true`)) + ) + + // Load the defaults and the optional settings file. + // yamlsettings.LoadFiles("defaults.yml", "settings.yml", &config) + var config Settings + yamlsettings.LoadReaders(defaultsYaml, settingsYaml, &config) + fmt.Printf("Redis host: %s\n", config.Databases.Redis.Host) + fmt.Printf("Secret: %s\n", config.AppConfig.SecretKey) + + // Load from yaml files on disk. + yamlsettings.LoadFiles("example/defaults.yml", "example/settings.yml", &config) + fmt.Printf("Redis host: %s\n", config.Databases.Redis.Host) +}