提交 a2d9bac0 编写于 作者: I imod

define new extension point to define ProjectNamingStrategy and add two...

define new extension point to define ProjectNamingStrategy and add two implementations: Default and Pattern
上级 88f56f5d
......@@ -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");
......
......@@ -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<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
save();
String newName = req.getParameter("name");
final ProjectNamingStrategy namingStrategy = Jenkins.getInstance().getProjectNamingStrategy();
if (newName != null && !newName.equals(name)) {
// check this error early to avoid HTTP response splitting.
Jenkins.checkGoodName(newName);
namingStrategy.checkName(newName);
rsp.sendRedirect("rename?newName=" + URLEncoder.encode(newName, "UTF-8"));
} else {
if(namingStrategy.isForceExistingJobs()){
namingStrategy.checkName(name);
}
FormApply.success(".").generateResponse(req, rsp, null);
}
} catch (JSONException e) {
......
/*
* 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.
*/
package jenkins.model;
import hudson.Extension;
import jenkins.model.ProjectNamingStrategy.DefaultProjectNamingStrategy;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
/**
* Configures the project naming strategy.
*
* @author Dominik Bartholdi (imod)
*/
@Extension(ordinal = 250)
public class GlobalProjectNamingStrategyConfiguration extends GlobalConfiguration {
@Override
public boolean configure(StaplerRequest req, JSONObject json) throws hudson.model.Descriptor.FormException {
// for compatibility reasons, the actual value is stored in Jenkins
Jenkins j = Jenkins.getInstance();
final JSONObject optJSONObject = json.optJSONObject("useProjectNamingStrategy");
if (optJSONObject != null) {
final JSONObject strategyObject = optJSONObject.getJSONObject("namingStrategy");
final String className = strategyObject.getString("stapler-class");
try {
Class clazz = Class.forName(className);
final ProjectNamingStrategy strategy = (ProjectNamingStrategy) req.bindJSON(clazz, strategyObject);
j.setProjectNamingStrategy(strategy);
} catch (ClassNotFoundException e) {
throw new FormException(e, "namingStrategy");
}
}
if (j.getProjectNamingStrategy() == null) {
j.setProjectNamingStrategy(DefaultProjectNamingStrategy.DEFAULT_NAMING_STRATEGY);
}
return true;
}
}
......@@ -192,6 +192,7 @@ import hudson.widgets.Widget;
import jenkins.ExtensionComponentSet;
import jenkins.ExtensionRefreshException;
import jenkins.InitReactorRunner;
import jenkins.model.ProjectNamingStrategy.DefaultProjectNamingStrategy;
import net.sf.json.JSONObject;
import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.AcegiSecurityException;
......@@ -365,6 +366,11 @@ public class Jenkins extends AbstractCIBase implements ModifiableItemGroup<TopLe
* @see #setSecurityRealm(SecurityRealm)
*/
private volatile SecurityRealm securityRealm = SecurityRealm.NO_AUTHENTICATION;
/**
* The project naming strategy defines/restricts the names which can be given to a project/job. e.g. does the name have to follow a naming convention?
*/
private ProjectNamingStrategy projectNamingStrategy = DefaultProjectNamingStrategy.DEFAULT_NAMING_STRATEGY;
/**
* Root directory for the workspaces. This value will be variable-expanded against
......@@ -1863,6 +1869,10 @@ public class Jenkins extends AbstractCIBase implements ModifiableItemGroup<TopLe
public boolean isUseSecurity() {
return securityRealm!=SecurityRealm.NO_AUTHENTICATION || authorizationStrategy!=AuthorizationStrategy.UNSECURED;
}
public boolean isUseProjectNamingStrategy(){
return projectNamingStrategy != DefaultProjectNamingStrategy.DEFAULT_NAMING_STRATEGY;
}
/**
* If true, all the POST requests to Hudson would have to have crumb in it to protect
......@@ -1933,6 +1943,13 @@ public class Jenkins extends AbstractCIBase implements ModifiableItemGroup<TopLe
markupFormatter = null;
}
public void setProjectNamingStrategy(ProjectNamingStrategy ns) {
if(ns == null){
ns = DefaultProjectNamingStrategy.DEFAULT_NAMING_STRATEGY;
}
projectNamingStrategy = ns;
}
public Lifecycle getLifecycle() {
return Lifecycle.get();
}
......@@ -2041,6 +2058,14 @@ public class Jenkins extends AbstractCIBase implements ModifiableItemGroup<TopLe
public AuthorizationStrategy getAuthorizationStrategy() {
return authorizationStrategy;
}
/**
* The strategy used to check the project names.
* @return never <code>null</code>
*/
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<TopLe
private String checkJobName(String name) throws Failure {
checkGoodName(name);
name = name.trim();
projectNamingStrategy.checkName(name);
if(getItem(name)!=null)
throw new Failure(Messages.Hudson_JobAlreadyExists(name));
// looks good
......@@ -3787,4 +3813,5 @@ public class Jenkins extends AbstractCIBase implements ModifiableItemGroup<TopLe
assert PERMISSIONS!=null;
assert ADMINISTER!=null;
}
}
/*
* 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.
*/
package jenkins.model;
import hudson.DescriptorExtensionList;
import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.Failure;
import java.io.Serializable;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
/**
* This ExtensionPoint allows to enforce the name of projects/jobs.
*
* @author Dominik Bartholdi (imod)
*/
public abstract class ProjectNamingStrategy implements Describable<ProjectNamingStrategy>, ExtensionPoint {
public ProjectNamingStrategyDescriptor getDescriptor() {
return (ProjectNamingStrategyDescriptor) Jenkins.getInstance().getDescriptor(getClass());
}
public static DescriptorExtensionList<ProjectNamingStrategy, ProjectNamingStrategyDescriptor> all() {
return Jenkins.getInstance().<ProjectNamingStrategy, ProjectNamingStrategyDescriptor> 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 <code>false</code> is
* returned, only new jobs have to follow the strategy.
*
* @return <code>true</code> 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<ProjectNamingStrategy> {
}
}
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())
}
}
}
# 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
......@@ -33,6 +33,7 @@ Hudson.NoJavaInPath=java is not in your PATH. Maybe you need to <a href=''{0}/co
Hudson.NoName=No name is specified
Hudson.NodeBeingRemoved=Node is being removed
Hudson.UnsafeChar=''{0}'' is an unsafe character
Hudson.JobNameConventionNotApplyed=''{0}'' does not match the job name convention pattern {1}
Hudson.ViewAlreadyExists=A view already exists with the name "{0}"
Hudson.ViewName=All
Hudson.NotUsesUTF8ToDecodeURL=\
......
package jenkins.model.GlobalProjectNamingStrategyConfiguration
def f=namespace(lib.FormTagLib)
f.entry(title:_("namePattern")) {
f.textbox(name:"namePattern",value:h.defaulted(instance?.namePattern, descriptor.DEFAULT_PATTERN),class:"fixed-width")
}
f.entry(title:_("forceExistingJobs"), field:"forceExistingJobs") {
f.checkbox(name:"forceExistingJobs")
}
# 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.
namePattern = Name Pattern
forceExistingJobs = force existing
......@@ -31,6 +31,10 @@ import com.gargoylesoftware.htmlunit.TextPage;
import hudson.util.TextFile;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import jenkins.model.ProjectNamingStrategy;
import org.junit.Test;
import org.jvnet.hudson.test.Bug;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.recipes.LocalData;
......@@ -239,4 +243,20 @@ public class JobTest extends HudsonTestCase {
project.setDescription(null);
assertEquals("", ((TextPage) wc.goTo("job/project/description", "text/plain")).getContent());
}
public void testProjectNamingStrategy() throws Exception {
hudson.setProjectNamingStrategy(new ProjectNamingStrategy.PatternProjectNamingStrategy("DUMMY.*", false));
final FreeStyleProject p = createFreeStyleProject("DUMMY_project");
assertNotNull("no project created", p);
try {
createFreeStyleProject("project");
fail("should not get here, the project name is not allowed, therefore the creation must fail!");
} catch (Failure e) {
// OK, expected
}finally{
// set it back to the default naming strategy, otherwise all other tests would fail to create jobs!
hudson.setProjectNamingStrategy(ProjectNamingStrategy.DEFAULT_NAMING_STRATEGY);
}
createFreeStyleProject("project");
}
}
<div>
This is the default configuration and allows the user the choose any name he likes.
</div>
<div>
Define a pattern (regular expression) to check whether the job name is valid or not. <br>
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.
</div>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册