blob: 21462a257dc40ef580da8fe37f861ffb05c41dd3 [file] [log] [blame]
// Copyright (C) 2021 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.plugins.codeowners.restapi;
import static com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_IMPLICIT_APPROVALS;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_VALIDATION_ON_COMMIT_RECEIVED;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_VALIDATION_ON_SUBMIT;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_EXEMPTED_USER;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_EXEMPT_PURE_REVERTS;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_FALLBACK_CODE_OWNERS;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_FILE_EXTENSION;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_GLOBAL_CODE_OWNER;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_INVALID_CODE_OWNER_CONFIG_INFO_URL;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_MAX_PATHS_IN_CHANGE_MESSAGES;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_MERGE_COMMIT_STRATEGY;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_OVERRIDE_INFO_URL;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_READ_ONLY;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_REJECT_NON_RESOLVABLE_IMPORTS;
import static com.google.gerrit.plugins.codeowners.backend.config.OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL;
import static com.google.gerrit.plugins.codeowners.backend.config.RequiredApprovalConfig.KEY_REQUIRED_APPROVAL;
import static com.google.gerrit.plugins.codeowners.backend.config.StatusConfig.KEY_DISABLED;
import static com.google.gerrit.plugins.codeowners.backend.config.StatusConfig.KEY_DISABLED_BRANCH;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.plugins.codeowners.api.CodeOwnerProjectConfigInfo;
import com.google.gerrit.plugins.codeowners.api.CodeOwnerProjectConfigInput;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfigValidator;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersProjectConfigFile;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.git.validators.CommitValidationMessage;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
/**
* REST endpoint that updates the code owner project configuration.
*
* <p>This REST endpoint handles {@code PUT /projects/<project-name>/code_owners.project_config}
* requests.
*/
@Singleton
public class PutCodeOwnerProjectConfig
implements RestModifyView<ProjectResource, CodeOwnerProjectConfigInput> {
private final String pluginName;
private final PermissionBackend permissionBackend;
private final GitRepositoryManager repoManager;
private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
private final ProjectCache projectCache;
private final CodeOwnersPluginConfigValidator codeOwnersPluginConfigValidator;
private final CodeOwnerProjectConfigJson codeOwnerProjectConfigJson;
@Inject
public PutCodeOwnerProjectConfig(
@PluginName String pluginName,
PermissionBackend permissionBackend,
GitRepositoryManager repoManager,
Provider<MetaDataUpdate.User> metaDataUpdateFactory,
ProjectCache projectCache,
CodeOwnersPluginConfigValidator codeOwnersPluginConfigValidator,
CodeOwnerProjectConfigJson codeOwnerProjectConfigJson) {
this.pluginName = pluginName;
this.permissionBackend = permissionBackend;
this.repoManager = repoManager;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.projectCache = projectCache;
this.codeOwnersPluginConfigValidator = codeOwnersPluginConfigValidator;
this.codeOwnerProjectConfigJson = codeOwnerProjectConfigJson;
}
@Override
public Response<CodeOwnerProjectConfigInfo> apply(
ProjectResource projectResource, CodeOwnerProjectConfigInput input)
throws RestApiException, PermissionBackendException, IOException, ConfigInvalidException {
// This REST endpoint requires the caller to be a project owner.
permissionBackend
.currentUser()
.project(projectResource.getNameKey())
.check(ProjectPermission.WRITE_CONFIG);
try (Repository repo = repoManager.openRepository(projectResource.getNameKey());
MetaDataUpdate metaDataUpdate =
metaDataUpdateFactory.get().create(projectResource.getNameKey())) {
metaDataUpdate.setMessage(String.format("Update %s configuration", pluginName));
CodeOwnersProjectConfigFile codeOwnersProjectConfigFile = new CodeOwnersProjectConfigFile();
codeOwnersProjectConfigFile.load(projectResource.getNameKey(), repo);
Config codeOwnersConfig = codeOwnersProjectConfigFile.getConfig();
if (input.disabled != null) {
codeOwnersConfig.setBoolean(
SECTION_CODE_OWNERS, /* subsection= */ null, KEY_DISABLED, input.disabled);
}
if (input.disabledBranches != null) {
codeOwnersConfig.setStringList(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_DISABLED_BRANCH,
input.disabledBranches);
}
if (input.fileExtension != null) {
codeOwnersConfig.setString(
SECTION_CODE_OWNERS, /* subsection= */ null, KEY_FILE_EXTENSION, input.fileExtension);
}
if (input.requiredApproval != null) {
if (input.requiredApproval.isEmpty()) {
codeOwnersConfig.unset(
SECTION_CODE_OWNERS, /* subsection= */ null, KEY_REQUIRED_APPROVAL);
} else {
codeOwnersConfig.setString(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_REQUIRED_APPROVAL,
input.requiredApproval);
}
}
if (input.overrideApprovals != null) {
codeOwnersConfig.setStringList(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_OVERRIDE_APPROVAL,
input.overrideApprovals);
}
if (input.fallbackCodeOwners != null) {
codeOwnersConfig.setEnum(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_FALLBACK_CODE_OWNERS,
input.fallbackCodeOwners);
}
if (input.globalCodeOwners != null) {
codeOwnersConfig.setStringList(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_GLOBAL_CODE_OWNER,
input.globalCodeOwners);
}
if (input.exemptedUsers != null) {
codeOwnersConfig.setStringList(
SECTION_CODE_OWNERS, /* subsection= */ null, KEY_EXEMPTED_USER, input.exemptedUsers);
}
if (input.mergeCommitStrategy != null) {
codeOwnersConfig.setEnum(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_MERGE_COMMIT_STRATEGY,
input.mergeCommitStrategy);
}
if (input.implicitApprovals != null) {
codeOwnersConfig.setBoolean(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_ENABLE_IMPLICIT_APPROVALS,
input.implicitApprovals);
}
if (input.overrideInfoUrl != null) {
codeOwnersConfig.setString(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_OVERRIDE_INFO_URL,
input.overrideInfoUrl);
}
if (input.invalidCodeOwnerConfigInfoUrl != null) {
codeOwnersConfig.setString(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_INVALID_CODE_OWNER_CONFIG_INFO_URL,
input.invalidCodeOwnerConfigInfoUrl);
}
if (input.readOnly != null) {
codeOwnersConfig.setBoolean(
SECTION_CODE_OWNERS, /* subsection= */ null, KEY_READ_ONLY, input.readOnly);
}
if (input.exemptPureReverts != null) {
codeOwnersConfig.setBoolean(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_EXEMPT_PURE_REVERTS,
input.exemptPureReverts);
}
if (input.enableValidationOnBranchCreation != null) {
codeOwnersConfig.setEnum(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION,
input.enableValidationOnBranchCreation);
}
if (input.enableValidationOnCommitReceived != null) {
codeOwnersConfig.setEnum(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_ENABLE_VALIDATION_ON_COMMIT_RECEIVED,
input.enableValidationOnCommitReceived);
}
if (input.enableValidationOnSubmit != null) {
codeOwnersConfig.setEnum(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_ENABLE_VALIDATION_ON_SUBMIT,
input.enableValidationOnSubmit);
}
if (input.rejectNonResolvableCodeOwners != null) {
codeOwnersConfig.setBoolean(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
input.rejectNonResolvableCodeOwners);
}
if (input.rejectNonResolvableImports != null) {
codeOwnersConfig.setBoolean(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_REJECT_NON_RESOLVABLE_IMPORTS,
input.rejectNonResolvableImports);
}
if (input.maxPathsInChangeMessages != null) {
codeOwnersConfig.setInt(
SECTION_CODE_OWNERS,
/* subsection= */ null,
KEY_MAX_PATHS_IN_CHANGE_MESSAGES,
input.maxPathsInChangeMessages);
}
validateConfig(projectResource.getProjectState(), codeOwnersConfig);
codeOwnersProjectConfigFile.commit(metaDataUpdate);
projectCache.evictAndReindex(projectResource.getNameKey());
}
CodeOwnerProjectConfigInfo updatedCodeOwnerProjectConfigInfo =
codeOwnerProjectConfigJson.format(projectResource);
return Response.created(updatedCodeOwnerProjectConfigInfo);
}
private void validateConfig(ProjectState projectState, Config codeOwnersConfig)
throws BadRequestException {
ImmutableList<CommitValidationMessage> validationMessages =
codeOwnersPluginConfigValidator.validateConfig(
projectState, CodeOwnersProjectConfigFile.FILE_NAME, codeOwnersConfig);
if (!validationMessages.isEmpty()) {
StringBuilder exceptionMessage = new StringBuilder();
exceptionMessage.append("invalid config:\n");
validationMessages.forEach(
validationMessage ->
exceptionMessage.append(String.format("* %s\n", validationMessage.getMessage())));
throw new BadRequestException(exceptionMessage.toString());
}
}
}