Line data Source code
1 : // Copyright (C) 2018 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.acceptance.testsuite.project; 16 : 17 : import static com.google.common.collect.ImmutableList.toImmutableList; 18 : import static com.google.gerrit.entities.RefNames.REFS_CONFIG; 19 : import static com.google.gerrit.server.project.ProjectConfig.PROJECT_CONFIG; 20 : import static java.nio.charset.StandardCharsets.UTF_8; 21 : import static java.util.Objects.requireNonNull; 22 : 23 : import com.google.common.base.Throwables; 24 : import com.google.common.collect.ImmutableList; 25 : import com.google.common.collect.ImmutableMap; 26 : import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.TestCapability; 27 : import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.TestLabelPermission; 28 : import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.TestPermission; 29 : import com.google.gerrit.common.Nullable; 30 : import com.google.gerrit.entities.AccessSection; 31 : import com.google.gerrit.entities.AccountGroup; 32 : import com.google.gerrit.entities.GroupReference; 33 : import com.google.gerrit.entities.Permission; 34 : import com.google.gerrit.entities.PermissionRule; 35 : import com.google.gerrit.entities.Project; 36 : import com.google.gerrit.entities.RefNames; 37 : import com.google.gerrit.server.config.AllProjectsName; 38 : import com.google.gerrit.server.git.GitRepositoryManager; 39 : import com.google.gerrit.server.git.meta.MetaDataUpdate; 40 : import com.google.gerrit.server.project.CreateProjectArgs; 41 : import com.google.gerrit.server.project.ProjectCache; 42 : import com.google.gerrit.server.project.ProjectConfig; 43 : import com.google.gerrit.server.project.ProjectCreator; 44 : import com.google.inject.Inject; 45 : import java.io.IOException; 46 : import java.util.ArrayList; 47 : import org.apache.commons.lang3.RandomStringUtils; 48 : import org.eclipse.jgit.errors.ConfigInvalidException; 49 : import org.eclipse.jgit.junit.TestRepository; 50 : import org.eclipse.jgit.lib.Config; 51 : import org.eclipse.jgit.lib.ObjectLoader; 52 : import org.eclipse.jgit.lib.Ref; 53 : import org.eclipse.jgit.lib.Repository; 54 : import org.eclipse.jgit.revwalk.RevCommit; 55 : import org.eclipse.jgit.revwalk.RevTree; 56 : import org.eclipse.jgit.revwalk.RevWalk; 57 : import org.eclipse.jgit.treewalk.TreeWalk; 58 : 59 : public class ProjectOperationsImpl implements ProjectOperations { 60 : private final AllProjectsName allProjectsName; 61 : private final GitRepositoryManager repoManager; 62 : private final MetaDataUpdate.Server metaDataUpdateFactory; 63 : private final ProjectCache projectCache; 64 : private final ProjectConfig.Factory projectConfigFactory; 65 : private final ProjectCreator projectCreator; 66 : 67 : @Inject 68 : ProjectOperationsImpl( 69 : AllProjectsName allProjectsName, 70 : GitRepositoryManager repoManager, 71 : MetaDataUpdate.Server metaDataUpdateFactory, 72 : ProjectCache projectCache, 73 : ProjectConfig.Factory projectConfigFactory, 74 139 : ProjectCreator projectCreator) { 75 139 : this.allProjectsName = allProjectsName; 76 139 : this.repoManager = repoManager; 77 139 : this.metaDataUpdateFactory = metaDataUpdateFactory; 78 139 : this.projectCache = projectCache; 79 139 : this.projectConfigFactory = projectConfigFactory; 80 139 : this.projectCreator = projectCreator; 81 139 : } 82 : 83 : @Override 84 : public TestProjectCreation.Builder newProject() { 85 35 : return TestProjectCreation.builder(this::createNewProject); 86 : } 87 : 88 : private Project.NameKey createNewProject(TestProjectCreation projectCreation) throws Exception { 89 35 : String name = projectCreation.name().orElse(RandomStringUtils.randomAlphabetic(8)); 90 : 91 35 : CreateProjectArgs args = new CreateProjectArgs(); 92 35 : args.setProjectName(name); 93 35 : args.permissionsOnly = projectCreation.permissionOnly().orElse(false); 94 35 : args.branch = 95 35 : projectCreation.branches().stream().map(RefNames::fullName).collect(toImmutableList()); 96 35 : args.createEmptyCommit = projectCreation.createEmptyCommit().orElse(true); 97 35 : projectCreation.parent().ifPresent(p -> args.newParent = p); 98 : // ProjectCreator wants non-null owner IDs. 99 35 : args.ownerIds = new ArrayList<>(projectCreation.owners()); 100 35 : projectCreation.submitType().ifPresent(st -> args.submitType = st); 101 35 : projectCreator.createProject(args); 102 35 : return Project.nameKey(name); 103 : } 104 : 105 : @Override 106 : public ProjectOperations.PerProjectOperations project(Project.NameKey key) { 107 85 : return new PerProjectOperations(key); 108 : } 109 : 110 : @Override 111 : public TestProjectUpdate.Builder allProjectsForUpdate() { 112 19 : return project(allProjectsName).forUpdate(); 113 : } 114 : 115 : private class PerProjectOperations implements ProjectOperations.PerProjectOperations { 116 : Project.NameKey nameKey; 117 : 118 85 : PerProjectOperations(Project.NameKey nameKey) { 119 85 : this.nameKey = nameKey; 120 85 : } 121 : 122 : @Override 123 : public RevCommit getHead(String branch) { 124 39 : return requireNonNull(headOrNull(branch)); 125 : } 126 : 127 : @Override 128 : public boolean hasHead(String branch) { 129 8 : return headOrNull(branch) != null; 130 : } 131 : 132 : @Override 133 : public TestProjectUpdate.Builder forUpdate() { 134 81 : return TestProjectUpdate.builder(nameKey, allProjectsName, this::updateProject); 135 : } 136 : 137 : private void updateProject(TestProjectUpdate projectUpdate) 138 : throws IOException, ConfigInvalidException { 139 81 : try (MetaDataUpdate metaDataUpdate = metaDataUpdateFactory.create(nameKey)) { 140 81 : ProjectConfig projectConfig = projectConfigFactory.read(metaDataUpdate); 141 81 : if (projectUpdate.removeAllAccessSections()) { 142 2 : projectConfig.getAccessSections().forEach(as -> projectConfig.remove(as)); 143 : } 144 81 : removePermissions(projectConfig, projectUpdate.removedPermissions()); 145 81 : addCapabilities(projectConfig, projectUpdate.addedCapabilities()); 146 81 : addPermissions(projectConfig, projectUpdate.addedPermissions()); 147 81 : addLabelPermissions(projectConfig, projectUpdate.addedLabelPermissions()); 148 81 : setExclusiveGroupPermissions(projectConfig, projectUpdate.exclusiveGroupPermissions()); 149 81 : projectConfig.commit(metaDataUpdate); 150 : } 151 81 : projectCache.evictAndReindex(nameKey); 152 81 : } 153 : 154 : private void removePermissions( 155 : ProjectConfig projectConfig, 156 : ImmutableList<TestProjectUpdate.TestPermissionKey> removedPermissions) { 157 81 : for (TestProjectUpdate.TestPermissionKey p : removedPermissions) { 158 17 : projectConfig.upsertAccessSection( 159 17 : p.section(), 160 : as -> { 161 17 : Permission.Builder permission = as.upsertPermission(p.name()); 162 17 : if (p.group().isPresent()) { 163 10 : GroupReference group = 164 10 : GroupReference.create(p.group().get(), p.group().get().get()); 165 10 : group = projectConfig.resolve(group); 166 10 : permission.removeRule(group); 167 10 : } else { 168 9 : permission.clearRules(); 169 : } 170 17 : }); 171 17 : } 172 81 : } 173 : 174 : private void addCapabilities( 175 : ProjectConfig projectConfig, ImmutableList<TestCapability> addedCapabilities) { 176 81 : for (TestCapability c : addedCapabilities) { 177 22 : PermissionRule.Builder rule = newRule(projectConfig, c.group()); 178 22 : rule.setRange(c.min(), c.max()); 179 22 : projectConfig.upsertAccessSection( 180 22 : AccessSection.GLOBAL_CAPABILITIES, as -> as.upsertPermission(c.name()).add(rule)); 181 22 : } 182 81 : } 183 : 184 : private void addPermissions( 185 : ProjectConfig projectConfig, ImmutableList<TestPermission> addedPermissions) { 186 81 : for (TestPermission p : addedPermissions) { 187 71 : PermissionRule.Builder rule = newRule(projectConfig, p.group()); 188 71 : rule.setAction(p.action()); 189 71 : rule.setForce(p.force()); 190 71 : projectConfig.upsertAccessSection(p.ref(), as -> as.upsertPermission(p.name()).add(rule)); 191 71 : } 192 81 : } 193 : 194 : private void addLabelPermissions( 195 : ProjectConfig projectConfig, ImmutableList<TestLabelPermission> addedLabelPermissions) { 196 81 : for (TestLabelPermission p : addedLabelPermissions) { 197 32 : PermissionRule.Builder rule = newRule(projectConfig, p.group()); 198 32 : rule.setAction(p.action()); 199 32 : rule.setRange(p.min(), p.max()); 200 : String permissionName = 201 32 : p.impersonation() ? Permission.forLabelAs(p.name()) : Permission.forLabel(p.name()); 202 32 : projectConfig.upsertAccessSection( 203 32 : p.ref(), as -> as.upsertPermission(permissionName).add(rule)); 204 32 : } 205 81 : } 206 : 207 : private void setExclusiveGroupPermissions( 208 : ProjectConfig projectConfig, 209 : ImmutableMap<TestProjectUpdate.TestPermissionKey, Boolean> exclusiveGroupPermissions) { 210 81 : exclusiveGroupPermissions.forEach( 211 : (key, exclusive) -> 212 4 : projectConfig.upsertAccessSection( 213 4 : key.section(), 214 4 : as -> as.upsertPermission(key.name()).setExclusiveGroup(exclusive))); 215 81 : } 216 : 217 : @Nullable 218 : private RevCommit headOrNull(String branch) { 219 39 : branch = RefNames.fullName(branch); 220 : 221 39 : try (Repository repo = repoManager.openRepository(nameKey); 222 39 : RevWalk rw = new RevWalk(repo)) { 223 39 : Ref r = repo.exactRef(branch); 224 39 : return r == null ? null : rw.parseCommit(r.getObjectId()); 225 0 : } catch (Exception e) { 226 0 : throw new IllegalStateException(e); 227 : } 228 : } 229 : 230 : @Override 231 : public ProjectConfig getProjectConfig() { 232 2 : try (Repository repo = repoManager.openRepository(nameKey)) { 233 2 : ProjectConfig projectConfig = projectConfigFactory.create(nameKey); 234 2 : projectConfig.load(nameKey, repo); 235 2 : return projectConfig; 236 0 : } catch (Exception e) { 237 0 : throw new IllegalStateException(e); 238 : } 239 : } 240 : 241 : @Override 242 : public Config getConfig() { 243 5 : try (Repository repo = repoManager.openRepository(nameKey); 244 5 : RevWalk rw = new RevWalk(repo)) { 245 5 : Ref ref = repo.exactRef(REFS_CONFIG); 246 5 : if (ref == null) { 247 1 : return new Config(); 248 : } 249 5 : RevTree tree = rw.parseTree(ref.getObjectId()); 250 5 : TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(), PROJECT_CONFIG, tree); 251 5 : if (tw == null) { 252 1 : return new Config(); 253 : } 254 5 : ObjectLoader loader = rw.getObjectReader().open(tw.getObjectId(0)); 255 5 : String text = new String(loader.getCachedBytes(), UTF_8); 256 5 : Config config = new Config(); 257 5 : config.fromText(text); 258 5 : return config; 259 1 : } catch (Exception e) { 260 0 : throw new IllegalStateException(e); 261 : } 262 : } 263 : 264 : private void setConfig(Config projectConfig) { 265 3 : try (TestRepository<Repository> repo = 266 3 : new TestRepository<>(repoManager.openRepository(nameKey))) { 267 3 : repo.update( 268 : RefNames.REFS_CONFIG, 269 3 : repo.commit() 270 3 : .message("Update project.config from test") 271 3 : .parent(getHead(RefNames.REFS_CONFIG)) 272 3 : .add(ProjectConfig.PROJECT_CONFIG, projectConfig.toText())); 273 0 : } catch (Exception e) { 274 0 : throw new IllegalStateException( 275 : "updating project.config of project " + nameKey + " failed", e); 276 3 : } 277 3 : } 278 : 279 : @Override 280 : public TestProjectInvalidation.Builder forInvalidation() { 281 3 : return TestProjectInvalidation.builder(this::invalidateProject); 282 : } 283 : 284 : private void invalidateProject(TestProjectInvalidation testProjectInvalidation) 285 : throws Exception { 286 3 : if (testProjectInvalidation.makeProjectConfigInvalid()) { 287 2 : Config projectConfig = new Config(); 288 2 : projectConfig.fromText(getConfig().toText()); 289 : 290 : // Make the project config invalid by adding a permission entry with an invalid permission 291 : // name. 292 2 : projectConfig.setString( 293 : "access", "refs/*", "Invalid Permission Name", "group Administrators"); 294 : 295 2 : setConfig(projectConfig); 296 : try { 297 0 : projectCache.evictAndReindex(nameKey); 298 2 : } catch (Exception e) { 299 : // Evicting the project from the cache, also triggers a reindex of the project. 300 : // The reindex step fails if the project config is invalid. That's fine, since it was our 301 : // intention to make the project config invalid. Hence we ignore exceptions that are cause 302 : // by an invalid project config here. 303 2 : if (!Throwables.getCausalChain(e).stream() 304 2 : .anyMatch(ConfigInvalidException.class::isInstance)) { 305 0 : throw e; 306 : } 307 0 : } 308 : } 309 3 : if (!testProjectInvalidation.projectConfigUpdater().isEmpty()) { 310 2 : Config projectConfig = new Config(); 311 2 : projectConfig.fromText(getConfig().toText()); 312 2 : testProjectInvalidation.projectConfigUpdater().forEach(c -> c.accept(projectConfig)); 313 2 : setConfig(projectConfig); 314 : try { 315 2 : projectCache.evictAndReindex(nameKey); 316 0 : } catch (Exception e) { 317 : // Evicting the project from the cache, also triggers a reindex of the project. 318 : // The reindex step fails if the project config is invalid. That's fine, since it was our 319 : // intention to make the project config invalid. Hence we ignore exceptions that are cause 320 : // by an invalid project config here. 321 0 : if (!Throwables.getCausalChain(e).stream() 322 0 : .anyMatch(ConfigInvalidException.class::isInstance)) { 323 0 : throw e; 324 : } 325 2 : } 326 : } 327 3 : } 328 : } 329 : 330 : private static PermissionRule.Builder newRule( 331 : ProjectConfig project, AccountGroup.UUID groupUUID) { 332 81 : GroupReference group = GroupReference.create(groupUUID, groupUUID.get()); 333 81 : group = project.resolve(group); 334 81 : return PermissionRule.builder(group); 335 : } 336 : }