Line data Source code
1 : // Copyright (C) 2022 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.restapi.project; 16 : 17 : import com.google.common.base.Strings; 18 : import com.google.gerrit.entities.SubmitRequirement; 19 : import com.google.gerrit.entities.SubmitRequirementExpression; 20 : import com.google.gerrit.extensions.common.SubmitRequirementInfo; 21 : import com.google.gerrit.extensions.common.SubmitRequirementInput; 22 : import com.google.gerrit.extensions.restapi.AuthException; 23 : import com.google.gerrit.extensions.restapi.BadRequestException; 24 : import com.google.gerrit.extensions.restapi.IdString; 25 : import com.google.gerrit.extensions.restapi.ResourceConflictException; 26 : import com.google.gerrit.extensions.restapi.Response; 27 : import com.google.gerrit.extensions.restapi.RestCollectionCreateView; 28 : import com.google.gerrit.server.CurrentUser; 29 : import com.google.gerrit.server.git.meta.MetaDataUpdate; 30 : import com.google.gerrit.server.permissions.PermissionBackend; 31 : import com.google.gerrit.server.permissions.PermissionBackendException; 32 : import com.google.gerrit.server.permissions.ProjectPermission; 33 : import com.google.gerrit.server.project.ProjectCache; 34 : import com.google.gerrit.server.project.ProjectConfig; 35 : import com.google.gerrit.server.project.ProjectResource; 36 : import com.google.gerrit.server.project.SubmitRequirementExpressionsValidator; 37 : import com.google.gerrit.server.project.SubmitRequirementJson; 38 : import com.google.gerrit.server.project.SubmitRequirementResource; 39 : import com.google.gerrit.server.project.SubmitRequirementsUtil; 40 : import com.google.inject.Inject; 41 : import com.google.inject.Provider; 42 : import com.google.inject.Singleton; 43 : import java.io.IOException; 44 : import java.util.List; 45 : import java.util.Optional; 46 : import org.eclipse.jgit.errors.ConfigInvalidException; 47 : 48 : /** A rest create view that creates a "submit requirement" for a project. */ 49 : @Singleton 50 : public class CreateSubmitRequirement 51 : implements RestCollectionCreateView< 52 : ProjectResource, SubmitRequirementResource, SubmitRequirementInput> { 53 : private final Provider<CurrentUser> user; 54 : private final PermissionBackend permissionBackend; 55 : private final MetaDataUpdate.User updateFactory; 56 : private final ProjectConfig.Factory projectConfigFactory; 57 : private final ProjectCache projectCache; 58 : private final SubmitRequirementExpressionsValidator submitRequirementExpressionsValidator; 59 : 60 : @Inject 61 : public CreateSubmitRequirement( 62 : Provider<CurrentUser> user, 63 : PermissionBackend permissionBackend, 64 : MetaDataUpdate.User updateFactory, 65 : ProjectConfig.Factory projectConfigFactory, 66 : ProjectCache projectCache, 67 138 : SubmitRequirementExpressionsValidator submitRequirementExpressionsValidator) { 68 138 : this.user = user; 69 138 : this.permissionBackend = permissionBackend; 70 138 : this.updateFactory = updateFactory; 71 138 : this.projectConfigFactory = projectConfigFactory; 72 138 : this.projectCache = projectCache; 73 138 : this.submitRequirementExpressionsValidator = submitRequirementExpressionsValidator; 74 138 : } 75 : 76 : @Override 77 : public Response<SubmitRequirementInfo> apply( 78 : ProjectResource rsrc, IdString id, SubmitRequirementInput input) 79 : throws AuthException, BadRequestException, IOException, PermissionBackendException { 80 3 : if (!user.get().isIdentifiedUser()) { 81 1 : throw new AuthException("Authentication required"); 82 : } 83 : 84 3 : permissionBackend 85 3 : .currentUser() 86 3 : .project(rsrc.getNameKey()) 87 3 : .check(ProjectPermission.WRITE_CONFIG); 88 : 89 3 : if (input == null) { 90 0 : input = new SubmitRequirementInput(); 91 : } 92 : 93 3 : if (input.name != null && !input.name.equals(id.get())) { 94 1 : throw new BadRequestException("name in input must match name in URL"); 95 : } 96 : 97 3 : try (MetaDataUpdate md = updateFactory.create(rsrc.getNameKey())) { 98 3 : ProjectConfig config = projectConfigFactory.read(md); 99 : 100 2 : SubmitRequirement submitRequirement = createSubmitRequirement(config, id.get(), input); 101 : 102 2 : md.setMessage(String.format("Create Submit Requirement %s", submitRequirement.name())); 103 2 : config.commit(md); 104 : 105 2 : projectCache.evict(rsrc.getProjectState().getProject().getNameKey()); 106 : 107 2 : return Response.created(SubmitRequirementJson.format(submitRequirement)); 108 0 : } catch (ConfigInvalidException e) { 109 0 : throw new IOException("Failed to read project config", e); 110 0 : } catch (ResourceConflictException e) { 111 0 : throw new BadRequestException("Failed to create submit requirement", e); 112 : } 113 : } 114 : 115 : public SubmitRequirement createSubmitRequirement( 116 : ProjectConfig config, String name, SubmitRequirementInput input) 117 : throws BadRequestException, ResourceConflictException { 118 3 : validateSRName(name); 119 3 : ensureSRUnique(name, config); 120 3 : if (Strings.isNullOrEmpty(input.submittabilityExpression)) { 121 2 : throw new BadRequestException("submittability_expression is required"); 122 : } 123 2 : if (input.allowOverrideInChildProjects == null) { 124 : // default is false 125 1 : input.allowOverrideInChildProjects = false; 126 : } 127 : SubmitRequirement submitRequirement = 128 2 : SubmitRequirement.builder() 129 2 : .setName(name) 130 2 : .setDescription(Optional.ofNullable(input.description)) 131 2 : .setApplicabilityExpression( 132 2 : SubmitRequirementExpression.of(input.applicabilityExpression)) 133 2 : .setSubmittabilityExpression( 134 2 : SubmitRequirementExpression.create(input.submittabilityExpression)) 135 2 : .setOverrideExpression(SubmitRequirementExpression.of(input.overrideExpression)) 136 2 : .setAllowOverrideInChildProjects(input.allowOverrideInChildProjects) 137 2 : .build(); 138 : 139 2 : List<String> validationMessages = 140 2 : submitRequirementExpressionsValidator.validateExpressions(submitRequirement); 141 2 : if (!validationMessages.isEmpty()) { 142 1 : throw new BadRequestException( 143 1 : String.format("Invalid submit requirement input: %s", validationMessages)); 144 : } 145 : 146 2 : config.upsertSubmitRequirement(submitRequirement); 147 2 : return submitRequirement; 148 : } 149 : 150 : private void validateSRName(String name) throws BadRequestException { 151 : try { 152 3 : SubmitRequirementsUtil.validateName(name); 153 1 : } catch (IllegalArgumentException e) { 154 1 : throw new BadRequestException(e.getMessage(), e); 155 3 : } 156 3 : } 157 : 158 : private void ensureSRUnique(String name, ProjectConfig config) throws ResourceConflictException { 159 3 : for (String srName : config.getSubmitRequirementSections().keySet()) { 160 1 : if (srName.equalsIgnoreCase(name)) { 161 0 : throw new ResourceConflictException( 162 0 : String.format( 163 : "submit requirement \"%s\" conflicts with existing submit requirement \"%s\"", 164 : name, srName)); 165 : } 166 1 : } 167 3 : } 168 : }