Merge change 11109
* changes: Adding new ssh create-project command.
This commit is contained in:
@@ -22,6 +22,9 @@ import com.google.inject.Injector;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.assistedinject.FactoryProvider;
|
||||
|
||||
import com.jcraft.jsch.Channel;
|
||||
import com.jcraft.jsch.ChannelExec;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.Session;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -35,13 +38,16 @@ import org.spearce.jgit.transport.RemoteConfig;
|
||||
import org.spearce.jgit.transport.SshConfigSessionFactory;
|
||||
import org.spearce.jgit.transport.SshSessionFactory;
|
||||
import org.spearce.jgit.transport.URIish;
|
||||
import org.spearce.jgit.util.QuotedString;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -168,6 +174,111 @@ public class PushReplication implements ReplicationQueue {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void replicateNewProject(Project.NameKey projectName) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator<ReplicationConfig> configIter = configs.iterator();
|
||||
|
||||
while (configIter.hasNext()) {
|
||||
ReplicationConfig rp = configIter.next();
|
||||
List<URIish> uriList = rp.getURIs(projectName, "*");
|
||||
|
||||
Iterator<URIish> uriIter = uriList.iterator();
|
||||
|
||||
while (uriIter.hasNext()) {
|
||||
replicateProject(uriIter.next());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void replicateProject(final URIish replicateURI) {
|
||||
SshSessionFactory sshFactory = SshSessionFactory.getInstance();
|
||||
Session sshSession;
|
||||
String projectPath = QuotedString.BOURNE.quote(replicateURI.getPath());
|
||||
|
||||
if(!usingSSH(replicateURI)) {
|
||||
log.warn("Cannot create new project on remote site since the connection "
|
||||
+ "method is not SSH: " + replicateURI.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
OutputStream errStream = createErrStream();
|
||||
String cmd = "mkdir " + projectPath
|
||||
+ "&& cd " + projectPath
|
||||
+ "&& git init --bare";
|
||||
|
||||
try {
|
||||
sshSession = sshFactory.getSession(replicateURI.getUser(),
|
||||
replicateURI.getPass(), replicateURI.getHost(), replicateURI.getPort());
|
||||
sshSession.connect();
|
||||
|
||||
Channel channel = sshSession.openChannel("exec");
|
||||
((ChannelExec) channel).setCommand(cmd);
|
||||
|
||||
channel.setInputStream(null);
|
||||
|
||||
((ChannelExec) channel).setErrStream(errStream);
|
||||
|
||||
channel.connect();
|
||||
|
||||
while (!channel.isClosed()) {
|
||||
try {
|
||||
final int delay = 50;
|
||||
Thread.sleep(delay);
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
channel.disconnect();
|
||||
sshSession.disconnect();
|
||||
} catch(JSchException e) {
|
||||
log.error("Communication error when trying to replicate to: "
|
||||
+ replicateURI.toString() + "\n"
|
||||
+ "Error reported: " + e.getMessage() + "\n"
|
||||
+ "Error in communication: " + errStream.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private OutputStream createErrStream() {
|
||||
return new OutputStream() {
|
||||
private StringBuilder all = new StringBuilder();
|
||||
private StringBuilder sb = new StringBuilder();
|
||||
|
||||
public String toString() {
|
||||
String r = all.toString();
|
||||
while (r.endsWith("\n"))
|
||||
r = r.substring(0, r.length() - 1);
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final int b) throws IOException {
|
||||
if (b == '\r') {
|
||||
return;
|
||||
}
|
||||
|
||||
sb.append((char) b);
|
||||
|
||||
if (b == '\n') {
|
||||
all.append(sb);
|
||||
sb.setLength(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean usingSSH(final URIish uri) {
|
||||
final String scheme = uri.getScheme();
|
||||
if (!uri.isRemote())
|
||||
return false;
|
||||
if (scheme != null && scheme.toLowerCase().contains("ssh"))
|
||||
return true;
|
||||
if (scheme == null && uri.getHost() != null && uri.getPath() != null)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static class ReplicationConfig {
|
||||
private final RemoteConfig remote;
|
||||
private final int delay;
|
||||
@@ -247,4 +358,4 @@ public class PushReplication implements ReplicationQueue {
|
||||
return uri.toString().contains(urlMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public interface ReplicationQueue {
|
||||
* local project state. If not, they are updated by pushing new refs, updating
|
||||
* existing ones which don't match, and deleting stale refs which have been
|
||||
* removed from the local repository.
|
||||
*
|
||||
*
|
||||
* @param project identity of the project to replicate.
|
||||
* @param urlMatch substring that must appear in a URI to support replication.
|
||||
*/
|
||||
@@ -40,9 +40,19 @@ public interface ReplicationQueue {
|
||||
* This method automatically tries to batch together multiple requests in the
|
||||
* same project, to take advantage of Git's native ability to update multiple
|
||||
* refs during a single push operation.
|
||||
*
|
||||
*
|
||||
* @param project identity of the project to replicate.
|
||||
* @param ref unique name of the ref; must start with {@code refs/}.
|
||||
*/
|
||||
void scheduleUpdate(Project.NameKey project, String ref);
|
||||
|
||||
/**
|
||||
* Create new empty project at the remote sites.
|
||||
* <p>
|
||||
* When a new project has been created locally call this method to make sure
|
||||
* that the project will be created at the remote sites as well.
|
||||
*
|
||||
* @param project of the project to be created.
|
||||
*/
|
||||
void replicateNewProject(Project.NameKey project);
|
||||
}
|
||||
|
||||
@@ -91,6 +91,39 @@ public class GerritServer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create (and open) a repository by name.
|
||||
*
|
||||
* @param name the repository name, relative to the base directory.
|
||||
* @return the cached Repository instance. Caller must call {@code close()}
|
||||
* when done to decrement the resource handle.
|
||||
* @throws RepositoryNotFoundException the name does not denote an existing
|
||||
* repository, or the name cannot be read as a repository.
|
||||
*/
|
||||
public Repository createRepository(String name)
|
||||
throws RepositoryNotFoundException {
|
||||
if (basepath == null) {
|
||||
throw new RepositoryNotFoundException("No gerrit.basepath configured");
|
||||
}
|
||||
|
||||
if (isUnreasonableName(name)) {
|
||||
throw new RepositoryNotFoundException("Invalid name: " + name);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!name.endsWith(".git")) {
|
||||
name = name + ".git";
|
||||
}
|
||||
final FileKey loc = FileKey.exact(new File(basepath, name));
|
||||
return RepositoryCache.open(loc, false);
|
||||
} catch (IOException e1) {
|
||||
final RepositoryNotFoundException e2;
|
||||
e2 = new RepositoryNotFoundException("Cannot open repository " + name);
|
||||
e2.initCause(e1);
|
||||
throw e2;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUnreasonableName(final String name) {
|
||||
if (name.length() == 0) return true; // no empty paths
|
||||
|
||||
|
||||
@@ -58,6 +58,10 @@ public class GroupCache {
|
||||
mgr.replaceCacheWithDecoratedCache(dc, self);
|
||||
}
|
||||
|
||||
public final AccountGroup.Id getAdministrators() {
|
||||
return administrators;
|
||||
}
|
||||
|
||||
private AccountGroup lookup(final AccountGroup.Id groupId)
|
||||
throws OrmException {
|
||||
final ReviewDb db = schema.open();
|
||||
@@ -110,4 +114,21 @@ public class GroupCache {
|
||||
self.remove(groupId);
|
||||
}
|
||||
}
|
||||
|
||||
public AccountGroup lookup(final String groupName) throws OrmException {
|
||||
final ReviewDb db = schema.open();
|
||||
try {
|
||||
final AccountGroup.NameKey nameKey =
|
||||
new AccountGroup.NameKey(groupName);
|
||||
|
||||
final AccountGroup group = db.accountGroups().get(nameKey);
|
||||
if (group != null) {
|
||||
return group;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
// Copyright (C) 2009 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.ssh.commands;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.client.reviewdb.ApprovalCategory;
|
||||
import com.google.gerrit.client.reviewdb.Branch;
|
||||
import com.google.gerrit.client.reviewdb.Project;
|
||||
import com.google.gerrit.client.reviewdb.ProjectRight;
|
||||
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.client.reviewdb.Project.SubmitType;
|
||||
import com.google.gerrit.client.rpc.NoSuchEntityException;
|
||||
import com.google.gerrit.git.ReplicationQueue;
|
||||
import com.google.gerrit.server.GerritServer;
|
||||
import com.google.gerrit.server.account.GroupCache;
|
||||
import com.google.gerrit.server.ssh.AdminCommand;
|
||||
import com.google.gerrit.server.ssh.BaseCommand;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.Transaction;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.kohsuke.args4j.Option;
|
||||
import org.spearce.jgit.lib.Repository;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collections;
|
||||
|
||||
/** Create a new project. **/
|
||||
@AdminCommand
|
||||
final class AdminCreateProject extends BaseCommand {
|
||||
@Option(name = "--name", required = true, aliases = { "-n" }, usage = "name of project to be created")
|
||||
private String projectName;
|
||||
|
||||
@Option(name = "--owner", aliases = { "-o" }, usage = "name of group that will own the project (defaults to: Administrators)")
|
||||
private String ownerName;
|
||||
|
||||
@Option(name = "--description", aliases = { "-d" }, usage = "description of the project")
|
||||
private String projectDescription;
|
||||
|
||||
@Option(name = "--submit-type", aliases = { "-t" }, usage = "project submit type (F)ast forward only, (M)erge if necessary, merge (A)lways or (C)herry pick (defaults to: F)")
|
||||
private String submitTypeStr;
|
||||
|
||||
@Option(name = "--use-contributor-agreements", aliases = { "--ca" }, usage = "set this to true if project should make the user sign a contributor agreement (defaults to: N)")
|
||||
private String useContributorAgreements;
|
||||
|
||||
@Option(name = "--use-signed-off-by", aliases = { "--so" }, usage = "set this to true if the project should mandate signed-off-by (defaults to: N)")
|
||||
private String useSignedOffBy;
|
||||
|
||||
@Inject
|
||||
private ReviewDb db;
|
||||
|
||||
@Inject
|
||||
private GerritServer gs;
|
||||
|
||||
@Inject
|
||||
private GroupCache groupCache;
|
||||
|
||||
@Inject
|
||||
private ReplicationQueue rq;
|
||||
|
||||
private AccountGroup.Id ownerId = null;
|
||||
private boolean contributorAgreements = false;
|
||||
private boolean signedOffBy = false;
|
||||
private SubmitType submitType = null;
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
PrintWriter p = toPrintWriter(out);
|
||||
|
||||
parseCommandLine();
|
||||
|
||||
try {
|
||||
validateParameters();
|
||||
|
||||
Transaction txn = db.beginTransaction();
|
||||
|
||||
createProject(txn);
|
||||
|
||||
Repository repo = gs.createRepository(projectName);
|
||||
repo.create(true);
|
||||
|
||||
txn.commit();
|
||||
|
||||
rq.replicateNewProject(new Project.NameKey(projectName));
|
||||
} catch (Exception e) {
|
||||
p.print("Error when trying to create project: "
|
||||
+ e.getMessage() + "\n");
|
||||
p.flush();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void createProject(Transaction txn) throws OrmException,
|
||||
NoSuchEntityException {
|
||||
final Project.NameKey newProjectNameKey =
|
||||
new Project.NameKey(projectName);
|
||||
|
||||
final Project newProject =
|
||||
new Project(newProjectNameKey,
|
||||
new Project.Id(db.nextProjectId()));
|
||||
|
||||
newProject.setDescription(projectDescription);
|
||||
newProject.setSubmitType(submitType);
|
||||
newProject.setUseContributorAgreements(contributorAgreements);
|
||||
newProject.setUseSignedOffBy(signedOffBy);
|
||||
|
||||
db.projects().insert(Collections.singleton(newProject), txn);
|
||||
|
||||
final ProjectRight.Key prk =
|
||||
new ProjectRight.Key(newProjectNameKey,
|
||||
ApprovalCategory.OWN, ownerId);
|
||||
final ProjectRight pr = new ProjectRight(prk);
|
||||
pr.setMaxValue((short) 1);
|
||||
pr.setMinValue((short) 1);
|
||||
db.projectRights().insert(Collections.singleton(pr), txn);
|
||||
|
||||
final Branch newBranch =
|
||||
new Branch(
|
||||
new Branch.NameKey(newProjectNameKey, Branch.R_HEADS + "master"));
|
||||
|
||||
db.branches().insert(Collections.singleton(newBranch), txn);
|
||||
}
|
||||
|
||||
private boolean stringToBoolean(final String boolStr,
|
||||
final boolean defaultValue) throws Failure {
|
||||
if (boolStr == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (boolStr.equalsIgnoreCase("FALSE")
|
||||
|| boolStr.equalsIgnoreCase("F")
|
||||
|| boolStr.equalsIgnoreCase("NO")
|
||||
|| boolStr.equalsIgnoreCase("N")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (boolStr.equalsIgnoreCase("TRUE")
|
||||
|| boolStr.equalsIgnoreCase("T")
|
||||
|| boolStr.equalsIgnoreCase("YES")
|
||||
|| boolStr.equalsIgnoreCase("Y")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new Failure(1, "Parameter must have boolean value (true, false)");
|
||||
}
|
||||
|
||||
private void validateParameters() throws Failure, OrmException {
|
||||
if (projectName.endsWith(".git")) {
|
||||
projectName = projectName.substring(0,
|
||||
projectName.length() - ".git".length());
|
||||
}
|
||||
|
||||
if (ownerName == null) {
|
||||
ownerId = groupCache.getAdministrators();
|
||||
} else {
|
||||
AccountGroup ownerGroup = groupCache.lookup(ownerName);
|
||||
if (ownerGroup == null) {
|
||||
throw new Failure(1, "Specified group does not exist");
|
||||
}
|
||||
ownerId = ownerGroup.getId();
|
||||
}
|
||||
|
||||
if (projectDescription == null) {
|
||||
projectDescription = "";
|
||||
}
|
||||
|
||||
contributorAgreements = stringToBoolean(useContributorAgreements, false);
|
||||
signedOffBy = stringToBoolean(useSignedOffBy, false);
|
||||
|
||||
if (submitTypeStr == null) {
|
||||
submitTypeStr = "fast-forward-only";
|
||||
}
|
||||
|
||||
if (submitTypeStr.toLowerCase().equalsIgnoreCase("fast-forward-only")) {
|
||||
submitType = SubmitType.FAST_FORWARD_ONLY;
|
||||
} else if (submitTypeStr.toLowerCase().equalsIgnoreCase("merge-if-necessary")) {
|
||||
submitType = SubmitType.MERGE_IF_NECESSARY;
|
||||
} else if (submitTypeStr.toLowerCase().equalsIgnoreCase("merge-always")) {
|
||||
submitType = SubmitType.MERGE_ALWAYS;
|
||||
} else if (submitTypeStr.toLowerCase().equalsIgnoreCase("cherry-pick")) {
|
||||
submitType = SubmitType.CHERRY_PICK;
|
||||
}
|
||||
|
||||
if (submitType == null) {
|
||||
throw new Failure(1, "Submit type must be either: fast-forward-only, "
|
||||
+ "merge-if-necessary, merge-always or cherry-pick");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ public class DefaultCommandModule extends CommandModule {
|
||||
final CommandName gerrit = Commands.named("gerrit");
|
||||
|
||||
command(gerrit).toProvider(new DispatchCommandProvider(gerrit));
|
||||
command(gerrit, "create-project").to(AdminCreateProject.class);
|
||||
command(gerrit, "flush-caches").to(AdminFlushCaches.class);
|
||||
command(gerrit, "ls-projects").to(ListProjects.class);
|
||||
command(gerrit, "receive-pack").to(Receive.class);
|
||||
|
||||
Reference in New Issue
Block a user