ScriptRunner scripts for cloning projects

Jira Server / Data Center

1. ScriptRunner GUI for copying project

One of the ScriptRunner features is the Copy project built-in script with a GUI. You can find it in the Built-in Scripts (Administration -> ScriptRunner -> Build-in Scripts). It is called Copy project.

This image has an empty alt attribute; its file name is image-1024x485.png

After it opens, the user can define a source project and some necessary parameters.

  • Target project key.
  • Target project name.
  • Whether all versions, components, issues, dashboards and filters should be copied.
This image has an empty alt attribute; its file name is image-1-1024x892.png

2. Clone a project using Groovy scripts.

The method showed in point 1 allows for manual project cloning, but administrator expectations could be much more complex, e.g.

  • Projects cloning after transitions.
  • Requirement, that each new project must be cloned from a template.
  • Modifying a project after creation, e.g. change a leader, members, description, etc.

All such operations require a custom Groovy script which will do these. If we use the ScriptRunner plugin we can take a script hidden under the GUI, and put it in the console. So let’s create a simple code which can be run individually or as a post-function script. We will use methods from the ScriptRunner CopyProject class (com.onresolve.scriptrunner.canned.jira.admin.CopyProject). Before I describe the script, let’s refer to the manufacturer’s guidance that recommends running copy project in threads (see SRJIRA-3793).

Groovy
import com.atlassian.jira.util.thread.JiraThreadLocalUtils;

// Initialize a thread.
Thread executorThread = new Thread(JiraThreadLocalUtils.wrap() {
    // Cloning a project code.
})

// Start the thread.
executorThread.start();

We don’t need to discuss, is it a bug or feature, let’s just explain what the copying problem is. Each project contains many elements such as components, versions, schemas, and issues (sometimes there are many issues to copy). The entire copying process contains many individual threads and can take several milliseconds or seconds. The operation executing order is critical, e.g., creating issues processes should start after creating project completion; sub-tasks creation should start only after task creation is finished.

If we don’t wrap the processes in a JiraThreadLocalUtils we can’t be sure that all operations will be synchronized correctly, and it will prevent random threads execution. Here is an example script which clone selected project.

Groovy
// This example is from the book: J. Kalinowski, Atlassian Jira Server & Data Center (Helion, Gliwice, 2023).

import com.atlassian.jira.util.thread.JiraThreadLocalUtils;

// 1. Get an ApplicationUser object with 'create project' privileges
def adminUser = ComponentAccessor.getUserManager().getUserByName("admin");

// 2. Get managers.
def projectManager = ComponentAccessor.getProjectManager();

// 3. Set project parameters.

// A source project key.
def sourceProjectKey = "TBP";

// A target project key and name.
def newProjectKey = "BP1";
def newProjectName = "Business project - Processing sth";

// 4. Run a cloning process.

// 4.1. Initialize a thread and use the JiraThreadLocalUtils interface to wrap a cloning code.
Thread executorThread = new Thread(JiraThreadLocalUtils.wrap() {

    // 4.2. Create a CopyProject object.
    def copyProject = new CopyProject();

    // 4.3 Set cloned project parameters.
    def copyProjectSettings = [
      (CopyProject.FIELD_SOURCE_PROJECT) : sourceProjectKey,
      (CopyProject.FIELD_TARGET_PROJECT) : newProjectKey,
      (CopyProject.FIELD_TARGET_PROJECT_NAME) : newProjectName,
      (CopyProject.FIELD_COPY_VERSIONS) : true,
      (CopyProject.FIELD_COPY_COMPONENTS) : true,
      (CopyProject.FIELD_COPY_ISSUES) : true,
      (CopyProject.FIELD_COPY_DASH_AND_FILTERS) : true,
    ];

    // 4.4. Validate project parameters.
    def validateResult = copyProject.doValidate(copyProjectSettings, false);

    if(validateResult.hasAnyErrors()) {
        // 4.5. Log errors if validation failed.
        log.warn("Couldn't create project: $validateResult");
    } else {
        // 4.6. [OPTION] Switch a current user to the admin user.
        ComponentAccessor.getJiraAuthenticationContext().setLoggedInUser(adminUser);
        
        // 4.7. Clone a project.
        copyProject.doScript(copyProjectSettings);
    }
});

