├── .editorconfig ├── .gitattributes ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── cd.yml │ └── jenkins-security-scan.yml ├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── .idea └── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── .mvn ├── extensions.xml └── maven.config ├── Jenkinsfile ├── LICENSE ├── README.md ├── docs └── img │ ├── add-server.png │ ├── branch-build-strategy.gif │ ├── branch-source.png │ ├── gitlab-credentials.png │ ├── gitlab-token-creator.png │ └── server-config.png ├── pom.xml └── src ├── main ├── java │ └── io │ │ └── jenkins │ │ └── plugins │ │ ├── gitlabbranchsource │ │ ├── AbstractGitLabJobTrigger.java │ │ ├── AbstractGitLabSCMHeadEvent.java │ │ ├── BranchDiscoveryTrait.java │ │ ├── BranchSCMHead.java │ │ ├── BranchSCMRevision.java │ │ ├── BuildStatusNameCustomPartTrait.java │ │ ├── Cause │ │ │ ├── GitLabCauseUtils.java │ │ │ ├── GitLabMergeRequestCauseData.java │ │ │ ├── GitLabMergeRequestNoteData.java │ │ │ ├── GitLabPushCauseData.java │ │ │ └── GitLabTagPushCauseData.java │ │ ├── Environment │ │ │ └── GitLabWebHookEnvironmentContributor.java │ │ ├── ExcludeArchivedRepositoriesTrait.java │ │ ├── ForkMergeRequestDiscoveryTrait.java │ │ ├── GitLabAvatarTrait.java │ │ ├── GitLabHookCreator.java │ │ ├── GitLabHookRegistration.java │ │ ├── GitLabMarkUnstableAsSuccessTrait.java │ │ ├── GitLabMergeRequestCommentCause.java │ │ ├── GitLabMergeRequestCommentTrigger.java │ │ ├── GitLabMergeRequestSCMEvent.java │ │ ├── GitLabMergeRequestTrigger.java │ │ ├── GitLabProjectSCMEvent.java │ │ ├── GitLabPushSCMEvent.java │ │ ├── GitLabSCMBuilder.java │ │ ├── GitLabSCMCauseAction.java │ │ ├── GitLabSCMFile.java │ │ ├── GitLabSCMFileSystem.java │ │ ├── GitLabSCMNavigator.java │ │ ├── GitLabSCMNavigatorContext.java │ │ ├── GitLabSCMNavigatorRequest.java │ │ ├── GitLabSCMSource.java │ │ ├── GitLabSCMSourceBuilder.java │ │ ├── GitLabSCMSourceContext.java │ │ ├── GitLabSCMSourceRequest.java │ │ ├── GitLabSkipNotificationsTrait.java │ │ ├── GitLabSystemHookAction.java │ │ ├── GitLabSystemHookListener.java │ │ ├── GitLabTagPushSCMEvent.java │ │ ├── GitLabTagSCMHead.java │ │ ├── GitLabWebHookAction.java │ │ ├── GitLabWebHookCause.java │ │ ├── GitLabWebHookListener.java │ │ ├── HookRegistrationTrait.java │ │ ├── LogCommentTrait.java │ │ ├── MergeRequestSCMHead.java │ │ ├── MergeRequestSCMRevision.java │ │ ├── MergeWithGitSCMExtension.java │ │ ├── OriginMergeRequestDiscoveryTrait.java │ │ ├── ProjectNamingStrategyTrait.java │ │ ├── SSHCheckoutTrait.java │ │ ├── SharedProjectsDiscoveryTrait.java │ │ ├── SubGroupProjectDiscoveryTrait.java │ │ ├── TagDiscoveryTrait.java │ │ ├── TriggerMRCommentTrait.java │ │ ├── WebhookListenerBuildConditionsTrait.java │ │ ├── helpers │ │ │ ├── GitLabAvatar.java │ │ │ ├── GitLabAvatarCache.java │ │ │ ├── GitLabBrowser.java │ │ │ ├── GitLabGroup.java │ │ │ ├── GitLabHelper.java │ │ │ ├── GitLabIcons.java │ │ │ ├── GitLabLink.java │ │ │ ├── GitLabOwner.java │ │ │ ├── GitLabPipelineStatusNotifier.java │ │ │ └── GitLabUser.java │ │ └── package-info.java │ │ └── gitlabserverconfig │ │ ├── action │ │ └── GitlabAction.java │ │ ├── credentials │ │ ├── PersonalAccessToken.java │ │ ├── PersonalAccessTokenImpl.java │ │ ├── helpers │ │ │ └── GitLabCredentialMatcher.java │ │ └── package-info.java │ │ └── servers │ │ ├── GitLabServer.java │ │ ├── GitLabServers.java │ │ ├── helpers │ │ └── GitLabPersonalAccessTokenCreator.java │ │ └── package-info.java ├── resources │ ├── index.jelly │ └── io │ │ └── jenkins │ │ └── plugins │ │ ├── gitlabbranchsource │ │ ├── BranchDiscoveryTrait │ │ │ ├── config.jelly │ │ │ ├── help-branchesAlwaysIncludedRegex.html │ │ │ ├── help-strategyId.html │ │ │ └── help.html │ │ ├── BuildStatusNameCustomPartTrait │ │ │ ├── config.jelly │ │ │ ├── help-buildStatusNameCustomPart.html │ │ │ ├── help-buildStatusNameOverwrite.html │ │ │ └── help.html │ │ ├── ExcludeArchivedRepositoriesTrait │ │ │ └── help.html │ │ ├── ForkMergeRequestDiscoveryTrait │ │ │ ├── TrustEveryone │ │ │ │ ├── config.jelly │ │ │ │ └── config.properties │ │ │ ├── TrustMembers │ │ │ │ ├── config.jelly │ │ │ │ └── config.properties │ │ │ ├── TrustPermission │ │ │ │ ├── config.jelly │ │ │ │ └── config.properties │ │ │ ├── config.jelly │ │ │ ├── help-buildMRForksNotMirror.html │ │ │ ├── help-strategyId.html │ │ │ ├── help-trust.html │ │ │ └── help.html │ │ ├── GitLabAvatarTrait │ │ │ ├── config.jelly │ │ │ └── help-disableProjectAvatar.html │ │ ├── GitLabMarkUnstableAsSuccessTrait │ │ │ └── config.jelly │ │ ├── GitLabSCMNavigator │ │ │ ├── config.jelly │ │ │ ├── help-credentialsId.html │ │ │ ├── help-projectOwner.html │ │ │ └── help-serverName.html │ │ ├── GitLabSCMSource │ │ │ ├── config-detail.jelly │ │ │ ├── help-credentialsId.html │ │ │ ├── help-projectOwner.html │ │ │ ├── help-projectPath.html │ │ │ └── help-serverName.html │ │ ├── HookRegistrationTrait │ │ │ └── config.jelly │ │ ├── LogCommentTrait │ │ │ ├── config.jelly │ │ │ ├── help-logSuccess.html │ │ │ └── help-sudoUser.html │ │ ├── Messages.properties │ │ ├── OriginMergeRequestDiscoveryTrait │ │ │ ├── config.jelly │ │ │ ├── help-strategyId.html │ │ │ └── help.html │ │ ├── ProjectNamingStrategyTrait │ │ │ ├── config.jelly │ │ │ ├── help-strategyId.html │ │ │ └── help.html │ │ ├── SSHCheckoutTrait │ │ │ ├── config.jelly │ │ │ ├── help-credentialsId.html │ │ │ └── help.html │ │ ├── SharedProjectsDiscoveryTrait │ │ │ └── help.html │ │ ├── SubGroupProjectDiscoveryTrait │ │ │ └── help.html │ │ ├── TriggerMRCommentTrait │ │ │ ├── config.jelly │ │ │ ├── help-commentBody.html │ │ │ └── help-onlyTrustedMembers.html │ │ ├── WebhookListenerBuildConditionsTrait │ │ │ ├── config.jelly │ │ │ └── help-alwaysIgnoreNonCodeRelatedUpdates.html │ │ └── helpers │ │ │ └── GitLabBrowser │ │ │ ├── config.jelly │ │ │ ├── help-projectUrl.html │ │ │ └── help.html │ │ └── gitlabserverconfig │ │ ├── credentials │ │ ├── Messages.properties │ │ └── PersonalAccessTokenImpl │ │ │ └── credentials.jelly │ │ └── servers │ │ ├── GitLabServer │ │ ├── config.groovy │ │ ├── help-credentialsId.html │ │ ├── help-genButton.html │ │ ├── help-hookTriggerDelay.html │ │ ├── help-hooksRootUrl.html │ │ ├── help-immediateHookTrigger.html │ │ ├── help-manageSystemHooks.html │ │ ├── help-manageWebHooks.html │ │ ├── help-name.html │ │ ├── help-serverUrl.html │ │ └── help-webhookSecretCredentialsId.html │ │ ├── GitLabServers │ │ ├── config.groovy │ │ └── help-additional.html │ │ ├── Messages.properties │ │ └── helpers │ │ └── GitLabPersonalAccessTokenCreator │ │ └── config.groovy └── webapp │ └── images │ ├── gitlab-branch.svg │ ├── gitlab-commit.svg │ ├── gitlab-logo.svg │ ├── gitlab-mr.svg │ ├── gitlab-project.svg │ └── gitlab-tag.svg └── test ├── java └── io │ └── jenkins │ └── plugins │ ├── gitlabbranchsource │ ├── GitLabHookCreatorParameterizedTest.java │ ├── GitLabSCMSourceDeserializationTest.java │ ├── GitLabSCMSourceTest.java │ └── helpers │ │ ├── GitLabHelperTest.java │ │ └── GitLabPipelineStatusNotifierTest.java │ └── gitlabserverconfig │ ├── casc │ └── ConfigurationAsCodeTest.java │ ├── credentials │ └── PersonalAccessTokenImplTest.java │ └── servers │ ├── GitLabServerTest.java │ └── GitLabServersTest.java └── resources └── io └── jenkins └── plugins └── gitlabserverconfig ├── casc ├── configuration-as-code.yml └── expected_output.yml ├── credentials └── PersonalAccessTokenImplTest │ └── CredentialsBuilder │ └── config.jelly └── servers └── GitLabServersTest └── migrationToCredentials └── io.jenkins.plugins.gitlabserverconfig.servers.GitLabServers.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | indent_style = space 8 | 9 | [*.{java,groovy}] 10 | indent_size = 4 11 | 12 | [*.{xml,html,jelly}] 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | # Denote all files that are truly binary and should not be modified. 3 | *.png binary 4 | *.jpg binary 5 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jetersen 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: github-actions 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins 2 | 3 | name: cd 4 | on: 5 | workflow_dispatch: 6 | check_run: 7 | types: 8 | - completed 9 | 10 | jobs: 11 | maven-cd: 12 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1 13 | secrets: 14 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 15 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }} 16 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Jenkins Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [ opened, synchronize, reopened ] 9 | workflow_dispatch: 10 | 11 | permissions: 12 | security-events: write 13 | contents: read 14 | actions: read 15 | 16 | jobs: 17 | security-scan: 18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 19 | with: 20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | /work* 3 | *.iml 4 | .idea/* 5 | !.idea/codeStyles/ 6 | .classpath 7 | .project 8 | .settings 9 | bin 10 | .DS_Store 11 | .vscode/ 12 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | 3 | RUN sudo rm -rf /usr/bin/hd && \ 4 | brew install linuxsuren/linuxsuren/hd && \ 5 | hd install cli/cli 6 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.Dockerfile 3 | 4 | tasks: 5 | - init: | 6 | [[ ! -z "${DOCKER_USER}" && ! -z "${DOCKER_PASSWD}" ]] && docker login -u${DOCKER_USER} -p${DOCKER_PASSWD} 7 | git config --global user.name $GIT_AUTHOR_NAME 8 | git config --global user.email $GIT_COMMITTER_EMAIL 9 | gh repo fork --remote 10 | 11 | vscode: 12 | extensions: 13 | - redhat.java 14 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.8 6 | 7 | 8 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -Dchangelist.format=%d.v%s 4 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!groovy 2 | 3 | def configurations = [ 4 | [ platform: "linux", jdk: "17", jenkins: null ], 5 | [ platform: "windows", jdk: "17", jenkins: null ], 6 | [ platform: "linux", jdk: "21", jenkins: null ], 7 | ] 8 | buildPlugin(useContainerAgent: true, configurations: configurations) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Parichay Barpanda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/img/add-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/gitlab-branch-source-plugin/40b5f0e67cd36c3e8b925baf41b17f907ef32314/docs/img/add-server.png -------------------------------------------------------------------------------- /docs/img/branch-build-strategy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/gitlab-branch-source-plugin/40b5f0e67cd36c3e8b925baf41b17f907ef32314/docs/img/branch-build-strategy.gif -------------------------------------------------------------------------------- /docs/img/branch-source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/gitlab-branch-source-plugin/40b5f0e67cd36c3e8b925baf41b17f907ef32314/docs/img/branch-source.png -------------------------------------------------------------------------------- /docs/img/gitlab-credentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/gitlab-branch-source-plugin/40b5f0e67cd36c3e8b925baf41b17f907ef32314/docs/img/gitlab-credentials.png -------------------------------------------------------------------------------- /docs/img/gitlab-token-creator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/gitlab-branch-source-plugin/40b5f0e67cd36c3e8b925baf41b17f907ef32314/docs/img/gitlab-token-creator.png -------------------------------------------------------------------------------- /docs/img/server-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/gitlab-branch-source-plugin/40b5f0e67cd36c3e8b925baf41b17f907ef32314/docs/img/server-config.png -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/AbstractGitLabJobTrigger.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | public abstract class AbstractGitLabJobTrigger { 4 | 5 | private final E payload; 6 | 7 | protected AbstractGitLabJobTrigger(E payload) { 8 | this.payload = payload; 9 | } 10 | 11 | public static void fireNow(AbstractGitLabJobTrigger trigger) { 12 | trigger.isMatch(); 13 | } 14 | 15 | public E getPayload() { 16 | return this.payload; 17 | } 18 | 19 | public abstract void isMatch(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/AbstractGitLabSCMHeadEvent.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import hudson.scm.SCM; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.logging.Logger; 8 | import java.util.regex.Pattern; 9 | import jenkins.scm.api.SCMHead; 10 | import jenkins.scm.api.SCMHeadEvent; 11 | import jenkins.scm.api.SCMNavigator; 12 | import jenkins.scm.api.SCMRevision; 13 | import jenkins.scm.api.SCMSource; 14 | import org.gitlab4j.api.webhook.AbstractPushEvent; 15 | 16 | public abstract class AbstractGitLabSCMHeadEvent extends SCMHeadEvent { 17 | 18 | public static final Logger LOGGER = Logger.getLogger(AbstractGitLabSCMHeadEvent.class.getName()); 19 | 20 | private static final Pattern NONE_HASH_PATTERN = Pattern.compile("^0+$"); 21 | 22 | static Type typeOf(E pushEvent) { 23 | Type result; 24 | boolean hasBefore = isPresent(pushEvent.getBefore()); 25 | boolean hasAfter = isPresent(pushEvent.getAfter()); 26 | if (hasBefore && hasAfter) { 27 | result = Type.UPDATED; 28 | } else if (hasAfter) { 29 | result = Type.CREATED; 30 | } else if (hasBefore) { 31 | result = Type.REMOVED; 32 | } else { 33 | LOGGER.warning( 34 | "Received push event with both \"before\" and \"after\" set to non-existing revision. Assuming removal."); 35 | result = Type.REMOVED; 36 | } 37 | return result; 38 | } 39 | 40 | private static boolean isPresent(String ref) { 41 | return !(NONE_HASH_PATTERN.matcher(ref).matches()); 42 | } 43 | 44 | public AbstractGitLabSCMHeadEvent(Type type, E createEvent, String origin) { 45 | super(type, createEvent, origin); 46 | } 47 | 48 | @Override 49 | public boolean isMatch(@NonNull SCMNavigator navigator) { 50 | return navigator instanceof GitLabSCMNavigator && isMatch((GitLabSCMNavigator) navigator); 51 | } 52 | 53 | public abstract boolean isMatch(@NonNull GitLabSCMNavigator navigator); 54 | 55 | @Override 56 | public boolean isMatch(@NonNull SCMSource source) { 57 | return source instanceof GitLabSCMSource && isMatch((GitLabSCMSource) source); 58 | } 59 | 60 | public abstract boolean isMatch(@NonNull GitLabSCMSource source); 61 | 62 | @NonNull 63 | @Override 64 | public final Map heads(@NonNull SCMSource source) { 65 | Map heads = new HashMap<>(); 66 | if (source instanceof GitLabSCMSource) { 67 | return headsFor((GitLabSCMSource) source); 68 | } 69 | return heads; 70 | } 71 | 72 | @Override 73 | public boolean isMatch(@NonNull SCM scm) { 74 | return false; 75 | } 76 | 77 | @NonNull 78 | protected abstract Map headsFor(GitLabSCMSource source); 79 | 80 | public abstract GitLabWebHookCause getCause(); 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/BranchSCMHead.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import jenkins.scm.api.SCMHead; 5 | 6 | public class BranchSCMHead extends SCMHead { 7 | 8 | /** 9 | * Constructor. 10 | * 11 | * @param name the name.of the branch 12 | */ 13 | public BranchSCMHead(@NonNull String name) { 14 | super(name); 15 | } 16 | 17 | @Override 18 | public String getPronoun() { 19 | return Messages.BranchSCMHead_Pronoun(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/BranchSCMRevision.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import jenkins.plugins.git.AbstractGitSCMSource; 4 | 5 | public class BranchSCMRevision extends AbstractGitSCMSource.SCMRevisionImpl { 6 | 7 | public BranchSCMRevision(BranchSCMHead head, String hash) { 8 | super(head, hash); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/BuildStatusNameCustomPartTrait.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import hudson.Extension; 5 | import jenkins.scm.api.SCMSource; 6 | import jenkins.scm.api.trait.SCMSourceContext; 7 | import jenkins.scm.api.trait.SCMSourceTrait; 8 | import jenkins.scm.api.trait.SCMSourceTraitDescriptor; 9 | import org.jenkinsci.Symbol; 10 | import org.kohsuke.stapler.DataBoundConstructor; 11 | import org.kohsuke.stapler.DataBoundSetter; 12 | 13 | public class BuildStatusNameCustomPartTrait extends SCMSourceTrait { 14 | 15 | @NonNull 16 | private String buildStatusNameCustomPart = ""; 17 | 18 | private boolean buildStatusNameOverwrite; 19 | private boolean ignoreTypeInStatusName; 20 | 21 | /** 22 | * Constructor for stapler. 23 | */ 24 | @DataBoundConstructor 25 | public BuildStatusNameCustomPartTrait() { 26 | // empty 27 | } 28 | 29 | /** 30 | * Setter for stapler to set the buildStatusNameCustomPart of the build status 31 | */ 32 | @DataBoundSetter 33 | public void setBuildStatusNameCustomPart(@NonNull String buildStatusNameCustomPart) { 34 | this.buildStatusNameCustomPart = buildStatusNameCustomPart; 35 | } 36 | 37 | @DataBoundSetter 38 | public void setBuildStatusNameOverwrite(@NonNull Boolean buildStatusNameOverwrite) { 39 | this.buildStatusNameOverwrite = buildStatusNameOverwrite; 40 | } 41 | 42 | @DataBoundSetter 43 | public void setIgnoreTypeInStatusName(@NonNull Boolean ignoreTypeInStatusName) { 44 | this.ignoreTypeInStatusName = ignoreTypeInStatusName; 45 | } 46 | 47 | @Override 48 | protected void decorateContext(SCMSourceContext context) { 49 | if (context instanceof GitLabSCMSourceContext) { 50 | GitLabSCMSourceContext ctx = (GitLabSCMSourceContext) context; 51 | ctx.withBuildStatusNameCustomPart(getBuildStatusNameCustomPart()); 52 | ctx.withBuildStatusNameOverwrite(getBuildStatusNameOverwrite()); 53 | ctx.withIgnoreTypeInStatusName(getIgnoreTypeInStatusName()); 54 | } 55 | } 56 | 57 | /** 58 | * Getter method for the build status context prefix 59 | * 60 | * @return build status context prefix 61 | */ 62 | @NonNull 63 | public String getBuildStatusNameCustomPart() { 64 | return buildStatusNameCustomPart; 65 | } 66 | 67 | /** 68 | * Getter method for the build status name overwrite 69 | * 70 | * @return build status name overwrite option 71 | */ 72 | public boolean getBuildStatusNameOverwrite() { 73 | return buildStatusNameOverwrite; 74 | } 75 | 76 | /** 77 | * Getter method for the build status name overwrite 78 | * 79 | * @return build status name overwrite option 80 | */ 81 | public boolean getIgnoreTypeInStatusName() { 82 | return ignoreTypeInStatusName; 83 | } 84 | 85 | /** 86 | * Our descriptor. 87 | */ 88 | @Extension 89 | @Symbol("buildStatusNameCustomPart") 90 | public static class DescriptorImpl extends SCMSourceTraitDescriptor { 91 | 92 | @NonNull 93 | @Override 94 | public String getDisplayName() { 95 | return Messages.BuildStatusNameCustomPartTrait_displayName(); 96 | } 97 | 98 | @Override 99 | public Class getContextClass() { 100 | return GitLabSCMSourceContext.class; 101 | } 102 | 103 | @Override 104 | public Class getSourceClass() { 105 | return GitLabSCMSource.class; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/Cause/GitLabCauseUtils.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource.Cause; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | import org.gitlab4j.api.models.AccessLevel; 6 | import org.gitlab4j.api.webhook.EventLabel.LabelType; 7 | 8 | public class GitLabCauseUtils { 9 | public static int defaultListSize(List anyList) { 10 | return anyList == null ? 0 : anyList.size(); 11 | } 12 | 13 | public static String defaultLabelString(LabelType labelType) { 14 | return labelType == null ? "" : labelType.toString(); 15 | } 16 | 17 | public static String defaultBooleanString(Boolean bool) { 18 | return bool == null ? "" : bool.toString(); 19 | } 20 | 21 | public static String defaultVisibilityString(AccessLevel accessLevel) { 22 | return accessLevel == null ? "" : accessLevel.toString(); 23 | } 24 | 25 | public static String defaultDateString(Date date) { 26 | return date == null ? "" : date.toString(); 27 | } 28 | 29 | public static String defaultIntString(Integer val) { 30 | return val == null ? "" : val.toString(); 31 | } 32 | 33 | public static String defaultLongString(Long val) { 34 | return val == null ? "" : val.toString(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/Cause/GitLabMergeRequestNoteData.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource.Cause; 2 | 3 | import static org.apache.commons.lang.StringUtils.defaultString; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import org.gitlab4j.api.webhook.NoteEvent; 8 | import org.kohsuke.stapler.export.Exported; 9 | import org.kohsuke.stapler.export.ExportedBean; 10 | 11 | @ExportedBean 12 | public class GitLabMergeRequestNoteData { 13 | 14 | private final Map variables = new HashMap<>(); 15 | 16 | public GitLabMergeRequestNoteData(NoteEvent noteEvent) { 17 | this.variables.put("GITLAB_OBJECT_KIND", defaultString(NoteEvent.OBJECT_KIND)); 18 | this.variables.put( 19 | "GITLAB_COMMENT_TRIGGER", 20 | defaultString(noteEvent.getObjectAttributes().getNote())); 21 | } 22 | 23 | @Exported 24 | public Map getBuildVariables() { 25 | return variables; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/Environment/GitLabWebHookEnvironmentContributor.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource.Environment; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import hudson.EnvVars; 5 | import hudson.Extension; 6 | import hudson.model.EnvironmentContributor; 7 | import hudson.model.Run; 8 | import hudson.model.TaskListener; 9 | import io.jenkins.plugins.gitlabbranchsource.GitLabMergeRequestCommentCause; 10 | import io.jenkins.plugins.gitlabbranchsource.GitLabWebHookCause; 11 | import java.util.Map; 12 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 13 | 14 | @Extension 15 | public class GitLabWebHookEnvironmentContributor extends EnvironmentContributor { 16 | 17 | @Override 18 | public void buildEnvironmentFor(@NonNull Run r, @NonNull EnvVars envs, @NonNull TaskListener listener) { 19 | GitLabWebHookCause gitLabWebHookCause = null; 20 | GitLabMergeRequestCommentCause gitLabMergeRequestCommentCause = null; 21 | 22 | if (r instanceof WorkflowRun) { 23 | gitLabWebHookCause = (GitLabWebHookCause) r.getCause(GitLabWebHookCause.class); 24 | gitLabMergeRequestCommentCause = 25 | (GitLabMergeRequestCommentCause) r.getCause(GitLabMergeRequestCommentCause.class); 26 | } 27 | envs.override("GITLAB_OBJECT_KIND", "none"); 28 | 29 | if (gitLabWebHookCause != null) { 30 | if (gitLabWebHookCause.getGitLabPushCauseData() != null) { 31 | envs.overrideAll(gitLabWebHookCause.getGitLabPushCauseData().getBuildVariables()); 32 | } else if (gitLabWebHookCause.getGitLabMergeRequestCauseData() != null) { 33 | envs.overrideAll( 34 | gitLabWebHookCause.getGitLabMergeRequestCauseData().getBuildVariables()); 35 | } else if (gitLabWebHookCause.getGitLabTagPushCauseData() != null) { 36 | envs.overrideAll(gitLabWebHookCause.getGitLabTagPushCauseData().getBuildVariables()); 37 | } 38 | } 39 | 40 | // There is GitLabMergeRequestCommentCause so we have to do extra check 41 | // for processing these variables. If someone wants to refactor it - look at 42 | // inheritance hierarchy of GitLabWebHookCause and GitLabMergeRequestCommentCause 43 | // TODO combine GitLabWebHookCause and GitLabMergeRequestCommentCause to refactor this class properly 44 | if (gitLabMergeRequestCommentCause != null) { 45 | if (gitLabMergeRequestCommentCause.getGitLabMergeRequestNoteData() != null) { 46 | Map buildVariables = gitLabMergeRequestCommentCause 47 | .getGitLabMergeRequestNoteData() 48 | .getBuildVariables(); 49 | envs.overrideAll(buildVariables); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/ExcludeArchivedRepositoriesTrait.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import hudson.Extension; 5 | import jenkins.scm.api.trait.SCMNavigatorContext; 6 | import jenkins.scm.api.trait.SCMNavigatorTrait; 7 | import jenkins.scm.api.trait.SCMNavigatorTraitDescriptor; 8 | import jenkins.scm.impl.trait.Selection; 9 | import org.jenkinsci.Symbol; 10 | import org.kohsuke.stapler.DataBoundConstructor; 11 | 12 | /** 13 | * A {@link Selection} trait that will restrict the discovery of repositories that have been 14 | * archived. 15 | */ 16 | public class ExcludeArchivedRepositoriesTrait extends SCMNavigatorTrait { 17 | 18 | /** Constructor for stapler. */ 19 | @DataBoundConstructor 20 | public ExcludeArchivedRepositoriesTrait() {} 21 | 22 | /** {@inheritDoc} */ 23 | @Override 24 | protected void decorateContext(SCMNavigatorContext context) { 25 | super.decorateContext(context); 26 | GitLabSCMNavigatorContext ctx = (GitLabSCMNavigatorContext) context; 27 | ctx.setExcludeArchivedRepositories(true); 28 | } 29 | 30 | /** Exclude archived repositories filter */ 31 | @Symbol("gitLabExcludeArchivedRepositories") 32 | @Extension 33 | @Selection 34 | public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { 35 | 36 | @Override 37 | public Class getContextClass() { 38 | return GitLabSCMNavigatorContext.class; 39 | } 40 | 41 | @NonNull 42 | @Override 43 | public String getDisplayName() { 44 | return Messages.ExcludeArchivedRepositoriesTrait_displayName(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabAvatarTrait.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import hudson.Extension; 4 | import jenkins.scm.api.SCMSource; 5 | import jenkins.scm.api.trait.SCMSourceContext; 6 | import jenkins.scm.api.trait.SCMSourceTrait; 7 | import jenkins.scm.api.trait.SCMSourceTraitDescriptor; 8 | import org.jenkinsci.Symbol; 9 | import org.kohsuke.stapler.DataBoundConstructor; 10 | import org.kohsuke.stapler.DataBoundSetter; 11 | 12 | public class GitLabAvatarTrait extends SCMSourceTrait { 13 | 14 | /** 15 | * Stores true or false to disable avatar for projects (but not owner) 16 | */ 17 | private boolean disableProjectAvatar = false; 18 | 19 | /** 20 | * Constructor for stapler. 21 | */ 22 | @DataBoundConstructor 23 | public GitLabAvatarTrait() { 24 | // empty 25 | } 26 | 27 | @DataBoundSetter 28 | public void setDisableProjectAvatar(boolean disableProjectAvatar) { 29 | this.disableProjectAvatar = disableProjectAvatar; 30 | } 31 | 32 | @Override 33 | protected void decorateContext(SCMSourceContext context) { 34 | if (context instanceof GitLabSCMSourceContext) { 35 | GitLabSCMSourceContext ctx = (GitLabSCMSourceContext) context; 36 | ctx.withProjectAvatarDisabled(isDisableProjectAvatar()); 37 | } 38 | } 39 | 40 | public boolean isDisableProjectAvatar() { 41 | return disableProjectAvatar; 42 | } 43 | 44 | /** 45 | * Our descriptor. 46 | */ 47 | @Extension 48 | @Symbol("gitlabAvatar") 49 | public static class DescriptorImpl extends SCMSourceTraitDescriptor { 50 | 51 | @Override 52 | public String getDisplayName() { 53 | return Messages.GitLabAvatarTrait_displayName(); 54 | } 55 | 56 | @Override 57 | public Class getContextClass() { 58 | return GitLabSCMSourceContext.class; 59 | } 60 | 61 | @Override 62 | public Class getSourceClass() { 63 | return GitLabSCMSource.class; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabHookRegistration.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import io.jenkins.plugins.gitlabserverconfig.servers.GitLabServers; 4 | 5 | /** 6 | * Enumeration of the different web hook/system hook registration modes. 7 | */ 8 | public enum GitLabHookRegistration { 9 | /** 10 | * Disable hook registration. 11 | */ 12 | DISABLE, 13 | /** 14 | * Use the global system configuration for hook registration. (If the {@link GitLabServers} does 15 | * not have hook registration configured then this will be the same as {@link #DISABLE}) 16 | */ 17 | SYSTEM, 18 | /** 19 | * Use the item scoped credentials to register the hook. 20 | */ 21 | ITEM 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabMarkUnstableAsSuccessTrait.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import hudson.Extension; 4 | import jenkins.scm.api.SCMSource; 5 | import jenkins.scm.api.trait.SCMSourceContext; 6 | import jenkins.scm.api.trait.SCMSourceTrait; 7 | import jenkins.scm.api.trait.SCMSourceTraitDescriptor; 8 | import org.jenkinsci.Symbol; 9 | import org.kohsuke.stapler.DataBoundConstructor; 10 | import org.kohsuke.stapler.DataBoundSetter; 11 | 12 | public class GitLabMarkUnstableAsSuccessTrait extends SCMSourceTrait { 13 | 14 | private boolean markUnstableAsSuccess = false; 15 | 16 | @DataBoundConstructor 17 | public GitLabMarkUnstableAsSuccessTrait() { 18 | // empty 19 | } 20 | 21 | @DataBoundSetter 22 | public void setMarkUnstableAsSuccess(boolean markUnstableAsSuccess) { 23 | this.markUnstableAsSuccess = markUnstableAsSuccess; 24 | } 25 | 26 | public boolean getMarkUnstableAsSuccess() { 27 | return markUnstableAsSuccess; 28 | } 29 | 30 | @Override 31 | protected void decorateContext(SCMSourceContext context) { 32 | if (context instanceof GitLabSCMSourceContext) { 33 | GitLabSCMSourceContext ctx = (GitLabSCMSourceContext) context; 34 | ctx.withMarkUnstableAsSuccess(doMarkUnstableAsSuccess()); 35 | } 36 | } 37 | 38 | public boolean doMarkUnstableAsSuccess() { 39 | return markUnstableAsSuccess; 40 | } 41 | 42 | /** 43 | * Our descriptor. 44 | */ 45 | @Extension 46 | @Symbol("gitlabMarkUnstableAsSuccess") 47 | public static class DescriptorImpl extends SCMSourceTraitDescriptor { 48 | 49 | @Override 50 | public String getDisplayName() { 51 | return Messages.GitLabMarkUnstableAsSuccessTrait_displayName(); 52 | } 53 | 54 | @Override 55 | public Class getContextClass() { 56 | return GitLabSCMSourceContext.class; 57 | } 58 | 59 | @Override 60 | public Class getSourceClass() { 61 | return GitLabSCMSource.class; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabMergeRequestCommentCause.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import hudson.model.Cause; 4 | import io.jenkins.plugins.gitlabbranchsource.Cause.GitLabMergeRequestNoteData; 5 | import org.gitlab4j.api.webhook.NoteEvent; 6 | 7 | public final class GitLabMergeRequestCommentCause extends Cause { 8 | 9 | private final String commentUrl; 10 | private final GitLabMergeRequestNoteData gitLabMergeRequestNoteData; 11 | 12 | /** 13 | * Constructor. 14 | * 15 | * @param commentUrl the URL for the GitLab comment 16 | */ 17 | public GitLabMergeRequestCommentCause(String commentUrl) { 18 | this.commentUrl = commentUrl; 19 | this.gitLabMergeRequestNoteData = null; 20 | } 21 | 22 | /** 23 | * Constructor. 24 | * 25 | * @param commentUrl the URL for the GitLab comment 26 | * @param noteEvent note event 27 | */ 28 | public GitLabMergeRequestCommentCause(String commentUrl, NoteEvent noteEvent) { 29 | this.commentUrl = commentUrl; 30 | this.gitLabMergeRequestNoteData = new GitLabMergeRequestNoteData(noteEvent); 31 | } 32 | 33 | @Override 34 | public String getShortDescription() { 35 | return "GitLab merge request comment"; 36 | } 37 | 38 | /** 39 | * Retrieves the URL for the GitLab comment for this cause. 40 | * 41 | * @return the URL for the GitLab comment 42 | */ 43 | public String getCommentUrl() { 44 | return commentUrl; 45 | } 46 | 47 | /** 48 | * Retrieves the cause data 49 | * @return cause data 50 | */ 51 | public GitLabMergeRequestNoteData getGitLabMergeRequestNoteData() { 52 | return gitLabMergeRequestNoteData; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabMergeRequestTrigger.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import java.util.logging.Level; 5 | import java.util.logging.Logger; 6 | import jenkins.scm.api.SCMHeadObserver; 7 | import org.gitlab4j.api.webhook.MergeRequestEvent; 8 | import org.gitlab4j.api.webhook.MergeRequestEvent.ObjectAttributes; 9 | 10 | public class GitLabMergeRequestTrigger extends GitLabMergeRequestSCMEvent { 11 | 12 | public static final Logger LOGGER = Logger.getLogger(GitLabMergeRequestTrigger.class.getName()); 13 | 14 | public GitLabMergeRequestTrigger(MergeRequestEvent mrEvent, String origin) { 15 | super(mrEvent, origin); 16 | } 17 | 18 | @Override 19 | public boolean isMatch(@NonNull GitLabSCMSource source) { 20 | final GitLabSCMSourceContext sourceContext = 21 | new GitLabSCMSourceContext(null, SCMHeadObserver.none()).withTraits(source.getTraits()); 22 | 23 | boolean shouldBuild = this.shouldBuild(getPayload(), sourceContext); 24 | LOGGER.log(Level.FINE, "isMatch() result for MR-{0}: {1}", new Object[] { 25 | getPayload().getObjectAttributes().getIid(), String.valueOf(shouldBuild) 26 | }); 27 | 28 | return getPayload().getObjectAttributes().getTargetProjectId().equals(source.getProjectId()) && shouldBuild; 29 | } 30 | 31 | private boolean shouldBuild(MergeRequestEvent mrEvent, GitLabSCMSourceContext context) { 32 | ObjectAttributes attributes = mrEvent.getObjectAttributes(); 33 | String action = attributes.getAction(); 34 | boolean shouldBuild = true; 35 | 36 | if (attributes.getWorkInProgress() && context.alwaysIgnoreMRWorkInProgress()) { 37 | LOGGER.log( 38 | Level.FINE, 39 | "shouldBuild for MR-{0} set to false due to WorkInProgress=true.", 40 | getPayload().getObjectAttributes().getIid()); 41 | return false; 42 | } 43 | 44 | if (action != null) { 45 | if (action.equals("update") && context.alwaysIgnoreNonCodeRelatedUpdates()) { 46 | if (attributes.getOldrev() == null) { 47 | shouldBuild = false; 48 | } 49 | } 50 | 51 | if (!shouldBuild) { 52 | LOGGER.log( 53 | Level.FINE, 54 | "shouldBuild for MR-{0} set to false due to non-code related updates.", 55 | getPayload().getObjectAttributes().getIid()); 56 | } 57 | 58 | LOGGER.log( 59 | Level.FINEST, 60 | "shouldBuild for MR-{0} will be set for action {1} based on pipeline configuration.", 61 | new Object[] {getPayload().getObjectAttributes().getIid(), action}); 62 | 63 | if (action.equals("open")) { 64 | return context.alwaysBuildMROpen(); 65 | } 66 | 67 | if (action.equals("reopen")) { 68 | return context.alwaysBuildMRReOpen(); 69 | } 70 | 71 | if (action.equals("approval")) { 72 | return !context.alwaysIgnoreMRApproval(); 73 | } 74 | 75 | if (action.equals("unapproval")) { 76 | return !context.alwaysIgnoreMRUnApproval(); 77 | } 78 | 79 | if (action.equals("approved")) { 80 | return !context.alwaysIgnoreMRApproved(); 81 | } 82 | 83 | if (action.equals("unapproved")) { 84 | return !context.alwaysIgnoreMRUnApproved(); 85 | } 86 | } 87 | 88 | return shouldBuild; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabProjectSCMEvent.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import jenkins.scm.api.SCMNavigator; 5 | import jenkins.scm.api.SCMSource; 6 | import jenkins.scm.api.SCMSourceEvent; 7 | import org.gitlab4j.api.systemhooks.ProjectSystemHookEvent; 8 | 9 | public class GitLabProjectSCMEvent extends SCMSourceEvent { 10 | 11 | public GitLabProjectSCMEvent(ProjectSystemHookEvent projectSystemHookEvent, String origin) { 12 | super(typeOf(projectSystemHookEvent), projectSystemHookEvent, origin); 13 | } 14 | 15 | private static Type typeOf(ProjectSystemHookEvent projectSystemHookEvent) { 16 | switch (projectSystemHookEvent.getEventName()) { 17 | case ProjectSystemHookEvent.PROJECT_CREATE_EVENT: 18 | return Type.CREATED; 19 | case ProjectSystemHookEvent.PROJECT_DESTROY_EVENT: 20 | return Type.REMOVED; 21 | case ProjectSystemHookEvent.PROJECT_UPDATE_EVENT: 22 | return Type.UPDATED; 23 | default: 24 | throw new IllegalArgumentException("cannot handle system-hook " + projectSystemHookEvent); 25 | } 26 | } 27 | 28 | /** 29 | * {@inheritDoc} 30 | */ 31 | @Override 32 | public String descriptionFor(@NonNull SCMNavigator navigator) { 33 | return description(); 34 | } 35 | 36 | /** 37 | * {@inheritDoc} 38 | */ 39 | @Override 40 | public String description() { 41 | return "Project event to branch " + getPayload().getPath() + " in namespace " 42 | + getPayload().getPathWithNamespace(); 43 | } 44 | 45 | /** 46 | * {@inheritDoc} 47 | */ 48 | @Override 49 | public String descriptionFor(SCMSource source) { 50 | return "Project event in " + getPayload().getPathWithNamespace(); 51 | } 52 | 53 | @NonNull 54 | @Override 55 | public String getSourceName() { 56 | return getPayload().getPathWithNamespace(); 57 | } 58 | 59 | @Override 60 | public boolean isMatch(@NonNull SCMNavigator navigator) { 61 | return navigator instanceof GitLabSCMNavigator && isMatch((GitLabSCMNavigator) navigator); 62 | } 63 | 64 | private boolean isMatch(@NonNull GitLabSCMNavigator navigator) { 65 | switch (getType()) { 66 | case CREATED: 67 | String projectPathWithNamespace = getPayload().getPathWithNamespace(); 68 | String projectOwner = GitLabSCMNavigator.getProjectOwnerFromNamespace(projectPathWithNamespace); 69 | if (navigator.isGroup()) { 70 | // checks when project owner is a Group 71 | if (navigator.isWantSubGroupProjects()) { 72 | // can be a subgroup so needs to at least start with the project owner when subgroup projects 73 | // are required 74 | return projectOwner.startsWith(navigator.getProjectOwner()); 75 | } else { 76 | // when subgroup projects are not required, project owner should match project owner 77 | return projectOwner.equals(navigator.getProjectOwner()); 78 | } 79 | } else { 80 | // check if username matches when subgroup projects are not required 81 | // project owner is derived from project namespace 82 | return projectOwner.equals(navigator.getProjectOwner()); 83 | } 84 | case UPDATED: 85 | return navigator.getNavigatorProjects().contains(getPayload().getPathWithNamespace()); 86 | case REMOVED: 87 | return navigator.getNavigatorProjects().contains(getPayload().getPathWithNamespace()); 88 | default: 89 | return false; 90 | } 91 | } 92 | 93 | @Override 94 | public boolean isMatch(@NonNull SCMSource source) { 95 | return source instanceof GitLabSCMSource 96 | && getPayload().getProjectId().equals(((GitLabSCMSource) source).getProjectId()); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabPushSCMEvent.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import java.util.Collections; 5 | import java.util.Map; 6 | import jenkins.scm.api.SCMHead; 7 | import jenkins.scm.api.SCMNavigator; 8 | import jenkins.scm.api.SCMRevision; 9 | import jenkins.scm.api.SCMSource; 10 | import org.apache.commons.lang.StringUtils; 11 | import org.eclipse.jgit.lib.Constants; 12 | import org.gitlab4j.api.webhook.PushEvent; 13 | 14 | public class GitLabPushSCMEvent extends AbstractGitLabSCMHeadEvent { 15 | 16 | public GitLabPushSCMEvent(PushEvent pushEvent, String origin) { 17 | super(typeOf(pushEvent), pushEvent, origin); 18 | } 19 | 20 | /** 21 | * {@inheritDoc} 22 | */ 23 | @Override 24 | public String descriptionFor(@NonNull SCMNavigator navigator) { 25 | return description(); 26 | } 27 | 28 | /** 29 | * {@inheritDoc} 30 | */ 31 | @NonNull 32 | @Override 33 | public String getSourceName() { 34 | return getPayload().getProject().getPathWithNamespace(); 35 | } 36 | 37 | /** 38 | * {@inheritDoc} 39 | */ 40 | @Override 41 | public String descriptionFor(SCMSource source) { 42 | String ref = getPayload().getRef(); 43 | ref = ref.startsWith(Constants.R_HEADS) ? ref.substring(Constants.R_HEADS.length()) : ref; 44 | return "Push event to branch " + ref; 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | @Override 51 | public String description() { 52 | String ref = getPayload().getRef(); 53 | ref = ref.startsWith(Constants.R_HEADS) ? ref.substring(Constants.R_HEADS.length()) : ref; 54 | return "Push event to branch " + ref + " in project " 55 | + getPayload().getProject().getPathWithNamespace(); 56 | } 57 | 58 | @Override 59 | public boolean isMatch(@NonNull GitLabSCMNavigator navigator) { 60 | return navigator 61 | .getNavigatorProjects() 62 | .contains(getPayload().getProject().getPathWithNamespace()); 63 | } 64 | 65 | @Override 66 | public boolean isMatch(@NonNull GitLabSCMSource source) { 67 | return getPayload().getProject().getId().equals(source.getProjectId()); 68 | } 69 | 70 | @NonNull 71 | @Override 72 | public Map headsFor(GitLabSCMSource source) { 73 | String ref = getPayload().getRef(); 74 | ref = ref.startsWith(Constants.R_HEADS) ? ref.substring(Constants.R_HEADS.length()) : ref; 75 | BranchSCMHead h = new BranchSCMHead(ref); 76 | return Collections.singletonMap( 77 | h, 78 | StringUtils.isNotBlank(getPayload().getAfter()) 79 | ? new BranchSCMRevision(h, getPayload().getAfter()) 80 | : null); 81 | } 82 | 83 | @Override 84 | public GitLabWebHookCause getCause() { 85 | return new GitLabWebHookCause().fromPush(getPayload()); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabSCMCauseAction.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import hudson.model.Cause; 4 | import hudson.model.CauseAction; 5 | 6 | public class GitLabSCMCauseAction extends CauseAction { 7 | 8 | public GitLabSCMCauseAction(Cause... causes) { 9 | super(causes); 10 | } 11 | 12 | public String getDescription() { 13 | GitLabWebHookCause cause = findCause(GitLabWebHookCause.class); 14 | return (cause != null) ? cause.getShortDescription() : null; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabSCMFile.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import jenkins.scm.api.SCMFile; 9 | import org.gitlab4j.api.GitLabApi; 10 | import org.gitlab4j.api.GitLabApiException; 11 | import org.gitlab4j.api.models.TreeItem; 12 | 13 | public class GitLabSCMFile extends SCMFile { 14 | 15 | private final GitLabApi gitLabApi; 16 | private final String projectPath; 17 | private final String ref; 18 | private boolean isDir; 19 | 20 | public GitLabSCMFile(GitLabApi gitLabApi, String projectPath, String ref) { 21 | super(); 22 | this.gitLabApi = gitLabApi; 23 | this.isDir = true; 24 | type(Type.DIRECTORY); 25 | this.projectPath = projectPath; 26 | this.ref = ref; 27 | } 28 | 29 | private GitLabSCMFile(@NonNull GitLabSCMFile parent, String name, boolean isDir) { 30 | super(parent, name); 31 | this.gitLabApi = parent.gitLabApi; 32 | this.projectPath = parent.projectPath; 33 | this.ref = parent.ref; 34 | this.isDir = isDir; 35 | } 36 | 37 | private GitLabSCMFile(GitLabSCMFile parent, String name, Type type) { 38 | super(parent, name); 39 | this.gitLabApi = parent.gitLabApi; 40 | this.projectPath = parent.projectPath; 41 | this.ref = parent.ref; 42 | isDir = type == Type.DIRECTORY; 43 | type(type); 44 | } 45 | 46 | @NonNull 47 | @Override 48 | protected SCMFile newChild(@NonNull String name, boolean assumeIsDirectory) { 49 | return new GitLabSCMFile(this, name, assumeIsDirectory); 50 | } 51 | 52 | @NonNull 53 | @Override 54 | public Iterable children() throws IOException, InterruptedException { 55 | if (!this.isDirectory()) { 56 | throw new IOException("Cannot get children from a regular file"); 57 | } 58 | List treeItems = fetchTree(); 59 | List result = new ArrayList<>(treeItems.size()); 60 | for (TreeItem c : treeItems) { 61 | Type t; 62 | if (c.getType() == TreeItem.Type.TREE) { 63 | t = Type.DIRECTORY; 64 | } else if (c.getType() == TreeItem.Type.BLOB) { 65 | if ("120000".equals(c.getMode())) { 66 | // File Mode 120000 is a symlink 67 | t = Type.LINK; 68 | } else { 69 | t = Type.REGULAR_FILE; 70 | } 71 | } else { 72 | t = Type.OTHER; 73 | } 74 | result.add(new GitLabSCMFile(this, c.getName(), t)); 75 | } 76 | return result; 77 | } 78 | 79 | @Override 80 | public long lastModified() throws IOException, InterruptedException { 81 | // TODO Fix this 82 | return 0L; 83 | } 84 | 85 | @NonNull 86 | @Override 87 | protected Type type() throws IOException, InterruptedException { 88 | if (isDir) { 89 | return Type.DIRECTORY; 90 | } 91 | try { 92 | gitLabApi.getRepositoryFileApi().getFile(projectPath, getPath(), ref); 93 | return Type.REGULAR_FILE; 94 | } catch (GitLabApiException e) { 95 | if (e.getHttpStatus() != 404) { 96 | throw new IOException(e); 97 | } 98 | try { 99 | List files = gitLabApi.getRepositoryApi().getTree(projectPath, getPath(), ref); 100 | if (files.size() == 0) { 101 | return Type.NONEXISTENT; 102 | } 103 | return Type.DIRECTORY; 104 | } catch (GitLabApiException ex) { 105 | if (e.getHttpStatus() != 404) { 106 | throw new IOException(e); 107 | } 108 | } 109 | } 110 | return Type.NONEXISTENT; 111 | } 112 | 113 | @NonNull 114 | @Override 115 | public InputStream content() throws IOException, InterruptedException { 116 | if (this.isDirectory()) { 117 | throw new IOException("Cannot get raw content from a directory"); 118 | } else { 119 | return fetchFile(); 120 | } 121 | } 122 | 123 | private InputStream fetchFile() throws IOException { 124 | try { 125 | return gitLabApi.getRepositoryFileApi().getRawFile(projectPath, ref, getPath()); 126 | } catch (GitLabApiException e) { 127 | throw new IOException(String.format("%s not found at %s", getPath(), ref)); 128 | } 129 | } 130 | 131 | private List fetchTree() throws IOException { 132 | try { 133 | return gitLabApi.getRepositoryApi().getTree(projectPath, getPath(), ref); 134 | } catch (GitLabApiException e) { 135 | throw new IOException(String.format("%s not found at %s", getPath(), ref)); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabSCMFileSystem.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import static io.jenkins.plugins.gitlabbranchsource.helpers.GitLabHelper.apiBuilder; 4 | 5 | import edu.umd.cs.findbugs.annotations.CheckForNull; 6 | import edu.umd.cs.findbugs.annotations.NonNull; 7 | import hudson.Extension; 8 | import hudson.model.Item; 9 | import hudson.scm.SCM; 10 | import hudson.scm.SCMDescriptor; 11 | import java.io.IOException; 12 | import jenkins.scm.api.SCMFile; 13 | import jenkins.scm.api.SCMFileSystem; 14 | import jenkins.scm.api.SCMHead; 15 | import jenkins.scm.api.SCMRevision; 16 | import jenkins.scm.api.SCMSource; 17 | import jenkins.scm.api.SCMSourceDescriptor; 18 | import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; 19 | import org.gitlab4j.api.GitLabApi; 20 | import org.gitlab4j.api.GitLabApiException; 21 | 22 | public class GitLabSCMFileSystem extends SCMFileSystem { 23 | 24 | private final GitLabApi gitLabApi; 25 | private final String projectPath; 26 | private final String ref; 27 | 28 | protected GitLabSCMFileSystem(GitLabApi gitLabApi, String projectPath, String ref, @CheckForNull SCMRevision rev) 29 | throws IOException { 30 | super(rev); 31 | this.gitLabApi = gitLabApi; 32 | this.projectPath = projectPath; 33 | this.ref = ref; 34 | } 35 | 36 | @Override 37 | public long lastModified() throws IOException { 38 | try { 39 | return gitLabApi 40 | .getCommitsApi() 41 | .getCommit(projectPath, ref) 42 | .getCommittedDate() 43 | .getTime(); 44 | } catch (GitLabApiException e) { 45 | throw new IOException("Failed to retrieve last modified time", e); 46 | } 47 | } 48 | 49 | @NonNull 50 | @Override 51 | public SCMFile getRoot() { 52 | return new GitLabSCMFile(gitLabApi, projectPath, ref); 53 | } 54 | 55 | @Extension 56 | public static class BuilderImpl extends Builder { 57 | 58 | @Override 59 | public boolean supports(SCM source) { 60 | return false; 61 | } 62 | 63 | @Override 64 | public boolean supports(SCMSource source) { 65 | return source instanceof GitLabSCMSource; 66 | } 67 | 68 | @Override 69 | protected boolean supportsDescriptor(SCMDescriptor scmDescriptor) { 70 | return false; 71 | } 72 | 73 | @Override 74 | protected boolean supportsDescriptor(SCMSourceDescriptor scmSourceDescriptor) { 75 | return false; 76 | } 77 | 78 | @Override 79 | public SCMFileSystem build(@NonNull Item owner, @NonNull SCM scm, @CheckForNull SCMRevision rev) { 80 | return null; 81 | } 82 | 83 | @Override 84 | public SCMFileSystem build(@NonNull SCMSource source, @NonNull SCMHead head, @CheckForNull SCMRevision rev) 85 | throws IOException, InterruptedException { 86 | GitLabSCMSource gitlabScmSource = (GitLabSCMSource) source; 87 | GitLabApi gitLabApi = apiBuilder(source.getOwner(), gitlabScmSource.getServerName()); 88 | String projectPath = gitlabScmSource.getProjectPath(); 89 | return build(head, rev, gitLabApi, projectPath); 90 | } 91 | 92 | public SCMFileSystem build( 93 | @NonNull SCMHead head, 94 | @CheckForNull SCMRevision rev, 95 | @NonNull GitLabApi gitLabApi, 96 | @NonNull String projectPath) 97 | throws IOException, InterruptedException { 98 | String ref; 99 | if (head instanceof MergeRequestSCMHead) { 100 | MergeRequestSCMHead mrHead = (MergeRequestSCMHead) head; 101 | ChangeRequestCheckoutStrategy checkoutStrategy = mrHead.getCheckoutStrategy(); 102 | String mrRef; 103 | if (checkoutStrategy == ChangeRequestCheckoutStrategy.HEAD) { 104 | mrRef = "head"; 105 | } else if (checkoutStrategy == ChangeRequestCheckoutStrategy.MERGE) { 106 | mrRef = "merge"; 107 | } else { 108 | return null; 109 | } 110 | ref = String.format("merge-requests/%s/%s", mrHead.getId(), mrRef); 111 | } else if (head instanceof BranchSCMHead) { 112 | ref = head.getName(); 113 | } else if (head instanceof GitLabTagSCMHead) { 114 | ref = head.getName(); 115 | } else { 116 | return null; 117 | } 118 | return new GitLabSCMFileSystem(gitLabApi, projectPath, ref, rev); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabSCMNavigatorContext.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import jenkins.scm.api.SCMNavigator; 5 | import jenkins.scm.api.SCMSourceObserver; 6 | import jenkins.scm.api.trait.SCMNavigatorContext; 7 | 8 | public class GitLabSCMNavigatorContext 9 | extends SCMNavigatorContext { 10 | 11 | private boolean wantSubgroupProjects; 12 | 13 | private boolean wantSharedProjects; 14 | 15 | private int projectNamingStrategy = 1; 16 | 17 | /** If true, archived repositories will be ignored. */ 18 | private boolean excludeArchivedRepositories; 19 | 20 | @NonNull 21 | @Override 22 | public GitLabSCMNavigatorRequest newRequest(@NonNull SCMNavigator navigator, @NonNull SCMSourceObserver observer) { 23 | return new GitLabSCMNavigatorRequest(navigator, this, observer); 24 | } 25 | 26 | /** 27 | * @return whether to include subgroup projects 28 | */ 29 | public boolean wantSubgroupProjects() { 30 | return wantSubgroupProjects; 31 | } 32 | 33 | public GitLabSCMNavigatorContext wantSubgroupProjects(boolean include) { 34 | this.wantSubgroupProjects = include; 35 | return this; 36 | } 37 | 38 | public boolean wantSharedProjects() { 39 | return wantSharedProjects; 40 | } 41 | 42 | public GitLabSCMNavigatorContext wantSharedProjects(boolean include) { 43 | this.wantSharedProjects = include; 44 | return this; 45 | } 46 | 47 | /** 48 | * Returns the project naming strategy id. 49 | * 50 | * @return the project naming strategy id. 51 | */ 52 | public int withProjectNamingStrategy() { 53 | return projectNamingStrategy; 54 | } 55 | 56 | public GitLabSCMNavigatorContext withProjectNamingStrategy(int strategyId) { 57 | this.projectNamingStrategy = strategyId; 58 | return this; 59 | } 60 | 61 | /** @return True if archived repositories should be ignored, false if they should be included. */ 62 | public boolean isExcludeArchivedRepositories() { 63 | return excludeArchivedRepositories; 64 | } 65 | 66 | /** @param excludeArchivedRepositories Set true to exclude archived repositories */ 67 | public void setExcludeArchivedRepositories(boolean excludeArchivedRepositories) { 68 | this.excludeArchivedRepositories = excludeArchivedRepositories; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabSCMNavigatorRequest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import jenkins.scm.api.SCMNavigator; 5 | import jenkins.scm.api.SCMSourceObserver; 6 | import jenkins.scm.api.trait.SCMNavigatorRequest; 7 | 8 | public class GitLabSCMNavigatorRequest extends SCMNavigatorRequest { 9 | 10 | private final boolean wantSharedProjects; 11 | private boolean wantSubgroupProjects; 12 | 13 | private int projectNamingStrategy; 14 | 15 | protected GitLabSCMNavigatorRequest( 16 | @NonNull SCMNavigator source, 17 | @NonNull GitLabSCMNavigatorContext context, 18 | @NonNull SCMSourceObserver observer) { 19 | super(source, context, observer); 20 | wantSubgroupProjects = context.wantSubgroupProjects(); 21 | wantSharedProjects = context.wantSharedProjects(); 22 | projectNamingStrategy = context.withProjectNamingStrategy(); 23 | } 24 | 25 | /** 26 | * @return whether to include subgroup projects 27 | */ 28 | public boolean wantSubgroupProjects() { 29 | return wantSubgroupProjects; 30 | } 31 | 32 | /** 33 | * 34 | * @return wether to include projects that are shared with the group from other groups. 35 | */ 36 | public boolean wantSharedProjects() { 37 | return wantSharedProjects; 38 | } 39 | 40 | /** 41 | * Returns the project naming strategy id. 42 | * 43 | * @return the project naming strategy id. 44 | */ 45 | public int withProjectNamingStrategy() { 46 | return projectNamingStrategy; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabSCMSourceBuilder.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.CheckForNull; 4 | import edu.umd.cs.findbugs.annotations.NonNull; 5 | import jenkins.scm.api.trait.SCMSourceBuilder; 6 | 7 | public class GitLabSCMSourceBuilder extends SCMSourceBuilder { 8 | 9 | @CheckForNull 10 | private final String id; 11 | 12 | @CheckForNull 13 | private final String serverName; 14 | 15 | @CheckForNull 16 | private final String credentialsId; 17 | 18 | @NonNull 19 | private final String projectOwner; 20 | 21 | @NonNull 22 | private final String projectPath; 23 | 24 | @NonNull 25 | private final String projectName; 26 | 27 | public GitLabSCMSourceBuilder( 28 | @CheckForNull String id, 29 | @CheckForNull String serverName, 30 | @CheckForNull String credentialsId, 31 | @NonNull String projectOwner, 32 | @NonNull String projectPath, 33 | @NonNull String projectName) { 34 | super(GitLabSCMSource.class, projectName); 35 | this.id = id; 36 | this.serverName = serverName; 37 | this.credentialsId = credentialsId; 38 | this.projectOwner = projectOwner; 39 | this.projectPath = projectPath; 40 | this.projectName = projectName; 41 | } 42 | 43 | @CheckForNull 44 | public String getId() { 45 | return id; 46 | } 47 | 48 | @CheckForNull 49 | public String getCredentialsId() { 50 | return credentialsId; 51 | } 52 | 53 | @NonNull 54 | @Override 55 | public GitLabSCMSource build() { 56 | // projectName() should have been getProjectName() 57 | GitLabSCMSource result = new GitLabSCMSource(serverName, projectOwner, projectPath); 58 | result.setId(id); 59 | result.setCredentialsId(credentialsId); 60 | result.setTraits(traits()); 61 | result.setProjectName(projectName); 62 | return result; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabSkipNotificationsTrait.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import hudson.Extension; 5 | import jenkins.scm.api.SCMSource; 6 | import jenkins.scm.api.trait.SCMSourceContext; 7 | import jenkins.scm.api.trait.SCMSourceTrait; 8 | import jenkins.scm.api.trait.SCMSourceTraitDescriptor; 9 | import org.jenkinsci.Symbol; 10 | import org.kohsuke.stapler.DataBoundConstructor; 11 | 12 | public class GitLabSkipNotificationsTrait extends SCMSourceTrait { 13 | 14 | /** 15 | * Constructor for stapler. 16 | */ 17 | @DataBoundConstructor 18 | public GitLabSkipNotificationsTrait() { 19 | // empty 20 | } 21 | 22 | @Override 23 | protected void decorateContext(SCMSourceContext context) { 24 | if (context instanceof GitLabSCMSourceContext) { 25 | GitLabSCMSourceContext ctx = (GitLabSCMSourceContext) context; 26 | ctx.withNotificationsDisabled(true); 27 | } 28 | } 29 | 30 | /** 31 | * Our descriptor. 32 | */ 33 | @Extension 34 | @Symbol("gitlabSkipNotifications") 35 | public static class DescriptorImpl extends SCMSourceTraitDescriptor { 36 | 37 | @NonNull 38 | @Override 39 | public String getDisplayName() { 40 | return Messages.GitLabSkipNotificationsTrait_displayName(); 41 | } 42 | 43 | @Override 44 | public Class getContextClass() { 45 | return GitLabSCMSourceContext.class; 46 | } 47 | 48 | @Override 49 | public Class getSourceClass() { 50 | return GitLabSCMSource.class; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabSystemHookListener.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.logging.Level; 5 | import java.util.logging.Logger; 6 | import jenkins.scm.api.SCMSourceEvent; 7 | import org.gitlab4j.api.systemhooks.GroupSystemHookEvent; 8 | import org.gitlab4j.api.systemhooks.ProjectSystemHookEvent; 9 | import org.gitlab4j.api.systemhooks.SystemHookListener; 10 | 11 | public class GitLabSystemHookListener implements SystemHookListener { 12 | 13 | public static final Logger LOGGER = Logger.getLogger(GitLabSystemHookListener.class.getName()); 14 | 15 | private String origin; 16 | 17 | public GitLabSystemHookListener(String origin) { 18 | this.origin = origin; 19 | } 20 | 21 | @Override 22 | public void onProjectEvent(ProjectSystemHookEvent projectSystemHookEvent) { 23 | LOGGER.log(Level.FINE, projectSystemHookEvent.toString()); 24 | // TODO: implement handling `project_transfer` and `project_renamed` 25 | switch (projectSystemHookEvent.getEventName()) { 26 | case ProjectSystemHookEvent.PROJECT_CREATE_EVENT: 27 | case ProjectSystemHookEvent.PROJECT_DESTROY_EVENT: 28 | case ProjectSystemHookEvent.PROJECT_UPDATE_EVENT: 29 | GitLabProjectSCMEvent trigger = new GitLabProjectSCMEvent(projectSystemHookEvent, origin); 30 | SCMSourceEvent.fireLater(trigger, 5, TimeUnit.SECONDS); 31 | break; 32 | default: 33 | LOGGER.log( 34 | Level.INFO, 35 | String.format( 36 | "unsupported System hook event: %s", 37 | projectSystemHookEvent.getEventName().toString())); 38 | } 39 | } 40 | 41 | @Override 42 | public void onGroupEvent(GroupSystemHookEvent groupSystemHookEvent) { 43 | LOGGER.log(Level.FINE, groupSystemHookEvent.toString()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabTagPushSCMEvent.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import static jenkins.scm.api.SCMEvent.Type.CREATED; 4 | 5 | import edu.umd.cs.findbugs.annotations.NonNull; 6 | import java.util.Collections; 7 | import java.util.Map; 8 | import jenkins.plugins.git.GitTagSCMRevision; 9 | import jenkins.scm.api.SCMHead; 10 | import jenkins.scm.api.SCMNavigator; 11 | import jenkins.scm.api.SCMRevision; 12 | import jenkins.scm.api.SCMSource; 13 | import org.eclipse.jgit.lib.Constants; 14 | import org.gitlab4j.api.webhook.TagPushEvent; 15 | 16 | public class GitLabTagPushSCMEvent extends AbstractGitLabSCMHeadEvent { 17 | 18 | public GitLabTagPushSCMEvent(TagPushEvent tagPushEvent, String origin) { 19 | super(typeOf(tagPushEvent), tagPushEvent, origin); 20 | } 21 | 22 | /** 23 | * {@inheritDoc} 24 | */ 25 | @Override 26 | public String descriptionFor(@NonNull SCMNavigator navigator) { 27 | return description(); 28 | } 29 | 30 | /** 31 | * {@inheritDoc} 32 | */ 33 | @NonNull 34 | @Override 35 | public String getSourceName() { 36 | return getPayload().getProject().getPathWithNamespace(); 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | @Override 43 | public String descriptionFor(SCMSource source) { 44 | String ref = getPayload().getRef(); 45 | ref = ref.startsWith(Constants.R_TAGS) ? ref.substring(Constants.R_TAGS.length()) : ref; 46 | return "Tag push event of tag " + ref + " in project " 47 | + getPayload().getProject().getPathWithNamespace(); 48 | } 49 | 50 | @Override 51 | public boolean isMatch(@NonNull GitLabSCMNavigator navigator) { 52 | return navigator 53 | .getNavigatorProjects() 54 | .contains(getPayload().getProject().getPathWithNamespace()); 55 | } 56 | 57 | @Override 58 | public boolean isMatch(@NonNull GitLabSCMSource source) { 59 | return getPayload().getProject().getId().equals(source.getProjectId()); 60 | } 61 | 62 | @NonNull 63 | @Override 64 | public Map headsFor(GitLabSCMSource source) { 65 | String ref = getPayload().getRef(); 66 | ref = ref.startsWith(Constants.R_TAGS) ? ref.substring(Constants.R_TAGS.length()) : ref; 67 | long time = 0L; 68 | if (getType() == CREATED) { 69 | time = getPayload().getCommits().get(0).getTimestamp().getTime(); 70 | } 71 | GitLabTagSCMHead h = new GitLabTagSCMHead(ref, time); 72 | String hash = getPayload().getCheckoutSha(); 73 | return Collections.singletonMap( 74 | h, (getType() == CREATED) ? new GitTagSCMRevision(h, hash) : null); 75 | } 76 | 77 | @Override 78 | public GitLabWebHookCause getCause() { 79 | return new GitLabWebHookCause().fromTag(getPayload()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabTagSCMHead.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import jenkins.plugins.git.GitTagSCMHead; 5 | import jenkins.scm.api.mixin.TagSCMHead; 6 | 7 | public class GitLabTagSCMHead extends GitTagSCMHead implements TagSCMHead { 8 | 9 | /** 10 | * Constructor. 11 | * 12 | * @param name the name. 13 | * @param timestamp the tag timestamp; 14 | */ 15 | public GitLabTagSCMHead(@NonNull String name, long timestamp) { 16 | super(name, timestamp); 17 | } 18 | 19 | /** 20 | * {@inheritDoc} 21 | */ 22 | @Override 23 | public String getPronoun() { 24 | return Messages.GitLabTagSCMHead_Pronoun(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabWebHookAction.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import static java.nio.charset.StandardCharsets.UTF_8; 4 | 5 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 6 | import hudson.Extension; 7 | import hudson.model.UnprotectedRootAction; 8 | import hudson.security.csrf.CrumbExclusion; 9 | import hudson.util.HttpResponses; 10 | import io.jenkins.plugins.gitlabserverconfig.servers.GitLabServer; 11 | import io.jenkins.plugins.gitlabserverconfig.servers.GitLabServers; 12 | import jakarta.servlet.FilterChain; 13 | import jakarta.servlet.ServletException; 14 | import jakarta.servlet.http.HttpServletRequest; 15 | import jakarta.servlet.http.HttpServletResponse; 16 | import java.io.IOException; 17 | import java.security.MessageDigest; 18 | import java.util.List; 19 | import java.util.logging.Level; 20 | import java.util.logging.Logger; 21 | import jenkins.scm.api.SCMEvent; 22 | import org.apache.commons.lang.StringUtils; 23 | import org.gitlab4j.api.GitLabApiException; 24 | import org.gitlab4j.api.webhook.WebHookManager; 25 | import org.kohsuke.stapler.HttpResponse; 26 | import org.kohsuke.stapler.StaplerRequest; 27 | import org.kohsuke.stapler.StaplerRequest2; 28 | 29 | @Extension 30 | public final class GitLabWebHookAction extends CrumbExclusion implements UnprotectedRootAction { 31 | 32 | public static final Logger LOGGER = Logger.getLogger(GitLabWebHookAction.class.getName()); 33 | 34 | @Override 35 | public String getIconFileName() { 36 | return null; 37 | } 38 | 39 | @Override 40 | public String getDisplayName() { 41 | return null; 42 | } 43 | 44 | @Override 45 | public String getUrlName() { 46 | return "gitlab-webhook"; 47 | } 48 | 49 | @Override 50 | public boolean process(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) 51 | throws IOException, ServletException { 52 | String pathInfo = req.getPathInfo(); 53 | if (pathInfo != null && pathInfo.startsWith("/" + getUrlName() + "/post")) { 54 | chain.doFilter(req, resp); 55 | return true; 56 | } 57 | return false; 58 | } 59 | 60 | public HttpResponse doPost(StaplerRequest2 request) throws IOException, GitLabApiException { 61 | if (!request.getMethod().equals("POST")) { 62 | return HttpResponses.error( 63 | HttpServletResponse.SC_BAD_REQUEST, 64 | "Only POST requests are supported, this was a " + request.getMethod() + " request"); 65 | } 66 | if (!"application/json".equals(request.getContentType())) { 67 | return HttpResponses.error( 68 | HttpServletResponse.SC_BAD_REQUEST, 69 | "Only application/json content is supported, this was " + request.getContentType()); 70 | } 71 | String type = request.getHeader("X-Gitlab-Event"); 72 | if (StringUtils.isBlank(type)) { 73 | return HttpResponses.error( 74 | HttpServletResponse.SC_BAD_REQUEST, 75 | "Expecting a GitLab event, missing expected X-Gitlab-Event header"); 76 | } 77 | String secretToken = request.getHeader("X-Gitlab-Token"); 78 | if (!isValidToken(secretToken)) { 79 | return HttpResponses.error(HttpServletResponse.SC_UNAUTHORIZED, "Expecting a valid secret token"); 80 | } 81 | String origin = SCMEvent.originOf(request); 82 | WebHookManager webHookManager = new WebHookManager(); 83 | webHookManager.addListener(new GitLabWebHookListener(origin)); 84 | webHookManager.handleEvent(StaplerRequest.fromStaplerRequest2(request)); 85 | return HttpResponses.ok(); // TODO find a better response 86 | } 87 | 88 | @SuppressFBWarnings( 89 | value = "NP_NULL_PARAM_DEREF", 90 | justification = "MessageDigest.isEqual does handle null and spotbugs is wrong") 91 | private boolean isValidToken(String secretToken) { 92 | try { 93 | List servers = GitLabServers.get().getServers(); 94 | byte[] secretTokenBytes = secretToken != null ? secretToken.getBytes(UTF_8) : null; 95 | for (GitLabServer server : servers) { 96 | String secretTokenAsPlainText = server.getSecretTokenAsPlainText(); 97 | byte[] secretTokenAsPlainTextBytes = 98 | secretTokenAsPlainText != null ? secretTokenAsPlainText.getBytes(UTF_8) : null; 99 | if (MessageDigest.isEqual(secretTokenBytes, secretTokenAsPlainTextBytes) 100 | || (secretTokenAsPlainText != null 101 | && secretTokenAsPlainText.isEmpty() 102 | && secretToken == null)) { 103 | return true; 104 | } 105 | } 106 | } catch (Exception e) { 107 | LOGGER.log(Level.WARNING, String.format("Error while validating token: %s", e.getMessage())); 108 | } 109 | return false; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabWebHookCause.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import hudson.triggers.SCMTrigger.SCMTriggerCause; 4 | import io.jenkins.plugins.gitlabbranchsource.Cause.GitLabMergeRequestCauseData; 5 | import io.jenkins.plugins.gitlabbranchsource.Cause.GitLabPushCauseData; 6 | import io.jenkins.plugins.gitlabbranchsource.Cause.GitLabTagPushCauseData; 7 | import org.apache.commons.lang.StringUtils; 8 | import org.gitlab4j.api.webhook.MergeRequestEvent; 9 | import org.gitlab4j.api.webhook.MergeRequestEvent.ObjectAttributes; 10 | import org.gitlab4j.api.webhook.PushEvent; 11 | import org.gitlab4j.api.webhook.TagPushEvent; 12 | 13 | public class GitLabWebHookCause extends SCMTriggerCause { 14 | 15 | private String description; 16 | // possible NPEs 17 | private GitLabPushCauseData gitLabPushCauseData; 18 | private GitLabMergeRequestCauseData gitLabMergeRequestCauseData; 19 | private GitLabTagPushCauseData gitLabTagPushCauseData; 20 | 21 | public GitLabWebHookCause() { 22 | super(""); 23 | } 24 | 25 | public GitLabWebHookCause fromPush(PushEvent pushEvent) { 26 | String userName = pushEvent.getUserName(); 27 | if (StringUtils.isBlank(userName)) { 28 | description = Messages.GitLabWebHookCause_ShortDescription_Push_noUser(); 29 | } else { 30 | description = Messages.GitLabWebHookCause_ShortDescription_Push(userName); 31 | } 32 | this.gitLabPushCauseData = new GitLabPushCauseData(pushEvent); 33 | return this; 34 | } 35 | 36 | public GitLabWebHookCause fromMergeRequest(MergeRequestEvent mergeRequestEvent) { 37 | ObjectAttributes objectAttributes = mergeRequestEvent.getObjectAttributes(); 38 | String id = String.valueOf(objectAttributes.getIid()); 39 | String sourceNameSpace = objectAttributes.getSource().getNamespace(); 40 | String targetNameSpace = objectAttributes.getTarget().getNamespace(); 41 | String nameSpace = StringUtils.equals(sourceNameSpace, targetNameSpace) ? "" : sourceNameSpace + "/"; 42 | String source = String.format("%s%s", nameSpace, objectAttributes.getSourceBranch()); 43 | description = Messages.GitLabWebHookCause_ShortDescription_MergeRequestHook( 44 | id, source, objectAttributes.getTargetBranch()); 45 | this.gitLabMergeRequestCauseData = new GitLabMergeRequestCauseData(mergeRequestEvent); 46 | return this; 47 | } 48 | 49 | public GitLabWebHookCause fromTag(TagPushEvent tagPushEvent) { 50 | String userName = tagPushEvent.getUserName(); 51 | if (StringUtils.isBlank(userName)) { 52 | description = Messages.GitLabWebHookCause_ShortDescription_Push_noUser(); 53 | } else { 54 | description = Messages.GitLabWebHookCause_ShortDescription_Push(userName); 55 | } 56 | this.gitLabTagPushCauseData = new GitLabTagPushCauseData(tagPushEvent); 57 | return this; 58 | } 59 | 60 | @Override 61 | public String getShortDescription() { 62 | return description; 63 | } 64 | 65 | @Override 66 | public boolean equals(Object o) { 67 | if (!super.equals(o)) { 68 | return false; 69 | } 70 | return o instanceof GitLabWebHookCause; 71 | } 72 | 73 | @Override 74 | public int hashCode() { 75 | return super.hashCode(); 76 | } 77 | 78 | public GitLabPushCauseData getGitLabPushCauseData() { 79 | return gitLabPushCauseData; 80 | } 81 | 82 | public GitLabMergeRequestCauseData getGitLabMergeRequestCauseData() { 83 | return gitLabMergeRequestCauseData; 84 | } 85 | 86 | public GitLabTagPushCauseData getGitLabTagPushCauseData() { 87 | return gitLabTagPushCauseData; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabWebHookListener.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.Nullable; 4 | import io.jenkins.plugins.gitlabserverconfig.servers.GitLabServer; 5 | import io.jenkins.plugins.gitlabserverconfig.servers.GitLabServers; 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.logging.Level; 8 | import java.util.logging.Logger; 9 | import jenkins.scm.api.SCMHeadEvent; 10 | import org.gitlab4j.api.webhook.MergeRequestEvent; 11 | import org.gitlab4j.api.webhook.NoteEvent; 12 | import org.gitlab4j.api.webhook.NoteEvent.NoteableType; 13 | import org.gitlab4j.api.webhook.PushEvent; 14 | import org.gitlab4j.api.webhook.TagPushEvent; 15 | import org.gitlab4j.api.webhook.WebHookListener; 16 | 17 | public class GitLabWebHookListener implements WebHookListener { 18 | public static final Logger LOGGER = Logger.getLogger(GitLabWebHookListener.class.getName()); 19 | 20 | // GitLab API caching timeout used for branches endpoint 21 | private static final long GITLAB_CACHING_TIMEOUT = 30; 22 | 23 | private String origin; 24 | 25 | public GitLabWebHookListener(String origin) { 26 | this.origin = origin; 27 | } 28 | 29 | @Override 30 | public void onNoteEvent(NoteEvent noteEvent) { 31 | LOGGER.log(Level.FINE, noteEvent.toString()); 32 | 33 | // Add additional checks to process different noteable types 34 | if (noteEvent.getObjectAttributes().getNoteableType() == NoteableType.MERGE_REQUEST) { 35 | GitLabMergeRequestCommentTrigger trigger = new GitLabMergeRequestCommentTrigger(noteEvent); 36 | AbstractGitLabJobTrigger.fireNow(trigger); 37 | } 38 | } 39 | 40 | @Override 41 | public void onMergeRequestEvent(MergeRequestEvent mrEvent) { 42 | LOGGER.log(Level.FINE, mrEvent.toString()); 43 | GitLabMergeRequestTrigger trigger = new GitLabMergeRequestTrigger(mrEvent, origin); 44 | fireTrigger(trigger, mrEvent.getProject().getWebUrl()); 45 | } 46 | 47 | @Override 48 | public void onPushEvent(PushEvent pushEvent) { 49 | LOGGER.log(Level.FINE, pushEvent.toString()); 50 | GitLabPushSCMEvent trigger = new GitLabPushSCMEvent(pushEvent, origin); 51 | fireTrigger(trigger, pushEvent.getProject().getWebUrl()); 52 | } 53 | 54 | @Override 55 | public void onTagPushEvent(TagPushEvent tagPushEvent) { 56 | LOGGER.log(Level.FINE, tagPushEvent.toString()); 57 | GitLabTagPushSCMEvent trigger = new GitLabTagPushSCMEvent(tagPushEvent, origin); 58 | fireTrigger(trigger, tagPushEvent.getProject().getWebUrl()); 59 | } 60 | 61 | private void fireTrigger(final SCMHeadEvent trigger, final String projectUrl) { 62 | final GitLabServer projectServer = findProjectServer(projectUrl); 63 | if (findImmediateHookTrigger(projectServer)) { 64 | SCMHeadEvent.fireNow(trigger); 65 | } 66 | final long triggerDelay = findTriggerDelay(projectServer); 67 | SCMHeadEvent.fireLater(trigger, triggerDelay, TimeUnit.SECONDS); 68 | } 69 | 70 | private boolean findImmediateHookTrigger(@Nullable final GitLabServer projectServer) { 71 | if (projectServer == null) { 72 | LOGGER.log(Level.WARNING, "Falling back to no immediate trigger"); 73 | return false; 74 | } 75 | 76 | return projectServer.isImmediateHookTrigger(); 77 | } 78 | 79 | private long findTriggerDelay(@Nullable final GitLabServer projectServer) { 80 | if (projectServer == null) { 81 | LOGGER.log(Level.WARNING, "Falling back to default trigger delay equal GitLab caching timeout"); 82 | return GITLAB_CACHING_TIMEOUT; 83 | } 84 | 85 | final Integer configuredDelay = projectServer.getHookTriggerDelay(); 86 | if (configuredDelay != null) { 87 | return configuredDelay; 88 | } else { 89 | return GITLAB_CACHING_TIMEOUT; 90 | } 91 | } 92 | 93 | private GitLabServer findProjectServer(final String projectUrl) { 94 | for (GitLabServer server : GitLabServers.get().getServers()) { 95 | if (projectUrl.startsWith(server.getServerUrl())) { 96 | return server; 97 | } 98 | } 99 | LOGGER.log(Level.WARNING, String.format("No GitLab server for project URL: %s", projectUrl)); 100 | return null; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/LogCommentTrait.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import hudson.Extension; 5 | import jenkins.scm.api.SCMSource; 6 | import jenkins.scm.api.trait.SCMSourceContext; 7 | import jenkins.scm.api.trait.SCMSourceTrait; 8 | import jenkins.scm.api.trait.SCMSourceTraitDescriptor; 9 | import org.jenkinsci.Symbol; 10 | import org.kohsuke.stapler.DataBoundConstructor; 11 | import org.kohsuke.stapler.DataBoundSetter; 12 | 13 | public class LogCommentTrait extends SCMSourceTrait { 14 | 15 | @NonNull 16 | private String sudoUser = ""; 17 | 18 | private boolean logSuccess; 19 | 20 | /** 21 | * Constructor for stapler. 22 | */ 23 | @DataBoundConstructor 24 | public LogCommentTrait() { 25 | // empty 26 | } 27 | 28 | /** 29 | * Setter for stapler to enable logging of successful builds. 30 | */ 31 | @DataBoundSetter 32 | public void setLogSuccess(boolean logSuccess) { 33 | this.logSuccess = logSuccess; 34 | } 35 | 36 | /** 37 | * Setter for stapler to set the username of the sudo user. 38 | */ 39 | @DataBoundSetter 40 | public void setSudoUser(@NonNull String sudoUser) { 41 | this.sudoUser = sudoUser; 42 | } 43 | 44 | @Override 45 | protected void decorateContext(SCMSourceContext context) { 46 | if (context instanceof GitLabSCMSourceContext) { 47 | GitLabSCMSourceContext ctx = (GitLabSCMSourceContext) context; 48 | ctx.withLogCommentEnabled(true); 49 | ctx.withLogSuccess(getLogSuccess()); 50 | ctx.withSudoUser(getSudoUser()); 51 | } 52 | } 53 | 54 | /** 55 | * Getter method for username of sudo user. 56 | * 57 | * @return username of sudo user. 58 | */ 59 | @NonNull 60 | public String getSudoUser() { 61 | return sudoUser; 62 | } 63 | 64 | /** 65 | * Getter method for logging successful build. 66 | * 67 | * @return true if logs of successful build required. 68 | */ 69 | public boolean getLogSuccess() { 70 | return logSuccess; 71 | } 72 | 73 | /** 74 | * Our descriptor. 75 | */ 76 | @Extension 77 | @Symbol("logComment") 78 | public static class DescriptorImpl extends SCMSourceTraitDescriptor { 79 | 80 | @NonNull 81 | @Override 82 | public String getDisplayName() { 83 | return Messages.LogCommentTrait_displayName(); 84 | } 85 | 86 | @Override 87 | public Class getContextClass() { 88 | return GitLabSCMSourceContext.class; 89 | } 90 | 91 | @Override 92 | public Class getSourceClass() { 93 | return GitLabSCMSource.class; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/MergeRequestSCMHead.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import jenkins.scm.api.SCMHead; 5 | import jenkins.scm.api.SCMHeadOrigin; 6 | import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; 7 | import jenkins.scm.api.mixin.ChangeRequestSCMHead2; 8 | 9 | public class MergeRequestSCMHead extends SCMHead implements ChangeRequestSCMHead2 { 10 | 11 | private final long id; 12 | private final BranchSCMHead target; 13 | private final ChangeRequestCheckoutStrategy strategy; 14 | private final String originName; 15 | private final String originOwner; 16 | private final SCMHeadOrigin origin; 17 | private String originProjectPath; 18 | private String title; 19 | 20 | /** 21 | * Constructor. 22 | * 23 | * @param id the merge request id. 24 | * @param name the name of the head. 25 | * @param target the target of this merge request. 26 | * @param strategy the checkout strategy 27 | * @param origin the origin of the merge request 28 | * @param originOwner the name of the owner of the origin project 29 | * @param originProjectPath the name of the origin project path 30 | * @param originName the name of the branch in the origin project 31 | * @param title the title of the merge request 32 | */ 33 | public MergeRequestSCMHead( 34 | @NonNull String name, 35 | long id, 36 | BranchSCMHead target, 37 | ChangeRequestCheckoutStrategy strategy, 38 | SCMHeadOrigin origin, 39 | String originOwner, 40 | String originProjectPath, 41 | String originName, 42 | String title) { 43 | super(name); 44 | this.id = id; 45 | this.target = target; 46 | this.strategy = strategy; 47 | this.origin = origin; 48 | this.originOwner = originOwner; 49 | this.originProjectPath = originProjectPath; 50 | this.originName = originName; 51 | this.title = title; 52 | } 53 | 54 | @Override 55 | public String getPronoun() { 56 | return Messages.MergeRequestSCMHead_Pronoun(); 57 | } 58 | 59 | @NonNull 60 | @Override 61 | public ChangeRequestCheckoutStrategy getCheckoutStrategy() { 62 | return strategy; 63 | } 64 | 65 | @NonNull 66 | @Override 67 | public String getOriginName() { 68 | return originName; 69 | } 70 | 71 | @NonNull 72 | @Override 73 | public String getId() { 74 | return Long.toString(id); 75 | } 76 | 77 | @NonNull 78 | @Override 79 | public BranchSCMHead getTarget() { 80 | return target; 81 | } 82 | 83 | @NonNull 84 | @Override 85 | public SCMHeadOrigin getOrigin() { 86 | return origin; 87 | } 88 | 89 | public String getOriginOwner() { 90 | return originOwner; 91 | } 92 | 93 | public String getOriginProjectPath() { 94 | return originProjectPath; 95 | } 96 | 97 | public String getTitle() { 98 | return title; 99 | } 100 | 101 | public void setTitle(String title) { 102 | this.title = title; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/MergeRequestSCMRevision.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import jenkins.scm.api.SCMRevision; 5 | import jenkins.scm.api.mixin.ChangeRequestSCMRevision; 6 | import org.kohsuke.stapler.export.Exported; 7 | 8 | public class MergeRequestSCMRevision extends ChangeRequestSCMRevision { 9 | 10 | private final @NonNull String baseHash; 11 | private final @NonNull String headHash; 12 | private BranchSCMRevision origin; 13 | 14 | /** 15 | * Constructor. 16 | * 17 | * @param head the {@link MergeRequestSCMHead} that the {@link SCMRevision} belongs to. 18 | * @param target the {@link BranchSCMRevision} of the {@link MergeRequestSCMHead#getTarget()}. 19 | * @param origin the {@link BranchSCMRevision} of the {@link MergeRequestSCMHead#getOrigin()} 20 | * head. 21 | */ 22 | public MergeRequestSCMRevision( 23 | @NonNull MergeRequestSCMHead head, @NonNull BranchSCMRevision target, @NonNull BranchSCMRevision origin) { 24 | super(head, target); 25 | this.baseHash = target.getHash(); 26 | this.headHash = origin.getHash(); 27 | this.origin = origin; 28 | } 29 | 30 | @NonNull 31 | public String getBaseHash() { 32 | return baseHash; 33 | } 34 | 35 | @NonNull 36 | public String getHeadHash() { 37 | return headHash; 38 | } 39 | 40 | @Exported 41 | @NonNull 42 | public final BranchSCMRevision getOrigin() { 43 | return origin; 44 | } 45 | 46 | @Override 47 | public boolean equivalent(ChangeRequestSCMRevision revision) { 48 | return (revision instanceof MergeRequestSCMRevision) 49 | && origin.equals(((MergeRequestSCMRevision) revision).getOrigin()); 50 | } 51 | 52 | @Override 53 | protected int _hashCode() { 54 | return origin.hashCode(); 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return (isMerge() ? ((BranchSCMRevision) getTarget()).getHash() + "+" : "") + origin.getHash(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/MergeWithGitSCMExtension.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.CheckForNull; 4 | import edu.umd.cs.findbugs.annotations.NonNull; 5 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 6 | import java.io.ObjectStreamException; 7 | import org.kohsuke.accmod.Restricted; 8 | import org.kohsuke.accmod.restrictions.NoExternalUse; 9 | 10 | /** 11 | * Retained for data migration. 12 | * 13 | * @deprecated use {@link jenkins.plugins.git.MergeWithGitSCMExtension} 14 | */ 15 | @Deprecated 16 | @Restricted(NoExternalUse.class) 17 | @SuppressFBWarnings(value = "NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") 18 | public class MergeWithGitSCMExtension extends jenkins.plugins.git.MergeWithGitSCMExtension { 19 | 20 | MergeWithGitSCMExtension(@NonNull String baseName, @CheckForNull String baseHash) { 21 | super(baseName, baseHash); 22 | } 23 | 24 | private Object readResolve() throws ObjectStreamException { 25 | return new jenkins.plugins.git.MergeWithGitSCMExtension(getBaseName(), getBaseHash()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/ProjectNamingStrategyTrait.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import hudson.Extension; 5 | import hudson.util.ListBoxModel; 6 | import jenkins.scm.api.SCMNavigator; 7 | import jenkins.scm.api.trait.SCMNavigatorContext; 8 | import jenkins.scm.api.trait.SCMNavigatorTrait; 9 | import jenkins.scm.api.trait.SCMNavigatorTraitDescriptor; 10 | import jenkins.scm.impl.trait.Discovery; 11 | import org.jenkinsci.Symbol; 12 | import org.kohsuke.accmod.Restricted; 13 | import org.kohsuke.accmod.restrictions.NoExternalUse; 14 | import org.kohsuke.stapler.DataBoundConstructor; 15 | import org.kohsuke.stapler.DataBoundSetter; 16 | 17 | public class ProjectNamingStrategyTrait extends SCMNavigatorTrait { 18 | 19 | private int strategyId = 1; 20 | 21 | @DataBoundConstructor 22 | public ProjectNamingStrategyTrait() { 23 | // empty 24 | } 25 | 26 | public int getStrategyId() { 27 | return strategyId; 28 | } 29 | 30 | @DataBoundSetter 31 | public void setStrategyId(int strategyId) { 32 | this.strategyId = strategyId; 33 | } 34 | 35 | /** 36 | * {@inheritDoc} 37 | */ 38 | @Override 39 | protected void decorateContext(SCMNavigatorContext context) { 40 | if (context instanceof GitLabSCMNavigatorContext) { 41 | GitLabSCMNavigatorContext ctx = (GitLabSCMNavigatorContext) context; 42 | ctx.withProjectNamingStrategy(getStrategyId()); 43 | } 44 | } 45 | 46 | /** 47 | * Our descriptor. 48 | */ 49 | @Symbol("gitLabProjectNamingStrategy") 50 | @Extension 51 | @Discovery 52 | public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { 53 | 54 | /** 55 | * {@inheritDoc} 56 | */ 57 | @Override 58 | @NonNull 59 | public String getDisplayName() { 60 | return Messages.ProjectNamingStrategyTrait_displayName(); 61 | } 62 | 63 | @Override 64 | public Class getNavigatorClass() { 65 | return GitLabSCMNavigator.class; 66 | } 67 | 68 | /** 69 | * Populates the strategy options. 70 | * 71 | * @return the stategy options. 72 | */ 73 | @NonNull 74 | @Restricted(NoExternalUse.class) 75 | @SuppressWarnings("unused") // stapler 76 | public ListBoxModel doFillStrategyIdItems() { 77 | ListBoxModel result = new ListBoxModel(); 78 | result.add(Messages.ProjectNamingStrategyTrait_fullProjectPath(), "1"); 79 | result.add(Messages.ProjectNamingStrategyTrait_projectName(), "2"); 80 | result.add(Messages.ProjectNamingStrategyTrait_contextualProjectPath(), "3"); 81 | result.add(Messages.ProjectNamingStrategyTrait_simpleProjectPath(), "4"); 82 | return result; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/SSHCheckoutTrait.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; 4 | import com.cloudbees.plugins.credentials.CredentialsMatchers; 5 | import com.cloudbees.plugins.credentials.CredentialsProvider; 6 | import com.cloudbees.plugins.credentials.common.StandardListBoxModel; 7 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 8 | import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder; 9 | import edu.umd.cs.findbugs.annotations.CheckForNull; 10 | import edu.umd.cs.findbugs.annotations.NonNull; 11 | import hudson.Extension; 12 | import hudson.Util; 13 | import hudson.model.Item; 14 | import hudson.model.Queue; 15 | import hudson.plugins.git.GitSCM; 16 | import hudson.scm.SCM; 17 | import hudson.security.ACL; 18 | import hudson.util.ListBoxModel; 19 | import jenkins.model.Jenkins; 20 | import jenkins.plugins.git.GitSCMBuilder; 21 | import jenkins.scm.api.SCMSource; 22 | import jenkins.scm.api.trait.SCMBuilder; 23 | import jenkins.scm.api.trait.SCMSourceContext; 24 | import jenkins.scm.api.trait.SCMSourceTrait; 25 | import jenkins.scm.api.trait.SCMSourceTraitDescriptor; 26 | import org.jenkinsci.Symbol; 27 | import org.kohsuke.accmod.Restricted; 28 | import org.kohsuke.accmod.restrictions.NoExternalUse; 29 | import org.kohsuke.stapler.AncestorInPath; 30 | import org.kohsuke.stapler.DataBoundConstructor; 31 | import org.kohsuke.stapler.QueryParameter; 32 | 33 | public class SSHCheckoutTrait extends SCMSourceTrait { 34 | 35 | @CheckForNull 36 | private final String credentialsId; 37 | 38 | @DataBoundConstructor 39 | public SSHCheckoutTrait(String credentialsId) { 40 | this.credentialsId = Util.fixEmpty(credentialsId); 41 | } 42 | 43 | @CheckForNull 44 | public final String getCredentialsId() { 45 | return credentialsId; 46 | } 47 | 48 | @Override 49 | protected void decorateBuilder(SCMBuilder builder) { 50 | ((GitSCMBuilder) builder).withCredentials(credentialsId); 51 | } 52 | 53 | @Symbol("gitLabSshCheckout") 54 | @Extension 55 | public static class DescriptorImpl extends SCMSourceTraitDescriptor { 56 | 57 | @NonNull 58 | @Override 59 | public String getDisplayName() { 60 | return "Checkout over SSH"; 61 | } 62 | 63 | @Override 64 | public Class getBuilderClass() { 65 | return GitSCMBuilder.class; 66 | } 67 | 68 | @Override 69 | public Class getContextClass() { 70 | return GitLabSCMSourceContext.class; 71 | } 72 | 73 | @Override 74 | public Class getSourceClass() { 75 | return GitLabSCMSource.class; 76 | } 77 | 78 | @Override 79 | public Class getScmClass() { 80 | return GitSCM.class; 81 | } 82 | 83 | @Restricted(NoExternalUse.class) 84 | @SuppressWarnings("unused") // stapler form binding 85 | public ListBoxModel doFillCredentialsIdItems( 86 | @CheckForNull @AncestorInPath Item context, 87 | @QueryParameter String serverUrl, 88 | @QueryParameter String credentialsId) { 89 | StandardListBoxModel result = new StandardListBoxModel(); 90 | if (context == null) { 91 | if (!Jenkins.get().hasPermission(Jenkins.MANAGE)) { 92 | // must have admin if you want the list without a context 93 | result.includeCurrentValue(credentialsId); 94 | return result; 95 | } 96 | } else { 97 | if (!context.hasPermission(Item.EXTENDED_READ) 98 | && !context.hasPermission(CredentialsProvider.USE_ITEM)) { 99 | // must be able to read the configuration or use the item credentials if you want the list 100 | result.includeCurrentValue(credentialsId); 101 | return result; 102 | } 103 | } 104 | result.includeEmptyValue(); 105 | result.includeMatchingAs( 106 | context instanceof Queue.Task ? ((Queue.Task) context).getDefaultAuthentication() : ACL.SYSTEM, 107 | context, 108 | StandardUsernameCredentials.class, 109 | URIRequirementBuilder.fromUri(serverUrl).build(), 110 | CredentialsMatchers.instanceOf(SSHUserPrivateKey.class)); 111 | return result; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/SharedProjectsDiscoveryTrait.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import hudson.Extension; 5 | import jenkins.scm.api.SCMNavigator; 6 | import jenkins.scm.api.trait.SCMNavigatorContext; 7 | import jenkins.scm.api.trait.SCMNavigatorTrait; 8 | import jenkins.scm.api.trait.SCMNavigatorTraitDescriptor; 9 | import jenkins.scm.impl.trait.Discovery; 10 | import org.jenkinsci.Symbol; 11 | import org.kohsuke.stapler.DataBoundConstructor; 12 | 13 | public class SharedProjectsDiscoveryTrait extends SCMNavigatorTrait { 14 | 15 | @DataBoundConstructor 16 | public SharedProjectsDiscoveryTrait() {} 17 | 18 | @Override 19 | protected void decorateContext(SCMNavigatorContext context) { 20 | if (context instanceof GitLabSCMNavigatorContext) { 21 | GitLabSCMNavigatorContext ctx = (GitLabSCMNavigatorContext) context; 22 | ctx.wantSharedProjects(true); 23 | } 24 | } 25 | 26 | /** 27 | * Our descriptor. 28 | */ 29 | @Symbol("gitLabSharedProjectsDiscovery") 30 | @Extension 31 | @Discovery 32 | public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { 33 | 34 | /** 35 | * {@inheritDoc} 36 | */ 37 | @Override 38 | @NonNull 39 | public String getDisplayName() { 40 | return Messages.SharedProjectsDiscoveryTrait_displayName(); 41 | } 42 | 43 | @Override 44 | public Class getNavigatorClass() { 45 | return GitLabSCMNavigator.class; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/SubGroupProjectDiscoveryTrait.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import hudson.Extension; 5 | import jenkins.scm.api.SCMNavigator; 6 | import jenkins.scm.api.trait.SCMNavigatorContext; 7 | import jenkins.scm.api.trait.SCMNavigatorTrait; 8 | import jenkins.scm.api.trait.SCMNavigatorTraitDescriptor; 9 | import jenkins.scm.impl.trait.Discovery; 10 | import org.jenkinsci.Symbol; 11 | import org.kohsuke.stapler.DataBoundConstructor; 12 | 13 | public class SubGroupProjectDiscoveryTrait extends SCMNavigatorTrait { 14 | 15 | @DataBoundConstructor 16 | public SubGroupProjectDiscoveryTrait() { 17 | // empty 18 | } 19 | 20 | /** 21 | * {@inheritDoc} 22 | */ 23 | @Override 24 | protected void decorateContext(SCMNavigatorContext context) { 25 | if (context instanceof GitLabSCMNavigatorContext) { 26 | GitLabSCMNavigatorContext ctx = (GitLabSCMNavigatorContext) context; 27 | ctx.wantSubgroupProjects(true); 28 | } 29 | } 30 | 31 | /** 32 | * Our descriptor. 33 | */ 34 | @Symbol("gitLabSubGroupProjectDiscovery") 35 | @Extension 36 | @Discovery 37 | public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | @Override 43 | @NonNull 44 | public String getDisplayName() { 45 | return Messages.SubGroupProjectDiscoveryTrait_displayName(); 46 | } 47 | 48 | @Override 49 | public Class getNavigatorClass() { 50 | return GitLabSCMNavigator.class; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/TagDiscoveryTrait.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import hudson.Extension; 5 | import jenkins.plugins.git.GitTagSCMRevision; 6 | import jenkins.scm.api.SCMHeadCategory; 7 | import jenkins.scm.api.SCMHeadOrigin; 8 | import jenkins.scm.api.SCMSource; 9 | import jenkins.scm.api.trait.SCMHeadAuthority; 10 | import jenkins.scm.api.trait.SCMHeadAuthorityDescriptor; 11 | import jenkins.scm.api.trait.SCMSourceContext; 12 | import jenkins.scm.api.trait.SCMSourceRequest; 13 | import jenkins.scm.api.trait.SCMSourceTrait; 14 | import jenkins.scm.api.trait.SCMSourceTraitDescriptor; 15 | import jenkins.scm.impl.TagSCMHeadCategory; 16 | import jenkins.scm.impl.trait.Discovery; 17 | import org.jenkinsci.Symbol; 18 | import org.kohsuke.stapler.DataBoundConstructor; 19 | 20 | /** 21 | * A {@link Discovery} trait for GitLab that will discover tags on the project. 22 | */ 23 | public class TagDiscoveryTrait extends SCMSourceTrait { 24 | 25 | /** 26 | * Constructor for stapler. 27 | */ 28 | @DataBoundConstructor 29 | public TagDiscoveryTrait() {} 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | @Override 35 | protected void decorateContext(SCMSourceContext context) { 36 | GitLabSCMSourceContext ctx = (GitLabSCMSourceContext) context; 37 | ctx.wantTags(true); 38 | ctx.withAuthority(new TagSCMHeadAuthority()); 39 | } 40 | 41 | /** 42 | * {@inheritDoc} 43 | */ 44 | @Override 45 | public boolean includeCategory(@NonNull SCMHeadCategory category) { 46 | return category instanceof TagSCMHeadCategory; 47 | } 48 | 49 | /** 50 | * Our descriptor. 51 | */ 52 | @Symbol("gitLabTagDiscovery") 53 | @Extension 54 | @Discovery 55 | public static class DescriptorImpl extends SCMSourceTraitDescriptor { 56 | 57 | /** 58 | * {@inheritDoc} 59 | */ 60 | @NonNull 61 | @Override 62 | public String getDisplayName() { 63 | return Messages.TagDiscoveryTrait_displayName(); 64 | } 65 | 66 | /** 67 | * {@inheritDoc} 68 | */ 69 | @Override 70 | public Class getContextClass() { 71 | return GitLabSCMSourceContext.class; 72 | } 73 | 74 | /** 75 | * {@inheritDoc} 76 | */ 77 | @Override 78 | public Class getSourceClass() { 79 | return GitLabSCMSource.class; 80 | } 81 | } 82 | 83 | /** 84 | * Trusts tags from the origin project. 85 | */ 86 | public static class TagSCMHeadAuthority 87 | extends SCMHeadAuthority { 88 | 89 | /** 90 | * {@inheritDoc} 91 | */ 92 | @Override 93 | protected boolean checkTrusted(@NonNull SCMSourceRequest request, @NonNull GitLabTagSCMHead head) { 94 | return true; 95 | } 96 | 97 | /** 98 | * Out descriptor. 99 | */ 100 | @Extension 101 | public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { 102 | 103 | /** 104 | * {@inheritDoc} 105 | */ 106 | @NonNull 107 | @Override 108 | public String getDisplayName() { 109 | return Messages.TagDiscoveryTrait_authorityDisplayName(); 110 | } 111 | 112 | /** 113 | * {@inheritDoc} 114 | */ 115 | @Override 116 | public boolean isApplicableToOrigin(@NonNull Class originClass) { 117 | return SCMHeadOrigin.Default.class.isAssignableFrom(originClass); 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/TriggerMRCommentTrait.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import hudson.Extension; 5 | import jenkins.scm.api.SCMSource; 6 | import jenkins.scm.api.trait.SCMSourceContext; 7 | import jenkins.scm.api.trait.SCMSourceTrait; 8 | import jenkins.scm.api.trait.SCMSourceTraitDescriptor; 9 | import org.jenkinsci.Symbol; 10 | import org.kohsuke.stapler.DataBoundConstructor; 11 | 12 | /** 13 | * Allows a GitLab merge request comment to trigger an immediate build based on a comment string. 14 | */ 15 | public class TriggerMRCommentTrait extends SCMSourceTrait { 16 | 17 | /** 18 | * The comment body to trigger a new build on. 19 | */ 20 | private final String commentBody; 21 | 22 | /** 23 | * Only comment trigger by trusted members only (members having access to project) 24 | */ 25 | private final boolean onlyTrustedMembersCanTrigger; 26 | 27 | /** 28 | * Constructor. 29 | * 30 | * @param commentBody the comment body to trigger a new build on 31 | * @param onlyTrustedMembersCanTrigger if true then only trusted members can trigger the job 32 | */ 33 | @DataBoundConstructor 34 | public TriggerMRCommentTrait(String commentBody, boolean onlyTrustedMembersCanTrigger) { 35 | this.commentBody = commentBody; 36 | this.onlyTrustedMembersCanTrigger = onlyTrustedMembersCanTrigger; 37 | } 38 | 39 | /** 40 | * The comment body to trigger a new build on. 41 | * 42 | * @return the comment body to use 43 | */ 44 | public String getCommentBody() { 45 | if (commentBody == null || commentBody.isEmpty()) { 46 | return "^REBUILD$"; 47 | } 48 | return commentBody; 49 | } 50 | 51 | /** 52 | * Allow trigger a new build by trusted members only. 53 | * 54 | * @return true if allow trusted members only 55 | * @since TODO 56 | */ 57 | public boolean getOnlyTrustedMembersCanTrigger() { 58 | return onlyTrustedMembersCanTrigger; 59 | } 60 | 61 | /** 62 | * {@inheritDoc} 63 | */ 64 | @Override 65 | protected void decorateContext(SCMSourceContext context) { 66 | GitLabSCMSourceContext ctx = (GitLabSCMSourceContext) context; 67 | ctx.withMRCommentTriggerEnabled(true); 68 | ctx.withOnlyTrustedMembersCanTrigger(getOnlyTrustedMembersCanTrigger()); 69 | ctx.withCommentBody(getCommentBody()); 70 | } 71 | 72 | @Extension 73 | @Symbol("mrTriggerComment") 74 | public static class DescriptorImpl extends SCMSourceTraitDescriptor { 75 | 76 | @NonNull 77 | @Override 78 | public String getDisplayName() { 79 | return Messages.TriggerMRCommentTrait_displayName(); 80 | } 81 | 82 | @Override 83 | public Class getContextClass() { 84 | return GitLabSCMSourceContext.class; 85 | } 86 | 87 | @Override 88 | public Class getSourceClass() { 89 | return GitLabSCMSource.class; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/helpers/GitLabAvatar.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource.helpers; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import java.util.Objects; 5 | import jenkins.scm.api.metadata.AvatarMetadataAction; 6 | import org.apache.commons.lang.StringUtils; 7 | 8 | public class GitLabAvatar extends AvatarMetadataAction { 9 | 10 | private final String avatar; 11 | 12 | public GitLabAvatar(String avatar) { 13 | this.avatar = avatar; 14 | } 15 | 16 | @Override 17 | public String getAvatarImageOf(@NonNull String size) { 18 | return StringUtils.isBlank(avatar) ? null : GitLabAvatarCache.buildUrl(avatar, size); 19 | } 20 | 21 | @Override 22 | public boolean equals(Object o) { 23 | if (this == o) { 24 | return true; 25 | } 26 | if (o == null || getClass() != o.getClass()) { 27 | return false; 28 | } 29 | 30 | GitLabAvatar that = (GitLabAvatar) o; 31 | 32 | return Objects.equals(avatar, that.avatar); 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return avatar != null ? avatar.hashCode() : 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/helpers/GitLabBrowser.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource.helpers; 2 | 3 | import static io.jenkins.plugins.gitlabbranchsource.helpers.GitLabHelper.commitUriTemplate; 4 | import static io.jenkins.plugins.gitlabbranchsource.helpers.GitLabHelper.getUriTemplateFromServer; 5 | import static io.jenkins.plugins.gitlabbranchsource.helpers.GitLabHelper.splitPath; 6 | 7 | import com.damnhandy.uri.template.UriTemplateBuilder; 8 | import edu.umd.cs.findbugs.annotations.NonNull; 9 | import hudson.Extension; 10 | import hudson.model.Descriptor; 11 | import hudson.plugins.git.GitChangeSet; 12 | import hudson.plugins.git.browser.GitRepositoryBrowser; 13 | import hudson.scm.EditType; 14 | import hudson.scm.RepositoryBrowser; 15 | import java.io.IOException; 16 | import java.net.URL; 17 | import net.sf.json.JSONObject; 18 | import org.jenkinsci.Symbol; 19 | import org.kohsuke.stapler.DataBoundConstructor; 20 | import org.kohsuke.stapler.StaplerRequest2; 21 | 22 | public class GitLabBrowser extends GitRepositoryBrowser { 23 | 24 | @DataBoundConstructor 25 | public GitLabBrowser(String projectUrl) { 26 | super(projectUrl); 27 | } 28 | 29 | public String getProjectUrl() { 30 | return super.getRepoUrl(); 31 | } 32 | 33 | @Override 34 | public URL getChangeSetLink(GitChangeSet changeSet) throws IOException { 35 | return new URL(commitUriTemplate(getProjectUrl()) 36 | .set("hash", changeSet.getId()) 37 | .expand()); 38 | } 39 | 40 | @Override 41 | public URL getDiffLink(GitChangeSet.Path path) throws IOException { 42 | if (path.getEditType() != EditType.EDIT 43 | || path.getSrc() == null 44 | || path.getDst() == null 45 | || path.getChangeSet().getParentCommit() == null) { 46 | return null; 47 | } 48 | return diffLink(path); 49 | } 50 | 51 | @Override 52 | public URL getFileLink(GitChangeSet.Path path) throws IOException { 53 | if (path.getEditType().equals(EditType.DELETE)) { 54 | return diffLink(path); 55 | } else { 56 | return new URL(getUriTemplateFromServer(getProjectUrl()) 57 | .literal("/blob") 58 | .path(UriTemplateBuilder.var("changeSet")) 59 | .path(UriTemplateBuilder.var("path", true)) 60 | .build() 61 | .set("changeSet", path.getChangeSet().getId()) 62 | .set("path", splitPath(path.getPath())) 63 | .expand()); 64 | } 65 | } 66 | 67 | private URL diffLink(GitChangeSet.Path path) throws IOException { 68 | return new URL(getUriTemplateFromServer(getProjectUrl()) 69 | .literal("/commit") 70 | .path(UriTemplateBuilder.var("changeSet")) 71 | .fragment(UriTemplateBuilder.var("diff")) 72 | .build() 73 | .set("changeSet", path.getChangeSet().getId()) 74 | .set("diff", "#diff-" + getIndexOfPath(path)) 75 | .expand()); 76 | } 77 | 78 | // [JENKINS-72104] notes that the symbol 'gitLabBrowser' is used 79 | // instead of the preferred 'gitLab' symbol in order to not break 80 | // compatibility for existing git plugin users. The git plugin 81 | // already defines a repository browser with the symbol "gitLab". 82 | @Symbol("gitLabBrowser") 83 | @Extension 84 | public static class DescriptorImpl extends Descriptor> { 85 | 86 | @NonNull 87 | public String getDisplayName() { 88 | return "GitLab"; 89 | } 90 | 91 | @Override 92 | public GitLabBrowser newInstance(StaplerRequest2 req, @NonNull JSONObject jsonObject) throws FormException { 93 | return req.bindJSON(GitLabBrowser.class, jsonObject); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/helpers/GitLabGroup.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource.helpers; 2 | 3 | public class GitLabGroup extends GitLabOwner { 4 | 5 | private String fullName; 6 | private String description; 7 | 8 | public GitLabGroup(String name, String webUrl, String avatarUrl, Long id, String fullName, String description) { 9 | super(name, webUrl, avatarUrl, id); 10 | this.fullName = fullName; 11 | this.description = description; 12 | } 13 | 14 | @Override 15 | public String getFullName() { 16 | return fullName; 17 | } 18 | 19 | public void setFullName(String fullName) { 20 | this.fullName = fullName; 21 | } 22 | 23 | @Override 24 | public String getWord() { 25 | if (fullName.indexOf('/') == -1) { 26 | return "Group"; 27 | } 28 | return "Subgroup"; 29 | } 30 | 31 | public String getDescription() { 32 | return description; 33 | } 34 | 35 | public void setDescription(String description) { 36 | this.description = description; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/helpers/GitLabIcons.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource.helpers; 2 | 3 | import static org.jenkins.ui.icon.Icon.ICON_LARGE_STYLE; 4 | import static org.jenkins.ui.icon.Icon.ICON_MEDIUM_STYLE; 5 | import static org.jenkins.ui.icon.Icon.ICON_SMALL_STYLE; 6 | import static org.jenkins.ui.icon.Icon.ICON_XLARGE_STYLE; 7 | import static org.jenkins.ui.icon.IconSet.icons; 8 | 9 | import hudson.init.Initializer; 10 | import java.util.NoSuchElementException; 11 | import jenkins.model.Jenkins; 12 | import org.apache.commons.jelly.JellyContext; 13 | import org.jenkins.ui.icon.Icon; 14 | import org.kohsuke.stapler.Stapler; 15 | 16 | public final class GitLabIcons { 17 | 18 | public static final String ICON_PROJECT = "gitlab-project"; 19 | public static final String ICON_BRANCH = "gitlab-branch"; 20 | public static final String ICON_GITLAB = "gitlab-logo"; 21 | public static final String ICON_COMMIT = "gitlab-commit"; 22 | public static final String ICON_MR = "gitlab-mr"; 23 | public static final String ICON_TAG = "gitlab-tag"; 24 | private static final String ICON_PATH = "plugin/gitlab-branch-source/images/"; 25 | 26 | private GitLabIcons() { 27 | /* no instances allowed */ 28 | } 29 | 30 | @Initializer 31 | public static void initialize() { 32 | addIcon(ICON_GITLAB); 33 | addIcon(ICON_PROJECT); 34 | addIcon(ICON_BRANCH); 35 | addIcon(ICON_COMMIT); 36 | addIcon(ICON_MR); 37 | addIcon(ICON_TAG); 38 | } 39 | 40 | public static String iconFileName(String name, Size size) { 41 | Icon icon = icons.getIconByClassSpec(classSpec(name, size)); 42 | if (icon == null) { 43 | return null; 44 | } 45 | 46 | JellyContext ctx = new JellyContext(); 47 | ctx.setVariable("resURL", Stapler.getCurrentRequest2().getContextPath() + Jenkins.RESOURCE_PATH); 48 | return icon.getQualifiedUrl(ctx); 49 | } 50 | 51 | public static String iconFilePathPattern(String name) { 52 | return ICON_PATH + name + ".svg"; 53 | } 54 | 55 | private static String classSpec(String name, Size size) { 56 | return name + " " + size.className; 57 | } 58 | 59 | private static void addIcon(String name) { 60 | for (Size size : Size.values()) { 61 | icons.addIcon(new Icon(classSpec(name, size), ICON_PATH + "/" + name + ".svg", size.style)); 62 | } 63 | } 64 | 65 | public enum Size { 66 | SMALL("icon-sm", "16x16", ICON_SMALL_STYLE), 67 | MEDIUM("icon-md", "24x24", ICON_MEDIUM_STYLE), 68 | LARGE("icon-lg", "32x32", ICON_LARGE_STYLE), 69 | XLARGE("icon-xlg", "48x48", ICON_XLARGE_STYLE); 70 | 71 | private final String className; 72 | private final String dimensions; 73 | private final String style; 74 | 75 | Size(String className, String dimensions, String style) { 76 | this.className = className; 77 | this.dimensions = dimensions; 78 | this.style = style; 79 | } 80 | 81 | public static Size byDimensions(String dimensions) { 82 | for (Size s : values()) { 83 | if (s.dimensions.equals(dimensions)) { 84 | return s; 85 | } 86 | } 87 | throw new NoSuchElementException("unknown dimensions: " + dimensions); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/helpers/GitLabLink.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource.helpers; 2 | 3 | import static org.apache.commons.lang.StringUtils.defaultIfBlank; 4 | 5 | import edu.umd.cs.findbugs.annotations.NonNull; 6 | import hudson.model.Action; 7 | import jenkins.model.Jenkins; 8 | import org.apache.commons.jelly.JellyContext; 9 | import org.jenkins.ui.icon.Icon; 10 | import org.jenkins.ui.icon.IconSet; 11 | import org.jenkins.ui.icon.IconSpec; 12 | import org.kohsuke.stapler.Stapler; 13 | 14 | /** 15 | * Link to GitLab 16 | */ 17 | public class GitLabLink implements Action, IconSpec { 18 | 19 | /** 20 | * The icon class name to use. 21 | */ 22 | @NonNull 23 | private final String iconClassName; 24 | 25 | /** 26 | * Target of the hyperlink to take the user to. 27 | */ 28 | @NonNull 29 | private final String url; 30 | 31 | private String displayName; 32 | 33 | public GitLabLink(@NonNull String iconClassName, @NonNull String url) { 34 | this.iconClassName = iconClassName; 35 | this.url = url; 36 | this.displayName = ""; 37 | } 38 | 39 | public GitLabLink(@NonNull String iconClassName, @NonNull String url, String displayName) { 40 | this.iconClassName = iconClassName; 41 | this.url = url; 42 | this.displayName = displayName; 43 | } 44 | 45 | public static GitLabLink toGroup(String url) { 46 | return new GitLabLink("gitlab-logo", url, "Group"); 47 | } 48 | 49 | public static GitLabLink toProject(String url) { 50 | return new GitLabLink("gitlab-project", url, "Project"); 51 | } 52 | 53 | public static GitLabLink toBranch(String url) { 54 | return new GitLabLink("gitlab-branch", url, "Branch"); 55 | } 56 | 57 | public static GitLabLink toMergeRequest(String url) { 58 | return new GitLabLink("gitlab-mr", url, "Merge Request"); 59 | } 60 | 61 | public static GitLabLink toTag(String url) { 62 | return new GitLabLink("gitlab-tag", url, "Tag"); 63 | } 64 | 65 | public static GitLabLink toCommit(String url) { 66 | return new GitLabLink("gitlab-commit", url, "Commit"); 67 | } 68 | 69 | @NonNull 70 | public String getUrl() { 71 | return url; 72 | } 73 | 74 | @NonNull 75 | @Override 76 | public String getIconClassName() { 77 | return iconClassName; 78 | } 79 | 80 | @Override 81 | public String getIconFileName() { 82 | String iconClassName = getIconClassName(); 83 | Icon icon = IconSet.icons.getIconByClassSpec(iconClassName + " icon-md"); 84 | if (icon != null) { 85 | JellyContext ctx = new JellyContext(); 86 | ctx.setVariable("resURL", Stapler.getCurrentRequest2().getContextPath() + Jenkins.RESOURCE_PATH); 87 | return icon.getQualifiedUrl(ctx); 88 | } 89 | return null; 90 | } 91 | 92 | @Override 93 | public String getDisplayName() { 94 | return defaultIfBlank(displayName, "GitLab"); 95 | } 96 | 97 | public void setDisplayName(@NonNull String displayName) { 98 | this.displayName = displayName; 99 | } 100 | 101 | @Override 102 | public String getUrlName() { 103 | return url; 104 | } 105 | 106 | @Override 107 | public int hashCode() { 108 | int result = iconClassName.hashCode(); 109 | result = 31 * result + url.hashCode(); 110 | return result; 111 | } 112 | 113 | @Override 114 | public boolean equals(Object o) { 115 | if (this == o) { 116 | return true; 117 | } 118 | if (o == null || getClass() != o.getClass()) { 119 | return false; 120 | } 121 | 122 | GitLabLink that = (GitLabLink) o; 123 | 124 | if (!iconClassName.equals(that.iconClassName)) { 125 | return false; 126 | } 127 | return url.equals(that.url); 128 | } 129 | 130 | @Override 131 | public String toString() { 132 | return "GitLabLink{" + "iconClassName='" + iconClassName + '\'' + ", url='" + url + '\'' + '}'; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/helpers/GitLabOwner.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource.helpers; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import org.gitlab4j.api.GitLabApi; 5 | import org.gitlab4j.api.GitLabApiException; 6 | import org.gitlab4j.api.models.Group; 7 | import org.gitlab4j.api.models.User; 8 | 9 | public abstract class GitLabOwner { 10 | 11 | private String name; 12 | private String webUrl; 13 | private String avatarUrl; 14 | private Long id; 15 | 16 | public GitLabOwner(String name, String webUrl, String avatarUrl, Long id) { 17 | this.name = name; 18 | this.webUrl = webUrl; 19 | this.avatarUrl = avatarUrl; 20 | this.id = id; 21 | } 22 | 23 | @NonNull 24 | public static GitLabOwner fetchOwner(GitLabApi gitLabApi, String projectOwner) { 25 | try { 26 | Group group = gitLabApi.getGroupApi().getGroup(projectOwner); 27 | return new GitLabGroup( 28 | group.getName(), 29 | group.getWebUrl(), 30 | group.getAvatarUrl(), 31 | group.getId(), 32 | group.getFullName(), 33 | group.getDescription()); 34 | } catch (GitLabApiException e) { 35 | if (e.getHttpStatus() != 404) { 36 | throw new IllegalStateException("Unable to fetch Group", e); 37 | } 38 | 39 | try { 40 | User user = gitLabApi.getUserApi().getUser(projectOwner); 41 | // If user is not found, null is returned 42 | if (user == null) { 43 | throw new IllegalStateException( 44 | String.format("Owner '%s' is neither a user/group/subgroup", projectOwner)); 45 | } 46 | return new GitLabUser(user.getName(), user.getWebUrl(), user.getAvatarUrl(), user.getId()); 47 | } catch (GitLabApiException e1) { 48 | throw new IllegalStateException("Unable to fetch User", e1); 49 | } 50 | } 51 | } 52 | 53 | public String getName() { 54 | return name; 55 | } 56 | 57 | public void setName(String name) { 58 | this.name = name; 59 | } 60 | 61 | public String getFullName() { 62 | return name; 63 | } 64 | 65 | public String getWebUrl() { 66 | return webUrl; 67 | } 68 | 69 | public void setWebUrl(String webUrl) { 70 | this.webUrl = webUrl; 71 | } 72 | 73 | public String getAvatarUrl() { 74 | return avatarUrl; 75 | } 76 | 77 | public void setAvatarUrl(String avatarUrl) { 78 | this.avatarUrl = avatarUrl; 79 | } 80 | 81 | public Long getId() { 82 | return id; 83 | } 84 | 85 | public void setId(Long id) { 86 | this.id = id; 87 | } 88 | 89 | public abstract String getWord(); 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/helpers/GitLabUser.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource.helpers; 2 | 3 | public class GitLabUser extends GitLabOwner { 4 | 5 | public GitLabUser(String name, String webUrl, String avatarUrl, Long id) { 6 | super(name, webUrl, avatarUrl, id); 7 | } 8 | 9 | @Override 10 | public String getWord() { 11 | return "User"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabbranchsource/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | *

