Line data Source code
1 : // Copyright (C) 2009 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.sshd.commands;
16 :
17 : import com.google.common.annotations.VisibleForTesting;
18 : import com.google.common.base.Splitter;
19 : import com.google.common.collect.Lists;
20 : import com.google.gerrit.common.data.GlobalCapability;
21 : import com.google.gerrit.entities.AccountGroup;
22 : import com.google.gerrit.entities.Project;
23 : import com.google.gerrit.extensions.annotations.RequiresCapability;
24 : import com.google.gerrit.extensions.api.GerritApi;
25 : import com.google.gerrit.extensions.api.projects.ConfigValue;
26 : import com.google.gerrit.extensions.api.projects.ProjectInput;
27 : import com.google.gerrit.extensions.client.InheritableBoolean;
28 : import com.google.gerrit.extensions.client.SubmitType;
29 : import com.google.gerrit.extensions.restapi.RestApiException;
30 : import com.google.gerrit.server.permissions.PermissionBackendException;
31 : import com.google.gerrit.server.project.ProjectState;
32 : import com.google.gerrit.server.project.SuggestParentCandidates;
33 : import com.google.gerrit.sshd.CommandMetaData;
34 : import com.google.gerrit.sshd.SshCommand;
35 : import com.google.inject.Inject;
36 : import java.util.HashMap;
37 : import java.util.List;
38 : import java.util.Map;
39 : import org.kohsuke.args4j.Argument;
40 : import org.kohsuke.args4j.Option;
41 :
42 : /** Create a new project. * */
43 : @RequiresCapability(GlobalCapability.CREATE_PROJECT)
44 : @CommandMetaData(
45 : name = "create-project",
46 : description = "Create a new project and associated Git repository")
47 2 : final class CreateProjectCommand extends SshCommand {
48 : @Option(
49 : name = "--suggest-parents",
50 : aliases = {"-S"},
51 : usage =
52 : "suggest parent candidates, "
53 : + "if this option is used all other options and arguments are ignored")
54 : private boolean suggestParent;
55 :
56 : @Option(
57 : name = "--owner",
58 : aliases = {"-o"},
59 : usage = "owner(s) of project")
60 : private List<AccountGroup.UUID> ownerIds;
61 :
62 : @Option(
63 : name = "--parent",
64 : aliases = {"-p"},
65 : metaVar = "NAME",
66 : usage = "parent project")
67 : private ProjectState newParent;
68 :
69 : @Option(name = "--permissions-only", usage = "create project for use only as parent")
70 : private boolean permissionsOnly;
71 :
72 2 : @Option(
73 : name = "--description",
74 : aliases = {"-d"},
75 : metaVar = "DESCRIPTION",
76 : usage = "description of project")
77 : private String projectDescription = "";
78 :
79 : @Option(
80 : name = "--submit-type",
81 : aliases = {"-t"},
82 : usage = "project submit type")
83 : private SubmitType submitType;
84 :
85 2 : @Option(name = "--contributor-agreements", usage = "if contributor agreement is required")
86 : private InheritableBoolean contributorAgreements = InheritableBoolean.INHERIT;
87 :
88 2 : @Option(name = "--signed-off-by", usage = "if signed-off-by is required")
89 : private InheritableBoolean signedOffBy = InheritableBoolean.INHERIT;
90 :
91 2 : @Option(name = "--content-merge", usage = "allow automatic conflict resolving within files")
92 : private InheritableBoolean contentMerge = InheritableBoolean.INHERIT;
93 :
94 2 : @Option(name = "--change-id", usage = "if change-id is required")
95 : private InheritableBoolean requireChangeID = InheritableBoolean.INHERIT;
96 :
97 2 : @Option(name = "--reject-empty-commit", usage = "if empty commits should be rejected on submit")
98 : private InheritableBoolean rejectEmptyCommit = InheritableBoolean.INHERIT;
99 :
100 2 : @Option(
101 : name = "--new-change-for-all-not-in-target",
102 : usage = "if a new change will be created for every commit not in target branch")
103 : private InheritableBoolean createNewChangeForAllNotInTarget = InheritableBoolean.INHERIT;
104 :
105 : @Option(
106 : name = "--use-contributor-agreements",
107 : aliases = {"--ca"},
108 : usage = "if contributor agreement is required")
109 : void setUseContributorArgreements(@SuppressWarnings("unused") boolean on) {
110 0 : contributorAgreements = InheritableBoolean.TRUE;
111 0 : }
112 :
113 : @Option(
114 : name = "--use-signed-off-by",
115 : aliases = {"--so"},
116 : usage = "if signed-off-by is required")
117 : void setUseSignedOffBy(@SuppressWarnings("unused") boolean on) {
118 0 : signedOffBy = InheritableBoolean.TRUE;
119 0 : }
120 :
121 : @Option(name = "--use-content-merge", usage = "allow automatic conflict resolving within files")
122 : void setUseContentMerge(@SuppressWarnings("unused") boolean on) {
123 0 : contentMerge = InheritableBoolean.TRUE;
124 0 : }
125 :
126 : @Option(
127 : name = "--require-change-id",
128 : aliases = {"--id"},
129 : usage = "if change-id is required")
130 : void setRequireChangeId(@SuppressWarnings("unused") boolean on) {
131 0 : requireChangeID = InheritableBoolean.TRUE;
132 0 : }
133 :
134 : @Option(
135 : name = "--create-new-change-for-all-not-in-target",
136 : aliases = {"--ncfa"},
137 : usage = "if a new change will be created for every commit not in target branch")
138 : void setNewChangeForAllNotInTarget(@SuppressWarnings("unused") boolean on) {
139 0 : createNewChangeForAllNotInTarget = InheritableBoolean.TRUE;
140 0 : }
141 :
142 : @Option(
143 : name = "--branch",
144 : aliases = {"-b"},
145 : metaVar = "BRANCH",
146 : usage = "initial branch name\n(default: gerrit.defaultProject)")
147 : private List<String> branch;
148 :
149 : @Option(name = "--empty-commit", usage = "to create initial empty commit")
150 : private boolean createEmptyCommit;
151 :
152 : @Option(name = "--max-object-size-limit", usage = "max Git object size for this project")
153 : private String maxObjectSizeLimit;
154 :
155 : @Option(
156 : name = "--plugin-config",
157 : usage = "plugin configuration parameter with format '<plugin-name>.<parameter-name>=<value>'")
158 : private List<String> pluginConfigValues;
159 :
160 : @Argument(index = 0, metaVar = "NAME", usage = "name of project to be created")
161 : private String projectName;
162 :
163 : @Inject private GerritApi gApi;
164 :
165 : @Inject private SuggestParentCandidates suggestParentCandidates;
166 :
167 : @Override
168 : protected void run() throws Failure {
169 1 : enableGracefulStop();
170 : try {
171 1 : if (!suggestParent) {
172 1 : if (projectName == null) {
173 0 : throw die("Project name is required.");
174 : }
175 :
176 1 : ProjectInput input = new ProjectInput();
177 1 : input.name = projectName;
178 1 : if (ownerIds != null) {
179 1 : input.owners = Lists.transform(ownerIds, AccountGroup.UUID::get);
180 : }
181 1 : if (newParent != null) {
182 0 : input.parent = newParent.getName();
183 : }
184 1 : input.permissionsOnly = permissionsOnly;
185 1 : input.description = projectDescription;
186 1 : input.submitType = submitType;
187 1 : input.useContributorAgreements = contributorAgreements;
188 1 : input.useSignedOffBy = signedOffBy;
189 1 : input.useContentMerge = contentMerge;
190 1 : input.requireChangeId = requireChangeID;
191 1 : input.createNewChangeForAllNotInTarget = createNewChangeForAllNotInTarget;
192 1 : input.branches = branch;
193 1 : input.createEmptyCommit = createEmptyCommit;
194 1 : input.maxObjectSizeLimit = maxObjectSizeLimit;
195 1 : input.rejectEmptyCommit = rejectEmptyCommit;
196 1 : if (pluginConfigValues != null) {
197 0 : input.pluginConfigValues = parsePluginConfigValues(pluginConfigValues);
198 : }
199 :
200 1 : gApi.projects().create(input);
201 1 : } else {
202 0 : for (Project.NameKey parent : suggestParentCandidates.getNameKeys()) {
203 0 : stdout.print(parent.get() + '\n');
204 0 : }
205 : }
206 0 : } catch (RestApiException err) {
207 0 : throw die(err);
208 0 : } catch (PermissionBackendException err) {
209 0 : throw new Failure(1, "permissions unavailable", err);
210 1 : }
211 1 : }
212 :
213 : @VisibleForTesting
214 : Map<String, Map<String, ConfigValue>> parsePluginConfigValues(List<String> pluginConfigValues)
215 : throws UnloggedFailure {
216 1 : Map<String, Map<String, ConfigValue>> m = new HashMap<>();
217 1 : for (String pluginConfigValue : pluginConfigValues) {
218 1 : List<String> s = Splitter.on('=').splitToList(pluginConfigValue);
219 1 : List<String> s2 = Splitter.on('.').splitToList(s.get(0));
220 1 : if (s.size() != 2 || s2.size() != 2) {
221 0 : throw die(
222 : "Invalid plugin config value '"
223 : + pluginConfigValue
224 : + "', expected format '<plugin-name>.<parameter-name>=<value>'"
225 : + " or '<plugin-name>.<parameter-name>=<value1,value2,...>'");
226 : }
227 1 : ConfigValue value = new ConfigValue();
228 1 : String v = s.get(1);
229 1 : if (v.contains(",")) {
230 1 : value.values = Splitter.on(",").splitToList(v);
231 : } else {
232 1 : value.value = v;
233 : }
234 1 : String pluginName = s2.get(0);
235 1 : String paramName = s2.get(1);
236 1 : Map<String, ConfigValue> l = m.get(pluginName);
237 1 : if (l == null) {
238 1 : l = new HashMap<>();
239 1 : m.put(pluginName, l);
240 : }
241 1 : l.put(paramName, value);
242 1 : }
243 1 : return m;
244 : }
245 : }
|