This commit is contained in:
Adam Majer 2024-11-07 18:25:35 +01:00
parent 0d9451e92c
commit 8bedcc5195
14 changed files with 258 additions and 133 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
mock

View File

@ -7,7 +7,8 @@ gitea-generated/client/gitea_api_client.go:: api.json
[ -d gitea-generated ] || mkdir gitea-generated
podman run --rm -v $$(pwd):/api ghcr.io/go-swagger/go-swagger generate client -f /api/api.json -t /api/gitea-generated
api: gitea-generated/client/gitea_api_client.go
api: gitea-generated/client/gitea_api_client.go mock_gitea_utils.go
go generate
build: api
go build

View File

@ -36,6 +36,8 @@ import (
"src.opensuse.org/autogits/common/gitea-generated/models"
)
//go:generate mockgen -source=gitea_utils.go -destination=mock/gitea_utils.go -typed
const PrPattern = "PR: %s/%s#%d"
const (

View File

@ -9,6 +9,7 @@ require (
github.com/go-openapi/swag v0.23.0
github.com/go-openapi/validate v0.24.0
github.com/rabbitmq/amqp091-go v1.10.0
go.uber.org/mock v0.5.0
)
require (

View File

@ -68,6 +68,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=

View File

@ -54,7 +54,9 @@ const RequestType_PRReviewRequest = "pull_request_review_request"
const RequestType_PRReviewComment = "pull_request_review_comment"
const RequestType_Wiki = "wiki"
type RequestProcessor func(*Request) error
type RequestProcessor interface {
ProcessFunc(*Request) error
}
type ListenDefinitions struct {
RabbitURL string // amqps://user:password@host/queue
@ -181,7 +183,7 @@ func ProcessEvent(f RequestProcessor, request *Request) {
}
}()
if err := f(request); err != nil {
if err := f.ProcessFunc(request); err != nil {
log.Println(err)
}

View File

@ -234,7 +234,7 @@ func processRepoBuildStatus(results, ref []common.PackageBuildStatus) BuildStatu
return BuildStatusSummarySuccess
}
func generateObsPrjMeta(git *common.GitHandler, gitea *common.GiteaTransport, pr *models.PullRequest, obsClient *common.ObsClient) (*common.ProjectMeta, error) {
func generateObsPrjMeta(git *common.GitHandler, gitea common.Gitea, pr *models.PullRequest, obsClient *common.ObsClient) (*common.ProjectMeta, error) {
log.Println("repo content fetching ...")
err := fetchPrGit(git, pr)
if err != nil {
@ -312,7 +312,7 @@ func generateObsPrjMeta(git *common.GitHandler, gitea *common.GiteaTransport, pr
return meta, nil
}
func startOrUpdateBuild(git *common.GitHandler, gitea *common.GiteaTransport, pr *models.PullRequest, obsClient *common.ObsClient) error {
func startOrUpdateBuild(git *common.GitHandler, gitea common.Gitea, pr *models.PullRequest, obsClient *common.ObsClient) error {
log.Println("fetching OBS project Meta")
obsPrProject := getObsProjectAssociatedWithPr(obsClient.HomeProject, pr)
meta, err := obsClient.GetProjectMeta(obsPrProject)
@ -353,7 +353,7 @@ func startOrUpdateBuild(git *common.GitHandler, gitea *common.GiteaTransport, pr
return nil
}
func processPullNotification(gitea *common.GiteaTransport, thread *models.NotificationThread) {
func processPullNotification(gitea common.Gitea, thread *models.NotificationThread) {
defer func() {
err := recover()
if err != nil {

View File

@ -4,7 +4,10 @@ go 1.23.2
replace src.opensuse.org/autogits/common => ../bots-common
require src.opensuse.org/autogits/common v0.0.0-00010101000000-000000000000
require (
go.uber.org/mock v0.5.0
src.opensuse.org/autogits/common v0.0.0-00010101000000-000000000000
)
require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect

View File

@ -68,6 +68,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=

View File

@ -31,6 +31,8 @@ import (
"src.opensuse.org/autogits/common"
)
//go:generate mockgen -source=main.go -destination=mock/main.go -typed
const (
AppName = "workflow-pr"
GitAuthor = "AutoGits - pr-review"
@ -56,32 +58,6 @@ func fetchPrGit(h *common.RequestHandler, pr *models.PullRequest) error {
return h.Error
}*/
func processPullRequestClosed(req *common.PullRequestWebhookEvent, git *common.GitHandler, config *common.AutogitConfig) error {
if req.Repository.Name != config.GitProjectName {
return nil
}
log.Println("request was:", req.Pull_Request.State)
return nil
/*
req := h.Data.(*common.PullRequestAction)
if req.Repository.Name != common.DefaultGitPrj {
// we only handle project git PR updates here
return nil
}
if err := fetchPrGit(h, req.Pull_Request); err != nil {
return err
}
headSubmodules := h.GitSubmoduleList(dir, pr.Head.Sha)
baseSubmodules := h.GitSubmoduleList(dir, pr.Base.Sha)
return nil
*/
}
func processPrjGitPullRequestSync(req *common.PullRequestWebhookEvent) error {
// req := h.Data.(*common.PullRequestAction)
@ -101,95 +77,15 @@ func updateOrCreatePRBranch(req *common.PullRequestWebhookEvent, git *common.Git
return nil
}
func processPullRequestSync(req *common.PullRequestWebhookEvent, git *common.GitHandler, config *common.AutogitConfig) error {
if req.Repository.Name == config.GitProjectName {
return processPrjGitPullRequestSync(req)
}
// need to verify that submodule in the PR for prjgit
// is still pointing to the HEAD of the PR
prjPr, err := gitea.GetAssociatedPrjGitPR(req)
if err != nil {
return fmt.Errorf("Cannot get associated PrjGit PR in processPullRequestSync. Err: %w", err)
}
log.Println("associated pr:", prjPr)
common.PanicOnError(git.GitExec("", "clone", "--branch", prjPr.Head.Name, "--depth", "1", prjPr.Head.Repo.SSHURL, common.DefaultGitPrj))
commitId, ok := git.GitSubmoduleCommitId(common.DefaultGitPrj, req.Repository.Name, prjPr.Head.Sha)
if !ok {
return fmt.Errorf("Cannot fetch submodule commit id in prjgit for '%s'", req.Repository.Name)
}
// nothing changed, still in sync
if commitId == req.Pull_Request.Head.Sha {
log.Println("commitID already match - nothing to do")
return nil
}
log.Printf("different ids: '%s' vs. '%s'\n", req.Pull_Request.Head.Sha, commitId)
commitMsg := fmt.Sprintf(`Sync PR
Update to %s`, req.Pull_Request.Head.Sha)
log.Println("will create new commit msg:", commitMsg)
// we need to update prjgit PR with the new head hash
branchName := prGitBranchNameForPR(req)
return updateOrCreatePRBranch(req, git, commitMsg, branchName)
type PullRequestProcessor interface {
Process(req *common.PullRequestWebhookEvent, git *common.GitHandler, config *common.AutogitConfig) error
}
func processPullRequestOpened(req *common.PullRequestWebhookEvent, git *common.GitHandler, config *common.AutogitConfig) error {
// requests against project are not handled here
if req.Repository.Name == config.GitProjectName {
return nil
}
// create PrjGit branch for buidling the pull request
branchName := prGitBranchNameForPR(req)
commitMsg := fmt.Sprintf(`auto-created for %s
This commit was autocreated by %s
referencing
PullRequest: %s/%s#%d`, req.Repository.Owner.Username,
req.Repository.Name, GitAuthor, req.Repository.Name, req.Pull_Request.Number)
prjGit, err := gitea.CreateRepositoryIfNotExist(git, *req.Repository.Owner, config.GitProjectName)
if err != nil {
return err
}
common.PanicOnError(git.GitExec("", "clone", "--depth", "1", prjGit.SSHURL, common.DefaultGitPrj))
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "checkout", "-B", branchName, prjGit.DefaultBranch))
common.PanicOnError(updateOrCreatePRBranch(req, git, commitMsg, branchName))
PR, err := gitea.CreatePullRequestIfNotExist(prjGit, branchName, prjGit.DefaultBranch,
fmt.Sprintf("Forwarded PR: %s", req.Repository.Name),
fmt.Sprintf(`This is a forwarded pull request by %s
referencing the following pull request:
`+common.PrPattern,
GitAuthor, req.Repository.Owner.Username, req.Repository.Name, req.Pull_Request.Number),
)
if err != nil {
return err
}
// request build review
for _, reviewer := range config.Reviewers {
_, err := gitea.RequestReviews(PR, reviewer)
if err != nil {
return fmt.Errorf("Failed to create reviewer '%s' for request: %s/%s/%d Err: %w", reviewer, PR.Base.Repo.Owner.UserName, PR.Base.Repo.Name, PR.Index, err)
}
}
return err
type RequestProcessor struct {
Opened, Synced, Closed PullRequestProcessor
}
func processPullRequest(request *common.Request) error {
func (w *RequestProcessor) ProcessFunc(request *common.Request) error {
req, ok := request.Data.(*common.PullRequestWebhookEvent)
if !ok {
return fmt.Errorf("*** Invalid data format for PR processing.")
@ -221,14 +117,14 @@ func processPullRequest(request *common.Request) error {
switch req.Action {
case "opened", "reopened":
return processPullRequestOpened(req, git, config)
return w.Opened.Process(req, git, config)
case "synchronized":
return processPullRequestSync(req, git, config)
return w.Synced.Process(req, git, config)
case "edited":
// not need to be handled??
return nil
case "closed":
return processPullRequestClosed(req, git, config)
return w.Closed.Process(req, git, config)
}
return fmt.Errorf("Unhandled pull request action: %s", req.Action)
@ -238,7 +134,7 @@ var DebugMode bool
var checkOnStart bool
var checkInterval time.Duration
func verifyProjectState(git *common.GitHandler, orgName string, config *common.AutogitConfig, configs []*common.AutogitConfig) error {
func verifyProjectState(processor *RequestProcessor, git *common.GitHandler, orgName string, config *common.AutogitConfig, configs []*common.AutogitConfig) error {
org := common.Organization{
Username: orgName,
}
@ -288,9 +184,9 @@ nextSubmodule:
switch pr.State {
case "open":
processPullRequestOpened(&event, git, config)
processor.Opened.Process(&event, git, config)
case "closed":
processPullRequestClosed(&event, git, config)
processor.Closed.Process(&event, git, config)
default:
return fmt.Errorf("Unhandled pull request state: '%s'. %s/%s/%d", pr.State, config.Organization, submoduleName, pr.Index)
}
@ -336,7 +232,7 @@ nextSubmodule:
return nil
}
func checkRepos() {
func checkRepos(processor *RequestProcessor) {
for org, configs := range configuredRepos {
for _, config := range configs {
if checkInterval > 0 {
@ -354,7 +250,7 @@ func checkRepos() {
if !DebugMode {
defer git.Close()
}
if err := verifyProjectState(git, org, config, configs); err != nil {
if err := verifyProjectState(processor, git, org, config, configs); err != nil {
log.Printf(" *** verification failed, org: `%s`, err: %#v\n", org, err)
}
log.Printf(" ++ verification complete, org: `%s` config: `%s`\n", org, config.GitProjectName)
@ -362,18 +258,18 @@ func checkRepos() {
}
}
func consistencyCheckProcess() {
func consistencyCheckProcess(processor *RequestProcessor) {
if checkOnStart {
savedCheckInterval := checkInterval
checkInterval = 0
log.Println("== Startup consistency check begin...")
checkRepos()
checkRepos(processor)
log.Println("== Startup consistency check done...")
checkInterval = savedCheckInterval
}
for {
checkRepos()
checkRepos(processor)
}
}
@ -423,7 +319,14 @@ func main() {
}
gitea = common.AllocateGiteaTransport(*giteaHost)
go consistencyCheckProcess()
req := new(RequestProcessor)
req.Synced = &PullRequestSynced{}
req.Opened = &PullRequestOpened{}
req.Closed = &PullRequestClosed{}
go consistencyCheckProcess(req)
var defs common.ListenDefinitions
@ -431,8 +334,8 @@ func main() {
defs.RabbitURL = *rabbitUrl
defs.Handlers = make(map[string]common.RequestProcessor)
defs.Handlers[common.RequestType_PR] = processPullRequest
defs.Handlers[common.RequestType_PRSync] = processPullRequest
defs.Handlers[common.RequestType_PR] = req
defs.Handlers[common.RequestType_PRSync] = req
log.Fatal(common.ProcessRabbitMQEvents(defs, orgs))
}

View File

@ -9,7 +9,12 @@ import (
"strings"
"testing"
// "go.uber.org/mock/gomock"
"go.uber.org/mock/gomock"
"src.opensuse.org/autogits/common"
mock_common "src.opensuse.org/autogits/common/mock"
mock_main "src.opensuse.org/workflow-pr/mock"
// "src.opensuse.org/autogits/common/mock"
)
func TestProjectBranchName(t *testing.T) {
@ -193,3 +198,52 @@ func TestCreatePrBranch(t *testing.T) {
}
}
func TestPRProcessor(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
var logBuf bytes.Buffer
oldOut := log.Writer()
log.SetOutput(&logBuf)
defer log.SetOutput(oldOut)
req := &RequestProcessor {
Opened: mock_main.NewMockPullRequestProcessor(ctl),
Closed: mock_main.NewMockPullRequestProcessor(ctl),
Synced: mock_main.NewMockPullRequestProcessor(ctl),
}
gitea = mock_common.NewMockGitea(ctl)
pr_opened := mock_main.NewMockPullRequestProcessor(ctl)
pr_opened.EXPECT().Process(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
event := &common.PullRequestWebhookEvent {
Action: "opened",
Number: 1,
Pull_Request: &common.PullRequest {
Id: 1,
Base: common.Head {
Ref: "HEAD",
Sha: "abc",
Repo: nil,
},
Head: common.Head {
Ref: "HEAD",
Sha: "abc",
Repo: nil,
},
},
}
err := req.ProcessFunc(&common.Request{
Data: event,
})
if err != nil {
t.Error("Error processing open PR", err)
t.Error(logBuf.String())
}
}

36
workflow-pr/pr_closed.go Normal file
View File

@ -0,0 +1,36 @@
package main
import (
"log"
"src.opensuse.org/autogits/common"
)
type PullRequestClosed struct {}
func (*PullRequestClosed) Process(req *common.PullRequestWebhookEvent, git *common.GitHandler, config *common.AutogitConfig) error {
if req.Repository.Name != config.GitProjectName {
return nil
}
log.Println("request was:", req.Pull_Request.State)
return nil
/*
req := h.Data.(*common.PullRequestAction)
if req.Repository.Name != common.DefaultGitPrj {
// we only handle project git PR updates here
return nil
}
if err := fetchPrGit(h, req.Pull_Request); err != nil {
return err
}
headSubmodules := h.GitSubmoduleList(dir, pr.Head.Sha)
baseSubmodules := h.GitSubmoduleList(dir, pr.Base.Sha)
return nil
*/
}

58
workflow-pr/pr_opened.go Normal file
View File

@ -0,0 +1,58 @@
package main
import (
"fmt"
"src.opensuse.org/autogits/common"
)
type PullRequestOpened struct {
}
func (*PullRequestOpened) Process(req *common.PullRequestWebhookEvent, git *common.GitHandler, config *common.AutogitConfig) error {
// requests against project are not handled here
if req.Repository.Name == config.GitProjectName {
return nil
}
// create PrjGit branch for buidling the pull request
branchName := prGitBranchNameForPR(req)
commitMsg := fmt.Sprintf(`auto-created for %s
This commit was autocreated by %s
referencing
PullRequest: %s/%s#%d`, req.Repository.Owner.Username,
req.Repository.Name, GitAuthor, req.Repository.Name, req.Pull_Request.Number)
prjGit, err := gitea.CreateRepositoryIfNotExist(git, *req.Repository.Owner, config.GitProjectName)
if err != nil {
return err
}
common.PanicOnError(git.GitExec("", "clone", "--depth", "1", prjGit.SSHURL, common.DefaultGitPrj))
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "checkout", "-B", branchName, prjGit.DefaultBranch))
common.PanicOnError(updateOrCreatePRBranch(req, git, commitMsg, branchName))
PR, err := gitea.CreatePullRequestIfNotExist(prjGit, branchName, prjGit.DefaultBranch,
fmt.Sprintf("Forwarded PR: %s", req.Repository.Name),
fmt.Sprintf(`This is a forwarded pull request by %s
referencing the following pull request:
`+common.PrPattern,
GitAuthor, req.Repository.Owner.Username, req.Repository.Name, req.Pull_Request.Number),
)
if err != nil {
return err
}
// request build review
for _, reviewer := range config.Reviewers {
_, err := gitea.RequestReviews(PR, reviewer)
if err != nil {
return fmt.Errorf("Failed to create reviewer '%s' for request: %s/%s/%d Err: %w", reviewer, PR.Base.Repo.Owner.UserName, PR.Base.Repo.Name, PR.Index, err)
}
}
return err
}

59
workflow-pr/pr_sync.go Normal file
View File

@ -0,0 +1,59 @@
package main
import (
"fmt"
"log"
"src.opensuse.org/autogits/common"
)
/*
func processPrjGitPullRequestSync(req *common.PullRequestWebhookEvent) error {
// req := h.Data.(*common.PullRequestAction)
return nil
}
*/
type PullRequestSynced struct {
}
func (*PullRequestSynced) Process(req *common.PullRequestWebhookEvent, git *common.GitHandler, config *common.AutogitConfig) error {
if req.Repository.Name == config.GitProjectName {
return processPrjGitPullRequestSync(req)
}
// need to verify that submodule in the PR for prjgit
// is still pointing to the HEAD of the PR
prjPr, err := gitea.GetAssociatedPrjGitPR(req)
if err != nil {
return fmt.Errorf("Cannot get associated PrjGit PR in processPullRequestSync. Err: %w", err)
}
log.Println("associated pr:", prjPr)
common.PanicOnError(git.GitExec("", "clone", "--branch", prjPr.Head.Name, "--depth", "1", prjPr.Head.Repo.SSHURL, common.DefaultGitPrj))
commitId, ok := git.GitSubmoduleCommitId(common.DefaultGitPrj, req.Repository.Name, prjPr.Head.Sha)
if !ok {
return fmt.Errorf("Cannot fetch submodule commit id in prjgit for '%s'", req.Repository.Name)
}
// nothing changed, still in sync
if commitId == req.Pull_Request.Head.Sha {
log.Println("commitID already match - nothing to do")
return nil
}
log.Printf("different ids: '%s' vs. '%s'\n", req.Pull_Request.Head.Sha, commitId)
commitMsg := fmt.Sprintf(`Sync PR
Update to %s`, req.Pull_Request.Head.Sha)
log.Println("will create new commit msg:", commitMsg)
// we need to update prjgit PR with the new head hash
branchName := prGitBranchNameForPR(req)
return updateOrCreatePRBranch(req, git, commitMsg, branchName)
}