Proof of concept #1
6 changed files with 151 additions and 25 deletions
|
@ -27,7 +27,7 @@ steps:
|
|||
GOMEMLIMIT: "1150MiB"
|
||||
NITTER_BASE_ADDRESS: https://code.icb4dc0.de
|
||||
NITTER_TOKEN:
|
||||
from_secret: gitea_token
|
||||
from_secret: nitter_token
|
||||
commands:
|
||||
- go run github.com/magefile/mage -d build -w . lint
|
||||
depends_on:
|
||||
|
|
|
@ -123,7 +123,7 @@ func runGiteaPRNitting(cmd *cobra.Command, args []string) error {
|
|||
return nitter.Report(report, issues)
|
||||
}
|
||||
|
||||
func runGiteaPRLintNitting(cmd *cobra.Command, args []string) error {
|
||||
func runGiteaPRLintNitting(cmd *cobra.Command, _ []string) error {
|
||||
cfg, err := LoadConfig[gitea.GiteaPRLintConfig](cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -133,7 +133,11 @@ func runGiteaPRLintNitting(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
giteaClient, err := giteaSdk.NewClient(cfg.BaseAddress, giteaSdk.SetContext(cmd.Context()), giteaSdk.SetToken(cfg.Token))
|
||||
giteaClient, err := giteaSdk.NewClient(
|
||||
cfg.BaseAddress,
|
||||
giteaSdk.SetContext(cmd.Context()),
|
||||
giteaSdk.SetToken(cfg.Token),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
57
main.go
57
main.go
|
@ -2,21 +2,47 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"flag"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/exp/slog"
|
||||
|
||||
"code.icb4dc0.de/prskr/nitter/internal/commands"
|
||||
)
|
||||
|
||||
var root = &cobra.Command{
|
||||
Use: "nitter",
|
||||
TraverseChildren: true,
|
||||
}
|
||||
var (
|
||||
loggingConfig = struct {
|
||||
LogLevel *slog.LevelVar
|
||||
AddSource bool
|
||||
}{
|
||||
LogLevel: new(slog.LevelVar),
|
||||
}
|
||||
|
||||
root = &cobra.Command{
|
||||
Use: "nitter",
|
||||
TraverseChildren: true,
|
||||
PersistentPreRun: func(_ *cobra.Command, _ []string) {
|
||||
slogOptions := slog.HandlerOptions{
|
||||
AddSource: loggingConfig.AddSource,
|
||||
Level: loggingConfig.LogLevel.Level(),
|
||||
}
|
||||
|
||||
slog.SetDefault(slog.New(slogOptions.NewTextHandler(os.Stderr)))
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
slogOptions := slog.HandlerOptions{
|
||||
AddSource: true,
|
||||
Level: slog.LevelInfo,
|
||||
}
|
||||
|
||||
slog.SetDefault(slog.New(slogOptions.NewTextHandler(os.Stderr)))
|
||||
|
||||
root.PersistentFlags().AddGoFlagSet(prepareLoggingFlags())
|
||||
root.PersistentFlags().StringP("namespace", "n", "", "Namespace a.k.a. organization/owner/group of the repository [$NITTER_NAMESPACE]")
|
||||
root.PersistentFlags().StringP("repo", "r", "", "Repo to interact with [$NITTER_REPO]")
|
||||
|
||||
|
@ -26,6 +52,25 @@ func main() {
|
|||
root.AddCommand(commands.Gitea())
|
||||
|
||||
if err := root.ExecuteContext(ctx); err != nil {
|
||||
log.Fatal(err)
|
||||
slog.Error("Error occurred during exeuction", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func prepareLoggingFlags() *flag.FlagSet {
|
||||
flagSet := flag.NewFlagSet("logging", flag.PanicOnError)
|
||||
flagSet.TextVar(
|
||||
loggingConfig.LogLevel,
|
||||
"log-level",
|
||||
loggingConfig.LogLevel,
|
||||
"set log level",
|
||||
)
|
||||
flagSet.BoolVar(
|
||||
&loggingConfig.AddSource,
|
||||
"add source line to log messages",
|
||||
false,
|
||||
"Enable to get detailed information where the log was produced",
|
||||
)
|
||||
|
||||
return flagSet
|
||||
}
|
||||
|
|
19
nitters/gitea/api.go
Normal file
19
nitters/gitea/api.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package gitea
|
||||
|
||||
import "code.gitea.io/sdk/gitea"
|
||||
|
||||
type PullReviewManager interface {
|
||||
CreatePullReview(owner, repo string, index int64, opt gitea.CreatePullReviewOptions) (*gitea.PullReview, *gitea.Response, error)
|
||||
ListPullReviews(owner, repo string, index int64, opt gitea.ListPullReviewsOptions) ([]*gitea.PullReview, *gitea.Response, error)
|
||||
DeletePullReview(owner, repo string, index, id int64) (*gitea.Response, error)
|
||||
}
|
||||
|
||||
type WhoAmIer interface {
|
||||
GetMyUserInfo() (*gitea.User, *gitea.Response, error)
|
||||
}
|
||||
|
||||
//go:generate mockery --name Client --filename gitea_client.mock.go
|
||||
type Client interface {
|
||||
PullReviewManager
|
||||
WhoAmIer
|
||||
}
|
|
@ -3,15 +3,19 @@ package gitea
|
|||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/golangci/golangci-lint/pkg/report"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
"golang.org/x/exp/slog"
|
||||
|
||||
"code.icb4dc0.de/prskr/nitter/nitters"
|
||||
)
|
||||
|
||||
const summaryFooter = "\n\n(Created by nitter)"
|
||||
|
||||
var (
|
||||
_ nitters.Nitter = (*prNitter)(nil)
|
||||
|
||||
|
@ -28,24 +32,37 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
func NewPRNitter(cli PullReviewCreator, cfg *GiteaPRConfig) *prNitter {
|
||||
func NewPRNitter(cli Client, cfg *GiteaPRConfig) *prNitter {
|
||||
return &prNitter{
|
||||
PullReviewCreator: cli,
|
||||
cfg: cfg,
|
||||
Client: cli,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate mockery --name PullReviewCreator --filename pull_review_creator.mock.go
|
||||
type PullReviewCreator interface {
|
||||
CreatePullReview(owner, repo string, index int64, opt gitea.CreatePullReviewOptions) (*gitea.PullReview, *gitea.Response, error)
|
||||
}
|
||||
|
||||
type prNitter struct {
|
||||
PullReviewCreator
|
||||
Client
|
||||
cfg *GiteaPRConfig
|
||||
}
|
||||
|
||||
func (p prNitter) Report(report *report.Data, issues []result.Issue) error {
|
||||
review, err := p.preparePullReview(report, issues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.cleanOutdatedReviews(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, _, err := p.CreatePullReview(p.cfg.Namespace, p.cfg.Repo, p.cfg.PRIndex, *review); err != nil {
|
||||
slog.Error("Failed to submit new PR review", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p prNitter) preparePullReview(report *report.Data, issues []result.Issue) (*gitea.CreatePullReviewOptions, error) {
|
||||
templateBuf := bytes.Buffer{}
|
||||
summaryData := map[string]any{
|
||||
"Report": report,
|
||||
|
@ -53,10 +70,10 @@ func (p prNitter) Report(report *report.Data, issues []result.Issue) error {
|
|||
}
|
||||
|
||||
if err := templates.ExecuteTemplate(&templateBuf, "issue_summary.tmpl.md", summaryData); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pullReviewOptions := gitea.CreatePullReviewOptions{
|
||||
pullReviewOptions := &gitea.CreatePullReviewOptions{
|
||||
State: p.cfg.ReviewState,
|
||||
Body: templateBuf.String(),
|
||||
Comments: make([]gitea.CreatePullReviewComment, 0, len(issues)),
|
||||
|
@ -71,7 +88,12 @@ func (p prNitter) Report(report *report.Data, issues []result.Issue) error {
|
|||
"Issue": issues[i],
|
||||
}
|
||||
if err := templates.ExecuteTemplate(&templateBuf, "issue_comment.tmpl.md", templateData); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := templateBuf.WriteString(summaryFooter); err != nil {
|
||||
slog.Error("Failed to append summary footer", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pullReviewOptions.Comments = append(pullReviewOptions.Comments, gitea.CreatePullReviewComment{
|
||||
|
@ -84,10 +106,31 @@ func (p prNitter) Report(report *report.Data, issues []result.Issue) error {
|
|||
}
|
||||
}
|
||||
|
||||
_, _, err := p.CreatePullReview(p.cfg.Namespace, p.cfg.Repo, p.cfg.PRIndex, pullReviewOptions)
|
||||
return pullReviewOptions, nil
|
||||
}
|
||||
|
||||
func (p prNitter) cleanOutdatedReviews() error {
|
||||
me, _, err := p.GetMyUserInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
slog.Debug("Running as", slog.String("name", me.UserName))
|
||||
|
||||
reviews, _, err := p.ListPullReviews(p.cfg.Namespace, p.cfg.Repo, p.cfg.PRIndex, gitea.ListPullReviewsOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range reviews {
|
||||
review := reviews[i]
|
||||
if review.Reviewer.ID == me.ID && strings.HasSuffix(review.Body, summaryFooter) {
|
||||
if _, err = p.DeletePullReview(p.cfg.Namespace, p.cfg.Repo, p.cfg.PRIndex, review.ID); err != nil {
|
||||
slog.Error("Failed to delete existing review", err, slog.Int64("reviewId", review.ID))
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"errors"
|
||||
"testing"
|
||||
|
||||
giteasdk "code.gitea.io/sdk/gitea"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/report"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
|
@ -12,15 +14,28 @@ import (
|
|||
)
|
||||
|
||||
func Test_PRNitter_Report_MockCreatorError_Error(t *testing.T) {
|
||||
t.Parallel()
|
||||
expectedError := errors.New("error")
|
||||
creator := mocks.NewPullReviewCreator(t)
|
||||
creator.
|
||||
client := mocks.NewClient(t)
|
||||
client.
|
||||
EXPECT().
|
||||
CreatePullReview(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(nil, nil, expectedError).
|
||||
Times(1)
|
||||
|
||||
p := gitea.NewPRNitter(creator, &gitea.GiteaPRConfig{})
|
||||
client.
|
||||
EXPECT().
|
||||
GetMyUserInfo().
|
||||
Return(&giteasdk.User{ID: 11}, nil, nil).
|
||||
Times(1)
|
||||
|
||||
client.
|
||||
EXPECT().
|
||||
ListPullReviews(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(nil, nil, nil).
|
||||
Times(1)
|
||||
|
||||
p := gitea.NewPRNitter(client, &gitea.GiteaPRConfig{})
|
||||
|
||||
if err := p.Report(new(report.Data), nil); err == nil {
|
||||
t.Error("expected error bot got none")
|
||||
|
|
Loading…
Reference in a new issue