Page History
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate import groovy.json.JsonOutput import groovy.transform.BaseScript import groovy.json.JsonSlurper; import groovy.json.StreamingJsonBuilder; import javax.ws.rs.core.MultivaluedMap import javax.ws.rs.core.Response @BaseScript CustomEndpointDelegate delegate triggerJenkinsBuild(httpMethod: "GET") { MultivaluedMap queryParams -> def issueId = queryParams.getFirst("issueId") as String // use the issueId to retrieve this issue def flag = [ type : 'success', title: "Build scheduled", close: 'auto', body : "A new build has been scheduled related with "+issueId ] URL url; def jobName = "java-junit-calc" // could come from a CF in the Test Plan def jenkinsHostPort = "192.168.56.102:8081" // could be defined elsewhere def token = "iFBDOBhNhaxL4T9ass93HRXun2JF161Z" // could also come from a CF in the Test Plan def username = "admin" // probably, would need to be stored elsewhere def password = "fa02840152aa2e4da3d8db933ec708d6" // probably, would need to be stored elsewhere def baseURL = "http://${jenkinsHostPort}/job/${jobName}/buildWithParameters?token=${token}&TESTPLAN=$issueId" url = new URL(baseURL); def body_req = [] def authString = "${username}:${password}".bytes.encodeBase64().toString() URLConnection connection = url.openConnection(); connection.requestMethod = "POST" connection.doOutput = true connection.addRequestProperty("Authorization", "Basic ${authString}") connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8") connection.outputStream.withWriter("UTF-8") { new StreamingJsonBuilder(it, body_req) } connection.connect(); log.debug(connection.getResponseCode()) log.debug(connection.getResponseMessage()) if (connection.getResponseCode() == 201) { Response.ok(JsonOutput.toJson(flag)).build() } else { //Response.status(Response.Status.NOT_FOUND).entity("Problem scheduling job!").build(); } } |
Example
...
ScriptRunner configuration
Jenkins configuration
In Jenkins, we need to generate an API token for some user, which can be done from the profile settings page.
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
import com.atlassian.jira.issue.Issue import com.atlassian.jira.issue.link.IssueLinkManager import com.atlassian.jira.issue.link.IssueLinkType import com.atlassian.jira.issue.link.IssueLinkTypeManager import com.atlassian.jira.ComponentManager import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.jql.builder.JqlQueryBuilder import com.atlassian.jira.user.util.UserUtil import com.atlassian.jira.user.util.UserManager; import com.atlassian.jira.bc.issue.IssueService import com.atlassian.jira.bc.issue.search.SearchService; import com.atlassian.jira.issue.search.SearchProvider import com.atlassian.jira.issue.search.SearchResults import com.atlassian.jira.web.bean.PagerFilter; import com.atlassian.jira.issue.MutableIssue import com.atlassian.jira.user.UserPropertyManager import com.atlassian.jira.propertyset.JiraPropertySetFactory; import com.google.common.collect.ImmutableMap; import com.opensymphony.module.propertyset.PropertySet; import com.opensymphony.module.propertyset.PropertySetManager; import com.atlassian.jira.util.BuildUtils import com.atlassian.jira.util.BuildUtilsInfo import com.atlassian.jira.util.BuildUtilsInfoImpl import com.atlassian.plugin.PluginAccessor import com.atlassian.plugin.PluginManager import com.atlassian.jira.bc.license.JiraLicenseService import com.atlassian.jira.bc.license.JiraLicenseServiceImpl import org.apache.log4j.Level import org.apache.log4j.Logger import com.atlassian.jira.issue.IssueManager import com.opensymphony.workflow.InvalidInputException import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate import groovy.json.JsonOutput import groovy.transform.BaseScript import groovy.json.JsonSlurper; import groovy.json.StreamingJsonBuilder; import javax.ws.rs.core.MultivaluedMap import javax.ws.rs.core.Response @BaseScript CustomEndpointDelegate delegate issueManager = ComponentAccessor.getIssueManager() searchService = ComponentAccessor.getComponent(SearchService.class); serviceAccount = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser() customFieldManager = ComponentAccessor.getCustomFieldManager() Object getIssues(jqlQuery){ // A list of GenericValues representing issues List<Issue> searchResults = null; SearchService.ParseResult parseResult = searchService.parseQuery(serviceAccount, jqlQuery); if (parseResult.isValid()) { // throws SearchException SearchResults results = searchService.search(serviceAccount, parseResult.getQuery(), PagerFilter.getUnlimitedFilter()); searchResults = results.getIssues(); return searchResults; } return [] } Object getFieldValue(issue,customField) { //def cField = customFieldManager.getCustomFieldObject(customField) def cField = customFieldManager.getCustomFieldObjectByName(customField) def cFieldValue = issue.getCustomFieldValue(cField) return cFieldValue } String replaceLast(String string, String substring, String replacement) { int index = string.lastIndexOf(substring); if (index == -1) return string; return string.substring(0, index) + replacement + string.substring(index+substring.length()); } triggerJenkinsBuildWithTestList(httpMethod: "GET") { MultivaluedMap queryParams -> // the details of getting and modifying the current issue are ommitted for brevity def issueId = queryParams.getFirst("issueId") as String // use the issueId to retrieve this issue def flag = [ type : 'success', title: "Build scheduled", close: 'auto', body : "A new build has been scheduled related with "+issueId ] URL url; def jobName = "java-junit-calc-triggered" // could be defined in a CF in the Test Plan def jenkinsHostPort = "192.168.56.102:8081" // could be defined elsewhere def token = "iFBDOBhNhaxL4T9ass93HRXun2JF161Z" // could also come from a CF in the Test Plan def username = "admin" // probably, would need to be stored elsewhere def password = "fa02840152aa2e4da3d8db933ec708d6" // probably, would need to be stored elsewhere //def baseURL = "http://${username}:${password}@${jenkinsHostPort}/job/${jobName}/build?token=${token}" //def baseURL = "http://${jenkinsHostPort}/job/${jobName}/build?token=${token}" jql = "issue in testPlanTests('${issueId}') and \"Test Type\" = Generic" issues = getIssues(jql) // we're assuming that we have Junit based Tests.. so we need to do some conversion beforehand, so maven can process the list of tests to be run def testlist = issues.collect { getFieldValue(it,"Generic Test Definition")} def testlist2 = testlist.collect { replaceLast(it,".","%23") } def baseURL = "http://${jenkinsHostPort}/job/${jobName}/buildWithParameters?token=${token}&TESTPLAN=${issueId}&TESTLIST=${testlist2.join(',')}" url = new URL(baseURL); def body_req = [] def authString = "${username}:${password}".bytes.encodeBase64().toString() URLConnection connection = url.openConnection(); connection.requestMethod = "POST" connection.doOutput = true connection.addRequestProperty("Authorization", "Basic ${authString}") connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8") connection.outputStream.withWriter("UTF-8") { new StreamingJsonBuilder(it, body_req) } connection.connect(); //connection.getContent(); log.debug(connection.getResponseCode()) log.debug(connection.getResponseMessage()) if (connection.getResponseCode() == 201) { Response.ok(JsonOutput.toJson(flag)).build() } else { //Response.status(Response.Status.NOT_FOUND).entity("Problem scheduling job!").build(); } } |
Example
...
ScriptRunner configuration
Jenkins configuration
In Jenkins, we need to generate an API token for some user, which can be done from the profile settings page.
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate import groovy.json.JsonOutput import groovy.transform.BaseScript import groovy.json.JsonSlurper; import groovy.json.StreamingJsonBuilder; import javax.ws.rs.core.MultivaluedMap import javax.ws.rs.core.Response import java.nio.charset.StandardCharsets @BaseScript CustomEndpointDelegate delegate triggerBambooBuild(httpMethod: "GET") { MultivaluedMap queryParams -> def issueId = queryParams.getFirst("issueId") as String // use the issueId to retrieve this issue def flag = [ type : 'success', title: "Build scheduled", close: 'auto', body : "A new build has been scheduled related with "+issueId ] URL url; // curl --user admin:admin -X POST -d "default&ExecuteAllStages=true" http://yourbambooserver/rest/api/latest/queue/XRAY-JUNITCALC def projectKey = "XRAY" // could come from a CF in the Test Plan def planKey = "JUNITCALC" // could come from a CF in the Test Plan def bambooHostPort = "192.168.56.102:8085" // could be defined elsewhere def username = "admin" // probably, would need to be stored elesewhere def password = "admin" // probably, would need to be stored elesewhere def baseURL = "http://${bambooHostPort}/rest/api/latest/queue/${projectKey}-${planKey}" String urlParameters = "default&ExecuteAllStages=true&bamboo.TESTPLAN=${issueId}"; byte[] postData = urlParameters.getBytes( StandardCharsets.UTF_8 ); int postDataLength = postData.length; url = new URL(baseURL); def body_req = [] def authString = "${username}:${password}".bytes.encodeBase64().toString() URLConnection connection = url.openConnection(); connection.requestMethod = "POST" connection.doOutput = true connection.addRequestProperty("Authorization", "Basic ${authString}") connection.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty( "charset", "utf-8"); connection.setRequestProperty( "Content-Length", Integer.toString( postDataLength )); connection.setUseCaches( false ); DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); wr.write( postData ); connection.connect(); //connection.getContent(); log.debug(connection.getResponseCode()) log.debug(connection.getResponseMessage()) if (connection.getResponseCode() == 200) { Response.ok(JsonOutput.toJson(flag)).build() } else { //Response.status(Response.Status.NOT_FOUND).entity("Problem scheduling job!").build(); } } |
Example
ScripRunner configuration
Bamboo configuration
The project itself is a normal one; the only thing relevant to mention is that this project is a parameterized one, so it receives a TESTPLAN variable, that in our case will be coming from Jira.
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
import com.atlassian.jira.issue.Issue import com.atlassian.jira.issue.link.IssueLinkManager import com.atlassian.jira.issue.link.IssueLinkType import com.atlassian.jira.issue.link.IssueLinkTypeManager import com.atlassian.jira.ComponentManager import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.jql.builder.JqlQueryBuilder import com.atlassian.jira.user.util.UserUtil import com.atlassian.jira.user.util.UserManager; import com.atlassian.jira.bc.issue.IssueService import com.atlassian.jira.bc.issue.search.SearchService; import com.atlassian.jira.issue.search.SearchProvider import com.atlassian.jira.issue.search.SearchResults import com.atlassian.jira.web.bean.PagerFilter; import com.atlassian.jira.issue.MutableIssue import com.atlassian.jira.user.UserPropertyManager import com.atlassian.jira.propertyset.JiraPropertySetFactory; import com.google.common.collect.ImmutableMap; import com.opensymphony.module.propertyset.PropertySet; import com.opensymphony.module.propertyset.PropertySetManager; import com.atlassian.jira.util.BuildUtils import com.atlassian.jira.util.BuildUtilsInfo import com.atlassian.jira.util.BuildUtilsInfoImpl import com.atlassian.plugin.PluginAccessor import com.atlassian.plugin.PluginManager import com.atlassian.jira.bc.license.JiraLicenseService import com.atlassian.jira.bc.license.JiraLicenseServiceImpl import org.apache.log4j.Level import org.apache.log4j.Logger import com.atlassian.jira.issue.IssueManager import com.opensymphony.workflow.InvalidInputException import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate import groovy.json.JsonOutput import groovy.transform.BaseScript import groovy.json.JsonSlurper; import groovy.json.StreamingJsonBuilder; import javax.ws.rs.core.MultivaluedMap import javax.ws.rs.core.Response import java.nio.charset.StandardCharsets @BaseScript CustomEndpointDelegate delegate issueManager = ComponentAccessor.getIssueManager() searchService = ComponentAccessor.getComponent(SearchService.class); serviceAccount = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser() customFieldManager = ComponentAccessor.getCustomFieldManager() Object getIssues(jqlQuery){ // A list of GenericValues representing issues List<Issue> searchResults = null; SearchService.ParseResult parseResult = searchService.parseQuery(serviceAccount, jqlQuery); if (parseResult.isValid()) { // throws SearchException SearchResults results = searchService.search(serviceAccount, parseResult.getQuery(), PagerFilter.getUnlimitedFilter()); searchResults = results.getIssues(); return searchResults; } return [] } Object getFieldValue(issue,customField) { def cField = customFieldManager.getCustomFieldObjectByName(customField) def cFieldValue = issue.getCustomFieldValue(cField) return cFieldValue } String replaceLast(String string, String substring, String replacement) { int index = string.lastIndexOf(substring); if (index == -1) return string; return string.substring(0, index) + replacement + string.substring(index+substring.length()); } triggerBambooBuildWithTestList(httpMethod: "GET") { MultivaluedMap queryParams -> def issueId = queryParams.getFirst("issueId") as String // use the issueId to retrieve this issue def flag = [ type : 'success', title: "Build scheduled", close: 'auto', body : "A new build has been scheduled related with "+issueId ] jql = "issue in testPlanTests('${issueId}') and \"Test Type\" = Generic" issues = getIssues(jql) // // we're assuming that we have Junit based Tests.. so we need to do some conversion beforehand, so maven can process the list of tests to be run def testlist = issues.collect { getFieldValue(it,"Generic Test Definition")} def testlist2 = testlist.collect { replaceLast(it,".","%23") } URL url; // curl --user admin:admin -X POST -d "default&ExecuteAllStages=true&bamboo.TESTLIST=com.xpand.java.CalcTest#CanAddNumbers" http://yourbambooserver/rest/api/latest/queue/XRAY-JUNITCALCPARAMS def projectKey = "XRAY" // could come from a CF in the Test Plan def planKey = "JUNITCALCPARAMS" // could come from a CF in the Test Plan def stage = "default" // could be hardcoded or come from a CF in the Test Plan def bambooHostPort = "192.168.56.102:8085" // could be defined elsewhere def username = "admin" // probably, would need to be stored elsewhere def password = "admin" // probably, would need to be stored elsewhere def baseURL = "http://${bambooHostPort}/rest/api/latest/queue/${projectKey}-${planKey}" String urlParameters = "${stage}&ExecuteAllStages=true&bamboo.TESTPLAN=${issueId}&bamboo.TESTLIST=${testlist2.join(',')}"; byte[] postData = urlParameters.getBytes( StandardCharsets.UTF_8 ); int postDataLength = postData.length; url = new URL(baseURL); def body_req = [] def authString = "${username}:${password}".bytes.encodeBase64().toString() URLConnection connection = url.openConnection(); connection.requestMethod = "POST" connection.doOutput = true connection.addRequestProperty("Authorization", "Basic ${authString}") connection.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty( "charset", "utf-8"); connection.setRequestProperty( "Content-Length", Integer.toString( postDataLength )); connection.setUseCaches( false ); DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); wr.write( postData ); connection.connect(); //connection.getContent(); log.debug(connection.getResponseCode()) log.debug(connection.getResponseMessage()) if (connection.getResponseCode() == 200) { Response.ok(JsonOutput.toJson(flag)).build() } else { //Response.status(Response.Status.NOT_FOUND).entity("Problem scheduling job!").build(); } } |
Example
ScripRunner configuration
Bamboo configuration
...
- receive the source issue key that triggered the synchronization request (e.g. Test Plan, Test Execution, Sub Test Execution)
- obtain the linked Test Sets, using JQL
- obtain the Tests on each Test Set, using JQL
- submit a REST API request to Xray specific endpoints to add the Test to the entity (e.g Test Plan)
ScripRunner configuration
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate import groovy.json.JsonOutput import groovy.transform.BaseScript import groovy.json.JsonSlurper; import groovy.json.StreamingJsonBuilder; import javax.ws.rs.core.MultivaluedMap import javax.ws.rs.core.Response import java.nio.charset.StandardCharsets import com.atlassian.jira.issue.Issue import com.atlassian.jira.issue.link.IssueLinkManager import com.atlassian.jira.issue.link.IssueLinkType import com.atlassian.jira.issue.link.IssueLinkTypeManager import com.atlassian.jira.ComponentManager import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.jql.builder.JqlQueryBuilder import com.atlassian.jira.user.util.UserUtil import com.atlassian.jira.user.util.UserManager; import com.atlassian.jira.bc.issue.IssueService import com.atlassian.jira.bc.issue.search.SearchService; import com.atlassian.jira.issue.search.SearchProvider import com.atlassian.jira.issue.search.SearchResults import com.atlassian.jira.web.bean.PagerFilter; import com.atlassian.jira.issue.MutableIssue import com.atlassian.jira.user.UserPropertyManager import com.atlassian.jira.propertyset.JiraPropertySetFactory; import com.google.common.collect.ImmutableMap; import com.opensymphony.module.propertyset.PropertySet; import com.opensymphony.module.propertyset.PropertySetManager; import com.atlassian.jira.util.BuildUtils import com.atlassian.jira.util.BuildUtilsInfo import com.atlassian.jira.util.BuildUtilsInfoImpl import com.atlassian.plugin.PluginAccessor import com.atlassian.plugin.PluginManager import com.atlassian.jira.bc.license.JiraLicenseService import com.atlassian.jira.bc.license.JiraLicenseServiceImpl import org.apache.log4j.Level import org.apache.log4j.Logger import com.atlassian.jira.issue.IssueManager import groovy.json.StreamingJsonBuilder @BaseScript CustomEndpointDelegate delegate boolean associateTestsToXrayIssue(endpoint, issueKey,listOfTestKeys){ def jiraBaseUrl = com.atlassian.jira.component.ComponentAccessor.getApplicationProperties().getString("jira.baseurl") def endpointUrl = "${jiraBaseUrl}/rest/raven/1.0/api/${endpoint}/${issueKey}/test" log.debug("issueKey: "+issueKey) log.debug("listOfTestKeys: "+listOfTestKeys) log.debug("jirabaseurl: "+jiraBaseUrl) log.debug("endpoint: "+endpointUrl) url = new URL(endpointUrl); def body_req = [ "add": listOfTestKeys ] // you should use a specific user for this purpose username = "admin" password = "admin" def authString = "${username}:${password}".bytes.encodeBase64().toString() URLConnection connection = url.openConnection(); connection.requestMethod = "POST" connection.doOutput = true connection.addRequestProperty("Authorization", "Basic ${authString}") connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8") connection.outputStream.withWriter("UTF-8") { new StreamingJsonBuilder(it, body_req) } connection.connect(); log.debug(connection.getResponseCode()) log.debug(connection.getResponseMessage()) if (connection.getResponseCode() == 200) { // OK return true; } else { // error return false; } } boolean associateTestsToTestPlan(testPlanKey, listOfTestKeys){ return associateTestsToXrayIssue("testplan", testPlanKey, listOfTestKeys) } boolean associateTestsToTestExecution(testExecutionKey, listOfTestKeys){ return associateTestsToXrayIssue("testexec", testExecutionKey, listOfTestKeys) } Object getIssues(jqlQuery){ // A list of GenericValues representing issues List<Issue> searchResults = null; SearchService.ParseResult parseResult = searchService.parseQuery(serviceAccount, jqlQuery); if (parseResult.isValid()) { // throws SearchException SearchResults results = searchService.search(serviceAccount, parseResult.getQuery(), PagerFilter.getUnlimitedFilter()); searchResults = results.getIssues(); return searchResults; } return [] } synchTestsFromRelatedTestSets(httpMethod: "GET") { MultivaluedMap queryParams -> // issue_key may refer to a Test Plan or to a Test Execution def issue_key = queryParams.getFirst("issueId") as String // use the issueId to retrieve this issue projectManager = ComponentAccessor.getProjectManager() componentManager = ComponentManager.getInstance(); searchService = ComponentAccessor.getComponent(SearchService.class); issueManager = ComponentAccessor.getIssueManager() customFieldManager = ComponentAccessor.getCustomFieldManager() userUtil = ComponentAccessor.getUserUtil(); serviceAccount = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser() pluginAccessor = componentManager.getPluginAccessor(); MutableIssue issue = issueManager.getIssueObject(issue_key) Logger.getLogger("com.onresolve").setLevel(Level.DEBUG) // assume that Test Plan/Execution is linked to Test Sets using the issue link "relates to"; customize if needed jql = "issue in linkedIssues('${issue_key}', 'relates to')" testset_issues = getIssues(jql) def had_errors = false def success = false testset_issues.each { // process only "Test Set" issues if (it.issueType.name == "Test Set") { jql = "issue in testSetTests('${it.key}')" issues = getIssues(jql) test_keys = issues.collect{ it.key } //log.debug(test_keys) if (issue.issueType.name == "Test Plan") { success = associateTestsToTestPlan(issue_key, test_keys) } else if ((issue.issueType.name == "Sub Test Execution") || (issue.issueType.name == "Test Execution")) { success = associateTestsToTestExecution(issue_key, test_keys) } if (!success) { had_errors = true } } } def flag = [] if (success) { flag = [ type : 'success', title: "Test Sets Synchronization", close: 'auto', body : "Tests have been synchronized for " + issue_key ] Response.ok(JsonOutput.toJson(flag)).build() } else { flag = [ type : 'success', title: "Test Sets Synchronization", close: 'auto', body : "Tests have been synchronized for " + issue_key ] Response.serverError().entity([error: "some Tests were no synchronized"]).build() } } |
...
As an example, you may have an internal documentation/Confluence space with some valuable information concerning Xray usage in your organization.
ScripRunner configuration
Adding an entry to the top menu is easy.
...