From 4a39ebb03079ee45689d621769ecaf39f5d29256 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Fri, 4 Oct 2024 11:37:16 +0200 Subject: [PATCH 1/7] Document `config#Validator` --- config/contracts.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/config/contracts.go b/config/contracts.go index 76022991..fb74bf6b 100644 --- a/config/contracts.go +++ b/config/contracts.go @@ -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 } From b420065e9f44b729f2c091f2a5fa9aefcf5f9401 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Fri, 4 Oct 2024 11:38:14 +0200 Subject: [PATCH 2/7] Document example usage for `config#FromYAMLFile()` --- config/config.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/config/config.go b/config/config.go index be67b01e..328c481a 100644 --- a/config/config.go +++ b/config/config.go @@ -20,6 +20,30 @@ 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. +// +// 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() { From 9b12f13988ababd93128d04c041e095267649118 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Fri, 4 Oct 2024 11:38:44 +0200 Subject: [PATCH 3/7] Document example usage for `config#ParseFlags()` --- config/config.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/config/config.go b/config/config.go index 328c481a..86ffeeaa 100644 --- a/config/config.go +++ b/config/config.go @@ -110,6 +110,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() { From f955717e381b06a30d815e56c740d5428815099f Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Fri, 4 Oct 2024 11:39:51 +0200 Subject: [PATCH 4/7] Extend `config#TLS` documentation --- config/tls.go | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/config/tls.go b/config/tls.go index d159c3bc..b9b2ce6b 100644 --- a/config/tls.go +++ b/config/tls.go @@ -7,16 +7,32 @@ 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. 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 From b49f0826bd42bcae93049a070f80b5b8c9c1b403 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Fri, 4 Oct 2024 11:40:25 +0200 Subject: [PATCH 5/7] Document example usage for `config#TLS` --- config/tls.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/config/tls.go b/config/tls.go index b9b2ce6b..5976b1cc 100644 --- a/config/tls.go +++ b/config/tls.go @@ -11,6 +11,25 @@ import ( // 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 indicates whether TLS is enabled. Enable bool `yaml:"tls" env:"TLS"` From afec91759e5eaf9f40909c8f5e4592b15ba7bd5a Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 7 Oct 2024 10:33:17 +0200 Subject: [PATCH 6/7] Extend `config#FromYAMLFile()` documentation --- config/config.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/config.go b/config/config.go index 86ffeeaa..78825bba 100644 --- a/config/config.go +++ b/config/config.go @@ -20,6 +20,9 @@ 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: // From 0ab410383dffacdceff9d024b87513599fe8d5af Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 7 Oct 2024 10:33:54 +0200 Subject: [PATCH 7/7] Add `config` package docs --- config/config.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/config/config.go b/config/config.go index 78825bba..28a45911 100644 --- a/config/config.go +++ b/config/config.go @@ -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 (