Skip to content

Commit

Permalink
Merge branch 'JCloudsApiExtensionPoint-JENKINS-51460' into jclouds-mock
Browse files Browse the repository at this point in the history
  • Loading branch information
jglick committed May 22, 2018
2 parents 4155cb0 + 3670da5 commit ca1464c
Show file tree
Hide file tree
Showing 10 changed files with 210 additions and 169 deletions.
Expand Up @@ -31,37 +31,39 @@

import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.domain.Credentials;
import org.jclouds.providers.ProviderMetadata;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.Beta;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.ExtensionPoint;
import shaded.com.google.common.base.Supplier;
import hudson.model.AbstractDescribableImpl;

/**
* Provider for jclouds-based blob stores usable for artifact storage.
* An instance will be copied into a build record together with any fields it defines.
*/
@Restricted(Beta.class)
public abstract class JCloudsApiExtensionPoint implements ExtensionPoint, Serializable {
public abstract class BlobStoreProvider extends AbstractDescribableImpl<BlobStoreProvider> implements ExtensionPoint, Serializable {

private static final long serialVersionUID = -861350249543443493L;

public enum HttpMethod {
GET, PUT;
}

/** A constant for the blob path prefix to use. */
@NonNull
public abstract String id();
public abstract String getPrefix();

/** A constant for the blob container name to use. */
@NonNull
public abstract ProviderMetadata getProvider();
public abstract String getContainer();

/** Creates the jclouds handle for working with blobs. */
@NonNull
public abstract BlobStoreContext getContext() throws IOException;

@NonNull
public abstract Supplier<Credentials> getCredentialsSupplier() throws IOException;

/**
* Get a provider-specific URI.
*
Expand All @@ -88,4 +90,10 @@ public enum HttpMethod {
public URL toExternalURL(@NonNull Blob blob, @NonNull HttpMethod httpMethod) throws IOException {
return null;
}

@Override
public BlobStoreProviderDescriptor getDescriptor() {
return (BlobStoreProviderDescriptor) super.getDescriptor();
}

}
@@ -0,0 +1,32 @@
/*
* The MIT License
*
* Copyright 2018 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package io.jenkins.plugins.artifact_manager_s3;

import hudson.model.Descriptor;

/**
* Descriptor type for {@link BlobStoreProvider}.
*/
public abstract class BlobStoreProviderDescriptor extends Descriptor<BlobStoreProvider> {}
Expand Up @@ -37,25 +37,19 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jclouds.ContextBuilder;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.StorageMetadata;
import org.jclouds.blobstore.options.CopyOptions;
import org.jclouds.blobstore.options.ListContainerOptions;
import org.jclouds.domain.Credentials;
import org.jclouds.osgi.ProviderRegistry;
import org.jenkinsci.plugins.workflow.flow.StashManager;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.AbortException;
import hudson.EnvVars;
import hudson.ExtensionList;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
Expand All @@ -66,29 +60,24 @@
import hudson.slaves.WorkspaceList;
import hudson.util.DirScanner;
import hudson.util.io.ArchiverFactory;
import io.jenkins.plugins.artifact_manager_s3.JCloudsApiExtensionPoint.HttpMethod;
import io.jenkins.plugins.artifact_manager_s3.BlobStoreProvider.HttpMethod;
import jenkins.MasterToSlaveFileCallable;
import jenkins.model.ArtifactManager;
import jenkins.util.VirtualFile;
import shaded.com.google.common.base.Supplier;

/**
* Artifact manager that stores files in a JClouds BlobStore using any of JClouds supported backends
*/
class JCloudsArtifactManager extends ArtifactManager implements StashManager.StashAwareArtifactManager {
final class JCloudsArtifactManager extends ArtifactManager implements StashManager.StashAwareArtifactManager {

private static final Logger LOGGER = Logger.getLogger(JCloudsArtifactManager.class.getName());

private static String PROVIDER = System.getProperty("jclouds.provider", "aws-s3");

private static String BLOB_CONTAINER = System.getenv("S3_BUCKET");
private static String PREFIX = System.getenv("S3_DIR");
private final BlobStoreProvider provider;

private transient String key; // e.g. myorg/myrepo/master/123

private transient String prefix;

JCloudsArtifactManager(Run<?, ?> build) {
JCloudsArtifactManager(Run<?, ?> build, BlobStoreProvider provider) {
this.provider = provider;
onLoad(build);
}

Expand All @@ -97,22 +86,12 @@ public void onLoad(Run<?, ?> build) {
this.key = String.format("%s/%s", build.getParent().getFullName(), build.getNumber());
}

// testing only
String getPrefix() {
return prefix == null ? PREFIX : prefix;
}

// testing only
void setPrefix(String prefix) {
this.prefix = prefix;
}

private String getBlobPath(String s3path) {
return getBlobPath(key, s3path);
}

private String getBlobPath(String key, String s3path) {
return String.format("%s%s/%s", getPrefix(), key, s3path);
return String.format("%s%s/%s", provider.getPrefix(), key, s3path);
}

/*
Expand All @@ -123,65 +102,62 @@ public void archive(FilePath workspace, Launcher launcher, BuildListener listene
throws IOException, InterruptedException {
LOGGER.log(Level.FINE, "Archiving from {0}: {1}", new Object[] { workspace, artifacts });
Map<String, URL> artifactUrls = new HashMap<>();
BlobStore blobStore = getContext(getExtension(PROVIDER).getCredentialsSupplier())
.getBlobStore();
JCloudsApiExtensionPoint extension = getExtension(PROVIDER);
BlobStore blobStore = getContext().getBlobStore();

// Map artifacts to urls for upload
for (Map.Entry<String, String> entry : artifacts.entrySet()) {
String s3path = "artifacts/" + entry.getKey();
String blobPath = getBlobPath(s3path);
Blob blob = blobStore.blobBuilder(blobPath).build();
blob.getMetadata().setContainer(BLOB_CONTAINER);
artifactUrls.put(entry.getValue(), extension.toExternalURL(blob, HttpMethod.PUT));
blob.getMetadata().setContainer(provider.getContainer());
artifactUrls.put(entry.getValue(), provider.toExternalURL(blob, HttpMethod.PUT));
}

workspace.act(new UploadToBlobStorage(artifactUrls));
listener.getLogger().printf("Uploaded %s artifact(s) to %s%n", artifactUrls.size(), getExtension(PROVIDER).toURI(BLOB_CONTAINER, getBlobPath("artifacts/")));
listener.getLogger().printf("Uploaded %s artifact(s) to %s%n", artifactUrls.size(), provider.toURI(provider.getContainer(), getBlobPath("artifacts/")));
}

@Override
public boolean delete() throws IOException, InterruptedException {
return delete(getContext(getExtension(PROVIDER).getCredentialsSupplier()).getBlobStore(), getBlobPath(""));
return delete(provider, getContext().getBlobStore(), getBlobPath(""));
}

/**
* Delete all blobs starting with prefix
*/
static boolean delete(BlobStore blobStore, String prefix) throws IOException, InterruptedException {
Iterator<StorageMetadata> it = new JCloudsBlobStore.PageSetIterable(blobStore, BLOB_CONTAINER, ListContainerOptions.Builder.prefix(prefix).recursive());
static boolean delete(BlobStoreProvider provider, BlobStore blobStore, String prefix) throws IOException, InterruptedException {
Iterator<StorageMetadata> it = new JCloudsVirtualFile.PageSetIterable(blobStore, provider.getContainer(), ListContainerOptions.Builder.prefix(prefix).recursive());
boolean found = false;
while (it.hasNext()) {
StorageMetadata sm = it.next();
String path = sm.getName();
assert path.startsWith(prefix);
LOGGER.fine("deleting " + path);
blobStore.removeBlob(BLOB_CONTAINER, path);
blobStore.removeBlob(provider.getContainer(), path);
found = true;
}
return found;
}

@Override
public VirtualFile root() {
return new JCloudsBlobStore(getExtension(PROVIDER), BLOB_CONTAINER, getBlobPath("artifacts"));
return new JCloudsVirtualFile(provider, provider.getContainer(), getBlobPath("artifacts"));
}

@Override
public void stash(String name, FilePath workspace, Launcher launcher, EnvVars env, TaskListener listener, String includes, String excludes, boolean useDefaultExcludes, boolean allowEmpty) throws IOException, InterruptedException {
JCloudsApiExtensionPoint extension = getExtension(PROVIDER);
BlobStore blobStore = getContext(extension.getCredentialsSupplier()).getBlobStore();
BlobStore blobStore = getContext().getBlobStore();

// Map stash to url for upload
String path = getBlobPath("stashes/" + name + ".tgz");
Blob blob = blobStore.blobBuilder(path).build();
blob.getMetadata().setContainer(BLOB_CONTAINER);
URL url = extension.toExternalURL(blob, HttpMethod.PUT);
blob.getMetadata().setContainer(provider.getContainer());
URL url = provider.toExternalURL(blob, HttpMethod.PUT);
int count = workspace.act(new Stash(url, includes, excludes, useDefaultExcludes, WorkspaceList.tempDir(workspace).getRemote()));
if (count == 0 && !allowEmpty) {
throw new AbortException("No files included in stash");
}
listener.getLogger().printf("Stashed %d file(s) to %s%n", count, extension.toURI(BLOB_CONTAINER, path));
listener.getLogger().printf("Stashed %d file(s) to %s%n", count, provider.toURI(provider.getContainer(), path));
}

private static final class Stash extends MasterToSlaveFileCallable<Integer> {
Expand Down Expand Up @@ -225,19 +201,18 @@ public Integer invoke(File f, VirtualChannel channel) throws IOException, Interr

@Override
public void unstash(String name, FilePath workspace, Launcher launcher, EnvVars env, TaskListener listener) throws IOException, InterruptedException {
JCloudsApiExtensionPoint extension = getExtension(PROVIDER);
BlobStore blobStore = getContext(extension.getCredentialsSupplier()).getBlobStore();
BlobStore blobStore = getContext().getBlobStore();

// Map stash to url for download
String blobPath = getBlobPath("stashes/" + name + ".tgz");
Blob blob = blobStore.getBlob(BLOB_CONTAINER, blobPath);
Blob blob = blobStore.getBlob(provider.getContainer(), blobPath);
if (blob == null) {
throw new AbortException(
String.format("No such saved stash ‘%s’ found at %s/%s", name, BLOB_CONTAINER, blobPath));
String.format("No such saved stash ‘%s’ found at %s/%s", name, provider.getContainer(), blobPath));
}
URL url = extension.toExternalURL(blob, HttpMethod.GET);
URL url = provider.toExternalURL(blob, HttpMethod.GET);
workspace.act(new Unstash(url));
listener.getLogger().printf("Unstashed file(s) from %s%n", extension.toURI(BLOB_CONTAINER, blobPath));
listener.getLogger().printf("Unstashed file(s) from %s%n", provider.toURI(provider.getContainer(), blobPath));
}

private static final class Unstash extends MasterToSlaveFileCallable<Void> {
Expand All @@ -261,19 +236,18 @@ public Void invoke(File f, VirtualChannel channel) throws IOException, Interrupt
@Override
public void clearAllStashes(TaskListener listener) throws IOException, InterruptedException {
String stashPrefix = getBlobPath("stashes/");
JCloudsApiExtensionPoint extension = getExtension(PROVIDER);
BlobStore blobStore = getContext(extension.getCredentialsSupplier()).getBlobStore();
Iterator<StorageMetadata> it = new JCloudsBlobStore.PageSetIterable(blobStore, BLOB_CONTAINER, ListContainerOptions.Builder.prefix(stashPrefix).recursive());
BlobStore blobStore = getContext().getBlobStore();
Iterator<StorageMetadata> it = new JCloudsVirtualFile.PageSetIterable(blobStore, provider.getContainer(), ListContainerOptions.Builder.prefix(stashPrefix).recursive());
int count = 0;
while (it.hasNext()) {
StorageMetadata sm = it.next();
String path = sm.getName();
assert path.startsWith(stashPrefix);
LOGGER.fine("deleting " + path);
blobStore.removeBlob(BLOB_CONTAINER, path);
blobStore.removeBlob(provider.getContainer(), path);
count++;
}
listener.getLogger().printf("Deleted %d stash(es) from %s%n", count, extension.toURI(BLOB_CONTAINER, stashPrefix));
listener.getLogger().printf("Deleted %d stash(es) from %s%n", count, provider.toURI(provider.getContainer(), stashPrefix));
}

@Override
Expand All @@ -284,48 +258,23 @@ public void copyAllArtifactsAndStashes(Run<?, ?> to, TaskListener listener) thro
}
JCloudsArtifactManager dest = (JCloudsArtifactManager) am;
String allPrefix = getBlobPath("");
JCloudsApiExtensionPoint extension = getExtension(PROVIDER);
BlobStore blobStore = getContext(extension.getCredentialsSupplier()).getBlobStore();
Iterator<StorageMetadata> it = new JCloudsBlobStore.PageSetIterable(blobStore, BLOB_CONTAINER, ListContainerOptions.Builder.prefix(allPrefix).recursive());
BlobStore blobStore = getContext().getBlobStore();
Iterator<StorageMetadata> it = new JCloudsVirtualFile.PageSetIterable(blobStore, provider.getContainer(), ListContainerOptions.Builder.prefix(allPrefix).recursive());
int count = 0;
while (it.hasNext()) {
StorageMetadata sm = it.next();
String path = sm.getName();
assert path.startsWith(allPrefix);
String destPath = getBlobPath(dest.key, path.substring(allPrefix.length()));
LOGGER.fine("copying " + path + " to " + destPath);
blobStore.copyBlob(BLOB_CONTAINER, path, BLOB_CONTAINER, destPath, CopyOptions.NONE);
blobStore.copyBlob(provider.getContainer(), path, provider.getContainer(), destPath, CopyOptions.NONE);
count++;
}
listener.getLogger().printf("Copied %d artifact(s)/stash(es) from %s to %s%n", count, extension.toURI(BLOB_CONTAINER, allPrefix), extension.toURI(BLOB_CONTAINER, dest.getBlobPath("")));
listener.getLogger().printf("Copied %d artifact(s)/stash(es) from %s to %s%n", count, provider.toURI(provider.getContainer(), allPrefix), provider.toURI(provider.getContainer(), dest.getBlobPath("")));
}

/**
* Get the extension implementation for the specific JClouds provider or api id
*
* @param providerOrApi
* @throws IllegalStateException
* if extension is not present or run from the agent
* @return the extension implementation
*/
@NonNull
private static JCloudsApiExtensionPoint getExtension(String providerOrApi) {
return ExtensionList.lookup(JCloudsApiExtensionPoint.class).stream().filter(e -> providerOrApi.equals(e.id()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find an extension for " + providerOrApi));
}

private static BlobStoreContext getContext(Supplier<Credentials> credentialsSupplier) throws IOException {
try {
// for some reason it won't find it at runtime otherwise
ProviderRegistry.registerProvider(getExtension(PROVIDER).getProvider());

return ContextBuilder.newBuilder(PROVIDER)
.credentialsSupplier(credentialsSupplier)
.buildView(BlobStoreContext.class);
} catch (NoSuchElementException x) {
throw new IOException(x);
}
private BlobStoreContext getContext() throws IOException {
return provider.getContext();
}

private static class UploadToBlobStorage extends MasterToSlaveFileCallable<Void> {
Expand Down
Expand Up @@ -36,21 +36,28 @@
*/
public class JCloudsArtifactManagerFactory extends ArtifactManagerFactory {

private final BlobStoreProvider provider;

@DataBoundConstructor
public JCloudsArtifactManagerFactory() {
public JCloudsArtifactManagerFactory(BlobStoreProvider provider) {
this.provider = provider;
}

public BlobStoreProvider getProvider() {
return provider;
}

@Override
public ArtifactManager managerFor(Run<?, ?> build) {
return new JCloudsArtifactManager(build);
return new JCloudsArtifactManager(build, provider);
}

@Extension
public static final class DescriptorImpl extends ArtifactManagerFactoryDescriptor {

@Override
public String getDisplayName() {
return "S3-based Artifact Storage";
return "Cloud Artifact Storage";
}

}
Expand Down

0 comments on commit ca1464c

Please sign in to comment.