cURL command not working in ScriptRunner post function

We are currently trying to use ScriptRunner to execute a workflow transition on a remote JIRA instance via a post function and are running into some interesting issues.  Basically, we have a custom workflows on two JIRA instances (say A and B).  When we execute a specific workflow transition on an issue in A, we want to also execute a workflow transition on a remotely linked issue in B.

I have attached the script we are using.  It starts off by declaring parameters that are going to be used in the remainder of the script.  It then grabs all remote issue links for the current issue, and for each one of them, calls the executeTransition function.  That function will grab all transitions for the remote issue using the JIRA REST API, find the appropriate transition, and then make a POST call to the JIRA REST API to execute the workflow transition.

Here is the API call https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition.  We also use cURL in order to accomplish this (I haven't found a better way of making POST requests using the native groovy language).

Here's the issue we are running into: the script works everywhere EXCEPT in JIRA instance A.  I have a local copy of JIRA and the script works just fine there.  We even made a dummy field that would spit out the cURL commands generated by the script (specifically the one in line 63), then tried executing those commands in command line on a local machine AND in bash on the server that hosts JIRA instance A.  The cURL command generated by the script works in both cases, it just doesn't work when we call curl.execute() (line 64) in the script.

We're not sure why it's working only outside of that specific instance of JIRA and were wondering if you have any clue as to what could be causing this.

Thank you for any help you can provide!

 

package clearleap

import com.atlassian.jira.ComponentManager
import com.atlassian.jira.bc.issue.link.RemoteIssueLinkService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.RemoteIssueLink
import groovy.json.JsonSlurper


// === BEGIN PARAMETERS ===

def curlCmd = "curl"
def username = "USERNAME"
def password = "PASSWORD"
def transitionName = "TRANSITION_NAME"
def protocol = "https://"

// === END PARAMETERS ===

RemoteIssueLinkService linkService = ComponentManager.getComponentInstanceOfType(RemoteIssueLinkService.class)

String requestAuth = ""
String requestData = ""
String request = ""
String curl = ""
InputStream response;
Map responseObj;

def executeTransition = { RemoteIssueLink link ->
    String url = link.getUrl()
    if(!url || url.length() == 0 || !url.contains(protocol))
        return

    String remoteBaseUrl = url.substring(0, url.indexOf("/", url.indexOf(protocol) + protocol.length()))
    String remoteIssueId = url.substring(url.lastIndexOf("/") + 1)

    request = "$remoteBaseUrl/rest/api/2/issue/$remoteIssueId/transitions"
    curl = "$curlCmd -u $username:$password $request"
    response = curl.execute().inputStream
    responseObj = new JsonSlurper().parse(response) as Map

    ArrayList<Map> transitions = responseObj.transitions as ArrayList<Map>
    String transitionId = null

    transitions.each { Map transition ->
        if(transition.name == transitionName) {
            transitionId = transition.id
        }
    }

    if(transitionId == null || transitionId.length() == 0)
        return

    requestAuth = "Basic " + "$username:$password".bytes.encodeBase64()
    requestData = """
        {
            \\"transition\\": {
                \\"id\\": \\"$transitionId\\"
            }
        }
    """
    request = "$remoteBaseUrl/rest/api/2/issue/$remoteIssueId/transitions"
    curl = "$curlCmd -H \"Content-Type: application/json\" -H \"Authorization: $requestAuth\" -X POST -d \"$requestData\" -u $username:$password $request"
    curl.execute()
}

def loggedInUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
def getLinksResult = linkService.getRemoteIssueLinksForIssue(loggedInUser, issue)

getLinksResult.getRemoteIssueLinks().forEach { RemoteIssueLink link ->
    executeTransition(link)
}

1 answer

I don't know... I would have guessed an SSL cert problem but if works from bash it should work from jira.

Did you try both curl commands including with the data that's it's supposed to send?

Capture the curl output with curl.execute().text, then log that. You might need to check the docs for the execute method to see if stdout and stderr goes to different places. curl -v may help too.

Another way is to install Fiddler, then tell curl to use a proxy which is the machine you install fiddler on, port 8888. Then you will see clearly what the issue is. But unless you can use http, not https, you have to dick around with certificates so you can effectively do a man-in-the-middle attack.

 

The native way to do this by the way is using HttpBuilder, which is included in SR. Not sure that that will help though, although the error reporting will be easier.

Thank you for the suggestions - I apologize for the late response, some competing priorities got in the way of our work on this.  Yes, we tried the commands with the data its supposed to send, and receive back the expected data in the response when run from bash.  We've seen this on two completely separate systems now, so we'll give some of the troubleshooting approaches you suggest a try.  Thanks again for the input and suggestions!

Thanks Jamie.  I have changed the script to use HTTPBuilder instead of cURL.  Much cleaner!  It looks like, however, I am running into a similar issue as this one: https://answers.atlassian.com/questions/9385129.  Here is my HTTPBuilder setup:

def http = new HTTPBuilder(remoteUrl)
http.client.addRequestInterceptor(new HttpRequestInterceptor() {
    void process(HttpRequest httpRequest, HttpContext httpContext) {
        httpRequest.addHeader("Authorization", "Basic " + "$username:$password".bytes.encodeBase64().toString())
    }
})
http.request(POST, JSON) { req ->
    headers.Accept = 'application/json'
    body = {
        transition {
            id: transitionId
        }
    }
};

For some reason I get a 400 Bad Request as well.

The error should be in the response entity... you can grab it by using a failure handler (or a debugging proxy). Have a look at the HttpBuilder docs.

I was able to find the error!!  I added a response.failure handler and printed out the details of the failure context.  For some reason, it was not setting the transition id to any value.  I looked at the JsonGroovyBuilder docs and tried setting body in several ways that should have been parsed correctly, but for some reason the transition id was always null, even if I hard-coded a value instead of using the transitionId variable.  Eventually, I just had to settle on using a raw string and it worked:

body = """
    {
        "transition": {
            "id": "$transitionId"
        }
    }
"""

Thanks Jamie, you were a great help in getting this done!

 

Tarun Sapra Community Champion Jul 22, 2016

Hi Irtiza,
Can you Please share the complete code sample for making http call from a post-function.

Thanks, 

Hi Tarun, 

Here is the part of the script that revolves around making an HTTP POST request in a post-function.

First, we create an instance of HTTPBuilder, which is built into ScriptRunner.  The important part is adding the request interceptor so the target URL will authenticate properly.

def username = "USERNAME"
def password = "PASSWORD"
 
def getBuilder = {
    String url = "DESIRED URL TO PING"

    def http = new HTTPBuilder(url)
    http.client.addRequestInterceptor(new HttpRequestInterceptor() {
        void process(HttpRequest httpRequest, HttpContext httpContext) {
            httpRequest.addHeader("Authorization", "Basic " + "$username:$password".bytes.encodeBase64().toString())
        }
    })

    return http
}

Then, we use the HTTPBuilder object to ping the desired URL and get a response.  This is just a sample of what the body of the request should look like.

def http = getBuilder()

def result = http.request(POST, JSON) { req ->
    headers.Accept = 'application/json'
    body = """
        {
            "transition": {
                "id": "$transitionId"
            }
        }
    """

    response.success = { resp, json ->
        return json
    }
    response.failure = { resp ->
        return resp.statusLine
    }
};

If you are making a GET request, you can follow the same procedure, just change the request type from POST to GET.  The result will be a map object which you can parse accordingly.

Let me know if this helps.

Suggest an answer

Log in or Sign up to answer
Community showcase
Posted Monday in Confluence

Organizing your space just got easier - Page Tree Drag & Drop is here

Hi Community! I’m Elaine, Confluence Product Manager. You may have read my earlier post about page tree in space navigation sidebar. I'm excited to share another improvement that helps you organize ...

66 views 3 2
Join discussion

Atlassian User Groups

Connect with like-minded Atlassian users at free events near you!

Find a group

Connect with like-minded Atlassian users at free events near you!

Find my local user group

Unfortunately there are no AUG chapters near you at the moment.

Start an AUG

You're one step closer to meeting fellow Atlassian users at your local meet up. Learn more about AUGs

Groups near you