Merge pull request #747 from gdasson/fix_742

Add support for Bitbucket Context path
This commit is contained in:
Stefan Prodan 2024-04-23 17:16:49 +03:00 committed by GitHub
commit 678f374bc3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 111 additions and 33 deletions

View File

@ -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 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
When `.spec.type` is set to `azuredevops`, the referenced secret must contain a key called `token` with the value set to a

View File

@ -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 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
When `.spec.type` is set to `azuredevops`, the referenced secret must contain a key called `token` with the value set to a

View File

@ -37,11 +37,9 @@ import (
// BitbucketServer is a notifier for BitBucket Server and Data Center.
type BitbucketServer struct {
ProjectKey string
RepositorySlug string
ProviderUID string
Url *url.URL
ProviderAddress string
Host string
Username string
Password string
Token string
@ -49,8 +47,10 @@ type BitbucketServer struct {
}
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"
bbServerSourceCodeMgmtString = "/scm/"
)
type bbServerBuildStatus struct {
@ -82,18 +82,11 @@ type bbServerBuildStatusSetRequest struct {
// 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) {
hst, id, err := parseBitbucketServerGitAddress(addr)
url, err := parseBitbucketServerGitAddress(addr)
if err != nil {
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()
if certPool != nil {
httpClient.HTTPClient.Transport = &http.Transport{
@ -114,10 +107,8 @@ func NewBitbucketServer(providerUID string, addr string, token string, certPool
}
return &BitbucketServer{
ProjectKey: projectkey,
RepositorySlug: reposlug,
ProviderUID: providerUID,
Host: hst,
Url: url,
ProviderAddress: addr,
Token: token,
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 := sha1String(id)
u := b.Host + b.createApiPath(rev)
dupe, err := b.duplicateBitbucketServerStatus(ctx, rev, state, name, desc, id, key, u)
u := b.Url.JoinPath(b.createBuildPath(rev)).String()
dupe, err := b.duplicateBitbucketServerStatus(ctx, state, name, desc, key, u)
if err != nil {
return fmt.Errorf("could not get existing commit status: %w", err)
}
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 {
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
req, err := b.prepareCommonRequest(ctx, u, nil, http.MethodGet, key, rev)
req, err := b.prepareCommonRequest(ctx, u, nil, http.MethodGet)
if err != nil {
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
}
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
j := &bbServerBuildStatusSetRequest{
Key: key,
@ -235,7 +226,7 @@ func (b BitbucketServer) postBuildStatus(ctx context.Context, rev, state, name,
}
//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 {
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
}
func (b BitbucketServer) createApiPath(rev string) string {
return fmt.Sprintf(bbServerEndPointTmpl, b.ProjectKey, b.RepositorySlug, rev)
func (b BitbucketServer) createBuildPath(rev string) string {
return fmt.Sprintf(bbServerEndPointBuildsTmpl, rev)
}
func parseBitbucketServerGitAddress(s string) (string, string, error) {
host, id, err := parseGitAddress(s)
func parseBitbucketServerGitAddress(s string) (*url.URL, error) {
u, err := url.Parse(s)
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
id = strings.TrimPrefix(id, "scm/")
return host, id, nil
if u.Scheme != "http" && u.Scheme != "https" {
return nil, fmt.Errorf("could not parse git address: unsupported scheme type in address: %s. Must be http or https", u.Scheme)
}
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)
if err != nil {
return nil, fmt.Errorf("could not prepare request: %w", err)

View File

@ -33,11 +33,69 @@ import (
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")
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 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) {
@ -55,7 +113,7 @@ func TestNewBitbucketServerInvalidCreds(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, "", "")
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) {