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