/*
* The MIT License
*
* Copyright (c) 2011-2014, 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 com.cloudbees.plugins.deployer.impl.run;
import com.cloudbees.EndPoints;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.cloudbees.CloudBeesAccount;
import com.cloudbees.plugins.credentials.cloudbees.CloudBeesUser;
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
import com.cloudbees.plugins.deployer.DeployNowRunAction;
import com.cloudbees.plugins.deployer.hosts.DeployHost;
import com.cloudbees.plugins.deployer.hosts.DeployHostDescriptor;
import com.cloudbees.plugins.deployer.hosts.Messages;
import com.cloudbees.plugins.deployer.sources.DeploySourceOrigin;
import com.cloudbees.plugins.deployer.sources.MavenArtifactDeploySource;
import com.cloudbees.plugins.deployer.sources.StaticSelectionDeploySource;
import com.cloudbees.plugins.registration.run.CloudBeesClient;
import com.cloudbees.plugins.registration.run.CloudBeesClientFactory;
import com.ning.http.client.AsyncHttpClientConfig;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.Util;
import hudson.maven.MavenBuild;
import hudson.maven.MavenModuleSetBuild;
import hudson.maven.reporters.MavenArtifact;
import hudson.maven.reporters.MavenArtifactRecord;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.Run;
import hudson.security.ACL;
import hudson.util.ComboBoxModel;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import jenkins.plugins.asynchttpclient.AHCUtils;
import org.acegisecurity.Authentication;
import org.apache.commons.lang.StringUtils;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.FileSet;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
import javax.servlet.ServletException;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
/**
* @author stephenc
* @since 13/12/2012 13:00
*/
public class RunHostImpl extends DeployHost<RunHostImpl, RunTargetImpl> {
/**
* The maximum number of targets to infer.
*/
private static int MAX_AUTO_TARGETS = Integer.getInteger(RunHostImpl.class.getName() + ".MAX_AUTO_TARGETS", 10);
@CheckForNull
private final String user;
@CheckForNull
private final String account;
@DataBoundConstructor
public RunHostImpl(String user, String account, List<RunTargetImpl> targets) {
super(targets);
this.user = user;
this.account = account;
}
@CheckForNull
public String getUser() {
return user;
}
@CheckForNull
public String getAccount() {
return account;
}
/**
* {@inheritDoc}
*/
@Override
protected boolean isAuthenticationValid(Authentication authentication) {
CloudBeesUser cloudBeesUser = DescriptorImpl.getCloudBeesUser(user, authentication);
if (cloudBeesUser == null) {
return false;
}
List<CloudBeesAccount> accounts = cloudBeesUser.getAccounts();
if (accounts == null) {
return false;
}
CloudBeesAccount cloudBeesAccount = null;
for (CloudBeesAccount a : accounts) {
if (a.getName().equals(account)) {
cloudBeesAccount = a;
break;
}
}
return cloudBeesAccount != null;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("RunHostImpl");
sb.append("{user='").append(user).append('\'');
sb.append(", account='").append(account).append('\'');
sb.append(", super='").append(super.toString()).append('\'');
sb.append('}');
return sb.toString();
}
public String getDisplayName() {
final StringBuilder sb = new StringBuilder();
boolean first = true;
for (RunTargetImpl target : getTargets()) {
if (first) {
sb.append(account);
sb.append("/(");
first = false;
} else {
sb.append(", ");
}
sb.append(target.getApplicationId());
}
if (!first) {
sb.append(")");
}
return sb.toString();
}
/**
* The {@link com.cloudbees.plugins.deployer.hosts.DeployHostDescriptor} for {@link RunHostImpl}
*/
@Extension(ordinal = 1.0)
public static class DescriptorImpl extends DeployHostDescriptor<RunHostImpl, RunTargetImpl> {
/**
* A cache of targets that have been built
*/
private final LinkedHashMap<TargetKey, SoftReference<List<RunTargetImpl>>> newTargetCache = new LinkedHashMap
<TargetKey, SoftReference<List<RunTargetImpl>>>(25, 0.75f, true) {
@Override
protected boolean removeEldestEntry(
Map.Entry<TargetKey, SoftReference<List<RunTargetImpl>>> eldest) {
return size() >= 25;
}
};
/**
* {@inheritDoc}
*/
@NonNull
@Override
public Class<RunTargetImpl> getDeployTargetClass() {
return RunTargetImpl.class;
}
/**
* {@inheritDoc}
*/
@Override
public String getDisplayName() {
return Messages.CloudBeesRunSet_DisplayName();
}
@SuppressWarnings("unused") // used by stapler
public ListBoxModel doFillUserItems(@QueryParameter String usersAuth) {
ListBoxModel m = new ListBoxModel();
Set<String> names = new HashSet<String>();
Item item = Stapler.getCurrentRequest().findAncestorObject(Item.class);
if (!StringUtils.isEmpty(usersAuth) && item.hasPermission(DeployNowRunAction.OWN_AUTH)) {
for (CloudBeesUser u : CredentialsProvider.lookupCredentials(CloudBeesUser.class, item,
Hudson.getAuthentication(), Collections.<DomainRequirement>emptyList())) {
m.add(u.getDisplayName(), u.getName());
names.add(u.getName());
}
}
if (item.hasPermission(DeployNowRunAction.JOB_AUTH)) {
for (CloudBeesUser u : CredentialsProvider.lookupCredentials(CloudBeesUser.class, item, ACL.SYSTEM)) {
if (!names.contains(u.getName())) {
m.add(u.getDisplayName(), u.getName());
names.add(u.getName());
}
}
}
return m;
}
@SuppressWarnings("unused") // used by stapler
public FormValidation doCheckUser(@QueryParameter String usersAuth,
@QueryParameter final String value)
throws IOException, ServletException {
if (StringUtils.isBlank(value)) {
return FormValidation.warning("The user to deploy as must be specified");
}
Item item = Stapler.getCurrentRequest().findAncestorObject(Item.class);
CloudBeesUser cloudBeesUser = null;
if (!StringUtils.isEmpty(usersAuth) && item.hasPermission(DeployNowRunAction.OWN_AUTH)) {
cloudBeesUser = getCloudBeesUser(value, Hudson.getAuthentication());
}
if (cloudBeesUser == null && item.hasPermission(DeployNowRunAction.JOB_AUTH)) {
cloudBeesUser = getCloudBeesUser(value, ACL.SYSTEM);
}
if (cloudBeesUser == null) {
return FormValidation
.error("The specified user does not exist / you do not have permission to access the "
+ "specified user's credentials");
}
return FormValidation.ok();
}
@SuppressWarnings("unused") // used by stapler
public ListBoxModel doFillAccountItems(@QueryParameter String usersAuth, @QueryParameter String user) {
ListBoxModel m = new ListBoxModel();
user = Util.fixEmptyAndTrim(user);
if (user == null) {
return m;
}
Item item = Stapler.getCurrentRequest().findAncestorObject(Item.class);
CloudBeesUser u = null;
if (!StringUtils.isEmpty(usersAuth) && item.hasPermission(DeployNowRunAction.OWN_AUTH)) {
u = getCloudBeesUser(user, Hudson.getAuthentication());
}
if (u == null && item.hasPermission(DeployNowRunAction.JOB_AUTH)) {
u = getCloudBeesUser(user, ACL.SYSTEM);
}
if (u == null) {
return m;
}
List<CloudBeesAccount> accounts = u.getAccounts();
if (accounts != null) {
for (CloudBeesAccount a : accounts) {
m.add(a.getName(), a.getName());
}
}
return m;
}
@SuppressWarnings("unused") // used by stapler
public FormValidation doCheckAccount(@QueryParameter String usersAuth,
@QueryParameter final String user,
@QueryParameter final String value)
throws IOException, ServletException {
if (StringUtils.isBlank(user)) {
return FormValidation.ok(); // somebody else will flag this issue
}
if (StringUtils.isBlank(value)) {
return FormValidation.warning("The account to deploy into must be specified");
}
Item item = Stapler.getCurrentRequest().findAncestorObject(Item.class);
CloudBeesUser cloudBeesUser = null;
if (!StringUtils.isEmpty(usersAuth) && item.hasPermission(DeployNowRunAction.OWN_AUTH)) {
cloudBeesUser = getCloudBeesUser(user, Hudson.getAuthentication());
}
if (cloudBeesUser == null && item.hasPermission(DeployNowRunAction.JOB_AUTH)) {
cloudBeesUser = getCloudBeesUser(user, ACL.SYSTEM);
}
if (cloudBeesUser == null) {
return FormValidation.ok(); // somebody else will flag this issue
}
CloudBeesAccount cloudBeesAccount = cloudBeesUser.getAccount(value);
if (cloudBeesAccount == null) {
return FormValidation
.error("The specified account does not exist / the selected user does not have access to the "
+ "specified account");
}
return FormValidation.ok();
}
private static CloudBeesUser getCloudBeesUser(String user, Authentication authentication) {
for (CloudBeesUser u : CredentialsProvider.lookupCredentials(CloudBeesUser.class,
Stapler.getCurrentRequest().findAncestorObject(Item.class), authentication)) {
if (u.getName().equals(user)) {
return u;
}
}
return null;
}
@SuppressWarnings("unused") // used by stapler
public FormValidation doCheckApplicationId(@QueryParameter String usersAuth,
@QueryParameter final String value,
@QueryParameter final String user,
@QueryParameter final String account)
throws IOException, ServletException {
try {
if (StringUtils.isBlank(user) || StringUtils.isBlank(account)) {
return FormValidation.ok(); // somebody else will flag this issue
}
if (StringUtils.isBlank(value)) {
return FormValidation.error("Application Id cannot be empty");
}
Item item = Stapler.getCurrentRequest().findAncestorObject(Item.class);
CloudBeesUser cloudBeesUser = null;
if (!StringUtils.isEmpty(usersAuth) && item.hasPermission(DeployNowRunAction.OWN_AUTH)) {
cloudBeesUser = getCloudBeesUser(user, Hudson.getAuthentication());
}
if (cloudBeesUser == null && item.hasPermission(DeployNowRunAction.JOB_AUTH)) {
cloudBeesUser = getCloudBeesUser(user, ACL.SYSTEM);
}
if (cloudBeesUser == null) {
return FormValidation.ok(); // somebody else will flag this issue
}
CloudBeesAccount cloudBeesAccount = cloudBeesUser.getAccount(account);
if (cloudBeesAccount == null) {
return FormValidation.ok(); // somebody else will flag this issue
}
CloudBeesClient client = new CloudBeesClientFactory()
.withClientConfig(
new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(25000)
.build())
.withProxyServer(AHCUtils.getProxyServer())
.withAuthentication(cloudBeesUser.getAPIKey(), cloudBeesUser.getAPISecret().getPlainText())
.build();
try {
String matchName = account + "/" + value;
for (String name : client
.getApplicationsStatuses(cloudBeesAccount.getName())
.get(30, TimeUnit.SECONDS).keySet()) {
if (name.equals(matchName)) {
return FormValidation.ok();
}
}
} finally {
client.close();
}
return FormValidation
.warning("This application ID was not found, so using it will create a new application");
} catch (Exception e) {
return FormValidation.error(e, "error during check applicationId " + e.getMessage());
}
}
@SuppressWarnings("unused") // used by stapler
public ComboBoxModel doFillApplicationIdItems(@QueryParameter final String usersAuth,
@QueryParameter final String user,
@QueryParameter final String account) {
try {
if (StringUtils.isBlank(user) || StringUtils.isBlank(account)) {
return new ComboBoxModel();
}
Item item = Stapler.getCurrentRequest().findAncestorObject(Item.class);
CloudBeesUser cloudBeesUser = null;
if (!StringUtils.isEmpty(usersAuth) && item.hasPermission(DeployNowRunAction.OWN_AUTH)) {
cloudBeesUser = getCloudBeesUser(user, Hudson.getAuthentication());
}
if (cloudBeesUser == null && item.hasPermission(DeployNowRunAction.JOB_AUTH)) {
cloudBeesUser = getCloudBeesUser(user, ACL.SYSTEM);
}
if (cloudBeesUser == null) {
return new ComboBoxModel();
}
CloudBeesAccount cloudBeesAccount = cloudBeesUser.getAccount(account);
if (cloudBeesAccount == null) {
return new ComboBoxModel();
}
Set<String> names = new TreeSet<String>();
CloudBeesClient client = new CloudBeesClientFactory()
.withClientConfig(
new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(25000)
.build())
.withProxyServer(AHCUtils.getProxyServer())
.withAuthentication(cloudBeesUser.getAPIKey(), cloudBeesUser.getAPISecret().getPlainText())
.build();
try {
String prefix = account + "/";
for (String name : client
.getApplicationsStatuses(cloudBeesAccount.getName())
.get(30, TimeUnit.SECONDS).keySet()) {
if (name.startsWith(prefix)) {
names.add(name.substring(prefix.length()));
}
}
} finally {
client.close();
}
return new ComboBoxModel(names);
} catch (Exception e) {
return new ComboBoxModel();
}
}
/**
* {@inheritDoc}
*/
@Override
@CheckForNull
public RunHostImpl createDefault(@CheckForNull Run<?, ?> run,
@CheckForNull Set<DeploySourceOrigin> origins) {
return isSupported(origins, run) ? new RunHostImpl(null, null, createTargets(run, origins)) : null;
}
/**
* {@inheritDoc}
*/
@Override
@CheckForNull
public RunHostImpl updateDefault(@CheckForNull Run<?, ?> run, @CheckForNull Set<DeploySourceOrigin> origins,
RunHostImpl template) {
return isSupported(origins, run)
? new RunHostImpl(template.getUser(), template.getAccount(), createTargets(run, origins))
: null;
}
private List<RunTargetImpl> createTargets(Run<?, ?> run, Set<DeploySourceOrigin> origins) {
TargetKey key;
if (run != null) {
key = new TargetKey(run, origins);
final SoftReference<List<RunTargetImpl>> reference = newTargetCache.get(key);
if (reference != null) {
final List<RunTargetImpl> targets = reference.get();
if (targets != null) {
return targets;
}
}
} else {
key = null;
}
List<RunTargetImpl> result = new ArrayList<RunTargetImpl>();
if (origins.contains(DeploySourceOrigin.RUN)) {
if (run instanceof MavenModuleSetBuild) {
final MavenModuleSetBuild builds = (MavenModuleSetBuild) run;
for (List<MavenBuild> mavenBuilds : builds.getModuleBuilds().values()) {
if (result.size() > MAX_AUTO_TARGETS) {
break;
}
for (MavenBuild build : mavenBuilds) {
if (result.size() > MAX_AUTO_TARGETS) {
break;
}
List<MavenArtifactRecord> records = build.getActions(MavenArtifactRecord.class);
if (records != null) {
for (MavenArtifactRecord record : records) {
if (result.size() > MAX_AUTO_TARGETS) {
break;
}
MavenArtifact mainArtifact = record.mainArtifact;
if ("war".equals(mainArtifact.type)) {
result.add(new RunTargetImpl(EndPoints.runAPI(), null, null, null, null,
new MavenArtifactDeploySource(mainArtifact.groupId,
mainArtifact.artifactId,
mainArtifact.classifier, mainArtifact.type), false, null, null, null));
}
for (MavenArtifact artifact : record.attachedArtifacts) {
if (result.size() > MAX_AUTO_TARGETS) {
break;
}
if ("war".equals(artifact.type)) {
result.add(new RunTargetImpl(EndPoints.runAPI(), null, null, null, null,
new MavenArtifactDeploySource(mainArtifact.groupId,
mainArtifact.artifactId,
mainArtifact.classifier, mainArtifact.type), false, null, null, null));
}
}
}
}
}
}
} else if (run instanceof MavenBuild) {
final MavenBuild build = (MavenBuild) run;
List<MavenArtifactRecord> records = build.getActions(MavenArtifactRecord.class);
if (records != null) {
for (MavenArtifactRecord record : records) {
if (result.size() > MAX_AUTO_TARGETS) {
break;
}
MavenArtifact mainArtifact = record.mainArtifact;
if ("war".equals(mainArtifact.type)) {
result.add(new RunTargetImpl(EndPoints.runAPI(), null, null, null, null,
new MavenArtifactDeploySource(mainArtifact.groupId,
mainArtifact.artifactId,
mainArtifact.classifier, mainArtifact.type), false, null, null, null));
}
for (MavenArtifact artifact : record.attachedArtifacts) {
if (result.size() > MAX_AUTO_TARGETS) {
break;
}
if ("war".equals(artifact.type)) {
result.add(new RunTargetImpl(EndPoints.runAPI(), null, null, null, null,
new MavenArtifactDeploySource(mainArtifact.groupId,
mainArtifact.artifactId,
mainArtifact.classifier, mainArtifact.type), false, null, null, null));
}
}
}
}
} else {
if (run != null && run.getArtifactsDir().isDirectory() && run.getHasArtifacts()) {
FileSet fileSet = new FileSet();
fileSet.setProject(new Project());
fileSet.setDir(run.getArtifactsDir());
fileSet.setIncludes("**/*.war");
for (String path : fileSet.getDirectoryScanner().getIncludedFiles()) {
if (result.size() > MAX_AUTO_TARGETS) {
break;
}
result.add(new RunTargetImpl(EndPoints.runAPI(), null, null, null, null,
new StaticSelectionDeploySource(path), false, null, null, null));
}
}
}
}
if (key != null) {
newTargetCache.put(key, new SoftReference<List<RunTargetImpl>>(result));
}
return result;
}
}
/**
* A simple holder for the caching of calls to {@link DescriptorImpl#createTargets(hudson.model.Run,
* java.util.Set)}.
*/
private static final class TargetKey {
/**
* The run.
*/
@NonNull
private final Run<?, ?> run;
/**
* The list of origins.
*/
@CheckForNull
private Set<DeploySourceOrigin> origins;
/**
* Creates an instance.
*
* @param run the run.
* @param origins the list of origins.
*/
private TargetKey(@NonNull Run<?, ?> run, @CheckForNull Set<DeploySourceOrigin> origins) {
run.getClass(); // throw NPE if null
this.run = run;
this.origins = origins;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TargetKey)) {
return false;
}
TargetKey targetKey = (TargetKey) o;
if (origins != null ? !origins.equals(targetKey.origins) : targetKey.origins != null) {
return false;
}
if (!run.equals(targetKey.run)) {
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int result = run.hashCode();
result = 31 * result + (origins != null ? origins.hashCode() : 0);
return result;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("TargetKey");
sb.append("{run=").append(run);
sb.append(", origins=").append(origins);
sb.append('}');
return sb.toString();
}
}
}