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