Skip to content
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

Adding configurable webhook endpoint for exposing jenkins commands with a Slack outgoing-webhook #160

Merged
merged 1 commit into from
Mar 7, 2016

Conversation

dpires
Copy link
Member

@dpires dpires commented Nov 23, 2015

I've added an endpoint extension (/webhook/) to allow Slack outgoing-webhook functionality.

There is a new Slack Webhook Settings configuration option to add an outgoing-webhook token.

I have placed everything in a new jenkins.plugins.slack.webhook package.

@samrocketman
Copy link
Member

Thanks for this contribution. I'm going to open this up for code review from others.

@jenkinsadmin
Copy link
Member

Thank you for this pull request! Please check this document for how the Jenkins project handles pull requests.

@@ -55,6 +60,11 @@
<version>20131018</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this dependency can be scoped to test.

@OwensCode
Copy link
Contributor

My experience is that the build time increases from about 20-30 seconds to 1:20-1:30 seconds with these changes, and most of it is spent running tests. It's not a crucial issue, but it is likely to become a frustration for developers, particularly if you like following a TDD approach. Is there any specific reason for the extra time, and can it be reduced (without reducing test coverage)? I thought it was possibly related to having to start up a Jenkins instance, but I also have that happening in another branch I'm working on, and it builds in around 30 seconds.

@dpires
Copy link
Member Author

dpires commented Jan 6, 2016

I can see the test time being an issue. The reasoning is that the i'm waiting for projects to be created and builds to finish with a sleep.

I'll look into using a Future callback for those tests, although the outcome is probably going to be the same time.

@OwensCode
Copy link
Contributor

I'm not that familiar with the outgoing webhooks integration. I assume that these URLs on the Jenkins server would need to be accessible from the slack servers for this to work, right? Just trying to get my head around the general idea and how it would be used.

@dpires
Copy link
Member Author

dpires commented Jan 6, 2016

Yes, Slack sends a POST to an endpoint which is why this /webhook endpoint is unprotected as Slack controls the body of the request so you can't add additional authorization parameters etc.

I have usage description in my original plugin before I created this PR:

https://github.com/dpires/jenkins-slack-webhook-plugin

globalConfig = GlobalConfiguration.all().get(GlobalConfig.class);
}

@Override
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pom file specifies that the compiler should target JDK 1.5, which actually means that these @OVERRIDES shouldn't be allowed because they're implementations of an interface rather than overrides of a parent class, which was only allowed from 1.6 on. I definitely prefer to have the annotation, and it works for building because our more modern compilers aren't strictly enforcing it, but my IDE doesn't like them.

However, why are we stuck using 1.5? I followed the path of where it comes from:

org.jenkins-ci.plugins.slack pom.xml
    --> org.jenkins-ci.plugins.plugin 1.567
        -> org.jenkins-ci.jenkins 1.33

org.jenkins-ci.jenkins 1.33 was last updated in 2013, so maybe we can move to the latest org.jenkins-ci.plugins.plugin. Even that will only give us 1.6, which isn't a supported version of Java anymore.

Not sure this has anything to do with this review, but @samrocketman do you know if there are any plans to move Jenkins in general to a newer version of Java? Otherwise, maybe the next thing I can work on is updating these versions to the latest.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Jenkins project seems to want Java 1.7 and later. ref https://jenkins-ci.org/blog/2015/04/06/good-bye-java6/

Here's a link to another plugin I support updating the compatibility.

jenkinsci/github-oauth-plugin#42

@OwensCode
Copy link
Contributor

Either an alternative way to implement this, or an idea for a future feature: send messages over Amazon SQS instead of posting over http directly to the Jenkins server.

https://slack.com/apps/A0F827G56-amazon-sqs
http://aws.amazon.com/sqs/
https://wiki.jenkins-ci.org/display/JENKINS/GitHub+SQS+Plugin

I know my company would be more interested in this route.

@dpires
Copy link
Member Author

dpires commented Jan 8, 2016

You can also use a Slack bot like Hubot or Lita to implement something similar to this PR, however this is the simplest solution as it requires zero dependencies/integrations.

@dpires
Copy link
Member Author

dpires commented Jan 8, 2016

I've modified the endpoint to be configurable. Without setting the endpoint Jenkins will return a standard 404 like any other unknown endpoints.

@dpires dpires changed the title Adding webhook/ endpoint for exposing jenkins commands with a Slack outgoing-webhook Adding configurable webhook endpoint for exposing jenkins commands with a Slack outgoing-webhook Jan 8, 2016

