Compare commits
30 commits
Author | SHA1 | Date | |
---|---|---|---|
b632381c90 | |||
f808a58177 | |||
b6b9087651 | |||
54beca9c25 | |||
51211d77cd | |||
d943a7f420 | |||
525fa03065 | |||
99fe6800b0 | |||
477564bbab | |||
81a7251081 | |||
576fc94517 | |||
1b02303a4a | |||
089523c56e | |||
7e008773b0 | |||
c6d62861a6 | |||
385252cd72 | |||
685c834b61 | |||
4aad9c3e17 | |||
02ad0c0bf0 | |||
eb3cb301fc | |||
471b25e682 | |||
0cc1cdc6c8 | |||
ce13a040b8 | |||
dc3969cd05 | |||
5cb3daab60 | |||
e203034228 | |||
7f5c3390c4 | |||
4d28133b12 | |||
34e2783cb1 | |||
f59a6530b8 |
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -6,7 +6,7 @@
|
|||
/vendor/
|
||||
/node_modules/
|
||||
/helm-releases/
|
||||
/gitea-sonarqube-bot
|
||||
/gitea-sonarqube-bot*
|
||||
/coverage.html
|
||||
/*.log
|
||||
/cover.out
|
||||
|
|
58
CHANGELOG.md
Normal file
58
CHANGELOG.md
Normal file
|
@ -0,0 +1,58 @@
|
|||
# Changelog
|
||||
|
||||
## Pending...
|
||||
|
||||
### 👻 Maintenance
|
||||
|
||||
- Remove `fvbock/endless` dependency
|
||||
- Require Golang 1.18 for builds
|
||||
- Update base Docker images
|
||||
|
||||
## v0.2.1
|
||||
|
||||
### 🤖 Application
|
||||
|
||||
- Allow configuring listening port
|
||||
- Allow changing naming pattern for Pull Requests
|
||||
- Improve error handling for SonarQube communication
|
||||
|
||||
### 🐳 Docker image
|
||||
|
||||
- Add `GITEA_SQ_BOT_PORT` environment variable
|
||||
|
||||
### ☸️ Helm Chart
|
||||
|
||||
- Add `.Values.app.listeningPort` parameter
|
||||
- Add `.Values.app.configuration.namingPattern` parameters
|
||||
|
||||
## v0.2.0
|
||||
|
||||
### 🤖 Application
|
||||
|
||||
- Add webhook secret validation
|
||||
- Improve configuration file flexibility
|
||||
- Stop log output for `/ping` and `/favicon.ico` endpoints
|
||||
|
||||
### 🐳 Docker image
|
||||
|
||||
- Add `GITEA_SQ_BOT_CONFIG_PATH` environment variable
|
||||
|
||||
### ☸️ Helm Chart
|
||||
|
||||
- Add `.Values.app.configLocationOverride` parameter
|
||||
- Bump default image tag to `v0.2.0`
|
||||
|
||||
## v0.1.1
|
||||
|
||||
### ☸️ Helm Chart
|
||||
|
||||
- Bump default image tag to `v0.1.1`
|
||||
|
||||
### 👻 Maintenance
|
||||
|
||||
- Bump Golang version to 1.18
|
||||
- Update dependencies to newest versions
|
||||
|
||||
## v0.1.0
|
||||
|
||||
Initial release
|
|
@ -15,10 +15,10 @@
|
|||
|
||||
```bash
|
||||
# Build docker environment
|
||||
docker build -t gitea-sonarqube-pr-bot/dev -f contrib/Dockerfile contrib
|
||||
docker build -t gitea-sonarqube-bot/dev -f contrib/Dockerfile contrib
|
||||
|
||||
# Start the environment
|
||||
docker run --rm -it -p 49182:3000 -v "$(pwd):/projects" gitea-sonarqube-pr-bot/dev
|
||||
docker run --rm -it -p 49182:3000 -v "$(pwd):/projects" gitea-sonarqube-bot/dev
|
||||
```
|
||||
|
||||
## Build and Run
|
||||
|
@ -55,7 +55,7 @@ make helm-params
|
|||
For local purposes
|
||||
|
||||
```bash
|
||||
docker build -t gitea-sonarqube-pr-bot/prod .
|
||||
docker build -t gitea-sonarqube-bot/prod .
|
||||
```
|
||||
|
||||
**Docker image**
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
###################################
|
||||
# Build stages
|
||||
###################################
|
||||
FROM golang:1.18-alpine3.15 AS build-go
|
||||
FROM golang:1.18-alpine3.16@sha256:7cc62574fcf9c5fb87ad42a9789d5539a6a085971d58ee75dd2ee146cb8a8695 AS build-go
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY ${GOPROXY:-direct}
|
||||
|
@ -17,7 +17,7 @@ RUN go build ./cmd/gitea-sonarqube-bot
|
|||
###################################
|
||||
# Production image
|
||||
###################################
|
||||
FROM alpine:3.15
|
||||
FROM alpine:3.16@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c
|
||||
LABEL maintainer="justusbunsi <sk.bunsenbrenner@gmail.com>"
|
||||
|
||||
RUN apk update \
|
||||
|
@ -40,6 +40,8 @@ ENV HOME=/home/bot
|
|||
|
||||
EXPOSE 3000
|
||||
ENV GIN_MODE "release"
|
||||
ENV GITEA_SQ_BOT_CONFIG_PATH "/home/bot/config/config.yaml"
|
||||
ENV GITEA_SQ_BOT_PORT "3000"
|
||||
|
||||
VOLUME ["/home/bot/config/"]
|
||||
RUN ["chmod", "+x", "/usr/local/bin/docker-entrypoint.sh"]
|
||||
|
|
10
Makefile
10
Makefile
|
@ -10,7 +10,8 @@ help:
|
|||
@echo " - test Run full test suite"
|
||||
@echo " - test p=./path/to/package Run test suite for specific package"
|
||||
@echo " - test\#SpecificTestName Run a specific"
|
||||
@echo " - coverage Run full test suite and generates coverage report as HTML file"
|
||||
@echo " - coverage Run full test suite and generate coverage report as HTML file"
|
||||
@echo " - coverage p=./path/to/package Run test suite for specific package and generate coverage report as HTML file"
|
||||
@echo " - helm-params Auto-generates 'Parameters' section of 'helm/README.md' based on comments in values.yaml"
|
||||
@echo " - helm-pack Prepares Helm Chart release artifacts for pushing to 'charts' branch"
|
||||
@echo " - dep Dependency maintenance (tidy, vendor, verify)"
|
||||
|
@ -27,7 +28,7 @@ run:
|
|||
|
||||
clean:
|
||||
go clean
|
||||
rm -f ${BINARY_NAME}
|
||||
rm -f ${BINARY_NAME}*
|
||||
rm -f cover.out cover.html test-report.out
|
||||
|
||||
test:
|
||||
|
@ -44,8 +45,13 @@ test-ci:
|
|||
go test -mod=vendor -coverprofile=cover.out -json ./... > test-report.out
|
||||
|
||||
coverage:
|
||||
ifdef p
|
||||
go test -coverprofile=cover.out $(p)
|
||||
go tool cover -html=cover.out -o cover.html
|
||||
else
|
||||
go test -coverprofile=cover.out ./...
|
||||
go tool cover -html=cover.out -o cover.html
|
||||
endif
|
||||
|
||||
helm-params:
|
||||
npm install
|
||||
|
|
39
README.md
39
README.md
|
@ -22,6 +22,7 @@ Luckily, both endpoints have a proper REST API to communicate with each others.
|
|||
- [SonarQube](#sonarqube)
|
||||
- [Gitea](#gitea)
|
||||
- [CI system](#ci-system)
|
||||
- [Changelog](#changelog)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
- [Screenshots](#screenshots)
|
||||
|
@ -52,15 +53,43 @@ See [config.example.yaml](config/config.example.yaml) for a full configuration s
|
|||
|
||||
## Installation
|
||||
|
||||
Supported environment variables for application runtime configuration:
|
||||
|
||||
| Environment Variable | Purpose | Since |
|
||||
|-----------------------------|---------------------------------|--------|
|
||||
| `GITEA_SQ_BOT_PORT` | Port the bot will listen on | v0.2.1 |
|
||||
| `GITEA_SQ_BOT_CONFIG_PATH` | Full path to configuration file | v0.2.0 |
|
||||
|
||||
For detailed information, use the `--help` flag.
|
||||
|
||||
### Docker
|
||||
|
||||
Create a directory `config` and place your [config.yaml](config/config.example.yaml) inside it. Open a terminal next to this directory
|
||||
and execute the following (replace `$TAG` first):
|
||||
Create a directory `config` and place your [config.yaml](config/config.example.yaml) inside it. Open a terminal inside the newly created directory and execute the following command (replace `$TAG` first):
|
||||
|
||||
```bash
|
||||
docker run --rm -it -p 9000:3000 -v "$(pwd)/config/:/home/bot/config/" justusbunsi/gitea-sonarqube-bot:$TAG
|
||||
docker run --rm -it -p 9000:3000 -v "$(pwd):/home/bot/config/" justusbunsi/gitea-sonarqube-bot:$TAG
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
If there are port mapping issues, you can use any other free port from your host. If you wish to use another port for the bot itself, you can override the default port `3000` by using the environment variable `GITEA_SQ_BOT_PORT`. Let's say you want to consistently use port _9001_ inside and outside the container, a correct command would be:
|
||||
|
||||
```bash
|
||||
# your terminals' pwd is the bot config directory
|
||||
docker run --rm -it -p 9001:9001 -e "GITEA_SQ_BOT_PORT=9001" -v "$(pwd):/home/bot/config/" justusbunsi/gitea-sonarqube-bot:$TAG
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Helm Chart
|
||||
|
||||
See [Helm Chart README](helm/README.md) for detailed instructions.
|
||||
|
@ -89,6 +118,10 @@ To mitigate that situation, the bot will look inside the `properties` object for
|
|||
key can contain the actual commit hash to use for updating the status in Gitea.
|
||||
See [SonarQube docs](https://docs.sonarqube.org/latest/project-administration/webhooks) for details.
|
||||
|
||||
## Changelog
|
||||
|
||||
See [CHANGELOG.md](./CHANGELOG.md) for a complete list of changes.
|
||||
|
||||
## Contributing
|
||||
|
||||
Expected workflow is: Fork -> Patch -> Push -> Pull Request
|
||||
|
|
|
@ -1,33 +1,52 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gitea-sonarqube-pr-bot/internal/api"
|
||||
"gitea-sonarqube-pr-bot/internal/settings"
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/api"
|
||||
giteaSdk "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/clients/gitea"
|
||||
sonarQubeSdk "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/clients/sonarqube"
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/settings"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"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
|
||||
}
|
||||
var (
|
||||
HammerTime time.Duration = 15 * time.Second
|
||||
)
|
||||
|
||||
func main() {
|
||||
settings.Load(getConfigLocation())
|
||||
|
||||
app := &cli.App{
|
||||
Name: "gitea-sonarqube-pr-bot",
|
||||
Name: "gitea-sonarqube-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: api.Serve,
|
||||
Description: `Start an instance of gitea-sonarqube-bot to integrate SonarQube analysis into Gitea Pull Requests.`,
|
||||
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,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "port",
|
||||
Aliases: []string{"p"},
|
||||
Value: 3000,
|
||||
Usage: "Port the bot will listen on.",
|
||||
EnvVars: []string{"GITEA_SQ_BOT_PORT"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run(os.Args)
|
||||
|
@ -35,3 +54,42 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func serveApi(c *cli.Context) error {
|
||||
config := c.Path("config")
|
||||
settings.Load(config)
|
||||
|
||||
log.Println("Hi! I'm Gitea SonarQube Bot. At your service.")
|
||||
log.Println("Config file in use:", config)
|
||||
|
||||
giteaHandler := api.NewGiteaWebhookHandler(giteaSdk.New(&settings.Gitea, gitea.NewClient), sonarQubeSdk.New(&settings.SonarQube))
|
||||
sqHandler := api.NewSonarQubeWebhookHandler(giteaSdk.New(&settings.Gitea, gitea.NewClient), sonarQubeSdk.New(&settings.SonarQube))
|
||||
server := api.New(giteaHandler, sqHandler)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", c.Int("port")),
|
||||
Handler: server.Engine,
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Println("Listen on", srv.Addr)
|
||||
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
log.Println("Shutting down server...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), HammerTime)
|
||||
defer cancel()
|
||||
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
log.Fatal("[STOP - Hammer Time] Forcefully shutting down\n", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
})
|
||||
}
|
|
@ -57,3 +57,17 @@ projects:
|
|||
gitea:
|
||||
owner: justusbunsi
|
||||
name: example-repo
|
||||
|
||||
# Define pull request names from SonarScanner analysis. Default pattern matches the Jenkins Gitea plugin schema.
|
||||
namingPattern:
|
||||
# Regular expression that MUST HAVE exactly ONE GROUP that matches the integer part of the PR.
|
||||
# That integer part is identical to the pull request ID in Gitea.
|
||||
regex: "^PR-(\\d+)$"
|
||||
|
||||
# Valid Go format string. It MUST have one integer placeholder which will be replaced by the pull request ID.
|
||||
# See: https://pkg.go.dev/fmt#hdr-Printing
|
||||
template: "PR-%d"
|
||||
|
||||
# Example for integer-only names
|
||||
# # regex: "^(\\d+)$"
|
||||
# # template: "%d"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1.18-alpine3.15
|
||||
FROM golang:1.18-alpine3.16@sha256:7cc62574fcf9c5fb87ad42a9789d5539a6a085971d58ee75dd2ee146cb8a8695
|
||||
|
||||
RUN apk --no-cache add build-base git bash curl openssl npm
|
||||
|
||||
|
|
31
go.mod
31
go.mod
|
@ -1,14 +1,13 @@
|
|||
module gitea-sonarqube-pr-bot
|
||||
module codeberg.org/justusbunsi/gitea-sonarqube-bot
|
||||
|
||||
go 1.17
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
code.gitea.io/sdk/gitea v0.15.1
|
||||
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
|
||||
github.com/gin-gonic/gin v1.7.7
|
||||
github.com/spf13/viper v1.11.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/urfave/cli/v2 v2.6.0
|
||||
github.com/gin-gonic/gin v1.8.1
|
||||
github.com/spf13/viper v1.12.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/urfave/cli/v2 v2.10.3
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -19,8 +18,8 @@ require (
|
|||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/hashicorp/go-version v1.4.0 // indirect
|
||||
github.com/goccy/go-json v0.9.8 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
|
@ -30,7 +29,7 @@ require (
|
|||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/afero v1.8.2 // indirect
|
||||
|
@ -38,13 +37,15 @@ require (
|
|||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.4.0 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.0 // indirect
|
||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 // indirect
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
||||
golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 // indirect
|
||||
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/ini.v1 v1.66.4 // indirect
|
||||
gopkg.in/ini.v1 v1.66.6 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
387
go.sum
387
go.sum
|
@ -17,30 +17,14 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb
|
|||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
|
||||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
|
||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
||||
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
|
||||
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
|
||||
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
|
@ -56,44 +40,15 @@ code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M=
|
|||
code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
|
@ -105,52 +60,31 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
|||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 h1:6VSn3hB5U5GeA6kQw4TwWIWbOhtvR2hmbBJnTOtqTWc=
|
||||
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6/go.mod h1:YxOVT5+yHzKvwhsiSIWmbAYM3Dr9AEEbER2dVayfBkg=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
|
||||
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/goccy/go-json v0.9.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE=
|
||||
github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
|
@ -158,8 +92,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
|||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -175,10 +107,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
|
|||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
|
@ -189,17 +117,13 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
|
@ -210,70 +134,27 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
|
|||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
|
||||
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
|
||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4=
|
||||
github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
||||
github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
|
@ -281,97 +162,46 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.0.0-beta.8/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
|
||||
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagikazarmark/crypt v0.5.0/go.mod h1:l+nzl7KWh51rpzp2h7t4MZWyiEWdhNpOAnclKvg+mdA=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
|
||||
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44=
|
||||
github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk=
|
||||
github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
|
||||
github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
|
@ -380,51 +210,39 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs=
|
||||
github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/urfave/cli/v2 v2.6.0 h1:yj2Drkflh8X/zUrkWlWlUjZYHyWN7WMmpVxyxXIUyv8=
|
||||
github.com/urfave/cli/v2 v2.6.0/go.mod h1:oDzoM7pVwz6wHn5ogWgFUU1s4VJayeQS+aEZDqXIEJs=
|
||||
github.com/urfave/cli/v2 v2.10.3 h1:oi571Fxz5aHugfBAJd5nkwSk3fzATXtMlpxdLylSCMo=
|
||||
github.com/urfave/cli/v2 v2.10.3/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.2/go.mod h1:2D7ZejHVMIfog1221iLSYlQRzrtECw3kz4I4VAQm3qI=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c=
|
||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -448,7 +266,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
|
|||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
|
@ -459,10 +276,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
|
@ -470,11 +285,9 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -491,20 +304,12 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
|
|||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 h1:8NSylCMxLW4JvserAndSgFL7aPli6A68yf0bYFTcWCM=
|
||||
golang.org/x/net v0.0.0-20220706163947-c90051bbdb60/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -514,17 +319,6 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
|
|||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -535,32 +329,20 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -578,46 +360,22 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY=
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I=
|
||||
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
|
@ -637,7 +395,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
|
|||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
|
@ -663,7 +420,6 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
|
|||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
|
@ -672,20 +428,12 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f
|
|||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
|
@ -705,23 +453,6 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513
|
|||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
|
||||
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
|
||||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
|
||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
|
||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
||||
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
||||
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
|
||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
||||
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
|
||||
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
|
||||
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
|
||||
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
|
||||
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
@ -752,7 +483,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
|
|||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
|
@ -765,42 +495,7 @@ google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6D
|
|||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
||||
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
||||
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
|
@ -814,22 +509,9 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
|
|||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
@ -841,31 +523,22 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
|||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
|
||||
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
|
||||
gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99 h1:dbuHpmKjkDzSOMKAWl10QNlgaZUd3V1q99xc81tt2Kc=
|
||||
gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -2,18 +2,25 @@ 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.1
|
||||
appVersion: "v0.1.1"
|
||||
keywords:
|
||||
- code review
|
||||
- git
|
||||
- gitea
|
||||
- pull request
|
||||
- sonarqube
|
||||
- sonarcloud
|
||||
sources:
|
||||
- https://codeberg.org/justusbunsi/gitea-sonarqube-bot/
|
||||
- https://hub.docker.com/r/justusbunsi/gitea-sonarqube-bot/
|
||||
version: 0.2.1
|
||||
appVersion: "v0.2.1"
|
||||
home: https://codeberg.org/justusbunsi/gitea-sonarqube-bot/
|
||||
maintainers:
|
||||
- name: Steven Kriegler
|
||||
email: sk.bunsenbrenner@gmail.com
|
||||
keywords:
|
||||
- code-quality
|
||||
- code-review
|
||||
- git
|
||||
- gitea
|
||||
- pull-request
|
||||
- sonarqube
|
||||
- sonarcloud
|
||||
sources:
|
||||
- https://codeberg.org/justusbunsi/gitea-sonarqube-bot/src/branch/main/helm
|
||||
annotations:
|
||||
artifacthub.io/links: |
|
||||
- name: support
|
||||
url: https://codeberg.org/justusbunsi/gitea-sonarqube-bot/issues
|
||||
- name: Container image
|
||||
url: https://hub.docker.com/r/justusbunsi/gitea-sonarqube-bot/
|
||||
|
|
19
helm/LICENSE
Normal file
19
helm/LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2022 Steven Kriegler
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -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
|
||||
|
@ -12,6 +26,10 @@ You have to modify the `app.configuration` values. Otherwise, the bot won't star
|
|||
to your Gitea instance. See [config.example.yaml](https://codeberg.org/justusbunsi/gitea-sonarqube-bot/src/branch/main/config/config.example.yaml)
|
||||
for full configuration options.
|
||||
|
||||
## Changelog
|
||||
|
||||
You can find a full changelog in the [main repository](https://codeberg.org/justusbunsi/gitea-sonarqube-bot/src/branch/main/CHANGELOG.md) of this project.
|
||||
|
||||
## Parameters
|
||||
|
||||
### Common parameters
|
||||
|
@ -36,15 +54,23 @@ for full configuration options.
|
|||
### App parameters
|
||||
|
||||
| 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.listeningPort` | Port the application will listening on inside the pod container. **Available since Chart version `0.2.1`. Requires at least image tag `v0.2.1`**. | `3000` |
|
||||
| `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.gitea.webhook.secret` | Secret for signature header (in plaintext) | `""` |
|
||||
| `app.configuration.gitea.webhook.secretFile` | Path to file containing the plain text secret. Alternative to inline `app.configuration.gitea.webhook.secret` | |
|
||||
| `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.webhook.secret` | Secret for signature header (in plaintext) | `""` |
|
||||
| `app.configuration.sonarqube.webhook.secretFile` | Path to file containing the plain text secret. Alternative to inline `app.configuration.sonarqube.webhook.secret` | |
|
||||
| `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 | `""` |
|
||||
| `app.configuration.namingPattern.regex` | Regular expression that MUST HAVE exactly ONE GROUP that matches the integer part of the PR. That integer part is identical to the pull request ID in Gitea. | `^PR-(\d+)$` |
|
||||
| `app.configuration.namingPattern.template` | Valid Go format string. It MUST have one integer placeholder which will be replaced by the pull request ID. See: https://pkg.go.dev/fmt#hdr-Printing | `PR-%d` |
|
||||
| `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 | `[]` |
|
||||
|
||||
|
@ -77,3 +103,6 @@ for full configuration options.
|
|||
| `ingress.tls` | The tls configuration for additional hostnames to be covered with configured ingress. | `[]` |
|
||||
|
||||
|
||||
## License
|
||||
|
||||
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.
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"comments": {
|
||||
"format": "#"
|
||||
},
|
||||
"tags": {
|
||||
"param": "@param",
|
||||
"section": "@section",
|
||||
"skip": "@skip",
|
||||
"extra": "@extra"
|
||||
},
|
||||
"modifiers": {
|
||||
"array": "array",
|
||||
"object": "object",
|
||||
"string": "string"
|
||||
},
|
||||
"regexp": {
|
||||
"paramsSectionTitle": "Parameters"
|
||||
}
|
||||
}
|
|
@ -11,8 +11,9 @@ spec:
|
|||
{{- include "helm.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/config.yaml") . | sha256sum }}
|
||||
{{- with .Values.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
|
@ -31,9 +32,16 @@ spec:
|
|||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
env:
|
||||
{{- if .Values.app.configLocationOverride }}
|
||||
- name: GITEA_SQ_BOT_CONFIG_PATH
|
||||
value: "{{ .Values.app.configLocationOverride }}"
|
||||
{{- end}}
|
||||
- name: GITEA_SQ_BOT_PORT
|
||||
value: "{{ .Values.app.listeningPort }}"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 3000
|
||||
containerPort: {{ .Values.app.listeningPort }}
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
@ -47,7 +55,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 +80,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 }}
|
||||
|
|
178
helm/values.yaml
178
helm/values.yaml
|
@ -1,32 +1,32 @@
|
|||
# @section Common parameters
|
||||
## @section Common parameters
|
||||
|
||||
# @param replicaCount Number of replicas for the bot
|
||||
## @param replicaCount Number of replicas for the bot
|
||||
replicaCount: 1
|
||||
|
||||
# ref: https://hub.docker.com/r/justusbunsi/gitea-sonarqube-bot/tags/
|
||||
# @param image.repository Image repository
|
||||
# @param image.pullPolicy Image pull policy
|
||||
# @param image.tag Image tag (Overrides the image tag whose default is the chart `appVersion`)
|
||||
## ref: https://hub.docker.com/r/justusbunsi/gitea-sonarqube-bot/tags/
|
||||
## @param image.repository Image repository
|
||||
## @param image.pullPolicy Image pull policy
|
||||
## @param image.tag Image tag (Overrides the image tag whose default is the chart `appVersion`)
|
||||
image:
|
||||
repository: justusbunsi/gitea-sonarqube-bot
|
||||
pullPolicy: IfNotPresent
|
||||
tag: ""
|
||||
|
||||
# @param imagePullSecrets Specify docker-registry secret names as an array
|
||||
## @param imagePullSecrets Specify docker-registry secret names as an array
|
||||
imagePullSecrets: []
|
||||
|
||||
# @param nameOverride String to partially override common.names.fullname template (will maintain the release name)
|
||||
## @param nameOverride String to partially override common.names.fullname template (will maintain the release name)
|
||||
nameOverride: ""
|
||||
|
||||
# @param fullnameOverride String to fully override common.names.fullname template
|
||||
## @param fullnameOverride String to fully override common.names.fullname template
|
||||
fullnameOverride: ""
|
||||
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# @param resources.limits The resources limits for the container
|
||||
# @param resources.requests The requested resources for the container
|
||||
## We usually recommend not to specify default resources and to leave this as a conscious
|
||||
## choice for the user. This also increases chances charts run on environments with little
|
||||
## resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
## lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
## @param resources.limits The resources limits for the container
|
||||
## @param resources.requests The requested resources for the container
|
||||
resources:
|
||||
limits: {}
|
||||
# cpu: 100m
|
||||
|
@ -35,94 +35,110 @@ resources:
|
|||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
# @param nodeSelector Node labels for pod assignment. Evaluated as a template.
|
||||
# ref: https://kubernetes.io/docs/user-guide/node-selection/
|
||||
## @param nodeSelector Node labels for pod assignment. Evaluated as a template.
|
||||
## ref: https://kubernetes.io/docs/user-guide/node-selection/
|
||||
nodeSelector: {}
|
||||
|
||||
# @param tolerations Tolerations for pod assignment. Evaluated as a template.
|
||||
# ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
|
||||
## @param tolerations Tolerations for pod assignment. Evaluated as a template.
|
||||
## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
|
||||
tolerations: []
|
||||
|
||||
# @param affinity Affinity for pod assignment. Evaluated as a template.
|
||||
# ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
|
||||
## @param affinity Affinity for pod assignment. Evaluated as a template.
|
||||
## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
|
||||
affinity: {}
|
||||
|
||||
# @param podAnnotations Pod annotations.
|
||||
## @param podAnnotations Pod annotations.
|
||||
podAnnotations: {}
|
||||
|
||||
# @section App parameters
|
||||
## @section App parameters
|
||||
|
||||
app:
|
||||
# This object represents the [config.yaml](https://codeberg.org/justusbunsi/gitea-sonarqube-bot/src/branch/main/config/config.example.yaml) provided to the application.
|
||||
## @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: ""
|
||||
|
||||
## @param app.listeningPort Port the application will listening on inside the pod container. **Available since Chart version `0.2.1`. Requires at least image tag `v0.2.1`**.
|
||||
listeningPort: 3000
|
||||
|
||||
## 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
|
||||
## Gitea related configuration. Necessary for adding/updating comments on repository pull requests
|
||||
gitea:
|
||||
# @param 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.
|
||||
## @param 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.
|
||||
url: ""
|
||||
|
||||
# Created access token for the user that shall be used as bot account.
|
||||
# User needs "Read project" permissions with access to "Pull Requests"
|
||||
# @param app.configuration.gitea.token.value Gitea token as plain text. Can be replaced with `file` key containing path to file.
|
||||
## Created access token for the user that shall be used as bot account.
|
||||
## User needs "Read project" permissions with access to "Pull Requests"
|
||||
## @param app.configuration.gitea.token.value Gitea token as plain text. Can be replaced with `file` key containing path to file.
|
||||
token:
|
||||
value: ""
|
||||
# # or path to file containing the plain text secret
|
||||
# file: /bot/secrets/gitea/user-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.
|
||||
# The bot looks for `X-Gitea-Signature` header containing the sha256 hmac hash of the plain text secret. If the header
|
||||
# exists and no webhookSecret is defined here, the bot will ignore the request, because it cannot be validated.
|
||||
# @skip app.configuration.gitea.webhook
|
||||
## 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.
|
||||
## The bot looks for `X-Gitea-Signature` header containing the sha256 hmac hash of the plain text secret. If the header
|
||||
## exists and no webhookSecret is defined here, the bot will ignore the request, because it cannot be validated.
|
||||
## @param app.configuration.gitea.webhook.secret Secret for signature header (in plaintext)
|
||||
## @extra app.configuration.gitea.webhook.secretFile Path to file containing the plain text secret. Alternative to inline `app.configuration.gitea.webhook.secret`
|
||||
webhook:
|
||||
secret: ""
|
||||
# # or path to file containing the plain text secret
|
||||
# secretFile: /bot/secrets/gitea/webhook-secret
|
||||
|
||||
# SonarQube related configuration. Necessary for requesting data from the API and processing the webhook.
|
||||
## SonarQube related configuration. Necessary for requesting data from the API and processing the webhook.
|
||||
sonarqube:
|
||||
# @param app.configuration.sonarqube.url Endpoint of your SonarQube instance. Must be expandable by '/api' to form the API base path.
|
||||
## @param app.configuration.sonarqube.url Endpoint of your SonarQube instance. Must be expandable by '/api' to form the API base path.
|
||||
url: ""
|
||||
|
||||
# Created access token for the user that shall be used as bot account.
|
||||
# User needs "Browse on project" permissions
|
||||
# @param app.configuration.sonarqube.token.value SonarQube token as plain text. Can be replaced with `file` key containing path to file.
|
||||
## Created access token for the user that shall be used as bot account.
|
||||
## User needs "Browse on project" permissions
|
||||
## @param app.configuration.sonarqube.token.value SonarQube token as plain text. Can be replaced with `file` key containing path to file.
|
||||
token:
|
||||
value: ""
|
||||
# # or path to file containing the plain text secret
|
||||
# file: /bot/secrets/sonarqube/user-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.
|
||||
# The bot looks for `X-Sonar-Webhook-HMAC-SHA256` header containing the sha256 hmac hash of the plain text secret.
|
||||
# If the header exists and no webhookSecret is defined here, the bot will ignore the request, because it cannot be
|
||||
# validated.
|
||||
# @skip app.configuration.sonarqube.webhook
|
||||
## 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.
|
||||
## The bot looks for `X-Sonar-Webhook-HMAC-SHA256` header containing the sha256 hmac hash of the plain text secret.
|
||||
## If the header exists and no webhookSecret is defined here, the bot will ignore the request, because it cannot be
|
||||
## validated.
|
||||
## @param app.configuration.sonarqube.webhook.secret Secret for signature header (in plaintext)
|
||||
## @extra app.configuration.sonarqube.webhook.secretFile Path to file containing the plain text secret. Alternative to inline `app.configuration.sonarqube.webhook.secret`
|
||||
webhook:
|
||||
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"
|
||||
# @param app.configuration.sonarqube.additionalMetrics Setting this option you can extend that default list by your own metrics.
|
||||
## 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"
|
||||
## @param app.configuration.sonarqube.additionalMetrics 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.
|
||||
# @param app.configuration.projects[0].sonarqube.key Project key inside SonarQube
|
||||
# @param app.configuration.projects[0].gitea.owner Repository owner inside Gitea
|
||||
# @param app.configuration.projects[0].gitea.name Repository name inside Gitea
|
||||
## 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.
|
||||
## @param app.configuration.projects[0].sonarqube.key Project key inside SonarQube
|
||||
## @param app.configuration.projects[0].gitea.owner Repository owner inside Gitea
|
||||
## @param app.configuration.projects[0].gitea.name Repository name inside Gitea
|
||||
projects:
|
||||
- sonarqube:
|
||||
key: ""
|
||||
# A repository specification contains the owner name and the repository name itself. The owner can be the name of a
|
||||
# real account or an organization in which the repository is located.
|
||||
## A repository specification contains the owner name and the repository name itself. The owner can be the name of a
|
||||
## real account or an organization in which the repository is located.
|
||||
gitea:
|
||||
owner: ""
|
||||
name: ""
|
||||
|
||||
# @param volumes If token and webhook secrets shall be provided via file, volumes and volume mounts can be configured to setup the environment accordingly
|
||||
## Define pull request names from SonarScanner analysis. Default pattern matches the Jenkins Gitea plugin schema.
|
||||
## @param app.configuration.namingPattern.regex Regular expression that MUST HAVE exactly ONE GROUP that matches the integer part of the PR. That integer part is identical to the pull request ID in Gitea.
|
||||
## @param app.configuration.namingPattern.template Valid Go format string. It MUST have one integer placeholder which will be replaced by the pull request ID. See: https://pkg.go.dev/fmt#hdr-Printing
|
||||
namingPattern:
|
||||
regex: "^PR-(\\d+)$"
|
||||
template: "PR-%d"
|
||||
|
||||
## @param volumes If token and webhook secrets shall be provided via file, volumes and volume mounts can be configured to setup the environment accordingly
|
||||
volumes: []
|
||||
# - name: gitea-connection
|
||||
# secret:
|
||||
|
@ -131,7 +147,7 @@ volumes: []
|
|||
# secret:
|
||||
# secretName: sonarqube-secret-with-token-and-maybe-webhook-secret
|
||||
|
||||
# @param volumeMounts If token and webhook secrets shall be provided via file, volumes and volume mounts can be configured to setup the environment accordingly
|
||||
## @param volumeMounts If token and webhook secrets shall be provided via file, volumes and volume mounts can be configured to setup the environment accordingly
|
||||
volumeMounts: []
|
||||
# - name: gitea-connection
|
||||
# readOnly: true
|
||||
|
@ -140,25 +156,25 @@ volumeMounts: []
|
|||
# readOnly: true
|
||||
# mountPath: "/bot/secrets/sonarqube/"
|
||||
|
||||
# @section Security parameters
|
||||
## @section Security parameters
|
||||
|
||||
serviceAccount:
|
||||
# @param serviceAccount.create Specifies whether a service account should be created
|
||||
## @param serviceAccount.create Specifies whether a service account should be created
|
||||
create: true
|
||||
# @param serviceAccount.annotations Annotations to add to the service account
|
||||
## @param serviceAccount.annotations Annotations to add to the service account
|
||||
annotations: {}
|
||||
# @param serviceAccount.name The name of the service account to use. If not set and create is true, a name is generated using the fullname template
|
||||
## @param serviceAccount.name The name of the service account to use. If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
|
||||
# ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod
|
||||
# @param podSecurityContext.fsGroup Group ID for the container
|
||||
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod
|
||||
## @param podSecurityContext.fsGroup Group ID for the container
|
||||
podSecurityContext:
|
||||
fsGroup: 1000
|
||||
|
||||
# ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container
|
||||
# @param securityContext.readOnlyRootFilesystem Mounts the container's root filesystem as read-only
|
||||
# @param securityContext.runAsNonRoot Avoid running as root user
|
||||
# @param securityContext.runAsUser User ID for the container
|
||||
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container
|
||||
## @param securityContext.readOnlyRootFilesystem Mounts the container's root filesystem as read-only
|
||||
## @param securityContext.runAsNonRoot Avoid running as root user
|
||||
## @param securityContext.runAsUser User ID for the container
|
||||
securityContext:
|
||||
# capabilities:
|
||||
# drop:
|
||||
|
@ -167,41 +183,41 @@ securityContext:
|
|||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
|
||||
# @section Traffic exposure parameters
|
||||
## @section Traffic exposure parameters
|
||||
|
||||
# @param service.type Service type
|
||||
# @param service.port Service port
|
||||
## @param service.type Service type
|
||||
## @param service.port Service port
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
# ref: https://kubernetes.io/docs/user-guide/ingress/
|
||||
## ref: https://kubernetes.io/docs/user-guide/ingress/
|
||||
ingress:
|
||||
|
||||
# @param ingress.enabled Enable ingress controller resource
|
||||
## @param ingress.enabled Enable ingress controller resource
|
||||
enabled: false
|
||||
|
||||
# @param ingress.className IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+)
|
||||
# This is supported in Kubernetes 1.18+ and required if you have more than one IngressClass marked as the default for your cluster.
|
||||
# ref: https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/
|
||||
## @param ingress.className IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+)
|
||||
## This is supported in Kubernetes 1.18+ and required if you have more than one IngressClass marked as the default for your cluster.
|
||||
## ref: https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/
|
||||
className: ""
|
||||
|
||||
# @param ingress.annotations Additional annotations for the Ingress resource.
|
||||
## @param ingress.annotations Additional annotations for the Ingress resource.
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
|
||||
# @param ingress.hosts[0].host Host for the ingress resource
|
||||
# @param ingress.hosts[0].paths[0].path The path to the bot endpoint
|
||||
# @param ingress.hosts[0].paths[0].pathType Ingress path type
|
||||
## @param ingress.hosts[0].host Host for the ingress resource
|
||||
## @param ingress.hosts[0].paths[0].path The path to the bot endpoint
|
||||
## @param ingress.hosts[0].paths[0].pathType Ingress path type
|
||||
hosts:
|
||||
- host: sqbot.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
|
||||
# @param ingress.tls The tls configuration for additional hostnames to be covered with configured ingress.
|
||||
# see: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls
|
||||
## @param ingress.tls The tls configuration for additional hostnames to be covered with configured ingress.
|
||||
## see: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls
|
||||
tls: []
|
||||
# - hosts:
|
||||
# - sqbot.example.com
|
||||
|
|
|
@ -6,12 +6,14 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsValidBotCommentForInvalidComment(t *testing.T) {
|
||||
func TestIsValidBotComment(t *testing.T) {
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
assert.True(t, IsValidBotComment("/sq-bot review"), "Correct bot comment not recognized")
|
||||
})
|
||||
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
assert.False(t, IsValidBotComment(""), "Undetected missing action prefix")
|
||||
assert.False(t, IsValidBotComment("/sq-bot invalid-command"), "Undetected invalid bot command")
|
||||
assert.False(t, IsValidBotComment("Some context with /sq-bot review within"), "Incorrect bot prefix detected inside random comment")
|
||||
}
|
||||
|
||||
func TestIsValidBotCommentForValidComment(t *testing.T) {
|
||||
assert.True(t, IsValidBotComment("/sq-bot review"), "Correct bot comment not recognized")
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,23 +1,27 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
giteaSdk "gitea-sonarqube-pr-bot/internal/clients/gitea"
|
||||
sqSdk "gitea-sonarqube-pr-bot/internal/clients/sonarqube"
|
||||
webhook "gitea-sonarqube-pr-bot/internal/webhooks/gitea"
|
||||
giteaSdk "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/clients/gitea"
|
||||
sqSdk "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/clients/sonarqube"
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/settings"
|
||||
webhook "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/webhooks/gitea"
|
||||
)
|
||||
|
||||
type GiteaWebhookHandlerInferface interface {
|
||||
HandleSynchronize(r *http.Request) (int, string)
|
||||
HandleComment(r *http.Request) (int, string)
|
||||
}
|
||||
|
||||
type GiteaWebhookHandler struct {
|
||||
giteaSdk giteaSdk.GiteaSdkInterface
|
||||
sqSdk sqSdk.SonarQubeSdkInterface
|
||||
}
|
||||
|
||||
func (h *GiteaWebhookHandler) parseBody(rw http.ResponseWriter, r *http.Request) ([]byte, error) {
|
||||
func (h *GiteaWebhookHandler) parseBody(r *http.Request) ([]byte, error) {
|
||||
if r.Body != nil {
|
||||
defer r.Body.Close()
|
||||
}
|
||||
|
@ -26,69 +30,65 @@ func (h *GiteaWebhookHandler) parseBody(rw http.ResponseWriter, r *http.Request)
|
|||
|
||||
if err != nil {
|
||||
log.Printf("Error reading request body %s", err.Error())
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
io.WriteString(rw, fmt.Sprintf(`{"message": "%s"}`, err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func (h *GiteaWebhookHandler) HandleSynchronize(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
raw, err := h.parseBody(rw, r)
|
||||
func (h *GiteaWebhookHandler) HandleSynchronize(r *http.Request) (int, string) {
|
||||
raw, err := h.parseBody(r)
|
||||
if err != nil {
|
||||
return
|
||||
return http.StatusInternalServerError, err.Error()
|
||||
}
|
||||
|
||||
ok, err := isValidWebhook(raw, settings.Gitea.Webhook.Secret, r.Header.Get("X-Gitea-Signature"), "Gitea")
|
||||
if !ok {
|
||||
log.Print(err.Error())
|
||||
return http.StatusPreconditionFailed, "Webhook validation failed. Request rejected."
|
||||
}
|
||||
|
||||
w, ok := webhook.NewPullWebhook(raw)
|
||||
if !ok {
|
||||
rw.WriteHeader(http.StatusUnprocessableEntity)
|
||||
io.WriteString(rw, `{"message": "Error parsing POST body."}`)
|
||||
return
|
||||
return http.StatusUnprocessableEntity, "Error parsing POST body."
|
||||
}
|
||||
|
||||
if err := w.Validate(); err != nil {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
io.WriteString(rw, fmt.Sprintf(`{"message": "%s"}`, err.Error()))
|
||||
return
|
||||
return http.StatusOK, err.Error()
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
io.WriteString(rw, `{"message": "Processing data. See bot logs for details."}`)
|
||||
|
||||
w.ProcessData(h.giteaSdk, h.sqSdk)
|
||||
|
||||
return http.StatusOK, "Processing data. See bot logs for details."
|
||||
}
|
||||
|
||||
func (h *GiteaWebhookHandler) HandleComment(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
raw, err := h.parseBody(rw, r)
|
||||
func (h *GiteaWebhookHandler) HandleComment(r *http.Request) (int, string) {
|
||||
raw, err := h.parseBody(r)
|
||||
if err != nil {
|
||||
return
|
||||
return http.StatusInternalServerError, err.Error()
|
||||
}
|
||||
|
||||
ok, err := isValidWebhook(raw, settings.Gitea.Webhook.Secret, r.Header.Get("X-Gitea-Signature"), "Gitea")
|
||||
if !ok {
|
||||
log.Print(err.Error())
|
||||
return http.StatusPreconditionFailed, "Webhook validation failed. Request rejected."
|
||||
}
|
||||
|
||||
w, ok := webhook.NewCommentWebhook(raw)
|
||||
if !ok {
|
||||
rw.WriteHeader(http.StatusUnprocessableEntity)
|
||||
io.WriteString(rw, `{"message": "Error parsing POST body."}`)
|
||||
return
|
||||
return http.StatusUnprocessableEntity, "Error parsing POST body."
|
||||
}
|
||||
|
||||
if err := w.Validate(); err != nil {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
io.WriteString(rw, fmt.Sprintf(`{"message": "%s"}`, err.Error()))
|
||||
return
|
||||
return http.StatusOK, err.Error()
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
io.WriteString(rw, `{"message": "Processing data. See bot logs for details."}`)
|
||||
|
||||
w.ProcessData(h.giteaSdk, h.sqSdk)
|
||||
|
||||
return http.StatusOK, "Processing data. See bot logs for details."
|
||||
}
|
||||
|
||||
func NewGiteaWebhookHandler(g giteaSdk.GiteaSdkInterface, sq sqSdk.SonarQubeSdkInterface) *GiteaWebhookHandler {
|
||||
func NewGiteaWebhookHandler(g giteaSdk.GiteaSdkInterface, sq sqSdk.SonarQubeSdkInterface) GiteaWebhookHandlerInferface {
|
||||
return &GiteaWebhookHandler{
|
||||
giteaSdk: g,
|
||||
sqSdk: sq,
|
||||
|
|
241
internal/api/gitea_test.go
Normal file
241
internal/api/gitea_test.go
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,32 +1,38 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
giteaSdk "gitea-sonarqube-pr-bot/internal/clients/gitea"
|
||||
sqSdk "gitea-sonarqube-pr-bot/internal/clients/sonarqube"
|
||||
|
||||
"github.com/fvbock/endless"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func addPingApi(r *gin.Engine) {
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "pong",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type validSonarQubeEndpointHeader struct {
|
||||
SonarQubeProject string `header:"X-SonarQube-Project" binding:"required"`
|
||||
}
|
||||
|
||||
func addSonarQubeEndpoint(r *gin.Engine) {
|
||||
webhookHandler := NewSonarQubeWebhookHandler(giteaSdk.New(), sqSdk.New())
|
||||
r.POST("/hooks/sonarqube", func(c *gin.Context) {
|
||||
type validGiteaEndpointHeader struct {
|
||||
GiteaEvent string `header:"X-Gitea-Event" binding:"required"`
|
||||
}
|
||||
|
||||
type ApiServer struct {
|
||||
Engine *gin.Engine
|
||||
sonarQubeWebhookHandler SonarQubeWebhookHandlerInferface
|
||||
giteaWebhookHandler GiteaWebhookHandlerInferface
|
||||
}
|
||||
|
||||
func (s *ApiServer) setup() {
|
||||
s.Engine.Use(gin.Recovery())
|
||||
s.Engine.Use(gin.LoggerWithConfig(gin.LoggerConfig{
|
||||
SkipPaths: []string{"/ping", "/favicon.ico"},
|
||||
}))
|
||||
|
||||
s.Engine.GET("/favicon.ico", func(c *gin.Context) {
|
||||
c.Status(http.StatusNoContent)
|
||||
}).GET("/ping", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "pong",
|
||||
})
|
||||
}).POST("/hooks/sonarqube", func(c *gin.Context) {
|
||||
h := validSonarQubeEndpointHeader{}
|
||||
|
||||
if err := c.ShouldBindHeader(&h); err != nil {
|
||||
|
@ -34,17 +40,11 @@ func addSonarQubeEndpoint(r *gin.Engine) {
|
|||
return
|
||||
}
|
||||
|
||||
webhookHandler.Handle(c.Writer, c.Request)
|
||||
status, response := s.sonarQubeWebhookHandler.Handle(c.Request)
|
||||
c.JSON(status, gin.H{
|
||||
"message": response,
|
||||
})
|
||||
}
|
||||
|
||||
type validGiteaEndpointHeader struct {
|
||||
GiteaEvent string `header:"X-Gitea-Event" binding:"required"`
|
||||
}
|
||||
|
||||
func addGiteaEndpoint(r *gin.Engine) {
|
||||
webhookHandler := NewGiteaWebhookHandler(giteaSdk.New(), sqSdk.New())
|
||||
r.POST("/hooks/gitea", func(c *gin.Context) {
|
||||
}).POST("/hooks/gitea", func(c *gin.Context) {
|
||||
h := validGiteaEndpointHeader{}
|
||||
|
||||
if err := c.ShouldBindHeader(&h); err != nil {
|
||||
|
@ -52,31 +52,33 @@ func addGiteaEndpoint(r *gin.Engine) {
|
|||
return
|
||||
}
|
||||
|
||||
var status int
|
||||
var response string
|
||||
|
||||
switch h.GiteaEvent {
|
||||
case "pull_request":
|
||||
webhookHandler.HandleSynchronize(c.Writer, c.Request)
|
||||
status, response = s.giteaWebhookHandler.HandleSynchronize(c.Request)
|
||||
case "issue_comment":
|
||||
webhookHandler.HandleComment(c.Writer, c.Request)
|
||||
status, response = s.giteaWebhookHandler.HandleComment(c.Request)
|
||||
default:
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ignore unknown event",
|
||||
})
|
||||
status = http.StatusOK
|
||||
response = "ignore unknown event"
|
||||
}
|
||||
|
||||
c.JSON(status, gin.H{
|
||||
"message": response,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Serve(c *cli.Context) error {
|
||||
fmt.Println("Hi! I'm the Gitea-SonarQube-PR bot. At your service.")
|
||||
func New(giteaHandler GiteaWebhookHandlerInferface, sonarQubeHandler SonarQubeWebhookHandlerInferface) *ApiServer {
|
||||
s := &ApiServer{
|
||||
Engine: gin.New(),
|
||||
giteaWebhookHandler: giteaHandler,
|
||||
sonarQubeWebhookHandler: sonarQubeHandler,
|
||||
}
|
||||
|
||||
r := gin.Default()
|
||||
s.setup()
|
||||
|
||||
addPingApi(r)
|
||||
addSonarQubeEndpoint(r)
|
||||
addGiteaEndpoint(r)
|
||||
|
||||
r.GET("/favicon.ico", func(c *gin.Context) {
|
||||
c.Status(http.StatusNoContent)
|
||||
})
|
||||
|
||||
return endless.ListenAndServe(":3000", r)
|
||||
return s
|
||||
}
|
||||
|
|
|
@ -1,14 +1,207 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
giteaSdk "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/clients/gitea"
|
||||
sqSdk "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/clients/sonarqube"
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/settings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type SonarQubeHandlerMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (h *SonarQubeHandlerMock) Handle(r *http.Request) (int, string) {
|
||||
h.Called(r)
|
||||
return http.StatusOK, "test-execution"
|
||||
}
|
||||
|
||||
type GiteaHandlerMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (h *GiteaHandlerMock) HandleSynchronize(r *http.Request) (int, string) {
|
||||
h.Called(r)
|
||||
return http.StatusOK, "test-execution"
|
||||
}
|
||||
|
||||
func (h *GiteaHandlerMock) HandleComment(r *http.Request) (int, string) {
|
||||
h.Called(r)
|
||||
return http.StatusOK, "test-execution"
|
||||
}
|
||||
|
||||
type GiteaSdkMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (h *GiteaSdkMock) PostComment(_ settings.GiteaRepository, _ int, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *GiteaSdkMock) DetermineHEAD(_ settings.GiteaRepository, _ int64) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (h *GiteaSdkMock) UpdateStatus(_ settings.GiteaRepository, _ string, _ giteaSdk.StatusDetails) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type SQSdkMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (h *SQSdkMock) GetMeasures(project string, branch string) (*sqSdk.MeasuresResponse, error) {
|
||||
return &sqSdk.MeasuresResponse{}, nil
|
||||
}
|
||||
|
||||
func (h *SQSdkMock) GetPullRequestUrl(project string, index int64) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *SQSdkMock) GetPullRequest(project string, index int64) (*sqSdk.PullRequest, error) {
|
||||
return &sqSdk.PullRequest{
|
||||
Status: struct {
|
||||
QualityGateStatus string "json:\"qualityGateStatus\""
|
||||
}{
|
||||
QualityGateStatus: "OK",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *SQSdkMock) ComposeGiteaComment(data *sqSdk.CommentComposeData) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// SETUP: mute logs
|
||||
func TestMain(m *testing.M) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
gin.DefaultWriter = ioutil.Discard
|
||||
gin.DefaultErrorWriter = ioutil.Discard
|
||||
log.SetOutput(ioutil.Discard)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestNonAPIRoutes(t *testing.T) {
|
||||
t.Run("favicon", func(t *testing.T) {
|
||||
router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/favicon.ico", nil)
|
||||
router.Engine.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
})
|
||||
|
||||
t.Run("ping", func(t *testing.T) {
|
||||
router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/ping", nil)
|
||||
router.Engine.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSonarQubeAPIRoute(t *testing.T) {
|
||||
t.Run("Missing project header", func(t *testing.T) {
|
||||
router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer([]byte(`{}`)))
|
||||
router.Engine.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
})
|
||||
|
||||
t.Run("Processing", func(t *testing.T) {
|
||||
sonarQubeHandlerMock := new(SonarQubeHandlerMock)
|
||||
sonarQubeHandlerMock.On("Handle", mock.IsType(&http.Request{}))
|
||||
|
||||
router := New(new(GiteaHandlerMock), sonarQubeHandlerMock)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer([]byte(`{}`)))
|
||||
req.Header.Add("X-SonarQube-Project", "gitea-sonarqube-bot")
|
||||
router.Engine.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
sonarQubeHandlerMock.AssertNumberOfCalls(t, "Handle", 1)
|
||||
sonarQubeHandlerMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGiteaAPIRoute(t *testing.T) {
|
||||
t.Run("Missing event header", func(t *testing.T) {
|
||||
router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`)))
|
||||
router.Engine.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
})
|
||||
|
||||
t.Run("Processing synchronize", func(t *testing.T) {
|
||||
giteaHandlerMock := new(GiteaHandlerMock)
|
||||
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil)
|
||||
giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Maybe()
|
||||
|
||||
router := New(giteaHandlerMock, new(SonarQubeHandlerMock))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`)))
|
||||
req.Header.Add("X-Gitea-Event", "pull_request")
|
||||
router.Engine.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 1)
|
||||
giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 0)
|
||||
giteaHandlerMock.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Processing comment", func(t *testing.T) {
|
||||
giteaHandlerMock := new(GiteaHandlerMock)
|
||||
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Maybe()
|
||||
giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
router := New(giteaHandlerMock, new(SonarQubeHandlerMock))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`)))
|
||||
req.Header.Add("X-Gitea-Event", "issue_comment")
|
||||
router.Engine.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0)
|
||||
giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 1)
|
||||
giteaHandlerMock.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Unknown event", func(t *testing.T) {
|
||||
giteaHandlerMock := new(GiteaHandlerMock)
|
||||
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Maybe()
|
||||
giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Maybe()
|
||||
|
||||
router := New(giteaHandlerMock, new(SonarQubeHandlerMock))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`)))
|
||||
req.Header.Add("X-Gitea-Event", "unknown")
|
||||
router.Engine.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0)
|
||||
giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 0)
|
||||
giteaHandlerMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
|
38
internal/api/request_validation.go
Normal file
38
internal/api/request_validation.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func isValidWebhook(message []byte, key string, signature string, component string) (bool, error) {
|
||||
if key == "" && signature == "" {
|
||||
// No webhook token configured and no signature header received. Skipping request validation.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if key == "" && signature != "" {
|
||||
return false, fmt.Errorf("Signature header received but no %s webhook secret configured. Request rejected due to possible configuration mismatch.", component)
|
||||
}
|
||||
|
||||
if key != "" && signature == "" {
|
||||
return false, fmt.Errorf("%s webhook secret configured but no signature header received. Request rejected due to possible configuration mismatch.", component)
|
||||
}
|
||||
|
||||
decodedSignature, err := hex.DecodeString(signature)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Error decoding signature for %s webhook.", component)
|
||||
}
|
||||
|
||||
mac := hmac.New(sha256.New, []byte(key))
|
||||
mac.Write(message)
|
||||
sum := mac.Sum(nil)
|
||||
|
||||
if !hmac.Equal(decodedSignature, sum) {
|
||||
return false, fmt.Errorf("Signature header does not match the received %s webhook content. Request rejected.", component)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
47
internal/api/request_validation_test.go
Normal file
47
internal/api/request_validation_test.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func getRequestData() []byte {
|
||||
return []byte(`{"serverUrl":"https://example.com","status":"SUCCESS","analysedAt":"2022-05-15T16:45:31+0000","revision":"378080777919s07657a07f7a3e2d05dc75f64edd","changedAt":"2022-05-15T16:41:39+0000","project":{"key":"gitea-sonarqube-bot","name":"Gitea SonarQube Bot","url":"https://example.com/dashboard?id=gitea-sonarqube-bot"},"branch":{"name":"PR-1822","type":"PULL_REQUEST","isMain":false,"url":"https://example.com/dashboard?id=gitea-sonarqube-bot&pullRequest=PR-1822"},"qualityGate":{"name":"GiteaSonarQubeBot","status":"OK","conditions":[{"metric":"new_reliability_rating","operator":"GREATER_THAN","value":"1","status":"OK","errorThreshold":"1"},{"metric":"new_security_rating","operator":"GREATER_THAN","value":"1","status":"OK","errorThreshold":"1"},{"metric":"new_maintainability_rating","operator":"GREATER_THAN","value":"1","status":"OK","errorThreshold":"1"},{"metric":"new_security_hotspots_reviewed","operator":"LESS_THAN","status":"OK","errorThreshold":"100"}]},"properties":{"sonar.analysis.sqbot":"378080777919s07657a07f7a3e2d05dc75f64edd"}}`)
|
||||
}
|
||||
|
||||
func TestIsValidWebhook(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
actual, _ := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467", "test-component")
|
||||
assert.True(t, actual, "Expected successful webhook signature validation")
|
||||
})
|
||||
|
||||
t.Run("Nothing configured or provided", func(t *testing.T) {
|
||||
actual, _ := isValidWebhook(getRequestData(), "", "", "test-component")
|
||||
assert.True(t, actual, "Webhook signature validation not skipped")
|
||||
})
|
||||
|
||||
t.Run("Signature decoding error", func(t *testing.T) {
|
||||
actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "invalid-signature", "test-component")
|
||||
assert.False(t, actual)
|
||||
assert.EqualError(t, err, "Error decoding signature for test-component webhook.", "Undetected signature encoding error")
|
||||
})
|
||||
|
||||
t.Run("Signature mismatch", func(t *testing.T) {
|
||||
actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "fde6a666b7a1a46c27efb1961c17b46b6cf7aa13db5560e5ac95e801a18a92f3", "test-component")
|
||||
assert.False(t, actual)
|
||||
assert.EqualError(t, err, "Signature header does not match the received test-component webhook content. Request rejected.", "Undetected signature mismatch")
|
||||
})
|
||||
|
||||
t.Run("Empty secret configuration", func(t *testing.T) {
|
||||
actual, err := isValidWebhook(getRequestData(), "", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467", "test-component")
|
||||
assert.False(t, actual)
|
||||
assert.EqualError(t, err, "Signature header received but no test-component webhook secret configured. Request rejected due to possible configuration mismatch.", "Undetected configuration mismatch (1)")
|
||||
})
|
||||
|
||||
t.Run("Empty signature configuration", func(t *testing.T) {
|
||||
actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "", "test-component")
|
||||
assert.False(t, actual)
|
||||
assert.EqualError(t, err, "test-component webhook secret configured but no signature header received. Request rejected due to possible configuration mismatch.", "Undetected configuration mismatch (2)")
|
||||
})
|
||||
}
|
|
@ -2,20 +2,22 @@ package api
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
giteaSdk "gitea-sonarqube-pr-bot/internal/clients/gitea"
|
||||
sqSdk "gitea-sonarqube-pr-bot/internal/clients/sonarqube"
|
||||
"gitea-sonarqube-pr-bot/internal/settings"
|
||||
webhook "gitea-sonarqube-pr-bot/internal/webhooks/sonarqube"
|
||||
giteaSdk "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/clients/gitea"
|
||||
sqSdk "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/clients/sonarqube"
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/settings"
|
||||
webhook "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/webhooks/sonarqube"
|
||||
)
|
||||
|
||||
type SonarQubeWebhookHandlerInferface interface {
|
||||
Handle(r *http.Request) (int, string)
|
||||
}
|
||||
|
||||
type SonarQubeWebhookHandler struct {
|
||||
fetchDetails func(w *webhook.Webhook)
|
||||
giteaSdk giteaSdk.GiteaSdkInterface
|
||||
sqSdk sqSdk.SonarQubeSdkInterface
|
||||
}
|
||||
|
@ -31,13 +33,6 @@ func (*SonarQubeWebhookHandler) inProjectsMapping(p []settings.Project, n string
|
|||
}
|
||||
|
||||
func (h *SonarQubeWebhookHandler) processData(w *webhook.Webhook, repo settings.GiteaRepository) {
|
||||
if strings.ToLower(w.Branch.Type) != "pull_request" {
|
||||
log.Println("Ignore Hook for non-PR")
|
||||
return
|
||||
}
|
||||
|
||||
h.fetchDetails(w)
|
||||
|
||||
status := giteaSdk.StatusOK
|
||||
if w.QualityGate.Status != "OK" {
|
||||
status = giteaSdk.StatusFailure
|
||||
|
@ -60,17 +55,12 @@ func (h *SonarQubeWebhookHandler) processData(w *webhook.Webhook, repo settings.
|
|||
h.giteaSdk.PostComment(repo, w.PRIndex, comment)
|
||||
}
|
||||
|
||||
func (h *SonarQubeWebhookHandler) Handle(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
func (h *SonarQubeWebhookHandler) Handle(r *http.Request) (int, string) {
|
||||
projectName := r.Header.Get("X-SonarQube-Project")
|
||||
found, pIdx := h.inProjectsMapping(settings.Projects, projectName)
|
||||
if !found {
|
||||
log.Printf("Received hook for project '%s' which is not configured. Request ignored.", projectName)
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
io.WriteString(rw, fmt.Sprintf(`{"message": "Project '%s' not in configured list. Request ignored."}`, projectName))
|
||||
return
|
||||
return http.StatusOK, fmt.Sprintf("Project '%s' not in configured list. Request ignored.", projectName)
|
||||
}
|
||||
|
||||
log.Printf("Received hook for project '%s'. Processing data.", projectName)
|
||||
|
@ -82,32 +72,32 @@ func (h *SonarQubeWebhookHandler) Handle(rw http.ResponseWriter, r *http.Request
|
|||
raw, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Printf("Error reading request body %s", err.Error())
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
io.WriteString(rw, fmt.Sprintf(`{"message": "%s"}`, err.Error()))
|
||||
return
|
||||
return http.StatusInternalServerError, err.Error()
|
||||
}
|
||||
|
||||
ok, err := isValidWebhook(raw, settings.SonarQube.Webhook.Secret, r.Header.Get("X-Sonar-Webhook-HMAC-SHA256"), "SonarQube")
|
||||
if !ok {
|
||||
log.Print(err.Error())
|
||||
return http.StatusPreconditionFailed, "Webhook validation failed. Request rejected."
|
||||
}
|
||||
|
||||
w, ok := webhook.New(raw)
|
||||
if !ok {
|
||||
rw.WriteHeader(http.StatusUnprocessableEntity)
|
||||
io.WriteString(rw, `{"message": "Error parsing POST body."}`)
|
||||
return
|
||||
return http.StatusUnprocessableEntity, "Error parsing POST body."
|
||||
}
|
||||
|
||||
// Send response to SonarQube at this point to ensure being within 10 seconds limit of webhook response timeout
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
io.WriteString(rw, `{"message": "Processing data. See bot logs for details."}`)
|
||||
if strings.ToLower(w.Branch.Type) != "pull_request" {
|
||||
log.Println("Ignore Hook for non-PR analysis")
|
||||
return http.StatusOK, "Ignore Hook for non-PR analysis."
|
||||
}
|
||||
|
||||
h.processData(w, settings.Projects[pIdx].Gitea)
|
||||
|
||||
return http.StatusOK, "Processing data. See bot logs for details."
|
||||
}
|
||||
|
||||
func fetchDetails(w *webhook.Webhook) {
|
||||
log.Printf("This method will load additional data from SonarQube based on PR %d", w.PRIndex)
|
||||
}
|
||||
|
||||
func NewSonarQubeWebhookHandler(g giteaSdk.GiteaSdkInterface, sq sqSdk.SonarQubeSdkInterface) *SonarQubeWebhookHandler {
|
||||
func NewSonarQubeWebhookHandler(g giteaSdk.GiteaSdkInterface, sq sqSdk.SonarQubeSdkInterface) SonarQubeWebhookHandlerInferface {
|
||||
return &SonarQubeWebhookHandler{
|
||||
fetchDetails: fetchDetails,
|
||||
giteaSdk: g,
|
||||
sqSdk: sq,
|
||||
}
|
||||
|
|
|
@ -2,73 +2,19 @@ package api
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
giteaSdk "gitea-sonarqube-pr-bot/internal/clients/gitea"
|
||||
sqSdk "gitea-sonarqube-pr-bot/internal/clients/sonarqube"
|
||||
"gitea-sonarqube-pr-bot/internal/settings"
|
||||
webhook "gitea-sonarqube-pr-bot/internal/webhooks/sonarqube"
|
||||
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/settings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type HandlerPartialMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (h *HandlerPartialMock) fetchDetails(w *webhook.Webhook) {
|
||||
h.Called(w)
|
||||
}
|
||||
|
||||
type GiteaSdkMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (h *GiteaSdkMock) PostComment(_ settings.GiteaRepository, _ int, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *GiteaSdkMock) DetermineHEAD(_ settings.GiteaRepository, _ int64) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (h *GiteaSdkMock) UpdateStatus(_ settings.GiteaRepository, _ string, _ giteaSdk.StatusDetails) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type SQSdkMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (h *SQSdkMock) GetMeasures(project string, branch string) (*sqSdk.MeasuresResponse, error) {
|
||||
return &sqSdk.MeasuresResponse{}, nil
|
||||
}
|
||||
|
||||
func (h *SQSdkMock) GetPullRequestUrl(project string, index int64) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *SQSdkMock) GetPullRequest(project string, index int64) (*sqSdk.PullRequest, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (h *SQSdkMock) ComposeGiteaComment(data *sqSdk.CommentComposeData) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func defaultMockPreparation(h *HandlerPartialMock) {
|
||||
h.On("fetchDetails", mock.Anything).Return(nil)
|
||||
}
|
||||
|
||||
func withValidRequestData(t *testing.T, mockPreparation func(*HandlerPartialMock), jsonBody []byte) (*http.Request, *httptest.ResponseRecorder, http.HandlerFunc, *HandlerPartialMock) {
|
||||
partialMock := new(HandlerPartialMock)
|
||||
mockPreparation(partialMock)
|
||||
|
||||
func withValidSonarQubeRequestData(t *testing.T, jsonBody []byte) (*http.Request, *httptest.ResponseRecorder, http.HandlerFunc) {
|
||||
webhookHandler := NewSonarQubeWebhookHandler(new(GiteaSdkMock), new(SQSdkMock))
|
||||
webhookHandler.fetchDetails = partialMock.fetchDetails
|
||||
|
||||
req, err := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer(jsonBody))
|
||||
if err != nil {
|
||||
|
@ -77,12 +23,26 @@ func withValidRequestData(t *testing.T, mockPreparation func(*HandlerPartialMock
|
|||
req.Header.Set("X-SonarQube-Project", "pr-bot")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
handler := http.HandlerFunc(webhookHandler.Handle)
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
status, response := webhookHandler.Handle(r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
io.WriteString(w, fmt.Sprintf(`{"message": "%s"}`, response))
|
||||
})
|
||||
|
||||
return req, rr, handler, partialMock
|
||||
return req, rr, handler
|
||||
}
|
||||
|
||||
func TestHandleSonarQubeWebhookProjectMapped(t *testing.T) {
|
||||
func TestHandleSonarQubeWebhook(t *testing.T) {
|
||||
t.Run("With mapped Project", func(t *testing.T) {
|
||||
settings.Pattern = &settings.PatternConfig{
|
||||
RegExp: regexp.MustCompile(`^PR-(\d+)$`),
|
||||
}
|
||||
settings.SonarQube = settings.SonarQubeConfig{
|
||||
Webhook: &settings.Webhook{
|
||||
Secret: "",
|
||||
},
|
||||
}
|
||||
settings.Projects = []settings.Project{
|
||||
{
|
||||
SonarQube: struct{ Key string }{
|
||||
|
@ -90,14 +50,18 @@ func TestHandleSonarQubeWebhookProjectMapped(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
req, rr, handler, _ := withValidRequestData(t, defaultMockPreparation, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`))
|
||||
req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`))
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String())
|
||||
}
|
||||
|
||||
func TestHandleSonarQubeWebhookProjectNotMapped(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
settings.Pattern = nil
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Without mapped project", func(t *testing.T) {
|
||||
settings.Projects = []settings.Project{
|
||||
{
|
||||
SonarQube: struct{ Key string }{
|
||||
|
@ -105,14 +69,14 @@ func TestHandleSonarQubeWebhookProjectNotMapped(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
req, rr, handler, _ := withValidRequestData(t, defaultMockPreparation, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`))
|
||||
req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`))
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Equal(t, `{"message": "Project 'pr-bot' not in configured list. Request ignored."}`, rr.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
func TestHandleSonarQubeWebhookInvalidJSONBody(t *testing.T) {
|
||||
t.Run("With invalid JSON body", func(t *testing.T) {
|
||||
settings.Projects = []settings.Project{
|
||||
{
|
||||
SonarQube: struct{ Key string }{
|
||||
|
@ -121,14 +85,43 @@ func TestHandleSonarQubeWebhookInvalidJSONBody(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
req, rr, handler, _ := withValidRequestData(t, defaultMockPreparation, []byte(`{ "serverUrl": ["invalid-server-url-content"] }`))
|
||||
req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": ["invalid-server-url-content"] }`))
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusUnprocessableEntity, rr.Code)
|
||||
assert.Equal(t, `{"message": "Error parsing POST body."}`, rr.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
func TestHandleSonarQubeWebhookForPullRequest(t *testing.T) {
|
||||
t.Run("With invalid webhook signature", func(t *testing.T) {
|
||||
settings.SonarQube = settings.SonarQubeConfig{
|
||||
Webhook: &settings.Webhook{
|
||||
Secret: "sonarqube-test-webhook-secret",
|
||||
},
|
||||
}
|
||||
settings.Projects = []settings.Project{
|
||||
{
|
||||
SonarQube: struct{ Key string }{
|
||||
Key: "pr-bot",
|
||||
},
|
||||
},
|
||||
}
|
||||
req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`))
|
||||
req.Header.Set("X-Sonar-Webhook-HMAC-SHA256", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467")
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusPreconditionFailed, rr.Code)
|
||||
assert.Equal(t, `{"message": "Webhook validation failed. Request rejected."}`, rr.Body.String())
|
||||
})
|
||||
|
||||
t.Run("Running for Pull Request", func(t *testing.T) {
|
||||
settings.Pattern = &settings.PatternConfig{
|
||||
RegExp: regexp.MustCompile(`^PR-(\d+)$`),
|
||||
}
|
||||
settings.SonarQube = settings.SonarQubeConfig{
|
||||
Webhook: &settings.Webhook{
|
||||
Secret: "",
|
||||
},
|
||||
}
|
||||
settings.Projects = []settings.Project{
|
||||
{
|
||||
SonarQube: struct{ Key string }{
|
||||
|
@ -137,15 +130,26 @@ func TestHandleSonarQubeWebhookForPullRequest(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
req, rr, handler, partialMock := withValidRequestData(t, defaultMockPreparation, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`))
|
||||
req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`))
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String())
|
||||
partialMock.AssertNumberOfCalls(t, "fetchDetails", 1)
|
||||
}
|
||||
|
||||
func TestHandleSonarQubeWebhookForBranch(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
settings.Pattern = nil
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Running for branch", func(t *testing.T) {
|
||||
settings.Pattern = &settings.PatternConfig{
|
||||
RegExp: regexp.MustCompile(`^PR-(\d+)$`),
|
||||
}
|
||||
settings.SonarQube = settings.SonarQubeConfig{
|
||||
Webhook: &settings.Webhook{
|
||||
Secret: "",
|
||||
},
|
||||
}
|
||||
settings.Projects = []settings.Project{
|
||||
{
|
||||
SonarQube: struct{ Key string }{
|
||||
|
@ -154,10 +158,14 @@ func TestHandleSonarQubeWebhookForBranch(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
req, rr, handler, partialMock := withValidRequestData(t, defaultMockPreparation, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "BRANCH", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`))
|
||||
req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "BRANCH", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`))
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String())
|
||||
partialMock.AssertNumberOfCalls(t, "fetchDetails", 0)
|
||||
assert.Equal(t, `{"message": "Ignore Hook for non-PR analysis."}`, rr.Body.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
settings.Pattern = nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@ package gitea
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea-sonarqube-pr-bot/internal/settings"
|
||||
"log"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/settings"
|
||||
)
|
||||
|
||||
type GiteaSdkInterface interface {
|
||||
|
@ -14,8 +14,14 @@ type GiteaSdkInterface interface {
|
|||
DetermineHEAD(settings.GiteaRepository, int64) (string, error)
|
||||
}
|
||||
|
||||
type ClientInterface interface {
|
||||
CreateIssueComment(owner, repo string, index int64, opt gitea.CreateIssueCommentOption) (*gitea.Comment, *gitea.Response, error)
|
||||
CreateStatus(owner, repo, sha string, opts gitea.CreateStatusOption) (*gitea.Status, *gitea.Response, error)
|
||||
GetPullRequest(owner, repo string, index int64) (*gitea.PullRequest, *gitea.Response, error)
|
||||
}
|
||||
|
||||
type GiteaSdk struct {
|
||||
client *gitea.Client
|
||||
client ClientInterface
|
||||
}
|
||||
|
||||
func (sdk *GiteaSdk) PostComment(repo settings.GiteaRepository, idx int, msg string) error {
|
||||
|
@ -31,7 +37,7 @@ func (sdk *GiteaSdk) PostComment(repo settings.GiteaRepository, idx int, msg str
|
|||
func (sdk *GiteaSdk) UpdateStatus(repo settings.GiteaRepository, ref string, details StatusDetails) error {
|
||||
opt := gitea.CreateStatusOption{
|
||||
TargetURL: details.Url,
|
||||
Context: "gitea-sonarqube-pr-bot",
|
||||
Context: "gitea-sonarqube-bot",
|
||||
Description: details.Message,
|
||||
State: gitea.StatusState(details.State),
|
||||
}
|
||||
|
@ -53,8 +59,8 @@ func (sdk *GiteaSdk) DetermineHEAD(repo settings.GiteaRepository, idx int64) (st
|
|||
return pr.Head.Sha, nil
|
||||
}
|
||||
|
||||
func New() *GiteaSdk {
|
||||
client, err := gitea.NewClient(settings.Gitea.Url, gitea.SetToken(settings.Gitea.Token.Value))
|
||||
func New[T ClientInterface](configuration *settings.GiteaConfig, newClient func(url string, options ...gitea.ClientOption) (T, error)) *GiteaSdk {
|
||||
client, err := newClient(configuration.Url, gitea.SetToken(configuration.Token.Value))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot initialize Gitea client: %w", err))
|
||||
}
|
||||
|
|
198
internal/clients/gitea/gitea_test.go
Normal file
198
internal/clients/gitea/gitea_test.go
Normal file
|
@ -0,0 +1,198 @@
|
|||
package gitea
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/settings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type SdkMock struct {
|
||||
simulatedError error
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *SdkMock) CreateIssueComment(owner, repo string, index int64, opt gitea.CreateIssueCommentOption) (*gitea.Comment, *gitea.Response, error) {
|
||||
m.Called(owner, repo, index, opt)
|
||||
return nil, nil, m.simulatedError
|
||||
}
|
||||
func (m *SdkMock) CreateStatus(owner, repo, sha string, opts gitea.CreateStatusOption) (*gitea.Status, *gitea.Response, error) {
|
||||
m.Called(owner, repo, sha, opts)
|
||||
r := &gitea.Response{
|
||||
Response: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
},
|
||||
}
|
||||
if m.simulatedError != nil {
|
||||
r.StatusCode = http.StatusInternalServerError
|
||||
}
|
||||
return nil, r, m.simulatedError
|
||||
}
|
||||
func (m *SdkMock) GetPullRequest(owner, repo string, index int64) (*gitea.PullRequest, *gitea.Response, error) {
|
||||
m.Called(owner, repo, index)
|
||||
return &gitea.PullRequest{
|
||||
Head: &gitea.PRBranchInfo{
|
||||
Sha: "a1aada0b7b19e58ae539b4812d960bca35ev78cb",
|
||||
},
|
||||
}, nil, m.simulatedError
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
config := &settings.GiteaConfig{
|
||||
Url: "http://example.com",
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
}
|
||||
|
||||
callback := func(url string, options ...gitea.ClientOption) (*SdkMock, error) {
|
||||
return &SdkMock{}, nil
|
||||
}
|
||||
assert.IsType(t, &GiteaSdk{}, New(config, callback), "")
|
||||
})
|
||||
|
||||
t.Run("Initialization errors", func(t *testing.T) {
|
||||
config := &settings.GiteaConfig{
|
||||
Url: "http://example.com",
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
}
|
||||
|
||||
callback := func(url string, options ...gitea.ClientOption) (*SdkMock, error) {
|
||||
return nil, errors.New("Simulated initialization error")
|
||||
}
|
||||
assert.PanicsWithError(t, "cannot initialize Gitea client: Simulated initialization error", func() { New(config, callback) })
|
||||
})
|
||||
}
|
||||
|
||||
func TestDetermineHEAD(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
clientMock := &SdkMock{}
|
||||
clientMock.On("GetPullRequest", "test-owner", "test-repo", int64(1)).Once()
|
||||
|
||||
sdk := GiteaSdk{
|
||||
client: clientMock,
|
||||
}
|
||||
sha, err := sdk.DetermineHEAD(settings.GiteaRepository{
|
||||
Owner: "test-owner",
|
||||
Name: "test-repo",
|
||||
}, 1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "a1aada0b7b19e58ae539b4812d960bca35ev78cb", sha)
|
||||
clientMock.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("API error", func(t *testing.T) {
|
||||
clientMock := &SdkMock{
|
||||
simulatedError: errors.New("Simulated error"),
|
||||
}
|
||||
clientMock.On("GetPullRequest", "test-owner", "test-repo", int64(1)).Once()
|
||||
|
||||
sdk := GiteaSdk{
|
||||
client: clientMock,
|
||||
}
|
||||
|
||||
_, err := sdk.DetermineHEAD(settings.GiteaRepository{
|
||||
Owner: "test-owner",
|
||||
Name: "test-repo",
|
||||
}, 1)
|
||||
|
||||
assert.Errorf(t, err, "Simulated error")
|
||||
clientMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateStatus(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
clientMock := &SdkMock{}
|
||||
clientMock.On("CreateStatus", "test-owner", "test-repo", "a1aada0b7b19e58ae539b4812d960bca35ev78cb", mock.Anything).Once()
|
||||
sdk := GiteaSdk{
|
||||
client: clientMock,
|
||||
}
|
||||
|
||||
err := sdk.UpdateStatus(settings.GiteaRepository{
|
||||
Owner: "test-owner",
|
||||
Name: "test-repo",
|
||||
}, "a1aada0b7b19e58ae539b4812d960bca35ev78cb", StatusDetails{
|
||||
Url: "http://example.com",
|
||||
Message: "expected message",
|
||||
State: StatusOK,
|
||||
})
|
||||
|
||||
assert.Nil(t, err)
|
||||
clientMock.AssertExpectations(t)
|
||||
|
||||
actualStatusOption := clientMock.Calls[0].Arguments[3].(gitea.CreateStatusOption)
|
||||
assert.Equal(t, "http://example.com", actualStatusOption.TargetURL)
|
||||
assert.Equal(t, "expected message", actualStatusOption.Description)
|
||||
assert.Equal(t, gitea.StatusSuccess, actualStatusOption.State)
|
||||
})
|
||||
|
||||
t.Run("API error", func(t *testing.T) {
|
||||
clientMock := &SdkMock{
|
||||
simulatedError: errors.New("Simulated error"),
|
||||
}
|
||||
clientMock.On("CreateStatus", "test-owner", "test-repo", "a1aada0b7b19e58ae539b4812d960bca35ev78cb", mock.Anything).Once()
|
||||
sdk := GiteaSdk{
|
||||
client: clientMock,
|
||||
}
|
||||
|
||||
err := sdk.UpdateStatus(settings.GiteaRepository{
|
||||
Owner: "test-owner",
|
||||
Name: "test-repo",
|
||||
}, "a1aada0b7b19e58ae539b4812d960bca35ev78cb", StatusDetails{
|
||||
Url: "http://example.com",
|
||||
Message: "expected message",
|
||||
State: StatusOK,
|
||||
})
|
||||
|
||||
assert.Errorf(t, err, "Simulated error")
|
||||
clientMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPostComment(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
clientMock := &SdkMock{}
|
||||
clientMock.On("CreateIssueComment", "test-owner", "test-repo", int64(1), mock.Anything).Once()
|
||||
sdk := GiteaSdk{
|
||||
client: clientMock,
|
||||
}
|
||||
|
||||
err := sdk.PostComment(settings.GiteaRepository{
|
||||
Owner: "test-owner",
|
||||
Name: "test-repo",
|
||||
}, 1, "test post comment")
|
||||
|
||||
assert.Nil(t, err)
|
||||
clientMock.AssertExpectations(t)
|
||||
|
||||
actualCommentOption := clientMock.Calls[0].Arguments[3].(gitea.CreateIssueCommentOption)
|
||||
assert.Equal(t, "test post comment", actualCommentOption.Body)
|
||||
})
|
||||
|
||||
t.Run("API error", func(t *testing.T) {
|
||||
clientMock := &SdkMock{
|
||||
simulatedError: errors.New("Simulated error"),
|
||||
}
|
||||
clientMock.On("CreateIssueComment", "test-owner", "test-repo", int64(1), mock.Anything).Once()
|
||||
sdk := GiteaSdk{
|
||||
client: clientMock,
|
||||
}
|
||||
|
||||
err := sdk.PostComment(settings.GiteaRepository{
|
||||
Owner: "test-owner",
|
||||
Name: "test-repo",
|
||||
}, 1, "test post comment")
|
||||
|
||||
assert.Errorf(t, err, "Simulated error")
|
||||
clientMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
14
internal/clients/gitea/main_test.go
Normal file
14
internal/clients/gitea/main_test.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package gitea
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// SETUP: mute logs
|
||||
func TestMain(m *testing.M) {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
os.Exit(m.Run())
|
||||
}
|
14
internal/clients/sonarqube/main_test.go
Normal file
14
internal/clients/sonarqube/main_test.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package sonarqube
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// SETUP: mute logs
|
||||
func TestMain(m *testing.M) {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
os.Exit(m.Run())
|
||||
}
|
|
@ -28,6 +28,7 @@ type MeasuresComponent struct {
|
|||
type MeasuresResponse struct {
|
||||
Component MeasuresComponent `json:"component"`
|
||||
Metrics []MeasuresComponentMetric `json:"metrics"`
|
||||
Errors []Error `json:"errors"`
|
||||
}
|
||||
|
||||
func (mr *MeasuresResponse) GetRenderedMarkdownTable() string {
|
||||
|
|
|
@ -9,6 +9,7 @@ type PullRequest struct {
|
|||
|
||||
type PullsResponse struct {
|
||||
PullRequests []PullRequest `json:"pullRequests"`
|
||||
Errors []Error `json:"errors"`
|
||||
}
|
||||
|
||||
func (r *PullsResponse) GetPullRequest(name string) *PullRequest {
|
||||
|
|
|
@ -7,26 +7,24 @@ import (
|
|||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea-sonarqube-pr-bot/internal/actions"
|
||||
"gitea-sonarqube-pr-bot/internal/settings"
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/actions"
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/settings"
|
||||
)
|
||||
|
||||
func ParsePRIndex(name string) (int, error) {
|
||||
re := regexp.MustCompile(`^PR-(\d+)$`)
|
||||
res := re.FindSubmatch([]byte(name))
|
||||
res := settings.Pattern.RegExp.FindSubmatch([]byte(name))
|
||||
if len(res) != 2 {
|
||||
return 0, fmt.Errorf("branch name '%s' does not match regex '%s'", name, re.String())
|
||||
return 0, fmt.Errorf("branch name '%s' does not match regex '%s'", name, settings.Pattern.RegExp.String())
|
||||
}
|
||||
|
||||
return strconv.Atoi(string(res[1]))
|
||||
}
|
||||
|
||||
func PRNameFromIndex(index int64) string {
|
||||
return fmt.Sprintf("PR-%d", index)
|
||||
return fmt.Sprintf(settings.Pattern.Template, index)
|
||||
}
|
||||
|
||||
func GetRenderedQualityGate(qg string) string {
|
||||
|
@ -38,6 +36,38 @@ func GetRenderedQualityGate(qg string) string {
|
|||
return fmt.Sprintf("**Quality Gate**: %s", status)
|
||||
}
|
||||
|
||||
func retrieveDataFromApi(sdk *SonarQubeSdk, request *http.Request, wrapper interface{}) error {
|
||||
request.Header.Add("Authorization", sdk.basicAuth())
|
||||
rawResponse, err := sdk.client.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rawResponse.StatusCode == http.StatusUnauthorized {
|
||||
return fmt.Errorf("missing or invalid API token")
|
||||
}
|
||||
|
||||
if rawResponse.Body != nil {
|
||||
defer rawResponse.Body.Close()
|
||||
}
|
||||
|
||||
body, err := sdk.bodyReader(rawResponse.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, wrapper)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Message string `json:"msg"`
|
||||
}
|
||||
|
||||
type SonarQubeSdkInterface interface {
|
||||
GetMeasures(string, string) (*MeasuresResponse, error)
|
||||
GetPullRequestUrl(string, int64) string
|
||||
|
@ -52,44 +82,48 @@ type CommentComposeData struct {
|
|||
QualityGate string
|
||||
}
|
||||
|
||||
type ClientInterface interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
type BodyReader func(io.Reader) ([]byte, error)
|
||||
type HttpRequest func(method string, target string, body io.Reader) (*http.Request, error)
|
||||
|
||||
type SonarQubeSdk struct {
|
||||
client *http.Client
|
||||
baseUrl string
|
||||
token string
|
||||
client ClientInterface
|
||||
bodyReader BodyReader
|
||||
httpRequest HttpRequest
|
||||
settings *settings.SonarQubeConfig
|
||||
}
|
||||
|
||||
func (sdk *SonarQubeSdk) GetPullRequestUrl(project string, index int64) string {
|
||||
return fmt.Sprintf("%s/dashboard?id=%s&pullRequest=%s", sdk.baseUrl, project, PRNameFromIndex(index))
|
||||
return fmt.Sprintf("%s/dashboard?id=%s&pullRequest=%s", sdk.settings.Url, project, PRNameFromIndex(index))
|
||||
}
|
||||
|
||||
func (sdk *SonarQubeSdk) fetchPullRequests(project string) *PullsResponse {
|
||||
url := fmt.Sprintf("%s/api/project_pull_requests/list?project=%s", sdk.baseUrl, project)
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
func (sdk *SonarQubeSdk) fetchPullRequests(project string) (*PullsResponse, error) {
|
||||
url := fmt.Sprintf("%s/api/project_pull_requests/list?project=%s", sdk.settings.Url, project)
|
||||
request, err := sdk.httpRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
log.Printf("Cannot initialize Request: %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
req.Header.Add("Authorization", sdk.basicAuth())
|
||||
rawResp, _ := sdk.client.Do(req)
|
||||
if rawResp.Body != nil {
|
||||
defer rawResp.Body.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(rawResp.Body)
|
||||
response := &PullsResponse{}
|
||||
err = json.Unmarshal(body, &response)
|
||||
err = retrieveDataFromApi(sdk, request, response)
|
||||
if err != nil {
|
||||
log.Printf("cannot parse response from SonarQube: %s", err.Error())
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response
|
||||
if len(response.Errors) != 0 {
|
||||
return nil, fmt.Errorf("%s", response.Errors[0].Message)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (sdk *SonarQubeSdk) GetPullRequest(project string, index int64) (*PullRequest, error) {
|
||||
response := sdk.fetchPullRequests(project)
|
||||
if response == nil {
|
||||
return nil, fmt.Errorf("unable to retrieve pull requests from SonarQube")
|
||||
response, err := sdk.fetchPullRequests(project)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching pull requests failed: %w", err)
|
||||
}
|
||||
|
||||
name := PRNameFromIndex(index)
|
||||
|
@ -102,22 +136,20 @@ 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=%s&component=%s&pullRequest=%s", sdk.baseUrl, settings.SonarQube.GetMetricsList(), project, branch)
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
url := fmt.Sprintf("%s/api/measures/component?additionalFields=metrics&metricKeys=%s&component=%s&pullRequest=%s", sdk.settings.Url, settings.SonarQube.GetMetricsList(), project, branch)
|
||||
request, err := sdk.httpRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot initialize Request: %w", err)
|
||||
}
|
||||
req.Header.Add("Authorization", sdk.basicAuth())
|
||||
rawResp, _ := sdk.client.Do(req)
|
||||
if rawResp.Body != nil {
|
||||
defer rawResp.Body.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(rawResp.Body)
|
||||
response := &MeasuresResponse{}
|
||||
err = json.Unmarshal(body, &response)
|
||||
err = retrieveDataFromApi(sdk, request, response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse response from SonarQube: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(response.Errors) != 0 {
|
||||
return nil, fmt.Errorf("%s", response.Errors[0].Message)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
|
@ -141,14 +173,15 @@ func (sdk *SonarQubeSdk) ComposeGiteaComment(data *CommentComposeData) (string,
|
|||
}
|
||||
|
||||
func (sdk *SonarQubeSdk) basicAuth() string {
|
||||
auth := []byte(fmt.Sprintf("%s:", sdk.token))
|
||||
auth := []byte(fmt.Sprintf("%s:", sdk.settings.Token.Value))
|
||||
return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString(auth))
|
||||
}
|
||||
|
||||
func New() *SonarQubeSdk {
|
||||
func New(configuration *settings.SonarQubeConfig) *SonarQubeSdk {
|
||||
return &SonarQubeSdk{
|
||||
client: &http.Client{},
|
||||
baseUrl: settings.SonarQube.Url,
|
||||
token: settings.SonarQube.Token.Value,
|
||||
bodyReader: io.ReadAll,
|
||||
httpRequest: http.NewRequest,
|
||||
settings: configuration,
|
||||
}
|
||||
}
|
||||
|
|
621
internal/clients/sonarqube/sonarqube_test.go
Normal file
621
internal/clients/sonarqube/sonarqube_test.go
Normal file
|
@ -0,0 +1,621 @@
|
|||
package sonarqube
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/settings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type ClientMock struct {
|
||||
responseError error
|
||||
handler http.HandlerFunc
|
||||
recoder *httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
func (c *ClientMock) Do(req *http.Request) (*http.Response, error) {
|
||||
c.handler.ServeHTTP(c.recoder, req)
|
||||
|
||||
return &http.Response{
|
||||
StatusCode: c.recoder.Code,
|
||||
Body: c.recoder.Result().Body,
|
||||
}, c.responseError
|
||||
}
|
||||
|
||||
func TestParsePRIndex(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
settings.Pattern = &settings.PatternConfig{
|
||||
RegExp: regexp.MustCompile(`^PR-(\d+)$`),
|
||||
}
|
||||
|
||||
actual, _ := ParsePRIndex("PR-1337")
|
||||
assert.Equal(t, 1337, actual, "PR index parsing is broken")
|
||||
|
||||
t.Cleanup(func() {
|
||||
settings.Pattern = nil
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("No integer value", func(t *testing.T) {
|
||||
settings.Pattern = &settings.PatternConfig{
|
||||
RegExp: regexp.MustCompile(`^PR-(\d+)$`),
|
||||
}
|
||||
|
||||
_, err := ParsePRIndex("PR-invalid")
|
||||
assert.EqualErrorf(t, err, "branch name 'PR-invalid' does not match regex '^PR-(\\d+)$'", "Integer parsing succeeds unexpectedly")
|
||||
|
||||
t.Cleanup(func() {
|
||||
settings.Pattern = nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestPRNameFromIndex(t *testing.T) {
|
||||
settings.Pattern = &settings.PatternConfig{
|
||||
Template: "PR-%d",
|
||||
}
|
||||
|
||||
assert.Equal(t, "PR-1337", PRNameFromIndex(1337))
|
||||
|
||||
t.Cleanup(func() {
|
||||
settings.Pattern = nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetRenderedQualityGate(t *testing.T) {
|
||||
t.Run("Passed", func(t *testing.T) {
|
||||
assert.Contains(t, GetRenderedQualityGate("OK"), ":white_check_mark:", "Undetected successful quality gate during status rendering")
|
||||
})
|
||||
|
||||
t.Run("Failed", func(t *testing.T) {
|
||||
assert.Contains(t, GetRenderedQualityGate("ERROR"), ":x:", "Undetected failed quality gate during status rendering")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetPullRequestUrl(t *testing.T) {
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Url: "https://sonarqube.example.com",
|
||||
},
|
||||
}
|
||||
settings.Pattern = &settings.PatternConfig{
|
||||
Template: "PR-%d",
|
||||
}
|
||||
|
||||
actual := sdk.GetPullRequestUrl("test-project", 1337)
|
||||
assert.Equal(t, "https://sonarqube.example.com/dashboard?id=test-project&pullRequest=PR-1337", actual, "PR Dashboard URL building broken")
|
||||
|
||||
t.Cleanup(func() {
|
||||
settings.Pattern = nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestRetrieveDataFromApi(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||
})
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: nil,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
wrapper := &PullsResponse{}
|
||||
err := retrieveDataFromApi(sdk, request, wrapper)
|
||||
|
||||
assert.Nil(t, err, "Successful data retrieval broken and throws error")
|
||||
assert.Equal(t, "Basic dGVzdC10b2tlbjo=", request.Header.Get("Authorization"), "Authorization header not set")
|
||||
assert.Equal(t, "PR-1", wrapper.PullRequests[0].Key, "Unmarshallowing into wrapper broken")
|
||||
})
|
||||
|
||||
t.Run("Internal error", func(t *testing.T) {
|
||||
expected := fmt.Errorf("This error indicates an error while performing the request")
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: expected,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
err := retrieveDataFromApi(sdk, request, &PullsResponse{})
|
||||
|
||||
assert.ErrorIs(t, err, expected, "Undetected request performing error")
|
||||
})
|
||||
|
||||
t.Run("Unauthorized", func(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
recorder.Code = http.StatusUnauthorized
|
||||
})
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "simulated-invalid-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: recorder,
|
||||
responseError: nil,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
err := retrieveDataFromApi(sdk, request, &PullsResponse{})
|
||||
|
||||
assert.Errorf(t, err, "missing or invalid API token", "Undetected unauthorized error")
|
||||
})
|
||||
|
||||
t.Run("Body read error", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||
})
|
||||
expected := fmt.Errorf("Error reading body content")
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: nil,
|
||||
},
|
||||
bodyReader: func(r io.Reader) ([]byte, error) {
|
||||
return []byte(``), expected
|
||||
},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
err := retrieveDataFromApi(sdk, request, &PullsResponse{})
|
||||
|
||||
assert.ErrorIs(t, err, expected, "Undetected body processing error")
|
||||
})
|
||||
|
||||
t.Run("Unmarshal error", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"pullReq`))
|
||||
})
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: nil,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
err := retrieveDataFromApi(sdk, request, &PullsResponse{})
|
||||
|
||||
assert.Errorf(t, err, "unexpected end of JSON input", "Undetected body unmarshal error")
|
||||
})
|
||||
}
|
||||
|
||||
func TestFetchPullRequests(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||
})
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: nil,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||
return httptest.NewRequest(method, target, body), nil
|
||||
},
|
||||
}
|
||||
|
||||
actual, err := sdk.fetchPullRequests("test-project")
|
||||
|
||||
assert.Nil(t, err, "Successful data retrieval broken and throws error")
|
||||
assert.IsType(t, &PullsResponse{}, actual, "Happy path broken")
|
||||
})
|
||||
|
||||
t.Run("Building failure", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||
})
|
||||
expected := fmt.Errorf("Some simulated error")
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: nil,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||
return nil, expected
|
||||
},
|
||||
}
|
||||
|
||||
_, err := sdk.fetchPullRequests("test-project")
|
||||
|
||||
assert.Equal(t, expected, err, "Unexpected error instance returned")
|
||||
})
|
||||
|
||||
t.Run("Internal error", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||
})
|
||||
expected := fmt.Errorf("Some simulated error")
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: expected,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||
return httptest.NewRequest(method, target, body), nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := sdk.fetchPullRequests("test-project")
|
||||
|
||||
assert.Equal(t, expected, err)
|
||||
})
|
||||
|
||||
t.Run("Errors in response", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"errors":[{"msg":"Project 'test-project' not found"}]}`))
|
||||
})
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: nil,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||
return httptest.NewRequest(method, target, body), nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := sdk.fetchPullRequests("test-project")
|
||||
|
||||
assert.Errorf(t, err, "Project 'test-project' not found", "Response error parsing broken")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetPullRequest(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
settings.Pattern = &settings.PatternConfig{
|
||||
Template: "PR-%d",
|
||||
}
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||
})
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: nil,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||
return httptest.NewRequest(method, target, body), nil
|
||||
},
|
||||
}
|
||||
|
||||
actual, err := sdk.GetPullRequest("test-project", 1)
|
||||
|
||||
assert.Nil(t, err, "Successful data retrieval broken and throws error")
|
||||
assert.IsType(t, &PullRequest{}, actual, "Happy path broken")
|
||||
|
||||
t.Cleanup(func() {
|
||||
settings.Pattern = nil
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Fetch error", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||
})
|
||||
expected := fmt.Errorf("Some simulated error")
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: expected,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||
return httptest.NewRequest(method, target, body), nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := sdk.GetPullRequest("test-project", 1)
|
||||
|
||||
assert.Errorf(t, err, "fetching pull requests failed", "Incorrect edge case is throwing errors")
|
||||
assert.Errorf(t, err, "Some simulated error", "Unexpected error cause")
|
||||
})
|
||||
|
||||
t.Run("Unknown PR", func(t *testing.T) {
|
||||
settings.Pattern = &settings.PatternConfig{
|
||||
Template: "PR-%d",
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||
})
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: nil,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||
return httptest.NewRequest(method, target, body), nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := sdk.GetPullRequest("test-project", 1337)
|
||||
|
||||
assert.Errorf(t, err, "no pull request found with name 'PR-1337'")
|
||||
|
||||
t.Cleanup(func() {
|
||||
settings.Pattern = nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetMeasures(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"0","bestValue":true}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`))
|
||||
})
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: nil,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||
return httptest.NewRequest(method, target, body), nil
|
||||
},
|
||||
}
|
||||
|
||||
actual, err := sdk.GetMeasures("test-project", "PR-1")
|
||||
|
||||
assert.Nil(t, err, "Successful data retrieval broken and throws error")
|
||||
assert.IsType(t, &MeasuresResponse{}, actual, "Happy path broken")
|
||||
})
|
||||
|
||||
t.Run("Building failure", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"0","bestValue":true}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`))
|
||||
})
|
||||
expected := fmt.Errorf("Some simulated error")
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: nil,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||
return nil, expected
|
||||
},
|
||||
}
|
||||
|
||||
_, err := sdk.GetMeasures("test-project", "PR-1")
|
||||
|
||||
assert.Equal(t, expected, err, "Unexpected error instance returned")
|
||||
})
|
||||
|
||||
t.Run("Request error", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"0","bestValue":true}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`))
|
||||
})
|
||||
expected := fmt.Errorf("Some simulated error")
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: expected,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||
return httptest.NewRequest(method, target, body), nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := sdk.GetMeasures("test-project", "PR-1")
|
||||
|
||||
assert.Equal(t, expected, err)
|
||||
})
|
||||
|
||||
t.Run("Errors in response", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"errors":[{"msg":"Component 'non-existing-project' of pull request 'PR-1' not found"}]}`))
|
||||
})
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: nil,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||
return httptest.NewRequest(method, target, body), nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := sdk.GetMeasures("non-existing-project", "PR-1")
|
||||
|
||||
assert.Errorf(t, err, "Component 'non-existing-project' of pull request 'PR-1' not found", "Response error parsing broken")
|
||||
})
|
||||
}
|
||||
|
||||
func TestComposeGiteaComment(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"10","bestValue":false}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`))
|
||||
})
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: nil,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||
return httptest.NewRequest(method, target, body), nil
|
||||
},
|
||||
}
|
||||
|
||||
actual, err := sdk.ComposeGiteaComment(&CommentComposeData{
|
||||
Key: "test-project",
|
||||
PRName: "PR-1",
|
||||
Url: "https://sonarqube.example.com",
|
||||
QualityGate: "OK",
|
||||
})
|
||||
|
||||
assert.Nil(t, err, "Successful comment composing throwing errors")
|
||||
assert.Contains(t, actual, ":white_check_mark:", "Happy path [Quality Gate] broken")
|
||||
assert.Contains(t, actual, "| Metric | Current |", "Happy path [Metrics Header] broken")
|
||||
assert.Contains(t, actual, "| Bugs | 10 |", "Happy path [Metrics Values] broken")
|
||||
assert.Contains(t, actual, "https://sonarqube.example.com", "Happy path [Link] broken")
|
||||
assert.Contains(t, actual, "/sq-bot review", "Happy path [Command] broken")
|
||||
})
|
||||
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"10","bestValue":false}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`))
|
||||
})
|
||||
expected := fmt.Errorf("Expected error from GetMeasures")
|
||||
sdk := &SonarQubeSdk{
|
||||
settings: &settings.SonarQubeConfig{
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
},
|
||||
client: &ClientMock{
|
||||
handler: handler,
|
||||
recoder: httptest.NewRecorder(),
|
||||
responseError: nil,
|
||||
},
|
||||
bodyReader: io.ReadAll,
|
||||
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||
return nil, expected
|
||||
},
|
||||
}
|
||||
|
||||
_, err := sdk.ComposeGiteaComment(&CommentComposeData{
|
||||
Key: "test-project",
|
||||
PRName: "PR-1",
|
||||
Url: "https://sonarqube.example.com",
|
||||
QualityGate: "OK",
|
||||
})
|
||||
|
||||
assert.Errorf(t, err, expected.Error(), "Undetected error while composing comment")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
config := &settings.SonarQubeConfig{
|
||||
Url: "http://example.com",
|
||||
Token: &settings.Token{
|
||||
Value: "test-token",
|
||||
},
|
||||
}
|
||||
actual := New(config)
|
||||
assert.IsType(t, &SonarQubeSdk{}, actual, "Unexpected return type")
|
||||
assert.Equal(t, config, actual.settings)
|
||||
}
|
|
@ -5,7 +5,7 @@ type GiteaRepository struct {
|
|||
Name string
|
||||
}
|
||||
|
||||
type giteaConfig struct {
|
||||
type GiteaConfig struct {
|
||||
Url string
|
||||
Token *Token
|
||||
Webhook *Webhook
|
||||
|
|
8
internal/settings/pattern.go
Normal file
8
internal/settings/pattern.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package settings
|
||||
|
||||
import "regexp"
|
||||
|
||||
type PatternConfig struct {
|
||||
RegExp *regexp.Regexp
|
||||
Template string
|
||||
}
|
|
@ -2,21 +2,22 @@ package settings
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
Gitea giteaConfig
|
||||
SonarQube sonarQubeConfig
|
||||
Gitea GiteaConfig
|
||||
SonarQube SonarQubeConfig
|
||||
Projects []Project
|
||||
Pattern *PatternConfig
|
||||
)
|
||||
|
||||
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)
|
||||
|
@ -34,13 +35,14 @@ func newConfigReader() *viper.Viper {
|
|||
v.SetDefault("sonarqube.webhook.secretFile", "")
|
||||
v.SetDefault("sonarqube.additionalMetrics", []string{})
|
||||
v.SetDefault("projects", []Project{})
|
||||
v.SetDefault("namingPattern.regex", `^PR-(\d+)$`)
|
||||
v.SetDefault("namingPattern.template", "PR-%d")
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func Load(configPath string) {
|
||||
r := newConfigReader()
|
||||
r.AddConfigPath(configPath)
|
||||
func Load(configFile string) {
|
||||
r := newConfigReader(configFile)
|
||||
|
||||
err := r.ReadInConfig()
|
||||
if err != nil {
|
||||
|
@ -62,15 +64,19 @@ func Load(configPath string) {
|
|||
|
||||
errCallback := func(msg string) { panic(msg) }
|
||||
|
||||
Gitea = giteaConfig{
|
||||
Gitea = GiteaConfig{
|
||||
Url: r.GetString("gitea.url"),
|
||||
Token: NewToken(r.GetString, "gitea", errCallback),
|
||||
Webhook: NewWebhook(r.GetString, "gitea", errCallback),
|
||||
}
|
||||
SonarQube = sonarQubeConfig{
|
||||
SonarQube = SonarQubeConfig{
|
||||
Url: r.GetString("sonarqube.url"),
|
||||
Token: NewToken(r.GetString, "sonarqube", errCallback),
|
||||
Webhook: NewWebhook(r.GetString, "sonarqube", errCallback),
|
||||
AdditionalMetrics: r.GetStringSlice("sonarqube.additionalMetrics"),
|
||||
}
|
||||
Pattern = &PatternConfig{
|
||||
RegExp: regexp.MustCompile(r.GetString("namingPattern.regex")),
|
||||
Template: r.GetString("namingPattern.template"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,14 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var defaultConfig []byte = []byte(
|
||||
func defaultConfig() []byte {
|
||||
return []byte(
|
||||
`gitea:
|
||||
url: https://example.com/gitea
|
||||
token:
|
||||
|
@ -25,13 +27,17 @@ sonarqube:
|
|||
additionalMetrics: []
|
||||
projects:
|
||||
- sonarqube:
|
||||
key: gitea-sonarqube-pr-bot
|
||||
key: gitea-sonarqube-bot
|
||||
gitea:
|
||||
owner: example-organization
|
||||
name: pr-bot
|
||||
namingPattern:
|
||||
regex: "^PR-(\\d+)$"
|
||||
template: "PR-%d"
|
||||
`)
|
||||
}
|
||||
|
||||
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,23 +46,102 @@ 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")
|
||||
func TestLoad(t *testing.T) {
|
||||
t.Run("Missing file", func(t *testing.T) {
|
||||
assert.Panics(t, func() { Load(path.Join(os.TempDir(), "config.yaml")) }, "No panic while reading missing file")
|
||||
})
|
||||
|
||||
t.Run("Existing file", func(t *testing.T) {
|
||||
c := WriteConfigFile(t, defaultConfig())
|
||||
assert.NotPanics(t, func() { Load(c) }, "Unexpected panic while reading existing file")
|
||||
})
|
||||
|
||||
t.Run("File references", func(t *testing.T) {
|
||||
giteaWebhookSecretFile := path.Join(os.TempDir(), "webhook-secret-gitea")
|
||||
_ = ioutil.WriteFile(giteaWebhookSecretFile, []byte(`gitea-totally-secret`), 0444)
|
||||
|
||||
giteaTokenFile := path.Join(os.TempDir(), "token-secret-gitea")
|
||||
_ = ioutil.WriteFile(giteaTokenFile, []byte(`d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565`), 0444)
|
||||
|
||||
sonarqubeWebhookSecretFile := path.Join(os.TempDir(), "webhook-secret-sonarqube")
|
||||
_ = ioutil.WriteFile(sonarqubeWebhookSecretFile, []byte(`sonarqube-totally-secret`), 0444)
|
||||
|
||||
sonarqubeTokenFile := path.Join(os.TempDir(), "token-secret-sonarqube")
|
||||
_ = ioutil.WriteFile(sonarqubeTokenFile, []byte(`a09eb5785b25bb2cbacf48808a677a0709f02d8e`), 0444)
|
||||
|
||||
c := 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
|
||||
projects:
|
||||
- sonarqube:
|
||||
key: gitea-sonarqube-bot
|
||||
gitea:
|
||||
owner: example-organization
|
||||
name: pr-bot
|
||||
`))
|
||||
os.Setenv("PRBOT_GITEA_WEBHOOK_SECRETFILE", giteaWebhookSecretFile)
|
||||
os.Setenv("PRBOT_GITEA_TOKEN_FILE", giteaTokenFile)
|
||||
os.Setenv("PRBOT_SONARQUBE_WEBHOOK_SECRETFILE", sonarqubeWebhookSecretFile)
|
||||
os.Setenv("PRBOT_SONARQUBE_TOKEN_FILE", sonarqubeTokenFile)
|
||||
|
||||
expectedGitea := GiteaConfig{
|
||||
Url: "https://example.com/gitea",
|
||||
Token: &Token{
|
||||
Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565",
|
||||
file: giteaTokenFile,
|
||||
},
|
||||
Webhook: &Webhook{
|
||||
Secret: "gitea-totally-secret",
|
||||
secretFile: giteaWebhookSecretFile,
|
||||
},
|
||||
}
|
||||
|
||||
expectedSonarQube := SonarQubeConfig{
|
||||
Url: "https://example.com/sonarqube",
|
||||
Token: &Token{
|
||||
Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e",
|
||||
file: sonarqubeTokenFile,
|
||||
},
|
||||
Webhook: &Webhook{
|
||||
Secret: "sonarqube-totally-secret",
|
||||
secretFile: sonarqubeWebhookSecretFile,
|
||||
},
|
||||
AdditionalMetrics: []string{},
|
||||
}
|
||||
|
||||
Load(c)
|
||||
assert.EqualValues(t, expectedGitea, Gitea)
|
||||
assert.EqualValues(t, expectedSonarQube, SonarQube)
|
||||
|
||||
t.Cleanup(func() {
|
||||
os.Remove(giteaWebhookSecretFile)
|
||||
os.Remove(giteaTokenFile)
|
||||
os.Remove(sonarqubeWebhookSecretFile)
|
||||
os.Remove(sonarqubeTokenFile)
|
||||
os.Unsetenv("PRBOT_GITEA_WEBHOOK_SECRETFILE")
|
||||
os.Unsetenv("PRBOT_GITEA_TOKEN_FILE")
|
||||
os.Unsetenv("PRBOT_SONARQUBE_WEBHOOK_SECRETFILE")
|
||||
os.Unsetenv("PRBOT_SONARQUBE_TOKEN_FILE")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadWithExistingFile(t *testing.T) {
|
||||
WriteConfigFile(t, defaultConfig)
|
||||
func TestLoadGitea(t *testing.T) {
|
||||
t.Run("Default", func(t *testing.T) {
|
||||
c := WriteConfigFile(t, defaultConfig())
|
||||
Load(c)
|
||||
|
||||
assert.NotPanics(t, func() { Load(os.TempDir()) }, "Unexpected panic while reading existing file")
|
||||
}
|
||||
|
||||
func TestLoadGiteaStructure(t *testing.T) {
|
||||
WriteConfigFile(t, defaultConfig)
|
||||
Load(os.TempDir())
|
||||
|
||||
expected := giteaConfig{
|
||||
expected := GiteaConfig{
|
||||
Url: "https://example.com/gitea",
|
||||
Token: &Token{
|
||||
Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565",
|
||||
|
@ -67,15 +152,15 @@ func TestLoadGiteaStructure(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.EqualValues(t, expected, Gitea)
|
||||
}
|
||||
})
|
||||
|
||||
func TestLoadGiteaStructureInjectedEnvs(t *testing.T) {
|
||||
t.Run("Injected envs", func(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{
|
||||
expected := GiteaConfig{
|
||||
Url: "https://example.com/gitea",
|
||||
Token: &Token{
|
||||
Value: "injected-token",
|
||||
|
@ -91,13 +176,15 @@ func TestLoadGiteaStructureInjectedEnvs(t *testing.T) {
|
|||
os.Unsetenv("PRBOT_GITEA_WEBHOOK_SECRET")
|
||||
os.Unsetenv("PRBOT_GITEA_TOKEN_VALUE")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadSonarQubeStructure(t *testing.T) {
|
||||
WriteConfigFile(t, defaultConfig)
|
||||
Load(os.TempDir())
|
||||
func TestLoadSonarQube(t *testing.T) {
|
||||
t.Run("Default", func(t *testing.T) {
|
||||
c := WriteConfigFile(t, defaultConfig())
|
||||
Load(c)
|
||||
|
||||
expected := sonarQubeConfig{
|
||||
expected := SonarQubeConfig{
|
||||
Url: "https://example.com/sonarqube",
|
||||
Token: &Token{
|
||||
Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e",
|
||||
|
@ -109,10 +196,10 @@ 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(
|
||||
t.Run("Additional metrics", func(t *testing.T) {
|
||||
c := WriteConfigFile(t, []byte(
|
||||
`gitea:
|
||||
url: https://example.com/gitea
|
||||
token:
|
||||
|
@ -124,14 +211,14 @@ sonarqube:
|
|||
additionalMetrics: "new_security_hotspots"
|
||||
projects:
|
||||
- sonarqube:
|
||||
key: gitea-sonarqube-pr-bot
|
||||
key: gitea-sonarqube-bot
|
||||
gitea:
|
||||
owner: example-organization
|
||||
name: pr-bot
|
||||
`))
|
||||
Load(os.TempDir())
|
||||
Load(c)
|
||||
|
||||
expected := sonarQubeConfig{
|
||||
expected := SonarQubeConfig{
|
||||
Url: "https://example.com/sonarqube",
|
||||
Token: &Token{
|
||||
Value: "fake-sonarqube-token",
|
||||
|
@ -147,15 +234,15 @@ projects:
|
|||
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) {
|
||||
t.Run("Injected envs", func(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{
|
||||
expected := SonarQubeConfig{
|
||||
Url: "https://example.com/sonarqube",
|
||||
Token: &Token{
|
||||
Value: "injected-token",
|
||||
|
@ -171,91 +258,18 @@ func TestLoadSonarQubeStructureInjectedEnvs(t *testing.T) {
|
|||
os.Unsetenv("PRBOT_SONARQUBE_WEBHOOK_SECRET")
|
||||
os.Unsetenv("PRBOT_SONARQUBE_TOKEN_VALUE")
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadStructureWithFileReferenceResolving(t *testing.T) {
|
||||
giteaWebhookSecretFile := path.Join(os.TempDir(), "webhook-secret-gitea")
|
||||
_ = ioutil.WriteFile(giteaWebhookSecretFile, []byte(`gitea-totally-secret`), 0444)
|
||||
|
||||
giteaTokenFile := path.Join(os.TempDir(), "token-secret-gitea")
|
||||
_ = ioutil.WriteFile(giteaTokenFile, []byte(`d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565`), 0444)
|
||||
|
||||
sonarqubeWebhookSecretFile := path.Join(os.TempDir(), "webhook-secret-sonarqube")
|
||||
_ = ioutil.WriteFile(sonarqubeWebhookSecretFile, []byte(`sonarqube-totally-secret`), 0444)
|
||||
|
||||
sonarqubeTokenFile := path.Join(os.TempDir(), "token-secret-sonarqube")
|
||||
_ = ioutil.WriteFile(sonarqubeTokenFile, []byte(`a09eb5785b25bb2cbacf48808a677a0709f02d8e`), 0444)
|
||||
|
||||
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
|
||||
projects:
|
||||
- sonarqube:
|
||||
key: gitea-sonarqube-pr-bot
|
||||
gitea:
|
||||
owner: example-organization
|
||||
name: pr-bot
|
||||
`))
|
||||
os.Setenv("PRBOT_GITEA_WEBHOOK_SECRETFILE", giteaWebhookSecretFile)
|
||||
os.Setenv("PRBOT_GITEA_TOKEN_FILE", giteaTokenFile)
|
||||
os.Setenv("PRBOT_SONARQUBE_WEBHOOK_SECRETFILE", sonarqubeWebhookSecretFile)
|
||||
os.Setenv("PRBOT_SONARQUBE_TOKEN_FILE", sonarqubeTokenFile)
|
||||
|
||||
expectedGitea := giteaConfig{
|
||||
Url: "https://example.com/gitea",
|
||||
Token: &Token{
|
||||
Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565",
|
||||
file: giteaTokenFile,
|
||||
},
|
||||
Webhook: &Webhook{
|
||||
Secret: "gitea-totally-secret",
|
||||
secretFile: giteaWebhookSecretFile,
|
||||
},
|
||||
}
|
||||
|
||||
expectedSonarQube := sonarQubeConfig{
|
||||
Url: "https://example.com/sonarqube",
|
||||
Token: &Token{
|
||||
Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e",
|
||||
file: sonarqubeTokenFile,
|
||||
},
|
||||
Webhook: &Webhook{
|
||||
Secret: "sonarqube-totally-secret",
|
||||
secretFile: sonarqubeWebhookSecretFile,
|
||||
},
|
||||
AdditionalMetrics: []string{},
|
||||
}
|
||||
|
||||
Load(os.TempDir())
|
||||
assert.EqualValues(t, expectedGitea, Gitea)
|
||||
assert.EqualValues(t, expectedSonarQube, SonarQube)
|
||||
|
||||
t.Cleanup(func() {
|
||||
os.Remove(giteaWebhookSecretFile)
|
||||
os.Remove(giteaTokenFile)
|
||||
os.Remove(sonarqubeWebhookSecretFile)
|
||||
os.Remove(sonarqubeTokenFile)
|
||||
os.Unsetenv("PRBOT_GITEA_WEBHOOK_SECRETFILE")
|
||||
os.Unsetenv("PRBOT_GITEA_TOKEN_FILE")
|
||||
os.Unsetenv("PRBOT_SONARQUBE_WEBHOOK_SECRETFILE")
|
||||
os.Unsetenv("PRBOT_SONARQUBE_TOKEN_FILE")
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadProjectsStructure(t *testing.T) {
|
||||
WriteConfigFile(t, defaultConfig)
|
||||
Load(os.TempDir())
|
||||
func TestLoadProjects(t *testing.T) {
|
||||
t.Run("Default", func(t *testing.T) {
|
||||
c := WriteConfigFile(t, defaultConfig())
|
||||
Load(c)
|
||||
|
||||
expectedProjects := []Project{
|
||||
{
|
||||
SonarQube: struct{ Key string }{
|
||||
Key: "gitea-sonarqube-pr-bot",
|
||||
Key: "gitea-sonarqube-bot",
|
||||
},
|
||||
Gitea: GiteaRepository{
|
||||
Owner: "example-organization",
|
||||
|
@ -265,9 +279,9 @@ func TestLoadProjectsStructure(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.EqualValues(t, expectedProjects, Projects)
|
||||
}
|
||||
})
|
||||
|
||||
func TestLoadProjectsStructureWithNoMapping(t *testing.T) {
|
||||
t.Run("Empty mapping", func(t *testing.T) {
|
||||
invalidConfig := []byte(
|
||||
`gitea:
|
||||
url: https://example.com/gitea
|
||||
|
@ -283,7 +297,86 @@ 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")
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadNamingPattern(t *testing.T) {
|
||||
t.Run("Default", func(t *testing.T) {
|
||||
c := WriteConfigFile(t, defaultConfig())
|
||||
Load(c)
|
||||
|
||||
expected := &PatternConfig{
|
||||
RegExp: regexp.MustCompile(`^PR-(\d+)$`),
|
||||
Template: "PR-%d",
|
||||
}
|
||||
|
||||
assert.EqualValues(t, expected, Pattern)
|
||||
})
|
||||
|
||||
t.Run("Internal defaults", func(t *testing.T) {
|
||||
c := 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-bot
|
||||
gitea:
|
||||
owner: example-organization
|
||||
name: pr-bot
|
||||
`))
|
||||
Load(c)
|
||||
|
||||
expected := &PatternConfig{
|
||||
RegExp: regexp.MustCompile(`^PR-(\d+)$`),
|
||||
Template: "PR-%d",
|
||||
}
|
||||
|
||||
assert.EqualValues(t, expected, Pattern)
|
||||
})
|
||||
|
||||
t.Run("Injected envs", func(t *testing.T) {
|
||||
os.Setenv("PRBOT_NAMINGPATTERN_REGEX", "test-(\\d+)-pullrequest")
|
||||
os.Setenv("PRBOT_NAMINGPATTERN_TEMPLATE", "test-%d-pullrequest")
|
||||
c := WriteConfigFile(t, defaultConfig())
|
||||
Load(c)
|
||||
|
||||
expected := &PatternConfig{
|
||||
RegExp: regexp.MustCompile(`test-(\d+)-pullrequest`),
|
||||
Template: "test-%d-pullrequest",
|
||||
}
|
||||
|
||||
assert.EqualValues(t, expected, Pattern)
|
||||
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv("PRBOT_NAMINGPATTERN_REGEX")
|
||||
os.Unsetenv("PRBOT_NAMINGPATTERN_TEMPLATE")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Mixed input", func(t *testing.T) {
|
||||
os.Setenv("PRBOT_NAMINGPATTERN_REGEX", "test-(\\d+)-pullrequest")
|
||||
c := WriteConfigFile(t, defaultConfig())
|
||||
Load(c)
|
||||
|
||||
expected := &PatternConfig{
|
||||
RegExp: regexp.MustCompile(`test-(\d+)-pullrequest`),
|
||||
Template: "PR-%d",
|
||||
}
|
||||
|
||||
assert.EqualValues(t, expected, Pattern)
|
||||
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv("PRBOT_NAMINGPATTERN_REGEX")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,14 +2,14 @@ package settings
|
|||
|
||||
import "strings"
|
||||
|
||||
type sonarQubeConfig struct {
|
||||
type SonarQubeConfig struct {
|
||||
Url string
|
||||
Token *Token
|
||||
Webhook *Webhook
|
||||
AdditionalMetrics []string
|
||||
}
|
||||
|
||||
func (c *sonarQubeConfig) GetMetricsList() string {
|
||||
func (c *SonarQubeConfig) GetMetricsList() string {
|
||||
metrics := []string{
|
||||
"bugs",
|
||||
"vulnerabilities",
|
||||
|
|
|
@ -5,10 +5,10 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"gitea-sonarqube-pr-bot/internal/actions"
|
||||
giteaSdk "gitea-sonarqube-pr-bot/internal/clients/gitea"
|
||||
sqSdk "gitea-sonarqube-pr-bot/internal/clients/sonarqube"
|
||||
"gitea-sonarqube-pr-bot/internal/settings"
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/actions"
|
||||
giteaSdk "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/clients/gitea"
|
||||
sqSdk "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/clients/sonarqube"
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/settings"
|
||||
)
|
||||
|
||||
type issue struct {
|
||||
|
|
|
@ -5,9 +5,9 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
giteaSdk "gitea-sonarqube-pr-bot/internal/clients/gitea"
|
||||
sqSdk "gitea-sonarqube-pr-bot/internal/clients/sonarqube"
|
||||
"gitea-sonarqube-pr-bot/internal/settings"
|
||||
giteaSdk "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/clients/gitea"
|
||||
sqSdk "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/clients/sonarqube"
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/settings"
|
||||
)
|
||||
|
||||
type pullRequest struct {
|
||||
|
|
|
@ -4,9 +4,13 @@ import (
|
|||
"encoding/json"
|
||||
"log"
|
||||
|
||||
sqSdk "gitea-sonarqube-pr-bot/internal/clients/sonarqube"
|
||||
sqSdk "codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/clients/sonarqube"
|
||||
)
|
||||
|
||||
type properties struct {
|
||||
OriginalCommit string `json:"sonar.analysis.sqbot,omitempty"`
|
||||
}
|
||||
|
||||
type Webhook struct {
|
||||
ServerUrl string `json:"serverUrl"`
|
||||
Revision string `json:"revision"`
|
||||
|
@ -27,9 +31,7 @@ type Webhook struct {
|
|||
Status string
|
||||
} `json:"conditions"`
|
||||
} `json:"qualityGate"`
|
||||
Properties *struct {
|
||||
OriginalCommit string `json:"sonar.analysis.sqbot,omitempty"`
|
||||
} `json:"properties,omitempty"`
|
||||
Properties *properties `json:"properties,omitempty"`
|
||||
PRIndex int
|
||||
}
|
||||
|
||||
|
|
|
@ -1,30 +1,81 @@
|
|||
package sonarqube
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"codeberg.org/justusbunsi/gitea-sonarqube-bot/internal/settings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewWebhook(t *testing.T) {
|
||||
raw := []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
settings.Pattern = &settings.PatternConfig{
|
||||
RegExp: regexp.MustCompile(`^PR-(\d+)$`),
|
||||
}
|
||||
|
||||
raw := []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": { "sonar.analysis.sqbot": "a84442009c09b1adc278b6bb80a3853419f54007" } }`)
|
||||
response, ok := New(raw)
|
||||
|
||||
assert.NotNil(t, response)
|
||||
assert.Equal(t, 1337, response.PRIndex)
|
||||
assert.Equal(t, "a84442009c09b1adc278b6bb80a3853419f54007", response.Properties.OriginalCommit)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestNewWebhookInvalidJSON(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
settings.Pattern = nil
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Invalid JSON", func(t *testing.T) {
|
||||
raw := []byte(`{ "serverUrl": ["invalid-server-url-content"] }`)
|
||||
_, ok := New(raw)
|
||||
|
||||
assert.False(t, ok)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Invalid branch name", func(t *testing.T) {
|
||||
settings.Pattern = &settings.PatternConfig{
|
||||
RegExp: regexp.MustCompile(`^PR-(\d+)$`),
|
||||
}
|
||||
|
||||
func TestNewWebhookInvalidBranchName(t *testing.T) {
|
||||
raw := []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "invalid", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)
|
||||
_, ok := New(raw)
|
||||
|
||||
assert.False(t, ok)
|
||||
|
||||
t.Cleanup(func() {
|
||||
settings.Pattern = nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestWebhookGetRevision(t *testing.T) {
|
||||
t.Run("Default", func(t *testing.T) {
|
||||
w := Webhook{
|
||||
Revision: "225fa0306c0ab83297d0cb5db0717b194ccb2e76",
|
||||
}
|
||||
|
||||
assert.Equal(t, w.Revision, w.GetRevision())
|
||||
})
|
||||
|
||||
t.Run("Incomplete properties", func(t *testing.T) {
|
||||
w := Webhook{
|
||||
Revision: "225fa0306c0ab83297d0cb5db0717b194ccb2e76",
|
||||
Properties: &properties{},
|
||||
}
|
||||
|
||||
assert.Equal(t, w.Revision, w.GetRevision())
|
||||
})
|
||||
|
||||
t.Run("Original commit from properties", func(t *testing.T) {
|
||||
w := Webhook{
|
||||
Revision: "225fa0306c0ab83297d0cb5db0717b194ccb2e76",
|
||||
Properties: &properties{
|
||||
OriginalCommit: "a9fe6800b0bbb70748aff53a011d8c09bbff42fe",
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, w.Properties.OriginalCommit, w.GetRevision())
|
||||
})
|
||||
}
|
||||
|
|
137
package-lock.json
generated
137
package-lock.json
generated
|
@ -1,24 +1,30 @@
|
|||
{
|
||||
"name": "gitea-sonarqube-pr-bot",
|
||||
"name": "gitea-sonarqube-bot",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "gitea-sonarqube-pr-bot",
|
||||
"name": "gitea-sonarqube-bot",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readme-generator-for-helm": "^1.3.1"
|
||||
"devDependencies": {
|
||||
"readme-generator-for-helm": "https://github.com/bitnami-labs/readme-generator-for-helm/tarball/main"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.21.0",
|
||||
"npm": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -28,6 +34,7 @@
|
|||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
|
@ -35,12 +42,14 @@
|
|||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/dot-object": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.4.tgz",
|
||||
"integrity": "sha512-7FXnyyCLFawNYJ+NhkqyP9Wd2yzuo+7n9pGiYpkmXCTYa8Ci2U0eUNDVg5OuO5Pm6aFXI2SWN8/N/w7SJWu1WA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"commander": "^4.0.0",
|
||||
"glob": "^7.1.5"
|
||||
|
@ -53,6 +62,7 @@
|
|||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
|
@ -60,12 +70,14 @@
|
|||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.2.tgz",
|
||||
"integrity": "sha512-NzDgHDiJwKYByLrL5lONmQFpK/2G78SMMfo+E9CuGlX4IkvfKDsiQSNPwAYxEy+e6p7ZQ3uslSLlwlJcqezBmQ==",
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
|
@ -76,12 +88,16 @@
|
|||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
|
@ -90,25 +106,33 @@
|
|||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/markdown-table": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz",
|
||||
"integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"repeat-string": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
|
@ -119,7 +143,8 @@
|
|||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
@ -127,15 +152,18 @@
|
|||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readme-generator-for-helm": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/readme-generator-for-helm/-/readme-generator-for-helm-1.3.1.tgz",
|
||||
"integrity": "sha512-3esincvKfR32K+xdxbYXcDG2bhiPSJdmie4dDFJxEu2Y93Dm8xPoDn7ieppyEpSiTBKiIYBBfE6BwHW2HE/j+A==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://github.com/bitnami-labs/readme-generator-for-helm/tarball/main",
|
||||
"integrity": "sha512-W5ziOuId0M00YQRDlA5le3oEguWe8hoINhivOAgEF+AZkk2bDoNxuFUaJIxqAUEvZRA8qlTfUlu+w90EOFbTLw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"commander": "^7.1.0",
|
||||
"dot-object": "^2.1.4",
|
||||
|
@ -150,7 +178,8 @@
|
|||
"node_modules/repeat-string": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
|
||||
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
|
||||
"integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
|
@ -158,12 +187,14 @@
|
|||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.1.tgz",
|
||||
"integrity": "sha512-1NpAYQ3wjzIlMs0mgdBmYzLkFgWBIWrzYVDYfrixhoFNNgJ444/jT2kUT2sicRbJES3oQYRZugjB6Ro8SjKeFg==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz",
|
||||
"integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
|
@ -173,12 +204,14 @@
|
|||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -187,17 +220,20 @@
|
|||
"commander": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="
|
||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true
|
||||
},
|
||||
"dot-object": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.4.tgz",
|
||||
"integrity": "sha512-7FXnyyCLFawNYJ+NhkqyP9Wd2yzuo+7n9pGiYpkmXCTYa8Ci2U0eUNDVg5OuO5Pm6aFXI2SWN8/N/w7SJWu1WA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^4.0.0",
|
||||
"glob": "^7.1.5"
|
||||
|
@ -206,19 +242,22 @@
|
|||
"commander": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="
|
||||
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.2.tgz",
|
||||
"integrity": "sha512-NzDgHDiJwKYByLrL5lONmQFpK/2G78SMMfo+E9CuGlX4IkvfKDsiQSNPwAYxEy+e6p7ZQ3uslSLlwlJcqezBmQ==",
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
|
@ -231,7 +270,8 @@
|
|||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
|
@ -240,17 +280,20 @@
|
|||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"markdown-table": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz",
|
||||
"integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"repeat-string": "^1.0.0"
|
||||
}
|
||||
|
@ -259,6 +302,7 @@
|
|||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
@ -266,7 +310,8 @@
|
|||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
@ -274,12 +319,13 @@
|
|||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"dev": true
|
||||
},
|
||||
"readme-generator-for-helm": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/readme-generator-for-helm/-/readme-generator-for-helm-1.3.1.tgz",
|
||||
"integrity": "sha512-3esincvKfR32K+xdxbYXcDG2bhiPSJdmie4dDFJxEu2Y93Dm8xPoDn7ieppyEpSiTBKiIYBBfE6BwHW2HE/j+A==",
|
||||
"version": "https://github.com/bitnami-labs/readme-generator-for-helm/tarball/main",
|
||||
"integrity": "sha512-W5ziOuId0M00YQRDlA5le3oEguWe8hoINhivOAgEF+AZkk2bDoNxuFUaJIxqAUEvZRA8qlTfUlu+w90EOFbTLw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^7.1.0",
|
||||
"dot-object": "^2.1.4",
|
||||
|
@ -291,17 +337,20 @@
|
|||
"repeat-string": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
|
||||
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
|
||||
"integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yaml": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.1.tgz",
|
||||
"integrity": "sha512-1NpAYQ3wjzIlMs0mgdBmYzLkFgWBIWrzYVDYfrixhoFNNgJ444/jT2kUT2sicRbJES3oQYRZugjB6Ro8SjKeFg=="
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz",
|
||||
"integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
15
package.json
15
package.json
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "gitea-sonarqube-pr-bot",
|
||||
"name": "gitea-sonarqube-bot",
|
||||
"description": "Integrate SonarQube analysis into Gitea Pull Requests",
|
||||
"author": "Steven Kriegler <sk.bunsenbrenner@gmail.com>",
|
||||
"license": "MIT",
|
||||
|
@ -14,10 +14,15 @@
|
|||
"pull request",
|
||||
"bot"
|
||||
],
|
||||
"scripts": {
|
||||
"helm-params": "cd node_modules/readme-generator-for-helm && node bin/index.js --config ./../../helm/readme-generator-config.json --readme ./../../helm/README.md --values ./../../helm/values.yaml"
|
||||
"engineStrict": true,
|
||||
"engines": {
|
||||
"node": ">=12.21.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"readme-generator-for-helm": "^1.3.1"
|
||||
"scripts": {
|
||||
"helm-params": "readme-generator --readme ./helm/README.md --values ./helm/values.yaml"
|
||||
},
|
||||
"devDependencies": {
|
||||
"readme-generator-for-helm": "https://github.com/bitnami-labs/readme-generator-for-helm/tarball/main"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,5 +11,8 @@ sonar.exclusions=**/*_test.go,contrib/**,docker/**,docs/**,helm/**
|
|||
sonar.tests=.
|
||||
sonar.test.inclusions=**/*_test.go
|
||||
|
||||
# Entrypoint of the application and not properly testable
|
||||
sonar.coverage.exclusions=cmd/gitea-sonarqube-bot/main.go
|
||||
|
||||
sonar.go.tests.reportPaths=test-report.out
|
||||
sonar.go.coverage.reportPaths=cover.out
|
||||
|
|
Loading…
Reference in a new issue