io.jenkins.blueocean.blueocean_git_pipeline.GitReadSaveTest.java Source code

Java tutorial

Introduction

Here is the source code for io.jenkins.blueocean.blueocean_git_pipeline.GitReadSaveTest.java

Source

/*
 * The MIT License
 *
 * Copyright (c) 2017, CloudBees, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package io.jenkins.blueocean.blueocean_git_pipeline;

import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey;
import com.google.common.collect.ImmutableMap;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.KeyPair;
import hudson.model.User;
import hudson.remoting.Base64;
import io.jenkins.blueocean.rest.impl.pipeline.PipelineBaseTest;
import io.jenkins.blueocean.ssh.UserSSHKeyManager;
import io.jenkins.blueocean.test.ssh.SSHServer;
import jenkins.plugins.git.GitSCMFileSystem;
import jenkins.plugins.git.GitSampleRepoRule;
import jenkins.scm.impl.mock.AbstractSampleDVCSRepoRule;
import jenkins.scm.impl.mock.AbstractSampleRepoRule;
import org.apache.commons.io.FileUtils;
import org.apache.sshd.common.util.OsUtils;
import org.eclipse.jgit.lib.Repository;
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
 * Testing the git load/save backend
 * @author kzantow
 */
public class GitReadSaveTest extends PipelineBaseTest {
    @Rule
    public GitSampleRepoRule repoWithJenkinsfiles = new GitSampleRepoRule();

    @Rule
    public GitSampleRepoRule repoNoJenkinsfile = new GitSampleRepoRule();

    @Rule
    public GitSampleRepoRule repoForSSH = new GitSampleRepoRule();

    private final Logger logger = Logger.getLogger(getClass().getName());

    private SSHServer sshd;

    public GitReadSaveTest() {
    }

    @Before
    @Override
    public void setup() throws Exception {
        super.setup();
        setupScm();
    }

    private String getOrgName() {
        return "jenkins";
    }

    private static final String masterPipelineScript = "pipeline { stage('Build 1') { steps { echo 'build' } } }";
    private static final String branchPipelineScript = "pipeline { stage('Build 2') { steps { echo 'build' } } }";
    private static final String newPipelineScript = "pipeline { stage('Build 3') { steps { echo 'build' } } }";

    private void setupScm() throws Exception {
        // create git repo
        repoWithJenkinsfiles.init();
        repoWithJenkinsfiles.write("Jenkinsfile", masterPipelineScript);
        repoWithJenkinsfiles.write("file", "initial content");
        repoWithJenkinsfiles.git("add", "Jenkinsfile");
        repoWithJenkinsfiles.git("commit", "--all", "--message=flow");

        //create feature branch
        repoWithJenkinsfiles.git("checkout", "-b", "feature/ux-1");
        repoWithJenkinsfiles.write("Jenkinsfile", branchPipelineScript);
        ScriptApproval.get().approveSignature("method java.lang.String toUpperCase");
        repoWithJenkinsfiles.write("file", "subsequent content1");
        repoWithJenkinsfiles.git("commit", "--all", "--message=tweaked1");

        // we're using this to test push/pull, allow pushes to current branch, we reset it to match
        repoWithJenkinsfiles.git("config", "--local", "--add", "receive.denyCurrentBranch", "false");

        repoNoJenkinsfile.init();
        repoNoJenkinsfile.write("file", "nearly empty file");
        repoNoJenkinsfile.git("add", "file");
        repoNoJenkinsfile.git("commit", "--all", "--message=initialize the repo");

        // we're using this to test push/pull, allow pushes to current branch, we reset it to match
        repoNoJenkinsfile.git("config", "--local", "--add", "receive.denyCurrentBranch", "false");

        String gitRoot = System.getProperty("TEST_SSH_SERVER_GIT_ROOT", null);
        boolean createRepoForSSH = true;
        if (gitRoot != null) {
            Field f = AbstractSampleRepoRule.class.getDeclaredField("tmp");
            f.setAccessible(true);
            Object tmpFolder = f.get(repoForSSH);
            f = TemporaryFolder.class.getDeclaredField("folder");
            f.setAccessible(true);
            File dir = new File(gitRoot);
            if (!dir.exists()) {
                dir.mkdirs();
            } else {
                createRepoForSSH = false;
            }
            f.set(tmpFolder, dir);
            f = AbstractSampleDVCSRepoRule.class.getDeclaredField("sampleRepo");
            f.setAccessible(true);
            f.set(repoForSSH, dir);
        }

        if (createRepoForSSH) {
            repoForSSH.init();
            repoForSSH.write("Jenkinsfile", masterPipelineScript);
            repoForSSH.git("add", "Jenkinsfile");
            repoForSSH.git("commit", "--all", "--message=initialize the repo");

            // we're using this to test push/pull, allow pushes to current branch, we reset it to match
            repoForSSH.git("config", "--local", "--add", "receive.denyCurrentBranch", "false");
        }
    }

