Skip to content

Commit

Permalink
Add support for Bitbucket Context path
Browse files Browse the repository at this point in the history
Signed-off-by: Gaurav Dasson <gaurav.dasson@gmail.com>
  • Loading branch information
gdasson committed Mar 30, 2024
1 parent e960fb1 commit b5ea6c1
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 56 deletions.
2 changes: 1 addition & 1 deletion docs/spec/v1beta2/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -1539,7 +1539,7 @@ 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.
**NOTE:** Please provide HTTPS clone URL in the `address` field of this provider. SSH URLs are not supported by this provider type.

#### Azure DevOps

Expand Down
2 changes: 1 addition & 1 deletion docs/spec/v1beta3/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -1567,7 +1567,7 @@ 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.
**NOTE:** Please provide HTTPS clone URL in the `address` field of this provider. SSH URLs are not supported by this provider type.

#### Azure DevOps

Expand Down
63 changes: 30 additions & 33 deletions internal/notifier/bitbucketserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,20 @@ 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
ContextPath string
Username string
Password string
Token string
Client *retryablehttp.Client
}

const (
bbServerEndPointTmpl = "%[1]s/rest/api/latest/projects/%[2]s/repos/%[3]s/commits/%[4]s/builds"
bbServerEndPointCommits = "%[1]s/rest/api/latest/projects/%[2]s/repos/%[3]s/commits"
bbServerEndPointTmplBuilds = "%[1]s/builds"
bbServerGetBuildStatusQueryString = "key"
bbServerSourceCodeMgmtString = "/scm/"
)

type bbServerBuildStatus struct {
Expand Down Expand Up @@ -83,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, cntxtPath, 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{
Expand All @@ -115,12 +107,9 @@ func NewBitbucketServer(providerUID string, addr string, token string, certPool
}

return &BitbucketServer{
ProjectKey: projectkey,
RepositorySlug: reposlug,
ProviderUID: providerUID,
Host: hst,
Url: url,
ProviderAddress: addr,
ContextPath: cntxtPath,
Token: token,
Username: username,
Password: password,
Expand Down Expand Up @@ -153,7 +142,7 @@ 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)
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)
Expand Down Expand Up @@ -259,35 +248,43 @@ func (b BitbucketServer) postBuildStatus(ctx context.Context, state, name, desc,
return resp, nil
}

func (b BitbucketServer) createApiPath(rev string) string {
return fmt.Sprintf(bbServerEndPointTmpl, b.ContextPath, b.ProjectKey, b.RepositorySlug, rev)
func (b BitbucketServer) createBuildPath(rev string) string {
return fmt.Sprintf(bbServerEndPointTmplBuilds, rev)
}

func parseBitbucketServerGitAddress(s string) (string, string, string, error) {
scheme := strings.Split(s, ":")[0]
if scheme != "http" && scheme != "https" {
return "", "", "", fmt.Errorf("could not parse git address: unsupported scheme type in address: %s. Must be http or https", scheme)
}

host, idWithContext, 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 &url.URL{}, fmt.Errorf("could not parse git address: %w", err)
}
if u.Scheme != "http" && u.Scheme != "https" {
return &url.URL{}, fmt.Errorf("could not parse git address: unsupported scheme type in address: %s. Must be http or https", u.Scheme)
}

idWithContext = "/" + idWithContext
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, "/scm/")
lastIndex := strings.LastIndex(idWithContext, bbServerSourceCodeMgmtString)
if lastIndex < 0 {
return "", "", "", fmt.Errorf("could not parse git address: supplied provider address is not http(s) git clone url")
return &url.URL{}, 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+5:] // Remove last `/scm/` from id as it's is not used in API calls
id := idWithContext[lastIndex+len(bbServerSourceCodeMgmtString):] // Remove last `/scm/` from id as it's is not used in API calls

comp := strings.Split(id, "/")
if len(comp) != 2 {
return &url.URL{}, 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(bbServerEndPointCommits, cntxtPath, projectkey, reposlug)

return host, cntxtPath, id, nil
return u, nil
}

func (b BitbucketServer) prepareCommonRequest(ctx context.Context, path string, body io.Reader, method string) (*retryablehttp.Request, error) {
Expand Down
44 changes: 23 additions & 21 deletions internal/notifier/bitbucketserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,65 +38,67 @@ func TestNewBitbucketServerBasicNoContext(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, b.Username, "dummyuser")
assert.Equal(t, b.Password, "testpassword")
assert.Equal(t, b.ContextPath, "")
assert.Equal(t, b.Host, "https://example.com:7990")
assert.Equal(t, b.ProjectKey, "projectfoo")
assert.Equal(t, b.RepositorySlug, "repobar")
//assert.Equal(t, b.ContextPath, "")
assert.Equal(t, b.Url.Scheme, "https")
assert.Equal(t, b.Url.Host, "example.com:7990")
//assert.Equal(t, b.ProjectKey, "projectfoo")
//assert.Equal(t, b.RepositorySlug, "repobar")
}

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.ContextPath, "/context")
assert.Equal(t, b.Host, "https://example.com:7990")
assert.Equal(t, b.ProjectKey, "projectfoo")
assert.Equal(t, b.RepositorySlug, "repobar")
//assert.Equal(t, b.ContextPath, "/context")
assert.Equal(t, b.Url.Scheme, "https")
assert.Equal(t, b.Url.Host, "example.com:7990")
//assert.Equal(t, b.ProjectKey, "projectfoo")
//assert.Equal(t, b.RepositorySlug, "repobar")
}

func TestApiPathNoContext(t *testing.T) {
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.Host + b.createApiPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")
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 TestApiPathOneWordContext(t *testing.T) {
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.Host + b.createApiPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")
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 TestApiPathMultipleWordContext(t *testing.T) {
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.Host + b.createApiPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")
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 TestApiPathOneWordScmInContext(t *testing.T) {
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.Host + b.createApiPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")
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 TestApiPathMultipleWordScmInContext(t *testing.T) {
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.Host + b.createApiPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")
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 TestApiPathScmAlreadyRemovedInInput(t *testing.T) {
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 TestSshAddress(t *testing.T) {
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")
Expand All @@ -117,7 +119,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) {
Expand Down

0 comments on commit b5ea6c1

Please sign in to comment.