2022-10-18 01:24:12 +00:00
|
|
|
// Copyright 2022 Woodpecker Authors
|
2018-02-19 22:24:10 +00:00
|
|
|
// Copyright 2018 Drone.IO Inc.
|
2018-03-21 13:02:17 +00:00
|
|
|
//
|
2018-02-19 22:24:10 +00:00
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
2018-03-21 13:02:17 +00:00
|
|
|
//
|
2018-02-19 22:24:10 +00:00
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
2018-03-21 13:02:17 +00:00
|
|
|
//
|
2018-02-19 22:24:10 +00:00
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2017-07-22 09:12:09 +00:00
|
|
|
package coding
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2022-08-29 23:14:07 +00:00
|
|
|
"io"
|
2017-07-22 09:12:09 +00:00
|
|
|
"net/http"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
2021-09-27 17:51:55 +00:00
|
|
|
"github.com/woodpecker-ci/woodpecker/server/model"
|
2017-07-22 09:12:09 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
hookEvent = "X-Coding-Event"
|
|
|
|
hookPush = "push"
|
|
|
|
hookPR = "pull_request"
|
|
|
|
hookMR = "merge_request"
|
|
|
|
)
|
|
|
|
|
|
|
|
type User struct {
|
|
|
|
GlobalKey string `json:"global_key"`
|
|
|
|
Avatar string `json:"avatar"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Repository struct {
|
|
|
|
Name string `json:"name"`
|
2021-12-01 13:22:06 +00:00
|
|
|
HTTPSURL string `json:"https_url"`
|
|
|
|
SSHURL string `json:"ssh_url"`
|
2017-07-22 09:12:09 +00:00
|
|
|
WebURL string `json:"web_url"`
|
|
|
|
Owner *User `json:"owner"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Committer struct {
|
|
|
|
Email string `json:"email"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Commit struct {
|
|
|
|
SHA string `json:"sha"`
|
|
|
|
ShortMessage string `json:"short_message"`
|
|
|
|
Committer *Committer `json:"committer"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type PullRequest MergeRequest
|
|
|
|
|
|
|
|
type MergeRequest struct {
|
|
|
|
SourceBranch string `json:"source_branch"`
|
|
|
|
TargetBranch string `json:"target_branch"`
|
|
|
|
CommitSHA string `json:"merge_commit_sha"`
|
|
|
|
Status string `json:"status"`
|
|
|
|
Action string `json:"action"`
|
|
|
|
Number float64 `json:"number"`
|
|
|
|
Title string `json:"title"`
|
|
|
|
Body string `json:"body"`
|
|
|
|
WebURL string `json:"web_url"`
|
|
|
|
User *User `json:"user"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type PushHook struct {
|
|
|
|
Event string `json:"event"`
|
|
|
|
Repository *Repository `json:"repository"`
|
|
|
|
Ref string `json:"ref"`
|
|
|
|
Before string `json:"before"`
|
|
|
|
After string `json:"after"`
|
|
|
|
Commits []*Commit `json:"commits"`
|
|
|
|
User *User `json:"user"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type PullRequestHook struct {
|
|
|
|
Event string `json:"event"`
|
|
|
|
Repository *Repository `json:"repository"`
|
|
|
|
PullRequest *PullRequest `json:"pull_request"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type MergeRequestHook struct {
|
|
|
|
Event string `json:"event"`
|
|
|
|
Repository *Repository `json:"repository"`
|
|
|
|
MergeRequest *MergeRequest `json:"merge_request"`
|
|
|
|
}
|
|
|
|
|
2022-10-18 01:24:12 +00:00
|
|
|
func parseHook(r *http.Request) (*model.Repo, *model.Pipeline, error) {
|
2022-08-29 23:14:07 +00:00
|
|
|
raw, err := io.ReadAll(r.Body)
|
2017-07-22 09:12:09 +00:00
|
|
|
defer r.Body.Close()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch r.Header.Get(hookEvent) {
|
|
|
|
case hookPush:
|
|
|
|
return parsePushHook(raw)
|
|
|
|
case hookPR:
|
|
|
|
return parsePullRequestHook(raw)
|
|
|
|
case hookMR:
|
2022-11-09 07:12:17 +00:00
|
|
|
return parseMergeRequestHook(raw)
|
2017-07-22 09:12:09 +00:00
|
|
|
}
|
|
|
|
return nil, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func findLastCommit(commits []*Commit, sha string) *Commit {
|
|
|
|
var lastCommit *Commit
|
|
|
|
for _, commit := range commits {
|
|
|
|
if commit.SHA == sha {
|
|
|
|
lastCommit = commit
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if lastCommit == nil {
|
|
|
|
lastCommit = &Commit{}
|
|
|
|
}
|
|
|
|
if lastCommit.Committer == nil {
|
|
|
|
lastCommit.Committer = &Committer{}
|
|
|
|
}
|
|
|
|
return lastCommit
|
|
|
|
}
|
|
|
|
|
|
|
|
func convertRepository(repo *Repository) (*model.Repo, error) {
|
|
|
|
// tricky stuff for a team project without a team owner instead of a user owner
|
|
|
|
re := regexp.MustCompile(`git@.+:([^/]+)/.+\.git`)
|
2021-12-01 13:22:06 +00:00
|
|
|
matches := re.FindStringSubmatch(repo.SSHURL)
|
2017-07-22 09:12:09 +00:00
|
|
|
if len(matches) != 2 {
|
2021-12-01 13:22:06 +00:00
|
|
|
return nil, fmt.Errorf("Unable to resolve owner from ssh url %q", repo.SSHURL)
|
2017-07-22 09:12:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &model.Repo{
|
2022-11-04 23:35:06 +00:00
|
|
|
// TODO ForgeID: repo.ID,
|
2017-07-22 09:12:09 +00:00
|
|
|
Owner: matches[1],
|
|
|
|
Name: repo.Name,
|
|
|
|
FullName: projectFullName(repo.Owner.GlobalKey, repo.Name),
|
|
|
|
Link: repo.WebURL,
|
2022-09-07 17:16:40 +00:00
|
|
|
Clone: repo.HTTPSURL,
|
2021-11-22 11:55:13 +00:00
|
|
|
SCMKind: model.RepoGit,
|
2017-07-22 09:12:09 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2022-10-18 01:24:12 +00:00
|
|
|
func parsePushHook(raw []byte) (*model.Repo, *model.Pipeline, error) {
|
2017-07-22 09:12:09 +00:00
|
|
|
hook := &PushHook{}
|
|
|
|
err := json.Unmarshal(raw, hook)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2022-10-18 01:24:12 +00:00
|
|
|
// no pipeline triggered when removing ref
|
2017-07-22 09:12:09 +00:00
|
|
|
if hook.After == "0000000000000000000000000000000000000000" {
|
|
|
|
return nil, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
repo, err := convertRepository(hook.Repository)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
lastCommit := findLastCommit(hook.Commits, hook.After)
|
2022-10-18 01:24:12 +00:00
|
|
|
pipeline := &model.Pipeline{
|
2022-11-04 23:35:06 +00:00
|
|
|
Event: model.EventPush,
|
|
|
|
Commit: hook.After,
|
|
|
|
Ref: hook.Ref,
|
|
|
|
Link: fmt.Sprintf("%s/git/commit/%s", hook.Repository.WebURL, hook.After),
|
|
|
|
Branch: strings.Replace(hook.Ref, "refs/heads/", "", -1),
|
|
|
|
Message: lastCommit.ShortMessage,
|
|
|
|
Email: lastCommit.Committer.Email,
|
|
|
|
Avatar: hook.User.Avatar,
|
|
|
|
Author: hook.User.GlobalKey,
|
|
|
|
CloneURL: hook.Repository.HTTPSURL,
|
2017-07-22 09:12:09 +00:00
|
|
|
}
|
2022-10-18 01:24:12 +00:00
|
|
|
return repo, pipeline, nil
|
2017-07-22 09:12:09 +00:00
|
|
|
}
|
|
|
|
|
2022-10-18 01:24:12 +00:00
|
|
|
func parsePullRequestHook(raw []byte) (*model.Repo, *model.Pipeline, error) {
|
2017-07-22 09:12:09 +00:00
|
|
|
hook := &PullRequestHook{}
|
|
|
|
err := json.Unmarshal(raw, hook)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
if hook.PullRequest.Status != "CANMERGE" ||
|
|
|
|
(hook.PullRequest.Action != "create" && hook.PullRequest.Action != "synchronize") {
|
|
|
|
return nil, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
repo, err := convertRepository(hook.Repository)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2022-10-18 01:24:12 +00:00
|
|
|
pipeline := &model.Pipeline{
|
2022-11-04 23:35:06 +00:00
|
|
|
Event: model.EventPull,
|
|
|
|
Commit: hook.PullRequest.CommitSHA,
|
|
|
|
Link: hook.PullRequest.WebURL,
|
|
|
|
Ref: fmt.Sprintf("refs/pull/%d/MERGE", int(hook.PullRequest.Number)),
|
|
|
|
Branch: hook.PullRequest.TargetBranch,
|
|
|
|
Message: hook.PullRequest.Body,
|
|
|
|
Author: hook.PullRequest.User.GlobalKey,
|
|
|
|
Avatar: hook.PullRequest.User.Avatar,
|
|
|
|
Title: hook.PullRequest.Title,
|
|
|
|
CloneURL: hook.Repository.HTTPSURL,
|
|
|
|
Refspec: fmt.Sprintf("%s:%s", hook.PullRequest.SourceBranch, hook.PullRequest.TargetBranch),
|
2017-07-22 09:12:09 +00:00
|
|
|
}
|
|
|
|
|
2022-10-18 01:24:12 +00:00
|
|
|
return repo, pipeline, nil
|
2017-07-22 09:12:09 +00:00
|
|
|
}
|
|
|
|
|
2022-11-09 07:12:17 +00:00
|
|
|
func parseMergeRequestHook(raw []byte) (*model.Repo, *model.Pipeline, error) {
|
2017-07-22 09:12:09 +00:00
|
|
|
hook := &MergeRequestHook{}
|
|
|
|
err := json.Unmarshal(raw, hook)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
if hook.MergeRequest.Status != "CANMERGE" ||
|
|
|
|
(hook.MergeRequest.Action != "create" && hook.MergeRequest.Action != "synchronize") {
|
|
|
|
return nil, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
repo, err := convertRepository(hook.Repository)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2022-10-18 01:24:12 +00:00
|
|
|
pipeline := &model.Pipeline{
|
2022-11-04 23:35:06 +00:00
|
|
|
Event: model.EventPull,
|
|
|
|
Commit: hook.MergeRequest.CommitSHA,
|
|
|
|
Link: hook.MergeRequest.WebURL,
|
|
|
|
Ref: fmt.Sprintf("refs/merge/%d/MERGE", int(hook.MergeRequest.Number)),
|
|
|
|
Branch: hook.MergeRequest.TargetBranch,
|
|
|
|
Message: hook.MergeRequest.Body,
|
|
|
|
Author: hook.MergeRequest.User.GlobalKey,
|
|
|
|
Avatar: hook.MergeRequest.User.Avatar,
|
|
|
|
Title: hook.MergeRequest.Title,
|
|
|
|
CloneURL: hook.Repository.HTTPSURL,
|
|
|
|
Refspec: fmt.Sprintf("%s:%s", hook.MergeRequest.SourceBranch, hook.MergeRequest.TargetBranch),
|
2017-07-22 09:12:09 +00:00
|
|
|
}
|
2022-10-18 01:24:12 +00:00
|
|
|
return repo, pipeline, nil
|
2017-07-22 09:12:09 +00:00
|
|
|
}
|