phabricator-jenkins-plugin
phabricator-jenkins-plugin copied to clipboard
Jenkinsfile support
Hiya,
We've been using the plugin and it's been a godsend.
We've also migrated to using Jenkinsfile to enable tests-as-code tooling. Is it possible to call this plugin from Jenkinsfile (or pipeline groovy scripts) ?
Basically, we want to enable uberalls and running coverage report on all commits on master branch.
See #156 for previous discussion.
We'd love to support Jenkinsfile! I have never used it and don't have the bandwidth to work on it. If there's anybody in the community with experience/advice, we'd love a PR :)
Thanks
I'll have a crack at this. I'm not a java programmer, but I really need this feature.
How did your crack at adding pipeline / jenkinsfile support go @ananthb ? I would like to add notifications in a similar situation to how #108 describes them to a multi-branch pipeline job. If you had some code even partly working that would be a big help.
I'm looking for this feature as well. We are finally moving to use phabricator for more of our code and pipeline for more of the builds. Would be a shame not be able to combine the two ;)
I noticed that there's a stalled PR for this: https://github.com/uber/phabricator-jenkins-plugin/pull/185. I echo @anitakrueger that this would be really great to have, at least as a start. I'm trying to get to having differentials go through the pipeline but I guess we're still a long way from that.
As workaround: Phabricator -> freestyle build with this plugin -> Pipeline build also you can archive *.xml with test resuts and copy them back to freestyle build and publish. In that case plugin will pass them to phabricator
It will be very grateful to support Jenkinsfile
, we have similar use case to integrate Jenkins with Phabricator.
@fallenworld1 I am interested in that workaround, and have been trying to implement it − how do you make the Pipeline build aware of the Differential changes?
In my attempted setup for my repo dummy-app
, Harbormaster triggers a job differential-trigger
, which makes a Conduit lookup to retrieve the repo name, and pass it all to differential-apply
, which 1/ clones the dummy-app
Git repository, 2/ applies the Differential revision (using the uber plugin); 3/ archives the result (using the “Clone Workspace SCM” plugin) ; 4/ trigger a build of the Pipeline-style job dummy-app
. That job which would "clone" the repo from the previous job, detect the Jenkinsfile there and run the pipeline. However the “Clone Workspace SCM” appears incompatible with Pipeline :-/
Would you be able to detail a bit more your solution ? :)
@JeanFred I am using next setup:
- Phabricator triggers
manager-job
with parameters passingdiffid
-
manager-job
triggers pipeline with same parameters - pipeline performs git and conduit calls
Pipeline code is separated: boilerplate is in job configuration, common parts are in global library.
But if I understand correctly, you want to use 'pipeline as code' idiom, so can't use pipeline before full checkout) may be 'staging area' config in phabricator could help.
My colleague and I have been working more on this problem in the last couple of days. I thought I would share our recipe in more detail. We now have Harbormaster triggering Jenkins declarative pipelines, and the notifier replying back to Phabricator with build status.
We took the PR @balihb produced-- https://github.com/uber/phabricator-jenkins-plugin/pull/185 -- and rebased it on top of the lastest version of this plugin (it applies cleanly). We built the plugin using gradle assemble
and then loaded the resultant .hpi
file into jenkins.
Even with @balihb's patch, the plugin still lacks "build wrapper" support-- the part which applies the arc patch
to the checked out source. So we have configured Phabricator to use a staging repo (see https://secure.phabricator.com/book/phabricator/article/harbormaster/). We have found the staging repo approach to be superior anyway, since it allows reviews between arbitrary changes.
Then we have harbormaster trigger the build via HTTP, and have configured git in our jenkins job as follows:
It's very important not to miss the hint from @balihb in his sample jenkinsfile. You need to do something like this:
post {
always {
script {
if (currentBuild.result == null) {
currentBuild.result = currentBuild.currentResult
}
step([$class: 'PhabricatorNotifier', commentOnSuccess: true, commentWithConsoleLinkOnFailure: true])
}
}
}
I missed this assignment the first time, and the plugin was crashing. Setting currentBuild.result
(which is basically a global) is critical because the plugin references it, and, despite being in the 'post' step in the pipeline, it seems to be null most of the time, whereas currentResult is documented never to be null. More on this in the Jenkins Global Variable Reference inside of your Jenkins instance (for example: http://localhost:8080/pipeline-syntax/globals#currentBuild).
Not tested:
- We didn't test anything with coverage, or unit test results.
Things that need more work:
- Not having the build wrapper means that phabricator won't get an initial reply from jenkins saying "here is my URI". Presumably the right incantation to
arc conduit
would solve this in the short-term.
Anyway, I hope this helps the next person.
#185 has been accepted. This is probably good to close once it is made available through a release.
@danielbprice Really helpful - thank you! One quick question did you still need to configure the pipeline job in Jenkins to accept build parameters? I have had to but I was hoping that I could avoid this if at all possible as they are also in the Jenkinsfile
Even with @balihb's patch, the plugin still lacks "build wrapper" support-- the part which applies the arc patch to the checked out source. So we have configured Phabricator to use a staging repo (see https://secure.phabricator.com/book/phabricator/article/harbormaster/). We have found the staging repo approach to be superior anyway, since it allows reviews between arbitrary changes.
Maybe I'm over simplifying things but wouldn't it be relatively easy to implement an "arc patch" step (the logic is already in the PhabricatorBuildWrapper
class)?
You could then use that step after GIT SCM, something like:
stage('Checkout source') {
steps {
git([url: 'ssh://[email protected]/source/test-repository.git', branch: 'master', credentialsId: 'foo-bar'])
}
}
stage('Apply diff') {
steps {
step([$class: 'PhabricatorArcPatch'])
}
}
Is there an advantage to use it as a build wrapper?
@Sykomaniac our pipeline jobs take DIFF_ID and PHID as input parameters which we pass from Harbormaster. Possibly you are referring to something else? So basically in harbormaster we hit jenkins as follows:
And our jenkins jobs are then called differential-<repoCallsign>. This lets us have a single Harbormaster rule for Differential for a variety of repositories. I'm not sure if I really answered your question, though.
@siepkes Yes, I expect that it could work, however I have not tested it. I have found the staging repo approach to be superior, as it supports a wider range of development patterns, and it is a built-in feature of core Phabricator. If I recall correctly, the other useful function of the build wrapper is that it notifies Phabricator that it is starting the job-- if there was a recipe to get that working, I would be glad to have it. I believe that it's possible by piping some json into arc conduit
but I haven't had time to work out an exact recipe.
We have continued to use the above recipe for several months without modification, and it has been great.
@danielbprice yes you did that's fine that is similar to what I had configured :)
I want to recommend this: https://github.com/balihb/drydock-docker-jenkins whith this the trigger token plugin is not needed anymore.
On Tue, Aug 14, 2018, 19:21 Ash Sykes [email protected] wrote:
@danielbprice https://github.com/danielbprice yes you did that's fine that is similar to what I had configured :)
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/uber/phabricator-jenkins-plugin/issues/198#issuecomment-412949574, or mute the thread https://github.com/notifications/unsubscribe-auth/ACvMmGDgHQLjZfJPbAASyFWRSKKgYxp2ks5uQwczgaJpZM4LoGK3 .
@danielbprice While I think the automatic staging repo's (which aren't implemented in Phabricator yet) would be great I don't really like the manual ones we have to use. For example they require manual maintenance like deleting and recreating the repo since it will be ever growing. Also everyone needs to have access to this staging repo since arc
just pushes the entire repo + diff it to this second repo.
While I appreciate it's a working solution for you (can't argue with that, though I'm still going to ;-) and it might be a "built-in feature" of Phabricator it's basically a built-in hack. Even the Phabricator guys themselves are not happy with it:
This is a bit leaky and I intend for it to eventually be positioned as a less-preferred solution, but from the perspective of build systems it's the same as the real (virtual ref) solution that I want to build.
We see how bad this is (does pushing tags to the same repository create a big user-facing mess?) and then go from there.
Ideally we would get the automatic staging area's. Basically just ref's in GIT for diff like GitHub has for PR's (T8092). Though I don't think these will be implemented anytime soon.
I'm going to take a stab at either fixing the BuildWrapper or maybe implement an arc patch apply step.
@siepkes any luck fixing the BuildWrapper? I've seen some snippets where people seem to be using it, but I'm not able on my jenkins instance.
https://github.com/SW7JKMMTT/SW7-Rapport/blob/6aa7a99f3e96b6b85b9bb8a2b11d6d908850637d/Jenkinsfile-phabricator#L22
@uSpike In the end I got side tracked with other work and wasn't able to finish it. At the moment I use the Phabricator staging area (so one gigantic GIT repo where arc pushes all diffs to). This is still somewhere on my wishlist.
I also toyed with the idea of instead of calling (and requiring) arc
it might be easier to just request the diff from Phabricator and apply it to the right base commit.
stage("apply-patch") {
steps {
container("mxnetone") {
sh """
env
whoami
pwd
echo -e "## Getting base rev for diff"
BASE_GIT_COMMIT=\$( echo "{\\"diff_id\\": \${DIFF_ID}}" | /phacility/arcanis/bin/arc call-conduit differential.getdiff --conduit-token \"cli-uvlhcptydhpaaaaaaa\" --conduit-uri \"http://ph.yoursite/\" | awk -v RS=',' -v FS=':' '\$1~/\\"sourceControlBaseRevision\\"/ {print \$2}' | tr -d \\")
echo \${BASE_GIT_COMMIT}
echo -e "## Reset to base commit"
git reset --hard \${BASE_GIT_COMMIT}
echo -e "## Cleaning out repo"
git clean -fdx
echo -e "## Apply diff"
/phacility/arcanis/bin/arc patch --nobranch --no-ansi --diff \${DIFF_ID} --nocommit --conduit-token \"cli-uvlhcptydhpaaaaaaa\" --conduit-uri \"http://ph.yoursite/\"
"""
}
}
}
I ran into this issue today, realizing that the BuildWrapper is also the reason we cannot use the ABORT_ON_REVISION_ID
feature.
I reimplemented that portion as a Groovy function. In case it's helpful to anyone else:
package com.company.jenkins
import com.uber.jenkins.phabricator.PhabricatorCauseOfInterruption
class Utils implements Serializable {
/**
* Aborts all running builds with the given ABORT_ON_REVISION_ID value.
* This replicates the behavior of the phabricator-jenkins-plugin.
*
* @param revisionId current build's ABORT_ON_REVISION_ID value
*/
@NonCPS
def abortBuildsWithRevisionId(String revisionId) {
// Iterate over all of the other running builds for this project
script.currentBuild.rawBuild.parent.builds.each { build ->
if (build.isBuilding() && build.number != script.currentBuild.number) {
// Find the "ABORT_ON_REVISION_ID" value of the previous running build
// .parameters gives us an ArrayList of length 1 containing an ArrayList of ParameterValue items
def buildParams = build.getActions(hudson.model.ParametersAction)?.parameters?.find()
def prevBuildRevisionId = buildParams?.find { it.name == 'ABORT_ON_REVISION_ID' }?.value
if (prevBuildRevisionId && prevBuildRevisionId == revisionId) {
script.println "Aborting previous build #${build.number}"
// Use the special plugin-defined CauseOfInterruption, which skips the notification step.
def causeOfInterruption = new PhabricatorCauseOfInterruption(script.currentBuild.absoluteUrl)
build.executor.interrupt(Result.ABORTED, causeOfInterruption)
}
}
}
}
}
I should stress that communicating with Phabricator from pipeline (Jenkinsfile) itself might be a bad idea if you want to report build status in Differential (and you probably do). Reasons:
- you might want to call
harbormaster.createartifact
to provide initial link to Jenkins build as soon as build starts. This plugin currently has no means to do this in Pipeline, AFAIK (we currently resort to callingarc call-conduit
manually from shell, @timbrown5's comment in #330 provides means to do this from Groovy itself) - pipeline may fail because of various reasons, so you need to use constructions like
try {
... // my whole pipeline
} catch (exc) {
currentBuild.result = 'ERROR' // plugin won't detect that the build failed without it
} finally {
step([$class: 'PhabricatorNotifier', commentOnSuccess: false, commentWithConsoleLinkOnFailure: false])
}
- pipeline may still fail after the
PhabricatorNotifier
is called, leading to a green indicator in Phabricator and red one in Jenkins - pipeline may fail because of syntax error in Groovy, in which case pipeline doesn't even have a chance to call
harbormaster.createartifact
orharbormaster.sendmessage
.
With all these bumps I've encountered while trying to integrate Phabricator with Pipeline jobs, I believe the right way should be in style of Freestyle jobs: there should be a checkbox "notify Phabricator" somewhere in Build environment
, that should provide a BUILD_URL
to .createartifact
as soon as the build starts and send build status to .sendmessage
regardless of what caused the failure: error
call, exception thrown, syntax error, etc.
Hi @Artalus, I agree that the best solution would be for the plugin to natively support pipelines, so we don't have to handle reporting to Phabricator in each of our pipelines. That said I'm not sure how actively this plugin is being maintained, and in the meantime, I think we will have to make do.
I agree with your point about the try/catches
- my scripted pipeline is awash with them. That said I think declarative pipelines could simplify working with the Phabricator groovy functions, e.g:
@Library('my-phabricator-lib')
def common = new org.acme.pipeline.CommonFunctions()
pipeline {
agent none
stages {
// Run this stage on any agent to it runs as soon as possible.
stage ("Report to Phabricator") {
agent any
steps {
script {
try {
common.report_build_start_to_phabricator(
params.DIFF_IDS,
params.DIFF_REPOS,
params.PHID
)
} catch (err) {
echo "Failed to repo the builds status to Phabricator (is this a rebuild)?" + err.toString()
}
}
}
}
stage ("Start job") {
agent any
steps {
sh "echo Hello World!"
}
}
}
post {
always {
node('master') {
script {
common.report_build_end_to_phabricator(
params.DIFF_IDS,
params.DIFF_REPOS,
params.PHID
)
}
}
}
}
}
In the above, I have the report_build_start_to_phabricator
and report_build_end_to_phabricator
pulled out into a separate Groovy module which gets pulled in by Pipeline Shared Groovy Libraries Plugin.
I do wonder if, in lieu of having Pipeline support in the plugin, we should try getting a groovy module together on github with useful Phabricator pipeline functions (like @clintharrison's function above).
I've read this thread several times but can't work out whether anyone has got Phabricator diffs to build using a Jenkinsfile.
Here's what I've got.
- a Jenkinsfile with parameters
PHID
andDIFF_ID
which correctly creates a job under a multibranch pipeline with those same parameters. - a "post-always" step in my Jenkinsfile with
step([$class: 'PhabricatorNotifier', commentOnSuccess: true, commentWithConsoleLinkOnFailure: true])
to notify Phabricator of build status - a Harbormaster build plan which calls
http://myjenkins/job/MyMultibranchBuild/job/MyBranch/buildWithParameters?job=differential-${repository.callsign}&DIFF_ID=${buildable.diff}&PHID=${target.phid}&ABORT_ON_REVISION_ID=${buildable.revision}
- a Herald rule which calls the build plan when a differential revision is created
- my repository set up to use a staging repo for diffs
When I run arc diff
on my workstation, the build runs on Jenkins, the diff in Phabricator gets feedback that it's been successful, and the build in Jenkins gets a link to the diff. So far, so good.
The problem is that my build is running the base commit without applying the diff. I'll try again without using the staging area but would appreciate any feedback on:
- whether this is known to work with the latest available version of the plugin (2.1.2)
- whether I can run the parameterised build without specifying a branch in the Jenkins API URL. I've tried but get a 404 (this might be a "feature" of multibranch pipelines).
I've seen comments here about using the staging area and I believe I could create a separate Jenkins build that would detect new refs in the staging repository by polling every few minutes but then I don't see how to report back to Phabricator since I wouldn't have any parameters.
@carlfischerjba trigger the job for a staging area tag from Phabricator
@ogonkov Thanks, so I need a separate job for commit and differential builds since they will be looking at different repositories. I thought the plugin would figure out where to get the code from by querying arcanist but I guess not.
I've set up a job identically to the one in the screenshot above by @danielbprice but I get errors during checkout.
> git fetch --tags --force --progress https://phabricator/diffusion/MYSTAG/my-staging.git +refs/heads/*:refs/remotes/origin/* --depth=1
ERROR: Error fetching remote repo 'origin'
hudson.plugins.git.GitException: Failed to fetch from https://phabricator/diffusion/MYSTAG/my-staging.git
at hudson.plugins.git.GitSCM.fetchFrom(GitSCM.java:894)
at hudson.plugins.git.GitSCM.retrieveChanges(GitSCM.java:1161)
at hudson.plugins.git.GitSCM.checkout(GitSCM.java:1192)
at org.jenkinsci.plugins.workflow.steps.scm.SCMStep.checkout(SCMStep.java:120)
at org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition.create(CpsScmFlowDefinition.java:144)
at org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition.create(CpsScmFlowDefinition.java:67)
at org.jenkinsci.plugins.workflow.job.WorkflowRun.run(WorkflowRun.java:293)
at hudson.model.ResourceController.execute(ResourceController.java:97)
at hudson.model.Executor.run(Executor.java:429)
Caused by: hudson.plugins.git.GitException: Command "git fetch --tags --force --progress https://phabricator/diffusion/MYSTAG/my-staging.git +refs/heads/*:refs/remotes/origin/* --depth=1" returned status code 128:
stdout:
stderr: error: RPC failed; HTTP 500 curl 22 The requested URL returned error: 500
fatal: the remote end hung up unexpectedly
The job is parameterised with the PHID
and DIFF_ID
but doesn't seem to reach the stage where they're used.
If I uncheck the fetch tags
box then I get a different error because it can't find the tag corresponding to the diff. This makes sense but I don't know why fetching tags would be a problem. I've been able to clone the repository and fetch tags from a shell but Jenkins must be doing something different.
> git fetch --no-tags --force --progress https://phabricator/diffusion/MYSTAG/my-staging.git +refs/heads/*:refs/remotes/origin/*
> git rev-parse refs/tags/phabricator/diff/10217^{commit} # timeout=10
> git rev-parse refs/remotes/origin/refs/tags/phabricator/diff/10217^{commit} # timeout=10
> git rev-parse refs/tags/phabricator/diff/10217^{commit} # timeout=10
ERROR: Couldn't find any revision to build. Verify the repository and branch configuration for this job.
The 500 response is logged in the Apache error logs but no indication of what caused it.
Here's the full build configuration in case it's useful to anyone for their own setup (or pointing out problems in mine).
@carlfischerjba Did you get the problem solved yet? Thanks!
@pasikarkkainen I'm afraid not quite. One of my colleagues got a build working from a staging repo and didn't encounter the "remote end hung up" error, so that must have been specific to my repository. But his build doesn't report back to Phabricator.
I've proposed we change the trigger URL in Harbormaster to something like
http://myjenkins/job/MYBUILD/view/tags/job/${repository.staging.ref}/build?delay=0sec&PHID=${target.phid}&DIFF_ID=${buildable.diff}
but haven't tried it out yet.
@carlfischerjba
For what it's worth, here is a simple example of a declaratve pipeline using the plugin ~~(although I send a URL to Phabricator by adding an artifact using the httpRequest
- this can be omitted~~ [EDIT: Sorry I removed this to make the example cleaner but didn't update the comment].
def PHABRICATOR_URL="https://phabricator.acme.com"
//This requires:
// - A credential containing a valid Arcanist token of type 'Secret Text' and ID 'phab_arc_token'
properties([[$class: 'RebuildSettings', autoRebuild: false, rebuildDisabled: false],
parameters([
string(defaultValue: '', description: 'Phabricator Diff ID [Dxxxx].', name: 'DIFF_ID', trim: false),
string(defaultValue: '', description: 'Phabricator Build Target PHID [PHID-HMBT-xxxx...].', name: 'PHID', trim: false)
])
])
pipeline {
agent {label "python3_agent"}
stages {
stage ("Run Unit tests") {
steps {
dir('src'){
git(
url: 'https://github.com/pluralsight/intro-to-pytest.git',
branch: "master"
)
sh """
python3 -m venv tutorial-env
source ./tutorial-env/bin/activate
python3 -m pip install -r requirements.txt
python3 -m pip install pytest-cov
python3 -m pytest --junitxml=junit-python3.xml --cov-branch --cov-report term-missing --cov=./ --cov-report xml:coverage-python3.xml
deactivate
"""
}
cobertura autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: '**/coverage*.xml', conditionalCoverageTargets: '70, 0, 0', failUnhealthy: false, failUnstable: false, lineCoverageTargets: '80, 0, 0', maxNumberOfBuilds: 0, methodCoverageTargets: '80, 0, 0', onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false
}
}
}
post {
always {
script {
step([$class: 'PhabricatorNotifier', commentFile: '.phabricator-comment', commentOnSuccess: false,
commentSize: '1000', commentWithConsoleLinkOnFailure: false, coverageCheck: false,
coverageReportPattern: '**/coverage*.xml, **/cobertura*.xml, **/jacoco*.xml',
coverageThreshold: 0.0, customComment: false, lintFile: '.phabricator-lint', lintFileSize: '100000',
minCoverageThreshold: 100.0, preserveFormatting: false, processLint: false, uberallsEnabled: false])
}
}
}
}
It could use some cleaning up, some better error handling and probably isn't the best way to do it but hopefully, it will give you some ideas.I find that this does most of what I want/need. The one issue is it doesn't seem to find the line coverage. I get:
[phabricator:uberalls] Using coverage metrics from Cobertura Jenkins Plugin
[phabricator:process-build-result] No unit results available.
[phabricator:process-build-result] No line coverage available to post to Harbormaster.
For completeness my Harbormaster step which calls this is an HTTP Request http://my.jenkins.server:8080/buildByToken/buildWithParameters?job=phab_simple_pytest&DIFF_ID=${buildable.diff}&PHID=${target.phid}&token=my_secret_token
.
Note: I use the 'Build Authorization Token Root Plugin' to try and secure my web-hook a little more, but I don't think that is a requirement.
So I have just managed to get the Coverage being sent to Phabricator. In doing this I found two things:
- The Plugin seems to detect when a differential repo is used so it can enable certain functionality. This makes sense but wasn't obvious to me before.
- (This one is a mistake on my part, but might help someone) The parameter passed to the Jenkins job needs to be
DIFF_ID=${buildable.diff}
- I was passingDIFF_ID=${buildable.revision}
which started throwing exceptions.
Adding a new diff, as I think the diff above is could still be a useful starting point (as it uses a freely available Github repo).
properties([[$class: 'RebuildSettings', autoRebuild: false, rebuildDisabled: false],
parameters([
string(defaultValue: '', description: 'Phabricator Diff ID [Dxxxx].', name: 'DIFF_ID', trim: false),
string(defaultValue: '', description: 'Phabricator Build Target PHID [PHID-HMBT-xxxx...].', name: 'PHID', trim: false)
])
])
pipeline {
agent {label "python3_agent"}
stages {
stage ("Run Unit tests") {
steps {
sh "rm -rf src"
dir('src'){
// use checkout and not git so we can add additional submodule behaviours.
checkout([
$class: 'GitSCM',
branches: [[name: '*/master']],
doGenerateSubmoduleConfigurations: false,
extensions: [[
$class: 'SubmoduleOption',
disableSubmodules: false,
parentCredentials: true,
recursiveSubmodules: true,
reference: '',
trackingSubmodules: false
]],
submoduleCfg: [],
userRemoteConfigs: [[
credentialsId: 'my_credential',
url: 'ssh://[email protected]/diffusion/MYPYTHONREPO/my_python_repo.git'
]]
])
sh """
arc patch --diff ${DIFF_ID}
python3 -m venv tutorial-env
source ./tutorial-env/bin/activate
python3 -m pip install -r requirements.txt
python3 -m pip install -r test-requirements.txt
python3 -m pytest --junitxml=junit-python3.xml --cov-branch --cov-report term-missing --cov=./ --cov-report xml:coverage.xml
deactivate
"""
}
junit '**/*junit*.xml'
cobertura coberturaReportFile: '**/coverage.xml'
}
}
}
post {
always {
script {
step([$class: 'PhabricatorNotifier', commentOnSuccess: false, commentWithConsoleLinkOnFailure: true])
}
}
}
}