├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── pom.xml └── src └── main ├── java └── hudson │ └── plugins │ └── templateproject │ ├── ItemListenerImpl.java │ ├── ProxyBuildEnvironment.java │ ├── ProxyBuilder.java │ ├── ProxyPublisher.java │ ├── ProxySCM.java │ ├── TemplateUtils.java │ └── UpdateTransientProperty.java └── resources ├── hudson └── plugins │ └── templateproject │ ├── ProxyBuildEnvironment │ └── config.jelly │ ├── ProxyBuilder │ └── config.jelly │ ├── ProxyPublisher │ └── config.jelly │ └── ProxySCM │ └── config.jelly └── index.jelly /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | work 3 | .classpath 4 | .project 5 | .settings 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 1.5.2 4 | * **Released Feb 2, 2016** 5 | * Add support for build wrappers (pre-SCM steps) and parameters 6 | * Fix error with jobLink 7 | * Mitigate JENKINS-30243 8 | 9 | ## 1.5.1 10 | * **Released Sep 2, 2015** 11 | * Re-fix for JENKINS-24404 by ensuring passing build 12 | 13 | ## 1.5 14 | * **Released Aug 31, 2015** 15 | * Lots of updates, bounced up to 1.5 16 | * Expand parameter values, some limitations may apply to SCM (JENKINS-28249) 17 | * Support Cloudbees folder plugin (JENKINS-24396) 18 | * Support for build environment variables (JENKINS-24404) 19 | * Support for Multiple-SCM when using paramater value 20 | * Additional logging info 21 | 22 | ## 1.4.2 23 | * **Released July 9, 2014** 24 | * Support of dependency declarations interface from included templates. 25 | 26 | ## 1.4.1 27 | * **Released Mar 12, 2014** 28 | * Fixed performance issue when Use SCM is not checked (JENKINS-22150) 29 | 30 | ## 1.3 31 | * **Released Aug 15, 2011** 32 | * Updated SCM proxy for latest Jenkins 33 | 34 | ## 1.2 35 | * **Released Feb 11, 2010** 36 | * Fix Hudson-breaking 1.1 release. (JENKINS-5612) 37 | * Fix to allow using build steps from a matrix project. (JENKINS-5146) 38 | * Get form field validators working again. 39 | 40 | ## 1.1 41 | * **Released Feb 10, 2010** 42 | * Update code for more recent Hudson 43 | 44 | ## 1.0 45 | * **Released Feb 13, 2009** 46 | * Initial release 47 | 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Jenkins 2 | For information on contributing to Jenkins, check out the https://wiki.jenkins-ci.org/display/JENKINS/contributing and https://wiki.jenkins-ci.org/display/JENKINS/Extend+Jenkins wiki pages. 3 | They will help you get started with contributing to Jenkins. 4 | 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2004-, Jenkins, and a number of other of contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # template-project-plugin 2 | 3 | The aim of this plugin is to be able to use builders, publishers 4 | and SCM settings from another project. 5 | 6 | More documentation available on the Jenkins wiki: 7 | https://wiki.jenkins-ci.org/display/JENKINS/Template+Project+Plugin 8 | https://issues.jenkins-ci.org/browse/JENKINS/component/15623/ 9 | 10 | ## Setup 11 | * Set up a template project that has all the settings you want to share. E.g. you could create one with no SCM filled in, but with all the builders and publishers you want for all your projects. Its best to mark this project as disabled, since you are not actually going to run it. 12 | * Then set up a concrete project. Configure the SCM as you want. Then select 'use all the publishers from this project' and pick the template project. Ditto for the builders. 13 | 14 | ## Limitations 15 | * General: 16 | * It may be using some plugins in ways that were not intended. Compatibility with all plugins is not guaranteed. 17 | * It does not support project actions. That means that links that should be on the project page (e.g. 'latest test results') will not be there. 18 | * Publishers: 19 | * Post-build publishers need to be 'self-contained', meaning they may not work if a publisher relies on configs in the template project. 20 | * SCM: 21 | * Only supports build variables (not environment variables) as gets into infinite loop using `getEnvironment()` since it loops back to `getScm().buildEnvVars()`. 22 | * It has had virtually no testing. 23 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | org.jenkins-ci.plugins 5 | plugin 6 | 1.580.1 7 | 8 | 9 | org.jenkins-ci.plugins 10 | template-project 11 | 1.5.3-SNAPSHOT 12 | hpi 13 | 14 | Template Project plugin 15 | Use builders, publishers and SCM settings from another project. 16 | http://wiki.jenkins-ci.org/display/JENKINS/Template+Project+Plugin 17 | 18 | 19 | 20 | MIT license 21 | All source code is under the MIT license. 22 | 23 | 24 | 25 | 26 | 27 | huybrechts 28 | Tom Huybrechts 29 | 30 | 31 | Brantone 32 | Brenton B 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | maven-javadoc-plugin 41 | 2.2 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | commons-lang 50 | commons-lang 51 | 2.4 52 | 53 | 54 | org.jenkins-ci.plugins 55 | multiple-scms 56 | 0.5 57 | true 58 | 59 | 60 | org.jenkins-ci.plugins 61 | cloudbees-folder 62 | 4.9 63 | true 64 | 65 | 66 | org.jenkins-ci.plugins 67 | matrix-project 68 | 1.4 69 | true 70 | 71 | 72 | 73 | 74 | scm:git:git://github.com/jenkinsci/template-project-plugin.git 75 | scm:git:git@github.com:jenkinsci/template-project-plugin.git 76 | https://github.com/jenkinsci/template-project-plugin 77 | HEAD 78 | 79 | 80 | 81 | 82 | repo.jenkins-ci.org 83 | http://repo.jenkins-ci.org/public/ 84 | 85 | 86 | 87 | 88 | 89 | repo.jenkins-ci.org 90 | http://repo.jenkins-ci.org/public/ 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/templateproject/ItemListenerImpl.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.templateproject; 2 | 3 | import java.io.IOException; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import hudson.matrix.MatrixProject; 8 | import hudson.model.Hudson; 9 | import hudson.model.Project; 10 | import hudson.Extension; 11 | import hudson.model.AbstractProject; 12 | import hudson.model.listeners.ItemListener; 13 | import hudson.tasks.BuildWrapper; 14 | import hudson.tasks.Builder; 15 | import java.util.logging.Logger; 16 | 17 | /** 18 | * This ItemListener implementation will force job using 19 | * template for publisher to regenerate the transient actions list. 20 | * 21 | * To do so we use the {@link UpdateTransientProperty} property which does not do 22 | * anything. This is a bit a hack that relies on the behavior, but 23 | * there does not seem to be any better way to force updating projects transients actions. 24 | * 25 | * @author william.bernardet@gmail.com 26 | * 27 | */ 28 | @Extension 29 | public class ItemListenerImpl extends ItemListener { 30 | private static final Logger LOGGER = Logger.getLogger(ItemListenerImpl.class.getName()); 31 | 32 | /** 33 | * Let's force the projects using either the ProxyPublisher or the ProxyBuilder 34 | * to update their transient actions. 35 | */ 36 | @Override 37 | public void onLoaded() { 38 | for (AbstractProject project : Hudson.getInstance().getAllItems(AbstractProject.class)) { 39 | if (project.getPublishersList().get(ProxyPublisher.class) != null || 40 | hasBuilder(project, ProxyBuilder.class) 41 | || hasBuildWrappers(project, ProxyBuildEnvironment.class)) { 42 | try { 43 | project.addProperty(new UpdateTransientProperty()); 44 | project.removeProperty(UpdateTransientProperty.class); 45 | } catch (IOException e) { 46 | LOGGER.severe(e.getMessage()); 47 | } 48 | } 49 | } 50 | } 51 | 52 | private List getBuilders(AbstractProject project) { 53 | if (project instanceof Project) { 54 | return ((Project)project).getBuilders(); 55 | } else if (project instanceof MatrixProject) { 56 | return ((MatrixProject)project).getBuilders(); 57 | } else { 58 | return Collections.emptyList(); 59 | } 60 | } 61 | 62 | private List getBuildWrappers(AbstractProject project) { 63 | if (project instanceof Project) { 64 | return ((Project)project).getBuildWrappersList(); 65 | } else if (project instanceof MatrixProject) { 66 | return ((MatrixProject)project).getBuildWrappersList(); 67 | } else { 68 | return Collections.emptyList(); 69 | } 70 | } 71 | 72 | public boolean hasBuilder(AbstractProject project, Class type) { 73 | for (Builder b : getBuilders(project)) { 74 | if (type.isInstance(b)) { 75 | return true; 76 | } 77 | } 78 | return false; 79 | } 80 | 81 | public boolean hasBuildWrappers(AbstractProject project, Class type) { 82 | for (BuildWrapper b : getBuildWrappers(project)) { 83 | if (type.isInstance(b)) { 84 | return true; 85 | } 86 | } 87 | return false; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/templateproject/ProxyBuildEnvironment.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.templateproject; 2 | 3 | import hudson.Extension; 4 | import hudson.Launcher; 5 | import hudson.console.HyperlinkNote; 6 | import hudson.matrix.MatrixProject; 7 | import hudson.model.AbstractBuild; 8 | import hudson.model.AbstractProject; 9 | import hudson.model.Action; 10 | import hudson.model.BuildListener; 11 | import jenkins.model.DependencyDeclarer; 12 | import hudson.model.DependencyGraph; 13 | import hudson.model.Hudson; 14 | import hudson.model.Item; 15 | import hudson.model.Project; 16 | import hudson.model.Run; 17 | import hudson.security.AccessControlled; 18 | import hudson.tasks.BuildWrapper; 19 | import hudson.tasks.BuildWrapperDescriptor; 20 | import hudson.tasks.Messages; 21 | import hudson.util.FormValidation; 22 | import java.io.IOException; 23 | import java.io.OutputStream; 24 | 25 | import java.util.ArrayList; 26 | import java.util.Collection; 27 | import java.util.Collections; 28 | import java.util.List; 29 | 30 | import org.kohsuke.stapler.AncestorInPath; 31 | import org.kohsuke.stapler.DataBoundConstructor; 32 | import org.kohsuke.stapler.QueryParameter; 33 | 34 | public class ProxyBuildEnvironment extends BuildWrapper implements DependencyDeclarer { 35 | 36 | private final String projectName; 37 | 38 | @DataBoundConstructor 39 | public ProxyBuildEnvironment(String projectName) { 40 | this.projectName = projectName; 41 | } 42 | 43 | public String getProjectName() { 44 | return projectName; 45 | } 46 | 47 | public String getExpandedProjectName(AbstractBuild build) { 48 | return TemplateUtils.getExpandedProjectName(projectName, build); 49 | } 50 | 51 | public AbstractProject getProject() { 52 | return TemplateUtils.getProject(projectName, null); 53 | } 54 | 55 | public List getProjectBuildWrappers(AbstractBuild build) { 56 | AbstractProject p = TemplateUtils.getProject(getProjectName(), build); 57 | 58 | if (p instanceof Project) { 59 | return ((Project) p).getBuildWrappersList(); 60 | } else if (p instanceof MatrixProject) { 61 | return ((MatrixProject) p).getBuildWrappersList(); 62 | } else { 63 | return Collections.emptyList(); 64 | } 65 | } 66 | 67 | @Override 68 | public final void buildDependencyGraph(final AbstractProject project, final DependencyGraph graph) { 69 | final Item item = Hudson.getInstance().getItemByFullName(getProjectName()); 70 | AbstractProject templateProject = (AbstractProject) Hudson.getInstance().getItem(getProjectName()); 71 | if (item instanceof Project) { 72 | // @TODO : see how important it is that this gets expanded projectName 73 | for (BuildWrapper wrapper : getProjectBuildWrappers(null)) { 74 | if (wrapper instanceof DependencyDeclarer) { 75 | ((DependencyDeclarer) wrapper).buildDependencyGraph(project, graph); 76 | } 77 | } 78 | } 79 | } 80 | 81 | @Override 82 | public Environment setUp(@SuppressWarnings("rawtypes") AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, 83 | InterruptedException { 84 | 85 | AbstractProject p = TemplateUtils.getProject(getProjectName(), build); 86 | listener.getLogger().println("[TemplateProject] Getting environment from: " + HyperlinkNote.encodeTo('/'+ p.getUrl(), p.getFullDisplayName())); 87 | for (BuildWrapper builder : getProjectBuildWrappers(build)) { 88 | builder.setUp(build, launcher, listener); 89 | } 90 | listener.getLogger().println("[TemplateProject] Successfully setup environment from: '" + p.getFullDisplayName() + "'"); 91 | 92 | return new Environment() { 93 | @Override 94 | public boolean tearDown(@SuppressWarnings("rawtypes") AbstractBuild build, BuildListener listener) throws IOException, InterruptedException { 95 | // let build continue 96 | return true; 97 | } 98 | }; 99 | } 100 | 101 | @Override 102 | public void preCheckout(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException { 103 | AbstractProject p = TemplateUtils.getProject(getProjectName(), build); 104 | listener.getLogger().println("[TemplateProject] Starting pre-checkout from: " + HyperlinkNote.encodeTo('/'+ p.getUrl(), p.getFullDisplayName())); 105 | for (BuildWrapper builder : getProjectBuildWrappers(build)) { 106 | builder.preCheckout(build, launcher, listener); 107 | } 108 | listener.getLogger().println("[TemplateProject] Successfully performed pre-checkout from: '" + p.getFullDisplayName() + "'"); 109 | } 110 | 111 | @Override 112 | public Launcher decorateLauncher(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException, Run.RunnerAbortedException { 113 | for (BuildWrapper builder : getProjectBuildWrappers(build)) { 114 | launcher = builder.decorateLauncher(build, launcher, listener); 115 | } 116 | return launcher; 117 | } 118 | 119 | @Override 120 | public OutputStream decorateLogger(AbstractBuild build, OutputStream logger) throws IOException, InterruptedException, Run.RunnerAbortedException { 121 | for (BuildWrapper builder : getProjectBuildWrappers(build)) { 122 | logger = builder.decorateLogger(build, logger); 123 | } 124 | return logger; 125 | } 126 | 127 | @Extension 128 | public static class DescriptorImpl extends BuildWrapperDescriptor { 129 | 130 | @Override 131 | public String getDisplayName() { 132 | return "Use build environment from another project"; 133 | } 134 | 135 | @Override 136 | public boolean isApplicable(AbstractProject jobType) { 137 | return true; 138 | } 139 | 140 | /** 141 | * Form validation method. 142 | */ 143 | public FormValidation doCheckProjectName(@AncestorInPath AccessControlled anc, @QueryParameter String value) { 144 | // Require CONFIGURE permission on this project 145 | if (!anc.hasPermission(Item.CONFIGURE)) { 146 | return FormValidation.ok(); 147 | } 148 | Item item = Hudson.getInstance().getItemByFullName( 149 | value, Item.class); 150 | if (item == null) { 151 | return FormValidation.error(Messages.BuildTrigger_NoSuchProject(value, 152 | AbstractProject.findNearest(value) 153 | .getName())); 154 | } 155 | if (!(item instanceof Project) && !(item instanceof MatrixProject)) { 156 | return FormValidation.error(Messages.BuildTrigger_NotBuildable(value)); 157 | } 158 | return FormValidation.ok(); 159 | } 160 | } 161 | 162 | @Override 163 | public Collection getProjectActions(AbstractProject project) { 164 | List actions = new ArrayList(); 165 | // @TODO : see how important it is that this gets expanded projectName 166 | for (BuildWrapper wrapper : getProjectBuildWrappers(null)) { 167 | actions.addAll(wrapper.getProjectActions(project)); 168 | } 169 | return actions; 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/templateproject/ProxyBuilder.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.templateproject; 2 | 3 | import hudson.Extension; 4 | import hudson.Launcher; 5 | import hudson.console.HyperlinkNote; 6 | import hudson.matrix.MatrixProject; 7 | import hudson.model.AbstractBuild; 8 | import hudson.model.AbstractProject; 9 | import hudson.model.Action; 10 | import hudson.model.BuildListener; 11 | import hudson.model.DependecyDeclarer; 12 | import hudson.model.DependencyGraph; 13 | import hudson.model.Hudson; 14 | import hudson.model.Item; 15 | import hudson.model.Project; 16 | import hudson.security.AccessControlled; 17 | import hudson.tasks.BuildStepDescriptor; 18 | import hudson.tasks.Builder; 19 | import hudson.tasks.Messages; 20 | import hudson.tasks.Publisher; 21 | import hudson.util.FormValidation; 22 | 23 | import java.io.IOException; 24 | import java.util.ArrayList; 25 | import java.util.Collection; 26 | import java.util.Collections; 27 | import java.util.List; 28 | 29 | import jenkins.model.Jenkins; 30 | 31 | import org.kohsuke.stapler.AncestorInPath; 32 | import org.kohsuke.stapler.DataBoundConstructor; 33 | import org.kohsuke.stapler.QueryParameter; 34 | 35 | public class ProxyBuilder extends Builder implements DependecyDeclarer { 36 | 37 | private final String projectName; 38 | 39 | @DataBoundConstructor 40 | public ProxyBuilder(String projectName) { 41 | this.projectName = projectName; 42 | } 43 | 44 | public String getProjectName() { 45 | return projectName; 46 | } 47 | 48 | public String getExpandedProjectName(AbstractBuild build) { 49 | return TemplateUtils.getExpandedProjectName(projectName, build); 50 | } 51 | 52 | public AbstractProject getProject() { 53 | return TemplateUtils.getProject(projectName, null); 54 | } 55 | 56 | public List getProjectBuilders(AbstractBuild build) { 57 | AbstractProject p = TemplateUtils.getProject(getProjectName(), build); 58 | 59 | if (p instanceof Project) return ((Project)p).getBuilders(); 60 | else if (p instanceof MatrixProject) return ((MatrixProject)p).getBuilders(); 61 | else return Collections.emptyList(); 62 | } 63 | 64 | @Override 65 | public void buildDependencyGraph(AbstractProject project, DependencyGraph graph) { 66 | AbstractProject templateProject = (AbstractProject) Hudson.getInstance().getItem(getProjectName()); 67 | if (templateProject != null) { 68 | for (Publisher publisher : templateProject.getPublishersList().toList()) { 69 | if (publisher instanceof DependecyDeclarer) { 70 | ((DependecyDeclarer)publisher).buildDependencyGraph(project, graph); 71 | } 72 | } 73 | } 74 | } 75 | 76 | 77 | @Extension 78 | public static class DescriptorImpl extends BuildStepDescriptor { 79 | 80 | @Override 81 | public String getDisplayName() { 82 | return "Use builders from another project"; 83 | } 84 | 85 | @Override 86 | public boolean isApplicable(Class jobType) { 87 | return true; 88 | } 89 | 90 | /** 91 | * Form validation method. 92 | */ 93 | public FormValidation doCheckProjectName(@AncestorInPath AccessControlled anc, @QueryParameter String value) { 94 | // Require CONFIGURE permission on this project 95 | if (!anc.hasPermission(Item.CONFIGURE)) return FormValidation.ok(); 96 | Item item = Hudson.getInstance().getItemByFullName( 97 | value, Item.class); 98 | if (item == null) { 99 | return FormValidation.error(Messages.BuildTrigger_NoSuchProject(value, 100 | AbstractProject.findNearest(value) 101 | .getName())); 102 | } 103 | if (!(item instanceof Project) && !(item instanceof MatrixProject)) { 104 | return FormValidation.error(Messages.BuildTrigger_NotBuildable(value)); 105 | } 106 | return FormValidation.ok(); 107 | } 108 | } 109 | 110 | @Override 111 | public boolean perform(AbstractBuild build, Launcher launcher, 112 | BuildListener listener) throws InterruptedException, IOException { 113 | for (Builder builder: getProjectBuilders(build)) { 114 | AbstractProject p = TemplateUtils.getProject(getProjectName(), build); 115 | listener.getLogger().println("[TemplateProject] Starting builders from: " + HyperlinkNote.encodeTo('/'+ p.getUrl(), p.getFullDisplayName())); 116 | if (!builder.perform(build, launcher, listener)) { 117 | listener.getLogger().println("[TemplateProject] FAILED performing builders from: '" + p.getFullDisplayName() + "'"); 118 | return false; 119 | } 120 | listener.getLogger().println("[TemplateProject] Successfully performed builders from: '" + p.getFullDisplayName() + "'"); 121 | } 122 | return true; 123 | } 124 | 125 | @Override 126 | public boolean prebuild(AbstractBuild build, BuildListener listener) { 127 | for (Builder builder: getProjectBuilders(build)) { 128 | if (!builder.prebuild(build, listener)) { 129 | return false; 130 | } 131 | } 132 | return true; 133 | } 134 | 135 | @Override 136 | public Collection getProjectActions(AbstractProject project) { 137 | List actions = new ArrayList(); 138 | // @TODO : see how important it is that this gets expanded projectName 139 | for (Builder builder : getProjectBuilders(null)) { 140 | actions.addAll(builder.getProjectActions(project)); 141 | } 142 | return actions; 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/templateproject/ProxyPublisher.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.templateproject; 2 | 3 | import hudson.Extension; 4 | import hudson.Launcher; 5 | import hudson.console.HyperlinkNote; 6 | import hudson.model.AbstractBuild; 7 | import hudson.model.AbstractProject; 8 | import hudson.model.Action; 9 | import hudson.model.BuildListener; 10 | import hudson.model.DependecyDeclarer; 11 | import hudson.model.DependencyGraph; 12 | import hudson.model.Hudson; 13 | import hudson.model.Item; 14 | import hudson.security.AccessControlled; 15 | import hudson.tasks.BuildStepDescriptor; 16 | import hudson.tasks.BuildStepMonitor; 17 | import hudson.tasks.Messages; 18 | import hudson.tasks.Publisher; 19 | import hudson.tasks.Recorder; 20 | import hudson.util.FormValidation; 21 | 22 | import java.io.IOException; 23 | import java.util.ArrayList; 24 | import java.util.Collection; 25 | import java.util.List; 26 | 27 | import jenkins.model.Jenkins; 28 | 29 | import org.kohsuke.stapler.AncestorInPath; 30 | import org.kohsuke.stapler.DataBoundConstructor; 31 | import org.kohsuke.stapler.QueryParameter; 32 | 33 | public class ProxyPublisher extends Recorder implements DependecyDeclarer { 34 | 35 | private final String projectName; 36 | 37 | @DataBoundConstructor 38 | public ProxyPublisher(String projectName) { 39 | this.projectName = projectName; 40 | } 41 | 42 | public String getProjectName() { 43 | return projectName; 44 | } 45 | 46 | public String getExpandedProjectName(AbstractBuild build) { 47 | return TemplateUtils.getExpandedProjectName(projectName, build); 48 | } 49 | 50 | public AbstractProject getProject() { 51 | return TemplateUtils.getProject(projectName, null); 52 | } 53 | 54 | public BuildStepMonitor getRequiredMonitorService() { 55 | return BuildStepMonitor.NONE; 56 | } 57 | 58 | @Override 59 | public boolean needsToRunAfterFinalized() { 60 | return false; 61 | } 62 | 63 | public List getProjectPublishersList(AbstractBuild build) { 64 | // @TODO: exception handling 65 | return TemplateUtils.getProject(projectName, build).getPublishersList().toList(); 66 | } 67 | 68 | @Override 69 | public boolean prebuild(AbstractBuild build, BuildListener listener) { 70 | for (Publisher publisher : getProjectPublishersList(build)) { 71 | if (!publisher.prebuild(build, listener)) { 72 | return false; 73 | } 74 | } 75 | return true; 76 | } 77 | 78 | @Override 79 | public boolean perform(AbstractBuild build, Launcher launcher, 80 | BuildListener listener) throws InterruptedException, IOException { 81 | boolean publishersResult = true; 82 | for (Publisher publisher : getProjectPublishersList(build)) { 83 | AbstractProject p = TemplateUtils.getProject(getProjectName(), build); 84 | listener.getLogger().println("[TemplateProject] Starting publishers from: " + HyperlinkNote.encodeTo('/'+ p.getUrl(), p.getFullDisplayName())); 85 | if (!publisher.perform(build, launcher, listener)) { 86 | listener.getLogger().println("[TemplateProject] FAILED performing publishers from: '" + p.getFullDisplayName() + "'"); 87 | publishersResult = false; 88 | } else { 89 | listener.getLogger().println("[TemplateProject] Successfully performed publishers from: '" + p.getFullDisplayName() + "'"); 90 | } 91 | } 92 | return publishersResult; 93 | } 94 | 95 | @Override 96 | public Collection getProjectActions(AbstractProject project) { 97 | List actions = new ArrayList(); 98 | // project might not defined when loading the first time 99 | AbstractProject templateProject = getProject(); 100 | if (templateProject != null) { 101 | for (Publisher publisher : templateProject.getPublishersList().toList()) { 102 | actions.addAll(publisher.getProjectActions(project)); 103 | } 104 | } 105 | return actions; 106 | } 107 | 108 | /** 109 | * Any of the publisher could support the DependecyDeclarer interface, 110 | * so proxy will handle it as well. 111 | * {@inheritDoc} 112 | */ 113 | public void buildDependencyGraph(AbstractProject project, DependencyGraph graph) { 114 | AbstractProject templateProject = getProject(); 115 | if (templateProject != null) { 116 | for (Publisher publisher : templateProject.getPublishersList().toList()) { 117 | if (publisher instanceof DependecyDeclarer) { 118 | ((DependecyDeclarer)publisher).buildDependencyGraph(project, graph); 119 | } 120 | } 121 | } 122 | } 123 | 124 | @Extension 125 | public static class DescriptorImpl extends BuildStepDescriptor { 126 | 127 | @Override 128 | public String getDisplayName() { 129 | return "Use publishers from another project"; 130 | } 131 | 132 | @Override 133 | public boolean isApplicable(Class jobType) { 134 | return true; 135 | } 136 | 137 | /** 138 | * Form validation method. 139 | */ 140 | public FormValidation doCheckProjectName(@AncestorInPath AccessControlled anc, @QueryParameter String value) { 141 | // Require CONFIGURE permission on this project 142 | if (!anc.hasPermission(Item.CONFIGURE)) return FormValidation.ok(); 143 | Item item = Hudson.getInstance().getItemByFullName( 144 | value, Item.class); 145 | if (item == null) { 146 | return FormValidation.error(Messages.BuildTrigger_NoSuchProject(value, 147 | AbstractProject.findNearest(value) 148 | .getName())); 149 | } 150 | if (!(item instanceof AbstractProject)) { 151 | return FormValidation.error(Messages.BuildTrigger_NotBuildable(value)); 152 | } 153 | return FormValidation.ok(); 154 | } 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/templateproject/ProxySCM.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.templateproject; 2 | 3 | import hudson.Extension; 4 | import hudson.FilePath; 5 | import hudson.Launcher; 6 | import hudson.console.HyperlinkNote; 7 | import hudson.Util; 8 | import hudson.model.BuildListener; 9 | import hudson.model.Item; 10 | import hudson.model.TaskListener; 11 | import hudson.model.AbstractBuild; 12 | import hudson.model.AbstractProject; 13 | import hudson.model.Hudson; 14 | import hudson.model.Node; 15 | import hudson.model.Run; 16 | import hudson.scm.ChangeLogParser; 17 | import hudson.scm.NullSCM; 18 | import hudson.scm.PollingResult; 19 | import hudson.scm.RepositoryBrowser; 20 | import hudson.scm.SCMDescriptor; 21 | import hudson.scm.SCMRevisionState; 22 | import hudson.scm.SCM; 23 | import hudson.security.AccessControlled; 24 | import hudson.tasks.Messages; 25 | import hudson.util.FormValidation; 26 | 27 | import java.io.File; 28 | import java.io.IOException; 29 | import javax.annotation.CheckForNull; 30 | import javax.annotation.Nonnull; 31 | 32 | import org.apache.commons.lang.StringUtils; 33 | import org.kohsuke.stapler.AncestorInPath; 34 | import org.kohsuke.stapler.DataBoundConstructor; 35 | import org.kohsuke.stapler.QueryParameter; 36 | 37 | import java.util.List; 38 | import org.jenkinsci.plugins.multiplescms.MultiSCM; 39 | import org.jenkinsci.plugins.multiplescms.MultiSCMRevisionState; 40 | 41 | 42 | public class ProxySCM extends SCM { 43 | 44 | private final String projectName; 45 | 46 | @DataBoundConstructor 47 | public ProxySCM(String projectName) { 48 | this.projectName = projectName; 49 | } 50 | 51 | public String getProjectName() { 52 | return projectName; 53 | } 54 | 55 | public String getExpandedProjectName(AbstractBuild build) { 56 | return TemplateUtils.getExpandedProjectName(projectName, build); 57 | } 58 | 59 | // Primarily used for polling, not building. 60 | public AbstractProject getProject() { 61 | return TemplateUtils.getProject(projectName, null); 62 | } 63 | 64 | public SCM getProjectScm(AbstractBuild build) { 65 | try { 66 | return TemplateUtils.getProject(projectName, build).getScm(); 67 | } catch (Exception e) { 68 | return new NullSCM(); 69 | } 70 | } 71 | 72 | public SCM getProjectScm() { 73 | return getProjectScm(null); 74 | } 75 | 76 | public void checkout(@Nonnull Run build, @Nonnull Launcher launcher, @Nonnull FilePath workspace, 77 | @Nonnull TaskListener listener, @CheckForNull File changelogFile, @CheckForNull SCMRevisionState baseline) 78 | throws IOException, InterruptedException { 79 | 80 | // Unique situation where MultiSCM has $None for SCMRevisionState 81 | // Potentially due to SCM polling and references lost, or fixed with: 82 | // https://github.com/jenkinsci/multiple-scms-plugin/pull/6 83 | // https://issues.jenkins-ci.org/browse/JENKINS-27638 84 | // Since MultiSCM is optional, might not be installed, hacky check string name. 85 | if (getProjectScm((AbstractBuild) build).toString().contains("multiplescms")) { 86 | if ((baseline == SCMRevisionState.NONE) || (baseline == null)) { 87 | baseline = new MultiSCMRevisionState(); 88 | } 89 | } 90 | 91 | AbstractProject p = TemplateUtils.getProject(getProjectName(), (AbstractBuild) build); 92 | listener.getLogger().println("[TemplateProject] Using SCM from: " + HyperlinkNote.encodeTo('/'+ p.getUrl(), p.getFullDisplayName())); 93 | getProjectScm((AbstractBuild) build).checkout(build, launcher, workspace, listener, changelogFile, baseline); 94 | } 95 | 96 | @Override 97 | public ChangeLogParser createChangeLogParser() { 98 | return getProjectScm().createChangeLogParser(); 99 | } 100 | 101 | @Override 102 | @Deprecated 103 | public boolean pollChanges(AbstractProject project, Launcher launcher, 104 | FilePath workspace, TaskListener listener) throws IOException, 105 | InterruptedException { 106 | return getProjectScm().pollChanges(project, launcher, workspace, listener); 107 | } 108 | 109 | @Extension 110 | public static class DescriptorImpl extends SCMDescriptor { 111 | 112 | public DescriptorImpl() { 113 | super(null); 114 | } 115 | 116 | @Override 117 | public String getDisplayName() { 118 | return "Use SCM from another project"; 119 | } 120 | 121 | /** 122 | * Form validation method. 123 | */ 124 | public FormValidation doCheckProjectName(@AncestorInPath AccessControlled anc, @QueryParameter String value) { 125 | // Require CONFIGURE permission on this project 126 | if (!anc.hasPermission(Item.CONFIGURE)) return FormValidation.ok(); 127 | //this check is important because otherwise plugin will check for similar project which impacts performance 128 | //the check will be performed even if this plugin is not used as SCM for the current project 129 | if(StringUtils.isEmpty(value)) { 130 | return FormValidation.error("Project cannot be empty"); 131 | } 132 | Item item = Hudson.getInstance().getItemByFullName(value, Item.class); 133 | if (item == null) { 134 | return FormValidation.error(Messages.BuildTrigger_NoSuchProject(value, 135 | AbstractProject.findNearest(value).getName())); 136 | } 137 | if (!(item instanceof AbstractProject)) { 138 | return FormValidation.error(Messages.BuildTrigger_NotBuildable(value)); 139 | } 140 | return FormValidation.ok(); 141 | } 142 | } 143 | 144 | // If a Parameter is used for projectName, some of these won't return anythign useful. 145 | // Because of it's nature `expand()`-ing the parameter is only useful at run time. 146 | 147 | @Override 148 | public RepositoryBrowser getBrowser() { 149 | return getProjectScm().getBrowser(); 150 | } 151 | 152 | @Override 153 | public FilePath getModuleRoot(FilePath workspace) { 154 | return getProjectScm().getModuleRoot(workspace); 155 | } 156 | 157 | @Override 158 | public FilePath[] getModuleRoots(FilePath workspace) { 159 | return getProjectScm().getModuleRoots(workspace); 160 | } 161 | 162 | @Override 163 | public boolean processWorkspaceBeforeDeletion( 164 | AbstractProject project, FilePath workspace, Node node) 165 | throws IOException, InterruptedException { 166 | return getProjectScm().processWorkspaceBeforeDeletion(project, workspace, node); 167 | } 168 | 169 | @Override 170 | public boolean requiresWorkspaceForPolling() { 171 | return getProjectScm().requiresWorkspaceForPolling(); 172 | } 173 | 174 | @Override 175 | public boolean supportsPolling() { 176 | // @TODO: worth adding check if expandedProjectName even exists? 177 | // If still $PROJECT, won't expand so nothing to poll. 178 | return getProjectScm().supportsPolling(); 179 | } 180 | 181 | @Override 182 | public void buildEnvVars(AbstractBuild build, java.util.Map env) { 183 | getProjectScm(build).buildEnvVars(build, env); 184 | } 185 | 186 | @Override 187 | public SCMRevisionState calcRevisionsFromBuild(AbstractBuild paramAbstractBuild, Launcher paramLauncher, 188 | TaskListener paramTaskListener) throws IOException, InterruptedException { 189 | return getProjectScm(paramAbstractBuild).calcRevisionsFromBuild(paramAbstractBuild, paramLauncher, paramTaskListener); 190 | } 191 | 192 | 193 | @Override 194 | protected PollingResult compareRemoteRevisionWith(AbstractProject project, Launcher launcher, 195 | FilePath workspace, TaskListener listener, SCMRevisionState baseline) 196 | throws IOException, InterruptedException { 197 | return getProjectScm().poll(project, launcher, workspace, listener, baseline); 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/templateproject/TemplateUtils.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.templateproject; 2 | 3 | import hudson.EnvVars; 4 | import hudson.Util; 5 | import hudson.model.AbstractBuild; 6 | import hudson.model.AbstractProject; 7 | import hudson.model.BuildListener; 8 | import hudson.model.TaskListener; 9 | import hudson.model.Hudson; 10 | import hudson.util.LogTaskListener; 11 | import static java.util.logging.Level.INFO; 12 | import java.util.logging.Logger; 13 | 14 | public class TemplateUtils { 15 | private static final Logger logger = Logger.getLogger("TemplateProject"); 16 | 17 | public static AbstractProject getProject(String projectName, AbstractBuild build) { 18 | String pName = projectName; 19 | 20 | if (build != null) { 21 | pName = TemplateUtils.getExpandedProjectName(projectName, build); 22 | 23 | if (Hudson.getInstance().getItemByFullName(pName) == null) { 24 | logger.info("[TemplateProject] Template Project '" + pName + "' not found. Skipping."); 25 | } 26 | } 27 | 28 | return (AbstractProject) Hudson.getInstance().getItemByFullName(pName); 29 | } 30 | 31 | public static String getExpandedProjectName(String projectName, AbstractBuild build) { 32 | // Limitation : Currently only supports build variable for replacement. 33 | // Gets into infinite loop using `getEnvironment() since it loops 34 | // back to `getScm().buildEnvVars()` 35 | return Util.replaceMacro(projectName, build.getBuildVariables()); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/templateproject/UpdateTransientProperty.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.templateproject; 2 | 3 | import org.kohsuke.stapler.DataBoundConstructor; 4 | 5 | import hudson.Extension; 6 | import hudson.model.AbstractProject; 7 | import hudson.model.JobProperty; 8 | import hudson.model.JobPropertyDescriptor; 9 | 10 | /** 11 | * A property that is only used to trigger the transient actions creations 12 | * under a project. 13 | * 14 | * @author william.bernardet@gmail.com 15 | * 16 | */ 17 | public class UpdateTransientProperty extends JobProperty> { 18 | 19 | @DataBoundConstructor 20 | public UpdateTransientProperty() { 21 | } 22 | 23 | @Extension 24 | public static class DescriptorImpl extends JobPropertyDescriptor { 25 | 26 | @Override 27 | public String getDisplayName() { 28 | return "Property used to for job to update transient actions."; 29 | } 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/templateproject/ProxyBuildEnvironment/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/templateproject/ProxyBuilder/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/templateproject/ProxyPublisher/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/templateproject/ProxySCM/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 |
2 | This plugin lets you use builders, publishers and SCM settings from another project. 3 |
4 | --------------------------------------------------------------------------------