Jenkins SCM API implementation for GitLab

. 3 | * 4 | * The primary entry points for the implementation are: 5 | * 6 | *
    7 | *
  • {@link io.jenkins.plugins.gitlabbranchsource.GitLabSCMSource} - the {@link jenkins.scm.api.SCMSource} implementation
  • 8 | *
  • {@link io.jenkins.plugins.gitlabbranchsource.GitLabSCMNavigator} - the {@link jenkins.scm.api.SCMNavigator} 9 | * implementation
  • 10 | *
11 | * 12 | * These implementations are {@link jenkins.scm.api.trait.SCMTrait} based and accept the traits for 13 | * {@link jenkins.plugins.git.AbstractGitSCMSource} as well as the GitLab specific traits: 14 | *
    15 | *
  • {@link io.jenkins.plugins.gitlabbranchsource.BranchDiscoveryTrait}
  • 16 | *
  • {@link io.jenkins.plugins.gitlabbranchsource.ForkMergeRequestDiscoveryTrait}
  • 17 | *
  • {@link io.jenkins.plugins.gitlabbranchsource.OriginMergeRequestDiscoveryTrait}
  • 18 | *
  • {@link io.jenkins.plugins.gitlabbranchsource.SSHCheckoutTrait}
  • 19 | *
  • {@link io.jenkins.plugins.gitlabbranchsource.HookRegistrationTrait}
  • 20 | *
