Skip to content
Open
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
12 changes: 6 additions & 6 deletions docs/content/docs/providers/bitbucket-datacenter.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ 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
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.

## Create a Bitbucket HTTP Access Token

Generate a personal access token as the manager of the project by following the steps here:
Generate a HTTP access token as the manager of the project or repository by following the steps here:

<https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html>

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.
The token needs the `PROJECT_ADMIN` or `REPOSITORY_ADMIN` permissions. It also needs admin access to forked repositories in pull requests, otherwise Pipelines-as-Code cannot process and access the pull request.

{{< 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 on lines +325 to 327
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.

Suggested change
if resp != nil && resp.Status == http.StatusInternalServerError {
return fmt.Errorf("token validation failed: Internal Server Error")
}

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.

this is very less likely to happen...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

you assume... I know well enough the bitbucket when we do upgrades, sometimes plugins have strange behaviors during the upgrade requiring further reboots of the BB DC.
With this change now not only we interact with the REST but also with the applinks plugin (installed together but managed independently).

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