|
| 1 | +package com.cloudbees.jenkins.plugins.bitbucket; |
| 2 | + |
| 3 | +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi; |
| 4 | +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketHref; |
| 5 | +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; |
| 6 | +import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint; |
| 7 | +import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerAPIClient; |
| 8 | +import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey; |
| 9 | +import com.cloudbees.plugins.credentials.CredentialsProvider; |
| 10 | +import com.cloudbees.plugins.credentials.CredentialsScope; |
| 11 | +import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; |
| 12 | +import com.cloudbees.plugins.credentials.domains.Domain; |
| 13 | +import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; |
| 14 | +import hudson.plugins.git.GitSCM; |
| 15 | +import hudson.plugins.git.UserRemoteConfig; |
| 16 | +import hudson.plugins.git.extensions.impl.BuildChooserSetting; |
| 17 | +import java.io.ByteArrayOutputStream; |
| 18 | +import java.nio.charset.StandardCharsets; |
| 19 | +import java.util.Arrays; |
| 20 | +import java.util.List; |
| 21 | +import java.util.Map; |
| 22 | +import jenkins.plugins.git.AbstractGitSCMSource; |
| 23 | +import jenkins.plugins.git.GitSCMSourceDefaults; |
| 24 | +import jenkins.plugins.git.GitSampleRepoRule; |
| 25 | +import org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition; |
| 26 | +import org.jenkinsci.plugins.workflow.job.WorkflowJob; |
| 27 | +import org.jenkinsci.plugins.workflow.job.WorkflowRun; |
| 28 | +import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject; |
| 29 | +import org.junit.Rule; |
| 30 | +import org.junit.Test; |
| 31 | +import org.junit.runner.RunWith; |
| 32 | +import org.jvnet.hudson.test.Issue; |
| 33 | +import org.jvnet.hudson.test.JenkinsRule; |
| 34 | +import org.jvnet.hudson.test.recipes.WithTimeout; |
| 35 | +import org.mockito.junit.MockitoJUnitRunner; |
| 36 | + |
| 37 | +import static org.hamcrest.MatcherAssert.assertThat; |
| 38 | +import static org.hamcrest.Matchers.containsInAnyOrder; |
| 39 | +import static org.hamcrest.Matchers.containsString; |
| 40 | +import static org.hamcrest.Matchers.instanceOf; |
| 41 | +import static org.hamcrest.Matchers.is; |
| 42 | +import static org.mockito.Mockito.mock; |
| 43 | +import static org.mockito.Mockito.when; |
| 44 | + |
| 45 | +/** |
| 46 | + * Tests different scenarios of the |
| 47 | + * {@link BitbucketSCMSource#build(jenkins.scm.api.SCMHead, jenkins.scm.api.SCMRevision)} method. |
| 48 | + */ |
| 49 | +@RunWith(MockitoJUnitRunner.class) |
| 50 | +public class BitbucketSCMSourceBuildTest { |
| 51 | + |
| 52 | + private static final String CLOUD_REPO_OWNER = "cloudbeers"; |
| 53 | + private static final String REPO_NAME = "stunning-adventure"; |
| 54 | + private static final String BRANCH_NAME = "master"; |
| 55 | + // Test private key from ssh-credentials test case |
| 56 | + // https://github.com/jenkinsci/ssh-credentials-plugin/blob/343.v884f71d78167/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyTest/readOldCredentials/credentials.xml |
| 57 | + private static final String PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\n" + |
| 58 | + "MIIEpAIBAAKCAQEAu1r+HHzmpybc4iwoP5+44FjvcaMkNEWeGQZlmPwLx70XW8+8\n" + |
| 59 | + "3d2BMLjhcw1zLsYG3FWpCyn8cB1OmjKiGjvnP5EBoAolvh3qOcWKyWlVGWGgs10B\n" + |
| 60 | + "0Cgnd3OBXRPQd1cBdiQZmmeCrrH0OjQefYIF2WYN+F8iuNGraAaRsXLgITanjTb1\n" + |
| 61 | + "6w1dnk+KLpU2J5G6kG1f/Qxl4pgny80S/3TktqoknbOrYDMOSA1Zdww39cpXJHp6\n" + |
| 62 | + "feEs8tavC93rOsR2O4ZfVUCjTFAF5FtIdRv3LXY3Q5W/AyY1h45Wk6mMVnEluFjB\n" + |
| 63 | + "aA+gzVAVaHFQfuhwoj4B7jWCmfHsPG1WmyK0YQIDAQABAoIBADt1qlXiMdV0mP9S\n" + |
| 64 | + "okdm6maQ8xTugKvyODWa+R1vSFHQqhwiNr927+xFkI9SAm8iu8SrjuWTIqF2O57m\n" + |
| 65 | + "WNnYjxB2dbyT29yVY+OH1P8M5cwTVsv1xYCJbdUUHEcs5akqPLWAyXteRHQq1+as\n" + |
| 66 | + "6cxNOov/PonHr55WNH7kLtLRMV54jZ68nrEh5pWdabFa0f7d/ByIvYRJm7lpjtSp\n" + |
| 67 | + "EBp5AseXzSg2EZP3HDPYYPDHK0tMPginz9+YuQCQFoMYAkVZoKFJGsWICktd5Uk7\n" + |
| 68 | + "wveOJLOfMng1Iww6CEc871GcLn5LbafLWRxjZssK2Z1fC+pZYLLZPeKDMSxoRXm6\n" + |
| 69 | + "PShUC1ECgYEA623dmwJNYfVRgAgOYhhcxzH4TAXUmUIpadEUjOkAhe3w/abDawFT\n" + |
| 70 | + "9ianhqfhTjSZGBtUttcN40Vy+P4bsqfQKZ7p6KzrdR2zWjlYcICWhZDZPGmMxpMZ\n" + |
| 71 | + "mUFhZXsLRVhn8qed8w1t6eju7S+t9satKIMC/KrhNsFzjrgbU9eC+m0CgYEAy7nN\n" + |
| 72 | + "gMwQeGxAQSJr9H7eKkthnjMe77rLIAZEbDJhcwYVz+Iie/E4hjESQ+IuvXa1VawD\n" + |
| 73 | + "O6cD0wWdOhH2McNdMNIbM4IOaO/TOaR5jfQwBWmb4iG2BZQWQQE/HUBnoJQWUhqm\n" + |
| 74 | + "b+D+s4bHh4J+bs+ptgg9Sd9V+VxJBcu2QDmI6UUCgYBTb1pMJyK5hrFdiH1gcnXe\n" + |
| 75 | + "+myetKpFrlby83AvCBxxWoQ/wKwc7hmNcOGKLVEB4E4pZvY83jZDx0cZyySRyjtR\n" + |
| 76 | + "pMoM9ct0dBQt84jORiQSLeVvLZEAhv1ZfPxBdLvn1Y7xRkoJ60Z60Vxrnqwueva/\n" + |
| 77 | + "Fr8mQIEUYLbNa53ztrrqeQKBgQCqOY4k2F3KwWjPA9wAZyFrZaEjdsOavBGNqK7z\n" + |
| 78 | + "WQVj/umq0eDOfzgjqE0Cu7MiTFYoR5pL9bmUUVSWePuliQANEwH3f+xackmkGHIY\n" + |
| 79 | + "0rhtTVkbEd/tuVb+6fO6lV4BJrufzvTS9sTbbPq7l6XdIVdE6o2LdDl6Kko5tYWL\n" + |
| 80 | + "FIf5oQKBgQDLHK/9NTb3VHp+Qriu2Vp8Pnaw6YF6pETfgjyrH2ULSW/R07v+AECC\n" + |
| 81 | + "sPr+d/hx2MQWp54HglY8lv98rOrRjMiRw1GVoXs+Ut9vkupmrpvzNE7ITl0tzBqD\n" + |
| 82 | + "sroT/IHW2jKMD0v8kKLUnKCZYzlw0By7+RvJ8lgzHB0D71f6EC1UWg==\n" + |
| 83 | + "-----END RSA PRIVATE KEY-----\n"; |
| 84 | + |
| 85 | + @Rule |
| 86 | + public JenkinsRule j = new JenkinsRule(); |
| 87 | + |
| 88 | + @Rule |
| 89 | + public GitSampleRepoRule sampleRepo = new GitSampleRepoRule(); |
| 90 | + |
| 91 | + |
| 92 | + @Test |
| 93 | + @Issue("JENKINS-73471") |
| 94 | + @WithTimeout(120) |
| 95 | + public void buildWhenSetSSHCheckoutTraitThenEmptyAuthenticatorExtension() throws Exception { |
| 96 | + String jenkinsFile = "Jenkinsfile"; |
| 97 | + sampleRepo.init(); |
| 98 | + sampleRepo.write(jenkinsFile, "node { checkout scm }"); |
| 99 | + sampleRepo.git("add", jenkinsFile); |
| 100 | + sampleRepo.git("commit", "--all", "--message=defined"); |
| 101 | + |
| 102 | + StandardUsernameCredentials userPassCredentials = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, |
| 103 | + "user-pass", null, "user", "pass"); |
| 104 | + CredentialsProvider.lookupStores(j.jenkins).iterator().next() |
| 105 | + .addCredentials(Domain.global(), userPassCredentials); |
| 106 | + StandardUsernameCredentials sshCredentials = new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "user-key", "user", |
| 107 | + new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource(PRIVATE_KEY), null, null); |
| 108 | + CredentialsProvider.lookupStores(j.jenkins).iterator().next() |
| 109 | + .addCredentials(Domain.global(), sshCredentials); |
| 110 | + |
| 111 | + WorkflowMultiBranchProject owner = j.createProject(WorkflowMultiBranchProject.class, "testMultibranch"); |
| 112 | + BitbucketSCMSource instance = new BitbucketSCMSource(CLOUD_REPO_OWNER, REPO_NAME); |
| 113 | + instance.setOwner(owner); |
| 114 | + instance.setCredentialsId(userPassCredentials.getId()); |
| 115 | + instance.setTraits(Arrays.asList( |
| 116 | + new BranchDiscoveryTrait(1), |
| 117 | + new SSHCheckoutTrait(sshCredentials.getId()))); |
| 118 | + |
| 119 | + BitbucketRepository repository = mock(BitbucketRepository.class); |
| 120 | + when(repository.getLinks()).thenReturn(Map.of("clone", List.of( |
| 121 | + new BitbucketHref("http", sampleRepo.toString()), |
| 122 | + new BitbucketHref("ssh", String.format("ssh://user@localhost/%s", sampleRepo)) |
| 123 | + ))); |
| 124 | + BitbucketApi client = mock(BitbucketApi.class); |
| 125 | + BitbucketMockApiFactory.add(BitbucketCloudEndpoint.SERVER_URL, client); |
| 126 | + when(client.getRepository()).thenReturn(repository); |
| 127 | + |
| 128 | + BranchSCMHead head = new BranchSCMHead(BRANCH_NAME); |
| 129 | + AbstractGitSCMSource.SCMRevisionImpl revision = |
| 130 | + new AbstractGitSCMSource.SCMRevisionImpl(head, sampleRepo.head()); |
| 131 | + GitSCM build = (GitSCM)instance.build(head, revision); |
| 132 | + assertThat(build.getUserRemoteConfigs().size(), is(1)); |
| 133 | + UserRemoteConfig remoteConfig = build.getUserRemoteConfigs().get(0); |
| 134 | + assertThat(remoteConfig.getUrl(), is(String.format("ssh://user@localhost/%s", sampleRepo))); |
| 135 | + assertThat(remoteConfig.getRefspec(), is(String.format("+refs/heads/%s:refs/remotes/origin/%s", BRANCH_NAME, BRANCH_NAME))); |
| 136 | + assertThat(remoteConfig.getCredentialsId(), is(sshCredentials.getId())); |
| 137 | + assertThat(build.getExtensions(), containsInAnyOrder( |
| 138 | + instanceOf(BuildChooserSetting.class), |
| 139 | + instanceOf(GitSCMSourceDefaults.class), |
| 140 | + instanceOf(GitClientAuthenticatorExtension.class)) |
| 141 | + ); |
| 142 | + |
| 143 | + // Create a Pipeline with CpsScmFlowDefinition based of the GitSCM produced |
| 144 | + // Then check that the checkout uses GIT_SSH from the git-client logs |
| 145 | + WorkflowJob job = j.jenkins.createProject(WorkflowJob.class, "testGitScm"); |
| 146 | + job.setDefinition(new CpsScmFlowDefinition(build, jenkinsFile)); |
| 147 | + WorkflowRun run = job.scheduleBuild2(0).get(); |
| 148 | + |
| 149 | + ByteArrayOutputStream byteArrayOutStr = new ByteArrayOutputStream(); |
| 150 | + run.writeWholeLogTo(byteArrayOutStr); |
| 151 | + assertThat(byteArrayOutStr.toString(StandardCharsets.UTF_8), containsString("using GIT_SSH to set credentials")); |
| 152 | + } |
| 153 | + |
| 154 | + @Test |
| 155 | + @WithTimeout(120) |
| 156 | + public void buildBasicAuthThenAuthenticatorExtension() throws Exception { |
| 157 | + String jenkinsFile = "Jenkinsfile"; |
| 158 | + sampleRepo.init(); |
| 159 | + sampleRepo.write(jenkinsFile, "node { checkout scm }"); |
| 160 | + sampleRepo.git("add", jenkinsFile); |
| 161 | + sampleRepo.git("commit", "--all", "--message=defined"); |
| 162 | + |
| 163 | + StandardUsernameCredentials userPassCredentials = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, |
| 164 | + "user-pass", null, "user", "pass"); |
| 165 | + CredentialsProvider.lookupStores(j.jenkins).iterator().next() |
| 166 | + .addCredentials(Domain.global(), userPassCredentials); |
| 167 | + |
| 168 | + WorkflowMultiBranchProject owner = j.createProject(WorkflowMultiBranchProject.class, "testMultibranch"); |
| 169 | + BitbucketSCMSource instance = new BitbucketSCMSource(CLOUD_REPO_OWNER, REPO_NAME); |
| 170 | + instance.setOwner(owner); |
| 171 | + instance.setCredentialsId(userPassCredentials.getId()); |
| 172 | + instance.setTraits(List.of(new BranchDiscoveryTrait(1))); |
| 173 | + |
| 174 | + BitbucketRepository repository = mock(BitbucketRepository.class); |
| 175 | + when(repository.getLinks()).thenReturn(Map.of("clone", List.of( |
| 176 | + new BitbucketHref("http", sampleRepo.toString()), |
| 177 | + new BitbucketHref("ssh", String.format("ssh://localhost:%s", sampleRepo)) |
| 178 | + ))); |
| 179 | + BitbucketServerAPIClient client = mock(BitbucketServerAPIClient.class); |
| 180 | + BitbucketMockApiFactory.add(BitbucketCloudEndpoint.SERVER_URL, client); |
| 181 | + when(client.getRepository()).thenReturn(repository); |
| 182 | + |
| 183 | + BranchSCMHead head = new BranchSCMHead(BRANCH_NAME); |
| 184 | + AbstractGitSCMSource.SCMRevisionImpl revision = |
| 185 | + new AbstractGitSCMSource.SCMRevisionImpl(head, sampleRepo.head()); |
| 186 | + GitSCM build = (GitSCM)instance.build(head, revision); |
| 187 | + assertThat(build.getUserRemoteConfigs().size(), is(1)); |
| 188 | + UserRemoteConfig remoteConfig = build.getUserRemoteConfigs().get(0); |
| 189 | + assertThat(remoteConfig.getUrl(), is(sampleRepo.toString())); |
| 190 | + assertThat(remoteConfig.getRefspec(), is(String.format("+refs/heads/%s:refs/remotes/origin/%s", BRANCH_NAME, BRANCH_NAME))); |
| 191 | + assertThat(remoteConfig.getCredentialsId(), is(userPassCredentials.getId())); |
| 192 | + assertThat(build.getExtensions(), containsInAnyOrder( |
| 193 | + instanceOf(BuildChooserSetting.class), |
| 194 | + instanceOf(GitSCMSourceDefaults.class), |
| 195 | + instanceOf(GitClientAuthenticatorExtension.class) |
| 196 | + )); |
| 197 | + |
| 198 | + // Create a Pipeline with CpsScmFlowDefinition based of the GitSCM produced |
| 199 | + // Then check that the checkout scm uses GIT_ASKPASS from the git-client logs |
| 200 | + WorkflowJob job = j.jenkins.createProject(WorkflowJob.class, "testGitScm"); |
| 201 | + job.setDefinition(new CpsScmFlowDefinition(build, jenkinsFile)); |
| 202 | + WorkflowRun run = job.scheduleBuild2(0).get(); |
| 203 | + |
| 204 | + ByteArrayOutputStream byteArrayOutStr = new ByteArrayOutputStream(); |
| 205 | + run.writeWholeLogTo(byteArrayOutStr); |
| 206 | + assertThat(byteArrayOutStr.toString(StandardCharsets.UTF_8), containsString("using GIT_ASKPASS to set credentials")); |
| 207 | + } |
| 208 | +} |
0 commit comments