-
Notifications
You must be signed in to change notification settings - Fork 355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[JENKINS-43507] Migrate Bitbucket Plugin to Traits #53
Changes from all commits
43b5668
301f8d0
74b8531
b8f9be3
3530fca
dfd47ff
ee67e57
fd9e115
824dfd2
0e1e96a
88c4be3
933a75a
8f636d6
95aac94
50166af
a4c6bf3
71b6cae
0d083de
e0e18b2
b0c72b8
933aae0
255396d
4c80d63
e0875fa
9b71dd1
b5f8765
0874e73
23b61e5
c0cfdd7
e1b6547
103082f
107f747
ec0fc5b
6821f23
8b3b9eb
17541eb
3fa5f43
b9a3016
3c9bb0f
6f9a49f
7ecc02d
b307aaf
425d7bd
ee66530
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,158 +25,124 @@ | |
|
||
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi; | ||
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatus; | ||
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; | ||
|
||
import edu.umd.cs.findbugs.annotations.CheckForNull; | ||
import edu.umd.cs.findbugs.annotations.NonNull; | ||
import hudson.Extension; | ||
import hudson.FilePath; | ||
import hudson.model.Item; | ||
import hudson.model.ItemGroup; | ||
import hudson.model.Job; | ||
import hudson.model.Result; | ||
import hudson.model.Run; | ||
import hudson.model.TaskListener; | ||
import hudson.model.listeners.RunListener; | ||
import hudson.model.listeners.SCMListener; | ||
import hudson.plugins.git.Revision; | ||
import hudson.plugins.git.util.BuildData; | ||
import hudson.plugins.mercurial.MercurialTagAction; | ||
import hudson.plugins.mercurial.MercurialSCMSource; | ||
import hudson.scm.SCM; | ||
import hudson.scm.SCMRevisionState; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import jenkins.scm.api.SCMHead; | ||
import javax.annotation.CheckForNull; | ||
import jenkins.plugins.git.AbstractGitSCMSource; | ||
import jenkins.scm.api.SCMHeadObserver; | ||
import jenkins.scm.api.SCMRevision; | ||
import jenkins.scm.api.SCMRevisionAction; | ||
import jenkins.scm.api.SCMSource; | ||
import jenkins.scm.api.SCMSourceOwner; | ||
import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider; | ||
|
||
/** | ||
* This class encapsulates all Bitbucket notifications logic. | ||
* {@link JobCompletedListener} sends a notification to Bitbucket after a build finishes. | ||
* Only builds derived from a job that was created as part of a multi branch project will be processed by this listener. | ||
* | ||
* The way the notification is sent is defined by the implementation of {@link BitbucketNotifier} returned by {@link #getNotifier(BitbucketApi)}. | ||
* | ||
*/ | ||
public class BitbucketBuildStatusNotifications { | ||
|
||
private static void createBuildCommitStatus(@NonNull Run<?,?> build, @NonNull TaskListener listener, @NonNull BitbucketApi bitbucket) | ||
private static void createStatus(@NonNull Run<?, ?> build, @NonNull TaskListener listener, | ||
@NonNull BitbucketApi bitbucket, @NonNull String hash) | ||
throws IOException, InterruptedException { | ||
String revision = extractRevision(build); | ||
if (revision != null) { | ||
Result result = build.getResult(); | ||
String url; | ||
try { | ||
url = DisplayURLProvider.get().getRunURL(build); | ||
} catch (IllegalStateException e) { | ||
listener.getLogger().println("Can not determine Jenkins root URL. Commit status notifications are disabled until a root URL is configured in Jenkins global configuration."); | ||
listener.getLogger().println( | ||
"Can not determine Jenkins root URL. Commit status notifications are disabled until a root URL is" | ||
+ " configured in Jenkins global configuration."); | ||
return; | ||
} | ||
BitbucketBuildStatus status = null; | ||
String key = build.getParent().getFullName(); // use the job full name as the key for the status | ||
String name = build.getDisplayName(); // use the build number as the display name of the status | ||
BitbucketBuildStatus status; | ||
Result result = build.getResult(); | ||
if (Result.SUCCESS.equals(result)) { | ||
status = new BitbucketBuildStatus(revision, "This commit looks good", "SUCCESSFUL", url, build.getParent().getName(), build.getDisplayName()); | ||
status = new BitbucketBuildStatus(hash, "This commit looks good", "SUCCESSFUL", url, key, name); | ||
} else if (Result.UNSTABLE.equals(result)) { | ||
status = new BitbucketBuildStatus(revision, "This commit has test failures", "FAILED", url, build.getParent().getName(), build.getDisplayName()); | ||
status = new BitbucketBuildStatus(hash, "This commit has test failures", "FAILED", url, key, name); | ||
} else if (Result.FAILURE.equals(result)) { | ||
status = new BitbucketBuildStatus(revision, "There was a failure building this commit", "FAILED", url, build.getParent().getName(), build.getDisplayName()); | ||
status = new BitbucketBuildStatus(hash, "There was a failure building this commit", "FAILED", url, key, | ||
name); | ||
} else if (result != null) { // ABORTED etc. | ||
status = new BitbucketBuildStatus(revision, "Something is wrong with the build of this commit", "FAILED", url, build.getParent().getName(), build.getDisplayName()); | ||
status = new BitbucketBuildStatus(hash, "Something is wrong with the build of this commit", "FAILED", url, | ||
key, name); | ||
} else { | ||
status = new BitbucketBuildStatus(revision, "The tests have started...", "INPROGRESS", url, build.getParent().getName(), build.getDisplayName()); | ||
} | ||
if (status != null) { | ||
getNotifier(bitbucket).buildStatus(status); | ||
status = new BitbucketBuildStatus(hash, "The tests have started...", "INPROGRESS", url, key, name); | ||
} | ||
new BitbucketChangesetCommentNotifier(bitbucket).buildStatus(status); | ||
if (result != null) { | ||
listener.getLogger().println("[Bitbucket] Build result notified"); | ||
} | ||
} | ||
} | ||
|
||
@CheckForNull | ||
private static String extractRevision(Run<?, ?> build) { | ||
String revision = null; | ||
BuildData gitBuildData = build.getAction(BuildData.class); | ||
if (gitBuildData != null) { | ||
Revision lastBuiltRevision = gitBuildData.getLastBuiltRevision(); | ||
if (lastBuiltRevision != null) { | ||
revision = lastBuiltRevision.getSha1String(); | ||
} | ||
} else { | ||
MercurialTagAction action = build.getAction(MercurialTagAction.class); | ||
if (action != null) { | ||
revision = action.getId(); | ||
} | ||
} | ||
return revision; | ||
} | ||
|
||
private static void createPullRequestCommitStatus(Run<?,?> build, TaskListener listener, BitbucketApi bitbucket) | ||
private static void sendNotifications(Run<?, ?> build, TaskListener listener) | ||
throws IOException, InterruptedException { | ||
createBuildCommitStatus(build, listener, bitbucket); | ||
final SCMSource s = SCMSource.SourceByItem.findSource(build.getParent()); | ||
if (!(s instanceof BitbucketSCMSource)) { | ||
return; | ||
} | ||
|
||
private static BitbucketNotifier getNotifier(BitbucketApi bitbucket) { | ||
return new BitbucketChangesetCommentNotifier(bitbucket); | ||
BitbucketSCMSource source = (BitbucketSCMSource) s; | ||
if (new BitbucketSCMSourceContext(null, SCMHeadObserver.none()) | ||
.withTraits(source.getTraits()) | ||
.notificationsDisabled()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess I'll find a notification trait later in the review... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope. I do not see the value in there being a notification disabling trait in the core plugin. But if somebody wants to write such a trait in an extension plugin they need this to disable the notifications... given that there are a number of people seeking just that I thought it worth the minor YAGNI to add this |
||
return; | ||
} | ||
|
||
@CheckForNull | ||
private static BitbucketSCMSource lookUpSCMSource(Run<?, ?> build) { | ||
ItemGroup<?> multiBranchProject = build.getParent().getParent(); | ||
if (multiBranchProject instanceof SCMSourceOwner) { | ||
SCMSourceOwner scmSourceOwner = (SCMSourceOwner) multiBranchProject; | ||
BitbucketSCMSource source = lookUpBitbucketSCMSource(scmSourceOwner); | ||
if (source != null) { | ||
return source; | ||
SCMRevision r = SCMRevisionAction.getRevision(build); // TODO JENKINS-44648 getRevision(s, build) | ||
String hash = getHash(r); | ||
if (hash == null) { | ||
return; | ||
} | ||
if (r instanceof PullRequestSCMRevision) { | ||
listener.getLogger().println("[Bitbucket] Notifying pull request build result"); | ||
createStatus(build, listener, source.buildBitbucketClient((PullRequestSCMHead) r.getHead()), hash); | ||
|
||
} else { | ||
listener.getLogger().println("[Bitbucket] Notifying commit build result"); | ||
createStatus(build, listener, source.buildBitbucketClient(), hash); | ||
} | ||
return null; | ||
} | ||
|
||
|
||
|
||
/** | ||
* It is possible having more than one SCMSource in our MultiBranch project. | ||
* TODO: Does it make sense having more than one of the same type? | ||
* | ||
* @param scmSourceOwner An {@link Item} that owns {@link SCMSource} instances. | ||
* @return A source or null | ||
*/ | ||
@CheckForNull | ||
private static BitbucketSCMSource lookUpBitbucketSCMSource(final SCMSourceOwner scmSourceOwner) { | ||
for (SCMSource scmSource : scmSourceOwner.getSCMSources()) { | ||
if (scmSource instanceof BitbucketSCMSource) { | ||
return (BitbucketSCMSource) scmSource; | ||
private static String getHash(@CheckForNull SCMRevision revision) { | ||
if (revision instanceof PullRequestSCMRevision) { | ||
// unwrap | ||
revision = ((PullRequestSCMRevision) revision).getPull(); | ||
} | ||
if (revision instanceof MercurialSCMSource.MercurialRevision) { | ||
return ((MercurialSCMSource.MercurialRevision) revision).getHash(); | ||
} else if (revision instanceof AbstractGitSCMSource.SCMRevisionImpl) { | ||
return ((AbstractGitSCMSource.SCMRevisionImpl) revision).getHash(); | ||
} | ||
return null; | ||
} | ||
|
||
private static void sendNotifications(Run<?, ?> build, TaskListener listener) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Diff on this class in horrible, difficult to follow changes, but I think it's ok. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Part of the issue here is that it is actually also fixing bugs that I found during the refactoring... and then after I found them I noticed that others had raise JIRA issues for these bugs... and in some cases also PRs... e.g. #52 was a parallel fix to the fix I had developed here |
||
throws IOException, InterruptedException { | ||
BitbucketSCMSource source = lookUpSCMSource(build); | ||
if (source != null && extractRevision(build) != null) { | ||
SCMHead head = SCMHead.HeadByItem.findHead(build.getParent()); | ||
if (head instanceof PullRequestSCMHead) { | ||
listener.getLogger().println("[Bitbucket] Notifying pull request build result"); | ||
createPullRequestCommitStatus(build, listener, source.buildBitbucketClient((PullRequestSCMHead) head)); | ||
} else { | ||
listener.getLogger().println("[Bitbucket] Notifying commit build result"); | ||
createBuildCommitStatus(build, listener, source.buildBitbucketClient()); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Sends notifications to Bitbucket on Checkout (for the "In Progress" Status). | ||
*/ | ||
@Extension | ||
public static class JobCheckOutListener extends SCMListener { | ||
|
||
@Override | ||
public void onCheckout(Run<?, ?> build, SCM scm, FilePath workspace, TaskListener listener, File changelogFile, SCMRevisionState pollingBaseline) throws Exception { | ||
public void onCheckout(Run<?, ?> build, SCM scm, FilePath workspace, TaskListener listener, File changelogFile, | ||
SCMRevisionState pollingBaseline) throws Exception { | ||
try { | ||
sendNotifications(build, listener); | ||
} catch (IOException | InterruptedException e) { | ||
e.printStackTrace(listener.error("Could not send notifications")); | ||
} | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be 2.2.0-SNAPSHOT to match what is in master?