@Override
public String getUrlName() {
return "/"+globalConfig.getSlackOutgoingWebhookURL();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better. Thanks for the changes. Looking at jenkins.model.Jenkins.getDynamic(String) which calls this, it would be better to return null when the url from the configuration is null/empty.

public Object getDynamic(String token) {
    for (Action a : getActions()) {
        String url = a.getUrlName();
        if (url==null)  continue;
        if (url.equals(token) || url.equals('/' + token))
            return a;
    }
    // etc.
    return null;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good find. Done.

@@ -0,0 +1,5 @@
<div>
This is the webhook endpoint to send requests to.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to point out here that leaving it blank will disable the functionality, and that the endpoint needs to be publicly accessible from the Slack servers. I think that will alleviate some concerns, and clarify why it isn't working when someone hasn't opened their firewall etc.

triggerWord+" list projects", "Return a list of buildable projects",
this,
"listProjects")
.addRoute("^"+triggerWord+" run ([a-zA-Z0-9_\\-\\.]+)",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commands work well, but do not support projects that have spaces in the name. Not sure if there are other characters that Jenkins allows that are not accepted here.

@dpires
Copy link
Member Author

dpires commented Jan 12, 2016

I've updated the command regex to support all valid/safe characters (and charsets) for a project name and updated the tests.

@dpires
Copy link
Member Author

dpires commented Jan 18, 2016

Can this be added as a merge candidate?

@OwensCode
Copy link
Contributor

I've been through most of the code in this pull request and @dpires has fixed everything I've commented on so far. Will try to get through the rest of it asap, but it's looking good so far. Not sure what it takes to make it a merge candidate.

@Extension
public class WebhookEndpoint implements UnprotectedRootAction {

private String slackUser;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed that it looks like WebhookEndpoint is a singleton; there is only ever 1 instantiated per Jenkins instance. If I'm correct, you have a race condition on the slackUser member variable. I think you'll need to pass it around in method calls instead of using a member variable. Maybe you could encapsulate all the required information in an object.

The globalConfig seems fine. I was concerned it would not dynamically pickup configuration changes, but it does.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good find, that is a race condition.

return message;
}

private Method getHandlerMethod(Object handlerInstance,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is not being used after the class was refactored.

@OwensCode
Copy link
Contributor

This is getting really close to getting a thumbs up from me. There are a few unaddressed comments (none of which are critical) and I'd like to see if I can get the test to run a little faster, but otherwise it's looking good.

@dpires
Copy link
Member Author

dpires commented Jan 27, 2016

I've added a condensed project list when projects are > 10. Still has all information, just without 3 line breaks.

I've tried it with 30+ projects and it's not that bad, still readable.

@dpires dpires force-pushed the master branch 2 times, most recently from a4934d8 to abacc96 Compare January 28, 2016 17:22
@OwensCode
Copy link
Contributor

I think @dpires has addressed all the concerns I raised. Thanks! @samrocketman this pull request looks good to me. The only thing that would be nice to improve is the run time for the unit tests, but it's not terrible and I'm not sure whether it is actually possible to improve it substantially or not. Let me know if I can do anything more to help get this merged.

@kmadel kmadel added this to the slack-2.0 milestone Mar 7, 2016
kmadel added a commit that referenced this pull request Mar 7, 2016
Adding configurable webhook endpoint for exposing jenkins commands with a Slack outgoing-webhook
@kmadel kmadel merged commit 734d4bc into jenkinsci:master Mar 7, 2016
@OwensCode
Copy link
Contributor

Thanks @kmadel!

@kmadel
Copy link
Contributor

kmadel commented Mar 16, 2016

@dpires There appears to be certain Jenkins configurations where your changes cause a NPE:

Caused by: java.lang.NullPointerException
    at jenkins.plugins.slack.webhook.WebhookEndpoint.getUrlName(WebhookEndpoint.java:50)
    at jenkins.model.Jenkins.getDynamic(Jenkins.java:2892)
    ... 83 more

May be related to #189
Would you be able to update with a default value - say slack-webhook - that may be overridden from the config already there?

@yoanisgil
Copy link

@dpires @kmadel those of us who upgraded to version 2.0 are experiencing a NPE. See here for details:

https://issues.jenkins-ci.org/browse/JENKINS-33556

the solution is either to downgrade to version 1.8.1 or set a for the Outgoing Webhook URL Endpoint setting. We should probably track this on a different issue. Let me know if you want me to create one.

public String getUrlName() {
String url = globalConfig.getSlackOutgoingWebhookURL();
if (url == null || url.equals(""))
return null;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it needs to return a default value other wise it will cause a NPE here:

https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/jenkins/model/Jenkins.java#L4295

Issue reported here: https://issues.jenkins-ci.org/browse/JENKINS-33556

@OwensCode
Copy link
Contributor

@dpires originally had a default value for the endpoint, but when I reviewed the code I was concerned that some administrators might not like that this is enabled by default as they might see it as a security risk. The version of Jenkins we tested against specifically skipped endpoints that returned null for the URL, which is why @dpires ended up taking the current approach. Hopefully there is an alternative to conditionally enabling the endpoint, otherwise a quick fix that would maybe be okay is to default it to a randomly generated string.

@ssbarnea
Copy link

The NPE was a really upsetting surprise on our instance. Please remove version 2.0.0 from jenkins plugin directory until you fix it!

@dpires
Copy link
Member Author

dpires commented Mar 16, 2016

I've changed the url to a random uuid string if the endpoint is not configured. See pr #190

@samrocketman
Copy link
Member

@ssbarnea unfortunately I'm not sure I have access to remove it. Jenkins synchronizes the update center every 8 hours roughly.

@samrocketman
Copy link
Member

The web endpoint can be disabled permanently with a script console script I crafted.

https://github.com/samrocketman/jenkins-script-console-scripts/blob/master/disable-slack-webhooks.groovy

@Blaisorblade
Copy link

Blaisorblade commented Apr 29, 2016

@dpires I found out about this because, even after the fix, this feature got in my way with its bad UI.
I don't care for the feature, but with this (version 2.0.1), I get a warning that I need to set the fill in the extra configuration, without mentioning this is entirely optional.

TL; DR. That warning sign is spam, stop using it.

What's worse, docs are missing enough context that to figure out that I can ignore this whole thing, I needed to figure it out in full by skimming this pull request (spending >= half an hour). (In case you wonder, I'm no newbie).

I was not looking to use this feature. I was just reviewing my settings after upgrading the Slack plugin, because the plugin update form told me that the configuration format changed, so I feared I would need to setup the connection from scratch.

After upgrading to 2.0.1, my "Manage Jenkins" page has a section "Slack Webhook Settings" which tells me (in yellow, with warning signs) "Please set a Slack outgoing webhook token" and "Please set a url endpoint".

I only want to keep using the existing integration with Slack. Do I now need to fill those as well? The GUI suggests yes, but it seems that I should ignore them unless I need some unspecified functionality.

  • This is not documented here: https://wiki.jenkins-ci.org/display/JENKINS/Slack+Plugin
  • The docs for these settings aren't very helpful.
  • This seems related to https://github.com/dpires/jenkins-slack-webhook-plugin, and its README doesn't state when I would need it.
  • On reflection, it seems that when the plugin README says "the following commands are available", it means "they're available in Slack". And the whole point is to "allow triggering Jenkins builds from inside Slack", something entirely optional.
  • Docs are awfully incomplete.
    1. Apparently I need to divine that to configure outgoing webhooks in Slack I need to add a Slack app, otherwise good luck finding the settings.
    2. Where do I define the name of the webhook endpoint? It seems that the name is defined in this plugin config, and is an arbitrary string that is not already used by some other URL (so why is it configurable instead of being fixed to slackwebhook)?

In fairness, lots of things in Jenkins show little care for UX, but this was pretty bad.

@OwensCode
Copy link
Contributor

@Blaisorblade these are good points and somebody else did remark that it would work better with a checkbox for enabling the feature (with it disabled by default). I think that would have alleviated your confusion because you would have been able to ignore it more easily. I think I'd be interested in making that improvement (if nobody else does in the mean time) if we can get a few other things moving first.

I did want to point out that all this work is voluntary; people spending time to contribute something they feel will be valuable to others. @dpires invested his own time to add this feature and others agreed it could be useful.

@Blaisorblade
Copy link

@OwensCode Thanks for your kind answer.

I did want to point out that all this work is voluntary; people spending time to contribute something they feel will be valuable to others. @dpires invested his own time to add this feature and others agreed it could be useful.

Well, sorry for the tone of my comment. The UX was frustrating, but venting that frustration to developers who didn't realize it is a bad habit, I'm trying to stop.

a checkbox for enabling the feature (with it disabled by default). I think that would have alleviated your confusion because you would have been able to ignore it more easily

I agree. Better descriptions would also help, but writing for somebody who knows less can be hard, so I guess I should contribute those.
Also, I guess it'd be better to have this checkbox (and subordinate options) together with the main ones. Somehow, options for the Slack plugin webhook are totally separate from options for the Slack plugin, and come before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants