Skip to content
Closed
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
78 changes: 78 additions & 0 deletions src/main/java/hudson/plugins/s3/Redirect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package hudson.plugins.s3;

import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.util.FormValidation;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;

import java.net.URI;
import java.net.URISyntaxException;

public final class Redirect extends AbstractDescribableImpl<Redirect> {
public final String bucket;
public final String key;
public final String redirectLocation;

public String getKey() {
return this.key;
}

public String getRedirectLocation() {
return this.redirectLocation;
}

@DataBoundConstructor
public Redirect(String bucket, String key, String redirectLocation) {
this.bucket = bucket;
this.key = key;
this.redirectLocation = redirectLocation;
}

@Extension
public static class DescriptorImpl extends Descriptor<Redirect> {
public String getDisplayName() {
return "S3 Redirect";
}

public FormValidation doCheckKey(@QueryParameter String value) {
if (value.isEmpty()) {
return FormValidation.warning("This field cannot be empty.");
}

if (value.charAt(0) == '/') {
return FormValidation.error("Key should not have a leading slash `/`.");
}

return FormValidation.ok();
}


/**
* A valid redirect is a URI that is either:
* 1. has a HTTP or HTTPS Scheme
* 2. does not have a scheme and points to a key in the same bucket.
*/
public FormValidation doCheckRedirectLocation(@QueryParameter String value) {
if (value.isEmpty()) {
return FormValidation.warning("This field cannot be empty.");
}

try {
URI uri = new URI(value);

if ("http".equals(uri.getScheme())
|| "https".equals(uri.getScheme())
|| ((uri.getScheme() == null) && (uri.getPath().charAt(0) == '/'))) {
return FormValidation.ok();
} else {
return FormValidation.error(
"RedirectLocation must refer to `http`, `https` or to a key in the same bucket (beginning with '/').");
}
} catch (URISyntaxException e) {
return FormValidation.error("Not a valid URI.");
}
}
}
}
36 changes: 32 additions & 4 deletions src/main/java/hudson/plugins/s3/S3BucketPublisher.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package hudson.plugins.s3;

import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.model.PutObjectRequest;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
Expand Down Expand Up @@ -30,8 +31,10 @@
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -44,16 +47,18 @@ public final class S3BucketPublisher extends Recorder implements Describable<Pub
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

private final List<Entry> entries;

private boolean dontWaitForConcurrentBuildCompletion;

private List<Redirect> redirects;

/**
* User metadata key/value pairs to tag the upload with.
*/
private /*almost final*/ List<MetadataPair> userMetadata;

