From 617909819cd1b955647dd8584036fc7b2a014265 Mon Sep 17 00:00:00 2001 From: James Moger Date: Wed, 26 Mar 2014 21:33:21 -0400 Subject: [PATCH] Improve command help with formatting and usage examples --- .../gitblit/transport/ssh/WelcomeShell.java | 71 ++++- .../transport/ssh/commands/BaseCommand.java | 60 +++- .../ssh/commands/DispatchCommand.java | 4 +- .../transport/ssh/commands/SshCommand.java | 4 + .../transport/ssh/commands/UsageExample.java | 32 ++ .../transport/ssh/commands/UsageExamples.java | 31 ++ .../ssh/git/GarbageCollectionCommand.java | 2 + .../transport/ssh/gitblit/BaseKeyCommand.java | 2 +- .../transport/ssh/gitblit/ConfigCommand.java | 8 + .../ssh/gitblit/CreateRepository.java | 37 --- .../ssh/gitblit/GitblitDispatcher.java | 2 - .../transport/ssh/gitblit/KeysDispatcher.java | 7 +- .../ssh/gitblit/RepositoriesDispatcher.java | 4 +- .../ssh/gitblit/SetAccountCommand.java | 88 ------ .../ssh/gitblit/UsersDispatcher.java | 293 ++++++++++++++++-- .../java/com/gitblit/utils/FlipTable.java | 2 +- src/site/setup_transport_ssh.mkd | 27 +- 17 files changed, 480 insertions(+), 194 deletions(-) create mode 100644 src/main/java/com/gitblit/transport/ssh/commands/UsageExample.java create mode 100644 src/main/java/com/gitblit/transport/ssh/commands/UsageExamples.java delete mode 100644 src/main/java/com/gitblit/transport/ssh/gitblit/CreateRepository.java delete mode 100644 src/main/java/com/gitblit/transport/ssh/gitblit/SetAccountCommand.java diff --git a/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java b/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java index ccf2586b..a9fe6f0f 100644 --- a/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java +++ b/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java @@ -113,36 +113,71 @@ public class WelcomeShell implements Factory { String getMessage() { SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY); UserModel user = client.getUser(); + String hostname = getHostname(); + int port = settings.getInteger(Keys.git.sshPort, 0); + + final String b1 = StringUtils.rightPad("", 72, '═'); + final String b2 = StringUtils.rightPad("", 72, '─'); + final String nl = "\r\n"; StringBuilder msg = new StringBuilder(); - msg.append("\r\n"); - msg.append("Hi "); + msg.append(nl); + msg.append(b1); + msg.append(nl); + msg.append(" "); + msg.append(com.gitblit.Constants.getGitBlitVersion()); + msg.append(nl); + msg.append(b1); + msg.append(nl); + msg.append(nl); + msg.append(" Hi "); msg.append(user.getDisplayName()); - msg.append(", you have successfully connected to Gitblit over SSH"); - msg.append("\r\n"); - msg.append("with client: "); + msg.append(", you have successfully connected over SSH."); + msg.append(nl); + msg.append(nl); + msg.append(" client: "); msg.append(session.getClientVersion()); - msg.append("\r\n"); - msg.append("\r\n"); + msg.append(nl); + msg.append(nl); - msg.append("You may clone a repository with the following Git syntax:\r\n"); - msg.append("\r\n"); + msg.append(b2); + msg.append(nl); + msg.append(nl); + msg.append(" You may clone a repository with the following Git syntax:"); + msg.append(nl); + msg.append(nl); msg.append(" git clone "); - msg.append(formatUrl(user.username)); - msg.append("\r\n"); - msg.append("\r\n"); + msg.append(formatUrl(hostname, port, user.username)); + msg.append(nl); + msg.append(nl); + + msg.append(b2); + msg.append(nl); + msg.append(nl); + + msg.append(" You may upload an SSH public key with the following syntax:"); + msg.append(nl); + msg.append(nl); + + msg.append(String.format(" cat ~/.ssh/id_rsa.pub | ssh -l %s -p %d %s gitblit keys add -", user.username, port, hostname)); + msg.append(nl); + msg.append(nl); + + msg.append(b2); + msg.append(nl); + msg.append(nl); // display the core commands SshCommandFactory cmdFactory = (SshCommandFactory) session.getFactoryManager().getCommandFactory(); DispatchCommand root = cmdFactory.createRootDispatcher(client, ""); - String usage = root.usage().replace("\n", "\r\n"); + String usage = root.usage().replace("\n", nl); msg.append(usage); return msg.toString(); } - private String formatUrl(String username) { + private String getHostname() { String host = null; String url = settings.getString(Keys.web.canonicalUrl, "https://localhost:8443"); if (url != null) { @@ -154,15 +189,17 @@ public class WelcomeShell implements Factory { if (StringUtils.isEmpty(host)) { host = SystemReader.getInstance().getHostname(); } + return host; + } - int port = settings.getInteger(Keys.git.sshPort, 0); + private String formatUrl(String hostname, int port, String username) { if (port == 22) { // standard port - return MessageFormat.format("{0}@{1}/REPOSITORY.git", username, host); + return MessageFormat.format("{0}@{1}/REPOSITORY.git", username, hostname); } else { // non-standard port return MessageFormat.format("ssh://{0}@{1}:{2,number,0}/REPOSITORY.git", - username, host, port); + username, hostname, port); } } } diff --git a/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java index 6a190df6..d24a7163 100644 --- a/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java @@ -37,7 +37,9 @@ import org.kohsuke.args4j.Option; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.gitblit.Keys; import com.gitblit.utils.IdGenerator; +import com.gitblit.utils.StringUtils; import com.gitblit.utils.WorkQueue; import com.gitblit.utils.WorkQueue.CancelableRunnable; import com.gitblit.utils.cli.CmdLineParser; @@ -200,9 +202,39 @@ public abstract class BaseCommand implements Command, SessionAware { } if (clp.wasHelpRequestedByOption()) { + CommandMetaData meta = getClass().getAnnotation(CommandMetaData.class); + String title = meta.name().toUpperCase() + ": " + meta.description(); + String b = com.gitblit.utils.StringUtils.leftPad("", title.length() + 2, '═'); StringWriter msg = new StringWriter(); - clp.printDetailedUsage(commandName, msg); - msg.write(usage()); + msg.write('\n'); + msg.write(b); + msg.write('\n'); + msg.write(' '); + msg.write(title); + msg.write('\n'); + msg.write(b); + msg.write("\n\n"); + msg.write("USAGE\n"); + msg.write("─────\n"); + msg.write(' '); + msg.write(commandName); + msg.write('\n'); + msg.write(' '); + clp.printSingleLineUsage(msg, null); + msg.write("\n\n"); + msg.write("ARGUMENTS & OPTIONS\n"); + msg.write("───────────────────\n"); + clp.printUsage(msg, null); + msg.write('\n'); + String examples = usage().trim(); + if (!StringUtils.isEmpty(examples)) { + msg.write('\n'); + msg.write("EXAMPLES\n"); + msg.write("────────\n"); + msg.write(examples); + msg.write('\n'); + } + throw new UnloggedFailure(1, msg.toString()); } } @@ -213,9 +245,33 @@ public abstract class BaseCommand implements Command, SessionAware { } public String usage() { + Class clazz = getClass(); + if (clazz.isAnnotationPresent(UsageExamples.class)) { + return examples(clazz.getAnnotation(UsageExamples.class).examples()); + } else if (clazz.isAnnotationPresent(UsageExample.class)) { + return examples(clazz.getAnnotation(UsageExample.class)); + } return ""; } + protected String examples(UsageExample... examples) { + int sshPort = getContext().getGitblit().getSettings().getInteger(Keys.git.sshPort, 29418); + String username = getContext().getClient().getUsername(); + String hostname = "localhost"; + String ssh = String.format("ssh -l %s -p %d %s", username, sshPort, hostname); + + StringBuilder sb = new StringBuilder(); + for (UsageExample example : examples) { + sb.append(example.description()).append("\n\n"); + String syntax = example.syntax(); + syntax = syntax.replace("${ssh}", ssh); + syntax = syntax.replace("${username}", username); + syntax = syntax.replace("${cmd}", commandName); + sb.append(" ").append(syntax).append("\n\n"); + } + return sb.toString(); + } + protected void showHelp() throws UnloggedFailure { argv = new String [] { "--help" }; parseCommandLine(); diff --git a/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java index b916bb17..6e9a87dd 100644 --- a/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java @@ -332,9 +332,9 @@ public abstract class DispatchCommand extends BaseCommand implements ExtensionPo continue; } - String displayName = name; + String displayName = name + (meta.admin() ? "*" : ""); if (commandToAliases.containsKey(meta.name())) { - displayName = name + " (" + Joiner.on(',').join(commandToAliases.get(meta.name())) + ")"; + displayName = name + (meta.admin() ? "*" : "")+ " (" + Joiner.on(',').join(commandToAliases.get(meta.name())) + ")"; } displayNames.put(name, displayName); diff --git a/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java index ee464e7c..67e2805f 100644 --- a/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java @@ -18,8 +18,12 @@ import java.io.IOException; import java.io.PrintWriter; import org.apache.sshd.server.Environment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class SshCommand extends BaseCommand { + + protected Logger log = LoggerFactory.getLogger(getClass()); protected PrintWriter stdout; protected PrintWriter stderr; diff --git a/src/main/java/com/gitblit/transport/ssh/commands/UsageExample.java b/src/main/java/com/gitblit/transport/ssh/commands/UsageExample.java new file mode 100644 index 00000000..428dfde8 --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/commands/UsageExample.java @@ -0,0 +1,32 @@ +/* + * Copyright 2014 gitblit.com. + * + * 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.gitblit.transport.ssh.commands; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** +* Annotation tagged on a concrete Command to describe how to use it. +*/ +@Target({ElementType.TYPE}) +@Retention(RUNTIME) +public @interface UsageExample { +String syntax(); +String description() default ""; +} diff --git a/src/main/java/com/gitblit/transport/ssh/commands/UsageExamples.java b/src/main/java/com/gitblit/transport/ssh/commands/UsageExamples.java new file mode 100644 index 00000000..0193a98a --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/commands/UsageExamples.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 gitblit.com. + * + * 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.gitblit.transport.ssh.commands; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** +* Annotation tagged on a concrete Command to describe how to use it. +*/ +@Target({ElementType.TYPE}) +@Retention(RUNTIME) +public @interface UsageExamples { +UsageExample [] examples() default {}; +} diff --git a/src/main/java/com/gitblit/transport/ssh/git/GarbageCollectionCommand.java b/src/main/java/com/gitblit/transport/ssh/git/GarbageCollectionCommand.java index cc0c00a3..5383786a 100644 --- a/src/main/java/com/gitblit/transport/ssh/git/GarbageCollectionCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/git/GarbageCollectionCommand.java @@ -25,8 +25,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.transport.ssh.commands.CommandMetaData; +import com.gitblit.transport.ssh.commands.UsageExample; @CommandMetaData(name = "gc", description = "Cleanup unnecessary files and optimize the local repository", admin = true) +@UsageExample(syntax = "${cmd} test/myrepository.git", description = "Garbage collect \"test/myrepository.git\"") public class GarbageCollectionCommand extends BaseGitCommand { private static final Logger log = LoggerFactory.getLogger(GarbageCollectionCommand.class); diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java b/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java index 55a87e4f..56f2c355 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java @@ -36,7 +36,7 @@ abstract class BaseKeyCommand extends SshCommand { protected List readKeys(List sshKeys) throws UnsupportedEncodingException, IOException { int idx = -1; - if (sshKeys.isEmpty() || ((idx = sshKeys.indexOf("-")) >= 0)) { + if ((idx = sshKeys.indexOf("-")) >= 0) { String sshKey = ""; BufferedReader br = new BufferedReader(new InputStreamReader( in, Charsets.UTF_8)); diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/ConfigCommand.java b/src/main/java/com/gitblit/transport/ssh/gitblit/ConfigCommand.java index 695f0850..f6740349 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/ConfigCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/ConfigCommand.java @@ -14,9 +14,17 @@ import com.gitblit.models.ServerSettings; import com.gitblit.models.SettingModel; import com.gitblit.transport.ssh.commands.CommandMetaData; import com.gitblit.transport.ssh.commands.SshCommand; +import com.gitblit.transport.ssh.commands.UsageExample; +import com.gitblit.transport.ssh.commands.UsageExamples; import com.google.common.collect.Maps; @CommandMetaData(name = "config", description = "Administer Gitblit settings", admin = true) +@UsageExamples(examples = { + @UsageExample(syntax = "${cmd} --list", description = "List all settings"), + @UsageExample(syntax = "${cmd} git.sshPort", description = "Describe the git.sshPort setting"), + @UsageExample(syntax = "${cmd} git.sshPort 29418", description = "Set git.sshPort to 29418"), + @UsageExample(syntax = "${cmd} git.sshPort --reset", description = "Reset git.sshPort to it's default value"), +}) public class ConfigCommand extends SshCommand { @Argument(index = 0, metaVar = "KEY", usage = "The setting to describe or update") diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/CreateRepository.java b/src/main/java/com/gitblit/transport/ssh/gitblit/CreateRepository.java deleted file mode 100644 index 2917b6d2..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/CreateRepository.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2014 gitblit.com. - * - * 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.gitblit.transport.ssh.gitblit; - -import org.kohsuke.args4j.Option; - -import com.gitblit.transport.ssh.commands.CommandMetaData; -import com.gitblit.transport.ssh.commands.SshCommand; - -@CommandMetaData(name = "create-repository", description = "Create new GIT repository", admin = true, hidden = true) -public class CreateRepository extends SshCommand { - - @Option(name = "--name", aliases = {"-n"}, required = true, metaVar = "NAME", usage = "name of repository to be created") - private String name; - - @Option(name = "--description", aliases = {"-d"}, metaVar = "DESCRIPTION", usage = "description of repository") - private String repositoryDescription; - - @Override - public void run() { - stdout.println(String.format("Repository <%s> was created", name)); - } -} diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java index 86d8a8c9..42000646 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java @@ -26,8 +26,6 @@ public class GitblitDispatcher extends DispatchCommand { protected void setup(UserModel user) { // commands in this dispatcher register(user, VersionCommand.class); - register(user, CreateRepository.class); - register(user, SetAccountCommand.class); register(user, ConfigCommand.class); // nested dispatchers diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java index 8c4aa228..61764c42 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java @@ -30,6 +30,7 @@ import com.gitblit.transport.ssh.SshKey; import com.gitblit.transport.ssh.commands.CommandMetaData; import com.gitblit.transport.ssh.commands.DispatchCommand; import com.gitblit.transport.ssh.commands.SshCommand; +import com.gitblit.transport.ssh.commands.UsageExample; import com.gitblit.utils.FlipTable; import com.gitblit.utils.FlipTable.Borders; @@ -50,11 +51,12 @@ public class KeysDispatcher extends DispatchCommand { } @CommandMetaData(name = "add", description = "Add an SSH public key to your account") + @UsageExample(syntax = "cat ~/.ssh/id_rsa.pub | ${ssh} ${cmd} -", description = "Upload your SSH public key and add it to your account") public static class AddKey extends BaseKeyCommand { protected final Logger log = LoggerFactory.getLogger(getClass()); - @Argument(metaVar = "|KEY", usage = "the key to add") + @Argument(metaVar = "-|", usage = "the key(s) to add", required = true) private List addKeys = new ArrayList(); @Override @@ -70,6 +72,7 @@ public class KeysDispatcher extends DispatchCommand { } @CommandMetaData(name = "remove", aliases = { "rm" }, description = "Remove an SSH public key from your account") + @UsageExample(syntax = "${cmd} 2", description = "Remove the SSH key identified as #2 in `keys list`") public static class RemoveKey extends BaseKeyCommand { protected final Logger log = LoggerFactory.getLogger(getClass()); @@ -131,7 +134,7 @@ public class KeysDispatcher extends DispatchCommand { } } - @CommandMetaData(name = "list", aliases = { "ls" }, description = "List your registered public keys") + @CommandMetaData(name = "list", aliases = { "ls" }, description = "List your registered SSH public keys") public static class ListKeys extends SshCommand { @Option(name = "-L", usage = "list complete public key parameters") diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/RepositoriesDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/RepositoriesDispatcher.java index 4be60abc..f2fbabbe 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/RepositoriesDispatcher.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/RepositoriesDispatcher.java @@ -23,6 +23,7 @@ import com.gitblit.models.UserModel; import com.gitblit.transport.ssh.commands.CommandMetaData; import com.gitblit.transport.ssh.commands.DispatchCommand; import com.gitblit.transport.ssh.commands.ListFilterCommand; +import com.gitblit.transport.ssh.commands.UsageExample; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.FlipTable; import com.gitblit.utils.FlipTable.Borders; @@ -38,6 +39,7 @@ public class RepositoriesDispatcher extends DispatchCommand { /* List repositories */ @CommandMetaData(name = "list", aliases = { "ls" }, description = "List repositories") + @UsageExample(syntax = "${cmd} mirror/.* -v", description = "Verbose list of all repositories in the 'mirror' directory") public static class ListRepositories extends ListFilterCommand { @Override @@ -72,7 +74,7 @@ public class RepositoriesDispatcher extends DispatchCommand { String size = r.size; if (!r.hasCommits) { lm = ""; - size = "(empty)"; + size = FlipTable.EMPTY; } if (verbose) { String owners = ""; diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/SetAccountCommand.java b/src/main/java/com/gitblit/transport/ssh/gitblit/SetAccountCommand.java deleted file mode 100644 index 3f98778a..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/SetAccountCommand.java +++ /dev/null @@ -1,88 +0,0 @@ -//Copyright (C) 2012 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.gitblit.transport.ssh.gitblit; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.Option; - -import com.gitblit.transport.ssh.SshKey; -import com.gitblit.transport.ssh.commands.CommandMetaData; - -/** Set a user's account settings. **/ -@CommandMetaData(name = "set-account", description = "Change an account's settings", admin = true) -public class SetAccountCommand extends BaseKeyCommand { - - private static final String ALL = "ALL"; - - @Argument(index = 0, required = true, metaVar = "USER", usage = "full name, email-address, ssh username or account id") - private String user; - - @Option(name = "--add-ssh-key", metaVar = "-|KEY", usage = "public keys to add to the account") - private List addSshKeys = new ArrayList(); - - @Option(name = "--delete-ssh-key", metaVar = "-|KEY", usage = "public keys to delete from the account") - private List deleteSshKeys = new ArrayList(); - - @Override - public void run() throws IOException, UnloggedFailure { - validate(); - setAccount(); - } - - private void validate() throws UnloggedFailure { - if (addSshKeys.contains("-") && deleteSshKeys.contains("-")) { - throw new UnloggedFailure(1, "Only one option may use the stdin"); - } - if (deleteSshKeys.contains(ALL)) { - deleteSshKeys = Collections.singletonList(ALL); - } - } - - private void setAccount() throws IOException, UnloggedFailure { - addSshKeys = readKeys(addSshKeys); - if (!addSshKeys.isEmpty()) { - addSshKeys(addSshKeys); - } - - deleteSshKeys = readKeys(deleteSshKeys); - if (!deleteSshKeys.isEmpty()) { - deleteSshKeys(deleteSshKeys); - } - } - - private void addSshKeys(List keys) throws UnloggedFailure, - IOException { - for (String key : keys) { - SshKey sshKey = new SshKey(key); - getKeyManager().addKey(user, sshKey); - } - } - - private void deleteSshKeys(List keys) { - if (keys.contains(ALL)) { - getKeyManager().removeAllKeys(user); - } else { - for (String key : keys) { - SshKey sshKey = new SshKey(key); - getKeyManager().removeKey(user, sshKey); - } - } - } -} diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/UsersDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/UsersDispatcher.java index bed966da..d892d9a0 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/UsersDispatcher.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/UsersDispatcher.java @@ -15,75 +15,308 @@ */ package com.gitblit.transport.ssh.gitblit; +import java.util.ArrayList; import java.util.List; import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import com.gitblit.Constants.AccessPermission; import com.gitblit.manager.IGitblit; import com.gitblit.models.RegistrantAccessPermission; +import com.gitblit.models.RepositoryModel; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.transport.ssh.commands.CommandMetaData; import com.gitblit.transport.ssh.commands.DispatchCommand; import com.gitblit.transport.ssh.commands.ListFilterCommand; import com.gitblit.transport.ssh.commands.SshCommand; +import com.gitblit.transport.ssh.commands.UsageExample; +import com.gitblit.transport.ssh.commands.UsageExamples; +import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.FlipTable; import com.gitblit.utils.FlipTable.Borders; +import com.gitblit.utils.StringUtils; +import com.google.common.base.Joiner; @CommandMetaData(name = "users", description = "User management commands", admin = true) public class UsersDispatcher extends DispatchCommand { @Override protected void setup(UserModel user) { + // primary user commands + register(user, NewUser.class); + register(user, RemoveUser.class); register(user, ShowUser.class); register(user, ListUsers.class); + + // user-specific commands + register(user, SetName.class); + register(user, Permissions.class); + register(user, DisableUser.class); + register(user, EnableUser.class); } - @CommandMetaData(name = "show", description = "Show a user") - public static class ShowUser extends SshCommand { + public static abstract class UserCommand extends SshCommand { @Argument(index = 0, required = true, metaVar = "USERNAME", usage = "username") protected String username; + protected UserModel getUser(boolean requireUser) throws UnloggedFailure { + IGitblit gitblit = getContext().getGitblit(); + UserModel user = gitblit.getUserModel(username); + if (requireUser && user == null) { + throw new UnloggedFailure(1, String.format("User %s does not exist!", username)); + } + return user; + } + } + + @CommandMetaData(name = "new", description = "Create a new user account") + @UsageExample(syntax = "${cmd} john 12345 --email john@smith.com --canFork --canCreate") + public static class NewUser extends UserCommand { + + @Argument(index = 1, required = true, metaVar = "PASSWORD", usage = "password") + protected String password; + + @Option(name = "--email", metaVar = "ADDRESS", usage = "email address") + protected String email; + + @Option(name = "--canAdmin", usage = "can administer the server") + protected boolean canAdmin; + + @Option(name = "--canFork", usage = "can fork repositories") + protected boolean canFork; + + @Option(name = "--canCreate", usage = "can create personal repositories") + protected boolean canCreate; + + @Option(name = "--disabled", usage = "create a disabled user account") + protected boolean disabled; + + @Override + public void run() throws UnloggedFailure { + + if (getUser(false) != null) { + throw new UnloggedFailure(1, String.format("User %s already exists!", username)); + } + + UserModel user = new UserModel(username); + user.password = password; + + if (email != null) { + user.emailAddress = email; + } + + user.canAdmin = canAdmin; + user.canFork = canFork; + user.canCreate = canCreate; + user.disabled = disabled; + + IGitblit gitblit = getContext().getGitblit(); + if (gitblit.updateUserModel(username, user)) { + stdout.println(String.format("%s created.", username)); + } else { + throw new UnloggedFailure(1, String.format("Failed to create %s!", username)); + } + } + } + + @CommandMetaData(name = "set-name", description = "Set the display name of an account") + @UsageExample(syntax = "${cmd} john John Smith", description = "The display name to \"John Smith\" for john's account") + public static class SetName extends UserCommand { + + @Argument(index = 1, multiValued = true, required = true, metaVar = "NAME", usage = "display name") + protected List displayName = new ArrayList(); + + @Override + public void run() throws UnloggedFailure { + UserModel user = getUser(true); + + IGitblit gitblit = getContext().getGitblit(); + user.displayName = Joiner.on(" ").join(displayName); + if (gitblit.updateUserModel(username, user)) { + stdout.println(String.format("Set the display name of %s to \"%s\".", username, user.displayName)); + } else { + throw new UnloggedFailure(1, String.format("Failed to set the display name of %s!", username)); + } + } + } + + @CommandMetaData(name = "disable", description = "Prohibit an account from authenticating") + @UsageExample(syntax = "${cmd} john", description = "Prevent John from authenticating") + public static class DisableUser extends UserCommand { + + @Override + public void run() throws UnloggedFailure { + + UserModel user = getUser(true); + user.disabled = true; + + IGitblit gitblit = getContext().getGitblit(); + if (gitblit.updateUserModel(username, user)) { + stdout.println(String.format("%s is not allowed to authenticate.", username)); + } else { + throw new UnloggedFailure(1, String.format("Failed to disable %s!", username)); + } + } + } + + @CommandMetaData(name = "enable", description = "Allow an account to authenticate") + @UsageExample(syntax = "${cmd} john", description = "Allow John to authenticate") + public static class EnableUser extends UserCommand { + + @Override + public void run() throws UnloggedFailure { + + UserModel user = getUser(true); + user.disabled = false; + + IGitblit gitblit = getContext().getGitblit(); + if (gitblit.updateUserModel(username, user)) { + stdout.println(String.format("%s may now authenticate.", username)); + } else { + throw new UnloggedFailure(1, String.format("Failed to enable %s!", username)); + } + } + } + + @CommandMetaData(name = "permissions", aliases = { "perms" }, description = "Add or remove permissions from an account") + @UsageExample(syntax = "${cmd} john RW:alpha/repo.git RWC:alpha/repo2.git", description = "Add or set permissions for John") + public static class Permissions extends UserCommand { + + @Argument(index = 1, multiValued = true, metaVar = "[PERMISSION:]REPOSITORY", usage = "a repository expression") + protected List permissions; + + @Option(name = "--remove", aliases = { "-r" }, metaVar = "REPOSITORY|ALL", usage = "remove a repository permission") + protected List removals; + + @Override + public void run() throws UnloggedFailure { + IGitblit gitblit = getContext().getGitblit(); + UserModel user = getUser(true); + + boolean modified = false; + if (!ArrayUtils.isEmpty(removals)) { + if (removals.contains("ALL")) { + user.permissions.clear(); + } else { + for (String repo : removals) { + user.removeRepositoryPermission(repo); + log.info(String.format("Removing permission for %s from %s", repo, username)); + } + } + modified = true; + } + + if (!ArrayUtils.isEmpty(permissions)) { + for (String perm : permissions) { + String repo = AccessPermission.repositoryFromRole(perm); + if (StringUtils.findInvalidCharacter(repo) == null) { + // explicit permision, confirm repository + RepositoryModel r = gitblit.getRepositoryModel(repo); + if (r == null) { + throw new UnloggedFailure(1, String.format("Repository %s does not exist!", repo)); + } + } + AccessPermission ap = AccessPermission.permissionFromRole(perm); + user.setRepositoryPermission(repo, ap); + log.info(String.format("Setting %s:%s for %s", ap.name(), repo, username)); + } + modified = true; + } + + if (modified && gitblit.updateUserModel(username, user)) { + // reload & display new permissions + user = gitblit.getUserModel(username); + } + + showPermissions(user); + } + + protected void showPermissions(UserModel user) { + List perms = user.getRepositoryPermissions(); + String[] pheaders = { "Repository", "Permission", "Type", "Source", "Mutable" }; + Object [][] pdata = new Object[perms.size()][]; + for (int i = 0; i < perms.size(); i++) { + RegistrantAccessPermission ap = perms.get(i); + pdata[i] = new Object[] { ap.registrant, ap.permission, ap.permissionType, ap.source, ap.mutable ? "Y":"" }; + } + stdout.println(FlipTable.of(pheaders, pdata, Borders.BODY_HCOLS)); + } + } + + @CommandMetaData(name = "remove", aliases = { "rm" }, description = "Remove a user account") + @UsageExample(syntax = "${cmd} john", description = "Delete john's account") + public static class RemoveUser extends UserCommand { + @Override public void run() throws UnloggedFailure { + + UserModel user = getUser(true); IGitblit gitblit = getContext().getGitblit(); - UserModel u = gitblit.getUserModel(username); - if (u == null) { - throw new UnloggedFailure(1, String.format("Unknown user \"%s\"", username)); + if (gitblit.deleteUserModel(user)) { + stdout.println(String.format("%s has been deleted.", username)); + } else { + throw new UnloggedFailure(1, String.format("Failed to delete %s!", username)); } + } + } + + @CommandMetaData(name = "show", description = "Show the details of an account") + @UsageExample(syntax = "${cmd} john", description = "Display john's account") + public static class ShowUser extends UserCommand { + + @Override + public void run() throws UnloggedFailure { + + UserModel u = getUser(true); // fields String [] fheaders = new String [] { "Field", "Value" }; Object [][] fdata = new Object[5][]; fdata[0] = new Object [] { "Email", u.emailAddress }; fdata[1] = new Object [] { "Type", u.accountType }; - fdata[2] = new Object [] { "Can Admin", u.canAdmin() ? "Y":"N" }; - fdata[3] = new Object [] { "Can Fork", u.canFork() ? "Y":"N" }; - fdata[4] = new Object [] { "Can Create", u.canCreate() ? "Y":"N" }; + fdata[2] = new Object [] { "Can Admin", u.canAdmin() ? "Y":"" }; + fdata[3] = new Object [] { "Can Fork", u.canFork() ? "Y":"" }; + fdata[4] = new Object [] { "Can Create", u.canCreate() ? "Y":"" }; String fields = FlipTable.of(fheaders, fdata, Borders.COLS); // teams - String [] theaders = new String [] { "Team", "Type" }; - Object [][] tdata = new Object[u.teams.size()][]; - int i = 0; - for (TeamModel t : u.teams) { - tdata[i] = new Object [] { t.name, t.accountType }; - i++; + String teams; + if (u.teams.size() == 0) { + teams = FlipTable.EMPTY; + } else { + String [] theaders = new String [] { "Team", "Type" }; + Object [][] tdata = new Object[u.teams.size()][]; + int i = 0; + for (TeamModel t : u.teams) { + tdata[i] = new Object [] { t.name, t.accountType }; + i++; + } + teams = FlipTable.of(theaders, tdata, Borders.COLS); } - String teams = FlipTable.of(theaders, tdata, Borders.COLS); // permissions List perms = u.getRepositoryPermissions(); - String[] pheaders = { "Repository", "Permission", "Type", "Source", "Mutable" }; - Object [][] pdata = new Object[perms.size()][]; - for (i = 0; i < perms.size(); i++) { - RegistrantAccessPermission ap = perms.get(i); - pdata[i] = new Object[] { ap.registrant, ap.permission, ap.permissionType, ap.source, ap.mutable ? "Y":"N" }; + String permissions; + if (perms.isEmpty()) { + permissions = FlipTable.EMPTY; + } else { + String[] pheaders = { "Repository", "Permission", "Type", "Source", "Mutable" }; + Object [][] pdata = new Object[perms.size()][]; + for (int i = 0; i < perms.size(); i++) { + RegistrantAccessPermission ap = perms.get(i); + pdata[i] = new Object[] { ap.registrant, ap.permission, ap.permissionType, ap.source, ap.mutable ? "Y":"" }; + } + permissions = FlipTable.of(pheaders, pdata, Borders.COLS); } - String permissions = FlipTable.of(pheaders, pdata, Borders.COLS); // assemble user table - String [] headers = new String[] { u.getDisplayName() + (u.username.equals(u.getDisplayName()) ? "" : (" (" + u.username + ")")) }; + String userTitle = u.getDisplayName() + (u.username.equals(u.getDisplayName()) ? "" : (" (" + u.username + ")")); + if (u.disabled) { + userTitle += " [DISABLED]"; + } + String [] headers = new String[] { userTitle }; String[][] data = new String[6][]; data[0] = new String [] { "FIELDS" }; data[1] = new String [] { fields }; @@ -95,7 +328,11 @@ public class UsersDispatcher extends DispatchCommand { } } - @CommandMetaData(name = "list", aliases= { "ls" }, description = "List users") + @CommandMetaData(name = "list", aliases= { "ls" }, description = "List accounts") + @UsageExamples( examples = { + @UsageExample(syntax = "${cmd}", description = "List accounts as a table"), + @UsageExample(syntax = "${cmd} j.*", description = "List all accounts that start with 'j'"), + }) public static class ListUsers extends ListFilterCommand { @Override @@ -125,10 +362,12 @@ public class UsersDispatcher extends DispatchCommand { for (int i = 0; i < list.size(); i++) { UserModel u = list.get(i); - String name = u.disabled ? "-" : ((u.canAdmin() ? "*" : " ")) + u.username; + String name = (u.disabled ? "-" : ((u.canAdmin() ? "*" : " "))) + u.username; if (verbose) { data[i] = new Object[] { name, u.displayName, u.accountType, - u.emailAddress, u.canCreate() ? "Y":"", u.canFork() ? "Y" : ""}; + u.emailAddress, + (u.canAdmin() || u.canCreate()) ? "Y":"", + (u.canAdmin() || u.canFork()) ? "Y" : ""}; } else { data[i] = new Object[] { name, u.displayName, u.accountType, u.emailAddress }; @@ -147,8 +386,8 @@ public class UsersDispatcher extends DispatchCommand { u.getDisplayName(), u.accountType, u.emailAddress == null ? "" : u.emailAddress, - u.canCreate() ? "Y":"", - u.canFork() ? "Y" : ""); + (u.canAdmin() || u.canCreate()) ? "Y":"", + (u.canAdmin() || u.canFork()) ? "Y" : ""); } } else { for (UserModel u : users) { diff --git a/src/main/java/com/gitblit/utils/FlipTable.java b/src/main/java/com/gitblit/utils/FlipTable.java index 7aa5f0b1..0197517d 100644 --- a/src/main/java/com/gitblit/utils/FlipTable.java +++ b/src/main/java/com/gitblit/utils/FlipTable.java @@ -36,7 +36,7 @@ package com.gitblit.utils; * */ public final class FlipTable { - private static final String EMPTY = "(empty)"; + public static final String EMPTY = "(empty)"; public static enum Borders { FULL(15), BODY_HCOLS(13), HCOLS(12), BODY(9), HEADER(8), COLS(4); diff --git a/src/site/setup_transport_ssh.mkd b/src/site/setup_transport_ssh.mkd index c3d97a30..5bac2ff4 100644 --- a/src/site/setup_transport_ssh.mkd +++ b/src/site/setup_transport_ssh.mkd @@ -23,14 +23,14 @@ First you'll need to create an SSH key pair, if you don't already have one or if Then you can upload your *public* key right from the command-line. - cat ~/.ssh/id_rsa.pub | ssh -l -p 29418 gitblit keys add - cat c:\\.ssh\id_rsa.pub | ssh -l -p 29418 gitblit keys add + cat ~/.ssh/id_rsa.pub | ssh -l -p 29418 gitblit keys add - + cat c:\\.ssh\id_rsa.pub | ssh -l -p 29418 gitblit keys add - **NOTE:** It is important to note that *ssh-keygen* generates a public/private keypair (e.g. id_rsa and id_rsa.pub). You want to upload the *public* key, which is denoted by the *.pub* file extension. Once you've done both of those steps you should be able to execute the following command without a password prompt. - ssh -l -p 29418 gitblit version + ssh -l -p 29418 ### Setting up an SSH alias @@ -40,7 +40,7 @@ Typing the following command syntax all the time gets to be rather tedious. You can define an alias for your server which will reduce your command syntax to something like this. - ssh gitblit version + ssh Create or modify your `~/.ssh/config` file and add a host entry. If you are on Windows, you'll want to create or modify `\.ssh\config`, where *userfolder* is dependent on your version of Windows. Most recently this is `c:\users\`. @@ -62,22 +62,21 @@ The *gitblit* command has many subcommands for interacting with Gitblit. Add an SSH public key to your account. This command accepts a public key piped to stdin. - cat ~/.ssh/id_rsa.pub | ssh -l -p 29418 gitblit keys add - -##### keys remove + cat ~/.ssh/id_rsa.pub | ssh -l -p 29418 gitblit keys add - -Remove an SSH public key from your account. This command accepts a public key piped to stdin. +##### keys list - cat ~/.ssh/id_rsa.pub | ssh -l -p 29418 gitblit keys remove +Show the SSH public keys you have added to your account. -You can also remove all your public keys from your account. + ssh -l -p 29418 gitblit keys list - ssh -l -p 29418 gitblit keys remove ALL +##### keys remove -##### keys list +Remove an SSH public key from your account. This command accepts several input values, the most useful one is an index number which matches the index number displayed in the `list` command. -Show the SSH keys you have added to your account. + ssh -l -p 29418 gitblit keys remove 2 - ssh -l -p 29418 gitblit keys list +You can also remove all your public keys from your account. + ssh -l -p 29418 gitblit keys remove ALL -- 2.39.5