    private void startSSH() throws Exception {
        startSSH(null);
    }

    private void startSSH(@Nullable User u) throws Exception {
        if (sshd == null) {
            // Set up an SSH server with access to a git repo
            User user;
            if (u == null) {
                user = login();
            } else {
                user = u;
            }
            final BasicSSHUserPrivateKey key = UserSSHKeyManager.getOrCreate(user);
            final JSch jsch = new JSch();
            final KeyPair pair = KeyPair.load(jsch, key.getPrivateKey().getBytes(), null);

            File keyFile = new File(System.getProperty("TEST_SSH_SERVER_KEY_FILE",
                    File.createTempFile("hostkey", "ser").getCanonicalPath()));
            int port = Integer.parseInt(System.getProperty("TEST_SSH_SERVER_PORT", "0"));
            boolean allowLocalUser = Boolean.getBoolean("TEST_SSH_SERVER_ALLOW_LOCAL");
            String userPublicKey = Base64.encode(pair.getPublicKeyBlob());
            sshd = new SSHServer(repoForSSH.getRoot(), keyFile, port, allowLocalUser,
                    ImmutableMap.of("bob", userPublicKey), true);
            // Go, go, go
            sshd.start();
        }
    }

    @After
    public void stopSSHServer() throws InterruptedException, IOException {
        if (sshd != null) {
            String ssh = "ssh -p " + sshd.getPort() + " bob@127.0.0.1";
            String remote = "ssh://bob@127.0.0.1:" + sshd.getPort() + "" + repoForSSH.getRoot().getCanonicalPath();
            logger.fine(ssh + " // remote: " + remote);
            sshd.stop();
            sshd = null;
        }
    }

    @Test
    public void testRepositoryCallbackToFSFunctionAdapter() throws IOException, InterruptedException {
        final boolean[] called = { false };
        new GitCacheCloneReadSaveRequest.RepositoryCallbackToFSFunctionAdapter<>(
                new GitSCMFileSystem.FSFunction<Object>() {
                    @Override
                    public Object invoke(Repository repository) throws IOException, InterruptedException {
                        called[0] = true;
                        return null;
                    }
                }).invoke(null, null);
        Assert.assertTrue(called[0]);
    }

    @Test
    public void testGitCloneReadWrite() throws Exception {
        testGitReadWrite(GitReadSaveService.ReadSaveType.CLONE, repoWithJenkinsfiles, masterPipelineScript);
        testGitReadWrite(GitReadSaveService.ReadSaveType.CLONE, repoNoJenkinsfile, null);
    }

    @Test
    public void testGitCacheCloneReadWrite() throws Exception {
        testGitReadWrite(GitReadSaveService.ReadSaveType.CACHE_CLONE, repoWithJenkinsfiles, masterPipelineScript);
        testGitReadWrite(GitReadSaveService.ReadSaveType.CACHE_CLONE, repoNoJenkinsfile, null);
    }

