package org.jfrog.hudson.generic; import com.google.common.collect.*; import hudson.EnvVars; import hudson.FilePath; import hudson.Util; import hudson.model.BuildListener; import hudson.model.Cause; import hudson.model.Run; import hudson.model.TaskListener; import hudson.remoting.VirtualChannel; import jenkins.model.Jenkins; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringUtils; import org.jfrog.build.api.Artifact; import org.jfrog.build.api.BuildInfoFields; import org.jfrog.build.api.builder.ArtifactBuilder; import org.jfrog.build.api.util.FileChecksumCalculator; import org.jfrog.build.client.DeployDetails; import org.jfrog.build.client.ProxyConfiguration; import org.jfrog.build.extractor.clientConfiguration.client.ArtifactoryBuildInfoClient; import org.jfrog.build.extractor.clientConfiguration.util.PublishedItemsHelper; import org.jfrog.build.extractor.clientConfiguration.util.spec.Spec; import org.jfrog.build.extractor.clientConfiguration.util.spec.SpecsHelper; import org.jfrog.hudson.ArtifactoryServer; import org.jfrog.hudson.CredentialsConfig; import org.jfrog.hudson.action.ActionableHelper; import org.jfrog.hudson.util.*; import java.io.File; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Map; import java.util.Set; /** * Deploys artifacts to Artifactory. This class is used only in free style generic configurator. * * @author Shay Yaakov */ public class GenericArtifactsDeployer { private static final String SHA1 = "SHA1"; private static final String MD5 = "MD5"; private Run build; private ArtifactoryGenericConfigurator configurator; private BuildListener listener; private CredentialsConfig credentialsConfig; private EnvVars env; private List<Artifact> artifactsToDeploy = Lists.newArrayList(); public GenericArtifactsDeployer(Run build, ArtifactoryGenericConfigurator configurator, BuildListener listener, CredentialsConfig credentialsConfig) throws IOException, InterruptedException, NoSuchAlgorithmException { this.build = build; this.configurator = configurator; this.listener = listener; this.credentialsConfig = credentialsConfig; this.env = build.getEnvironment(listener); } public List<Artifact> getDeployedArtifacts() { return artifactsToDeploy; } public void deploy() throws IOException, InterruptedException { FilePath workingDir = build.getExecutor().getCurrentWorkspace(); ArrayListMultimap<String, String> propertiesToAdd = getbuildPropertiesMap(); ArtifactoryServer artifactoryServer = configurator.getArtifactoryServer(); if (configurator.isUseSpecs()) { String spec = Util.replaceMacro(SpecUtils.getSpecStringFromSpecConf( configurator.getUploadSpec(), env, workingDir, listener.getLogger()) , env); artifactsToDeploy = workingDir.act(new FilesDeployerCallable(listener, spec, artifactoryServer, credentialsConfig.getCredentials(build.getParent()), propertiesToAdd, artifactoryServer.createProxyConfiguration(Jenkins.getInstance().proxy))); } else { String deployPattern = Util.replaceMacro(configurator.getDeployPattern(), env); deployPattern = StringUtils.replace(deployPattern, "\r\n", "\n"); deployPattern = StringUtils.replace(deployPattern, ",", "\n"); Multimap<String, String> pairs = PublishedItemsHelper.getPublishedItemsPatternPairs(deployPattern); if (pairs.isEmpty()) { return; } String repositoryKey = Util.replaceMacro(configurator.getRepositoryKey(), env); artifactsToDeploy = workingDir.act(new FilesDeployerCallable(listener, pairs, artifactoryServer, credentialsConfig.getCredentials(build.getParent()), repositoryKey, propertiesToAdd, artifactoryServer.createProxyConfiguration(Jenkins.getInstance().proxy))); } } private ArrayListMultimap<String, String> getbuildPropertiesMap() { ArrayListMultimap<String, String> properties = ArrayListMultimap.create(); String buildName = BuildUniqueIdentifierHelper.getBuildNameConsiderOverride(configurator, build); properties.put("build.name", buildName); properties.put("build.number", BuildUniqueIdentifierHelper.getBuildNumber(build)); properties.put("build.timestamp", build.getTimestamp().getTime().getTime() + ""); Cause.UpstreamCause parent = ActionableHelper.getUpstreamCause(build); if (parent != null) { properties.put("build.parentName", ExtractorUtils.sanitizeBuildName(parent.getUpstreamProject())); properties.put("build.parentNumber", parent.getUpstreamBuild() + ""); } String revision = ExtractorUtils.getVcsRevision(env); if (StringUtils.isNotBlank(revision)) { properties.put(BuildInfoFields.VCS_REVISION, revision); } addMatrixParams(properties); return properties; } private void addMatrixParams(Multimap<String, String> properties) { String[] matrixParams = StringUtils.split(configurator.getMatrixParams(), ";"); if (matrixParams == null) { return; } for (String matrixParam : matrixParams) { String[] split = StringUtils.split(matrixParam, '='); if (split.length == 2) { String value = Util.replaceMacro(split[1], env); //Space is not allowed in property key properties.put(split[0].replace(" ", StringUtils.EMPTY), value); } } } public static class FilesDeployerCallable implements FilePath.FileCallable<List<Artifact>> { private String repositoryKey; private TaskListener listener; private Multimap<String, String> patternPairs; private ArtifactoryServer server; private Credentials credentials; private ArrayListMultimap<String, String> buildProperties; private ProxyConfiguration proxyConfiguration; private PatternType patternType = PatternType.ANT; private String spec; public enum PatternType { ANT, WILDCARD } public FilesDeployerCallable(TaskListener listener, Multimap<String, String> patternPairs, ArtifactoryServer server, Credentials credentials, String repositoryKey, ArrayListMultimap<String, String> buildProperties, ProxyConfiguration proxyConfiguration) { this.listener = listener; this.patternPairs = patternPairs; this.server = server; this.credentials = credentials; this.repositoryKey = repositoryKey; this.buildProperties = buildProperties; this.proxyConfiguration = proxyConfiguration; } public FilesDeployerCallable(TaskListener listener, String spec, ArtifactoryServer server, Credentials credentials, ArrayListMultimap<String, String> buildProperties, ProxyConfiguration proxyConfiguration) { this.listener = listener; this.spec = spec; this.server = server; this.credentials = credentials; this.buildProperties = buildProperties; this.proxyConfiguration = proxyConfiguration; } public List<Artifact> invoke(File workspace, VirtualChannel channel) throws IOException, InterruptedException { Set<DeployDetails> artifactsToDeploy = Sets.newHashSet(); ArtifactoryBuildInfoClient client = server.createArtifactoryClient(credentials.getUsername(), credentials.getPassword(), proxyConfiguration); if (StringUtils.isNotEmpty(spec)) { SpecsHelper specsHelper = new SpecsHelper(new JenkinsBuildInfoLog(listener)); try { return specsHelper.uploadArtifactsBySpec(spec, workspace, buildProperties, client); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Failed uploading artifacts by spec", e); } } else { Multimap<String, File> targetPathToFilesMap = buildTargetPathToFiles(workspace); for (Map.Entry<String, File> entry : targetPathToFilesMap.entries()) { artifactsToDeploy.addAll(buildDeployDetailsFromFileEntry(entry)); } try { deploy(client, artifactsToDeploy); return convertDeployDetailsToArtifacts(artifactsToDeploy); } finally { client.close(); } } } private List<Artifact> convertDeployDetailsToArtifacts(Set<DeployDetails> details) { List<Artifact> result = Lists.newArrayList(); for (DeployDetails detail : details) { String ext = FilenameUtils.getExtension(detail.getFile().getName()); Artifact artifact = new ArtifactBuilder(detail.getFile().getName()).md5(detail.getMd5()) .sha1(detail.getSha1()).type(ext).build(); result.add(artifact); } return result; } public void deploy(ArtifactoryBuildInfoClient client, Set<DeployDetails> artifactsToDeploy) throws IOException { for (DeployDetails deployDetail : artifactsToDeploy) { StringBuilder deploymentPathBuilder = new StringBuilder(server.getUrl()); deploymentPathBuilder.append("/").append(deployDetail.getTargetRepository()); if (!deployDetail.getArtifactPath().startsWith("/")) { deploymentPathBuilder.append("/"); } deploymentPathBuilder.append(deployDetail.getArtifactPath()); listener.getLogger().println("Deploying artifact: " + deploymentPathBuilder.toString()); client.deployArtifact(deployDetail); } } private Multimap<String, File> buildTargetPathToFiles(File workspace) throws IOException { Multimap<String, File> result = HashMultimap.create(); if (patternPairs == null) { return result; } for (Map.Entry<String, String> entry : patternPairs.entries()) { String pattern = entry.getKey(); String targetPath = entry.getValue(); Multimap<String, File> publishingData = PublishedItemsHelper.buildPublishingData(workspace, pattern, targetPath); if (publishingData != null) { listener.getLogger().println( "For pattern: " + pattern + " " + publishingData.size() + " artifacts were found"); result.putAll(publishingData); } else { listener.getLogger().println("For pattern: " + pattern + " no artifacts were found"); } } return result; } private Set<DeployDetails> buildDeployDetailsFromFileEntry(Map.Entry<String, File> fileEntry) throws IOException { Set<DeployDetails> result = Sets.newHashSet(); String targetPath = fileEntry.getKey(); File artifactFile = fileEntry.getValue(); String path; if (patternType == PatternType.ANT) { path = PublishedItemsHelper.calculateTargetPath(targetPath, artifactFile); } else { path = PublishedItemsHelper.wildcardCalculateTargetPath(targetPath, artifactFile); } path = StringUtils.replace(path, "//", "/"); // calculate the sha1 checksum that is not given by Jenkins and add it to the deploy artifactsToDeploy Map<String, String> checksums = Maps.newHashMap(); try { checksums = FileChecksumCalculator.calculateChecksums(artifactFile, SHA1, MD5); } catch (NoSuchAlgorithmException e) { listener.getLogger().println("Could not find checksum algorithm for " + SHA1 + " or " + MD5); } DeployDetails.Builder builder = new DeployDetails.Builder() .file(artifactFile) .artifactPath(path) .targetRepository(repositoryKey) .md5(checksums.get(MD5)).sha1(checksums.get(SHA1)) .addProperties(buildProperties); result.add(builder.build()); return result; } } }