My Jenkins pipeline currently successfully invokes Bitbucket REST API by invoking curl
in a shell, as in the code below:
// Jenkinsfile
@Library('my-sandbox-libs@dev') my_lib
pipeline {
agent any
stages {
stage( "1" ) { steps { script { echo "hello" } } }
stage( "2" ) {
steps {
script {
log = new org.log.Log()
def cred_id = "bitbucket_cred_id"
def url_base = "https://bitbucket.company.com"
def commit = "76136485c45df256a62cbc2c3c5f1f3efcc86258"
def status =
//"INPROGRESS",
//"SUCCESSFUL",
"FAILED"
def viz_url = "https://path/to/nowhere"
try {
my_lib.notifyBitbucketBuildStatus(cred_id,
url_base,
commit,
status,
"foo",
42,
viz_url,
log)
}
}
}
}
stage( "3" ) { steps { script { echo "world" } } }
}
post { always { script { echo log.asJsonString() } } }
}
import groovy.json.JsonOutput
def notifyBitbucketBuildStatus(cred_id,
url_base,
commit,
build_state,
build_info_name,
build_info_number,
viz_url,
log) {
def rest_path = "rest/build-status/1.0/commits"
def dict = [:]
dict.state = build_state
dict.key = "${build_info_name}_${build_info_number}"
dict.url = viz_url
withCredentials([string(credentialsId: cred_id,
variable: 'auth_token')]) {
def cmd = "curl -f -L "
"-H \"Authorization: Bearer ${auth_token}\" "
"-H \"Content-Type:application/json\" "
"-X POST ${url_base}/${rest_path}/${commit} "
"-d \'${JsonOutput.toJson(dict)}\'"
if ( 0 != sh(script: cmd, returnStatus: true) ) {
log.warn("Failed updating build status with Bitbucket")
}
}
}
I would like to refactor function notifyBitbucketBuildStatus()
to use a "native" Groovy-language solution, rather than invoking curl
in a shell. I read the following on this topic:
...based on which I thought the refactored function would look like this:
def notifyBitbucketBuildStatus(cred_id,
url_base,
commit,
build_state,
build_info_name,
build_info_number,
viz_url,
log) {
def rest_path = "rest/build-status/1.0/commits"
def dict = [:]
dict.state = build_state
dict.key = "${build_info_name}_${build_info_number}"
dict.url = viz_url
def req = new URL("${url_base}/${rest_path}/${commit}").openConnection()
req.setRequestMethod("POST")
req.setDoOutput(true)
req.setRequestProperty("Content-Type", "application/json")
withCredentials([string(credentialsId: cred_id,
variable: 'auth_token')]) {
req.setRequestProperty("Authorization", "Bearer ${auth_token}")
}
def msg = JsonOutput.toJson(dict)
req.getOutputStream().write(msg.getBytes("UTF-8"));
if ( 200 != req.getResponseCode() ) {
log.warn("Failed updating build status with Bitbucket")
}
}
...but this generates the exception java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl
That "not serializable" made me think the error had something to do with a failure to transform something to a string, so I also tried this, but it did not change the error:
def msg = JsonOutput.toJson(dict).toString()
What is wrong with the refactored code that uses class URL
, and what is the right way to use it to invoke the REST API?
For the life of me, I can't see what's different between the above and the linked Stack Overflow Q&A, and my inexperience with the language is such that I rely largely on adapting existing example.
CodePudding user response:
Solution
I would highly suggest you use the HTTP Request and the Pipeline Steps Utility plugin for this. You can then use those steps in a Groovy script as follows
node('master') {
withCredentials([string(credentialsId: cred_id, variable: 'auth_token')]) {
def response = httpRequest url: "https://jsonplaceholder.typicode.com/todos", customHeaders: [[name: 'Authorization', value: "Bearer ${auth_token}"]]
}
if( response.status != 200 ) {
error("Service returned a ${response.status}")
}
def json = readJSON text: response.content
println "The User ID is ${json[0]['userId']}"
println "The follow json obj is ${json}"
}
Obviously you can modify the code if you want to build a method, and you will need to update with the appropriate URL.
CodePudding user response:
I found a sucky and unsatisfying answer - but an answer nonetheless - that I posted here: https://stackoverflow.com/a/69486890/5437543
I hate that solution because it would appear to demonstrate that the Jenkins/Groovy language itself imposes an artificial contrivance on how I can organize my code. Effectively, I am prevented from doing
// Jenkinsfile
@Library('my-sandbox-libs@dev') my_lib
pipeline {
agent any
stages {
stage( "1" ) { steps { script { my_lib.func() } } }
}
}
// vars/my_lib.groovy
def func() {
def post = new URL("https://whatever").openConnection();
...
withCredentials([string(credentialsId: cred_id,
variable: 'auth_token')]) {
req.setRequestProperty("Authorization", "Bearer ${auth_token}")
}
...
}
...and I am forced to do
// Jenkinsfile
@Library('my-sandbox-libs@dev') my_lib
pipeline {
agent any
stages {
stage( "1" ) { steps { script { my_lib.func(my_lib.getCred()) } } }
}
}
// vars/my_lib.groovy
def getCred() {
withCredentials([string(credentialsId: cred_id,
variable: 'auth_token')]) {
return auth_token
}
}
def func(auth_token) {
def post = new URL("https://whatever").openConnection();
...
req.setRequestProperty("Authorization", "Bearer ${auth_token}")
...
}
Extremely dissatisfying conclusion. I hope another answerer can point out a solution that doesn't rely on this contrived code organization.