Use OOP-ish style for configuration loading
Signed-off-by: Steven Kriegler <61625851+justusbunsi@users.noreply.github.com>
This commit is contained in:
parent
4ba781d74f
commit
86a644f31f
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,3 +3,5 @@
|
||||||
/config/
|
/config/
|
||||||
/vendor/
|
/vendor/
|
||||||
/gitea-sonarqube-bot
|
/gitea-sonarqube-bot
|
||||||
|
/coverage.html
|
||||||
|
/*.log
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
38
internal/settings/token.go
Normal file
38
internal/settings/token.go
Normal 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
|
||||||
|
}
|
38
internal/settings/webhook.go
Normal file
38
internal/settings/webhook.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue