diff --git a/.gitignore b/.gitignore index 73c14b5..9cf8818 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /coverage.html /*.log /cover.out +/cover.html diff --git a/config/config.example.yaml b/config/config.example.yaml index 951ac4d..cd48617 100644 --- a/config/config.example.yaml +++ b/config/config.example.yaml @@ -7,8 +7,8 @@ gitea: # User needs "Read project" permissions with access to "Pull Requests" token: value: "" - # # or path to file containing the plain text secret - # file: /path/to/gitea/token + # # or path to file containing the plain text secret + # file: /path/to/gitea/token # If the sent webhook has a signature header, the bot validates the request payload. If the value does not match, the # request will be ignored. @@ -16,8 +16,8 @@ gitea: # exists and no webhookSecret is defined here, the bot will ignore the request, because it cannot be validated. webhook: secret: "" - # # or path to file containing the plain text secret - # secretFile: /path/to/gitea/webhook/secret + # # or path to file containing the plain text secret + # secretFile: /path/to/gitea/webhook/secret # SonarQube related configuration. Necessary for requesting data from the API and processing the webhook. sonarqube: @@ -28,8 +28,8 @@ sonarqube: # User needs "Browse on project" permissions token: value: "" - # # or path to file containing the plain text secret - # file: /path/to/sonarqube/token + # # or path to file containing the plain text secret + # file: /path/to/sonarqube/token # If the sent webhook has a signature header, the bot validates the request payload. If the value does not match, the # request will be ignored. @@ -38,8 +38,14 @@ sonarqube: # validated. webhook: secret: "" - # # or path to file containing the plain text secret - # secretFile: /path/to/sonarqube/webhook/secret + # # or path to file containing the plain text secret + # secretFile: /path/to/sonarqube/webhook/secret + + # Some useful metrics depend on the edition in use. There are various ones like code_smells, vulnerabilities, bugs, etc. + # By default the bot will extract "bugs,vulnerabilities,code_smells" + # Setting this option you can extend that default list by your own metrics. + additionalMetrics: [] + # - "new_security_hotspots" # List of project mappings to take care of. Webhooks for other projects will be ignored. # At least one must be configured. Otherwise all webhooks (no matter which source) because the bot cannot map on its own. diff --git a/helm/values.yaml b/helm/values.yaml index d9b3aa9..aec6e51 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -55,6 +55,12 @@ app: secret: "" # # or path to file containing the plain text secret # secretFile: /bot/secrets/sonarqube/webhook-secret + + # Some useful metrics depend on the edition in use. There are various ones like code_smells, vulnerabilities, bugs, etc. + # By default the bot will extract "bugs,vulnerabilities,code_smells" + # Setting this option you can extend that default list by your own metrics. + additionalMetrics: [] + # - "new_security_hotspots" # List of project mappings to take care of. Webhooks for other projects will be ignored. # At least one must be configured. Otherwise all webhooks (no matter which source) because the bot cannot map on its own. diff --git a/internal/clients/sonarqube/sonarqube.go b/internal/clients/sonarqube/sonarqube.go index 5565948..be657b0 100644 --- a/internal/clients/sonarqube/sonarqube.go +++ b/internal/clients/sonarqube/sonarqube.go @@ -102,7 +102,7 @@ func (sdk *SonarQubeSdk) GetPullRequest(project string, index int64) (*PullReque } func (sdk *SonarQubeSdk) GetMeasures(project string, branch string) (*MeasuresResponse, error) { - url := fmt.Sprintf("%s/api/measures/component?additionalFields=metrics&metricKeys=bugs,vulnerabilities,new_security_hotspots,code_smells&component=%s&pullRequest=%s", sdk.baseUrl, project, branch) + url := fmt.Sprintf("%s/api/measures/component?additionalFields=metrics&metricKeys=%s&component=%s&pullRequest=%s", sdk.baseUrl, settings.SonarQube.GetMetricsList(), project, branch) req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("cannot initialize Request: %w", err) diff --git a/internal/settings/settings.go b/internal/settings/settings.go index 44f9850..918cd72 100644 --- a/internal/settings/settings.go +++ b/internal/settings/settings.go @@ -32,6 +32,7 @@ func newConfigReader() *viper.Viper { v.SetDefault("sonarqube.token.file", "") v.SetDefault("sonarqube.webhook.secret", "") v.SetDefault("sonarqube.webhook.secretFile", "") + v.SetDefault("sonarqube.additionalMetrics", []string{}) v.SetDefault("projects", []Project{}) return v @@ -43,14 +44,14 @@ func Load(configPath string) { err := r.ReadInConfig() if err != nil { - panic(fmt.Errorf("Fatal error while reading config file: %w \n", err)) + panic(fmt.Errorf("fatal error while reading config file: %w", err)) } var projects []Project err = r.UnmarshalKey("projects", &projects) if err != nil { - panic(fmt.Errorf("Unable to load project mapping: %s", err.Error())) + panic(fmt.Errorf("unable to load project mapping: %s", err.Error())) } if len(projects) == 0 { @@ -67,8 +68,9 @@ func Load(configPath string) { Webhook: NewWebhook(r, "gitea", errCallback), } SonarQube = sonarQubeConfig{ - Url: r.GetString("sonarqube.url"), - Token: NewToken(r, "sonarqube", errCallback), - Webhook: NewWebhook(r, "sonarqube", errCallback), + Url: r.GetString("sonarqube.url"), + Token: NewToken(r, "sonarqube", errCallback), + Webhook: NewWebhook(r, "sonarqube", errCallback), + AdditionalMetrics: r.GetStringSlice("sonarqube.additionalMetrics"), } } diff --git a/internal/settings/settings_test.go b/internal/settings/settings_test.go index 82cd6cd..051c27f 100644 --- a/internal/settings/settings_test.go +++ b/internal/settings/settings_test.go @@ -22,6 +22,7 @@ sonarqube: value: a09eb5785b25bb2cbacf48808a677a0709f02d8e webhook: secret: haxxor-sonarqube-secret + additionalMetrics: [] projects: - sonarqube: key: gitea-sonarqube-pr-bot @@ -107,6 +108,45 @@ func TestLoadSonarQubeStructure(t *testing.T) { } assert.EqualValues(t, expected, SonarQube) + assert.EqualValues(t, expected.GetMetricsList(), "bugs,vulnerabilities,code_smells") +} + +func TestLoadSonarQubeStructureWithAdditionalMetrics(t *testing.T) { + WriteConfigFile(t, []byte( + `gitea: + url: https://example.com/gitea + token: + value: fake-gitea-token +sonarqube: + url: https://example.com/sonarqube + token: + value: fake-sonarqube-token + additionalMetrics: "new_security_hotspots" +projects: + - sonarqube: + key: gitea-sonarqube-pr-bot + gitea: + owner: example-organization + name: pr-bot +`)) + Load(os.TempDir()) + + expected := sonarQubeConfig{ + Url: "https://example.com/sonarqube", + Token: &token{ + Value: "fake-sonarqube-token", + }, + Webhook: &webhook{ + Secret: "", + }, + AdditionalMetrics: []string{ + "new_security_hotspots", + }, + } + + assert.EqualValues(t, expected, SonarQube) + assert.EqualValues(t, expected.AdditionalMetrics, []string{"new_security_hotspots"}) + assert.EqualValues(t, "bugs,vulnerabilities,code_smells,new_security_hotspots", SonarQube.GetMetricsList()) } func TestLoadSonarQubeStructureInjectedEnvs(t *testing.T) { @@ -189,6 +229,7 @@ projects: Secret: "sonarqube-totally-secret", secretFile: sonarqubeWebhookSecretFile, }, + AdditionalMetrics: []string{}, } Load(os.TempDir()) diff --git a/internal/settings/sonarqube.go b/internal/settings/sonarqube.go index b7ef864..1c7fde2 100644 --- a/internal/settings/sonarqube.go +++ b/internal/settings/sonarqube.go @@ -1,7 +1,22 @@ package settings +import "strings" + type sonarQubeConfig struct { - Url string - Token *token - Webhook *webhook + Url string + Token *token + Webhook *webhook + AdditionalMetrics []string +} + +func (c *sonarQubeConfig) GetMetricsList() string { + metrics := []string{ + "bugs", + "vulnerabilities", + "code_smells", + } + if len(c.AdditionalMetrics) != 0 { + metrics = append(metrics, c.AdditionalMetrics...) + } + return strings.Join(metrics, ",") }