diff --git a/core/src/main/java/hudson/model/ItemGroupMixIn.java b/core/src/main/java/hudson/model/ItemGroupMixIn.java index 7225ab6089c0efe2ac71bfa3cad0f5fea85b941e..43209cbaf689ea83e9f3ee6c7a1dbb1c6f5fd6f4 100644 --- a/core/src/main/java/hudson/model/ItemGroupMixIn.java +++ b/core/src/main/java/hudson/model/ItemGroupMixIn.java @@ -216,6 +216,7 @@ public abstract class ItemGroupMixIn { public synchronized TopLevelItem createProjectFromXML(String name, InputStream xml) throws IOException { acl.checkPermission(Job.CREATE); + Jenkins.getInstance().getProjectNamingStrategy().checkName(name); // place it as config.xml File configXml = Items.getConfigFile(getRootDirFor(name)).getFile(); configXml.getParentFile().mkdirs(); @@ -241,6 +242,7 @@ public abstract class ItemGroupMixIn { throws IOException { acl.checkPermission(Job.CREATE); + Jenkins.getInstance().getProjectNamingStrategy().checkName(name); if(parent.getItem(name)!=null) throw new IllegalArgumentException("Project of the name "+name+" already exists"); diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index ca770000967b57d5798c2e56bc232b968902f39b..253ce8ca17a6019a828e09c3e622bb0b04012170 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -50,8 +50,6 @@ import hudson.util.DataSetBuilder; import hudson.util.DescribableList; import hudson.util.FormApply; import hudson.util.Graph; -import hudson.util.HttpResponses; -import hudson.util.IOException2; import hudson.util.RunList; import hudson.util.ShiftedCategoryAxis; import hudson.util.StackedAreaRenderer2; @@ -60,6 +58,7 @@ import hudson.widgets.HistoryWidget; import hudson.widgets.HistoryWidget.Adapter; import hudson.widgets.Widget; import jenkins.model.Jenkins; +import jenkins.model.ProjectNamingStrategy; import net.sf.json.JSONException; import net.sf.json.JSONObject; import org.jfree.chart.ChartFactory; @@ -992,11 +991,16 @@ public abstract class Job, RunT extends Runnull + */ + public ProjectNamingStrategy getProjectNamingStrategy() { + return projectNamingStrategy == null ? ProjectNamingStrategy.DEFAULT_NAMING_STRATEGY : projectNamingStrategy; + } /** * Returns true if Hudson is quieting down. @@ -2773,6 +2798,7 @@ public class Jenkins extends AbstractCIBase implements ModifiableItemGroup, ExtensionPoint { + + public ProjectNamingStrategyDescriptor getDescriptor() { + return (ProjectNamingStrategyDescriptor) Jenkins.getInstance().getDescriptor(getClass()); + } + + public static DescriptorExtensionList all() { + return Jenkins.getInstance(). getDescriptorList(ProjectNamingStrategy.class); + } + + /** + * Called when creating a new job. + * + * @param name + * the name given from the UI + * @throws Failure + * if the user has to be informed about an illegal name, forces the user to change the name before submitting. The message of the failure will be presented to the user. + */ + public void checkName(String name) throws Failure { + // no op + } + + /** + * This flag can be used to force existing jobs to be migrated to a new naming strategy - if this method returns true, the naming will be enforced at every config change. If false is + * returned, only new jobs have to follow the strategy. + * + * @return true if existing jobs should be enforced to confirm to the naming standard. + */ + public boolean isForceExistingJobs() { + return false; + } + + /** + * The default naming strategy which does not restrict the name of a job. + */ + public static final ProjectNamingStrategy DEFAULT_NAMING_STRATEGY = new DefaultProjectNamingStrategy(); + + /** + * Default implementation which does not restrict the name to any form. + */ + public static final class DefaultProjectNamingStrategy extends ProjectNamingStrategy implements Serializable { + + private static final long serialVersionUID = 1L; + + @DataBoundConstructor + public DefaultProjectNamingStrategy() { + } + + @Override + public void checkName(String origName) throws Failure { + // default - should just do nothing (this is how Jenkins worked before introducing this ExtensionPoint) + } + + /** + * DefaultProjectNamingStrategy is stateless, therefore save to keep the same instance + */ + private Object readResolve() { + return DEFAULT_NAMING_STRATEGY; + } + + @Extension + public static final class DescriptorImpl extends ProjectNamingStrategyDescriptor { + @Override + public String getDisplayName() { + return "Default"; + } + + @Override + public String getHelpFile() { + return "help/system-config/defaultJobNamingStrategy.html"; + } + } + } + + /** + * Naming strategy which allows the admin to define a pattern a job's name has to follow. + */ + public static final class PatternProjectNamingStrategy extends ProjectNamingStrategy implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * regex pattern a job's name has to follow + */ + private final String namePattern; + + private boolean forceExistingJobs; + + @DataBoundConstructor + public PatternProjectNamingStrategy(String namePattern, boolean forceExistingJobs) { + this.namePattern = namePattern; + this.forceExistingJobs = forceExistingJobs; + } + + @Override + public void checkName(String name) throws Failure { + if (StringUtils.isNotBlank(namePattern) && StringUtils.isNotBlank(name)) { + if (!Pattern.matches(namePattern, name)) { + throw new Failure(jenkins.model.Messages._Hudson_JobNameConventionNotApplyed(name, namePattern).toString()); + } + } + } + + public String getNamePattern() { + return namePattern; + } + + public boolean isForceExistingJobs() { + return forceExistingJobs; + } + + @Extension + public static final class DescriptorImpl extends ProjectNamingStrategyDescriptor { + + public static final String DEFAULT_PATTERN = ".*"; + + @Override + public String getDisplayName() { + return "Pattern"; + } + + @Override + public String getHelpFile() { + return "help/system-config/patternJobNamingStrategy.html"; + } + } + } + + public static abstract class ProjectNamingStrategyDescriptor extends Descriptor { + } + +} diff --git a/core/src/main/resources/jenkins/model/GlobalProjectNamingStrategyConfiguration/config.groovy b/core/src/main/resources/jenkins/model/GlobalProjectNamingStrategyConfiguration/config.groovy new file mode 100644 index 0000000000000000000000000000000000000000..2be2077f72bfeb287d62ff78cea8132fb7675cc6 --- /dev/null +++ b/core/src/main/resources/jenkins/model/GlobalProjectNamingStrategyConfiguration/config.groovy @@ -0,0 +1,15 @@ +package jenkins.model.GlobalProjectNamingStrategyConfiguration + +import jenkins.model.ProjectNamingStrategy + +def f=namespace(lib.FormTagLib) + +f.optionalBlock( field:"useProjectNamingStrategy", title:_("useNamingStrategy"), checked:app.useProjectNamingStrategy) { + + f.entry(title:_("namingStrategyTitel")) { + table(style:"width:100%") { + f.descriptorRadioList(title:_("strategy"), varName:"namingStrategy", instance:app.projectNamingStrategy, descriptors:ProjectNamingStrategy.all()) + } + } + +} diff --git a/core/src/main/resources/jenkins/model/GlobalProjectNamingStrategyConfiguration/config.properties b/core/src/main/resources/jenkins/model/GlobalProjectNamingStrategyConfiguration/config.properties new file mode 100644 index 0000000000000000000000000000000000000000..4a4ac6fca4d4a770938598f8204e0907d68afea5 --- /dev/null +++ b/core/src/main/resources/jenkins/model/GlobalProjectNamingStrategyConfiguration/config.properties @@ -0,0 +1,27 @@ +# The MIT License +# +# Copyright (c) 2012, Dominik Bartholdi +# +# 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. + +useNamingStrategy = Restrict project naming +namingStrategyTitel = Naming Strategy +strategy = Strategy + + diff --git a/core/src/main/resources/jenkins/model/Messages.properties b/core/src/main/resources/jenkins/model/Messages.properties index 3c41b49e94e7742d3ca8decf3e3f5d3e1f729cff..5b5c49c42d670d890e196374c65f6c0682f91558 100644 --- a/core/src/main/resources/jenkins/model/Messages.properties +++ b/core/src/main/resources/jenkins/model/Messages.properties @@ -33,6 +33,7 @@ Hudson.NoJavaInPath=java is not in your PATH. Maybe you need to + This is the default configuration and allows the user the choose any name he likes. + diff --git a/war/src/main/webapp/help/system-config/patternJobNamingStrategy.html b/war/src/main/webapp/help/system-config/patternJobNamingStrategy.html new file mode 100644 index 0000000000000000000000000000000000000000..fc9c24d971fcbdf08a2f127fb4e7eb989493a2d4 --- /dev/null +++ b/war/src/main/webapp/help/system-config/patternJobNamingStrategy.html @@ -0,0 +1,5 @@ +
+ Define a pattern (regular expression) to check whether the job name is valid or not.
+ Forcing the check on existing jobs, will allow you to enforce a naming convention on existing jobs - e.g. even if the user does not change the name, + it will be validated with the given pattern at every submit and no updates can be made until the name confirms. +