blob: ae04330faf28b0b19c81560d765096a79d34c829 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.dist.tools.masterjobs;
import java.io.IOException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.maven.dist.tools.JsoupRetry;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
/**
* Generate report with build status of the Jenkins job for the master branch of every Git repository in
* <a href="https://ci-maven.apache.org/job/Maven/job/maven-box/">{@code maven-box} Apache Hosted Git Folder job</a>.
*
* @author Robert Scholte
*/
@Mojo(name = "list-master-jobs", requiresProject = false)
public class ListMasterJobsReport extends AbstractMavenReport {
private String gitboxUrl = "https://gitbox.apache.org/repos/asf";
private String mavenboxJobsBaseUrl = "https://ci-maven.apache.org/job/Maven/job/maven-box/";
private Collection<String> excluded = Arrays.asList(
"maven-integration-testing", // runs with Maven core job
"maven-jenkins-env",
"maven-jenkins-lib",
"maven-sources",
"maven-studies",
"maven-mvnd",
"maven-metric-extension",
"maven-gh-actions-shared");
/**
* <p>Constructor for DistCheckSiteReport.</p>
*/
public ListMasterJobsReport() {}
/** {@inheritDoc} */
@Override
public String getOutputName() {
return "dist-tool-master-jobs";
}
/** {@inheritDoc} */
@Override
public String getName(Locale locale) {
return "Dist Tool> List Master Jobs";
}
/** {@inheritDoc} */
@Override
public String getDescription(Locale locale) {
return "Shows the status of Jenkins job for the master branch of every Git repository on one page";
}
/** {@inheritDoc} */
@Override
protected void executeReport(Locale locale) throws MavenReportException {
Collection<String> repositoryNames;
try {
repositoryNames = repositoryNames();
} catch (IOException e) {
throw new MavenReportException("Failed to extract repositorynames from Gitbox", e);
}
List<Result> repoStatus = new ArrayList<>(repositoryNames.size());
Collection<String> included =
repositoryNames.stream().filter(s -> !excluded.contains(s)).collect(Collectors.toList());
for (String repository : included) {
final String repositoryJobUrl = mavenboxJobsBaseUrl + "job/" + repository;
try {
Document doc = JsoupRetry.get(repositoryJobUrl);
Result result = new Result(repository, repositoryJobUrl);
Element masterRow = doc.getElementById("job_master");
if (masterRow == null) {
getLog().warn(mavenboxJobsBaseUrl + repository + " is missing id job_master");
continue;
} else if (masterRow.hasClass("job-status-red") || masterRow.hasClass("job-status-red-anime")) {
result.setStatus("FAILURE");
} else if (masterRow.hasClass("job-status-yellow") || masterRow.hasClass("job-status-yellow-anime")) {
result.setStatus("UNSTABLE");
} else if (masterRow.hasClass("job-status-blue") || masterRow.hasClass("job-status-blue-anime")) {
result.setStatus("SUCCESS");
} else {
result.setStatus("UNKNOWN");
}
result.setIcon(masterRow
.select("span.build-status-icon__wrapper")
.first()
.outerHtml());
result.setLastBuild(getLastBuild(
masterRow.child(3).attr("data"), masterRow.child(4).attr("data")));
repoStatus.add(result);
} catch (IOException e) {
getLog().warn("Failed to read status for " + repository + " Jenkins job " + repositoryJobUrl);
}
}
generateReport(repoStatus);
}
private void generateReport(List<Result> repoStatus) {
Sink sink = getSink();
sink.head();
sink.title();
sink.text("List Master Jobs");
sink.title_();
sink.head_();
sink.body();
sink.text("Jenkins jobs for master branch sorted by status of last build:");
sink.list();
Map<String, List<Result>> groupedResults =
repoStatus.stream().collect(Collectors.groupingBy(Result::getStatus));
groupedResults.entrySet().stream()
.sorted(Map.Entry.comparingByKey(resultComparator()))
.forEach(e -> {
sink.listItem();
int size = e.getValue().size();
sink.text(size + " job" + (size > 1 ? "s" : "") + " with status " + e.getKey() + ":");
sink.list();
e.getValue().forEach(r -> {
sink.listItem();
sink.rawText(r.getIcon());
sink.rawText("<span");
if ((r.getLastBuild() == null)
|| r.getLastBuild().isBefore(ZonedDateTime.now().minusMonths(1))) {
sink.rawText(" style=\"color:red\"");
}
sink.rawText(">("
+ ((r.getLastBuild() == null)
? "-"
: r.getLastBuild().format(DateTimeFormatter.ISO_LOCAL_DATE))
+ ")</span> ");
sink.link(r.getBuildUrl());
sink.rawText(r.getRepositoryName());
sink.link_();
sink.listItem_();
});
sink.list_();
sink.listItem_();
});
sink.list_();
sink.body_();
}
private Comparator<String> resultComparator() {
final List<String> orderedStatus = Arrays.asList("FAILURE", "UNSTABLE", "UNKNOWN", "SUCCESS");
return (l, r) -> {
return Integer.compare(orderedStatus.indexOf(l), orderedStatus.indexOf(r));
};
}
private ZonedDateTime getLastBuild(String lastSuccess, String lastFailure) {
ZonedDateTime success = null;
if (!"-".equals(lastSuccess)) {
success = ZonedDateTime.parse(lastSuccess);
}
ZonedDateTime failure = null;
if (!"-".equals(lastFailure)) {
failure = ZonedDateTime.parse(lastFailure);
}
if (success == null) {
return failure;
} else if (failure == null) {
return success;
} else if (success.compareTo(failure) >= 0) {
return success;
} else {
return failure;
}
}
/**
* Extract Git repository names for Apache Maven from
* <a href="https://gitbox.apache.org/repos/asf">Gitbox main page</a>.
*
* @return the list of repository names (without ".git")
* @throws java.io.IOException problem with reading repository index
*/
protected Collection<String> repositoryNames() throws IOException {
List<String> names = new ArrayList<>(100);
Document doc = JsoupRetry.get(gitboxUrl);
// find Apache Maven table
Element apacheMavenTable =
doc.getElementsMatchingText("^Apache Maven$").parents().get(0);
Elements gitRepo =
apacheMavenTable.select("tbody tr").not("tr.disabled").select("td:first-child a");
for (Element element : gitRepo) {
names.add(element.text().split("\\.git")[0]);
}
return names;
}
}