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:
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
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
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 :)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Not an answer but https://jira.atlassian.com/browse/JRASERVER-77129 may be relevant to you
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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 NowOnline forums and learning are now in one easy-to-use experience.
By continuing, you accept the updated Community Terms of Use and acknowledge the Privacy Policy. Your public name, photo, and achievements may be publicly visible and available in search engines.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.