    @Test
    public void testBareRepoReadWrite() throws Exception {
        testGitReadWrite(GitReadSaveService.ReadSaveType.CACHE_BARE, repoWithJenkinsfiles, masterPipelineScript);
        testGitReadWrite(GitReadSaveService.ReadSaveType.CACHE_BARE, repoNoJenkinsfile, null);
    }

    @Test
    public void testGitScmValidate() throws Exception {
        if (!OsUtils.isUNIX()) {
            return; // can't really run this on windows
        }
        startSSH();
        String userHostPort = "bob@127.0.0.1:" + sshd.getPort();
        String remote = "ssh://" + userHostPort + "" + repoForSSH.getRoot().getCanonicalPath();

        User bob = login();

        // Validate bob via repositoryUrl
        Map r = new RequestBuilder(baseUrl).status(200).jwtToken(getJwtToken(j.jenkins, bob.getId(), bob.getId()))
                .put("/organizations/" + getOrgName() + "/scm/git/validate/").data(ImmutableMap.of("repositoryUrl",
                        remote, "credentialId", UserSSHKeyManager.getOrCreate(bob).getId()))
                .build(Map.class);

        assertTrue(r.get("error") == null);

        // Create a job
        String jobName = "test-token-validation";
        r = new RequestBuilder(baseUrl).status(201).jwtToken(getJwtToken(j.jenkins, bob.getId(), bob.getId()))
                .post("/organizations/" + getOrgName() + "/pipelines/")
                .data(ImmutableMap.of("name", jobName, "$class",
                        "io.jenkins.blueocean.blueocean_git_pipeline.GitPipelineCreateRequest", "scmConfig",
                        ImmutableMap.of("uri", remote, "credentialId", UserSSHKeyManager.getOrCreate(bob).getId())))
                .build(Map.class);

        assertEquals(jobName, r.get("name"));

        // Test for existing pipeline/job
        r = new RequestBuilder(baseUrl).status(200).jwtToken(getJwtToken(j.jenkins, bob.getId(), bob.getId()))
                .put("/organizations/" + getOrgName() + "/scm/git/validate/")
                .data(ImmutableMap.of("pipeline", ImmutableMap.of("fullName", jobName), "credentialId",
                        UserSSHKeyManager.getOrCreate(bob).getId()))
                .build(Map.class);

        User alice = login("alice", "Alice Cooper", "alice@jenkins-ci.org");

        // Test alice fails
        r = new RequestBuilder(baseUrl).status(428).jwtToken(getJwtToken(j.jenkins, alice.getId(), alice.getId()))
                .put("/organizations/" + getOrgName() + "/scm/git/validate/").data(ImmutableMap.of("repositoryUrl",
                        remote, "credentialId", UserSSHKeyManager.getOrCreate(alice).getId()))
                .build(Map.class);

        r = new RequestBuilder(baseUrl).status(428).jwtToken(getJwtToken(j.jenkins, alice.getId(), alice.getId()))
                .put("/organizations/" + getOrgName() + "/scm/git/validate/")
                .data(ImmutableMap.of("pipeline", ImmutableMap.of("fullName", jobName), "credentialId",
                        UserSSHKeyManager.getOrCreate(alice).getId()))
                .build(Map.class);
    }

    @Test
    public void bareRepoReadWriteOverSSH() throws Exception {
        if (!OsUtils.isUNIX()) {
            return; // can't really run this on windows
        }
        startSSH();
        String userHostPort = "bob@127.0.0.1:" + sshd.getPort();
        String remote = "ssh://" + userHostPort + "" + repoForSSH.getRoot().getCanonicalPath() + "/.git";
        testGitReadWrite(GitReadSaveService.ReadSaveType.CACHE_BARE, remote, repoForSSH, masterPipelineScript);
    }

    @Test
    public void bareRepoReadWriteNoEmail() throws Exception {
        if (!OsUtils.isUNIX()) {
            return; // can't really run this on windows
        }
        User user = login("bob", "Bob Smith", null);

        startSSH(user);
        String userHostPort = "bob@127.0.0.1:" + sshd.getPort();
        String remote = "ssh://" + userHostPort + "" + repoForSSH.getRoot().getCanonicalPath() + "/.git";
        testGitReadWrite(GitReadSaveService.ReadSaveType.CACHE_BARE, remote, repoForSSH, masterPipelineScript,
                user);
    }