21 | * 22 | * Extension plugins wanting to add GitLab-specific traits should target at least one of: 23 | *
    24 | *
  • {@link io.jenkins.plugins.gitlabbranchsource.GitLabSCMNavigatorContext} for 25 | * {@linkplain jenkins.scm.api.trait.SCMNavigatorTrait}s
  • 26 | *
  • {@link io.jenkins.plugins.gitlabbranchsource.GitLabSCMNavigatorRequest} for 27 | * {@linkplain jenkins.scm.api.trait.SCMNavigatorTrait}s
  • 28 | *
  • {@link io.jenkins.plugins.gitlabbranchsource.GitLabSCMSourceBuilder} for 29 | * {@linkplain jenkins.scm.api.trait.SCMNavigatorTrait}s
  • 30 | *
  • {@link io.jenkins.plugins.gitlabbranchsource.GitLabSCMSourceContext} for 31 | * {@linkplain jenkins.scm.api.trait.SCMSourceTrait}s
  • 32 | *
  • {@link io.jenkins.plugins.gitlabbranchsource.GitLabSCMSourceRequest} for 33 | * {@linkplain jenkins.scm.api.trait.SCMSourceTrait}s
  • 34 | *
  • {@link io.jenkins.plugins.gitlabbranchsource.GitLabSCMBuilder} for 35 | * {@linkplain jenkins.scm.api.trait.SCMSourceTrait}s
  • 36 | *
