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"
|
"syscall"
|
||||||
|
|
||||||
"github.com/nemunaire/repeater/internal/app"
|
"github.com/nemunaire/repeater/internal/app"
|
||||||
|
"github.com/nemunaire/repeater/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed all:static
|
//go:embed all:static
|
||||||
var assets embed.FS
|
var assets embed.FS
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Load and parse options
|
||||||
|
cfg, err := config.ConsolidateConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Create application instance
|
// Create application instance
|
||||||
application := app.New(assets)
|
application := app.New(assets)
|
||||||
|
|
||||||
|
|
@ -34,7 +41,7 @@ func main() {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Start the server
|
// 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)
|
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