Merge branch 'master' into fix-workflow-steps

This commit is contained in:
Brassely Stéphane 2018-02-02 17:41:43 +02:00 committed by GitHub
commit 0f77523137
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 5779 additions and 1166 deletions

BIN
.mvn/wrapper/maven-wrapper.jar vendored Normal file

Binary file not shown.

1
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@ -0,0 +1 @@
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip

52
.travis.yml Normal file
View File

@ -0,0 +1,52 @@
language: java
env:
global:
- DOCKER_CACHE_DIR=$HOME/.docker-images
matrix:
fast_finish: true
before_install:
- chmod +x travis/*.sh
install: true
jobs:
include:
- stage: test
script: ./travis/test.sh
- stage: integration-test
sudo: required
services:
- docker
env:
- GITLAB_VERSION=8.17.4
script: ./travis/integration-test.sh
- stage: integration-test
sudo: required
services:
- docker
env:
- GITLAB_VERSION=9.5.5
script: ./travis/integration-test.sh
- stage: integration-test
sudo: required
services:
- docker
env:
- GITLAB_VERSION=latest
script: ./travis/integration-test.sh
cache:
directories:
- $DOCKER_CACHE_DIR
- $HOME/.m2/repository
- $HOME/.m2/wrapper
before_cache:
- travis/before_cache.sh

View File

@ -1,5 +1,49 @@
ChangeLog
1.5.2
=====================
* #524: If Blue Ocean is installed, build URL in GitLab will point to Blue Ocean
* #564: Build status can now be sent to GitLab from builds downstream of the one that GitLab triggered
* #589: Make it easier to distinguish a commit push from a tag push
* #616: Make it easier to configure gitlab-plugin from Job DSL plugin.
* #639: Don't NPE if one of the filter specs is not specified in a Jenkinsfile
* #658: Send current state of build to GitLab when making commit API calls so it can be seen in the GitLab UI
* #659: Trigger builds when MR is approved in GitLab
1.5.1
=====================
* #648: Fix NPE when an MR build is triggered
* #650: Improve GitLab API version autodetection
* #653: Fix unsupported date format in MR trigger
* #656: Fix 404 error when making v4 API calls for MRs
1.5.0
=====================
* #614: Add optional support for GitLab API v4
1.4.8
=====================
* #483: If 'Add message for failed builds' feature is used, send the message for both failed and 'unstable' builds
* #514: Fix branch name comparison to avoid spurious builds, fixes issue #512
* #540: Allow jobs to be triggered by GitLab 'Pipeline' event
* #552: Use GitLab's host url to calculate project's ID - allows Jenkins to work with GitLab projects that are in subgroups (issue #538)
* #567: Plugin should have secure defaults - first-time installs will now have plugin endpoint require auth by default
* #604: Recursively retrieve all BuildData - prevents Jenkins from rebuilding when MR assignee changes (issue #603)
1.4.7
=====================
* #584: Fixes commit status exception found in issue #583
1.4.6
=====================
* #508 and #542: Trigger build when merge request has been merged or closed
* #510: Add gitlabMergeRequestTargetProjectId to available variables in builds
* #516: Fix: Trigger for pushes to the destination branch of open merge requests does not work in pipeline scripts
* #532: Allow publishing a comment to the GitLab MR if the build result is 'unstable'
* #543: Matrix/multi-configuration project support
* #544: Add a button to clear the security token in build configuration
* #559: Add a function to (re)set the Gitlab connections for bootstrapping new Jenkins installs
* #562: Fix issue #523 - Build result sent to Pipeline library repo instead of project repo
1.4.5
=====================

1
Jenkinsfile vendored Normal file
View File

@ -0,0 +1 @@
buildPlugin()

195
README.md
View File

@ -3,25 +3,19 @@
- [User support](#user-support)
- [Known bugs/issues](#known-bugsissues)
- [Supported GitLab versions](#supported-gitlab-versions)
- [Configuring access to GitLab](#configuring-access-to-gitlab)
- [Configuring the plugin](#configuring-the-plugin)
- [Global configuration and authentication](#global-configuration)
- [Jenkins Job Configuration](#jenkins-job-configuration)
- [Gitlab Configuration (>= 8.1)](#gitlab-configuration)
- [Branch filtering](#branch-filtering)
- [Build Tags](#build-tags)
- [Parameterized builds](#parameterized-builds)
- [Contributing to the Plugin](#contributing-to-the-plugin)
- [Quick test environment setup using Docker](#quick-test-environment-setup-using-docker)
- [Access GitLab](#access-gitlab)
- [Access Jenkins](#access-jenkins)
- [Testing With Docker](#testing-with-docker)
- [Release Workflow](#release-workflow)
# Introduction
This plugin allows GitLab to trigger builds in Jenkins after code is pushed and/or after a merge request is created and report build status back to GitLab.
# Seeking maintainers
We are seeking new maintainers for the plugin! The existing codebase is clean and well-tested thanks to a lot of hard work done by former lead maintainer [coder-hugo,](https://github.com/coder-hugo) but he is no longer using GitLab and does not have spare time to keep working on the plugin. We're looking for an active GitLab user and experienced Java programmer to take over as lead maintainer. The main work necessary at this point is resolving minor bugs and maintaining support for new versions of GitLab. If you're interested, reach out to [Owen](https://github.com/omehegan) - email address in the project's commit log.
This plugin allows GitLab to trigger builds in Jenkins after code is pushed and/or after a merge request is created and/or after an existing merge request was merged/closed, and report build status back to GitLab.
# User support
@ -45,35 +39,72 @@ You can also try chatting with us in the #gitlab-plugin channel on the Freenode
# Known bugs/issues
This is not an exhaustive list of issues, but rather a place for us to note significant bugs that may impact your use of the plugin in certain circumstances. For most things, please search the [Issues](https://github.com/jenkinsci/gitlab-plugin/issues) section and open a new one if you don't find anything.
* [#272](https://github.com/jenkinsci/gitlab-plugin/issues/272) - Plugin version 1.2.0+ does not work with GitLab Enterprise Edition < 8.8.3, due to a bug on their side.
* [#272](https://github.com/jenkinsci/gitlab-plugin/issues/272) - Plugin version 1.2.0+ does not work with GitLab Enterprise Edition < 8.8.3. Subsequent versions work fine.
* Jenkins versions 1.651.2 and 2.3 removed the ability of plugins to set arbitrary job parameters that are not specifically defined in each job's configuration. This was an important security update, but it has broken compatibility with some plugins, including ours. See [here](https://jenkins.io/blog/2016/05/11/security-update/) for more information and workarounds if you are finding parameters unset or empty that you expect to have values.
* [#473](https://github.com/jenkinsci/gitlab-plugin/issues/473) - When upgrading from plugin versions older than 1.2.0, you must upgrade to that version first, and then to the latest version. Otherwise, you will get a NullPointerException in com.cloudbees.plugins.credentials.matchers.IdMatcher after you upgrade. See the linked issue for specific instructions.
* [#608](https://github.com/jenkinsci/gitlab-plugin/issues/608) - GitLab 9.5.0 - 9.5.4 has a bug that causes the "Test Webhook" function to fail when it sends a test to Jenkins. This was fixed in 9.5.5.
# Supported GitLab versions
* GitLab versions 8.1.x and newer (both CE and EE editions) are supported via the GitLab commit status API which supports with external CI services like Jenkins
* GitLab versions 8.1.x and newer (both CE and EE editions) are supported via the GitLab [commit status API](https://docs.gitlab.com/ce/api/commits.html#commit-status) which supports with external CI services like Jenkins
* Versions older than 8.1.x may work but are no longer officially supported
# Configuring access to GitLab
# Configuring the plugin
## Global configuration
### GitLab-to-Jenkins authentication (required by default)
**Disabling authentication**
Optionally, the plugin communicates with the GitLab server in order to fetch additional information. At this moment, this information is limited to fetching the source project of a Merge Request, in order to support merging from forked repositories.
By default the plugin will require authentication to be set up for the connection from GitLab to Jenkins, in order to prevent unauthorized persons from being able to trigger jobs. If you want to disable this (not recommended):
1. In Jenkins, go to Manage Jenkins -> Configure System
2. Scroll down to the section labeled 'GitLab'
3. Uncheck "Enable authentication for '/project' end-point" - you will now be able to trigger Jenkins jobs from GitLab without needing authentication
To enable this functionality, a user should be set up on GitLab, with GitLab 'Developer' permissions, to access the repository. You will need to give this user access to each repo you want Jenkins to be able to clone. Log in to GitLab as that user, go to its profile, and copy its secret API key. On the Global Configuration page in Jenkins, supply the GitLab host URL, e.g. ``http://your.gitlab.server.`` Click the 'Add' button to add a credential, choose 'GitLab API token' as the kind of credential, and paste your GitLab user's API key into the 'API token' field. Testing the connection should succeed.
**Configuring global authentication**
Otherwise, to set up authentication for GitLab to trigger builds:
1. Create a user in Jenkins which has, at a minimum, Job/Build permissions
2. Log in as that user (this is required even if you are a Jenkins admin user), then click on the user's name in the top right corner of the page
3. Click 'Configure,' then 'Show API Token...', and note/copy the User ID and API Token
4. In GitLab, when you create webhooks to trigger Jenkins jobs, use this format for the URL and do not enter anything for 'Secret Token': `http://USERID:APITOKEN@JENKINS_URL/project/YOUR_JOB`
5. After you add the webhook, click the 'Test' button, and it should succeed
**Configuring per-project authentication**
If you want to create separate authentication credentials for each Jenkins job:
1. In the configuration of your Jenkins job, in the GitLab configuration section, click 'Advanced'
2. Click the 'Generate' button under the 'Secret Token' field
3. Copy the resulting token, and save the job configuration
4. In GitLab, create a webhook for your project, enter the trigger URL (e.g. `http://JENKINS_URL/project/YOUR_JOB`) and paste the token in the Secret Token field
5. After you add the webhook, click the 'Test' button, and it should succeed
### Jenkins-to-GitLab authentication (optional)
**PLEASE NOTE:** This auth configuration is only used for accessing the GitLab API for sending build status to GitLab. It is **not** used for cloning git repos. The credentials for cloning (usually SSH credentials) should be configuring separately, in the git plugin.
This plugin can be configured to send build status messages to GitLab, which show up in the GitLab Merge Request UI. To enable this functionality:
1. Create a new user in GitLab
2. Give this user 'Developer' permissions on each repo you want Jenkins to send build status to
3. Log in or 'Impersonate' that user in GitLab, click the user's icon/avatar and choose Settings
4. Click on 'Access Tokens'
5. Create a token named e.g. 'jenkins' with 'api' scope; expiration is optional
6. Copy the token immediately, it cannot be accessed after you leave this page
7. On the Global Configuration page in Jenkins, in the GitLab configuration section, supply the GitLab host URL, e.g. `http://your.gitlab.server`
8. Click the 'Add' button to add a credential, choose 'GitLab API token' as the kind of credential, and paste your GitLab user's API key into the 'API token' field
9. Click the 'Test Connection' button; it should succeed
## Jenkins Job Configuration
### Git configuration for Freestyle jobs
1. In the *Source Code Management* section:
1. Click *Git*
2. Enter your *Repository URL*, such as ``git@your.gitlab.server:gitlab_group/gitlab_project.git``
* In the *Advanced* settings, set *Name* to ``origin`` and *Refspec* to
* In the *Advanced* settings, set *Name* to ``origin`` and *Refspec* to
``+refs/heads/*:refs/remotes/origin/* +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*``
3. In order to merge from forked repositories: <br/>**Note:** this requires [configuring communication to the GitLab server](#configuring-access-to-gitlab)
* Click *Add Repository* to specify the merge request source repository. Then specify:
* *URL*: ``${gitlabSourceRepoURL}``
* In the *Advanced* settings, set *Name* to ``${gitlabSourceRepoName}``. Leave *Refspec* blank.
* Click *Add Repository* to specify the merge request source repository. Then specify:
* *URL*: ``${gitlabSourceRepoURL}``
* In the *Advanced* settings, set *Name* to ``${gitlabSourceRepoName}``. Leave *Refspec* blank.
4. In *Branch Specifier* enter:
* For single-repository workflows: ``origin/${gitlabSourceBranch}``
* For forked repository workflows: ``merge-requests/${gitlabMergeRequestIid}``
* For single-repository workflows: ``origin/${gitlabSourceBranch}``
* For forked repository workflows: ``merge-requests/${gitlabMergeRequestIid}``
5. In *Additional Behaviours*:
* Click the *Add* drop-down button
* Select *Merge before build* from the drop-down
@ -88,21 +119,31 @@ To enable this functionality, a user should be set up on GitLab, with GitLab 'De
* A Jenkins Pipeline bug will prevent the Git clone from working when you use a Pipeline script from SCM. It works if you use the Jenkins job config UI to edit the script. There is a workaround mentioned here: https://issues.jenkins-ci.org/browse/JENKINS-33719
* Use the Snippet generator, General SCM step, to generate sample Groovy code for the git checkout/merge etc.
* Example that performs merge before build: `checkout changelog: true, poll: true, scm: [$class: 'GitSCM', branches: [[name: "origin/${env.gitlabSourceBranch}"]], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: 'origin', mergeStrategy: 'default', mergeTarget: "${env.gitlabTargetBranch}"]]], submoduleCfg: [], userRemoteConfigs: [[name: 'origin', url: 'git@mygitlab:foo/testrepo.git']]]`
* Example that performs merge before build:
```
checkout changelog: true, poll: true, scm: [
$class: 'GitSCM',
branches: [[name: "origin/${env.gitlabSourceBranch}"]],
doGenerateSubmoduleConfigurations: false,
extensions: [[$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: 'origin', mergeStrategy: 'default', mergeTarget: "${env.gitlabTargetBranch}"]]],
submoduleCfg: [],
userRemoteConfigs: [[name: 'origin', url: 'git@gitlab.example.com:foo/testrepo.git']]
]
```
### Git configuration for Multibranch Pipeline/Workflow jobs
**Note:** none of the GitLab environment variables are available for mulitbranch pipeline jobs as there is no way to pass some additional data to a multibranch pipeline build while notifying a multibranch pipeline job about SCM changes.
**Note:** none of the GitLab environment variables are available for multibranch pipeline jobs as there is no way to pass some additional data to a multibranch pipeline build while notifying a multibranch pipeline job about SCM changes.
Due to this the plugin just listens for GitLab Push Hooks for multibranch pipeline jobs; Merge Request hooks are ignored.
1. Click **Add source**
2. Select **Git**
3. Enter your *Repository URL* (e.g.: ``git@your.gitlab.server:group/repo_name.git``)
4. Unlike other job types, there is no 'Trigger' setting required for a Multibranch job configuration; just create a webhook in GitLab for push requests which points to ``http://JENKINS_URL/project/PROJECT_NAME``
4. Unlike other job types, there is no 'Trigger' setting required for a Multibranch job configuration; just create a webhook in GitLab for push requests which points to ``http://JENKINS_URL/project/PROJECT_NAME`` or ``http://JENKINS_URL/project/FOLDER/PROJECT_NAME`` if the project in inside a folder in Jenkins.
Example `Jenkinsfile` for multibranch pipeline jobs
```
// Reference the GitLab connection name from your Jenkins Global configuration (http://JENKINS_URL/configure, GitLab section)
properties([gitLabConnection('<your-gitlab-connection-name')])
properties([gitLabConnection('your-gitlab-connection-name')])
node {
stage "checkout"
@ -125,12 +166,15 @@ node {
* Select *Build when a change is pushed to GitLab*
* Make a note of the *GitLab CI Service URL* appearing on the same line with *Build when a change is
pushed to GitLab*. You will later use this URL to define a GitLab web hook.
* Use the check boxes to trigger builds on *Push Events* and/or *Merge Request Events*
* Use the check boxes to trigger builds on *Push Events* and/or *Created Merge Request Events* and/or *Accepted Merge Request Events* and/or *Closed Merge Request Events*
* Optionally use *Rebuild open Merge Requests* to enable re-building open merge requests after a
push to the source branch
* If you selected *Rebuild open Merge Requests* other than *None*, check *Comments*, and specify the
*Comment for triggering a build*. A new build will be triggered when this phrase appears in a
commit comment. In addition to a literal phrase, you can also specify a Java regular expression.
* You can use *Build on successful pipeline events* to trigger on a successful pipeline run in Gitlab. Note that
this build trigger will only trigger a build if the commit is not already built and does not set the Gitlab status.
Otherwise you might end up in a loop.
2. Configure any other pre build, build or post build actions as necessary
3. Click *Save* to preserve your changes in Jenkins.
@ -141,9 +185,16 @@ The plugin supports the new [declarative pipeline syntax](https://github.com/jen
```
pipeline {
agent any
post {
failure {
updateGitlabCommitStatus name: 'build', state: 'failed'
}
success {
updateGitlabCommitStatus name: 'build', state: 'success'
}
}
options {
gitLabConnection('<your-gitlab-connection-name')
gitlabCommitStatus(name: 'jenkins')
}
triggers {
gitlab(triggerOnPush: true, triggerOnMergeRequest: true, branchFilterType: 'All')
@ -159,12 +210,56 @@ pipeline {
}
```
If you make use of the "Merge When Pipeline Succeeds" option for Merge Requests in GitLab, and your Declarative Pipeline jobs have more than one stage, you will need to define those stages in an `options` block. Otherwise, when and if the first stage passes, GitLab will merge the change. For example, if you have three stages named build, test, and deploy:
```
options {
gitLabConnection('<your-gitlab-connection-name')
gitlabBuilds(builds: ['build', 'test', 'deploy'])
}
```
If you want to configure any of the optional job triggers that the plugin supports in a Declarative build, use a `triggers` block. The full list of configurable trigger options is as follows:
```
triggers {
gitlab(
triggerOnPush: false,
triggerOnMergeRequest: true, triggerOpenMergeRequestOnPush: "never",
triggerOnNoteRequest: true,
noteRegex: "Jenkins please retry a build",
skipWorkInProgressMergeRequest: true,
ciSkip: false,
setBuildDescription: true,
addNoteOnMergeRequest: true,
addCiMessage: true,
addVoteOnMergeRequest: true,
acceptMergeRequestOnSuccess: false,
branchFilterType: "NameBasedFilter",
includeBranchesSpec: "release/qat",
excludeBranchesSpec: "",
secretToken: "abcdefghijklmnopqrstuvwxyz0123456789ABCDEF")
}
```
### Matrix/Multi-configuration jobs
**The Jenkins Matrix/Multi-configuration job type is not supported.**
This plugin can be used on Matrix/Multi-configuration jobs together with the [Flexible Publish](https://plugins.jenkins.io/flexible-publish) plugin which allows to run publishers after all axis jobs are done.
To use GitLab with Flexible Publish, configure the *Post-build Actions* as follows:
1. Add a *Flexible publish* action
2. In the *Flexible publish* section:
1. *Add conditional action*
2. In the *Conditional action* section:
1. Set *Run?* to *Never*
2. Select *Condition for Matrix Aggregation*
3. Set *Run on Parent?* to *Always*
4. Add GitLab actions as required
## Gitlab Configuration
GitLab 8.1 has implemented a commit status api, you need an extra post-build step to support commit status.
GitLab 8.1 has implemented a commit status API, you need an extra post-build step to support commit status.
* In GitLab go to your repository's project *Settings*
* Click on *Web Hooks*
@ -235,13 +330,13 @@ In order to build when a new tag is pushed:
* In the ``GitLab server`` add ``Tag push events`` to the ``Web Hook``
* In the ``Jenkins`` under the ``Source Code Management`` section:
* select ``Advance...`` and add ``+refs/tags/*:refs/remotes/origin/tags/*`` as ``Refspec``
* you can also use ``Branch Specifier`` to specify which tag need to be built (exampple ``refs/tags/${TAGNAME}``)
* you can also use ``Branch Specifier`` to specify which tag need to be built (example ``refs/tags/${TAGNAME}``)
# Send message on complete of a build
1. In the *Post build steps* section:
1. Click *Add post build step*
2. Click *Add note with build status on GitLab merge requests* and save build settings (You enabled autoumatically sending default message on result of a build)
2. Click *Add note with build status on GitLab merge requests* and save build settings (You enabled automatically sending default message on result of a build)
2. If you want make custom message on result of a build:
1. In *Add note with build status on GitLab merge requests* section click to *Custom message on success/failure/abort*
@ -267,7 +362,11 @@ These include:
* gitlabMergeRequestDescription
* gitlabMergeRequestId
* gitlabMergeRequestIid
* gitlabMergeRequestState
* gitlabMergedByUser
* gitlabMergeRequestAssignee
* gitlabMergeRequestLastCommit
* gitlabMergeRequestTargetProjectId
* gitlabTargetBranch
* gitlabTargetRepoName
* gitlabTargetNamespace
@ -298,38 +397,10 @@ Before submitting your change make sure that:
* you updated the README
* you have used findbugs to see if you haven't introduced any new warnings.
# Quick test environment setup using Docker
# Testing With Docker
In order to test the plugin on different versions of `GitLab` and `Jenkins` you may want to use `Docker` containers.
A example docker-compose file is available at `gitlab-plugin/src/docker` which allows to set up instances of the latest `GitLab` and `Jenkins` versions.
To start the containers, run below command from the `docker` folder:
```bash
docker-compose up -d
```
## Access GitLab
To access `GitLab`, point your browser to `http://172.17.0.1:10080` and set a password for the `root` user account.
For more information on the supported `GitLab` versions and how to configure the containers, visit Sameer Naik's github page at https://github.com/sameersbn/docker-gitlab.
## Access Jenkins
To see `Jenkins`, point your browser to `http://localhost:8080`.
For more information on the supported `Jenkins` tags and how to configure the containers, visit https://hub.docker.com/r/library/jenkins.
See https://github.com/jenkinsci/gitlab-plugin/tree/master/src/docker/README.md
# Release Workflow
GitLab-Plugin admins should adhere to the following rules when releasing a new plugin version:
* Ensure codestyle conformity
* Run unit tests
* Run manual tests on both, oldest and latest GitLab versions
* Update documentation
* Create change log
* Create release tag
* Create release notes (on github)
To perform a full plugin release, maintainers can run ``mvn release:prepare release:perform`` To release a snapshot, e.g. with a bug fix for users to test, just run ``mvn deploy``

236
mvnw vendored Executable file
View File

@ -0,0 +1,236 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven2 Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
#
# Look for the Apple JDKs first to preserve the existing behaviour, and then look
# for the new JDKs provided by Oracle.
#
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
#
# Apple JDKs
#
export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
fi
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
#
# Apple JDKs
#
export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
fi
if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
#
# Oracle JDKs
#
export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
fi
if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
#
# Apple JDKs
#
export JAVA_HOME=`/usr/libexec/java_home`
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Migwn, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
# TODO classpath?
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
local basedir=$(pwd)
local wdir=$(pwd)
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
wdir=$(cd "$wdir/.."; pwd)
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
# avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in $@
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

146
mvnw.cmd vendored Normal file
View File

@ -0,0 +1,146 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
set MAVEN_CMD_LINE_ARGS=%MAVEN_CONFIG% %*
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR=""%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar""
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
@REM avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in %*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

55
pom.xml
View File

@ -14,7 +14,7 @@
</properties>
<artifactId>gitlab-plugin</artifactId>
<version>1.4.5</version>
<version>1.5.3-SNAPSHOT</version>
<name>GitLab Plugin</name>
<url>https://wiki.jenkins-ci.org/display/JENKINS/GitLab+Plugin</url>
<packaging>hpi</packaging>
@ -27,20 +27,15 @@
</licenses>
<developers>
<developer>
<id>coderhugo</id>
<name>Robin Müller</name>
<email>coderhugo@googlemail.com</email>
</developer>
<developer>
<id>owenmehegan</id>
<name>Owen Mehegan</name>
<email>owen@nerdnetworks.org</email>
</developer>
<developer>
<id>omorillo</id>
<name>Oscar Salvador Morillo Victoria</name>
<email>omorillovictoria@googlemail.com</email>
<id>milena</id>
<name>Milena Reichel</name>
<email>milena.reichel@gmail.com</email>
</developer>
</developers>
@ -61,7 +56,7 @@
<connection>scm:git:ssh://github.com:jenkinsci/gitlab-plugin.git</connection>
<developerConnection>scm:git:git@github.com:jenkinsci/gitlab-plugin.git</developerConnection>
<url>https://github.com/jenkinsci/gitlab-plugin</url>
<tag>gitlab-plugin-1.4.5</tag>
<tag>HEAD</tag>
</scm>
<!-- get every artifact through repo.jenkins-ci.org, which proxies all the artifacts that we need -->
<repositories>
@ -84,6 +79,7 @@
</pluginRepositories>
<build>
<defaultGoal>clean install</defaultGoal>
<directory>${project.basedir}/target</directory>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<finalName>${project.artifactId}-${project.version}</finalName>
@ -192,6 +188,17 @@
<artifactId>org.eclipse.jgit</artifactId>
<version>3.5.2.201411120430-r</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>matrix-project</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>display-url-api</artifactId>
<version>1.1.1</version>
</dependency>
<!-- REST client dependencies -->
<dependency>
@ -252,6 +259,18 @@
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
@ -288,7 +307,8 @@
<profile>
<id>integration-test</id>
<properties>
<gitlab.version>8.5.8</gitlab.version>
<gitlab.version>8.17.4</gitlab.version>
<postgres.version>9.5-1</postgres.version>
</properties>
<build>
<plugins>
@ -359,7 +379,7 @@
<verbose>true</verbose>
<images>
<image>
<name>sameersbn/postgresql:9.4-15</name>
<name>sameersbn/postgresql:${postgres.version}</name>
<alias>it-gitlab-postgres</alias>
<run>
<namingStrategy>alias</namingStrategy>
@ -367,6 +387,7 @@
<DB_NAME>gitlabhq_production</DB_NAME>
<DB_USER>gitlab</DB_USER>
<DB_PASS>password</DB_PASS>
<DB_EXTENSION>pg_trgm</DB_EXTENSION>
</env>
<ports>
<port>${postgres.port}:5432</port>
@ -382,7 +403,9 @@
</image>
<image>
<name>sameersbn/gitlab:${gitlab.version}</name>
<alias>it-gitlab-gitlab</alias>
<run>
<namingStrategy>alias</namingStrategy>
<links>
<link>it-gitlab-postgres:postgresql</link>
<link>it-gitlab-redis:redisio</link>
@ -392,9 +415,15 @@
<port>${gitlab.ssh.port}:22</port>
</ports>
<env>
<DEBUG>false</DEBUG>
<TZ>Asia/Kolkata</TZ>
<GITLAB_TIMEZONE>Kolkata</GITLAB_TIMEZONE>
<GITLAB_PORT>${gitlab.http.port}</GITLAB_PORT>
<GITLAB_SSH_PORT>${gitlab.ssh.port}</GITLAB_SSH_PORT>
<GITLAB_SECRETS_DB_KEY_BASE>abc123</GITLAB_SECRETS_DB_KEY_BASE>
<GITLAB_SECRETS_DB_KEY_BASE>long-and-random-alpha-numeric-string</GITLAB_SECRETS_DB_KEY_BASE>
<GITLAB_SECRETS_SECRET_KEY_BASE>long-and-random-alphanumeric-string</GITLAB_SECRETS_SECRET_KEY_BASE>
<GITLAB_SECRETS_OTP_KEY_BASE>long-and-random-alpha-numeric-string</GITLAB_SECRETS_OTP_KEY_BASE>
<GITLAB_HOST>172.17.0.1</GITLAB_HOST>
</env>
<wait>
<http>

26
src/docker/README.md Normal file
View File

@ -0,0 +1,26 @@
# Quick test environment setup using Docker
In order to test the plugin on different versions of `GitLab` and `Jenkins` you may want to use `Docker` containers.
A example docker-compose file is available at `gitlab-plugin/src/docker` which allows to set up instances of the latest `GitLab` and `Jenkins` versions.
If they don't already exist, create the following directories and make sure the user that Docker is running as owns them:
* /srv/docker/gitlab/postgresql
* /srv/docker/gitlab/gitlab
* /srv/docker/gitlab/redis
* /srv/docker/jenkins
To start the containers, run `docker-compose up -d` from the `docker` folder. If you have problems accessing the services in the containers, run `docker-compose up` by itself to see output from the services as they start.
## Access GitLab
To access `GitLab`, point your browser to `http://localhost:10080` and set a password for the `root` user account. Then create a user for Jenkins, impersonate that user, get its API key, set up test repos, etc. When creating webhooks to trigger Jenkins jobs, use `http://jenkins:8080` as the base URL.
For more information on the supported `GitLab` versions and how to configure the containers, visit Sameer Naik's github page at https://github.com/sameersbn/docker-gitlab.
## Access Jenkins
To see `Jenkins`, point your browser to `http://localhost:8080`. Jenkins will be able to access GitLab at `http://gitlab`
For more information on the supported `Jenkins` tags and how to configure the containers, visit https://hub.docker.com/r/jenkins/jenkins/.

View File

@ -1,78 +1,98 @@
postgresql:
container_name: docker-postgresql
restart: always
image: sameersbn/postgresql:9.5-1
environment:
- DB_USER=gitlab
- DB_PASS=password
- DB_NAME=gitlabhq_production
- DB_EXTENSION=pg_trgm
volumes:
- /srv/docker/gitlab/postgresql:/var/lib/postgresql
gitlab:
container_name: docker-gitlab
restart: always
image: sameersbn/gitlab:8.11.5
links:
- redis:redisio
- postgresql:postgresql
ports:
- "10080:80"
- "10022:22"
environment:
- DEBUG=false
- TZ=Asia/Kolkata
- GITLAB_TIMEZONE=Kolkata
version: '2'
- GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alpha-numeric-string
- GITLAB_SECRETS_SECRET_KEY_BASE=long-and-random-alphanumeric-string
- GITLAB_SECRETS_OTP_KEY_BASE=long-and-random-alpha-numeric-string
services:
postgresql:
restart: always
image: sameersbn/postgresql:9.5-1
ports:
- "5432:5432"
environment:
- DB_USER=gitlab
- DB_PASS=password
- DB_NAME=gitlabhq_production
- DB_EXTENSION=pg_trgm
volumes:
- /srv/docker/gitlab/postgresql:/var/lib/postgresql
- GITLAB_HOST=172.17.0.1
- GITLAB_PORT=10080
- GITLAB_SSH_PORT=10022
- GITLAB_RELATIVE_URL_ROOT=
gitlab:
restart: always
image: sameersbn/gitlab:9.4.1
depends_on:
- redis
- postgresql
ports:
- "10080:80"
- "10022:22"
environment:
- DEBUG=false
- GITLAB_NOTIFY_ON_BROKEN_BUILDS=true
- GITLAB_NOTIFY_PUSHER=false
- DB_ADAPTER=postgresql
- DB_HOST=postgresql
- DB_PORT=5432
- DB_USER=gitlab
- DB_PASS=password
- DB_NAME=gitlabhq_production
- GITLAB_EMAIL=notifications@example.com
- GITLAB_EMAIL_REPLY_TO=noreply@example.com
- GITLAB_INCOMING_EMAIL_ADDRESS=reply@example.com
- REDIS_HOST=redis
- REDIS_PORT=6379
- GITLAB_BACKUP_SCHEDULE=daily
- GITLAB_BACKUP_TIME=01:00
- TZ=Asia/Kolkata
- GITLAB_TIMEZONE=Kolkata
- SMTP_ENABLED=false
- SMTP_DOMAIN=www.example.com
- SMTP_HOST=smtp.gmail.com
- SMTP_PORT=587
- SMTP_USER=mailer@example.com
- SMTP_PASS=password
- SMTP_STARTTLS=true
- SMTP_AUTHENTICATION=login
- GITLAB_HTTPS=false
- SSL_SELF_SIGNED=false
- IMAP_ENABLED=false
- IMAP_HOST=imap.gmail.com
- IMAP_PORT=993
- IMAP_USER=mailer@example.com
- IMAP_PASS=password
- IMAP_SSL=true
- IMAP_STARTTLS=false
volumes:
- /srv/docker/gitlab/gitlab:/home/git/data
redis:
container_name: docker-redis
restart: always
image: sameersbn/redis:latest
volumes:
- /srv/docker/gitlab/redis:/var/lib/redis
jenkins:
container_name: docker-jenkins
restart: always
image: jenkins:2.7.3
ports:
- "8080:8080"
- "50000:50000"
volumes:
- /srv/docker/jenkins:/var/jenkins_home
- GITLAB_HOST=localhost
- GITLAB_PORT=10080
- GITLAB_SSH_PORT=10022
- GITLAB_RELATIVE_URL_ROOT=
- GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alpha-numeric-string
- GITLAB_SECRETS_SECRET_KEY_BASE=long-and-random-alphanumeric-string
- GITLAB_SECRETS_OTP_KEY_BASE=long-and-random-alpha-numeric-string
- GITLAB_ROOT_PASSWORD=
- GITLAB_ROOT_EMAIL=
- GITLAB_NOTIFY_ON_BROKEN_BUILDS=true
- GITLAB_NOTIFY_PUSHER=false
- GITLAB_EMAIL=notifications@example.com
- GITLAB_EMAIL_REPLY_TO=noreply@example.com
- GITLAB_INCOMING_EMAIL_ADDRESS=reply@example.com
- GITLAB_BACKUP_SCHEDULE=daily
- GITLAB_BACKUP_TIME=01:00
- SMTP_ENABLED=false
- SMTP_DOMAIN=www.example.com
- SMTP_HOST=smtp.gmail.com
- SMTP_PORT=587
- SMTP_USER=mailer@example.com
- SMTP_PASS=password
- SMTP_STARTTLS=true
- SMTP_AUTHENTICATION=login
- IMAP_ENABLED=false
- IMAP_HOST=imap.gmail.com
- IMAP_PORT=993
- IMAP_USER=mailer@example.com
- IMAP_PASS=password
- IMAP_SSL=true
- IMAP_STARTTLS=false
volumes:
- /srv/docker/gitlab/gitlab:/home/git/data
redis:
restart: always
image: sameersbn/redis:latest
volumes:
- /srv/docker/gitlab/redis:/var/lib/redis
jenkins:
restart: always
image: jenkins/jenkins:lts
ports:
- "8080:8080"
- "50000:50000"
volumes:
- /srv/docker/jenkins:/var/jenkins_home

View File

@ -1,10 +1,12 @@
package com.dabsquared.gitlabjenkins;
import com.dabsquared.gitlabjenkins.connection.GitLabConnection;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionConfig;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.MergeRequestHook;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.NoteHook;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.PipelineHook;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.PushHook;
import com.dabsquared.gitlabjenkins.publisher.GitLabAcceptMergeRequestPublisher;
import com.dabsquared.gitlabjenkins.publisher.GitLabCommitStatusPublisher;
@ -20,6 +22,7 @@ import com.dabsquared.gitlabjenkins.trigger.filter.MergeRequestLabelFilterConfig
import com.dabsquared.gitlabjenkins.trigger.filter.MergeRequestLabelFilterFactory;
import com.dabsquared.gitlabjenkins.trigger.handler.merge.MergeRequestHookTriggerHandler;
import com.dabsquared.gitlabjenkins.trigger.handler.note.NoteHookTriggerHandler;
import com.dabsquared.gitlabjenkins.trigger.handler.pipeline.PipelineHookTriggerHandler;
import com.dabsquared.gitlabjenkins.trigger.handler.push.PushHookTriggerHandler;
import com.dabsquared.gitlabjenkins.trigger.label.ProjectLabelsProvider;
import com.dabsquared.gitlabjenkins.webhook.GitLabWebHook;
@ -48,6 +51,7 @@ import org.jenkinsci.Symbol;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
@ -60,6 +64,7 @@ import java.security.SecureRandom;
import static com.dabsquared.gitlabjenkins.trigger.filter.BranchFilterConfig.BranchFilterConfigBuilder.branchFilterConfig;
import static com.dabsquared.gitlabjenkins.trigger.handler.merge.MergeRequestHookTriggerHandlerFactory.newMergeRequestHookTriggerHandler;
import static com.dabsquared.gitlabjenkins.trigger.handler.note.NoteHookTriggerHandlerFactory.newNoteHookTriggerHandler;
import static com.dabsquared.gitlabjenkins.trigger.handler.pipeline.PipelineHookTriggerHandlerFactory.newPipelineHookTriggerHandler;
import static com.dabsquared.gitlabjenkins.trigger.handler.push.PushHookTriggerHandlerFactory.newPushHookTriggerHandler;
@ -74,9 +79,13 @@ public class GitLabPushTrigger extends Trigger<Job<?, ?>> {
private boolean triggerOnPush = true;
private boolean triggerOnMergeRequest = true;
private final TriggerOpenMergeRequest triggerOpenMergeRequestOnPush;
private boolean triggerOnPipelineEvent = false;
private boolean triggerOnAcceptedMergeRequest = false;
private boolean triggerOnClosedMergeRequest = false;
private boolean triggerOnApprovedMergeRequest = false;
private TriggerOpenMergeRequest triggerOpenMergeRequestOnPush;
private boolean triggerOnNoteRequest = true;
private final String noteRegex;
private String noteRegex = "";
private boolean ciSkip = true;
private boolean skipWorkInProgressMergeRequest;
private boolean setBuildDescription = true;
@ -89,30 +98,38 @@ public class GitLabPushTrigger extends Trigger<Job<?, ?>> {
private String includeBranchesSpec;
private String excludeBranchesSpec;
private String targetBranchRegex;
private final MergeRequestLabelFilterConfig mergeRequestLabelFilterConfig;
private MergeRequestLabelFilterConfig mergeRequestLabelFilterConfig;
private volatile Secret secretToken;
private transient BranchFilter branchFilter;
private transient PushHookTriggerHandler pushHookTriggerHandler;
private transient MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler;
private transient NoteHookTriggerHandler noteHookTriggerHandler;
private transient PipelineHookTriggerHandler pipelineTriggerHandler;
private transient boolean acceptMergeRequestOnSuccess;
private transient MergeRequestLabelFilter mergeRequestLabelFilter;
@DataBoundConstructor
/**
* @deprecated use {@link #GitLabPushTrigger()} with setters to configure an instance of this class.
*/
@Deprecated
@GeneratePojoBuilder(intoPackage = "*.builder.generated", withFactoryMethod = "*")
public GitLabPushTrigger(boolean triggerOnPush, boolean triggerOnMergeRequest, TriggerOpenMergeRequest triggerOpenMergeRequestOnPush,
boolean triggerOnNoteRequest, String noteRegex, boolean skipWorkInProgressMergeRequest, boolean ciSkip,
public GitLabPushTrigger(boolean triggerOnPush, boolean triggerOnMergeRequest, boolean triggerOnAcceptedMergeRequest, boolean triggerOnClosedMergeRequest,
TriggerOpenMergeRequest triggerOpenMergeRequestOnPush, boolean triggerOnNoteRequest, String noteRegex,
boolean skipWorkInProgressMergeRequest, boolean ciSkip,
boolean setBuildDescription, boolean addNoteOnMergeRequest, boolean addCiMessage, boolean addVoteOnMergeRequest,
boolean acceptMergeRequestOnSuccess, BranchFilterType branchFilterType,
String includeBranchesSpec, String excludeBranchesSpec, String targetBranchRegex,
MergeRequestLabelFilterConfig mergeRequestLabelFilterConfig, String secretToken) {
MergeRequestLabelFilterConfig mergeRequestLabelFilterConfig, String secretToken, boolean triggerOnPipelineEvent,
boolean triggerOnApprovedMergeRequest) {
this.triggerOnPush = triggerOnPush;
this.triggerOnMergeRequest = triggerOnMergeRequest;
this.triggerOnAcceptedMergeRequest = triggerOnAcceptedMergeRequest;
this.triggerOnClosedMergeRequest = triggerOnClosedMergeRequest;
this.triggerOnNoteRequest = triggerOnNoteRequest;
this.noteRegex = noteRegex;
this.triggerOpenMergeRequestOnPush = triggerOpenMergeRequestOnPush;
this.triggerOnPipelineEvent = triggerOnPipelineEvent;
this.ciSkip = ciSkip;
this.skipWorkInProgressMergeRequest = skipWorkInProgressMergeRequest;
this.setBuildDescription = setBuildDescription;
@ -126,20 +143,26 @@ public class GitLabPushTrigger extends Trigger<Job<?, ?>> {
this.acceptMergeRequestOnSuccess = acceptMergeRequestOnSuccess;
this.mergeRequestLabelFilterConfig = mergeRequestLabelFilterConfig;
this.secretToken = Secret.fromString(secretToken);
this.triggerOnApprovedMergeRequest = triggerOnApprovedMergeRequest;
initializeTriggerHandler();
initializeBranchFilter();
initializeMergeRequestLabelFilter();
}
@DataBoundConstructor
public GitLabPushTrigger() { }
@Initializer(after = InitMilestone.JOB_LOADED)
public static void migrateJobs() throws IOException {
GitLabPushTrigger.DescriptorImpl oldConfig = Trigger.all().get(GitLabPushTrigger.DescriptorImpl.class);
if (!oldConfig.jobsMigrated) {
GitLabConnectionConfig gitLabConfig = (GitLabConnectionConfig) Jenkins.getInstance().getDescriptor(GitLabConnectionConfig.class);
gitLabConfig.getConnections().add(new GitLabConnection(oldConfig.gitlabHostUrl,
gitLabConfig.getConnections().add(new GitLabConnection(
oldConfig.gitlabHostUrl,
oldConfig.gitlabHostUrl,
oldConfig.gitlabApiToken,
"autodetect",
oldConfig.ignoreCertificateErrors,
10,
10));
@ -188,10 +211,20 @@ public class GitLabPushTrigger extends Trigger<Job<?, ?>> {
return triggerOnMergeRequest;
}
public boolean isTriggerOnAcceptedMergeRequest() {
return triggerOnAcceptedMergeRequest;
}
public boolean isTriggerOnClosedMergeRequest() {
return triggerOnClosedMergeRequest;
}
public boolean getTriggerOnNoteRequest() {
return triggerOnNoteRequest;
}
public boolean getTriggerOnPipelineEvent() { return triggerOnPipelineEvent; }
public String getNoteRegex() {
return this.noteRegex == null ? "" : this.noteRegex;
}
@ -236,25 +269,170 @@ public class GitLabPushTrigger extends Trigger<Job<?, ?>> {
return secretToken == null ? null : secretToken.getPlainText();
}
@DataBoundSetter
public void setTriggerOnPush(boolean triggerOnPush) {
this.triggerOnPush = triggerOnPush;
}
@DataBoundSetter
public void setTriggerOnApprovedMergeRequest(boolean triggerOnApprovedMergeRequest) {
this.triggerOnApprovedMergeRequest = triggerOnApprovedMergeRequest;
}
@DataBoundSetter
public void setTriggerOnMergeRequest(boolean triggerOnMergeRequest) {
this.triggerOnMergeRequest = triggerOnMergeRequest;
}
@DataBoundSetter
public void setTriggerOnAcceptedMergeRequest(boolean triggerOnAcceptedMergeRequest) {
this.triggerOnAcceptedMergeRequest = triggerOnAcceptedMergeRequest;
}
@DataBoundSetter
public void setTriggerOnClosedMergeRequest(boolean triggerOnClosedMergeRequest) {
this.triggerOnClosedMergeRequest = triggerOnClosedMergeRequest;
}
@DataBoundSetter
public void setTriggerOpenMergeRequestOnPush(TriggerOpenMergeRequest triggerOpenMergeRequestOnPush) {
this.triggerOpenMergeRequestOnPush = triggerOpenMergeRequestOnPush;
}
@DataBoundSetter
public void setTriggerOnNoteRequest(boolean triggerOnNoteRequest) {
this.triggerOnNoteRequest = triggerOnNoteRequest;
}
@DataBoundSetter
public void setNoteRegex(String noteRegex) {
this.noteRegex = noteRegex;
}
@DataBoundSetter
public void setCiSkip(boolean ciSkip) {
this.ciSkip = ciSkip;
}
@DataBoundSetter
public void setSkipWorkInProgressMergeRequest(boolean skipWorkInProgressMergeRequest) {
this.skipWorkInProgressMergeRequest = skipWorkInProgressMergeRequest;
}
@DataBoundSetter
public void setSetBuildDescription(boolean setBuildDescription) {
this.setBuildDescription = setBuildDescription;
}
@DataBoundSetter
public void setAddNoteOnMergeRequest(boolean addNoteOnMergeRequest) {
this.addNoteOnMergeRequest = addNoteOnMergeRequest;
}
@DataBoundSetter
public void setAddCiMessage(boolean addCiMessage) {
this.addCiMessage = addCiMessage;
}
@DataBoundSetter
public void setAddVoteOnMergeRequest(boolean addVoteOnMergeRequest) {
this.addVoteOnMergeRequest = addVoteOnMergeRequest;
}
@DataBoundSetter
public void setBranchFilterName(String branchFilterName) {
this.branchFilterName = branchFilterName;
}
@DataBoundSetter
public void setBranchFilterType(BranchFilterType branchFilterType) {
this.branchFilterType = branchFilterType;
}
@DataBoundSetter
public void setIncludeBranchesSpec(String includeBranchesSpec) {
this.includeBranchesSpec = includeBranchesSpec;
}
@DataBoundSetter
public void setExcludeBranchesSpec(String excludeBranchesSpec) {
this.excludeBranchesSpec = excludeBranchesSpec;
}
@DataBoundSetter
public void setTargetBranchRegex(String targetBranchRegex) {
this.targetBranchRegex = targetBranchRegex;
}
@DataBoundSetter
public void setMergeRequestLabelFilterConfig(MergeRequestLabelFilterConfig mergeRequestLabelFilterConfig) {
this.mergeRequestLabelFilterConfig = mergeRequestLabelFilterConfig;
}
@DataBoundSetter
public void setSecretToken(String secretToken) {
this.secretToken = Secret.fromString(secretToken);
}
@DataBoundSetter
public void setAcceptMergeRequestOnSuccess(boolean acceptMergeRequestOnSuccess) {
this.acceptMergeRequestOnSuccess = acceptMergeRequestOnSuccess;
}
// executes when the Trigger receives a push request
public void onPost(final PushHook hook) {
if (branchFilter == null) {
initializeBranchFilter();
}
if (mergeRequestLabelFilter == null) {
initializeMergeRequestLabelFilter();
}
if (pushHookTriggerHandler == null) {
initializeTriggerHandler();
}
pushHookTriggerHandler.handle(job, hook, ciSkip, branchFilter, mergeRequestLabelFilter);
}
// executes when the Trigger receives a merge request
public void onPost(final MergeRequestHook hook) {
if (branchFilter == null) {
initializeBranchFilter();
}
if (mergeRequestLabelFilter == null) {
initializeMergeRequestLabelFilter();
}
if (mergeRequestHookTriggerHandler == null) {
initializeTriggerHandler();
}
mergeRequestHookTriggerHandler.handle(job, hook, ciSkip, branchFilter, mergeRequestLabelFilter);
}
// executes when the Trigger receives a note request
public void onPost(final NoteHook hook) {
if (branchFilter == null) {
initializeBranchFilter();
}
if (mergeRequestLabelFilter == null) {
initializeMergeRequestLabelFilter();
}
if (noteHookTriggerHandler == null) {
initializeTriggerHandler();
}
noteHookTriggerHandler.handle(job, hook, ciSkip, branchFilter, mergeRequestLabelFilter);
}
// executes when the Trigger receives a pipeline event
public void onPost(final PipelineHook hook) {
pipelineTriggerHandler.handle(job, hook, ciSkip, branchFilter, mergeRequestLabelFilter);
}
private void initializeTriggerHandler() {
mergeRequestHookTriggerHandler = newMergeRequestHookTriggerHandler(triggerOnMergeRequest, triggerOpenMergeRequestOnPush, skipWorkInProgressMergeRequest);
mergeRequestHookTriggerHandler = newMergeRequestHookTriggerHandler(triggerOnMergeRequest,
triggerOnAcceptedMergeRequest, triggerOnClosedMergeRequest, triggerOpenMergeRequestOnPush,
skipWorkInProgressMergeRequest, triggerOnApprovedMergeRequest);
noteHookTriggerHandler = newNoteHookTriggerHandler(triggerOnNoteRequest, noteRegex);
pushHookTriggerHandler = newPushHookTriggerHandler(triggerOnPush, triggerOpenMergeRequestOnPush, skipWorkInProgressMergeRequest);
pipelineTriggerHandler = newPipelineHookTriggerHandler(triggerOnPipelineEvent);
}
private void initializeBranchFilter() {
@ -404,5 +582,9 @@ public class GitLabPushTrigger extends Trigger<Job<?, ?>> {
String secretToken = Util.toHexString(random);
response.setHeader("script", "document.getElementById('secretToken').value='" + secretToken + "'");
}
public void doClearSecretToken(@AncestorInPath final Job<?, ?> project, StaplerResponse response) {;
response.setHeader("script", "document.getElementById('secretToken').value=''");
}
}
}

View File

@ -1,5 +1,6 @@
package com.dabsquared.gitlabjenkins.cause;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
import hudson.markup.EscapedMarkupFormatter;
import jenkins.model.Jenkins;
import net.karneim.pojobuilder.GeneratePojoBuilder;
@ -8,10 +9,7 @@ import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.*;
import static com.google.common.base.Preconditions.checkNotNull;
@ -36,6 +34,10 @@ public final class CauseData {
private final String mergeRequestDescription;
private final Integer mergeRequestId;
private final Integer mergeRequestIid;
private final String mergeRequestState;
private final String mergedByUser;
private final String mergeRequestAssignee;
private final Integer mergeRequestTargetProjectId;
private final String targetBranch;
private final String targetRepoName;
private final String targetNamespace;
@ -47,14 +49,24 @@ public final class CauseData {
private final String lastCommit;
private final String targetProjectUrl;
private final String triggerPhrase;
private final String ref;
private final String beforeSha;
private final String isTag;
private final String sha;
private final String status;
private final String stages;
private final String createdAt;
private final String finishedAt;
private final String buildDuration;
@GeneratePojoBuilder(withFactoryMethod = "*")
CauseData(ActionType actionType, Integer sourceProjectId, Integer targetProjectId, String branch, String sourceBranch, String userName,
String userEmail, String sourceRepoHomepage, String sourceRepoName, String sourceNamespace, String sourceRepoUrl,
String sourceRepoSshUrl, String sourceRepoHttpUrl, String mergeRequestTitle, String mergeRequestDescription, Integer mergeRequestId,
Integer mergeRequestIid, String targetBranch, String targetRepoName, String targetNamespace, String targetRepoSshUrl,
Integer mergeRequestIid, Integer mergeRequestTargetProjectId, String targetBranch, String targetRepoName, String targetNamespace, String targetRepoSshUrl,
String targetRepoHttpUrl, String triggeredByUser, String before, String after, String lastCommit, String targetProjectUrl,
String triggerPhrase) {
String triggerPhrase, String mergeRequestState, String mergedByUser, String mergeRequestAssignee, String ref, String isTag,
String sha, String beforeSha, String status, String stages, String createdAt, String finishedAt, String buildDuration) {
this.actionType = checkNotNull(actionType, "actionType must not be null.");
this.sourceProjectId = checkNotNull(sourceProjectId, "sourceProjectId must not be null.");
this.targetProjectId = checkNotNull(targetProjectId, "targetProjectId must not be null.");
@ -72,6 +84,10 @@ public final class CauseData {
this.mergeRequestDescription = mergeRequestDescription == null ? "" : mergeRequestDescription;
this.mergeRequestId = mergeRequestId;
this.mergeRequestIid = mergeRequestIid;
this.mergeRequestState = mergeRequestState == null ? "" : mergeRequestState;
this.mergedByUser = mergedByUser == null ? "" : mergedByUser;
this.mergeRequestAssignee = mergeRequestAssignee == null ? "" : mergeRequestAssignee;
this.mergeRequestTargetProjectId = mergeRequestTargetProjectId;
this.targetBranch = checkNotNull(targetBranch, "targetBranch must not be null.");
this.targetRepoName = checkNotNull(targetRepoName, "targetRepoName must not be null.");
this.targetNamespace = checkNotNull(targetNamespace, "targetNamespace must not be null.");
@ -83,6 +99,15 @@ public final class CauseData {
this.lastCommit = checkNotNull(lastCommit, "lastCommit must not be null");
this.targetProjectUrl = targetProjectUrl;
this.triggerPhrase = triggerPhrase;
this.ref = ref;
this.isTag = isTag;
this.sha = sha;
this.beforeSha = beforeSha;
this.status = status;
this.stages = stages;
this.createdAt = createdAt;
this.finishedAt = finishedAt;
this.buildDuration = buildDuration;
}
public Map<String, String> getBuildVariables() {
@ -102,7 +127,11 @@ public final class CauseData {
variables.put("gitlabMergeRequestDescription", mergeRequestDescription);
variables.put("gitlabMergeRequestId", mergeRequestId == null ? "" : mergeRequestId.toString());
variables.put("gitlabMergeRequestIid", mergeRequestIid == null ? "" : mergeRequestIid.toString());
variables.put("gitlabMergeRequestTargetProjectId", mergeRequestTargetProjectId == null ? "" : mergeRequestTargetProjectId.toString());
variables.put("gitlabMergeRequestLastCommit", lastCommit);
variables.putIfNotNull("gitlabMergeRequestState", mergeRequestState);
variables.putIfNotNull("gitlabMergedByUser", mergedByUser);
variables.putIfNotNull("gitlabMergeRequestAssignee", mergeRequestAssignee);
variables.put("gitlabTargetBranch", targetBranch);
variables.put("gitlabTargetRepoName", targetRepoName);
variables.put("gitlabTargetNamespace", targetNamespace);
@ -110,7 +139,16 @@ public final class CauseData {
variables.put("gitlabTargetRepoHttpUrl", targetRepoHttpUrl);
variables.put("gitlabBefore", before);
variables.put("gitlabAfter", after);
variables.pufIfNotNull("gitlabTriggerPhrase", triggerPhrase);
variables.put("ref", ref);
variables.put("beforeSha", beforeSha);
variables.put("isTag", isTag);
variables.put("sha", sha);
variables.put("status", status);
variables.put("stages", stages);
variables.put("createdAt", createdAt);
variables.put("finishedAt", finishedAt);
variables.put("duration", buildDuration);
variables.putIfNotNull("gitlabTriggerPhrase", triggerPhrase);
return variables;
}
@ -182,6 +220,10 @@ public final class CauseData {
return mergeRequestIid;
}
public Integer getMergeRequestTargetProjectId() {
return mergeRequestTargetProjectId;
}
public String getTargetBranch() {
return targetBranch;
}
@ -222,10 +264,50 @@ public final class CauseData {
return targetProjectUrl;
}
public String getRef() { return ref; }
public String getIsTag() { return isTag; }
public String getSha() { return sha; }
public String getBeforeSha() {return beforeSha; }
public String getStatus() { return status; }
public String getStages() { return stages; }
public String getCreatedAt() { return createdAt; }
public String getFinishedAt() { return finishedAt; }
public String getBuildDuration() { return buildDuration; }
String getShortDescription() {
return actionType.getShortDescription(this);
}
public String getMergeRequestState() {
return mergeRequestState;
}
public String getMergedByUser() {
return mergedByUser;
}
public String getMergeRequestAssignee() {
return mergeRequestAssignee;
}
public MergeRequest getMergeRequest() {
if (mergeRequestId == null) {
return null;
}
return new MergeRequest(mergeRequestId, mergeRequestIid, sourceBranch, targetBranch, mergeRequestTitle,
sourceProjectId, targetProjectId, mergeRequestDescription, mergeRequestState);
}
@Override
public boolean equals(Object o) {
if (this == o) {
@ -253,6 +335,10 @@ public final class CauseData {
.append(mergeRequestDescription, causeData.mergeRequestDescription)
.append(mergeRequestId, causeData.mergeRequestId)
.append(mergeRequestIid, causeData.mergeRequestIid)
.append(mergeRequestState, causeData.mergeRequestState)
.append(mergedByUser, causeData.mergedByUser)
.append(mergeRequestAssignee, causeData.mergeRequestAssignee)
.append(mergeRequestTargetProjectId, causeData.mergeRequestTargetProjectId)
.append(targetBranch, causeData.targetBranch)
.append(targetRepoName, causeData.targetRepoName)
.append(targetNamespace, causeData.targetNamespace)
@ -263,6 +349,15 @@ public final class CauseData {
.append(after, causeData.after)
.append(lastCommit, causeData.lastCommit)
.append(targetProjectUrl, causeData.targetProjectUrl)
.append(ref, causeData.getRef())
.append(isTag, causeData.getIsTag())
.append(sha, causeData.getSha())
.append(beforeSha, causeData.getBeforeSha())
.append(status, causeData.getStatus())
.append(stages, causeData.getStages())
.append(createdAt, causeData.getCreatedAt())
.append(finishedAt, causeData.getFinishedAt())
.append(buildDuration, causeData.getBuildDuration())
.isEquals();
}
@ -286,6 +381,10 @@ public final class CauseData {
.append(mergeRequestDescription)
.append(mergeRequestId)
.append(mergeRequestIid)
.append(mergeRequestState)
.append(mergedByUser)
.append(mergeRequestAssignee)
.append(mergeRequestTargetProjectId)
.append(targetBranch)
.append(targetRepoName)
.append(targetNamespace)
@ -296,6 +395,15 @@ public final class CauseData {
.append(after)
.append(lastCommit)
.append(targetProjectUrl)
.append(ref)
.append(isTag)
.append(sha)
.append(beforeSha)
.append(status)
.append(stages)
.append(createdAt)
.append(finishedAt)
.append(buildDuration)
.toHashCode();
}
@ -319,6 +427,10 @@ public final class CauseData {
.append("mergeRequestDescription", mergeRequestDescription)
.append("mergeRequestId", mergeRequestId)
.append("mergeRequestIid", mergeRequestIid)
.append("mergeRequestState", mergeRequestState)
.append("mergedByUser", mergedByUser)
.append("mergeRequestAssignee", mergeRequestAssignee)
.append("mergeRequestTargetProjectId", mergeRequestTargetProjectId)
.append("targetBranch", targetBranch)
.append("targetRepoName", targetRepoName)
.append("targetNamespace", targetNamespace)
@ -329,6 +441,15 @@ public final class CauseData {
.append("after", after)
.append("lastCommit", lastCommit)
.append("targetProjectUrl", targetProjectUrl)
.append("ref", ref)
.append("isTag", isTag)
.append("sha", sha)
.append("beforeSha", beforeSha)
.append("status", status)
.append("stages", stages)
.append("createdAt", createdAt)
.append("finishedAt", finishedAt)
.append("duration", buildDuration)
.toString();
}
@ -336,12 +457,12 @@ public final class CauseData {
PUSH {
@Override
String getShortDescription(CauseData data) {
String pushedBy = data.getTriggeredByUser();
if (pushedBy == null) {
return Messages.GitLabWebHookCause_ShortDescription_PushHook_noUser();
} else {
return Messages.GitLabWebHookCause_ShortDescription_PushHook(pushedBy);
}
return getShortDescriptionPush(data);
}
}, TAG_PUSH {
@Override
String getShortDescription(CauseData data) {
return getShortDescriptionPush(data);
}
}, MERGE {
@Override
@ -365,19 +486,38 @@ public final class CauseData {
String forkNamespace = StringUtils.equals(data.getSourceNamespace(), data.getTargetBranch()) ? "" : data.getSourceNamespace() + "/";
if (Jenkins.getActiveInstance().getMarkupFormatter() instanceof EscapedMarkupFormatter || data.getTargetProjectUrl() == null) {
return Messages.GitLabWebHookCause_ShortDescription_NoteHook_plain(triggeredBy,
String.valueOf(data.getMergeRequestIid()),
forkNamespace + data.getSourceBranch(),
data.getTargetBranch());
String.valueOf(data.getMergeRequestIid()),
forkNamespace + data.getSourceBranch(),
data.getTargetBranch());
} else {
return Messages.GitLabWebHookCause_ShortDescription_NoteHook_html(triggeredBy,
String.valueOf(data.getMergeRequestIid()),
forkNamespace + data.getSourceBranch(),
data.getTargetBranch(),
data.getTargetProjectUrl());
String.valueOf(data.getMergeRequestIid()),
forkNamespace + data.getSourceBranch(),
data.getTargetBranch(),
data.getTargetProjectUrl());
}
}
}, PIPELINE {
@Override
String getShortDescription(CauseData data) {
String getStatus = data.getStatus();
if (getStatus == null) {
return Messages.GitLabWebHookCause_ShortDescription_PipelineHook_noStatus();
} else {
return Messages.GitLabWebHookCause_ShortDescription_PipelineHook(getStatus);
}
}
};
private static String getShortDescriptionPush(CauseData data) {
String pushedBy = data.getTriggeredByUser();
if (pushedBy == null) {
return Messages.GitLabWebHookCause_ShortDescription_PushHook_noUser();
} else {
return Messages.GitLabWebHookCause_ShortDescription_PushHook(pushedBy);
}
}
abstract String getShortDescription(CauseData data);
}
@ -385,7 +525,7 @@ public final class CauseData {
private final Map<K, V> map;
public MapWrapper(Map<K, V> map) {
MapWrapper(Map<K, V> map) {
this.map = map;
}
@ -399,7 +539,7 @@ public final class CauseData {
return map.entrySet();
}
public void pufIfNotNull(K key, V value) {
void putIfNotNull(K key, V value) {
if (value != null) {
map.put(key, value);
}

View File

@ -1,39 +1,83 @@
package com.dabsquared.gitlabjenkins.connection;
import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.CredentialsStore;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClientBuilder;
import com.dabsquared.gitlabjenkins.gitlab.api.impl.AutodetectGitLabClientBuilder;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.model.Item;
import hudson.security.ACL;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials;
import static com.dabsquared.gitlabjenkins.gitlab.api.GitLabClientBuilder.getGitLabClientBuilderById;
/**
* @author Robin Müller
*/
public class GitLabConnection {
private final String name;
private final String url;
private transient String apiToken;
// TODO make final when migration code gets removed
private String apiTokenId;
private GitLabClientBuilder clientBuilder;
private final boolean ignoreCertificateErrors;
private final Integer connectionTimeout;
private final Integer readTimeout;
private transient GitLabClient apiCache;
public GitLabConnection(String name, String url, String apiTokenId, boolean ignoreCertificateErrors, Integer connectionTimeout, Integer readTimeout) {
this(
name,
url,
apiTokenId,
new AutodetectGitLabClientBuilder(),
ignoreCertificateErrors,
connectionTimeout,
readTimeout
);
}
@DataBoundConstructor
public GitLabConnection(String name, String url, String apiTokenId, boolean ignoreCertificateErrors, Integer connectionTimeout, Integer readTimeout) {
public GitLabConnection(String name, String url, String apiTokenId, String clientBuilderId, boolean ignoreCertificateErrors, Integer connectionTimeout, Integer readTimeout) {
this(
name,
url,
apiTokenId,
getGitLabClientBuilderById(clientBuilderId),
ignoreCertificateErrors,
connectionTimeout,
readTimeout
);
}
@Restricted(NoExternalUse.class)
public GitLabConnection(String name, String url, String apiTokenId, GitLabClientBuilder clientBuilder, boolean ignoreCertificateErrors, Integer connectionTimeout, Integer readTimeout) {
this.name = name;
this.url = url;
this.apiTokenId = apiTokenId;
this.clientBuilder = clientBuilder;
this.ignoreCertificateErrors = ignoreCertificateErrors;
this.connectionTimeout = connectionTimeout;
this.readTimeout = readTimeout;
@ -51,6 +95,10 @@ public class GitLabConnection {
return apiTokenId;
}
public String getClientBuilderId() {
return clientBuilder.id();
}
public boolean isIgnoreCertificateErrors() {
return ignoreCertificateErrors;
}
@ -63,10 +111,38 @@ public class GitLabConnection {
return readTimeout;
}
public GitLabClient getClient() {
if (apiCache == null) {
apiCache = clientBuilder.buildClient(url, getApiToken(apiTokenId), ignoreCertificateErrors, connectionTimeout, readTimeout);
}
return apiCache;
}
private String getApiToken(String apiTokenId) {
StandardCredentials credentials = CredentialsMatchers.firstOrNull(
lookupCredentials(StandardCredentials.class, (Item) null, ACL.SYSTEM, new ArrayList<DomainRequirement>()),
CredentialsMatchers.withId(apiTokenId));
if (credentials != null) {
if (credentials instanceof GitLabApiToken) {
return ((GitLabApiToken) credentials).getApiToken().getPlainText();
}
if (credentials instanceof StringCredentials) {
return ((StringCredentials) credentials).getSecret().getPlainText();
}
}
throw new IllegalStateException("No credentials found for credentialsId: " + apiTokenId);
}
protected GitLabConnection readResolve() {
if (connectionTimeout == null || readTimeout == null) {
return new GitLabConnection(name, url, apiTokenId, ignoreCertificateErrors, 10, 10);
return new GitLabConnection(name, url, apiTokenId, new AutodetectGitLabClientBuilder(), ignoreCertificateErrors, 10, 10);
}
if (clientBuilder == null) {
return new GitLabConnection(name, url, apiTokenId, new AutodetectGitLabClientBuilder(), ignoreCertificateErrors, connectionTimeout, readTimeout);
}
return this;
}

View File

@ -1,13 +1,14 @@
package com.dabsquared.gitlabjenkins.connection;
import com.cloudbees.plugins.credentials.Credentials;
import com.cloudbees.plugins.credentials.CredentialsMatcher;
import com.cloudbees.plugins.credentials.common.AbstractIdCredentialsListBoxModel;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
import com.dabsquared.gitlabjenkins.gitlab.GitLabClientBuilder;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClientBuilder;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Item;
@ -29,16 +30,18 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.dabsquared.gitlabjenkins.gitlab.api.GitLabClientBuilder.getAllGitLabClientBuilders;
/**
* @author Robin Müller
*/
@Extension
public class GitLabConnectionConfig extends GlobalConfiguration {
private boolean useAuthenticatedEndpoint;
private Boolean useAuthenticatedEndpoint = true;
private List<GitLabConnection> connections = new ArrayList<>();
private transient Map<String, GitLabConnection> connectionMap = new HashMap<>();
private transient Map<String, GitLabApi> clients = new HashMap<>();
public GitLabConnectionConfig() {
load();
@ -50,7 +53,6 @@ public class GitLabConnectionConfig extends GlobalConfiguration {
connections = req.bindJSONToList(GitLabConnection.class, json.get("connections"));
useAuthenticatedEndpoint = json.getBoolean("useAuthenticatedEndpoint");
refreshConnectionMap();
clients.clear();
save();
return super.configure(req, json);
}
@ -72,11 +74,19 @@ public class GitLabConnectionConfig extends GlobalConfiguration {
connectionMap.put(connection.getName(), connection);
}
public GitLabApi getClient(String connectionName) {
if (!clients.containsKey(connectionName) && connectionMap.containsKey(connectionName)) {
clients.put(connectionName, GitLabClientBuilder.buildClient(connectionMap.get(connectionName)));
public void setConnections(List<GitLabConnection> newConnections) {
connections = new ArrayList<>();
connectionMap = new HashMap<>();
for (GitLabConnection connection: newConnections){
addConnection(connection);
}
return clients.get(connectionName);
}
public GitLabClient getClient(String connectionName) {
if (!connectionMap.containsKey(connectionName)) {
return null;
}
return connectionMap.get(connectionName).getClient();
}
public FormValidation doCheckName(@QueryParameter String id, @QueryParameter String value) {
@ -123,11 +133,12 @@ public class GitLabConnectionConfig extends GlobalConfiguration {
public FormValidation doTestConnection(@QueryParameter String url,
@QueryParameter String apiTokenId,
@QueryParameter String clientBuilderId,
@QueryParameter boolean ignoreCertificateErrors,
@QueryParameter int connectionTimeout,
@QueryParameter int readTimeout) {
try {
GitLabClientBuilder.buildClient(url, apiTokenId, ignoreCertificateErrors, connectionTimeout, readTimeout).headCurrentUser();
new GitLabConnection("", url, apiTokenId, clientBuilderId, ignoreCertificateErrors, connectionTimeout, readTimeout).getClient().getCurrentUser();
return FormValidation.ok(Messages.connection_success());
} catch (WebApplicationException e) {
return FormValidation.error(Messages.connection_error(e.getMessage()));
@ -159,6 +170,15 @@ public class GitLabConnectionConfig extends GlobalConfiguration {
return new StandardListBoxModel();
}
public ListBoxModel doFillClientBuilderIdItems() {
ListBoxModel model = new ListBoxModel();
for (GitLabClientBuilder builder : getAllGitLabClientBuilders()) {
model.add(builder.id());
}
return model;
}
private void refreshConnectionMap() {
connectionMap.clear();
for (GitLabConnection connection : connections) {
@ -176,4 +196,11 @@ public class GitLabConnectionConfig extends GlobalConfiguration {
}
}
}
//For backwards compatibility. ReadResolve is called on startup
protected GitLabConnectionConfig readResolve() {
if (useAuthenticatedEndpoint == null) {
setUseAuthenticatedEndpoint(false);
}
return this;
}
}

View File

@ -1,6 +1,7 @@
package com.dabsquared.gitlabjenkins.connection;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import hudson.Extension;
import hudson.model.Job;
import hudson.model.JobProperty;
@ -30,7 +31,7 @@ public class GitLabConnectionProperty extends JobProperty<Job<?, ?>> {
return gitLabConnection;
}
public GitLabApi getClient() {
public GitLabClient getClient() {
if (StringUtils.isNotEmpty(gitLabConnection)) {
GitLabConnectionConfig connectionConfig = (GitLabConnectionConfig) Jenkins.getInstance().getDescriptor(GitLabConnectionConfig.class);
return connectionConfig != null ? connectionConfig.getClient(gitLabConnection) : null;
@ -38,7 +39,7 @@ public class GitLabConnectionProperty extends JobProperty<Job<?, ?>> {
return null;
}
public static GitLabApi getClient(Run<?, ?> build) {
public static GitLabClient getClient(Run<?, ?> build) {
final GitLabConnectionProperty connectionProperty = build.getParent().getProperty(GitLabConnectionProperty.class);
if (connectionProperty != null) {
return connectionProperty.getClient();

View File

@ -3,6 +3,8 @@ package com.dabsquared.gitlabjenkins.environment;
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
import hudson.EnvVars;
import hudson.Extension;
import hudson.matrix.MatrixRun;
import hudson.matrix.MatrixBuild;
import hudson.model.EnvironmentContributor;
import hudson.model.Run;
import hudson.model.TaskListener;
@ -17,7 +19,15 @@ import java.io.IOException;
public class GitLabEnvironmentContributor extends EnvironmentContributor {
@Override
public void buildEnvironmentFor(@Nonnull Run r, @Nonnull EnvVars envs, @Nonnull TaskListener listener) throws IOException, InterruptedException {
GitLabWebHookCause cause = (GitLabWebHookCause) r.getCause(GitLabWebHookCause.class);
GitLabWebHookCause cause = null;
if (r instanceof MatrixRun) {
MatrixBuild parent = ((MatrixRun)r).getParentBuild();
if (parent != null) {
cause = (GitLabWebHookCause) parent.getCause(GitLabWebHookCause.class);
}
} else {
cause = (GitLabWebHookCause) r.getCause(GitLabWebHookCause.class);
}
if (cause != null) {
envs.overrideAll(cause.getData().getBuildVariables());
}

View File

@ -0,0 +1,48 @@
package com.dabsquared.gitlabjenkins.gitlab.api;
import com.dabsquared.gitlabjenkins.gitlab.api.model.*;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.State;
import java.util.List;
public interface GitLabClient {
String getHostUrl();
Project createProject(String projectName);
MergeRequest createMergeRequest(Integer projectId, String sourceBranch, String targetBranch, String title);
Project getProject(String projectName);
Project updateProject(String projectId, String name, String path);
void deleteProject(String projectId);
void addProjectHook(String projectId, String url, Boolean pushEvents, Boolean mergeRequestEvents, Boolean noteEvents);
void changeBuildStatus(String projectId, String sha, BuildState state, String ref, String context, String targetUrl, String description);
void changeBuildStatus(Integer projectId, String sha, BuildState state, String ref, String context, String targetUrl, String description);
void getCommit(String projectId, String sha);
void acceptMergeRequest(MergeRequest mr, String mergeCommitMessage, boolean shouldRemoveSourceBranch);
void createMergeRequestNote(MergeRequest mr, String body);
List<MergeRequest> getMergeRequests(String projectId, State state, int page, int perPage);
List<Branch> getBranches(String projectId);
Branch getBranch(String projectId, String branch);
User getCurrentUser();
User addUser(String email, String username, String name, String password);
User updateUser(String userId, String email, String username, String name, String password);
List<Label> getLabels(String projectId);
List<Pipeline> getPipelines(String projectName);
}

View File

@ -0,0 +1,56 @@
package com.dabsquared.gitlabjenkins.gitlab.api;
import hudson.ExtensionPoint;
import jenkins.model.Jenkins;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import javax.annotation.Nonnull;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import static java.util.Collections.sort;
@Restricted(NoExternalUse.class)
public abstract class GitLabClientBuilder implements Comparable<GitLabClientBuilder>, ExtensionPoint, Serializable {
public static GitLabClientBuilder getGitLabClientBuilderById(String id) {
for (GitLabClientBuilder provider : getAllGitLabClientBuilders()) {
if (provider.id().equals(id)) {
return provider;
}
}
throw new NoSuchElementException("unknown client-builder-id: " + id);
}
public static List<GitLabClientBuilder> getAllGitLabClientBuilders() {
List<GitLabClientBuilder> builders = new ArrayList<>(Jenkins.getInstance().getExtensionList(GitLabClientBuilder.class));
sort(builders);
return builders;
}
private final String id;
private final int ordinal;
protected GitLabClientBuilder(String id, int ordinal) {
this.id = id;
this.ordinal = ordinal;
}
@Nonnull
public final String id() {
return id;
}
@Nonnull
public abstract GitLabClient buildClient(String url, String token, boolean ignoreCertificateErrors, int connectionTimeout, int readTimeout);
@Override
public final int compareTo(@Nonnull GitLabClientBuilder other) {
int o = ordinal - other.ordinal;
return o != 0 ? o : id().compareTo(other.id());
}
}

View File

@ -0,0 +1,29 @@
package com.dabsquared.gitlabjenkins.gitlab.api.impl;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClientBuilder;
import hudson.Extension;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
@Extension
@Restricted(NoExternalUse.class)
public final class AutodetectGitLabClientBuilder extends GitLabClientBuilder {
public AutodetectGitLabClientBuilder() {
super("autodetect", 0);
}
@Override
@Nonnull
public GitLabClient buildClient(String url, String token, boolean ignoreCertificateErrors, int connectionTimeout, int readTimeout) {
Collection<GitLabClientBuilder> candidates = new ArrayList<>(getAllGitLabClientBuilders());
candidates.remove(this);
return new AutodetectingGitLabClient(candidates, url, token, ignoreCertificateErrors, connectionTimeout, readTimeout);
}
}

View File

@ -0,0 +1,309 @@
package com.dabsquared.gitlabjenkins.gitlab.api.impl;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClientBuilder;
import com.dabsquared.gitlabjenkins.gitlab.api.model.*;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.State;
import javax.ws.rs.NotFoundException;
import java.util.List;
import java.util.NoSuchElementException;
final class AutodetectingGitLabClient implements GitLabClient {
private final Iterable<GitLabClientBuilder> builders;
private final String url;
private final String token;
private final boolean ignoreCertificateErrors;
private final int connectionTimeout;
private final int readTimeout;
private GitLabClient delegate;
AutodetectingGitLabClient(Iterable<GitLabClientBuilder> builders, String url, String token, boolean ignoreCertificateErrors, int connectionTimeout, int readTimeout) {
this.builders = builders;
this.url = url;
this.token = token;
this.ignoreCertificateErrors = ignoreCertificateErrors;
this.connectionTimeout = connectionTimeout;
this.readTimeout = readTimeout;
}
@Override
public String getHostUrl() {
return url;
}
@Override
public Project createProject(final String projectName) {
return execute(
new GitLabOperation<Project>() {
@Override
Project execute(GitLabClient client) {
return client.createProject(projectName);
}
});
}
@Override
public MergeRequest createMergeRequest(final Integer projectId, final String sourceBranch, final String targetBranch, final String title) {
return execute(
new GitLabOperation<MergeRequest>() {
@Override
MergeRequest execute(GitLabClient client) {
return client.createMergeRequest(projectId, sourceBranch, targetBranch, title);
}
});
}
@Override
public Project getProject(final String projectName) {
return execute(
new GitLabOperation<Project>() {
@Override
Project execute(GitLabClient client) {
return client.getProject(projectName);
}
});
}
@Override
public Project updateProject(final String projectId, final String name, final String path) {
return execute(
new GitLabOperation<Project>() {
@Override
Project execute(GitLabClient client) {
return client.updateProject(projectId, name, path);
}
});
}
@Override
public void deleteProject(final String projectId) {
execute(
new GitLabOperation<Void>() {
@Override
Void execute(GitLabClient client) {
client.deleteProject(projectId);
return null;
}
});
}
@Override
public void addProjectHook(final String projectId, final String url, final Boolean pushEvents, final Boolean mergeRequestEvents, final Boolean noteEvents) {
execute(
new GitLabOperation<Void>() {
@Override
Void execute(GitLabClient client) {
client.addProjectHook(projectId, url, pushEvents, mergeRequestEvents, noteEvents);
return null;
}
});
}
@Override
public void changeBuildStatus(final String projectId, final String sha, final BuildState state, final String ref, final String context, final String targetUrl, final String description) {
execute(
new GitLabOperation<Void>() {
@Override
Void execute(GitLabClient client) {
client.changeBuildStatus(projectId, sha, state, ref, context, targetUrl, description);
return null;
}
});
}
@Override
public void changeBuildStatus(final Integer projectId, final String sha, final BuildState state, final String ref, final String context, final String targetUrl, final String description) {
execute(
new GitLabOperation<Void>() {
@Override
Void execute(GitLabClient client) {
client.changeBuildStatus(projectId, sha, state, ref, context, targetUrl, description);
return null;
}
});
}
@Override
public void getCommit(final String projectId, final String sha) {
execute(
new GitLabOperation<Void>() {
@Override
Void execute(GitLabClient client) {
client.getCommit(projectId, sha);
return null;
}
});
}
@Override
public void acceptMergeRequest(final MergeRequest mr, final String mergeCommitMessage, final boolean shouldRemoveSourceBranch) {
execute(
new GitLabOperation<Void>() {
@Override
Void execute(GitLabClient client) {
client.acceptMergeRequest(mr, mergeCommitMessage, shouldRemoveSourceBranch);
return null;
}
}
);
}
@Override
public void createMergeRequestNote(final MergeRequest mr, final String body) {
execute(
new GitLabOperation<Void>() {
@Override
Void execute(GitLabClient client) {
client.createMergeRequestNote(mr, body);
return null;
}
}
);
}
@Override
public List<MergeRequest> getMergeRequests(final String projectId, final State state, final int page, final int perPage) {
return execute(
new GitLabOperation<List<MergeRequest>>() {
@Override
List<MergeRequest> execute(GitLabClient client) {
return client.getMergeRequests(projectId, state, page, perPage);
}
});
}
@Override
public List<Branch> getBranches(final String projectId) {
return execute(
new GitLabOperation<List<Branch>>() {
@Override
List<Branch> execute(GitLabClient client) {
return client.getBranches(projectId);
}
});
}
@Override
public Branch getBranch(final String projectId, final String branch) {
return execute(
new GitLabOperation<Branch>() {
@Override
Branch execute(GitLabClient client) {
return client.getBranch(projectId, branch);
}
});
}
@Override
public User getCurrentUser() {
return execute(
new GitLabOperation<User>() {
@Override
User execute(GitLabClient client) {
return client.getCurrentUser();
}
});
}
@Override
public User addUser(final String email, final String username, final String name, final String password) {
return execute(
new GitLabOperation<User>() {
@Override
User execute(GitLabClient client) {
return client.addUser(email, username, name, password);
}
});
}
@Override
public User updateUser(final String userId, final String email, final String username, final String name, final String password) {
return execute(
new GitLabOperation<User>() {
@Override
User execute(GitLabClient client) {
return client.updateUser(userId, email, username, name, password);
}
});
}
@Override
public List<Label> getLabels(final String projectId) {
return execute(
new GitLabOperation<List<Label>>() {
@Override
List<Label> execute(GitLabClient client) {
return client.getLabels(projectId);
}
});
}
@Override
public List<Pipeline> getPipelines(final String projectName) {
return execute(
new GitLabOperation<List<Pipeline>>() {
@Override
List<Pipeline> execute(GitLabClient client) {
return client.getPipelines(projectName);
}
});
}
private GitLabClient delegate(boolean reset) {
if (reset || delegate == null) {
delegate = autodetectOrDie();
}
return delegate;
}
private GitLabClient autodetectOrDie() {
GitLabClient client = autodetect();
if (client != null) {
return client;
}
throw new NoSuchElementException("no client-builder found that supports server at " + url);
}
private GitLabClient autodetect() {
for (GitLabClientBuilder candidate : builders) {
GitLabClient client = candidate.buildClient(url, token, ignoreCertificateErrors, connectionTimeout, readTimeout);
try {
client.getCurrentUser();
return client;
} catch (NotFoundException ignored) {
// api-endpoint not found (== api-level not supported by this client)
}
}
return null;
}
private <R> R execute(GitLabOperation<R> operation) {
return operation.execute(false);
}
private abstract class GitLabOperation<R> {
private R execute(boolean reset) {
try {
return execute(delegate(reset));
} catch (NotFoundException e) {
if (reset) {
throw e;
}
return execute(true);
}
}
abstract R execute(GitLabClient client);
}
}

View File

@ -0,0 +1,50 @@
package com.dabsquared.gitlabjenkins.gitlab.api.impl;
import com.dabsquared.gitlabjenkins.gitlab.api.model.*;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.State;
import java.util.List;
interface GitLabApiProxy {
Project createProject(String projectName);
MergeRequest createMergeRequest(Integer projectId, String sourceBranch, String targetBranch, String title);
Project getProject(String projectName);
Project updateProject(String projectId, String name, String path);
void deleteProject(String projectId);
void addProjectHook(String projectId, String url, Boolean pushEvents, Boolean mergeRequestEvents, Boolean noteEvents);
void changeBuildStatus(String projectId, String sha, BuildState state, String ref, String context, String targetUrl, String description);
void changeBuildStatus(Integer projectId, String sha, BuildState state, String ref, String context, String targetUrl, String description);
void getCommit(String projectId, String sha);
void acceptMergeRequest(Integer projectId, Integer mergeRequestId, String mergeCommitMessage, boolean shouldRemoveSourceBranch);
void createMergeRequestNote(Integer projectId, Integer mergeRequestId, String body);
List<MergeRequest> getMergeRequests(String projectId, State state, int page, int perPage);
List<Branch> getBranches(String projectId);
Branch getBranch(String projectId, String branch);
void headCurrentUser();
User getCurrentUser();
User addUser(String email, String username, String name, String password);
User updateUser(String userId, String email, String username, String name, String password);
List<Label> getLabels(String projectId);
List<Pipeline> getPipelines(String projectName);
}

View File

@ -0,0 +1,122 @@
package com.dabsquared.gitlabjenkins.gitlab.api.impl;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.*;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.State;
import com.google.common.base.Function;
import java.util.List;
final class ResteasyGitLabClient implements GitLabClient {
private final String hostUrl;
private final GitLabApiProxy api;
private final Function<MergeRequest, Integer> mergeRequestIdProvider;
ResteasyGitLabClient(String hostUrl, GitLabApiProxy api, Function<MergeRequest, Integer> mergeRequestIdProvider) {
this.hostUrl = hostUrl;
this.api = api;
this.mergeRequestIdProvider = mergeRequestIdProvider;
}
@Override
public final String getHostUrl() {
return hostUrl;
}
@Override
public Project createProject(String projectName) {
return api.createProject(projectName);
}
@Override
public MergeRequest createMergeRequest(Integer projectId, String sourceBranch, String targetBranch, String title) {
return api.createMergeRequest(projectId, sourceBranch, targetBranch, title);
}
@Override
public Project getProject(String projectName) {
return api.getProject(projectName);
}
@Override
public Project updateProject(String projectId, String name, String path) {
return api.updateProject(projectId, name, path);
}
@Override
public void deleteProject(String projectId) {
api.deleteProject(projectId);
}
@Override
public void addProjectHook(String projectId, String url, Boolean pushEvents, Boolean mergeRequestEvents, Boolean noteEvents) {
api.addProjectHook(projectId, url, pushEvents, mergeRequestEvents, noteEvents);
}
@Override
public void changeBuildStatus(String projectId, String sha, BuildState state, String ref, String context, String targetUrl, String description) {
api.changeBuildStatus(projectId, sha, state, ref, context, targetUrl, description);
}
@Override
public void changeBuildStatus(Integer projectId, String sha, BuildState state, String ref, String context, String targetUrl, String description) {
api.changeBuildStatus(projectId, sha, state, ref, context, targetUrl, description);
}
@Override
public void getCommit(String projectId, String sha) {
api.getCommit(projectId, sha);
}
@Override
public void acceptMergeRequest(MergeRequest mr, String mergeCommitMessage, boolean shouldRemoveSourceBranch) {
api.acceptMergeRequest(mr.getProjectId(), mergeRequestIdProvider.apply(mr), mergeCommitMessage, shouldRemoveSourceBranch);
}
@Override
public void createMergeRequestNote(MergeRequest mr, String body) {
api.createMergeRequestNote(mr.getProjectId(), mergeRequestIdProvider.apply(mr), body);
}
@Override
public List<MergeRequest> getMergeRequests(String projectId, State state, int page, int perPage) {
return api.getMergeRequests(projectId, state, page, perPage);
}
@Override
public List<Branch> getBranches(String projectId) {
return api.getBranches(projectId);
}
@Override
public Branch getBranch(String projectId, String branch) {
return api.getBranch(projectId, branch);
}
@Override
public User getCurrentUser() {
return api.getCurrentUser();
}
@Override
public User addUser(String email, String username, String name, String password) {
return api.addUser(email, username, name, password);
}
@Override
public User updateUser(String userId, String email, String username, String name, String password) {
return api.updateUser(userId, email, username, name, password);
}
@Override
public List<Label> getLabels(String projectId) {
return api.getLabels(projectId);
}
@Override
public List<Pipeline> getPipelines(String projectName) {
return api.getPipelines(projectName);
}
}

View File

@ -1,11 +1,10 @@
package com.dabsquared.gitlabjenkins.gitlab;
package com.dabsquared.gitlabjenkins.gitlab.api.impl;
import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
import com.dabsquared.gitlabjenkins.connection.GitLabApiToken;
import com.dabsquared.gitlabjenkins.connection.GitLabConnection;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.JacksonConfig;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClientBuilder;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
import com.dabsquared.gitlabjenkins.util.JsonUtil;
import com.dabsquared.gitlabjenkins.util.LoggerUtil;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
@ -15,8 +14,6 @@ import com.google.common.collect.FluentIterable;
import hudson.ProxyConfiguration;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.model.Item;
import hudson.security.ACL;
import jenkins.model.Jenkins;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
@ -29,8 +26,10 @@ import org.jboss.resteasy.client.jaxrs.ClientHttpEngine;
import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine;
import org.jboss.resteasy.plugins.providers.JaxrsFormProvider;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
@ -48,93 +47,90 @@ import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials;
import static java.net.Proxy.NO_PROXY;
/**
* @author Robin Müller
*/
public class GitLabClientBuilder {
private final static Logger LOGGER = Logger.getLogger(GitLabClientBuilder.class.getName());
@Restricted(NoExternalUse.class)
public class ResteasyGitLabClientBuilder extends GitLabClientBuilder {
private static final Logger LOGGER = Logger.getLogger(ResteasyGitLabClientBuilder.class.getName());
private static final String PRIVATE_TOKEN = "PRIVATE-TOKEN";
public static GitLabApi buildClient(String gitlabHostUrl, final String gitlabApiTokenId, boolean ignoreCertificateErrors, int connectionTimeout, int readTimeout) {
ResteasyClientBuilder builder = new ResteasyClientBuilder();
if (ignoreCertificateErrors) {
builder.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY);
builder.disableTrustManager();
}
ProxyConfiguration proxyConfiguration = Jenkins.getActiveInstance().proxy;
Proxy proxy = proxyConfiguration == null ? Proxy.NO_PROXY : proxyConfiguration.createProxy(getHost(gitlabHostUrl));
if (!proxy.equals(Proxy.NO_PROXY)) {
InetSocketAddress address = (InetSocketAddress) proxy.address();
builder.defaultProxy(address.getHostName().replaceFirst("^.*://", ""),
address.getPort(),
address.getHostName().startsWith("https") ? "https" : "http",
proxyConfiguration.getUserName(),
proxyConfiguration.getPassword());
}
return builder
.connectionPoolSize(60)
.maxPooledPerRoute(30)
.establishConnectionTimeout(connectionTimeout, TimeUnit.SECONDS)
.socketTimeout(readTimeout, TimeUnit.SECONDS)
.register(new JacksonJsonProvider())
.register(new JacksonConfig())
.register(new ApiHeaderTokenFilter(getApiToken(gitlabApiTokenId)))
.register(new LoggingFilter())
.register(new RemoveAcceptEncodingFilter())
.register(new JaxrsFormProvider())
.build().target(gitlabHostUrl)
.proxyBuilder(GitLabApi.class)
.classloader(GitLabApi.class.getClassLoader())
.build();
}
public static GitLabApi buildClient(GitLabConnection connection) {
return buildClient(connection.getUrl(),
connection.getApiTokenId(),
connection.isIgnoreCertificateErrors(),
connection.getConnectionTimeout(),
connection.getReadTimeout());
}
@Initializer(before = InitMilestone.PLUGINS_STARTED)
public static void setRuntimeDelegate() {
RuntimeDelegate.setInstance(new ResteasyProviderFactory());
}
private static String getHost(String gitlabUrl) {
private final Class<? extends GitLabApiProxy> apiProxyClass;
private final Function<MergeRequest, Integer> mergeRequestIdProvider;
ResteasyGitLabClientBuilder(String id, int ordinal, Class<? extends GitLabApiProxy> apiProxyClass, Function<MergeRequest, Integer> mergeRequestIdProvider) {
super(id, ordinal);
this.apiProxyClass = apiProxyClass;
this.mergeRequestIdProvider = mergeRequestIdProvider;
}
@Nonnull
@Override
public final GitLabClient buildClient(String url, String apiToken, boolean ignoreCertificateErrors, int connectionTimeout, int readTimeout) {
return buildClient(
url,
apiToken,
Jenkins.getActiveInstance().proxy,
ignoreCertificateErrors,
connectionTimeout,
readTimeout
);
}
private GitLabClient buildClient(String url, String apiToken, ProxyConfiguration httpProxyConfig, boolean ignoreCertificateErrors, int connectionTimeout, int readTimeout) {
ResteasyClientBuilder builder = new ResteasyClientBuilder();
if (ignoreCertificateErrors) {
builder.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY);
builder.disableTrustManager();
}
Proxy proxy = httpProxyConfig != null ? httpProxyConfig.createProxy(getHost(url)) : NO_PROXY;
if (proxy != NO_PROXY) {
InetSocketAddress address = (InetSocketAddress) proxy.address();
builder.defaultProxy(address.getHostName().replaceFirst("^.*://", ""),
address.getPort(),
address.getHostName().startsWith("https") ? "https" : "http",
httpProxyConfig.getUserName(),
httpProxyConfig.getPassword());
}
GitLabApiProxy apiProxy = builder
.connectionPoolSize(60)
.maxPooledPerRoute(30)
.establishConnectionTimeout(connectionTimeout, TimeUnit.SECONDS)
.socketTimeout(readTimeout, TimeUnit.SECONDS)
.register(new JacksonJsonProvider())
.register(new JacksonConfig())
.register(new ApiHeaderTokenFilter(apiToken))
.register(new LoggingFilter())
.register(new RemoveAcceptEncodingFilter())
.register(new JaxrsFormProvider())
.build().target(url)
.proxyBuilder(apiProxyClass)
.classloader(apiProxyClass.getClassLoader())
.build();
return new ResteasyGitLabClient(url, apiProxy, mergeRequestIdProvider);
}
private String getHost(String url) {
try {
return new URL(gitlabUrl).getHost();
return new URL(url).getHost();
} catch (MalformedURLException e) {
return null;
}
}
private static String getApiToken(String apiTokenId) {
StandardCredentials credentials = CredentialsMatchers.firstOrNull(
lookupCredentials(StandardCredentials.class, (Item) null, ACL.SYSTEM, new ArrayList<DomainRequirement>()),
CredentialsMatchers.withId(apiTokenId));
if (credentials != null) {
if (credentials instanceof GitLabApiToken) {
return ((GitLabApiToken) credentials).getApiToken().getPlainText();
}
if (credentials instanceof StringCredentials) {
return ((StringCredentials) credentials).getSecret().getPlainText();
}
}
throw new IllegalStateException("No credentials found for credentialsId: " + apiTokenId);
}
@Priority(Priorities.HEADER_DECORATOR)
private static class ApiHeaderTokenFilter implements ClientRequestFilter {
private final String gitlabApiToken;
@ -230,9 +226,9 @@ public class GitLabClientBuilder {
}
private static class ResteasyClientBuilder extends org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder {
private CredentialsProvider proxyCredentials;
@SuppressWarnings("UnusedReturnValue")
ResteasyClientBuilder defaultProxy(String hostname, int port, final String scheme, String username, String password) {
super.defaultProxy(hostname, port, scheme);
if (username != null && password != null) {
@ -242,6 +238,7 @@ public class GitLabClientBuilder {
return this;
}
@SuppressWarnings("deprecation")
@Override
protected ClientHttpEngine initDefaultEngine() {
ApacheHttpClient4Engine httpEngine = (ApacheHttpClient4Engine) super.initDefaultEngine();

View File

@ -1,11 +1,7 @@
package com.dabsquared.gitlabjenkins.gitlab.api;
package com.dabsquared.gitlabjenkins.gitlab.api.impl;
import com.dabsquared.gitlabjenkins.gitlab.api.model.Branch;
import com.dabsquared.gitlabjenkins.gitlab.api.model.BuildState;
import com.dabsquared.gitlabjenkins.gitlab.api.model.Label;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
import com.dabsquared.gitlabjenkins.gitlab.api.model.Project;
import com.dabsquared.gitlabjenkins.gitlab.api.model.User;
import com.dabsquared.gitlabjenkins.gitlab.api.model.*;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.State;
import javax.ws.rs.Consumes;
@ -22,23 +18,29 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.List;
import static com.dabsquared.gitlabjenkins.gitlab.api.impl.V3GitLabApiProxy.ID;
/**
* @author Robin Müller
*/
@Path("/api/v3")
public interface GitLabApi {
@Path("/api/" + ID)
interface V3GitLabApiProxy extends GitLabApiProxy {
String ID = "v3";
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects")
@Override
Project createProject(@FormParam("name") String projectName);
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects/{projectId}/merge_requests")
void createMergeRequest(
@Override
MergeRequest createMergeRequest(
@PathParam("projectId") Integer projectId,
@FormParam("source_branch") String sourceBranch,
@FormParam("target_branch") String targetBranch,
@ -47,24 +49,28 @@ public interface GitLabApi {
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectName}")
@Override
Project getProject(@PathParam("projectName") String projectName);
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects/{projectId}")
@Override
Project updateProject(@PathParam("projectId") String projectId,
@FormParam("name") String name,
@FormParam("path") String path);
@DELETE
@Path("/projects/{projectId}")
@Override
void deleteProject(@PathParam("projectId") String projectId);
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects/{projectId}/hooks")
@Override
void addProjectHook(@PathParam("projectId") String projectId,
@FormParam("url") String url,
@FormParam("push_events") Boolean pushEvents,
@ -75,6 +81,7 @@ public interface GitLabApi {
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects/{projectId}/statuses/{sha}")
@Override
void changeBuildStatus(@PathParam("projectId") String projectId,
@PathParam("sha") String sha,
@FormParam("state") BuildState state,
@ -87,6 +94,7 @@ public interface GitLabApi {
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects/{projectId}/statuses/{sha}")
@Override
void changeBuildStatus(@PathParam("projectId") Integer projectId,
@PathParam("sha") String sha,
@FormParam("state") BuildState state,
@ -98,6 +106,7 @@ public interface GitLabApi {
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/repository/commits/{sha}")
@Override
void getCommit(@PathParam("projectId") String projectId, @PathParam("sha") String sha);
@ -105,6 +114,7 @@ public interface GitLabApi {
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects/{projectId}/merge_requests/{mergeRequestId}/merge")
@Override
void acceptMergeRequest(@PathParam("projectId") Integer projectId,
@PathParam("mergeRequestId") Integer mergeRequestId,
@FormParam("merge_commit_message") String mergeCommitMessage,
@ -114,6 +124,7 @@ public interface GitLabApi {
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects/{projectId}/merge_requests/{mergeRequestId}/notes")
@Override
void createMergeRequestNote(@PathParam("projectId") Integer projectId,
@PathParam("mergeRequestId") Integer mergeRequestId,
@FormParam("body") String body);
@ -121,6 +132,7 @@ public interface GitLabApi {
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/merge_requests")
@Override
List<MergeRequest> getMergeRequests(@PathParam("projectId") String projectId,
@QueryParam("state") State state,
@QueryParam("page") int page,
@ -129,28 +141,33 @@ public interface GitLabApi {
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/repository/branches")
@Override
List<Branch> getBranches(@PathParam("projectId") String projectId);
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/repository/branches/{branch}")
@Override
Branch getBranch(@PathParam("projectId") String projectId,
@PathParam("branch") String branch);
@HEAD
@Produces(MediaType.APPLICATION_JSON)
@Path("/user")
@Override
void headCurrentUser();
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/user")
@Override
User getCurrentUser();
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/users")
@Override
User addUser(@FormParam("email") String email,
@FormParam("username") String username,
@FormParam("name") String name,
@ -160,6 +177,7 @@ public interface GitLabApi {
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/users/{userId}")
@Override
User updateUser(@PathParam("userId") String userId,
@FormParam("email") String email,
@FormParam("username") String username,
@ -169,5 +187,12 @@ public interface GitLabApi {
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/labels")
@Override
List<Label> getLabels(@PathParam("projectId") String projectId);
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/pipelines")
@Override
List<Pipeline> getPipelines(@PathParam("projectId") String projectId);
}

View File

@ -0,0 +1,25 @@
package com.dabsquared.gitlabjenkins.gitlab.api.impl;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
import com.google.common.base.Function;
import hudson.Extension;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@Extension
@Restricted(NoExternalUse.class)
public final class V3GitLabClientBuilder extends ResteasyGitLabClientBuilder {
private static final int ORDINAL = 2;
private static final Function<MergeRequest, Integer> MERGE_REQUEST_ID_PROVIDER = new Function<MergeRequest, Integer>() {
@Override
public Integer apply(MergeRequest mergeRequest) {
return mergeRequest.getId();
}
};
public V3GitLabClientBuilder() {
super(V3GitLabApiProxy.ID, ORDINAL, V3GitLabApiProxy.class, MERGE_REQUEST_ID_PROVIDER);
}
}

View File

@ -0,0 +1,198 @@
package com.dabsquared.gitlabjenkins.gitlab.api.impl;
import com.dabsquared.gitlabjenkins.gitlab.api.model.*;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.State;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.List;
import static com.dabsquared.gitlabjenkins.gitlab.api.impl.V4GitLabApiProxy.ID;
/**
* @author Robin Müller
*/
@Path("/api/" + ID)
interface V4GitLabApiProxy extends GitLabApiProxy {
String ID = "v4";
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects")
@Override
Project createProject(@FormParam("name") String projectName);
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects/{projectId}/merge_requests")
@Override
MergeRequest createMergeRequest(
@PathParam("projectId") Integer projectId,
@FormParam("source_branch") String sourceBranch,
@FormParam("target_branch") String targetBranch,
@FormParam("title") String title);
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectName}")
@Override
Project getProject(@PathParam("projectName") String projectName);
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects/{projectId}")
@Override
Project updateProject(@PathParam("projectId") String projectId,
@FormParam("name") String name,
@FormParam("path") String path);
@DELETE
@Path("/projects/{projectId}")
@Override
void deleteProject(@PathParam("projectId") String projectId);
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects/{projectId}/hooks")
@Override
void addProjectHook(@PathParam("projectId") String projectId,
@FormParam("url") String url,
@FormParam("push_events") Boolean pushEvents,
@FormParam("merge_requests_events") Boolean mergeRequestEvents,
@FormParam("note_events") Boolean noteEvents);
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects/{projectId}/statuses/{sha}")
@Override
void changeBuildStatus(@PathParam("projectId") String projectId,
@PathParam("sha") String sha,
@FormParam("state") BuildState state,
@FormParam("ref") String ref,
@FormParam("context") String context,
@FormParam("target_url") String targetUrl,
@FormParam("description") String description);
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects/{projectId}/statuses/{sha}")
@Override
void changeBuildStatus(@PathParam("projectId") Integer projectId,
@PathParam("sha") String sha,
@FormParam("state") BuildState state,
@FormParam("ref") String ref,
@FormParam("context") String context,
@FormParam("target_url") String targetUrl,
@FormParam("description") String description);
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/repository/commits/{sha}")
@Override
void getCommit(@PathParam("projectId") String projectId, @PathParam("sha") String sha);
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects/{projectId}/merge_requests/{mergeRequestIid}/merge")
@Override
void acceptMergeRequest(@PathParam("projectId") Integer projectId,
@PathParam("mergeRequestIid") Integer mergeRequestIid,
@FormParam("merge_commit_message") String mergeCommitMessage,
@FormParam("should_remove_source_branch") boolean shouldRemoveSourceBranch);
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/projects/{projectId}/merge_requests/{mergeRequestIid}/notes")
@Override
void createMergeRequestNote(@PathParam("projectId") Integer projectId,
@PathParam("mergeRequestIid") Integer mergeRequestIid,
@FormParam("body") String body);
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/merge_requests")
@Override
List<MergeRequest> getMergeRequests(@PathParam("projectId") String projectId,
@QueryParam("state") State state,
@QueryParam("page") int page,
@QueryParam("per_page") int perPage);
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/repository/branches")
@Override
List<Branch> getBranches(@PathParam("projectId") String projectId);
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/repository/branches/{branch}")
@Override
Branch getBranch(@PathParam("projectId") String projectId,
@PathParam("branch") String branch);
@HEAD
@Produces(MediaType.APPLICATION_JSON)
@Path("/user")
@Override
void headCurrentUser();
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/user")
@Override
User getCurrentUser();
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/users")
@Override
User addUser(@FormParam("email") String email,
@FormParam("username") String username,
@FormParam("name") String name,
@FormParam("password") String password);
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/users/{userId}")
@Override
User updateUser(@PathParam("userId") String userId,
@FormParam("email") String email,
@FormParam("username") String username,
@FormParam("name") String name,
@FormParam("password") String password);
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/labels")
@Override
List<Label> getLabels(@PathParam("projectId") String projectId);
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/pipelines")
@Override
List<Pipeline> getPipelines(@PathParam("projectId") String projectId);
}

View File

@ -0,0 +1,25 @@
package com.dabsquared.gitlabjenkins.gitlab.api.impl;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
import com.google.common.base.Function;
import hudson.Extension;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@Extension
@Restricted(NoExternalUse.class)
public final class V4GitLabClientBuilder extends ResteasyGitLabClientBuilder {
private static final int ORDINAL = 1;
private static final Function<MergeRequest, Integer> MERGE_REQUEST_ID_PROVIDER = new Function<MergeRequest, Integer>() {
@Override
public Integer apply(MergeRequest mergeRequest) {
return mergeRequest.getIid();
}
};
public V4GitLabClientBuilder() {
super(V4GitLabApiProxy.ID, ORDINAL, V4GitLabApiProxy.class, MERGE_REQUEST_ID_PROVIDER);
}
}

View File

@ -13,7 +13,6 @@ import java.util.List;
*/
@GeneratePojoBuilder(intoPackage = "*.builder.generated", withFactoryMethod = "*")
public class MergeRequest {
private Integer id;
private Integer iid;
private String sourceBranch;
@ -33,6 +32,22 @@ public class MergeRequest {
private Boolean mergeWhenBuildSucceeds;
private String mergeStatus;
public MergeRequest() { /* default-constructor for Resteasy-based-api-proxies */ }
public MergeRequest(int id, int iid, String sourceBranch, String targetBranch, String title,
int sourceProjectId, int targetProjectId,
String description, String mergeStatus) {
this.id = id;
this.iid= iid;
this.sourceBranch = sourceBranch;
this.targetBranch = targetBranch;
this.title = title;
this.sourceProjectId = sourceProjectId;
this.projectId = targetProjectId;
this.description = description;
this.mergeStatus = mergeStatus;
}
public Integer getId() {
return id;
}

View File

@ -0,0 +1,34 @@
package com.dabsquared.gitlabjenkins.gitlab.api.model;
import net.karneim.pojobuilder.GeneratePojoBuilder;
@GeneratePojoBuilder(intoPackage = "*.builder.generated", withFactoryMethod = "*")
public class Pipeline {
private Integer id;
private String sha;
private String status;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getSha() {
return sha;
}
public void setSha(String sha) {
this.sha = sha;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}

View File

@ -4,5 +4,5 @@ package com.dabsquared.gitlabjenkins.gitlab.hook.model;
* @author Robin Müller
*/
public enum Action {
open, update
open, update, approved
}

View File

@ -13,6 +13,7 @@ import org.apache.commons.lang.builder.ToStringBuilder;
public class MergeRequestHook extends WebHook {
private User user;
private User assignee;
private Project project;
private MergeRequestObjectAttributes objectAttributes;
@ -40,7 +41,15 @@ public class MergeRequestHook extends WebHook {
this.objectAttributes = objectAttributes;
}
@Override
public User getAssignee() {
return assignee;
}
public void setAssignee(User assignee) {
this.assignee = assignee;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
@ -51,6 +60,7 @@ public class MergeRequestHook extends WebHook {
MergeRequestHook that = (MergeRequestHook) o;
return new EqualsBuilder()
.append(user, that.user)
.append(assignee, that.assignee)
.append(project, that.project)
.append(objectAttributes, that.objectAttributes)
.isEquals();
@ -60,6 +70,7 @@ public class MergeRequestHook extends WebHook {
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(user)
.append(assignee)
.append(project)
.append(objectAttributes)
.toHashCode();
@ -69,6 +80,7 @@ public class MergeRequestHook extends WebHook {
public String toString() {
return new ToStringBuilder(this)
.append("user", user)
.append("assignee", assignee)
.append("project", project)
.append("objectAttributes", objectAttributes)
.toString();

View File

@ -0,0 +1,166 @@
package com.dabsquared.gitlabjenkins.gitlab.hook.model;
import net.karneim.pojobuilder.GeneratePojoBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import java.util.Date;
import java.util.List;
/**
* @author Milena Zachow
*/
@GeneratePojoBuilder(intoPackage = "*.builder.generated", withFactoryMethod = "*")
public class PipelineEventObjectAttributes {
private Integer id;
private String ref;
private boolean tag;
private String sha;
private String beforeSha;
private String status;
private List<String> stages;
private Date createdAt;
private Date finishedAt;
private int duration;
public String getRef() {
return ref;
}
public void setRef(String ref) {
this.ref = ref;
}
public boolean getIsTag() {
return tag;
}
public void setTag(boolean tag) {
this.tag = tag;
}
public String getSha() {
return sha;
}
public void setSha(String sha) {
this.sha = sha;
}
public String getBeforeSha() {
return beforeSha;
}
public void setBeforeSha(String beforeSha) {
this.beforeSha = beforeSha;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public List<String> getStages() {
return stages;
}
public void setStages(List<String> stages) {
this.stages = stages;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
public Date getFinishedAt() {
return finishedAt;
}
public void setFinishedAt(Date finishedAt) {
this.finishedAt = finishedAt;
}
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PipelineEventObjectAttributes that = (PipelineEventObjectAttributes) o;
return new EqualsBuilder()
.append(id, that.id)
.append(ref, that.ref)
.append(tag, that.tag)
.append(sha, that.sha)
.append(beforeSha, that.beforeSha)
.append(status, that.status)
.append(stages, that.stages)
.append(createdAt, that.createdAt)
.append(finishedAt, that.finishedAt)
.append(duration, that.duration)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id)
.append(ref)
.append(tag)
.append(sha)
.append(beforeSha)
.append(status)
.append(stages)
.append(createdAt)
.append(finishedAt)
.append(duration)
.toHashCode();
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("id", id)
.append("ref", ref)
.append("tag", tag)
.append("sha", sha)
.append("beforeSha", beforeSha)
.append("status", status)
.append("stages", stages)
.append("createdAt", createdAt)
.append("finishedAt", finishedAt)
.append("duration", duration)
.toString();
}
}

View File

@ -0,0 +1,103 @@
package com.dabsquared.gitlabjenkins.gitlab.hook.model;
import net.karneim.pojobuilder.GeneratePojoBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import java.util.List;
/**
* @author Milena Zachow
*/
@GeneratePojoBuilder(intoPackage = "*.builder.generated", withFactoryMethod = "*")
public class PipelineHook extends WebHook {
private User user;
public Integer projectId;
private List<Commit> commits;
private Project project;
private PipelineEventObjectAttributes objectAttributes;
public Integer getProjectId() {
return projectId;
}
public void setProjectId(Integer projectId) {
this.projectId = projectId;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public List<Commit> getCommits() {
return commits;
}
public void setCommits(List<Commit> commits) {
this.commits = commits;
}
public Project getProject() {
return project;
}
public void setProject(Project project) {
this.project = project;
}
public PipelineEventObjectAttributes getObjectAttributes() {
return objectAttributes;
}
public void setObjectAttributes(PipelineEventObjectAttributes objectAttributes) {
this.objectAttributes = objectAttributes;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PipelineHook that = (PipelineHook) o;
return new EqualsBuilder()
.append(user, that.user)
.append(project, that.project)
.append(projectId, that.projectId)
.append(commits, that.commits)
.append(objectAttributes, that.objectAttributes)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(user)
.append(projectId)
.append(project)
.append(commits)
.append(objectAttributes)
.toHashCode();
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("user", user)
.append("project", project)
.append("projectId", projectId)
.append("objectAttributes", objectAttributes)
.append("commits", commits)
.toString();
}
}

View File

@ -11,6 +11,7 @@ import org.apache.commons.lang.builder.ToStringBuilder;
@GeneratePojoBuilder(intoPackage = "*.builder.generated", withFactoryMethod = "*")
public class Project {
private Integer id;
private String name;
private String description;
private String webUrl;
@ -120,6 +121,14 @@ public class Project {
this.httpUrl = httpUrl;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) {
@ -130,6 +139,7 @@ public class Project {
}
Project project = (Project) o;
return new EqualsBuilder()
.append(id, project.id)
.append(name, project.name)
.append(description, project.description)
.append(webUrl, project.webUrl)
@ -148,6 +158,7 @@ public class Project {
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id)
.append(name)
.append(description)
.append(webUrl)
@ -166,6 +177,7 @@ public class Project {
@Override
public String toString() {
return new ToStringBuilder(this)
.append("id", id)
.append("name", name)
.append("description", description)
.append("webUrl", webUrl)

View File

@ -1,6 +1,8 @@
package com.dabsquared.gitlabjenkins.publisher;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.Result;
@ -44,14 +46,14 @@ public class GitLabAcceptMergeRequestPublisher extends MergeRequestNotifier {
}
@Override
protected void perform(Run<?, ?> build, TaskListener listener, GitLabApi client, Integer projectId, Integer mergeRequestId) {
protected void perform(Run<?, ?> build, TaskListener listener, GitLabClient client, MergeRequest mergeRequest) {
try {
if (build.getResult() == Result.SUCCESS) {
client.acceptMergeRequest(projectId, mergeRequestId, "Merge Request accepted by jenkins build success", false);
client.acceptMergeRequest(mergeRequest, "Merge Request accepted by jenkins build success", false);
}
} catch (WebApplicationException | ProcessingException e) {
listener.getLogger().printf("Failed to accept merge request for project '%s': %s%n", projectId, e.getMessage());
LOGGER.log(Level.SEVERE, String.format("Failed to accept merge request for project '%s'", projectId), e);
listener.getLogger().printf("Failed to accept merge request for project '%s': %s%n", mergeRequest.getProjectId(), e.getMessage());
LOGGER.log(Level.SEVERE, String.format("Failed to accept merge request for project '%s'", mergeRequest.getProjectId()), e);
}
}
}

View File

@ -4,6 +4,9 @@ import com.dabsquared.gitlabjenkins.gitlab.api.model.BuildState;
import com.dabsquared.gitlabjenkins.util.CommitStatusUpdater;
import hudson.Extension;
import hudson.Launcher;
import hudson.matrix.MatrixAggregatable;
import hudson.matrix.MatrixAggregator;
import hudson.matrix.MatrixBuild;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
@ -22,7 +25,7 @@ import java.io.IOException;
/**
* @author Robin Müller
*/
public class GitLabCommitStatusPublisher extends Notifier {
public class GitLabCommitStatusPublisher extends Notifier implements MatrixAggregatable {
private String name;
private boolean markUnstableAsSuccess;
@ -71,6 +74,16 @@ public class GitLabCommitStatusPublisher extends Notifier {
return this;
}
public MatrixAggregator createAggregator(MatrixBuild build, Launcher launcher, BuildListener listener) {
return new MatrixAggregator(build, launcher, listener) {
@Override
public boolean endBuild() throws InterruptedException, IOException {
perform(build, launcher, listener);
return super.endBuild();
}
};
}
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {

View File

@ -1,6 +1,8 @@
package com.dabsquared.gitlabjenkins.publisher;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractProject;
@ -11,6 +13,7 @@ import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Publisher;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.WebApplicationException;
@ -29,22 +32,30 @@ public class GitLabMessagePublisher extends MergeRequestNotifier {
private boolean replaceSuccessNote = false;
private boolean replaceFailureNote = false;
private boolean replaceAbortNote = false;
private boolean replaceUnstableNote = false;
private String successNoteText;
private String failureNoteText;
private String abortNoteText;
private String unstableNoteText;
@DataBoundConstructor
public GitLabMessagePublisher(boolean onlyForFailure, boolean replaceSuccessNote, boolean replaceFailureNote, boolean replaceAbortNote,
String successNoteText, String failureNoteText, String abortNoteText) {
/**
* @deprecated use {@link #GitLabMessagePublisher()} with setters to configure an instance of this class.
*/
@Deprecated
public GitLabMessagePublisher(boolean onlyForFailure, boolean replaceSuccessNote, boolean replaceFailureNote, boolean replaceAbortNote, boolean replaceUnstableNote,
String successNoteText, String failureNoteText, String abortNoteText, String unstableNoteText) {
this.onlyForFailure = onlyForFailure;
this.replaceSuccessNote = replaceSuccessNote;
this.replaceFailureNote = replaceFailureNote;
this.replaceAbortNote = replaceAbortNote;
this.replaceUnstableNote = replaceUnstableNote;
this.successNoteText = successNoteText;
this.failureNoteText = failureNoteText;
this.abortNoteText = abortNoteText;
this.unstableNoteText = unstableNoteText;
}
@DataBoundConstructor
public GitLabMessagePublisher() { }
public boolean isOnlyForFailure() {
@ -63,6 +74,10 @@ public class GitLabMessagePublisher extends MergeRequestNotifier {
return replaceAbortNote;
}
public boolean isReplaceUnstableNote() {
return replaceUnstableNote;
}
public String getSuccessNoteText() {
return this.successNoteText == null ? "" : this.successNoteText;
}
@ -75,6 +90,55 @@ public class GitLabMessagePublisher extends MergeRequestNotifier {
return this.abortNoteText == null ? "" : this.abortNoteText;
}
public String getUnstableNoteText() {
return this.unstableNoteText == null ? "" : this.unstableNoteText;
}
@DataBoundSetter
public void setOnlyForFailure(boolean onlyForFailure) {
this.onlyForFailure = onlyForFailure;
}
@DataBoundSetter
public void setReplaceSuccessNote(boolean replaceSuccessNote) {
this.replaceSuccessNote = replaceSuccessNote;
}
@DataBoundSetter
public void setReplaceFailureNote(boolean replaceFailureNote) {
this.replaceFailureNote = replaceFailureNote;
}
@DataBoundSetter
public void setReplaceAbortNote(boolean replaceAbortNote) {
this.replaceAbortNote = replaceAbortNote;
}
@DataBoundSetter
public void setReplaceUnstableNote(boolean replaceUnstableNote) {
this.replaceUnstableNote = replaceUnstableNote;
}
@DataBoundSetter
public void setSuccessNoteText(String successNoteText) {
this.successNoteText = successNoteText;
}
@DataBoundSetter
public void setFailureNoteText(String failureNoteText) {
this.failureNoteText = failureNoteText;
}
@DataBoundSetter
public void setAbortNoteText(String abortNoteText) {
this.abortNoteText = abortNoteText;
}
@DataBoundSetter
public void setUnstableNoteText(String unstableNoteText) {
this.unstableNoteText = unstableNoteText;
}
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
@ -95,14 +159,14 @@ public class GitLabMessagePublisher extends MergeRequestNotifier {
}
@Override
protected void perform(Run<?, ?> build, TaskListener listener, GitLabApi client, Integer projectId, Integer mergeRequestId) {
protected void perform(Run<?, ?> build, TaskListener listener, GitLabClient client, MergeRequest mergeRequest) {
try {
if (!onlyForFailure || build.getResult() == Result.FAILURE) {
client.createMergeRequestNote(projectId, mergeRequestId, getNote(build, listener));
if (!onlyForFailure || build.getResult() == Result.FAILURE || build.getResult() == Result.UNSTABLE) {
client.createMergeRequestNote(mergeRequest, getNote(build, listener));
}
} catch (WebApplicationException | ProcessingException e) {
listener.getLogger().printf("Failed to add comment on Merge Request for project '%s': %s%n", projectId, e.getMessage());
LOGGER.log(Level.SEVERE, String.format("Failed to add comment on Merge Request for project '%s'", projectId), e);
listener.getLogger().printf("Failed to add comment on Merge Request for project '%s': %s%n", mergeRequest.getProjectId(), e.getMessage());
LOGGER.log(Level.SEVERE, String.format("Failed to add comment on Merge Request for project '%s'", mergeRequest.getProjectId()), e);
}
}
@ -111,6 +175,8 @@ public class GitLabMessagePublisher extends MergeRequestNotifier {
return ":white_check_mark:";
} else if (result == Result.ABORTED) {
return ":point_up:";
} else if (result == Result.UNSTABLE) {
return ":warning:";
} else {
return ":negative_squared_cross_mark:";
}
@ -151,6 +217,8 @@ public class GitLabMessagePublisher extends MergeRequestNotifier {
message = replaceMacros(build, listener, this.getSuccessNoteText());
} else if (this.replaceAbortNote && build.getResult() == Result.ABORTED) {
message = replaceMacros(build, listener, this.getAbortNoteText());
} else if (this.replaceUnstableNote && build.getResult() == Result.UNSTABLE) {
message = replaceMacros(build, listener, this.getUnstableNoteText());
} else if (this.replaceFailureNote && build.getResult() == Result.FAILURE) {
message = replaceMacros(build, listener, this.getFailureNoteText());
} else {

View File

@ -1,6 +1,8 @@
package com.dabsquared.gitlabjenkins.publisher;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.Result;
@ -44,12 +46,12 @@ public class GitLabVotePublisher extends MergeRequestNotifier {
}
@Override
protected void perform(Run<?, ?> build, TaskListener listener, GitLabApi client, Integer projectId, Integer mergeRequestId) {
protected void perform(Run<?, ?> build, TaskListener listener, GitLabClient client, MergeRequest mergeRequest) {
try {
client.createMergeRequestNote(projectId, mergeRequestId, getResultIcon(build.getResult()));
client.createMergeRequestNote(mergeRequest, getResultIcon(build.getResult()));
} catch (WebApplicationException | ProcessingException e) {
listener.getLogger().printf("Failed to add vote on Merge Request for project '%s': %s%n", projectId, e.getMessage());
LOGGER.log(Level.SEVERE, String.format("Failed to add vote on Merge Request for project '%s'", projectId), e);
listener.getLogger().printf("Failed to add vote on Merge Request for project '%s': %s%n", mergeRequest.getProjectId(), e.getMessage());
LOGGER.log(Level.SEVERE, String.format("Failed to add vote on Merge Request for project '%s'", mergeRequest.getProjectId()), e);
}
}

View File

@ -1,8 +1,12 @@
package com.dabsquared.gitlabjenkins.publisher;
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
import hudson.Launcher;
import hudson.matrix.MatrixAggregatable;
import hudson.matrix.MatrixAggregator;
import hudson.matrix.MatrixBuild;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Run;
@ -17,35 +21,41 @@ import static com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty.g
/**
* @author Robin Müller
*/
public abstract class MergeRequestNotifier extends Notifier {
public abstract class MergeRequestNotifier extends Notifier implements MatrixAggregatable {
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
GitLabApi client = getClient(build);
GitLabClient client = getClient(build);
if (client == null) {
listener.getLogger().println("No GitLab connection configured");
return true;
}
Integer projectId = getProjectId(build);
Integer mergeRequestId = getMergeRequestId(build);
if (projectId != null && mergeRequestId != null) {
perform(build, listener, client, projectId, mergeRequestId);
MergeRequest mergeRequest = getMergeRequest(build);
if (mergeRequest != null) {
perform(build, listener, client, mergeRequest);
}
return true;
}
protected abstract void perform(Run<?, ?> build, TaskListener listener, GitLabApi client, Integer projectId, Integer mergeRequestId);
Integer getProjectId(Run<?, ?> build) {
GitLabWebHookCause cause = build.getCause(GitLabWebHookCause.class);
return cause == null ? null : cause.getData().getTargetProjectId();
public MatrixAggregator createAggregator(MatrixBuild build, Launcher launcher, BuildListener listener) {
return new MatrixAggregator(build, launcher, listener) {
@Override
public boolean endBuild() throws InterruptedException, IOException {
perform(build, launcher, listener);
return super.endBuild();
}
};
}
Integer getMergeRequestId(Run<?, ?> build) {
GitLabWebHookCause cause = build.getCause(GitLabWebHookCause.class);
return cause == null ? null : cause.getData().getMergeRequestId();
protected abstract void perform(Run<?, ?> build, TaskListener listener, GitLabClient client, MergeRequest mergeRequest);
MergeRequest getMergeRequest(Run<?, ?> run) {
GitLabWebHookCause cause = run.getCause(GitLabWebHookCause.class);
return cause == null ? null : cause.getData().getMergeRequest();
}
}

View File

@ -1,6 +1,7 @@
package com.dabsquared.gitlabjenkins.service;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.Branch;
import com.dabsquared.gitlabjenkins.util.LoggerUtil;
import com.dabsquared.gitlabjenkins.util.ProjectIdUtil;
@ -37,7 +38,7 @@ public class GitLabProjectBranchesService {
return gitLabProjectBranchesService;
}
public List<String> getBranches(GitLabApi client, String sourceRepositoryString) {
public List<String> getBranches(GitLabClient client, String sourceRepositoryString) {
synchronized (projectBranchCache) {
try {
return projectBranchCache.get(sourceRepositoryString, new BranchNamesLoader(client, sourceRepositoryString));
@ -54,10 +55,10 @@ public class GitLabProjectBranchesService {
}
private static class BranchNamesLoader implements Callable<List<String>> {
private final GitLabApi client;
private final GitLabClient client;
private final String sourceRepository;
private BranchNamesLoader(GitLabApi client, String sourceRepository) {
private BranchNamesLoader(GitLabClient client, String sourceRepository) {
this.client = client;
this.sourceRepository = sourceRepository;
}
@ -65,7 +66,7 @@ public class GitLabProjectBranchesService {
@Override
public List<String> call() throws Exception {
List<String> result = new ArrayList<>();
String projectId = ProjectIdUtil.retrieveProjectId(sourceRepository);
String projectId = ProjectIdUtil.retrieveProjectId(client, sourceRepository);
for (Branch branch : client.getBranches(projectId)) {
result.add(branch.getName());
}

View File

@ -1,6 +1,7 @@
package com.dabsquared.gitlabjenkins.service;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.Label;
import com.dabsquared.gitlabjenkins.util.LoggerUtil;
import com.dabsquared.gitlabjenkins.util.ProjectIdUtil;
@ -37,7 +38,7 @@ public class GitLabProjectLabelsService {
return instance;
}
public List<String> getLabels(GitLabApi client, String sourceRepositoryString) {
public List<String> getLabels(GitLabClient client, String sourceRepositoryString) {
synchronized (projectLabelsCache) {
try {
return projectLabelsCache.get(sourceRepositoryString, new LabelNamesLoader(client, sourceRepositoryString));
@ -54,10 +55,10 @@ public class GitLabProjectLabelsService {
}
private static class LabelNamesLoader implements Callable<List<String>> {
private final GitLabApi client;
private final GitLabClient client;
private final String sourceRepository;
private LabelNamesLoader(GitLabApi client, String sourceRepository) {
private LabelNamesLoader(GitLabClient client, String sourceRepository) {
this.client = client;
this.sourceRepository = sourceRepository;
}
@ -65,7 +66,7 @@ public class GitLabProjectLabelsService {
@Override
public List<String> call() throws Exception {
List<String> result = new ArrayList<>();
String projectId = ProjectIdUtil.retrieveProjectId(sourceRepository);
String projectId = ProjectIdUtil.retrieveProjectId(client, sourceRepository);
for (Label label : client.getLabels(projectId)) {
result.add(label.getName());
}

View File

@ -1,21 +1,28 @@
package com.dabsquared.gitlabjenkins.trigger.filter;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
/**
* @author Robin Müller
*/
public class MergeRequestLabelFilterConfig {
private final String include;
private final String exclude;
private String include;
private String exclude;
@DataBoundConstructor
/**
* @deprecated use {@link #MergeRequestLabelFilterConfig()} with setters to configure an instance of this class.
*/
@Deprecated
public MergeRequestLabelFilterConfig(String include, String exclude) {
this.include = include;
this.exclude = exclude;
}
@DataBoundConstructor
public MergeRequestLabelFilterConfig() { }
public String getInclude() {
return include;
}
@ -23,4 +30,14 @@ public class MergeRequestLabelFilterConfig {
public String getExclude() {
return exclude;
}
@DataBoundSetter
public void setInclude(String include) {
this.include = include;
}
@DataBoundSetter
public void setExclude(String exclude) {
this.exclude = exclude;
}
}

View File

@ -4,6 +4,7 @@ import com.google.common.base.Splitter;
import org.springframework.util.AntPathMatcher;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@ -49,6 +50,9 @@ class NameBasedFilter implements BranchFilter {
}
private List<String> convert(String commaSeparatedString) {
if (commaSeparatedString == null)
return Collections.EMPTY_LIST;
ArrayList<String> result = new ArrayList<>();
for (String s : Splitter.on(',').omitEmptyStrings().trimResults().split(commaSeparatedString)) {
result.add(s);

View File

@ -3,7 +3,7 @@ package com.dabsquared.gitlabjenkins.trigger.handler;
import com.dabsquared.gitlabjenkins.cause.CauseData;
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.BuildState;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.WebHook;
import com.dabsquared.gitlabjenkins.publisher.GitLabCommitStatusPublisher;
@ -63,7 +63,7 @@ public abstract class AbstractWebHookTriggerHandler<H extends WebHook> implement
if (job instanceof AbstractProject && ((AbstractProject) job).getPublishersList().get(GitLabCommitStatusPublisher.class) != null) {
GitLabCommitStatusPublisher publisher =
(GitLabCommitStatusPublisher) ((AbstractProject) job).getPublishersList().get(GitLabCommitStatusPublisher.class);
GitLabApi client = job.getProperty(GitLabConnectionProperty.class).getClient();
GitLabClient client = job.getProperty(GitLabConnectionProperty.class).getClient();
BuildStatusUpdate buildStatusUpdate = retrieveBuildStatusUpdate(hook);
try {
if (client == null) {
@ -72,7 +72,7 @@ public abstract class AbstractWebHookTriggerHandler<H extends WebHook> implement
String targetUrl =
Jenkins.getInstance().getRootUrl() + job.getUrl() + job.getNextBuildNumber() + "/";
client.changeBuildStatus(buildStatusUpdate.getProjectId(), buildStatusUpdate.getSha(),
BuildState.pending, buildStatusUpdate.getRef(), publisher.getName(), targetUrl, null);
BuildState.pending, buildStatusUpdate.getRef(), publisher.getName(), targetUrl, BuildState.pending.name());
}
} catch (WebApplicationException | ProcessingException e) {
LOGGER.log(Level.SEVERE, "Failed to set build state to pending", e);
@ -80,7 +80,7 @@ public abstract class AbstractWebHookTriggerHandler<H extends WebHook> implement
}
}
private Action[] createActions(Job<?, ?> job, H hook) {
protected Action[] createActions(Job<?, ?> job, H hook) {
ArrayList<Action> actions = new ArrayList<>();
actions.add(new CauseAction(new GitLabWebHookCause(retrieveCauseData(hook))));
try {
@ -113,7 +113,7 @@ public abstract class AbstractWebHookTriggerHandler<H extends WebHook> implement
return null;
}
private void scheduleBuild(Job<?, ?> job, Action[] actions) {
protected void scheduleBuild(Job<?, ?> job, Action[] actions) {
int projectBuildDelay = 0;
if (job instanceof ParameterizedJobMixIn.ParameterizedJob) {
ParameterizedJobMixIn.ParameterizedJob abstractProject = (ParameterizedJobMixIn.ParameterizedJob) job;

View File

@ -1,10 +1,13 @@
package com.dabsquared.gitlabjenkins.trigger.handler.merge;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.Action;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.State;
import com.dabsquared.gitlabjenkins.trigger.TriggerOpenMergeRequest;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
/**
* @author Robin Müller
@ -14,25 +17,46 @@ public final class MergeRequestHookTriggerHandlerFactory {
private MergeRequestHookTriggerHandlerFactory() {}
public static MergeRequestHookTriggerHandler newMergeRequestHookTriggerHandler(boolean triggerOnMergeRequest,
boolean triggerOnAcceptedMergeRequest,
boolean triggerOnClosedMergeRequest,
TriggerOpenMergeRequest triggerOpenMergeRequest,
boolean skipWorkInProgressMergeRequest) {
if (triggerOnMergeRequest || triggerOpenMergeRequest != TriggerOpenMergeRequest.never) {
return new MergeRequestHookTriggerHandlerImpl(retrieveAllowedStates(triggerOnMergeRequest, triggerOpenMergeRequest),
boolean skipWorkInProgressMergeRequest,
boolean triggerOnApprovedMergeRequest) {
if (triggerOnMergeRequest || triggerOnAcceptedMergeRequest || triggerOnClosedMergeRequest || triggerOpenMergeRequest != TriggerOpenMergeRequest.never || triggerOnApprovedMergeRequest) {
return new MergeRequestHookTriggerHandlerImpl(retrieveAllowedStates(triggerOnMergeRequest, triggerOnAcceptedMergeRequest, triggerOnClosedMergeRequest, triggerOpenMergeRequest),
retrieveAllowedActions(triggerOnApprovedMergeRequest),
skipWorkInProgressMergeRequest);
} else {
return new NopMergeRequestHookTriggerHandler();
}
}
private static List<State> retrieveAllowedStates(boolean triggerOnMergeRequest, TriggerOpenMergeRequest triggerOpenMergeRequest) {
private static Set<Action> retrieveAllowedActions(boolean triggerOnApprovedMergeRequest) {
Set<Action> allowedActions = EnumSet.noneOf(Action.class);
if (triggerOnApprovedMergeRequest)
allowedActions.add(Action.approved);
return allowedActions;
}
private static List<State> retrieveAllowedStates(boolean triggerOnMergeRequest,
boolean triggerOnAcceptedMergeRequest,
boolean triggerOnClosedMergeRequest,
TriggerOpenMergeRequest triggerOpenMergeRequest) {
List<State> result = new ArrayList<>();
if (triggerOnMergeRequest) {
result.add(State.opened);
result.add(State.reopened);
}
if (triggerOnAcceptedMergeRequest) {
result.add(State.merged);
}
if (triggerOnClosedMergeRequest) {
result.add(State.closed);
}
if (triggerOpenMergeRequest != TriggerOpenMergeRequest.never) {
result.add(State.updated);
}
return result;
}
}

View File

@ -2,6 +2,7 @@ package com.dabsquared.gitlabjenkins.trigger.handler.merge;
import com.dabsquared.gitlabjenkins.cause.CauseData;
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.Action;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.MergeRequestHook;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.MergeRequestObjectAttributes;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.State;
@ -16,7 +17,8 @@ import hudson.plugins.git.GitSCM;
import hudson.plugins.git.RevisionParameterAction;
import org.apache.commons.lang.StringUtils;
import java.util.List;
import java.util.Collection;
import java.util.EnumSet;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -31,18 +33,24 @@ class MergeRequestHookTriggerHandlerImpl extends AbstractWebHookTriggerHandler<M
private static final Logger LOGGER = Logger.getLogger(MergeRequestHookTriggerHandlerImpl.class.getName());
private final List<State> allowedStates;
private final Collection<State> allowedStates;
private final boolean skipWorkInProgressMergeRequest;
private final Collection<Action> allowedActions;
MergeRequestHookTriggerHandlerImpl(List<State> allowedStates, boolean skipWorkInProgressMergeRequest) {
MergeRequestHookTriggerHandlerImpl(Collection<State> allowedStates, boolean skipWorkInProgressMergeRequest) {
this(allowedStates, EnumSet.noneOf(Action.class),skipWorkInProgressMergeRequest);
}
MergeRequestHookTriggerHandlerImpl(Collection<State> allowedStates, Collection<Action> allowedActions, boolean skipWorkInProgressMergeRequest) {
this.allowedStates = allowedStates;
this.allowedActions = allowedActions;
this.skipWorkInProgressMergeRequest = skipWorkInProgressMergeRequest;
}
@Override
public void handle(Job<?, ?> job, MergeRequestHook hook, boolean ciSkip, BranchFilter branchFilter, MergeRequestLabelFilter mergeRequestLabelFilter) {
MergeRequestObjectAttributes objectAttributes = hook.getObjectAttributes();
if (allowedStates.contains(objectAttributes.getState())
if (isAllowedByConfig(objectAttributes)
&& isLastCommitNotYetBuild(job, hook)
&& isNotSkipWorkInProgressMergeRequest(objectAttributes)
&& mergeRequestLabelFilter.isMergeRequestAllowed(hook.getObjectAttributes().getLabels())) {
@ -87,6 +95,10 @@ class MergeRequestHookTriggerHandlerImpl extends AbstractWebHookTriggerHandler<M
.withMergeRequestDescription(hook.getObjectAttributes().getDescription())
.withMergeRequestId(hook.getObjectAttributes().getId())
.withMergeRequestIid(hook.getObjectAttributes().getIid())
.withMergeRequestState(hook.getObjectAttributes().getState().toString())
.withMergedByUser(hook.getUser() == null ? null : hook.getUser().getUsername())
.withMergeRequestAssignee(hook.getAssignee() == null ? null : hook.getAssignee().getUsername())
.withMergeRequestTargetProjectId(hook.getObjectAttributes().getTargetProjectId())
.withTargetBranch(hook.getObjectAttributes().getTargetBranch())
.withTargetRepoName(hook.getObjectAttributes().getTarget().getName())
.withTargetNamespace(hook.getObjectAttributes().getTarget().getNamespace())
@ -125,6 +137,11 @@ class MergeRequestHookTriggerHandlerImpl extends AbstractWebHookTriggerHandler<M
private boolean isLastCommitNotYetBuild(Job<?, ?> project, MergeRequestHook hook) {
MergeRequestObjectAttributes objectAttributes = hook.getObjectAttributes();
if (objectAttributes.getAction() == Action.approved) {
LOGGER.log(Level.FINEST, "Skipping LastCommitNotYetBuild check for approve action");
return true;
}
if (objectAttributes != null && objectAttributes.getLastCommit() != null) {
Run<?, ?> mergeBuild = BuildUtil.getBuildBySHA1IncludingMergeBuilds(project, objectAttributes.getLastCommit().getId());
if (mergeBuild != null && StringUtils.equals(getTargetBranchFromBuild(mergeBuild), objectAttributes.getTargetBranch())) {
@ -140,6 +157,11 @@ class MergeRequestHookTriggerHandlerImpl extends AbstractWebHookTriggerHandler<M
return cause == null ? null : cause.getData().getTargetBranch();
}
private boolean isAllowedByConfig(MergeRequestObjectAttributes objectAttributes) {
return allowedStates.contains(objectAttributes.getState())
|| allowedActions.contains(objectAttributes.getAction());
}
private boolean isNotSkipWorkInProgressMergeRequest(MergeRequestObjectAttributes objectAttributes) {
Boolean workInProgress = objectAttributes.getWorkInProgress();
if (skipWorkInProgressMergeRequest && workInProgress != null && workInProgress) {

View File

@ -75,6 +75,7 @@ class NoteHookTriggerHandlerImpl extends AbstractWebHookTriggerHandler<NoteHook>
.withMergeRequestDescription(hook.getMergeRequest().getDescription())
.withMergeRequestId(hook.getMergeRequest().getId())
.withMergeRequestIid(hook.getMergeRequest().getIid())
.withMergeRequestTargetProjectId(hook.getMergeRequest().getTargetProjectId())
.withTargetBranch(hook.getMergeRequest().getTargetBranch())
.withTargetRepoName(hook.getMergeRequest().getTarget().getName())
.withTargetNamespace(hook.getMergeRequest().getTarget().getNamespace())

View File

@ -0,0 +1,19 @@
package com.dabsquared.gitlabjenkins.trigger.handler.pipeline;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.PipelineHook;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.PushHook;
import com.dabsquared.gitlabjenkins.trigger.filter.BranchFilter;
import com.dabsquared.gitlabjenkins.trigger.filter.MergeRequestLabelFilter;
import com.dabsquared.gitlabjenkins.trigger.handler.push.PushHookTriggerHandler;
import hudson.model.Job;
/**
* @author Milena Zachow
*/
class NopPipelineHookTriggerHandler implements PipelineHookTriggerHandler {
@Override
public void handle(Job<?, ?> job, PipelineHook hook, boolean ciSkip, BranchFilter branchFilter, MergeRequestLabelFilter mergeRequestLabelFilter) {
}
}

View File

@ -0,0 +1,9 @@
package com.dabsquared.gitlabjenkins.trigger.handler.pipeline;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.PipelineHook;
import com.dabsquared.gitlabjenkins.trigger.handler.WebHookTriggerHandler;
/**
* @author Milena Zachow
*/
public interface PipelineHookTriggerHandler extends WebHookTriggerHandler<PipelineHook> { }

View File

@ -0,0 +1,34 @@
package com.dabsquared.gitlabjenkins.trigger.handler.pipeline;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.State;
import java.util.ArrayList;
import java.util.List;
/**
* @author Milena Zachow
*/
public final class PipelineHookTriggerHandlerFactory {
public static final String SUCCESS = "success";
private PipelineHookTriggerHandlerFactory() {
}
public static PipelineHookTriggerHandler newPipelineHookTriggerHandler(boolean triggerOnPipelineEvent) {
if (triggerOnPipelineEvent) {
return new PipelineHookTriggerHandlerImpl(retrieve(triggerOnPipelineEvent));
} else {
return new NopPipelineHookTriggerHandler();
}
}
private static List<String> retrieve(boolean triggerOnPipelineEvent) {
List<String> result = new ArrayList<>();
if (triggerOnPipelineEvent) {
result.add(SUCCESS);
}
return result;
}
}

View File

@ -0,0 +1,159 @@
package com.dabsquared.gitlabjenkins.trigger.handler.pipeline;
import com.dabsquared.gitlabjenkins.cause.CauseData;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.PipelineEventObjectAttributes;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.PipelineHook;
import com.dabsquared.gitlabjenkins.trigger.exception.NoRevisionToBuildException;
import com.dabsquared.gitlabjenkins.trigger.filter.BranchFilter;
import com.dabsquared.gitlabjenkins.trigger.filter.MergeRequestLabelFilter;
import com.dabsquared.gitlabjenkins.trigger.handler.AbstractWebHookTriggerHandler;
import com.dabsquared.gitlabjenkins.util.BuildUtil;
import com.dabsquared.gitlabjenkins.util.LoggerUtil;
import hudson.model.AbstractProject;
import hudson.model.Job;
import hudson.model.Run;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.RevisionParameterAction;
import javax.ws.rs.WebApplicationException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.dabsquared.gitlabjenkins.cause.CauseDataBuilder.causeData;
import static com.dabsquared.gitlabjenkins.trigger.handler.builder.generated.BuildStatusUpdateBuilder.buildStatusUpdate;
/**
* @author Milena Zachow
*/
class PipelineHookTriggerHandlerImpl extends AbstractWebHookTriggerHandler<PipelineHook> implements PipelineHookTriggerHandler {
private static final Logger LOGGER = Logger.getLogger(PipelineHookTriggerHandlerImpl.class.getName());
private final List<String> allowedStates;
PipelineHookTriggerHandlerImpl(List<String> allowedStates) {
this.allowedStates = allowedStates;
}
@Override
public void handle(Job<?, ?> job, PipelineHook hook, boolean ciSkip, BranchFilter branchFilter, MergeRequestLabelFilter mergeRequestLabelFilter) {
PipelineEventObjectAttributes objectAttributes = hook.getObjectAttributes();
try {
if (job instanceof AbstractProject<?, ?>) {
GitLabConnectionProperty property = job.getProperty(GitLabConnectionProperty.class);
if (property != null && property.getClient() != null) {
GitLabClient client = property.getClient();
com.dabsquared.gitlabjenkins.gitlab.api.model.Project projectForName = client.getProject(hook.getProject().getPathWithNamespace());
hook.setProjectId(projectForName.getId());
}
}
} catch (WebApplicationException e) {
LOGGER.log(Level.WARNING, "Failed to communicate with gitlab server to determine project id: " + e.getMessage(), e);
}
if (allowedStates.contains(objectAttributes.getStatus()) && !isLastAlreadyBuild(job,hook)) {
if (ciSkip && isCiSkip(hook)) {
LOGGER.log(Level.INFO, "Skipping due to ci-skip.");
return;
}
//we do not call super here, since we do not want the status to be changed
//in case of pipeline events that could lead to a deadlock
String targetBranch = getTargetBranch(hook);
if (branchFilter.isBranchAllowed(targetBranch)) {
LOGGER.log(Level.INFO, "{0} triggered for {1}.", LoggerUtil.toArray(job.getFullName(), getTriggerType()));
super.scheduleBuild(job, createActions(job, hook));
} else {
LOGGER.log(Level.INFO, "branch {0} is not allowed", targetBranch);
}
}
}
@Override
protected boolean isCiSkip(PipelineHook hook) {
//we don't get a commit message or suchlike that could contain ci-skip
return false;
}
@Override
protected String getTargetBranch(PipelineHook hook) {
return hook.getObjectAttributes().getRef() == null ? null : hook.getObjectAttributes().getRef().replaceFirst("^refs/heads/", "");
}
@Override
protected String getTriggerType() {
return "pipeline event";
}
@Override
protected CauseData retrieveCauseData(PipelineHook hook) {
return causeData()
.withActionType(CauseData.ActionType.PIPELINE)
.withSourceProjectId(hook.getProjectId())
.withBranch(getTargetBranch(hook)==null?"":getTargetBranch(hook))
.withSourceBranch(getTargetBranch(hook)==null?"":getTargetBranch(hook))
.withUserName(hook.getUser()==null||hook.getUser().getName()==null?"":hook.getUser().getName())
.withSourceRepoName(hook.getRepository()==null||hook.getRepository().getName()==null?"":hook.getRepository().getName())
.withSourceNamespace(hook.getProject()==null||hook.getProject().getNamespace()==null?"":hook.getProject().getNamespace())
.withSourceRepoSshUrl(hook.getRepository()==null||hook.getRepository().getGitSshUrl()==null?"":hook.getRepository().getGitSshUrl())
.withSourceRepoHttpUrl(hook.getRepository()==null||hook.getRepository()==null?"":hook.getRepository().getGitHttpUrl())
.withMergeRequestTitle("")
.withTargetProjectId(hook.getProjectId())
.withTargetBranch(getTargetBranch(hook)==null?"":getTargetBranch(hook))
.withTargetRepoName("")
.withTargetNamespace("")
.withTargetRepoSshUrl("")
.withTargetRepoHttpUrl("")
.withLastCommit(hook.getObjectAttributes().getSha())
.withTriggeredByUser(hook.getUser()==null||hook.getUser().getName()==null?"":hook.getUser().getName())
.withRef(hook.getObjectAttributes().getRef()==null?"":hook.getObjectAttributes().getRef())
.withSha(hook.getObjectAttributes().getSha()==null?"":hook.getObjectAttributes().getSha())
.withBeforeSha(hook.getObjectAttributes().getBeforeSha()==null?"":hook.getObjectAttributes().getBeforeSha())
.withStatus(hook.getObjectAttributes().getStatus()==null?"":hook.getObjectAttributes().getStatus().toString())
.withStages(hook.getObjectAttributes().getStages()==null?"":hook.getObjectAttributes().getStages().toString())
.withCreatedAt(hook.getObjectAttributes().getCreatedAt()==null?"":hook.getObjectAttributes().getCreatedAt().toString())
.withFinishedAt(hook.getObjectAttributes().getFinishedAt()==null?"":hook.getObjectAttributes().getFinishedAt().toString())
.withBuildDuration(String.valueOf(hook.getObjectAttributes().getDuration()))
.build();
}
@Override
protected RevisionParameterAction createRevisionParameter(PipelineHook hook, GitSCM gitSCM) throws NoRevisionToBuildException {
return new RevisionParameterAction(retrieveRevisionToBuild(hook), retrieveUrIish(hook));
}
@Override
protected BuildStatusUpdate retrieveBuildStatusUpdate(PipelineHook hook) {
return buildStatusUpdate()
.withProjectId(hook.getProjectId())
.withSha(hook.getObjectAttributes().getSha())
.withRef(hook.getObjectAttributes().getRef())
.build();
}
private String retrieveRevisionToBuild(PipelineHook hook) throws NoRevisionToBuildException {
if (hook.getObjectAttributes() != null
&& hook.getObjectAttributes().getSha() != null) {
return hook.getObjectAttributes().getSha();
} else {
throw new NoRevisionToBuildException();
}
}
private boolean isLastAlreadyBuild(Job<?, ?> project, PipelineHook hook) {
PipelineEventObjectAttributes objectAttributes = hook.getObjectAttributes();
if (objectAttributes != null && objectAttributes.getSha() != null) {
Run<?, ?> lastBuild = BuildUtil.getBuildBySHA1IncludingMergeBuilds(project, objectAttributes.getSha());
if (lastBuild != null) {
LOGGER.log(Level.INFO, "Last commit has already been built in build #" + lastBuild.getNumber());
return true;
}
}
return false;
}
}

View File

@ -1,10 +1,11 @@
package com.dabsquared.gitlabjenkins.trigger.handler.push;
import com.dabsquared.gitlabjenkins.GitLabPushTrigger;
import com.dabsquared.gitlabjenkins.cause.CauseData;
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.Branch;
import com.dabsquared.gitlabjenkins.gitlab.api.model.BuildState;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
@ -20,8 +21,10 @@ import hudson.model.Action;
import hudson.model.CauseAction;
import hudson.model.Job;
import hudson.plugins.git.RevisionParameterAction;
import hudson.triggers.Trigger;
import jenkins.model.Jenkins;
import jenkins.model.ParameterizedJobMixIn;
import jenkins.model.ParameterizedJobMixIn.ParameterizedJob;
import org.eclipse.jgit.transport.URIish;
import javax.ws.rs.ProcessingException;
@ -51,27 +54,34 @@ class OpenMergeRequestPushHookTriggerHandler implements PushHookTriggerHandler {
@Override
public void handle(Job<?, ?> job, PushHook hook, boolean ciSkip, BranchFilter branchFilter, MergeRequestLabelFilter mergeRequestLabelFilter) {
try {
if (job instanceof AbstractProject<?, ?>) {
AbstractProject<?, ?> project = (AbstractProject<?, ?>) job;
try {
if (job instanceof ParameterizedJobMixIn.ParameterizedJob) {
ParameterizedJob project = (ParameterizedJobMixIn.ParameterizedJob) job;
GitLabConnectionProperty property = job.getProperty(GitLabConnectionProperty.class);
final GitLabPushTrigger trigger = project.getTrigger(GitLabPushTrigger.class);
Integer projectId = hook.getProjectId();
if (property != null && property.getClient() != null && projectId != null && trigger != null) {
GitLabApi client = property.getClient();
for (MergeRequest mergeRequest : getOpenMergeRequests(client, projectId.toString())) {
if (mergeRequestLabelFilter.isMergeRequestAllowed(mergeRequest.getLabels())) {
handleMergeRequest(job, hook, ciSkip, branchFilter, client, mergeRequest);
for (Trigger t : project.getTriggers().values()) {
if (t instanceof GitLabPushTrigger) {
final GitLabPushTrigger trigger = (GitLabPushTrigger) t;
Integer projectId = hook.getProjectId();
if (property != null && property.getClient() != null && projectId != null && trigger != null) {
GitLabClient client = property.getClient();
for (MergeRequest mergeRequest : getOpenMergeRequests(client, projectId.toString())) {
if (mergeRequestLabelFilter.isMergeRequestAllowed(mergeRequest.getLabels())) {
handleMergeRequest(job, hook, ciSkip, branchFilter, client, mergeRequest);
}
}
}
}
}
}
} else {
LOGGER.log(Level.FINE, "Not a ParameterizedJob: {0}",LoggerUtil.toArray(job.getClass().getName()));
}
} catch (WebApplicationException | ProcessingException e) {
LOGGER.log(Level.WARNING, "Failed to communicate with gitlab server to determine if this is an update for a merge request: " + e.getMessage(), e);
}
}
private List<MergeRequest> getOpenMergeRequests(GitLabApi client, String projectId) {
private List<MergeRequest> getOpenMergeRequests(GitLabClient client, String projectId) {
List<MergeRequest> result = new ArrayList<>();
Integer page = 1;
do {
@ -82,7 +92,7 @@ class OpenMergeRequestPushHookTriggerHandler implements PushHookTriggerHandler {
return result;
}
private void handleMergeRequest(Job<?, ?> job, PushHook hook, boolean ciSkip, BranchFilter branchFilter, GitLabApi client, MergeRequest mergeRequest) {
private void handleMergeRequest(Job<?, ?> job, PushHook hook, boolean ciSkip, BranchFilter branchFilter, GitLabClient client, MergeRequest mergeRequest) {
if (ciSkip && mergeRequest.getDescription() != null && mergeRequest.getDescription().contains("[ci-skip]")) {
LOGGER.log(Level.INFO, "Skipping MR " + mergeRequest.getTitle() + " due to ci-skip.");
return;
@ -96,7 +106,7 @@ class OpenMergeRequestPushHookTriggerHandler implements PushHookTriggerHandler {
String targetBranch = mergeRequest.getTargetBranch();
String sourceBranch = mergeRequest.getSourceBranch();
if (targetBranch != null && branchFilter.isBranchAllowed(targetBranch) && hook.getRef().endsWith(targetBranch) && sourceBranch != null) {
if (targetBranch != null && branchFilter.isBranchAllowed(targetBranch) && hook.getRef().equals("refs/heads/"+targetBranch) && sourceBranch != null) {
LOGGER.log(Level.INFO, "{0} triggered for push to target branch of open merge request #{1}.",
LoggerUtil.toArray(job.getFullName(), mergeRequest.getId()));
@ -130,6 +140,7 @@ class OpenMergeRequestPushHookTriggerHandler implements PushHookTriggerHandler {
.withMergeRequestDescription(mergeRequest.getDescription())
.withMergeRequestId(mergeRequest.getId())
.withMergeRequestIid(mergeRequest.getIid())
.withMergeRequestTargetProjectId(mergeRequest.getTargetProjectId())
.withTargetBranch(mergeRequest.getTargetBranch())
.withTargetRepoName(hook.getRepository().getName())
.withTargetNamespace(hook.getProject().getNamespace())
@ -145,10 +156,10 @@ class OpenMergeRequestPushHookTriggerHandler implements PushHookTriggerHandler {
if (job instanceof AbstractProject && ((AbstractProject) job).getPublishersList().get(GitLabCommitStatusPublisher.class) != null) {
GitLabCommitStatusPublisher publisher =
(GitLabCommitStatusPublisher) ((AbstractProject) job).getPublishersList().get(GitLabCommitStatusPublisher.class);
GitLabApi client = job.getProperty(GitLabConnectionProperty.class).getClient();
GitLabClient client = job.getProperty(GitLabConnectionProperty.class).getClient();
try {
String targetUrl = Jenkins.getInstance().getRootUrl() + job.getUrl() + job.getNextBuildNumber() + "/";
client.changeBuildStatus(projectId, commit, BuildState.pending, ref, publisher.getName(), targetUrl, null);
client.changeBuildStatus(projectId, commit, BuildState.pending, ref, publisher.getName(), targetUrl, BuildState.pending.name());
} catch (WebApplicationException | ProcessingException e) {
LOGGER.log(Level.SEVERE, "Failed to set build state to pending", e);
}

View File

@ -42,8 +42,9 @@ class PushHookTriggerHandlerImpl extends AbstractWebHookTriggerHandler<PushHook>
@Override
protected CauseData retrieveCauseData(PushHook hook) {
CauseData.ActionType actionType = hook.getObjectKind().equals("tag_push") ? CauseData.ActionType.TAG_PUSH : CauseData.ActionType.PUSH;
return causeData()
.withActionType(CauseData.ActionType.PUSH)
.withActionType(actionType)
.withSourceProjectId(hook.getProjectId())
.withTargetProjectId(hook.getProjectId())
.withBranch(getTargetBranch(hook))
@ -60,6 +61,10 @@ class PushHookTriggerHandlerImpl extends AbstractWebHookTriggerHandler<PushHook>
.withMergeRequestDescription("")
.withMergeRequestId(null)
.withMergeRequestIid(null)
.withMergeRequestState(null)
.withMergedByUser("")
.withMergeRequestAssignee("")
.withMergeRequestTargetProjectId(null)
.withTargetBranch(getTargetBranch(hook))
.withTargetRepoName("")
.withTargetNamespace("")

View File

@ -14,7 +14,7 @@ public class BuildUtil {
for (Run<?, ?> build : project.getBuilds()) {
BuildData data = build.getAction(BuildData.class);
MergeRecord merge = build.getAction(MergeRecord.class);
if (hasLastBuild(data) && isNoMergeBuild(data, merge)) {
if (hasLastBuild(data) && isNoMergeBuild(data, merge)) {
for (Branch branch : data.lastBuild.getRevision().getBranches()) {
if (branch.getName().endsWith("/" + branchName)) {
return build;
@ -27,10 +27,11 @@ public class BuildUtil {
public static Run<?, ?> getBuildBySHA1WithoutMergeBuilds(Job<?, ?> project, String sha1) {
for (Run<?, ?> build : project.getBuilds()) {
BuildData data = build.getAction(BuildData.class);
MergeRecord merge = build.getAction(MergeRecord.class);
if (hasLastBuild(data) && isNoMergeBuild(data, merge) && data.lastBuild.isFor(sha1)) {
return build;
for(BuildData data : build.getActions(BuildData.class)) {
if (hasLastBuild(data) && isNoMergeBuild(data, merge) && data.lastBuild.isFor(sha1)) {
return build;
}
}
}
return null;
@ -38,12 +39,13 @@ public class BuildUtil {
public static Run<?, ?> getBuildBySHA1IncludingMergeBuilds(Job<?, ?> project, String sha1) {
for (Run<?, ?> build : project.getBuilds()) {
BuildData data = build.getAction(BuildData.class);
if (data != null
&& data.lastBuild != null
&& data.lastBuild.getMarked() != null
&& data.lastBuild.getMarked().getSha1String().equals(sha1)) {
return build;
for(BuildData data : build.getActions(BuildData.class)) {
if (data != null
&& data.lastBuild != null
&& data.lastBuild.getMarked() != null
&& data.lastBuild.getMarked().getSha1String().equals(sha1)) {
return build;
}
}
}
return null;

View File

@ -1,15 +1,25 @@
package com.dabsquared.gitlabjenkins.util;
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.BuildState;
import hudson.EnvVars;
import hudson.model.Cause;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.Cause.UpstreamCause;
import hudson.plugins.git.Revision;
import hudson.plugins.git.util.Build;
import hudson.plugins.git.util.BuildData;
import jenkins.model.Jenkins;
import jenkins.plugins.git.AbstractGitSCMSource;
import jenkins.scm.api.SCMRevision;
import jenkins.scm.api.SCMRevisionAction;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.lib.ObjectId;
import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.ProcessingException;
@ -18,6 +28,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -32,24 +43,23 @@ public class CommitStatusUpdater {
private final static Logger LOGGER = Logger.getLogger(CommitStatusUpdater.class.getName());
public static void updateCommitStatus(Run<?, ?> build, TaskListener listener, BuildState state, String name) {
GitLabApi client = getClient(build);
GitLabClient client = getClient(build);
if (client == null) {
println(listener, "No GitLab connection configured");
return;
}
try {
String commitHash = getBuildRevision(build);
String buildUrl = getBuildUrl(build);
final String buildUrl = getBuildUrl(build);
for (String gitlabProjectId : retrieveGitlabProjectIds(build, build.getEnvironment(listener))) {
for (final GitLabBranchBuild gitLabBranchBuild : retrieveGitlabProjectIds(build, build.getEnvironment(listener))) {
try {
if (existsCommit(client, gitlabProjectId, commitHash)) {
client.changeBuildStatus(gitlabProjectId, commitHash, state, getBuildBranch(build), name, buildUrl, null);
if (existsCommit(client, gitLabBranchBuild.getProjectId(), gitLabBranchBuild.getRevisionHash())) {
client.changeBuildStatus(gitLabBranchBuild.getProjectId(), gitLabBranchBuild.getRevisionHash(), state, getBuildBranch(build), name, buildUrl, state.name());
}
} catch (WebApplicationException | ProcessingException e) {
printf(listener, "Failed to update Gitlab commit status for project '%s': %s%n", gitlabProjectId, e.getMessage());
LOGGER.log(Level.SEVERE, String.format("Failed to update Gitlab commit status for project '%s'", gitlabProjectId), e);
printf(listener, "Failed to update Gitlab commit status for project '%s': %s%n", gitLabBranchBuild.getProjectId(), e.getMessage());
LOGGER.log(Level.SEVERE, String.format("Failed to update Gitlab commit status for project '%s'", gitLabBranchBuild.getProjectId()), e);
}
}
} catch (IOException | InterruptedException | IllegalStateException e) {
@ -73,6 +83,82 @@ public class CommitStatusUpdater {
}
}
private static boolean existsCommit(GitLabClient client, String gitlabProjectId, String commitHash) {
try {
client.getCommit(gitlabProjectId, commitHash);
return true;
} catch (NotFoundException e) {
LOGGER.log(Level.FINE, String.format("Project (%s) and commit (%s) combination not found", gitlabProjectId, commitHash));
return false;
}
}
private static String getBuildBranch(Run<?, ?> build) {
GitLabWebHookCause cause = build.getCause(GitLabWebHookCause.class);
return cause == null ? null : cause.getData().getSourceBranch();
}
private static String getBuildUrl(Run<?, ?> build) {
return DisplayURLProvider.get().getRunURL(build);
}
private static List<GitLabBranchBuild> retrieveGitlabProjectIds(Run<?, ?> build, EnvVars environment) {
LOGGER.log(Level.INFO, "Retrieving gitlab project ids");
final List<GitLabBranchBuild> result = new ArrayList<>();
GitLabWebHookCause gitlabCause = build.getCause(GitLabWebHookCause.class);
if (gitlabCause != null) {
return Collections.singletonList(new GitLabBranchBuild(
gitlabCause.getData().getSourceProjectId().toString(), gitlabCause.getData().getLastCommit()));
}
// Check upstream causes for GitLabWebHookCause
List<GitLabBranchBuild> builds = findBuildsFromUpstreamCauses(build.getCauses());
if (!builds.isEmpty()) {
return builds;
}
final GitLabClient gitLabClient = getClient(build);
if (gitLabClient == null) {
LOGGER.log(Level.WARNING, "No gitlab client found.");
return result;
}
final List<BuildData> buildDatas = build.getActions(BuildData.class);
if (CollectionUtils.isEmpty(buildDatas)) {
LOGGER.log(Level.INFO, "Build does not contain build data.");
return result;
}
if (buildDatas.size() == 1) {
addGitLabBranchBuild(result, getBuildRevision(build), buildDatas.get(0).getRemoteUrls(), environment, gitLabClient);
} else {
final SCMRevisionAction scmRevisionAction = build.getAction(SCMRevisionAction.class);
if (scmRevisionAction == null) {
LOGGER.log(Level.INFO, "Build does not contain SCM revision action.");
return result;
}
final SCMRevision scmRevision = scmRevisionAction.getRevision();
String scmRevisionHash = null;
if (scmRevision instanceof AbstractGitSCMSource.SCMRevisionImpl) {
scmRevisionHash = ((AbstractGitSCMSource.SCMRevisionImpl) scmRevision).getHash();
}
for (final BuildData buildData : buildDatas) {
for (final Entry<String, Build> buildByBranchName : buildData.getBuildsByBranchName().entrySet()) {
if (buildByBranchName.getValue().getSHA1().equals(ObjectId.fromString(scmRevisionHash))) {
addGitLabBranchBuild(result, scmRevisionHash, buildData.getRemoteUrls(), environment, gitLabClient);
}
}
}
}
return result;
}
private static String getBuildRevision(Run<?, ?> build) {
GitLabWebHookCause cause = build.getCause(GitLabWebHookCause.class);
if (cause != null) {
@ -92,67 +178,66 @@ public class CommitStatusUpdater {
return action.getLastBuild(lastBuiltRevision.getSha1()).getMarked().getSha1String();
}
private static boolean existsCommit(GitLabApi client, String gitlabProjectId, String commitHash) {
try {
client.getCommit(gitlabProjectId, commitHash);
return true;
} catch (NotFoundException e) {
LOGGER.log(Level.FINE, String.format("Project (%s) and commit (%s) combination not found", gitlabProjectId, commitHash));
return false;
}
}
private static String getBuildBranch(Run<?, ?> build) {
GitLabWebHookCause cause = build.getCause(GitLabWebHookCause.class);
return cause == null ? null : cause.getData().getSourceBranch();
}
private static String getBuildUrl(Run<?, ?> build) {
return Jenkins.getInstance().getRootUrl() + build.getUrl();
}
private static List<String> retrieveGitlabProjectIds(Run<?, ?> build, EnvVars environment) {
LOGGER.log(Level.INFO, "Retrieving gitlab project ids");
GitLabWebHookCause cause = build.getCause(GitLabWebHookCause.class);
if (cause != null) {
return Collections.singletonList(cause.getData().getSourceProjectId().toString());
}
List<String> result = new ArrayList<>();
GitLabApi gitLabClient = getClient(build);
if (gitLabClient == null) {
LOGGER.log(Level.WARNING, "No gitlab client found.");
return result;
}
final BuildData buildData = build.getAction(BuildData.class);
if (buildData == null) {
LOGGER.log(Level.INFO, "Build does not contain build data.");
return result;
}
final Set<String> remoteUrls = buildData.getRemoteUrls();
private static void addGitLabBranchBuild(List<GitLabBranchBuild> result, String scmRevisionHash,
Set<String> remoteUrls, EnvVars environment, GitLabClient gitLabClient) {
for (String remoteUrl : remoteUrls) {
try {
LOGGER.log(Level.INFO, "Retrieving the gitlab project id from remote url {0}", remoteUrl);
final String projectNameWithNameSpace = ProjectIdUtil.retrieveProjectId(environment.expand(remoteUrl));
final String projectNameWithNameSpace = ProjectIdUtil.retrieveProjectId(gitLabClient, environment.expand(remoteUrl));
if (StringUtils.isNotBlank(projectNameWithNameSpace)) {
String projectId = projectNameWithNameSpace;
if (projectNameWithNameSpace.contains(".")) {
try {
projectId = gitLabClient.getProject(projectNameWithNameSpace).getId().toString();
} catch (WebApplicationException | ProcessingException e) {
LOGGER.log(Level.SEVERE, String.format("Failed to retrieve projectId for project '%s'", projectNameWithNameSpace), e);
LOGGER.log(Level.SEVERE, String.format("Failed to retrieve projectId for project '%s'",
projectNameWithNameSpace), e);
}
}
result.add(projectId);
result.add(new GitLabBranchBuild(projectId, scmRevisionHash));
}
} catch (ProjectIdUtil.ProjectIdResolutionException e) {
// nothing to do
LOGGER.log(Level.WARNING, "Did not match project id in remote url.");
}
}
return result;
}
private static List<GitLabBranchBuild> findBuildsFromUpstreamCauses(List<Cause> causes) {
for (Cause cause : causes) {
if (cause instanceof UpstreamCause) {
List<Cause> upCauses = ((UpstreamCause) cause).getUpstreamCauses(); // Non null, returns empty list when none are set
for (Cause upCause : upCauses) {
if (upCause instanceof GitLabWebHookCause) {
GitLabWebHookCause gitlabCause = (GitLabWebHookCause) upCause;
return Collections.singletonList(
new GitLabBranchBuild(gitlabCause.getData().getSourceProjectId().toString(),
gitlabCause.getData().getLastCommit()));
}
}
List<GitLabBranchBuild> builds = findBuildsFromUpstreamCauses(upCauses);
if (!builds.isEmpty()) {
return builds;
}
}
}
return Collections.emptyList();
}
public static class GitLabBranchBuild {
private final String projectId;
private final String revisionHash;
public GitLabBranchBuild(final String projectId, final String revisionHash) {
this.projectId = projectId;
this.revisionHash = revisionHash;
}
public String getProjectId() {
return this.projectId;
}
public String getRevisionHash() {
return this.revisionHash;
}
}
}

View File

@ -46,7 +46,7 @@ public final class JsonUtil {
private static class DateModule extends SimpleModule {
private static final String[] DATE_FORMATS = new String[] {
"yyyy-MM-dd HH:mm:ss Z", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "yyyy-MM-dd'T'HH:mm:ssX"
"yyyy-MM-dd HH:mm:ss Z", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "yyyy-MM-dd'T'HH:mm:ssX", "yyyy-MM-dd'T'HH:mm:ss.SSSX", "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
};
private DateModule() {

View File

@ -1,5 +1,7 @@
package com.dabsquared.gitlabjenkins.util;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import org.eclipse.jgit.transport.URIish;
import java.net.URISyntaxException;
@ -11,13 +13,23 @@ import java.util.regex.Pattern;
*/
public final class ProjectIdUtil {
private static final Pattern PROJECT_ID_PATTERN = Pattern.compile("^/?(.*/)?(?<projectId>.*/.*)(\\.git)$");
private static final Pattern PROJECT_ID_PATTERN = Pattern.compile("^/?(?<projectId>.*)(\\.git)$");
private ProjectIdUtil() { }
public static String retrieveProjectId(String remoteUrl) throws ProjectIdResolutionException {
public static String retrieveProjectId(GitLabClient client, String remoteUrl) throws ProjectIdResolutionException {
try {
String projectId = new URIish(remoteUrl).getPath();
String baseUri = client.getHostUrl();
String projectId;
if (baseUri != null && remoteUrl.startsWith(baseUri)) {
projectId = new URIish(remoteUrl.substring(baseUri.length(), remoteUrl.length())).getPath();
} else {
projectId = new URIish(remoteUrl).getPath();
}
if (projectId.startsWith(":")) {
projectId = projectId.substring(1);
}
Matcher matcher = PROJECT_ID_PATTERN.matcher(projectId);
if (matcher.matches()) {
return matcher.group("projectId");

View File

@ -3,6 +3,7 @@ package com.dabsquared.gitlabjenkins.webhook;
import com.dabsquared.gitlabjenkins.util.ACLUtil;
import com.dabsquared.gitlabjenkins.webhook.build.MergeRequestBuildAction;
import com.dabsquared.gitlabjenkins.webhook.build.NoteBuildAction;
import com.dabsquared.gitlabjenkins.webhook.build.PipelineBuildAction;
import com.dabsquared.gitlabjenkins.webhook.build.PushBuildAction;
import com.dabsquared.gitlabjenkins.webhook.status.BranchBuildPageRedirectAction;
import com.dabsquared.gitlabjenkins.webhook.status.BranchStatusPngAction;
@ -110,7 +111,9 @@ public class ActionResolver {
case "Tag Push Hook":
return new PushBuildAction(project, getRequestBody(request), tokenHeader);
case "Note Hook":
return new NoteBuildAction(project, getRequestBody(request), tokenHeader);
return new NoteBuildAction(project, getRequestBody(request), tokenHeader);
case "Pipeline Hook":
return new PipelineBuildAction(project, getRequestBody(request), tokenHeader);
default:
LOGGER.log(Level.FINE, "Unsupported X-Gitlab-Event header: {0}", eventHeader);
return new NoopAction();

View File

@ -0,0 +1,69 @@
package com.dabsquared.gitlabjenkins.webhook.build;
import com.dabsquared.gitlabjenkins.GitLabPushTrigger;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.*;
import com.dabsquared.gitlabjenkins.util.JsonUtil;
import hudson.model.Item;
import hudson.model.Job;
import hudson.security.ACL;
import hudson.util.HttpResponses;
import jenkins.model.Jenkins;
import org.apache.commons.lang.StringUtils;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.dabsquared.gitlabjenkins.util.JsonUtil.toPrettyPrint;
/**
* @author Milena Zachow
*/
public class PipelineBuildAction extends BuildWebHookAction {
private final static Logger LOGGER = Logger.getLogger(PipelineBuildAction.class.getName());
private Item project;
private PipelineHook pipelineBuildHook;
private final String secretToken;
public PipelineBuildAction(Item project, String json, String secretToken) {
LOGGER.log(Level.FINE, "Pipeline event: {0}", toPrettyPrint(json));
this.project = project;
this.pipelineBuildHook = JsonUtil.read(json, PipelineHook.class);
this.secretToken = secretToken;
}
void processForCompatibility() {
//if no project is defined, set it here
if (this.pipelineBuildHook.getProject() == null && this.pipelineBuildHook.getRepository() != null) {
try {
String path = new URL(this.pipelineBuildHook.getRepository().getGitHttpUrl()).getPath();
if (StringUtils.isNotBlank(path)) {
Project project = new Project();
project.setNamespace(path.replaceFirst("/", "").substring(0, path.lastIndexOf("/")));
this.pipelineBuildHook.setProject(project);
} else {
LOGGER.log(Level.WARNING, "Could not find suitable namespace.");
}
} catch (MalformedURLException ignored) {
LOGGER.log(Level.WARNING, "Invalid repository url found while building namespace.");
}
}
}
void execute() {
if (!(project instanceof Job<?, ?>)) {
throw HttpResponses.errorWithoutStack(409, "Pipeline Hook is not supported for this project");
}
ACL.impersonate(ACL.SYSTEM, new TriggerNotifier(project, secretToken, Jenkins.getAuthentication()) {
@Override
protected void performOnPost(GitLabPushTrigger trigger) {
trigger.onPost(pipelineBuildHook);
}
});
throw HttpResponses.ok();
}
}

View File

@ -10,17 +10,16 @@ import javax.ws.rs.ProcessingException;
import javax.ws.rs.WebApplicationException;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousStepExecution;
import org.jenkinsci.plugins.workflow.steps.Step;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.jenkinsci.plugins.workflow.steps.*;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.export.ExportedBean;
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
import com.google.common.collect.ImmutableSet;
import hudson.Extension;
@ -73,18 +72,17 @@ public class AcceptGitLabMergeRequestStep extends Step {
protected Void run() throws Exception {
GitLabWebHookCause cause = run.getCause(GitLabWebHookCause.class);
if (cause != null) {
Integer projectId = cause.getData().getTargetProjectId();
Integer mergeRequestId = cause.getData().getMergeRequestId();
if (projectId != null && mergeRequestId != null) {
GitLabApi client = getClient(run);
MergeRequest mergeRequest = cause.getData().getMergeRequest();
if (mergeRequest != null) {
GitLabClient client = getClient(run);
if (client == null) {
println("No GitLab connection configured");
} else {
try {
client.acceptMergeRequest(projectId, mergeRequestId, step.mergeCommitMessage, false);
client.acceptMergeRequest(mergeRequest, step.mergeCommitMessage, false);
} catch (WebApplicationException | ProcessingException e) {
printf("Failed to accept merge request for project '%s': %s%n", projectId, e.getMessage());
LOGGER.log(Level.SEVERE, String.format("Failed to accept merge request for project '%s'", projectId), e);
printf("Failed to accept merge request for project '%s': %s%n", mergeRequest.getProjectId(), e.getMessage());
LOGGER.log(Level.SEVERE, String.format("Failed to accept merge request for project '%s'", mergeRequest.getProjectId()), e);
}
}
}

View File

@ -21,6 +21,8 @@ import org.kohsuke.stapler.export.ExportedBean;
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
import com.google.common.collect.ImmutableSet;
import hudson.Extension;
@ -73,18 +75,17 @@ public class AddGitLabMergeRequestCommentStep extends Step {
protected Void run() throws Exception {
GitLabWebHookCause cause = run.getCause(GitLabWebHookCause.class);
if (cause != null) {
Integer projectId = cause.getData().getTargetProjectId();
Integer mergeRequestId = cause.getData().getMergeRequestId();
if (projectId != null && mergeRequestId != null) {
GitLabApi client = getClient(run);
MergeRequest mergeRequest = cause.getData().getMergeRequest();
if (mergeRequest != null) {
GitLabClient client = getClient(run);
if (client == null) {
println("No GitLab connection configured");
} else {
try {
client.createMergeRequestNote(projectId, mergeRequestId, step.getComment());
client.createMergeRequestNote(mergeRequest, step.getComment());
} catch (WebApplicationException | ProcessingException e) {
printf("Failed to add comment on Merge Request for project '%s': %s%n", projectId, e.getMessage());
LOGGER.log(Level.SEVERE, String.format("Failed to add comment on Merge Request for project '%s'", projectId), e);
printf("Failed to add comment on Merge Request for project '%s': %s%n", mergeRequest.getProjectId(), e.getMessage());
LOGGER.log(Level.SEVERE, String.format("Failed to add comment on Merge Request for project '%s'", mergeRequest.getProjectId()), e);
}
}
}

View File

@ -6,16 +6,25 @@
<f:entry title="Push Events" field="triggerOnPush">
<f:checkbox default="true"/>
</f:entry>
<f:entry title="Merge Request Events" field="triggerOnMergeRequest">
<f:entry title="Opened Merge Request Events" field="triggerOnMergeRequest">
<f:checkbox default="true"/>
</f:entry>
<f:entry title="Accepted Merge Request Events" field="triggerOnAcceptedMergeRequest">
<f:checkbox default="false"/>
</f:entry>
<f:entry title="Closed Merge Request Events" field="triggerOnClosedMergeRequest">
<f:checkbox default="false"/>
</f:entry>
<f:entry title="Rebuild open Merge Requests" field="triggerOpenMergeRequestOnPush">
<f:select/>
</f:entry>
<f:entry title="Approved Merge Requests (EE-only)" field="triggerOnApprovedMergeRequest">
<f:checkbox default="true"/>
</f:entry>
<f:entry title="Comments" field="triggerOnNoteRequest">
<f:checkbox default="true"/>
</f:entry>
<f:entry title="Comment for triggering a build" help="/plugin/gitlab-plugin/help/help-noteRegex.html">
<f:entry title="Comment (regex) for triggering a build" help="/plugin/gitlab-plugin/help/help-noteRegex.html">
<f:textbox field="noteRegex" default="Jenkins please retry a build"/>
</f:entry>
</table>
@ -30,6 +39,9 @@
<f:entry title="Set build description to build cause (eg. Merge request or Git Push )" field="setBuildDescription">
<f:checkbox default="true"/>
</f:entry>
<f:entry title="Build on successful pipeline events" field="triggerOnPipelineEvent">
<f:checkbox default="false"/>
</f:entry>
<f:entry title="Allowed branches">
<table>
@ -73,6 +85,7 @@
<table>
<f:readOnlyTextbox field="secretToken" id="secretToken"/>
<f:validateButton title="${%Generate}" method="generateSecretToken"/>
<f:validateButton title="${%Clear}" method="clearSecretToken"/>
</table>
</f:entry>
</f:advanced>

View File

@ -4,3 +4,5 @@ GitLabWebHookCause.ShortDescription.MergeRequestHook_html=Triggered by <a href="
GitLabWebHookCause.ShortDescription.MergeRequestHook_plain=Triggered by GitLab Merge Request #{0}: {1} => {2}
GitLabWebHookCause.ShortDescription.NoteHook_html=Triggered by {0} <a href="{4}/merge_requests/{1}" target="_blank">GitLab Merge Request #{1}</a>: {2} => {3}
GitLabWebHookCause.ShortDescription.NoteHook_plain=Triggered by {0} GitLab Merge Request #{1}: {2} => {3}
GitLabWebHookCause.ShortDescription.PipelineHook_noStatus=Started by GitLab Pipeline event
GitLabWebHookCause.ShortDescription.PipelineHook=Started by GitLab Pipeline event {0}

View File

@ -2,7 +2,7 @@
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form" xmlns:c="/lib/credentials" xmlns:st="jelly:stapler">
<f:section title="Gitlab">
<f:entry title="${%Enable authentication for '/project' end-point}" field="useAuthenticatedEndpoint">
<f:checkbox/>
<f:checkbox default="true"/>
</f:entry>
<f:entry title="${%GitLab connections}">
<f:repeatable var="connection" items="${descriptor.connections}" name="connections">
@ -18,6 +18,9 @@
<c:select/>
</f:entry>
<f:advanced>
<f:entry title="${%API-Level}" field="clientBuilderId" description="${%API Level for accessing Gitlab}">
<f:select value="${connection.clientBuilderId}" default="autodetect"/>
</f:entry>
<f:entry title="${%Ignore SSL Certificate Errors}" field="ignoreCertificateErrors">
<f:checkbox checked="${connection.ignoreCertificateErrors}"/>
</f:entry>
@ -30,7 +33,7 @@
<st:include page="configure-advanced.jelly" optional="true" />
</f:advanced>
<f:validateButton title="${%Test Connection}" progress="${%Testing...}" method="testConnection"
with="apiTokenId,url,ignoreCertificateErrors"/>
with="apiTokenId,clientBuilderId,url,ignoreCertificateErrors"/>
<f:entry title="">
<div align="right">
<f:repeatableDeleteButton/>

View File

@ -20,5 +20,10 @@
<f:textarea name="abortNoteText" field="abortNoteText"/>
</f:entry>
</f:optionalBlock>
<f:optionalBlock name="replaceUnstableNote" checked="${instance.replaceUnstableNote}" title="Custom message on unstable" inline="true">
<f:entry>
<f:textarea name="unstableNoteText" field="unstableNoteText"/>
</f:entry>
</f:optionalBlock>
</f:advanced>
</j:jelly>

View File

@ -1,5 +1,5 @@
<div>
<div>
<p>When filled, commenting this phrase in the merge request will trigger a build.</p>
<p>When filled, a comment in the merge request matching this regular expression will trigger a build.</p>
</div>
</div>

View File

@ -1,5 +1,6 @@
package com.dabsquared.gitlabjenkins.connection;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.CredentialsStore;
@ -87,7 +88,7 @@ public class GitLabConnectionConfigSSLTest {
public void doCheckConnection_ignoreCertificateErrors() {
GitLabConnectionConfig connectionConfig = jenkins.get(GitLabConnectionConfig.class);
FormValidation formValidation = connectionConfig.doTestConnection("https://localhost:" + port + "/gitlab", API_TOKEN_ID, true, 10, 10);
FormValidation formValidation = connectionConfig.doTestConnection("https://localhost:" + port + "/gitlab", API_TOKEN_ID, "v3", true, 10, 10);
assertThat(formValidation.getMessage(), is(Messages.connection_success()));
}
@ -95,7 +96,7 @@ public class GitLabConnectionConfigSSLTest {
public void doCheckConnection_certificateError() throws IOException {
GitLabConnectionConfig connectionConfig = jenkins.get(GitLabConnectionConfig.class);
FormValidation formValidation = connectionConfig.doTestConnection("https://localhost:" + port + "/gitlab", API_TOKEN_ID, false, 10, 10);
FormValidation formValidation = connectionConfig.doTestConnection("https://localhost:" + port + "/gitlab", API_TOKEN_ID, "v3", false, 10, 10);
assertThat(formValidation.getMessage(), containsString(Messages.connection_error("")));
}
}

View File

@ -1,11 +1,14 @@
package com.dabsquared.gitlabjenkins.connection;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.CredentialsStore;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.dabsquared.gitlabjenkins.GitLabPushTrigger;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.impl.V3GitLabClientBuilder;
import hudson.model.FreeStyleProject;
import hudson.model.Item;
import hudson.security.GlobalMatrixAuthorizationStrategy;
@ -33,10 +36,14 @@ import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertSame;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
@ -70,31 +77,33 @@ public class GitLabConnectionConfigTest {
@Test
public void doCheckConnection_success() {
HttpRequest request = request().withPath("/gitlab/api/v3/.*").withHeader("PRIVATE-TOKEN", API_TOKEN);
mockServerClient.when(request).respond(response().withStatusCode(Response.Status.OK.getStatusCode()));
GitLabConnectionConfig connectionConfig = jenkins.get(GitLabConnectionConfig.class);
FormValidation formValidation = connectionConfig.doTestConnection(gitLabUrl, API_TOKEN_ID, false, 10, 10);
assertThat(formValidation.getMessage(), is(Messages.connection_success()));
mockServerClient.verify(request);
String expected = Messages.connection_success();
assertThat(doCheckConnection("v3", Response.Status.OK), is(expected));
assertThat(doCheckConnection("v4", Response.Status.OK), is(expected));
}
@Test
public void doCheckConnection_forbidden() throws IOException {
HttpRequest request = request().withPath("/gitlab/api/v3/.*").withHeader("PRIVATE-TOKEN", API_TOKEN);
mockServerClient.when(request).respond(response().withStatusCode(Response.Status.FORBIDDEN.getStatusCode()));
String expected = Messages.connection_error("HTTP 403 Forbidden");
assertThat(doCheckConnection("v3", Response.Status.FORBIDDEN), is(expected));
assertThat(doCheckConnection("v4", Response.Status.FORBIDDEN), is(expected));
}
private String doCheckConnection(String clientBuilderId, Response.Status status) {
HttpRequest request = request().withPath("/gitlab/api/" + clientBuilderId + "/.*").withHeader("PRIVATE-TOKEN", API_TOKEN);
mockServerClient.when(request).respond(response().withStatusCode(status.getStatusCode()));
GitLabConnectionConfig connectionConfig = jenkins.get(GitLabConnectionConfig.class);
FormValidation formValidation = connectionConfig.doTestConnection(gitLabUrl, API_TOKEN_ID, false, 10, 10);
assertThat(formValidation.getMessage(), is(Messages.connection_error("HTTP 403 Forbidden")));
FormValidation formValidation = connectionConfig.doTestConnection(gitLabUrl, API_TOKEN_ID, clientBuilderId, false, 10, 10);
mockServerClient.verify(request);
return formValidation.getMessage();
}
@Test
public void authenticationEnabled_anonymous_forbidden() throws IOException, URISyntaxException {
jenkins.get(GitLabConnectionConfig.class).setUseAuthenticatedEndpoint(true);
Boolean defaultValue = jenkins.get(GitLabConnectionConfig.class).isUseAuthenticatedEndpoint();
assertTrue(defaultValue);
jenkins.getInstance().setAuthorizationStrategy(new GlobalMatrixAuthorizationStrategy());
URL jenkinsURL = jenkins.getURL();
FreeStyleProject project = jenkins.createFreeStyleProject("test");
@ -114,7 +123,6 @@ public class GitLabConnectionConfigTest {
@Test
public void authenticationEnabled_registered_success() throws Exception {
String username = "test-user";
jenkins.get(GitLabConnectionConfig.class).setUseAuthenticatedEndpoint(true);
jenkins.getInstance().setSecurityRealm(jenkins.createDummySecurityRealm());
GlobalMatrixAuthorizationStrategy authorizationStrategy = new GlobalMatrixAuthorizationStrategy();
authorizationStrategy.add(Item.BUILD, username);
@ -150,4 +158,39 @@ public class GitLabConnectionConfigTest {
assertThat(response.getStatusLine().getStatusCode(), is(200));
}
@Test
public void setConnectionsTest() {
GitLabConnection connection1 = new GitLabConnection("1", "http://localhost", null, new V3GitLabClientBuilder(), false, 10, 10);
GitLabConnection connection2 = new GitLabConnection("2", "http://localhost", null, new V3GitLabClientBuilder(), false, 10, 10);
GitLabConnectionConfig config = jenkins.get(GitLabConnectionConfig.class);
List<GitLabConnection> connectionList1 = new ArrayList<>();
connectionList1.add(connection1);
config.setConnections(connectionList1);
assertThat(config.getConnections(), is(connectionList1));
List<GitLabConnection> connectionList2 = new ArrayList<>();
connectionList2.add(connection1);
connectionList2.add(connection2);
config.setConnections(connectionList2);
assertThat(config.getConnections(), is(connectionList2));
config.setConnections(connectionList1);
assertThat(config.getConnections(), is(connectionList1));
}
@Test
public void getClient_is_cached() {
GitLabConnection connection = new GitLabConnection("test", "http://localhost", API_TOKEN_ID, new V3GitLabClientBuilder(), false, 10, 10);
GitLabConnectionConfig config = jenkins.get(GitLabConnectionConfig.class);
List<GitLabConnection> connectionList1 = new ArrayList<>();
connectionList1.add(connection);
config.setConnections(connectionList1);
GitLabClient client = config.getClient(connection.getName());
assertNotNull(client);
assertSame(client, config.getClient(connection.getName()));
}
}

View File

@ -0,0 +1,124 @@
package com.dabsquared.gitlabjenkins.environment;
import com.dabsquared.gitlabjenkins.cause.CauseData;
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
import hudson.EnvVars;
import hudson.matrix.AxisList;
import hudson.matrix.MatrixBuild;
import hudson.matrix.MatrixProject;
import hudson.matrix.MatrixRun;
import hudson.matrix.TextAxis;
import hudson.model.BuildListener;
import hudson.model.FreeStyleProject;
import hudson.model.FreeStyleBuild;
import hudson.model.StreamBuildListener;
import jenkins.model.Jenkins;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import java.io.IOException;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.HashSet;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.dabsquared.gitlabjenkins.cause.CauseDataBuilder.causeData;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
/**
* @author Evgeni Golov
*/
public class GitLabEnvironmentContributorTest {
@ClassRule
public static JenkinsRule jenkins = new JenkinsRule();
private BuildListener listener;
@Before
public void setup() {
listener = new StreamBuildListener(jenkins.createTaskListener().getLogger(), Charset.defaultCharset());
}
@Test
public void freeStyleProjectTest() throws IOException, InterruptedException, ExecutionException {
FreeStyleProject p = jenkins.createFreeStyleProject();
GitLabWebHookCause cause = new GitLabWebHookCause(generateCauseData());
FreeStyleBuild b = p.scheduleBuild2(0, cause).get();
EnvVars env = b.getEnvironment(listener);
assertEnv(env);
}
@Test
public void matrixProjectTest() throws IOException, InterruptedException, ExecutionException {
EnvVars env;
MatrixProject p = jenkins.jenkins.createProject(MatrixProject.class, "matrixbuild");
GitLabWebHookCause cause = new GitLabWebHookCause(generateCauseData());
// set up 2x2 matrix
AxisList axes = new AxisList();
axes.add(new TextAxis("db","mysql","oracle"));
axes.add(new TextAxis("direction","north","south"));
p.setAxes(axes);
MatrixBuild build = p.scheduleBuild2(0, cause).get();
List<MatrixRun> runs = build.getRuns();
assertEquals(4,runs.size());
for (MatrixRun run : runs) {
env = run.getEnvironment(listener);
assertNotNull(env.get("db"));
assertEnv(env);
}
}
private CauseData generateCauseData() {
return causeData()
.withActionType(CauseData.ActionType.MERGE)
.withSourceProjectId(1)
.withTargetProjectId(1)
.withBranch("feature")
.withSourceBranch("feature")
.withUserName("")
.withSourceRepoHomepage("https://gitlab.org/test")
.withSourceRepoName("test")
.withSourceNamespace("test-namespace")
.withSourceRepoUrl("git@gitlab.org:test.git")
.withSourceRepoSshUrl("git@gitlab.org:test.git")
.withSourceRepoHttpUrl("https://gitlab.org/test.git")
.withMergeRequestTitle("Test")
.withMergeRequestId(1)
.withMergeRequestIid(1)
.withTargetBranch("master")
.withTargetRepoName("test")
.withTargetNamespace("test-namespace")
.withTargetRepoSshUrl("git@gitlab.org:test.git")
.withTargetRepoHttpUrl("https://gitlab.org/test.git")
.withTriggeredByUser("test")
.withLastCommit("123")
.withTargetProjectUrl("https://gitlab.org/test")
.build();
}
private void assertEnv(EnvVars env) {
assertEquals("1", env.get("gitlabMergeRequestId"));
assertEquals("git@gitlab.org:test.git", env.get("gitlabSourceRepoUrl"));
assertEquals("master", env.get("gitlabTargetBranch"));
assertEquals("test", env.get("gitlabTargetRepoName"));
assertEquals("feature", env.get("gitlabSourceBranch"));
assertEquals("test", env.get("gitlabSourceRepoName"));
}
}

View File

@ -0,0 +1,41 @@
package com.dabsquared.gitlabjenkins.gitlab.api;
import com.dabsquared.gitlabjenkins.gitlab.api.impl.AutodetectGitLabClientBuilder;
import com.dabsquared.gitlabjenkins.gitlab.api.impl.V3GitLabClientBuilder;
import com.dabsquared.gitlabjenkins.gitlab.api.impl.V4GitLabClientBuilder;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import java.util.List;
import java.util.NoSuchElementException;
import static com.dabsquared.gitlabjenkins.gitlab.api.GitLabClientBuilder.getAllGitLabClientBuilders;
import static com.dabsquared.gitlabjenkins.gitlab.api.GitLabClientBuilder.getGitLabClientBuilderById;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
public class GitLabClientBuilderTest {
@Rule
public JenkinsRule jenkins = new JenkinsRule();
@Test
public void getAllGitLabClientBuilders_list_is_sorted_by_ordinal() {
List<GitLabClientBuilder> builders = getAllGitLabClientBuilders();
assertThat(builders.get(0), instanceOf(AutodetectGitLabClientBuilder.class));
assertThat(builders.get(1), instanceOf(V4GitLabClientBuilder.class));
assertThat(builders.get(2), instanceOf(V3GitLabClientBuilder.class));
}
@Test
public void getGitLabClientBuilderById_success() {
assertThat(getGitLabClientBuilderById(new V3GitLabClientBuilder().id()), instanceOf(V3GitLabClientBuilder.class));
}
@Test(expected = NoSuchElementException.class)
public void getGitLabClientBuilderById_no_match() {
getGitLabClientBuilderById("unknown");
}
}

View File

@ -0,0 +1,89 @@
package com.dabsquared.gitlabjenkins.gitlab.api.impl;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClientBuilder;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.mockserver.client.server.MockServerClient;
import org.mockserver.junit.MockServerRule;
import org.mockserver.model.HttpRequest;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import static com.dabsquared.gitlabjenkins.gitlab.api.impl.TestUtility.*;
import static org.junit.Assert.fail;
import static org.mockserver.matchers.Times.exactly;
import static org.mockserver.matchers.Times.once;
public class AutodetectingGitLabClientTest {
@Rule
public MockServerRule mockServer = new MockServerRule(this);
@Rule
public JenkinsRule jenkins = new JenkinsRule();
private MockServerClient mockServerClient;
private String gitLabUrl;
private GitLabClientBuilder clientBuilder;
private AutodetectingGitLabClient api;
private HttpRequest v3Request;
private HttpRequest v4Request;
@Before
public void setup() throws IOException {
gitLabUrl = "http://localhost:" + mockServer.getPort() + "/gitlab";
addGitLabApiToken();
List<GitLabClientBuilder> builders = Arrays.<GitLabClientBuilder>asList(new V3GitLabClientBuilder(), new V4GitLabClientBuilder());
api = new AutodetectingGitLabClient(builders, gitLabUrl, API_TOKEN, true, 10, 10);
v3Request = versionRequest(V3GitLabApiProxy.ID);
v4Request = versionRequest(V4GitLabApiProxy.ID);
}
@Test
public void buildClient_success_v3() throws Exception {
mockServerClient.when(v3Request).respond(responseOk());
api.getCurrentUser();
assertApiImpl(api, V3GitLabApiProxy.class);
mockServerClient.verify(v3Request, v3Request);
}
@Test
public void buildClient_success_v4() throws Exception {
mockServerClient.when(v3Request).respond(responseNotFound());
mockServerClient.when(v4Request).respond(responseOk());
api.getCurrentUser();
assertApiImpl(api, V4GitLabApiProxy.class);
mockServerClient.verify(v3Request, v4Request, v4Request);
}
@Test
public void buildClient_success_switching_apis() throws Exception {
mockServerClient.when(v3Request, once()).respond(responseNotFound());
mockServerClient.when(v4Request, exactly(2)).respond(responseOk());
api.getCurrentUser();
assertApiImpl(api, V4GitLabApiProxy.class);
mockServerClient.when(v4Request, once()).respond(responseNotFound());
mockServerClient.when(v3Request, exactly(2)).respond(responseOk());
api.getCurrentUser();
assertApiImpl(api, V3GitLabApiProxy.class);
mockServerClient.verify(v3Request, v4Request, v4Request, v3Request, v3Request);
}
@Test
public void buildClient_no_match() {
mockServerClient.when(v3Request).respond(responseNotFound());
mockServerClient.when(v4Request).respond(responseNotFound());
try {
api.getCurrentUser();
fail("endpoint should throw exception when no matching delegate is found");
} catch (NoSuchElementException e) {
mockServerClient.verify(v3Request, v4Request);
}
}
}

View File

@ -0,0 +1,35 @@
package com.dabsquared.gitlabjenkins.gitlab.api.impl;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClientBuilder;
import hudson.ProxyConfiguration;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.mockserver.junit.MockServerRule;
import static com.dabsquared.gitlabjenkins.gitlab.api.impl.TestUtility.assertApiImpl;
import static com.dabsquared.gitlabjenkins.gitlab.api.impl.TestUtility.buildClientWithDefaults;
import static junit.framework.TestCase.assertNotNull;
public class ResteasyGitLabClientBuilderTest {
@Rule
public MockServerRule mockServer = new MockServerRule(this);
@Rule
public JenkinsRule jenkins = new JenkinsRule();
@Test
public void buildClient() throws Exception {
GitLabClientBuilder clientBuilder = new ResteasyGitLabClientBuilder("test", 0, V3GitLabApiProxy.class, null);
assertApiImpl(buildClientWithDefaults(clientBuilder, "http://localhost/"), V3GitLabApiProxy.class);
}
@Test
public void buildClientWithProxy() throws Exception {
jenkins.getInstance().proxy = new ProxyConfiguration("example.com", 8080, "test", "test", "*localhost*");
GitLabClientBuilder clientBuilder = new ResteasyGitLabClientBuilder("test", 0, V3GitLabApiProxy.class, null);
assertNotNull(buildClientWithDefaults(clientBuilder, "http://localhost"));
}
}

View File

@ -0,0 +1,81 @@
package com.dabsquared.gitlabjenkins.gitlab.api.impl;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.CredentialsStore;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClientBuilder;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;
import javax.ws.rs.core.Response.Status;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;
import static javax.ws.rs.HttpMethod.GET;
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
import static javax.ws.rs.core.Response.Status.OK;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
class TestUtility {
static final String API_TOKEN = "secret";
private static final String API_TOKEN_ID = "apiTokenId";
private static final boolean IGNORE_CERTIFICATE_ERRORS = true;
private static final int CONNECTION_TIMEOUT = 10;
private static final int READ_TIMEOUT = 10;
static void addGitLabApiToken() throws IOException {
for (CredentialsStore credentialsStore : CredentialsProvider.lookupStores(Jenkins.getInstance())) {
if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) {
List<Domain> domains = credentialsStore.getDomains();
credentialsStore.addCredentials(domains.get(0),
new StringCredentialsImpl(CredentialsScope.SYSTEM, API_TOKEN_ID, "GitLab API Token", Secret.fromString(API_TOKEN)));
}
}
}
static HttpRequest versionRequest(String id) {
return request().withMethod(GET).withPath("/gitlab/api/" + id + "/.*").withHeader("PRIVATE-TOKEN", API_TOKEN);
}
static HttpResponse responseOk() {
return responseWithStatus(OK);
}
static HttpResponse responseNotFound() {
return responseWithStatus(NOT_FOUND);
}
private static HttpResponse responseWithStatus(Status status) {
return response().withStatusCode(status.getStatusCode());
}
static GitLabClient buildClientWithDefaults(GitLabClientBuilder clientBuilder, String url) {
return clientBuilder.buildClient(url, API_TOKEN, IGNORE_CERTIFICATE_ERRORS, CONNECTION_TIMEOUT, READ_TIMEOUT);
}
static void assertApiImpl(GitLabClient client, Class<? extends GitLabApiProxy> apiImplClass) throws Exception {
Field apiField = ((ResteasyGitLabClient) client).getClass().getDeclaredField("api");
apiField.setAccessible(true);
assertThat(apiField.get(client), instanceOf(apiImplClass));
}
static void assertApiImpl(AutodetectingGitLabClient api, Class<? extends GitLabApiProxy> apiImplClass) throws Exception {
Field delegate = api.getClass().getDeclaredField("delegate");
delegate.setAccessible(true);
assertApiImpl((GitLabClient) delegate.get(api), apiImplClass);
}
private TestUtility() { /* utility class */ }
}

View File

@ -1,22 +1,10 @@
package com.dabsquared.gitlabjenkins.publisher;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.CredentialsStore;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.dabsquared.gitlabjenkins.connection.GitLabConnection;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionConfig;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Result;
import hudson.model.StreamBuildListener;
import hudson.plugins.git.util.BuildData;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
@ -30,14 +18,8 @@ import org.mockserver.model.HttpRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static com.dabsquared.gitlabjenkins.publisher.TestUtility.*;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
@ -45,10 +27,6 @@ import static org.mockserver.model.HttpResponse.response;
* @author Nikolay Ustinov
*/
public class GitLabAcceptMergeRequestPublisherTest {
private static final String GIT_LAB_CONNECTION = "GitLab";
private static final String API_TOKEN = "secret";
@ClassRule
public static MockServerRule mockServer = new MockServerRule(new Object());
@ -59,17 +37,8 @@ public class GitLabAcceptMergeRequestPublisherTest {
private BuildListener listener;
@BeforeClass
public static void setupConnection() throws IOException {
GitLabConnectionConfig connectionConfig = jenkins.get(GitLabConnectionConfig.class);
String apiTokenId = "apiTokenId";
for (CredentialsStore credentialsStore : CredentialsProvider.lookupStores(Jenkins.getInstance())) {
if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) {
List<Domain> domains = credentialsStore.getDomains();
credentialsStore.addCredentials(domains.get(0),
new StringCredentialsImpl(CredentialsScope.SYSTEM, apiTokenId, "GitLab API Token", Secret.fromString(API_TOKEN)));
}
}
connectionConfig.addConnection(new GitLabConnection(GIT_LAB_CONNECTION, "http://localhost:" + mockServer.getPort() + "/gitlab", apiTokenId, false, 10, 10));
public static void setupClass() throws IOException {
setupGitLabConnections(jenkins, mockServer);
}
@Before
@ -83,68 +52,45 @@ public class GitLabAcceptMergeRequestPublisherTest {
mockServerClient.reset();
}
@Test
public void matrixAggregatable() throws InterruptedException, IOException {
verifyMatrixAggregatable(GitLabAcceptMergeRequestPublisher.class, listener);
}
@Test
public void success() throws IOException, InterruptedException {
Integer buildNumber = 1;
Integer projectId = 3;
Integer mergeRequestId = 1;
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.SUCCESS, buildNumber);
publish(mockSimpleBuild(GITLAB_CONNECTION_V3, Result.SUCCESS));
publish(mockSimpleBuild(GITLAB_CONNECTION_V4, Result.SUCCESS));
HttpRequest[] requests = new HttpRequest[] {
prepareAcceptMergeRequestWithSuccessResponse(projectId, mergeRequestId)
};
GitLabAcceptMergeRequestPublisher publisher = spy(new GitLabAcceptMergeRequestPublisher());
doReturn(projectId).when(publisher).getProjectId(build);
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
mockServerClient.verify(
prepareAcceptMergeRequestWithSuccessResponse("v3", MERGE_REQUEST_ID),
prepareAcceptMergeRequestWithSuccessResponse("v4", MERGE_REQUEST_IID));
}
@Test
public void failed() throws IOException, InterruptedException {
Integer buildNumber = 1;
Integer projectId = 3;
Integer mergeRequestId = 1;
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.FAILURE, buildNumber);
GitLabAcceptMergeRequestPublisher publisher = spy(new GitLabAcceptMergeRequestPublisher());
doReturn(projectId).when(publisher).getProjectId(build);
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
publisher.perform(build, null, listener);
publish(mockSimpleBuild(GITLAB_CONNECTION_V3, Result.FAILURE));
publish(mockSimpleBuild(GITLAB_CONNECTION_V4, Result.FAILURE));
mockServerClient.verifyZeroInteractions();
}
private HttpRequest prepareAcceptMergeRequestWithSuccessResponse(Integer projectId, Integer mergeRequestId) throws UnsupportedEncodingException {
HttpRequest updateCommitStatus = prepareAcceptMergeRequest(projectId, mergeRequestId);
private void publish(AbstractBuild build) throws InterruptedException, IOException {
GitLabAcceptMergeRequestPublisher publisher = preparePublisher(new GitLabAcceptMergeRequestPublisher(), build);
publisher.perform(build, null, listener);
}
private HttpRequest prepareAcceptMergeRequestWithSuccessResponse(String apiLevel, int mergeRequestId) throws UnsupportedEncodingException {
HttpRequest updateCommitStatus = prepareAcceptMergeRequest(apiLevel, mergeRequestId);
mockServerClient.when(updateCommitStatus).respond(response().withStatusCode(200));
return updateCommitStatus;
}
private HttpRequest prepareAcceptMergeRequest(Integer projectId, Integer mergeRequestId) throws UnsupportedEncodingException {
private HttpRequest prepareAcceptMergeRequest(String apiLevel, int mergeRequestId) throws UnsupportedEncodingException {
return request()
.withPath("/gitlab/api/v3/projects/" + projectId + "/merge_requests/" + mergeRequestId + "/merge")
.withPath("/gitlab/api/" + apiLevel + "/projects/" + PROJECT_ID + "/merge_requests/" + mergeRequestId + "/merge")
.withMethod("PUT")
.withHeader("PRIVATE-TOKEN", "secret")
.withBody("merge_commit_message=Merge+Request+accepted+by+jenkins+build+success&should_remove_source_branch=false");
}
private AbstractBuild mockBuild(String buildUrl, String gitLabConnection, Result result, Integer buildNumber, String... remoteUrls) {
AbstractBuild build = mock(AbstractBuild.class);
BuildData buildData = mock(BuildData.class);
when(buildData.getRemoteUrls()).thenReturn(new HashSet<>(Arrays.asList(remoteUrls)));
when(build.getAction(BuildData.class)).thenReturn(buildData);
when(build.getResult()).thenReturn(result);
when(build.getUrl()).thenReturn(buildUrl);
when(build.getResult()).thenReturn(result);
when(build.getUrl()).thenReturn(buildUrl);
when(build.getNumber()).thenReturn(buildNumber);
AbstractProject<?, ?> project = mock(AbstractProject.class);
when(project.getProperty(GitLabConnectionProperty.class)).thenReturn(new GitLabConnectionProperty(gitLabConnection));
when(build.getProject()).thenReturn(project);
return build;
}
}

View File

@ -1,12 +1,6 @@
package com.dabsquared.gitlabjenkins.publisher;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.CredentialsStore;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.dabsquared.gitlabjenkins.connection.GitLabConnection;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionConfig;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
import com.dabsquared.gitlabjenkins.gitlab.api.model.BuildState;
import hudson.EnvVars;
@ -14,17 +8,18 @@ import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.StreamBuildListener;
import hudson.model.TaskListener;
import hudson.plugins.git.Revision;
import hudson.plugins.git.util.Build;
import hudson.plugins.git.util.BuildData;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import jenkins.plugins.git.AbstractGitSCMSource;
import jenkins.scm.api.SCMRevisionAction;
import org.apache.commons.io.IOUtils;
import org.eclipse.jgit.lib.ObjectId;
import org.hamcrest.CoreMatchers;
import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl;
import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
@ -45,10 +40,19 @@ import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import static com.dabsquared.gitlabjenkins.publisher.TestUtility.BUILD_URL;
import static com.dabsquared.gitlabjenkins.publisher.TestUtility.GITLAB_CONNECTION_V3;
import static com.dabsquared.gitlabjenkins.publisher.TestUtility.GITLAB_CONNECTION_V4;
import static com.dabsquared.gitlabjenkins.publisher.TestUtility.PROJECT_ID;
import static com.dabsquared.gitlabjenkins.publisher.TestUtility.setupGitLabConnections;
import static com.dabsquared.gitlabjenkins.publisher.TestUtility.verifyMatrixAggregatable;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
@ -61,9 +65,7 @@ import static org.mockserver.model.HttpResponse.response;
* @author Robin Müller
*/
public class GitLabCommitStatusPublisherTest {
private static final String GIT_LAB_CONNECTION = "GitLab";
private static final String API_TOKEN = "secret";
private static final String SHA1 = "0616d12a3a24068691027a1e113147e3c1cfa2f4";
@ClassRule
public static MockServerRule mockServer = new MockServerRule(new Object());
@ -75,17 +77,8 @@ public class GitLabCommitStatusPublisherTest {
private BuildListener listener;
@BeforeClass
public static void setupConnection() throws IOException {
GitLabConnectionConfig connectionConfig = jenkins.get(GitLabConnectionConfig.class);
String apiTokenId = "apiTokenId";
for (CredentialsStore credentialsStore : CredentialsProvider.lookupStores(Jenkins.getInstance())) {
if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) {
List<Domain> domains = credentialsStore.getDomains();
credentialsStore.addCredentials(domains.get(0),
new StringCredentialsImpl(CredentialsScope.SYSTEM, apiTokenId, "GitLab API Token", Secret.fromString(API_TOKEN)));
}
}
connectionConfig.addConnection(new GitLabConnection(GIT_LAB_CONNECTION, "http://localhost:" + mockServer.getPort() + "/gitlab", apiTokenId, false, 10, 10));
public static void setupClass() throws IOException {
setupGitLabConnections(jenkins, mockServer);
}
@Before
@ -100,203 +93,257 @@ public class GitLabCommitStatusPublisherTest {
}
@Test
public void running() throws UnsupportedEncodingException {
HttpRequest[] requests = new HttpRequest[] {
prepareExistsCommitWithSuccessResponse("test/project", "123abc"),
prepareUpdateCommitStatusWithSuccessResponse("test/project", "123abc", jenkins.getInstance().getRootUrl() + "/build/123", BuildState.running)
};
AbstractBuild build = mockBuild("123abc", "/build/123", GIT_LAB_CONNECTION, null, "test/project.git");
GitLabCommitStatusPublisher publisher = new GitLabCommitStatusPublisher("jenkins", false);
publisher.prebuild(build, listener);
mockServerClient.verify(requests);
public void matrixAggregatable() throws InterruptedException, IOException {
verifyMatrixAggregatable(GitLabCommitStatusPublisher.class, listener);
}
@Test
public void running_v3() throws UnsupportedEncodingException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V3, null, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v3", build, BuildState.running);
prebuildAndVerify(build, listener, requests);
}
@Test
public void running_v4() throws UnsupportedEncodingException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, null, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v4", build, BuildState.running);
prebuildAndVerify(build, listener, requests);
}
@Test
public void runningWithLibrary() throws UnsupportedEncodingException {
AbstractBuild build = mockBuildWithLibrary(GITLAB_CONNECTION_V4, null, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v4", build, BuildState.running);
prebuildAndVerify(build, listener, requests);
}
@Test
public void runningWithDotInProjectId() throws IOException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, null, "test/project.test.git");
HttpRequest[] requests = new HttpRequest[] {
prepareGetProjectResponse("test/project.test",1),
prepareExistsCommitWithSuccessResponse("1", "123abc"),
prepareUpdateCommitStatusWithSuccessResponse("1", "123abc", jenkins.getInstance().getRootUrl() + "/build/123", BuildState.running)
prepareGetProjectResponse("test/project.test"),
prepareExistsCommitWithSuccessResponse("v4", String.valueOf(PROJECT_ID)),
prepareUpdateCommitStatusWithSuccessResponse("v4", String.valueOf(PROJECT_ID), build, BuildState.running)
};
AbstractBuild build = mockBuild("123abc", "/build/123", GIT_LAB_CONNECTION, null, "test/project.test.git");
GitLabCommitStatusPublisher publisher = new GitLabCommitStatusPublisher("jenkins", false);
publisher.prebuild(build, listener);
mockServerClient.verify(requests);
prebuildAndVerify(build, listener, requests);
}
@Test
public void canceled() throws IOException, InterruptedException {
HttpRequest[] requests = new HttpRequest[] {
prepareExistsCommitWithSuccessResponse("test/project", "123abc"),
prepareUpdateCommitStatusWithSuccessResponse("test/project", "123abc", jenkins.getInstance().getRootUrl() + "/build/123", BuildState.canceled)
};
AbstractBuild build = mockBuild("123abc", "/build/123", GIT_LAB_CONNECTION, Result.ABORTED, "test/project.git");
public void canceled_v3() throws IOException, InterruptedException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V3, Result.ABORTED, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v3", build, BuildState.canceled);
GitLabCommitStatusPublisher publisher = new GitLabCommitStatusPublisher("jenkins", false);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
performAndVerify(build, false, requests);
}
@Test
public void success() throws IOException, InterruptedException {
HttpRequest[] requests = new HttpRequest[] {
prepareExistsCommitWithSuccessResponse("test/project", "123abc"),
prepareUpdateCommitStatusWithSuccessResponse("test/project", "123abc", jenkins.getInstance().getRootUrl() + "/build/123", BuildState.success)
};
AbstractBuild build = mockBuild("123abc", "/build/123", GIT_LAB_CONNECTION, Result.SUCCESS, "test/project.git");
public void canceled_v4() throws IOException, InterruptedException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, Result.ABORTED, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v4", build, BuildState.canceled);
GitLabCommitStatusPublisher publisher = new GitLabCommitStatusPublisher("jenkins", false);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
performAndVerify(build, false, requests);
}
@Test
public void failed() throws IOException, InterruptedException {
HttpRequest[] requests = new HttpRequest[] {
prepareExistsCommitWithSuccessResponse("test/project", "123abc"),
prepareUpdateCommitStatusWithSuccessResponse("test/project", "123abc", jenkins.getInstance().getRootUrl() + "/build/123", BuildState.failed)
};
AbstractBuild build = mockBuild("123abc", "/build/123", GIT_LAB_CONNECTION, Result.FAILURE, "test/project.git");
public void canceledWithLibrary() throws IOException, InterruptedException {
AbstractBuild build = mockBuildWithLibrary(GITLAB_CONNECTION_V4, Result.ABORTED, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v4", build, BuildState.canceled);
GitLabCommitStatusPublisher publisher = new GitLabCommitStatusPublisher("jenkins", false);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
performAndVerify(build, false, requests);
}
@Test
public void success_v3() throws IOException, InterruptedException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V3, Result.SUCCESS, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v3", build, BuildState.success);
performAndVerify(build, false, requests);
}
@Test
public void success_v4() throws IOException, InterruptedException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, Result.SUCCESS, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v4", build, BuildState.success);
performAndVerify(build, false, requests);
}
@Test
public void successWithLibrary() throws IOException, InterruptedException {
AbstractBuild build = mockBuildWithLibrary(GITLAB_CONNECTION_V4, Result.SUCCESS, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v4", build, BuildState.success);
performAndVerify(build, false, requests);
}
@Test
public void failed_v3() throws IOException, InterruptedException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V3, Result.FAILURE, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v3", build, BuildState.failed);
performAndVerify(build, false, requests);
}
@Test
public void failed_v4() throws IOException, InterruptedException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V3, Result.FAILURE, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v3", build, BuildState.failed);
performAndVerify(build, false, requests);
}
@Test
public void failedWithLibrary() throws IOException, InterruptedException {
AbstractBuild build = mockBuildWithLibrary(GITLAB_CONNECTION_V4, Result.FAILURE, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v4", build, BuildState.failed);
performAndVerify(build, false, requests);
}
@Test
public void unstable() throws IOException, InterruptedException {
HttpRequest[] requests = new HttpRequest[] {
prepareExistsCommitWithSuccessResponse("test/project", "123abc"),
prepareUpdateCommitStatusWithSuccessResponse("test/project", "123abc", jenkins.getInstance().getRootUrl() + "/build/123", BuildState.failed)
};
AbstractBuild build = mockBuild("123abc", "/build/123", GIT_LAB_CONNECTION, Result.UNSTABLE, "test/project.git");
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, Result.UNSTABLE, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v4", build, BuildState.failed);
GitLabCommitStatusPublisher publisher = new GitLabCommitStatusPublisher("jenkins", false);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
performAndVerify(build, false, requests);
}
@Test
public void unstableWithLibrary() throws IOException, InterruptedException {
AbstractBuild build = mockBuildWithLibrary(GITLAB_CONNECTION_V4, Result.UNSTABLE, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v4", build, BuildState.failed);
performAndVerify(build, false, requests);
}
@Test
public void unstableAsSuccess() throws IOException, InterruptedException {
HttpRequest[] requests = new HttpRequest[] {
prepareExistsCommitWithSuccessResponse("test/project", "123abc"),
prepareUpdateCommitStatusWithSuccessResponse("test/project", "123abc", jenkins.getInstance().getRootUrl() + "/build/123", BuildState.success)
};
AbstractBuild build = mockBuild("123abc", "/build/123", GIT_LAB_CONNECTION, Result.UNSTABLE, "test/project.git");
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, Result.UNSTABLE, "test/project.git");
HttpRequest[] requests = prepareCheckCommitAndUpdateStatusRequests("v4", build, BuildState.success);
GitLabCommitStatusPublisher publisher = new GitLabCommitStatusPublisher("jenkins", true);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
performAndVerify(build, true, requests);
}
@Test
public void running_multipleRepos() throws UnsupportedEncodingException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, null, "test/project-1.git", "test/project-2.git");
HttpRequest[] requests = new HttpRequest[] {
prepareExistsCommitWithSuccessResponse("test/project-1", "123abc"),
prepareUpdateCommitStatusWithSuccessResponse("test/project-1", "123abc", jenkins.getInstance().getRootUrl() + "/build/123", BuildState.running),
prepareExistsCommitWithSuccessResponse("test/project-2", "123abc"),
prepareUpdateCommitStatusWithSuccessResponse("test/project-2", "123abc", jenkins.getInstance().getRootUrl() + "/build/123", BuildState.running)
prepareExistsCommitWithSuccessResponse("v4", "test/project-1"),
prepareUpdateCommitStatusWithSuccessResponse("v4", "test/project-1", build, BuildState.running),
prepareExistsCommitWithSuccessResponse("v4", "test/project-2"),
prepareUpdateCommitStatusWithSuccessResponse("v4", "test/project-2", build, BuildState.running)
};
AbstractBuild build = mockBuild("123abc", "/build/123", GIT_LAB_CONNECTION, null, "test/project-1.git", "test/project-2.git");
GitLabCommitStatusPublisher publisher = new GitLabCommitStatusPublisher("jenkins", false);
publisher.prebuild(build, listener);
mockServerClient.verify(requests);
prebuildAndVerify(build, listener, requests);
}
@Test
public void running_commitNotExists() throws UnsupportedEncodingException {
HttpRequest updateCommitStatus = prepareUpdateCommitStatusWithSuccessResponse("test/project", "123abc", jenkins.getInstance().getRootUrl() + "/build/123", BuildState.running);
AbstractBuild build = mockBuild("123abc", "/build/123", GIT_LAB_CONNECTION, null, "test/project.git");
GitLabCommitStatusPublisher publisher = new GitLabCommitStatusPublisher("jenkins", false);
publisher.prebuild(build, listener);
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, null, "test/project.git");
HttpRequest updateCommitStatus = prepareUpdateCommitStatusWithSuccessResponse("v4", "test/project", build, BuildState.running);
new GitLabCommitStatusPublisher("jenkins", false).prebuild(build, listener);
mockServerClient.verify(updateCommitStatus, VerificationTimes.exactly(0));
}
@Test
public void running_failToUpdate() throws UnsupportedEncodingException {
prepareExistsCommitWithSuccessResponse("test/project", "123abc");
HttpRequest updateCommitStatus = prepareUpdateCommitStatus("test/project", "123abc", jenkins.getInstance().getRootUrl() + "/build/123", BuildState.running);
mockServerClient.when(updateCommitStatus).respond(response().withStatusCode(403));
AbstractBuild build = mockBuild("123abc", "/build/123", GIT_LAB_CONNECTION, null, "test/project.git");
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, null, "test/project.git");
BuildListener buildListener = mock(BuildListener.class);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
when(buildListener.getLogger()).thenReturn(new PrintStream(outputStream));
GitLabCommitStatusPublisher publisher = new GitLabCommitStatusPublisher("jenkins", false);
publisher.prebuild(build, buildListener);
prepareExistsCommitWithSuccessResponse("v4", "test/project");
HttpRequest updateCommitStatus = prepareUpdateCommitStatus("v4", "test/project", build, BuildState.running);
mockServerClient.when(updateCommitStatus).respond(response().withStatusCode(403));
prebuildAndVerify(build, buildListener, updateCommitStatus);
assertThat(outputStream.toString(), CoreMatchers.containsString("Failed to update Gitlab commit status for project 'test/project': HTTP 403 Forbidden"));
mockServerClient.verify(updateCommitStatus);
}
private void prebuildAndVerify(AbstractBuild build, BuildListener listener, HttpRequest... requests) {
new GitLabCommitStatusPublisher("jenkins", false).prebuild(build, listener);
mockServerClient.verify(requests);
}
private HttpRequest prepareUpdateCommitStatusWithSuccessResponse(String projectId, String sha, String targetUrl, BuildState state) throws UnsupportedEncodingException {
HttpRequest updateCommitStatus = prepareUpdateCommitStatus(projectId, sha, targetUrl, state);
private void performAndVerify(AbstractBuild build, boolean markUnstableAsSuccess, HttpRequest... requests) throws InterruptedException, IOException {
new GitLabCommitStatusPublisher("jenkins", markUnstableAsSuccess).perform(build, null, listener);
mockServerClient.verify(requests);
}
private HttpRequest[] prepareCheckCommitAndUpdateStatusRequests(String apiLevel, Run<?, ?> build, BuildState buildState) throws UnsupportedEncodingException {
return new HttpRequest[] {
prepareExistsCommitWithSuccessResponse(apiLevel, "test/project"),
prepareUpdateCommitStatusWithSuccessResponse(apiLevel, "test/project", build, buildState)
};
}
private HttpRequest prepareUpdateCommitStatusWithSuccessResponse(String apiLevel, String projectName, Run<?, ?> build, BuildState state) throws UnsupportedEncodingException {
HttpRequest updateCommitStatus = prepareUpdateCommitStatus(apiLevel, projectName, build, state);
mockServerClient.when(updateCommitStatus).respond(response().withStatusCode(200));
return updateCommitStatus;
}
private HttpRequest prepareUpdateCommitStatus(String projectId, String sha, String targetUrl, BuildState state) throws UnsupportedEncodingException {
private HttpRequest prepareUpdateCommitStatus(final String apiLevel, String projectName, Run<?, ?> build, BuildState state) throws UnsupportedEncodingException {
return request()
.withPath("/gitlab/api/v3/projects/" + URLEncoder.encode(projectId, "UTF-8") + "/statuses/" + sha)
.withPath("/gitlab/api/" + apiLevel + "/projects/" + URLEncoder.encode(projectName, "UTF-8") + "/statuses/" + SHA1)
.withMethod("POST")
.withHeader("PRIVATE-TOKEN", "secret")
.withBody("state=" + URLEncoder.encode(state.name(), "UTF-8") + "&context=jenkins&" + "target_url=" + URLEncoder.encode(targetUrl, "UTF-8"));
.withBody("state=" + URLEncoder.encode(state.name(), "UTF-8") + "&context=jenkins&" + "target_url=" + URLEncoder.encode(DisplayURLProvider.get().getRunURL(build), "UTF-8") + "&description=" + URLEncoder.encode(state.name(), "UTF-8"));
}
private HttpRequest prepareExistsCommitWithSuccessResponse(String projectId, String sha) throws UnsupportedEncodingException {
HttpRequest existsCommit = prepareExistsCommit(projectId, sha);
private HttpRequest prepareExistsCommitWithSuccessResponse(String apiLevel, String projectName) throws UnsupportedEncodingException {
HttpRequest existsCommit = prepareExistsCommit(apiLevel, projectName);
mockServerClient.when(existsCommit).respond(response().withStatusCode(200));
return existsCommit;
}
private HttpRequest prepareExistsCommit(String projectId, String sha) throws UnsupportedEncodingException {
private HttpRequest prepareExistsCommit(String apiLevel, String projectName) throws UnsupportedEncodingException {
return request()
.withPath("/gitlab/api/v3/projects/" + URLEncoder.encode(projectId, "UTF-8") + "/repository/commits/" + sha)
.withPath("/gitlab/api/" + apiLevel + "/projects/" + URLEncoder.encode(projectName, "UTF-8") + "/repository/commits/" + SHA1)
.withMethod("GET")
.withHeader("PRIVATE-TOKEN", "secret");
}
private HttpRequest prepareGetProjectResponse(String projectName, int projectId) throws IOException {
private HttpRequest prepareGetProjectResponse(String projectName) throws IOException {
HttpRequest request= request()
.withPath("/gitlab/api/v3/projects/" + URLEncoder.encode(projectName, "UTF-8"))
.withPath("/gitlab/api/v4/projects/" + URLEncoder.encode(projectName, "UTF-8"))
.withMethod("GET")
. withHeader("PRIVATE-TOKEN", "secret");
HttpResponse response = response().withBody(getSingleProjectJson("GetSingleProject.json",projectName,projectId));
HttpResponse response = response().withBody(getSingleProjectJson("GetSingleProject.json", projectName, PROJECT_ID));
response.withHeader("Content-Type", "application/json");
mockServerClient.when(request).respond(response.withStatusCode(200));
return request;
}
private AbstractBuild mockBuild(String sha, String buildUrl, String gitLabConnection, Result result, String... remoteUrls) {
private AbstractBuild mockBuild(String gitLabConnection, Result result, String... remoteUrls) {
AbstractBuild build = mock(AbstractBuild.class);
List<BuildData> buildDatas = new ArrayList<>();
BuildData buildData = mock(BuildData.class);
Revision revision = mock(Revision.class);
when(revision.getSha1String()).thenReturn(sha);
when(revision.getSha1String()).thenReturn(SHA1);
when(buildData.getLastBuiltRevision()).thenReturn(revision);
when(buildData.getRemoteUrls()).thenReturn(new HashSet<>(Arrays.asList(remoteUrls)));
Build gitBuild = mock(Build.class);
when(gitBuild.getMarked()).thenReturn(revision);
when(buildData.getLastBuild(any(ObjectId.class))).thenReturn(gitBuild);
buildDatas.add(buildData);
when(build.getActions(BuildData.class)).thenReturn(buildDatas);
when(build.getAction(BuildData.class)).thenReturn(buildData);
when(build.getResult()).thenReturn(result);
when(build.getUrl()).thenReturn(buildUrl);
when(build.getUrl()).thenReturn(BUILD_URL);
AbstractProject<?, ?> project = mock(AbstractProject.class);
when(project.getProperty(GitLabConnectionProperty.class)).thenReturn(new GitLabConnectionProperty(gitLabConnection));
when(build.getProject()).thenReturn(project);
@ -314,11 +361,70 @@ public class GitLabCommitStatusPublisherTest {
}
return build;
}
private String getSingleProjectJson(String name,String projectNameWithNamespace, int porjectId) throws IOException {
private AbstractBuild mockBuildWithLibrary(String gitLabConnection, Result result, String... remoteUrls) {
AbstractBuild build = mock(AbstractBuild.class);
List<BuildData> buildDatas = new ArrayList<>();
BuildData buildData = mock(BuildData.class);
SCMRevisionAction scmRevisionAction = mock(SCMRevisionAction.class);
AbstractGitSCMSource.SCMRevisionImpl revisionImpl = mock(AbstractGitSCMSource.SCMRevisionImpl.class);
when(build.getAction(SCMRevisionAction.class)).thenReturn(scmRevisionAction);
when(scmRevisionAction.getRevision()).thenReturn(revisionImpl);
when(revisionImpl.getHash()).thenReturn(SHA1);
Revision revision = mock(Revision.class);
when(revision.getSha1String()).thenReturn(SHA1);
when(buildData.getLastBuiltRevision()).thenReturn(revision);
when(buildData.getRemoteUrls()).thenReturn(new HashSet<>(Arrays.asList(remoteUrls)));
Build gitBuild = mock(Build.class);
when(gitBuild.getMarked()).thenReturn(revision);
when(gitBuild.getSHA1()).thenReturn(ObjectId.fromString(SHA1));
when(buildData.getLastBuild(any(ObjectId.class))).thenReturn(gitBuild);
Map<String, Build> buildsByBranchName = new HashMap<>();
buildsByBranchName.put("develop", gitBuild);
when(buildData.getBuildsByBranchName()).thenReturn(buildsByBranchName);
buildDatas.add(buildData);
//Second build data (@librabry)
BuildData buildDataLib = mock(BuildData.class);
Revision revisionLib = mock(Revision.class);
when(revisionLib.getSha1String()).thenReturn("SHALIB");
when(buildDataLib.getLastBuiltRevision()).thenReturn(revisionLib);
Build gitBuildLib = mock(Build.class);
when(gitBuildLib.getMarked()).thenReturn(revisionLib);
when(buildDataLib.getLastBuild(any(ObjectId.class))).thenReturn(gitBuildLib);
buildDatas.add(buildDataLib);
when(build.getActions(BuildData.class)).thenReturn(buildDatas);
when(build.getResult()).thenReturn(result);
when(build.getUrl()).thenReturn(BUILD_URL);
AbstractProject<?, ?> project = mock(AbstractProject.class);
when(project.getProperty(GitLabConnectionProperty.class)).thenReturn(new GitLabConnectionProperty(gitLabConnection));
when(build.getProject()).thenReturn(project);
EnvVars environment = mock(EnvVars.class);
when(environment.expand(anyString())).thenAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
return (String) invocation.getArguments()[0];
}
});
try {
when(build.getEnvironment(any(TaskListener.class))).thenReturn(environment);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
return build;
}
private String getSingleProjectJson(String name,String projectNameWithNamespace, int projectId) throws IOException {
String nameSpace = projectNameWithNamespace.split("/")[0];
String projectName = projectNameWithNamespace.split("/")[1];
return IOUtils.toString(getClass().getResourceAsStream(name))
.replace("${projectId}", porjectId + "")
.replace("${projectId}", projectId + "")
.replace("${nameSpace}", nameSpace)
.replace("${projectName}", projectName);
}

View File

@ -1,12 +1,6 @@
package com.dabsquared.gitlabjenkins.publisher;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.CredentialsStore;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.dabsquared.gitlabjenkins.connection.GitLabConnection;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionConfig;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
import hudson.EnvVars;
import hudson.model.AbstractBuild;
@ -16,9 +10,6 @@ import hudson.model.Result;
import hudson.model.StreamBuildListener;
import hudson.model.TaskListener;
import hudson.plugins.git.util.BuildData;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
@ -35,16 +26,13 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import static com.dabsquared.gitlabjenkins.publisher.TestUtility.*;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
@ -53,10 +41,6 @@ import static org.mockserver.model.HttpResponse.response;
* @author Nikolay Ustinov
*/
public class GitLabMessagePublisherTest {
private static final String GIT_LAB_CONNECTION = "GitLab";
private static final String API_TOKEN = "secret";
@ClassRule
public static MockServerRule mockServer = new MockServerRule(new Object());
@ -67,17 +51,8 @@ public class GitLabMessagePublisherTest {
private BuildListener listener;
@BeforeClass
public static void setupConnection() throws IOException {
GitLabConnectionConfig connectionConfig = jenkins.get(GitLabConnectionConfig.class);
String apiTokenId = "apiTokenId";
for (CredentialsStore credentialsStore : CredentialsProvider.lookupStores(Jenkins.getInstance())) {
if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) {
List<Domain> domains = credentialsStore.getDomains();
credentialsStore.addCredentials(domains.get(0),
new StringCredentialsImpl(CredentialsScope.SYSTEM, apiTokenId, "GitLab API Token", Secret.fromString(API_TOKEN)));
}
}
connectionConfig.addConnection(new GitLabConnection(GIT_LAB_CONNECTION, "http://localhost:" + mockServer.getPort() + "/gitlab", apiTokenId, false, 10, 10));
public static void setupClass() throws IOException {
setupGitLabConnections(jenkins, mockServer);
}
@Before
@ -92,192 +67,168 @@ public class GitLabMessagePublisherTest {
}
@Test
public void canceled() throws IOException, InterruptedException {
Integer buildNumber = 1;
Integer projectId = 3;
Integer mergeRequestId = 1;
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.ABORTED, buildNumber);
String buildUrl = Jenkins.getInstance().getRootUrl() + build.getUrl();
String defaultNote = MessageFormat.format(":point_up: Jenkins Build {0}\n\nResults available at: [Jenkins [{1} #{2}]]({3})",
Result.ABORTED, build.getParent().getDisplayName(), buildNumber, buildUrl);
HttpRequest[] requests = new HttpRequest[] {
prepareSendMessageWithSuccessResponse(projectId, mergeRequestId, defaultNote)
};
GitLabMessagePublisher publisher = spy(new GitLabMessagePublisher(false, false, false, false, null, null, null));
doReturn(projectId).when(publisher).getProjectId(build);
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
public void matrixAggregatable() throws InterruptedException, IOException {
verifyMatrixAggregatable(GitLabMessagePublisher.class, listener);
}
@Test
public void success() throws IOException, InterruptedException {
Integer buildNumber = 1;
Integer projectId = 3;
Integer mergeRequestId = 1;
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.SUCCESS, buildNumber);
String buildUrl = Jenkins.getInstance().getRootUrl() + build.getUrl();
String defaultNote = MessageFormat.format(":white_check_mark: Jenkins Build {0}\n\nResults available at: [Jenkins [{1} #{2}]]({3})",
Result.SUCCESS, build.getParent().getDisplayName(), buildNumber, buildUrl);
public void canceled_v3() throws IOException, InterruptedException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V3, Result.ABORTED);
String defaultNote = formatNote(build, ":point_up: Jenkins Build {0}\n\nResults available at: [Jenkins [{1} #{2}]]({3})");
HttpRequest[] requests = new HttpRequest[] {
prepareSendMessageWithSuccessResponse(projectId, mergeRequestId, defaultNote)
};
performAndVerify(
build, defaultNote, false, false, false, false, false,
prepareSendMessageWithSuccessResponse("v3", MERGE_REQUEST_ID, defaultNote));
}
GitLabMessagePublisher publisher = spy(new GitLabMessagePublisher(false, false, false, false, null, null, null));
doReturn(projectId).when(publisher).getProjectId(build);
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
publisher.perform(build, null, listener);
@Test
public void canceled_v4() throws IOException, InterruptedException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, Result.ABORTED);
String defaultNote = formatNote(build, ":point_up: Jenkins Build {0}\n\nResults available at: [Jenkins [{1} #{2}]]({3})");
mockServerClient.verify(requests);
performAndVerify(
build, defaultNote, false, false, false, false, false,
prepareSendMessageWithSuccessResponse("v4", MERGE_REQUEST_IID, defaultNote));
}
@Test
public void success_v3() throws IOException, InterruptedException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V3, Result.SUCCESS);
String defaultNote = formatNote(build, ":white_check_mark: Jenkins Build {0}\n\nResults available at: [Jenkins [{1} #{2}]]({3})");
performAndVerify(
build, defaultNote, false, false, false, false, false,
prepareSendMessageWithSuccessResponse("v3", MERGE_REQUEST_ID, defaultNote));
}
@Test
public void success_v4() throws IOException, InterruptedException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, Result.SUCCESS);
String defaultNote = formatNote(build, ":white_check_mark: Jenkins Build {0}\n\nResults available at: [Jenkins [{1} #{2}]]({3})");
performAndVerify(
build, defaultNote, false, false, false, false, false,
prepareSendMessageWithSuccessResponse("v4", MERGE_REQUEST_IID, defaultNote));
}
@Test
public void success_withOnlyForFailure() throws IOException, InterruptedException {
Integer buildNumber = 1;
Integer projectId = 3;
Integer mergeRequestId = 1;
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.SUCCESS, buildNumber);
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, Result.SUCCESS);
GitLabMessagePublisher publisher = spy(new GitLabMessagePublisher(true, false, false, false, null, null, null));
doReturn(projectId).when(publisher).getProjectId(build);
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
publisher.perform(build, null, listener);
mockServerClient.verifyZeroInteractions();
performAndVerify(build, "test", true, false, false, false, false);
}
@Test
public void failed() throws IOException, InterruptedException {
Integer buildNumber = 1;
Integer projectId = 3;
Integer mergeRequestId = 1;
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.FAILURE, buildNumber);
String buildUrl = Jenkins.getInstance().getRootUrl() + build.getUrl();
String defaultNote = MessageFormat.format(":negative_squared_cross_mark: Jenkins Build {0}\n\nResults available at: [Jenkins [{1} #{2}]]({3})",
Result.FAILURE, build.getParent().getDisplayName(), buildNumber, buildUrl);
public void failed_v3() throws IOException, InterruptedException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V3, Result.FAILURE);
String defaultNote = formatNote(build, ":negative_squared_cross_mark: Jenkins Build {0}\n\nResults available at: [Jenkins [{1} #{2}]]({3})");
HttpRequest[] requests = new HttpRequest[] {
prepareSendMessageWithSuccessResponse(projectId, mergeRequestId, defaultNote)
};
GitLabMessagePublisher publisher = spy(new GitLabMessagePublisher(false, false, false, false, null, null, null));
doReturn(projectId).when(publisher).getProjectId(build);
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
performAndVerify(
build, defaultNote, false, false, false, false, false,
prepareSendMessageWithSuccessResponse("v3", MERGE_REQUEST_ID, defaultNote));
}
@Test
public void failed_v4() throws IOException, InterruptedException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, Result.FAILURE);
String defaultNote = formatNote(build, ":negative_squared_cross_mark: Jenkins Build {0}\n\nResults available at: [Jenkins [{1} #{2}]]({3})");
performAndVerify(
build, defaultNote, false, false, false, false, false,
prepareSendMessageWithSuccessResponse("v4", MERGE_REQUEST_IID, defaultNote));
}
@Test
public void failed_withOnlyForFailed() throws IOException, InterruptedException {
Integer buildNumber = 1;
Integer projectId = 3;
Integer mergeRequestId = 1;
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.FAILURE, buildNumber);
String buildUrl = Jenkins.getInstance().getRootUrl() + build.getUrl();
String defaultNote = MessageFormat.format(":negative_squared_cross_mark: Jenkins Build {0}\n\nResults available at: [Jenkins [{1} #{2}]]({3})",
Result.FAILURE, build.getParent().getDisplayName(), buildNumber, buildUrl);
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, Result.FAILURE);
String defaultNote = formatNote(build, ":negative_squared_cross_mark: Jenkins Build {0}\n\nResults available at: [Jenkins [{1} #{2}]]({3})");
HttpRequest[] requests = new HttpRequest[] {
prepareSendMessageWithSuccessResponse(projectId, mergeRequestId, defaultNote)
};
GitLabMessagePublisher publisher = spy(new GitLabMessagePublisher(true, false, false, false, null, null, null));
doReturn(projectId).when(publisher).getProjectId(build);
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
performAndVerify(
build, defaultNote, true, false, false, false, false,
prepareSendMessageWithSuccessResponse("v4", MERGE_REQUEST_IID, defaultNote));
}
@Test
public void canceledWithCustomNote() throws IOException, InterruptedException {
Integer buildNumber = 1;
Integer projectId = 3;
Integer mergeRequestId = 1;
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.ABORTED, buildNumber);
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, Result.ABORTED);
String defaultNote = "abort";
HttpRequest[] requests = new HttpRequest[] {
prepareSendMessageWithSuccessResponse(projectId, mergeRequestId, defaultNote)
};
GitLabMessagePublisher publisher = spy(new GitLabMessagePublisher(false, false, false, true, null, null, defaultNote));
doReturn(projectId).when(publisher).getProjectId(build);
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
performAndVerify(
build, defaultNote, false, false, false, true, false,
prepareSendMessageWithSuccessResponse("v4", MERGE_REQUEST_IID, defaultNote));
}
@Test
public void successWithCustomNote() throws IOException, InterruptedException {
Integer buildNumber = 1;
Integer projectId = 3;
Integer mergeRequestId = 1;
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.SUCCESS, buildNumber);
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, Result.SUCCESS);
String defaultNote = "success";
HttpRequest[] requests = new HttpRequest[] {
prepareSendMessageWithSuccessResponse(projectId, mergeRequestId, defaultNote)
};
GitLabMessagePublisher publisher = spy(new GitLabMessagePublisher(false, true, false, false, defaultNote, null, null));
doReturn(projectId).when(publisher).getProjectId(build);
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
performAndVerify(
build, defaultNote, false, true, false, false, false,
prepareSendMessageWithSuccessResponse("v4", MERGE_REQUEST_IID, defaultNote));
}
@Test
public void failedWithCustomNote() throws IOException, InterruptedException {
Integer buildNumber = 1;
Integer projectId = 3;
Integer mergeRequestId = 1;
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.FAILURE, buildNumber);
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, Result.FAILURE);
String defaultNote = "failure";
HttpRequest[] requests = new HttpRequest[] {
prepareSendMessageWithSuccessResponse(projectId, mergeRequestId, defaultNote)
};
GitLabMessagePublisher publisher = spy(new GitLabMessagePublisher(false, false, true, false, null, defaultNote, null));
doReturn(projectId).when(publisher).getProjectId(build);
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
performAndVerify(
build, defaultNote, false, false, true, false, false,
prepareSendMessageWithSuccessResponse("v4", MERGE_REQUEST_IID, defaultNote));
}
private HttpRequest prepareSendMessageWithSuccessResponse(Integer projectId, Integer mergeRequestId, String body) throws UnsupportedEncodingException {
HttpRequest updateCommitStatus = prepareSendMessageStatus(projectId, mergeRequestId, body);
@Test
public void unstableWithCustomNote() throws IOException, InterruptedException {
AbstractBuild build = mockBuild(GITLAB_CONNECTION_V4, Result.UNSTABLE);
String defaultNote = "unstable";
performAndVerify(
build, defaultNote, false, false, false, false, true,
prepareSendMessageWithSuccessResponse("v4", MERGE_REQUEST_IID, defaultNote));
}
private void performAndVerify(AbstractBuild build, String note, boolean onlyForFailure, boolean replaceSuccessNote, boolean replaceFailureNote, boolean replaceAbortNote, boolean replaceUnstableNote, HttpRequest... requests) throws InterruptedException, IOException {
String successNoteText = replaceSuccessNote ? note : null;
String failureNoteText = replaceFailureNote ? note : null;
String abortNoteText = replaceAbortNote ? note : null;
String unstableNoteText = replaceUnstableNote ? note : null;
GitLabMessagePublisher publisher = preparePublisher(new GitLabMessagePublisher(onlyForFailure, replaceSuccessNote, replaceFailureNote, replaceAbortNote, replaceUnstableNote, successNoteText, failureNoteText, abortNoteText, unstableNoteText), build);
publisher.perform(build, null, listener);
if (requests.length > 0) {
mockServerClient.verify(requests);
} else {
mockServerClient.verifyZeroInteractions();
}
}
private HttpRequest prepareSendMessageWithSuccessResponse(String apiLevel, int mergeRequestId, String body) throws UnsupportedEncodingException {
HttpRequest updateCommitStatus = prepareSendMessageStatus(apiLevel, mergeRequestId, body);
mockServerClient.when(updateCommitStatus).respond(response().withStatusCode(200));
return updateCommitStatus;
}
private HttpRequest prepareSendMessageStatus(Integer projectId, Integer mergeRequestId, String body) throws UnsupportedEncodingException {
private HttpRequest prepareSendMessageStatus(final String apiLevel, int mergeRequestId, String body) throws UnsupportedEncodingException {
return request()
.withPath("/gitlab/api/v3/projects/" + projectId + "/merge_requests/" + mergeRequestId + "/notes")
.withPath("/gitlab/api/" + apiLevel + "/projects/" + PROJECT_ID + "/merge_requests/" + mergeRequestId + "/notes")
.withMethod("POST")
.withHeader("PRIVATE-TOKEN", "secret")
.withBody("body=" + URLEncoder.encode(body, "UTF-8"));
}
private AbstractBuild mockBuild(String buildUrl, String gitLabConnection, Result result, Integer buildNumber, String... remoteUrls) {
private AbstractBuild mockBuild(String gitLabConnection, Result result, String... remoteUrls) {
AbstractBuild build = mock(AbstractBuild.class);
BuildData buildData = mock(BuildData.class);
when(buildData.getRemoteUrls()).thenReturn(new HashSet<>(Arrays.asList(remoteUrls)));
when(build.getAction(BuildData.class)).thenReturn(buildData);
when(build.getResult()).thenReturn(result);
when(build.getUrl()).thenReturn(buildUrl);
when(build.getUrl()).thenReturn(BUILD_URL);
when(build.getResult()).thenReturn(result);
when(build.getUrl()).thenReturn(buildUrl);
when(build.getNumber()).thenReturn(buildNumber);
when(build.getNumber()).thenReturn(BUILD_NUMBER);
AbstractProject<?, ?> project = mock(AbstractProject.class);
when(project.getProperty(GitLabConnectionProperty.class)).thenReturn(new GitLabConnectionProperty(gitLabConnection));

View File

@ -1,22 +1,10 @@
package com.dabsquared.gitlabjenkins.publisher;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.CredentialsStore;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.dabsquared.gitlabjenkins.connection.GitLabConnection;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionConfig;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Result;
import hudson.model.StreamBuildListener;
import hudson.plugins.git.util.BuildData;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
@ -31,15 +19,8 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static com.dabsquared.gitlabjenkins.publisher.TestUtility.*;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
@ -47,10 +28,6 @@ import static org.mockserver.model.HttpResponse.response;
* @author Nikolay Ustinov
*/
public class GitLabVotePublisherTest {
private static final String GIT_LAB_CONNECTION = "GitLab";
private static final String API_TOKEN = "secret";
@ClassRule
public static MockServerRule mockServer = new MockServerRule(new Object());
@ -61,17 +38,8 @@ public class GitLabVotePublisherTest {
private BuildListener listener;
@BeforeClass
public static void setupConnection() throws IOException {
GitLabConnectionConfig connectionConfig = jenkins.get(GitLabConnectionConfig.class);
String apiTokenId = "apiTokenId";
for (CredentialsStore credentialsStore : CredentialsProvider.lookupStores(Jenkins.getInstance())) {
if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) {
List<Domain> domains = credentialsStore.getDomains();
credentialsStore.addCredentials(domains.get(0),
new StringCredentialsImpl(CredentialsScope.SYSTEM, apiTokenId, "GitLab API Token", Secret.fromString(API_TOKEN)));
}
}
connectionConfig.addConnection(new GitLabConnection(GIT_LAB_CONNECTION, "http://localhost:" + mockServer.getPort() + "/gitlab", apiTokenId, false, 10, 10));
public static void setupClass() throws IOException {
setupGitLabConnections(jenkins, mockServer);
}
@Before
@ -86,77 +54,49 @@ public class GitLabVotePublisherTest {
}
@Test
public void success() throws IOException, InterruptedException {
Integer buildNumber = 1;
Integer projectId = 3;
Integer mergeRequestId = 1;
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.SUCCESS, buildNumber);
String buildUrl = Jenkins.getInstance().getRootUrl() + build.getUrl();
String defaultNote = MessageFormat.format(":+1:",
Result.SUCCESS, build.getParent().getDisplayName(), buildNumber, buildUrl);
HttpRequest[] requests = new HttpRequest[] {
prepareSendMessageWithSuccessResponse(projectId, mergeRequestId, defaultNote)
};
GitLabVotePublisher publisher = spy(new GitLabVotePublisher());
doReturn(projectId).when(publisher).getProjectId(build);
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
public void matrixAggregatable() throws InterruptedException, IOException {
verifyMatrixAggregatable(GitLabVotePublisher.class, listener);
}
@Test
public void failed() throws IOException, InterruptedException {
Integer buildNumber = 1;
Integer projectId = 3;
Integer mergeRequestId = 1;
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.FAILURE, buildNumber);
String buildUrl = Jenkins.getInstance().getRootUrl() + build.getUrl();
String defaultNote = MessageFormat.format(":-1:",
Result.FAILURE, build.getParent().getDisplayName(), buildNumber, buildUrl);
HttpRequest[] requests = new HttpRequest[] {
prepareSendMessageWithSuccessResponse(projectId, mergeRequestId, defaultNote)
};
GitLabVotePublisher publisher = spy(new GitLabVotePublisher());
doReturn(projectId).when(publisher).getProjectId(build);
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
public void success_v3() throws IOException, InterruptedException {
performAndVerify(mockSimpleBuild(GITLAB_CONNECTION_V3, Result.SUCCESS), "v3", MERGE_REQUEST_ID, ":+1:");
}
private HttpRequest prepareSendMessageWithSuccessResponse(Integer projectId, Integer mergeRequestId, String body) throws UnsupportedEncodingException {
HttpRequest updateCommitStatus = prepareSendMessageStatus(projectId, mergeRequestId, body);
@Test
public void success_v4() throws IOException, InterruptedException {
performAndVerify(mockSimpleBuild(GITLAB_CONNECTION_V4, Result.SUCCESS), "v4", MERGE_REQUEST_IID, ":+1:");
}
@Test
public void failed_v3() throws IOException, InterruptedException {
performAndVerify(mockSimpleBuild(GITLAB_CONNECTION_V3, Result.FAILURE), "v3", MERGE_REQUEST_ID, ":-1:");
}
@Test
public void failed_v4() throws IOException, InterruptedException {
performAndVerify(mockSimpleBuild(GITLAB_CONNECTION_V4, Result.FAILURE), "v4", MERGE_REQUEST_IID, ":-1:");
}
private void performAndVerify(AbstractBuild build, String apiLevel, int mergeRequestId, String defaultNote) throws InterruptedException, IOException {
GitLabVotePublisher publisher = preparePublisher(new GitLabVotePublisher(), build);
publisher.perform(build, null, listener);
mockServerClient.verify(prepareSendMessageWithSuccessResponse(build, apiLevel, mergeRequestId, defaultNote));
}
private HttpRequest prepareSendMessageWithSuccessResponse(AbstractBuild build, String apiLevel, int mergeRequestId, String body) throws UnsupportedEncodingException {
HttpRequest updateCommitStatus = prepareSendMessageStatus(apiLevel, mergeRequestId, formatNote(build, body));
mockServerClient.when(updateCommitStatus).respond(response().withStatusCode(200));
return updateCommitStatus;
}
private HttpRequest prepareSendMessageStatus(Integer projectId, Integer mergeRequestId, String body) throws UnsupportedEncodingException {
private HttpRequest prepareSendMessageStatus(final String apiLevel, int mergeRequestId, String body) throws UnsupportedEncodingException {
return request()
.withPath("/gitlab/api/v3/projects/" + projectId + "/merge_requests/" + mergeRequestId + "/notes")
.withPath("/gitlab/api/" + apiLevel + "/projects/" + PROJECT_ID + "/merge_requests/" + mergeRequestId + "/notes")
.withMethod("POST")
.withHeader("PRIVATE-TOKEN", "secret")
.withBody("body=" + URLEncoder.encode(body, "UTF-8"));
}
private AbstractBuild mockBuild(String buildUrl, String gitLabConnection, Result result, Integer buildNumber, String... remoteUrls) {
AbstractBuild build = mock(AbstractBuild.class);
BuildData buildData = mock(BuildData.class);
when(buildData.getRemoteUrls()).thenReturn(new HashSet<>(Arrays.asList(remoteUrls)));
when(build.getAction(BuildData.class)).thenReturn(buildData);
when(build.getResult()).thenReturn(result);
when(build.getUrl()).thenReturn(buildUrl);
when(build.getResult()).thenReturn(result);
when(build.getUrl()).thenReturn(buildUrl);
when(build.getNumber()).thenReturn(buildNumber);
AbstractProject<?, ?> project = mock(AbstractProject.class);
when(project.getProperty(GitLabConnectionProperty.class)).thenReturn(new GitLabConnectionProperty(gitLabConnection));
when(build.getProject()).thenReturn(project);
return build;
}
}

View File

@ -0,0 +1,114 @@
package com.dabsquared.gitlabjenkins.publisher;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.CredentialsStore;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.dabsquared.gitlabjenkins.connection.GitLabConnection;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionConfig;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
import com.dabsquared.gitlabjenkins.gitlab.api.impl.V3GitLabClientBuilder;
import com.dabsquared.gitlabjenkins.gitlab.api.impl.V4GitLabClientBuilder;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
import hudson.Launcher;
import hudson.matrix.MatrixAggregatable;
import hudson.matrix.MatrixAggregator;
import hudson.matrix.MatrixBuild;
import hudson.matrix.MatrixConfiguration;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Result;
import hudson.plugins.git.util.BuildData;
import hudson.tasks.Notifier;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl;
import org.jvnet.hudson.test.JenkinsRule;
import org.mockserver.junit.MockServerRule;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
final class TestUtility {
static final String GITLAB_CONNECTION_V3 = "GitLabV3";
static final String GITLAB_CONNECTION_V4 = "GitLabV4";
static final String BUILD_URL = "/build/123";
static final int BUILD_NUMBER = 1;
static final int PROJECT_ID = 3;
static final int MERGE_REQUEST_ID = 1;
static final int MERGE_REQUEST_IID = 2;
private static final String API_TOKEN = "secret";
static void setupGitLabConnections(JenkinsRule jenkins, MockServerRule mockServer) throws IOException {
GitLabConnectionConfig connectionConfig = jenkins.get(GitLabConnectionConfig.class);
String apiTokenId = "apiTokenId";
for (CredentialsStore credentialsStore : CredentialsProvider.lookupStores(Jenkins.getInstance())) {
if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) {
List<Domain> domains = credentialsStore.getDomains();
credentialsStore.addCredentials(domains.get(0),
new StringCredentialsImpl(CredentialsScope.SYSTEM, apiTokenId, "GitLab API Token", Secret.fromString(TestUtility.API_TOKEN)));
}
}
connectionConfig.addConnection(new GitLabConnection(TestUtility.GITLAB_CONNECTION_V3, "http://localhost:" + mockServer.getPort() + "/gitlab", apiTokenId, new V3GitLabClientBuilder(), false, 10, 10));
connectionConfig.addConnection(new GitLabConnection(TestUtility.GITLAB_CONNECTION_V4, "http://localhost:" + mockServer.getPort() + "/gitlab", apiTokenId, new V4GitLabClientBuilder(), false, 10, 10));
}
static <T extends Notifier & MatrixAggregatable> void verifyMatrixAggregatable(Class<T> publisherClass, BuildListener listener) throws InterruptedException, IOException {
AbstractBuild build = mock(AbstractBuild.class);
AbstractProject project = mock(MatrixConfiguration.class);
Notifier publisher = mock(publisherClass);
MatrixBuild parentBuild = mock(MatrixBuild.class);
when(build.getParent()).thenReturn(project);
when(((MatrixAggregatable) publisher).createAggregator(any(MatrixBuild.class), any(Launcher.class), any(BuildListener.class))).thenCallRealMethod();
when(publisher.perform(any(AbstractBuild.class), any(Launcher.class), any(BuildListener.class))).thenReturn(true);
MatrixAggregator aggregator = ((MatrixAggregatable) publisher).createAggregator(parentBuild, null, listener);
aggregator.startBuild();
aggregator.endBuild();
verify(publisher).perform(parentBuild, null, listener);
}
static AbstractBuild mockSimpleBuild(String gitLabConnection, Result result, String... remoteUrls) {
AbstractBuild build = mock(AbstractBuild.class);
BuildData buildData = mock(BuildData.class);
when(buildData.getRemoteUrls()).thenReturn(new HashSet<>(Arrays.asList(remoteUrls)));
when(build.getAction(BuildData.class)).thenReturn(buildData);
when(build.getResult()).thenReturn(result);
when(build.getUrl()).thenReturn(BUILD_URL);
when(build.getResult()).thenReturn(result);
when(build.getNumber()).thenReturn(BUILD_NUMBER);
AbstractProject<?, ?> project = mock(AbstractProject.class);
when(project.getProperty(GitLabConnectionProperty.class)).thenReturn(new GitLabConnectionProperty(gitLabConnection));
when(build.getProject()).thenReturn(project);
return build;
}
@SuppressWarnings("ConstantConditions")
static String formatNote(AbstractBuild build, String note) {
String buildUrl = Jenkins.getInstance().getRootUrl() + build.getUrl();
return MessageFormat.format(note, build.getResult(), build.getParent().getDisplayName(), BUILD_NUMBER, buildUrl);
}
static <P extends MergeRequestNotifier> P preparePublisher(P publisher, AbstractBuild build) {
P spyPublisher = spy(publisher);
MergeRequest mergeRequest = new MergeRequest(MERGE_REQUEST_ID, MERGE_REQUEST_IID, "", "", "", PROJECT_ID, PROJECT_ID, "", "");
doReturn(mergeRequest).when(spyPublisher).getMergeRequest(build);
return spyPublisher;
}
private TestUtility() { /* contains only static utility-methods */ }
}

View File

@ -0,0 +1,161 @@
package com.dabsquared.gitlabjenkins.service;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.*;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.State;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.Collections.emptyList;
class GitLabClientStub implements GitLabClient {
private final Map<Pair<String, Class>, List<?>> data;
private final Map<Pair<String, Class>, Integer> calls;
GitLabClientStub() {
data = new HashMap<>();
calls = new HashMap<>();
}
@Override
public String getHostUrl() {
return "";
}
void addBranches(String project, List<Branch> branches) {
addData(project, Branch.class, branches);
}
void addLabels(String project, List<Label> labels) {
addData(project, Label.class, labels);
}
int calls(String projectId, Class dataClass) {
Pair<String, Class> key = createKey(projectId, dataClass);
return calls.containsKey(key) ? calls.get(key) : 0;
}
@Override
public List<Branch> getBranches(String projectId) {
return getData(projectId, Branch.class);
}
@Override
public List<Label> getLabels(String projectId) {
return getData(projectId, Label.class);
}
private void addData(String projectId, Class dataClass, List<?> datas) {
data.put(createKey(projectId, dataClass), datas);
}
@SuppressWarnings("unchecked")
private <T> List<T> getData(String projectId, Class dataClass) {
Pair<String, Class> key = createKey(projectId, dataClass);
if (!calls.containsKey(key)) {
calls.put(key, 0);
}
calls.put(key, calls.get(key) + 1);
return (List<T>) data.get(key);
}
private Pair<String, Class> createKey(String projectId, Class dataClass) {
return new ImmutablePair<>(projectId, dataClass);
}
/************** no implementation below ********************************/
@Override
public Project createProject(String projectName) {
return null;
}
@Override
public MergeRequest createMergeRequest(Integer projectId, String sourceBranch, String targetBranch, String title) {
return null;
}
@Override
public Project getProject(String projectName) {
return null;
}
@Override
public Project updateProject(String projectId, String name, String path) {
return null;
}
@Override
public void deleteProject(String projectId) {
}
@Override
public void addProjectHook(String projectId, String url, Boolean pushEvents, Boolean mergeRequestEvents, Boolean noteEvents) {
}
@Override
public void changeBuildStatus(String projectId, String sha, BuildState state, String ref, String context, String targetUrl, String description) {
}
@Override
public void changeBuildStatus(Integer projectId, String sha, BuildState state, String ref, String context, String targetUrl, String description) {
}
@Override
public void getCommit(String projectId, String sha) {
}
@Override
public void acceptMergeRequest(MergeRequest mr, String mergeCommitMessage, boolean shouldRemoveSourceBranch) {
}
@Override
public void createMergeRequestNote(MergeRequest mr, String body) {
}
@Override
public List<MergeRequest> getMergeRequests(String projectId, State state, int page, int perPage) {
return null;
}
@Override
public Branch getBranch(String projectId, String branch) {
return null;
}
@Override
public User getCurrentUser() {
return null;
}
@Override
public User addUser(String email, String username, String name, String password) {
return null;
}
@Override
public User updateUser(String userId, String email, String username, String name, String password) {
return null;
}
@Override
public List<Pipeline> getPipelines(String projectName) {
return emptyList();
}
}

View File

@ -1,12 +1,9 @@
package com.dabsquared.gitlabjenkins.service;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.model.Branch;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.io.IOException;
import java.util.ArrayList;
@ -14,29 +11,24 @@ import java.util.List;
import static com.dabsquared.gitlabjenkins.gitlab.api.model.builder.generated.BranchBuilder.branch;
import static java.util.Arrays.asList;
import static junit.framework.TestCase.assertEquals;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class GitLabProjectBranchesServiceTest {
private final static List<String> BRANCH_NAMES_PROJECT_B = asList("master", "B-branch-1", "B-branch-2");
private GitLabProjectBranchesService branchesService;
@Mock
private GitLabApi gitlabApi;
private GitLabClientStub clientStub;
@Before
public void setUp() throws IOException {
List<Branch> branchNamesProjectA = convert(asList("master", "A-branch-1"));
clientStub = new GitLabClientStub();
clientStub.addBranches("groupOne/A", convert(asList("master", "A-branch-1")));
clientStub.addBranches("groupOne/B", convert(BRANCH_NAMES_PROJECT_B));
// mock the gitlab factory
when(gitlabApi.getBranches("groupOne/A")).thenReturn(branchNamesProjectA);
when(gitlabApi.getBranches("groupOne/B")).thenReturn(convert(BRANCH_NAMES_PROJECT_B));
// never expire cache for tests
branchesService = new GitLabProjectBranchesService();
@ -45,7 +37,7 @@ public class GitLabProjectBranchesServiceTest {
@Test
public void shouldReturnBranchNamesFromGitlabApi() {
// when
List<String> actualBranchNames = branchesService.getBranches(gitlabApi, "git@git.example.com:groupOne/B.git");
List<String> actualBranchNames = branchesService.getBranches(clientStub, "git@git.example.com:groupOne/B.git");
// then
assertThat(actualBranchNames, is(BRANCH_NAMES_PROJECT_B));
@ -54,11 +46,11 @@ public class GitLabProjectBranchesServiceTest {
@Test
public void shouldNotMakeUnnecessaryCallsToGitlabApiGetBranches() {
// when
branchesService.getBranches(gitlabApi, "git@git.example.com:groupOne/A.git");
branchesService.getBranches(clientStub, "git@git.example.com:groupOne/A.git");
// then
verify(gitlabApi, times(1)).getBranches("groupOne/A");
verify(gitlabApi, times(0)).getBranches("groupOne/B");
assertEquals(1, clientStub.calls("groupOne/A", Branch.class));
assertEquals(0, clientStub.calls("groupOne/B", Branch.class));
}
private List<Branch> convert(List<String> branchNames) {

View File

@ -1,12 +1,9 @@
package com.dabsquared.gitlabjenkins.service;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.dabsquared.gitlabjenkins.gitlab.api.model.Label;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.io.IOException;
import java.util.ArrayList;
@ -14,29 +11,24 @@ import java.util.List;
import static com.dabsquared.gitlabjenkins.gitlab.api.model.builder.generated.LabelBuilder.label;
import static java.util.Arrays.asList;
import static junit.framework.TestCase.assertEquals;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class GitLabProjectLabelsServiceTest {
private final static List<String> LABELS_PROJECT_B = asList("label1", "label2", "label3");
private GitLabProjectLabelsService labelsService;
@Mock
private GitLabApi gitlabApi;
private GitLabClientStub clientStub;
@Before
public void setUp() throws IOException {
List<Label> labelsProjectA = convert(asList("label1", "label2"));
// mock the gitlab factory
when(gitlabApi.getLabels("groupOne/A")).thenReturn(labelsProjectA);
when(gitlabApi.getLabels("groupOne/B")).thenReturn(convert(LABELS_PROJECT_B));
clientStub = new GitLabClientStub();
clientStub.addLabels("groupOne/A", convert(asList("label1", "label2")));
clientStub.addLabels("groupOne/B", convert(LABELS_PROJECT_B));
// never expire cache for tests
labelsService = new GitLabProjectLabelsService();
@ -45,7 +37,7 @@ public class GitLabProjectLabelsServiceTest {
@Test
public void shouldReturnLabelsFromGitlabApi() {
// when
List<String> actualLabels = labelsService.getLabels(gitlabApi, "git@git.example.com:groupOne/B.git");
List<String> actualLabels = labelsService.getLabels(clientStub, "git@git.example.com:groupOne/B.git");
// then
assertThat(actualLabels, is(LABELS_PROJECT_B));
@ -54,11 +46,11 @@ public class GitLabProjectLabelsServiceTest {
@Test
public void shouldNotMakeUnnecessaryCallsToGitlabApiGetLabels() {
// when
labelsService.getLabels(gitlabApi, "git@git.example.com:groupOne/A.git");
labelsService.getLabels(clientStub, "git@git.example.com:groupOne/A.git");
// then
verify(gitlabApi, times(1)).getLabels("groupOne/A");
verify(gitlabApi, times(0)).getLabels("groupOne/B");
assertEquals(1, clientStub.calls("groupOne/A", Label.class));
assertEquals(0, clientStub.calls("groupOne/B", Label.class));
}
private List<Label> convert(List<String> labels) {

View File

@ -1,12 +1,23 @@
package com.dabsquared.gitlabjenkins.testing.gitlab.rule;
import com.dabsquared.gitlabjenkins.gitlab.JacksonConfig;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.CredentialsStore;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.dabsquared.gitlabjenkins.connection.GitLabConnection;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionConfig;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.impl.V3GitLabClientBuilder;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
import com.dabsquared.gitlabjenkins.gitlab.api.model.Pipeline;
import com.dabsquared.gitlabjenkins.gitlab.api.model.Project;
import com.dabsquared.gitlabjenkins.gitlab.api.model.User;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@ -26,25 +37,19 @@ import java.util.UUID;
* @author Robin Müller
*/
public class GitLabRule implements TestRule {
private static final String API_TOKEN_ID = "apiTokenId";
private static final String PASSWORD = "integration-test";
private final GitLabApi client;
private final String username;
private final String password;
private final String url;
private final int postgresPort;
private GitLabClient clientCache;
private List<String> projectIds = new ArrayList<>();
public GitLabRule(String url, int postgresPort) {
client = new ResteasyClientBuilder()
.httpEngine(new ApacheHttpClient4Engine())
.register(new JacksonJsonProvider())
.register(new JacksonConfig())
.register(new ApiHeaderTokenFilter(getApiToken(postgresPort))).build().target(url)
.proxyBuilder(GitLabApi.class)
.build();
User user = client.getCurrentUser();
username = user.getUsername();
password = "integration-test";
client.updateUser(user.getId().toString(), user.getEmail(), user.getUsername(), user.getName(), password);
this.url = url;
this.postgresPort = postgresPort;
}
@Override
@ -53,7 +58,11 @@ public class GitLabRule implements TestRule {
}
public Project getProject(final String projectName) {
return client.getProject(projectName);
return client().getProject(projectName);
}
public List<Pipeline> getPipelines(int projectId) {
return client().getPipelines(String.valueOf(projectId));
}
public List<String> getProjectIds() {
@ -61,39 +70,59 @@ public class GitLabRule implements TestRule {
}
public String createProject(ProjectRequest request) {
Project project = client.createProject(request.getName());
Project project = client().createProject(request.getName());
projectIds.add(project.getId().toString());
if (request.getWebHookUrl() != null && (request.isPushHook() || request.isMergeRequestHook() || request.isNoteHook())) {
client.addProjectHook(project.getId().toString(), request.getWebHookUrl(), request.isPushHook(), request.isMergeRequestHook(), request.isNoteHook());
client().addProjectHook(project.getId().toString(), request.getWebHookUrl(), request.isPushHook(), request.isMergeRequestHook(), request.isNoteHook());
}
return project.getHttpUrlToRepo();
}
public void createMergeRequest(final Integer projectId,
final String sourceBranch,
final String targetBranch,
final String title) {
client.createMergeRequest(projectId, sourceBranch, targetBranch, title);
public GitLabConnectionProperty createGitLabConnectionProperty() throws IOException {
for (CredentialsStore credentialsStore : CredentialsProvider.lookupStores(Jenkins.getInstance())) {
if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) {
List<Domain> domains = credentialsStore.getDomains();
credentialsStore.addCredentials(domains.get(0),
new StringCredentialsImpl(CredentialsScope.SYSTEM, API_TOKEN_ID, "GitLab API Token", Secret.fromString(getApiToken())));
}
}
GitLabConnectionConfig config = Jenkins.getInstance().getDescriptorByType(GitLabConnectionConfig.class);
GitLabConnection connection = new GitLabConnection("test", url, API_TOKEN_ID, new V3GitLabClientBuilder(), true,10, 10);
config.addConnection(connection);
config.save();
return new GitLabConnectionProperty(connection.getName());
}
public MergeRequest createMergeRequest(final Integer projectId,
final String sourceBranch,
final String targetBranch,
final String title) {
return client().createMergeRequest(projectId, sourceBranch, targetBranch, title);
}
public void createMergeRequestNote(MergeRequest mr, String body) {
client().createMergeRequestNote(mr, body);
}
public String getUsername() {
return username;
return client().getCurrentUser().getUsername();
}
public String getPassword() {
return password;
return PASSWORD;
}
private void cleanup() {
for (String projectId : projectIds) {
String randomProjectName = UUID.randomUUID().toString();
// rename the project before deleting as the deletion will take a while
client.updateProject(projectId, randomProjectName, randomProjectName);
client.deleteProject(projectId);
client().updateProject(projectId, randomProjectName, randomProjectName);
client().deleteProject(projectId);
}
}
private String getApiToken(int postgresPort) {
private String getApiToken() {
try {
Class.forName("org.postgresql.Driver");
try (Connection connection = DriverManager.getConnection("jdbc:postgresql://localhost:" + postgresPort + "/gitlabhq_production", "gitlab", "password")) {
@ -106,6 +135,15 @@ public class GitLabRule implements TestRule {
}
}
private GitLabClient client() {
if (clientCache == null) {
clientCache = new V3GitLabClientBuilder().buildClient(url, getApiToken(), false, -1, -1);
User user = clientCache.getCurrentUser();
client().updateUser(user.getId().toString(), user.getEmail(), user.getUsername(), user.getName(), PASSWORD);
}
return clientCache;
}
private class GitlabStatement extends Statement {
private final Statement next;
@ -122,16 +160,4 @@ public class GitLabRule implements TestRule {
}
}
}
private static class ApiHeaderTokenFilter implements ClientRequestFilter {
private final String gitlabApiToken;
ApiHeaderTokenFilter(String gitlabApiToken) {
this.gitlabApiToken = gitlabApiToken;
}
public void filter(ClientRequestContext requestContext) throws IOException {
requestContext.getHeaders().putSingle("PRIVATE-TOKEN", gitlabApiToken);
}
}
}

View File

@ -12,14 +12,17 @@ public class ProjectRequest {
private final boolean pushHook;
private final boolean mergeRequestHook;
private final boolean noteHook;
private final boolean pipelineHook;
@GeneratePojoBuilder(intoPackage = "*.builder.generated", withFactoryMethod = "*")
public ProjectRequest(String name, String webHookUrl, boolean pushHook, boolean mergeRequestHook, boolean noteHook) {
public ProjectRequest(String name, String webHookUrl, boolean pushHook, boolean mergeRequestHook, boolean noteHook,
boolean pipelineHook) {
this.name = name;
this.webHookUrl = webHookUrl;
this.pushHook = pushHook;
this.mergeRequestHook = mergeRequestHook;
this.noteHook = noteHook;
this.pipelineHook = pipelineHook;
}
public String getName() {
@ -41,4 +44,8 @@ public class ProjectRequest {
public boolean isNoteHook() {
return noteHook;
}
public boolean isPipelineHook() {
return pipelineHook;
}
}

View File

@ -1,22 +1,34 @@
package com.dabsquared.gitlabjenkins.testing.integration;
import com.dabsquared.gitlabjenkins.GitLabPushTrigger;
import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest;
import com.dabsquared.gitlabjenkins.gitlab.api.model.Pipeline;
import com.dabsquared.gitlabjenkins.publisher.GitLabCommitStatusPublisher;
import com.dabsquared.gitlabjenkins.testing.gitlab.rule.GitLabRule;
import com.dabsquared.gitlabjenkins.trigger.filter.BranchFilterType;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Descriptor;
import hudson.model.FreeStyleProject;
import hudson.tasks.Publisher;
import hudson.util.DescribableList;
import hudson.util.OneShotEvent;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.SleepBuilder;
import org.jvnet.hudson.test.TestBuilder;
import org.jvnet.hudson.test.TestNotifier;
import java.io.IOException;
import java.net.Inet4Address;
@ -28,7 +40,11 @@ import java.util.List;
import static com.dabsquared.gitlabjenkins.builder.generated.GitLabPushTriggerBuilder.gitLabPushTrigger;
import static com.dabsquared.gitlabjenkins.testing.gitlab.rule.builder.generated.ProjectRequestBuilder.projectRequest;
import static java.lang.Integer.parseInt;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.collection.IsEmptyCollection.empty;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@ -37,10 +53,10 @@ import static org.junit.Assert.assertTrue;
* @author Robin Müller
*/
public class GitLabIT {
private static final String GITLAB_URL = "http://localhost:" + System.getProperty("gitlab.http.port", "10080");
@Rule
public GitLabRule gitlab = new GitLabRule("http://localhost:" + System.getProperty("gitlab.http.port", "10080"),
Integer.parseInt(System.getProperty("postgres.port", "5432")));
public GitLabRule gitlab = new GitLabRule(GITLAB_URL, parseInt(System.getProperty("postgres.port", "5432")));
@Rule
public JenkinsRule jenkins = new JenkinsRule();
@ -50,12 +66,6 @@ public class GitLabIT {
@Test
public void buildOnPush() throws IOException, InterruptedException, GitAPIException {
final String httpUrl = gitlab.createProject(projectRequest()
.withName("test")
.withWebHookUrl("http://" + getDocker0Ip() + ":" + jenkins.getURL().getPort() + "/jenkins/project/test")
.withPushHook(true)
.build());
final OneShotEvent buildTriggered = new OneShotEvent();
FreeStyleProject project = jenkins.createFreeStyleProject("test");
GitLabPushTrigger trigger = gitLabPushTrigger().withTriggerOnPush(true).withBranchFilterType(BranchFilterType.All).build();
@ -70,45 +80,15 @@ public class GitLabIT {
});
project.setQuietPeriod(0);
Git.init().setDirectory(tmp.getRoot()).call();
tmp.newFile("test");
Git git = Git.open(tmp.getRoot());
git.add().addFilepattern("test");
git.commit().setMessage("test").call();
StoredConfig config = git.getRepository().getConfig();
config.setString("remote", "origin", "url", httpUrl);
config.save();
git.push()
.setRemote("origin").add("master")
.setCredentialsProvider(new UsernamePasswordCredentialsProvider(gitlab.getUsername(), gitlab.getPassword()))
.call();
createGitLabProject(false,true, true, false);
buildTriggered.block(10000);
assertThat(buildTriggered.isSignaled(), is(true));
}
@Test
public void buildOnMergeRequest() throws IOException, InterruptedException, GitAPIException {
// check for clean slate
assertTrue(gitlab.getProjectIds().isEmpty());
final String httpUrl = gitlab.createProject(projectRequest()
.withName("test")
.withWebHookUrl("http://" + getDocker0Ip() + ":" + jenkins.getURL().getPort() + "/jenkins/project/test")
.withMergeRequestHook(true)
.build());
// Fix: Hack to get the project id
// A preferable approach would be here to get the target project by name using getProject function of the
// GitLabRule and to use the id from the project instance. However, due to a bug in GitLab (tested on 8.6.1)
// retrieving the project by name is not properly working.
// (see issue https://github.com/gitlabhq/gitlabhq/issues/4921).
// Once the issue is resolved, replace this implementation.
final List<String> projectIds = gitlab.getProjectIds();
assertSame(projectIds.size(), 1);
final Integer projectId = Integer.parseInt(projectIds.get(0));
final OneShotEvent buildTriggered = new OneShotEvent();
FreeStyleProject project = jenkins.createFreeStyleProject("test");
GitLabPushTrigger trigger = gitLabPushTrigger().withTriggerOnMergeRequest(true).withBranchFilterType(BranchFilterType.All).build();
@ -123,30 +103,9 @@ public class GitLabIT {
});
project.setQuietPeriod(0);
// Setup git repository
Git.init().setDirectory(tmp.getRoot()).call();
Git git = Git.open(tmp.getRoot());
StoredConfig config = git.getRepository().getConfig();
config.setString("remote", "origin", "url", httpUrl);
config.save();
Pair<Integer, String> gitlabData = createGitLabProject(true,false, false, true);
// Setup remote master branch
tmp.newFile("test");
git.add().addFilepattern("test");
git.commit().setMessage("test").call();
git.push()
.setRemote("origin").add("master")
.setCredentialsProvider(new UsernamePasswordCredentialsProvider(gitlab.getUsername(), gitlab.getPassword()))
.call();
// Setup remote feature branch
git.checkout().setName("feature").setCreateBranch(true).call();
tmp.newFile("feature");
git.commit().setMessage("feature").call();
git.push().setRemote("origin").add("feature").setCredentialsProvider(new UsernamePasswordCredentialsProvider(gitlab.getUsername(), gitlab.getPassword()))
.call();
gitlab.createMergeRequest(projectId, "feature", "master", "Merge feature branch to master.");
gitlab.createMergeRequest(gitlabData.getLeft(), "feature", "master", "Merge feature branch to master.");
buildTriggered.block(10000);
assertThat(buildTriggered.isSignaled(), is(true));
@ -154,31 +113,12 @@ public class GitLabIT {
@Test
public void buildOnNote() throws IOException, InterruptedException, GitAPIException {
// check for clean slate
assertTrue(gitlab.getProjectIds().isEmpty());
final String httpUrl = gitlab.createProject(projectRequest()
.withName("test")
.withWebHookUrl("http://" + getDocker0Ip() + ":" + jenkins.getURL().getPort() + "/jenkins/project/test")
.withNoteHook(true)
.build());
// Fix: Hack to get the project id
// A preferable approach would be here to get the target project by name using getProject function of the
// GitLabRule and to use the id from the project instance. However, due to a bug in GitLab (tested on 8.6.1)
// retrieving the project by name is not properly working.
// (see issue https://github.com/gitlabhq/gitlabhq/issues/4921).
// Once the issue is resolved, replace this implementation.
final List<String> projectIds = gitlab.getProjectIds();
assertSame(projectIds.size(), 1);
final Integer projectId = Integer.parseInt(projectIds.get(0));
final OneShotEvent buildTriggered = new OneShotEvent();
FreeStyleProject project = jenkins.createFreeStyleProject("test");
GitLabPushTrigger trigger = gitLabPushTrigger().withTriggerOnNoteRequest(true).withBranchFilterType(BranchFilterType.All).build();
project.addTrigger(trigger);
trigger.start(project, true);
GitLabPushTrigger trigger = gitLabPushTrigger()
.withTriggerOnNoteRequest(true)
.withNoteRegex(".*test.*")
.withBranchFilterType(BranchFilterType.All).build();
project.getBuildersList().add(new TestBuilder() {
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
@ -188,33 +128,132 @@ public class GitLabIT {
});
project.setQuietPeriod(0);
Pair<Integer, String> gitlabData = createGitLabProject(true, false, true, false);
// create merge-request
MergeRequest mr = gitlab.createMergeRequest(gitlabData.getLeft(), "feature", "master", "Merge feature branch to master.");
// add trigger after push/merge-request so it may only receive the note-hook
project.addTrigger(trigger);
trigger.start(project, true);
gitlab.createMergeRequestNote(mr, "this is a test note");
buildTriggered.block(20000);
assertThat(buildTriggered.isSignaled(), is(true));
}
@Test
public void reportBuildStatus() throws IOException, InterruptedException, GitAPIException {
final OneShotEvent buildTriggered = new OneShotEvent();
final OneShotEvent buildReported = new OneShotEvent();
GitLabPushTrigger trigger = gitLabPushTrigger()
.withTriggerOnPush(true)
.withBranchFilterType(BranchFilterType.All)
.build();
FreeStyleProject project = jenkins.createFreeStyleProject("test");
project.addProperty(gitlab.createGitLabConnectionProperty());
project.addTrigger(trigger);
project.getBuildersList().add(new TestBuilder() {
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
buildTriggered.signal();
return true;
}
});
project.getBuildersList().add(new SleepBuilder(20000));
DescribableList<Publisher, Descriptor<Publisher>> publishers = project.getPublishersList();
publishers.add(new GitLabCommitStatusPublisher("integration-test", false));
publishers.add(new TestNotifier() {
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
buildReported.signal();
return true;
}
});
trigger.start(project, true);
project.setQuietPeriod(0);
Pair<Integer, String> gitlabData = createGitLabProject(false,true, true, false);
assertThat(gitlab.getPipelines(gitlabData.getLeft()), empty());
buildTriggered.block(20000);
assertThat(buildTriggered.isSignaled(), is(true));
assertPipelineStatus(gitlabData, "running");
buildReported.block(40000);
assertThat(buildReported.isSignaled(), is(true));
Thread.sleep(5000); // wait for gitlab to update
assertPipelineStatus(gitlabData, "success");
}
private void assertPipelineStatus(Pair<Integer, String> gitlabData, String status) {
List<Pipeline> pipelines = gitlab.getPipelines(gitlabData.getLeft());
assertThat(pipelines, hasSize(1));
Pipeline pipeline = pipelines.get(0);
assertEquals(gitlabData.getRight(), pipeline.getSha());
assertEquals(status, pipeline.getStatus());
}
private Pair<Integer, String> createGitLabProject(boolean addFeatureBranch, boolean withPushHook, boolean withNoteHook, boolean withMergeRequestHook) throws IOException, GitAPIException {
// check for clean slate
assertTrue(gitlab.getProjectIds().isEmpty());
String url = gitlab.createProject(projectRequest()
.withName("test")
.withWebHookUrl("http://" + getDocker0Ip() + ":" + jenkins.getURL().getPort() + "/jenkins/project/test")
.withPushHook(withPushHook)
.withNoteHook(withNoteHook)
.withMergeRequestHook(withMergeRequestHook)
.build());
String sha = initGitLabProject(url, addFeatureBranch);
// Fix: Hack to get the project id
// A preferable approach would be here to get the target project by name using getProject function of the
// GitLabRule and to use the id from the project instance. However, due to a bug in GitLab (tested on 8.6.1)
// retrieving the project by name is not properly working.
// (see issue https://github.com/gitlabhq/gitlabhq/issues/4921).
// Once the issue is resolved, replace this implementation.
List<String> projectIds = gitlab.getProjectIds();
assertSame(projectIds.size(), 1);
return new ImmutablePair<>(parseInt(projectIds.get(0)), sha);
}
private String initGitLabProject(String url, boolean addFeatureBranch) throws GitAPIException, IOException {
// Setup git repository
Git.init().setDirectory(tmp.getRoot()).call();
Git git = Git.open(tmp.getRoot());
StoredConfig config = git.getRepository().getConfig();
config.setString("remote", "origin", "url", httpUrl);
config.setString("remote", "origin", "url", url);
config.save();
// Setup remote master branch
tmp.newFile("test");
git.add().addFilepattern("test");
git.commit().setMessage("test").call();
RevCommit commit = git.commit().setMessage("test").call();
git.push()
.setRemote("origin").add("master")
.setCredentialsProvider(new UsernamePasswordCredentialsProvider(gitlab.getUsername(), gitlab.getPassword()))
.call();
// Setup remote feature branch
git.checkout().setName("feature").setCreateBranch(true).call();
tmp.newFile("feature");
git.commit().setMessage("feature").call();
git.push().setRemote("origin").add("feature").setCredentialsProvider(new UsernamePasswordCredentialsProvider(gitlab.getUsername(), gitlab.getPassword()))
.call();
if (addFeatureBranch) {
// Setup remote feature branch
git.checkout().setName("feature").setCreateBranch(true).call();
tmp.newFile("feature");
commit = git.commit().setMessage("feature").call();
git.push().setRemote("origin").add("feature").setCredentialsProvider(new UsernamePasswordCredentialsProvider(gitlab.getUsername(), gitlab.getPassword()))
.call();
}
gitlab.createMergeRequest(projectId, "feature", "master", "Merge feature branch to master.");
buildTriggered.block(10000);
assertThat(buildTriggered.isSignaled(), is(true));
return commit.getName();
}
private String getDocker0Ip() {

View File

@ -16,6 +16,7 @@ public class NameBasedFilterTest {
assertThat(nameBasedFilter.isBranchAllowed("master"), is(true));
assertThat(nameBasedFilter.isBranchAllowed("develop"), is(true));
assertThat(nameBasedFilter.isBranchAllowed("not-included-branch"), is(false));
}
@Test
@ -35,4 +36,29 @@ public class NameBasedFilterTest {
assertThat(nameBasedFilter.isBranchAllowed("develop"), is(false));
assertThat(nameBasedFilter.isBranchAllowed("not-excluded-and-not-included-branch"), is(false));
}
@Test
public void allowIncludeAndExcludeToBeNull() {
NameBasedFilter nameBasedFilter = new NameBasedFilter(null, null);
assertThat(nameBasedFilter.isBranchAllowed("master"), is(true));
}
@Test
public void allowIncludeToBeNull() {
NameBasedFilter nameBasedFilter = new NameBasedFilter(null, "master, develop");
assertThat(nameBasedFilter.isBranchAllowed("master"), is(false));
assertThat(nameBasedFilter.isBranchAllowed("develop"), is(false));
assertThat(nameBasedFilter.isBranchAllowed("not-excluded-branch"), is(true));
}
@Test
public void allowExcludeToBeNull() {
NameBasedFilter nameBasedFilter = new NameBasedFilter("master, develop", null);
assertThat(nameBasedFilter.isBranchAllowed("master"), is(true));
assertThat(nameBasedFilter.isBranchAllowed("develop"), is(true));
assertThat(nameBasedFilter.isBranchAllowed("not-included-branch"), is(false));
}
}

View File

@ -1,6 +1,8 @@
package com.dabsquared.gitlabjenkins.trigger.handler.merge;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.Action;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.State;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.builder.generated.MergeRequestObjectAttributesBuilder;
import com.dabsquared.gitlabjenkins.trigger.filter.BranchFilterFactory;
import com.dabsquared.gitlabjenkins.trigger.filter.BranchFilterType;
import hudson.Launcher;
@ -10,11 +12,18 @@ import hudson.model.FreeStyleProject;
import hudson.plugins.git.GitSCM;
import hudson.util.OneShotEvent;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.NoMessageException;
import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
@ -24,6 +33,7 @@ import org.jvnet.hudson.test.TestBuilder;
import java.io.IOException;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.concurrent.ExecutionException;
import static com.dabsquared.gitlabjenkins.gitlab.hook.model.builder.generated.CommitBuilder.commit;
@ -47,13 +57,6 @@ public class MergeRequestHookTriggerHandlerImplTest {
@Rule
public TemporaryFolder tmp = new TemporaryFolder();
private MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler;
@Before
public void setup() {
mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.opened, State.reopened), false);
}
@Test
public void mergeRequest_ciSkip() throws IOException, InterruptedException {
final OneShotEvent buildTriggered = new OneShotEvent();
@ -66,6 +69,7 @@ public class MergeRequestHookTriggerHandlerImplTest {
}
});
project.setQuietPeriod(0);
MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.opened, State.reopened), false);
mergeRequestHookTriggerHandler.handle(project, mergeRequestHook()
.withObjectAttributes(mergeRequestObjectAttributes().withDescription("[ci-skip]").build())
.build(), true, BranchFilterFactory.newBranchFilter(branchFilterConfig().build(BranchFilterType.All)),
@ -77,7 +81,84 @@ public class MergeRequestHookTriggerHandlerImplTest {
@Test
public void mergeRequest_build() throws IOException, InterruptedException, GitAPIException, ExecutionException {
Git.init().setDirectory(tmp.getRoot()).call();
MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.opened, State.reopened), false);
OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, State.opened);
assertThat(buildTriggered.isSignaled(), is(true));
}
@Test
public void mergeRequest_build_when_accepted() throws IOException, InterruptedException, GitAPIException, ExecutionException {
MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.merged), false);
OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, State.merged);
assertThat(buildTriggered.isSignaled(), is(true));
}
@Test
public void mergeRequest_build_when_closed() throws IOException, InterruptedException, GitAPIException, ExecutionException {
MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.closed), false);
OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, State.closed);
assertThat(buildTriggered.isSignaled(), is(true));
}
@Test
public void mergeRequest_do_not_build_when_accepted() throws IOException, InterruptedException, GitAPIException, ExecutionException {
MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.opened, State.updated), false);
OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, State.merged);
assertThat(buildTriggered.isSignaled(), is(false));
}
@Test
public void mergeRequest_do_not_build_when_closed() throws IOException, InterruptedException, GitAPIException, ExecutionException {
MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.opened, State.updated), false);
OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, State.closed);
assertThat(buildTriggered.isSignaled(), is(false));
}
@Test
public void mergeRequest_build_when_approved() throws IOException, InterruptedException, GitAPIException, ExecutionException {
MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(EnumSet.noneOf(State.class), EnumSet.of(Action.approved), false);
OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, Action.approved);
assertThat(buildTriggered.isSignaled(), is(true));
}
@Test
public void mergeRequest_build_only_when_approved_and_not_when_opened() throws IOException, InterruptedException, GitAPIException, ExecutionException {
mergeRequest_build_only_when_approved(Action.open);
}
@Test
public void mergeRequest_build_only_when_approved_and_not_when_updated() throws IOException, InterruptedException, GitAPIException, ExecutionException {
mergeRequest_build_only_when_approved(Action.update);
}
private void mergeRequest_build_only_when_approved(Action action)
throws GitAPIException, IOException, InterruptedException {
MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(EnumSet.noneOf(State.class), EnumSet.of(Action.approved), false);
OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, action);
assertThat(buildTriggered.isSignaled(), is(false));
}
private OneShotEvent doHandle(MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler, Action action) throws GitAPIException, IOException, InterruptedException {
return doHandle(mergeRequestHookTriggerHandler, defaultMergeRequestObjectAttributes().withState(State.opened).withAction(action));
}
private OneShotEvent doHandle(MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler, State state) throws GitAPIException, IOException, InterruptedException {
return doHandle(mergeRequestHookTriggerHandler, defaultMergeRequestObjectAttributes().withState(state));
}
private OneShotEvent doHandle(MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler,
MergeRequestObjectAttributesBuilder objectAttributes) throws GitAPIException, IOException, NoHeadException,
NoMessageException, UnmergedPathsException, ConcurrentRefUpdateException, WrongRepositoryStateException,
AmbiguousObjectException, IncorrectObjectTypeException, MissingObjectException, InterruptedException {
Git.init().setDirectory(tmp.getRoot()).call();
tmp.newFile("test");
Git git = Git.open(tmp.getRoot());
git.add().addFilepattern("test");
@ -96,42 +177,46 @@ public class MergeRequestHookTriggerHandlerImplTest {
}
});
project.setQuietPeriod(0);
mergeRequestHookTriggerHandler.handle(project, mergeRequestHook()
.withObjectAttributes(mergeRequestObjectAttributes()
.withTargetBranch("refs/heads/" + git.nameRev().add(head).call().get(head))
.withState(State.opened)
.withIid(1)
.withTitle("test")
.withTargetProjectId(1)
.withSourceProjectId(1)
.withSourceBranch("feature")
.withTargetBranch("master")
.withLastCommit(commit().withAuthor(user().withName("test").build()).withId(commit.getName()).build())
.withSource(project()
.withName("test")
.withNamespace("test-namespace")
.withHomepage("https://gitlab.org/test")
.withUrl("git@gitlab.org:test.git")
.withSshUrl("git@gitlab.org:test.git")
.withHttpUrl("https://gitlab.org/test.git")
.build())
.withTarget(project()
.withName("test")
.withNamespace("test-namespace")
.withHomepage("https://gitlab.org/test")
.withUrl("git@gitlab.org:test.git")
.withSshUrl("git@gitlab.org:test.git")
.withHttpUrl("https://gitlab.org/test.git")
.build())
.build())
mergeRequestHookTriggerHandler.handle(project, mergeRequestHook()
.withObjectAttributes(objectAttributes
.withTargetBranch("refs/heads/" + git.nameRev().add(head).call().get(head))
.withLastCommit(commit().withAuthor(user().withName("test").build()).withId(commit.getName()).build())
.build())
.withProject(project()
.withWebUrl("https://gitlab.org/test.git")
.build()
)
.build(), true, BranchFilterFactory.newBranchFilter(branchFilterConfig().build(BranchFilterType.All)),
newMergeRequestLabelFilter(null));
newMergeRequestLabelFilter(null));
buildTriggered.block(10000);
assertThat(buildTriggered.isSignaled(), is(true));
}
return buildTriggered;
}
private MergeRequestObjectAttributesBuilder defaultMergeRequestObjectAttributes() {
return mergeRequestObjectAttributes()
.withIid(1)
.withTitle("test")
.withTargetProjectId(1)
.withSourceProjectId(1)
.withSourceBranch("feature")
.withTargetBranch("master")
.withSource(project()
.withName("test")
.withNamespace("test-namespace")
.withHomepage("https://gitlab.org/test")
.withUrl("git@gitlab.org:test.git")
.withSshUrl("git@gitlab.org:test.git")
.withHttpUrl("https://gitlab.org/test.git")
.build())
.withTarget(project()
.withName("test")
.withNamespace("test-namespace")
.withHomepage("https://gitlab.org/test")
.withUrl("git@gitlab.org:test.git")
.withSshUrl("git@gitlab.org:test.git")
.withHttpUrl("https://gitlab.org/test.git")
.build());
}
}

View File

@ -0,0 +1,140 @@
package com.dabsquared.gitlabjenkins.trigger.handler.pipeline;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.PipelineHook;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.State;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.User;
import com.dabsquared.gitlabjenkins.trigger.filter.BranchFilterType;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.FreeStyleProject;
import hudson.util.OneShotEvent;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import static com.dabsquared.gitlabjenkins.gitlab.hook.model.builder.generated.PipelineEventObjectAttributesBuilder.pipelineEventObjectAttributes;
import static com.dabsquared.gitlabjenkins.gitlab.hook.model.builder.generated.PipelineHookBuilder.pipelineHook;
import static com.dabsquared.gitlabjenkins.gitlab.hook.model.builder.generated.ProjectBuilder.project;
import static com.dabsquared.gitlabjenkins.gitlab.hook.model.builder.generated.RepositoryBuilder.repository;
import static com.dabsquared.gitlabjenkins.trigger.filter.BranchFilterConfig.BranchFilterConfigBuilder.branchFilterConfig;
import static com.dabsquared.gitlabjenkins.trigger.filter.BranchFilterFactory.newBranchFilter;
import static com.dabsquared.gitlabjenkins.trigger.filter.MergeRequestLabelFilterFactory.newMergeRequestLabelFilter;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* @author Robin Müller
*/
public class PipelineHookTriggerHandlerImplTest {
@ClassRule
public static JenkinsRule jenkins = new JenkinsRule();
@Rule
public TemporaryFolder tmp = new TemporaryFolder();
private PipelineHookTriggerHandler pipelineHookTriggerHandler;
private PipelineHook pipelineHook;
@Before
public void setup() throws IOException, GitAPIException {
List<String> allowedStates = new ArrayList<>();
allowedStates.add("success");
User user = new User();
user.setName("test");
user.setId(1);
Git.init().setDirectory(tmp.getRoot()).call();
tmp.newFile("test");
Git git = Git.open(tmp.getRoot());
git.add().addFilepattern("test");
git.commit().setMessage("test").call();
ObjectId head = git.getRepository().resolve(Constants.HEAD);
pipelineHookTriggerHandler = new PipelineHookTriggerHandlerImpl(allowedStates);
pipelineHook = pipelineHook()
.withProjectId(1)
.withUser(user)
.withRepository(repository()
.withName("test")
.withHomepage("https://gitlab.org/test")
.withUrl("git@gitlab.org:test.git")
.withGitSshUrl("git@gitlab.org:test.git")
.withGitHttpUrl("https://gitlab.org/test.git")
.build())
.withProject(project()
.withNamespace("test-namespace")
.withWebUrl("https://gitlab.org/test")
.withId(1)
.build())
.withObjectAttributes(pipelineEventObjectAttributes()
.withId(1)
.withStatus("success")
.withSha("bcbb5ec396a2c0f828686f14fac9b80b780504f2")
.withStages(new ArrayList<String>())
.withRef("refs/heads/" + git.nameRev().add(head).call().get(head))
.build())
.build();
}
@Test
/**
* always triggers since pipeline events do not contain ci skip message
*/
public void pipeline_ciSkip() throws IOException, InterruptedException {
final OneShotEvent buildTriggered = new OneShotEvent();
FreeStyleProject project = jenkins.createFreeStyleProject();
project.getBuildersList().add(new TestBuilder() {
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
buildTriggered.signal();
return true;
}
});
project.setQuietPeriod(0);
pipelineHookTriggerHandler.handle(project, pipelineHook , true, newBranchFilter(branchFilterConfig().build(BranchFilterType.All)),
newMergeRequestLabelFilter(null));
buildTriggered.block(10000);
assertThat(buildTriggered.isSignaled(), is(true));
}
@Test
public void pipeline_build() throws IOException, InterruptedException, GitAPIException, ExecutionException {
final OneShotEvent buildTriggered = new OneShotEvent();
FreeStyleProject project = jenkins.createFreeStyleProject();
project.getBuildersList().add(new TestBuilder() {
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
buildTriggered.signal();
return true;
}
});
project.setQuietPeriod(0);
pipelineHookTriggerHandler.handle(project, pipelineHook, false, newBranchFilter(branchFilterConfig().build(BranchFilterType.All)),
newMergeRequestLabelFilter(null));
buildTriggered.block(10000);
assertThat(buildTriggered.isSignaled(), is(true));
}
}

View File

@ -101,6 +101,7 @@ public class PushHookTriggerHandlerImplTest {
.withBefore("0000000000000000000000000000000000000000")
.withProjectId(1)
.withUserName("test")
.withObjectKind("tag_push")
.withRepository(repository()
.withName("test")
.withHomepage("https://gitlab.org/test")
@ -151,6 +152,7 @@ public class PushHookTriggerHandlerImplTest {
.withBefore("0000000000000000000000000000000000000000")
.withProjectId(1)
.withUserName("test")
.withObjectKind("push")
.withRepository(repository()
.withName("test")
.withHomepage("https://gitlab.org/test")

View File

@ -0,0 +1,139 @@
package com.dabsquared.gitlabjenkins.util;
import org.junit.Test;
import hudson.model.Job;
import hudson.model.Run;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.plugins.git.Revision;
import hudson.plugins.git.util.Build;
import hudson.plugins.git.util.BuildData;
import hudson.util.RunList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jgit.lib.ObjectId;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
/**
* @author Joshua Barker
*/
public class BuildUtilTest {
private static final String SHA1 = "0616d12a3a24068691027a1e113147e3c1cfa2f4";
private static final String SHALIB = "a53131154f6dfc0d1642451679fb977c5ecf31c0";
private static final String WRONGSHA = "5f106d47c2ce17dd65774c12c0826785d16b26f7";
public void getBuildBySHA1WithoutMergeBuilds_sha_found(){
Job<?,?> project = createProject(SHA1);
Run<?, ?> build = BuildUtil.getBuildBySHA1WithoutMergeBuilds(project, SHA1);
assertThat(build, is(notNullValue()));
}
@Test
public void getBuildBySHA1WithoutMergeBuilds_sha_missing(){
Job<?,?> project = createProject(SHA1);
Run<?, ?> build = BuildUtil.getBuildBySHA1WithoutMergeBuilds(project, WRONGSHA);
assertThat(build, is(nullValue()));
}
@Test
public void getBuildBySHA1WithoutMergeBuilds_libraryFirst(){
Job<?,?> project = createProject(SHA1, SHALIB);
Run<?, ?> build = BuildUtil.getBuildBySHA1WithoutMergeBuilds(project, SHA1);
assertThat(build, is(notNullValue()));
}
@Test
public void getBuildBySHA1WithoutMergeBuilds_libraryLast(){
Job<?,?> project = createProject(SHA1, SHALIB);
Run<?, ?> build = BuildUtil.getBuildBySHA1WithoutMergeBuilds(project, SHA1);
assertThat(build, is(notNullValue()));
}
@Test
public void getBuildBySHA1IncludingMergeBuilds_sha_found(){
Job<?,?> project = createProject(SHA1);
Run<?, ?> build = BuildUtil.getBuildBySHA1IncludingMergeBuilds(project, SHA1);
assertThat(build, is(notNullValue()));
}
@Test
public void getBuildBySHA1IncludingMergeBuilds_sha_missing(){
Job<?,?> project = createProject(SHA1);
Run<?, ?> build = BuildUtil.getBuildBySHA1IncludingMergeBuilds(project, WRONGSHA);
assertThat(build, is(nullValue()));
}
@Test
public void getBuildBySHA1IncludingMergeBuilds_libraryFirst(){
Job<?,?> project = createProject(SHA1, SHALIB);
Run<?, ?> build = BuildUtil.getBuildBySHA1IncludingMergeBuilds(project, SHA1);
assertThat(build, is(notNullValue()));
}
@Test
public void getBuildBySHA1IncludingMergeBuilds_libraryLast(){
Job<?,?> project = createProject(SHA1, SHALIB);
Run<?, ?> build = BuildUtil.getBuildBySHA1IncludingMergeBuilds(project, SHA1);
assertThat(build, is(notNullValue()));
}
private AbstractProject<?,?> createProject(String... shas) {
AbstractBuild build = mock(AbstractBuild.class);
List<BuildData> buildDatas = new ArrayList<BuildData>();
for(String sha : shas) {
BuildData buildData = createBuildData(sha);
buildDatas.add(buildData);
}
when(build.getAction(BuildData.class)).thenReturn(buildDatas.get(0));
when(build.getActions(BuildData.class)).thenReturn(buildDatas);
AbstractProject<?, ?> project = mock(AbstractProject.class);
when(build.getProject()).thenReturn(project);
RunList list = mock(RunList.class);
when(list.iterator()).thenReturn(Arrays.asList(build).iterator());
when(project.getBuilds()).thenReturn(list);
return project;
}
private BuildData createBuildData(String sha) {
BuildData buildData = mock(BuildData.class);
Revision revision = mock(Revision.class);
when(revision.getSha1String()).thenReturn(sha);
when(buildData.getLastBuiltRevision()).thenReturn(revision);
Build gitBuild = mock(Build.class);
when(gitBuild.getMarked()).thenReturn(revision);
when(buildData.getLastBuild(any(ObjectId.class))).thenReturn(gitBuild);
when(gitBuild.getRevision()).thenReturn(revision);
when(gitBuild.isFor(sha)).thenReturn(true);
buildData.lastBuild = gitBuild;
return buildData;
}
}

View File

@ -0,0 +1,123 @@
package com.dabsquared.gitlabjenkins.util;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static com.dabsquared.gitlabjenkins.cause.CauseDataBuilder.causeData;
import java.util.ArrayList;
import java.util.Collections;
import org.eclipse.jgit.lib.ObjectId;
import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import com.dabsquared.gitlabjenkins.cause.CauseData;
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.BuildState;
import hudson.EnvVars;
import hudson.model.Cause;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.Cause.UpstreamCause;
import hudson.plugins.git.Revision;
import hudson.plugins.git.util.Build;
import hudson.plugins.git.util.BuildData;
import jenkins.model.Jenkins;
/**
* @author Daumantas Stulgis
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({GitLabConnectionProperty.class, Jenkins.class})
public class CommitStatusUpdaterTest {
private static final int PROJECT_ID = 1;
private static final String BUILD_URL = "job/Test-Job";
private static final String STAGE = "test";
private static final String REVISION = "1111111";
private static final String JENKINS_URL = "https://gitlab.org/jenkins/";
@Mock Run<?, ?> build;
@Mock TaskListener taskListener;
@Mock GitLabClient client;
@Mock GitLabWebHookCause gitlabCause;
@Mock BuildData action;
@Mock Revision lastBuiltRevision;
@Mock Build lastBuild;
@Mock Revision revision;
@Mock EnvVars environment;
@Mock UpstreamCause upCauseLevel1;
@Mock UpstreamCause upCauseLevel2;
@Mock Jenkins jenkins;
CauseData causeData;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
PowerMockito.mockStatic(GitLabConnectionProperty.class);
PowerMockito.mockStatic(Jenkins.class);
when(Jenkins.getInstance()).thenReturn(jenkins);
when(jenkins.getRootUrl()).thenReturn(JENKINS_URL);
when(GitLabConnectionProperty.getClient(any(Run.class))).thenReturn(client);
when(build.getAction(BuildData.class)).thenReturn(action);
when(action.getLastBuiltRevision()).thenReturn(lastBuiltRevision);
when(action.getLastBuild(any(ObjectId.class))).thenReturn(lastBuild);
when(lastBuild.getMarked()).thenReturn(revision);
when(revision.getSha1String()).thenReturn(REVISION);
when(build.getUrl()).thenReturn(BUILD_URL);
when(build.getEnvironment(any(TaskListener.class))).thenReturn(environment);
when(build.getCauses()).thenReturn(new ArrayList<Cause>(Collections.singletonList(upCauseLevel1)));
when(upCauseLevel1.getUpstreamCauses()).thenReturn(new ArrayList<Cause>(Collections.singletonList(upCauseLevel2)));
when(upCauseLevel2.getUpstreamCauses()).thenReturn(new ArrayList<Cause>(Collections.singletonList(gitlabCause)));
causeData = causeData()
.withActionType(CauseData.ActionType.NOTE)
.withSourceProjectId(PROJECT_ID)
.withTargetProjectId(PROJECT_ID)
.withBranch("feature")
.withSourceBranch("feature")
.withUserName("")
.withSourceRepoHomepage("https://gitlab.org/test")
.withSourceRepoName("test")
.withSourceNamespace("test-namespace")
.withSourceRepoUrl("git@gitlab.org:test.git")
.withSourceRepoSshUrl("git@gitlab.org:test.git")
.withSourceRepoHttpUrl("https://gitlab.org/test.git")
.withMergeRequestTitle("Test")
.withMergeRequestId(1)
.withMergeRequestIid(1)
.withTargetBranch("master")
.withTargetRepoName("test")
.withTargetNamespace("test-namespace")
.withTargetRepoSshUrl("git@gitlab.org:test.git")
.withTargetRepoHttpUrl("https://gitlab.org/test.git")
.withTriggeredByUser("test")
.withLastCommit(REVISION)
.withTargetProjectUrl("https://gitlab.org/test")
.build();
when(gitlabCause.getData()).thenReturn(causeData);
PowerMockito.spy(client);
}
@Test
public void test() {
CommitStatusUpdater.updateCommitStatus(build, taskListener, BuildState.success, STAGE);
verify(client).changeBuildStatus(Integer.toString(PROJECT_ID), REVISION, BuildState.success, null, STAGE, DisplayURLProvider.get().getRunURL(build), BuildState.success.name());
}
}

View File

@ -0,0 +1,115 @@
package com.dabsquared.gitlabjenkins.util;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.api.model.*;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.State;
import java.util.List;
class GitLabClientStub implements GitLabClient {
private final String url;
GitLabClientStub(String url) {
this.url = url;
}
@Override
public String getHostUrl() {
return url;
}
@Override
public Project createProject(String projectName) {
return null;
}
@Override
public MergeRequest createMergeRequest(Integer projectId, String sourceBranch, String targetBranch, String title) {
return null;
}
@Override
public Project getProject(String projectName) {
return null;
}
@Override
public Project updateProject(String projectId, String name, String path) {
return null;
}
@Override
public void deleteProject(String projectId) {
}
@Override
public void addProjectHook(String projectId, String url, Boolean pushEvents, Boolean mergeRequestEvents, Boolean noteEvents) {
}
@Override
public void changeBuildStatus(String projectId, String sha, BuildState state, String ref, String context, String targetUrl, String description) {
}
@Override
public void changeBuildStatus(Integer projectId, String sha, BuildState state, String ref, String context, String targetUrl, String description) {
}
@Override
public void getCommit(String projectId, String sha) {
}
@Override
public void acceptMergeRequest(MergeRequest mr, String mergeCommitMessage, boolean shouldRemoveSourceBranch) {
}
@Override
public void createMergeRequestNote(MergeRequest mr, String body) {
}
@Override
public List<MergeRequest> getMergeRequests(String projectId, State state, int page, int perPage) {
return null;
}
@Override
public List<Branch> getBranches(String projectId) {
return null;
}
@Override
public Branch getBranch(String projectId, String branch) {
return null;
}
@Override
public User getCurrentUser() {
return null;
}
@Override
public User addUser(String email, String username, String name, String password) {
return null;
}
@Override
public User updateUser(String userId, String email, String username, String name, String password) {
return null;
}
@Override
public List<Label> getLabels(String projectId) {
return null;
}
@Override
public List<Pipeline> getPipelines(String projectName) {
return null;
}
}

View File

@ -1,5 +1,7 @@
package com.dabsquared.gitlabjenkins.util;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
@ -9,6 +11,7 @@ import static com.dabsquared.gitlabjenkins.util.ProjectIdUtilTest.TestData.forRe
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* @author Robin Müller
*/
@ -17,14 +20,21 @@ public class ProjectIdUtilTest {
@DataPoints
public static TestData[] testData = {
forRemoteUrl("git@gitlab.com:test/project.git").expectProjectId("test/project"),
forRemoteUrl("https://gitlab.com/test/project.git").expectProjectId("test/project"),
forRemoteUrl("https://myurl.com/gitlab/group/project.git").expectProjectId("group/project")
forRemoteUrl("git@gitlab.com", "git@gitlab.com:test/project.git").expectProjectId("test/project"),
forRemoteUrl("https://gitlab.com", "https://gitlab.com/test/project.git").expectProjectId("test/project"),
forRemoteUrl("https://myurl.com/gitlab", "https://myurl.com/gitlab/group/project.git").expectProjectId("group/project"),
forRemoteUrl("git@gitlab.com", "git@gitlab.com:group/subgroup/project.git").expectProjectId("group/subgroup/project"),
forRemoteUrl("https://myurl.com/gitlab", "https://myurl.com/gitlab/group/subgroup/project.git").expectProjectId("group/subgroup/project"),
forRemoteUrl("https://myurl.com", "https://myurl.com/group/subgroup/project.git").expectProjectId("group/subgroup/project"),
forRemoteUrl("https://myurl.com", "https://myurl.com/group/subgroup/subsubgroup/project.git").expectProjectId("group/subgroup/subsubgroup/project"),
forRemoteUrl("git@gitlab.com", "git@gitlab.com:group/subgroup/subsubgroup/project.git").expectProjectId("group/subgroup/subsubgroup/project"),
};
@Theory
public void retrieveProjectId(TestData testData) throws ProjectIdUtil.ProjectIdResolutionException {
String projectId = ProjectIdUtil.retrieveProjectId(testData.remoteUrl);
GitLabClient client = new GitLabClientStub(testData.hostUrl);
String projectId = ProjectIdUtil.retrieveProjectId(client, testData.remoteUrl);
assertThat(projectId, is(testData.expectedProjectId));
}
@ -32,10 +42,12 @@ public class ProjectIdUtilTest {
static final class TestData {
private final String hostUrl;
private final String remoteUrl;
private String expectedProjectId;
private TestData(String remoteUrl) {
private TestData(String hostUrl, String remoteUrl) {
this.hostUrl = hostUrl;
this.remoteUrl = remoteUrl;
}
@ -49,8 +61,8 @@ public class ProjectIdUtilTest {
return remoteUrl;
}
static TestData forRemoteUrl(String remoteUrl) {
return new TestData(remoteUrl);
static TestData forRemoteUrl(String baseUrl, String remoteUrl) {
return new TestData(baseUrl, remoteUrl);
}
}
}

View File

@ -0,0 +1,82 @@
package com.dabsquared.gitlabjenkins.webhook.build;
import com.dabsquared.gitlabjenkins.GitLabPushTrigger;
import com.dabsquared.gitlabjenkins.cause.CauseData;
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.NoteHook;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.PipelineHook;
import hudson.model.FreeStyleProject;
import hudson.model.ParametersAction;
import hudson.model.StringParameterValue;
import hudson.model.queue.QueueTaskFuture;
import hudson.plugins.git.GitSCM;
import org.apache.commons.io.IOUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.jvnet.hudson.test.JenkinsRule;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.StaplerResponse;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import static com.dabsquared.gitlabjenkins.cause.CauseDataBuilder.causeData;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
* @author Milena Zachow
*/
@RunWith(MockitoJUnitRunner.class)
public class PipelineBuildActionTest {
@ClassRule
public static JenkinsRule jenkins = new JenkinsRule();
@Rule
public ExpectedException exception = ExpectedException.none();
@Mock
private StaplerResponse response;
@Mock
private GitLabPushTrigger trigger;
FreeStyleProject testProject;
@Before
public void setUp() throws IOException{
testProject = jenkins.createFreeStyleProject();
testProject.addTrigger(trigger);
}
@Test
public void buildOnSuccess () throws IOException {
exception.expect(HttpResponses.HttpResponseException.class);
new PipelineBuildAction(testProject, getJson("PipelineEvent.json"), null).execute(response);
verify(trigger).onPost(any(PipelineHook.class));
}
@Test
public void doNotBuildOnFailure() throws IOException {
exception.expect(HttpResponses.HttpResponseException.class);
new PipelineBuildAction(testProject, getJson("PipelineFailureEvent.json"), null).execute(response);
verify(trigger, never()).onPost(any(PipelineHook.class));
}
private String getJson(String name) throws IOException {
return IOUtils.toString(getClass().getResourceAsStream(name));
}
}

View File

@ -59,7 +59,7 @@ public abstract class BuildPageRedirectActionTest {
testProject.setScm(new GitSCM(gitRepoUrl));
testProject.setQuietPeriod(0);
QueueTaskFuture<FreeStyleBuild> future = testProject.scheduleBuild2(0);
FreeStyleBuild build = future.get(5, TimeUnit.SECONDS);
FreeStyleBuild build = future.get(15, TimeUnit.SECONDS);
getBuildPageRedirectAction(testProject).execute(response);

View File

@ -0,0 +1,152 @@
{ "object_kind": "pipeline",
"object_attributes": {
"id": 31,
"ref": "master",
"tag": false,
"sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
"before_sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
"status": "success",
"stages": [
"build",
"test",
"deploy"
],
"created_at": "2016-08-12 15:23:28 UTC",
"finished_at": "2016-08-12 15:26:29 UTC",
"duration": 63
},
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"project": {
"name": "Gitlab Test",
"description": "Atque in sunt eos similique dolores voluptatem.",
"web_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test",
"avatar_url": null,
"git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git",
"git_http_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test.git",
"namespace": "Gitlab Org",
"visibility_level": 20,
"path_with_namespace": "gitlab-org/gitlab-test",
"default_branch": "master"
},
"commit": {
"id": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
"message": "test\n",
"timestamp": "2016-08-12T17:23:21+02:00",
"url": "http://example.com/gitlab-org/gitlab-test/commit/bcbb5ec396a2c0f828686f14fac9b80b780504f2",
"author": {
"name": "User",
"email": "user@gitlab.com"
}
},
"builds": [
{
"id": 380,
"stage": "deploy",
"name": "production",
"status": "skipped",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": null,
"finished_at": null,
"when": "manual",
"manual": true,
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file": {
"filename": null,
"size": null
}
},
{
"id": 377,
"stage": "test",
"name": "test-image",
"status": "success",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": "2016-08-12 15:26:12 UTC",
"finished_at": null,
"when": "on_success",
"manual": false,
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file": {
"filename": null,
"size": null
}
},
{
"id": 378,
"stage": "test",
"name": "test-build",
"status": "success",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": "2016-08-12 15:26:12 UTC",
"finished_at": "2016-08-12 15:26:29 UTC",
"when": "on_success",
"manual": false,
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file": {
"filename": null,
"size": null
}
},
{
"id": 376,
"stage": "build",
"name": "build-image",
"status": "success",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": "2016-08-12 15:24:56 UTC",
"finished_at": "2016-08-12 15:25:26 UTC",
"when": "on_success",
"manual": false,
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file": {
"filename": null,
"size": null
}
},
{
"id": 379,
"stage": "deploy",
"name": "staging",
"status": "created",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": null,
"finished_at": null,
"when": "on_success",
"manual": false,
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file": {
"filename": null,
"size": null
}
}
]
}

View File

@ -0,0 +1,152 @@
{ "object_kind": "pipeline",
"object_attributes": {
"id": 31,
"ref": "master",
"tag": false,
"sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
"before_sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
"status": "failure",
"stages": [
"build",
"test",
"deploy"
],
"created_at": "2016-08-12 15:23:28 UTC",
"finished_at": "2016-08-12 15:26:29 UTC",
"duration": 63
},
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"project": {
"name": "Gitlab Test",
"description": "Atque in sunt eos similique dolores voluptatem.",
"web_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test",
"avatar_url": null,
"git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git",
"git_http_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test.git",
"namespace": "Gitlab Org",
"visibility_level": 20,
"path_with_namespace": "gitlab-org/gitlab-test",
"default_branch": "master"
},
"commit": {
"id": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
"message": "test\n",
"timestamp": "2016-08-12T17:23:21+02:00",
"url": "http://example.com/gitlab-org/gitlab-test/commit/bcbb5ec396a2c0f828686f14fac9b80b780504f2",
"author": {
"name": "User",
"email": "user@gitlab.com"
}
},
"builds": [
{
"id": 380,
"stage": "deploy",
"name": "production",
"status": "skipped",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": null,
"finished_at": null,
"when": "manual",
"manual": true,
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file": {
"filename": null,
"size": null
}
},
{
"id": 377,
"stage": "test",
"name": "test-image",
"status": "success",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": "2016-08-12 15:26:12 UTC",
"finished_at": null,
"when": "on_success",
"manual": false,
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file": {
"filename": null,
"size": null
}
},
{
"id": 378,
"stage": "test",
"name": "test-build",
"status": "success",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": "2016-08-12 15:26:12 UTC",
"finished_at": "2016-08-12 15:26:29 UTC",
"when": "on_success",
"manual": false,
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file": {
"filename": null,
"size": null
}
},
{
"id": 376,
"stage": "build",
"name": "build-image",
"status": "success",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": "2016-08-12 15:24:56 UTC",
"finished_at": "2016-08-12 15:25:26 UTC",
"when": "on_success",
"manual": false,
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file": {
"filename": null,
"size": null
}
},
{
"id": 379,
"stage": "deploy",
"name": "staging",
"status": "created",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": null,
"finished_at": null,
"when": "on_success",
"manual": false,
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file": {
"filename": null,
"size": null
}
}
]
}

5
travis/before_cache.sh Normal file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
rm -rf $HOME/.m2/repository/com/dabsquared/gitlabjenkins
mkdir -p $DOCKER_CACHE_DIR
docker images -a --filter='dangling=false' --format '{{.Repository}}:{{.Tag}} {{.ID}}' | xargs -n 2 -t sh -c 'test -e $DOCKER_CACHE_DIR/$1.tar.gz || docker save $0 | gzip -2 > $DOCKER_CACHE_DIR/$1.tar.gz'

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
if [[ -d $DOCKER_CACHE_DIR ]]; then
echo "Loading cached images into docker..."
ls $DOCKER_CACHE_DIR/*.tar.gz | xargs -I {file} sh -c "zcat {file} | docker load";
fi
./mvnw integration-test -B -Pintegration-test -Dgitlab.version=$GITLAB_VERSION -Dfindbugs.skip=true -Dmaven.javadoc.skip=true

2
travis/test.sh Normal file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
./mvnw verify -B -Pall-tests,skip-javadoc-with-tests -Dmaven.javadoc.skip=true