Merge "Load actions from both actions.config and action.config"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java
index 101dfa6..685a639 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java
@@ -15,16 +15,16 @@
package com.googlesource.gerrit.plugins.hooks.workflow;
import java.io.File;
+import java.io.IOException;
import java.util.Collection;
-import java.util.Collections;
import com.google.common.collect.Lists;
import com.google.gerrit.server.config.SitePath;
import com.google.inject.Inject;
+import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,7 +38,7 @@
* File (relative to site) to load rules from
*/
private static final String ITS_CONFIG_FILE = "etc" + File.separatorChar +
- "its" + File.separator + "action.config";
+ "its" + File.separator + "actions.config";
/**
* The section for rules within {@link #ITS_CONFIG_FILE}
@@ -69,22 +69,26 @@
this.ruleFactory = ruleFactory;
this.conditionFactory = conditionFactory;
this.actionRequestFactory = actionRequestFactory;
- loadRules();
+ reloadRules();
}
/**
- * Loads the rules for the RuleBase.
+ * Adds rules from a file to the the RuleBase.
+ * <p>
+ * If the given file does not exist, it is silently ignored
*
- * Consider using {@link #loadRules()@}, as that method only loads the rules,
- * if they have not yet been loaded.
+ * @param ruleFile File from which to read the rules
*/
- private void forceLoadRules() throws Exception {
- File configFile = new File(sitePath, ITS_CONFIG_FILE);
- if (configFile.exists()) {
- FileBasedConfig cfg = new FileBasedConfig(configFile, FS.DETECTED);
- cfg.load();
+ private void addRulesFromFile(File ruleFile) {
+ if (ruleFile.exists()) {
+ FileBasedConfig cfg = new FileBasedConfig(ruleFile, FS.DETECTED);
+ try {
+ cfg.load();
+ } catch (IOException | ConfigInvalidException e) {
+ log.error("Invalid ITS action configuration", e);
+ return;
+ }
- rules = Lists.newArrayList();
Collection<String> subsections = cfg.getSubsections(RULE_SECTION);
for (String subsection : subsections) {
Rule rule = ruleFactory.create(subsection);
@@ -106,24 +110,36 @@
rules.add(rule);
}
} else {
- // configFile does not exist.
- log.warn("ITS actions configuration file (" + configFile + ") does not exist.");
- rules = Collections.emptySet();
+ log.warn("ITS actions configuration file (" + ruleFile + ") does not exist.");
}
}
/**
- * Loads the rules for the RuleBase, if they have not yet been loaded.
+ * Loads the rules for the RuleBase.
*/
- private void loadRules() {
- if (rules == null) {
- try {
- forceLoadRules();
- } catch (Exception e) {
- log.error("Invalid ITS action configuration", e);
- rules = Collections.emptySet();
- }
+ private void reloadRules() {
+ rules = Lists.newArrayList();
+
+ // Add rules from file with typo in filename
+ //
+ // While the documentation called for "actions.config" (Trailing "s" in
+ // "actions"), the code previously only loaded "action.config" (No
+ // trailing "s" in "action"). To give users time to gracefully migrate to
+ // "actions.config" (with trailing "s", we (for now) load files from both
+ // locations, but consider "actions.config" (with trailing "s" the
+ // canonical place.
+ File faultyNameRuleFile = new File(sitePath, "etc" + File.separatorChar
+ + "its" + File.separator + "action.config");
+ if (faultyNameRuleFile.exists()) {
+ log.warn("Loading rules from deprecated 'etc/its/action.config' (No "
+ + "trailing 's' in 'action'). Please migrate to "
+ + "'etc/its/actions.config' (Trailing 's' in 'actions').");
+ addRulesFromFile(faultyNameRuleFile);
}
+
+ // Add global rules
+ File globalRuleFile = new File(sitePath, ITS_CONFIG_FILE);
+ addRulesFromFile(globalRuleFile);
}
/**
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBaseTest.java
index 2c6b466..b8d20d9 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBaseTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBaseTest.java
@@ -218,16 +218,103 @@
assertEquals("Matched actionRequests do not match", expected, actual);
}
+ public void testWarnExistingFaultyNameRuleBaseFile() throws IOException {
+ injectRuleBase("", true);
+
+ replayMocks();
+
+ createRuleBase();
+
+ assertLogMessageContains("Please migrate"); // Migration warning for old name
+ assertLogMessageContains("does not exist"); // For global rule file at new name
+ }
+
+ public void testSimpleFaultyNameRuleBase() throws IOException {
+ injectRuleBase("[rule \"rule1\"]\n" +
+ "\tconditionA = value1\n" +
+ "\taction = action1", true);
+
+ Rule rule1 = createMock(Rule.class);
+ expect(ruleFactory.create("rule1")).andReturn(rule1);
+
+ Condition condition1 = createMock(Condition.class);
+ expect(conditionFactory.create("conditionA", "value1")).andReturn(condition1);
+ rule1.addCondition(condition1);
+
+ ActionRequest actionRequest1 = createMock(ActionRequest.class);
+ expect(actionRequestFactory.create("action1")).andReturn(actionRequest1);
+ rule1.addActionRequest(actionRequest1);
+
+ replayMocks();
+
+ createRuleBase();
+
+ assertLogMessageContains("Please migrate"); // Migration warning for old name
+ assertLogMessageContains("does not exist"); // For global rule file at new name
+ }
+
+ public void testGlobalRuleBaseFileAndFaultyNameFileAreLoaded() throws IOException {
+ injectRuleBase("[rule \"rule1\"]\n" +
+ "\taction = action1", false);
+
+ injectRuleBase("[rule \"rule2\"]\n" +
+ "\taction = action2", true);
+
+ Collection<Property> properties = Collections.emptySet();
+
+ Rule rule1 = createMock(Rule.class);
+ expect(ruleFactory.create("rule1")).andReturn(rule1);
+
+ ActionRequest actionRequest1 = createMock(ActionRequest.class);
+ expect(actionRequestFactory.create("action1")).andReturn(actionRequest1);
+ rule1.addActionRequest(actionRequest1);
+
+ List<ActionRequest> rule1Match = Lists.newArrayListWithCapacity(1);
+ rule1Match.add(actionRequest1);
+ expect(rule1.actionRequestsFor(properties)).andReturn(rule1Match);
+
+ Rule rule2 = createMock(Rule.class);
+ expect(ruleFactory.create("rule2")).andReturn(rule2);
+
+ ActionRequest actionRequest2 = createMock(ActionRequest.class);
+ expect(actionRequestFactory.create("action2")).andReturn(actionRequest2);
+ rule2.addActionRequest(actionRequest2);
+
+ List<ActionRequest> rule2Match = Lists.newArrayListWithCapacity(1);
+ rule1Match.add(actionRequest2);
+ expect(rule2.actionRequestsFor(properties)).andReturn(rule2Match);
+
+ replayMocks();
+
+ RuleBase ruleBase = createRuleBase();
+
+ Collection<ActionRequest> actual = ruleBase.actionRequestsFor(properties);
+
+ List<ActionRequest> expected = Lists.newArrayListWithCapacity(2);
+ expected.add(actionRequest1);
+ expected.add(actionRequest2);
+
+ assertEquals("Matched actionRequests do not match", expected, actual);
+
+ assertLogMessageContains("Please migrate"); // Migration warning for old name
+ }
+
private RuleBase createRuleBase() {
return injector.getInstance(RuleBase.class);
}
private void injectRuleBase(String rules) throws IOException {
+ injectRuleBase(rules, false);
+ }
+
+ private void injectRuleBase(String rules, Boolean faultyName) throws IOException {
File ruleBaseFile = new File(sitePath, "etc" + File.separatorChar + "its" +
- File.separator + "action.config");
+ File.separator + "action" + (faultyName ? "" : "s") + ".config");
File ruleBaseParentFile = ruleBaseFile.getParentFile();
- assertTrue("Failed to create parent (" + ruleBaseParentFile + ") for " +
- "rule base", ruleBaseParentFile.mkdirs());
+ if (!ruleBaseParentFile.exists()) {
+ assertTrue("Failed to create parent (" + ruleBaseParentFile + ") for " +
+ "rule base", ruleBaseParentFile.mkdirs());
+ }
FileWriter unbufferedWriter = new FileWriter(ruleBaseFile);
BufferedWriter writer = new BufferedWriter(unbufferedWriter);
writer.write(rules);