mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-30 05:41:12 +00:00
849e05bb8b
Ref: #745 Co-authored-by: Anbraten <anton@ju60.de> Co-authored-by: 6543 <6543@obermui.de>
477 lines
12 KiB
Go
477 lines
12 KiB
Go
// Copyright 2022 Woodpecker Authors
|
|
// Copyright 2018 Drone.IO Inc.
|
|
//
|
|
// 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
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// 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.
|
|
|
|
package internal
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/mrjones/oauth"
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/woodpecker-ci/woodpecker/server/model"
|
|
)
|
|
|
|
const (
|
|
currentUserID = "%s/plugins/servlet/applinks/whoami"
|
|
pathUser = "%s/rest/api/1.0/users/%s"
|
|
pathRepo = "%s/rest/api/1.0/projects/%s/repos/%s"
|
|
pathRepos = "%s/rest/api/1.0/repos?start=%s&limit=%s"
|
|
pathHook = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s"
|
|
pathSource = "%s/projects/%s/repos/%s/browse/%s?at=%s&raw"
|
|
hookName = "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook"
|
|
pathHookDetails = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s"
|
|
pathHookEnabled = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s/enabled"
|
|
pathHookSettings = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s/settings"
|
|
pathStatus = "%s/rest/build-status/1.0/commits/%s"
|
|
pathBranches = "%s/2.0/repositories/%s/%s/refs/branches"
|
|
)
|
|
|
|
type Client struct {
|
|
client *http.Client
|
|
base string
|
|
accessToken string
|
|
ctx context.Context
|
|
}
|
|
|
|
func NewClientWithToken(ctx context.Context, url string, consumer *oauth.Consumer, AccessToken string) *Client {
|
|
var token oauth.AccessToken
|
|
token.Token = AccessToken
|
|
client, err := consumer.MakeHttpClient(&token)
|
|
if err != nil {
|
|
log.Err(err).Msg("")
|
|
}
|
|
|
|
return &Client{
|
|
client: client,
|
|
base: url,
|
|
accessToken: AccessToken,
|
|
ctx: ctx,
|
|
}
|
|
}
|
|
|
|
func (c *Client) FindCurrentUser() (*User, error) {
|
|
CurrentUserIDResponse, err := c.doGet(fmt.Sprintf(currentUserID, c.base))
|
|
if CurrentUserIDResponse != nil {
|
|
defer CurrentUserIDResponse.Body.Close()
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bits, err := io.ReadAll(CurrentUserIDResponse.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
login := string(bits)
|
|
|
|
CurrentUserResponse, err := c.doGet(fmt.Sprintf(pathUser, c.base, login))
|
|
if CurrentUserResponse != nil {
|
|
defer CurrentUserResponse.Body.Close()
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
contents, err := io.ReadAll(CurrentUserResponse.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var user User
|
|
err = json.Unmarshal(contents, &user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
func (c *Client) FindRepo(owner, name string) (*Repo, error) {
|
|
urlString := fmt.Sprintf(pathRepo, c.base, owner, name)
|
|
response, err := c.doGet(urlString)
|
|
if response != nil {
|
|
defer response.Body.Close()
|
|
}
|
|
if err != nil {
|
|
log.Err(err).Msg("")
|
|
}
|
|
contents, err := io.ReadAll(response.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
repo := Repo{}
|
|
err = json.Unmarshal(contents, &repo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &repo, nil
|
|
}
|
|
|
|
func (c *Client) FindRepos() ([]*Repo, error) {
|
|
return c.paginatedRepos(0)
|
|
}
|
|
|
|
func (c *Client) FindRepoPerms(owner, repo string) (*model.Perm, error) {
|
|
perms := new(model.Perm)
|
|
// If you don't have access return none right away
|
|
_, err := c.FindRepo(owner, repo)
|
|
if err != nil {
|
|
return perms, err
|
|
}
|
|
// Must have admin to be able to list hooks. If have access the enable perms
|
|
resp, err := c.doGet(fmt.Sprintf(pathHook, c.base, owner, repo, hookName))
|
|
if resp != nil {
|
|
defer resp.Body.Close()
|
|
}
|
|
if err == nil {
|
|
perms.Push = true
|
|
perms.Admin = true
|
|
}
|
|
perms.Pull = true
|
|
return perms, nil
|
|
}
|
|
|
|
func (c *Client) FindFileForRepo(owner, repo, fileName, ref string) ([]byte, error) {
|
|
response, err := c.doGet(fmt.Sprintf(pathSource, c.base, owner, repo, fileName, ref))
|
|
if response != nil {
|
|
defer response.Body.Close()
|
|
}
|
|
if err != nil {
|
|
log.Err(err).Msg("")
|
|
}
|
|
if response.StatusCode == 404 {
|
|
return nil, nil
|
|
}
|
|
responseBytes, err := io.ReadAll(response.Body)
|
|
if err != nil {
|
|
log.Err(err).Msg("")
|
|
}
|
|
return responseBytes, nil
|
|
}
|
|
|
|
func (c *Client) CreateHook(owner, name, callBackLink string) error {
|
|
hookDetails, err := c.GetHookDetails(owner, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var hooks []string
|
|
if hookDetails.Enabled {
|
|
hookSettings, err := c.GetHooks(owner, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hooks = hookSettingsToArray(hookSettings)
|
|
}
|
|
if !stringInSlice(callBackLink, hooks) {
|
|
hooks = append(hooks, callBackLink)
|
|
}
|
|
|
|
putHookSettings := arrayToHookSettings(hooks)
|
|
hookBytes, err := json.Marshal(putHookSettings)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.doPut(fmt.Sprintf(pathHookEnabled, c.base, owner, name, hookName), hookBytes)
|
|
}
|
|
|
|
func (c *Client) CreateStatus(revision string, status *PipelineStatus) error {
|
|
uri := fmt.Sprintf(pathStatus, c.base, revision)
|
|
return c.doPost(uri, status)
|
|
}
|
|
|
|
func (c *Client) DeleteHook(owner, name, link string) error {
|
|
hookSettings, err := c.GetHooks(owner, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
putHooks := filter(hookSettingsToArray(hookSettings), func(item string) bool {
|
|
return !strings.Contains(item, link)
|
|
})
|
|
putHookSettings := arrayToHookSettings(putHooks)
|
|
hookBytes, err := json.Marshal(putHookSettings)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.doPut(fmt.Sprintf(pathHookEnabled, c.base, owner, name, hookName), hookBytes)
|
|
}
|
|
|
|
func (c *Client) GetHookDetails(owner, name string) (*HookPluginDetails, error) {
|
|
urlString := fmt.Sprintf(pathHookDetails, c.base, owner, name, hookName)
|
|
response, err := c.doGet(urlString)
|
|
if response != nil {
|
|
defer response.Body.Close()
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hookDetails := HookPluginDetails{}
|
|
err = json.NewDecoder(response.Body).Decode(&hookDetails)
|
|
return &hookDetails, err
|
|
}
|
|
|
|
func (c *Client) GetHooks(owner, name string) (*HookSettings, error) {
|
|
urlString := fmt.Sprintf(pathHookSettings, c.base, owner, name, hookName)
|
|
response, err := c.doGet(urlString)
|
|
if response != nil {
|
|
defer response.Body.Close()
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hookSettings := HookSettings{}
|
|
err = json.NewDecoder(response.Body).Decode(&hookSettings)
|
|
return &hookSettings, err
|
|
}
|
|
|
|
// TODO: make these as as general do with the action
|
|
|
|
// Helper function to help create get
|
|
func (c *Client) doGet(url string) (*http.Response, error) {
|
|
request, err := http.NewRequestWithContext(c.ctx, "GET", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
request.Header.Add("Content-Type", "application/json")
|
|
return c.client.Do(request)
|
|
}
|
|
|
|
// Helper function to help create the hook
|
|
func (c *Client) doPut(url string, body []byte) error {
|
|
request, err := http.NewRequestWithContext(c.ctx, "PUT", url, bytes.NewBuffer(body))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
request.Header.Add("Content-Type", "application/json")
|
|
response, err := c.client.Do(request)
|
|
if response != nil {
|
|
defer response.Body.Close()
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Helper function to help create the hook
|
|
func (c *Client) doPost(url string, status *PipelineStatus) error {
|
|
// write it to the body of the request.
|
|
var buf io.ReadWriter
|
|
if status != nil {
|
|
buf = new(bytes.Buffer)
|
|
err := json.NewEncoder(buf).Encode(status)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
request, err := http.NewRequestWithContext(c.ctx, "POST", url, buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
request.Header.Add("Content-Type", "application/json")
|
|
response, err := c.client.Do(request)
|
|
if response != nil {
|
|
defer response.Body.Close()
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Helper function to get repos paginated
|
|
func (c *Client) paginatedRepos(start int) ([]*Repo, error) {
|
|
limit := 1000
|
|
requestURL := fmt.Sprintf(pathRepos, c.base, strconv.Itoa(start), strconv.Itoa(limit))
|
|
response, err := c.doGet(requestURL)
|
|
if response != nil {
|
|
defer response.Body.Close()
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var repoResponse Repos
|
|
err = json.NewDecoder(response.Body).Decode(&repoResponse)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !repoResponse.IsLastPage {
|
|
reposList, err := c.paginatedRepos(start + limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
repoResponse.Values = append(repoResponse.Values, reposList...)
|
|
}
|
|
return repoResponse.Values, nil
|
|
}
|
|
|
|
func (c *Client) ListBranches(owner, name string) ([]*Branch, error) {
|
|
uri := fmt.Sprintf(pathBranches, c.base, owner, name)
|
|
response, err := c.doGet(uri)
|
|
if response != nil {
|
|
defer response.Body.Close()
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := new(BranchResp)
|
|
err = json.NewDecoder(response.Body).Decode(&out)
|
|
return out.Values, err
|
|
}
|
|
|
|
func filter(vs []string, f func(string) bool) []string {
|
|
var vsf []string
|
|
for _, v := range vs {
|
|
if f(v) {
|
|
vsf = append(vsf, v)
|
|
}
|
|
}
|
|
return vsf
|
|
}
|
|
|
|
// TODO: find a clean way of doing these next two methods- bitbucket server hooks only support 20 cb hooks
|
|
func arrayToHookSettings(hooks []string) HookSettings {
|
|
hookSettings := HookSettings{}
|
|
for loc, value := range hooks {
|
|
switch loc {
|
|
case 0:
|
|
hookSettings.HookURL0 = value
|
|
case 1:
|
|
hookSettings.HookURL1 = value
|
|
case 2:
|
|
hookSettings.HookURL2 = value
|
|
case 3:
|
|
hookSettings.HookURL3 = value
|
|
case 4:
|
|
hookSettings.HookURL4 = value
|
|
case 5:
|
|
hookSettings.HookURL5 = value
|
|
case 6:
|
|
hookSettings.HookURL6 = value
|
|
case 7:
|
|
hookSettings.HookURL7 = value
|
|
case 8:
|
|
hookSettings.HookURL8 = value
|
|
case 9:
|
|
hookSettings.HookURL9 = value
|
|
case 10:
|
|
hookSettings.HookURL10 = value
|
|
case 11:
|
|
hookSettings.HookURL11 = value
|
|
case 12:
|
|
hookSettings.HookURL12 = value
|
|
case 13:
|
|
hookSettings.HookURL13 = value
|
|
case 14:
|
|
hookSettings.HookURL14 = value
|
|
case 15:
|
|
hookSettings.HookURL15 = value
|
|
case 16:
|
|
hookSettings.HookURL16 = value
|
|
case 17:
|
|
hookSettings.HookURL17 = value
|
|
case 18:
|
|
hookSettings.HookURL18 = value
|
|
case 19:
|
|
hookSettings.HookURL19 = value
|
|
|
|
// Since there's only 19 hooks it will add to the latest if it doesn't exist :/
|
|
default:
|
|
hookSettings.HookURL19 = value
|
|
}
|
|
}
|
|
return hookSettings
|
|
}
|
|
|
|
func hookSettingsToArray(hookSettings *HookSettings) []string {
|
|
var hooks []string
|
|
|
|
if hookSettings.HookURL0 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL0)
|
|
}
|
|
if hookSettings.HookURL1 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL1)
|
|
}
|
|
if hookSettings.HookURL2 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL2)
|
|
}
|
|
if hookSettings.HookURL3 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL3)
|
|
}
|
|
if hookSettings.HookURL4 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL4)
|
|
}
|
|
if hookSettings.HookURL5 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL5)
|
|
}
|
|
if hookSettings.HookURL6 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL6)
|
|
}
|
|
if hookSettings.HookURL7 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL7)
|
|
}
|
|
if hookSettings.HookURL8 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL8)
|
|
}
|
|
if hookSettings.HookURL9 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL9)
|
|
}
|
|
if hookSettings.HookURL10 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL10)
|
|
}
|
|
if hookSettings.HookURL11 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL11)
|
|
}
|
|
if hookSettings.HookURL12 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL12)
|
|
}
|
|
if hookSettings.HookURL13 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL13)
|
|
}
|
|
if hookSettings.HookURL14 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL14)
|
|
}
|
|
if hookSettings.HookURL15 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL15)
|
|
}
|
|
if hookSettings.HookURL16 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL16)
|
|
}
|
|
if hookSettings.HookURL17 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL17)
|
|
}
|
|
if hookSettings.HookURL18 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL18)
|
|
}
|
|
if hookSettings.HookURL19 != "" {
|
|
hooks = append(hooks, hookSettings.HookURL19)
|
|
}
|
|
return hooks
|
|
}
|
|
|
|
func stringInSlice(a string, list []string) bool {
|
|
for _, b := range list {
|
|
if b == a {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|