diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubApp.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubApp.java new file mode 100644 index 000000000..37b385448 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubApp.java @@ -0,0 +1,51 @@ +package org.jenkinsci.plugins.github_branch_source; + +import com.cloudbees.plugins.credentials.CredentialsScope; +import hudson.util.Secret; + +public class GitHubApp { + + // PKCS8 private key (https://stackoverflow.com/a/22176759/4951015) + private static final String PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" + + + // Windows line ending + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD7vHsVwyDV8cj7\r\n" + + + // This should also work + "5yR4WWl6rlgf/e5zmeBgtm0PCgnitcSbD5FU33301DPY5a7AtqVBOwEnE14L9XS7\r" + + "ov61U+x1m4aQmqR/dPQaA2ayh2cYPszWNQMp42ArDIfg7DhSrvsRJKHsbPXlPjqe\n" + + "c0udLqhSLVIO9frNLf+dAsLsgYk8O39PKGb33akGG7tWTe0J+akNQjgbS7vOi8sS\n" + + "NLwHIdYfz/Am+6Xmm+J4yVs6+Xt3kOeLdFBkz8H/HGsJq854MbIAK/HuId1MOPS0\n" + + "cDWh37tzRsM+q/HZzYRkc5bhNKw/Mj9jN9jD5GH0Lfea0QFedjppf1KvWdcXn+/W\n" + + "M7OmyfhvAgMBAAECggEAN96H7reExRbJRWbySCeH6mthMZB46H0hODWklK7krMUs\n" + + "okFdPtnvKXQjIaMwGqMuoACJa/O3bq4GP1KYdwPuOdfPkK5RjdwWBOP2We8FKXNe\n" + + "oLfZQOWuxT8dtQSYJ3mgTRi1OzSfikY6Wko6YOMnBj36tUlQZVMtJNqlCjphi9Uz\n" + + "6EyvRURlDG8sBBbC7ods5B0789qk3iGH/97ia+1QIqXAUaVFg3/BA6wkxkbNG2sN\n" + + "tqULgVYTw32Oj/Y/H1Y250RoocTyfsUS3I3aPIlnvcgp2bugWqDyYJ58nDIt3Pku\n" + + "fjImWrNz/pNiEs+efnb0QEk7m5hYwxmyXN4KRSv0OQKBgQD+I3Y3iNKSVr6wXjur\n" + + "OPp45fxS2sEf5FyFYOn3u760sdJOH9fGlmf9sDozJ8Y8KCaQCN5tSe3OM+XDrmiw\n" + + "Cu/oaqJ1+G4RG+6w1RJF+5Nfg6PkUs7eJehUgZ2Tox8Tg1mfVIV8KbMwNi5tXpug\n" + + "MVmA2k9xjc4uMd2jSnSj9NAqrQKBgQD9lIO1tY6YKF0Eb0Qi/iLN4UqBdJfnALBR\n" + + "MjxYxqqI8G4wZEoZEJJvT1Lm6Q3o577N95SihZoj69tb10vvbEz1pb3df7c1HEku\n" + + "LXcyVMvjR/CZ7dOSNgLGAkFfOoPhcF/OjSm4DrGPe3GiBxhwXTBjwJ5TIgEDkVIx\n" + + "ZVo5r7gPCwKBgQCOvsZo/Q4hql2jXNqxGuj9PVkUBNFTI4agWEYyox7ECdlxjks5\n" + + "vUOd5/1YvG+JXJgEcSbWRh8volDdL7qXnx0P881a6/aO35ybcKK58kvd62gEGEsf\n" + + "1jUAOmmTAp2y7SVK7EOp8RY370b2oZxSR0XZrUXQJ3F22wV98ZVAfoLqZQKBgDIr\n" + + "PdunbezAn5aPBOX/bZdZ6UmvbZYwVrHZxIKz2214U/STAu3uj2oiQX6ZwTzBDMjn\n" + + "IKr+z74nnaCP+eAGhztabTPzXqXNUNUn/Zshl60BwKJToTYeJXJTY+eZRhpGB05w\n" + + "Mz7M+Wgvvg2WZcllRnuV0j0UTysLhz1qle0vzLR9AoGBAOukkFFm2RLm9N1P3gI8\n" + + "mUadeAlYRZ5o0MvumOHaB5pDOCKhrqAhop2gnM0f5uSlapCtlhj0Js7ZyS3Giezg\n" + + "38oqAhAYxy2LMoLD7UtsHXNp0OnZ22djcDwh+Wp2YORm7h71yOM0NsYubGbp+CmT\n" + + "Nw9bewRvqjySBlDJ9/aNSeEY\n" + + "-----END PRIVATE KEY-----"; + + public static GitHubAppCredentials createCredentials(final String id) { + return new GitHubAppCredentials(CredentialsScope.GLOBAL, id, "sample", "54321", Secret.fromString(PRIVATE_KEY)); + } + + public static GitHubAppCredentials createCredentials(final String id, final String owner) { + final var credentials = createCredentials(id); + credentials.setOwner(owner); + return credentials; + } +} diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentialsContextualizationTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentialsContextualizationTest.java new file mode 100644 index 000000000..ba6324781 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentialsContextualizationTest.java @@ -0,0 +1,97 @@ +package org.jenkinsci.plugins.github_branch_source; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; + +import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.domains.Domain; +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import jenkins.branch.BranchSource; +import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject; +import org.junit.Before; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; + +@Issue("JENKINS-73474") +public class GitHubAppCredentialsContextualizationTest extends AbstractGitHubWireMockTest { + + @Before + public void setUpWireMock() throws Exception { + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); + + // Add wiremock responses for App, App Installation, and App Installation Token + githubApi.stubFor(get(urlEqualTo("/app")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../AppCredentials/files/body-mapping-githubapp-app.json"))); + githubApi.stubFor(get(urlEqualTo("/app/installations")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../AppCredentials/files/body-mapping-githubapp-installations.json"))); + githubApi.stubFor(post(urlEqualTo("/app/installations/654321/access_tokens")) + .withRequestBody(equalToJson( + "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", + true, + false)) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBody("{\"token\":\"super-secret-token\",\"expires_at\":\"" + createTokenExpiration() + + "\"}"))); + + // Add wiremock responses for Repository + githubApi.stubFor(get(urlEqualTo("/repos/cloudbeers/multibranch-demo")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../contextualization/body-repos-cloudbeers-multibranch-demo.json"))); + githubApi.stubFor(get(urlEqualTo("/repos/cloudbeers/multibranch-demo/branches")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../contextualization/body-repos-cloudbeers-multibranch-demo-branches.json"))); + githubApi.stubFor(get(urlEqualTo("/repos/cloudbeers/multibranch-demo/contents/?ref=refs%2Fheads%2Fmaster")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../contextualization/body-repos-cloudbeers-multibranch-demo-contents.json"))); + githubApi.stubFor(get(urlEqualTo("/repos/cloudbeers/multibranch-demo/pulls?state=open")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../contextualization/body-repos-cloudbeers-multibranch-demo-pulls.json"))); + } + + @Test + public void ownerMustBeInferredFromRepository() throws Exception { + final var store = CredentialsProvider.lookupStores(r.jenkins).iterator().next(); + + final var credentials = GitHubApp.createCredentials("myAppCredentialsWithoutOwner"); + store.addCredentials(Domain.global(), credentials); + credentials.setApiUri(githubApi.baseUrl()); + + final var scmSource = new GitHubSCMSource("cloudbeers", "multibranch-demo", null, false); + scmSource.setTraits(Arrays.asList(new BranchDiscoveryTrait(true, true))); + scmSource.setCredentialsId(credentials.getId()); + scmSource.setApiUri(githubApi.baseUrl()); + + final var multiBranchProject = r.jenkins.createProject(WorkflowMultiBranchProject.class, "multibranch-demo"); + multiBranchProject.getSourcesList().add(new BranchSource(scmSource)); + multiBranchProject.scheduleBuild2(0).getFuture().get(); + + final var branchProject = multiBranchProject.getItem("master"); + assertThat(branchProject, notNullValue()); + } + + private String createTokenExpiration() { + // This token will go stale at the soonest allowed time but will not + // expire for the duration of the test + // Format: 2019-08-10T05:54:58Z + return DateTimeFormatter.ISO_INSTANT.format( + Instant.now().plus(Duration.ofMinutes(10)).truncatedTo(ChronoUnit.SECONDS)); + } +} diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsTest.java index cf1938519..f71b3d401 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsTest.java @@ -3,11 +3,11 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.equalTo; import static org.jenkinsci.plugins.github_branch_source.Connector.createGitHubBuilder; import static org.junit.Assert.assertThrows; import com.cloudbees.plugins.credentials.CredentialsProvider; -import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.CredentialsStore; import com.cloudbees.plugins.credentials.domains.Domain; import com.github.tomakehurst.wiremock.http.RequestMethod; @@ -19,7 +19,6 @@ import hudson.model.Result; import hudson.model.Slave; import hudson.model.StringParameterDefinition; -import hudson.util.Secret; import java.time.Duration; import java.time.Instant; import java.time.format.DateTimeFormatter; @@ -56,40 +55,6 @@ public class GithubAppCredentialsTest extends AbstractGitHubWireMockTest { private static GitHubAppCredentials appCredentials, appCredentialsNoOwner; private static LogRecorder logRecorder; - // https://stackoverflow.com/a/22176759/4951015 - public static final String PKCS8_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" - + - // Windows line ending - "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD7vHsVwyDV8cj7\r\n" - + - // This should also work - "5yR4WWl6rlgf/e5zmeBgtm0PCgnitcSbD5FU33301DPY5a7AtqVBOwEnE14L9XS7\r" - + "ov61U+x1m4aQmqR/dPQaA2ayh2cYPszWNQMp42ArDIfg7DhSrvsRJKHsbPXlPjqe\n" - + "c0udLqhSLVIO9frNLf+dAsLsgYk8O39PKGb33akGG7tWTe0J+akNQjgbS7vOi8sS\n" - + "NLwHIdYfz/Am+6Xmm+J4yVs6+Xt3kOeLdFBkz8H/HGsJq854MbIAK/HuId1MOPS0\n" - + "cDWh37tzRsM+q/HZzYRkc5bhNKw/Mj9jN9jD5GH0Lfea0QFedjppf1KvWdcXn+/W\n" - + "M7OmyfhvAgMBAAECggEAN96H7reExRbJRWbySCeH6mthMZB46H0hODWklK7krMUs\n" - + "okFdPtnvKXQjIaMwGqMuoACJa/O3bq4GP1KYdwPuOdfPkK5RjdwWBOP2We8FKXNe\n" - + "oLfZQOWuxT8dtQSYJ3mgTRi1OzSfikY6Wko6YOMnBj36tUlQZVMtJNqlCjphi9Uz\n" - + "6EyvRURlDG8sBBbC7ods5B0789qk3iGH/97ia+1QIqXAUaVFg3/BA6wkxkbNG2sN\n" - + "tqULgVYTw32Oj/Y/H1Y250RoocTyfsUS3I3aPIlnvcgp2bugWqDyYJ58nDIt3Pku\n" - + "fjImWrNz/pNiEs+efnb0QEk7m5hYwxmyXN4KRSv0OQKBgQD+I3Y3iNKSVr6wXjur\n" - + "OPp45fxS2sEf5FyFYOn3u760sdJOH9fGlmf9sDozJ8Y8KCaQCN5tSe3OM+XDrmiw\n" - + "Cu/oaqJ1+G4RG+6w1RJF+5Nfg6PkUs7eJehUgZ2Tox8Tg1mfVIV8KbMwNi5tXpug\n" - + "MVmA2k9xjc4uMd2jSnSj9NAqrQKBgQD9lIO1tY6YKF0Eb0Qi/iLN4UqBdJfnALBR\n" - + "MjxYxqqI8G4wZEoZEJJvT1Lm6Q3o577N95SihZoj69tb10vvbEz1pb3df7c1HEku\n" - + "LXcyVMvjR/CZ7dOSNgLGAkFfOoPhcF/OjSm4DrGPe3GiBxhwXTBjwJ5TIgEDkVIx\n" - + "ZVo5r7gPCwKBgQCOvsZo/Q4hql2jXNqxGuj9PVkUBNFTI4agWEYyox7ECdlxjks5\n" - + "vUOd5/1YvG+JXJgEcSbWRh8volDdL7qXnx0P881a6/aO35ybcKK58kvd62gEGEsf\n" - + "1jUAOmmTAp2y7SVK7EOp8RY370b2oZxSR0XZrUXQJ3F22wV98ZVAfoLqZQKBgDIr\n" - + "PdunbezAn5aPBOX/bZdZ6UmvbZYwVrHZxIKz2214U/STAu3uj2oiQX6ZwTzBDMjn\n" - + "IKr+z74nnaCP+eAGhztabTPzXqXNUNUn/Zshl60BwKJToTYeJXJTY+eZRhpGB05w\n" - + "Mz7M+Wgvvg2WZcllRnuV0j0UTysLhz1qle0vzLR9AoGBAOukkFFm2RLm9N1P3gI8\n" - + "mUadeAlYRZ5o0MvumOHaB5pDOCKhrqAhop2gnM0f5uSlapCtlhj0Js7ZyS3Giezg\n" - + "38oqAhAYxy2LMoLD7UtsHXNp0OnZ22djcDwh+Wp2YORm7h71yOM0NsYubGbp+CmT\n" - + "Nw9bewRvqjySBlDJ9/aNSeEY\n" - + "-----END PRIVATE KEY-----"; - @Rule public GitSampleRepoRule sampleRepo = new GitSampleRepoRule(); @@ -106,16 +71,9 @@ public static void setUpJenkins() throws Exception { // Add credential (Must have valid private key for Jwt to work, but App doesn't have to actually // exist) store = CredentialsProvider.lookupStores(r.jenkins).iterator().next(); - appCredentials = new GitHubAppCredentials( - CredentialsScope.GLOBAL, myAppCredentialsId, "sample", "54321", Secret.fromString(PKCS8_PRIVATE_KEY)); - appCredentials.setOwner("cloudBeers"); + appCredentials = GitHubApp.createCredentials(myAppCredentialsId, "cloudBeers"); store.addCredentials(Domain.global(), appCredentials); - appCredentialsNoOwner = new GitHubAppCredentials( - CredentialsScope.GLOBAL, - myAppCredentialsNoOwnerId, - "sample", - "54321", - Secret.fromString(PKCS8_PRIVATE_KEY)); + appCredentialsNoOwner = GitHubApp.createCredentials(myAppCredentialsNoOwnerId); store.addCredentials(Domain.global(), appCredentialsNoOwner); // Add agent diff --git a/src/test/resources/api/contextualization/body-repos-cloudbeers-multibranch-demo-branches.json b/src/test/resources/api/contextualization/body-repos-cloudbeers-multibranch-demo-branches.json new file mode 100644 index 000000000..2fe28b544 --- /dev/null +++ b/src/test/resources/api/contextualization/body-repos-cloudbeers-multibranch-demo-branches.json @@ -0,0 +1,10 @@ +[ + { + "name": "master", + "commit": { + "sha": "ba1fdaa6c2088cecea85333e763710cd3f39688f", + "url": "https://api.github.com/repos/cloudbeers/multibranch-demo/commits/ba1fdaa6c2088cecea85333e763710cd3f39688f" + }, + "protected": false + } +] \ No newline at end of file diff --git a/src/test/resources/api/contextualization/body-repos-cloudbeers-multibranch-demo-contents.json b/src/test/resources/api/contextualization/body-repos-cloudbeers-multibranch-demo-contents.json new file mode 100644 index 000000000..3212fcabf --- /dev/null +++ b/src/test/resources/api/contextualization/body-repos-cloudbeers-multibranch-demo-contents.json @@ -0,0 +1,50 @@ +[ + { + "name": "Jenkinsfile", + "path": "Jenkinsfile", + "sha": "98c0f1dad6a7874ff6a7ccc4dd72a5519ec8fdba", + "size": 351, + "url": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/Jenkinsfile?ref=refs/heads/master", + "html_url": "https://github.com/cloudbeers/multibranch-demo/blob/refs/heads/master/Jenkinsfile", + "git_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs/98c0f1dad6a7874ff6a7ccc4dd72a5519ec8fdba", + "download_url": "https://raw.githubusercontent.com/cloudbeers/multibranch-demo/refs/heads/master/Jenkinsfile", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/Jenkinsfile?ref=refs/heads/master", + "git": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs/98c0f1dad6a7874ff6a7ccc4dd72a5519ec8fdba", + "html": "https://github.com/cloudbeers/multibranch-demo/blob/refs/heads/master/Jenkinsfile" + } + }, + { + "name": "README.md", + "path": "README.md", + "sha": "59e16b0d408539ab4f23d64f2cafd33495eb21ad", + "size": 131, + "url": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/README.md?ref=refs/heads/master", + "html_url": "https://github.com/cloudbeers/multibranch-demo/blob/refs/heads/master/README.md", + "git_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs/59e16b0d408539ab4f23d64f2cafd33495eb21ad", + "download_url": "https://raw.githubusercontent.com/cloudbeers/multibranch-demo/refs/heads/master/README.md", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/README.md?ref=refs/heads/master", + "git": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs/59e16b0d408539ab4f23d64f2cafd33495eb21ad", + "html": "https://github.com/cloudbeers/multibranch-demo/blob/refs/heads/master/README.md" + } + }, + { + "name": "hello-world.go", + "path": "hello-world.go", + "sha": "b64817ed596a1dae19dfa3f8a6e6bc26506cc645", + "size": 75, + "url": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/hello-world.go?ref=refs/heads/master", + "html_url": "https://github.com/cloudbeers/multibranch-demo/blob/refs/heads/master/hello-world.go", + "git_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs/b64817ed596a1dae19dfa3f8a6e6bc26506cc645", + "download_url": "https://raw.githubusercontent.com/cloudbeers/multibranch-demo/refs/heads/master/hello-world.go", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/hello-world.go?ref=refs/heads/master", + "git": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs/b64817ed596a1dae19dfa3f8a6e6bc26506cc645", + "html": "https://github.com/cloudbeers/multibranch-demo/blob/refs/heads/master/hello-world.go" + } + } +] \ No newline at end of file diff --git a/src/test/resources/api/contextualization/body-repos-cloudbeers-multibranch-demo-pulls.json b/src/test/resources/api/contextualization/body-repos-cloudbeers-multibranch-demo-pulls.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/src/test/resources/api/contextualization/body-repos-cloudbeers-multibranch-demo-pulls.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/test/resources/api/contextualization/body-repos-cloudbeers-multibranch-demo.json b/src/test/resources/api/contextualization/body-repos-cloudbeers-multibranch-demo.json new file mode 100644 index 000000000..e3427456b --- /dev/null +++ b/src/test/resources/api/contextualization/body-repos-cloudbeers-multibranch-demo.json @@ -0,0 +1,310 @@ +{ + "id": 863392439, + "node_id": "R_kgDOM3ZStw", + "name": "multibranch-demo", + "full_name": "cloudbeers/multibranch-demo", + "private": false, + "owner": { + "login": "cloudbeers", + "id": 814127, + "node_id": "MDQ6VXNlcjgxNDEyNw==", + "avatar_url": "https://avatars.githubusercontent.com/u/4181899?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/cloudbeers", + "html_url": "https://github.com/cloudbeers", + "followers_url": "https://api.github.com/users/cloudbeers/followers", + "following_url": "https://api.github.com/users/cloudbeers/following{/other_user}", + "gists_url": "https://api.github.com/users/cloudbeers/gists{/gist_id}", + "starred_url": "https://api.github.com/users/cloudbeers/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/cloudbeers/subscriptions", + "organizations_url": "https://api.github.com/users/cloudbeers/orgs", + "repos_url": "https://api.github.com/users/cloudbeers/repos", + "events_url": "https://api.github.com/users/cloudbeers/events{/privacy}", + "received_events_url": "https://api.github.com/users/cloudbeers/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/cloudbeers/multibranch-demo", + "description": "Simple demonstration of how to use multibranch pipelines", + "fork": true, + "url": "https://api.github.com/repos/cloudbeers/multibranch-demo", + "forks_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/forks", + "keys_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/teams", + "hooks_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/hooks", + "issue_events_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/issues/events{/number}", + "events_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/events", + "assignees_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/assignees{/user}", + "branches_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/branches{/branch}", + "tags_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/tags", + "blobs_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/languages", + "stargazers_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/stargazers", + "contributors_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/contributors", + "subscribers_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/subscribers", + "subscription_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/subscription", + "commits_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/{+path}", + "compare_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/merges", + "archive_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/downloads", + "issues_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/issues{/number}", + "pulls_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/labels{/name}", + "releases_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/releases{/id}", + "deployments_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/deployments", + "created_at": "2024-09-26T08:09:07Z", + "updated_at": "2024-09-26T08:09:07Z", + "pushed_at": "2023-11-15T06:52:14Z", + "git_url": "git://github.com/cloudbeers/multibranch-demo.git", + "ssh_url": "git@github.com:cloudbeers/multibranch-demo.git", + "clone_url": "https://github.com/cloudbeers/multibranch-demo.git", + "svn_url": "https://github.com/cloudbeers/multibranch-demo", + "homepage": "", + "size": 16, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master", + "permissions": { + "admin": false, + "maintain": false, + "push": false, + "triage": false, + "pull": false + }, + "temp_clone_token": "", + "parent": { + "id": 41513585, + "node_id": "MDEwOlJlcG9zaXRvcnk0MTUxMzU4NQ==", + "name": "multibranch-demo", + "full_name": "cloudbeers/multibranch-demo", + "private": false, + "owner": { + "login": "cloudbeers", + "id": 4181899, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjQxODE4OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/4181899?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/cloudbeers", + "html_url": "https://github.com/cloudbeers", + "followers_url": "https://api.github.com/users/cloudbeers/followers", + "following_url": "https://api.github.com/users/cloudbeers/following{/other_user}", + "gists_url": "https://api.github.com/users/cloudbeers/gists{/gist_id}", + "starred_url": "https://api.github.com/users/cloudbeers/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/cloudbeers/subscriptions", + "organizations_url": "https://api.github.com/users/cloudbeers/orgs", + "repos_url": "https://api.github.com/users/cloudbeers/repos", + "events_url": "https://api.github.com/users/cloudbeers/events{/privacy}", + "received_events_url": "https://api.github.com/users/cloudbeers/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/cloudbeers/multibranch-demo", + "description": "Simple demonstration of how to use multibranch pipelines", + "fork": false, + "url": "https://api.github.com/repos/cloudbeers/multibranch-demo", + "forks_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/forks", + "keys_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/teams", + "hooks_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/hooks", + "issue_events_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/issues/events{/number}", + "events_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/events", + "assignees_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/assignees{/user}", + "branches_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/branches{/branch}", + "tags_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/tags", + "blobs_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/languages", + "stargazers_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/stargazers", + "contributors_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/contributors", + "subscribers_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/subscribers", + "subscription_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/subscription", + "commits_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/{+path}", + "compare_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/merges", + "archive_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/downloads", + "issues_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/issues{/number}", + "pulls_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/labels{/name}", + "releases_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/releases{/id}", + "deployments_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/deployments", + "created_at": "2015-08-27T22:02:12Z", + "updated_at": "2023-08-23T06:28:13Z", + "pushed_at": "2023-11-15T06:52:14Z", + "git_url": "git://github.com/cloudbeers/multibranch-demo.git", + "ssh_url": "git@github.com:cloudbeers/multibranch-demo.git", + "clone_url": "https://github.com/cloudbeers/multibranch-demo.git", + "svn_url": "https://github.com/cloudbeers/multibranch-demo", + "homepage": "", + "size": 16, + "stargazers_count": 16, + "watchers_count": 16, + "language": "Go", + "has_issues": false, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 289, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 12, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 289, + "open_issues": 12, + "watchers": 16, + "default_branch": "master" + }, + "source": { + "id": 41513585, + "node_id": "MDEwOlJlcG9zaXRvcnk0MTUxMzU4NQ==", + "name": "multibranch-demo", + "full_name": "cloudbeers/multibranch-demo", + "private": false, + "owner": { + "login": "cloudbeers", + "id": 4181899, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjQxODE4OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/4181899?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/cloudbeers", + "html_url": "https://github.com/cloudbeers", + "followers_url": "https://api.github.com/users/cloudbeers/followers", + "following_url": "https://api.github.com/users/cloudbeers/following{/other_user}", + "gists_url": "https://api.github.com/users/cloudbeers/gists{/gist_id}", + "starred_url": "https://api.github.com/users/cloudbeers/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/cloudbeers/subscriptions", + "organizations_url": "https://api.github.com/users/cloudbeers/orgs", + "repos_url": "https://api.github.com/users/cloudbeers/repos", + "events_url": "https://api.github.com/users/cloudbeers/events{/privacy}", + "received_events_url": "https://api.github.com/users/cloudbeers/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/cloudbeers/multibranch-demo", + "description": "Simple demonstration of how to use multibranch pipelines", + "fork": false, + "url": "https://api.github.com/repos/cloudbeers/multibranch-demo", + "forks_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/forks", + "keys_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/teams", + "hooks_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/hooks", + "issue_events_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/issues/events{/number}", + "events_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/events", + "assignees_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/assignees{/user}", + "branches_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/branches{/branch}", + "tags_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/tags", + "blobs_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/languages", + "stargazers_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/stargazers", + "contributors_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/contributors", + "subscribers_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/subscribers", + "subscription_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/subscription", + "commits_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/{+path}", + "compare_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/merges", + "archive_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/downloads", + "issues_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/issues{/number}", + "pulls_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/labels{/name}", + "releases_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/releases{/id}", + "deployments_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/deployments", + "created_at": "2015-08-27T22:02:12Z", + "updated_at": "2023-08-23T06:28:13Z", + "pushed_at": "2023-11-15T06:52:14Z", + "git_url": "git://github.com/cloudbeers/multibranch-demo.git", + "ssh_url": "git@github.com:cloudbeers/multibranch-demo.git", + "clone_url": "https://github.com/cloudbeers/multibranch-demo.git", + "svn_url": "https://github.com/cloudbeers/multibranch-demo", + "homepage": "", + "size": 16, + "stargazers_count": 16, + "watchers_count": 16, + "language": "Go", + "has_issues": false, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 289, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 12, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 289, + "open_issues": 12, + "watchers": 16, + "default_branch": "master" + }, + "network_count": 289, + "subscribers_count": 0 +} \ No newline at end of file