I am trying to add a custom JIRA REST endpoint with Script Runner.
The REST endpoint needs to set user properties: a list of users is provided, and for each user a list of user properties.
This is the script I have cobbled together so far:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.user.search.UserSearchService
import com.atlassian.sal.api.user.UserManager
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import groovy.transform.BaseScript
import javax.servlet.http.HttpServletRequest
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
@BaseScript CustomEndpointDelegate delegate
setUserProperties(httpMethod: "POST", groups: ["jira-administrators"])
{ MultivaluedMap queryParams, String body, HttpServletRequest request ->
def userPropertyManager = ComponentAccessor.getUserPropertyManager()
def userManager = ComponentAccessor.getUserManager()
def userSearchService = ComponentAccessor.getComponent(UserSearchService.class)
def json = new JsonSlurper().parseText(body)
def propertyCount = 0
def customerList = new ArrayList<>()
json.each {
aCustomer ->
def user = userManager.getUserByName(aCustomer.name.toString())
aCustomer.properties.each {
aProperty ->
userPropertyManager.getPropertySet(user).setString("jira.meta." + aProperty.key.toString(), aProperty.value.toString())
propertyCount++
}
customerList.add(user)
}
def response = new ArrayList<>()
response.add( new JsonSlurper().parseText('{ "properties_changed": ' + propertyCount + ' }') )
response.add( customerList )
return Response.ok( new JsonBuilder(response).toPrettyString() ).build();
}
The inline script editor complains about this line: def user = userManager.getUserByName(aCustomer.name.toString()), more precisely there is a wavy red line under aCustomer.name and the error is
[Static type checking] - No such property: name for class:
java.lang.object
@ line 28, column 44.
EDIT: according to @GlennV's answer, this is not really important and can be ignored.
A previous (working) iteration of this script, running on another server, used email addresses, see How to get a user by email in JIRA Script Runner. But, as stated above, now I'm not getting users by email address but by username.
This is the userprops.json file I use to test:
[
{
"name": "felicity.smoak",
"email": "[email protected]",
"properties": {
"Company": "Queen Consolidated",
"Foo": "bar"
}
},
{
"name": "johnny.cash",
"email": "[email protected]",
"properties": {
"Company": "Nashville",
"Nickname": "The Man In Black"
}
}
]
I test with this curl command:
curl -X POST \
-i \
-H "X-Atlassian-Token: nocheck" \
-H "Content-type: text/json" \
--data "@userprops.json" \
-u "$JIRA_USER":"$JIRA_PASSWORD" \
http://"$JIRA_SERVER"/rest/scriptrunner/latest/custom/setUserProperties
$JIRA_USER, $JIRA_PASSWORD and $JIRA_SERVER are just placeholders that I use to anonymize this for Stack Overflow. The actual values are correct.
This is the output:
HTTP/1.1 404 Not Found
Server: nginx
Date: Wed, 07 Sep 2016 14:55:38 GMT
Content-Type: application/json;charset=UTF-8
Content-Length: 0
Connection: keep-alive
X-AREQUESTID: 0123456789
X-ASEN: SEN-123456789
Set-Cookie: JSESSIONID=1A2B3C4D5E6F1A2B3C4D5E6F1A2B3C4D5E6F; Path=/; Secure; HttpOnly
X-Seraph-LoginReason: OK
Set-Cookie: atlassian.xsrf.token=ABCD-EFGH-IJKL-MNOP|abcdef0123456789abcdef0123456789abcdef0123456789|lin; Path=/; Secure
X-ASESSIONID: xyz123
X-AUSERNAME: amedee.vangasse
Cache-Control: no-cache, no-store, no-transform
X-Content-Type-Options: nosniff
So it's not finding the REST endpoint where I should find it according to the Scriptrunner documentation.
If I use any URL that I know to be wrong, then I get an expected "Oops, you've found a dead link." error page. Only with the correct URL I get a 404 + no other output.
Also, when I add the doSomething sample and try to GET that, I get a 404 and a content length of 0. Doing a GET on any other wrong URL gives me a 404 AND a content length of a few KiB.
The -Dplugin.script.roots= parameter is not defined in setenv.sh, nor should it be, because the script is inline. Setting script roots is only required for scripts on the filesystem, and only for scripts outside of the default scripts directory, as described in the Scriptrunner documentation.
Also asked on Atlassian Answers.
EDIT: I moved customerList.add(user) one line down as suggested by Glenn, and I got this error in atlassian-jira.log:
2016-09-09 11:01:43,683 http-nio-8086-exec-23 ERROR amedee.vangasse 661x42160x1 qq01uq 213.224.9.178,127.0.0.1 /rest/scriptrunner/latest/custom/customadmin/com.onresolve.scriptrunner.canned.common.rest.CustomRestEndpoint [c.a.p.r.c.error.jersey.ThrowableExceptionMapper] Uncaught exception thrown by REST service: Cannot get property 'name' on null object
java.lang.NullPointerException: Cannot get property 'name' on null object
at org.codehaus.groovy.runtime.NullObject.getProperty(NullObject.java:60)
at org.codehaus.groovy.runtime.InvokerHelper.getProperty(InvokerHelper.java:172)
at org.codehaus.groovy.runtime.callsite.NullCallSite.getProperty(NullCallSite.java:47)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGetProperty(AbstractCallSite.java:296)
at com.onresolve.scriptrunner.runner.RestEndpointManager$_onFullSystemStart_closure5.doCall(RestEndpointManager.groovy:141)
... 3 filtered
at java.lang.reflect.Method.invoke(Method.java:497)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1021)
at groovy.lang.Closure.call(Closure.java:426)
at groovy.lang.Closure.call(Closure.java:442)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2030)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2015)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2056)
at org.codehaus.groovy.runtime.dgm$162.invoke(Unknown Source)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:274)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:56)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
at com.onresolve.scriptrunner.runner.RestEndpointManager.onFullSystemStart(RestEndpointManager.groovy:131)
at com.onresolve.scriptrunner.runner.rest.common.UserCustomScriptEndpoint.refresh(UserCustomScriptEndpoint.groovy:364)
at com.onresolve.scriptrunner.runner.rest.common.UserCustomScriptEndpoint.saveConfiguredItems(UserCustomScriptEndpoint.groovy:359)
at com.onresolve.scriptrunner.runner.rest.common.UserCustomScriptEndpoint.addListener(UserCustomScriptEndpoint.groovy:127)
... 3 filtered
at java.lang.reflect.Method.invoke(Method.java:497)
at com.sun.jersey.spi.container.JavaMethodInvokerFactory$1.invoke(JavaMethodInvokerFactory.java:60)
... 13 filtered
at com.atlassian.plugins.rest.module.RestDelegatingServletFilter$JerseyOsgiServletContainer.doFilter(RestDelegatingServletFilter.java:154)
... 1 filtered
at com.atlassian.plugins.rest.module.RestDelegatingServletFilter.doFilter(RestDelegatingServletFilter.java:68)
... 86 filtered
at com.atlassian.jira.security.JiraSecurityFilter.doFilter(JiraSecurityFilter.java:70)
... 16 filtered
at com.atlassian.plugins.rest.module.servlet.RestSeraphFilter.doFilter(RestSeraphFilter.java:37)
... 73 filtered
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
It's not a problem with the nginx proxy either, because when I ssh into the server and curl on localhost, I get the same 404.