Skip to content

Commit

Permalink
feat: support yaml config files
Browse files Browse the repository at this point in the history
  • Loading branch information
soerenschneider committed Mar 3, 2024
1 parent 07b1602 commit 87248bb
Show file tree
Hide file tree
Showing 16 changed files with 228 additions and 80 deletions.
37 changes: 21 additions & 16 deletions conf/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/caarlos0/env/v6"
"github.com/rs/zerolog/log"
"github.com/soerenschneider/dyndns/internal/metrics"
"gopkg.in/yaml.v3"
)

var (
Expand All @@ -27,29 +28,29 @@ var (
}

configPathPreferences = []string{
"/etc/dyndns/client.json",
"~/.dyndns/config.json",
"/etc/dyndns/client.yaml",
"~/.dyndns/config.yaml",
}
)

type ClientConf struct {
Host string `json:"host,omitempty" env:"DYNDNS_HOST" validate:"required"`
AddrFamilies []string `json:"address_families" env:"DYNDNS_ADDRESS_FAMILIES" envSeparator:";" validate:"omitempty,addrfamilies"`
KeyPairPath string `json:"keypair_path,omitempty" env:"DYNDNS_KEYPAIR_PATH" validate:"required_if=KeyPair '',omitempty,filepath"`
KeyPair string `json:"keypair,omitempty" env:"DYNDNS_KEYPAIR" validate:"required_if=KeyPairPath ''"`
MetricsListener string `json:"metrics_listen,omitempty" env:"DYNDNS_METRICS_LISTEN"`
PreferredUrls []string `json:"http_resolver_preferred_urls,omitempty" env:"DYNDNS_HTTP_RESOLVER_PREFERRED_URLS" envSeparator:";"`
FallbackUrls []string `json:"http_resolver_fallback_urls,omitempty" env:"DYNDNS_HTTP_RESOLVER_FALLBACK_URLS" envSeparator:";"`
NetworkInterface string `json:"interface,omitempty"`
Host string `yaml:"host,omitempty" env:"HOST" validate:"required"`
AddrFamilies []string `yaml:"address_families" env:"ADDRESS_FAMILIES" envSeparator:";" validate:"omitempty,addrfamilies"`
KeyPairPath string `yaml:"keypair_path,omitempty" env:"KEYPAIR_PATH" validate:"required_if=KeyPair '',omitempty,filepath"`
KeyPair string `yaml:"keypair,omitempty" env:"KEYPAIR" validate:"required_if=KeyPairPath ''"`
MetricsListener string `yaml:"metrics_listen,omitempty" env:"METRICS_LISTEN"`
PreferredUrls []string `yaml:"http_resolver_preferred_urls,omitempty" env:"HTTP_RESOLVER_PREFERRED_URLS" envSeparator:";"`
FallbackUrls []string `yaml:"http_resolver_fallback_urls,omitempty" env:"HTTP_RESOLVER_FALLBACK_URLS" envSeparator:";"`
NetworkInterface string `yaml:"interface,omitempty"`
Once bool // this is not parsed via json, it's an cli flag

HttpDispatcherConf []HttpDispatcherConfig `json:"http_dispatcher" env:"DYNDNS_HTTP_DISPATCHER_CONF"`
MqttConfig
*EmailConfig `json:"notifications"`
HttpDispatcherConf []HttpDispatcherConfig `yaml:"http_dispatcher" env:"HTTP_DISPATCHER_CONF"`
*MqttConfig `yaml:"mqtt"`
*EmailConfig `yaml:"notifications"`
}

type HttpDispatcherConfig struct {
Url string `json:"url"`
Url string `yaml:"url"`
}

