/**
* This file is part of git-as-svn. It is subject to the license terms
* in the LICENSE file found in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/gpl-2.0.html. No part of git-as-svn,
* including this file, may be copied, modified, propagated, or distributed
* except according to the terms contained in the LICENSE file.
*/
package svnserver.ext.gitlab.mapping;
import org.gitlab.api.GitlabAPI;
import org.gitlab.api.models.GitlabProject;
import org.gitlab.api.models.GitlabSystemHook;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tmatesoft.svn.core.SVNException;
import svnserver.config.GitRepositoryConfig;
import svnserver.config.RepositoryMappingConfig;
import svnserver.config.serializer.ConfigType;
import svnserver.context.SharedContext;
import svnserver.ext.gitlab.config.GitLabContext;
import svnserver.ext.web.server.WebServer;
import svnserver.repository.VcsRepositoryMapping;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.net.URL;
import java.util.List;
/**
* Repository list mapping.
*
* @author Artem V. Navrotskiy <bozaro@users.noreply.github.com>
*/
@ConfigType("gitlabMapping")
public class GitLabMappingConfig implements RepositoryMappingConfig {
@NotNull
private GitRepositoryConfig template = new GitRepositoryConfig();
@NotNull
private String path = "/var/git/repositories/";
private int cacheTimeSec = 15;
private int cacheMaximumSize = 1000;
@NotNull
public GitRepositoryConfig getTemplate() {
return template;
}
@NotNull
public String getPath() {
return path;
}
public int getCacheTimeSec() {
return cacheTimeSec;
}
public int getCacheMaximumSize() {
return cacheMaximumSize;
}
@NotNull
@Override
public VcsRepositoryMapping create(@NotNull SharedContext context) throws IOException, SVNException {
final GitLabContext gitlab = context.sure(GitLabContext.class);
final GitlabAPI api = gitlab.connect();
// Get repositories.
final GitLabMapping mapping = new GitLabMapping(context, this);
for (GitlabProject project : api.getAllProjects()) {
mapping.addRepository(project);
}
// Web hook for repository list update.
final WebServer webServer = WebServer.get(context);
final URL hookUrl = new URL(gitlab.getHookUrl());
webServer.addServlet(hookUrl.getPath(), new GitLabHookServlet(mapping));
if (!isHookInstalled(api, hookUrl.toString())) {
api.addSystemHook(hookUrl.toString());
}
return mapping;
}
private boolean isHookInstalled(@NotNull GitlabAPI api, @NotNull String hookUrl) throws IOException {
final List<GitlabSystemHook> hooks = api.getSystemHooks();
for (GitlabSystemHook hook : hooks) {
if (hook.getUrl().equals(hookUrl)) {
return true;
}
}
return false;
}
private static class GitLabHookServlet extends HttpServlet {
@NotNull
private static final Logger log = LoggerFactory.getLogger(GitLabHookServlet.class);
@NotNull
private final GitLabMapping mapping;
public GitLabHookServlet(@NotNull GitLabMapping mapping) {
this.mapping = mapping;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
final GitLabHookEvent event = parseEvent(req);
if (event == null || event.getEventName() == null) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Can't parse event data");
return;
}
try {
switch (event.getEventName()) {
case "project_create":
if (event.getProjectId() == null || event.getPathWithNamespace() == null) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Can't parse event data");
return;
}
final GitlabAPI api = mapping.getContext().sure(GitLabContext.class).connect();
final GitLabProject project = mapping.addRepository(api.getProject(event.getProjectId()));
if (project != null) {
project.initRevisions();
}
return;
case "project_destroy":
if (event.getProjectId() == null || event.getPathWithNamespace() == null) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Can't parse event data");
return;
}
mapping.removeRepository(event.getProjectId(), event.getPathWithNamespace());
break;
default:
// Ignore hook.
return;
}
super.doPost(req, resp);
} catch (FileNotFoundException inored) {
log.warn("Event repository not exists: " + event.getProjectId());
} catch (SVNException e) {
log.error("Event processing error: " + event.getEventName(), e);
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
}
@Nullable
private GitLabHookEvent parseEvent(@NotNull HttpServletRequest req) {
try (final Reader reader = req.getReader()) {
return GitLabHookEvent.parseEvent(reader);
} catch (IOException e) {
log.warn("Can't read hook data", e);
return null;
}
}
}
}