Added a ls-members command to the ssh daemon.
Change-Id: Ie6272d48dfab90290ba6164acb01e1507f8d98b5
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 780231c..5637e80 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -60,6 +60,9 @@
link:cmd-ls-groups.html[gerrit ls-groups]::
List groups visible to the caller.
+link:cmd-ls-members.html[gerrit ls-members]::
+ List the membership of a group visible to the caller.
+
link:cmd-ls-projects.html[gerrit ls-projects]::
List projects visible to the caller.
diff --git a/Documentation/cmd-ls-members.txt b/Documentation/cmd-ls-members.txt
new file mode 100644
index 0000000..9814ff2
--- /dev/null
+++ b/Documentation/cmd-ls-members.txt
@@ -0,0 +1,64 @@
+gerrit ls-members
+================
+
+NAME
+----
+gerrit ls-members - Show members of a given group
+
+SYNOPSIS
+--------
+[verse]
+'ssh' -p <port> <host> 'gerrit ls-members GROUPNAME'
+ [--recursive]
+
+DESCRIPTION
+-----------
+Displays the members of the given group, one per line, so long as the given
+group is visible to the user. The users' id, username, full name and email are
+shown tab-separated.
+
+ACCESS
+------
+Any user who has configured an SSH key.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts. Output is either an error
+message or a heading followed by zero or more lines, one for each member of the
+group. If any field is not set, or if the field is the user's full name and the
+name is empty, "n/a" is emitted as the field value.
+
+All non-printable characters (ASCII value 31 or less) are escaped
+according to the conventions used in languages like C, Python, and Perl,
+employing standard sequences like `\n` and `\t`, and `\xNN` for all
+others. In shell scripts, the `printf` command can be used to unescape
+the output.
+
+OPTIONS
+-------
+--recursive::
+ If a member of the group is itself a group, the sub-group's
+ members are included in the list. Otherwise members of any sub-group
+ are not shown and no indication is given that a sub-group is present
+
+EXAMPLES
+--------
+
+List members of the Administrators group:
+=====
+ $ ssh -p 29418 review.example.com gerrit ls-members Administrators
+ id username full name email
+ 100000 jim Jim Bob somebody@example.com
+ 100001 johnny John Smith n/a
+ 100002 mrnoname n/a someoneelse@example.com
+=====
+
+List members of a non-existent group:
+=====
+ $ ssh -p 29418 review.example.com gerrit ls-members BadlySpelledGroup
+ Group not found or not visible
+=====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index f17a741..54b10fd 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -31,7 +31,8 @@
{
"_account_id": 1000096,
"name": "John Doe",
- "email": "john.doe@example.com"
+ "email": "john.doe@example.com",
+ "username": "john"
}
----
@@ -410,6 +411,8 @@
|`email` |optional|
The email address the user prefers to be contacted through. +
Only set if detailed account information is requested.
+|`username` |optional|The username of the user. +
+Only set if detailed account information is requested.
|===========================
[[capability-info]]
diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt
index e800d56..4785350 100644
--- a/Documentation/rest-api-groups.txt
+++ b/Documentation/rest-api-groups.txt
@@ -293,12 +293,14 @@
{
"_account_id": 1000097,
"name": "Jane Roe",
- "email": "jane.roe@example.com"
+ "email": "jane.roe@example.com",
+ "username": "jane"
},
{
"_account_id": 1000096,
"name": "John Doe",
"email": "john.doe@example.com"
+ "username": "john"
}
],
"includes": []
@@ -620,12 +622,14 @@
{
"_account_id": 1000097,
"name": "Jane Roe",
- "email": "jane.roe@example.com"
+ "email": "jane.roe@example.com",
+ "username": "jane"
},
{
"_account_id": 1000096,
"name": "John Doe",
- "email": "john.doe@example.com"
+ "email": "john.doe@example.com",
+ "username": "john"
}
]
----
@@ -657,17 +661,20 @@
{
"_account_id": 1000097,
"name": "Jane Roe",
- "email": "jane.roe@example.com"
+ "email": "jane.roe@example.com",
+ "username": "jane"
},
{
"_account_id": 1000096,
"name": "John Doe",
- "email": "john.doe@example.com"
+ "email": "john.doe@example.com",
+ "username": "john"
},
{
"_account_id": 1000098,
"name": "Richard Roe",
- "email": "richard.roe@example.com"
+ "email": "richard.roe@example.com",
+ "username": "rroe"
}
]
----
@@ -698,7 +705,8 @@
{
"_account_id": 1000096,
"name": "John Doe",
- "email": "john.doe@example.com"
+ "email": "john.doe@example.com",
+ "username": "john"
}
----
@@ -728,7 +736,8 @@
{
"_account_id": 1000037,
"name": "John Doe",
- "email": "john.doe@example.com"
+ "email": "john.doe@example.com",
+ "username": "john"
}
----
@@ -782,12 +791,14 @@
{
"_account_id": 1000057,
"name": "Jane Roe",
- "email": "jane.roe@example.com"
+ "email": "jane.roe@example.com",
+ "username": "jane"
},
{
"_account_id": 1000037,
"name": "John Doe",
- "email": "john.doe@example.com"
+ "email": "john.doe@example.com",
+ "username": "john"
}
]
----
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java
index a296716..2f95368 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java
@@ -112,12 +112,14 @@
public Integer _account_id;
public String name;
public String email;
+ public String username;
private void fill(Account account, boolean detailed) {
name = account.getFullName();
if (detailed) {
_account_id = account.getId().get();
email = account.getPreferredEmail();
+ username = account.getUserName();
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
index d32c632..28f908c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
@@ -49,7 +49,7 @@
private boolean recursive;
@Inject
- ListMembers(GroupCache groupCache,
+ protected ListMembers(GroupCache groupCache,
GroupDetailFactory.Factory groupDetailFactory,
AccountInfo.Loader.Factory accountLoaderFactory) {
this.groupCache = groupCache;
@@ -63,8 +63,19 @@
if (resource.toAccountGroup() == null) {
throw new MethodNotAllowedException();
}
+
+ return apply(resource.getGroupUUID());
+ }
+
+ public List<AccountInfo> apply(AccountGroup group)
+ throws MethodNotAllowedException, OrmException {
+ return apply(group.getGroupUUID());
+ }
+
+ public List<AccountInfo> apply(AccountGroup.UUID groupId)
+ throws MethodNotAllowedException, OrmException {
final Map<Account.Id, AccountInfo> members =
- getMembers(resource.getGroupUUID(), new HashSet<AccountGroup.UUID>());
+ getMembers(groupId, new HashSet<AccountGroup.UUID>());
final List<AccountInfo> memberInfos = Lists.newArrayList(members.values());
Collections.sort(memberInfos, new Comparator<AccountInfo>() {
@Override
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index 35a4e14..521103b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -39,6 +39,7 @@
command(gerrit, BanCommitCommand.class);
command(gerrit, FlushCaches.class);
command(gerrit, ListProjectsCommand.class);
+ command(gerrit, ListMembersCommand.class);
command(gerrit, ListGroupsCommand.class);
command(gerrit, LsUserRefs.class);
command(gerrit, Query.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
new file mode 100644
index 0000000..c5bda3c
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
@@ -0,0 +1,116 @@
+// Copyright (C) 2013 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.sshd.commands;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountInfo;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupDetailFactory.Factory;
+import com.google.gerrit.server.group.ListMembers;
+import com.google.gerrit.server.ioutil.ColumnFormatter;
+import com.google.gerrit.sshd.BaseCommand;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gwtorm.server.OrmException;
+
+import org.apache.sshd.server.Environment;
+import org.kohsuke.args4j.Argument;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Implements a command that allows the user to see the members of a group.
+ */
+@CommandMetaData(name = "ls-members", descr = "Lists the members of a given group")
+public class ListMembersCommand extends BaseCommand {
+ @Inject
+ ListMembersCommandImpl impl;
+
+ @Override
+ public void start(Environment env) {
+ startThread(new CommandRunnable() {
+ @Override
+ public void run() throws Exception {
+ parseCommandLine(impl);
+ final PrintWriter stdout = toPrintWriter(out);
+ try {
+ impl.display(stdout);
+ } finally {
+ stdout.flush();
+ }
+ }
+ });
+ }
+
+ private static class ListMembersCommandImpl extends ListMembers {
+ @Argument(required = true, usage = "the name of the group", metaVar = "GROUPNAME")
+ private String name;
+
+ private final GroupCache groupCache;
+
+ @Inject
+ protected ListMembersCommandImpl(GroupCache groupCache,
+ Factory groupDetailFactory,
+ AccountInfo.Loader.Factory accountLoaderFactory,
+ AccountCache accountCache) {
+ super(groupCache, groupDetailFactory, accountLoaderFactory);
+ this.groupCache = groupCache;
+ }
+
+ void display(PrintWriter writer) throws UnloggedFailure, OrmException {
+ AccountGroup group = groupCache.get(new AccountGroup.NameKey(name));
+ String errorText = "Group not found or not visible\n";
+
+ if (group == null) {
+ writer.write(errorText);
+ writer.flush();
+ return;
+ }
+
+ try {
+ List<AccountInfo> members = apply(group.getGroupUUID());
+ ColumnFormatter formatter = new ColumnFormatter(writer, '\t');
+ formatter.addColumn("id");
+ formatter.addColumn("username");
+ formatter.addColumn("full name");
+ formatter.addColumn("email");
+ formatter.nextLine();
+ for (AccountInfo member : members) {
+ if (member == null) {
+ continue;
+ }
+
+ formatter.addColumn(member._id.toString());
+ formatter.addColumn(Objects.firstNonNull(member.username, "n/a"));
+ formatter.addColumn(Objects.firstNonNull(
+ Strings.emptyToNull(member.name), "n/a"));
+ formatter.addColumn(Objects.firstNonNull(member.email, "n/a"));
+ formatter.nextLine();
+ }
+
+ formatter.finish();
+ } catch (MethodNotAllowedException e) {
+ writer.write(errorText);
+ writer.flush();
+ }
+ }
+ }
+}