37 | */ 38 | package io.jenkins.plugins.gitlabbranchsource; 39 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabserverconfig/action/GitlabAction.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabserverconfig.action; 2 | 3 | import edu.umd.cs.findbugs.annotations.CheckForNull; 4 | import hudson.Extension; 5 | import hudson.model.RootAction; 6 | import hudson.util.HttpResponses; 7 | import io.jenkins.plugins.gitlabbranchsource.helpers.GitLabHelper; 8 | import io.jenkins.plugins.gitlabserverconfig.servers.GitLabServers; 9 | import java.util.logging.Level; 10 | import java.util.logging.Logger; 11 | import jenkins.model.Jenkins; 12 | import jenkins.scm.api.SCMSourceOwner; 13 | import net.sf.json.JSONArray; 14 | import net.sf.json.JSONObject; 15 | import org.apache.commons.lang.StringUtils; 16 | import org.gitlab4j.api.GitLabApi; 17 | import org.gitlab4j.api.GitLabApiException; 18 | import org.gitlab4j.api.models.Project; 19 | import org.gitlab4j.api.models.ProjectFilter; 20 | import org.kohsuke.accmod.Restricted; 21 | import org.kohsuke.accmod.restrictions.NoExternalUse; 22 | import org.kohsuke.stapler.AncestorInPath; 23 | import org.kohsuke.stapler.HttpResponse; 24 | import org.kohsuke.stapler.QueryParameter; 25 | import org.kohsuke.stapler.interceptor.RequirePOST; 26 | 27 | /** 28 | * Provide an API for Jenkins integration purpose 29 | */ 30 | @Extension 31 | @Restricted(NoExternalUse.class) 32 | public class GitlabAction implements RootAction { 33 | public static final Logger LOGGER = Logger.getLogger(GitlabAction.class.getName()); 34 | 35 | @RequirePOST 36 | public HttpResponse doServerList() { 37 | if (!Jenkins.get().hasPermission(Jenkins.MANAGE)) { 38 | return HttpResponses.errorJSON("no permission to get Gitlab server list"); 39 | } 40 | 41 | JSONArray servers = new JSONArray(); 42 | GitLabServers.get().getServers().forEach(server -> { 43 | JSONObject serverObj = new JSONObject(); 44 | serverObj.put("name", server.getName()); 45 | serverObj.put("url", server.getServerUrl()); 46 | servers.add(serverObj); 47 | }); 48 | 49 | return HttpResponses.okJSON(servers); 50 | } 51 | 52 | @RequirePOST 53 | public HttpResponse doProjectList( 54 | @AncestorInPath SCMSourceOwner context, @QueryParameter String server, @QueryParameter String owner) { 55 | if (!Jenkins.get().hasPermission(Jenkins.MANAGE)) { 56 | return HttpResponses.errorJSON("no permission to get Gitlab server list"); 57 | } 58 | 59 | if (StringUtils.isEmpty(server) || StringUtils.isEmpty(owner)) { 60 | return HttpResponses.errorJSON("server or owner is empty"); 61 | } 62 | 63 | JSONArray servers = new JSONArray(); 64 | 65 | GitLabApi gitLabApi = GitLabHelper.apiBuilder(context, server); 66 | try { 67 | for (Project project : 68 | gitLabApi.getProjectApi().getUserProjects(owner, new ProjectFilter().withOwned(true))) { 69 | servers.add(project.getPathWithNamespace()); 70 | } 71 | } catch (GitLabApiException e) { 72 | LOGGER.log(Level.FINE, String.format("errors when get projects from %s/%s as a user", server, owner), e); 73 | } 74 | 75 | try { 76 | for (Project project : gitLabApi.getGroupApi().getProjects(owner)) { 77 | servers.add(project.getPathWithNamespace()); 78 | } 79 | } catch (GitLabApiException e) { 80 | LOGGER.log(Level.FINE, String.format("errors when get projects from %s/%s as a group", server, owner), e); 81 | } 82 | 83 | return HttpResponses.okJSON(servers); 84 | } 85 | 86 | @CheckForNull 87 | @Override 88 | public String getIconFileName() { 89 | return null; 90 | } 91 | 92 | @CheckForNull 93 | @Override 94 | public String getDisplayName() { 95 | return null; 96 | } 97 | 98 | @CheckForNull 99 | @Override 100 | public String getUrlName() { 101 | return "/gitlab"; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabserverconfig/credentials/PersonalAccessToken.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabserverconfig.credentials; 2 | 3 | import com.cloudbees.plugins.credentials.common.StandardCredentials; 4 | import edu.umd.cs.findbugs.annotations.NonNull; 5 | import hudson.util.Secret; 6 | 7 | public interface PersonalAccessToken extends StandardCredentials { 8 | 9 | /** 10 | * Returns the token. 11 | * 12 | * @return the token. 13 | */ 14 | @NonNull 15 | Secret getToken(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabserverconfig/credentials/PersonalAccessTokenImpl.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabserverconfig.credentials; 2 | 3 | import com.cloudbees.plugins.credentials.CredentialsDescriptor; 4 | import com.cloudbees.plugins.credentials.CredentialsProvider; 5 | import com.cloudbees.plugins.credentials.CredentialsScope; 6 | import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials; 7 | import edu.umd.cs.findbugs.annotations.CheckForNull; 8 | import edu.umd.cs.findbugs.annotations.NonNull; 9 | import hudson.Extension; 10 | import hudson.util.FormValidation; 11 | import hudson.util.Secret; 12 | import jenkins.model.Jenkins; 13 | import org.apache.commons.lang.StringUtils; 14 | import org.jenkinsci.Symbol; 15 | import org.kohsuke.accmod.Restricted; 16 | import org.kohsuke.accmod.restrictions.NoExternalUse; 17 | import org.kohsuke.stapler.DataBoundConstructor; 18 | import org.kohsuke.stapler.QueryParameter; 19 | 20 | /** 21 | * Default implementation of {@link PersonalAccessToken} for use by {@link Jenkins} {@link 22 | * CredentialsProvider} instances that store {@link Secret} locally. 23 | */ 24 | public class PersonalAccessTokenImpl extends BaseStandardCredentials implements PersonalAccessToken { 25 | 26 | /** 27 | * Our token. 28 | */ 29 | @NonNull 30 | private final Secret token; 31 | 32 | /** 33 | * Constructor. 34 | * 35 | * @param scope the credentials scope. 36 | * @param id the credentials id. 37 | * @param description the description of the token. 38 | * @param token the token itself (will be passed through {@link Secret#fromString(String)}) 39 | */ 40 | @DataBoundConstructor 41 | public PersonalAccessTokenImpl( 42 | @CheckForNull CredentialsScope scope, 43 | @CheckForNull String id, 44 | @CheckForNull String description, 45 | @NonNull String token) { 46 | super(scope, id, description); 47 | this.token = Secret.fromString(token); 48 | } 49 | 50 | /** 51 | * {@inheritDoc} 52 | */ 53 | @Override 54 | @NonNull 55 | public Secret getToken() { 56 | return token; 57 | } 58 | 59 | /** 60 | * Our descriptor. 61 | */ 62 | @Extension 63 | @Symbol("gitlabPersonalAccessToken") 64 | public static class DescriptorImpl extends CredentialsDescriptor { 65 | 66 | private static final int GITLAB_ACCESS_TOKEN_MINIMAL_LENGTH = 20; 67 | 68 | /** 69 | * {@inheritDoc} 70 | */ 71 | @Override 72 | @NonNull 73 | public String getDisplayName() { 74 | return Messages.PersonalAccessTokenImpl_displayName(); 75 | } 76 | 77 | /** 78 | * Sanity check for a Gitlab access token. 79 | * 80 | * @param value the personal access token. 81 | * @return the results of the sanity check. 82 | */ 83 | @Restricted(NoExternalUse.class) // stapler 84 | @SuppressWarnings("unused") 85 | public FormValidation doCheckToken(@QueryParameter String value) { 86 | Secret secret = Secret.fromString(value); 87 | if (StringUtils.equals(value, secret.getPlainText())) { 88 | if (value.length() < GITLAB_ACCESS_TOKEN_MINIMAL_LENGTH) { 89 | return FormValidation.error(Messages.PersonalAccessTokenImpl_tokenWrongLength()); 90 | } 91 | } else if (secret.getPlainText().length() < GITLAB_ACCESS_TOKEN_MINIMAL_LENGTH) { 92 | return FormValidation.error(Messages.PersonalAccessTokenImpl_tokenWrongLength()); 93 | } 94 | return FormValidation.ok(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabserverconfig/credentials/helpers/GitLabCredentialMatcher.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabserverconfig.credentials.helpers; 2 | 3 | import com.cloudbees.plugins.credentials.Credentials; 4 | import com.cloudbees.plugins.credentials.CredentialsMatcher; 5 | import edu.umd.cs.findbugs.annotations.NonNull; 6 | import io.jenkins.plugins.gitlabserverconfig.credentials.PersonalAccessToken; 7 | import org.jenkinsci.plugins.plaincredentials.StringCredentials; 8 | 9 | public class GitLabCredentialMatcher implements CredentialsMatcher { 10 | 11 | private static final long serialVersionUID = 1; 12 | 13 | @Override 14 | public boolean matches(@NonNull Credentials credentials) { 15 | try { 16 | return credentials instanceof PersonalAccessToken || credentials instanceof StringCredentials; 17 | } catch (Exception e) { 18 | return false; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabserverconfig/credentials/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * GitLab specific {@link com.cloudbees.plugins.credentials.Credentials} interface and default 3 | * implementation. Note we use an interface so that {@link com.cloudbees.plugins.credentials.CredentialsProvider} 4 | * implementations that store credentials external from {@link jenkins.model.Jenkins} can use {@link 5 | * java.lang.reflect.Proxy} to lazily instantiate {@link hudson.util.Secret} properties on access. 6 | */ 7 | package io.jenkins.plugins.gitlabserverconfig.credentials; 8 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/gitlabserverconfig/servers/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Jenkins Global Configuration UI for managing the list of GitLab servers. 3 | */ 4 | package io.jenkins.plugins.gitlabserverconfig.servers; 5 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | Provides branch source and folder organization functionality for GitLab Repositories in Jenkins 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/BranchDiscoveryTrait/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/BranchDiscoveryTrait/help-branchesAlwaysIncludedRegex.html: -------------------------------------------------------------------------------- 1 |
2 | Regular expression of branches that should always be included regardless of whether a merge request exists or not for those branches. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/BranchDiscoveryTrait/help-strategyId.html: -------------------------------------------------------------------------------- 1 |
2 | Determines which branches are discovered. 3 |
4 |
Only branches that are not also filed as MRs
5 |
6 | If you are discovering origin merge requests, it may not make sense to discover the same 7 | changes both as a 8 | merge request and as a branch. 9 |
10 |
Only branches that are also filed as MRs
11 |
12 | This option exists to preserve legacy behaviour when upgrading from older versions of the 13 | plugin. 14 | NOTE: If you have an actual use case for this option please file a merge request against this 15 | text. 16 |
17 |
All branches
18 |
19 | Ignores whether the branch is also filed as a merge request and instead discovers all branches 20 | on the 21 | origin project. 22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/BranchDiscoveryTrait/help.html: -------------------------------------------------------------------------------- 1 |
2 | Discovers branches on the repository. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/BuildStatusNameCustomPartTrait/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/BuildStatusNameCustomPartTrait/help-buildStatusNameCustomPart.html: -------------------------------------------------------------------------------- 1 |
2 | Enter a string to customize the status/context name for status updates published to GitLab. 3 | For a branch build the default name would be 'jenkinsci/branch'. With the buildStatusNameCustomPart 4 | 'custom' the name would be 'jenkinsci/custom/branch'. 5 | This allows to have multiple GitLab-Branch-Sources for the same GitLab-project configured. 6 |
7 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/BuildStatusNameCustomPartTrait/help-buildStatusNameOverwrite.html: -------------------------------------------------------------------------------- 1 |
2 | Overwrites the build status name including the jenkinsci default part.
3 | Instead of 'jenkinsci/custom/branch' just 'custom/branch'. 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/BuildStatusNameCustomPartTrait/help.html: -------------------------------------------------------------------------------- 1 |
2 | Customize the pipeline status name used by Jenkins 3 |
-------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ExcludeArchivedRepositoriesTrait/help.html: -------------------------------------------------------------------------------- 1 |
2 | Exclude GitLab repositories that have been archived. If set, no jobs will be created for archived repositories. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ForkMergeRequestDiscoveryTrait/TrustEveryone/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ${%blurb} 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ForkMergeRequestDiscoveryTrait/TrustEveryone/config.properties: -------------------------------------------------------------------------------- 1 | blurb=
This option is generally insecure. See help button.
2 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ForkMergeRequestDiscoveryTrait/TrustMembers/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ${%blurb} 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ForkMergeRequestDiscoveryTrait/TrustMembers/config.properties: -------------------------------------------------------------------------------- 1 | blurb=
This option may be insecure in some environments. See help button.
2 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ForkMergeRequestDiscoveryTrait/TrustPermission/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ${%blurb} 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ForkMergeRequestDiscoveryTrait/TrustPermission/config.properties: -------------------------------------------------------------------------------- 1 | blurb=Trusted Members with Developer / Maintainer / Owner access level 2 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ForkMergeRequestDiscoveryTrait/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ForkMergeRequestDiscoveryTrait/help-buildMRForksNotMirror.html: -------------------------------------------------------------------------------- 1 |
2 | Add discovery of merge requests where the origin project is a fork of a certain project, 3 | but the target project is not the original forked project. 4 | To be used in case one has a GitLab project which is a fork of another project from another team, in order to isolate artefacts and allow an MR flow. 5 | This means using MRs inside that fork from branches in the fork back to the fork's default branch. 6 | (Implements https://github.com/jenkinsci/gitlab-branch-source-plugin/issues/167) 7 |
-------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ForkMergeRequestDiscoveryTrait/help-strategyId.html: -------------------------------------------------------------------------------- 1 |
2 | Determines how merge requests are discovered: 3 |
4 |
Merging the merge request with the current target branch revision
5 |
Discover each merge request once with the discovered revision corresponding to the result of 6 | merging with the 7 | current revision of the target branch 8 |
9 |
The current merge request revision
10 |
Discover each merge request once with the discovered revision corresponding to the merge 11 | request head revision 12 | without merging 13 |
14 |
Both the current merge request revision and the merge request merged 15 | with the current target branch revision
16 |
Discover each merge request twice. The first discovered revision corresponds to the result 17 | of merging with 18 | the current revision of the target branch in each scan. The second parallel discovered 19 | revision corresponds 20 | to the merge request head revision without merging 21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ForkMergeRequestDiscoveryTrait/help-trust.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | One of the great powers of merge requests is that anyone with read access to a project can fork 4 | it, commit 5 | some changes to their fork and then create a merge request against the original project with 6 | their changes. 7 | There are some files stored in source control that are important. For example, a Jenkinsfile 8 | may contain configuration details to sandbox merge requests in order to mitigate against 9 | malicious merge requests. 10 | In order to protect against a malicious merge request itself modifying the 11 | Jenkinsfile to remove 12 | the protections, you can define the trust policy for merge requests from forks. 13 |

14 |

15 | Other plugins can extend the available trust policies. The default policies are: 16 |

17 |
18 |
Nobody
19 |
20 | Merge requests from forks will all be treated as untrusted. This means that where Jenkins 21 | requires a 22 | trusted file (e.g. Jenkinsfile) the contents of that file will be retrieved from 23 | the 24 | target branch on the origin project and not from the merge request branch on the fork project. 25 |
26 |
Members
27 |
28 | Merge requests from collaborators to the origin project will be treated as trusted, all other 29 | merge 30 | requests from fork repositories will be treated as untrusted. 31 | Note that if credentials used by Jenkins for scanning the project does not have permission to 32 | query the list of contributors to the origin project then only the origin account will be 33 | treated 34 | as trusted - i.e. this will fall back to Nobody. 35 |
36 |
Trusted Members
37 |
38 | Merge requests forks will be treated as trusted if and only if the fork owner has either 39 | Developer or 40 | Maintainer or Owner Access Level in the origin project. 41 | This is the recommended policy. 42 |
43 |
Everyone
44 |
45 | All merge requests from forks will be treated as trusted. NOTE: this option 46 | can be dangerous 47 | if used on a public project hosted on a GitLab instance. 48 |
49 |
50 |
51 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ForkMergeRequestDiscoveryTrait/help.html: -------------------------------------------------------------------------------- 1 |
2 | Discovers merge requests where the origin project is a fork of the target project. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/GitLabAvatarTrait/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/GitLabAvatarTrait/help-disableProjectAvatar.html: -------------------------------------------------------------------------------- 1 |
2 | Due to a GitLab bug, sometimes it is not possible to GitLab API to fetch GitLab Avatar for private projects 3 | or when the api doesn't have token access. You may choose to skip avatar for projects if you want to avoid broken 4 | or self generated avatars. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/GitLabMarkUnstableAsSuccessTrait/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/GitLabSCMNavigator/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/GitLabSCMNavigator/help-credentialsId.html: -------------------------------------------------------------------------------- 1 |
2 | Checkout credentials is only needed for private projects. Add `SSHPrivateKey` or 3 | `Username/Password` to checkout over 4 | SSH remote or HTTPS remote respectively. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/GitLabSCMNavigator/help-projectOwner.html: -------------------------------------------------------------------------------- 1 |
2 | Specify the namespace which owns your projects. It can be a user, group or subgroup (with full 3 | path). E.g: 4 | 5 | If you want projects from a subgroup `dummysubgroup` inside a group `dummygroup`, then `Owner` 6 | should be dummygroup/dummysubgroup` 7 |
8 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/GitLabSCMNavigator/help-serverName.html: -------------------------------------------------------------------------------- 1 |
2 | Select the GitLab Server where you want the projects to be discovered from. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/GitLabSCMSource/config-detail.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/GitLabSCMSource/help-credentialsId.html: -------------------------------------------------------------------------------- 1 |
2 | Checkout credentials is only needed for private projects. Add `SSHPrivateKey` or 3 | `Username/Password` to checkout over 4 | SSH remote or HTTPS remote respectively. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/GitLabSCMSource/help-projectOwner.html: -------------------------------------------------------------------------------- 1 |
2 | Specify the namespace which owns your projects. It can be a user, a group or a subgroup with full 3 | path. E.g: 4 | 5 | If you want projects from subgroup `a` inside group `b`, then `Owner` should be 6 | b/a 7 |
8 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/GitLabSCMSource/help-projectPath.html: -------------------------------------------------------------------------------- 1 |
2 | Select the project on which you want to perform the Multibranch Pipeline Job. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/GitLabSCMSource/help-serverName.html: -------------------------------------------------------------------------------- 1 |
2 | Select the GitLab Server where you want the projects to be discovered from. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/HookRegistrationTrait/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/LogCommentTrait/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/LogCommentTrait/help-logSuccess.html: -------------------------------------------------------------------------------- 1 |
2 | Sometimes the user doesn't want to log the builds that succeeded. The trait only enable logging of 3 | failed/aborted 4 | builds by default. Select this option to include logging of successful builds as well. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/LogCommentTrait/help-sudoUser.html: -------------------------------------------------------------------------------- 1 |
2 | Enter a sudo username of the user you want to comment as on GitLab Server. Remember the token 3 | specified 4 | should have api and sudo access both (which can only be created by your GitLab Server Admin). It 5 | is 6 | recommended to create a dummy user in your GitLab Server with an appropriate username like 7 | `jenkinsadmin` etc. Leave empty if you want use the owner of the project as the commenter. 8 |
9 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/Messages.properties: -------------------------------------------------------------------------------- 1 | BranchSCMHead.Pronoun=Branch 2 | BranchDiscoveryTrait.allBranches=All branches 3 | BranchDiscoveryTrait.authorityDisplayName=Trust origin branches 4 | BranchDiscoveryTrait.displayName=Discover branches 5 | BranchDiscoveryTrait.excludeMRs=Only branches that are not also filed as MRs 6 | BranchDiscoveryTrait.onlyMRs=Only branches that are also filed as MRs 7 | ExcludeArchivedRepositoriesTrait.displayName=Exclude archived repositories 8 | BuildStatusNameCustomPartTrait.displayName=Customize GitLab build status name 9 | ForkMergeRequestDiscoveryTrait.displayName=Discover merge requests from forks 10 | ForkMergeRequestDiscoveryTrait.headAndMerge=Both the current merge request revision and the merge request merged with \ 11 | the current target branch revision 12 | ForkMergeRequestDiscoveryTrait.headOnly=The current merge request revision 13 | ForkMergeRequestDiscoveryTrait.mergeOnly=Merging the merge request with the current target branch revision 14 | ForkMergeRequestDiscoveryTrait.contributorsDisplayName=Members 15 | ForkMergeRequestDiscoveryTrait.everyoneDisplayName=Everyone 16 | ForkMergeRequestDiscoveryTrait.nobodyDisplayName=Nobody 17 | ForkMergeRequestDiscoveryTrait.permissionsDisplayName=Trusted Members 18 | GitLabTagSCMHead.Pronoun=Tag 19 | ProjectNamingStrategyTrait.displayName=Project Naming Strategy 20 | ProjectNamingStrategyTrait.fullProjectPath=Full Project Path 21 | ProjectNamingStrategyTrait.contextualProjectPath=Contextual Project Path 22 | ProjectNamingStrategyTrait.simpleProjectPath=Simple Project Path 23 | ProjectNamingStrategyTrait.projectName=Project Name 24 | SubGroupProjectDiscoveryTrait.displayName=Discover subgroup projects 25 | SharedProjectsDiscoveryTrait.displayName=Discover shared projects 26 | TagDiscoveryTrait.authorityDisplayName=Trust origin tags 27 | TagDiscoveryTrait.displayName=Discover tags 28 | GitLabSCMSource.TagCategory=Tags 29 | GitLabBrowser.displayName=GitLab 30 | GitLabLink.displayName=GitLab 31 | GitLabSCMNavigator.description=Scans a GitLab Group (or user account) for all projects matching some defined \ 32 | markers. 33 | GitLabSCMNavigator.displayName=GitLab Group 34 | GitLabSCMNavigator.pronoun=GitLab Group 35 | GitLabSCMNavigator.selectedCredentialsMissing=Cannot find currently selected credentials 36 | GitLabSCMNavigator.traitSection_additional=Additional 37 | GitLabSCMNavigator.traitSection_projects=Projects 38 | GitLabSCMNavigator.traitSection_withinRepo=Within project 39 | GitLabSCMSource.ChangeRequestCategory=Merge Requests 40 | GitLabSCMSource.DisplayName=GitLab Project 41 | GitLabSCMSource.Pronoun=GitLab Project 42 | GitLabSCMSource.selectedCredentialsMissing=Cannot find currently selected credentials 43 | GitLabSCMSource.traitSection_additional=Additional 44 | GitLabSCMSource.traitSection_withinRepo=Within project 45 | GitLabSCMSource.UncategorizedCategory=Branches 46 | OriginMergeRequestDiscoveryTrait.authorityDisplayName=Trust origin merge requests 47 | MergeRequestSCMHead.Pronoun=Merge Request 48 | SSHCheckoutTrait.displayName=Checkout over SSH 49 | SSHCheckoutTrait.incompatibleCredentials=The currently configured credentials are incompatible with this behaviour 50 | SSHCheckoutTrait.missingCredentials=The currently configured credentials cannot be found 51 | SSHCheckoutTrait.useAgentKey=- use build agent''s key - 52 | HookRegistrationTrait.disable=Disable {0} management 53 | HookRegistrationTrait.displayName=Override GitLab hook management modes 54 | HookRegistrationTrait.useItem=Use Item credentials for {0} management 55 | HookRegistrationTrait.useSystem=Use System credentials for {0} management mode (default) 56 | GitLabSkipNotificationsTrait.displayName=Skip pipeline status notifications 57 | GitLabAvatarTrait.displayName=Disable GitLab project avatars 58 | LogCommentTrait.displayName=Log build status as comment on GitLab 59 | TriggerMRCommentTrait.displayName=Trigger build on merge request comment 60 | GitLabWebHookCause.ShortDescription.Push_noUser=Started by GitLab push 61 | GitLabWebHookCause.ShortDescription.Push=Started by GitLab push by {0} 62 | GitLabWebHookCause.ShortDescription.MergeRequestHook=Triggered by GitLab Merge Request #{0}: {1} => {2} 63 | WebhookListenerBuildConditionsTrait.displayName=Webhook Listener Conditions 64 | GitLabMarkUnstableAsSuccessTrait.displayName=Mark unstable build as successful on Gitlab 65 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/OriginMergeRequestDiscoveryTrait/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/OriginMergeRequestDiscoveryTrait/help-strategyId.html: -------------------------------------------------------------------------------- 1 |
2 | Determines how merge requests are discovered: 3 |
4 |
Merging the merge request with the current target branch revision
5 |
Discover each merge request once with the discovered revision corresponding to the result of 6 | merging with the 7 | current revision of the target branch 8 |
9 |
The current merge request revision
10 |
Discover each merge request once with the discovered revision corresponding to the merge 11 | request head revision 12 | without merging 13 |
14 |
Both the current merge request revision and the merge request merged 15 | with the current target branch revision
16 |
Discover each merge request twice. The first discovered revision corresponds to the result 17 | of merging with 18 | the current revision of the target branch in each scan. The second parallel discovered 19 | revision corresponds 20 | to the merge request head revision without merging 21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/OriginMergeRequestDiscoveryTrait/help.html: -------------------------------------------------------------------------------- 1 |
2 | Discovers merge requests where the origin project is the same as the target project. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ProjectNamingStrategyTrait/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ProjectNamingStrategyTrait/help-strategyId.html: -------------------------------------------------------------------------------- 1 |
2 | Determines the naming convention of projects 3 |
4 |
Full Project Path
5 |
6 | This option will use the full project path for the group and project. 7 | Examples: 8 |
    9 |
  • my-group/my-project
  • 10 |
  • my-group/other-project
  • 11 |
  • my-group/my-subgroup/third-project
  • 12 |
13 |
14 |
Project Name
15 |
16 | This option will use project names from GitLab project name. 17 | This is the "friendly" name of the project and could contain capitalization, spaces or other special characters. 18 | Examples: 19 |
    20 |
  • My Project
  • 21 |
  • Other project
  • 22 |
  • My Subgroup / Third project
  • 23 |
24 |
25 |
Contextual Project Path
26 |
27 | This option will use the project path with nested groups, but not the top-level owner segment specified on the Jenkins pipeline. 28 | Examples where owner on pipeline has been set to "my-group": 29 |
    30 |
  • my-project
  • 31 |
  • other-project
  • 32 |
  • my-subgroup/third-project
  • 33 |
34 |
35 |
Simple Project Path
36 |
37 | This option will use the project path with no parent group information added. 38 | Examples: 39 |
    40 |
  • my-project
  • 41 |
  • other-project
  • 42 |
  • third-project
  • 43 |
44 |
45 |
46 |
47 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/ProjectNamingStrategyTrait/help.html: -------------------------------------------------------------------------------- 1 |
2 | Naming convention of projects 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/SSHCheckoutTrait/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/SSHCheckoutTrait/help-credentialsId.html: -------------------------------------------------------------------------------- 1 |
2 | Credentials used to check out sources. Must be a SSH key based credential. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/SSHCheckoutTrait/help.html: -------------------------------------------------------------------------------- 1 |
2 | By default the discovered branches / merge requests will all use the same username / password 3 | credentials 4 | that were used for discovery when checking out sources. This means that the checkout will be using 5 | the 6 | https:// protocol for the Git repository. 7 |

8 | This behaviour allows you to select the SSH private key to be used for checking out sources, 9 | which will 10 | consequently force the checkout to use the ssh:// protocol. 11 |

12 |
13 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/SharedProjectsDiscoveryTrait/help.html: -------------------------------------------------------------------------------- 1 |

2 | Discovers all shared projects for an owner (Group/Subgroup) but not User. 3 |

4 |

5 | NOTE this trait used to be enabled by default, 6 | but it is potentially dangerous to allow repositories that are controlled by another group 7 | to execute on the Jenkins controller/folder. 8 | So the default behavior was changed and this trait is now optional. 9 |

10 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/SubGroupProjectDiscoveryTrait/help.html: -------------------------------------------------------------------------------- 1 |
2 | Discovers all subgroup projects for an owner (Group/Subgroup) but not User. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/TriggerMRCommentTrait/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/TriggerMRCommentTrait/help-commentBody.html: -------------------------------------------------------------------------------- 1 |
2 | Add comment body you want to use to instruct Jenkins CI to rebuild the MR 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/TriggerMRCommentTrait/help-onlyTrustedMembers.html: -------------------------------------------------------------------------------- 1 |
2 | If checked only trusted members of project can trigger MR. Trusted members mean members with Developer/Maintainer access (also includes inherited from ancestor groups) in the project. 3 | You may want to disable this option because trusted members do not include members inherited from shared group (there is no way to get it from GitLabApi as of GitLab 13.0.0). 4 | If disabled, MR comment trigger can be done by any user having access to your project. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/WebhookListenerBuildConditionsTrait/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/WebhookListenerBuildConditionsTrait/help-alwaysIgnoreNonCodeRelatedUpdates.html: -------------------------------------------------------------------------------- 1 |
2 | GitLab will send a webhook to Jenkins when there are updates to the MR including title changes, labels removed/added, etc. Enabling this option will prevent a build running if the cause was one of these updates. 3 | 4 | Note: these settings do not have any impact on build from comment settings. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/helpers/GitLabBrowser/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/helpers/GitLabBrowser/help-projectUrl.html: -------------------------------------------------------------------------------- 1 |
2 | Specify the HTTP URL for this project's GitLab page. The URL needs to include the owner and 3 | project so, for 4 | example, if the GitLab server is https://gitLab.example.com then the URL for bob's 5 | skunkworks 6 | project might be https://gitLab.example.com/bob/skunkworks 7 |
8 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabbranchsource/helpers/GitLabBrowser/help.html: -------------------------------------------------------------------------------- 1 |
2 | Specify the HTTP URL for this project's GitLab page so that links to changes can be automatically generated by Jenkins. 3 | The URL needs to include the owner and project. 4 | If the GitLab server is https://gitLab.example.com then the URL for bob's skunkworks 5 | project might be https://gitLab.example.com/bob/skunkworks. 6 |
7 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/credentials/Messages.properties: -------------------------------------------------------------------------------- 1 | PersonalAccessTokenImpl.displayName=GitLab Personal Access Token 2 | PersonalAccessTokenImpl.tokenRequired=Token required 3 | PersonalAccessTokenImpl.tokenWrongLength=Token should be at least 20 characters long 4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/credentials/PersonalAccessTokenImpl/credentials.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServer/config.groovy: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabserverconfig.servers.GitLabServer 2 | 3 | import io.jenkins.plugins.gitlabserverconfig.servers.GitLabServer 4 | import lib.CredentialsTagLib 5 | import lib.FormTagLib 6 | import org.apache.commons.lang.RandomStringUtils; 7 | 8 | def f = namespace(FormTagLib) 9 | def c = namespace(CredentialsTagLib) 10 | 11 | f.entry(title: _("Display Name"), field: "name", "description": "A unique name for the server") { 12 | f.textbox(default: String.format("gitlab-%s", RandomStringUtils.randomNumeric(GitLabServer.SHORT_NAME_LENGTH))) 13 | } 14 | 15 | f.entry(title: _("Server URL"), field: "serverUrl", "description": "The url to the GitLab server") { 16 | f.textbox(default: GitLabServer.GITLAB_SERVER_URL, checkMethod: 'post') 17 | } 18 | 19 | f.entry(title: _("Credentials"), field: "credentialsId", "description": "The Personal Access Token for GitLab APIs access") { 20 | c.select(context: app) 21 | } 22 | 23 | f.entry(title: _("Web Hook"), field: "manageWebHooks", "description": "Do you want to automatically manage GitLab Web Hooks on Jenkins Server?") { 24 | f.checkbox(title: _("Manage Web Hooks")) 25 | } 26 | 27 | f.entry(title: _("System Hook"), field: "manageSystemHooks", "description": "Do you want to automatically manage GitLab System Hooks on Jenkins Server?") { 28 | f.checkbox(title: _("Manage System Hooks")) 29 | } 30 | 31 | f.entry(title: _("Secret Token"), field: "webhookSecretCredentialsId", "description": "The secret token used while setting up hook url in the GitLab server") { 32 | c.select(context: app) 33 | } 34 | 35 | f.entry(title: _("Root URL for hooks"), field: "hooksRootUrl", "description": "Jenkins root URL to use in hooks URL (if different from the public Jenkins root URL)") { 36 | f.textbox() 37 | } 38 | 39 | f.advanced() { 40 | f.entry(title: _("Immediate Web Hook trigger"), field: "immediateHookTrigger", "description": "Trigger a build immediately on a GitLab Web Hook trigger") { 41 | f.checkbox(title: _("Immediate Web Hook trigger")) 42 | } 43 | f.entry(title: _("Web Hook trigger delay"), field: "hookTriggerDelay", "description": "Delay in seconds to be used for GitLab Web Hook build triggers (defaults to GitLab cache timeout)") { 44 | f.textbox() 45 | } 46 | } 47 | 48 | f.validateButton( 49 | title: _("Test connection"), 50 | progress: _("Testing.."), 51 | method: "testConnection", 52 | with: "serverUrl,credentialsId" 53 | ) 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServer/help-credentialsId.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | You can create your own personal 4 | access token. 5 |

6 | 7 | Token should be registered with scopes: 8 |
    9 |
  • api - For read/write access to the API, including all groups and projects
  • 10 |
  • repo - For read-only access to the authenticated user's profile through the /user API 11 | endpoint, which includes username, public email, and full name 12 |
  • 13 |
14 | 15 | In Jenkins, create a token credential as «GitLab Personal Access Token» or any 16 | «StringCredentials» type credential (e.g. «"Secret text"» provided by 17 | Plain Credentials plugin or 18 | «"Vault Secret Text Credential"» provided by 19 | HashiCorp Vault plugin). 20 | Human-readable id and description is recommended. 21 |
22 | 23 |

24 | If you have an existing GitLab login and password you can convert it to a token automatically 25 | with the help of «Manage additional GitLab actions» in Advanced section of 26 | GitLab Servers. 27 |

28 |
29 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServer/help-genButton.html: -------------------------------------------------------------------------------- 1 |
2 | Use this button to generate a new secret token to be used during hook creation. If older jobs exist they need to be rescanned to update existing hook. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServer/help-hookTriggerDelay.html: -------------------------------------------------------------------------------- 1 |
2 | Set non-empty if you want to use a GitLab Webhook trigger delay different from 3 | GitLab cache timeout. 4 | Note that setting this to a value smaller than GitLab cache timeout will result 5 | in GitLab repository changes not being detected and builds not being triggered reliably. 6 |
7 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServer/help-hooksRootUrl.html: -------------------------------------------------------------------------------- 1 |
2 | The Jenkins root URL (for instance https://something:8443/jenkins/) to use in hooks URL.
3 | This is only required when the globally configured public URL for Jenkins can not be reached from your GitLab server. 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServer/help-immediateHookTrigger.html: -------------------------------------------------------------------------------- 1 |
2 | Select this option if you want to immediately query gitlab for modifications when a 3 | web hook trigger is received. 4 | Using this option lets you start a build a soon as possible and rely on the "Web Hook trigger delay" 5 | value as a fallback in case gitlab's cache did not return the modifications. 6 |
7 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServer/help-manageSystemHooks.html: -------------------------------------------------------------------------------- 1 |
2 | Select this option if you want to setup System Hooks on your Jenkins instance. Remember Jenkins 3 | can register 4 | System Hook in GitLab Server only if the authenticated token has Admin access. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServer/help-manageWebHooks.html: -------------------------------------------------------------------------------- 1 |
2 | Selecting this option will enable the automatic management of web hooks for all items that use the 3 | GitLab Server 4 | endpoint, with the exception of those items that have explicitly opted out of hook management. 5 | When this option is not selected, individual items can still opt in to hook management provided 6 | the credentials 7 | those items have been configured with have permission to manage the required hooks. 8 |
9 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServer/help-name.html: -------------------------------------------------------------------------------- 1 |
2 | A human friendly and unique name for this GitLab server. If left blank, generates a unique default 3 | name. 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServer/help-serverUrl.html: -------------------------------------------------------------------------------- 1 |
2 | The default URL of this GitLab Server is https://gitlab.com. 3 | GitLab Ultimate, Gold and other self hosted versions can modify the url 4 | for example: https://gitlab.gnome.org/ 5 | and http://gitlab.example.com:7990. 6 |
7 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServer/help-webhookSecretCredentialsId.html: -------------------------------------------------------------------------------- 1 |
2 | The secret token is required to authenticate the webhook payloads received from a GitLab Server. Use generate secret token from Advanced options or use your own. If you are an old plugin user and did not set a secret token previously and want the secret token to be applied to the hooks of your existing jobs, you can add the secret token and rescan your jobs. Existing hooks with new secret token will be applied. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServers/config.groovy: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabserverconfig.servers.GitLabServers 2 | 3 | import lib.FormTagLib 4 | 5 | def f = namespace(FormTagLib) 6 | 7 | f.section(title: descriptor.displayName) { 8 | 9 | f.entry(title: _("GitLab Servers")) { 10 | f.repeatableHeteroProperty( 11 | field: "servers", 12 | hasHeader: "true", 13 | addCaption: _("Add GitLab Server") 14 | ) 15 | } 16 | f.advanced() { 17 | f.entry() { 18 | f.entry(title: _("Additional actions"), help: descriptor.getHelpFile('additional')) { 19 | f.hetero_list(items: [], 20 | addCaption: _("Manage additional GitLab actions"), 21 | name: "actions", 22 | oneEach: "true", hasHeader: "true", descriptors: instance.actions()) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServers/help-additional.html: -------------------------------------------------------------------------------- 1 |
2 | Additional actions can help you with some routines. For example, you can convert your existing 3 | login + password 4 | (stored in credentials or directly) to a GitLab Personal Access Token. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/Messages.properties: -------------------------------------------------------------------------------- 1 | GitLabServers.displayName=GitLab 2 | GitLabServer.displayName=GitLab Server 3 | GitLabServer.hookManagementAs=Hook management will be performed as {0} 4 | GitLabServer.validateInterrupted=Interrupted while trying to validate credentials 5 | GitLabServer.versionInterrupted=Interrupted while trying to determine server version 6 | GitLabServer.invalidUrl=Invalid GitLab Server URL: {0} 7 | GitLabServer.serverVersion=GitLab Version: {0} 8 | GitLabServer.failedValidation=Failed to validate the account {0} 9 | GitLabServer.credentialsNotResolved=Cannot resolve suitable credentials with id: {0} 10 | GitLabServer.advancedSectionForFuture=Advanced configurations will be added in later release 11 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/gitlabserverconfig/servers/helpers/GitLabPersonalAccessTokenCreator/config.groovy: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabserverconfig.servers.helpers.GitLabPersonalAccessTokenCreator 2 | 3 | import io.jenkins.plugins.gitlabserverconfig.servers.GitLabServer 4 | import lib.CredentialsTagLib 5 | import lib.FormTagLib 6 | 7 | 8 | def f = namespace(FormTagLib); 9 | def c = namespace(CredentialsTagLib) 10 | 11 | f.entry(title: _("GitLab Server URL"), field: "serverUrl", 12 | help: app.getDescriptor(GitLabServer.class)?.getHelpFile("serverUrl")) { 13 | f.textbox(default: GitLabServer.GITLAB_SERVER_URL) 14 | } 15 | 16 | f.radioBlock(checked: true, name: "credentials", value: "plugin", title: "From credentials") { 17 | f.entry(title: _("Credentials"), field: "credentialsId") { 18 | c.select(context: app, includeUser: true, expressionAllowed: false) 19 | } 20 | 21 | f.block() { 22 | f.validateButton( 23 | title: _("Create token credentials"), 24 | progress: _("Creating..."), 25 | method: "createTokenByCredentials", 26 | with: "serverUrl,credentialsId" 27 | ) 28 | } 29 | } 30 | 31 | f.radioBlock(checked: false, name: "credentials", value: "manually", title: "From login and password") { 32 | 33 | f.entry(title: _("Username"), field: "login") { 34 | f.textbox() 35 | } 36 | 37 | f.entry(title: _("Password"), field: "password") { 38 | f.password() 39 | } 40 | 41 | f.block() { 42 | f.validateButton( 43 | title: _("Create token credentials"), 44 | progress: _("Creating..."), 45 | method: "createTokenByPassword", 46 | with: "serverUrl,login,password" 47 | ) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/webapp/images/gitlab-branch.svg: -------------------------------------------------------------------------------- 1 | 2 | Git Branch 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/webapp/images/gitlab-commit.svg: -------------------------------------------------------------------------------- 1 | 2 | Git Commit 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/webapp/images/gitlab-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | gitlab-icon-rgb 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/webapp/images/gitlab-mr.svg: -------------------------------------------------------------------------------- 1 | 2 | Git Pull Request 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/webapp/images/gitlab-project.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | image/svg+xml 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/webapp/images/gitlab-tag.svg: -------------------------------------------------------------------------------- 1 | 2 | Pricetag 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/gitlabbranchsource/GitLabHookCreatorParameterizedTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import static org.hamcrest.Matchers.containsString; 4 | import static org.hamcrest.Matchers.not; 5 | import static org.hamcrest.core.Is.is; 6 | import static org.junit.Assert.assertThat; 7 | 8 | import io.jenkins.plugins.gitlabserverconfig.servers.GitLabServer; 9 | import java.util.Arrays; 10 | import jenkins.model.JenkinsLocationConfiguration; 11 | import org.junit.ClassRule; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.junit.runners.Parameterized; 15 | import org.junit.runners.Parameterized.Parameters; 16 | import org.jvnet.hudson.test.JenkinsRule; 17 | 18 | @RunWith(Parameterized.class) 19 | public class GitLabHookCreatorParameterizedTest { 20 | 21 | @ClassRule 22 | public static JenkinsRule r = new JenkinsRule(); 23 | 24 | private final String jenkinsUrl; 25 | private final boolean hookType; 26 | private final String expectedPath; 27 | 28 | @Parameters(name = "check {0}") 29 | public static Iterable data() { 30 | return Arrays.asList(new Object[][] { 31 | {"intranet.local:8080", false, "/gitlab-systemhook/post"}, 32 | {"intranet.local", true, "/gitlab-webhook/post"}, 33 | {"www.mydomain.com:8000", true, "/gitlab-webhook/post"}, 34 | {"www.mydomain.com", false, "/gitlab-systemhook/post"}, 35 | {"www.mydomain.com/jenkins", true, "/gitlab-webhook/post"} 36 | }); 37 | } 38 | 39 | public GitLabHookCreatorParameterizedTest(String jenkinsUrl, boolean hookType, String expectedPath) { 40 | this.jenkinsUrl = jenkinsUrl; 41 | this.hookType = hookType; 42 | this.expectedPath = expectedPath; 43 | } 44 | 45 | @Test 46 | public void hookUrl() { 47 | Arrays.asList("http://", "https://").forEach(proto -> { 48 | String expected = proto + jenkinsUrl + expectedPath; 49 | JenkinsLocationConfiguration.get().setUrl(proto + jenkinsUrl); 50 | String hookUrl = GitLabHookCreator.getHookUrl(null, hookType); 51 | GitLabHookCreator.checkURL(hookUrl); 52 | assertThat(hookUrl.replaceAll(proto, ""), not(containsString("//"))); 53 | assertThat(hookUrl, is(expected)); 54 | }); 55 | } 56 | 57 | @Test 58 | public void hookUrlFromCustomRootUrl() { 59 | Arrays.asList("http://", "https://").forEach(proto -> { 60 | String expected = proto + jenkinsUrl + expectedPath; 61 | JenkinsLocationConfiguration.get().setUrl("http://whatever"); 62 | GitLabServer server = new GitLabServer("https://gitlab.com", "GitLab", null); 63 | server.setHooksRootUrl(proto + jenkinsUrl); 64 | String hookUrl = GitLabHookCreator.getHookUrl(server, hookType); 65 | GitLabHookCreator.checkURL(hookUrl); 66 | assertThat(hookUrl.replaceAll(proto, ""), not(containsString("//"))); 67 | assertThat(hookUrl, is(expected)); 68 | }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/gitlabbranchsource/GitLabSCMSourceDeserializationTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | 6 | import java.lang.reflect.Field; 7 | import jenkins.branch.BranchSource; 8 | import jenkins.scm.api.SCMSource; 9 | import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject; 10 | import org.junit.Rule; 11 | import org.junit.Test; 12 | import org.jvnet.hudson.test.RestartableJenkinsRule; 13 | 14 | public class GitLabSCMSourceDeserializationTest { 15 | 16 | private static final String PROJECT_NAME = "project"; 17 | private static final String SOURCE_ID = "id"; 18 | 19 | @Rule 20 | public final RestartableJenkinsRule plan = new RestartableJenkinsRule(); 21 | 22 | @Test 23 | public void afterRestartingJenkinsTransientFieldsAreNotNull() throws Exception { 24 | plan.then(j -> { 25 | GitLabSCMSourceBuilder sb = 26 | new GitLabSCMSourceBuilder(SOURCE_ID, "server", "creds", "po", "group/project", "project"); 27 | WorkflowMultiBranchProject project = j.createProject(WorkflowMultiBranchProject.class, PROJECT_NAME); 28 | project.getSourcesList().add(new BranchSource(sb.build())); 29 | }); 30 | 31 | plan.then(j -> { 32 | SCMSource source = j.getInstance().getAllItems(WorkflowMultiBranchProject.class).stream() 33 | .filter(p -> PROJECT_NAME.equals(p.getName())) 34 | .map(p -> p.getSCMSource(SOURCE_ID)) 35 | .findFirst() 36 | .get(); 37 | 38 | Class clazz = source.getClass(); 39 | Field mergeRequestContributorCache = clazz.getDeclaredField("mergeRequestContributorCache"); 40 | mergeRequestContributorCache.setAccessible(true); 41 | Field mergeRequestMetadataCache = clazz.getDeclaredField("mergeRequestMetadataCache"); 42 | mergeRequestMetadataCache.setAccessible(true); 43 | assertNotNull(mergeRequestMetadataCache.get(source)); 44 | assertNotNull(mergeRequestContributorCache.get(source)); 45 | }); 46 | } 47 | 48 | @Test 49 | public void projectIdSurvivesConfigRoundtrip() { 50 | plan.then(j -> { 51 | GitLabSCMSourceBuilder sb = 52 | new GitLabSCMSourceBuilder(SOURCE_ID, "server", "creds", "po", "group/project", "project"); 53 | WorkflowMultiBranchProject project = j.createProject(WorkflowMultiBranchProject.class, PROJECT_NAME); 54 | GitLabSCMSource source = sb.build(); 55 | project.getSourcesList().add(new BranchSource(source)); 56 | long p = 42; 57 | source.setProjectId(p); 58 | j.configRoundtrip(project); 59 | 60 | WorkflowMultiBranchProject item = 61 | j.jenkins.getItemByFullName(PROJECT_NAME, WorkflowMultiBranchProject.class); 62 | assertNotNull(item); 63 | GitLabSCMSource scmSource = (GitLabSCMSource) item.getSCMSource(SOURCE_ID); 64 | assertNotNull(scmSource); 65 | assertEquals(Long.valueOf(p), scmSource.getProjectId()); 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/gitlabbranchsource/GitLabSCMSourceTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.mockito.ArgumentMatchers.any; 5 | import static org.mockito.ArgumentMatchers.anyString; 6 | 7 | import hudson.model.TaskListener; 8 | import hudson.security.AccessControlled; 9 | import hudson.util.StreamTaskListener; 10 | import io.jenkins.plugins.gitlabbranchsource.helpers.GitLabHelper; 11 | import io.jenkins.plugins.gitlabserverconfig.servers.GitLabServer; 12 | import io.jenkins.plugins.gitlabserverconfig.servers.GitLabServers; 13 | import java.io.ByteArrayOutputStream; 14 | import java.io.IOException; 15 | import java.nio.charset.StandardCharsets; 16 | import java.util.Arrays; 17 | import java.util.Set; 18 | import jenkins.branch.BranchSource; 19 | import jenkins.scm.api.SCMHead; 20 | import org.gitlab4j.api.GitLabApi; 21 | import org.gitlab4j.api.GitLabApiException; 22 | import org.gitlab4j.api.MergeRequestApi; 23 | import org.gitlab4j.api.ProjectApi; 24 | import org.gitlab4j.api.RepositoryApi; 25 | import org.gitlab4j.api.models.Project; 26 | import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject; 27 | import org.junit.ClassRule; 28 | import org.junit.Test; 29 | import org.jvnet.hudson.test.JenkinsRule; 30 | import org.mockito.MockedStatic; 31 | import org.mockito.Mockito; 32 | 33 | public class GitLabSCMSourceTest { 34 | 35 | private static final String SERVER = "server"; 36 | private static final String PROJECT_NAME = "project"; 37 | private static final String SOURCE_ID = "id"; 38 | 39 | @ClassRule 40 | public static JenkinsRule j = new JenkinsRule(); 41 | 42 | @Test 43 | public void retrieveMRWithEmptyProjectSettings() throws GitLabApiException, IOException, InterruptedException { 44 | GitLabApi gitLabApi = Mockito.mock(GitLabApi.class); 45 | ProjectApi projectApi = Mockito.mock(ProjectApi.class); 46 | RepositoryApi repoApi = Mockito.mock(RepositoryApi.class); 47 | MergeRequestApi mrApi = Mockito.mock(MergeRequestApi.class); 48 | Mockito.when(gitLabApi.getProjectApi()).thenReturn(projectApi); 49 | Mockito.when(gitLabApi.getMergeRequestApi()).thenReturn(mrApi); 50 | Mockito.when(gitLabApi.getRepositoryApi()).thenReturn(repoApi); 51 | Mockito.when(projectApi.getProject(any())).thenReturn(new Project()); 52 | try (MockedStatic utilities = Mockito.mockStatic(GitLabHelper.class)) { 53 | utilities 54 | .when(() -> GitLabHelper.apiBuilder(any(AccessControlled.class), anyString())) 55 | .thenReturn(gitLabApi); 56 | GitLabServers.get().addServer(new GitLabServer("", SERVER, "")); 57 | GitLabSCMSourceBuilder sb = 58 | new GitLabSCMSourceBuilder(SOURCE_ID, SERVER, "creds", "po", "group/project", "project"); 59 | WorkflowMultiBranchProject project = j.createProject(WorkflowMultiBranchProject.class, PROJECT_NAME); 60 | BranchSource source = new BranchSource(sb.build()); 61 | source.getSource() 62 | .setTraits(Arrays.asList(new BranchDiscoveryTrait(0), new OriginMergeRequestDiscoveryTrait(1))); 63 | project.getSourcesList().add(source); 64 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 65 | final TaskListener listener = new StreamTaskListener(out, StandardCharsets.UTF_8); 66 | Set scmHead = source.getSource().fetch(listener); 67 | assertEquals(0, scmHead.size()); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/gitlabbranchsource/helpers/GitLabHelperTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabbranchsource.helpers; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.is; 5 | 6 | import io.jenkins.plugins.gitlabserverconfig.servers.GitLabServer; 7 | import org.junit.Test; 8 | 9 | public class GitLabHelperTest { 10 | 11 | @Test 12 | public void server_url_does_not_have_trailing_slash() { 13 | assertThat(GitLabHelper.getServerUrl(null), is("https://gitlab.com")); 14 | 15 | GitLabServer server1 = new GitLabServer("https://company.com/gitlab/", "comp_server", "1245"); 16 | assertThat(GitLabHelper.getServerUrl(server1), is("https://company.com/gitlab")); 17 | 18 | GitLabServer server2 = new GitLabServer("https://gitlab.example.org", "", "pw-id"); 19 | assertThat(GitLabHelper.getServerUrl(server2), is("https://gitlab.example.org")); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/gitlabserverconfig/casc/ConfigurationAsCodeTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabserverconfig.casc; 2 | 3 | import static io.jenkins.plugins.casc.misc.Util.getUnclassifiedRoot; 4 | import static io.jenkins.plugins.casc.misc.Util.toStringFromYamlFile; 5 | import static io.jenkins.plugins.casc.misc.Util.toYamlString; 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | import static org.hamcrest.Matchers.hasSize; 8 | import static org.hamcrest.Matchers.is; 9 | import static org.hamcrest.Matchers.matchesPattern; 10 | import static org.hamcrest.Matchers.not; 11 | 12 | import com.cloudbees.plugins.credentials.CredentialsProvider; 13 | import hudson.security.ACL; 14 | import io.jenkins.plugins.casc.ConfigurationContext; 15 | import io.jenkins.plugins.casc.ConfiguratorRegistry; 16 | import io.jenkins.plugins.casc.misc.ConfiguredWithCode; 17 | import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule; 18 | import io.jenkins.plugins.casc.model.CNode; 19 | import io.jenkins.plugins.gitlabserverconfig.credentials.PersonalAccessTokenImpl; 20 | import io.jenkins.plugins.gitlabserverconfig.servers.GitLabServer; 21 | import io.jenkins.plugins.gitlabserverconfig.servers.GitLabServers; 22 | import java.util.Collections; 23 | import java.util.List; 24 | import org.junit.ClassRule; 25 | import org.junit.Test; 26 | 27 | public class ConfigurationAsCodeTest { 28 | 29 | @ClassRule 30 | @ConfiguredWithCode("configuration-as-code.yml") 31 | public static JenkinsConfiguredWithCodeRule j = new JenkinsConfiguredWithCodeRule(); 32 | 33 | @Test 34 | public void should_support_configuration_as_code() { 35 | List servers = GitLabServers.get().getServers(); 36 | assertThat(servers.size(), is(1)); 37 | GitLabServer server = servers.get(0); 38 | assertThat(server.getServerUrl(), is("https://gitlab.com")); 39 | assertThat(server.getName(), matchesPattern("gitlab-[0-9]{4}")); 40 | assertThat(server.isManageWebHooks(), is(true)); 41 | assertThat(server.isManageSystemHooks(), is(true)); 42 | assertThat(server.getHooksRootUrl(), is("https://jenkins.intranet/")); 43 | 44 | List credentials = CredentialsProvider.lookupCredentials( 45 | PersonalAccessTokenImpl.class, j.jenkins, ACL.SYSTEM, Collections.emptyList()); 46 | assertThat(credentials, hasSize(1)); 47 | final PersonalAccessTokenImpl credential = credentials.get(0); 48 | assertThat(credential.getToken().getPlainText(), is("glpat-XfsqZvVtAx5YCph5bq3r")); 49 | assertThat(credential.getToken().getEncryptedValue(), is(not("glpat-XfsqZvVtAx5YCph5bq3r"))); 50 | } 51 | 52 | @Test 53 | public void should_support_configuration_export() throws Exception { 54 | ConfiguratorRegistry registry = ConfiguratorRegistry.get(); 55 | ConfigurationContext context = new ConfigurationContext(registry); 56 | CNode gitLabServers = getUnclassifiedRoot(context).get("gitLabServers"); 57 | 58 | String exported = toYamlString(gitLabServers); 59 | 60 | String expected = toStringFromYamlFile(this, "expected_output.yml"); 61 | 62 | assertThat(exported, matchesPattern(expected)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/gitlabserverconfig/credentials/PersonalAccessTokenImplTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabserverconfig.credentials; 2 | 3 | import com.cloudbees.plugins.credentials.Credentials; 4 | import com.cloudbees.plugins.credentials.CredentialsScope; 5 | import hudson.model.AbstractProject; 6 | import hudson.tasks.BuildStepDescriptor; 7 | import hudson.tasks.Builder; 8 | import org.junit.ClassRule; 9 | import org.junit.Test; 10 | import org.jvnet.hudson.test.JenkinsRule; 11 | import org.jvnet.hudson.test.TestExtension; 12 | import org.kohsuke.stapler.DataBoundConstructor; 13 | 14 | public class PersonalAccessTokenImplTest { 15 | 16 | @ClassRule 17 | public static JenkinsRule j = new JenkinsRule(); 18 | 19 | @Test 20 | public void configRoundtrip() throws Exception { 21 | PersonalAccessTokenImpl expected = new PersonalAccessTokenImpl( 22 | CredentialsScope.GLOBAL, "magic-id", "configRoundtrip", "sAf_Xasnou47yxoAsC"); 23 | CredentialsBuilder builder = new CredentialsBuilder(expected); 24 | j.configRoundtrip(builder); 25 | j.assertEqualDataBoundBeans(expected, builder.credentials); 26 | } 27 | 28 | /** 29 | * Helper for {@link #configRoundtrip()}. 30 | */ 31 | public static class CredentialsBuilder extends Builder { 32 | 33 | public final Credentials credentials; 34 | 35 | @DataBoundConstructor 36 | public CredentialsBuilder(Credentials credentials) { 37 | this.credentials = credentials; 38 | } 39 | 40 | @TestExtension 41 | public static class DescriptorImpl extends BuildStepDescriptor { 42 | 43 | @Override 44 | public String getDisplayName() { 45 | return "CredentialsBuilder"; 46 | } 47 | 48 | @SuppressWarnings("rawtypes") 49 | @Override 50 | public boolean isApplicable(Class jobType) { 51 | return true; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServerTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabserverconfig.servers; 2 | 3 | import static org.hamcrest.CoreMatchers.nullValue; 4 | import static org.hamcrest.CoreMatchers.startsWith; 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | import static org.hamcrest.Matchers.is; 7 | import static org.junit.Assert.assertEquals; 8 | 9 | import java.io.IOException; 10 | import org.apache.http.HttpStatus; 11 | import org.htmlunit.html.HtmlPage; 12 | import org.junit.ClassRule; 13 | import org.junit.Test; 14 | import org.jvnet.hudson.test.Issue; 15 | import org.jvnet.hudson.test.JenkinsRule; 16 | import org.jvnet.hudson.test.JenkinsRule.WebClient; 17 | import org.xml.sax.SAXException; 18 | 19 | public class GitLabServerTest { 20 | 21 | @ClassRule 22 | public static JenkinsRule j = new JenkinsRule(); 23 | 24 | @Test 25 | public void testFixEmptyAndTrimOne() throws Exception { 26 | GitLabServer server = new GitLabServer("https://gitlab.com", "default", null); 27 | server.setHooksRootUrl("https://myhooks/"); 28 | assertThat(server.getName(), is("default")); 29 | assertThat(server.getServerUrl(), is("https://gitlab.com")); 30 | assertThat(server.getCredentialsId(), nullValue()); 31 | assertThat(server.getHooksRootUrl(), is("https://myhooks/")); 32 | } 33 | 34 | @Test 35 | public void testFixEmptyAndTrimTwo() throws Exception { 36 | GitLabServer server = new GitLabServer(" https://gitlab.com ", " default ", null); 37 | server.setHooksRootUrl(" https://myhooks/ "); 38 | assertThat(server.getName(), is("default")); 39 | assertThat(server.getServerUrl(), is("https://gitlab.com")); 40 | assertThat(server.getCredentialsId(), nullValue()); 41 | assertThat(server.getHooksRootUrl(), is("https://myhooks/")); 42 | } 43 | 44 | @Test 45 | public void testFixEmptyAndTrimThree() throws Exception { 46 | GitLabServer server = new GitLabServer(null, null, null); 47 | server.setHooksRootUrl(null); 48 | assertThat(server.getName(), startsWith("gitlab-")); 49 | assertThat(server.getServerUrl(), is("https://gitlab.com")); 50 | assertThat(server.getCredentialsId(), nullValue()); 51 | assertThat(server.getHooksRootUrl(), nullValue()); 52 | } 53 | 54 | @Test 55 | public void testFixEmptyAndTrimFour() throws Exception { 56 | GitLabServer server = new GitLabServer("https://whatever.com", "whatever", null); 57 | server.setHooksRootUrl("https://myhooks/"); 58 | assertThat(server.getName(), is("whatever")); 59 | assertThat(server.getServerUrl(), is("https://whatever.com")); 60 | assertThat(server.getCredentialsId(), nullValue()); 61 | assertThat(server.getHooksRootUrl(), is("https://myhooks/")); 62 | } 63 | 64 | @Test 65 | public void testFixEmptyAndTrimFive() throws Exception { 66 | GitLabServer server = new GitLabServer("", "", ""); 67 | server.setHooksRootUrl(""); 68 | assertThat(server.getName(), startsWith("gitlab-")); 69 | assertThat(server.getServerUrl(), is("https://gitlab.com")); 70 | assertThat(server.getCredentialsId(), is("")); 71 | assertThat(server.getHooksRootUrl(), nullValue()); 72 | } 73 | 74 | @Test 75 | @Issue("SECURITY-3251") 76 | public void testGetDoCheckServerUrl() throws IOException, SAXException { 77 | try (WebClient wc = j.createWebClient()) { 78 | wc.setThrowExceptionOnFailingStatusCode(false); 79 | HtmlPage page = wc.goTo( 80 | "descriptorByName/io.jenkins.plugins.gitlabserverconfig.servers.GitLabServer/checkServerUrl?serverUrl=http://attacker.example.com"); 81 | assertEquals( 82 | HttpStatus.SC_NOT_FOUND, 83 | page.getWebResponse().getStatusCode()); // Should be 405 but Stapler doesn't work that way. 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServersTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.gitlabserverconfig.servers; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.containsString; 5 | import static org.hamcrest.Matchers.equalTo; 6 | import static org.hamcrest.Matchers.hasItem; 7 | import static org.hamcrest.Matchers.hasSize; 8 | import static org.hamcrest.Matchers.not; 9 | 10 | import com.cloudbees.plugins.credentials.Credentials; 11 | import com.cloudbees.plugins.credentials.CredentialsMatchers; 12 | import com.cloudbees.plugins.credentials.CredentialsProvider; 13 | import com.cloudbees.plugins.credentials.domains.DomainRequirement; 14 | import edu.umd.cs.findbugs.annotations.NonNull; 15 | import edu.umd.cs.findbugs.annotations.Nullable; 16 | import hudson.diagnosis.OldDataMonitor; 17 | import hudson.model.ItemGroup; 18 | import hudson.security.ACL; 19 | import java.util.List; 20 | import java.util.logging.Level; 21 | import jenkins.model.Jenkins; 22 | import org.acegisecurity.Authentication; 23 | import org.jenkinsci.plugins.plaincredentials.StringCredentials; 24 | import org.junit.Rule; 25 | import org.junit.Test; 26 | import org.jvnet.hudson.test.JenkinsRule; 27 | import org.jvnet.hudson.test.LoggerRule; 28 | import org.jvnet.hudson.test.TestExtension; 29 | import org.jvnet.hudson.test.recipes.LocalData; 30 | 31 | public class GitLabServersTest { 32 | @Rule 33 | public LoggerRule logger = 34 | new LoggerRule().record(OldDataMonitor.class, Level.FINE).capture(50); 35 | 36 | @Rule 37 | public JenkinsRule j = new JenkinsRule(); 38 | 39 | @LocalData 40 | @Test 41 | public void migrationToCredentials() throws Throwable { 42 | // LocalData creating using the following: 43 | /* 44 | GitLabServer server = new GitLabServer("http://localhost", "my-server", null); 45 | server.setSecretToken(Secret.fromString("s3cr3t!")); 46 | GitLabServers.get().addServer(server); 47 | */ 48 | var server = GitLabServers.get().getServers().stream() 49 | .filter(s -> s.getName().equals("my-server")) 50 | .findFirst() 51 | .orElseThrow(); 52 | var credentialsId = server.getWebhookSecretCredentialsId(); 53 | var credentials = CredentialsMatchers.filter( 54 | CredentialsProvider.lookupCredentialsInItemGroup(StringCredentials.class, Jenkins.get(), ACL.SYSTEM2), 55 | CredentialsMatchers.withId(credentialsId)); 56 | assertThat(credentials, hasSize(1)); 57 | assertThat(credentials.get(0).getSecret().getPlainText(), equalTo("s3cr3t!")); 58 | assertThat( 59 | logger.getMessages(), not(hasItem(containsString("Trouble loading " + GitLabServers.class.getName())))); 60 | } 61 | 62 | @TestExtension("migrationToCredentials") 63 | public static class CredentialsProviderThatRequiresDescriptorLookup extends CredentialsProvider { 64 | @Override 65 | public List getCredentials( 66 | @NonNull Class type, 67 | @Nullable ItemGroup itemGroup, 68 | @Nullable Authentication authentication, 69 | @NonNull List domainRequirements) { 70 | // Prior to fix, this caused the GitLabServer migration code to recurse infinitely, causing problems when 71 | // starting Jenkins. 72 | // In practice this was caused by a lookup of another descriptor type, but I am using GitLabServers for 73 | // clarity. 74 | Jenkins.get().getDescriptor(GitLabServers.class); 75 | return List.of(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/gitlabserverconfig/casc/configuration-as-code.yml: -------------------------------------------------------------------------------- 1 | credentials: 2 | system: 3 | domainCredentials: 4 | - credentials: 5 | - gitlabPersonalAccessToken: 6 | scope: SYSTEM 7 | id: "i<3GitLab" 8 | token: "glpat-XfsqZvVtAx5YCph5bq3r" 9 | 10 | unclassified: 11 | gitLabServers: 12 | servers: 13 | - credentialsId: "i<3GitLab" 14 | hooksRootUrl: "https://jenkins.intranet/" 15 | manageSystemHooks: true 16 | manageWebHooks: true 17 | name: gitlab-3213 18 | serverUrl: "https://gitlab.com" 19 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/gitlabserverconfig/casc/expected_output.yml: -------------------------------------------------------------------------------- 1 | servers: 2 | - credentialsId: "i<3GitLab" 3 | hooksRootUrl: "https://jenkins.intranet/" 4 | manageSystemHooks: true 5 | manageWebHooks: true 6 | name: "gitlab-[0-9]{4}" 7 | serverUrl: "https://gitlab.com" 8 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/gitlabserverconfig/credentials/PersonalAccessTokenImplTest/CredentialsBuilder/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/gitlabserverconfig/servers/GitLabServersTest/migrationToCredentials/io.jenkins.plugins.gitlabserverconfig.servers.GitLabServers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | default 6 | https://gitlab.com 7 | false 8 | false 9 | 10 | 11 | false 12 | 13 | 14 | my-server 15 | http://localhost 16 | false 17 | false 18 | s3cr3t! 19 | 20 | false 21 | 22 | 23 | --------------------------------------------------------------------------------