Bubble up OWNERS file parsing errors
Problem:
The OWNERS file adheres to the YAML standard. In cases where it is not
parseable (due to simple errors like incorrect indentation or missing
`:`, etc.), the error message is logged and ignored. To discover what
went wrong, one needs access to the error_log.
Solution:
Ensure that information about broken OWNERS files reaches Gerrit's UI
through the following steps (bottom-up):
1. Do not catch `IOException` in the
`ConfigurationParser.getOwnersConfig` function; let it be caught by
the caller.
2. Catch it in the `PathOwners.getOwnersConfig` function. However, it is
too broad to be further conveyed (there are other `IOException`
instances that shouldn't be confused with OWNERS file issues).
Therefore, re-throw it as the newly introduced
`InvalidOwnersFileException`.
3. The OWNERS content is cached; therefore, the
`InvalidOwnersFileException` can only be thrown during cache value
load. As such, it is wrapped into an `ExecutionException`. Catch it
and unwrap it back to `InvalidOwnersFileException` if that was the
cause, leaving all other cases handled as they were before.
The `InvalidOwnersFileException` is re-thrown up to the callers.
4. The following handling was applied to `PathOwners` callers:
- In the owners-autoassign plugin, it is caught and logged at the
warning level (not much can be done there as there is no UI for it).
- In `OwnersStoredValues` (used by the owners plugin prolog predicate
to get the OWNERS file content), it is re-thrown as a runtime
exception. `PrologRuleEvaluator` nicely handles it and shows the
`gerrit~PrologRule` label with the status `ERROR`.
- In `OwnersSubmitRequirement`, it is caught and returned as
`RULE_ERROR`, represented as the `owners~OwnersSubmitRequirement`
label with the status `Error`. The following message is displayed
when hovering over it:
Invalid owners file: OWNERS, in project: [repo-name], on branch [branch-name]
- In the `GetFilesOwners` REST API endpoint, it is caught and thrown
as a `ResourceConflictException` (the same way as in `code-owners`).
When called, the following response is returned to the caller:
HTTP/1.1 409 Conflict - Invalid owners file: OWNERS, in project: [repo-name], on branch [branch-name]
The handling of `InvalidOwnersFileException` is confirmed in the
integration tests, but it was also verified manually.
Bug: Issue 310420006
Change-Id: I4425e6ede74332832de39c0769549c7e917e67fc
diff --git a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java
index d6fd574..9a8edb6 100644
--- a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java
+++ b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java
@@ -256,6 +256,8 @@
logger.warn("Could not open change: {}", cId, e);
} catch (ReviewerManagerException e) {
logger.warn("Could not add reviewers for change: {}", cId, e);
+ } catch (InvalidOwnersFileException e) {
+ logger.warn("Could not add reviewers for change: {} due to rules error", cId, e);
}
}
diff --git a/owners-common/src/main/java/com/googlesource/gerrit/owners/common/ConfigurationParser.java b/owners-common/src/main/java/com/googlesource/gerrit/owners/common/ConfigurationParser.java
index 2c8c619..54c624e 100644
--- a/owners-common/src/main/java/com/googlesource/gerrit/owners/common/ConfigurationParser.java
+++ b/owners-common/src/main/java/com/googlesource/gerrit/owners/common/ConfigurationParser.java
@@ -37,24 +37,19 @@
this.accounts = accounts;
}
- public Optional<OwnersConfig> getOwnersConfig(byte[] yamlBytes) {
- try {
- final OwnersConfig ret = new OwnersConfig();
- JsonNode jsonNode = new ObjectMapper(new YAMLFactory()).readValue(yamlBytes, JsonNode.class);
- Boolean inherited =
- Optional.ofNullable(jsonNode.get("inherited")).map(JsonNode::asBoolean).orElse(true);
- ret.setInherited(inherited);
- ret.setLabel(
- Optional.ofNullable(jsonNode.get("label"))
- .map(JsonNode::asText)
- .flatMap(LabelDefinition::parse));
- addClassicMatcher(jsonNode, ret);
- addMatchers(jsonNode, ret);
- return Optional.of(ret);
- } catch (IOException e) {
- log.warn("Unable to read YAML Owners file", e);
- return Optional.empty();
- }
+ public OwnersConfig getOwnersConfig(byte[] yamlBytes) throws IOException {
+ final OwnersConfig ret = new OwnersConfig();
+ JsonNode jsonNode = new ObjectMapper(new YAMLFactory()).readValue(yamlBytes, JsonNode.class);
+ Boolean inherited =
+ Optional.ofNullable(jsonNode.get("inherited")).map(JsonNode::asBoolean).orElse(true);
+ ret.setInherited(inherited);
+ ret.setLabel(
+ Optional.ofNullable(jsonNode.get("label"))
+ .map(JsonNode::asText)
+ .flatMap(LabelDefinition::parse));
+ addClassicMatcher(jsonNode, ret);
+ addMatchers(jsonNode, ret);
+ return ret;
}
private void addClassicMatcher(JsonNode jsonNode, OwnersConfig ret) {
diff --git a/owners-common/src/main/java/com/googlesource/gerrit/owners/common/InvalidOwnersFileException.java b/owners-common/src/main/java/com/googlesource/gerrit/owners/common/InvalidOwnersFileException.java
new file mode 100644
index 0000000..1914625
--- /dev/null
+++ b/owners-common/src/main/java/com/googlesource/gerrit/owners/common/InvalidOwnersFileException.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2024 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.googlesource.gerrit.owners.common;
+
+public class InvalidOwnersFileException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public InvalidOwnersFileException(
+ String project, String ownersPath, String branch, Throwable reason) {
+ super(exceptionMessage(project, ownersPath, branch), reason);
+ }
+
+ private static String exceptionMessage(String project, String ownersPath, String branch) {
+ return String.format(
+ "Invalid owners file: %s, in project: %s, on branch %s", ownersPath, project, branch);
+ }
+}
diff --git a/owners-common/src/main/java/com/googlesource/gerrit/owners/common/PathOwners.java b/owners-common/src/main/java/com/googlesource/gerrit/owners/common/PathOwners.java
index b904715..e7e437a 100644
--- a/owners-common/src/main/java/com/googlesource/gerrit/owners/common/PathOwners.java
+++ b/owners-common/src/main/java/com/googlesource/gerrit/owners/common/PathOwners.java
@@ -103,7 +103,8 @@
Map<String, FileDiffOutput> fileDiffMap,
boolean expandGroups,
String project,
- PathOwnersEntriesCache cache) {
+ PathOwnersEntriesCache cache)
+ throws InvalidOwnersFileException {
this(
accounts,
repositoryManager,
@@ -125,7 +126,8 @@
DiffSummary diffSummary,
boolean expandGroups,
String project,
- PathOwnersEntriesCache cache) {
+ PathOwnersEntriesCache cache)
+ throws InvalidOwnersFileException {
this(
accounts,
repositoryManager,
@@ -147,7 +149,8 @@
Set<String> modifiedPaths,
boolean expandGroups,
String project,
- PathOwnersEntriesCache cache) {
+ PathOwnersEntriesCache cache)
+ throws InvalidOwnersFileException {
this.repositoryManager = repositoryManager;
this.repository = repository;
this.parentProjectsNames = parentProjectsNames;
@@ -156,10 +159,12 @@
this.accounts = accounts;
this.expandGroups = expandGroups;
- OwnersMap map =
- branchWhenEnabled
- .map(branch -> fetchOwners(project, branch, cache))
- .orElse(new OwnersMap());
+ OwnersMap map;
+ if (branchWhenEnabled.isPresent()) {
+ map = fetchOwners(project, branchWhenEnabled.get(), cache);
+ } else {
+ map = new OwnersMap();
+ }
owners = Multimaps.unmodifiableSetMultimap(map.getPathOwners());
reviewers = Multimaps.unmodifiableSetMultimap(map.getPathReviewers());
matchers = map.getMatchers();
@@ -215,8 +220,10 @@
* Fetched the owners for the associated patch list.
*
* @return A structure containing matchers paths to owners
+ * @throws InvalidOwnersFileException when reading/parsing OWNERS file fails
*/
- private OwnersMap fetchOwners(String project, String branch, PathOwnersEntriesCache cache) {
+ private OwnersMap fetchOwners(String project, String branch, PathOwnersEntriesCache cache)
+ throws InvalidOwnersFileException {
OwnersMap ownersMap = new OwnersMap();
try {
// Using a `map` would have needed a try/catch inside the lamba, resulting in more code
@@ -267,16 +274,17 @@
}
}
ownersMap.setLabel(Optional.ofNullable(currentEntry).flatMap(PathOwnersEntry::getLabel));
- return ownersMap;
- } catch (IOException | ExecutionException e) {
- log.warn("Invalid OWNERS file", e);
- return ownersMap;
+ } catch (IOException e) {
+ log.warn("Opening repository {} failed", project, e);
+ } catch (ExecutionException e) {
+ log.warn("Reading OWNERS file for {} project, from cache failed", project, e);
}
+ return ownersMap;
}
private List<ReadOnlyPathOwnersEntry> getPathOwnersEntries(
List<Project.NameKey> projectNames, String branch, PathOwnersEntriesCache cache)
- throws IOException, ExecutionException {
+ throws IOException, InvalidOwnersFileException, ExecutionException {
ImmutableList.Builder<ReadOnlyPathOwnersEntry> pathOwnersEntries = ImmutableList.builder();
for (Project.NameKey projectName : projectNames) {
try (Repository repo = repositoryManager.openRepository(projectName)) {
@@ -290,7 +298,7 @@
private ReadOnlyPathOwnersEntry getPathOwnersEntryOrEmpty(
String project, Repository repo, String branch, PathOwnersEntriesCache cache)
- throws ExecutionException {
+ throws InvalidOwnersFileException, ExecutionException {
return getPathOwnersEntry(project, repo, branch, cache)
.map(v -> (ReadOnlyPathOwnersEntry) v)
.orElse(PathOwnersEntry.EMPTY);
@@ -298,27 +306,33 @@
private PathOwnersEntry getPathOwnersEntryOrNew(
String project, Repository repo, String branch, PathOwnersEntriesCache cache)
- throws ExecutionException {
+ throws InvalidOwnersFileException, ExecutionException {
return getPathOwnersEntry(project, repo, branch, cache).orElseGet(PathOwnersEntry::new);
}
private Optional<PathOwnersEntry> getPathOwnersEntry(
String project, Repository repo, String branch, PathOwnersEntriesCache cache)
- throws ExecutionException {
+ throws InvalidOwnersFileException, ExecutionException {
String rootPath = "OWNERS";
- return cache
- .get(project, branch, rootPath, () -> getOwnersConfig(repo, rootPath, branch))
- .map(
- conf ->
- new PathOwnersEntry(
+ return unwrapInvalidOwnersFileException(
+ () ->
+ cache
+ .get(
+ project,
+ branch,
rootPath,
- conf,
- accounts,
- Optional.empty(),
- Collections.emptySet(),
- Collections.emptySet(),
- Collections.emptySet(),
- Collections.emptySet()));
+ () -> getOwnersConfig(project, repo, rootPath, branch))
+ .map(
+ conf ->
+ new PathOwnersEntry(
+ rootPath,
+ conf,
+ accounts,
+ Optional.empty(),
+ Collections.emptySet(),
+ Collections.emptySet(),
+ Collections.emptySet(),
+ Collections.emptySet())));
}
private void processMatcherPerPath(
@@ -374,7 +388,7 @@
PathOwnersEntry rootEntry,
Map<String, PathOwnersEntry> entries,
PathOwnersEntriesCache cache)
- throws ExecutionException {
+ throws InvalidOwnersFileException, ExecutionException {
String[] parts = path.split("/");
PathOwnersEntry currentEntry = rootEntry;
StringBuilder builder = new StringBuilder();
@@ -401,31 +415,33 @@
String ownersPath = partial + "OWNERS";
PathOwnersEntry pathFallbackEntry = currentEntry;
currentEntry =
- cache
- .get(
- project,
- branch,
- ownersPath,
- () -> getOwnersConfig(repository, ownersPath, branch))
- .map(
- c -> {
- Optional<LabelDefinition> label = pathFallbackEntry.getLabel();
- final Set<Id> owners = pathFallbackEntry.getOwners();
- final Set<Id> reviewers = pathFallbackEntry.getReviewers();
- Collection<Matcher> inheritedMatchers =
- pathFallbackEntry.getMatchers().values();
- Set<String> groupOwners = pathFallbackEntry.getGroupOwners();
- return new PathOwnersEntry(
- ownersPath,
- c,
- accounts,
- label,
- owners,
- reviewers,
- inheritedMatchers,
- groupOwners);
- })
- .orElse(pathFallbackEntry);
+ unwrapInvalidOwnersFileException(
+ () ->
+ cache
+ .get(
+ project,
+ branch,
+ ownersPath,
+ () -> getOwnersConfig(project, repository, ownersPath, branch))
+ .map(
+ c -> {
+ Optional<LabelDefinition> label = pathFallbackEntry.getLabel();
+ final Set<Id> owners = pathFallbackEntry.getOwners();
+ final Set<Id> reviewers = pathFallbackEntry.getReviewers();
+ Collection<Matcher> inheritedMatchers =
+ pathFallbackEntry.getMatchers().values();
+ Set<String> groupOwners = pathFallbackEntry.getGroupOwners();
+ return new PathOwnersEntry(
+ ownersPath,
+ c,
+ accounts,
+ label,
+ owners,
+ reviewers,
+ inheritedMatchers,
+ groupOwners);
+ })
+ .orElse(pathFallbackEntry));
entries.put(partial, currentEntry);
}
}
@@ -480,12 +496,46 @@
/**
* Returns the parsed FileOwnersConfig file for the given path if it exists.
*
- * @param ownersPath path to OWNERS file in the git repo
- * @return config or null if it doesn't exist
- * @throws IOException
+ * @return Optional(config) or Optional.empty if it doesn't exist
+ * @throws InvalidOwnersFileException when reading/parsing of the OWNERS file fails
*/
- private Optional<OwnersConfig> getOwnersConfig(Repository repo, String ownersPath, String branch)
- throws IOException {
- return getBlobAsBytes(repo, branch, ownersPath).flatMap(parser::getOwnersConfig);
+ private Optional<OwnersConfig> getOwnersConfig(
+ String project, Repository repo, String ownersPath, String branch)
+ throws InvalidOwnersFileException {
+ try {
+ Optional<byte[]> configBytes = getBlobAsBytes(repo, branch, ownersPath);
+ if (configBytes.isEmpty()) {
+ return Optional.empty();
+ }
+ return Optional.of(parser.getOwnersConfig(configBytes.get()));
+ } catch (IOException e) {
+ throw new InvalidOwnersFileException(project, ownersPath, branch, e);
+ }
+ }
+
+ @FunctionalInterface
+ private interface Executable<T> {
+ T call() throws ExecutionException;
+ }
+
+ /**
+ * Unwraps the InvalidOwnersFileException from the ExecutionException so that callers can focus on
+ * handling InvalidOwnersFileException if/when needed. Note that ExecutionException is thrown only
+ * when loading values to PathOwnersEntriesCache fails.
+ *
+ * @throws InvalidOwnersFileException when call throws ExecutionException that is matchable with
+ * InvalidOwnersFileException
+ * @throws ExecutionException in all other cases
+ */
+ private <T> T unwrapInvalidOwnersFileException(Executable<T> action)
+ throws InvalidOwnersFileException, ExecutionException {
+ try {
+ return action.call();
+ } catch (ExecutionException e) {
+ if (e.getCause() instanceof InvalidOwnersFileException) {
+ throw (InvalidOwnersFileException) e.getCause();
+ }
+ throw e;
+ }
}
}
diff --git a/owners-common/src/test/java/com/googlesource/gerrit/owners/common/Config.java b/owners-common/src/test/java/com/googlesource/gerrit/owners/common/Config.java
index ae3fad2..e531a3f 100644
--- a/owners-common/src/test/java/com/googlesource/gerrit/owners/common/Config.java
+++ b/owners-common/src/test/java/com/googlesource/gerrit/owners/common/Config.java
@@ -85,7 +85,7 @@
return entry;
}
- Optional<OwnersConfig> getOwnersConfig(String string) {
+ OwnersConfig getOwnersConfig(String string) throws IOException {
return parser.getOwnersConfig(string.getBytes(Charsets.UTF_8));
}
diff --git a/owners-common/src/test/java/com/googlesource/gerrit/owners/common/PathOwnersTest.java b/owners-common/src/test/java/com/googlesource/gerrit/owners/common/PathOwnersTest.java
index 2e23f33..542ff6d 100644
--- a/owners-common/src/test/java/com/googlesource/gerrit/owners/common/PathOwnersTest.java
+++ b/owners-common/src/test/java/com/googlesource/gerrit/owners/common/PathOwnersTest.java
@@ -339,14 +339,11 @@
}
@Test
- public void testParsingYamlWithLabelWithScore() {
+ public void testParsingYamlWithLabelWithScore() throws IOException {
String yamlString =
"inherited: true\nlabel: " + EXPECTED_LABEL + ",1\nowners:\n- " + USER_C_EMAIL_COM;
- Optional<OwnersConfig> config = getOwnersConfig(yamlString);
+ OwnersConfig ownersConfig = getOwnersConfig(yamlString);
- assertTrue(config.isPresent());
-
- OwnersConfig ownersConfig = config.get();
assertTrue(ownersConfig.isInherited());
assertThat(ownersConfig.getLabel()).isPresent();
@@ -360,14 +357,11 @@
}
@Test
- public void testParsingYamlWithLabelWithoutScore() {
+ public void testParsingYamlWithLabelWithoutScore() throws IOException {
String yamlString =
"inherited: true\nlabel: " + EXPECTED_LABEL + "\nowners:\n- " + USER_C_EMAIL_COM;
- Optional<OwnersConfig> config = getOwnersConfig(yamlString);
+ OwnersConfig ownersConfig = getOwnersConfig(yamlString);
- assertTrue(config.isPresent());
-
- OwnersConfig ownersConfig = config.get();
assertTrue(ownersConfig.isInherited());
assertThat(ownersConfig.getLabel()).isPresent();
diff --git a/owners-common/src/test/java/com/googlesource/gerrit/owners/common/RegexTest.java b/owners-common/src/test/java/com/googlesource/gerrit/owners/common/RegexTest.java
index ac011b0..a035d93 100644
--- a/owners-common/src/test/java/com/googlesource/gerrit/owners/common/RegexTest.java
+++ b/owners-common/src/test/java/com/googlesource/gerrit/owners/common/RegexTest.java
@@ -31,7 +31,6 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Before;
@@ -85,11 +84,8 @@
regexMatcher(".*/a.*", ACCOUNT_D),
partialRegexMatcher("Product.sql", ACCOUNT_A));
// the function to test
- Optional<OwnersConfig> configNullable = getOwnersConfig(fullConfig);
+ OwnersConfig config = getOwnersConfig(fullConfig);
// check classical configuration
- assertThat(configNullable).isPresent();
-
- OwnersConfig config = configNullable.get();
assertTrue(config.isInherited());
Set<String> owners = config.getOwners();
@@ -237,7 +233,7 @@
public void testMatchersOnlyConfig() throws Exception {
replayAll();
- Optional<OwnersConfig> ownersConfigOpt =
+ OwnersConfig ownersConfig =
getOwnersConfig(
createConfig(
false,
@@ -245,9 +241,6 @@
suffixMatcher(".txt", ACCOUNT_B),
genericMatcher(".*", ACCOUNT_A)));
- assertThat(ownersConfigOpt).isPresent();
- OwnersConfig ownersConfig = ownersConfigOpt.get();
-
assertThat(ownersConfig.getOwners()).isEmpty();
assertThat(ownersConfig.getMatchers()).isNotEmpty();
}
diff --git a/owners/src/main/java/com/googlesource/gerrit/owners/OwnersStoredValues.java b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersStoredValues.java
index c4d27e4..c077bb3 100644
--- a/owners/src/main/java/com/googlesource/gerrit/owners/OwnersStoredValues.java
+++ b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersStoredValues.java
@@ -25,6 +25,7 @@
import com.google.gerrit.server.rules.StoredValues;
import com.googlecode.prolog_cafe.lang.Prolog;
import com.googlesource.gerrit.owners.common.Accounts;
+import com.googlesource.gerrit.owners.common.InvalidOwnersFileException;
import com.googlesource.gerrit.owners.common.PathOwners;
import com.googlesource.gerrit.owners.common.PathOwnersEntriesCache;
import com.googlesource.gerrit.owners.common.PluginSettings;
@@ -73,6 +74,10 @@
settings.expandGroups(),
projectState.getName(),
cache);
+ } catch (InvalidOwnersFileException e) {
+ // re-throw exception as it is already logged but more importantly it is nicely
+ // handled by the prolog rules evaluator and results in prolog rule error
+ throw new IllegalStateException(e);
}
}
};
diff --git a/owners/src/main/java/com/googlesource/gerrit/owners/OwnersSubmitRequirement.java b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersSubmitRequirement.java
index 3935a4b..0d8dac4 100644
--- a/owners/src/main/java/com/googlesource/gerrit/owners/OwnersSubmitRequirement.java
+++ b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersSubmitRequirement.java
@@ -47,6 +47,7 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.googlesource.gerrit.owners.common.Accounts;
+import com.googlesource.gerrit.owners.common.InvalidOwnersFileException;
import com.googlesource.gerrit.owners.common.LabelDefinition;
import com.googlesource.gerrit.owners.common.PathOwners;
import com.googlesource.gerrit.owners.common.PathOwnersEntriesCache;
@@ -165,6 +166,9 @@
String.format(
"Missing approvals for path(s): [%s]",
Joiner.on(", ").join(missingApprovals))));
+ } catch (InvalidOwnersFileException e) {
+ logger.atSevere().withCause(e).log("Reading/parsing OWNERS file error.");
+ return Optional.of(ruleError(e.getMessage()));
} catch (IOException e) {
String msg =
String.format(
@@ -194,7 +198,7 @@
}
private PathOwners getPathOwners(ChangeData cd, ProjectState projectState)
- throws IOException, DiffNotAvailableException {
+ throws IOException, DiffNotAvailableException, InvalidOwnersFileException {
metrics.countConfigLoads.increment();
try (Timer0.Context ctx = metrics.loadConfig.start()) {
String branch = cd.change().getDest().branch();
@@ -365,4 +369,11 @@
submitRecord.requirements = List.of(SUBMIT_REQUIREMENT);
return submitRecord;
}
+
+ private static SubmitRecord ruleError(String err) {
+ SubmitRecord submitRecord = new SubmitRecord();
+ submitRecord.status = SubmitRecord.Status.RULE_ERROR;
+ submitRecord.errorMessage = err;
+ return submitRecord;
+ }
}
diff --git a/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java b/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java
index 5f5b1fb..a724239 100644
--- a/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java
+++ b/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java
@@ -39,6 +39,7 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.googlesource.gerrit.owners.common.Accounts;
+import com.googlesource.gerrit.owners.common.InvalidOwnersFileException;
import com.googlesource.gerrit.owners.common.PathOwners;
import com.googlesource.gerrit.owners.common.PathOwnersEntriesCache;
import com.googlesource.gerrit.owners.common.PluginSettings;
@@ -144,6 +145,9 @@
fileExpandedOwners.get(fileOwnerEntry.getKey()), ownersLabels, label));
return Response.ok(new FilesOwnersResponse(ownersLabels, filesWithPendingOwners));
+ } catch (InvalidOwnersFileException e) {
+ logger.atSevere().withCause(e).log("Reading/parsing OWNERS file error.");
+ throw new ResourceConflictException(e.getMessage(), e);
}
}
diff --git a/owners/src/test/java/com/googlesource/gerrit/owners/OwnersSubmitRequirementITAbstract.java b/owners/src/test/java/com/googlesource/gerrit/owners/OwnersSubmitRequirementITAbstract.java
index d7cea60..3f7362d 100644
--- a/owners/src/test/java/com/googlesource/gerrit/owners/OwnersSubmitRequirementITAbstract.java
+++ b/owners/src/test/java/com/googlesource/gerrit/owners/OwnersSubmitRequirementITAbstract.java
@@ -394,6 +394,31 @@
assertThat(changeReady.requirements).containsExactly(READY);
}
+ @Test
+ @GlobalPluginConfig(
+ pluginName = "owners",
+ name = "owners.enableSubmitRequirement",
+ value = "true")
+ public void shouldIndicateRuleErrorForBrokenOwnersFile() throws Exception {
+ addBrokenOwnersFileToRoot();
+
+ PushOneCommit.Result r = createChange("Add a file", "README.md", "foo");
+ ChangeApi changeApi = forChange(r);
+ ChangeInfo changeNotReady = changeApi.get();
+ assertThat(changeNotReady.submittable).isFalse();
+ assertThat(changeNotReady.requirements).isEmpty();
+ verifyHasSubmitRecordWithRuleError(changeNotReady.submitRecords);
+ }
+
+ private void verifyHasSubmitRecordWithRuleError(Collection<SubmitRecordInfo> records) {
+ assertThat(
+ records.stream()
+ .filter(record -> SubmitRecordInfo.Status.RULE_ERROR == record.status)
+ .filter(record -> record.errorMessage.startsWith("Invalid owners file: OWNERS"))
+ .findAny())
+ .isPresent();
+ }
+
private void verifyHasSubmitRecord(
Collection<SubmitRecordInfo> records, String label, SubmitRecordInfo.Label.Status status) {
assertThat(
@@ -435,6 +460,10 @@
return gApi.changes().id(r.getChangeId());
}
+ private void addBrokenOwnersFileToRoot() throws Exception {
+ pushOwnersToMaster("{foo");
+ }
+
private void addOwnerFileWithMatchersToRoot(
boolean inherit, String extension, TestAccount... users) throws Exception {
// Add OWNERS file to root:
diff --git a/owners/src/test/java/com/googlesource/gerrit/owners/restapi/GetFilesOwnersITAbstract.java b/owners/src/test/java/com/googlesource/gerrit/owners/restapi/GetFilesOwnersITAbstract.java
index 936f3d3..c39974c 100644
--- a/owners/src/test/java/com/googlesource/gerrit/owners/restapi/GetFilesOwnersITAbstract.java
+++ b/owners/src/test/java/com/googlesource/gerrit/owners/restapi/GetFilesOwnersITAbstract.java
@@ -36,6 +36,7 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.server.project.testing.TestLabels;
+import com.googlesource.gerrit.owners.common.InvalidOwnersFileException;
import com.googlesource.gerrit.owners.entities.FilesOwnersResponse;
import com.googlesource.gerrit.owners.entities.GroupOwner;
import com.googlesource.gerrit.owners.entities.Owner;
@@ -223,6 +224,20 @@
assertThat(thrown).hasCauseThat().isInstanceOf(LabelNotFoundException.class);
}
+ @Test
+ @UseLocalDisk
+ public void shouldThrowResourceConflictWhenOwnersFileIsBroken() throws Exception {
+ addBrokenOwnersFileToRoot();
+ String changeId = createChange().getChangeId();
+
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> ownersApi.apply(parseCurrentRevisionResource(changeId)));
+ assertThat(thrown).hasMessageThat().startsWith("Invalid owners file: OWNERS");
+ assertThat(thrown).hasCauseThat().isInstanceOf(InvalidOwnersFileException.class);
+ }
+
protected void replaceCodeReviewWithLabel(LabelType label) throws Exception {
try (ProjectConfigUpdate u = updateProject(allProjects)) {
u.getConfig().getLabelSections().remove(LabelId.CODE_REVIEW);
@@ -264,6 +279,10 @@
.containsExactly("a.txt", Sets.newHashSet(rootOwner, projectOwner));
}
+ private void addBrokenOwnersFileToRoot() throws Exception {
+ merge(createChange(testRepo, "master", "Add OWNER file", "OWNERS", "{foo", ""));
+ }
+
private void addOwnerFileToProjectConfig(Project.NameKey projectNameKey, boolean inherit)
throws Exception {
addOwnerFileToProjectConfig(projectNameKey, inherit, user);