Merge "Support project specific plugin configuration in own config file"

This commit is contained in:
Edwin Kempin
2013-11-13 07:24:53 +00:00
committed by Gerrit Code Review
6 changed files with 268 additions and 9 deletions

View File

@@ -584,9 +584,9 @@ The plugin configuration is loaded only once and is then cached.
Similar to changes in 'gerrit.config', changes to the plugin
configuration file will only become effective after a Gerrit restart.
[[project-specific-configuration]]
Project Specific Configuration
------------------------------
[[simple-project-specific-configuration]]
Simple Project Specific Configuration in `project.config`
---------------------------------------------------------
In Gerrit, project specific configuration is stored in the project's
`project.config` file on the `refs/meta/config` branch. If a plugin
@@ -598,12 +598,12 @@ This approach of storing the plugin configuration is only suitable for
plugins that have a simple configuration that only consists of
key-value pairs. With this approach it is not possible to have
subsections in the plugin configuration. Plugins that require a complex
configuration need to store their configuration in their own
configuration file where they can make use of subsections. On the other
hand storing the plugin configuration in a 'plugin' subsection in the
`project.config` file has the advantage that project owners have all
configuration parameters in one file, instead of having one
configuration file per plugin.
configuration need to store their configuration in their
link:#project-specific-configuration[own configuration file] where they
can make use of subsections. On the other hand storing the plugin
configuration in a 'plugin' subsection in the `project.config` file has
the advantage that project owners have all configuration parameters in
one file, instead of having one configuration file per plugin.
To avoid conflicts with other plugins, it is recommended that plugins
only use the `plugin` subsection with their own name. For example the
@@ -648,6 +648,49 @@ Project owners can edit the project configuration by fetching the
`refs/meta/config` branch, editing the `project.config` file and
pushing the commit back.
[[project-specific-configuration]]
Project Specific Configuration in own config file
-------------------------------------------------
Plugins can store their project specific configuration in an own
configuration file in the projects `refs/meta/config` branch.
This makes sense if the plugins project specific configuration is
rather complex and requires the usage of subsections. Plugins that
have a simple key-value pair configuration can store their project
specific configuration in a link:#simple-project-specific-configuration[
`plugin` subsection of the `project.config` file].
The plugin configuration file in the `refs/meta/config` branch must be
named after the plugin. For example a configuration file for a
`default-reviewer` plugin could look like this:
.default-reviewer.config
----
[branch "refs/heads/master"]
reviewer = Project Owners
reviewer = john.doe@example.com
[match "file:^.*\.txt"]
reviewer = My Info Developers
----
Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
plugin can easily access its project specific configuration:
[source,java]
----
@Inject
private com.google.gerrit.server.config.PluginConfigFactory cfg;
[...]
String[] reviewers = cfg.getProjectPluginConfig(project, "default-reviewer")
.getStringList("branch", "refs/heads/master", "reviewer");
----
Project owners can edit the project configuration by fetching the
`refs/meta/config` branch, editing the `<plugin-name>.config` file and
pushing the commit back.
[[capabilities]]
Plugin Owned Capabilities
-------------------------

View File

