Proof of concept #1

Merged
prskr merged 8 commits from feature/proof-of-concept into main 2023-03-10 10:17:07 +00:00
6 changed files with 151 additions and 25 deletions
Showing only changes of commit d51a05fea1 - Show all commits

View file

@ -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:

View file

@ -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
View file

@ -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
View 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
}

View file

@ -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
}

View file

@ -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")