Initial Commit
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing

This commit is contained in:
2024-08-18 20:49:30 +02:00
commit f79b20e8a4
24 changed files with 1921 additions and 0 deletions

201
pkg/config/config.go Normal file
View File

@ -0,0 +1,201 @@
package config
import (
"context"
"encoding/xml"
"fmt"
"io"
"os"
"strings"
"time"
"git.cryptic.systems/volker.raschek/tarr/pkg/domain"
"github.com/fsnotify/fsnotify"
"gopkg.in/yaml.v3"
)
type XMLConfig struct {
XMLName xml.Name `xml:"Config"`
APIToken string `xml:"ApiKey,omitempty"`
AuthenticationMethod string `xml:"AuthenticationMethod,omitempty"`
BindAddress string `xml:"BindAddress,omitempty"`
Branch string `xml:"Branch,omitempty"`
EnableSSL string `xml:"EnableSsl,omitempty"`
InstanceName string `xml:"InstanceName,omitempty"`
LaunchBrowser string `xml:"LaunchBrowser,omitempty"`
LogLevel string `xml:"LogLevel,omitempty"`
Port string `xml:"Port,omitempty"`
SSLCertPassword string `xml:"SSLCertPassword,omitempty"`
SSLCertPath string `xml:"SSLCertPath,omitempty"`
SSLPort string `xml:"SslPort,omitempty"`
UpdateMechanism string `xml:"UpdateMechanism,omitempty"`
URLBase string `xml:"UrlBase,omitempty"`
}
type YAMLConfigAuth struct {
APIToken string `yaml:"apikey,omitempty"`
Password string `yaml:"password,omitempty"`
Type string `yaml:"type,omitempty"`
Username string `yaml:"username,omitempty"`
}
type YAMLConfig struct {
Auth YAMLConfigAuth `yaml:"auth,omitempty"`
}
// Read reads the config struct from a file. The decoding format will be determined by the file extension like
// `xml` or `yaml`.
func ReadConfig(name string) (*domain.Config, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
switch {
case strings.HasSuffix(name, "xml"):
return readXMLConfig(f)
case strings.HasSuffix(name, "yml") || strings.HasSuffix(name, "yaml"):
return readYAMLConfig(f)
default:
return nil, fmt.Errorf("Unsupported file extension")
}
}
func readXMLConfig(r io.Reader) (*domain.Config, error) {
xmlConfig := new(XMLConfig)
xmlDecoder := xml.NewDecoder(r)
err := xmlDecoder.Decode(xmlConfig)
if err != nil {
return nil, err
}
return &domain.Config{
API: &domain.API{
Token: xmlConfig.APIToken,
},
}, nil
}
func readYAMLConfig(r io.Reader) (*domain.Config, error) {
yamlConfig := new(YAMLConfig)
yamlDecoder := yaml.NewDecoder(r)
err := yamlDecoder.Decode(yamlConfig)
if err != nil {
return nil, err
}
return &domain.Config{
API: &domain.API{
Password: yamlConfig.Auth.Password,
Token: yamlConfig.Auth.APIToken,
Username: yamlConfig.Auth.Username,
},
}, nil
}
func WatchConfig(ctx context.Context, name string) (<-chan *domain.Config, <-chan error) {
configChannel := make(chan *domain.Config, 0)
errorChannel := make(chan error, 0)
go func() {
wait := time.Second * 3
timer := time.NewTimer(wait)
<-timer.C
watcher, err := fsnotify.NewWatcher()
if err != nil {
errorChannel <- err
return
}
watcher.Add(name)
for {
select {
case <-ctx.Done():
close(configChannel)
close(errorChannel)
break
case event, open := <-watcher.Events:
if !open {
errorChannel <- fmt.Errorf("FSWatcher closed channel: %w", err)
break
}
switch event.Op {
case fsnotify.Write:
timer.Reset(wait)
}
case <-timer.C:
config, err := ReadConfig(name)
if err != nil {
errorChannel <- err
continue
}
configChannel <- config
}
}
}()
return configChannel, errorChannel
}
// WriteConfig writes the config struct into the file. The encoding format will be determined by the file extension like
// `xml` or `yaml`.
func WriteConfig(name string, config *domain.Config) error {
f, err := os.Create(name)
if err != nil {
return err
}
defer f.Close()
switch {
case strings.HasSuffix(name, "xml"):
return writeXMLConfig(f, config)
case strings.HasSuffix(name, "yml") || strings.HasSuffix(name, "yaml"):
return writeYAMLConfig(f, config)
default:
return fmt.Errorf("Unsupported file extension")
}
}
func writeXMLConfig(w io.Writer, config *domain.Config) error {
xmlEncoder := xml.NewEncoder(w)
defer xmlEncoder.Close()
xmlConfig := &XMLConfig{
APIToken: config.API.Token,
}
xmlEncoder.Indent("", " ")
err := xmlEncoder.Encode(xmlConfig)
if err != nil {
return err
}
return nil
}
func writeYAMLConfig(w io.Writer, config *domain.Config) error {
yamlEncoder := yaml.NewEncoder(w)
defer yamlEncoder.Close()
yamlConfig := &YAMLConfig{
Auth: YAMLConfigAuth{
APIToken: config.API.Token,
Password: config.API.Password,
Username: config.API.Username,
},
}
yamlEncoder.SetIndent(2)
err := yamlEncoder.Encode(yamlConfig)
if err != nil {
return err
}
return nil
}

