diff --git a/.gitignore b/.gitignore index a3538d3..2a6750a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ /vendor/ /node_modules/ /helm-releases/ -/gitea-sonarqube-bot +/gitea-sonarqube-bot* /coverage.html /*.log /cover.out diff --git a/Dockerfile b/Dockerfile index c91dd76..93bf360 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,6 +40,7 @@ ENV HOME=/home/bot EXPOSE 3000 ENV GIN_MODE "release" +ENV GITEA_SQ_BOT_CONFIG_PATH "/home/bot/config/config.yaml" VOLUME ["/home/bot/config/"] RUN ["chmod", "+x", "/usr/local/bin/docker-entrypoint.sh"] diff --git a/README.md b/README.md index 7507ed6..efb8c47 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,19 @@ and execute the following (replace `$TAG` first): docker run --rm -it -p 9000:3000 -v "$(pwd)/config/:/home/bot/config/" justusbunsi/gitea-sonarqube-bot:$TAG ``` +**Starting with v0.2.0** + +By default, the bot expects its configuration file under `./config/config.yaml` next to the bot executable. Inside the Docker image the +corresponding full path is `/home/bot/config/config.yaml`. If you prefer using a different location or even a different filename, you can +also define the environment variable `GITEA_SQ_BOT_CONFIG_PATH` that allows for changing that full path. + +Imagine having a `./config/sqbot.config.yml` on your host that you want to populate inside `/mnt/`, the correct command to run a Docker +container would be: + +```bash +docker run --rm -it -p 9000:3000 -e "GITEA_SQ_BOT_CONFIG_PATH=/mnt/sqbot.config.yml" -v "$(pwd)/config/:/mnt/" justusbunsi/gitea-sonarqube-bot:$TAG +``` + ### Helm Chart See [Helm Chart README](helm/README.md) for detailed instructions. diff --git a/cmd/gitea-sonarqube-bot/main.go b/cmd/gitea-sonarqube-bot/main.go index 5c9eb4d..42b5de3 100644 --- a/cmd/gitea-sonarqube-bot/main.go +++ b/cmd/gitea-sonarqube-bot/main.go @@ -4,7 +4,6 @@ import ( "fmt" "log" "os" - "path" "gitea-sonarqube-pr-bot/internal/api" giteaSdk "gitea-sonarqube-pr-bot/internal/clients/gitea" @@ -15,23 +14,22 @@ import ( "github.com/urfave/cli/v2" ) -func getConfigLocation() string { - configPath := path.Join("config") - if customConfigPath, ok := os.LookupEnv("PRBOT_CONFIG_PATH"); ok { - configPath = customConfigPath - } - - return configPath -} - func main() { - settings.Load(getConfigLocation()) - app := &cli.App{ Name: "gitea-sonarqube-pr-bot", Usage: "Improve your experience with SonarQube and Gitea", Description: `By default, gitea-sonarqube-pr-bot will start running the webserver if no arguments are passed.`, Action: serveApi, + Flags: []cli.Flag{ + &cli.PathFlag{ + Name: "config", + Aliases: []string{"c"}, + Value: "./config/config.yaml", + Usage: "Full path to configuration file.", + EnvVars: []string{"GITEA_SQ_BOT_CONFIG_PATH"}, + TakesFile: true, + }, + }, } err := app.Run(os.Args) @@ -43,6 +41,10 @@ func main() { func serveApi(c *cli.Context) error { fmt.Println("Hi! I'm the Gitea-SonarQube-PR bot. At your service.") + config := c.Path("config") + settings.Load(config) + fmt.Printf("Config file in use: %s\n", config) + giteaHandler := api.NewGiteaWebhookHandler(giteaSdk.New(), sonarQubeSdk.New()) sqHandler := api.NewSonarQubeWebhookHandler(giteaSdk.New(), sonarQubeSdk.New()) server := api.New(giteaHandler, sqHandler) diff --git a/cmd/gitea-sonarqube-bot/main_test.go b/cmd/gitea-sonarqube-bot/main_test.go deleted file mode 100644 index b48aff4..0000000 --- a/cmd/gitea-sonarqube-bot/main_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package main - -import ( - "os" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetConfigLocationWithDefault(t *testing.T) { - assert.Equal(t, "config", getConfigLocation()) -} - -func TestGetConfigLocationWithEnvironmentOverride(t *testing.T) { - os.Setenv("PRBOT_CONFIG_PATH", "/tmp/") - - assert.Equal(t, "/tmp/", getConfigLocation()) - - t.Cleanup(func() { - os.Unsetenv("PRBOT_CONFIG_PATH") - }) -} diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 23adaee..13162e1 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: gitea-sonarqube-bot description: A Helm Chart for running a bot to communicate between both Gitea and SonarQube type: application -version: 0.1.2 -appVersion: "v0.1.1" +version: 0.2.0 +appVersion: "v0.2.0" home: https://codeberg.org/justusbunsi/gitea-sonarqube-bot/ maintainers: - name: Steven Kriegler diff --git a/helm/README.md b/helm/README.md index 4e929a1..3cd5ccc 100644 --- a/helm/README.md +++ b/helm/README.md @@ -1,5 +1,19 @@ # Gitea SonarQube Bot +_Gitea SonarQube Bot_ is a bot that receives messages from both SonarQube and Gitea to help developers +being productive. The idea behind this project is the missing ALM integration of Gitea in SonarQube. Unfortunately, +this [won't be added in near future](https://github.com/SonarSource/sonarqube/pull/3248#issuecomment-701334327). +_Gitea SonarQube Bot_ aims to fill the gap between working on pull requests and being notified on quality changes. + +- [Gitea SonarQube Bot](#gitea-sonarqube-bot) + - [Installation](#installation) + - [Parameters](#parameters) + - [Common parameters](#common-parameters) + - [App parameters](#app-parameters) + - [Security parameters](#security-parameters) + - [Traffic exposure parameters](#traffic-exposure-parameters) + - [License](#license) + ## Installation ```bash @@ -35,18 +49,19 @@ for full configuration options. ### App parameters -| Name | Description | Value | -| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| `app.configuration.gitea.url` | Endpoint of your Gitea instance. Must be expandable by '/api/v1' to form the API base path as shown in Swagger UI. | `""` | -| `app.configuration.gitea.token.value` | Gitea token as plain text. Can be replaced with `file` key containing path to file. | `""` | -| `app.configuration.sonarqube.url` | Endpoint of your SonarQube instance. Must be expandable by '/api' to form the API base path. | `""` | -| `app.configuration.sonarqube.token.value` | SonarQube token as plain text. Can be replaced with `file` key containing path to file. | `""` | -| `app.configuration.sonarqube.additionalMetrics` | Setting this option you can extend that default list by your own metrics. | `[]` | -| `app.configuration.projects[0].sonarqube.key` | Project key inside SonarQube | `""` | -| `app.configuration.projects[0].gitea.owner` | Repository owner inside Gitea | `""` | -| `app.configuration.projects[0].gitea.name` | Repository name inside Gitea | `""` | -| `volumes` | If token and webhook secrets shall be provided via file, volumes and volume mounts can be configured to setup the environment accordingly | `[]` | -| `volumeMounts` | If token and webhook secrets shall be provided via file, volumes and volume mounts can be configured to setup the environment accordingly | `[]` | +| Name | Description | Value | +| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | +| `app.configLocationOverride` | Override the default location of the configuration file (`/home/bot/config/config.yaml`). **Available since Chart version `0.2.0`. Requires at least image tag `v0.2.0`**. (See values file for details) | `""` | +| `app.configuration.gitea.url` | Endpoint of your Gitea instance. Must be expandable by '/api/v1' to form the API base path as shown in Swagger UI. | `""` | +| `app.configuration.gitea.token.value` | Gitea token as plain text. Can be replaced with `file` key containing path to file. | `""` | +| `app.configuration.sonarqube.url` | Endpoint of your SonarQube instance. Must be expandable by '/api' to form the API base path. | `""` | +| `app.configuration.sonarqube.token.value` | SonarQube token as plain text. Can be replaced with `file` key containing path to file. | `""` | +| `app.configuration.sonarqube.additionalMetrics` | Setting this option you can extend that default list by your own metrics. | `[]` | +| `app.configuration.projects[0].sonarqube.key` | Project key inside SonarQube | `""` | +| `app.configuration.projects[0].gitea.owner` | Repository owner inside Gitea | `""` | +| `app.configuration.projects[0].gitea.name` | Repository name inside Gitea | `""` | +| `volumes` | If token and webhook secrets shall be provided via file, volumes and volume mounts can be configured to setup the environment accordingly | `[]` | +| `volumeMounts` | If token and webhook secrets shall be provided via file, volumes and volume mounts can be configured to setup the environment accordingly | `[]` | ### Security parameters @@ -79,4 +94,4 @@ for full configuration options. ## License -This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for the full license text. +This project is licensed under the MIT License. See the [LICENSE](https://codeberg.org/justusbunsi/gitea-sonarqube-bot/src/branch/main/helm/LICENSE) file for the full license text. diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index 2a04847..481b9d3 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -31,6 +31,11 @@ spec: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.app.configLocationOverride }} + env: + - name: GITEA_SQ_BOT_CONFIG_PATH + value: "{{ .Values.app.configLocationOverride }}" + {{- end}} ports: - name: http containerPort: 3000 @@ -47,7 +52,11 @@ spec: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: - name: sq-bot-config + {{- if .Values.app.configLocationOverride }} + mountPath: {{ dir .Values.app.configLocationOverride }} + {{- else }} mountPath: /home/bot/config + {{- end }} readOnly: true {{- if .Values.volumeMounts }} {{- toYaml .Values.volumeMounts | nindent 12 }} @@ -68,6 +77,11 @@ spec: - name: sq-bot-config secret: secretName: {{ include "helm.fullname" . }} + {{- if .Values.app.configLocationOverride }} + items: + - key: config.yaml + path: {{ base .Values.app.configLocationOverride }} + {{- end }} {{- if .Values.volumes }} {{- toYaml .Values.volumes | nindent 8 }} {{- end }} diff --git a/helm/values.yaml b/helm/values.yaml index 6ac32a8..377758b 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -53,6 +53,10 @@ podAnnotations: {} # @section App parameters app: + # @param app.configLocationOverride Override the default location of the configuration file (`/home/bot/config/config.yaml`). **Available since Chart version `0.2.0`. Requires at least image tag `v0.2.0`**. (See values file for details) + # Setting this will also change the mount point for `.Values.app.configuration` to the directory part of the override value. + configLocationOverride: "" + # This object represents the [config.yaml](https://codeberg.org/justusbunsi/gitea-sonarqube-bot/src/branch/main/config/config.example.yaml) provided to the application. configuration: # Gitea related configuration. Necessary for adding/updating comments on repository pull requests diff --git a/internal/settings/settings.go b/internal/settings/settings.go index 1e1a3d0..373ccaf 100644 --- a/internal/settings/settings.go +++ b/internal/settings/settings.go @@ -13,10 +13,9 @@ var ( Projects []Project ) -func newConfigReader() *viper.Viper { +func newConfigReader(configFile string) *viper.Viper { v := viper.New() - v.SetConfigName("config.yaml") - v.SetConfigType("yaml") + v.SetConfigFile(configFile) v.SetEnvPrefix("prbot") v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.AllowEmptyEnv(true) @@ -38,9 +37,8 @@ func newConfigReader() *viper.Viper { return v } -func Load(configPath string) { - r := newConfigReader() - r.AddConfigPath(configPath) +func Load(configFile string) { + r := newConfigReader(configFile) err := r.ReadInConfig() if err != nil { diff --git a/internal/settings/settings_test.go b/internal/settings/settings_test.go index 91adf57..168c04f 100644 --- a/internal/settings/settings_test.go +++ b/internal/settings/settings_test.go @@ -31,7 +31,7 @@ projects: name: pr-bot `) -func WriteConfigFile(t *testing.T, content []byte) { +func WriteConfigFile(t *testing.T, content []byte) string { dir := os.TempDir() config := path.Join(dir, "config.yaml") @@ -40,21 +40,23 @@ func WriteConfigFile(t *testing.T, content []byte) { }) _ = ioutil.WriteFile(config, content, 0444) + + return config } func TestLoadWithMissingFile(t *testing.T) { - assert.Panics(t, func() { Load(os.TempDir()) }, "No panic while reading missing file") + assert.Panics(t, func() { Load(path.Join(os.TempDir(), "config.yaml")) }, "No panic while reading missing file") } func TestLoadWithExistingFile(t *testing.T) { - WriteConfigFile(t, defaultConfig) + c := WriteConfigFile(t, defaultConfig) - assert.NotPanics(t, func() { Load(os.TempDir()) }, "Unexpected panic while reading existing file") + assert.NotPanics(t, func() { Load(c) }, "Unexpected panic while reading existing file") } func TestLoadGiteaStructure(t *testing.T) { - WriteConfigFile(t, defaultConfig) - Load(os.TempDir()) + c := WriteConfigFile(t, defaultConfig) + Load(c) expected := GiteaConfig{ Url: "https://example.com/gitea", @@ -72,8 +74,8 @@ func TestLoadGiteaStructure(t *testing.T) { func TestLoadGiteaStructureInjectedEnvs(t *testing.T) { os.Setenv("PRBOT_GITEA_WEBHOOK_SECRET", "injected-webhook-secret") os.Setenv("PRBOT_GITEA_TOKEN_VALUE", "injected-token") - WriteConfigFile(t, defaultConfig) - Load(os.TempDir()) + c := WriteConfigFile(t, defaultConfig) + Load(c) expected := GiteaConfig{ Url: "https://example.com/gitea", @@ -94,8 +96,8 @@ func TestLoadGiteaStructureInjectedEnvs(t *testing.T) { } func TestLoadSonarQubeStructure(t *testing.T) { - WriteConfigFile(t, defaultConfig) - Load(os.TempDir()) + c := WriteConfigFile(t, defaultConfig) + Load(c) expected := SonarQubeConfig{ Url: "https://example.com/sonarqube", @@ -112,7 +114,7 @@ func TestLoadSonarQubeStructure(t *testing.T) { } func TestLoadSonarQubeStructureWithAdditionalMetrics(t *testing.T) { - WriteConfigFile(t, []byte( + c := WriteConfigFile(t, []byte( `gitea: url: https://example.com/gitea token: @@ -129,7 +131,7 @@ projects: owner: example-organization name: pr-bot `)) - Load(os.TempDir()) + Load(c) expected := SonarQubeConfig{ Url: "https://example.com/sonarqube", @@ -152,8 +154,8 @@ projects: func TestLoadSonarQubeStructureInjectedEnvs(t *testing.T) { os.Setenv("PRBOT_SONARQUBE_WEBHOOK_SECRET", "injected-webhook-secret") os.Setenv("PRBOT_SONARQUBE_TOKEN_VALUE", "injected-token") - WriteConfigFile(t, defaultConfig) - Load(os.TempDir()) + c := WriteConfigFile(t, defaultConfig) + Load(c) expected := SonarQubeConfig{ Url: "https://example.com/sonarqube", @@ -186,7 +188,7 @@ func TestLoadStructureWithFileReferenceResolving(t *testing.T) { sonarqubeTokenFile := path.Join(os.TempDir(), "token-secret-sonarqube") _ = ioutil.WriteFile(sonarqubeTokenFile, []byte(`a09eb5785b25bb2cbacf48808a677a0709f02d8e`), 0444) - WriteConfigFile(t, []byte( + c := WriteConfigFile(t, []byte( `gitea: url: https://example.com/gitea token: @@ -232,7 +234,7 @@ projects: AdditionalMetrics: []string{}, } - Load(os.TempDir()) + Load(c) assert.EqualValues(t, expectedGitea, Gitea) assert.EqualValues(t, expectedSonarQube, SonarQube) @@ -249,8 +251,8 @@ projects: } func TestLoadProjectsStructure(t *testing.T) { - WriteConfigFile(t, defaultConfig) - Load(os.TempDir()) + c := WriteConfigFile(t, defaultConfig) + Load(c) expectedProjects := []Project{ { @@ -283,7 +285,7 @@ sonarqube: secret: haxxor-sonarqube-secret projects: [] `) - WriteConfigFile(t, invalidConfig) + c := WriteConfigFile(t, invalidConfig) - assert.Panics(t, func() { Load(os.TempDir()) }, "No panic for empty project mapping that is required") + assert.Panics(t, func() { Load(c) }, "No panic for empty project mapping that is required") }