Handle config options
This commit is contained in:
parent
b1b9eaa028
commit
accd7e75d8
6 changed files with 192 additions and 1 deletions
|
|
@ -8,12 +8,19 @@ import (
|
|||
"syscall"
|
||||
|
||||
"github.com/nemunaire/repeater/internal/app"
|
||||
"github.com/nemunaire/repeater/internal/config"
|
||||
)
|
||||
|
||||
//go:embed all:static
|
||||
var assets embed.FS
|
||||
|
||||
func main() {
|
||||
// Load and parse options
|
||||
cfg, err := config.ConsolidateConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Create application instance
|
||||
application := app.New(assets)
|
||||
|
||||
|
|
@ -34,7 +41,7 @@ func main() {
|
|||
}()
|
||||
|
||||
// Start the server
|
||||
if err := application.Run(":8080"); err != nil {
|
||||
if err := application.Run(cfg.Bind); err != nil {
|
||||
log.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
24
internal/config/cli.go
Normal file
24
internal/config/cli.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
)
|
||||
|
||||
// declareFlags registers flags for the structure Options.
|
||||
func declareFlags(o *Config) {
|
||||
flag.StringVar(&o.Bind, "bind", ":8081", "Bind port/socket")
|
||||
}
|
||||
|
||||
// parseCLI parse the flags and treats extra args as configuration filename.
|
||||
func parseCLI(o *Config) error {
|
||||
flag.Parse()
|
||||
|
||||
for _, conf := range flag.Args() {
|
||||
err := parseFile(o, conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
79
internal/config/config.go
Normal file
79
internal/config/config.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Bind string
|
||||
}
|
||||
|
||||
// ConsolidateConfig fills an Options struct by reading configuration from
|
||||
// config files, environment, then command line.
|
||||
//
|
||||
// Should be called only one time.
|
||||
func ConsolidateConfig() (opts *Config, err error) {
|
||||
// Define defaults options
|
||||
opts = &Config{
|
||||
Bind: ":8080",
|
||||
}
|
||||
|
||||
declareFlags(opts)
|
||||
|
||||
// Establish a list of possible configuration file locations
|
||||
configLocations := []string{
|
||||
"repeater.conf",
|
||||
}
|
||||
|
||||
if home, err := os.UserConfigDir(); err == nil {
|
||||
configLocations = append(configLocations, path.Join(home, "repeater", "repeater.conf"))
|
||||
}
|
||||
|
||||
configLocations = append(configLocations, path.Join("etc", "repeater.conf"))
|
||||
|
||||
// If config file exists, read configuration from it
|
||||
for _, filename := range configLocations {
|
||||
if _, e := os.Stat(filename); !os.IsNotExist(e) {
|
||||
log.Printf("Loading configuration from %s\n", filename)
|
||||
err = parseFile(opts, filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Then, overwrite that by what is present in the environment
|
||||
err = parseEnvironmentVariables(opts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Finaly, command line takes precedence
|
||||
err = parseCLI(opts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// parseLine treats a config line and place the read value in the variable
|
||||
// declared to the corresponding flag.
|
||||
func parseLine(o *Config, line string) (err error) {
|
||||
fields := strings.SplitN(line, "=", 2)
|
||||
orig_key := strings.TrimSpace(fields[0])
|
||||
value := strings.TrimSpace(fields[1])
|
||||
|
||||
key := strings.TrimPrefix(orig_key, "REPEATER_")
|
||||
key = strings.Replace(key, "_", "-", -1)
|
||||
key = strings.ToLower(key)
|
||||
|
||||
err = flag.Set(key, value)
|
||||
|
||||
return
|
||||
}
|
||||
27
internal/config/custom.go
Normal file
27
internal/config/custom.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type URL struct {
|
||||
URL *url.URL
|
||||
}
|
||||
|
||||
func (i *URL) String() string {
|
||||
if i.URL != nil {
|
||||
return i.URL.String()
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (i *URL) Set(value string) error {
|
||||
u, err := url.Parse(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*i.URL = *u
|
||||
return nil
|
||||
}
|
||||
21
internal/config/env.go
Normal file
21
internal/config/env.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parseEnvironmentVariables analyzes all the environment variables to find
|
||||
// each one starting by REPEATER_
|
||||
func parseEnvironmentVariables(o *Config) (err error) {
|
||||
for _, line := range os.Environ() {
|
||||
if strings.HasPrefix(line, "REPEATER_") {
|
||||
err := parseLine(o, line)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in environment (%q): %w", line, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
33
internal/config/file.go
Normal file
33
internal/config/file.go
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parseFile opens the file at the given filename path, then treat each line
|
||||
// not starting with '#' as a configuration statement.
|
||||
func parseFile(o *Config, filename string) error {
|
||||
fp, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
scanner := bufio.NewScanner(fp)
|
||||
n := 0
|
||||
for scanner.Scan() {
|
||||
n += 1
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if len(line) > 0 && !strings.HasPrefix(line, "#") && strings.Index(line, "=") > 0 {
|
||||
err := parseLine(o, line)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v:%d: error in configuration: %w", filename, n, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue