Use OOP-ish style for configuration loading

Signed-off-by: Steven Kriegler <61625851+justusbunsi@users.noreply.github.com>
This commit is contained in:
justusbunsi 2021-06-29 10:29:20 +02:00
parent 4ba781d74f
commit 86a644f31f
No known key found for this signature in database
GPG key ID: 990B348ECAC9C7DB
6 changed files with 116 additions and 61 deletions

2
.gitignore vendored
View file

@ -3,3 +3,5 @@
/config/ /config/
/vendor/ /vendor/
/gitea-sonarqube-bot /gitea-sonarqube-bot
/coverage.html
/*.log

View file

@ -18,6 +18,7 @@ Luckily, both endpoints have a proper REST API to communicate with each others.
## TODOs ## TODOs
- [ ] Validate configuration on startup
- [ ] Maybe drop `PRBOT_CONFIG_PATH` environment variable in favor of `--config path/to/config.yaml` cli attribute - [ ] Maybe drop `PRBOT_CONFIG_PATH` environment variable in favor of `--config path/to/config.yaml` cli attribute
- [ ] Configure SonarQube PR branch naming pattern for more flexibility (currently focused on Jenkins with [Gitea Plugin](https://github.com/jenkinsci/gitea-plugin)) - [ ] Configure SonarQube PR branch naming pattern for more flexibility (currently focused on Jenkins with [Gitea Plugin](https://github.com/jenkinsci/gitea-plugin))
- [ ] Configuration live reloading - [ ] Configuration live reloading

View file

@ -2,7 +2,6 @@ package settings
import ( import (
"fmt" "fmt"
"io/ioutil"
"strings" "strings"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -13,26 +12,16 @@ type giteaRepository struct {
Name string Name string
} }
type token struct {
Value string
File string
}
type webhook struct {
Secret string
SecretFile string
}
type giteaConfig struct { type giteaConfig struct {
Url string Url string
Token token Token *token
Webhook webhook Webhook *webhook
} }
type sonarQubeConfig struct { type sonarQubeConfig struct {
Url string Url string
Token token Token *token
Webhook webhook Webhook *webhook
} }
type Project struct { type Project struct {
@ -42,31 +31,12 @@ type Project struct {
Gitea giteaRepository Gitea giteaRepository
} }
type fullConfig struct {
Gitea giteaConfig
SonarQube sonarQubeConfig `mapstructure:"sonarqube"`
Projects []Project
}
var ( var (
Gitea giteaConfig Gitea giteaConfig
SonarQube sonarQubeConfig SonarQube sonarQubeConfig
Projects []Project Projects []Project
) )
func readSecretFile(file string, defaultValue string) (string) {
if file == "" {
return defaultValue
}
content, err := ioutil.ReadFile(file)
if err != nil {
panic(fmt.Errorf("Cannot read '%s' or it is no regular file. %w", file, err))
}
return string(content)
}
func newConfigReader() *viper.Viper { func newConfigReader() *viper.Viper {
v := viper.New() v := viper.New()
v.SetConfigName("config.yaml") v.SetConfigName("config.yaml")
@ -100,23 +70,29 @@ func Load(configPath string) {
panic(fmt.Errorf("Fatal error while reading config file: %w \n", err)) panic(fmt.Errorf("Fatal error while reading config file: %w \n", err))
} }
var configuration fullConfig var projects []Project
err = r.Unmarshal(&configuration) err = r.UnmarshalKey("projects", &projects)
if err != nil { if err != nil {
panic(fmt.Errorf("Unable to load config into struct, %v", err)) panic(fmt.Errorf("Unable to load project mapping: %s", err.Error()))
} }
if len(configuration.Projects) == 0 { if len(projects) == 0 {
panic("Invalid configuration. At least one project mapping is necessary.") panic("Invalid configuration. At least one project mapping is necessary.")
} }
Gitea = configuration.Gitea Projects = projects
SonarQube = configuration.SonarQube
Projects = configuration.Projects
Gitea.Webhook.Secret = readSecretFile(Gitea.Webhook.SecretFile, Gitea.Webhook.Secret) errCallback := func(msg string) {panic(msg)}
Gitea.Token.Value = readSecretFile(Gitea.Token.File, Gitea.Token.Value)
SonarQube.Webhook.Secret = readSecretFile(SonarQube.Webhook.SecretFile, SonarQube.Webhook.Secret) Gitea = giteaConfig{
SonarQube.Token.Value = readSecretFile(SonarQube.Token.File, SonarQube.Token.Value) Url: r.GetString("gitea.url"),
Token: NewToken(r, "gitea", errCallback),
Webhook: NewWebhook(r, "gitea", errCallback),
}
SonarQube = sonarQubeConfig{
Url: r.GetString("sonarqube.url"),
Token: NewToken(r, "sonarqube", errCallback),
Webhook: NewWebhook(r, "sonarqube", errCallback),
}
} }

View file

@ -57,10 +57,10 @@ func TestLoadGiteaStructure(t *testing.T) {
expected := giteaConfig{ expected := giteaConfig{
Url: "https://example.com/gitea", Url: "https://example.com/gitea",
Token: token{ Token: &token{
Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565", Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565",
}, },
Webhook: webhook{ Webhook: &webhook{
Secret: "haxxor-gitea-secret", Secret: "haxxor-gitea-secret",
}, },
} }
@ -76,10 +76,10 @@ func TestLoadGiteaStructureInjectedEnvs(t *testing.T) {
expected := giteaConfig{ expected := giteaConfig{
Url: "https://example.com/gitea", Url: "https://example.com/gitea",
Token: token{ Token: &token{
Value: "injected-token", Value: "injected-token",
}, },
Webhook: webhook{ Webhook: &webhook{
Secret: "injected-webhook-secret", Secret: "injected-webhook-secret",
}, },
} }
@ -98,10 +98,10 @@ func TestLoadSonarQubeStructure(t *testing.T) {
expected := sonarQubeConfig{ expected := sonarQubeConfig{
Url: "https://example.com/sonarqube", Url: "https://example.com/sonarqube",
Token: token{ Token: &token{
Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e", Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e",
}, },
Webhook: webhook{ Webhook: &webhook{
Secret: "haxxor-sonarqube-secret", Secret: "haxxor-sonarqube-secret",
}, },
} }
@ -117,10 +117,10 @@ func TestLoadSonarQubeStructureInjectedEnvs(t *testing.T) {
expected := sonarQubeConfig{ expected := sonarQubeConfig{
Url: "https://example.com/sonarqube", Url: "https://example.com/sonarqube",
Token: token{ Token: &token{
Value: "injected-token", Value: "injected-token",
}, },
Webhook: webhook{ Webhook: &webhook{
Secret: "injected-webhook-secret", Secret: "injected-webhook-secret",
}, },
} }
@ -169,25 +169,25 @@ projects:
expectedGitea := giteaConfig{ expectedGitea := giteaConfig{
Url: "https://example.com/gitea", Url: "https://example.com/gitea",
Token: token{ Token: &token{
Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565", Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565",
File: giteaTokenFile, file: giteaTokenFile,
}, },
Webhook: webhook{ Webhook: &webhook{
Secret: "gitea-totally-secret", Secret: "gitea-totally-secret",
SecretFile: giteaWebhookSecretFile, secretFile: giteaWebhookSecretFile,
}, },
} }
expectedSonarQube := sonarQubeConfig{ expectedSonarQube := sonarQubeConfig{
Url: "https://example.com/sonarqube", Url: "https://example.com/sonarqube",
Token: token{ Token: &token{
Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e", Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e",
File: sonarqubeTokenFile, file: sonarqubeTokenFile,
}, },
Webhook: webhook{ Webhook: &webhook{
Secret: "sonarqube-totally-secret", Secret: "sonarqube-totally-secret",
SecretFile: sonarqubeWebhookSecretFile, secretFile: sonarqubeWebhookSecretFile,
}, },
} }

View file

@ -0,0 +1,38 @@
package settings
import (
"fmt"
"io/ioutil"
"github.com/spf13/viper"
)
type token struct {
Value string
file string
}
func (t *token) lookupSecret(errCallback func(string)) {
if t.file == "" {
return
}
content, err := ioutil.ReadFile(t.file)
if err != nil {
errCallback(fmt.Sprintf("Cannot read '%s' or it is no regular file: %s", t.file, err.Error()))
return
}
t.Value = string(content)
}
func NewToken(v *viper.Viper, confContainer string, errCallback func(string)) *token {
t := &token{
Value: v.GetString(fmt.Sprintf("%s.token.value", confContainer)),
file: v.GetString(fmt.Sprintf("%s.token.file", confContainer)),
}
t.lookupSecret(errCallback)
return t
}

View file

@ -0,0 +1,38 @@
package settings
import (
"fmt"
"io/ioutil"
"github.com/spf13/viper"
)
type webhook struct {
Secret string
secretFile string
}
func (w *webhook) lookupSecret(errCallback func(string)) {
if w.secretFile == "" {
return
}
content, err := ioutil.ReadFile(w.secretFile)
if err != nil {
errCallback(fmt.Sprintf("Cannot read '%s' or it is no regular file: %s", w.secretFile, err.Error()))
return
}
w.Secret = string(content)
}
func NewWebhook(v *viper.Viper, confContainer string, errCallback func(string)) *webhook {
w := &webhook{
Secret: v.GetString(fmt.Sprintf("%s.webhook.secret", confContainer)),
secretFile: v.GetString(fmt.Sprintf("%s.webhook.secretFile", confContainer)),
}
w.lookupSecret(errCallback)
return w
}