71
pkg/config/config_test.go Normal file
View File

@ -0,0 +1,71 @@
package config_test
import (
_ "embed"
"os"
"path/filepath"
"testing"
"git.cryptic.systems/volker.raschek/tarr/pkg/config"
"github.com/stretchr/testify/require"
)
//go:embed test/assets/xml/config.xml
var expectedXMLConfig string
//go:embed test/assets/yaml/config.yaml
var expectedYAMLConfig string
func TestReadWriteConfig_XML(t *testing.T) {
require := require.New(t)
tmpDir, err := os.MkdirTemp("", "*")
require.NoError(err)
t.Cleanup(func() { _ = os.RemoveAll(tmpDir) })
expectedXMLConfigName := filepath.Join(tmpDir, "expected_config.xml")
f, err := os.Create(expectedXMLConfigName)
require.NoError(err)
_, err = f.WriteString(expectedXMLConfig)
require.NoError(err)
actualConfig, err := config.ReadConfig(expectedXMLConfigName)
require.NoError(err)
require.NotNil(actualConfig)
actualXMLConfigName := filepath.Join(tmpDir, "actual_config.xml")
err = config.WriteConfig(actualXMLConfigName, actualConfig)
require.NoError(err)
b, err := os.ReadFile(actualXMLConfigName)
require.NoError(err)
require.Equal(expectedXMLConfig, string(b))
}
func TestReadWriteConfig_YAML(t *testing.T) {
require := require.New(t)
tmpDir, err := os.MkdirTemp("", "*")
require.NoError(err)
t.Cleanup(func() { _ = os.RemoveAll(tmpDir) })
expectedYAMLConfigName := filepath.Join(tmpDir, "expected_config.yaml")
f, err := os.Create(expectedYAMLConfigName)
require.NoError(err)
_, err = f.WriteString(expectedYAMLConfig)
require.NoError(err)
actualConfig, err := config.ReadConfig(expectedYAMLConfigName)
require.NoError(err)
require.NotNil(actualConfig)
actualYAMLConfigName := filepath.Join(tmpDir, "actual_config.yaml")
err = config.WriteConfig(actualYAMLConfigName, actualConfig)
require.NoError(err)
b, err := os.ReadFile(actualYAMLConfigName)
require.NoError(err)
require.Equal(expectedYAMLConfig, string(b))
}

View File

@ -0,0 +1,3 @@
<Config>
<ApiKey>my-token</ApiKey>
</Config>

View File

@ -0,0 +1,4 @@
auth:
apikey: my-token
password: my-password
username: my-username