blob: 573c050d76f7d0431afec45ef5f9b0531c668428 [file] [log] [blame]
// Copyright (C) 2020 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.google.gerrit.integration.git;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.PushOneCommit.FILE_CONTENT;
import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.GerritServer.TestSshServerAddress;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.StandaloneSiteTest;
import com.google.gerrit.acceptance.UseSsh;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.common.RawInputUtil;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.inject.Inject;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import org.junit.Test;
@NoHttpd
@UseSsh
public class UploadArchiveIT extends StandaloneSiteTest {
private static final String[] SSH_KEYGEN_CMD =
new String[] {"ssh-keygen", "-t", "rsa", "-q", "-P", "", "-f"};
private static final String GIT_SSH_COMMAND =
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o 'IdentitiesOnly yes' -i";
private static final String ARCHIVE = "archive";
@Inject private GerritApi gApi;
@Inject private @TestSshServerAddress InetSocketAddress sshAddress;
private String sshDestination;
private String identityPath;
private Project.NameKey project;
private CommitInfo commit;
@Test
@GerritConfig(name = "download.archive", value = "off")
public void archiveFeatureOff() throws Exception {
try (ServerContext ctx = startServer()) {
setUpTestHarness(ctx);
assertArchiveNotPermitted();
}
}
@Test
@GerritConfig(
name = "download.archive",
values = {"tar", "tbz2", "tgz", "txz"})
public void zipFormatDisabled() throws Exception {
try (ServerContext ctx = startServer()) {
setUpTestHarness(ctx);
assertArchiveNotPermitted();
}
}
@Test
public void verifyUploadArchiveFormats() throws Exception {
try (ServerContext ctx = startServer()) {
setUpTestHarness(ctx);
setUpChange();
for (String f : Arrays.asList("zip", "tar", "tar.gz", "tar.bz2", "tar.xz")) {
verifyUploadArchive(f);
}
}
}
private void verifyUploadArchive(String format) throws Exception {
Path outputPath = sitePaths.data_dir.resolve(ARCHIVE);
execute(
cmd(format, commit.commit),
sitePaths.data_dir.toFile(),
ImmutableMap.of("GIT_SSH_COMMAND", GIT_SSH_COMMAND + identityPath),
outputPath);
try (InputStream fi = Files.newInputStream(outputPath);
InputStream bi = new BufferedInputStream(fi);
ArchiveInputStream<?> archive = archiveStreamForFormat(bi, format)) {
assertEntries(archive);
}
}
private ArchiveInputStream<?> archiveStreamForFormat(InputStream bi, String format)
throws IOException {
switch (format) {
case "zip":
return new ZipArchiveInputStream(bi);
case "tar":
return new TarArchiveInputStream(bi);
case "tar.gz":
return new TarArchiveInputStream(new GzipCompressorInputStream(bi));
case "tar.bz2":
return new TarArchiveInputStream(new BZip2CompressorInputStream(bi));
case "tar.xz":
return new TarArchiveInputStream(new XZCompressorInputStream(bi));
default:
throw new IllegalArgumentException("Unknown archive format: " + format);
}
}
private void setUpTestHarness(ServerContext ctx) throws RestApiException, Exception {
ctx.getInjector().injectMembers(this);
project = Project.nameKey("upload-archive-project-test");
gApi.projects().create(project.get());
setUpAuthentication();
sshDestination =
String.format(
"ssh://%s@%s:%s/%s",
admin.username(), sshAddress.getHostName(), sshAddress.getPort(), project.get());
identityPath =
sitePaths.data_dir.resolve(String.format("id_rsa_%s", admin.username())).toString();
}
private void setUpAuthentication() throws Exception {
execute(
ImmutableList.<String>builder()
.add(SSH_KEYGEN_CMD)
.add(String.format("id_rsa_%s", admin.username()))
.build());
gApi.accounts()
.id(admin.id().get())
.addSshKey(
new String(
java.nio.file.Files.readAllBytes(
sitePaths.data_dir.resolve(String.format("id_rsa_%s.pub", admin.username()))),
UTF_8));
}
private ImmutableList<String> cmd(String format, String commit) {
return ImmutableList.<String>builder()
.add("git")
.add("archive")
.add("-f=" + format)
.add("--prefix=" + commit + "/")
// --remote makes git execute "git archive" on the server through SSH.
// The Gerrit/JGit version of the command understands the --compression-level
// argument below.
.add("--remote=" + sshDestination)
.add("--compression-level=1") // set to 1 to reduce the memory footprint
.add(commit)
.add(FILE_NAME)
.build();
}
private String execute(ImmutableList<String> cmd) throws Exception {
return execute(cmd, sitePaths.data_dir.toFile(), ImmutableMap.of());
}
private void assertArchiveNotPermitted() {
IOException exception =
assertThrows(
IOException.class,
() ->
execute(
cmd("zip", "master"),
sitePaths.data_dir.toFile(),
ImmutableMap.of("GIT_SSH_COMMAND", GIT_SSH_COMMAND + identityPath)));
assertThat(exception)
.hasMessageThat()
.contains("fatal: upload-archive not permitted for format zip");
}
private void setUpChange() throws Exception {
ChangeInput in = new ChangeInput(project.get(), "master", "Test change");
in.newBranch = true;
String changeId = gApi.changes().create(in).info().changeId;
gApi.changes().id(changeId).edit().modifyFile(FILE_NAME, RawInputUtil.create(FILE_CONTENT));
gApi.changes().id(changeId).edit().publish();
commit = gApi.changes().id(changeId).current().commit(false);
}
private void assertEntries(ArchiveInputStream o) throws IOException {
Set<String> entryNames = new TreeSet<>();
ArchiveEntry e;
while ((e = o.getNextEntry()) != null) {
entryNames.add(e.getName());
}
assertThat(entryNames)
.containsExactly(
String.format("%s/", commit.commit), String.format("%s/%s", commit.commit, FILE_NAME))
.inOrder();
}
}