Skip to content

Commit

Permalink
Fixed JENKINS-22178
Browse files Browse the repository at this point in the history
  • Loading branch information
felfert committed Nov 12, 2016
1 parent 56b9559 commit a64126f
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 50 deletions.
Expand Up @@ -12,29 +12,46 @@
* @author Vijay Kiran
*/
public final class BlobStoreEntry extends AbstractDescribableImpl<BlobStoreEntry> {

/**
* The container where the file is saved. See http://www.jclouds.org/documentation/userguide/blobstore-guide#container
*/
public String container;
public final String container;

/**
* The sub path under the container where the file is saved.
*/
public String path;
public final String path;

/**
* The source file relative to the workspace directory, which needs to be uploaded to the container.
*/
public String sourceFile;
public final String sourceFile;

/**
* Whether or not the sourceFile's path relative to the workspace should be preserved upon upload to the Blobstore.
*/
public boolean keepHierarchy;
public final boolean keepHierarchy;

/**
* Whether an empty file is allowed without failing the build.
*/
public final boolean allowEmptyFileset;

/**
* Whether to publish only for successful builds.
*/
public final boolean onlyIfSuccessful;

@DataBoundConstructor
public BlobStoreEntry(String container, String path, String sourceFile, boolean keepHierarchy) {
public BlobStoreEntry(final String container, final String path, final String sourceFile,
final boolean keepHierarchy, final boolean allowEmptyFileset, final boolean onlyIfSuccessful) {
this.container = container;
this.path = path;
this.sourceFile = sourceFile;
this.keepHierarchy = keepHierarchy;
this.allowEmptyFileset = allowEmptyFileset;
this.onlyIfSuccessful = onlyIfSuccessful;
}

@Extension
Expand Down
Expand Up @@ -266,17 +266,24 @@ public ListBoxModel doFillCredentialsIdItems(@AncestorInPath ItemGroup context,
.includeAs(ACL.SYSTEM, context, StandardUsernameCredentials.class).includeCurrentValue(currentValue);
}

public ListBoxModel doFillProviderNameItems(@AncestorInPath ItemGroup context) {
ListBoxModel m = new ListBoxModel();
private ImmutableSortedSet<String> getAllProviders() {
// correct the classloader so that extensions can be found
Thread.currentThread().setContextClassLoader(Apis.class.getClassLoader());
// TODO: apis need endpoints, providers don't; do something smarter
// with this stuff :)
Builder<String> builder = ImmutableSet.<String> builder();
builder.addAll(Iterables.transform(Apis.viewableAs(BlobStoreContext.class), Apis.idFunction()));
builder.addAll(Iterables.transform(Providers.viewableAs(BlobStoreContext.class), Providers.idFunction()));
Iterable<String> supportedProviders = ImmutableSortedSet.copyOf(builder.build());
for (String supportedProvider : supportedProviders) {
return ImmutableSortedSet.copyOf(builder.build());
}

public String defaultProviderName() {
return getAllProviders().first();
}

public ListBoxModel doFillProviderNameItems(@AncestorInPath ItemGroup context) {
ListBoxModel m = new ListBoxModel();
for (String supportedProvider : getAllProviders()) {
m.add(supportedProvider, supportedProvider);
}
return m;
Expand Down
Expand Up @@ -16,14 +16,12 @@
import hudson.util.CopyOnWriteList;
import hudson.util.ListBoxModel;

import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

import org.jclouds.rest.AuthorizationException;

import java.io.IOException;
import java.io.PrintStream;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
Expand Down Expand Up @@ -98,8 +96,8 @@ public void setName(String profileName) {
this.profileName = profileName;
}

protected void log(final PrintStream logger, final String message) {
logger.println(StringUtils.defaultString(getDescriptor().getDisplayName()) + " " + message);
private void log(final BuildListener listener, final String message) {
listener.getLogger().println(getClass().getSimpleName() + ": " + message);
}

/**
Expand All @@ -120,55 +118,60 @@ protected void log(final PrintStream logger, final String message) {
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {

if (build.getResult() == Result.FAILURE) {
// build failed. don't post
LOGGER.info("Build failed, not publishing any files to blobstore");
return true;
}
BlobStoreProfile blobStoreProfile = getProfile();
if (blobStoreProfile == null) {
log(listener.getLogger(), "No JClouds Blob Store blobStoreProfile is configured.");
build.setResult(Result.UNSTABLE);
log(listener, "No BlobStore profile is configured.");
build.setResult(Result.FAILURE);
return true;
}
log(listener.getLogger(), "Using JClouds blobStoreProfile: " + blobStoreProfile.getProfileName());
log(listener, "using BlobStore profile: " + blobStoreProfile.getProfileName());
try {
Map<String, String> envVars = build.getEnvironment(listener);

for (BlobStoreEntry blobStoreEntry : entries) {
String expandedSource = Util.replaceMacro(blobStoreEntry.sourceFile, envVars);
String expandedContainer = Util.replaceMacro(blobStoreEntry.container, envVars);
for (final BlobStoreEntry bse : entries) {
final Result res = build.getResult();
if (bse.onlyIfSuccessful && null != res && res.isWorseThan(Result.UNSTABLE)) {
log(listener, "Skip publishing entry, because build is not successful");
continue;
}
String xSource = Util.replaceMacro(bse.sourceFile, envVars);
String xContainer = Util.replaceMacro(bse.container, envVars);
FilePath ws = build.getWorkspace();
if (null != ws) {
FilePath[] paths = ws.list(expandedSource);
FilePath[] paths = ws.list(xSource);
String wsPath = ws.getRemote();

if (paths.length == 0) {
// try to do error diagnostics
log(listener.getLogger(), "No file(s) found: " + expandedSource);
String error = ws.validateAntFileMask(expandedSource, Integer.MAX_VALUE);
if (error != null)
log(listener.getLogger(), error);
String error = ws.validateAntFileMask(xSource, Integer.MAX_VALUE);
if (error != null) {
log(listener, error);
}
if (bse.allowEmptyFileset) {
log(listener, "Ignoring empty file set for pattern: " + xSource);
} else {
log(listener, "Failing build");
build.setResult(Result.FAILURE);
}
}
for (FilePath src : paths) {
String expandedPath = getDestinationPath(blobStoreEntry.path, blobStoreEntry.keepHierarchy, wsPath, src, envVars);
log(listener.getLogger(), "container=" + expandedContainer + ", path=" + expandedPath + ", file=" + src.getName());
blobStoreProfile.upload(expandedContainer, expandedPath, src);
String xPath = getDestinationPath(bse.path, bse.keepHierarchy, wsPath, src, envVars);
log(listener, String.format("Publishing \"%s\" to container \"%s\", path \"%s\"",
src.getName(), xContainer, xPath));
blobStoreProfile.upload(xContainer, xPath, src);
}
} else {
log(listener.getLogger(), "Unable to fetch workspace (NULL)");
build.setResult(Result.UNSTABLE);
log(listener, "Unable to fetch workspace (NULL)");
build.setResult(Result.FAILURE);
}
}
} catch (AuthorizationException e) {
LOGGER.severe("Failed to upload files to Blob Store due to authorization exception.");
RuntimeException overrideException = new RuntimeException("Failed to upload files to Blob Store due to authorization exception.");
overrideException.printStackTrace(listener.error("Failed to upload files"));
build.setResult(Result.UNSTABLE);
RuntimeException overrideException = new RuntimeException("Failed to publish files due to authorization exception.");
overrideException.printStackTrace(listener.error("Failed to publish files"));
build.setResult(Result.FAILURE);
} catch (IOException e) {
LOGGER.severe("Failed to upload files to Blob Store: " + e.getMessage());
e.printStackTrace(listener.error("Failed to upload files"));
build.setResult(Result.UNSTABLE);
LOGGER.severe("Failed to publish files: " + e.getMessage());
e.printStackTrace(listener.error("Failed to publish files"));
build.setResult(Result.FAILURE);
}

return true;
Expand Down Expand Up @@ -266,7 +269,7 @@ public BlobStoreProfile[] getProfiles() {

@Override
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true;
return profiles != null && !profiles.isEmpty();
}

public ListBoxModel doFillProfileNameItems() {
Expand Down
@@ -1,17 +1,25 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:entry title="Source" field="sourceFile">
<f:entry title="${%Source}" field="sourceFile">
<f:textbox/>
</f:entry>
<f:entry title="Destination Container" field="container">
<f:entry title="${%Destination Container}" field="container">
<f:textbox/>
</f:entry>
<f:entry title="Destination Path" field="path">
<f:entry title="${%Destination Path}" field="path">
<f:textbox/>
</f:entry>
<f:entry title="Keep Hierarchy" field="keepHierarchy">
<f:checkbox />
</f:entry>
<f:advanced>
<f:entry field="keepHierarchy">
<f:checkbox title="${%Keep hierarchy}"/>
</f:entry>
<f:entry field="allowEmptyFileset" >
<f:checkbox title="${%Do not fail build if file set is empty}"/>
</f:entry>
<f:entry field="onlyIfSuccessful" >
<f:checkbox title="${%Publish artifacts only if build is successful}"/>
</f:entry>
</f:advanced>
<f:entry title="">
<div align="right">
<f:repeatableDeleteButton/>
Expand Down
@@ -0,0 +1,5 @@
<div>
Normally, a build fails if publishing returns zero artifacts.
This option allows the publishing process to return nothing without failing the build.
Instead, the build will simply throw a warning.
</div>
@@ -0,0 +1,3 @@
<div>
The remote container name (variables are supported).
</div>
@@ -0,0 +1,3 @@
<div>
If enabled, the path relative to the workspace will be retained.
</div>
@@ -0,0 +1,3 @@
<div>
The destination path in the remote container (variables are supported).
</div>
@@ -0,0 +1,8 @@
<div>
Path to the file to be published (variables are supported).
You can use wildcards like 'module/dist/**/*.zip'.
See <a href='http://ant.apache.org/manual/Types/fileset.html'>
the includes attribute of Ant fileset</a> for the exact format.
The base directory is <a href='ws/'>the workspace</a>.
You can only archive files that are located in your workspace.
</div>
Expand Up @@ -5,7 +5,7 @@
<f:textbox/>
</f:entry>
<f:entry title="${%Provider Name}" field="providerName">
<f:select/>
<f:select default="${descriptor.defaultProviderName()}" />
</f:entry>
<f:entry title="${%End Point URL}" field="endPointUrl">
<f:textbox/>
Expand Down

0 comments on commit a64126f

Please sign in to comment.