Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,48 @@ public void suiteRetriggerAppearsInPrResults() throws Exception {
waitForPrResultsSuiteBuild(token, "pull/12006/head", secondSuiteId);
}

/** */
@Test
public void suiteRetriggerFallsBackWhenTeamCityRejectsBuildComments() throws Exception {
String token = env.login();
String branch = "pull/12999/head";
String suiteId = "IgniteTests24Java17_ComputeGrid";

IntegrationTestEnvironment.HttpResponse configured = request("POST", env.teamcityUrl
+ "/__test__/teamcity/reject-build-comments", null, "application/json",
"{\"enabled\":true}");

assertEquals(configured.body, 200, configured.status);
assertTrue(configured.body, configured.body.contains("\"rejectBuildComments\": true"));

try {
IntegrationTestEnvironment.HttpResponse trigger = request("GET", env.botUrl
+ "/rest/build/triggerBuildsAsync"
+ "?srvCode=apache"
+ "&branchName=" + enc(branch)
+ "&parentSuiteId=" + enc("IgniteTests24Java17_RunAll")
+ "&suiteIdList=" + enc(suiteId)
+ "&top=false"
+ "&observe=false"
+ "&cleanRebuild=false"
+ "&processId=700000046",
"Token " + token, null, null);

assertEquals(trigger.body, 200, trigger.status);
assertTrue(trigger.body, trigger.body.contains("Trigger process started"));

String status = waitForProcess(700000046L, token);

assertTrue(status, status.contains("Tests started."));
assertTrue("Suite retrigger should create a build after retrying without TeamCity comment",
waitForTriggeredBuild(branch, suiteId) > 0);
}
finally {
request("POST", env.teamcityUrl + "/__test__/teamcity/reject-build-comments", null, "application/json",
"{\"enabled\":false}");
}
}

/** */
@Test
public void currentPageRestModelForMasterIsNotEmpty() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ def do_POST(self):
if parsed.path == "/__test__/teamcity/forbid-build-details":
return self.forbid_build_details()

if parsed.path == "/__test__/teamcity/reject-build-comments":
return self.reject_build_comments()

if parsed.path == "/__test__/teamcity/reset":
return self.reset()

Expand Down Expand Up @@ -555,6 +558,12 @@ def trigger_build(self):
return self.json(401, {"message": "Authentication required"})

payload = self.read_trigger_request()

if self.server.reject_build_comments and payload.get("hasComment"):
return self.xml(403, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
'<errors><error><message>You do not have "Comment build" permission in project with '
'internal id: project17</message></error></errors>')

self.server.next_build += 1
build_id = str(self.server.next_build)
branch = payload.get("branchName", "pull/12001/head")
Expand Down Expand Up @@ -634,11 +643,21 @@ def complete_build(self):

return self.json(200, build_json(build_id, build, self.server.server_port))

def reject_build_comments(self):
payload = self.read_json()
self.server.reject_build_comments = payload.get("enabled", True)

return self.json(200, {
"status": "configured",
"rejectBuildComments": self.server.reject_build_comments
})

def reset(self):
self.server.next_build = 900000
self.server.builds = initial_builds()
self.server.forbidden_build_details = set()
self.server.build_details_requests = {}
self.server.reject_build_comments = False

return self.json(200, {"status": "reset", "service": "teamcity"})

Expand Down Expand Up @@ -678,7 +697,8 @@ def read_trigger_request(self):

return {
"buildTypeId": build_type.group(1) if build_type else RUN_ALL,
"branchName": branch.group(1) if branch else "pull/12001/head"
"branchName": branch.group(1) if branch else "pull/12001/head",
"hasComment": "<comment" in body
}

def json(self, status, payload):
Expand Down Expand Up @@ -721,6 +741,7 @@ def __init__(self, address):
self.builds = initial_builds()
self.forbidden_build_details = set()
self.build_details_requests = {}
self.reject_build_comments = False

