Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.offbytwo.jenkins.model.BuildWithDetails Maven / Gradle / Ivy
/*
* Copyright (c) 2013 Cosmin Stejerean, Karl Heinz Marbaise, and contributors.
*
* Distributed under the MIT license: http://opensource.org/licenses/MIT
*/
package com.offbytwo.jenkins.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.offbytwo.jenkins.helper.BuildConsoleStreamListener;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static com.google.common.collect.Collections2.filter;
/**
* This class represents build information with details about what has been done
* like duration start and of course the build result.
*
*/
public class BuildWithDetails extends Build {
private final Logger LOGGER = LoggerFactory.getLogger(getClass());
public final static String TEXT_SIZE_HEADER = "x-text-size";
public final static String MORE_DATA_HEADER = "x-more-data";
/**
* This will be returned by the API in cases where the build has never run.
* For example {@link Build#BUILD_HAS_NEVER_RUN}
*/
public static final BuildWithDetails BUILD_HAS_NEVER_RUN = new BuildWithDetails() {
@Override
public List getActions() {
return Collections.emptyList();
}
@Override
public List getArtifacts() {
return Collections.emptyList();
}
@Override
public List getCauses() {
return Collections.emptyList();
}
@Override
public List getCulprits() {
return Collections.emptyList();
}
@Override
public BuildResult getResult() {
return BuildResult.NOT_BUILT;
}
};
/**
* This will be returned by the API in cases where the build has been
* cancelled. For example {@link Build#BUILD_HAS_BEEN_CANCELLED}
*/
public static final BuildWithDetails BUILD_HAS_BEEN_CANCELLED = new BuildWithDetails() {
@Override
public List getActions() {
return Collections.emptyList();
}
@Override
public List getArtifacts() {
return Collections.emptyList();
}
@Override
public List getCauses() {
return Collections.emptyList();
}
@Override
public List getCulprits() {
return Collections.emptyList();
}
@Override
public BuildResult getResult() {
return BuildResult.CANCELLED;
}
};
private List actions; // TODO: Should be improved.
private boolean building;
private String description;
private String displayName;
private long duration;
private long estimatedDuration;
private String fullDisplayName;
private String id;
private long timestamp;
private BuildResult result;
private List artifacts;
private String consoleOutputText;
private String consoleOutputHtml;
private BuildChangeSet changeSet;
@JsonProperty("changeSets")
private List changeSets;
private String builtOn;
private List culprits;
public BuildWithDetails() {
// Default ctor is needed to jackson.
}
public BuildWithDetails(BuildWithDetails details) {
this.actions = details.actions;
this.description = details.description;
this.displayName = details.displayName;
this.building = details.building;
this.duration = details.duration;
this.estimatedDuration = details.estimatedDuration;
this.fullDisplayName = details.fullDisplayName;
this.id = details.id;
this.timestamp = details.timestamp;
this.result = details.result;
this.artifacts = details.artifacts;
this.consoleOutputHtml = details.consoleOutputHtml;
this.consoleOutputText = details.consoleOutputText;
this.changeSet = details.changeSet;
this.builtOn = details.builtOn;
this.culprits = details.culprits;
this.setClient(details.getClient());
}
public List getArtifacts() {
return artifacts;
}
public boolean isBuilding() {
return building;
}
public List getCauses() {
// actions is a List List[BuildCause]
Collection causes = filter(actions, new Predicate>() {
@Override
public boolean apply(Map action) {
return action.containsKey("causes");
}
});
List result = new ArrayList();
if (causes != null && !causes.isEmpty()) {
// The underlying key-value can be either a or a
// .
List> causes_blob = ((Map>>) causes.toArray()[0])
.get("causes");
for (Map cause : causes_blob) {
BuildCause convertToBuildCause = convertToBuildCause(cause);
result.add(convertToBuildCause);
}
}
return result;
}
/**
* Update displayName
and the description
of a
* build.
*
* @param displayName The new displayName which should be set.
* @param description The description which should be set.
* @param crumbFlag true
or false
.
* @throws IOException in case of errors.
*/
public void updateDisplayNameAndDescription(String displayName, String description, boolean crumbFlag)
throws IOException {
Objects.requireNonNull(displayName, "displayName is not allowed to be null.");
Objects.requireNonNull(description, "description is not allowed to be null.");
// TODO: Check what the "core:apply" means?
ImmutableMap params = ImmutableMap.of("displayName", displayName, "description", description,
"core:apply", "", "Submit", "Save");
client.post_form(this.getUrl() + "/configSubmit?", params, crumbFlag);
}
/**
* Update displayName
and the description
of a
* build.
*
* @param displayName The new displayName which should be set.
* @param description The description which should be set.
* @throws IOException in case of errors.
*/
public void updateDisplayNameAndDescription(String displayName, String description) throws IOException {
updateDisplayNameAndDescription(displayName, description, false);
}
/**
* Update displayName
of a build.
*
* @param displayName The new displayName which should be set.
* @param crumbFlag true
or false
.
* @throws IOException in case of errors.
*/
public void updateDisplayName(String displayName, boolean crumbFlag) throws IOException {
Objects.requireNonNull(displayName, "displayName is not allowed to be null.");
String description = getDescription() == null ? "" : getDescription();
// TODO: Check what the "core:apply" means?
ImmutableMap params = ImmutableMap.of("displayName", displayName, "description", description,
"core:apply", "", "Submit", "Save");
client.post_form(this.getUrl() + "/configSubmit?", params, crumbFlag);
}
/**
* Update displayName
of a build.
*
* @param displayName The new displayName which should be set.
* @throws IOException in case of errors.
*/
public void updateDisplayName(String displayName) throws IOException {
updateDisplayName(displayName, false);
}
/**
* Update the description
of a build.
*
* @param description The description which should be set.
* @param crumbFlag true
or false
.
* @throws IOException in case of errors.
*/
public void updateDescription(String description, boolean crumbFlag) throws IOException {
Objects.requireNonNull(description, "description is not allowed to be null.");
String displayName = getDisplayName() == null ? "" : getDisplayName();
// TODO: Check what the "core:apply" means?
ImmutableMap params = ImmutableMap.of("displayName", displayName, "description", description,
"core:apply", "", "Submit", "Save");
client.post_form(this.getUrl() + "/configSubmit?", params, crumbFlag);
}
/**
* Update the description
of a build.
*
* @param description The description which should be set.
* @throws IOException in case of errors.
*/
public void updateDescription(String description) throws IOException {
updateDescription(description, false);
}
private BuildCause convertToBuildCause(Map cause) {
BuildCause cause_object = new BuildCause();
// TODO: Think about it. Can this be done more simpler?
String description = (String) cause.get("shortDescription");
if (!Strings.isNullOrEmpty(description)) {
cause_object.setShortDescription(description);
}
Integer upstreamBuild = (Integer) cause.get("upstreamBuild");
if (upstreamBuild != null) {
cause_object.setUpstreamBuild(upstreamBuild);
}
String upstreamProject = (String) cause.get("upstreamProject");
if (!Strings.isNullOrEmpty(upstreamProject)) {
cause_object.setUpstreamProject(upstreamProject);
}
String upstreamUrl = (String) cause.get("upstreamUrl");
if (!Strings.isNullOrEmpty(upstreamUrl)) {
cause_object.setUpstreamUrl(upstreamUrl);
}
String userId = (String) cause.get("userId");
if (!Strings.isNullOrEmpty(userId)) {
cause_object.setUserId(userId);
}
String userName = (String) cause.get("userName");
if (!Strings.isNullOrEmpty(userName)) {
cause_object.setUserName(userName);
}
return cause_object;
}
public String getDescription() {
return description;
}
public long getDuration() {
return duration;
}
public long getEstimatedDuration() {
return estimatedDuration;
}
public String getFullDisplayName() {
return fullDisplayName;
}
public String getDisplayName() {
return displayName;
}
public String getId() {
return id;
}
public long getTimestamp() {
return timestamp;
}
public BuildResult getResult() {
return result;
}
public String getBuiltOn() {
return builtOn;
}
public List getActions() {
return actions;
}
public Map getParameters() {
Collection parameters = filter(actions, new Predicate>() {
@Override
public boolean apply(Map action) {
return action.containsKey("parameters");
}
});
Map params = new HashMap();
if (parameters != null && !parameters.isEmpty()) {
for (Map param : ((Map>>) parameters.toArray()[0])
.get("parameters")) {
String key = (String) param.get("name");
Object value = param.get("value");
params.put(key, String.valueOf(value));
}
}
return params;
}
/**
* @return The full console output of the build. The line separation is done by
* {@code CR+LF}.
*
* @see streamConsoleOutput method for obtaining logs for running build
*
* @throws IOException in case of a failure.
*/
public String getConsoleOutputText() throws IOException {
return client.get(getUrl() + "/logText/progressiveText");
}
/**
* The full console output with HTML.
*
* @see streamConsoleOutput method for obtaining logs for running build
*
* @return The console output as HTML.
* @throws IOException in case of an error.
*/
public String getConsoleOutputHtml() throws IOException {
return client.get(getUrl() + "/logText/progressiveHtml");
}
/**
* Stream build console output log as text using BuildConsoleStreamListener
* Method can be used to asynchronously obtain logs for running build.
*
* @param listener interface used to asynchronously obtain logs
* @param poolingInterval interval (seconds) used to pool jenkins for logs
* @param poolingTimeout pooling timeout (seconds) used to break pooling in case build stuck
* @throws InterruptedException in case of an error.
* @throws IOException in case of an error.
*
*/
public void streamConsoleOutput(final BuildConsoleStreamListener listener, final int poolingInterval, final int poolingTimeout) throws InterruptedException, IOException {
// Calculate start and timeout
final long startTime = System.currentTimeMillis();
final long timeoutTime = startTime + (poolingTimeout * 1000);
int bufferOffset = 0;
while (true) {
Thread.sleep(poolingInterval * 1000);
ConsoleLog consoleLog = null;
consoleLog = getConsoleOutputText(bufferOffset);
String logString = consoleLog.getConsoleLog();
if (logString != null && !logString.isEmpty()) {
listener.onData(logString);
}
if (consoleLog.getHasMoreData()) {
bufferOffset = consoleLog.getCurrentBufferSize();
} else {
listener.finished();
break;
}
long currentTime = System.currentTimeMillis();
if (currentTime > timeoutTime) {
LOGGER.warn("Pooling for build {0} for {2} timeout! Check if job stuck in jenkins",
BuildWithDetails.this.getDisplayName(), BuildWithDetails.this.getNumber());
break;
}
}
}
/**
* Get build console output log as text.
* Use this method to periodically obtain logs from jenkins and skip chunks that were already received
*
* @param bufferOffset offset in console lo
* @return ConsoleLog object containing console output of the build. The line separation is done by
* {@code CR+LF}.
* @throws IOException in case of a failure.
*/
public ConsoleLog getConsoleOutputText(int bufferOffset) throws IOException {
List formData = new ArrayList<>();
formData.add(new BasicNameValuePair("start", Integer.toString(bufferOffset)));
String path = getUrl() + "logText/progressiveText";
HttpResponse httpResponse = client.post_form_with_result(path, formData, false);
Header moreDataHeader = httpResponse.getFirstHeader(MORE_DATA_HEADER);
Header textSizeHeader = httpResponse.getFirstHeader(TEXT_SIZE_HEADER);
String response = EntityUtils.toString(httpResponse.getEntity());
boolean hasMoreData = false;
if (moreDataHeader != null) {
hasMoreData = Boolean.TRUE.toString().equals(moreDataHeader.getValue());
}
Integer currentBufferSize = bufferOffset;
if (textSizeHeader != null) {
try {
currentBufferSize = Integer.parseInt(textSizeHeader.getValue());
} catch (NumberFormatException e) {
LOGGER.warn("Cannot parse buffer size for job {0} build {1}. Using current offset!", this.getDisplayName(), this.getNumber());
}
}
return new ConsoleLog(response, hasMoreData, currentBufferSize);
}
/**
* Returns the change set of a build if available.
*
* If a build performs several scm checkouts (i.e. pipeline builds), the change set of the first
* checkout is returned. To get the complete list of change sets for all checkouts, use
* {@link #getChangeSets()}
*
* If no checkout is performed, null is returned.
*
* @return The change set of the build.
*
*/
public BuildChangeSet getChangeSet() {
BuildChangeSet result;
if (changeSet != null) {
result = changeSet;
} else if (changeSets != null && !changeSets.isEmpty()) {
result = changeSets.get(0);
} else {
result = null;
}
return result;
}
public void setChangeSet(BuildChangeSet changeSet) {
this.changeSet = changeSet;
}
/**
* Returns the complete list of change sets for all checkout the build has performed. If no
* checkouts have been performed, returns null.
*
* @return The complete list of change sets of the build.
*/
public List getChangeSets() {
List result;
if (changeSets != null) {
result = changeSets;
} else if (changeSet != null) {
result = Collections.singletonList(changeSet);
} else {
result = null;
}
return result;
}
public void setChangeSets(List changeSets) {
this.changeSets = changeSets;
}
public List getCulprits() {
return culprits;
}
public void setCulprits(List culprits) {
this.culprits = culprits;
}
public void setResult(BuildResult result) {
this.result = result;
}
public InputStream downloadArtifact(Artifact a) throws IOException, URISyntaxException {
// We can't just put the artifact's relative path at the end of the url
// string, as there could be characters that need to be escaped.
URI uri = new URI(getUrl());
String artifactPath = uri.getPath() + "artifact/" + a.getRelativePath();
URI artifactUri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), artifactPath, "",
"");
return client.getFile(artifactUri);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
BuildWithDetails other = (BuildWithDetails) obj;
if (actions == null) {
if (other.actions != null)
return false;
} else if (!actions.equals(other.actions))
return false;
if (artifacts == null) {
if (other.artifacts != null)
return false;
} else if (!artifacts.equals(other.artifacts))
return false;
if (building != other.building)
return false;
if (builtOn == null) {
if (other.builtOn != null)
return false;
} else if (!builtOn.equals(other.builtOn))
return false;
if (changeSet == null) {
if (other.changeSet != null)
return false;
} else if (!changeSet.equals(other.changeSet))
return false;
if (changeSets == null) {
if (other.changeSets != null)
return false;
} else if (!changeSets.equals(other.changeSets))
return false;
if (consoleOutputHtml == null) {
if (other.consoleOutputHtml != null)
return false;
} else if (!consoleOutputHtml.equals(other.consoleOutputHtml))
return false;
if (consoleOutputText == null) {
if (other.consoleOutputText != null)
return false;
} else if (!consoleOutputText.equals(other.consoleOutputText))
return false;
if (culprits == null) {
if (other.culprits != null)
return false;
} else if (!culprits.equals(other.culprits))
return false;
if (description == null) {
if (other.description != null)
return false;
} else if (!description.equals(other.description))
return false;
if (displayName == null) {
if (other.displayName != null)
return false;
} else if (!displayName.equals(other.displayName))
return false;
if (duration != other.duration)
return false;
if (estimatedDuration != other.estimatedDuration)
return false;
if (fullDisplayName == null) {
if (other.fullDisplayName != null)
return false;
} else if (!fullDisplayName.equals(other.fullDisplayName))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (result != other.result)
return false;
if (timestamp != other.timestamp)
return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((actions == null) ? 0 : actions.hashCode());
result = prime * result + ((artifacts == null) ? 0 : artifacts.hashCode());
result = prime * result + (building ? 1231 : 1237);
result = prime * result + ((builtOn == null) ? 0 : builtOn.hashCode());
result = prime * result + ((changeSet == null) ? 0 : changeSet.hashCode());
result = prime * result + ((changeSets == null) ? 0 : changeSets.hashCode());
result = prime * result + ((consoleOutputHtml == null) ? 0 : consoleOutputHtml.hashCode());
result = prime * result + ((consoleOutputText == null) ? 0 : consoleOutputText.hashCode());
result = prime * result + ((culprits == null) ? 0 : culprits.hashCode());
result = prime * result + ((description == null) ? 0 : description.hashCode());
result = prime * result + ((displayName == null) ? 0 : displayName.hashCode());
result = prime * result + (int) (duration ^ (duration >>> 32));
result = prime * result + (int) (estimatedDuration ^ (estimatedDuration >>> 32));
result = prime * result + ((fullDisplayName == null) ? 0 : fullDisplayName.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((this.result == null) ? 0 : this.result.hashCode());
result = prime * result + (int) (timestamp ^ (timestamp >>> 32));
return result;
}
}