Hookup a REST API handler to allow manual triggers of SuperManifest.
This allows for the initial population of a superproject, and recovery
for failed runs.
Change-Id: I9b650b6b7729d268b54828c89136e8d40b8d2993
diff --git a/src/main/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestModule.java b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestModule.java
index fd65725..33aa1dd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestModule.java
@@ -14,14 +14,15 @@
package com.googlesource.gerrit.plugins.supermanifest;
+import static com.google.gerrit.server.project.BranchResource.BRANCH_KIND;
import static com.google.inject.Scopes.SINGLETON;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.inject.AbstractModule;
+import com.google.gerrit.extensions.restapi.RestApiModule;
-public class SuperManifestModule extends AbstractModule {
+public class SuperManifestModule extends RestApiModule {
SuperManifestModule() {}
@Override
@@ -32,5 +33,6 @@
DynamicSet.bind(binder(), LifecycleListener.class)
.to(SuperManifestRefUpdatedListener.class)
.in(SINGLETON);
+ post(BRANCH_KIND, "update_manifest").to(SuperManifestRefUpdatedListener.class).in(SINGLETON);
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java
index ca339ac..1bc9976 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java
@@ -18,17 +18,27 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.BranchResource;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.Closeable;
import java.io.IOException;
@@ -62,7 +72,9 @@
*/
@Singleton
public class SuperManifestRefUpdatedListener
- implements GitReferenceUpdatedListener, LifecycleListener {
+ implements GitReferenceUpdatedListener,
+ LifecycleListener,
+ RestModifyView<BranchResource, BranchInput> {
private static final Logger log = LoggerFactory.getLogger(SuperManifestRefUpdatedListener.class);
private final GitRepositoryManager repoManager;
@@ -72,6 +84,8 @@
private final AllProjectsName allProjectsName;
private final ProjectCache projectCache;
private final PersonIdent serverIdent;
+ private final Provider<IdentifiedUser> identifiedUser;
+ private final PermissionBackend permissionBackend;
// Mutable.
private Set<ConfigEntry> config;
@@ -84,7 +98,10 @@
PluginConfigFactory cfgFactory,
ProjectCache projectCache,
@GerritPersonIdent PersonIdent serverIdent,
- GitRepositoryManager repoManager) {
+ GitRepositoryManager repoManager,
+ Provider<IdentifiedUser> identifiedUser,
+ PermissionBackend permissionBackend) {
+
this.pluginName = pluginName;
this.serverIdent = serverIdent;
this.allProjectsName = allProjectsName;
@@ -97,6 +114,8 @@
this.cfgFactory = cfgFactory;
this.projectCache = projectCache;
+ this.identifiedUser = identifiedUser;
+ this.permissionBackend = permissionBackend;
}
private void warn(String formatStr, Object... args) {
@@ -217,24 +236,48 @@
}
return;
}
+ try {
+ update(event.getProjectName(), event.getRefName(), true);
+ } catch (Exception e) {
+ // no exceptions since we set continueOnError = true.
+ }
+ }
+ @Override
+ public Response<?> apply(BranchResource resource, BranchInput input)
+ throws IOException, ConfigInvalidException, GitAPIException, AuthException,
+ PermissionBackendException {
+ permissionBackend.user(identifiedUser).check(GlobalPermission.ADMINISTRATE_SERVER);
+ update(resource.getProjectState().getProject().getName(), resource.getRef(), false);
+ return Response.none();
+ }
+
+ /**
+ * Updates projects in response to update in given project/ref. Only throws exceptions if
+ * continueOnError is false.
+ */
+ private void update(String project, String refName, boolean continueOnError)
+ throws IOException, GitAPIException, ConfigInvalidException {
for (ConfigEntry c : config) {
- if (!c.srcRepoKey.get().equals(event.getProjectName())) {
+ if (!c.srcRepoKey.get().equals(project)) {
continue;
}
- if (!(c.destBranch.equals("*") || c.srcRef.equals(event.getRefName()))) {
+ if (!(c.destBranch.equals("*") || c.srcRef.equals(refName))) {
continue;
}
- if (c.destBranch.equals("*") && !event.getRefName().startsWith(REFS_HEADS)) {
+ if (c.destBranch.equals("*") && !refName.startsWith(REFS_HEADS)) {
continue;
}
try {
- updateForConfig(c, event);
+ updateForConfig(c, refName);
} catch (ConfigInvalidException | IOException | GitAPIException e) {
- // We only want the trace up to here. We could recurse into the exception, but this at least
+ if (!continueOnError) {
+ throw e;
+ }
+ // We only want the trace up to here. We could recurse into the exception, but this at least
// trims the very common jgit.gitrepo.RepoCommand.RemoteUnavailableException.
StackTraceElement here = Thread.currentThread().getStackTrace()[1];
e.setStackTrace(trimStack(e.getStackTrace(), here));
@@ -246,12 +289,13 @@
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
- error("update for %s (ref %s) failed: %s", c.toString(), event.getRefName(), sw);
+ error("update for %s (ref %s) failed: %s", c.toString(), refName, sw);
}
}
}
- private void updateForConfig(ConfigEntry c, Event event) throws ConfigInvalidException, IOException, GitAPIException {
+ private void updateForConfig(ConfigEntry c, String refName)
+ throws ConfigInvalidException, IOException, GitAPIException {
SubModuleUpdater subModuleUpdater;
switch (c.getToolType()) {
case Repo:
@@ -265,7 +309,7 @@
String.format("invalid toolType: %s", c.getToolType().name()));
}
try (GerritRemoteReader reader = new GerritRemoteReader()) {
- subModuleUpdater.update(reader, c, event.getRefName());
+ subModuleUpdater.update(reader, c, refName);
}
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/supermanifest/RepoSuperManifestIT.java b/src/test/java/com/googlesource/gerrit/plugins/supermanifest/RepoSuperManifestIT.java
index 6775998..494648c 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/supermanifest/RepoSuperManifestIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/supermanifest/RepoSuperManifestIT.java
@@ -21,6 +21,7 @@
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.TestPlugin;
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -166,6 +167,54 @@
assertThat(branch.file("project3").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
}
+ @Test
+ public void httpEndpoint() throws Exception {
+ setupTestRepos("project");
+
+ // Make sure the manifest exists so the configuration loads successfully.
+ Project.NameKey manifestKey = createProject("manifest");
+ TestRepository<InMemoryRepository> manifestRepo = cloneProject(manifestKey, admin);
+
+ Project.NameKey superKey = createProject("superproject");
+ cloneProject(superKey, admin);
+
+ String remoteXml = " <remote name=\"origin\" fetch=\"" + canonicalWebUrl.get() + "\" />\n";
+ String defaultXml = " <default remote=\"origin\" revision=\"refs/heads/master\" />\n";
+ String xml =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<manifest>\n"
+ + remoteXml
+ + defaultXml
+ + " <project name=\""
+ + testRepoKeys[0].get()
+ + "\" path=\"project1\" />\n"
+ + "</manifest>\n";
+
+ pushFactory
+ .create(db, admin.getIdent(), manifestRepo, "Subject", "default.xml", xml)
+ .to("refs/heads/srcbranch")
+ .assertOkStatus();
+
+ // Push config after XML. Needs a manual trigger to create the destination.
+ pushConfig(
+ "[superproject \""
+ + superKey.get()
+ + ":refs/heads/destbranch\"]\n"
+ + " srcRepo = "
+ + manifestKey.get()
+ + "\n"
+ + " srcRef = refs/heads/srcbranch\n"
+ + " srcPath = default.xml\n");
+
+ RestResponse r = userRestSession.post("/projects/" + manifestKey + "/branches/srcbranch/update_manifest");
+ r.assertForbidden();
+ r = adminRestSession.post("/projects/" + manifestKey + "/branches/srcbranch/update_manifest");
+ r.assertNoContent();
+
+ BranchApi branch = gApi.projects().name(superKey.get()).branch("refs/heads/destbranch");
+ assertThat(branch.file("project1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
+ }
+
private void outer() {
inner();
}