    private void testGitReadWrite(final @Nonnull GitReadSaveService.ReadSaveType type,
            @Nonnull GitSampleRepoRule repo, @Nullable String startPipelineScript) throws Exception {
        testGitReadWrite(type, repo.getRoot().getCanonicalPath(), repo, startPipelineScript);
    }

    private void testGitReadWrite(final @Nonnull GitReadSaveService.ReadSaveType type, @Nonnull String remote,
            @Nonnull GitSampleRepoRule repo, @Nullable String startPipelineScript) throws Exception {
        testGitReadWrite(type, remote, repo, startPipelineScript, login());
    }

    private void testGitReadWrite(final @Nonnull GitReadSaveService.ReadSaveType type, @Nonnull String remote,
            @Nonnull GitSampleRepoRule repo, @Nullable String startPipelineScript, @Nullable User user)
            throws Exception {
        GitReadSaveService.setType(type);

        String jobName = repo.getRoot().getName();

        Map r = new RequestBuilder(baseUrl).status(201).jwtToken(getJwtToken(j.jenkins, user.getId(), user.getId()))
                .post("/organizations/" + getOrgName() + "/pipelines/")
                .data(ImmutableMap.of("name", jobName, "$class",
                        "io.jenkins.blueocean.blueocean_git_pipeline.GitPipelineCreateRequest", "scmConfig",
                        ImmutableMap.of("uri", remote, "credentialId",
                                UserSSHKeyManager.getOrCreate(user).getId())))
                .build(Map.class);

        assertEquals(jobName, r.get("name"));

        String urlJobPrefix = "/organizations/" + getOrgName() + "/pipelines/" + jobName;

        r = new RequestBuilder(baseUrl).status(200).jwtToken(getJwtToken(j.jenkins, user.getId(), user.getId()))
                .get(urlJobPrefix + "/scm/content/?branch=master&path=Jenkinsfile&type=" + type.name())
                .build(Map.class);

        String base64Data = (String) ((Map) r.get("content")).get("base64Data");

        assertEquals(startPipelineScript,
                base64Data == null ? null : new String(Base64.decode(base64Data), "utf-8"));

        // Update the remote
        String newBase64Data = Base64.encode(newPipelineScript.getBytes("utf-8"));
        Map<String, String> content = new HashMap<>();
        content.put("message", "Save Jenkinsfile");
        content.put("path", "Jenkinsfile");
        content.put("branch", "master");
        content.put("sourceBranch", "master");
        content.put("repo", jobName); // if no repo, this is not in an org folder
        content.put("sha", "");
        content.put("base64Data", newBase64Data);
        content.put("type", type.name());

        new RequestBuilder(baseUrl).status(200).jwtToken(getJwtToken(j.jenkins, user.getId(), user.getId()))
                .put(urlJobPrefix + "/scm/content/").data(ImmutableMap.of("content", content)).build(Map.class);

        // Check to make sure the remote was actually updated:
        // refs udpated in our sample repo, not working tree, update it to get contents:
        repo.git("reset", "--hard", "refs/heads/master");
        String remoteJenkinsfile = FileUtils.readFileToString(new File(repo.getRoot(), "Jenkinsfile"));
        Assert.assertEquals(newPipelineScript, remoteJenkinsfile);

        // check to make sure we get the same thing from the service
        r = new RequestBuilder(baseUrl).status(200).jwtToken(getJwtToken(j.jenkins, user.getId(), user.getId()))
                .get(urlJobPrefix + "/scm/content/?branch=master&path=Jenkinsfile&type=" + type.name())
                .build(Map.class);

        base64Data = (String) ((Map) r.get("content")).get("base64Data");
        Assert.assertEquals(base64Data, newBase64Data);
    }
}