LCOV - code coverage report
Current view: top level - server/restapi/project - CreateProject.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 96 101 95.0 %
Date: 2022-11-19 15:00:39 Functions: 8 9 88.9 %

          Line data    Source code
       1             : // Copyright (C) 2013 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 static java.util.Objects.requireNonNull;
      18             : 
      19             : import com.google.common.base.MoreObjects;
      20             : import com.google.common.base.Strings;
      21             : import com.google.common.collect.ImmutableList;
      22             : import com.google.common.collect.Lists;
      23             : import com.google.gerrit.common.data.GlobalCapability;
      24             : import com.google.gerrit.entities.RefNames;
      25             : import com.google.gerrit.extensions.annotations.RequiresCapability;
      26             : import com.google.gerrit.extensions.api.projects.ConfigInput;
      27             : import com.google.gerrit.extensions.api.projects.ProjectInput;
      28             : import com.google.gerrit.extensions.client.InheritableBoolean;
      29             : import com.google.gerrit.extensions.client.SubmitType;
      30             : import com.google.gerrit.extensions.common.ProjectInfo;
      31             : import com.google.gerrit.extensions.restapi.BadRequestException;
      32             : import com.google.gerrit.extensions.restapi.IdString;
      33             : import com.google.gerrit.extensions.restapi.ResourceConflictException;
      34             : import com.google.gerrit.extensions.restapi.Response;
      35             : import com.google.gerrit.extensions.restapi.RestApiException;
      36             : import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
      37             : import com.google.gerrit.extensions.restapi.TopLevelResource;
      38             : import com.google.gerrit.server.ProjectUtil;
      39             : import com.google.gerrit.server.config.AllProjectsName;
      40             : import com.google.gerrit.server.config.AllUsersName;
      41             : import com.google.gerrit.server.config.GerritServerConfig;
      42             : import com.google.gerrit.server.config.ProjectOwnerGroupsProvider;
      43             : import com.google.gerrit.server.group.GroupResolver;
      44             : import com.google.gerrit.server.permissions.PermissionBackendException;
      45             : import com.google.gerrit.server.plugincontext.PluginItemContext;
      46             : import com.google.gerrit.server.plugincontext.PluginSetContext;
      47             : import com.google.gerrit.server.project.CreateProjectArgs;
      48             : import com.google.gerrit.server.project.ProjectConfig;
      49             : import com.google.gerrit.server.project.ProjectCreator;
      50             : import com.google.gerrit.server.project.ProjectJson;
      51             : import com.google.gerrit.server.project.ProjectNameLockManager;
      52             : import com.google.gerrit.server.project.ProjectResource;
      53             : import com.google.gerrit.server.project.ProjectState;
      54             : import com.google.gerrit.server.validators.ProjectCreationValidationListener;
      55             : import com.google.gerrit.server.validators.ValidationException;
      56             : import com.google.inject.Inject;
      57             : import com.google.inject.Provider;
      58             : import com.google.inject.Singleton;
      59             : import java.io.IOException;
      60             : import java.util.ArrayList;
      61             : import java.util.List;
      62             : import java.util.concurrent.locks.Lock;
      63             : import org.eclipse.jgit.errors.ConfigInvalidException;
      64             : import org.eclipse.jgit.lib.Config;
      65             : import org.eclipse.jgit.lib.Constants;
      66             : import org.eclipse.jgit.lib.Repository;
      67             : 
      68             : @RequiresCapability(GlobalCapability.CREATE_PROJECT)
      69             : @Singleton
      70             : public class CreateProject
      71             :     implements RestCollectionCreateView<TopLevelResource, ProjectResource, ProjectInput> {
      72             :   private final Provider<ProjectsCollection> projectsCollection;
      73             :   private final Provider<GroupResolver> groupResolver;
      74             :   private final PluginSetContext<ProjectCreationValidationListener>
      75             :       projectCreationValidationListeners;
      76             :   private final ProjectJson json;
      77             :   private final ProjectOwnerGroupsProvider.Factory projectOwnerGroups;
      78             :   private final Provider<PutConfig> putConfig;
      79             :   private final AllProjectsName allProjects;
      80             :   private final AllUsersName allUsers;
      81             :   private final PluginItemContext<ProjectNameLockManager> lockManager;
      82             :   private final ProjectCreator projectCreator;
      83             : 
      84             :   private final Config gerritConfig;
      85             : 
      86             :   @Inject
      87             :   CreateProject(
      88             :       ProjectCreator projectCreator,
      89             :       Provider<ProjectsCollection> projectsCollection,
      90             :       Provider<GroupResolver> groupResolver,
      91             :       ProjectJson json,
      92             :       PluginSetContext<ProjectCreationValidationListener> projectCreationValidationListeners,
      93             :       ProjectOwnerGroupsProvider.Factory projectOwnerGroups,
      94             :       Provider<PutConfig> putConfig,
      95             :       AllProjectsName allProjects,
      96             :       AllUsersName allUsers,
      97             :       PluginItemContext<ProjectNameLockManager> lockManager,
      98         146 :       @GerritServerConfig Config gerritConfig) {
      99         146 :     this.projectsCollection = projectsCollection;
     100         146 :     this.projectCreator = projectCreator;
     101         146 :     this.groupResolver = groupResolver;
     102         146 :     this.projectCreationValidationListeners = projectCreationValidationListeners;
     103         146 :     this.json = json;
     104         146 :     this.projectOwnerGroups = projectOwnerGroups;
     105         146 :     this.putConfig = putConfig;
     106         146 :     this.allProjects = allProjects;
     107         146 :     this.allUsers = allUsers;
     108         146 :     this.lockManager = lockManager;
     109         146 :     this.gerritConfig = gerritConfig;
     110         146 :   }
     111             : 
     112             :   @Override
     113             :   public Response<ProjectInfo> apply(TopLevelResource resource, IdString id, ProjectInput input)
     114             :       throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
     115         143 :     String name = id.get();
     116         143 :     if (input == null) {
     117           0 :       input = new ProjectInput();
     118             :     }
     119         143 :     if (input.name != null && !name.equals(input.name)) {
     120           1 :       throw new BadRequestException("name must match URL");
     121             :     }
     122             : 
     123         143 :     CreateProjectArgs args = new CreateProjectArgs();
     124         143 :     args.setProjectName(ProjectUtil.sanitizeProjectName(name));
     125             : 
     126         143 :     String parentName =
     127         143 :         MoreObjects.firstNonNull(Strings.emptyToNull(input.parent), allProjects.get());
     128         143 :     args.newParent = projectsCollection.get().parse(parentName, false).getNameKey();
     129         143 :     if (args.newParent.equals(allUsers)) {
     130           1 :       throw new ResourceConflictException(
     131           1 :           String.format("Cannot inherit from '%s' project", allUsers.get()));
     132             :     }
     133         143 :     args.createEmptyCommit = input.createEmptyCommit;
     134         143 :     args.permissionsOnly = input.permissionsOnly;
     135         143 :     args.projectDescription = Strings.emptyToNull(input.description);
     136         143 :     args.submitType = input.submitType;
     137         143 :     args.branch = normalizeBranchNames(input.branches);
     138         143 :     if (input.owners == null || input.owners.isEmpty()) {
     139         143 :       args.ownerIds = new ArrayList<>(projectOwnerGroups.create(args.getProject()).get());
     140             :     } else {
     141           3 :       args.ownerIds = Lists.newArrayListWithCapacity(input.owners.size());
     142           3 :       for (String owner : input.owners) {
     143           3 :         args.ownerIds.add(groupResolver.get().parse(owner).getGroupUUID());
     144           3 :       }
     145             :     }
     146         143 :     args.contributorAgreements =
     147         143 :         MoreObjects.firstNonNull(input.useContributorAgreements, InheritableBoolean.INHERIT);
     148         143 :     args.signedOffBy = MoreObjects.firstNonNull(input.useSignedOffBy, InheritableBoolean.INHERIT);
     149         143 :     args.contentMerge =
     150         143 :         input.submitType == SubmitType.FAST_FORWARD_ONLY
     151           1 :             ? InheritableBoolean.FALSE
     152         143 :             : MoreObjects.firstNonNull(input.useContentMerge, InheritableBoolean.INHERIT);
     153         143 :     args.newChangeForAllNotInTarget =
     154         143 :         MoreObjects.firstNonNull(
     155             :             input.createNewChangeForAllNotInTarget, InheritableBoolean.INHERIT);
     156         143 :     args.changeIdRequired =
     157         143 :         MoreObjects.firstNonNull(input.requireChangeId, InheritableBoolean.INHERIT);
     158         143 :     args.rejectEmptyCommit =
     159         143 :         MoreObjects.firstNonNull(input.rejectEmptyCommit, InheritableBoolean.INHERIT);
     160         143 :     args.enableSignedPush =
     161         143 :         MoreObjects.firstNonNull(input.enableSignedPush, InheritableBoolean.INHERIT);
     162         143 :     args.requireSignedPush =
     163         143 :         MoreObjects.firstNonNull(input.requireSignedPush, InheritableBoolean.INHERIT);
     164             :     try {
     165         143 :       args.maxObjectSizeLimit = ProjectConfig.validMaxObjectSizeLimit(input.maxObjectSizeLimit);
     166           0 :     } catch (ConfigInvalidException e) {
     167           0 :       throw new BadRequestException(e.getMessage());
     168         143 :     }
     169             : 
     170         143 :     Lock nameLock = lockManager.call(lockManager -> lockManager.getLock(args.getProject()));
     171         143 :     nameLock.lock();
     172             :     try {
     173             :       try {
     174         143 :         projectCreationValidationListeners.runEach(
     175         143 :             l -> l.validateNewProject(args), ValidationException.class);
     176           1 :       } catch (ValidationException e) {
     177           1 :         throw new ResourceConflictException(e.getMessage(), e);
     178         143 :       }
     179             : 
     180         143 :       ProjectState projectState = projectCreator.createProject(args);
     181         143 :       requireNonNull(
     182             :           projectState,
     183           0 :           () -> String.format("failed to create project %s", args.getProject().get()));
     184             : 
     185         143 :       if (input.pluginConfigValues != null) {
     186           1 :         ConfigInput in = new ConfigInput();
     187           1 :         in.pluginConfigValues = input.pluginConfigValues;
     188           1 :         in.description = args.projectDescription;
     189           1 :         putConfig.get().apply(projectState, in);
     190             :       }
     191         143 :       return Response.created(json.format(projectState));
     192             :     } finally {
     193         143 :       nameLock.unlock();
     194             :     }
     195             :   }
     196             : 
     197             :   private ImmutableList<String> normalizeBranchNames(List<String> branches)
     198             :       throws BadRequestException {
     199         143 :     if (branches == null || branches.isEmpty()) {
     200             :       // Use host-level default for HEAD or fall back to 'master' if nothing else was specified in
     201             :       // the input.
     202          23 :       String defaultBranch = gerritConfig.getString("gerrit", null, "defaultBranch");
     203             :       defaultBranch =
     204          23 :           defaultBranch != null
     205           3 :               ? normalizeAndValidateBranch(defaultBranch)
     206          23 :               : Constants.R_HEADS + Constants.MASTER;
     207          23 :       return ImmutableList.of(defaultBranch);
     208             :     }
     209         132 :     List<String> normalizedBranches = new ArrayList<>();
     210         132 :     for (String branch : branches) {
     211         132 :       branch = normalizeAndValidateBranch(branch);
     212         132 :       if (!normalizedBranches.contains(branch)) {
     213         132 :         normalizedBranches.add(branch);
     214             :       }
     215         132 :     }
     216         132 :     return ImmutableList.copyOf(normalizedBranches);
     217             :   }
     218             : 
     219             :   private String normalizeAndValidateBranch(String branch) throws BadRequestException {
     220         132 :     while (branch.startsWith("/")) {
     221           0 :       branch = branch.substring(1);
     222             :     }
     223         132 :     branch = RefNames.fullName(branch);
     224         132 :     if (!Repository.isValidRefName(branch)) {
     225           1 :       throw new BadRequestException(String.format("Branch \"%s\" is not a valid name.", branch));
     226             :     }
     227         132 :     return branch;
     228             :   }
     229             : 
     230         143 :   static class ValidBranchListener implements ProjectCreationValidationListener {
     231             :     @Override
     232             :     public void validateNewProject(CreateProjectArgs args) throws ValidationException {
     233         143 :       for (String branch : args.branch) {
     234         143 :         if (RefNames.isGerritRef(branch)) {
     235           1 :           throw new ValidationException(
     236           1 :               String.format(
     237             :                   "Cannot create a project with branch %s. Branches in the Gerrit internal refs namespace are not allowed",
     238             :                   branch));
     239             :         }
     240         143 :       }
     241         143 :     }
     242             :   }
     243             : }

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