Add backend functionality needed to import groups
With this commit, imported groups are actually persisted in the target
system.
Import of groups in a recursive way is not supported, i.e. owner and
member groups need to be already existent on the target system,
otherwise the import fails. A possible self-ownership relation of a
group is not considered to be a recursive import and is supported.
Change-Id: I3a291785f6201cf03a61a84940bcea370f83032c
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/GroupCommand.java b/src/main/java/com/googlesource/gerrit/plugins/importer/GroupCommand.java
index 54f65bb..a9b2d58 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/GroupCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/GroupCommand.java
@@ -19,17 +19,14 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.extensions.annotations.RequiresCapability;
-import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.config.ConfigResource;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.validators.ValidationException;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
@@ -60,18 +57,19 @@
private ImportGroup.Factory importGroupFactory;
@Override
- protected void run() throws OrmException, IOException, UnloggedFailure,
- ValidationException, GitAPIException, NoSuchChangeException,
+ protected void run() throws UnloggedFailure, OrmException, IOException,
NoSuchAccountException {
ImportGroup.Input input = new ImportGroup.Input();
input.from = url;
input.user = user;
input.pass = getPassword();
- Response<String> response = importGroupFactory.create(new AccountGroup.NameKey(group)).apply(
- new ConfigResource(), input);
- stdout.println(response);
-
+ try {
+ importGroupFactory.create(new AccountGroup.NameKey(group)).apply(
+ new ConfigResource(), input);
+ } catch (RestApiException e){
+ throw die(e.getMessage());
+ }
}
private String getPassword() throws IOException, UnloggedFailure {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ImportGroup.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ImportGroup.java
index 8b79900..97afa04 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ImportGroup.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ImportGroup.java
@@ -14,17 +14,40 @@
package com.googlesource.gerrit.plugins.importer;
+import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.PreconditionFailedException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.AccountGroupById;
+import com.google.gerrit.reviewdb.client.AccountGroupMember;
+import com.google.gerrit.reviewdb.client.AccountGroupName;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupIncludeCache;
import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.group.GroupJson.GroupInfo;
+import com.google.gwtorm.server.OrmDuplicateKeyException;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.googlesource.gerrit.plugins.importer.ImportGroup.Input;
+import org.eclipse.jgit.lib.Config;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
@RequiresCapability(ImportCapability.ID)
class ImportGroup implements RestModifyView<ConfigResource, Input> {
public static class Input {
@@ -39,16 +62,130 @@
private final GroupCache groupCache;
private final AccountGroup.NameKey group;
+ private final ReviewDb db;
+ private final AccountUtil accountUtil;
+ private final AccountCache accountCache;
+ private final GroupIncludeCache groupIncludeCache;
+ private final Config cfg;
+ private RemoteApi api;
@Inject
- ImportGroup(GroupCache groupCache, @Assisted AccountGroup.NameKey group) {
+ ImportGroup(AccountUtil accountUtil, GroupCache groupCache,
+ AccountCache accountCache, GroupIncludeCache groupIncludeCache,
+ ReviewDb db, @Assisted AccountGroup.NameKey group,
+ @GerritServerConfig Config cfg) {
+ this.db = db;
+ this.accountUtil = accountUtil;
this.groupCache = groupCache;
+ this.accountCache = accountCache;
+ this.groupIncludeCache = groupIncludeCache;
+ this.cfg = cfg;
this.group = group;
}
@Override
- public Response<String> apply(ConfigResource rsrc, Input input) {
- return Response.<String> ok("TODO");
+ public Response<String> apply(ConfigResource rsrc, Input input)
+ throws ResourceConflictException, PreconditionFailedException,
+ BadRequestException, NoSuchAccountException, OrmException, IOException {
+ GroupInfo groupInfo;
+ this.api = new RemoteApi(input.from, input.user, input.pass);
+ groupInfo = api.getGroup(group.get());
+ validate(groupInfo);
+ createGroup(groupInfo);
+
+ return Response.<String> ok("OK");
+ }
+
+ private void validate(GroupInfo groupInfo) throws ResourceConflictException,
+ PreconditionFailedException, BadRequestException, IOException,
+ OrmException {
+ if (groupCache.get(new AccountGroup.NameKey(groupInfo.name)) != null) {
+ throw new ResourceConflictException(String.format(
+ "Group with name %s already exists", groupInfo.name));
+ }
+ if (groupCache.get(new AccountGroup.UUID(groupInfo.id)) != null) {
+ throw new ResourceConflictException(String.format(
+ "Group with UUID %s already exists", groupInfo.id));
+ }
+ if (!groupInfo.id.equals(groupInfo.ownerId))
+ if (groupCache.get(new AccountGroup.UUID(groupInfo.ownerId)) == null) {
+ throw new PreconditionFailedException(String.format(
+ "Owner group with UUID %s does not exist", groupInfo.ownerId));
+ }
+ for (AccountInfo member : groupInfo.members) {
+ try {
+ accountUtil.resolveUser(api, member);
+ } catch (NoSuchAccountException e) {
+ throw new PreconditionFailedException(e.getMessage());
+ }
+ }
+ for (GroupInfo include : groupInfo.includes) {
+ if (groupCache.get(new AccountGroup.UUID(include.id)) == null) {
+ throw new PreconditionFailedException(String.format(
+ "Included group with UUID %s does not exist", include.id));
+ }
+ }
+ }
+
+ private AccountGroup createGroup(GroupInfo info) throws OrmException,
+ ResourceConflictException, NoSuchAccountException, BadRequestException,
+ IOException {
+ AccountGroup.Id groupId = new AccountGroup.Id(db.nextAccountGroupId());
+ AccountGroup.UUID uuid = new AccountGroup.UUID(info.id);
+ AccountGroup group =
+ new AccountGroup(new AccountGroup.NameKey(info.name), groupId, uuid);
+ group.setVisibleToAll(cfg.getBoolean("groups", "newGroupsVisibleToAll",
+ false));
+ group.setDescription(info.description);
+ AccountGroupName gn = new AccountGroupName(group);
+ // first insert the group name to validate that the group name hasn't
+ // already been used to create another group
+ try {
+ db.accountGroupNames().insert(Collections.singleton(gn));
+ } catch (OrmDuplicateKeyException e) {
+ throw new ResourceConflictException(info.name);
+ }
+ db.accountGroups().insert(Collections.singleton(group));
+
+ addMembers(groupId, info.members);
+ addGroups(groupId, info.includes);
+
+ groupCache.evict(group);
+
+ return group;
+ }
+
+ private void addMembers(AccountGroup.Id groupId, List<AccountInfo> members)
+ throws OrmException, NoSuchAccountException, BadRequestException,
+ IOException {
+ List<AccountGroupMember> memberships = new ArrayList<>();
+ for (AccountInfo member : members) {
+ Account.Id userId = accountUtil.resolveUser(api, member);
+ AccountGroupMember membership =
+ new AccountGroupMember(new AccountGroupMember.Key(userId, groupId));
+ memberships.add(membership);
+ }
+ db.accountGroupMembers().insert(memberships);
+
+ for (AccountInfo member : members) {
+ accountCache.evict(accountUtil.resolveUser(api, member));
+ }
+ }
+
+ private void addGroups(AccountGroup.Id groupId, List<GroupInfo> includedGroups)
+ throws OrmException {
+ List<AccountGroupById> includeList = new ArrayList<>();
+ for (GroupInfo includedGroup : includedGroups) {
+ AccountGroup.UUID memberUUID = new AccountGroup.UUID(includedGroup.id);
+ AccountGroupById groupInclude =
+ new AccountGroupById(new AccountGroupById.Key(groupId, memberUUID));
+ includeList.add(groupInclude);
+ }
+ db.accountGroupById().insert(includeList);
+
+ for (GroupInfo member : includedGroups) {
+ groupIncludeCache.evictParentGroupsOf(new AccountGroup.UUID(member.id));
+ }
}
}