Skip to content

Commit

Permalink
Merge pull request #117 from OpenDataSpace/master
Browse files Browse the repository at this point in the history
Fix JENKINS-35560 and implement credetials handling for blobstore.
  • Loading branch information
felfert committed Oct 4, 2016
2 parents f5b6233 + 054357e commit 36328bc
Show file tree
Hide file tree
Showing 26 changed files with 360 additions and 199 deletions.
9 changes: 7 additions & 2 deletions jclouds-plugin/pom.xml
Expand Up @@ -20,10 +20,15 @@
<artifactId>opencsv</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
<version>2.1.5</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>ssh-slaves</artifactId>
<version>1.9</version>
<version>1.11</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
Expand Down Expand Up @@ -87,7 +92,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.5.2</version>
<version>3.0.4</version>
<configuration>
<excludeFilterFile>src/findbugs/excludesFilter.xml</excludeFilterFile>
<failOnError>false</failOnError>
Expand Down
Expand Up @@ -4,37 +4,87 @@
import java.util.Properties;
import java.util.logging.Logger;

import shaded.com.google.common.base.Strings;
import shaded.com.google.common.collect.ImmutableSet;
import shaded.com.google.common.collect.ImmutableSet.Builder;
import shaded.com.google.common.collect.ImmutableSortedSet;
import shaded.com.google.common.collect.Iterables;
import shaded.com.google.common.io.Closeables;
import com.google.inject.Module;

import hudson.Extension;
import hudson.FilePath;
import hudson.Util;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Computer;
import hudson.model.Descriptor;
import hudson.model.ItemGroup;
import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import hudson.util.Secret;
import hudson.util.XStream2;

import jenkins.model.Jenkins;

import org.jclouds.Constants;
import org.jclouds.ContextBuilder;
import org.jclouds.apis.Apis;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.enterprise.config.EnterpriseConfigurationModule;
import org.jclouds.providers.Providers;

import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;

import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;

import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import com.cloudbees.plugins.credentials.common.StandardUsernameListBoxModel;
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;

import com.thoughtworks.xstream.converters.UnmarshallingContext;

import jenkins.plugins.jclouds.internal.CredentialsHelper;

