Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions docs/content/docs/providers/bitbucket-datacenter.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,27 @@ This page covers how to configure Pipelines-as-Code with [Bitbucket Data Center]
## Prerequisites

- A running Pipelines-as-Code [installation]({{< relref "/docs/installation/installation" >}})
- A Bitbucket Data Center personal access token with `PROJECT_ADMIN` and `REPOSITORY_ADMIN` permissions (see below)
- A Bitbucket Data Center HTTP access token with `PROJECT_ADMIN` or `REPOSITORY_ADMIN` permissions (see below)
- The public URL of your Pipelines-as-Code controller route or ingress endpoint

## Create a Bitbucket Personal Access Token

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of removing info about access token, you should specify both that PaC supports both

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just matched the docs from Bitbucket, since personal, project or repository tokens are all called HTTP access tokens now.
So I am not removing info, just matching the same docs and added to the warning for the case of personal ones.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes but process to create them is different and the URL below is to show how to create personal access token not repo or project token...

## Create a Bitbucket HTTP Access Token

Generate a personal access token as the manager of the project by following the steps here:
Generate an HTTP access token using one of the following token types:

<https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html>
- Personal HTTP access token
- Repository HTTP access token
- Project HTTP access token

The token needs the `PROJECT_ADMIN` and `REPOSITORY_ADMIN` permissions. It also needs access to forked repositories in pull requests, otherwise Pipelines-as-Code cannot process and access the pull request.
For detailed instructions on creating and managing access tokens, refer to the official Bitbucket Data Center documentation:
<https://confluence.atlassian.com/bitbucketserver/http-access-tokens-939515499.html>

The token must have either `PROJECT_ADMIN` or `REPOSITORY_ADMIN` permissions. If Pipelines-as-Code needs to process pull requests from forked repositories, the token must also have administrative access to those forked repositories. Without this access, Pipelines-as-Code cannot retrieve the required pull request information or interact with the forked repository.

{{< callout type="info" >}}

The service account user that owns the token must be a **licensed Bitbucket
When using a personal HTTP token, the associated user must be a **licensed Bitbucket
user** (i.e., granted the `LICENSED_USER` global permission) for group-based
permission checks to work. If the service account is an unlicensed technical
permission checks to work. If the user account is an unlicensed technical
user, group membership cannot be resolved and users with group-only access
will not be able to trigger builds. As a workaround, add those users
individually to the project or repository permissions.
Expand Down
18 changes: 12 additions & 6 deletions pkg/provider/bitbucketdatacenter/bitbucketdatacenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,6 @@ func removeLastSegment(urlStr string) string {
}

