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(); }