Use gitlab generic webhooks instead of drone-ci-service (#620)

Benefits:
- the webhook delivery history of the drone-ci-service is broken (no way to check if a webhook was successfully delivered by Gitlab)
- drone-ci-service has limited events support (for example no comment or branch deleted event)
- independent from drone integration in general
This commit is contained in:
Anbraten 2021-12-19 01:12:09 +01:00 committed by GitHub
parent ce462ce4ac
commit ed0a9fd756
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 133 additions and 33 deletions

View file

@ -388,6 +388,16 @@ func (g *Gitlab) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
}, nil
}
func (g *Gitlab) getTokenAndWebURL(link string) (token string, webURL string, err error) {
uri, err := url.Parse(link)
if err != nil {
return "", "", err
}
token = uri.Query().Get("access_token")
webURL = fmt.Sprintf("%s://%s/api/hook", uri.Scheme, uri.Host)
return token, webURL, nil
}
// Activate activates a repository by adding a Post-commit hook and
// a Public Deploy key, if applicable.
func (g *Gitlab) Activate(ctx context.Context, user *model.User, repo *model.Repo, link string) error {
@ -395,23 +405,31 @@ func (g *Gitlab) Activate(ctx context.Context, user *model.User, repo *model.Rep
if err != nil {
return err
}
uri, err := url.Parse(link)
if err != nil {
return err
}
token := uri.Query().Get("access_token")
webURL := fmt.Sprintf("%s://%s", uri.Scheme, uri.Host)
_repo, err := g.getProject(ctx, client, repo.Owner, repo.Name)
if err != nil {
return err
}
// TODO: "WoodpeckerCIService"
_, err = client.Services.SetDroneCIService(_repo.ID, &gitlab.SetDroneCIServiceOptions{
Token: &token,
DroneURL: &webURL,
token, webURL, err := g.getTokenAndWebURL(link)
if err != nil {
return err
}
if len(token) == 0 {
return fmt.Errorf("no token found")
}
_, _, err = client.Projects.AddProjectHook(_repo.ID, &gitlab.AddProjectHookOptions{
URL: gitlab.String(webURL),
Token: gitlab.String(token),
PushEvents: gitlab.Bool(true),
TagPushEvents: gitlab.Bool(true),
MergeRequestsEvents: gitlab.Bool(true),
DeploymentEvents: gitlab.Bool(true),
EnableSSLVerification: gitlab.Bool(!g.SkipVerify),
}, gitlab.WithContext(ctx))
return err
}
@ -427,8 +445,44 @@ func (g *Gitlab) Deactivate(ctx context.Context, user *model.User, repo *model.R
if err != nil {
return err
}
// TODO: "WoodpeckerCIService"
_, err = client.Services.DeleteDroneCIService(_repo.ID, gitlab.WithContext(ctx))
_, webURL, err := g.getTokenAndWebURL(link)
if err != nil {
return err
}
hookID := -1
listProjectHooksOptions := &gitlab.ListProjectHooksOptions{
PerPage: 10,
Page: 1,
}
for {
hooks, resp, err := client.Projects.ListProjectHooks(_repo.ID, listProjectHooksOptions, gitlab.WithContext(ctx))
if err != nil {
return err
}
for _, hook := range hooks {
if hook.URL == webURL {
hookID = hook.ID
break
}
}
// Exit the loop when we've seen all pages
if resp.CurrentPage >= resp.TotalPages {
break
}
// Update the page number to get the next page
listProjectHooksOptions.Page = resp.NextPage
}
if hookID == -1 {
return fmt.Errorf("could not find hook to delete")
}
_, err = client.Projects.DeleteProjectHook(_repo.ID, hookID, gitlab.WithContext(ctx))
return err
}

View file

@ -224,3 +224,53 @@ var project6Paylod = []byte(`
}
}
`)
var project4PayloadHook = []byte(`
{
"id": 10717088,
"url": "http://example.com/api/hook",
"created_at": "2021-12-18T23:29:33.852Z",
"push_events": true,
"tag_push_events": true,
"merge_requests_events": true,
"repository_update_events": false,
"enable_ssl_verification": true,
"project_id": 4,
"issues_events": false,
"confidential_issues_events": false,
"note_events": false,
"confidential_note_events": null,
"pipeline_events": false,
"wiki_page_events": false,
"deployment_events": true,
"job_events": false,
"releases_events": false,
"push_events_branch_filter": null
}
`)
var project4PayloadHooks = []byte(`
[
{
"id": 10717088,
"url": "http://example.com/api/hook",
"created_at": "2021-12-18T23:29:33.852Z",
"push_events": true,
"tag_push_events": true,
"merge_requests_events": true,
"repository_update_events": false,
"enable_ssl_verification": true,
"project_id": 4,
"issues_events": false,
"confidential_issues_events": false,
"note_events": false,
"confidential_note_events": null,
"pipeline_events": false,
"wiki_page_events": false,
"deployment_events": true,
"job_events": false,
"releases_events": false,
"push_events_branch_filter": null
}
]
`)

View file

@ -15,13 +15,9 @@
package testdata
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
// NewServer setup a mock server for testing purposes.
@ -52,23 +48,17 @@ func NewServer(t *testing.T) *httptest.Server {
case "/api/v4/projects/brightbox/puppet":
w.Write(project6Paylod)
return
case "/api/v4/projects/4/services/drone-ci":
case "/api/v4/projects/4/hooks":
switch r.Method {
case "PUT":
body, _ := io.ReadAll(r.Body)
opts := make(map[string]interface{})
assert.NoError(t, json.Unmarshal(body, &opts))
token, ok := opts["token"].(string)
assert.True(t, ok)
if token == "" {
w.WriteHeader(404)
} else {
case "GET":
w.Write(project4PayloadHooks)
case "POST":
w.Write(project4PayloadHook)
w.WriteHeader(201)
}
case "DELETE":
return
case "/api/v4/projects/4/hooks/10717088":
w.WriteHeader(201)
}
return
case "/oauth/token":
w.Write(accessTokenPayload)

View file

@ -43,7 +43,9 @@ func (cf *configFetcher) Fetch(ctx context.Context) (files []*remote.FileMeta, e
// try to fetch 3 times, timeout is one second longer each time
for i := 0; i < 3; i++ {
files, err = cf.fetch(ctx, time.Second*time.Duration(configFetchTimeout), strings.TrimSpace(cf.repo.Config))
log.Trace().Msgf("%d try failed: %v", i, err)
if err != nil {
log.Trace().Err(err).Msgf("%d. try failed", i+1)
}
if errors.Is(err, context.DeadlineExceeded) {
continue
}

View file

@ -52,10 +52,9 @@ func parse(raw string, fn SecretFunc) (*Token, error) {
}
func ParseRequest(r *http.Request, fn SecretFunc) (*Token, error) {
token := r.Header.Get("Authorization")
// first we attempt to get the token from the
// authorization header.
token := r.Header.Get("Authorization")
if len(token) != 0 {
log.Trace().Msgf("token.ParseRequest: found token in header: %s", token)
bearer := token
@ -65,6 +64,11 @@ func ParseRequest(r *http.Request, fn SecretFunc) (*Token, error) {
return parse(bearer, fn)
}
token = r.Header.Get("X-Gitlab-Token")
if len(token) != 0 {
return parse(token, fn)
}
// then we attempt to get the token from the
// access_token url query parameter
token = r.FormValue("access_token")