Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

How to Use Jenkins to Install Jira Plugins w/ UPM REST API?

Vasily Prokhorov February 12, 2024

Hi everyone,

We're trying to create a Jenkins pipeline that installs a custom Jira plugin JAR file to our Jira instance using the UPM REST APIs.

Our process thus far is as follows:

  1. Make a DELETE call to '/rest/plugins/latest/com.fanniemae.atlassian.jira.<plugin>-key' to uninstall the current version.
  2. Make a call to GET call to '/rest/plugins/latest/?os_authType=basic' to get the value for the 'upm-token' header.
  3. Make a POST call to '/rest/plugins/latest/?token={{upm_token}}' and pass the file system path to the JAR file as form data

The above calls work in Postman. However, Jenkins always fails in #3 with a 403 status code and

 <textarea>{"errorMessage":"invalid token","subCode":"upm.error.invalid.token"}</textarea>

 

Our Jenkins pipeline is as follows:

stage("Deploy to Devl and Test") {
when {
expression { env.GIT_BRANCH != 'master'}
}
steps {
script {
logInfo("Download jar file from Nexus")
downloadArtifact(PLUGIN_NEXUS_URL: "atlassian/jira/releases/fmrelease",
PLUGIN_VERSION: env.BUILD_VERSION,
PLUGIN_JAR_FILE: "fmrelease")

logInfo("Uninstall the existing version of the plugin")
pluginUninstall(URL: "https://jira-devl:8443",
PLUGIN: "fmrelease",
PLUGIN_KEY: env.PLUGIN_KEY,
CREDENTIAL_ID: "grk_devl_internal_nuid")

logInfo("Get UPM for Jira Devl")
def UPM_TOKEN = upmToken(URL: "https://jira-devl:8443",
CREDENTIAL_ID: "grk_devl_internal_nuid")

logInfo(UPM_TOKEN)

pluginInstall(URL: "https://jira-devl:8443",
CREDENTIAL_ID: "grk_devl_internal_nuid",
UPM_TOKEN: UPM_TOKEN,
JAR_FILE: "fmrelease-${env.BUILD_VERSION}.jar",
PLUGIN: "fmrelease",
PLUGIN_VERSION: env.BUILD_VERSION)
}
}
}

Here is how we're getting the UPM token in Jenkins:

def call (Map params) {
def credentialId = required(params, "CREDENTIAL_ID")
def url = required(params, "URL")
withCredentials([string(credentialsId: credentialId, variable: 'SECRET')]) {
logInfo("Get UPM Token....")
def upmToken = sh(script:"""
set +x
curl -skIX GET \
'${url}/rest/plugins/latest/?os_authType=basic' \
-H 'Authorization: Basic ${SECRET}' | grep upm-token | cut -d: -f2- | tr -d '[[:space:]]'
set -x
""", returnStdout: true).trim()

return upmToken
}
}
return this

 

We then pass the output to the pluginInstall Groovy script:

def call (Map params) {

def url = required(params, "URL")
def upmToken = required(params, "UPM_TOKEN")
def credentialId = required(params, "CREDENTIAL_ID")
def jarFile = required(params, "JAR_FILE")
def plugin = required(params, 'PLUGIN')
def pluginVersion = required(params, "PLUGIN_VERSION")
def successStatusCode = 200

withCredentials([string(credentialsId: credentialId, variable: 'SECRET')]) {
logInfo("Install ${plugin} plugin Started...")
def output

echo """curl -kvX POST "${url}/rest/plugins/latest/?token=${upmToken}" -H 'Authorization: Basic ${SECRET}' -F 'plugin=@${jarFile}'"""
try {
output = sh(script: """
set +x
curl -kvX POST \
"${url}/rest/plugins/latest/?token=${upmToken.trim()}" \
-H 'Authorization: Basic ${SECRET}' \
-F 'plugin=@${jarFile}'
set -x
""", returnStdout: true)
} catch (Exception e) {
error("Failed to install ${plugin} plugin: ${e.toString()}")
}

logInfo("Installation output: ${output.toString()}")
def jsonOutput = readJSON text: output
def installationStatus = jsonOutput["status"]["done"]
def httpStatusCode = jsonOutput["status"]["statusCode"]


if (!installationStatus && httpStatusCode == successStatusCode) {
logInfo("Installtion of ${plugin} is intiated but not completed")
def apiEndpoint = jsonOutput["links"]["self"]

logInfo("Validation endpoint: ${apiEndpoint.toString()}")
def validationOutput

sh "sleep 30"
try {
validationOutput = sh(script: """
set +x
curl -kL GET \
"${url}${apiEndpoint}" \
-H 'Authorization: Basic ${SECRET}' \
""", returnStdout: true)
} catch (Exception e) {
error("Failed to validate the ${plugin} plugin installation: ${e.toString()}")
}

logInfo("Plugin install validation output: ${validationOutput.toString()}")
def validationJsonOutput = readJSON text: validationOutput
def pluginEnable = validationJsonOutput["enabled"]
def pluginInstallVersion = validationJsonOutput["version"]
if (pluginInstallVersion == PLUGIN_VERSION && pluginEnable) {
logInfo("Installtion of ${plugin} is Successfull")
}

} else if (httpStatusCode != successStatusCode) {
println(httpStatusCode)
error("Installation Failed...Please check the api output: ${output}")
} else {
error("Invalid Installation call: ${output}")
}
}
}

