Skip to content

Commit

Permalink
Merge pull request #964 from HelloCore/feature/get-bitbucket-cloud-uu…
Browse files Browse the repository at this point in the history
…id-from-api

Bitbucket Cloud: Getting UUID from API
  • Loading branch information
orta committed Dec 6, 2019
2 parents 377d18f + 48ea40e commit 85c686f
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 36 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ x

<!-- Your comment below this -->

<!-- Your comment above this -->
- Bitbucket Cloud: Allow DangerCI to get UUID from Bitbucket - [@hellocore]
<!-- Your comment above this -->

# 9.2.7-8-9

Expand Down
11 changes: 3 additions & 8 deletions docs/guides/the_dangerfile.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,14 @@ export DANGER_BITBUCKETSERVER_HOST='xxxx' DANGER_BITBUCKETSERVER_USERNAME='yyyy'
# or for BitBucket by username and personal access token
export DANGER_BITBUCKETSERVER_HOST='xxxx' DANGER_BITBUCKETSERVER_USERNAME='yyyy' DANGER_BITBUCKETSERVER_TOKEN='zzzz'

# or for BitBucket Cloud by username (from Account Settings page), password (App-password with Read Pull requests Permission is enough), and UUID
# We need UUID for updating comment, you can get it from the link on your home page of bitbucket.org
# For example, https://bitbucket.org/%7Bzzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz%7D/
# Then replace "%7B" with "{" and "%7D" with "}"
# or for BitBucket Cloud by username (from Account Settings page), password (App-password with Read Pull requests, and Read Account Permissions)
export DANGER_BITBUCKETCLOUD_USERNAME='xxxx'
export DANGER_BITBUCKETCLOUD_PASSWORD='yyyy'
export DANGER_BITBUCKETCLOUD_UUID='{zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz}'

# or for BitBucket Cloud by OAuth key, and OAuth secret, UUID
# You can get OAuth key from Settings > OAuth > Add consumer, put `https://bitbucket.org/site/oauth2/authorize` for `Callback URL`, and enable Pull requests Permission.
# or for BitBucket Cloud by OAuth key, and OAuth secret
# You can get OAuth key from Settings > OAuth > Add consumer, put `https://bitbucket.org/site/oauth2/authorize` for `Callback URL`, and enable Read Pull requests, and Read Account Permissions.
export DANGER_BITBUCKETCLOUD_OAUTH_KEY='xxxx'
export DANGER_BITBUCKETCLOUD_OAUTH_SECRET='yyyy'
export DANGER_BITBUCKETCLOUD_UUID='{zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz}'
```

Then the danger CLI will use authenticated API calls, which don't get this by API limits.
Expand Down
13 changes: 3 additions & 10 deletions docs/usage/bitbucket_cloud.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,6 @@ blurb: An overview of using Danger with BitBucket Cloud, and some examples
To use Danger JS with BitBucket Cloud: you'll need to create a new account for Danger to use, then set the following
environment variables on your CI:

We need UUID for updating comment, you can get it from the link on your home page of bitbucket.org. Then replace `%7B`
with `{` and `%7D` with `}`.

For example, the UUID of `https://bitbucket.org/%7Bzzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz%7D/` will be
`{zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz}`

- `DANGER_BITBUCKETCLOUD_UUID` = The uuid for the account used to comment.

You could use either username with password or OAuth key with OAuth secret.

