/******************************************************************************* * Copyright (c) 2009 Thales Corporate Services SAS * * Author : Gregory Boissinot * * * * 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.thalesgroup.hudson.plugins.copyarchiver; import com.thalesgroup.hudson.plugins.copyarchiver.util.CopyArchiverLogger; import hudson.*; import hudson.matrix.MatrixBuild; import hudson.matrix.MatrixConfiguration; import hudson.matrix.MatrixProject; import hudson.matrix.MatrixRun; import hudson.model.*; import hudson.remoting.VirtualChannel; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Notifier; import hudson.tasks.Publisher; import hudson.util.FormValidation; import net.sf.json.JSONObject; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.*; /** * @author Gregory Boissinot */ public class CopyArchiverPublisher extends Notifier implements Serializable { private static final long serialVersionUID = 1L; private String sharedDirectoryPath; private transient boolean useTimestamp; private transient String datePattern; private boolean flatten; private boolean deleteShared; private transient boolean usePreviousVersion043WithTimestamp; private List<ArchivedJobEntry> archivedJobList = new ArrayList<ArchivedJobEntry>(); public CopyArchiverPublisher() { } @DataBoundConstructor public CopyArchiverPublisher(String sharedDirectoryPath, String datePattern, boolean flatten, boolean deleteShared, List<ArchivedJobEntry> archivedJobList) { this.sharedDirectoryPath = sharedDirectoryPath; this.datePattern = datePattern; this.flatten = flatten; this.deleteShared = deleteShared; this.archivedJobList = archivedJobList; } @SuppressWarnings("unused") public String getSharedDirectoryPath() { return sharedDirectoryPath; } @SuppressWarnings("unused") public boolean isUsePreviousVersion043WithTimestamp() { return usePreviousVersion043WithTimestamp; } public void setSharedDirectoryPath(String sharedDirectoryPath) { this.sharedDirectoryPath = sharedDirectoryPath; } @SuppressWarnings("unused") public boolean getUseTimestamp() { return useTimestamp; } @SuppressWarnings("unused") public boolean getFlatten() { return flatten; } public void setFlatten(boolean flatten) { this.flatten = flatten; } public void setUseTimestamp(boolean useTimestamp) { this.useTimestamp = useTimestamp; } public List<ArchivedJobEntry> getArchivedJobList() { return archivedJobList; } public void setArchivedJobList(List<ArchivedJobEntry> archivedJobList) { this.archivedJobList = archivedJobList; } @SuppressWarnings("unused") public String getDatePattern() { return datePattern; } public void setDatePattern(String datePattern) { this.datePattern = datePattern; } @SuppressWarnings("unused") public boolean getDeleteShared() { return deleteShared; } public void setDeleteShared(boolean deleteShared) { this.deleteShared = deleteShared; } @Extension @SuppressWarnings("unused") public static final class CopyArchiverDescriptor extends BuildStepDescriptor<Publisher> { //CopyOnWriteList private List<AbstractProject> jobs; @SuppressWarnings("unused") public CopyArchiverDescriptor() { super(CopyArchiverPublisher.class); load(); } @Override public String getDisplayName() { return Messages.copyArchiver_displayName(); } @Override public Publisher newInstance(StaplerRequest req, JSONObject formData) throws FormException { CopyArchiverPublisher pub = new CopyArchiverPublisher(); req.bindJSON(pub, formData); List<ArchivedJobEntry> archivedJobEntries = req.bindParametersToList(ArchivedJobEntry.class, "copyarchiver.entry."); pub.getArchivedJobList().addAll(archivedJobEntries); return pub; } @Override public String getHelpFile() { return "/plugin/copyarchiver/help.html"; } public boolean isApplicable(Class<? extends AbstractProject> jobType) { return true; } @SuppressWarnings("unused") public List<AbstractProject> getJobs() { return Hudson.getInstance().getItems(AbstractProject.class); } @SuppressWarnings("unused") public FormValidation doDateTimePatternCheck(@QueryParameter("value") String pattern) { if (!Hudson.getInstance().hasPermission(Hudson.ADMINISTER)) return FormValidation.ok(); if (pattern == null || pattern.trim().length() == 0) { return FormValidation.error("You must provide a pattern value"); } try { new SimpleDateFormat(pattern); } catch (NullPointerException npe) { return FormValidation.error("Invalid input: " + npe.getMessage()); } catch (IllegalArgumentException iae) { return FormValidation.error("Invalid input: " + iae.getMessage()); } return FormValidation.ok(); } } @SuppressWarnings("unchecked") @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, final BuildListener listener) throws InterruptedException, IOException { if (build.getResult().equals(Result.UNSTABLE) || build.getResult().equals(Result.SUCCESS)) { final AbstractProject project = build.getProject(); try { if (useTimestamp) { CopyArchiverLogger.log(listener, "[WARNING] - You have installed a new version of te copyarchiver Hudson instance"); CopyArchiverLogger.log(listener, "[WARNING] - In this new version, the usuage of timestamp has been removed."); CopyArchiverLogger.log(listener, "[WARNING] - You need to use the zentimestamp Hudson plugin."); if (datePattern == null || datePattern.trim().length() == 0) { build.setResult(Result.FAILURE); throw new AbortException("The option 'Change the date format' is activated. You must provide a new date pattern."); } } final FilePath destDirFilePath = new FilePath(new File(filterField(build, listener, sharedDirectoryPath))); final boolean isMatrixExcutorProject = MatrixConfiguration.class.isAssignableFrom(project.getClass()); Boolean result = build.getWorkspace().act(new FilePath.FileCallable<Boolean>() { public Boolean invoke(File f, VirtualChannel channel) throws IOException { try { CopyArchiverLogger.log(listener, "Copying archived artifacts in the shared directory '" + destDirFilePath + "'."); if (deleteShared) { if (isMatrixExcutorProject) { CopyArchiverLogger.log(listener, "[WARNING] - The type of the current job is matrix project"); CopyArchiverLogger.log(listener, "[WARNING] - In this special case, the delete operation is not supported."); CopyArchiverLogger.log(listener, "[WARNING] - Checking 'Delete the shared directory.' option has no effect."); CopyArchiverLogger.log(listener, "[WARNING] - Uncheck 'Delete the shared directory.' option and manaed the shared directory in an other way."); return true; } else { destDirFilePath.deleteRecursive(); } } if (!destDirFilePath.isDirectory()) destDirFilePath.mkdirs(); return true; } catch (InterruptedException ie) { return false; } } }); if (!result) { CopyArchiverLogger.log(listener, "An error has been occured during the copyarchiver process."); build.setResult(Result.FAILURE); return false; } int numCopied = 0; for (ArchivedJobEntry archivedJobEntry : archivedJobList) { AbstractProject selectedProject = (AbstractProject) Hudson.getInstance().getItem(archivedJobEntry.getJobName()); FilePathArchiver lastSuccessfulDirFilePathArchiver; if (isSameProject(project, selectedProject)) { lastSuccessfulDirFilePathArchiver = new FilePathArchiver(build.getWorkspace()); //Copy numCopied += lastSuccessfulDirFilePathArchiver.copyRecursiveTo(flatten, filterField(build, listener, archivedJobEntry.getPattern()), filterField(build, listener, archivedJobEntry.getExcludes()), destDirFilePath); } else { File lastSuccessfulDir; if (MatrixProject.class.isAssignableFrom(selectedProject.getClass())) { MatrixProject matrixProject = (MatrixProject) selectedProject; MatrixBuild matrixBuild = matrixProject.getLastSuccessfulBuild(); if (matrixBuild == null) { CopyArchiverLogger.log(listener, "The selected project has never built. No copy will be proceded."); return true; } List<MatrixRun> runs = matrixBuild.getRuns(); for (MatrixRun run : runs) { lastSuccessfulDir = run.getArtifactsDir(); lastSuccessfulDirFilePathArchiver = new FilePathArchiver(new FilePath(launcher.getChannel(), lastSuccessfulDir.getAbsolutePath())); //Copy numCopied += lastSuccessfulDirFilePathArchiver.copyRecursiveTo(flatten, filterField(build, listener, archivedJobEntry.getPattern()), filterField(build, listener, archivedJobEntry.getExcludes()), destDirFilePath); } //lastSuccessfulDir = matrixBuild.getArtifactsDir(); } else { Run run = selectedProject.getLastSuccessfulBuild(); if (run == null) { CopyArchiverLogger.log(listener, "The selected has never built. No copy will be proceded."); return true; } lastSuccessfulDir = run.getArtifactsDir(); lastSuccessfulDirFilePathArchiver = new FilePathArchiver(new FilePath(launcher.getChannel(), lastSuccessfulDir.getAbsolutePath())); //Copy numCopied += lastSuccessfulDirFilePathArchiver.copyRecursiveTo(flatten, filterField(build, listener, archivedJobEntry.getPattern()), filterField(build, listener, archivedJobEntry.getExcludes()), destDirFilePath); } } } CopyArchiverLogger.log(listener, "'" + numCopied + "' artifacts have been copied."); CopyArchiverLogger.log(listener, "Stop copying archived artifacts in the shared directory."); return true; } catch (Exception e) { CopyArchiverLogger.log(listener, "Error on copyarchiver analysis: " + e); build.setResult(Result.FAILURE); return false; } } return true; } private boolean isSameProject(AbstractProject executorProject, AbstractProject selectedProject) { boolean matrixSelected = MatrixProject.class.isAssignableFrom(selectedProject.getClass()); boolean matrixExecutor = MatrixConfiguration.class.isAssignableFrom(executorProject.getClass()); if (matrixExecutor && !matrixSelected) { return false; } if (matrixExecutor && matrixSelected) { return (executorProject.getParent()).equals(selectedProject); } if (!matrixExecutor && matrixSelected) { return false; } return selectedProject.equals(executorProject); } private String filterField(AbstractBuild<?, ?> build, BuildListener listener, String fieldText) throws InterruptedException, IOException { Map<String, String> vars = new HashMap<String, String>(); Set<Map.Entry<String, String>> set = build.getEnvironment(listener).entrySet(); for (Map.Entry<String, String> entry : set) { vars.put(entry.getKey(), entry.getValue()); } // Add behaviour for the build timestanp (BUILD_ID) only for the previous versions of the copyarchiver plugin (<= 0.4.3) if (usePreviousVersion043WithTimestamp) { if (useTimestamp && datePattern != null) { SimpleDateFormat sdf = new SimpleDateFormat(datePattern); final String newBuildIdStr = sdf.format(build.getTimestamp().getTime()); vars.put("BUILD_ID", newBuildIdStr); } } String str = Util.replaceMacro(fieldText, vars); str = Util.replaceMacro(str, build.getBuildVariables()); return str; } public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.NONE; } /** * Call at Hudsion startup for backward compatibility * * @return the same instance with changes */ @SuppressWarnings("unused") public Object readResolve() { //If the config.xml has the field useTimestamp an datePatterm, it's a previous version of the plugin if (this.useTimestamp && this.datePattern != null) { this.usePreviousVersion043WithTimestamp = true; } return this; } }