/**
* Model class for Blobstore profile. User can configure multiple profiles to upload artifacts to different providers.
*
* @author Vijay Kiran
*/
public class BlobStoreProfile {
public class BlobStoreProfile extends AbstractDescribableImpl<BlobStoreProfile> {

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

private String profileName;
private String providerName;
private String identity;
private String credential;
private final String profileName;
private final String providerName;
private final String endPointUrl;
private String credentialsId;
private final boolean trustAll;

/** @deprecated Not used anymore, but retained for backward compatibility during deserialization. */
private final transient String identity;
/** @deprecated Not used anymore, but retained for backward compatibility during deserialization. */
private final transient String credential;

@DataBoundConstructor
public BlobStoreProfile(final String profileName, final String providerName, final String identity, final String credential) {
public BlobStoreProfile(final String profileName, final String providerName, final String credentialsId, final String endPointUrl, final boolean trustAll) {
this.profileName = profileName;
this.providerName = providerName;
this.identity = identity;
this.credential = credential;
this.credentialsId = credentialsId;
this.endPointUrl = endPointUrl;
this.trustAll = trustAll;
this.identity = null;
this.credential = null;
}

public boolean getTrustAll() {
return trustAll;
}

/**
Expand All @@ -56,28 +106,53 @@ public String getProviderName() {
}

/**
* Cloud provider identity.
*
* @return
* Provider endpoint.
*/
public String getIdentity() {
return identity;
public String getEndPointUrl() {
return endPointUrl;
}

/**
* Cloud provider credential.
* credentials.
*
* @return
*/
public String getCredential() {
return credential;
public String getCredentialsId() {
return credentialsId;
}

public void setCredentialsId(final String value) {
credentialsId = value;
}

static final Iterable<Module> MODULES = ImmutableSet.<Module>of(new EnterpriseConfigurationModule());

static BlobStoreContext ctx(String providerName, String identity, String credential, Properties overrides) {
return ContextBuilder.newBuilder(providerName).credentials(identity, credential).overrides(overrides).modules(MODULES)
.buildView(BlobStoreContext.class);
static BlobStoreContext ctx(String providerName, String credentialsId, Properties overrides) {
StandardUsernameCredentials u = CredentialsHelper.getCredentialsById(credentialsId);
if (null != u) {
// correct the classloader so that extensions can be found
Thread.currentThread().setContextClassLoader(Apis.class.getClassLoader());
if (u instanceof StandardUsernamePasswordCredentials) {
StandardUsernamePasswordCredentials up = (StandardUsernamePasswordCredentials)u;
return ContextBuilder.newBuilder(providerName).credentials(up.getUsername(), up.getPassword().toString())
.overrides(overrides).modules(MODULES).buildView(BlobStoreContext.class);
} else {
throw new RuntimeException("Using keys as credential for google cloud is not (yet) supported");
}
}
throw new RuntimeException("Could not retrieve credentials");
}

static BlobStoreContext ctx(String providerName, String credentialsId, String endPointUrl, boolean trustAll) {
Properties overrides = new Properties();
if (!Strings.isNullOrEmpty(endPointUrl)) {
overrides.setProperty(Constants.PROPERTY_ENDPOINT, endPointUrl);
}
if (trustAll) {
overrides.put(Constants.PROPERTY_TRUST_ALL_CERTS, "true");
overrides.put(Constants.PROPERTY_RELAX_HOSTNAME, "true");
}
return ctx(providerName, credentialsId, overrides);
}

/**
Expand All @@ -94,30 +169,115 @@ public void upload(String container, String path, FilePath filePath) throws IOEx
if (filePath.isDirectory()) {
throw new IOException(filePath + " is a directory");
}
// correct the classloader so that extensions can be found
Thread.currentThread().setContextClassLoader(Apis.class.getClassLoader());
// TODO: endpoint
final BlobStoreContext context = ctx(this.providerName, this.identity, this.credential, new Properties());
final BlobStoreContext context = ctx(providerName, credentialsId, endPointUrl, trustAll);
try {
final BlobStore blobStore = context.getBlobStore();
if (!blobStore.containerExists(container)) {
LOGGER.info("Creating container " + container);
blobStore.createContainerInLocation(null, container);
}
if (!path.equals("") && !blobStore.directoryExists(container, path)) {
if (!path.isEmpty() && !blobStore.directoryExists(container, path)) {
LOGGER.info("Creating directory " + path + " in container " + container);
blobStore.createDirectory(container, path);
}
String destPath;
if (path.equals("")) {
if (path.isEmpty()) {
destPath = filePath.getName();
} else {
destPath = path + "/" + filePath.getName();
}
LOGGER.info("Publishing now to container: " + container + " path: " + destPath);
Blob blob = context.getBlobStore().blobBuilder(destPath).payload(filePath.read()).build();
context.getBlobStore().putBlob(container, blob);
LOGGER.info("Published " + destPath + " to container " + container + " with profile " + this.profileName);
LOGGER.info("Published " + destPath + " to container " + container + " with profile " + profileName);
} finally {
context.close();
}
}

@Extension
public static class DescriptorImpl extends Descriptor<BlobStoreProfile> {

@Override
public String getDisplayName() {
return "";
}

public FormValidation doCheckProfileName(@QueryParameter String value) {
return FormValidation.validateRequired(value);
}

public FormValidation doCheckProviderName(@QueryParameter String value) {
return FormValidation.validateRequired(value);
}

public FormValidation doCheckCredentialsId(@QueryParameter String value) {
return FormValidation.validateRequired(value);
}

public FormValidation doCheckEndPointUrl(@QueryParameter String value) {
return FormValidation.ok();
}

public ListBoxModel doFillCredentialsIdItems(@AncestorInPath ItemGroup context, @QueryParameter String currentValue) {
if (!(context instanceof AccessControlled ? (AccessControlled) context : Jenkins.getActiveInstance()).hasPermission(Computer.CONFIGURE)) {
return new StandardUsernameListBoxModel().includeCurrentValue(currentValue);
}
return new StandardUsernameListBoxModel()
.includeAs(ACL.SYSTEM, context, StandardUsernameCredentials.class).includeCurrentValue(currentValue);
}

public ListBoxModel doFillProviderNameItems(@AncestorInPath ItemGroup context) {
ListBoxModel m = new ListBoxModel();
// 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) {
m.add(supportedProvider, supportedProvider);
}
return m;
}

public FormValidation doTestConnection(@QueryParameter String providerName, @QueryParameter String credentialsId,
@QueryParameter String endPointUrl, @QueryParameter boolean trustAll) throws IOException {
if (null == Util.fixEmptyAndTrim(credentialsId)) {
return FormValidation.error("BlobStore credentials not specified.");
}
// Remove empty text/whitespace from the fields.
providerName = Util.fixEmptyAndTrim(providerName);
endPointUrl = Util.fixEmptyAndTrim(endPointUrl);

FormValidation result = FormValidation.ok("Connection succeeded!");
BlobStoreContext ctx = null;
try {
ctx(providerName, credentialsId, endPointUrl, trustAll).getBlobStore().list();
} catch (Exception ex) {
result = FormValidation.error("Cannot connect to specified BlobStore, please check the credentials: " + ex.getMessage());
} finally {
Closeables.close(ctx, true);
}
return result;
}
}

@Restricted(DoNotUse.class)
public static class ConverterImpl extends XStream2.PassthruConverter<BlobStoreProfile> {
static final Logger LOGGER = Logger.getLogger(ConverterImpl.class.getName());

public ConverterImpl(XStream2 xstream) {
super(xstream);
}

@Override protected void callback(BlobStoreProfile bsp, UnmarshallingContext context) {
if (Strings.isNullOrEmpty(bsp.getCredentialsId()) && !Strings.isNullOrEmpty(bsp.identity)) {
final String description = "JClouds BlobStore " + bsp.profileName + " - auto-migrated";
bsp.setCredentialsId(CredentialsHelper.convertCredentials(description, bsp.identity, Secret.fromString(bsp.credential)));
}
}
}
}
Expand Up @@ -14,22 +14,22 @@
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import hudson.util.CopyOnWriteList;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;

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

import org.jclouds.rest.AuthorizationException;

import javax.servlet.ServletException;
import java.io.IOException;
import java.io.PrintStream;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import net.sf.json.JSONObject;

/**
* Publishes artifacts to Blobstore configured using JClouds
*
Expand All @@ -46,7 +46,7 @@ public class BlobStorePublisher extends Recorder implements Describable<Publishe
private final List<BlobStoreEntry> entries;

/**
* Create a new Blobstore publisher for the cofigured profile identified by profileName
* Create a new Blobstore publisher for the configured profile identified by profileName
*
* @param profileName - the name of the configured profile name
*/
Expand Down Expand Up @@ -235,12 +235,12 @@ public DescriptorImpl() {

@Override
public String getDisplayName() {
return "Publish artifacts to JClouds Clouds Storage ";
return "Publish artifacts to JClouds BlobStore";
}

@Override
public boolean configure(StaplerRequest req, net.sf.json.JSONObject json) throws FormException {
profiles.replaceBy(req.bindParametersToList(BlobStoreProfile.class, "jcblobstore."));
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
profiles.replaceBy(req.bindJSONToList(BlobStoreProfile.class, formData.get("profiles")));
save();
return true;
}
Expand All @@ -249,16 +249,6 @@ public BlobStoreProfile[] getProfiles() {
return profiles.toArray(new BlobStoreProfile[0]);
}

public FormValidation doLoginCheck(final StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
String name = Util.fixEmpty(req.getParameter("name"));
if (name == null) {// name is not entered yet
return FormValidation.ok();
}
BlobStoreProfile profile = new BlobStoreProfile(name, req.getParameter("providerName"), req.getParameter("identity"),
req.getParameter("credential"));
return FormValidation.ok();
}

@Override
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true;
Expand Down
Expand Up @@ -11,10 +11,11 @@ class ComputeLogger extends org.jclouds.logging.jdk.JDKLogger {

public static class Factory extends JDKLoggerFactory {
public org.jclouds.logging.Logger getLogger(String category) {
if (category.equals("jclouds.compute") || (category.equals("jclouds.wire") && wireLogging))
if (category.equals("jclouds.compute") || category.equals("jclouds.wire") && wireLogging) {
return new ComputeLogger(Logger.getLogger(category));
else
} else {
return super.getLogger(category);
}
}
}

Expand Down

0 comments on commit 36328bc

Please sign in to comment.