@@ -28,6 +28,7 @@ import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.FetchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.PushCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -178,6 +179,12 @@ public class GitUtil {
}
}
public static void fetch(Git git, String spec) throws GitAPIException {
FetchCommand fetch = git.fetch();
fetch.setRefSpecs(new RefSpec(spec));
fetch.call();
}
public static void checkout(Git git, String name) throws GitAPIException {
CheckoutCommand checkout = git.checkout();
checkout.setName(name);

View File

@@ -0,0 +1,102 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.acceptance.rest.project;
import static com.google.gerrit.acceptance.git.GitUtil.checkout;
import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
import static com.google.gerrit.acceptance.git.GitUtil.createProject;
import static com.google.gerrit.acceptance.git.GitUtil.fetch;
import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
import static org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.AccountCreator;
import com.google.gerrit.acceptance.SshSession;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.git.PushOneCommit;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Config;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
public class ProjectLevelConfigIT extends AbstractDaemonTest {
@Inject
private AccountCreator accounts;
@Inject
private SchemaFactory<ReviewDb> reviewDbProvider;
@Inject
private ProjectCache projectCache;
private ReviewDb db;
private TestAccount admin;
private String project;
private Git git;
@Before
public void setUp() throws Exception {
admin = accounts.admin();
initSsh(admin);
SshSession sshSession = new SshSession(server, admin);
project = "p";
createProject(sshSession, project, null, true);
git = cloneProject(sshSession.getUrl() + "/" + project);
fetch(git, GitRepositoryManager.REF_CONFIG + ":refs/heads/config");
checkout(git, "refs/heads/config");
db = reviewDbProvider.open();
}
@After
public void cleanup() {
db.close();
}
@Test
public void accessProjectSpecificConfig() throws GitAPIException, IOException {
String configName = "test.config";
Config cfg = new Config();
cfg.setString("s1", null, "k1", "v1");
cfg.setString("s2", "ss", "k2", "v2");
PushOneCommit push =
new PushOneCommit(db, admin.getIdent(), "Create Project Level Config",
configName, cfg.toText());
push.to(git, GitRepositoryManager.REF_CONFIG);
ProjectState state = projectCache.get(new Project.NameKey(project));
assertEquals(cfg.toText(), state.getConfig(configName).get().toText());
}
@Test
public void nonExistingConfig() {
ProjectState state = projectCache.get(new Project.NameKey(project));
assertEquals("", state.getConfig("test.config").get().toText());
}
}

View File

@@ -179,4 +179,27 @@ public class PluginConfigFactory {
return cfg;
}
/**
* Returns the configuration for the specified plugin that is stored in the
* '<plugin-name>.config' file in the 'refs/meta/config' branch of the
* specified project.
*
* @param projectName the name of the project for which the plugin
* configuration should be returned
* @param pluginName the name of the plugin for which the configuration should
* be returned
* @return the plugin configuration from the '<plugin-name>.config' file of
* the specified project
* @throws NoSuchProjectException thrown if the specified project does not
* exist
*/
public Config getProjectPluginConfig(Project.NameKey projectName,
String pluginName) throws NoSuchProjectException {
ProjectState projectState = projectCache.get(projectName);
if (projectState == null) {
throw new NoSuchProjectException(projectName);
}
return projectState.getConfig(pluginName + ".config").get();
}
}

View File

@@ -0,0 +1,57 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.git;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
import java.io.IOException;
/** Configuration file in the projects refs/meta/config branch. */
public class ProjectLevelConfig extends VersionedMetaData {
private final String fileName;
private Config cfg;
public ProjectLevelConfig(String fileName) {
this.fileName = fileName;
}
@Override
protected String getRefName() {
return GitRepositoryManager.REF_CONFIG;
}
@Override
protected void onLoad() throws IOException, ConfigInvalidException {
cfg = readConfig(fileName);
}
public Config get() {
if (cfg == null) {
cfg = new Config();
}
return cfg;
}
@Override
protected void onSave(CommitBuilder commit) throws IOException,
ConfigInvalidException {
if (commit.getMessage() == null || "".equals(commit.getMessage())) {
commit.setMessage("Updated configuration\n");
}
saveConfig(fileName, cfg);
}
}

View File

@@ -40,12 +40,14 @@ import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.ProjectLevelConfig;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.googlecode.prolog_cafe.compiler.CompileException;
import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
@@ -83,6 +85,7 @@ public class ProjectState {
private final List<CommentLinkInfo> commentLinks;
private final ProjectConfig config;
private final Map<String, ProjectLevelConfig> configs;
private final Set<AccountGroup.UUID> localOwners;
/** Prolog rule state. */
@@ -121,6 +124,7 @@ public class ProjectState {
this.rulesCache = rulesCache;
this.commentLinks = commentLinks;
this.config = config;
this.configs = Maps.newHashMap();
this.capabilities = isAllProjects
? new CapabilityCollection(config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES))
: null;
@@ -216,6 +220,29 @@ public class ProjectState {
return config;
}
public ProjectLevelConfig getConfig(String fileName) {
if (configs.containsKey(fileName)) {
return configs.get(fileName);
}
ProjectLevelConfig cfg = new ProjectLevelConfig(fileName);
try {
Repository git = gitMgr.openRepository(getProject().getNameKey());
try {
cfg.load(git);
} finally {
git.close();
}
} catch (IOException e) {
log.warn("Failed to load " + fileName + " for " + getProject().getName(), e);
} catch (ConfigInvalidException e) {
log.warn("Failed to load " + fileName + " for " + getProject().getName(), e);
}
configs.put(fileName, cfg);
return cfg;
}
public long getMaxObjectSizeLimit() {
return config.getMaxObjectSizeLimit();
}