Getting Started

Features

Configuration

This page is for users who want to move from Confluence Server to Confluence Cloud or vice-versa and want to maintain ScriptRunner functionality.

Migration

Due to the differences in the programming model and APIs, no automatic migration between Server and Cloud exists. The process of migration involves analysing the desired functionality and translating that into the target platform. Confluence Cloud uses the Atlassian Connect framework to allow apps like ScriptRunner to provide additional functionality whereas Confluence Server (and Data Center) uses the Atlassian Plugins framework, known as Plugins v2 or P2. There are fundamental differences between Connect and P2 that directly impact ScriptRunner functionality and script writing. Some of the differences are:

Table 1. Platform Differences
P2 Connect

API

Java API - it is possible to call almost any of the Host application code directly. Plugins are running in the same JVM as the host application.

REST APIs are available to interact with the host application. Connect has access to a limited set of REST APIs, this means that Connect add-ons can only do things officially supported by the APIs, but it also means that APIs are less likely to change and add-ons are less likely to break as a result.

UI

Any part of the UI can be customised. Javascript runs as on the page in the same context and frame as the host application. Host application code can be manipulated and broken by add-ons and add-on code can easily be broken by host code (or its dependent libraries) updates.

Specific areas can be cusomised. Each add-on runs in its own iframe and JavaScript Sandbox. Interaction with the host page is via a JavaScript API.

Programming Model

In the same JVM as the host application. Events can be vetoed and code runs synchronously. A bug in add-on code can seriously affect application stability or performance.

Asynchronous. Runs as a completely separate service. No code runs in the same JVM as the host. Events are processed as webhooks that fired from the host and processed asynchronously by the add-on - no waiting for events to be processed. A failing add-on does not impact the performance of an Atlassian application (although functionality may be missing).

Authentication and Authorisation

Code can run as any user in the system including as no user (which is not the same as Anonymous). Authentication and authorisation can be bypassed.

Add-ons make requests as the add-on user or the user that initiated the interaction. It is not possible to bypass authorisation or authentication

In summary there are significant differences between the Connect and P2 frameworks and it is necessary to re-think how ScriptRunner scripts execute and interact with the host application when moving from Server to Cloud or Cloud to Server. The asynchronous nature of Connect has implications with regards to host interaction, for example in Confluence it is not possible to implement scripted validators, conditions or event listeners that prevent event processing. Similarly when writing ScriptRunner scripts for server hosts it is important to consider the impact that scripts will have, particularly when interacting with external systems that might be slow. As with most programming situations there are trade offs when writing scripts for both Cloud and Server and the limitations of Cloud are at the expense of stability and performance of the Atlassian application.

Features

ScriptRunner for Confluence Cloud is a product that is under development and investment is being made to add features to the platform. It doesn’t currently have the same feature set as the server version and some features are likely never to come to Cloud due to restrictions in the Cloud platform.

Table 2. Feature Differences
Feature Cloud Status

Script Console

Implemented, implementation differs when compared to Server due to inherent differences between Server and Cloud

Built-in scripts

Implemented Bulk Add/Remove Labels on One or More Pages, Bulk Delete Attachments, Bulk Delete Comments from One or More Pages, Bulk Purge Trash, Copy Page Tree, Copy Space, Delete Page Tree, Rename Labels

Script Listeners

Implemented, implementation differs when compared to Server due to inherent differences between Server and Cloud

Script Macros

Not implemented, but on the roadmap, implementation will differ when compared to Server due to inherent differences between Server and Cloud

Search Extractors

Not implemeted and not on roadmap - Currently not possible to implement due to Atlassian’s Cloud implementation

Script CQL Functions

Not implemeted and not on roadmap - Currently not possible to implement due to Atlassian’s Cloud implementation

Script Fragments

Implemented, implementation differs when compared to Server due to inherent differences between Server and Cloud

Script Jobs

Implemented, implementation differs when compared to Server due to inherent differences between Server and Cloud

Space Admin Scripts

Implemented

REST API

Needs further input into use cases

Feedback on all these features would be most welcome via the Support Portal.

Migrating from ScriptRunner for Confluence Server to Cloud

Any migration from Server to Cloud will require rewriting scripts

The general approach to migrating a script from Server to Cloud is to analyse the purpose of the script, find the equivalent feature in Cloud (we have named them the same where possible) and rewrite the script. Interactions with APIs need to be replaced with calls to the appropriate REST APIs and it is possible that alternatives may need to be found for dependencies that are not available for ScriptRunner Cloud.

Grouping together operations

In a server script it is typical to call page.setX() many time to update several fields of a page at a time. When using ScriptRunner for Confluence Cloud these updates should be put into one page update POST call to ensure that they are all included in the same change-set. Multiple calls to update the page will issue multiple notifications which is not desirable. For example see the following two scripts which are from Workflow Functions:

import com.atlassian.confluence.core.DefaultSaveContext
import com.atlassian.confluence.pages.Page
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.spaces.Space
import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.confluence.user.UserAccessor
import com.atlassian.sal.api.component.ComponentLocator

def pageManager = ComponentLocator.getComponent(PageManager)
def spaceManager = ComponentLocator.getComponent(SpaceManager)
def userAccessor = ComponentLocator.getComponent(UserAccessor)

def spaceKey = "DS"
def parentTitle = "Parent Page"
def pageTitle = "New Page"

Space space = spaceManager.getSpace(spaceKey)
if (space == null) {
    return "Space " + spaceKey + " not found"
}

Page parent = null

if (parentTitle != null) {
    log.debug("Parent page: {}", parentTitle)
    parent = pageManager.getPage(spaceKey, parentTitle)
    if (parent == null) {
        return "Parent not found: " + parentTitle
    }
}

