Create config package and properly use Viper

This commit is contained in:
Alexandre Iooss 2020-10-11 21:35:43 +02:00
parent de5a48ded7
commit df88fd9e99
No known key found for this signature in database
GPG Key ID: 6C79278F3FCDCC02
6 changed files with 156 additions and 110 deletions

1
go.mod
View File

@ -11,4 +11,5 @@ require (
github.com/prometheus/client_golang v1.7.1
github.com/spf13/viper v1.7.1
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
gopkg.in/yaml.v2 v2.3.0
)

133
internal/config/config.go Normal file
View File

@ -0,0 +1,133 @@
package config
import (
"bytes"
"log"
"net"
"strings"
"github.com/spf13/viper"
"gitlab.crans.org/nounous/ghostream/auth"
"gitlab.crans.org/nounous/ghostream/auth/basic"
"gitlab.crans.org/nounous/ghostream/auth/ldap"
"gitlab.crans.org/nounous/ghostream/internal/monitoring"
"gitlab.crans.org/nounous/ghostream/stream/forwarding"
"gitlab.crans.org/nounous/ghostream/stream/srt"
"gitlab.crans.org/nounous/ghostream/stream/webrtc"
"gitlab.crans.org/nounous/ghostream/web"
"gopkg.in/yaml.v2"
)
// Config holds application configuration
type Config struct {
Auth auth.Options
Forwarding forwarding.Options
Monitoring monitoring.Options
Srt srt.Options
Web web.Options
WebRTC webrtc.Options
}
// New configuration with default values
func New() *Config {
return &Config{
Auth: auth.Options{
Enabled: true,
Backend: "Basic",
Basic: basic.Options{
Credentials: map[string]string{
// Demo user with password "demo"
"demo": "$2b$15$LRnG3eIHFlYIguTxZOLH7eHwbQC/vqjnLq6nDFiHSUDKIU.f5/1H6",
},
},
LDAP: ldap.Options{
URI: "ldap://127.0.0.1:389",
UserDn: "cn=users,dc=example,dc=com",
},
},
Forwarding: make(map[string][]string),
Monitoring: monitoring.Options{
Enabled: true,
ListenAddress: ":2112",
},
Srt: srt.Options{
Enabled: true,
ListenAddress: ":9710",
MaxClients: 64,
},
Web: web.Options{
Enabled: true,
Favicon: "/static/img/favicon.svg",
Hostname: "localhost",
ListenAddress: ":8080",
Name: "Ghostream",
OneStreamPerDomain: false,
ViewersCounterRefreshPeriod: 20000,
},
WebRTC: webrtc.Options{
Enabled: true,
MaxPortUDP: 10005,
MinPortUDP: 10000,
STUNServers: []string{"stun:stun.l.google.com:19302"},
},
}
}
// Load global configuration as a struct
func Load() (*Config, error) {
// Viper needs to know if a key exists in order to override it.
// See https://github.com/spf13/viper/issues/188
b, err := yaml.Marshal(New())
if err != nil {
return nil, err
}
defaultConfig := bytes.NewReader(b)
viper.SetConfigType("yaml")
if err := viper.MergeConfig(defaultConfig); err != nil {
return nil, err
}
// Overwrite configuration from file if exists
viper.SetConfigName("ghostream.yml")
viper.SetConfigType("yaml")
viper.AddConfigPath("$HOME/.ghostream")
viper.AddConfigPath("/etc/ghostream")
viper.AddConfigPath(".")
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Config file not found, ignore and use defaults
log.Print(err)
} else {
// Config file was found but another error was produced
return nil, err
}
} else {
// Config loaded
log.Printf("Using config file: %s", viper.ConfigFileUsed())
}
// Overwrite configuration from environnement variables
// Replace "." to "_" for nested structs
// e.g. GHOSTREAM_LDAP_URI will apply to Config.LDAP.URI
viper.AutomaticEnv()
viper.SetEnvPrefix("ghostream")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// Load onto struct
cfg := &Config{}
if err := viper.UnmarshalExact(cfg); err != nil {
return nil, err
}
// Copy STUN configuration to clients
cfg.Web.STUNServers = cfg.WebRTC.STUNServers
// Copy SRT server port to display it on web page
_, srtPort, err := net.SplitHostPort(cfg.Srt.ListenAddress)
if err != nil {
return nil, err
}
cfg.Web.SRTServerPort = srtPort
return cfg, nil
}

View File

@ -0,0 +1,12 @@
package config
import (
"testing"
)
func TestLoad(t *testing.T) {
_, err := Load()
if err != nil {
t.Error("Failed to load configuration:", err)
}
}

87
main.go
View File