@DataBoundConstructor
public S3BucketPublisher(String profileName, List<Entry> entries, List<MetadataPair> userMetadata,
public S3BucketPublisher(String profileName, List<Entry> entries, List<MetadataPair> userMetadata, List<Redirect> redirects,
boolean dontWaitForConcurrentBuildCompletion) {
if (profileName == null) {
// defaults to the first one
Expand All @@ -64,7 +69,8 @@ public S3BucketPublisher(String profileName, List<Entry> entries, List<MetadataP

this.profileName = profileName;
this.entries = entries;

this.redirects = redirects;

if (userMetadata==null)
userMetadata = new ArrayList<MetadataPair>();
this.userMetadata = userMetadata;
Expand Down Expand Up @@ -112,6 +118,14 @@ public static S3Profile getProfile(String profileName) {
return null;
}

public List<Redirect> getRedirects() {
return redirects;
}

public void setRedirects(List<Redirect> redirects) {
this.redirects = redirects;
}

@Override
public Collection<? extends Action> getProjectActions(AbstractProject<?, ?> project) {
return ImmutableList.of(new S3ArtifactsProjectAction(project));
Expand Down Expand Up @@ -174,13 +188,27 @@ public boolean perform(AbstractBuild<?, ?> build,
Util.replaceMacro(metadataPair.value, envVars))
);
}
Set<Redirect> escapedRedirects = new HashSet<Redirect>();
for (Redirect redirect : redirects) {
Redirect escapedRedirect = new Redirect(
Util.replaceMacro(redirect.bucket, envVars),
Util.replaceMacro(redirect.key, envVars),
Util.replaceMacro(redirect.redirectLocation, envVars));
escapedRedirects.add(escapedRedirect);
}

List<FingerprintRecord> records = Lists.newArrayList();

for (FilePath src : paths) {
log(listener.getLogger(), "bucket=" + bucket + ", file=" + src.getName() + " region=" + selRegion + ", upload from slave=" + entry.uploadFromSlave + " managed="+ entry.managedArtifacts + " , server encryption "+entry.useServerSideEncryption);
log(listener.getLogger(), String.format("bucket=%s, file=%s region=%s, upload from slave=%s managed=%s , server encryption %s", bucket, src.getName(), selRegion, entry.uploadFromSlave, entry.managedArtifacts, entry.useServerSideEncryption));
records.add(profile.upload(build, listener, bucket, src, searchPathLength, escapedUserMetadata, storageClass, selRegion, entry.uploadFromSlave, entry.managedArtifacts, entry.useServerSideEncryption, entry.flatten));
}

for (Redirect r : escapedRedirects) {
log(listener.getLogger(), String.format("S3 redirect: bucket=%s, source=%s, redirect=%s", r.bucket, r.key, r.redirectLocation));
profile.getClient().putObject(new PutObjectRequest(r.bucket, r.key, r.redirectLocation));
}

if (entry.managedArtifacts) {
artifacts.addAll(records);

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/hudson/plugins/s3/S3Profile.java
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ public FingerprintRecord upload(AbstractBuild<?,?> build, final BuildListener li

while (true) {
try {
S3UploadCallable callable = new S3UploadCallable(produced, getClient(), dest, userMetadata, storageClass, selregion,useServerSideEncryption);
S3UploadCallable callable = new S3UploadCallable(produced, getClient(), dest, userMetadata, storageClass, selregion, useServerSideEncryption);
if (uploadFromSlave) {
return filePath.act(callable);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import hudson.plugins.s3.FingerprintRecord;
import hudson.plugins.s3.MetadataPair;
import hudson.remoting.VirtualChannel;
import hudson.util.Secret;

import java.io.File;
import java.io.FileOutputStream;
Expand All @@ -34,8 +33,8 @@ public class S3UploadCallable extends AbstractS3Callable implements FileCallable
private final boolean produced;
private final boolean useServerSideEncryption;

public S3UploadCallable(boolean produced, AmazonS3Client client, Destination dest, List<MetadataPair> userMetadata, String storageClass,
String selregion, boolean useServerSideEncryption) {
public S3UploadCallable(boolean produced, AmazonS3Client client, Destination dest, List<MetadataPair> userMetadata,
String storageClass, String selregion, boolean useServerSideEncryption) {
super(client);
this.dest = dest;
this.storageClass = storageClass;
Expand Down Expand Up @@ -108,6 +107,7 @@ public FingerprintRecord invoke(FilePath file) throws IOException, InterruptedEx
final PutObjectRequest request = new PutObjectRequest(dest.bucketName, dest.objectName, localFile)
.withMetadata(buildMetadata(file));
final PutObjectResult result = getClient().putObject(request);

return new FingerprintRecord(produced, dest.bucketName, file.getName(), result.getETag());
} finally {
if (os != null) {
Expand Down
11 changes: 11 additions & 0 deletions src/main/resources/hudson/plugins/s3/Redirect/config.jelly
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:entry title="Bucket" field="bucket">
<f:textbox />
</f:entry>
<f:entry title="Key" field="key">
<f:textbox />
</f:entry>
<f:entry title="Target" field="redirectLocation">
<f:textbox />
</f:entry>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
The bucket in which the source file is located.
</div>
3 changes: 3 additions & 0 deletions src/main/resources/hudson/plugins/s3/Redirect/help-key.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
The key (path in the bucket) of the file to be redirected.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
Destination of the redirect. It must be prefixed with 'http://' or 'https://' or '/'
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,14 @@
<f:entry field="dontWaitForConcurrentBuildCompletion" title="">
<f:checkbox title="Don't wait for completion of concurrent builds before publishing to S3" />
</f:entry>

<f:entry title="Redirects">
<f:repeatableProperty field="redirects">
<f:entry title="">
<div align="right">
<f:repeatableDeleteButton />
</div>
</f:entry>
</f:repeatableProperty>
</f:entry>
</j:jelly>