return this

 

3 answers

2 votes
Mark Rekveld - Marvelution
Atlassian Partner
February 12, 2024

Hi @Vasily Prokhorov,

Are you also setting the header `X-Atlassian-Token` to `no-check`?

For the rest your flow looks the same as the flow done using the Maven plugins.

Cheers,
Mark

Vasily Prokhorov February 12, 2024

Hey @Mark Rekveld - Marvelution ,

What does that header do?

 

And interestingly when I run a cURL command in GitBash using the code from Postman I get a successful response:

cURL Command:

curl -L 'https://jira-devl:8443/rest/plugins/latest/?token=677111682887120084' \
-H 'Authorization: Basic c3h1dm1wOlJ5TCowQmdCakV6RA==' \
-H 'Cookie: AWSALB=9xFQfs1myfGs1kkwPGrWX3bX5hgpRcw4tI6pXdKjAv8KCgXDAg75gcgBK/cCg3Jvjyrl+3V2o7HSPixR3zb2iSXLjOUsazrcYZ5IVtMUiE8KkHMYt3TH8D5GHska; AWSALBCORS=9xFQfs1myfGs1kkwPGrWX3bX5hgpRcw4tI6pXdKjAv8KCgXDAg75gcgBK/cCg3Jvjyrl+3V2o7HSPixR3zb2iSXLjOUsazrcYZ5IVtMUiE8KkHMYt3TH8D5GHska' \
-F 'plugin="@/C:/Users/sxuvmp/Downloads/TeamBoards-1.0.2.jar"'

Response:

% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   619    0   432  100   187    211     91  0:00:02  0:00:02 --:--:--   303<textarea>{"type":"INSTALL","pingAfter":100,"status":{"done":false,"statusCode":200,"contentType":"application/vnd.atl.plugins.install.installing+json","source":"","name":""},"links":{"self":"/rest/plugins/1.0/pending/966ae16e-b4d7-4fcd-b615-39a3439bb7da","alternate":"/rest/plugins/1.0/tasks/966ae16e-b4d7-4fcd-b615-39a3439bb7da"},"timestamp":1707764717421,"userKey":"sxuvmp","id":"966ae16e-b4d7-4fcd-b615-39a3439bb7da"}</textarea>

 

My team and I are stuck since we see Postman and GitBash working as expected, whereas Jenkins is having trouble :) 

Mark Rekveld - Marvelution
Atlassian Partner
February 13, 2024

Hi @Vasily Prokhorov 

The header I believe is used for CORS and CSRF related checks, which can effect the response from Jira.

Can it be the slight difference in cURL command? On GitBash you run curl with the '-L' instead of '-kvX POST' and you also pass in cookies using GitBask but don't on Jenkins.

Have you tried the curl command exactly how Jenkins runs it? Or have you tried adopting the GitBash command in Jenkins?

If it is still not working, then I would expect there to be some access or request logging to be available in Jira why the request was rejected. Or do you have some other infrastructure that can also block the request?

In the cookies you set on GitBash I would think you use an AWS Load Balancer. Is this the case, and if so, does this do any request validation or authorization checks that can explain the error response in Jenkins?

Cheers,
Mark

0 votes
FG
I'm New Here
I'm New Here
Those new to the Atlassian Community have posted less than three times. Give them a warm welcome!
August 1, 2024 edited

Hi Vasily, 

we experienced the same problem and we nailed it down failing server affinity. We are running a jira cluster in azure and the upm_token seems to be server specific and NOT cluster wide valid. 

So as soon as we use the same affinity cookie (load balancer server sticky cookie) on both http request it works. I recognized an AWS-Loader balancer Cookie in your sample data, so it might be the same kind of problem.

Basically we do 

1.) curl -c cookies "https://jira/rest/plugins/1.0/" --> extracting the upm_token

2.) curl -b cookies  --request POST "https://jira/rest/plugins/1.0/?token=$UPM_TOKEN" -F plugin=@plugin.jar

We use an -H "Authorization: Bearer $JIRA_PAT" with both calls.

the first call saves cookies to cookies file, and the second call uses cookies. In our case the actual JSESSIONID cookie is not relevant only the load balancer cookie.

 

0 votes
Matt Doar
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 12, 2024

Not an answer but https://jira.atlassian.com/browse/JRASERVER-77129 may be relevant to you

Vasily Prokhorov February 12, 2024

Thanks @Matt Doar .

We're still on 9.4.8,thus we're not impacted by this yet :)

Suggest an answer

Log in or Sign up to answer
DEPLOYMENT TYPE
SERVER
TAGS
atlassian, atlassian government cloud, fedramp, webinar, register for webinar, atlassian cloud webinar, fedramp moderate offering, work faster with cloud

Unlocking the future with Atlassian Government Cloud ☁️

Atlassian Government Cloud has achieved FedRAMP Authorization at the Moderate level! Join our webinar to learn how you can accelerate mission success and move work forward faster in cloud, all while ensuring your critical data is secure.

Register Now
AUG Leaders

Atlassian Community Events