func ReadClientConfig(path string) (*ClientConf, error) {
Expand All @@ -63,7 +64,7 @@ func ReadClientConfig(path string) (*ClientConf, error) {
return nil, err
}

if err := json.Unmarshal(content, &conf); err != nil {
if err := yaml.Unmarshal(content, &conf); err != nil {
return nil, err
}

Expand All @@ -78,7 +79,11 @@ func ParseClientConfEnv(clientConf *ClientConf) error {
return ret, json.Unmarshal([]byte(input), &ret)
}

return env.ParseWithFuncs(clientConf, funk)
opts := env.Options{
Prefix: "DYNDNS_",
}

return env.ParseWithFuncs(clientConf, funk, opts)
}

func getDefaultClientConfig() *ClientConf {
Expand Down
24 changes: 20 additions & 4 deletions conf/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,31 @@ func TestReadClientConfig(t *testing.T) {
wantErr bool
}{
{
name: "happy path",
name: "happy path - yaml",
args: args{"../contrib/client.yaml"},
want: &ClientConf{
Host: "my.host.tld",
AddrFamilies: []string{AddrFamilyIpv4},
KeyPairPath: "/tmp/keypair.json",
PreferredUrls: defaultHttpResolverUrls,
MetricsListener: "0.0.0.0:9191",
MqttConfig: &MqttConfig{
Brokers: []string{"ssl://mqtt.eclipseprojects.io:8883"},
ClientId: "my-client-id",
},
},
wantErr: false,
},
{
name: "happy path - json",
args: args{"../contrib/client.json"},
want: &ClientConf{
Host: "my.host.tld",
AddrFamilies: []string{AddrFamilyIpv4},
KeyPairPath: "/tmp/keypair.json",
PreferredUrls: defaultHttpResolverUrls,
MetricsListener: "0.0.0.0:9191",
MqttConfig: MqttConfig{
MqttConfig: &MqttConfig{
Brokers: []string{"ssl://mqtt.eclipseprojects.io:8883"},
ClientId: "my-client-id",
},
Expand All @@ -45,8 +61,8 @@ func TestReadClientConfig(t *testing.T) {
wantErr: true,
},
{
name: "empty json file content",
args: args{"../contrib/empty.json"},
name: "empty yaml file content",
args: args{"../contrib/empty.yaml"},
want: getDefaultClientConfig(),
wantErr: false,
},
Expand Down
22 changes: 6 additions & 16 deletions conf/mqtt.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@ package conf
import (
"crypto/tls"
"crypto/x509"
"fmt"
"os"

"github.com/rs/zerolog/log"
)

type MqttConfig struct {
Brokers []string `json:"brokers" env:"DYNDNS_BROKERS" envSeparator:";" validate:"broker"`
ClientId string `json:"client_id" env:"DYNDNS_CLIENT_ID" validate:"required_with=Brokers ''"`
CaCertFile string `json:"tls_ca_cert" env:"DYNDNS_TLS_CA" validate:"omitempty,file"`
ClientCertFile string `json:"tls_client_cert" env:"DYNDNS_TLS_CERT" validate:"omitempty,required_unless=ClientKeyFile '',file"`
ClientKeyFile string `json:"tls_client_key" env:"DYNDNS_TLS_KEY" validate:"omitempty,required_unless=ClientCertFile '',file"`
TlsInsecure bool `json:"tls_insecure" env:"DYNDNS_TLS_INSECURE"`
Brokers []string `yaml:"brokers" env:"BROKERS" envSeparator:";" validate:"broker"`
ClientId string `yaml:"client_id" env:"CLIENT_ID" validate:"required_with=Brokers ''"`
CaCertFile string `yaml:"tls_ca_cert" env:"TLS_CA" validate:"omitempty,file"`
ClientCertFile string `yaml:"tls_client_cert" env:"TLS_CERT" validate:"omitempty,required_unless=ClientKeyFile '',file"`
ClientKeyFile string `yaml:"tls_client_key" env:"TLS_KEY" validate:"omitempty,required_unless=ClientCertFile '',file"`
TlsInsecure bool `yaml:"tls_insecure" env:"TLS_INSECURE"`
}

func (conf *MqttConfig) UsesTlsClientCerts() bool {
Expand Down Expand Up @@ -58,12 +57,3 @@ func (conf *MqttConfig) TlsConfig() *tls.Config {

return tlsConf
}

func (conf MqttConfig) String() string {
base := fmt.Sprintf("brokers=%v, clientId=%s", conf.Brokers, conf.ClientId)
if conf.UsesTlsClientCerts() {
base += fmt.Sprintf("ca=%s, crt=%s, key=%s", conf.CaCertFile, conf.ClientCertFile, conf.ClientKeyFile)
}

return base
}
76 changes: 64 additions & 12 deletions conf/notifications_email.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,72 @@ package conf

import (
"errors"
"fmt"
"os"
"strings"
)

type EmailConfig struct {
From string `json:"from" env:"DYNDNS_EMAIL_FROM"`
To []string `json:"to" env:"DYNDNS_EMAIL_TO" envSeparator:";"`
SmtpHost string `json:"host" env:"DYNDNS_EMAIL_HOST"`
SmtpPort int `json:"port" env:"DYNDNS_EMAIL_PORT"`
SmtpUsername string `json:"user" env:"DYNDNS_EMAIL_USER"`
SmtpPassword string `json:"password" env:"DYNDNS_EMAIL_PASSWORD"`
From string `yaml:"from" env:"EMAIL_FROM" validate:"required_without=FromFile"`
FromFile string `yaml:"from_file" env:"EMAIL_FROM_FILE" validate:"required_without=From"`
To []string `yaml:"to" env:"EMAIL_TO" envSeparator:";" validate:"required_without=ToFile"`
ToFile string `yaml:"to_file" env:"EMAIL_TO" validate:"required_without=To"`
SmtpHost string `yaml:"host" env:"EMAIL_HOST" validate:"required"`
SmtpPort int `yaml:"port" env:"EMAIL_PORT" validate:"required"`
SmtpUsername string `yaml:"user" env:"EMAIL_USER" validate:"required_without=SmtpUsernameFile"`
SmtpUsernameFile string `yaml:"user_file" env:"EMAIL_USER_FILE" validate:"required_without=smtpUsername"`
SmtpPassword string `yaml:"password" env:"EMAIL_PASSWORD" validate:"required_without=SmtpPasswordFile"`
SmtpPasswordFile string `yaml:"password_file" env:"EMAIL_PASSWORD_FILE" validate:"required_without=SmtpPassword"`
}

func (conf *EmailConfig) GetFrom() (string, error) {
if len(conf.From) > 0 {
return conf.From, nil
}

data, err := os.ReadFile(conf.FromFile)
if err != nil {
return "", err
}
return string(data), nil
}

func (conf *EmailConfig) GetTo() ([]string, error) {
if len(conf.To) > 0 {
return conf.To, nil
}

data, err := os.ReadFile(conf.ToFile)
if err != nil {
return nil, err
}
sData := string(data)
return strings.Split(sData, ","), nil
}

func (conf *EmailConfig) GetUsername() (string, error) {
if len(conf.SmtpUsername) > 0 {
return conf.SmtpUsername, nil
}

data, err := os.ReadFile(conf.SmtpUsernameFile)
if err != nil {
return "", err
}

return string(data), nil
}

func (conf *EmailConfig) GetPassword() (string, error) {
if len(conf.SmtpPassword) > 0 {
return conf.SmtpPassword, nil
}

data, err := os.ReadFile(conf.SmtpPasswordFile)
if err != nil {
return "", err
}

return string(data), nil
}

func (conf *EmailConfig) Validate() error {
Expand All @@ -25,14 +81,10 @@ func (conf *EmailConfig) Validate() error {
return errors.New("'SmtpHost' not defined")
}
if len(conf.SmtpUsername) == 0 {
return errors.New("'SmtpUsername' not defined")
return errors.New("'smtpUsername' not defined")
}
if len(conf.SmtpPassword) == 0 {
return errors.New("'SmtpPassword' not defined")
}
return nil
}

func (conf *EmailConfig) String() string {
return fmt.Sprintf("from=%s, to=%v, smtpHost=%s, smtpPort=%d, smtpUsername=%s", conf.From, conf.To, conf.SmtpHost, conf.SmtpPort, conf.SmtpUsername)
}
27 changes: 16 additions & 11 deletions conf/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,21 @@ import (
"github.com/rs/zerolog/log"
"github.com/soerenschneider/dyndns/internal/metrics"
"github.com/soerenschneider/dyndns/internal/verification"
"gopkg.in/yaml.v3"
)

type ServerConf struct {
KnownHosts map[string][]string `json:"known_hosts" env:"DYNDNS_KNOWN_HOSTS" validate:"required"`
HostedZoneId string `json:"hosted_zone_id" env:"DYNDNS_HOSTED_ZONE_ID" validate:"required"`
MetricsListener string `json:"metrics_listen,omitempty" validate:"omitempty,tcp_addr"`
KnownHosts map[string][]string `yaml:"known_hosts" env:"KNOWN_HOSTS" validate:"required"`
HostedZoneId string `yaml:"hosted_zone_id" env:"HOSTED_ZONE_ID" validate:"required"`
MetricsListener string `yaml:"metrics_listen,omitempty" validate:"omitempty,tcp_addr"`
HttpServer struct {
Addr string `json:"http"`
TlsCert string `json:"tls_cert"`
TlsKey string `json:"tls_key"`
Addr string `yaml:"http"`
TlsCert string `yaml:"tls_cert"`
TlsKey string `yaml:"tls_key"`
}
*MqttConfig
*VaultConfig
*EmailConfig `json:"notifications"`
*MqttConfig `yaml:"mqtt"`
*VaultConfig `yaml:"vault"`
*EmailConfig `yaml:"notifications"`
}

func GetDefaultServerConfig() *ServerConf {
Expand All @@ -50,7 +51,7 @@ func ReadServerConfig(path string) (*ServerConf, error) {
return nil, fmt.Errorf("could not read config file %s: %v", path, err)
}

if err := json.Unmarshal(content, &conf); err != nil {
if err := yaml.Unmarshal(content, &conf); err != nil {
return nil, fmt.Errorf("could not unmarshal json to config: %v", err)
}

Expand All @@ -65,7 +66,11 @@ func ParseEnvVariables(serverConf *ServerConf) error {
return ret, json.Unmarshal([]byte(input), &ret)
}

return env.ParseWithFuncs(serverConf, funk)
opts := env.Options{
Prefix: "DYNDNS_",
}

return env.ParseWithFuncs(serverConf, funk, opts)
}

func (conf *ServerConf) DecodePublicKeys() (map[string][]verification.VerificationKey, error) {
Expand Down
26 changes: 25 additions & 1 deletion conf/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,31 @@ func TestReadServerConfig(t *testing.T) {
wantErr bool
}{
{
name: "happy path",
name: "happy path - yaml",
args: args{"../contrib/server.yaml"},
want: &ServerConf{
KnownHosts: map[string][]string{
"host": []string{"key1", "key2"},
},
HostedZoneId: "hosted-zone-id-x",
MetricsListener: ":6666",
MqttConfig: &MqttConfig{
Brokers: []string{"tcp://mqtt.eclipseprojects.io:1883"},
ClientId: "my-client-id",
},
EmailConfig: &EmailConfig{
From: "from",
To: []string{"to-1"},
SmtpHost: "smtp-host",
SmtpPort: 465,
SmtpUsername: "username",
SmtpPassword: "password",
},
VaultConfig: GetDefaultVaultConfig(),
},
},
{
name: "happy path - json",
args: args{"../contrib/server.json"},
want: &ServerConf{
KnownHosts: map[string][]string{
Expand Down
14 changes: 7 additions & 7 deletions conf/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ var (
)

type VaultConfig struct {
VaultAddr string `json:"vault_addr,omitempty" env:"DYNDNS_VAULT_ADDR" validate:"required_unless=AuthStrategy ''"`
VaultAddr string `yaml:"vault_addr,omitempty" env:"VAULT_ADDR" validate:"required_unless=AuthStrategy ''"`

AuthStrategy VaultAuthStrategy `json:"vault_auth_strategy" env:"DYNDNS_VAULT_AUTH_STRATEGY" validate:"omitempty,oneof=token approle kubernetes"`
AuthStrategy VaultAuthStrategy `yaml:"vault_auth_strategy" env:"VAULT_AUTH_STRATEGY" validate:"omitempty,oneof=token approle kubernetes"`

AwsRoleName string `json:"vault_aws_role_name,omitempty" env:"DYNDNS_VAULT_AWS_ROLE_NAME"`
AwsMountPath string `json:"vault_aws_mount_path,omitempty" env:"DYNDNS_VAULT_AWS_MOUNT"`
AwsRoleName string `yaml:"vault_aws_role_name,omitempty" env:"VAULT_AWS_ROLE_NAME"`
AwsMountPath string `yaml:"vault_aws_mount_path,omitempty" env:"VAULT_AWS_MOUNT"`

AppRoleId string `json:"vault_app_role_id,omitempty" env:"DYNDNS_VAULT_APPROLE_ROLE_ID"`
AppRoleSecretId string `json:"vault_app_role_secret,omitempty" env:"DYNDNS_VAULT_APPROLE_SECRET_ID"`
AppRoleId string `yaml:"vault_app_role_id,omitempty" env:"VAULT_APPROLE_ROLE_ID"`
AppRoleSecretId string `yaml:"vault_app_role_secret,omitempty" env:"VAULT_APPROLE_SECRET_ID"`

VaultToken string `json:"vault_token,omitempty" env:"DYNDNS_VAULT_TOKEN"`
VaultToken string `yaml:"vault_token,omitempty" env:"VAULT_TOKEN"`
}

func GetDefaultVaultConfig() *VaultConfig {
Expand Down
8 changes: 6 additions & 2 deletions contrib/client.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
{
"host": "my.host.tld",
"keypair_path": "/tmp/keypair.json",
"client_id": "my-client-id",
"brokers": ["ssl://mqtt.eclipseprojects.io:8883"],
"mqtt": {
"brokers": [
"ssl://mqtt.eclipseprojects.io:8883"
],
"client_id": "my-client-id"
},
"email": {
"from": "bla"
}
Expand Down
8 changes: 8 additions & 0 deletions contrib/client.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
host: my.host.tld
keypair_path: /tmp/keypair.json
mqtt:
brokers:
- ssl://mqtt.eclipseprojects.io:8883
client_id: my-client-id
email:
from: bla
1 change: 0 additions & 1 deletion contrib/empty.json

This file was deleted.

1 change: 1 addition & 0 deletions contrib/empty.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Loading

0 comments on commit 87248bb

Please sign in to comment.