Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8936330
Initial version of setBuildStatus
serverhorror Oct 22, 2023
e879cc1
Remove calls to curl from BitBucketService
serverhorror Oct 22, 2023
cbfe4e7
Fix test 'create code insight report'
serverhorror Oct 24, 2023
6048a16
Fix test 'create code insight report without Aqua Link'
serverhorror Oct 24, 2023
5c3dfcf
Fix test 'create code insight report without Nexus link'
serverhorror Oct 24, 2023
7975820
Fix "create code insight report without Nexus and Aqua link"
serverhorror Oct 24, 2023
b38f869
Fix "create code insight report with links and messages"
serverhorror Oct 24, 2023
460a77d
Fix "create code insight report with messages but without links"
serverhorror Oct 24, 2023
38c4087
Fix "create code insight report with error in call"
serverhorror Oct 24, 2023
3e31272
make changelog enforcer happ[y|ier]
serverhorror Oct 24, 2023
17006d0
make codenarc happ[y|ier]
serverhorror Oct 24, 2023
39bebc3
fix "I can make a POST request"
serverhorror Oct 24, 2023
34c50ec
Fix ScanWithAquaStageSpec
serverhorror Oct 24, 2023
ed7e63a
Upgrade to a non-ancient version of Unirest (to 3.14.2 from 2.4.03)
serverhorror Oct 24, 2023
ac25a65
deprecate setBuildStatus to make note of new method
serverhorror Oct 24, 2023
2e2dc43
migrate Pipeline.setBitbucketBuildStatus to non-deprecated call
serverhorror Oct 24, 2023
e6364ed
Remove unused constructor
serverhorror Oct 24, 2023
6a6057a
Remove unused comments
serverhorror Oct 24, 2023
a771b5a
Remove unnecessary overload method
serverhorror Oct 24, 2023
e9f65e8
Merge branch 'master' into bug/989-bitbucketservice-uses-deprecated-a…
serverhorror Feb 6, 2024
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Changelog

