A flexible and composable configuration library for Go that doesn't suck

croconf

A flexible and composable configuration library for Go that doesn't suck

Ned's spec for Go configuration which doesn't suck:

  1. Fully testable: there is no relying on globals or os directly, everything is passed as parameters (e.g. it receives os.Environ() and os.Args, it doesn't directly access them).
  2. Supports layered configs: users can construct hierarchies of json/yaml/toml/env vars/CLI flags/etc., and the library will merge them
  3. Uses normal and simple Go types:
    • the end value should be a plain old Go struct with plain old Go types (e.g. string, int, etc.)
    • at the same time, users should have a way to query and access metadata to answer questions like "Has field X been changed?", "What is the default value of field Y?", etc.
    • there will be no custom types to check if an entry was set (i.e. no null.Int.Valid BS...)
    • the final consolidation result is a plain Go struct and a separate metadata layer allows users to reason about the config and answer the questions above
    • custom types will only be needed for complex options (and the library will have nicely defined interfaces for supporting custom types)
  4. This needs to be composable, in all three dimensions:
    • config values can be consolidated between multiple config layers (e.g. CLI flag overwrites env. var which overwrites JSON option, etc.)
    • configs can be combined (e.g. if I have configs for type A struct { ... } and type B struct { ...}, this should also be easy to make into a valid config: type C struct {A; B}
    • a config can contain another config as a property, i.e. you should be able to nest configs, and one config can be encapsulated in a single property of the other
  5. Everything is as type safe and compile-time-error-able as possible:
    • static go types and interfaces >> type assertions >> reflection
    • we won't use struct tags! type-safe methods/properties >>> struct tags
  6. Batteries built-in (e.g. support for JSON, env vars, CLI flags, basic data types), but completely extensible
    • Supports validation, has to have user-friendly error messages (without Go implementation details)
    • Supports warnings for things like deprecated variables
  7. An easy way to marshal the whole consolidated config, e.g. to a JSON file. Ideally, we should be able to specify whether we want only the changed values, or all of the values (incl. any default ones).
  8. Stretch goal: the metadata should be rich enough so that a whole application framework like cobra can be built on top of it, including generation of man pages and auto-completion
  9. We should only parse anything once

Misc thoughts:

  • The building of the final config can be a multi-step process. For example, you may first need to understand which sub-command is going to be used (e.g. k6 run, k6 cloud, k6 resume, etc.), before you actually know what config options are even possible.
  • At some of the config building steps, we need to be able to check some config sources for uknown/unused options. For example:
    • at the first step when we're determining the sub-command, we don't care that there will be unknown CLI flags, we expect that
    • at the next step, when we know the sub-command and all of its needed CLI flags and environment variables, an uknown CLI flag should be an error, but an unknown env var shouldn't be.
    • an unknown JSON option might be an error in some places, but for compatibility reasons, a warning in others...
  • If the config objects are pointers, and config properties are values in the config structs but passed by pointers to the croconf functions, you have these pros and cons:
    • pro: mostly have a very type safe API without reflection/type assertion
    • pro: you can use the property pointers as keys in the "Has field X been changed?" questions
    • con: some config user will be able to modify the config deep in the codebase
    • pro/con: you can copy the config values by just copying the struct, but if you have nested structs by pointer or a crocon.Manager (if we stick with that), it will be a big problem...
  • Error reporting is tricky... we want it to be as user-friendly as possible, bit there are at least 3 distinct parts:
    1. parsing errors, e.g. a completely invalid JSON/YAML/etc. file - we can't continue from this, we can only show as many details as possible
    2. parsing and type errors for specific fields (e.g. trying to pass a string as an int) - ideally, we should be able to collect all of these errors from all of the sources (CLI, env vars, JSON, etc.) and show them in a single user-friendly list
      • this is probably also the step where we can sometimes complain that there are unknown options (e.g. unknown CLI flag X or unknown JSON option Y, if we know what all of the possible options/values can be at this step)
    3. validation - this is tricky, it's the last step (i.e. we only validate the final consolidated values) and validation logic can spread between multiple fields (e.g. option X should be less than or equal to option Y)

Proposed TODO:

  1. Figure out a usable Go API (e.g. with initial support for just a few Go types like string, int64 and bool that satisfies the criteria ⬆️ 😅
  2. Write a PoC with some tests and mock real-life usage examples
  3. Iterate and expand on ⬆️
  4. Support all types (incl. custom types) and multiple sources
  5. Figure out an appropriate Go module structure
  6. Polish, set up GitHub Actions CI, etc.
  7. Profit
Owner
k6
Load testing for engineering teams
k6
Comments
  • Binding refactoring

    Binding refactoring

    It will probably be easier to read this commit by commit... :sweat_smile:

    The biggest change is that now we now have a clear difference in the Binding (the callback that reads some source (or other) value and saves it to the Destination) and the Binder (the thing that creates the Binding). Array and number bindings are now also very similar to the other simpler bindings :tada: The bitSize checking is now only done on the frontend side, sources are no longer concerned with it (because they don't need to be) :tada:

    I still want to tweak things things (potentially how default values are handled) and add more fields (esp. if I can reduce the boilerplate), so I may push a few more commits here... :sweat_smile:

  • Add a PoC for slice fields

    Add a PoC for slice fields

    I am not super satisfied with this, but it will probably do for now, so yey! :tada: :sweat_smile:

    I've only implemented slice support in the env vars and JSON sources so far, and I am not sure how to allow the configuration of different delimiters for the env vars, so please share if you have any ideas.

  • Add unified number parsing functions in sources

    Add unified number parsing functions in sources

    This preserves the completely type-safe "frontends" (i.e. Field constructors like NewInt64Field(dest *int64, ...)), but makes the job of implementing a Source much simpler. By using the biggest types, we don't need to implement a method for every single base Go number type (int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, byte, float32, float64), instead just 3 (int64, uint64, float64) and some extra validation.

  • Basic types extension

    Basic types extension

    status for int(), uint(), float(*):

    • env vars

      • [x] code
      • [x] tests
    • [x?] json

      • [x] code
      • [x?] tests (but fails for still unknown reasons)
    • [ ] flags:

      • [x] code
      • [ ] tests
  • `TODO`

    `TODO`

    We can do: - base types besides int64 and string: float{,32,64}, int, uint, etc. - tests for the current behavior and :arrow_up: - parsing CLI flags

    Items with major unknowns: - manager options - default values (for --help) - slices (e.g. a value []int64) - nested/deep values

  • Custom Types

    Custom Types

    What I don't like from the latest API's iteration is the requirement to re-declare the type into the To method. It's repetition and it opens to have a mismatch between types.

    I tried to implement a solution to remove it and re-use the already passed type in the first argument. I know, it requires using interface{} and type checking that is not optimal.

  • WIP proposal 2, with very basic (and ugly) CLI flag support

    WIP proposal 2, with very basic (and ugly) CLI flag support

    As you can see, pflag requires a ton of boilerplate, especially to distinguish between positional and flag arguments... :disappointed: And we don't use the majority of its features anyway. So we desperately needs a better library or to write our own CLI parser from scratch, but this serves as a useful demo for now...

  • Ned's very WIP proposal for the API

    Ned's very WIP proposal for the API

    This is very, very far from final, but it's an expansion on the ad-hoc proposal I sent in Slack, together with the rough requirements in and a basic TODO proposal the README.

  • Add a `strvals` source

    Add a `strvals` source

    github.com/kubernetes/helm/pkg/strvals is a very nice config format that allows complex hierarchical configurations to be encoded in a single comma-separated value. Pretty much meant to be a flattened YAML, from what I understand...

    We used it in several places in k6, but dropped it for simpler hand-roller parses in the few places where it was used, so we could drop the relatively big dependency (https://github.com/grafana/k6/issues/926). Still, if we can implement it in a sane manner, it will be quite useful. Especially if we can actually combine it with the environment variable and CLI flags source, it would allow for some quite flexible configurations!

Composable, observable and performant config handling for Go for the distributed processing era

Konfig Composable, observable and performant config handling for Go. Written for larger distributed systems where you may have plenty of configuration

Dec 11, 2022
go-up! A simple configuration library with recursive placeholders resolution and no magic.

go-up! A simple configuration library with placeholders resolution and no magic. go-up provides a simple way to configure an application from multiple

Nov 23, 2022
🛠 A configuration library for Go that parses environment variables, JSON files, and reloads automatically on SIGHUP
🛠 A configuration library for Go that parses environment variables, JSON files, and reloads automatically on SIGHUP

config A small configuration library for Go that parses environment variables, JSON files, and reloads automatically on SIGHUP. Example func main() {

Dec 11, 2022
Golang library for reading properties from configuration files in JSON and YAML format or from environment variables.

go-config Golang library for reading properties from configuration files in JSON and YAML format or from environment variables. Usage Create config in

Aug 22, 2022
Small library to read your configuration from environment variables

envconfig envconfig is a library which allows you to parse your configuration from environment variables and fill an arbitrary struct. See the example

Nov 3, 2022
A minimalist Go configuration library
A minimalist Go configuration library

fig fig is a tiny library for loading an application's config file and its environment into a Go struct. Individual fields can have default values def

Dec 23, 2022
go implementation of lightbend's HOCON configuration library https://github.com/lightbend/config

HOCON (Human-Optimized Config Object Notation) Configuration library for working with the Lightbend's HOCON format. HOCON is a human-friendly JSON sup

Dec 3, 2022
Golang library for managing configuration data from environment variables

envconfig import "github.com/kelseyhightower/envconfig" Documentation See godoc Usage Set some environment variables: export MYAPP_DEBUG=false export

Dec 26, 2022
Light weight, extensible configuration management library for Go. Built in support for JSON, TOML, YAML, env, command line, file, S3 etc. Alternative to viper.
Light weight, extensible configuration management library for Go. Built in support for JSON, TOML, YAML, env, command line, file, S3 etc. Alternative to viper.

koanf (pronounced conf; a play on the Japanese Koan) is a library for reading configuration from different sources in different formats in Go applicat

Jan 8, 2023
Cfginterpolator is an interpolate library in golang allowing to include data from external sources in your configuration

cfginterpolator cfginterpolator is an interpolate library in golang allowing to include data from external sources in your configuration cfginterpolat

Dec 14, 2021
Tinyini - Bare-bones Go library for reading INI-like configuration files

tinyini tinyini is a minimalistic library for parsing INI-like configuration files. example configuration file globalkey = globalvalue [section] key

Jan 10, 2022
goconfig uses a struct as input and populates the fields of this struct with parameters from command line, environment variables and configuration file.

goconfig goconfig uses a struct as input and populates the fields of this struct with parameters from command line, environment variables and configur

Dec 15, 2022
✨Clean and minimalistic environment configuration reader for Golang

Clean Env Minimalistic configuration reader Overview This is a simple configuration reading tool. It just does the following: reads and parses configu

Jan 8, 2023
An opinionated configuration loading framework for Containerized and Cloud-Native applications.
An opinionated configuration loading framework for Containerized and Cloud-Native applications.

Opinionated configuration loading framework for Containerized and 12-Factor compliant applications. Read configurations from Environment Variables, an

Dec 16, 2022
Harvest configuration, watch and notify subscriber

Harvester Harvester is a configuration library which helps setting up and monitoring configuration values in order to dynamically reconfigure your app

Dec 26, 2022
Manage local application configuration files using templates and data from etcd or consul

confd confd is a lightweight configuration management tool focused on: keeping local configuration files up-to-date using data stored in etcd, consul,

Dec 27, 2022
Jul 4, 2022
12 factor configuration as a typesafe struct in as little as two function calls

Config Manage your application config as a typesafe struct in as little as two function calls. type MyConfig struct { DatabaseUrl string `config:"DAT

Dec 13, 2022
JSON or YAML configuration wrapper with convenient access methods.

Config Package config provides convenient access methods to configuration stored as JSON or YAML. This is a fork of the original version. This version

Dec 16, 2022