You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CreateAction.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.server.project.ws;
  21. import java.util.Optional;
  22. import javax.annotation.CheckForNull;
  23. import javax.annotation.Nullable;
  24. import org.sonar.api.server.ws.Change;
  25. import org.sonar.api.server.ws.Request;
  26. import org.sonar.api.server.ws.Response;
  27. import org.sonar.api.server.ws.WebService;
  28. import org.sonar.db.DbClient;
  29. import org.sonar.db.DbSession;
  30. import org.sonar.db.component.BranchDto;
  31. import org.sonar.db.entity.EntityDto;
  32. import org.sonar.db.project.ProjectDto;
  33. import org.sonar.server.component.ComponentCreationData;
  34. import org.sonar.server.common.component.ComponentCreationParameters;
  35. import org.sonar.server.common.component.ComponentUpdater;
  36. import org.sonar.server.common.component.NewComponent;
  37. import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver;
  38. import org.sonar.server.project.DefaultBranchNameResolver;
  39. import org.sonar.server.project.ProjectDefaultVisibility;
  40. import org.sonar.server.project.Visibility;
  41. import org.sonar.server.user.UserSession;
  42. import org.sonarqube.ws.Projects.CreateWsResponse;
  43. import static java.util.Objects.requireNonNull;
  44. import static org.apache.commons.lang3.StringUtils.abbreviate;
  45. import static org.sonar.api.resources.Qualifiers.PROJECT;
  46. import static org.sonar.core.component.ComponentKeys.MAX_COMPONENT_KEY_LENGTH;
  47. import static org.sonar.db.component.ComponentValidator.MAX_COMPONENT_NAME_LENGTH;
  48. import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
  49. import static org.sonar.db.project.CreationMethod.Category.LOCAL;
  50. import static org.sonar.db.project.CreationMethod.getCreationMethod;
  51. import static org.sonar.server.common.component.NewComponent.newComponentBuilder;
  52. import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION;
  53. import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION;
  54. import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam;
  55. import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
  56. import static org.sonar.server.ws.WsUtils.writeProtobuf;
  57. import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_CREATE;
  58. import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_MAIN_BRANCH;
  59. import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NAME;
  60. import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE;
  61. import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE;
  62. import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT;
  63. import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_VISIBILITY;
  64. public class CreateAction implements ProjectsWsAction {
  65. private final DbClient dbClient;
  66. private final UserSession userSession;
  67. private final ComponentUpdater componentUpdater;
  68. private final ProjectDefaultVisibility projectDefaultVisibility;
  69. private final DefaultBranchNameResolver defaultBranchNameResolver;
  70. private final NewCodeDefinitionResolver newCodeDefinitionResolver;
  71. public CreateAction(DbClient dbClient, UserSession userSession, ComponentUpdater componentUpdater,
  72. ProjectDefaultVisibility projectDefaultVisibility, DefaultBranchNameResolver defaultBranchNameResolver, NewCodeDefinitionResolver newCodeDefinitionResolver) {
  73. this.dbClient = dbClient;
  74. this.userSession = userSession;
  75. this.componentUpdater = componentUpdater;
  76. this.projectDefaultVisibility = projectDefaultVisibility;
  77. this.defaultBranchNameResolver = defaultBranchNameResolver;
  78. this.newCodeDefinitionResolver = newCodeDefinitionResolver;
  79. }
  80. @Override
  81. public void define(WebService.NewController context) {
  82. WebService.NewAction action = context.createAction(ACTION_CREATE)
  83. .setDescription("Create a project.<br/>" +
  84. "If your project is hosted on a DevOps Platform, please use the import endpoint under api/alm_integrations, so it creates and properly configures the project." +
  85. "Requires 'Create Projects' permission.<br/>")
  86. .setSince("4.0")
  87. .setPost(true)
  88. .setResponseExample(getClass().getResource("create-example.json"))
  89. .setHandler(this);
  90. action.setChangelog(
  91. new Change("9.8", "Field 'mainBranch' added to the request"),
  92. new Change("7.1", "The 'visibility' parameter is public"));
  93. action.createParam(PARAM_PROJECT)
  94. .setDescription("Key of the project")
  95. .setRequired(true)
  96. .setMaximumLength(MAX_COMPONENT_KEY_LENGTH)
  97. .setExampleValue(KEY_PROJECT_EXAMPLE_001);
  98. action.createParam(PARAM_NAME)
  99. .setDescription("Name of the project. If name is longer than %d, it is abbreviated.", MAX_COMPONENT_NAME_LENGTH)
  100. .setRequired(true)
  101. .setExampleValue("SonarQube");
  102. action.createParam(PARAM_MAIN_BRANCH)
  103. .setDescription("Key of the main branch of the project. If not provided, the default main branch key will be used.")
  104. .setSince("9.8")
  105. .setExampleValue("develop");
  106. action.createParam(PARAM_VISIBILITY)
  107. .setDescription("Whether the created project should be visible to everyone, or only specific user/groups.<br/>" +
  108. "If no visibility is specified, the default project visibility will be used.")
  109. .setSince("6.4")
  110. .setPossibleValues(Visibility.getLabels());
  111. action.createParam(PARAM_NEW_CODE_DEFINITION_TYPE)
  112. .setDescription(NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION)
  113. .setSince("10.1");
  114. action.createParam(PARAM_NEW_CODE_DEFINITION_VALUE)
  115. .setDescription(NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION)
  116. .setSince("10.1");
  117. }
  118. @Override
  119. public void handle(Request request, Response response) throws Exception {
  120. CreateRequest createRequest = toCreateRequest(request);
  121. writeProtobuf(doHandle(createRequest), request, response);
  122. }
  123. private CreateWsResponse doHandle(CreateRequest request) {
  124. try (DbSession dbSession = dbClient.openSession(false)) {
  125. userSession.checkPermission(PROVISION_PROJECTS);
  126. checkNewCodeDefinitionParam(request.getNewCodeDefinitionType(), request.getNewCodeDefinitionValue());
  127. ComponentCreationData componentData = createProject(request, dbSession);
  128. ProjectDto projectDto = Optional.ofNullable(componentData.projectDto()).orElseThrow();
  129. BranchDto mainBranchDto = Optional.ofNullable(componentData.mainBranchDto()).orElseThrow();
  130. if (request.getNewCodeDefinitionType() != null) {
  131. String defaultBranchName = Optional.ofNullable(request.getMainBranchKey()).orElse(defaultBranchNameResolver.getEffectiveMainBranchName());
  132. newCodeDefinitionResolver.createNewCodeDefinition(dbSession, projectDto.getUuid(),
  133. mainBranchDto.getUuid(), defaultBranchName, request.getNewCodeDefinitionType(),
  134. request.getNewCodeDefinitionValue());
  135. }
  136. componentUpdater.commitAndIndex(dbSession, componentData);
  137. return toCreateResponse(projectDto);
  138. }
  139. }
  140. private ComponentCreationData createProject(CreateRequest request, DbSession dbSession) {
  141. String visibility = request.getVisibility();
  142. boolean changeToPrivate = visibility == null ? projectDefaultVisibility.get(dbSession).isPrivate() : "private".equals(visibility);
  143. NewComponent newProject = newComponentBuilder()
  144. .setKey(request.getProjectKey())
  145. .setName(request.getName())
  146. .setPrivate(changeToPrivate)
  147. .setQualifier(PROJECT)
  148. .build();
  149. ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder()
  150. .newComponent(newProject)
  151. .userUuid(userSession.getUuid())
  152. .userLogin(userSession.getLogin())
  153. .mainBranchName(request.getMainBranchKey())
  154. .creationMethod(getCreationMethod(LOCAL, userSession.isAuthenticatedBrowserSession()))
  155. .build();
  156. return componentUpdater.createWithoutCommit(dbSession, componentCreationParameters);
  157. }
  158. private static CreateRequest toCreateRequest(Request request) {
  159. return CreateRequest.builder()
  160. .setProjectKey(request.mandatoryParam(PARAM_PROJECT))
  161. .setName(abbreviate(request.mandatoryParam(PARAM_NAME), MAX_COMPONENT_NAME_LENGTH))
  162. .setVisibility(request.param(PARAM_VISIBILITY))
  163. .setMainBranchKey(request.param(PARAM_MAIN_BRANCH))
  164. .setNewCodeDefinitionType(request.param(PARAM_NEW_CODE_DEFINITION_TYPE))
  165. .setNewCodeDefinitionValue(request.param(PARAM_NEW_CODE_DEFINITION_VALUE))
  166. .build();
  167. }
  168. private static CreateWsResponse toCreateResponse(EntityDto project) {
  169. return CreateWsResponse.newBuilder()
  170. .setProject(CreateWsResponse.Project.newBuilder()
  171. .setKey(project.getKey())
  172. .setName(project.getName())
  173. .setQualifier(project.getQualifier())
  174. .setVisibility(Visibility.getLabel(project.isPrivate())))
  175. .build();
  176. }
  177. static class CreateRequest {
  178. private final String projectKey;
  179. private final String name;
  180. private final String mainBranchKey;
  181. @CheckForNull
  182. private final String visibility;
  183. @CheckForNull
  184. private final String newCodeDefinitionType;
  185. @CheckForNull
  186. private final String newCodeDefinitionValue;
  187. private CreateRequest(Builder builder) {
  188. this.projectKey = builder.projectKey;
  189. this.name = builder.name;
  190. this.visibility = builder.visibility;
  191. this.mainBranchKey = builder.mainBranchKey;
  192. this.newCodeDefinitionType = builder.newCodeDefinitionType;
  193. this.newCodeDefinitionValue = builder.newCodeDefinitionValue;
  194. }
  195. public String getProjectKey() {
  196. return projectKey;
  197. }
  198. public String getName() {
  199. return name;
  200. }
  201. @CheckForNull
  202. public String getVisibility() {
  203. return visibility;
  204. }
  205. public String getMainBranchKey() {
  206. return mainBranchKey;
  207. }
  208. @CheckForNull
  209. public String getNewCodeDefinitionType() {
  210. return newCodeDefinitionType;
  211. }
  212. @CheckForNull
  213. public String getNewCodeDefinitionValue() {
  214. return newCodeDefinitionValue;
  215. }
  216. public static Builder builder() {
  217. return new Builder();
  218. }
  219. }
  220. static class Builder {
  221. private String projectKey;
  222. private String name;
  223. private String mainBranchKey;
  224. @CheckForNull
  225. private String visibility;
  226. @CheckForNull
  227. private String newCodeDefinitionType;
  228. @CheckForNull
  229. private String newCodeDefinitionValue;
  230. private Builder() {
  231. }
  232. public Builder setProjectKey(String projectKey) {
  233. requireNonNull(projectKey);
  234. this.projectKey = projectKey;
  235. return this;
  236. }
  237. public Builder setName(String name) {
  238. requireNonNull(name);
  239. this.name = name;
  240. return this;
  241. }
  242. public Builder setVisibility(@Nullable String visibility) {
  243. this.visibility = visibility;
  244. return this;
  245. }
  246. public Builder setMainBranchKey(@Nullable String mainBranchKey) {
  247. this.mainBranchKey = mainBranchKey;
  248. return this;
  249. }
  250. public Builder setNewCodeDefinitionType(@Nullable String newCodeDefinitionType) {
  251. this.newCodeDefinitionType = newCodeDefinitionType;
  252. return this;
  253. }
  254. public Builder setNewCodeDefinitionValue(@Nullable String newCodeDefinitionValue) {
  255. this.newCodeDefinitionValue = newCodeDefinitionValue;
  256. return this;
  257. }
  258. public CreateRequest build() {
  259. requireNonNull(projectKey);
  260. requireNonNull(name);
  261. return new CreateRequest(this);
  262. }
  263. }
  264. }