def advance_build_lifecycle(self):
now = time.time()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,7 @@ private File logsDir() {
"/>\n";

String comments = " <comment><text>" +
Strings.nullToEmpty(freeTextComments) + ", " +
"Build triggered from Ignite TC Bot" +
" [cleanSources=" + cleanRebuild + ", cleanRebuild=" + cleanRebuild + ", top=" + queueAtTop + "]" +
XmlUtil.xmlEscapeText(buildComment(freeTextComments, cleanRebuild, queueAtTop)) +
"</text></comment>\n";

Map<String, Object> props = new HashMap<>();
Expand All @@ -213,10 +211,65 @@ private File logsDir() {

props.put(ITeamcity.TCBOT_TRIGGER_TIME, System.currentTimeMillis()); //

StringBuilder buildWithComment = triggerBuildRequest(branchName, buildTypeId, triggeringOptions, comments,
props);

String url = host() + "app/rest/buildQueue";

try {
return sendTriggerBuildRequest(url, buildWithComment.toString(), buildTypeId, branchName, cleanRebuild,
queueAtTop, props);
}
catch (IllegalStateException e) {
if (!isMissingBuildCommentPermission(e))
throw e;

logger.warn("TeamCity rejected build comment while triggering buildTypeId={}, branchName={}; " +
"retrying build queue request without comment.", buildTypeId, branchName);

StringBuilder buildWithoutComment = triggerBuildRequest(branchName, buildTypeId, triggeringOptions, null,
props);

try {
return sendTriggerBuildRequest(url, buildWithoutComment.toString(), buildTypeId, branchName,
cleanRebuild, queueAtTop, props);
}
catch (IOException retryErr) {
throw new UncheckedIOException(retryErr);
}
}
catch (IOException e) {
throw new UncheckedIOException(e);
}
}

/**
* @param freeTextComments Additional comment text.
* @param cleanRebuild Clean rebuild flag.
* @param queueAtTop Queue-at-top flag.
*/
private String buildComment(String freeTextComments, boolean cleanRebuild, boolean queueAtTop) {
return Strings.nullToEmpty(freeTextComments) + ", " +
"Build triggered from Ignite TC Bot" +
" [cleanSources=" + cleanRebuild + ", cleanRebuild=" + cleanRebuild + ", top=" + queueAtTop + "]";
}

/**
* @param branchName Branch name.
* @param buildTypeId Build type id.
* @param triggeringOptions Triggering options XML.
* @param comments Optional comment XML.
* @param props Build properties.
*/
private StringBuilder triggerBuildRequest(String branchName, String buildTypeId, String triggeringOptions,
@Nullable String comments, Map<String, Object> props) {
StringBuilder sb = new StringBuilder();
sb.append("<build branchName=\"").append(XmlUtil.xmlEscapeText(branchName)).append("\">\n");
sb.append(" <buildType id=\"").append(buildTypeId).append("\"/>\n");
sb.append(comments);

if (comments != null)
sb.append(comments);

sb.append(triggeringOptions);
sb.append(" <properties>\n");

Expand All @@ -228,29 +281,45 @@ private File logsDir() {
sb.append(" </properties>\n");
sb.append("</build>");

String url = host() + "app/rest/buildQueue";

try {
logger.info("Triggering build: buildTypeId={}, branchName={}, cleanRebuild={}, queueAtTop={}, buildParms={}",
buildTypeId, branchName, cleanRebuild, queueAtTop, props);
return sb;
}

String body = sb.toString();
/**
* @param url Build queue URL.
* @param body Request body.
* @param buildTypeId Build type id.
* @param branchName Branch name.
* @param cleanRebuild Clean rebuild flag.
* @param queueAtTop Queue-at-top flag.
* @param props Build properties.
*/
private Build sendTriggerBuildRequest(String url, String body, String buildTypeId, String branchName,
boolean cleanRebuild, boolean queueAtTop, Map<String, Object> props) throws IOException {
logger.info("Triggering build: buildTypeId={}, branchName={}, cleanRebuild={}, queueAtTop={}, buildParms={}",
buildTypeId, branchName, cleanRebuild, queueAtTop, props);

if (logger.isDebugEnabled())
logger.debug("(TRIGGER REQUEST):\n" + body);
if (logger.isDebugEnabled())
logger.debug("(TRIGGER REQUEST):\n" + body);

try (StringReader reader = new StringReader(HttpUtil.sendPostAsString(basicAuthTok, url, body))) {
return XmlUtil.load(Build.class, reader);
}
catch (JAXBException e) {
throw ExceptionUtil.propagateException(e);
}
try (StringReader reader = new StringReader(HttpUtil.sendPostAsString(basicAuthTok, url, body))) {
return XmlUtil.load(Build.class, reader);
}
catch (IOException e) {
throw new UncheckedIOException(e);
catch (JAXBException e) {
throw ExceptionUtil.propagateException(e);
}
}

/**
* @param e TeamCity error.
*/
private boolean isMissingBuildCommentPermission(IllegalStateException e) {
String msg = e.getMessage();

return msg != null
&& msg.contains("Invalid Response Code : 403")
&& msg.contains("\"Comment build\" permission");
}

/** {@inheritDoc} */
@AutoProfiling
@Override public ProblemOccurrences getProblems(int buildId) {
Expand Down