Merge branch 'stable-3.3' into stable-3.4 * stable-3.3: Add missing project creation support for replicas Add missing project creation support Change-Id: I7fb51085a988e1699f4f2884143c9e04cc25bce3
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java index 3ded8eb..2a77e09 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
@@ -216,6 +216,18 @@ FetchRestApiClient fetchClient = fetchClientFactory.create(source); HttpResult result = fetchClient.callSendObject(project, refName, revision, uri); + if (!result.isSuccessful() + && source.isCreateMissingRepositories() + && result.isProjectMissing(project)) { + HttpResult initProjectResult = fetchClient.initProject(project, uri); + if (initProjectResult.isSuccessful()) { + result = fetchClient.callFetch(project, "refs/*", uri); + } else { + String errorMessage = + initProjectResult.getMessage().map(e -> " - Error: " + e).orElse(""); + repLog.error("Cannot create project " + project + errorMessage); + } + } if (!result.isSuccessful()) { repLog.warn( String.format(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java index 2d8bce4..45713b8 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java
@@ -733,6 +733,10 @@ return config.getMaxRetries(); } + public boolean isCreateMissingRepositories() { + return config.createMissingRepositories(); + } + private static boolean matches(URIish uri, String urlMatch) { if (urlMatch == null || urlMatch.equals("") || urlMatch.equals("*")) { return true;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfiguration.java index 8046d49..4858b17 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfiguration.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfiguration.java
@@ -38,6 +38,7 @@ private final int poolThreads; private final boolean replicatePermissions; private final boolean replicateHiddenProjects; + private final boolean createMissingRepositories; private final String remoteNameStyle; private final ImmutableList<String> urls; private final ImmutableList<String> projects; @@ -74,6 +75,7 @@ authGroupNames = ImmutableList.copyOf(cfg.getStringList("remote", name, "authGroup")); lockErrorMaxRetries = cfg.getInt("replication", "lockErrorMaxRetries", 0); + createMissingRepositories = cfg.getBoolean("remote", name, "createMissingRepositories", true); replicatePermissions = cfg.getBoolean("remote", name, "replicatePermissions", true); replicateHiddenProjects = cfg.getBoolean("remote", name, "replicateHiddenProjects", false); useCGitClient = cfg.getBoolean("replication", "useCGitClient", false); @@ -191,6 +193,10 @@ return maxRetries; } + public boolean createMissingRepositories() { + return createMissingRepositories; + } + private static int getInt(RemoteConfig rc, Config cfg, String name, int defValue) { return cfg.getInt("remote", rc.getName(), name, defValue); }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpModule.java index 7ae7d54..b2ef28d 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpModule.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpModule.java
@@ -35,6 +35,8 @@ DynamicSet.bind(binder(), AllRequestFilter.class) .to(PullReplicationFilter.class) .in(Scopes.SINGLETON); + } else { + serveRegex("/init-project/.*$").with(ProjectInitializationAction.class); } } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpServletOps.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpServletOps.java new file mode 100644 index 0000000..b424555 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpServletOps.java
@@ -0,0 +1,55 @@ +// Copyright (C) 2021 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.googlesource.gerrit.plugins.replication.pull.api; + +import static com.google.common.net.HttpHeaders.ACCEPT; +import static org.eclipse.jgit.util.HttpSupport.TEXT_PLAIN; + +import com.google.common.net.MediaType; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Arrays; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +class HttpServletOps { + + static boolean checkAcceptHeader(HttpServletRequest req, HttpServletResponse rsp) + throws IOException { + if (req.getHeader(ACCEPT) == null + || (req.getHeader(ACCEPT) != null + && !Arrays.asList( + MediaType.PLAIN_TEXT_UTF_8.toString(), + MediaType.ANY_TEXT_TYPE.toString(), + MediaType.ANY_TYPE.toString()) + .contains(req.getHeader(ACCEPT)))) { + setResponse( + rsp, + HttpServletResponse.SC_BAD_REQUEST, + "No advertised 'Accept' headers can be honoured. 'text/plain' should be provided in the request 'Accept' header."); + return false; + } + + return true; + } + + static void setResponse(HttpServletResponse httpResponse, int statusCode, String value) + throws IOException { + httpResponse.setContentType(TEXT_PLAIN); + httpResponse.setStatus(statusCode); + PrintWriter writer = httpResponse.getWriter(); + writer.print(value); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java new file mode 100644 index 0000000..8f5c9d0 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
@@ -0,0 +1,125 @@ +// Copyright (C) 2021 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.googlesource.gerrit.plugins.replication.pull.api; + +import static com.googlesource.gerrit.plugins.replication.pull.api.FetchApiCapability.CALL_FETCH_ACTION; +import static com.googlesource.gerrit.plugins.replication.pull.api.HttpServletOps.checkAcceptHeader; +import static com.googlesource.gerrit.plugins.replication.pull.api.HttpServletOps.setResponse; + +import com.google.common.flogger.FluentLogger; +import com.google.gerrit.entities.Project; +import com.google.gerrit.entities.RefNames; +import com.google.gerrit.extensions.restapi.Url; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.config.GerritServerConfig; +import com.google.gerrit.server.config.SitePaths; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import com.googlesource.gerrit.plugins.replication.LocalFS; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.Optional; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.transport.URIish; + +@Singleton +public class ProjectInitializationAction extends HttpServlet { + private static final long serialVersionUID = 1L; + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + public static final String PROJECT_NAME = "project-name"; + + private final SitePaths sitePath; + private final Config gerritConfig; + private final Provider<CurrentUser> userProvider; + + @Inject + ProjectInitializationAction( + @GerritServerConfig Config cfg, SitePaths sitePath, Provider<CurrentUser> userProvider) { + this.sitePath = sitePath; + this.gerritConfig = cfg; + this.userProvider = userProvider; + } + + @Override + protected void doPut( + HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) + throws ServletException, IOException { + + if (!checkAcceptHeader(httpServletRequest, httpServletResponse)) { + return; + } + + if (!userProvider.get().isIdentifiedUser()) { + setResponse( + httpServletResponse, + HttpServletResponse.SC_UNAUTHORIZED, + "Unauthorized user. '" + CALL_FETCH_ACTION + "' capability needed."); + return; + } + + String path = httpServletRequest.getRequestURI(); + String projectName = Url.decode(path.substring(path.lastIndexOf('/') + 1)); + + if (initProject(projectName)) { + setResponse( + httpServletResponse, + HttpServletResponse.SC_CREATED, + "Project " + projectName + " initialized"); + return; + } + + setResponse( + httpServletResponse, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + "Cannot initialize project " + projectName); + } + + protected boolean initProject(String projectName) { + Optional<URIish> maybeUri = getGitRepositoryURI(projectName); + if (!maybeUri.isPresent()) { + logger.atSevere().log("Cannot initialize project '{}'", projectName); + return false; + } + LocalFS localFS = new LocalFS(maybeUri.get()); + Project.NameKey projectNameKey = Project.NameKey.parse(projectName); + return localFS.createProject(projectNameKey, RefNames.HEAD); + } + + private Optional<URIish> getGitRepositoryURI(String projectName) { + Path basePath = sitePath.resolve(gerritConfig.getString("gerrit", null, "basePath")); + URIish uri; + + try { + uri = new URIish("file://" + basePath + "/" + projectName); + return Optional.of(uri); + } catch (URISyntaxException e) { + logger.atSevere().withCause(e).log("Unsupported URI for project " + projectName); + } + + return Optional.empty(); + } + + public static String getProjectInitializationUrl(String pluginName, String projectName) { + return String.format( + "a/plugins/%s/init-project/%s", pluginName, Url.encode(projectName) + ".git"); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilter.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilter.java index 65b8e1b..1425be6 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilter.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilter.java
@@ -15,10 +15,13 @@ package com.googlesource.gerrit.plugins.replication.pull.api; import static com.google.gerrit.httpd.restapi.RestApiServlet.SC_UNPROCESSABLE_ENTITY; +import static com.googlesource.gerrit.plugins.replication.pull.api.HttpServletOps.checkAcceptHeader; +import static com.googlesource.gerrit.plugins.replication.pull.api.HttpServletOps.setResponse; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static javax.servlet.http.HttpServletResponse.SC_CONFLICT; import static javax.servlet.http.HttpServletResponse.SC_CREATED; import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; +import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; import static javax.servlet.http.HttpServletResponse.SC_OK; import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; @@ -28,10 +31,12 @@ import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.IdString; import com.google.gerrit.extensions.restapi.ResourceConflictException; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.TopLevelResource; import com.google.gerrit.extensions.restapi.UnprocessableEntityException; +import com.google.gerrit.extensions.restapi.Url; import com.google.gerrit.httpd.AllRequestFilter; import com.google.gerrit.httpd.restapi.RestApiServlet; import com.google.gerrit.json.OutputFormat; @@ -48,6 +53,7 @@ import com.google.inject.TypeLiteral; import com.googlesource.gerrit.plugins.replication.pull.api.FetchAction.Input; import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionInput; +import com.googlesource.gerrit.plugins.replication.pull.api.exception.InitProjectException; import java.io.BufferedReader; import java.io.EOFException; import java.io.IOException; @@ -67,6 +73,7 @@ private FetchAction fetchAction; private ApplyObjectAction applyObjectAction; + private ProjectInitializationAction projectInitializationAction; private ProjectsCollection projectsCollection; private Gson gson; private Provider<CurrentUser> userProvider; @@ -75,10 +82,12 @@ public PullReplicationFilter( FetchAction fetchAction, ApplyObjectAction applyObjectAction, + ProjectInitializationAction projectInitializationAction, ProjectsCollection projectsCollection, Provider<CurrentUser> userProvider) { this.fetchAction = fetchAction; this.applyObjectAction = applyObjectAction; + this.projectInitializationAction = projectInitializationAction; this.projectsCollection = projectsCollection; this.userProvider = userProvider; this.gson = OutputFormat.JSON.newGsonBuilder().create(); @@ -107,6 +116,15 @@ } else { httpResponse.sendError(SC_UNAUTHORIZED); } + } else if (isInitProjectAction(httpRequest)) { + if (userProvider.get().isIdentifiedUser()) { + if (!checkAcceptHeader(httpRequest, httpResponse)) { + return; + } + doInitProject(httpRequest, httpResponse); + } else { + httpResponse.sendError(SC_UNAUTHORIZED); + } } else { chain.doFilter(request, response); } @@ -126,11 +144,27 @@ } catch (ResourceConflictException e) { RestApiServlet.replyError( httpRequest, httpResponse, SC_CONFLICT, e.getMessage(), e.caching(), e); + } catch (InitProjectException | ResourceNotFoundException e) { + RestApiServlet.replyError( + httpRequest, httpResponse, SC_INTERNAL_SERVER_ERROR, e.getMessage(), e.caching(), e); } catch (Exception e) { throw new ServletException(e); } } + private void doInitProject(HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws RestApiException, IOException { + + String path = httpRequest.getRequestURI(); + String projectName = Url.decode(path.substring(path.lastIndexOf('/') + 1)); + if (projectInitializationAction.initProject(projectName)) { + setResponse( + httpResponse, HttpServletResponse.SC_CREATED, "Project " + projectName + " initialized"); + return; + } + throw new InitProjectException(projectName); + } + @SuppressWarnings("unchecked") private Response<Map<String, Object>> doApplyObject(HttpServletRequest httpRequest) throws RestApiException, IOException, PermissionBackendException { @@ -222,4 +256,8 @@ private boolean isFetchAction(HttpServletRequest httpRequest) { return httpRequest.getRequestURI().endsWith("pull-replication~fetch"); } + + private boolean isInitProjectAction(HttpServletRequest httpRequest) { + return httpRequest.getRequestURI().contains("pull-replication/init-project/"); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/exception/InitProjectException.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/exception/InitProjectException.java new file mode 100644 index 0000000..85a7729 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/exception/InitProjectException.java
@@ -0,0 +1,25 @@ +// Copyright (C) 2021 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.googlesource.gerrit.plugins.replication.pull.api.exception; + +import com.google.gerrit.extensions.restapi.RestApiException; + +public class InitProjectException extends RestApiException { + private static final long serialVersionUID = 1L; + + public InitProjectException(String projectName) { + super("Cannot create project " + projectName); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java index 7b876df..811d064 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
@@ -15,6 +15,7 @@ package com.googlesource.gerrit.plugins.replication.pull.client; import static com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES; +import static com.googlesource.gerrit.plugins.replication.pull.api.ProjectInitializationAction.getProjectInitializationUrl; import static java.util.Objects.requireNonNull; import com.google.common.base.Strings; @@ -44,6 +45,7 @@ import org.apache.http.client.CredentialsProvider; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.BasicCredentialsProvider; @@ -109,6 +111,16 @@ return httpClientFactory.create(source).execute(post, this, getContext(targetUri)); } + public HttpResult initProject(Project.NameKey project, URIish uri) throws IOException { + String url = + String.format( + "%s/%s", uri.toString(), getProjectInitializationUrl(pluginName, project.get())); + HttpPut put = new HttpPut(url); + put.addHeader(new BasicHeader("Accept", MediaType.ANY_TEXT_TYPE.toString())); + put.addHeader(new BasicHeader("Content-Type", MediaType.PLAIN_TEXT_UTF_8.toString())); + return httpClientFactory.create(source).execute(put, this, getContext(uri)); + } + public HttpResult callSendObject( Project.NameKey project, String refName, RevisionData revisionData, URIish targetUri) throws ClientProtocolException, IOException {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/HttpResult.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/HttpResult.java index dc01295..bd164df 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/HttpResult.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/HttpResult.java
@@ -19,6 +19,7 @@ import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; import static javax.servlet.http.HttpServletResponse.SC_OK; +import com.google.gerrit.entities.Project; import java.util.Optional; public class HttpResult { @@ -38,6 +39,11 @@ return responseCode == SC_CREATED || responseCode == SC_NO_CONTENT || responseCode == SC_OK; } + public boolean isProjectMissing(Project.NameKey projectName) { + String projectMissingMessage = String.format("Not found: %s", projectName.get()); + return message.map(msg -> msg.contains(projectMissingMessage)).orElse(false); + } + public boolean isParentObjectMissing() { return responseCode == SC_CONFLICT; }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md index d4c5abf..f2a596b 100644 --- a/src/main/resources/Documentation/config.md +++ b/src/main/resources/Documentation/config.md
@@ -349,6 +349,11 @@ By default, 1 thread. +remote.NAME.createMissingRepositories +: Replicate newly created repositories. + + By default, true. + remote.NAME.authGroup : Specifies the name of a group that the remote should use to access the repositories. Multiple authGroups may be specified
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java index 327c6ba..286fe24 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
@@ -17,6 +17,7 @@ import static java.nio.file.Files.createTempDirectory; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -95,6 +96,7 @@ .thenReturn(httpResult); when(fetchRestApiClient.callFetch(any(), anyString(), any())).thenReturn(httpResult); when(httpResult.isSuccessful()).thenReturn(true); + when(httpResult.isProjectMissing(any())).thenReturn(false); objectUnderTest = new ReplicationQueue(wq, rd, dis, sl, fetchClientFactory, refsFilter, revReader); @@ -110,6 +112,32 @@ } @Test + public void shouldCallInitProjectWhenProjectIsMissing() throws IOException { + Event event = new TestEvent("refs/changes/01/1/meta"); + when(httpResult.isSuccessful()).thenReturn(false); + when(httpResult.isProjectMissing(any())).thenReturn(true); + when(source.isCreateMissingRepositories()).thenReturn(true); + + objectUnderTest.start(); + objectUnderTest.onGitReferenceUpdated(event); + + verify(fetchRestApiClient).initProject(any(), any()); + } + + @Test + public void shouldNotCallInitProjectWhenReplicateNewRepositoriesNotSet() throws IOException { + Event event = new TestEvent("refs/changes/01/1/meta"); + when(httpResult.isSuccessful()).thenReturn(false); + + when(source.isCreateMissingRepositories()).thenReturn(false); + + objectUnderTest.start(); + objectUnderTest.onGitReferenceUpdated(event); + + verify(fetchRestApiClient, never()).initProject(any(), any()); + } + + @Test public void shouldCallSendObjectWhenPatchSetRef() throws ClientProtocolException, IOException { Event event = new TestEvent("refs/changes/01/1/1"); objectUnderTest.start();
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationActionIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationActionIT.java new file mode 100644 index 0000000..919ac34 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationActionIT.java
@@ -0,0 +1,136 @@ +// Copyright (C) 2021 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.googlesource.gerrit.plugins.replication.pull.api; + +import static com.googlesource.gerrit.plugins.replication.pull.api.ProjectInitializationAction.getProjectInitializationUrl; + +import com.google.common.net.MediaType; +import com.google.gerrit.acceptance.config.GerritConfig; +import com.google.gerrit.extensions.restapi.Url; +import javax.servlet.http.HttpServletResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.message.BasicHeader; +import org.junit.Test; + +public class ProjectInitializationActionIT extends ActionITBase { + public static final String INVALID_TEST_PROJECT_NAME = "\0"; + private String testProjectName = "new/Project"; + + @Test + public void shouldReturnUnauthorizedForUserWithoutPermissions() throws Exception { + httpClientFactory + .create(source) + .execute( + createPutRequestWithHeaders(), + assertHttpResponseCode(HttpServletResponse.SC_UNAUTHORIZED), + getAnonymousContext()); + } + + @Test + public void shouldReturnBadRequestIfContentNotSet() throws Exception { + httpClientFactory + .create(source) + .execute( + createPutRequestWithoutHeaders(), + assertHttpResponseCode(HttpServletResponse.SC_BAD_REQUEST), + getContext()); + } + + @Test + public void shouldCreateRepository() throws Exception { + httpClientFactory + .create(source) + .execute( + createPutRequestWithHeaders(), + assertHttpResponseCode(HttpServletResponse.SC_CREATED), + getContext()); + + HttpGet getNewProjectRequest = + new HttpGet(userRestSession.url() + "/a/projects/" + Url.encode("new/Project")); + httpClientFactory + .create(source) + .execute( + getNewProjectRequest, assertHttpResponseCode(HttpServletResponse.SC_OK), getContext()); + } + + @Test + @GerritConfig(name = "container.replica", value = "true") + public void shouldCreateRepositoryWhenNodeIsAReplica() throws Exception { + httpClientFactory + .create(source) + .execute( + createPutRequestWithHeaders(), + assertHttpResponseCode(HttpServletResponse.SC_CREATED), + getContext()); + } + + @Test + @GerritConfig(name = "container.replica", value = "true") + public void shouldReturnInternalServerErrorIfProjectCannotBeCreatedWhenNodeIsAReplica() + throws Exception { + testProjectName = INVALID_TEST_PROJECT_NAME; + url = getURL(); + + httpClientFactory + .create(source) + .execute( + createPutRequestWithHeaders(), + assertHttpResponseCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR), + getContext()); + } + + @Test + @GerritConfig(name = "container.replica", value = "true") + public void shouldReturnBadRequestIfContentNotSetWhenNodeIsAReplica() throws Exception { + httpClientFactory + .create(source) + .execute( + createPutRequestWithoutHeaders(), + assertHttpResponseCode(HttpServletResponse.SC_BAD_REQUEST), + getContext()); + } + + @Test + @GerritConfig(name = "container.replica", value = "true") + public void shouldReturnUnauthorizedForUserWithoutPermissionsWhenNodeIsAReplica() + throws Exception { + httpClientFactory + .create(source) + .execute( + createPutRequestWithHeaders(), + assertHttpResponseCode(HttpServletResponse.SC_UNAUTHORIZED), + getAnonymousContext()); + } + + @Override + protected String getURL() { + return userRestSession.url() + + "/" + + getProjectInitializationUrl("pull-replication", Url.encode(testProjectName)); + } + + protected HttpPut createPutRequestWithHeaders() { + HttpPut put = createPutRequestWithoutHeaders(); + put.addHeader(new BasicHeader("Accept", MediaType.ANY_TEXT_TYPE.toString())); + put.addHeader(new BasicHeader("Content-Type", MediaType.PLAIN_TEXT_UTF_8.toString())); + return put; + } + + protected HttpPut createPutRequestWithoutHeaders() { + HttpPut put = new HttpPut(url); + return put; + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java index a3b0b02..39a8f2d 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java
@@ -39,6 +39,7 @@ import org.apache.http.Header; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; import org.apache.http.message.BasicHeader; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.storage.file.FileBasedConfig; @@ -67,6 +68,7 @@ @Mock ReplicationFileBasedConfig replicationConfig; @Mock Source source; @Captor ArgumentCaptor<HttpPost> httpPostCaptor; + @Captor ArgumentCaptor<HttpPut> httpPutCaptor; String api = "http://gerrit-host"; String pluginName = "pull-replication"; @@ -347,6 +349,19 @@ source)); } + @Test + public void shouldCallInitProjectEndpoint() throws IOException, URISyntaxException { + + objectUnderTest.initProject(Project.nameKey("test_repo"), new URIish(api)); + + verify(httpClient, times(1)).execute(httpPutCaptor.capture(), any(), any()); + + HttpPut httpPut = httpPutCaptor.getValue(); + assertThat(httpPut.getURI().getHost()).isEqualTo("gerrit-host"); + assertThat(httpPut.getURI().getPath()) + .isEqualTo("/a/plugins/pull-replication/init-project/test_repo.git"); + } + public String readPayload(HttpPost entity) throws UnsupportedOperationException, IOException { ByteBuffer buf = IO.readWholeStream(entity.getEntity().getContent(), 1024); return RawParseUtils.decode(buf.array(), buf.arrayOffset(), buf.limit()).trim();