For username and password, you need to set.
Expand All @@ -25,13 +17,14 @@ For username and password, you need to set.
https://bitbucket.org/account/
- `DANGER_BITBUCKETCLOUD_PASSWORD` = The password for the account used to comment, you could use
[App passwords](https://confluence.atlassian.com/bitbucket/app-passwords-828781300.html#Apppasswords-Aboutapppasswords)
with Read Pull Requests Permission.
with Read Pull Requests and Read Account Permissions.

For OAuth key and OAuth secret, you can get them from.

- Open [BitBucket Cloud Website](https://bitbucket.org)
- Navigate to Settings > OAuth > Add consumer
- Put `https://bitbucket.org/site/oauth2/authorize` for `Callback URL`, and enable Pull requests Permission.
- Put `https://bitbucket.org/site/oauth2/authorize` for `Callback URL`, and enable Read Pull requests, and Read Account
Permission.

- `DANGER_BITBUCKETCLOUD_OAUTH_KEY` = The consumer key for the account used to comment, as show as `Key` on the website.
- `DANGER_BITBUCKETCLOUD_OAUTH_SECRET` = The consumer secret for the account used to comment, as show as `Secret` on the
Expand Down
2 changes: 1 addition & 1 deletion source/ci_source/ci_source_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export async function getPullRequestIDForBranch(metadata: RepoMetaData, env: Env
}
return 0
}
if (process.env["DANGER_BITBUCKETCLOUD_UUID"]) {
if (process.env["DANGER_BITBUCKETCLOUD_OAUTH_KEY"] || process.env["DANGER_BITBUCKETCLOUD_USERNAME"]) {
const api = new BitBucketCloudAPI(metadata, bitbucketCloudCredentialsFromEnv(env))
const prs = await api.getPullRequestsFromBranch(branch)
if (prs.length) {
Expand Down
1 change: 0 additions & 1 deletion source/ci_source/providers/BitbucketPipelines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { ensureEnvKeysExist, ensureEnvKeysAreInt } from "../ci_source_helpers"
*
* ### Token Setup
*
* Add `DANGER_BITBUCKETCLOUD_UUID` to your pipeline repository variable.
* You can either add `DANGER_BITBUCKETCLOUD_USERNAME`, `DANGER_BITBUCKETCLOUD_PASSWORD`
* or add `DANGER_BITBUCKETCLOUD_OAUTH_KEY`, `DANGER_BITBUCKETCLOUD_OAUTH_SECRET`
* -
Expand Down
5 changes: 3 additions & 2 deletions source/commands/danger-pr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ program
if (
!process.env["DANGER_GITHUB_API_TOKEN"] &&
!process.env["DANGER_BITBUCKETSERVER_HOST"] &&
!process.env["DANGER_BITBUCKETCLOUD_UUID"] &&
!process.env["DANGER_BITBUCKETCLOUD_OAUTH_KEY"] &&
!process.env["DANGER_BITBUCKETCLOUD_USERNAME"] &&
!gitLabApiCredentials.token
) {
log("")
log(
" You don't have a DANGER_GITHUB_API_TOKEN/DANGER_GITLAB_API_TOKEN/DANGER_BITBUCKETCLOUD_UUID set up, this is optional, but TBH, you want to do this."
" You don't have a DANGER_GITHUB_API_TOKEN/DANGER_GITLAB_API_TOKEN/DANGER_BITBUCKETCLOUD_OAUTH_KEY/DANGER_BITBUCKETCLOUD_USERNAME set up, this is optional, but TBH, you want to do this."
)
log(" Check out: http://danger.systems/js/guides/the_dangerfile.html#working-on-your-dangerfile")
log("")
Expand Down
35 changes: 28 additions & 7 deletions source/platforms/bitbucket_cloud/BitBucketCloudAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { RepoMetaData } from "../../dsl/BitBucketServerDSL"

export type BitBucketCloudCredentials = {
/** Unique ID for this user, must be wrapped with brackets */
uuid: string
uuid?: string
} & (BitBucketCloudCredentialsOAuth | BitBucketCloudCredentialsPassword)

interface BitBucketCloudCredentialsOAuth {
Expand All @@ -35,9 +35,11 @@ interface BitBucketCloudCredentialsPassword {
}

export function bitbucketCloudCredentialsFromEnv(env: Env): BitBucketCloudCredentials {
const uuid = `${env["DANGER_BITBUCKETCLOUD_UUID"]}`
if (!uuid.startsWith("{") || !uuid.endsWith("}")) {
throw new Error(`DANGER_BITBUCKETCLOUD_UUID must be wraped with brackets`)
const uuid: string | undefined = env["DANGER_BITBUCKETCLOUD_UUID"]
if (uuid != null && uuid.length > 0) {
if (!uuid.startsWith("{") || !uuid.endsWith("}")) {
throw new Error(`DANGER_BITBUCKETCLOUD_UUID must be wraped with brackets`)
}
}

if (env["DANGER_BITBUCKETCLOUD_OAUTH_KEY"]) {
Expand Down Expand Up @@ -67,6 +69,7 @@ export function bitbucketCloudCredentialsFromEnv(env: Env): BitBucketCloudCreden
export class BitBucketCloudAPI {
fetch: typeof fetch
accessToken: string | undefined
uuid: string | undefined

private readonly d = debug("BitBucketCloudAPI")
private pr: BitBucketCloudPRDSL | undefined
Expand All @@ -78,6 +81,9 @@ export class BitBucketCloudAPI {
// This allows Peril to DI in a new Fetch function
// which can handle unique API edge-cases around integrations
this.fetch = fetch

// Backward compatible,
this.uuid = credentials.uuid
}

getBaseRepoURL() {
Expand Down Expand Up @@ -214,8 +220,7 @@ export class BitBucketCloudAPI {

return comments
.filter(comment => comment.content.raw.includes(dangerIDMessage))
.filter(comment => comment.user.uuid === this.credentials.uuid)
.filter(comment => comment.content.raw.includes("Generated by"))
.filter(comment => comment.user.uuid === this.uuid)
}

getDangerInlineComments = async (dangerID: string): Promise<Comment[]> => {
Expand All @@ -224,7 +229,7 @@ export class BitBucketCloudAPI {

return comments.filter(comment => comment.inline).map(comment => ({
id: comment.id.toString(),
ownedByDanger: comment.content.raw.includes(dangerIDMessage) && comment.user.uuid === this.credentials.uuid,
ownedByDanger: comment.content.raw.includes(dangerIDMessage) && comment.user.uuid === this.uuid,
body: comment.content.raw,
}))
}
Expand Down Expand Up @@ -309,6 +314,7 @@ export class BitBucketCloudAPI {
).toString("base64")}`
} else {
if (this.accessToken == null) {
this.d(`accessToken not found, trying to get from ${this.oauthURL}.`)
const params = new URLSearchParams()

params.append("grant_type", "client_credentials")
Expand Down Expand Up @@ -337,6 +343,21 @@ export class BitBucketCloudAPI {
headers["Authorization"] = `Bearer ${this.accessToken}`
}

if (this.uuid == null) {
this.d(`UUID not found, trying to get from ${this.baseURL}/user`)
const profileResponse = await this.performAPI(`${this.baseURL}/user`, headers, null, "GET", suppressErrors)
if (profileResponse.ok) {
const jsonResp = await profileResponse.json()
this.uuid = jsonResp["uuid"]
} else {
let message = `${profileResponse.status} - ${profileResponse.statusText}`
if (profileResponse.status >= 400 && profileResponse.status < 500) {
message += ` (Have you allowed permission 'account' for this credential?)`
}
throw new Error(message)
}
}

return this.performAPI(url, headers, body, method, suppressErrors)
}

Expand Down
135 changes: 135 additions & 0 deletions source/platforms/bitbucket_cloud/_tests_/_bitbucket_cloud_api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,4 +382,139 @@ describe("API testing - BitBucket Cloud", () => {

expect(result).toEqual({ pr: "info" })
})

it("should fetch uuid if not exists given username", async () => {
api = new BitBucketCloudAPI(
{ repoSlug: "foo/bar", pullRequestID: "1" },
{
type: "PASSWORD",
username: "foo",
password: "bar",
}
)
let requestNo = 0
let fetch = jest.fn().mockReturnValue({
status: 200,
ok: true,
json: () => {
requestNo += 1
if (requestNo === 1) {
return {
uuid: "{1234-1234-1234-1234}",
}
} else {
return { pr: "info" }
}
},
text: () => textResult,
})

api.fetch = fetch

const result = await api.getPullRequestInfo()
expect(api.fetch).toBeCalledTimes(2)
expect(fetch.mock.calls[0][0]).toBe("https://api.bitbucket.org/2.0/user")
expect(result).toEqual({ pr: "info" })
})

it("should fetch uuid if not exists given oauth key", async () => {
api = new BitBucketCloudAPI(
{ repoSlug: "foo/bar", pullRequestID: "1" },
{
type: "OAUTH",
oauthSecret: "superSecretOAUTH",
oauthKey: "superOAUTHKey",
}
)
let requestNo = 0
let fetch = jest.fn().mockReturnValue({
status: 200,
ok: true,
json: () => {
requestNo += 1
if (requestNo === 1) {
return {
access_token: "bla bla bla bla",
}
} else if (requestNo === 2) {
return {
uuid: "{1234-1234-1234-1234}",
}
} else {
return { pr: "info" }
}
},
text: () => textResult,
})

api.fetch = fetch

const result = await api.getPullRequestInfo()
expect(api.fetch).toBeCalledTimes(3)
expect(fetch.mock.calls[1][0]).toBe("https://api.bitbucket.org/2.0/user")
expect(result).toEqual({ pr: "info" })
})

it("should fetch uuid if not exists given accessToken", async () => {
api = new BitBucketCloudAPI(
{ repoSlug: "foo/bar", pullRequestID: "1" },
{
type: "OAUTH",
oauthSecret: "superSecretOAUTH",
oauthKey: "superOAUTHKey",
}
)
let requestNo = 0
let fetch = jest.fn().mockReturnValue({
status: 200,
ok: true,
json: () => {
requestNo += 1
if (requestNo === 1) {
return {
uuid: "{1234-1234-1234-1234}",
}
} else {
return { pr: "info" }
}
},
text: () => textResult,
})

api.fetch = fetch
api.accessToken = "bla bla bla bla"

const result = await api.getPullRequestInfo()
expect(api.fetch).toBeCalledTimes(2)
expect(fetch.mock.calls[0][0]).toBe("https://api.bitbucket.org/2.0/user")
expect(result).toEqual({ pr: "info" })
})

it("shouldn't fetch uuid if uuid exists (from api calling)", async () => {
api = new BitBucketCloudAPI(
{ repoSlug: "foo/bar", pullRequestID: "1" },
{
type: "OAUTH",
oauthSecret: "superSecretOAUTH",
oauthKey: "superOAUTHKey",
}
)

let fetch = jest.fn().mockReturnValue({
status: 200,
ok: true,
json: () => {
return { pr: "info" }
},
text: () => textResult,
})

api.fetch = fetch
api.accessToken = "bla bla bla bla"
api.uuid = "{1234-1234-1234-1234}"

const result = await api.getPullRequestInfo()
expect(api.fetch).toBeCalledTimes(1)
expect(result).toEqual({ pr: "info" })
})
})
6 changes: 5 additions & 1 deletion source/platforms/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ export function getPlatformForEnv(env: Env, source: CISource): Platform {
}

// Bitbucket Cloud
if (env["DANGER_BITBUCKETCLOUD_UUID"] || env["DANGER_PR_PLATFORM"] === BitBucketCloud.name) {
if (
env["DANGER_BITBUCKETCLOUD_OAUTH_KEY"] ||
env["DANGER_BITBUCKETCLOUD_USERNAME"] ||
env["DANGER_PR_PLATFORM"] === BitBucketCloud.name
) {
const api = new BitBucketCloudAPI(
{
pullRequestID: source.pullRequestID,
Expand Down
6 changes: 3 additions & 3 deletions source/runner/Executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ export class Executor {
let comment
if (process.env["DANGER_BITBUCKETSERVER_HOST"]) {
comment = bitbucketServerTemplate(dangerID, mergedResults, commitID)
} else if (process.env["DANGER_BITBUCKETCLOUD_UUID"]) {
} else if (process.env["DANGER_BITBUCKETCLOUD_OAUTH_KEY"] || process.env["DANGER_BITBUCKETCLOUD_USERNAME"]) {
comment = bitbucketCloudTemplate(dangerID, mergedResults, commitID)
} else {
comment = githubResultsTemplate(dangerID, mergedResults, commitID)
Expand Down Expand Up @@ -414,7 +414,7 @@ export class Executor {
let comment
if (process.env["DANGER_BITBUCKETSERVER_HOST"]) {
comment = bitbucketServerInlineTemplate(this.options.dangerID, results, inlineResults.file, inlineResults.line)
} else if (process.env["DANGER_BITBUCKETCLOUD_UUID"]) {
} else if (process.env["DANGER_BITBUCKETCLOUD_OAUTH_KEY"] || process.env["DANGER_BITBUCKETCLOUD_USERNAME"]) {
comment = bitbucketCloudInlineTemplate(this.options.dangerID, results, inlineResults.file, inlineResults.line)
} else {
comment = githubResultsInlineTemplate(this.options.dangerID, results, inlineResults.file, inlineResults.line)
Expand Down Expand Up @@ -445,7 +445,7 @@ const messageForResults = (results: DangerResults) => {
} else {
if (process.env["DANGER_BITBUCKETSERVER_HOST"]) {
return bitbucketMessageForResultWithIssues
} else if (process.env["DANGER_BITBUCKETCLOUD_UUID"]) {
} else if (process.env["DANGER_BITBUCKETCLOUD_OAUTH_KEY"] || process.env["DANGER_BITBUCKETCLOUD_USERNAME"]) {
return bitbucketCloudMessageForResultWithIssues
} else {
return githubMessageForResultWithIssues
Expand Down
4 changes: 2 additions & 2 deletions source/runner/jsonToDSL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const jsonToDSL = async (dsl: DangerDSLJSONType, source: CISource): Promi
git = await localPlatform.getPlatformGitRepresentation()
} else if (process.env["DANGER_BITBUCKETSERVER_HOST"]) {
git = bitBucketServerGitDSL(bitbucket_server!, dsl.git, api as BitBucketServerAPI)
} else if (process.env["DANGER_BITBUCKETCLOUD_UUID"]) {
} else if (process.env["DANGER_BITBUCKETCLOUD_OAUTH_KEY"] || process.env["DANGER_BITBUCKETCLOUD_USERNAME"]) {
git = bitBucketCloudGitDSL(bitbucket_cloud!, dsl.git, api as BitBucketCloudAPI)
} else if (process.env["DANGER_GITLAB_API_TOKEN"]) {
git = gitLabGitDSL(gitlab!, dsl.git)
Expand Down Expand Up @@ -72,7 +72,7 @@ const apiForDSL = (dsl: DangerDSLJSONType): OctoKit | BitBucketServerAPI | GitLa
return new BitBucketServerAPI(dsl.bitbucket_server!.metadata, bitbucketServerRepoCredentialsFromEnv(process.env))
}

if (process.env["DANGER_BITBUCKETCLOUD_UUID"]) {
if (process.env["DANGER_BITBUCKETCLOUD_OAUTH_KEY"] || process.env["DANGER_BITBUCKETCLOUD_USERNAME"]) {
return new BitBucketCloudAPI(dsl.bitbucket_cloud!.metadata, bitbucketCloudCredentialsFromEnv(process.env))
}

Expand Down

0 comments on commit 85c686f

Please sign in to comment.