LCOV - code coverage report
Current view: top level - acceptance/testsuite/project - ProjectOperationsImpl.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 136 150 90.7 %
Date: 2022-11-19 15:00:39 Functions: 32 32 100.0 %

          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             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750