The Problem
One of the problems we encounter with migrating large Jira instances is that when it comes to JCMA, you either have to add all of the Advanced Roadmaps plans or none of them. There’s no facility for selectively adding Roadmaps plans.
One such migration involved moving a subset or portion of the instance data, rather than the whole thing. Though the instance had 1400+ Roadmaps plans, we only needed to migrate about 100 of them.
The solution we came up with was:
- Take a backup of the current production instance
- Delete all of the Advanced Roadmaps plans that didn’t need to be migrated
- Perform the migration
- Restore the production instance from the backup
Naturally, none of us fancied deleting 1300 Roadmaps plans by hand, especially if we had to do it more than once during the course of testing. So we scripted it.
The Solution
The actual deleting of an Advanced Roadmaps plan is pretty simple. To delete a plan, just send a DELETE request to the plan’s API endpoint, /rest/jpo/1.0/plans/<planID>. So long as you have a list of plan names or IDs that you want to keep, you can tell the script to delete any plans that don’t match an entry in that list.
The script below does a few things:
- First it retrieves a list of all of the Advanced Roadmaps plans in the system.
- Then it compares that list against the list of plans that you want to keep, which is defined in the toKeep array.
- Any plan with a name that doesn’t match an entry in toKeep gets added to a list of plans to be deleted.
- The script then loops through all of the plans to be deleted, and sends a DELETE request to the API endpoint along with the ID of the plan
import com.atlassian.sal.api.net.Request
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.properties.APKeys
import com.atlassian.sal.api.net.Response
import com.atlassian.sal.api.net.ResponseException
import com.atlassian.sal.api.net.ReturningResponseHandler
import com.atlassian.sal.api.net.TrustedRequest
import com.atlassian.sal.api.net.TrustedRequestFactory
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import groovyx.net.http.ContentType
import groovyx.net.http.URIBuilder
import java.net.URL;
def toKeep = ["1111", "22222", "33333"]
// Define the array of plan names that should be kept
def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def baseUrl = ComponentAccessor.applicationProperties.getString(APKeys.JIRA_BASEURL)
def trustedRequestFactory = ComponentAccessor.getOSGiComponentInstanceOfType(TrustedRequestFactory)
def endPointPath = '/rest/jpo/1.0/programs/list'
def url = baseUrl + endPointPath
// Reusable method for sending HTTP requests and parsing the JSON response
def sendRequest(url, method, headers=[:], body=null) {
def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def baseUrl = ComponentAccessor.applicationProperties.getString(APKeys.JIRA_BASEURL)
def trustedRequestFactory = ComponentAccessor.getOSGiComponentInstanceOfType(TrustedRequestFactory)
def request = trustedRequestFactory.createTrustedRequest(method, url) as TrustedRequest
headers.each { k, v -> request.addHeader(k, v) }
request.addTrustedTokenAuthentication(new URIBuilder(baseUrl).host, currentUser.name)
request.addHeader("X-Atlassian-Token", 'no-check')
if (body != null) {
request.setEntity(new JsonBuilder(body).toString())
}
try {
def response = request.executeAndReturn(new ReturningResponseHandler<Response, Object>() {
Object handle(Response response) throws ResponseException {
if (response.statusCode != HttpURLConnection.HTTP_OK) {
log.error "Received an error while posting to the rest api. StatusCode=$response.statusCode. Response Body: $response.responseBodyAsString"
return null
} else {
return new JsonSlurper().parseText(response.responseBodyAsString)
}
}
})
return response
} catch (Exception e) {
log.warn(e)
return null
}
}
// Retrieve a list of all of the Roadmaps Plans in the system
def response = sendRequest(url, Request.MethodType.GET, ["Content-Type": ContentType.JSON.toString()])
def jsonResp = response ?: [:]
// Filter out the plans to be deleted
def plansToDelete = jsonResp.plans.findAll {
plan -> !toKeep.contains(plan.title.toString())
}
// Delete the unwanted plans
plansToDelete.each {
plan ->
endPointPath = '/rest/jpo/1.0/plans/' + plan.id.toString()
url = baseUrl + endPointPath
sendRequest(url, Request.MethodType.DELETE, ["Content-Type": ContentType.JSON.toString()])
}
Leave a Reply