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/
/vendor/
/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
- [ ] Validate configuration on startup
- [ ] 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))
- [ ] Configuration live reloading

View file

@ -2,7 +2,6 @@ package settings
import (
"fmt"
"io/ioutil"
"strings"
"github.com/spf13/viper"
@ -13,26 +12,16 @@ type giteaRepository struct {
Name string
}
type token struct {
Value string
File string
}
type webhook struct {
Secret string
SecretFile string
}
type giteaConfig struct {
Url string
Token token
Webhook webhook
Token *token
Webhook *webhook
}
type sonarQubeConfig struct {
Url string
Token token
Webhook webhook
Token *token
Webhook *webhook
}
type Project struct {
@ -42,31 +31,12 @@ type Project struct {
Gitea giteaRepository
}
type fullConfig struct {
Gitea giteaConfig
SonarQube sonarQubeConfig `mapstructure:"sonarqube"`
Projects []Project
}
var (
Gitea giteaConfig
SonarQube sonarQubeConfig
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 {
v := viper.New()
v.SetConfigName("config.yaml")
@ -100,23 +70,29 @@ func Load(configPath string) {
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 {
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.")
}
Gitea = configuration.Gitea
SonarQube = configuration.SonarQube
Projects = configuration.Projects
Projects = projects
Gitea.Webhook.Secret = readSecretFile(Gitea.Webhook.SecretFile, Gitea.Webhook.Secret)
Gitea.Token.Value = readSecretFile(Gitea.Token.File, Gitea.Token.Value)
SonarQube.Webhook.Secret = readSecretFile(SonarQube.Webhook.SecretFile, SonarQube.Webhook.Secret)
SonarQube.Token.Value = readSecretFile(SonarQube.Token.File, SonarQube.Token.Value)
errCallback := func(msg string) {panic(msg)}
Gitea = giteaConfig{
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{
Url: "https://example.com/gitea",
Token: token{
Token: &token{
Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565",
},
Webhook: webhook{
Webhook: &webhook{
Secret: "haxxor-gitea-secret",
},
}
@ -76,10 +76,10 @@ func TestLoadGiteaStructureInjectedEnvs(t *testing.T) {
expected := giteaConfig{
Url: "https://example.com/gitea",
Token: token{
Token: &token{
Value: "injected-token",
},
Webhook: webhook{
Webhook: &webhook{
Secret: "injected-webhook-secret",
},
}
@ -98,10 +98,10 @@ func TestLoadSonarQubeStructure(t *testing.T) {
expected := sonarQubeConfig{
Url: "https://example.com/sonarqube",
Token: token{
Token: &token{
Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e",
},
Webhook: webhook{
Webhook: &webhook{
Secret: "haxxor-sonarqube-secret",
},
}
@ -117,10 +117,10 @@ func TestLoadSonarQubeStructureInjectedEnvs(t *testing.T) {
expected := sonarQubeConfig{
Url: "https://example.com/sonarqube",
Token: token{
Token: &token{
Value: "injected-token",
},
Webhook: webhook{
Webhook: &webhook{
Secret: "injected-webhook-secret",
},
}
@ -169,25 +169,25 @@ projects:
expectedGitea := giteaConfig{
Url: "https://example.com/gitea",
Token: token{
Token: &token{
Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565",
File: giteaTokenFile,
file: giteaTokenFile,
},
Webhook: webhook{
Webhook: &webhook{
Secret: "gitea-totally-secret",
SecretFile: giteaWebhookSecretFile,
secretFile: giteaWebhookSecretFile,
},
}
expectedSonarQube := sonarQubeConfig{
Url: "https://example.com/sonarqube",
Token: token{
Token: &token{
Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e",
File: sonarqubeTokenFile,
file: sonarqubeTokenFile,
},
Webhook: webhook{
Webhook: &webhook{
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
}