/*
* Copyright (C) 2010 JFrog Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jfrog.hudson;
import hudson.Extension;
import hudson.model.Descriptor;
import hudson.model.Item;
import hudson.security.ACL;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import jenkins.model.GlobalConfiguration;
import jenkins.model.Jenkins;
import net.sf.json.JSONNull;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jfrog.build.api.util.NullLog;
import org.jfrog.build.client.ArtifactoryVersion;
import org.jfrog.build.extractor.clientConfiguration.client.ArtifactoryBuildInfoClient;
import org.jfrog.hudson.action.ActionableHelper;
import org.jfrog.hudson.pipeline.docker.proxy.CertManager;
import org.jfrog.hudson.pipeline.docker.proxy.BuildInfoProxy;
import org.jfrog.hudson.util.Credentials;
import org.jfrog.hudson.util.RepositoriesUtils;
import org.jfrog.hudson.util.plugins.PluginsUtils;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.bind.JavaScriptMethod;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author Yossi Shaul
*/
public class ArtifactoryBuilder extends GlobalConfiguration {
/**
* Descriptor for {@link ArtifactoryBuilder}. Used as a singleton. The class is marked as public so that it can be
* accessed from views.
* <p/>
* <p/>
* See <tt>views/hudson/plugins/artifactory/ArtifactoryBuilder/*.jelly</tt> for the actual HTML fragment for the
* configuration screen.
*/
@Extension
// this marker indicates Hudson that this is an implementation of an extension point.
public static final class DescriptorImpl extends Descriptor<GlobalConfiguration> {
private boolean useCredentialsPlugin;
private List<ArtifactoryServer> artifactoryServers;
private boolean pushToBintrayEnabled = true;
private boolean buildInfoProxyEnabled = false;
private int buildInfoProxyPort;
private String buildInfoProxyCertPublic;
private String buildInfoProxyCertPrivate;
public DescriptorImpl() {
super(ArtifactoryBuilder.class);
initDefaultCertPaths();
load();
}
private void initDefaultCertPaths() {
if (StringUtils.isNotEmpty(buildInfoProxyCertPublic) || StringUtils.isNotEmpty(buildInfoProxyCertPrivate)) {
return;
}
File jenkinsHome = new File(Jenkins.getInstance().getRootDir().getPath());
File publicCert = new File(jenkinsHome, CertManager.DEFAULT_RELATIVE_CERT_PATH);
File privateCert = new File(jenkinsHome, CertManager.DEFAULT_RELATIVE_KEY_PATH);
buildInfoProxyCertPublic = publicCert.getPath();
buildInfoProxyCertPrivate = privateCert.getPath();
}
@SuppressWarnings("unused")
public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Item project) {
return PluginsUtils.fillPluginCredentials(project, ACL.SYSTEM);
}
/**
* Performs on-the-fly validation of the form field 'name'.
*
* @param value This parameter receives the value that the user has typed.
* @return Indicates the outcome of the validation. This is sent to the browser.
*/
public FormValidation doCheckName(@QueryParameter String value) throws IOException, ServletException {
if (value.length() == 0) {
return FormValidation.error("Please set a name");
}
if (value.length() < 4) {
return FormValidation.warning("Isn't the name too short?");
}
return FormValidation.ok();
}
/**
* Performs on-the-fly validation of the form field 'ServerId'.
*
* @param value This parameter receives the value that the user has typed.
* @param artifactoryUrl This parameter receives the value that the user has typed as artifactory Url.
* @return Indicates the outcome of the validation. This is sent to the browser.
*/
public FormValidation doCheckServerId(@QueryParameter String value, @QueryParameter String artifactoryUrl, @QueryParameter String username, @QueryParameter String password, @QueryParameter String credentialsId) throws IOException, ServletException {
if (value.length() == 0) {
return FormValidation.error("Please set server ID");
}
List<ArtifactoryServer> artifactoryServers = RepositoriesUtils.getArtifactoryServers();
if (artifactoryServers == null) {
return FormValidation.ok();
}
int countServersByValueAsName = 0;
for (ArtifactoryServer server : artifactoryServers) {
if (server.getName().equals(value)) {
countServersByValueAsName++;
if (countServersByValueAsName > 1) {
return FormValidation.error("Duplicated server ID");
}
}
}
return FormValidation.ok();
}
public FormValidation doTestConnection(
@QueryParameter("artifactoryUrl") final String url,
@QueryParameter("artifactory.timeout") final String timeout,
@QueryParameter("artifactory.bypassProxy") final boolean bypassProxy,
@QueryParameter("useCredentialsPlugin") final boolean useLegacyCredentials,
@QueryParameter("credentialsId") final String deployerCredentialsId,
@QueryParameter("username") final String deployerCredentialsUsername,
@QueryParameter("password") final String deployerCredentialsPassword,
@QueryParameter("connectionRetry") final int connectionRetry
) throws ServletException {
if (StringUtils.isBlank(url)) {
return FormValidation.error("Please set a valid Artifactory URL");
}
if (connectionRetry < 0) {
return FormValidation.error("Connection Retries can not be less then 0");
}
Credentials credentials = PluginsUtils.credentialsLookup(deployerCredentialsId, null);
String username = useLegacyCredentials ? credentials.getUsername() : deployerCredentialsUsername;
String password = useLegacyCredentials ? credentials.getPassword() : deployerCredentialsPassword;
ArtifactoryBuildInfoClient client;
if (StringUtils.isNotBlank(username)) {
client = new ArtifactoryBuildInfoClient(url, username, password, new NullLog());
} else {
client = new ArtifactoryBuildInfoClient(url, new NullLog());
}
if (!bypassProxy && Jenkins.getInstance().proxy != null) {
client.setProxyConfiguration(RepositoriesUtils.createProxyConfiguration(Jenkins.getInstance().proxy));
}
if (StringUtils.isNotBlank(timeout)) {
client.setConnectionTimeout(Integer.parseInt(timeout));
}
RepositoriesUtils.setRetryParams(connectionRetry, client);
ArtifactoryVersion version;
try {
version = client.verifyCompatibleArtifactoryVersion();
} catch (UnsupportedOperationException uoe) {
return FormValidation.warning(uoe.getMessage());
} catch (Exception e) {
return FormValidation.error(e.getMessage());
}
return FormValidation.ok("Found Artifactory " + version.toString());
}
/**
* This human readable name is used in the configuration screen.
*/
@Override
public String getDisplayName() {
return "Artifactory Plugin";
}
@SuppressWarnings({"unchecked"})
@Override
public boolean configure(StaplerRequest req, JSONObject o) throws FormException {
useCredentialsPlugin = (Boolean) o.get("useCredentialsPlugin");
pushToBintrayEnabled = (Boolean) o.get("pushToBintrayEnabled");
try {
configureProxy((JSONObject) o.get("buildInfoProxyEnabled"));
} catch (IOException e) {
throw new FormException(e, e.getMessage());
} catch (InterruptedException e) {
throw new FormException(e, e.getMessage());
}
Object servers = o.get("artifactoryServer"); // an array or single object
List<ArtifactoryServer> artifactoryServers;
if (!JSONNull.getInstance().equals(servers)) {
artifactoryServers = req.bindJSONToList(ArtifactoryServer.class, servers);
} else {
artifactoryServers = null;
}
if (!isServerIDConfigured(artifactoryServers)) {
throw new FormException("Please set the Artifactory server ID.", "ServerID");
}
if (isServerDuplicated(artifactoryServers)) {
throw new FormException("The Artifactory server ID you have entered is already configured", "Server ID");
}
setArtifactoryServers(artifactoryServers);
save();
return super.configure(req, o);
}
private synchronized void configureProxy(JSONObject proxyConfig) throws IOException, InterruptedException {
if (proxyConfig == null) {
BuildInfoProxy.stopAll();
buildInfoProxyEnabled = false;
return;
}
int portFromForm = Integer.parseInt(proxyConfig.get("buildInfoProxyPort").toString());
if (!buildInfoProxyEnabled || portFromForm != buildInfoProxyPort) {
BuildInfoProxy.startAll(portFromForm);
buildInfoProxyEnabled = true;
buildInfoProxyPort = portFromForm;
}
}
public List<ArtifactoryServer> getArtifactoryServers() {
return artifactoryServers;
}
public boolean getUseCredentialsPlugin() {
return useCredentialsPlugin;
}
// Required by external plugins.
@SuppressWarnings({"UnusedDeclaration"})
public void setArtifactoryServers(List<ArtifactoryServer> artifactoryServers) {
this.artifactoryServers = artifactoryServers;
}
@SuppressWarnings({"UnusedDeclaration"})
public void setUseCredentialsPlugin(boolean useCredentialsPlugin) {
this.useCredentialsPlugin = useCredentialsPlugin;
}
// global.jelly uses this method to retrieve the value of pushToBintrayEnabled to determine if the checkbox should be checked.
public boolean isPushToBintrayEnabled() {
return pushToBintrayEnabled;
}
@SuppressWarnings({"UnusedDeclaration"})
public boolean isBuildInfoProxyEnabled() {
return buildInfoProxyEnabled && StringUtils.isNotEmpty(getBuildInfoProxyCertPublic()) && StringUtils.isNotEmpty(getBuildInfoProxyCertPrivate());
}
public String getBuildInfoProxyCertPublic() {
if (new File(buildInfoProxyCertPublic).exists()) {
return buildInfoProxyCertPublic;
}
return "";
}
public String getBuildInfoProxyCertPrivate() {
if (new File(buildInfoProxyCertPrivate).exists()) {
return buildInfoProxyCertPrivate;
}
return "";
}
@JavaScriptMethod
public Pair<String, String> generateCerts(String buildInfoProxyPort) {
if (isProxyCertExist()) {
return null;
}
int port = Integer.parseInt(buildInfoProxyPort);
CertManager.createCertificateSource(buildInfoProxyCertPublic, buildInfoProxyCertPrivate);
try {
BuildInfoProxy.startAll(port);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
this.buildInfoProxyPort = port;
return Pair.of(buildInfoProxyCertPublic, buildInfoProxyCertPrivate);
}
private Boolean isProxyCertExist() {
File buildInfoProxyCertPublicFile = new File(buildInfoProxyCertPublic);
File buildInfoProxyCertPrivateFile = new File(buildInfoProxyCertPrivate);
if (buildInfoProxyCertPublicFile.exists() || buildInfoProxyCertPrivateFile.exists()) {
return true;
}
return false;
}
public int getBuildInfoProxyPort() {
return buildInfoProxyPort;
}
private boolean isServerDuplicated(List<ArtifactoryServer> artifactoryServers) {
Set<String> serversNames = new HashSet<String>();
if (artifactoryServers == null) {
return false;
}
for (ArtifactoryServer server : artifactoryServers) {
String name = server.getName();
if (serversNames.contains(name)) {
return true;
}
serversNames.add(name);
}
return false;
}
private boolean isServerIDConfigured(List<ArtifactoryServer> artifactoryServers) {
if (artifactoryServers == null) {
return true;
}
for (ArtifactoryServer server : artifactoryServers) {
String name = server.getName();
if (StringUtils.isBlank(name)) {
return false;
}
}
return true;
}
}
}