@ -6,11 +6,9 @@ package main
import (
"log"
"net"
"strings"
"github.com/spf13/viper"
"gitlab.crans.org/nounous/ghostream/auth"
"gitlab.crans.org/nounous/ghostream/internal/config"
"gitlab.crans.org/nounous/ghostream/internal/monitoring"
"gitlab.crans.org/nounous/ghostream/stream/forwarding"
"gitlab.crans.org/nounous/ghostream/stream/srt"
@ -18,89 +16,14 @@ import (
"gitlab.crans.org/nounous/ghostream/web"
)
func loadConfiguration() {
// Load configuration from environnement variables
// Replace "." to "_" for nested structs
// e.g. GHOSTREAM_LDAP_URI will apply to Config.LDAP.URI
viper.SetEnvPrefix("ghostream")
replacer := strings.NewReplacer(".", "_")
viper.SetEnvKeyReplacer(replacer)
viper.AutomaticEnv()
// Load configuration file if exists
viper.SetConfigName("ghostream.yml")
viper.SetConfigType("yaml")
viper.AddConfigPath("$HOME/.ghostream")
viper.AddConfigPath("/etc/ghostream")
viper.AddConfigPath(".")
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Config file not found, ignore and use defaults
log.Print(err)
} else {
// Config file was found but another error was produced
log.Fatal(err)
}
} else {
// Config loaded
log.Printf("Using config file: %s", viper.ConfigFileUsed())
}
// Define configuration default values
viper.SetDefault("Auth.Enabled", true)
viper.SetDefault("Auth.Backend", "Basic")
viper.SetDefault("Auth.Basic.Credentials", map[string]string{
// Demo user with password "demo"
"demo": "$2b$15$LRnG3eIHFlYIguTxZOLH7eHwbQC/vqjnLq6nDFiHSUDKIU.f5/1H6",
})
viper.SetDefault("Auth.LDAP.URI", "ldap://127.0.0.1:389")
viper.SetDefault("Auth.LDAP.UserDn", "cn=users,dc=example,dc=com")
viper.SetDefault("Forwarding", make(map[string][]string))
viper.SetDefault("Monitoring.Enabled", true)
viper.SetDefault("Monitoring.ListenAddress", ":2112")
viper.SetDefault("Srt.Enabled", true)
viper.SetDefault("Srt.ListenAddress", ":9710")
viper.SetDefault("Srt.MaxClients", 64)
viper.SetDefault("Web.Enabled", true)
viper.SetDefault("Web.Favicon", "/static/img/favicon.svg")
viper.SetDefault("Web.Hostname", "localhost")
viper.SetDefault("Web.ListenAddress", ":8080")
viper.SetDefault("Web.Name", "Ghostream")
viper.SetDefault("Web.OneStreamPerDomain", false)
viper.SetDefault("Web.ViewersCounterRefreshPeriod", 20000)
viper.SetDefault("WebRTC.Enabled", true)
viper.SetDefault("WebRTC.MaxPortUDP", 10005)
viper.SetDefault("WebRTC.MinPortUDP", 10000)
viper.SetDefault("WebRTC.STUNServers", []string{"stun:stun.l.google.com:19302"})
// Copy STUN configuration to clients
viper.Set("Web.STUNServers", viper.Get("WebRTC.STUNServers"))
// Copy SRT server port to display it on web page
hostport := viper.GetString("Srt.ListenAddress")
_, srtPort, err := net.SplitHostPort(hostport)
if err != nil {
log.Fatalf("Failed to split host and port from %s", hostport)
}
viper.Set("Web.SRTServerPort", srtPort)
}
func main() {
// Configure logger
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
// Load configuration
loadConfiguration()
cfg := struct {
Auth auth.Options
Forwarding forwarding.Options
Monitoring monitoring.Options
Srt srt.Options
Web web.Options
WebRTC webrtc.Options
}{}
if err := viper.Unmarshal(&cfg); err != nil {
log.Fatalln("Failed to load settings", err)
cfg, err := config.Load()
if err != nil {
log.Fatalln("Failed to load configuration:", err)
}
// Init authentification
@ -123,7 +46,7 @@ func main() {
forwardingChannel := make(chan srt.Packet, 65536)
webrtcChannel := make(chan srt.Packet, 65536)
// Start stream, web and monitoring server, and stream forwarding
// Start routines
go forwarding.Serve(forwardingChannel, cfg.Forwarding)
go monitoring.Serve(&cfg.Monitoring)
go srt.Serve(&cfg.Srt, authBackend, forwardingChannel, webrtcChannel)

View File

@ -1,29 +1 @@
package main
import (
"testing"
"github.com/spf13/viper"
"gitlab.crans.org/nounous/ghostream/auth"
"gitlab.crans.org/nounous/ghostream/internal/monitoring"
"gitlab.crans.org/nounous/ghostream/stream/forwarding"
"gitlab.crans.org/nounous/ghostream/stream/srt"
"gitlab.crans.org/nounous/ghostream/stream/webrtc"
"gitlab.crans.org/nounous/ghostream/web"
)
// TestLoadConfiguration tests the configuration file loading and the init of some parameters
func TestLoadConfiguration(t *testing.T) {
loadConfiguration()
cfg := struct {
Auth auth.Options
Forwarding forwarding.Options
Monitoring monitoring.Options
Srt srt.Options
Web web.Options
WebRTC webrtc.Options
}{}
if err := viper.Unmarshal(&cfg); err != nil {
t.Fatal("Failed to load settings", err)
}
}

View File

@ -16,6 +16,11 @@ type Options map[string][]string
// Serve handles incoming packets from SRT and forward them to other external services
func Serve(inputChannel chan srt.Packet, cfg Options) {
if len(cfg) < 1 {
// No forwarding, ignore
return
}
log.Printf("Stream forwarding initialized")
ffmpegInstances := make(map[string]*exec.Cmd)
ffmpegInputStreams := make(map[string]*io.WriteCloser)