mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-29 21:31:02 +00:00
parent
874c2ea114
commit
570f5044e8
13 changed files with 10 additions and 1546 deletions
|
@ -421,52 +421,6 @@ var flags = []cli.Flag{
|
||||||
Usage: "gitlab skip ssl verification",
|
Usage: "gitlab skip ssl verification",
|
||||||
},
|
},
|
||||||
//
|
//
|
||||||
// Bitbucket Stash
|
|
||||||
//
|
|
||||||
&cli.BoolFlag{
|
|
||||||
EnvVars: []string{"WOODPECKER_STASH"},
|
|
||||||
Name: "stash",
|
|
||||||
Usage: "stash driver is enabled",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
EnvVars: []string{"WOODPECKER_STASH_URL"},
|
|
||||||
Name: "stash-server",
|
|
||||||
Usage: "stash server address",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
EnvVars: []string{"WOODPECKER_STASH_CONSUMER_KEY"},
|
|
||||||
Name: "stash-consumer-key",
|
|
||||||
Usage: "stash oauth1 consumer key",
|
|
||||||
FilePath: os.Getenv("WOODPECKER_STASH_CONSUMER_KEY_FILE"),
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
EnvVars: []string{"WOODPECKER_STASH_CONSUMER_RSA"},
|
|
||||||
Name: "stash-consumer-rsa",
|
|
||||||
Usage: "stash oauth1 private key file",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
EnvVars: []string{"WOODPECKER_STASH_CONSUMER_RSA_STRING"},
|
|
||||||
Name: "stash-consumer-rsa-string",
|
|
||||||
Usage: "stash oauth1 private key string",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
EnvVars: []string{"WOODPECKER_STASH_GIT_USERNAME"},
|
|
||||||
Name: "stash-git-username",
|
|
||||||
Usage: "stash service account username",
|
|
||||||
FilePath: os.Getenv("WOODPECKER_STASH_GIT_USERNAME_FILE"),
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
EnvVars: []string{"WOODPECKER_STASH_GIT_PASSWORD"},
|
|
||||||
Name: "stash-git-password",
|
|
||||||
Usage: "stash service account password",
|
|
||||||
FilePath: os.Getenv("WOODPECKER_STASH_GIT_PASSWORD_FILE"),
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
EnvVars: []string{"WOODPECKER_STASH_SKIP_VERIFY"},
|
|
||||||
Name: "stash-skip-verify",
|
|
||||||
Usage: "stash skip ssl verification",
|
|
||||||
},
|
|
||||||
//
|
|
||||||
// development flags
|
// development flags
|
||||||
//
|
//
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
|
|
|
@ -38,7 +38,6 @@ import (
|
||||||
"github.com/woodpecker-ci/woodpecker/server/cache"
|
"github.com/woodpecker-ci/woodpecker/server/cache"
|
||||||
"github.com/woodpecker-ci/woodpecker/server/forge"
|
"github.com/woodpecker-ci/woodpecker/server/forge"
|
||||||
"github.com/woodpecker-ci/woodpecker/server/forge/bitbucket"
|
"github.com/woodpecker-ci/woodpecker/server/forge/bitbucket"
|
||||||
"github.com/woodpecker-ci/woodpecker/server/forge/bitbucketserver"
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/forge/gitea"
|
"github.com/woodpecker-ci/woodpecker/server/forge/gitea"
|
||||||
"github.com/woodpecker-ci/woodpecker/server/forge/github"
|
"github.com/woodpecker-ci/woodpecker/server/forge/github"
|
||||||
"github.com/woodpecker-ci/woodpecker/server/forge/gitlab"
|
"github.com/woodpecker-ci/woodpecker/server/forge/gitlab"
|
||||||
|
@ -193,8 +192,6 @@ func setupForge(c *cli.Context) (forge.Forge, error) {
|
||||||
return setupGitLab(c)
|
return setupGitLab(c)
|
||||||
case c.Bool("bitbucket"):
|
case c.Bool("bitbucket"):
|
||||||
return setupBitbucket(c)
|
return setupBitbucket(c)
|
||||||
case c.Bool("stash"):
|
|
||||||
return setupStash(c)
|
|
||||||
case c.Bool("gitea"):
|
case c.Bool("gitea"):
|
||||||
return setupGitea(c)
|
return setupGitea(c)
|
||||||
default:
|
default:
|
||||||
|
@ -231,21 +228,6 @@ func setupGitea(c *cli.Context) (forge.Forge, error) {
|
||||||
return gitea.New(opts)
|
return gitea.New(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupStash helper function to setup the Stash forge from the CLI arguments.
|
|
||||||
func setupStash(c *cli.Context) (forge.Forge, error) {
|
|
||||||
opts := bitbucketserver.Opts{
|
|
||||||
URL: c.String("stash-server"),
|
|
||||||
Username: c.String("stash-git-username"),
|
|
||||||
Password: c.String("stash-git-password"),
|
|
||||||
ConsumerKey: c.String("stash-consumer-key"),
|
|
||||||
ConsumerRSA: c.String("stash-consumer-rsa"),
|
|
||||||
ConsumerRSAString: c.String("stash-consumer-rsa-string"),
|
|
||||||
SkipVerify: c.Bool("stash-skip-verify"),
|
|
||||||
}
|
|
||||||
log.Trace().Msgf("Forge (bitbucketserver) opts: %#v", opts)
|
|
||||||
return bitbucketserver.New(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupGitLab helper function to setup the GitLab forge from the CLI arguments.
|
// setupGitLab helper function to setup the GitLab forge from the CLI arguments.
|
||||||
func setupGitLab(c *cli.Context) (forge.Forge, error) {
|
func setupGitLab(c *cli.Context) (forge.Forge, error) {
|
||||||
return gitlab.New(gitlab.Opts{
|
return gitlab.New(gitlab.Opts{
|
||||||
|
|
|
@ -536,10 +536,6 @@ See [Gitea configuration](forges/gitea/#configuration)
|
||||||
|
|
||||||
See [Bitbucket configuration](forges/bitbucket/#configuration)
|
See [Bitbucket configuration](forges/bitbucket/#configuration)
|
||||||
|
|
||||||
### `WOODPECKER_STASH_...`
|
|
||||||
|
|
||||||
See [Bitbucket server configuration](forges/bitbucket_server/#configuration)
|
|
||||||
|
|
||||||
### `WOODPECKER_GITLAB_...`
|
### `WOODPECKER_GITLAB_...`
|
||||||
|
|
||||||
See [Gitlab configuration](forges/gitlab/#configuration)
|
See [Gitlab configuration](forges/gitlab/#configuration)
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
## Supported features
|
## Supported features
|
||||||
|
|
||||||
| Feature | [GitHub](github/) | [Gitea / Forgejo](gitea/) | [Gitlab](gitlab/) | [Bitbucket](bitbucket/) | [Bitbucket Server](bitbucket_server/) |
|
| Feature | [GitHub](github/) | [Gitea / Forgejo](gitea/) | [Gitlab](gitlab/) | [Bitbucket](bitbucket/) |
|
||||||
| --- | :---: | :---: | :---: | :---: | :---: |
|
| --- | :---: | :---: | :---: | :---: |
|
||||||
| Event: Push | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
| Event: Push | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||||
| Event: Tag | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
|
| Event: Tag | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
|
||||||
| Event: Pull-Request | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
|
| Event: Pull-Request | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||||
| Event: Deploy | :white_check_mark: | :x: | :x: | :x: | :x: |
|
| Event: Deploy | :white_check_mark: | :x: | :x: | :x: |
|
||||||
| [Multiple workflows](../../20-usage/25-workflows.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: |
|
| [Multiple workflows](../../20-usage/25-workflows.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
|
||||||
| [when.path filter](../../20-usage/20-pipeline-syntax.md#path) | :white_check_mark: | :white_check_mark:¹ | :white_check_mark: | :x: | :x: |
|
| [when.path filter](../../20-usage/20-pipeline-syntax.md#path) | :white_check_mark: | :white_check_mark:¹ | :white_check_mark: | :x: |
|
||||||
|
|
||||||
¹ for pull requests at least Gitea version 1.17 is required
|
¹ for pull requests at least Gitea version 1.17 is required
|
||||||
|
|
|
@ -1,156 +0,0 @@
|
||||||
# Bitbucket Server
|
|
||||||
|
|
||||||
Woodpecker comes with experimental support for Bitbucket Server, formerly known as Atlassian Stash. To enable Bitbucket Server you should configure the Woodpecker container using the following environment variables:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
# docker-compose.yml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
woodpecker-server:
|
|
||||||
[...]
|
|
||||||
environment:
|
|
||||||
- [...]
|
|
||||||
+ - WOODPECKER_STASH=true
|
|
||||||
+ - WOODPECKER_STASH_GIT_USERNAME=foo
|
|
||||||
+ - WOODPECKER_STASH_GIT_PASSWORD=bar
|
|
||||||
+ - WOODPECKER_STASH_CONSUMER_KEY=95c0282573633eb25e82
|
|
||||||
+ - WOODPECKER_STASH_CONSUMER_RSA=/etc/bitbucket/key.pem
|
|
||||||
+ - WOODPECKER_STASH_URL=http://stash.mycompany.com
|
|
||||||
volumes:
|
|
||||||
+ - /path/to/key.pem:/path/to/key.pem
|
|
||||||
|
|
||||||
woodpecker-agent:
|
|
||||||
[...]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Private Key File
|
|
||||||
|
|
||||||
The OAuth process in Bitbucket server requires a private and a public RSA certificate. This is how you create the private RSA certificate.
|
|
||||||
|
|
||||||
```nohighlight
|
|
||||||
openssl genrsa -out /etc/bitbucket/key.pem 1024
|
|
||||||
```
|
|
||||||
|
|
||||||
This stores the private RSA certificate in `key.pem`. The next command generates the public RSA certificate and stores it in `key.pub`.
|
|
||||||
|
|
||||||
```nohighlight
|
|
||||||
openssl rsa -in /etc/bitbucket/key.pem -pubout >> /etc/bitbucket/key.pub
|
|
||||||
```
|
|
||||||
|
|
||||||
Please note that the private key file can be mounted into your Woodpecker container at runtime or as an environment variable
|
|
||||||
|
|
||||||
Private key file mounted into your Woodpecker container at runtime as a volume.
|
|
||||||
|
|
||||||
```diff
|
|
||||||
# docker-compose.yml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
woodpecker-server:
|
|
||||||
[...]
|
|
||||||
environment:
|
|
||||||
- [...]
|
|
||||||
- WOODPECKER_STASH=true
|
|
||||||
- WOODPECKER_STASH_GIT_USERNAME=foo
|
|
||||||
- WOODPECKER_STASH_GIT_PASSWORD=bar
|
|
||||||
- WOODPECKER_STASH_CONSUMER_KEY=95c0282573633eb25e82
|
|
||||||
+ - WOODPECKER_STASH_CONSUMER_RSA=/etc/bitbucket/key.pem
|
|
||||||
- WOODPECKER_STASH_URL=http://stash.mycompany.com
|
|
||||||
+ volumes:
|
|
||||||
+ - /etc/bitbucket/key.pem:/etc/bitbucket/key.pem
|
|
||||||
|
|
||||||
woodpecker-agent:
|
|
||||||
[...]
|
|
||||||
```
|
|
||||||
|
|
||||||
Private key as environment variable
|
|
||||||
|
|
||||||
```diff
|
|
||||||
# docker-compose.yml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
woodpecker-server:
|
|
||||||
[...]
|
|
||||||
environment:
|
|
||||||
- [...]
|
|
||||||
- WOODPECKER_STASH=true
|
|
||||||
- WOODPECKER_STASH_GIT_USERNAME=foo
|
|
||||||
- WOODPECKER_STASH_GIT_PASSWORD=bar
|
|
||||||
- WOODPECKER_STASH_CONSUMER_KEY=95c0282573633eb25e82
|
|
||||||
+ - WOODPECKER_STASH_CONSUMER_RSA_STRING=contentOfPemKeyAsString
|
|
||||||
- WOODPECKER_STASH_URL=http://stash.mycompany.com
|
|
||||||
|
|
||||||
woodpecker-agent:
|
|
||||||
[...]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Service Account
|
|
||||||
|
|
||||||
Woodpecker uses `git+https` to clone repositories, however, Bitbucket Server does not currently support cloning repositories with OAuth token. To work around this limitation, you must create a service account and provide the username and password to Woodpecker. This service account will be used to authenticate and clone private repositories.
|
|
||||||
|
|
||||||
## Registration
|
|
||||||
|
|
||||||
You must register your application with Bitbucket Server in order to generate a consumer key. Navigate to your account settings and choose Applications from the menu, and click Register new application. Now copy & paste the text value from `/etc/bitbucket/key.pub` into the `Public Key` in the incoming link part of the application registration.
|
|
||||||
|
|
||||||
Please use http://woodpecker.mycompany.com/authorize as the Authorization callback URL.
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
This is a full list of configuration options. Please note that many of these options use default configuration values that should work for the majority of installations.
|
|
||||||
|
|
||||||
### `WOODPECKER_STASH`
|
|
||||||
> Default: `false`
|
|
||||||
|
|
||||||
Enables the Bitbucket Server driver.
|
|
||||||
|
|
||||||
### `WOODPECKER_STASH_URL`
|
|
||||||
> Default: empty
|
|
||||||
|
|
||||||
Configures the Bitbucket Server address.
|
|
||||||
|
|
||||||
### `WOODPECKER_STASH_CONSUMER_KEY`
|
|
||||||
> Default: empty
|
|
||||||
|
|
||||||
Configures your Bitbucket Server consumer key.
|
|
||||||
|
|
||||||
### `WOODPECKER_STASH_CONSUMER_KEY_FILE`
|
|
||||||
> Default: empty
|
|
||||||
|
|
||||||
Read the value for `WOODPECKER_STASH_CONSUMER_KEY` from the specified filepath
|
|
||||||
|
|
||||||
### `WOODPECKER_STASH_CONSUMER_RSA`
|
|
||||||
> Default: empty
|
|
||||||
|
|
||||||
Configures the path to your Bitbucket Server private key file.
|
|
||||||
|
|
||||||
### `WOODPECKER_STASH_CONSUMER_RSA_STRING`
|
|
||||||
> Default: empty
|
|
||||||
|
|
||||||
Configures your Bitbucket Server private key.
|
|
||||||
|
|
||||||
### `WOODPECKER_STASH_GIT_USERNAME`
|
|
||||||
> Default: empty
|
|
||||||
|
|
||||||
This username is used to authenticate and clone all private repositories.
|
|
||||||
|
|
||||||
### `WOODPECKER_STASH_GIT_USERNAME_FILE`
|
|
||||||
> Default: empty
|
|
||||||
|
|
||||||
Read the value for `WOODPECKER_STASH_GIT_USERNAME` from the specified filepath
|
|
||||||
|
|
||||||
### `WOODPECKER_STASH_GIT_PASSWORD`
|
|
||||||
> Default: empty
|
|
||||||
|
|
||||||
The password is used to authenticate and clone all private repositories.
|
|
||||||
|
|
||||||
### `WOODPECKER_STASH_GIT_PASSWORD_FILE`
|
|
||||||
> Default: empty
|
|
||||||
|
|
||||||
Read the value for `WOODPECKER_STASH_GIT_PASSWORD` from the specified filepath
|
|
||||||
|
|
||||||
### `WOODPECKER_STASH_SKIP_VERIFY`
|
|
||||||
> Default: `false`
|
|
||||||
|
|
||||||
Configure if SSL verification should be skipped.
|
|
|
@ -16,7 +16,7 @@ Some versions need some changes to the server configuration or the pipeline conf
|
||||||
- Updated Prometheus gauge `*_job_*` to `*_step_*`
|
- Updated Prometheus gauge `*_job_*` to `*_step_*`
|
||||||
- Renamed config env `WOODPECKER_MAX_PROCS` to `WOODPECKER_MAX_WORKFLOWS` (still available as fallback)
|
- Renamed config env `WOODPECKER_MAX_PROCS` to `WOODPECKER_MAX_WORKFLOWS` (still available as fallback)
|
||||||
- The pipelines are now also read from `.yaml` files, the new default order is `.woodpecker/*.yml` and `.woodpecker/*.yaml` (without any prioritization) -> `.woodpecker.yml` -> `.woodpecker.yaml`
|
- The pipelines are now also read from `.yaml` files, the new default order is `.woodpecker/*.yml` and `.woodpecker/*.yaml` (without any prioritization) -> `.woodpecker.yml` -> `.woodpecker.yaml`
|
||||||
- Dropped support for [Coding](https://coding.net/) and [Gogs](https://gogs.io).
|
- Dropped support for [Coding](https://coding.net/), [Gogs](https://gogs.io) and Bitbucket Server (Stash).
|
||||||
- `/api/queue/resume` & `/api/queue/pause` endpoint methods were changed from `GET` to `POST`
|
- `/api/queue/resume` & `/api/queue/pause` endpoint methods were changed from `GET` to `POST`
|
||||||
- rename `pipeline:` key in your workflow config to `steps:`
|
- rename `pipeline:` key in your workflow config to `steps:`
|
||||||
- If you want to migrate old logs to the new format, watch the error messages on start. If there are none we are good to go, else you have to plan a migration that can take hours. Set `WOODPECKER_MIGRATIONS_ALLOW_LONG` to true and let it run.
|
- If you want to migrate old logs to the new format, watch the error messages on start. If there are none we are good to go, else you have to plan a migration that can take hours. Set `WOODPECKER_MIGRATIONS_ALLOW_LONG` to true and let it run.
|
||||||
|
|
|
@ -1,293 +0,0 @@
|
||||||
// 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 bitbucketserver
|
|
||||||
|
|
||||||
// WARNING! This is an work-in-progress patch and does not yet conform to the coding,
|
|
||||||
// quality or security standards expected of this project. Please use with caution.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/mrjones/oauth"
|
|
||||||
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/forge"
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/forge/bitbucketserver/internal"
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/forge/common"
|
|
||||||
forge_types "github.com/woodpecker-ci/woodpecker/server/forge/types"
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
requestTokenURL = "%s/plugins/servlet/oauth/request-token"
|
|
||||||
authorizeTokenURL = "%s/plugins/servlet/oauth/authorize"
|
|
||||||
accessTokenURL = "%s/plugins/servlet/oauth/access-token"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Opts defines configuration options.
|
|
||||||
type Opts struct {
|
|
||||||
URL string // Stash server url.
|
|
||||||
Username string // Git machine account username.
|
|
||||||
Password string // Git machine account password.
|
|
||||||
ConsumerKey string // Oauth1 consumer key.
|
|
||||||
ConsumerRSA string // Oauth1 consumer key file.
|
|
||||||
ConsumerRSAString string
|
|
||||||
SkipVerify bool // Skip ssl verification.
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
url string
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
SkipVerify bool
|
|
||||||
Consumer *oauth.Consumer
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a Forge implementation that integrates with Bitbucket Server,
|
|
||||||
// the on-premise edition of Bitbucket Cloud, formerly known as Stash.
|
|
||||||
func New(opts Opts) (forge.Forge, error) {
|
|
||||||
config := &Config{
|
|
||||||
url: opts.URL,
|
|
||||||
Username: opts.Username,
|
|
||||||
Password: opts.Password,
|
|
||||||
SkipVerify: opts.SkipVerify,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case opts.Username == "":
|
|
||||||
return nil, fmt.Errorf("Must have a git machine account username")
|
|
||||||
case opts.Password == "":
|
|
||||||
return nil, fmt.Errorf("Must have a git machine account password")
|
|
||||||
case opts.ConsumerKey == "":
|
|
||||||
return nil, fmt.Errorf("Must have a oauth1 consumer key")
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.ConsumerRSA == "" && opts.ConsumerRSAString == "" {
|
|
||||||
return nil, fmt.Errorf("must have CONSUMER_RSA_KEY set to the path of a oauth1 consumer key file or CONSUMER_RSA_KEY_STRING set to the value of a oauth1 consumer key")
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyFileBytes []byte
|
|
||||||
if opts.ConsumerRSA != "" {
|
|
||||||
var err error
|
|
||||||
keyFileBytes, err = os.ReadFile(opts.ConsumerRSA)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
keyFileBytes = []byte(opts.ConsumerRSAString)
|
|
||||||
}
|
|
||||||
|
|
||||||
block, _ := pem.Decode(keyFileBytes)
|
|
||||||
PrivateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Consumer = CreateConsumer(opts.URL, opts.ConsumerKey, PrivateKey)
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the string name of this driver
|
|
||||||
func (c *Config) Name() string {
|
|
||||||
return "stash"
|
|
||||||
}
|
|
||||||
|
|
||||||
// URL returns the root url of a configured forge
|
|
||||||
func (c *Config) URL() string {
|
|
||||||
return c.url
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Login(ctx context.Context, res http.ResponseWriter, req *http.Request) (*model.User, error) {
|
|
||||||
requestToken, u, err := c.Consumer.GetRequestTokenAndUrl("oob")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
code := req.FormValue("oauth_verifier")
|
|
||||||
if len(code) == 0 {
|
|
||||||
http.Redirect(res, req, u, http.StatusSeeOther)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
requestToken.Token = req.FormValue("oauth_token")
|
|
||||||
accessToken, err := c.Consumer.AuthorizeToken(requestToken, code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := internal.NewClientWithToken(ctx, c.url, c.Consumer, accessToken.Token)
|
|
||||||
|
|
||||||
user, err := client.FindCurrentUser()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return convertUser(user, accessToken), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auth is not supported by the Stash driver.
|
|
||||||
func (*Config) Auth(_ context.Context, _, _ string) (string, error) {
|
|
||||||
return "", fmt.Errorf("Not Implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Teams is not supported by the Stash driver.
|
|
||||||
func (*Config) Teams(_ context.Context, _ *model.User) ([]*model.Team, error) {
|
|
||||||
var teams []*model.Team
|
|
||||||
return teams, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TeamPerm is not supported by the Stash driver.
|
|
||||||
func (*Config) TeamPerm(_ *model.User, _ string) (*model.Perm, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Repo(ctx context.Context, u *model.User, _ model.ForgeRemoteID, owner, name string) (*model.Repo, error) {
|
|
||||||
client := internal.NewClientWithToken(ctx, c.url, c.Consumer, u.Token)
|
|
||||||
repo, err := client.FindRepo(owner, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
perm, err := client.FindRepoPerms(repo.Project.Key, repo.Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return convertRepo(repo, perm), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Repos(ctx context.Context, u *model.User) ([]*model.Repo, error) {
|
|
||||||
client := internal.NewClientWithToken(ctx, c.url, c.Consumer, u.Token)
|
|
||||||
repos, err := client.FindRepos()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var all []*model.Repo
|
|
||||||
for _, repo := range repos {
|
|
||||||
perm, err := client.FindRepoPerms(repo.Project.Key, repo.Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
all = append(all, convertRepo(repo, perm))
|
|
||||||
}
|
|
||||||
|
|
||||||
return all, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) File(ctx context.Context, u *model.User, r *model.Repo, p *model.Pipeline, f string) ([]byte, error) {
|
|
||||||
client := internal.NewClientWithToken(ctx, c.url, c.Consumer, u.Token)
|
|
||||||
|
|
||||||
return client.FindFileForRepo(r.Owner, r.Name, f, p.Ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Dir(_ context.Context, _ *model.User, _ *model.Repo, _ *model.Pipeline, _ string) ([]*forge_types.FileMeta, error) {
|
|
||||||
return nil, forge_types.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status is not supported by the bitbucketserver driver.
|
|
||||||
func (c *Config) Status(ctx context.Context, user *model.User, repo *model.Repo, pipeline *model.Pipeline, _ *model.Workflow) error {
|
|
||||||
status := internal.PipelineStatus{
|
|
||||||
State: convertStatus(pipeline.Status),
|
|
||||||
Desc: common.GetPipelineStatusDescription(pipeline.Status),
|
|
||||||
Name: fmt.Sprintf("Woodpecker #%d - %s", pipeline.Number, pipeline.Branch),
|
|
||||||
Key: "Woodpecker",
|
|
||||||
URL: common.GetPipelineStatusLink(repo, pipeline, nil),
|
|
||||||
}
|
|
||||||
|
|
||||||
client := internal.NewClientWithToken(ctx, c.url, c.Consumer, user.Token)
|
|
||||||
|
|
||||||
return client.CreateStatus(pipeline.Commit, &status)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Netrc(_ *model.User, r *model.Repo) (*model.Netrc, error) {
|
|
||||||
host, err := common.ExtractHostFromCloneURL(r.Clone)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &model.Netrc{
|
|
||||||
Login: c.Username,
|
|
||||||
Password: c.Password,
|
|
||||||
Machine: host,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Activate(ctx context.Context, u *model.User, r *model.Repo, link string) error {
|
|
||||||
client := internal.NewClientWithToken(ctx, c.url, c.Consumer, u.Token)
|
|
||||||
|
|
||||||
return client.CreateHook(r.Owner, r.Name, link)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Branches returns the names of all branches for the named repository.
|
|
||||||
func (c *Config) Branches(ctx context.Context, u *model.User, r *model.Repo, p *model.ListOptions) ([]string, error) {
|
|
||||||
bitbucketBranches, err := internal.NewClientWithToken(ctx, c.url, c.Consumer, common.UserToken(ctx, r, u)).ListBranches(r.Owner, r.Name, p.Page, p.PerPage)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
branches := make([]string, 0)
|
|
||||||
for _, branch := range bitbucketBranches {
|
|
||||||
branches = append(branches, branch.Name)
|
|
||||||
}
|
|
||||||
return branches, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BranchHead returns the sha of the head (latest commit) of the specified branch
|
|
||||||
func (c *Config) BranchHead(_ context.Context, _ *model.User, _ *model.Repo, _ string) (string, error) {
|
|
||||||
// TODO(1138): missing implementation
|
|
||||||
return "", forge_types.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) PullRequests(_ context.Context, _ *model.User, _ *model.Repo, _ *model.ListOptions) ([]*model.PullRequest, error) {
|
|
||||||
return nil, forge_types.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Deactivate(ctx context.Context, u *model.User, r *model.Repo, link string) error {
|
|
||||||
client := internal.NewClientWithToken(ctx, c.url, c.Consumer, u.Token)
|
|
||||||
return client.DeleteHook(r.Owner, r.Name, link)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Hook(_ context.Context, r *http.Request) (*model.Repo, *model.Pipeline, error) {
|
|
||||||
return parseHook(r, c.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OrgMembership returns if user is member of organization and if user
|
|
||||||
// is admin/owner in this organization.
|
|
||||||
func (c *Config) OrgMembership(_ context.Context, _ *model.User, _ string) (*model.OrgPerm, error) {
|
|
||||||
// TODO: Not implemented currently
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateConsumer(URL, ConsumerKey string, PrivateKey *rsa.PrivateKey) *oauth.Consumer {
|
|
||||||
consumer := oauth.NewRSAConsumer(
|
|
||||||
ConsumerKey,
|
|
||||||
PrivateKey,
|
|
||||||
oauth.ServiceProvider{
|
|
||||||
RequestTokenUrl: fmt.Sprintf(requestTokenURL, URL),
|
|
||||||
AuthorizeTokenUrl: fmt.Sprintf(authorizeTokenURL, URL),
|
|
||||||
AccessTokenUrl: fmt.Sprintf(accessTokenURL, URL),
|
|
||||||
HttpMethod: "POST",
|
|
||||||
})
|
|
||||||
consumer.HttpClient = &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return consumer
|
|
||||||
}
|
|
|
@ -1,138 +0,0 @@
|
||||||
// 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 bitbucketserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mrjones/oauth"
|
|
||||||
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/forge/bitbucketserver/internal"
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
statusPending = "INPROGRESS"
|
|
||||||
statusSuccess = "SUCCESSFUL"
|
|
||||||
statusFailure = "FAILED"
|
|
||||||
)
|
|
||||||
|
|
||||||
// convertStatus is a helper function used to convert a Woodpecker status to a
|
|
||||||
// Bitbucket commit status.
|
|
||||||
func convertStatus(status model.StatusValue) string {
|
|
||||||
switch status {
|
|
||||||
case model.StatusPending, model.StatusRunning:
|
|
||||||
return statusPending
|
|
||||||
case model.StatusSuccess:
|
|
||||||
return statusSuccess
|
|
||||||
default:
|
|
||||||
return statusFailure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertRepo is a helper function used to convert a Bitbucket server repository
|
|
||||||
// structure to the common Woodpecker repository structure.
|
|
||||||
func convertRepo(from *internal.Repo, perm *model.Perm) *model.Repo {
|
|
||||||
repo := model.Repo{
|
|
||||||
ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(from.ID)),
|
|
||||||
Name: from.Slug,
|
|
||||||
Owner: from.Project.Key,
|
|
||||||
Branch: "master",
|
|
||||||
SCMKind: model.RepoGit,
|
|
||||||
IsSCMPrivate: true, // Since we have to use Netrc it has to always be private :/
|
|
||||||
FullName: fmt.Sprintf("%s/%s", from.Project.Key, from.Slug),
|
|
||||||
Perm: perm,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range from.Links.Clone {
|
|
||||||
if item.Name == "http" {
|
|
||||||
uri, err := url.Parse(item.Href)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
uri.User = nil
|
|
||||||
repo.Clone = uri.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, item := range from.Links.Self {
|
|
||||||
if item.Href != "" {
|
|
||||||
repo.Link = item.Href
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &repo
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertPushHook is a helper function used to convert a Bitbucket push
|
|
||||||
// hook to the Woodpecker pipeline struct holding commit information.
|
|
||||||
func convertPushHook(hook *internal.PostHook, baseURL string) *model.Pipeline {
|
|
||||||
branch := strings.TrimPrefix(
|
|
||||||
strings.TrimPrefix(
|
|
||||||
hook.RefChanges[0].RefID,
|
|
||||||
"refs/heads/",
|
|
||||||
),
|
|
||||||
"refs/tags/",
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensuring the author label is not longer then 40 for the label of the commit author (default size in the db)
|
|
||||||
authorLabel := hook.Changesets.Values[0].ToCommit.Author.Name
|
|
||||||
if len(authorLabel) > 40 {
|
|
||||||
authorLabel = authorLabel[0:37] + "..."
|
|
||||||
}
|
|
||||||
|
|
||||||
pipeline := &model.Pipeline{
|
|
||||||
Commit: hook.RefChanges[0].ToHash, // TODO check for index value
|
|
||||||
Branch: branch,
|
|
||||||
Message: hook.Changesets.Values[0].ToCommit.Message, // TODO check for index Values
|
|
||||||
Avatar: avatarLink(hook.Changesets.Values[0].ToCommit.Author.EmailAddress),
|
|
||||||
Author: authorLabel,
|
|
||||||
Email: hook.Changesets.Values[0].ToCommit.Author.EmailAddress,
|
|
||||||
Timestamp: time.Now().UTC().Unix(),
|
|
||||||
Ref: hook.RefChanges[0].RefID, // TODO check for index Values
|
|
||||||
Link: fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s", baseURL, hook.Repository.Project.Key, hook.Repository.Slug, hook.RefChanges[0].ToHash),
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(hook.RefChanges[0].RefID, "refs/tags/") {
|
|
||||||
pipeline.Event = model.EventTag
|
|
||||||
} else {
|
|
||||||
pipeline.Event = model.EventPush
|
|
||||||
}
|
|
||||||
|
|
||||||
return pipeline
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertUser is a helper function used to convert a Bitbucket user account
|
|
||||||
// structure to the Woodpecker User structure.
|
|
||||||
func convertUser(from *internal.User, token *oauth.AccessToken) *model.User {
|
|
||||||
return &model.User{
|
|
||||||
Login: from.Slug,
|
|
||||||
Token: token.Token,
|
|
||||||
Email: from.EmailAddress,
|
|
||||||
Avatar: avatarLink(from.EmailAddress),
|
|
||||||
ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(from.ID)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func avatarLink(email string) string {
|
|
||||||
hasher := md5.New()
|
|
||||||
hasher.Write([]byte(strings.ToLower(email)))
|
|
||||||
emailHash := fmt.Sprintf("%v", hex.EncodeToString(hasher.Sum(nil)))
|
|
||||||
avatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s.jpg", emailHash)
|
|
||||||
return avatarURL
|
|
||||||
}
|
|
|
@ -1,156 +0,0 @@
|
||||||
// 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 bitbucketserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/franela/goblin"
|
|
||||||
"github.com/mrjones/oauth"
|
|
||||||
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/forge/bitbucketserver/internal"
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_helper(t *testing.T) {
|
|
||||||
g := goblin.Goblin(t)
|
|
||||||
g.Describe("Bitbucket Server converter", func() {
|
|
||||||
g.It("should convert repository", func() {
|
|
||||||
from := &internal.Repo{
|
|
||||||
Slug: "hello-world",
|
|
||||||
}
|
|
||||||
from.Project.Key = "octocat"
|
|
||||||
|
|
||||||
// var links [1]internal.LinkType
|
|
||||||
link := internal.CloneLink{
|
|
||||||
Name: "http",
|
|
||||||
Href: "https://x7hw@server.org/foo/bar.git",
|
|
||||||
}
|
|
||||||
from.Links.Clone = append(from.Links.Clone, link)
|
|
||||||
|
|
||||||
selfRef := internal.SelfRefLink{
|
|
||||||
Href: "https://server.org/foo/bar",
|
|
||||||
}
|
|
||||||
|
|
||||||
from.Links.Self = append(from.Links.Self, selfRef)
|
|
||||||
|
|
||||||
to := convertRepo(from, &model.Perm{Pull: true})
|
|
||||||
g.Assert(to.FullName).Equal("octocat/hello-world")
|
|
||||||
g.Assert(to.Owner).Equal("octocat")
|
|
||||||
g.Assert(to.Name).Equal("hello-world")
|
|
||||||
g.Assert(to.Branch).Equal("master")
|
|
||||||
g.Assert(to.SCMKind).Equal(model.RepoGit)
|
|
||||||
g.Assert(to.IsSCMPrivate).Equal(true)
|
|
||||||
g.Assert(to.Clone).Equal("https://server.org/foo/bar.git")
|
|
||||||
g.Assert(to.Link).Equal("https://server.org/foo/bar")
|
|
||||||
g.Assert(to.Perm.Pull).IsTrue()
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("should convert user", func() {
|
|
||||||
token := &oauth.AccessToken{
|
|
||||||
Token: "foo",
|
|
||||||
}
|
|
||||||
user := &internal.User{
|
|
||||||
Slug: "x12f",
|
|
||||||
EmailAddress: "huh@huh.com",
|
|
||||||
}
|
|
||||||
|
|
||||||
result := convertUser(user, token)
|
|
||||||
g.Assert(result.Avatar).Equal(avatarLink("huh@huh.com"))
|
|
||||||
g.Assert(result.Login).Equal("x12f")
|
|
||||||
g.Assert(result.Token).Equal("foo")
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("branch should be empty", func() {
|
|
||||||
change := internal.PostHook{}
|
|
||||||
change.RefChanges = append(change.RefChanges, internal.RefChange{
|
|
||||||
RefID: "refs/heads/",
|
|
||||||
ToHash: "73f9c44d",
|
|
||||||
})
|
|
||||||
|
|
||||||
value := internal.Value{}
|
|
||||||
value.ToCommit.Author.Name = "John Doe, Appleboy, Mary, Janet E. Dawson and Ann S. Palmer"
|
|
||||||
value.ToCommit.Author.EmailAddress = "huh@huh.com"
|
|
||||||
value.ToCommit.Message = "message"
|
|
||||||
|
|
||||||
change.Changesets.Values = append(change.Changesets.Values, value)
|
|
||||||
|
|
||||||
change.Repository.Project = internal.Project{
|
|
||||||
Key: "octocat",
|
|
||||||
}
|
|
||||||
change.Repository.Slug = "hello-world"
|
|
||||||
pipeline := convertPushHook(&change, "http://base.com")
|
|
||||||
g.Assert(pipeline.Branch).Equal("")
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("should convert push hook to pipeline", func() {
|
|
||||||
change := internal.PostHook{}
|
|
||||||
|
|
||||||
change.RefChanges = append(change.RefChanges, internal.RefChange{
|
|
||||||
RefID: "refs/heads/release/some-feature",
|
|
||||||
ToHash: "73f9c44d",
|
|
||||||
})
|
|
||||||
|
|
||||||
value := internal.Value{}
|
|
||||||
value.ToCommit.Author.Name = "John Doe, Appleboy, Mary, Janet E. Dawson and Ann S. Palmer"
|
|
||||||
value.ToCommit.Author.EmailAddress = "huh@huh.com"
|
|
||||||
value.ToCommit.Message = "message"
|
|
||||||
|
|
||||||
change.Changesets.Values = append(change.Changesets.Values, value)
|
|
||||||
|
|
||||||
change.Repository.Project.Key = "octocat"
|
|
||||||
change.Repository.Slug = "hello-world"
|
|
||||||
|
|
||||||
pipeline := convertPushHook(&change, "http://base.com")
|
|
||||||
g.Assert(pipeline.Event).Equal(model.EventPush)
|
|
||||||
// Ensuring the author label is not longer then 40
|
|
||||||
g.Assert(pipeline.Author).Equal("John Doe, Appleboy, Mary, Janet E. Da...")
|
|
||||||
g.Assert(pipeline.Avatar).Equal(avatarLink("huh@huh.com"))
|
|
||||||
g.Assert(pipeline.Commit).Equal("73f9c44d")
|
|
||||||
g.Assert(pipeline.Branch).Equal("release/some-feature")
|
|
||||||
g.Assert(pipeline.Link).Equal("http://base.com/projects/octocat/repos/hello-world/commits/73f9c44d")
|
|
||||||
g.Assert(pipeline.Ref).Equal("refs/heads/release/some-feature")
|
|
||||||
g.Assert(pipeline.Message).Equal("message")
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("should convert tag hook to pipeline", func() {
|
|
||||||
change := internal.PostHook{}
|
|
||||||
change.RefChanges = append(change.RefChanges, internal.RefChange{
|
|
||||||
RefID: "refs/tags/v1",
|
|
||||||
ToHash: "73f9c44d",
|
|
||||||
})
|
|
||||||
|
|
||||||
value := internal.Value{}
|
|
||||||
value.ToCommit.Author.Name = "John Doe"
|
|
||||||
value.ToCommit.Author.EmailAddress = "huh@huh.com"
|
|
||||||
value.ToCommit.Message = "message"
|
|
||||||
|
|
||||||
change.Changesets.Values = append(change.Changesets.Values, value)
|
|
||||||
change.Repository.Project.Key = "octocat"
|
|
||||||
change.Repository.Slug = "hello-world"
|
|
||||||
|
|
||||||
pipeline := convertPushHook(&change, "http://base.com")
|
|
||||||
g.Assert(pipeline.Event).Equal(model.EventTag)
|
|
||||||
g.Assert(pipeline.Author).Equal("John Doe")
|
|
||||||
g.Assert(pipeline.Avatar).Equal(avatarLink("huh@huh.com"))
|
|
||||||
g.Assert(pipeline.Commit).Equal("73f9c44d")
|
|
||||||
g.Assert(pipeline.Branch).Equal("v1")
|
|
||||||
g.Assert(pipeline.Link).Equal("http://base.com/projects/octocat/repos/hello-world/commits/73f9c44d")
|
|
||||||
g.Assert(pipeline.Ref).Equal("refs/tags/v1")
|
|
||||||
g.Assert(pipeline.Message).Equal("message")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,473 +0,0 @@
|
||||||
// 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/rest/api/1.0/projects/%s/repos/%s/branches?limit=%d&start=%d"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer currentUserIDResponse.Body.Close()
|
|
||||||
|
|
||||||
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, page, limit int) ([]*Branch, error) {
|
|
||||||
uri := fmt.Sprintf(pathBranches, c.base, owner, name, limit, limit*(page-1))
|
|
||||||
response, err := c.doGet(uri)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,215 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Active bool `json:"active"`
|
|
||||||
DisplayName string `json:"displayName"`
|
|
||||||
EmailAddress string `json:"emailAddress"`
|
|
||||||
ID int `json:"id"`
|
|
||||||
Links struct {
|
|
||||||
Self []struct {
|
|
||||||
Href string `json:"href"`
|
|
||||||
} `json:"self"`
|
|
||||||
} `json:"links"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Slug string `json:"slug"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloneLink struct {
|
|
||||||
Href string `json:"href"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SelfRefLink struct {
|
|
||||||
Href string `json:"href"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PipelineStatus struct {
|
|
||||||
State string `json:"state"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Desc string `json:"description,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Repo struct {
|
|
||||||
Forkable bool `json:"forkable"`
|
|
||||||
ID int `json:"id"`
|
|
||||||
Links struct {
|
|
||||||
Clone []CloneLink `json:"clone"`
|
|
||||||
Self []struct {
|
|
||||||
Href string `json:"href"`
|
|
||||||
} `json:"self"`
|
|
||||||
} `json:"links"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Project Project `json:"project"`
|
|
||||||
Public bool `json:"public"`
|
|
||||||
ScmID string `json:"scmId"`
|
|
||||||
Slug string `json:"slug"`
|
|
||||||
State string `json:"state"`
|
|
||||||
StatusMessage string `json:"statusMessage"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Project struct {
|
|
||||||
Description string `json:"description"`
|
|
||||||
ID int `json:"id"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
Links struct {
|
|
||||||
Self []SelfRefLink `json:"self"`
|
|
||||||
} `json:"links"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Public bool `json:"public"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Repos struct {
|
|
||||||
IsLastPage bool `json:"isLastPage"`
|
|
||||||
Limit int `json:"limit"`
|
|
||||||
Size int `json:"size"`
|
|
||||||
Start int `json:"start"`
|
|
||||||
Values []*Repo `json:"values"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Hook struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
Details *HookDetail `json:"details"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HookDetail struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
ConfigFormKey string `json:"configFormKey"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Value struct {
|
|
||||||
Changes struct {
|
|
||||||
Filter interface{} `json:"filter"`
|
|
||||||
IsLastPage bool `json:"isLastPage"`
|
|
||||||
Limit int `json:"limit"`
|
|
||||||
Size int `json:"size"`
|
|
||||||
Start int `json:"start"`
|
|
||||||
Values []struct {
|
|
||||||
ContentID string `json:"contentId"`
|
|
||||||
Executable bool `json:"executable"`
|
|
||||||
Link struct {
|
|
||||||
Rel string `json:"rel"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
} `json:"link"`
|
|
||||||
NodeType string `json:"nodeType"`
|
|
||||||
Path struct {
|
|
||||||
Components []string `json:"components"`
|
|
||||||
Extension string `json:"extension"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Parent string `json:"parent"`
|
|
||||||
ToString string `json:"toString"`
|
|
||||||
} `json:"path"`
|
|
||||||
PercentUnchanged int `json:"percentUnchanged"`
|
|
||||||
SrcExecutable bool `json:"srcExecutable"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
} `json:"values"`
|
|
||||||
} `json:"changes"`
|
|
||||||
FromCommit struct {
|
|
||||||
DisplayID string `json:"displayId"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
} `json:"fromCommit"`
|
|
||||||
Link struct {
|
|
||||||
Rel string `json:"rel"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
} `json:"link"`
|
|
||||||
ToCommit struct {
|
|
||||||
Author struct {
|
|
||||||
EmailAddress string `json:"emailAddress"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
} `json:"author"`
|
|
||||||
AuthorTimestamp int `json:"authorTimestamp"`
|
|
||||||
DisplayID string `json:"displayId"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Parents []struct {
|
|
||||||
DisplayID string `json:"displayId"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
} `json:"parents"`
|
|
||||||
} `json:"toCommit"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PostHook struct {
|
|
||||||
Changesets struct {
|
|
||||||
Filter interface{} `json:"filter"`
|
|
||||||
IsLastPage bool `json:"isLastPage"`
|
|
||||||
Limit int `json:"limit"`
|
|
||||||
Size int `json:"size"`
|
|
||||||
Start int `json:"start"`
|
|
||||||
Values []Value `json:"values"`
|
|
||||||
} `json:"changesets"`
|
|
||||||
RefChanges []RefChange `json:"refChanges"`
|
|
||||||
Repository Repo `json:"repository"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RefChange struct {
|
|
||||||
FromHash string `json:"fromHash"`
|
|
||||||
RefID string `json:"refId"`
|
|
||||||
ToHash string `json:"toHash"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HookPluginDetails struct {
|
|
||||||
Details struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
ConfigFormKey string `json:"configFormKey"`
|
|
||||||
} `json:"details"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
Configured bool `json:"configured"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HookSettings struct {
|
|
||||||
HookURL0 string `json:"hook-url-0,omitempty"`
|
|
||||||
HookURL1 string `json:"hook-url-1,omitempty"`
|
|
||||||
HookURL2 string `json:"hook-url-2,omitempty"`
|
|
||||||
HookURL3 string `json:"hook-url-3,omitempty"`
|
|
||||||
HookURL4 string `json:"hook-url-4,omitempty"`
|
|
||||||
HookURL5 string `json:"hook-url-5,omitempty"`
|
|
||||||
HookURL6 string `json:"hook-url-6,omitempty"`
|
|
||||||
HookURL7 string `json:"hook-url-7,omitempty"`
|
|
||||||
HookURL8 string `json:"hook-url-8,omitempty"`
|
|
||||||
HookURL9 string `json:"hook-url-9,omitempty"`
|
|
||||||
HookURL10 string `json:"hook-url-10,omitempty"`
|
|
||||||
HookURL11 string `json:"hook-url-11,omitempty"`
|
|
||||||
HookURL12 string `json:"hook-url-12,omitempty"`
|
|
||||||
HookURL13 string `json:"hook-url-13,omitempty"`
|
|
||||||
HookURL14 string `json:"hook-url-14,omitempty"`
|
|
||||||
HookURL15 string `json:"hook-url-15,omitempty"`
|
|
||||||
HookURL16 string `json:"hook-url-16,omitempty"`
|
|
||||||
HookURL17 string `json:"hook-url-17,omitempty"`
|
|
||||||
HookURL18 string `json:"hook-url-18,omitempty"`
|
|
||||||
HookURL19 string `json:"hook-url-19,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BranchResp struct {
|
|
||||||
Values []*Branch `json:"values"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Branch struct {
|
|
||||||
Name string `json:"displayId"`
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
// 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 bitbucketserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/forge/bitbucketserver/internal"
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
// parseHook parses a Bitbucket hook from an http.Request request and returns
|
|
||||||
// Repo and Pipeline detail. TODO: find a way to support PR hooks
|
|
||||||
func parseHook(r *http.Request, baseURL string) (*model.Repo, *model.Pipeline, error) {
|
|
||||||
hook := new(internal.PostHook)
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(hook); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
pipeline := convertPushHook(hook, baseURL)
|
|
||||||
repo := convertRepo(&hook.Repository, &model.Perm{})
|
|
||||||
|
|
||||||
return repo, pipeline, nil
|
|
||||||
}
|
|
|
@ -21,7 +21,7 @@
|
||||||
<Icon v-if="forge === 'github'" name="github" />
|
<Icon v-if="forge === 'github'" name="github" />
|
||||||
<Icon v-else-if="forge === 'gitea'" name="gitea" />
|
<Icon v-else-if="forge === 'gitea'" name="gitea" />
|
||||||
<Icon v-else-if="forge === 'gitlab'" name="gitlab" />
|
<Icon v-else-if="forge === 'gitlab'" name="gitlab" />
|
||||||
<Icon v-else-if="forge === 'bitbucket' || forge === 'stash'" name="bitbucket" />
|
<Icon v-else-if="forge === 'bitbucket'" name="bitbucket" />
|
||||||
<Icon v-else name="repo" />
|
<Icon v-else name="repo" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
Loading…
Reference in a new issue