diff --git a/src/main/java/hudson/plugins/s3/Entry.java b/src/main/java/hudson/plugins/s3/Entry.java index 63f95cc6..6ea990a0 100644 --- a/src/main/java/hudson/plugins/s3/Entry.java +++ b/src/main/java/hudson/plugins/s3/Entry.java @@ -90,11 +90,16 @@ public final class Entry implements Describable { */ public List userMetadata; + /** + * Tags overrides + */ + public List userTags; + @DataBoundConstructor public Entry(String bucket, String sourceFile, String excludedFile, String storageClass, String selectedRegion, boolean noUploadOnFailure, boolean uploadFromSlave, boolean managedArtifacts, boolean useServerSideEncryption, boolean flatten, boolean gzipFiles, boolean keepForever, - boolean showDirectlyInBrowser, List userMetadata) { + boolean showDirectlyInBrowser, List userMetadata, List userTags) { this.bucket = bucket; this.sourceFile = sourceFile; this.excludedFile = excludedFile; @@ -108,6 +113,7 @@ public Entry(String bucket, String sourceFile, String excludedFile, String stora this.gzipFiles = gzipFiles; this.keepForever = keepForever; this.userMetadata = userMetadata; + this.userTags = userTags; this.showDirectlyInBrowser = showDirectlyInBrowser; } diff --git a/src/main/java/hudson/plugins/s3/S3BucketPublisher.java b/src/main/java/hudson/plugins/s3/S3BucketPublisher.java index 476bab90..28406903 100644 --- a/src/main/java/hudson/plugins/s3/S3BucketPublisher.java +++ b/src/main/java/hudson/plugins/s3/S3BucketPublisher.java @@ -85,8 +85,13 @@ public final class S3BucketPublisher extends Recorder implements SimpleBuildStep */ private /*almost final*/ List userMetadata; + /** + * User tag key/value pairs to tag the upload with. + */ + private /*almost final*/ List userTags; + @DataBoundConstructor - public S3BucketPublisher(String profileName, List entries, List userMetadata, + public S3BucketPublisher(String profileName, List entries, List userMetadata, List userTags, boolean dontWaitForConcurrentBuildCompletion, String consoleLogLevel, String pluginFailureResultConstraint, boolean dontSetBuildResultOnFailure) { if (profileName == null) { @@ -104,6 +109,11 @@ public S3BucketPublisher(String profileName, List entries, List(); } + this.userTags = userTags; + if (this.userTags == null) { + this.userTags = new ArrayList<>(); + } + this.dontWaitForConcurrentBuildCompletion = dontWaitForConcurrentBuildCompletion; this.dontSetBuildResultOnFailure = dontSetBuildResultOnFailure; this.consoleLogLevel = parseLevel(consoleLogLevel); @@ -129,6 +139,9 @@ protected Object readResolve() { if (userMetadata == null) userMetadata = new ArrayList<>(); + if (userTags == null) + userTags = new ArrayList<>(); + if (pluginFailureResultConstraint == null) pluginFailureResultConstraint = Result.FAILURE; @@ -172,6 +185,11 @@ public List getUserMetadata() { return userMetadata; } + @SuppressWarnings("unused") + public List getUserTags() { + return userTags; + } + @SuppressWarnings("unused") public String getProfileName() { return this.profileName; @@ -317,9 +335,10 @@ public void perform(@NonNull Run run, @NonNull FilePath ws, @NonNull Launc final Map escapedMetadata = buildMetadata(envVars, entry); + final Map escapedTags = buildTags(envVars, entry); final List records = Lists.newArrayList(); - final List fingerprints = profile.upload(run, bucket, paths, filenames, escapedMetadata, storageClass, selRegion, entry.uploadFromSlave, entry.managedArtifacts, entry.useServerSideEncryption, entry.gzipFiles); + final List fingerprints = profile.upload(run, bucket, paths, filenames, escapedMetadata, escapedTags, storageClass, selRegion, entry.uploadFromSlave, entry.managedArtifacts, entry.useServerSideEncryption, entry.gzipFiles); for (FingerprintRecord fingerprintRecord : fingerprints) { records.add(fingerprintRecord); @@ -420,6 +439,32 @@ private Map buildMetadata(Map envVars, Entry ent return escapedMetadata; } + private Map buildTags(Map envVars, Entry entry) { + final Map mergedTags = new HashMap<>(); + + if (userTags != null) { + for (TagPair pair : userTags) { + mergedTags.put(pair.key, pair.value); + } + } + + if (entry.userTags != null) { + for (TagPair pair : entry.userTags) { + mergedTags.put(pair.key, pair.value); + } + } + + final Map escapedTags = new HashMap<>(); + + for (Map.Entry mapEntry : mergedTags.entrySet()) { + escapedTags.put( + Util.replaceMacro(mapEntry.getKey(), envVars), + Util.replaceMacro(mapEntry.getValue(), envVars)); + } + + return escapedTags; + } + private String getFilename(FilePath src, boolean flatten, int searchIndex) { final String fileName; if (flatten) { diff --git a/src/main/java/hudson/plugins/s3/S3Profile.java b/src/main/java/hudson/plugins/s3/S3Profile.java index 6095e8da..a8738eed 100644 --- a/src/main/java/hudson/plugins/s3/S3Profile.java +++ b/src/main/java/hudson/plugins/s3/S3Profile.java @@ -126,6 +126,7 @@ public List upload(Run run, final List filePaths, final List fileNames, final Map userMetadata, + final Map userTags, final String storageClass, final String selregion, final boolean uploadFromSlave, @@ -151,10 +152,10 @@ public List upload(Run run, final MasterSlaveCallable upload; if (gzipFiles) { - upload = new S3GzipCallable(accessKey, secretKey, useRole, dest, userMetadata, + upload = new S3GzipCallable(accessKey, secretKey, useRole, dest, userMetadata, userTags, storageClass, selregion, useServerSideEncryption, getProxy()); } else { - upload = new S3UploadCallable(accessKey, secretKey, useRole, dest, userMetadata, + upload = new S3UploadCallable(accessKey, secretKey, useRole, dest, userMetadata, userTags, storageClass, selregion, useServerSideEncryption, getProxy()); } diff --git a/src/main/java/hudson/plugins/s3/TagPair.java b/src/main/java/hudson/plugins/s3/TagPair.java new file mode 100644 index 00000000..b070be02 --- /dev/null +++ b/src/main/java/hudson/plugins/s3/TagPair.java @@ -0,0 +1,43 @@ +package hudson.plugins.s3; + +import hudson.Extension; +import hudson.model.Describable; +import hudson.model.Descriptor; +import org.kohsuke.stapler.DataBoundConstructor; + +public final class TagPair implements Describable { + + /** + * The key of the user tag pair to tag an upload with. + * Can contain macros. + */ + public String key; + + /** + * The key of the user tag pair to tag an upload with. + * Can contain macros. + */ + public String value; + + @DataBoundConstructor + public TagPair(String key, String value) { + this.key = key; + this.value = value; + } + + public Descriptor getDescriptor() { + return DESCRIPOR; + } + + @Extension + public final static DescriptorImpl DESCRIPOR = new DescriptorImpl(); + + public static class DescriptorImpl extends Descriptor { + + @Override + public String getDisplayName() { + return "Tag"; + } + }; +} + diff --git a/src/main/java/hudson/plugins/s3/Uploads.java b/src/main/java/hudson/plugins/s3/Uploads.java index c7d6a02a..1fb77549 100644 --- a/src/main/java/hudson/plugins/s3/Uploads.java +++ b/src/main/java/hudson/plugins/s3/Uploads.java @@ -2,6 +2,7 @@ import com.amazonaws.AmazonServiceException; import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.ObjectTagging; import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.transfer.TransferManager; import com.amazonaws.services.s3.transfer.Upload; @@ -21,8 +22,8 @@ private Uploads() {} private final transient HashMap startedUploads = new HashMap<>(); private final transient HashMap openedStreams = new HashMap<>(); - public Upload startUploading(TransferManager manager, FilePath file, InputStream inputsStream, String bucketName, String objectName, ObjectMetadata metadata) throws AmazonServiceException { - final PutObjectRequest request = new PutObjectRequest(bucketName, objectName, inputsStream, metadata); + public Upload startUploading(TransferManager manager, FilePath file, InputStream inputsStream, String bucketName, String objectName, ObjectMetadata metadata, ObjectTagging tags) throws AmazonServiceException { + final PutObjectRequest request = new PutObjectRequest(bucketName, objectName, inputsStream, metadata).withTagging(tags); // Set the buffer size (ReadLimit) equal to the multipart upload size, // allowing us to resend data if the connection breaks. diff --git a/src/main/java/hudson/plugins/s3/callable/S3BaseUploadCallable.java b/src/main/java/hudson/plugins/s3/callable/S3BaseUploadCallable.java index 76e72cc5..3168bbab 100644 --- a/src/main/java/hudson/plugins/s3/callable/S3BaseUploadCallable.java +++ b/src/main/java/hudson/plugins/s3/callable/S3BaseUploadCallable.java @@ -2,6 +2,8 @@ import com.amazonaws.services.s3.internal.Mimetypes; import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.ObjectTagging; +import com.amazonaws.services.s3.model.Tag; import hudson.FilePath; import hudson.ProxyConfiguration; import hudson.plugins.s3.Destination; @@ -12,24 +14,28 @@ import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; public abstract class S3BaseUploadCallable extends S3Callable { private static final long serialVersionUID = 1L; private final Destination dest; private final String storageClass; private final Map userMetadata; + private final Map userTags; private final boolean useServerSideEncryption; - public S3BaseUploadCallable(String accessKey, Secret secretKey, boolean useRole, - Destination dest, Map userMetadata, String storageClass, String selregion, - boolean useServerSideEncryption, ProxyConfiguration proxy) { + Destination dest, Map userMetadata, Map userTags, + String storageClass, String selregion, boolean useServerSideEncryption, ProxyConfiguration proxy) { super(accessKey, secretKey, useRole, selregion, proxy); this.dest = dest; this.storageClass = storageClass; this.userMetadata = userMetadata; + this.userTags = userTags; this.useServerSideEncryption = useServerSideEncryption; } @@ -86,6 +92,15 @@ protected ObjectMetadata buildMetadata(FilePath filePath) throws IOException, In return metadata; } + protected ObjectTagging s3Tagging() { + + final List tags = userTags.entrySet().stream() + .map(entry -> new Tag(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + + return new ObjectTagging(tags); + } + public Destination getDest() { return dest; } diff --git a/src/main/java/hudson/plugins/s3/callable/S3GzipCallable.java b/src/main/java/hudson/plugins/s3/callable/S3GzipCallable.java index 16ebe5b8..2f041aa5 100644 --- a/src/main/java/hudson/plugins/s3/callable/S3GzipCallable.java +++ b/src/main/java/hudson/plugins/s3/callable/S3GzipCallable.java @@ -1,6 +1,7 @@ package hudson.plugins.s3.callable; import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.ObjectTagging; import com.amazonaws.services.s3.transfer.Upload; import com.amazonaws.event.ProgressListener; import com.amazonaws.event.ProgressEvent; @@ -23,8 +24,8 @@ import java.util.zip.GZIPOutputStream; public final class S3GzipCallable extends S3BaseUploadCallable implements MasterSlaveCallable { - public S3GzipCallable(String accessKey, Secret secretKey, boolean useRole, Destination dest, Map userMetadata, String storageClass, String selregion, boolean useServerSideEncryption, ProxyConfiguration proxy) { - super(accessKey, secretKey, useRole, dest, userMetadata, storageClass, selregion, useServerSideEncryption, proxy); + public S3GzipCallable(String accessKey, Secret secretKey, boolean useRole, Destination dest, Map userMetadata, Map userTags, String storageClass, String selregion, boolean useServerSideEncryption, ProxyConfiguration proxy) { + super(accessKey, secretKey, useRole, dest, userMetadata, userTags, storageClass, selregion, useServerSideEncryption, proxy); } // Return a File containing the gzipped contents of the input file. @@ -78,10 +79,11 @@ public String invoke(FilePath file) throws IOException, InterruptedException { // close the stream before the upload has succeeded. final InputStream gzippedStream = new FileInputStream(localFile); final ObjectMetadata metadata = buildMetadata(file); + final ObjectTagging tags = s3Tagging(); metadata.setContentEncoding("gzip"); metadata.setContentLength(localFile.length()); - upload = Uploads.getInstance().startUploading(getTransferManager(), file, gzippedStream, getDest().bucketName, getDest().objectName, metadata); + upload = Uploads.getInstance().startUploading(getTransferManager(), file, gzippedStream, getDest().bucketName, getDest().objectName, metadata, tags); String md5 = MD5.generateFromFile(localFile); diff --git a/src/main/java/hudson/plugins/s3/callable/S3UploadCallable.java b/src/main/java/hudson/plugins/s3/callable/S3UploadCallable.java index 064aea57..db674b9b 100644 --- a/src/main/java/hudson/plugins/s3/callable/S3UploadCallable.java +++ b/src/main/java/hudson/plugins/s3/callable/S3UploadCallable.java @@ -1,6 +1,7 @@ package hudson.plugins.s3.callable; import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.ObjectTagging; import hudson.FilePath; import hudson.ProxyConfiguration; import hudson.plugins.s3.Destination; @@ -14,8 +15,8 @@ public final class S3UploadCallable extends S3BaseUploadCallable implements MasterSlaveCallable { private static final long serialVersionUID = 1L; - public S3UploadCallable(String accessKey, Secret secretKey, boolean useRole, Destination dest, Map userMetadata, String storageClass, String selregion, boolean useServerSideEncryption, ProxyConfiguration proxy) { - super(accessKey, secretKey, useRole, dest, userMetadata, storageClass, selregion, useServerSideEncryption, proxy); + public S3UploadCallable(String accessKey, Secret secretKey, boolean useRole, Destination dest, Map userMetadata, Map userTags, String storageClass, String selregion, boolean useServerSideEncryption, ProxyConfiguration proxy) { + super(accessKey, secretKey, useRole, dest, userMetadata, userTags, storageClass, selregion, useServerSideEncryption, proxy); } /** @@ -24,9 +25,11 @@ public S3UploadCallable(String accessKey, Secret secretKey, boolean useRole, Des @Override public String invoke(FilePath file) throws IOException, InterruptedException { final ObjectMetadata metadata = buildMetadata(file); + final ObjectTagging tags = s3Tagging(); - Uploads.getInstance().startUploading(getTransferManager(), file, file.read(), getDest().bucketName, getDest().objectName, metadata); + Uploads.getInstance().startUploading(getTransferManager(), file, file.read(), getDest().bucketName, getDest().objectName, metadata, tags); return MD5.generateFromFile(file); } -} \ No newline at end of file + +} diff --git a/src/main/resources/hudson/plugins/s3/Entry/config.jelly b/src/main/resources/hudson/plugins/s3/Entry/config.jelly index 7046a7ed..3effbebd 100644 --- a/src/main/resources/hudson/plugins/s3/Entry/config.jelly +++ b/src/main/resources/hudson/plugins/s3/Entry/config.jelly @@ -49,5 +49,14 @@ + + + +
+ +
+
+
+
diff --git a/src/main/resources/hudson/plugins/s3/S3BucketPublisher/config.jelly b/src/main/resources/hudson/plugins/s3/S3BucketPublisher/config.jelly index 6b46643c..9e2b5a83 100644 --- a/src/main/resources/hudson/plugins/s3/S3BucketPublisher/config.jelly +++ b/src/main/resources/hudson/plugins/s3/S3BucketPublisher/config.jelly @@ -26,6 +26,16 @@ + + + +
+ +
+
+
+
+ diff --git a/src/main/resources/hudson/plugins/s3/TagPair/config.jelly b/src/main/resources/hudson/plugins/s3/TagPair/config.jelly new file mode 100644 index 00000000..18244ea8 --- /dev/null +++ b/src/main/resources/hudson/plugins/s3/TagPair/config.jelly @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/main/resources/hudson/plugins/s3/TagPair/help-key.html b/src/main/resources/hudson/plugins/s3/TagPair/help-key.html new file mode 100644 index 00000000..930b5b56 --- /dev/null +++ b/src/main/resources/hudson/plugins/s3/TagPair/help-key.html @@ -0,0 +1,4 @@ +
+ Tag key for the files from this build. Can contain macros (e.g. environment + variables). +
diff --git a/src/main/resources/hudson/plugins/s3/TagPair/help-value.html b/src/main/resources/hudson/plugins/s3/TagPair/help-value.html new file mode 100644 index 00000000..e71d2c11 --- /dev/null +++ b/src/main/resources/hudson/plugins/s3/TagPair/help-value.html @@ -0,0 +1,4 @@ +
+ Tag value for the files from this build. Can contain macros (e.g. + environment variables). +
diff --git a/src/test/java/hudson/plugins/s3/MinIOTest.java b/src/test/java/hudson/plugins/s3/MinIOTest.java index aede518c..464b4080 100644 --- a/src/test/java/hudson/plugins/s3/MinIOTest.java +++ b/src/test/java/hudson/plugins/s3/MinIOTest.java @@ -146,11 +146,14 @@ private static void createAndRunPublisher(final JenkinsRule r) throws Exception false, true, false, + Collections.emptyList(), Collections.emptyList())), Collections.emptyList(), + Collections.emptyList(), true, "FINE", - null, false)); + null, + false)); r.buildAndAssertSuccess(job); } diff --git a/src/test/java/hudson/plugins/s3/S3Test.java b/src/test/java/hudson/plugins/s3/S3Test.java index 7d59f947..8cdab8d5 100644 --- a/src/test/java/hudson/plugins/s3/S3Test.java +++ b/src/test/java/hudson/plugins/s3/S3Test.java @@ -58,6 +58,7 @@ public void multiplePublishersUseExistingActions() throws Exception { profileName, newArrayList(entryForFile(fileName)), Collections.emptyList(), + Collections.emptyList(), true, "INFO", "SUCCESS", @@ -85,6 +86,7 @@ public void dontSetBuildResultTest() throws Exception { missingProfileName, newArrayList(entryForFile(fileName)), Collections.emptyList(), + Collections.emptyList(), true, "DEBUG", "SUCCESS", @@ -102,7 +104,7 @@ public void dontSetBuildResultTest() throws Exception { } private Entry entryForFile(String fileName) { - return new Entry("bucket", fileName, "", "", "", false, false, true, false, false, false, false, false, null); + return new Entry("bucket", fileName, "", "", "", false, false, true, false, false, false, false, false, null, null); } private Builder stepCreatingFile(String fileName) { @@ -129,6 +131,7 @@ private S3Profile mockS3Profile(String profileName) throws IOException, Interrup Mockito.anyList(), Mockito.anyList(), Mockito.anyMap(), + Mockito.anyMap(), Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean(),