- Pipeline: GitHub
- License
- Prerequisites
- Credentials
- Triggers
- Global Variables
- Auxiliary Classes
- Examples
The entry points for this plugin’s functionality are additional global variables, available to pipeline scripts when the plugin is enabled and the prerequisites are met.
MIT
-
Jenkins running Java 8 or higher.
-
Projects/jobs must be automatically created by the GitHub Organization folder/project type.
See: GitHub Branch Source Plugin
Currently all operations against GitHub will be performed using the builds GitHubSCMSource
credentials. These will typically be the Scan Credentials
you configured in your GitHub Organization.
However you can override this in a pipeline script by calling setCredentials(String userName, String password)
before any properties or methods are accessed/invoked on the pullRequest
global variable.
pullRequest.setCredentials('John.Smith', 'qwerty4321')
If you plan to use this plugin to add/modify/remove comments, labels, commit statuses etc. Please ensure that the required permissions are assigned to the token supplied in the credentials (Scan Credentials
or Manually
supplied).
This plugin adds the following pipeline triggers
- issueCommentTrigger
- pullRequestReview
- This trigger only works on Pull Requests, created by the GitHub Branch Source Plugin.
- Currently this trigger will only allow collaborators of the repository in question to trigger builds.
The Pull Request's job/build must have run at least once for the trigger to be registered. If an initial run never takes place then the trigger won't be registered and cannot pickup on any comments made.
This should not be an issue in practice, because a requirement of using this plugin is that your jobs are setup automatically by the GitHub Branch Source Plugin, which will trigger an initial build when it is notified of a new Pull Request.
This trigger would be of limited usefulness for people wishing to build public GitHub/Jenkins bots, using pipeline scripts. As there is no way to ensure that a Pull Request's Jenkinsfile
contains any triggers. Not to mention you would not want to trust just any Jenkinsfile
from a random Pull Request/non-collaborator.
This trigger is intended to be used inside enterprise organizations:
- Where all branches and forks just contain a token
Jenkinsfile
that delegates to the real pipeline script, using shared libraries. - Trust all their Pull Request authors.
commentPattern
(Required) - A Java style regular expression
properties([
pipelineTriggers([
issueCommentTrigger('.*test this please.*')
])
])
pipeline {
triggers {
issueCommentTrigger('.*test this please.*')
}
}
Note that the following uses currentBuild.rawBuild
and should therefore only be done in a @NonCPS
context. See the workflow-cps-plugin Technical Design for more information.
def triggerCause = currentBuild.rawBuild.getCause(org.jenkinsci.plugins.pipeline.github.trigger.IssueCommentCause)
if (triggerCause) {
echo("Build was started by ${triggerCause.userLogin}, who wrote: " +
"\"${triggerCause.comment}\", which matches the " +
"\"${triggerCause.triggerPattern}\" trigger pattern.")
} else {
echo('Build was not started by a trigger')
}
The GitHub comment and author that triggered the build are exposed as environment variables (from version > 2.5).
GITHUB_COMMENT
GITHUB_COMMENT_AUTHOR
reviewStates
(Optional) - A Java array of the PR review states you wish to trigger the build with. If not specified it will trigger for any review state. Possible states arepending, approved, changes_requested, commented, dismissed
properties([
pipelineTriggers([
pullRequestReview(reviewStates: ['approved'])
])
])
pipeline {
triggers {
pullRequestReview(reviewStates: ['approved'])
}
}
Note that the following uses currentBuild.rawBuild
and should therefore only be done in a @NonCPS
context. See the workflow-cps-plugin Technical Design for more information.
def triggerCause = currentBuild.rawBuild.getCause(org.jenkinsci.plugins.pipeline.github.trigger.PullRequestReviewCause)
if (triggerCause) {
echo("Build was started by ${triggerCause.userLogin}, who reviewed the PR: " +
"\"${triggerCause.state}\", which matches one of " +
"\"${triggerCause.reviewStates}\" trigger pattern.")
} else {
echo('Build was not started by a trigger')
}
The GitHub review comment and author that triggered the build are exposed as environment variables (from version > 2.8).
GITHUB_REVIEW_COMMENT
GITHUB_REVIEW_AUTHOR
GITHUB_REVIEW_STATE
Coming soon!
Before you can use the pullRequest
global variable you must ensure you are actually in a Pull Request build job. The best way to do this is to check for the existence of the CHANGE_ID
environment variable.
node {
stage('Build') {
try {
echo 'Hello World'
} catch (err) {
// CHANGE_ID is set only for pull requests, so it is safe to access the pullRequest global variable
if (env.CHANGE_ID) {
pullRequest.addLabel('Build Failed')
}
throw err
}
}
}
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Hello World'
}
}
}
post {
failure {
script {
// CHANGE_ID is set only for pull requests, so it is safe to access the pullRequest global variable
if (env.CHANGE_ID) {
pullRequest.addLabel('Build Failed')
}
}
}
}
}
Name | Type | Setter | Description |
---|---|---|---|
id | Integer |
false | |
state | String |
true | Valid values open or closed |
number | Integer |
false | |
url | String |
false | |
patchUrl | String |
false | |
diffUrl | String |
false | |
issueUrl | String |
false | |
title | String |
true | |
body | String |
true | |
locked | Boolean |
true | Accepts true , false or 'true' , 'false' |
milestone | Milestone |
true | Setter accepts int or Milestone class. |
head | String |
false | Revision (SHA) of the head commit of this pull request |
headRef | String |
false | Name of the branch this pull request is created for |
base | String |
true | Name of the base branch in the current repository this pull request targets |
files | Iterable<CommitFile> |
false | |
assignees | Iterable<String> |
true | Accepts a List<String> |
commits | Iterable<Commit> |
false | |
comments | Iterable<IssueComment> |
false | |
reviewComments | Iterable<ReviewComment> |
false | |
labels | Iterable<String> |
true | Accepts a List<String> |
statuses | Iterable<CommitStatus> |
false | |
requestedReviewers | Iterable<String> |
false | |
reviews | Iterable<Review> |
false | |
updatedAt | Date |
false | |
createdAt | Date |
false | |
createdBy | String |
false | |
closedAt | Date |
false | |
closedBy | String |
false | |
mergedAt | Date |
false | |
mergedBy | String |
false | |
commitCount | Integer |
false | |
commentCount | Integer |
false | |
additions | Integer |
false | |
deletions | Integer |
false | |
changedFiles | Integer |
false | |
merged | Boolean |
false | |
mergeable | Boolean |
false | |
mergeCommitSha | String |
false | |
mergeableState | String |
false | |
maintainerCanModify | Boolean |
true | Accepts true , false or 'true' , 'false' |
draft | Boolean |
false |
String merge([String commitTitle, String commitMessage, String sha, String mergeMethod])
Returns the merge's SHA/commit id.
CommitStatus createStatus(String status [, String context, String description, String targetUrl])
void addLabels(List labels)
void removeLabel(String label)
void addAssignees(List assignees)
void removeAssignees(List assignees)
void review(String event)
void review(String event, String body)
void review(String commitId, String event, String body)
ReviewComment reviewComment(String commitId, String path, int position, String body)
ReviewComment editReviewComment(long commentId, String body)
ReviewComment replyToReviewComment(long commentId, String body)
void deleteReviewComment(long commentId)
IssueComment comment(String body)
IssueComment editComment(long commentId, String body)
void deleteComment(long commentId)
Iterable getRequestedReviewers<>()
void createReviewRequests(List reviewers)
void createTeamReviewRequests(List teams)
Iterable getRequestedTeamReviewers<>()
void deleteReviewRequests(List reviewers)
void deleteTeamReviewRequests(List teams)
void deleteBranch()
void setCredentials(String userName, String password)
Name | Type | Setter | Description |
---|---|---|---|
id | String |
false | |
url | String |
false | |
state | String |
false | One of pending , success , failure or error |
context | String |
false | |
description | String |
false | |
targetUrl | String |
false | |
createdAt | Date |
false | |
updatedAt | Date |
false | |
creator | String |
false |
None.
Name | Type | Setter | Description |
---|---|---|---|
sha | String |
false | |
url | String |
false | |
author | String |
false | |
committer | String |
false | |
parents | Iterable<String> |
false | List of parent commit SHA's |
message | String |
false | |
commentCount | Integer |
false | |
comments | Iterable<ReviewComment> |
false | |
additions | Integer |
false | |
deletions | Integer |
false | |
totalChanges | Integer |
false | |
files | Iterable<CommitFile> |
false | List of files added, removed and or modified in this commit |
statuses | Iterable<CommitStatus> |
false | List of statuses associated with this commit |
CommitStatus createStatus(String status [, String context, String description, String targetUrl])
ReviewComment comment(String body [, String path, Integer position])
Name | Type | Setter | Description |
---|---|---|---|
sha | String |
false | |
filename | String |
false | |
status | String |
false | |
patch | String |
false | |
additions | Integer |
false | |
deletions | Integer |
false | |
changes | Integer |
false | |
rawUrl | String |
false | |
blobUrl | String |
false |
None.
Name | Type | Setter | Description |
---|---|---|---|
id | Integer |
false | |
url | String |
false | |
user | String |
false | |
body | String |
true | |
createdAt | Date |
false | |
updatedAt | Date |
false |
void delete()
Name | Type | Setter | Description |
---|---|---|---|
id | Long |
false | |
url | String |
false | |
user | String |
false | |
createdAt | Date |
false | |
updatedAt | Date |
false | |
commitId | String |
false | |
originalCommitId | Long |
false | |
body | String |
true | |
path | String |
false | |
line | Integer |
false | This will always return null as the GitHub APIs no longer return line , use position instead. |
position | Integer |
false | |
originalPosition | Integer |
false | |
diffHunk | String |
false | |
pullRequestUrl | String |
false | |
pullRequestReviewId | Long |
false | |
inReplyToId | Long |
false |
void delete()
Name | Type | Setter | Description |
---|---|---|---|
id | long |
false | |
user | String |
false | |
body | String |
false | |
commitId | String |
false | |
state | String |
One of APPROVED, PENDING, CHANGES_REQUESTED, DISMISSED, COMMENTED |
None.
Name | Type | Setter | Description |
---|---|---|---|
number | Integer |
false | |
createdAt | Date |
false | |
dueOn | Date |
false | |
updatedAt | Date |
false | |
closedAt | Date |
false | |
closedIssues | Integer |
false | |
openIssues | Integer |
false | |
description | String |
false | |
state | String |
false | |
title | String |
false | |
url | String |
false | |
creator | String |
false |
None.
pullRequest.title = 'Updated title'
pullRequest.body = pullRequest.body + '\nEdited by Pipeline'
pullRequest.status = 'closed'
pullRequest.createStatus(status: 'success',
context: 'continuous-integration/jenkins/pr-merge/tests',
description: 'All tests are passing',
targetUrl: "${env.JOB_URL}/testResults")
if (pullRequest.locked) {
pullRequest.locked = false
}
if (pullRequest.mergeable) {
pullRequest.merge('merge commit message here')
}
// or
if (pullRequest.mergeable) {
pullRequest.merge(commitTitle: 'Make it so..', commitMessage: 'TO BOLDLY GO WHERE NO MAN HAS GONE BEFORE...', mergeMethod: 'squash')
}
pullRequest.addLabel('Build Passing')
pullRequest.removeLabel('Build Passing')
pullRequest.labels = ['Bug', 'Feature']
pullRequest.addAssignee('Spock')
pullRequest.removeAssignee('McCoy')
pullRequest.assignees = ['Data', 'Scotty']
for (commitFile in pullRequest.files) {
echo "SHA: ${commitFile.sha} File Name: ${commitFile.filename} Status: ${commitFile.status}"
}
pullRequest.review('APPROVE')
// or
pullRequest.review('REQUEST_CHANGES', 'Change is the essential process of all existence.')
// or
def commitId = 'SHA of the commit containing the change/file you wish to review' // if unspecified, defaults to the most recent commit
def event = 'REQUEST_CHANGES' // valid review events: APPROVE, REQUEST_CHANGES, COMMENT
def body = 'Change is the essential process of all existence.' // body is required when event equals REQUEST_CHANGES or COMMENT
pullRequest.review(commitId, event, body)
def comment = pullRequest.comment('This PR is highly illogical..')
pullRequest.editComment(comment.id, 'Live long and prosper.')
// or
comment.body = 'Live long and prosper.'
pullRequest.deleteComment(commentId)
// or
comment.delete()
def commitId = 'SHA of the commit containing the change/file you wish to review';
def path = 'src/main/java/Main.java'
def lineNumber = 5
def body = 'The review comment'
def comment = pullRequest.reviewComment(commitId, path, lineNumber, body)
pullRequest.editReviewComment(comment.id, 'Live long and prosper.')
// or
comment.body = 'Live long and prosper.'
pullRequest.deleteReviewComment(comment.id)
// or
comment.delete()
pullRequest.replyToReviewComment(comment.id, 'Khaaannnn!')
// or
comment.createReply('Khaaannnn!')
for (commit in pullRequest.commits) {
echo "SHA: ${commit.sha}, Committer: ${commit.committer}, Commit Message: ${commit.message}"
}
for (comment in pullRequest.comments) {
echo "Author: ${comment.user}, Comment: ${comment.body}"
}
for (reviewComment in pullRequest.reviewComments) {
echo "File: ${reviewComment.path}, Position: ${reviewComment.position}, Author: ${reviewComment.user}, Comment: ${reviewComment.body}"
}
for (commit in pullRequest.commits) {
for (status in commit.statuses) {
echo "Commit: ${commit.sha}, State: ${status.state}, Context: ${status.context}, URL: ${status.targetUrl}"
}
}
for (commit in pullRequest.commits) {
commit.createStatus(status: 'pending')
}
for (status in pullRequest.statuses) {
echo "Commit: ${pullRequest.head}, State: ${status.state}, Context: ${status.context}, URL: ${status.targetUrl}"
}
for (requestedReviewer in pullRequest.requestedReviewers) {
echo "${requestedReviewer} was requested to review this Pull Request"
}
for (requestedTeamReviewer in pullRequest.requestedTeamReviewers) {
echo "${requestedTeamReviewer} was requested to review this Pull Request"
}
for (review in pullRequest.reviews) {
echo "${review.user} has a review in ${review.state} state for Pull Request. Review body: ${review.body}"
}
pullRequest.createReviewRequests(['Spock', 'McCoy'])
pullRequest.deleteReviewRequests(['McCoy'])
pullRequest.createTeamReviewRequests(['justice-league'])
pullRequest.deleteTeamReviewRequests(['justice-league'])
pullRequest.merge(pullRequest.title)
pullRequest.deleteBranch()