package core; import java.io.BufferedReader; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Properties; import org.apache.commons.io.FileUtils; import org.jenkinsci.test.acceptance.docker.fixtures.GitContainer; import org.zeroturnaround.zip.ZipUtil; import com.jcraft.jsch.ChannelExec; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import com.jcraft.jsch.SftpException; import static java.lang.ProcessBuilder.Redirect.*; import static java.nio.file.attribute.PosixFilePermission.*; import static java.util.Collections.*; import static org.jenkinsci.test.acceptance.docker.fixtures.GitContainer.*; /** * Manipulates git repository locally. * * @author Kohsuke Kawaguchi */ public class GitRepo implements Closeable { public final File dir; /** * Path to the script that acts like SSH. */ private File ssh; /** * Private key file that contains /ssh_keys/unsafe. */ private File privateKey; public GitRepo() { dir = initDir(); git("init"); setIdentity(dir); } public GitRepo(final String url) { dir = initDir(); git("clone", url, "."); setIdentity(dir); } /** * Configures and identity for the repo, just in case global config is not set. */ private void setIdentity(File dir) { gitDir(dir, "config", "user.name", "Jenkins-ATH"); gitDir(dir, "config", "user.email", "[email protected]"); } public void setIdentity(final String username, final String userMail) { gitDir(dir, "config", "user.name", username); gitDir(dir, "config", "user.email", userMail); } private File initDir() { try { // FIXME: perhaps this logic that makes it use a separate key should be moved elsewhere? privateKey = File.createTempFile("ssh", "key"); FileUtils.copyURLToFile(GitContainer.class.getResource("GitContainer/unsafe"), privateKey); Files.setPosixFilePermissions(privateKey.toPath(), singleton(OWNER_READ)); ssh = File.createTempFile("jenkins", "ssh"); FileUtils.writeStringToFile(ssh, "#!/bin/sh\n" + "exec ssh -o StrictHostKeyChecking=no -i " + privateKey.getAbsolutePath() + " \"$@\""); Files.setPosixFilePermissions(ssh.toPath(), new HashSet<>(Arrays.asList(OWNER_READ, OWNER_EXECUTE))); return createTempDir("git"); } catch (IOException e) { throw new AssertionError("Can't initialize git directory", e); } } public String git(Object... args) { return gitDir(this.dir, args); } public String gitDir(File dir, Object... args) { List<String> cmds = new ArrayList<>(); cmds.add("git"); for (Object a : args) { if (a != null) { cmds.add(a.toString()); } } ProcessBuilder pb = new ProcessBuilder(cmds); pb.environment().put("GIT_SSH", ssh.getAbsolutePath()); String errorMessage = cmds + " failed"; try { Process p = pb.directory(dir) .redirectInput(INHERIT) .redirectError(INHERIT) .start(); BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); StringBuilder builder = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { builder.append(line); builder.append(System.getProperty("line.separator")); } int r = p.waitFor(); if (r != 0) { throw new AssertionError(errorMessage); } return builder.toString(); } catch (InterruptedException | IOException e) { throw new AssertionError(errorMessage, e); } } /** * Appends the string "more" to the file "foo", adds it to the repository and commits it. * * @param message * commit message */ public void changeAndCommitFoo(final String message) { try { String fileName = "foo"; try (FileWriter o = new FileWriter(new File(dir, fileName), true)) { o.write("more"); } git("add", fileName); commit(message); } catch (IOException e) { throw new AssertionError("Can't append line to file foo", e); } } public void commitFileWithMessage(final String message, final String fileName, final String fileContent) { try { try (FileWriter o = new FileWriter(new File(dir, fileName), true)) { o.write(fileContent); } git("add", fileName); commit(message); } catch (IOException e) { throw new AssertionError("Can't append line to file foo", e); } } /** * Records all changes to the repository. * * @param message * commit message */ public void commit(final String message) { git("commit", "-m", message); } public void touch(final String fileName) { try { FileUtils.writeStringToFile(file(fileName), ""); } catch (IOException e) { throw new AssertionError("Can't change file " + fileName, e); } } /** * Get sha1 hash of the most recent commit. * * @return Hash value */ public String getLastSha1() { return git("rev-parse", "HEAD").trim(); } public void checkout(String name) { git("checkout", name); } public File file(String name) { return new File(dir, name); } @Override public void close() throws IOException { FileUtils.deleteDirectory(dir); ssh.delete(); privateKey.delete(); } /** * Add a submodule to the main repository. * * @param submoduleName * name of the submodule * @return gitRepo * mocked git repo */ public GitRepo addSubmodule(String submoduleName) { try { File submoduleDir = new File(createTempDir(submoduleName).getAbsolutePath() + "/" + submoduleName); submoduleDir.delete(); submoduleDir.mkdir(); gitDir(submoduleDir, "init"); setIdentity(submoduleDir); try (FileWriter o = new FileWriter(new File(submoduleDir, "foo"), true)) { o.write("more"); } gitDir(submoduleDir, "add", "foo"); gitDir(submoduleDir, "commit", "-m", "Initial commit"); git("submodule", "add", submoduleDir.getAbsolutePath()); git("commit", "-am", "Added submodule"); return this; } catch (IOException e) { throw new AssertionError("Can't create submodule " + submoduleName, e); } } private File createTempDir(String name) throws IOException { File tmp = File.createTempFile("jenkins", name); tmp.delete(); tmp.mkdir(); return tmp; } /** * Zip bare repository, copy to Docker container using sftp, then unzip. The repo is now accessible over * "ssh://git@ip:port/home/git/gitRepo.git" * * @param host * IP of Docker container * @param port * SSH port of Docker container */ public void transferToDockerContainer(String host, int port) { try { Path zipPath = Files.createTempFile("git", "zip"); File zippedRepo = zipPath.toFile(); String zippedFilename = zipPath.getFileName().toString(); ZipUtil.pack(new File(dir.getPath()), zippedRepo); Properties props = new Properties(); props.put("StrictHostKeyChecking", "no"); JSch jSch = new JSch(); jSch.addIdentity(privateKey.getAbsolutePath()); Session session = jSch.getSession("git", host, port); session.setConfig(props); session.connect(); ChannelSftp channel = (ChannelSftp) session.openChannel("sftp"); channel.connect(); channel.cd("/home/git"); channel.put(new FileInputStream(zippedRepo), zippedFilename); ChannelExec channelExec = (ChannelExec) session.openChannel("exec"); InputStream in = channelExec.getInputStream(); channelExec.setCommand("unzip " + zippedFilename + " -d " + REPO_NAME); channelExec.connect(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String line; int index = 0; while ((line = reader.readLine()) != null) { System.out.println(++index + " : " + line); } channelExec.disconnect(); channel.disconnect(); session.disconnect(); // Files.delete(zipPath); } catch (IOException | JSchException | SftpException e) { throw new AssertionError("Can't transfer git repository to docker container", e); } } private Path path(Path path) { return dir.toPath().resolve(path); } /** * Copies all files of the specified folder to the root folder of this git repository and adds the copied files * using 'git add. * * @param sourceFolder * the folder with the files to copy */ public void addFilesIn(final String sourceFolder) { addFilesIn(getResource(sourceFolder)); } private URL getResource(final String sourceFolder) { URL resource = getClass().getResource(sourceFolder); if (resource == null) { throw new IllegalArgumentException("No such directory: " + sourceFolder); } return resource; } /** * Copies all files of the specified directory to the {@code destinationFolder} of this git repository and adds the * copied files using git add. * * @param sourceFolder * the folder with the files to copy * @param destinationFolder * the destination folder for the copied files */ public void addFilesIn(final String sourceFolder, final String destinationFolder) { addFilesIn(getResource(sourceFolder), Paths.get(destinationFolder)); } /** * Copies all files of the specified folder to the root folder of this git repository and adds the copied files * using 'git add. * * @param sourceFolder * the folder with the files to copy */ public void addFilesIn(final URL sourceFolder) { addFilesIn(sourceFolder, dir.toPath()); } /** * Copies all files of the specified directory to the {@code destinationFolder} of this git repository and adds the * copied files using git add. * * @param sourceFolder * the folder with the files to copy * @param destinationFolder * the destination folder for the copied files */ public void addFilesIn(final URL sourceFolder, final Path destinationFolder) { Path gitPath; if (destinationFolder.isAbsolute()) { gitPath = destinationFolder; } else { gitPath = dir.toPath().resolve(destinationFolder); } try { Path source = Paths.get(sourceFolder.toURI()); try (DirectoryStream<Path> paths = Files.newDirectoryStream(source, entry -> !Files.isDirectory(entry))) { for (Path path : paths) { Files.copy(path, gitPath.resolve(path.getFileName())); } } git("add", "*"); } catch (URISyntaxException | IOException e) { throw new AssertionError(String.format("Can't copy files from %s", sourceFolder), e); } } /** * Creates the specified branch in this repository. * * @param name * the name of the branch */ public void createBranch(final String name) { git("branch", name); } public Path mkdir(String path) { try { return Files.createDirectories(dir.toPath().resolve(path)); } catch (IOException e) { throw new AssertionError(String.format("Can't created directories %s", path), e); } } }