// 4.8. Run the thread.
executorThread.start();

The synchronization problem shows up in post-functions, e.g. if we would like to create a project and directly change something on it after creation. I this case we should run both operation in speared thread using Thread.join() method. It could look like below.

Groovy
// 1. Initialize the first thread.
Thread executorThread1 = new Thread(JiraThreadLocalUtils.wrap() {
  // Cloning a project code.
});

// 2. Initialize the second thread.
Thread executorThread2 = new Thread(JiraThreadLocalUtils.wrap() {
  // Modify something in previously created project.
});

// 3. Run and synchronize threads.
// The second thread will wait for the first one completion.
executorThread1.start();
executorThread1.join();

executorThread2.start();
executorThread2.join();

In the listing below, you have complete code for creating and modifying a project in post-functions.

Groovy
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.project.Project;
import com.onresolve.scriptrunner.canned.jira.admin.CopyProject;
import com.atlassian.jira.util.thread.JiraThreadLocalUtils;
import com.atlassian.jira.project.UpdateProjectParameters;


// ##### I. Copying a project (the first thread).
// 1. Get an ApplicationUser object with 'create project' privileges
def adminUser = ComponentAccessor.getUserManager().getUserByName("admin");

// 2. Get managers.
def projectManager = ComponentAccessor.getProjectManager();

// 3. Set project parameters.

// A source project key.
def sourceProjectKey = "TBP";

// A target project key and name.
def newProjectKey = "BP1";
def newProjectName = "Business project - Processing sth";

// 4. Run a cloning process.
// 4.1. Initialize a thread and use the JiraThreadLocalUtils interface to wrap a cloning code.
Thread executorThread1 = new Thread(JiraThreadLocalUtils.wrap() {

    // 4.2. Create a CopyProject object.
    def copyProject = new CopyProject();

    // 4.3 Set cloned project parameters.
    def copyProjectSettings = [
    (CopyProject.FIELD_SOURCE_PROJECT) : sourceProjectKey,
    (CopyProject.FIELD_TARGET_PROJECT) : newProjectKey,
    (CopyProject.FIELD_TARGET_PROJECT_NAME) : newProjectName,
    (CopyProject.FIELD_COPY_VERSIONS) : true,
    (CopyProject.FIELD_COPY_COMPONENTS) : true,
    (CopyProject.FIELD_COPY_ISSUES) : true,
    (CopyProject.FIELD_COPY_DASH_AND_FILTERS) : true,
    ];

    // 4.4. Validate project parameters.
    def validateResult = copyProject.doValidate(copyProjectSettings, false);

    if(validateResult.hasAnyErrors()) {
        // 4.5. Log errors if validation failed.
        log.warn("Couldn't create project: ${validateResult}");
    } else {
        // 4.6. [OPTION] Switch a current user to the admin user.
        ComponentAccessor.getJiraAuthenticationContext().setLoggedInUser(adminUser);
        
        // 4.7. Clone a project.
        copyProject.doScript(copyProjectSettings);
    }
});

// ##### II. Modify previously created project (the second thread).
Thread executorThread2 = new Thread(JiraThreadLocalUtils.wrap() {
    // 5. Get previously created project object.
    def newProjectObject = projectManager.getProjectObjByKey(newProjectKey);
    
    // 6. Set projects parameters to change.
    def updateProjectParameters = UpdateProjectParameters
        .forProject(newProjectObject.getId())
        .description("Some new description") // New project description.
        .leadUserKey("JIRAUSER10000"); // New project lead.
    
    // 7. Update a project.
    projectManager.updateProject(updateProjectParameters);
});

// 8. Run and sync threads.
executorThread1.start();
executorThread1.join();

executorThread2.start();
executorThread2.join();

More information about cloning and modifying projects you can find in my book.

Go to top