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

Document config #75

Merged
merged 7 commits into from
Oct 23, 2024
Merged
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
85 changes: 85 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
// Package config provides utilities for configuration parsing and loading.
// It includes functionality for handling command-line flags and loading configuration from YAML files,
// with additional support for setting default values and validation.
// Additionally, it provides a struct that defines common settings for a TLS client.
//
// Example usage:
//
// type Config struct {
// ServerAddress string `yaml:"server_address" default:"localhost:8080"`
// TLS config.TLS `yaml:",inline"`
// }
//
// // Validate implements the Validator interface.
// func (c *Config) Validate() error {
// if _, _, err := net.SplitHostPort(c.ServerAddress); err != nil {
// return errors.Wrapf(err, "invalid server address: %s", c.ServerAddress)
// }
//
// return nil
// }
//
// type Flags struct {
// Config string `short:"c" long:"config" description:"Path to config file" required:"true"`
// }
//
// func main() {
// var flags Flags
// if err := config.ParseFlags(&flags); err != nil {
// log.Fatalf("error parsing flags: %v", err)
// }
//
// var cfg Config
// if err := config.FromYAMLFile(flags.Config, &cfg); err != nil {
// log.Fatalf("error loading config: %v", err)
// }
//
// tlsCfg, err := cfg.TLS.MakeConfig("icinga.com")
// if err != nil {
// log.Fatalf("error creating TLS config: %v", err)
// }
//
// // ...
// }
package config

import (
Expand All @@ -20,6 +63,33 @@ var ErrInvalidArgument = stderrors.New("invalid argument")
// FromYAMLFile parses the given YAML file and stores the result
// in the value pointed to by v. If v is nil or not a pointer,
// FromYAMLFile returns an [ErrInvalidArgument] error.
// It is possible to define default values via the struct tag `default`.
// The function also validates the configuration using the Validate method
// of the provided [Validator] interface.
//
// Example usage:
//
// type Config struct {
// ServerAddress string `yaml:"server_address" default:"localhost:8080"`
// }
//
// // Validate implements the Validator interface.
// func (c *Config) Validate() error {
// if _, _, err := net.SplitHostPort(c.ServerAddress); err != nil {
// return errors.Wrapf(err, "invalid server address: %s", c.ServerAddress)
// }
//
// return nil
// }
//
// func main() {
// var cfg Config
// if err := config.FromYAMLFile("config.yml", &cfg); err != nil {
// log.Fatalf("error loading config: %v", err)
// }
//
// // ...
// }
func FromYAMLFile(name string, v Validator) error {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
Expand Down Expand Up @@ -86,6 +156,21 @@ func FromEnv(v Validator, options EnvOptions) error {
// ParseFlags prints the help message to [os.Stdout] and exits.
// Note that errors are not printed automatically,
// so error handling is the sole responsibility of the caller.
//
// Example usage:
//
// type Flags struct {
// Config string `short:"c" long:"config" description:"Path to config file" required:"true"`
// }
//
// func main() {
// var flags Flags
// if err := config.ParseFlags(&flags); err != nil {
// log.Fatalf("error parsing flags: %v", err)
// }
//
// // ...
// }
func ParseFlags(v any) error {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
Expand Down
12 changes: 12 additions & 0 deletions config/contracts.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
package config

// Validator is an interface that must be implemented by any configuration struct used in [FromYAMLFile].
//
// The Validate method checks the configuration values and
// returns an error if any value is invalid or missing when required.
//
// For fields such as file paths, the responsibility of Validate is limited to
// verifying the presence and format of the value,
// not checking external conditions like file existence or readability.
// This principle applies generally to any field where external validation
// (e.g., network availability, resource accessibility) is beyond the scope of basic configuration validation.
type Validator interface {
// Validate checks the configuration values and
// returns an error if any value is invalid or missing when required.
Validate() error
}
49 changes: 42 additions & 7 deletions config/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,51 @@ import (
"os"
)

// TLS provides TLS configuration options.
// TLS represents configuration for a TLS client.
// It provides options to enable TLS, specify certificate and key files,
// CA certificate, and whether to skip verification of the server's certificate chain and host name.
// Use the [TLS.MakeConfig] method to assemble a [*tls.Config] from the TLS struct.
//
// Example usage:
//
// func main() {
// tlsConfig := &config.TLS{
// Enable: true,
// Cert: "path/to/cert.pem",
// Key: "path/to/key.pem",
// Ca: "path/to/ca.pem",
// Insecure: false,
// }
//
// cfg, err := tlsConfig.MakeConfig("example.com")
// if err != nil {
// log.Fatalf("error creating TLS config: %v", err)
// }
//
// // ...
// }
type TLS struct {
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"`
// Enable indicates whether TLS is enabled.
Enable bool `yaml:"tls" env:"TLS"`

// Cert is the path to the TLS certificate file. If provided, Key must also be specified.
Cert string `yaml:"cert" env:"CERT"`

// Key is the path to the TLS key file. If specified, Cert must also be provided.
Key string `yaml:"key" env:"KEY"`

// Ca is the path to the CA certificate file.
Ca string `yaml:"ca" env:"CA"`

// Insecure indicates whether to skip verification of the server's certificate chain and host name.
// If true, any certificate presented by the server and any host name in that certificate is accepted.
// In this mode, TLS is susceptible to machine-in-the-middle attacks unless custom verification is used.
Insecure bool `yaml:"insecure" env:"INSECURE"`
}

// MakeConfig assembles a tls.Config from t and serverName.
// MakeConfig assembles a [*tls.Config] from the TLS struct and the provided serverName.
// It returns a configured *tls.Config or an error if there are issues with the provided TLS settings.
// If TLS is not enabled (t.Enable is false), it returns nil without an error.
func (t *TLS) MakeConfig(serverName string) (*tls.Config, error) {
if !t.Enable {
return nil, nil
Expand Down
Loading