func (v *Provider) SetClient(ctx context.Context, run *params.Run, event *info.Event, repo *v1alpha1.Repository, _ *events.EventEmitter) error {
if event.Provider.User == "" {
return fmt.Errorf("no spec.git_provider.user has been set in the repo crd")
}
if event.Provider.Token == "" {
return fmt.Errorf("no spec.git_provider.secret has been set in the repo crd")
}
Expand Down Expand Up @@ -317,14 +314,23 @@ func (v *Provider) SetClient(ctx context.Context, run *params.Run, event *info.E
v.run = run
v.repo = repo
v.triggerEvent = event.EventType
_, resp, err := v.Client().Users.FindLogin(ctx, event.Provider.User)

var resp *scm.Response
var err error
Comment thread
zakisk marked this conversation as resolved.
// we only need a valid token to access rest api
_, resp, err = v.Client().Users.Find(ctx)
if resp != nil && resp.Status == http.StatusUnauthorized {
return fmt.Errorf("cannot get user %s with token: %w", event.Provider.User, err)
return fmt.Errorf("token validation failed: unauthorized")
}
if resp != nil && resp.Status == http.StatusInternalServerError {
return fmt.Errorf("token validation failed: Internal Server Error")
}
Comment thread
Ru13en marked this conversation as resolved.
if err != nil {
return fmt.Errorf("cannot get user %s: %w", event.Provider.User, err)
return fmt.Errorf("token validation failed: http status: %d : %w", resp.Status, err)
}
Comment thread
zakisk marked this conversation as resolved.

// the token must have admin permissions at project or repository level

return nil
}

Expand Down
104 changes: 89 additions & 15 deletions pkg/provider/bitbucketdatacenter/bitbucketdatacenter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"testing"
"time"

"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/clients"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
Expand Down Expand Up @@ -309,13 +310,15 @@ func TestSetClient(t *testing.T) {
name string
apiURL string
opts *info.Event
repo *v1alpha1.Repository
wantErrSubstr string
muxToken func(w http.ResponseWriter, r *http.Request)
muxUser func(w http.ResponseWriter, r *http.Request)
}{
{
name: "bad/no username",
name: "bad/no token",
opts: info.NewEvent(),
wantErrSubstr: "no spec.git_provider.user",
wantErrSubstr: "no spec.git_provider.secret",
},
{
name: "bad/no secret",
Expand All @@ -330,57 +333,125 @@ func TestSetClient(t *testing.T) {
name: "bad/no url",
opts: &info.Event{
Provider: &info.Provider{
User: "foo",
Token: "bar",
},
},
wantErrSubstr: "no spec.git_provider.url",
},
{
name: "bad/invalid user",
name: "bad/invalid user in whomi",
opts: &info.Event{
Provider: &info.Provider{
Token: "bar",
URL: "https://fakebitbucketdc",
},
},
muxToken: func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"errors": [{"message": "Unauthorized"}]}`))
},
apiURL: "https://fakebitbucketdc/rest",
wantErrSubstr: "token validation failed: unauthorized",
},
{
name: "bad/invalid user at rest after whomi",
opts: &info.Event{
Provider: &info.Provider{
User: "foo",
Token: "bar",
URL: "https://foo.bar",
URL: "https://fakebitbucketdc",
},
},
muxToken: func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, `foo`)
},
muxUser: func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"errors": [{"message": "Unauthorized"}]}`))
},
apiURL: "https://foo.bar/rest",
wantErrSubstr: "cannot get user foo with token",
apiURL: "https://fakebitbucketdc/rest",
wantErrSubstr: "token validation failed: unauthorized",
},
{
name: "internal error at whoami",
opts: &info.Event{
Provider: &info.Provider{
User: "foo",
Token: "bar",
URL: "https://fakebitbucketdc",
},
},
muxToken: func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
},
apiURL: "https://fakebitbucketdc/rest",
wantErrSubstr: "token validation failed: Internal Server Error",
},
{
name: "bad/unknown error",
name: "not found at whoami",
opts: &info.Event{
Provider: &info.Provider{
User: "foo",
Token: "bar",
URL: "https://foo.bar",
URL: "https://fakebitbucketdc",
},
},
muxToken: func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNotFound)
},
apiURL: "https://fakebitbucketdc/rest",
wantErrSubstr: "token validation failed: http status: 404 : ",
},
{
name: "not found at whoami with error message",
opts: &info.Event{
Provider: &info.Provider{
User: "foo",
Token: "bar",
URL: "https://fakebitbucketdc",
},
},
muxToken: func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"errors": [{"message": "Not Found"}]}`))
},
apiURL: "https://fakebitbucketdc/rest",
wantErrSubstr: "token validation failed: http status: 404 : Not Found",
},
{
name: "internal error at users rest",
opts: &info.Event{
Provider: &info.Provider{
User: "foo",
Token: "bar",
URL: "https://fakebitbucketdc",
},
},
muxToken: func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, `foo`)
},
muxUser: func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(`{"errors": [{"message": "Internal Server Error"}]}`))
},
apiURL: "https://foo.bar/rest",
wantErrSubstr: "cannot get user foo: Internal Server Error",
apiURL: "https://fakebitbucketdc/rest",
wantErrSubstr: "token validation failed: Internal Server Error",
},
{
name: "good/url append /rest",
opts: &info.Event{
Provider: &info.Provider{
User: "foo",
Token: "bar",
URL: "https://foo.bar",
URL: "https://fakebitbucketdc",
},
},
muxToken: func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, `foo`)
},
muxUser: func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, `{"name": "foo"}`)
},
apiURL: "https://foo.bar/rest",
apiURL: "https://fakebitbucketdc/rest",
},
}
for _, tt := range tests {
Expand All @@ -395,11 +466,14 @@ func TestSetClient(t *testing.T) {
ctx, _ := rtesting.SetupFakeContext(t)
client, mux, tearDown, tURL := bbtest.SetupBBDataCenterClient()
defer tearDown()
if tt.muxToken != nil {
mux.HandleFunc("/whoami", tt.muxToken)
}
if tt.muxUser != nil {
mux.HandleFunc("/users/foo", tt.muxUser)
}
v := &Provider{client: client, baseURL: tURL}
err := v.SetClient(ctx, fakeRun, tt.opts, nil, nil)
err := v.SetClient(ctx, fakeRun, tt.opts, tt.repo, nil)
if tt.wantErrSubstr != "" {
assert.ErrorContains(t, err, tt.wantErrSubstr)
return
Expand Down
6 changes: 4 additions & 2 deletions pkg/provider/bitbucketdatacenter/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@ import (
)

var (
defaultAPIURL = "/rest/api/1.0"
buildAPIURL = "/rest/build-status/1.0"
defaultAPIURL = "/rest/api/1.0"
buildAPIURL = "/rest/build-status/1.0"
defaultApplinksURL = "/plugins/servlet/applinks"
)

func SetupBBDataCenterClient() (*scm.Client, *http.ServeMux, func(), string) {
mux := http.NewServeMux()
apiHandler := http.NewServeMux()
apiHandler.Handle(defaultAPIURL+"/", http.StripPrefix(defaultAPIURL, mux))
apiHandler.Handle(buildAPIURL+"/", http.StripPrefix(buildAPIURL, mux))
apiHandler.Handle(defaultApplinksURL+"/", http.StripPrefix(defaultApplinksURL, mux))
apiHandler.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintln(os.Stderr, "FAIL: Client.BaseURL path prefix is not preserved in the request URL:")
fmt.Fprintln(os.Stderr)
Expand Down
Loading