## Unreleased
* Fix BitbucketService uses deprecated API call ([#989](https://github.com/opendevstack/ods-jenkins-shared-library/issues/989))
* Add better documentation for Helm ([#1027](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1027))
* Avoid Groovy string interpolation [#1030](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1030)
* Fix documentation refers to qs with prefix infra- however there are only inf- quickstarters ([#1060](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1060))
* Fix Tailor deployment drifts for D, Q envs ([#1055](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1055))
* Aqua scanner and Helm deployment conflict fix for jenkins shared library ([#1067](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1067))
Expand Down
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ dependencies {
implementation group: 'org.jenkins-ci.plugins.workflow', name: 'workflow-cps', version: '2.41', ext: 'jar'
implementation 'org.slf4j:jcl-over-slf4j:1.7.30'
implementation 'ch.qos.logback:logback-classic:1.2.3'
implementation "com.konghq:unirest-java:2.4.03:standalone"
implementation "com.konghq:unirest-java:3.14.2"
implementation "com.konghq:unirest-object-mappers-gson:3.14.1"
implementation "fr.opensagres.xdocreport:fr.opensagres.poi.xwpf.converter.core:2.0.2"
implementation "fr.opensagres.xdocreport:fr.opensagres.poi.xwpf.converter.pdf:2.0.2"
implementation "net.lingala.zip4j:zip4j:2.1.1"
Expand Down
2 changes: 1 addition & 1 deletion src/org/ods/component/Pipeline.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ class Pipeline implements Serializable {
}

def buildName = "${context.gitCommit.take(8)}"
bitbucketService.setBuildStatus(context.buildUrl, context.gitCommit, state, buildName)
bitbucketService.setBuildStatus(context.buildUrl, "${context.projectId}-${context.componentId}", context.gitCommit, state, buildName)
}

private void doNotifyNotGreen(List<String> emailextRecipients) {
Expand Down
198 changes: 83 additions & 115 deletions src/org/ods/services/BitbucketService.groovy
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package org.ods.services

@Grab(group='com.konghq', module='unirest-java', version='2.4.03', classifier='standalone')

import com.cloudbees.groovy.cps.NonCPS
import groovy.json.JsonSlurperClassic
import kong.unirest.Unirest
import org.ods.util.ILogger
import com.cloudbees.groovy.cps.NonCPS
import org.ods.util.AuthUtil
import org.ods.util.ILogger

@SuppressWarnings(['PublicMethodsBeforeNonPublicMethods', 'ParameterCount'])
class BitbucketService {
Expand All @@ -16,7 +14,7 @@ class BitbucketService {

private final def script

// Bae URL of Bitbucket server, such as "https://bitbucket.example.com".
// Base URL of Bitbucket server, such as "https://bitbucket.example.com".
private final String bitbucketUrl

// Name of Bitbucket project, such as "foo".
Expand Down Expand Up @@ -44,6 +42,7 @@ class BitbucketService {

private final ILogger logger

private HttpRequestService httpRequestService
BitbucketService(def script, String bitbucketUrl, String project,
String passwordCredentialsId, ILogger logger) {
this.script = script
Expand All @@ -53,6 +52,7 @@ class BitbucketService {
this.passwordCredentialsId = passwordCredentialsId
this.tokenSecretName = 'cd-user-bitbucket-token'
this.logger = logger
this.httpRequestService = new HttpRequestService(script, logger)
}

static BitbucketService newFromEnv(
Expand Down Expand Up @@ -102,21 +102,13 @@ class BitbucketService {
}

String getDefaultReviewerConditions(String repo) {
String res
withTokenCredentials { username, token ->
def authHeader = '\"Authorization: Bearer $TOKEN\"' // codenarc-disable GStringExpressionWithinString
res = script.sh(
label: 'Get default reviewer conditions via API',
script: """curl \\
--fail \\
-sS \\
--request GET \\
--header ${authHeader} \\
${bitbucketUrl}/rest/default-reviewers/1.0/projects/${project}/repos/${repo}/conditions""",
returnStdout: true
).trim()
return withTokenCredentials { username, token ->
def url = "${bitbucketUrl}/rest/default-reviewers/1.0/projects/${project}/repos/${repo}/conditions"
return this.httpRequestService.asString(
HttpRequestService.HTTP_METHOD_GET,
HttpRequestService.AUTHORIZATION_SCHEME_BEARER,
token as String, url)
}
return res
}

// Returns a list of bitbucket user names (not display names)
Expand All @@ -142,7 +134,6 @@ class BitbucketService {
// Creates pull request in "repo" from branch "fromRef" to "toRef". "reviewers" is a list of bitbucket user names.
String createPullRequest(String repo, String fromRef, String toRef, String title, String description,
List<String> reviewers) {
String res
def payload = """{
"title": "${title}",
"description": "${description}",
Expand Down Expand Up @@ -172,40 +163,24 @@ class BitbucketService {
"locked": false,
"reviewers": [${reviewers ? reviewers.collect { "{\"user\": { \"name\": \"${it}\" }}" }.join(',') : ''}]
}"""
withTokenCredentials { username, token ->
def authHeader = '\"Authorization: Bearer $TOKEN\"' // codenarc-disable GStringExpressionWithinString
res = script.sh(
label: 'Create pull request via API',
script: """curl \\
--fail \\
-sS \\
--request POST \\
--header ${authHeader} \\
--header \"Content-Type: application/json\" \\
--data '${payload}' \\
${bitbucketUrl}/rest/api/1.0/projects/${project}/repos/${repo}/pull-requests""",
returnStdout: true
).trim()
return withTokenCredentials { username, token ->
def url = "${bitbucketUrl}/rest/api/1.0/projects/${project}/repos/${repo}/pull-requests"
return this.httpRequestService.asString(
HttpRequestService.HTTP_METHOD_POST,
HttpRequestService.AUTHORIZATION_SCHEME_BEARER,
token as String, url, payload as String)
}
res
}

// Get pull requests of "repo" in given "state" (can be OPEN, DECLINED or MERGED).
String getPullRequests(String repo, String state = 'OPEN') {
String res
withTokenCredentials { username, token ->
def authHeader = '\"Authorization: Bearer $TOKEN\"' // codenarc-disable GStringExpressionWithinString
res = script.sh(
label: 'Get pullrequests via API',
script: """curl \\
--fail \\
-sS \\
--header ${authHeader} \\
${bitbucketUrl}/rest/api/1.0/projects/${project}/repos/${repo}/pull-requests?state=${state}""",
returnStdout: true
).trim()
return withTokenCredentials { username, token ->
def url = "${bitbucketUrl}/rest/api/1.0/projects/${project}/repos/${repo}/pull-requests?state=${state}"
return this.httpRequestService.asString(
HttpRequestService.HTTP_METHOD_GET,
HttpRequestService.AUTHORIZATION_SCHEME_BEARER,
token as String, url)
}
res
}

Map findPullRequest(String repo, String branch, String state = 'OPEN') {
Expand Down Expand Up @@ -243,18 +218,11 @@ class BitbucketService {
void postComment(String repo, int pullRequestId, String comment) {
withTokenCredentials { username, token ->
def payload = """{"text":"${comment}"}"""
def authHeader = '\"Authorization: Bearer $TOKEN\"' // codenarc-disable GStringExpressionWithinString
script.sh(
label: "Post comment to PR#${pullRequestId}",
script: """curl \\
--fail \\
-sS \\
--request POST \\
--header ${authHeader} \\
--header \"Content-Type: application/json\" \\
--data '${payload}' \\
${bitbucketUrl}/rest/api/1.0/projects/${project}/repos/${repo}/pull-requests/${pullRequestId}/comments"""
)
def url = "${bitbucketUrl}/rest/api/1.0/projects/${project}/repos/${repo}/pull-requests/${pullRequestId}/comments"
return this.httpRequestService.asString(
HttpRequestService.HTTP_METHOD_POST,
HttpRequestService.AUTHORIZATION_SCHEME_BEARER,
token as String, url, payload as String)
}
}

Expand All @@ -273,21 +241,44 @@ class BitbucketService {
"startPoint": "${startPoint}",
"type": ${message == '' ? 'LIGHTWEIGHT' : 'ANNOTATED'}
}"""
def authHeader = '\"Authorization: Bearer $TOKEN\"' // codenarc-disable GStringExpressionWithinString
script.sh(
label: "Post git tag to branch",
script: """curl \\
--fail \\
-sS \\
--request POST \\
--header ${authHeader} \\
--header \"Content-Type: application/json\" \\
--data '${payload}' \\
${bitbucketUrl}/api/1.0/projects/${project}/repos/${repo}/tags"""
)
def url = "${bitbucketUrl}/api/1.0/projects/${project}/repos/${repo}/tags"
return this.httpRequestService.asString(
HttpRequestService.HTTP_METHOD_POST,
HttpRequestService.AUTHORIZATION_SCHEME_BEARER,
token as String, url, payload as String)
}
}

// https://docs.atlassian.com/bitbucket-server/rest/7.21.0/bitbucket-rest.html#idp228
void setBuildStatus(String buildUrl, String repo, String gitCommit, String state, String buildName) {
logger.debugClocked("buildstatus-${buildName}-${state}",
"Setting Bitbucket build status to '${state}' on commit '${gitCommit}' / '${buildUrl}'")
withTokenCredentials { username, token ->
def maxAttempts = 3
def retries = 0
def payload = [
key: repo,
state: state,
url: buildUrl,
name: buildName]
def url = "${bitbucketUrl}" +
"/rest/api/1.0/projects/${this.project}/repos/${repo}/commits/${gitCommit}/builds"
while (retries++ < maxAttempts) {
try {
this.httpRequestService.asString(
HttpRequestService.HTTP_METHOD_POST,
HttpRequestService.AUTHORIZATION_SCHEME_BEARER,
token as String, url, payload)
return
} catch (err) {
logger.warn("Could not set Bitbucket build status to '${state}' due to: ${err}")
}
}
}
logger.debugClocked("buildstatus-${buildName}-${state}")
}

@Deprecated
@SuppressWarnings('LineLength')
void setBuildStatus(String buildUrl, String gitCommit, String state, String buildName) {
logger.debugClocked("buildstatus-${buildName}-${state}",
Expand All @@ -296,20 +287,13 @@ class BitbucketService {
def maxAttempts = 3
def retries = 0
def payload = "{\"state\":\"${state}\",\"key\":\"${buildName}\",\"name\":\"${buildName}\",\"url\":\"${buildUrl}\"}"
def url = "${bitbucketUrl}/rest/build-status/1.0/commits/${gitCommit}"
while (retries++ < maxAttempts) {
try {
def authHeader = '\"Authorization: Bearer $TOKEN\"' // codenarc-disable GStringExpressionWithinString
script.sh(
label: 'Set bitbucket build status via API',
script: """curl \\
--fail \\
-sS \\
--request POST \\
--header ${authHeader} \\
--header \"Content-Type: application/json\" \\
--data '${payload}' \\
${bitbucketUrl}/rest/build-status/1.0/commits/${gitCommit}"""
)
this.httpRequestService.asString(
HttpRequestService.HTTP_METHOD_POST,
HttpRequestService.AUTHORIZATION_SCHEME_BEARER,
token as String, url, payload as String)
return
} catch (err) {
logger.warn("Could not set Bitbucket build status to '${state}' due to: ${err}")
Expand Down Expand Up @@ -360,20 +344,12 @@ class BitbucketService {
payload += "]" +
"}"
try {
def authHeader = '\"Authorization: Bearer $TOKEN\"' // codenarc-disable GStringExpressionWithinString
script.sh(
label: 'Create Bitbucket Code Insight report via API',
script: """curl \\
--fail \\
-sS \\
--request PUT \\
--header ${authHeader} \\
--header \"Content-Type: application/json\" \\
--data '${payload}' \\
${bitbucketUrl}/rest/insights/1.0/projects/${project}/\
repos/${repo}/commits/${gitCommit}/reports/${data.key}"""
)
return
def url = "${bitbucketUrl}" +
"/rest/insights/1.0/projects/${project}/repos/${repo}/commits/${gitCommit}/reports/${data.key}"
this.httpRequestService.asString(
HttpRequestService.HTTP_METHOD_PUT,
HttpRequestService.AUTHORIZATION_SCHEME_BEARER,
token as String, url, payload as String)
} catch (err) {
logger.warn("Could not create Bitbucket Code Insight report due to: ${err}")
}
Expand Down Expand Up @@ -452,19 +428,11 @@ repos/${repo}/commits/${gitCommit}/reports/${data.key}"""
tokenMap['username'] = username
String password = script.env.PASSWORD
String url = "${bitbucketUrl}/rest/access-tokens/1.0/users/${username.replace('@', '_')}"
script.echo "Requesting token via PUT ${url} with payload=${payload}"
res = script.sh(
returnStdout: true,
script: """set +x; curl \\
--fail \\
-sS \\
--request PUT \\
--header \"Content-Type: application/json\" \\
--header \"${AuthUtil.header(AuthUtil.SCHEME_BASIC, username, password)}\" \\
--data '${payload}' \\
${url}
"""
).trim()

res = this.httpRequestService.asString(
HttpRequestService.HTTP_METHOD_PUT,
HttpRequestService.AUTHORIZATION_SCHEME_BASIC,
AuthUtil.base64("${username}:${password}"), url, payload as String)
try {
// call readJSON inside of withCredentials block,
// otherwise token will be displayed in output
Expand All @@ -479,11 +447,11 @@ repos/${repo}/commits/${gitCommit}/reports/${data.key}"""
}

String getToken() {
withTokenCredentials { username, token -> return token}
withTokenCredentials { username, token -> return token }
}

@NonCPS
Map getCommitsForIntegrationBranch(String token, String repo, int limit, int nextPageStart){
Map getCommitsForIntegrationBranch(String token, String repo, int limit, int nextPageStart) {
String request = "${bitbucketUrl}/rest/api/1.0/projects/${project}/repos/${repo}/commits"
return queryRepo(token, request, limit, nextPageStart)
}
Expand All @@ -499,10 +467,10 @@ repos/${repo}/commits/${gitCommit}/reports/${data.key}"""
private Map queryRepo(String token, String request, int limit, int nextPageStart) {
Map<String, String> headers = buildHeaders(token)
def httpRequest = Unirest.get(request).headers(headers)
if (limit>0) {
if (limit > 0) {
httpRequest.queryString("limit", limit)
}
if (nextPageStart>0) {
if (nextPageStart > 0) {
httpRequest.queryString("start", nextPageStart)
}
def response = httpRequest.asString()
Expand Down
54 changes: 54 additions & 0 deletions src/org/ods/services/HttpRequestService.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.ods.services

import com.cloudbees.groovy.cps.NonCPS
import kong.unirest.Unirest
import org.ods.util.ILogger

class HttpRequestService {

public static final String AUTHORIZATION_SCHEME_BASIC = "Basic"
public static final String AUTHORIZATION_SCHEME_BEARER = "Bearer"

public static final String HTTP_METHOD_GET = "GET"
public static final String HTTP_METHOD_POST = "POST"
public static final String HTTP_METHOD_PUT = "PUT"

private final def script

private final ILogger logger

HttpRequestService(def script, ILogger logger) {
this.script = script
this.logger = logger
}

@NonCPS
String asString(String httpMethod, String authScheme, String authParameter, String url) {
def response = Unirest.request(httpMethod, url)
.header("Authorization", "${authScheme} ${authParameter}")
.asString()

response.ifFailure {
def message = "Could not send HTTP request ${response.getStatus()} ${response.getStatusText()} to ${url}"
logger.error(message)
throw new RuntimeException(message)
}
return response.body
}

@NonCPS
String asString(String httpMethod, String authScheme, String authParameter, String url, def body) {
def response = Unirest.request(httpMethod, url)
.header("Authorization", "${authScheme} ${authParameter}")
.body(body)
.asString()

response.ifFailure {
def message = "Could not send HTTP request ${response.getStatus()} ${response.getStatusText()} to ${url}"
logger.error(message)
throw new RuntimeException(message)
}
return response.body
}

}
Loading