Merge pull request #747 from gdasson/fix_742
Add support for Bitbucket Context path
This commit is contained in:
commit
678f374bc3
|
|
@ -1539,6 +1539,8 @@ kubectl create secret generic bb-server-token --from-literal=token=<token>
|
||||||
The HTTP access token must have `Repositories (Read/Write)` permission for
|
The HTTP access token must have `Repositories (Read/Write)` permission for
|
||||||
the repository specified in `.spec.address`.
|
the repository specified in `.spec.address`.
|
||||||
|
|
||||||
|
**NOTE:** Please provide HTTPS clone URL in the `address` field of this provider. SSH URLs are not supported by this provider type.
|
||||||
|
|
||||||
#### Azure DevOps
|
#### Azure DevOps
|
||||||
|
|
||||||
When `.spec.type` is set to `azuredevops`, the referenced secret must contain a key called `token` with the value set to a
|
When `.spec.type` is set to `azuredevops`, the referenced secret must contain a key called `token` with the value set to a
|
||||||
|
|
|
||||||
|
|
@ -1567,6 +1567,8 @@ kubectl create secret generic bb-server-token --from-literal=token=<token>
|
||||||
The HTTP access token must have `Repositories (Read/Write)` permission for
|
The HTTP access token must have `Repositories (Read/Write)` permission for
|
||||||
the repository specified in `.spec.address`.
|
the repository specified in `.spec.address`.
|
||||||
|
|
||||||
|
**NOTE:** Please provide HTTPS clone URL in the `address` field of this provider. SSH URLs are not supported by this provider type.
|
||||||
|
|
||||||
#### Azure DevOps
|
#### Azure DevOps
|
||||||
|
|
||||||
When `.spec.type` is set to `azuredevops`, the referenced secret must contain a key called `token` with the value set to a
|
When `.spec.type` is set to `azuredevops`, the referenced secret must contain a key called `token` with the value set to a
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,9 @@ import (
|
||||||
|
|
||||||
// BitbucketServer is a notifier for BitBucket Server and Data Center.
|
// BitbucketServer is a notifier for BitBucket Server and Data Center.
|
||||||
type BitbucketServer struct {
|
type BitbucketServer struct {
|
||||||
ProjectKey string
|
|
||||||
RepositorySlug string
|
|
||||||
ProviderUID string
|
ProviderUID string
|
||||||
|
Url *url.URL
|
||||||
ProviderAddress string
|
ProviderAddress string
|
||||||
Host string
|
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
Token string
|
Token string
|
||||||
|
|
@ -49,8 +47,10 @@ type BitbucketServer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
bbServerEndPointTmpl = "/rest/api/latest/projects/%[1]s/repos/%[2]s/commits/%[3]s/builds"
|
bbServerEndPointCommitsTmpl = "%[1]s/rest/api/latest/projects/%[2]s/repos/%[3]s/commits"
|
||||||
|
bbServerEndPointBuildsTmpl = "%[1]s/builds"
|
||||||
bbServerGetBuildStatusQueryString = "key"
|
bbServerGetBuildStatusQueryString = "key"
|
||||||
|
bbServerSourceCodeMgmtString = "/scm/"
|
||||||
)
|
)
|
||||||
|
|
||||||
type bbServerBuildStatus struct {
|
type bbServerBuildStatus struct {
|
||||||
|
|
@ -82,18 +82,11 @@ type bbServerBuildStatusSetRequest struct {
|
||||||
|
|
||||||
// NewBitbucketServer creates and returns a new BitbucketServer notifier.
|
// NewBitbucketServer creates and returns a new BitbucketServer notifier.
|
||||||
func NewBitbucketServer(providerUID string, addr string, token string, certPool *x509.CertPool, username string, password string) (*BitbucketServer, error) {
|
func NewBitbucketServer(providerUID string, addr string, token string, certPool *x509.CertPool, username string, password string) (*BitbucketServer, error) {
|
||||||
hst, id, err := parseBitbucketServerGitAddress(addr)
|
url, err := parseBitbucketServerGitAddress(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
comp := strings.Split(id, "/")
|
|
||||||
if len(comp) != 2 {
|
|
||||||
return nil, fmt.Errorf("invalid repository id %q", id)
|
|
||||||
}
|
|
||||||
projectkey := comp[0]
|
|
||||||
reposlug := comp[1]
|
|
||||||
|
|
||||||
httpClient := retryablehttp.NewClient()
|
httpClient := retryablehttp.NewClient()
|
||||||
if certPool != nil {
|
if certPool != nil {
|
||||||
httpClient.HTTPClient.Transport = &http.Transport{
|
httpClient.HTTPClient.Transport = &http.Transport{
|
||||||
|
|
@ -114,10 +107,8 @@ func NewBitbucketServer(providerUID string, addr string, token string, certPool
|
||||||
}
|
}
|
||||||
|
|
||||||
return &BitbucketServer{
|
return &BitbucketServer{
|
||||||
ProjectKey: projectkey,
|
|
||||||
RepositorySlug: reposlug,
|
|
||||||
ProviderUID: providerUID,
|
ProviderUID: providerUID,
|
||||||
Host: hst,
|
Url: url,
|
||||||
ProviderAddress: addr,
|
ProviderAddress: addr,
|
||||||
Token: token,
|
Token: token,
|
||||||
Username: username,
|
Username: username,
|
||||||
|
|
@ -151,14 +142,14 @@ func (b BitbucketServer) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
// key has a limitation of 40 characters in bitbucket api
|
// key has a limitation of 40 characters in bitbucket api
|
||||||
key := sha1String(id)
|
key := sha1String(id)
|
||||||
|
|
||||||
u := b.Host + b.createApiPath(rev)
|
u := b.Url.JoinPath(b.createBuildPath(rev)).String()
|
||||||
dupe, err := b.duplicateBitbucketServerStatus(ctx, rev, state, name, desc, id, key, u)
|
dupe, err := b.duplicateBitbucketServerStatus(ctx, state, name, desc, key, u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not get existing commit status: %w", err)
|
return fmt.Errorf("could not get existing commit status: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !dupe {
|
if !dupe {
|
||||||
_, err = b.postBuildStatus(ctx, rev, state, name, desc, id, key, u)
|
_, err = b.postBuildStatus(ctx, state, name, desc, key, u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not post build status: %w", err)
|
return fmt.Errorf("could not post build status: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -178,9 +169,9 @@ func (b BitbucketServer) state(severity string) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b BitbucketServer) duplicateBitbucketServerStatus(ctx context.Context, rev, state, name, desc, id, key, u string) (bool, error) {
|
func (b BitbucketServer) duplicateBitbucketServerStatus(ctx context.Context, state, name, desc, key, u string) (bool, error) {
|
||||||
// Prepare request object
|
// Prepare request object
|
||||||
req, err := b.prepareCommonRequest(ctx, u, nil, http.MethodGet, key, rev)
|
req, err := b.prepareCommonRequest(ctx, u, nil, http.MethodGet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("could not check duplicate commit status: %w", err)
|
return false, fmt.Errorf("could not check duplicate commit status: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -219,7 +210,7 @@ func (b BitbucketServer) duplicateBitbucketServerStatus(ctx context.Context, rev
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b BitbucketServer) postBuildStatus(ctx context.Context, rev, state, name, desc, id, key, url string) (*http.Response, error) {
|
func (b BitbucketServer) postBuildStatus(ctx context.Context, state, name, desc, key, url string) (*http.Response, error) {
|
||||||
//Prepare json body
|
//Prepare json body
|
||||||
j := &bbServerBuildStatusSetRequest{
|
j := &bbServerBuildStatusSetRequest{
|
||||||
Key: key,
|
Key: key,
|
||||||
|
|
@ -235,7 +226,7 @@ func (b BitbucketServer) postBuildStatus(ctx context.Context, rev, state, name,
|
||||||
}
|
}
|
||||||
|
|
||||||
//Prepare request
|
//Prepare request
|
||||||
req, err := b.prepareCommonRequest(ctx, url, p, http.MethodPost, key, rev)
|
req, err := b.prepareCommonRequest(ctx, url, p, http.MethodPost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed preparing request for post build commit status: %w", err)
|
return nil, fmt.Errorf("failed preparing request for post build commit status: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -257,21 +248,46 @@ func (b BitbucketServer) postBuildStatus(ctx context.Context, rev, state, name,
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b BitbucketServer) createApiPath(rev string) string {
|
func (b BitbucketServer) createBuildPath(rev string) string {
|
||||||
return fmt.Sprintf(bbServerEndPointTmpl, b.ProjectKey, b.RepositorySlug, rev)
|
return fmt.Sprintf(bbServerEndPointBuildsTmpl, rev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBitbucketServerGitAddress(s string) (string, string, error) {
|
func parseBitbucketServerGitAddress(s string) (*url.URL, error) {
|
||||||
host, id, err := parseGitAddress(s)
|
u, err := url.Parse(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("could not parse git address: %w", err)
|
return nil, fmt.Errorf("could not parse git address: %w", err)
|
||||||
}
|
}
|
||||||
//Remove "scm/" --> https://community.atlassian.com/t5/Bitbucket-questions/remote-url-in-Bitbucket-server-what-does-scm-represent-is-it/qaq-p/2060987
|
if u.Scheme != "http" && u.Scheme != "https" {
|
||||||
id = strings.TrimPrefix(id, "scm/")
|
return nil, fmt.Errorf("could not parse git address: unsupported scheme type in address: %s. Must be http or https", u.Scheme)
|
||||||
return host, id, nil
|
}
|
||||||
|
|
||||||
|
idWithContext := strings.TrimSuffix(u.Path, ".git")
|
||||||
|
|
||||||
|
// /scm/ is always part of http/https clone urls : https://community.atlassian.com/t5/Bitbucket-questions/remote-url-in-Bitbucket-server-what-does-scm-represent-is-it/qaq-p/2060987
|
||||||
|
lastIndex := strings.LastIndex(idWithContext, bbServerSourceCodeMgmtString)
|
||||||
|
if lastIndex < 0 {
|
||||||
|
return nil, fmt.Errorf("could not parse git address: supplied provider address is not http(s) git clone url")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle context scenarios --> https://confluence.atlassian.com/bitbucketserver/change-bitbucket-s-context-path-776640153.html
|
||||||
|
cntxtPath := idWithContext[:lastIndex] // Context path is anything that comes before last /scm/
|
||||||
|
|
||||||
|
id := idWithContext[lastIndex+len(bbServerSourceCodeMgmtString):] // Remove last `/scm/` from id as it is not used in API calls
|
||||||
|
|
||||||
|
comp := strings.Split(id, "/")
|
||||||
|
if len(comp) != 2 {
|
||||||
|
return nil, fmt.Errorf("could not parse git address: invalid repository id %q", id)
|
||||||
|
}
|
||||||
|
projectkey := comp[0]
|
||||||
|
reposlug := comp[1]
|
||||||
|
|
||||||
|
// Update the path till commits endpoint. The final builds endpoint would be added in Post function.
|
||||||
|
u.Path = fmt.Sprintf(bbServerEndPointCommitsTmpl, cntxtPath, projectkey, reposlug)
|
||||||
|
|
||||||
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b BitbucketServer) prepareCommonRequest(ctx context.Context, path string, body io.Reader, method string, key, rev string) (*retryablehttp.Request, error) {
|
func (b BitbucketServer) prepareCommonRequest(ctx context.Context, path string, body io.Reader, method string) (*retryablehttp.Request, error) {
|
||||||
req, err := retryablehttp.NewRequestWithContext(ctx, method, path, body)
|
req, err := retryablehttp.NewRequestWithContext(ctx, method, path, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not prepare request: %w", err)
|
return nil, fmt.Errorf("could not prepare request: %w", err)
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,69 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewBitbucketServerBasic(t *testing.T) {
|
func TestNewBitbucketServerBasicNoContext(t *testing.T) {
|
||||||
b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, b.Username, "dummyuser")
|
assert.Equal(t, b.Username, "dummyuser")
|
||||||
assert.Equal(t, b.Password, "testpassword")
|
assert.Equal(t, b.Password, "testpassword")
|
||||||
|
assert.Equal(t, b.Url.Scheme, "https")
|
||||||
|
assert.Equal(t, b.Url.Host, "example.com:7990")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewBitbucketServerBasicWithContext(t *testing.T) {
|
||||||
|
b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/context/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, b.Username, "dummyuser")
|
||||||
|
assert.Equal(t, b.Password, "testpassword")
|
||||||
|
assert.Equal(t, b.Url.Scheme, "https")
|
||||||
|
assert.Equal(t, b.Url.Host, "example.com:7990")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketServerApiPathNoContext(t *testing.T) {
|
||||||
|
b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u := b.Url.JoinPath(b.createBuildPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")).String()
|
||||||
|
assert.Equal(t, u, "https://example.com:7990/rest/api/latest/projects/projectfoo/repos/repobar/commits/00151b98e303e19610378e6f1c49e31e5e80cd3b/builds")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketServerApiPathOneWordContext(t *testing.T) {
|
||||||
|
b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/context1/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u := b.Url.JoinPath(b.createBuildPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")).String()
|
||||||
|
assert.Equal(t, u, "https://example.com:7990/context1/rest/api/latest/projects/projectfoo/repos/repobar/commits/00151b98e303e19610378e6f1c49e31e5e80cd3b/builds")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketServerApiPathMultipleWordContext(t *testing.T) {
|
||||||
|
b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/context1/context2/context3/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u := b.Url.JoinPath(b.createBuildPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")).String()
|
||||||
|
assert.Equal(t, u, "https://example.com:7990/context1/context2/context3/rest/api/latest/projects/projectfoo/repos/repobar/commits/00151b98e303e19610378e6f1c49e31e5e80cd3b/builds")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketServerApiPathOneWordScmInContext(t *testing.T) {
|
||||||
|
b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u := b.Url.JoinPath(b.createBuildPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")).String()
|
||||||
|
assert.Equal(t, u, "https://example.com:7990/scm/rest/api/latest/projects/projectfoo/repos/repobar/commits/00151b98e303e19610378e6f1c49e31e5e80cd3b/builds")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketServerApiPathMultipleWordScmInContext(t *testing.T) {
|
||||||
|
b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/context2/scm/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u := b.Url.JoinPath(b.createBuildPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")).String()
|
||||||
|
assert.Equal(t, u, "https://example.com:7990/scm/context2/scm/rest/api/latest/projects/projectfoo/repos/repobar/commits/00151b98e303e19610378e6f1c49e31e5e80cd3b/builds")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketServerApiPathScmAlreadyRemovedInInput(t *testing.T) {
|
||||||
|
_, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/context1/context2/context3/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, err.Error(), "could not parse git address: supplied provider address is not http(s) git clone url")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketServerSshAddress(t *testing.T) {
|
||||||
|
_, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "ssh://git@mybitbucket:2222/ap/fluxcd-sandbox.git", "", nil, "", "")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, err.Error(), "could not parse git address: unsupported scheme type in address: ssh. Must be http or https")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBitbucketServerToken(t *testing.T) {
|
func TestNewBitbucketServerToken(t *testing.T) {
|
||||||
|
|
@ -55,7 +113,7 @@ func TestNewBitbucketServerInvalidCreds(t *testing.T) {
|
||||||
func TestNewBitbucketServerInvalidRepo(t *testing.T) {
|
func TestNewBitbucketServerInvalidRepo(t *testing.T) {
|
||||||
_, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar/invalid.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "")
|
_, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar/invalid.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "")
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
assert.Equal(t, err.Error(), "invalid repository id \"projectfoo/repobar/invalid\"")
|
assert.Equal(t, err.Error(), "could not parse git address: invalid repository id \"projectfoo/repobar/invalid\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPostBitbucketServerMissingRevision(t *testing.T) {
|
func TestPostBitbucketServerMissingRevision(t *testing.T) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue