Remote Control - BETA
Remote Control allows you to easily execute code on other Atlassian applications with ScriptRunner installed.
You could do the same by writing and deploying remote APIs, eg REST endpoints. However, this method allows you to run arbitrary code on any instance, without deploying it first. You can execute code locally, pass a value to a remote instance, manipulate it, and return some new value to your local instance.
If you have many instances of an application you can easily run code across all the instances - possible examples might be:
Check if any projects are anonymously accessible
Find the version of a common plugin
Anything you could do with remote events, e.g. mark a JIRA version as released when a tag is created in Bitbucket
This script gets the number of issues in both the local instance, and the "applinked" instance, and returns a message to the console.
Unresolved directive in content/common/remote-control.adoc - include::../jira/src/test/resources/com/onresolve/jira/groovy/test/workflow/testscripts/remoteevents/CountAllIssues.groovy[tags=ex1]
As you can see it’s as easy to write a script to operate on a remote instance as it is on a local instance.
Executing code on the remote application requires administrator privileges on the target application, in the same way as executing custom code or a built-in script requires administrator permissions. If you are running code as the current you may need to switch user before executing the remote closure code, as described here.
Under the Hood
Under the covers, we serialize the closure you pass to the exec method, and any variables it uses. This is sent to the remote machine in binary form, then executed. Any results are sent back to the calling instance.
When you use code like
This means, that any variables passed in or back from the closure must be serializable. In practice, if you stick to passing simple types like strings, numbers, dates, or Collections (Lists, Maps etc) of these types it will work fine.
|If you don’t read any further, just pass around strings, numbers, dates etc or Collections thereof.|
If you attempt to pass something not simple, e.g. a JIRA project object you will get an exception:
Unresolved directive in content/common/remote-control.adoc - include::../jira/src/test/resources/com/onresolve/jira/groovy/test/workflow/testscripts/remoteevents/PassProjectFail.groovy[tags=ex1]
results in an exception:
Caused by: java.io.NotSerializableException: com.atlassian.jira.project.ProjectImpl.
This applies equally to returning objects from the closure. So, rather than returning a Collection of
Version objects we return just their names:
Unresolved directive in content/common/remote-control.adoc - include::../jira/src/test/resources/com/onresolve/jira/groovy/test/workflow/testscripts/remoteevents/PassProjectSuccess.groovy[tags=ex1]
Similarly, objects like ProjectManager etc. cannot be serialized and passed to the remote (or back), so you need to retrieve it from the
ComponentLocator on Bitbucket and Confluence) inside the closure.
You may recall that the
return keyword is optional in groovy… if omitted the result of the last statement executed in a closure or method will be returned.
This can cause inadvertent problems with remote control - for example, imagine a case where you are creating an issue on a remote instance:
Unresolved directive in content/common/remote-control.adoc - include::../jira/src/test/resources/com/onresolve/jira/groovy/test/workflow/testscripts/remoteevents/CreateIssueWithRC.groovy[tags=ex1]
createIssueObject returns an
Issue, and this statement is the last in the closure, the system attempts to return the
Issue object from the remote application. This will fail because
Issue is not serialisable:
io.remotecontrol.client.UnserializableReturnException: The return value of the command was not serializable, its string representation was 'JRA-13'
Solutions might be just returning the issue key, or, if you don’t care about the return value, return nothing:
Unresolved directive in content/common/remote-control.adoc - include::../jira/src/test/resources/com/onresolve/jira/groovy/test/workflow/testscripts/remoteevents/CreateIssueWithRC.groovy[tags=ex2]
Running code on multiple instances
By iterating through the application links of a particular application type, you can execute code on multiple instances sequentially:
Unresolved directive in content/common/remote-control.adoc - include::../jira/src/test/resources/com/onresolve/jira/groovy/test/workflow/testscripts/remoteevents/ExecuteOnAll.groovy[tags=ex1]
|1||get all JIRA-type application links|
Running code on unlinked instances
You can also execute code on instances without an application link, so long as you can authenticate as an administrator. Example:
Unresolved directive in content/common/remote-control.adoc - include::../jira/src/test/resources/com/onresolve/jira/groovy/test/workflow/testscripts/remoteevents/ExecuteHttp.groovy[tags=ex1]
Running code on different products
So far all the examples have focused on running code an another JIRA instance, or instances, from an existing JIRA. This is relatively easy, and the same approach works for all supported products.
What if you needed to run a script on Bitbucket from a JIRA instance? For example, you may wish to find all projects that don’t have a matching Bitbucket project (by key), or check that no users are permissioned in a Bitbucket repository except the developers role for the corresponding project.
In this case you need to use an API for an application that is not native - the problem is the Bitbucket API is not available from JIRA. Whilst you could do it all with reflection this is quite tricky.
What we do here is use Grape to grab the relevant API dependency. This will take a few seconds to download the relevant jar file, but that delay is only the first time the script is run.
Unresolved directive in content/common/remote-control.adoc - include::../jira/src/test/resources/com/onresolve/jira/groovy/test/workflow/testscripts/remoteevents/CheckProjectKeys.txt[tags=ex1]
|1||Grab the Bitbucket API dependency|
|2||a dummy variable, as annotations need to annotate "something"|
Synchronising Worklogs Example
Internally, we use several JIRA instances. We do our support and development on one instance, but have to log our time on another instance. We don’t want to duplicate issues across as this causes confusion, just on the time-tracking instance we just have several bucket tickets for the combination of products and whether it’s development, maintenance or support.
The problem with this is that the development leads cannot drill down to issues where the time spent. Our solution uses an event listener or worklog created/updated/deleted, and remote control to create or update the worklog on the target ticket.
The event listener looks like:
The script can be found here.
As in the example above, the