Page page = pageManager.getPage(spaceKey, pageTitle)
Page originalPage = null;
if (page != null) {
    try {
        originalPage = (Page) page.clone()
    } catch (CloneNotSupportedException e) {
        return "Failed to clone the page"
    }
} else {
    page = new Page();
    page.setCreator(userAccessor.getUser("rfranco"))
    page.setCreationDate(new Date())
}
page.setTitle(pageTitle)
page.setSpace(space)
if (parent != null) {
    parent.addChild(page)
}

page.setBodyAsString("<ac:structured-macro ac:macro-id=\"d9988eac-6ebd-483f-9299-524ed6ba3cc7\" ac:name=\"mail-form\" ac:schema-version=\"1\">\n" +
    "    <ac:parameter ac:name=\"generatedUUID\">efd69365-bd02-4486-872a-c45537b51c0e</ac:parameter>\n" +
    "    <ac:parameter ac:name=\"destination\">test-config</ac:parameter>\n" +
    "    <ac:rich-text-body>\n" +
    "        <p>\n" +
    "            <ac:structured-macro ac:macro-id=\"d4d15fb0-81fd-4799-81fc-92f9ae613713\" ac:name=\"mail-input\"\n" +
    "                                 ac:schema-version=\"1\">\n" +
    "                <ac:parameter ac:name=\"labelText\">First name</ac:parameter>\n" +
    "                <ac:parameter ac:name=\"name\">First name</ac:parameter>\n" +
    "                <ac:parameter ac:name=\"required\">true</ac:parameter>\n" +
    "                <ac:parameter ac:name=\"validation\">alpha</ac:parameter>\n" +
    "            </ac:structured-macro>\n" +
    "        </p>\n" +
    "    </ac:rich-text-body>\n" +
    "</ac:structured-macro>")


DefaultSaveContext saveContext = new DefaultSaveContext(true, false, true)
if (originalPage != null) {
    pageManager.saveContentEntity(page, originalPage, saveContext)
} else {
    pageManager.saveContentEntity(page, saveContext)
}
package com.adaptavist.sr.cloud.samples.console
//required imports
import org.apache.commons.codec.binary.Base64

// Specify all the required parameters
def sourcePageId = "<SourcePageIDHere>"
def parentPageId = "<ParentPageIDHere>"
def pageTitle = "<Page Title Here>"
def spaceKey = "<Space Key Here>"

// Specify crednetails to access Confluence

String authString = "<UsernameHere>"+":"+"<PasswordHere>"
byte[] authEncBytes = Base64.encodeBase64(authString.getBytes())
String authStringEnc = new String(authEncBytes)

def confluenceBaseURL ="<ConfluenceBaseURLHere>"

// Get the content of the source page
def template = get ("${confluenceBaseURL}/rest/api/content/${sourcePageId}?expand=body.storage").header("Authorization", "Basic ${authStringEnc}").asObject(Map).body

// Specify the body of the rest request
def body = [
    type: "page",
    title: pageTitle,
    space: [
        key: spaceKey
    ],
    ancestors: [[
                    id: parentPageId
                ]],
    body: template.body
]

//create confluence (cloud) page
def createPageResult = post("${confluenceBaseURL}/rest/api/content")
    .header("Content-Type", "application/json")
    .header("Accept", "application/json")
    .header("Authorization", "Basic ${authStringEnc}")
    .body(body)
    .asObject(Map)



return createPageResult

Notice that the Cloud script is much shorter, and all of the updates occur in the same API call. There are no managers involved or other API calls. The documentation for the create/update is much simpler and smaller than the equivalent server implementation too.

Assertions for response codes will print out the response body and relevant information. A good pattern is to use assert resp.status == 200 (replace 200 with the correct response code). Successful APIs call may respond with 204, others with 200, 201 or 303 depending on the API in use.

Logging

Logging in scripts is very helpful when debugging. In cloud scripts anything printed to stdout using println, or using a logger.info('message') call will be available in the Logs page, and in the execution history of Script Listeners and Script Jobs. As noted above, usage of assertions can also help debugging and diagnosing the behaviour of scripts.

Pitfalls

Asynchronous Event Processing

Asynchronous execution of functions and events means that any updates performed by scripts will happen after the page loads for the user. However, ScriptRunner has a feature that will notify users when issues have been updated. The order of event processing is not preserved, so when multiple scripts are triggered from the same event the ordering is arbitrary and will likely not be consistent.

Event Triggering

Updates made in scripts will cause events to be fired. This means that Script Listeners need to check values before updating them as the script may trigger due to a previous invocation of the same script.

Migration Approach

Migration has three stages. First analysis of the functionality of the existing scripts, next an analysis of the APIs and ScriptRunner features that should be created and finally the implementation of the new scripts.

Each interaction with Confluence should be compared against the REST API docs and the ScriptRunner features. Some features of ScriptRunner for Confluence have not yet been implemented in the cloud edition. Feature requests and Suggestions are welcome in the ScriptRunner Cloud support portal.

The final task is to re-implement the relevant scripts using ScriptRunner for Confluence Cloud.

Please ask on Atlassian Answers for any migration help. Be sure to mention Cloud or Server in your question.

Migrating from ScriptRunner for Confluence Cloud to Server

Any migration from Cloud to Server will require rewriting scripts

The process of migrating from Cloud to Server is a more straight forward procedure, but care still has to be taken. All ScriptRunner features in the Cloud version are available in the Server edition. The process for migration follows the same general path of discovery, analysis and implementation so much of the migration approach for Server to Cloud also applies to Cloud to Server.

First find all scripts in ScriptRunner for Confluence Cloud and analyse the functionality. Next each script needs to be ported from the REST API into the Java API. The ScriptRunner for Confluence Server documentation has examples and guides for implementing scripts.