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.project; 16 : 17 : import com.google.common.collect.ImmutableList; 18 : import com.google.gerrit.entities.RefNames; 19 : import com.google.gerrit.entities.SubmitRequirement; 20 : import com.google.gerrit.server.events.CommitReceivedEvent; 21 : import com.google.gerrit.server.git.validators.CommitValidationException; 22 : import com.google.gerrit.server.git.validators.CommitValidationListener; 23 : import com.google.gerrit.server.git.validators.CommitValidationMessage; 24 : import com.google.gerrit.server.git.validators.ValidationMessage; 25 : import com.google.gerrit.server.patch.DiffNotAvailableException; 26 : import com.google.gerrit.server.patch.DiffOperations; 27 : import com.google.gerrit.server.patch.DiffOptions; 28 : import com.google.inject.Inject; 29 : import java.io.IOException; 30 : import java.util.List; 31 : import java.util.stream.Collectors; 32 : import org.eclipse.jgit.errors.ConfigInvalidException; 33 : 34 : /** 35 : * Validates the expressions of submit requirements in {@code project.config}. 36 : * 37 : * <p>Other validation of submit requirements is done in {@link ProjectConfig}, see {@code 38 : * ProjectConfig#loadSubmitRequirementSections(Config)}. 39 : * 40 : * <p>The validation of the expressions cannot be in {@link ProjectConfig} as it requires injecting 41 : * {@link SubmitRequirementsEvaluator} and we cannot do injections into {@link ProjectConfig} (since 42 : * {@link ProjectConfig} is cached in the project cache). 43 : */ 44 : public class SubmitRequirementConfigValidator implements CommitValidationListener { 45 : private final DiffOperations diffOperations; 46 : private final ProjectConfig.Factory projectConfigFactory; 47 : private final SubmitRequirementExpressionsValidator submitRequirementExpressionsValidator; 48 : 49 : @Inject 50 : SubmitRequirementConfigValidator( 51 : DiffOperations diffOperations, 52 : ProjectConfig.Factory projectConfigFactory, 53 109 : SubmitRequirementExpressionsValidator submitRequirementExpressionsValidator) { 54 109 : this.diffOperations = diffOperations; 55 109 : this.projectConfigFactory = projectConfigFactory; 56 109 : this.submitRequirementExpressionsValidator = submitRequirementExpressionsValidator; 57 109 : } 58 : 59 : @Override 60 : public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent event) 61 : throws CommitValidationException { 62 : try { 63 109 : if (!event.refName.equals(RefNames.REFS_CONFIG) 64 13 : || !isFileChanged(event, ProjectConfig.PROJECT_CONFIG)) { 65 : // the project.config file in refs/meta/config was not modified, hence we do not need to 66 : // validate the submit requirements in it 67 109 : return ImmutableList.of(); 68 : } 69 : 70 6 : ProjectConfig projectConfig = getProjectConfig(event); 71 6 : ImmutableList.Builder<String> validationMsgsBuilder = ImmutableList.builder(); 72 : for (SubmitRequirement submitRequirement : 73 6 : projectConfig.getSubmitRequirementSections().values()) { 74 2 : validationMsgsBuilder.addAll( 75 2 : submitRequirementExpressionsValidator.validateExpressions(submitRequirement)); 76 2 : } 77 6 : ImmutableList<String> validationMsgs = validationMsgsBuilder.build(); 78 6 : if (!validationMsgs.isEmpty()) { 79 1 : throw new CommitValidationException( 80 1 : String.format( 81 : "invalid submit requirement expressions in %s (revision = %s)", 82 1 : ProjectConfig.PROJECT_CONFIG, projectConfig.getRevision().name()), 83 : new ImmutableList.Builder<CommitValidationMessage>() 84 1 : .add( 85 : new CommitValidationMessage( 86 : "Invalid project configuration", ValidationMessage.Type.ERROR)) 87 1 : .addAll( 88 1 : validationMsgs.stream() 89 1 : .map(m -> toCommitValidationMessage(m)) 90 1 : .collect(Collectors.toList())) 91 1 : .build()); 92 : } 93 6 : return ImmutableList.of(); 94 0 : } catch (IOException | DiffNotAvailableException | ConfigInvalidException e) { 95 0 : throw new CommitValidationException( 96 0 : String.format( 97 : "failed to validate submit requirement expressions in %s for revision %s in ref %s" 98 : + " of project %s", 99 : ProjectConfig.PROJECT_CONFIG, 100 0 : event.commit.getName(), 101 : RefNames.REFS_CONFIG, 102 0 : event.project.getNameKey()), 103 : e); 104 : } 105 : } 106 : 107 : private static CommitValidationMessage toCommitValidationMessage(String message) { 108 1 : return new CommitValidationMessage(message, ValidationMessage.Type.ERROR); 109 : } 110 : 111 : /** 112 : * Whether the given file was changed in the given revision. 113 : * 114 : * @param receiveEvent the receive event 115 : * @param fileName the name of the file 116 : */ 117 : private boolean isFileChanged(CommitReceivedEvent receiveEvent, String fileName) 118 : throws DiffNotAvailableException { 119 13 : return diffOperations 120 13 : .listModifiedFilesAgainstParent( 121 13 : receiveEvent.project.getNameKey(), 122 : receiveEvent.commit, 123 : /* parentNum=*/ 0, 124 : DiffOptions.DEFAULTS) 125 13 : .keySet().stream() 126 13 : .anyMatch(fileName::equals); 127 : } 128 : 129 : private ProjectConfig getProjectConfig(CommitReceivedEvent receiveEvent) 130 : throws IOException, ConfigInvalidException { 131 6 : ProjectConfig projectConfig = projectConfigFactory.create(receiveEvent.project.getNameKey()); 132 6 : projectConfig.load(receiveEvent.revWalk, receiveEvent.commit); 133 6 : return projectConfig; 134 : } 135 : }