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

Add support for global/project level webhooks #10

Merged
merged 11 commits into from
Nov 13, 2019
Merged

Add support for global/project level webhooks #10

merged 11 commits into from
Nov 13, 2019

Conversation

kzh
Copy link
Contributor

@kzh kzh commented Oct 28, 2019

This PR adds support for global and project scoped webhooks.

REST Endpoints:

<bitbucket url>/rest/sourcegraph-admin/1.0/webhook

Listing:

  • $ curl <bitbucket url>/rest/sourcegraph-admin/1.0/webhook

Creating:

  • $ curl <bitbucket url>/rest/sourcegraph-admin/1.0/webhook -d 'scope=<scope>&identifier=<identifier>&events=<events>&external=<external>'
    • scope: global | project | repository
    • identifier: name of project or repository (empty if scope is global)
    • events: comma separated list of events
    • external: URL at which the events data will be sent

Deleting:

  • $ curl -X DELETE <bitbucket url>/rest/sourcegraph-admin/1.0/webhook -d 'id=<id>'
  • $ curl -X DELETE <bitbucket url>/rest/sourcegraph-admin/1.0/webhook -d 'name=<name>'

For authorization, include these flags: -u admin:admin -H "X-Atlassian-Token: no-check".

The event payload aims to follow these JSON schemas:

Todo:

  • Persistence layer for storing Webhooks via ActiveObjects
  • Expose REST endpoint (/webhooks) for CRUDing Webhooks
  • Serialize events to JSON via google/gson and send out HTTP requests to external URLs
  • Support more event listeners for PullRequestEvent
  • Use project/repository ID instead of name slug for unique identifiers
  • Support webhook secret to validate HTTP requests
  • Add unit tests
  • Document available events and general behaviour

This is a part of sourcegraph/sourcegraph#6091.

@kzh kzh marked this pull request as ready for review November 6, 2019 06:16
@kzh kzh requested review from nicksnyder, tsenart, a team and unknwon November 6, 2019 06:17
@kzh
Copy link
Contributor Author

kzh commented Nov 6, 2019

This PR is ready for first round review. cc @nicksnyder @tsenart @unknwon

Copy link
Member

@unknwon unknwon left a comment

Choose a reason for hiding this comment

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

Thanks for making this!

I am really not sure how should I review this... because all of them LGTM.

Maybe add more comments in the code explain things like:

  1. How would the method be used/invoked.
  2. What role a class/method is.

@tsenart
Copy link

tsenart commented Nov 6, 2019

@kzh: Would it be possible to remove the whitespace / formatting changes or put them in a separate commit?

src/main/java/com/sourcegraph/webhook/Dispatcher.java Outdated Show resolved Hide resolved
src/main/java/com/sourcegraph/webhook/Dispatcher.java Outdated Show resolved Hide resolved
src/main/java/com/sourcegraph/webhook/EventSerializer.java Outdated Show resolved Hide resolved
private JsonObject payload;

private static JsonElement render(Object o) {
String raw = renderer.render(o, new HashMap<>());
Copy link

Choose a reason for hiding this comment

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

Java noob question: What is a HashMap doing here? Aren't we rendering an Object directly to a JSON String?

Copy link
Contributor Author

@kzh kzh Nov 6, 2019

Choose a reason for hiding this comment

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

The HashMap is for the options parameter of the render function. I think passing in a null will throw a null pointer exception, so I just pass in an empty one.

I'm not exactly sure how this parameter works either since the docs don't say much 🤔https://docs.atlassian.com/bitbucket-server/javadoc/4.0.2/spi/reference/com/atlassian/bitbucket/json/JsonRenderer.html

Choose a reason for hiding this comment

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

I had a similar question. Can you assign this to a variable so it is clearer?

Suggested change
String raw = renderer.render(o, new HashMap<>());
HashMap<> options = new HashMap<>();
String raw = renderer.render(o, options);

Do we really have to indirect through a JSON string? There is no method to go directly from a regular object to a JsonElement?

Copy link
Contributor Author

@kzh kzh Nov 13, 2019

Choose a reason for hiding this comment

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

The bitbucket API seems to only expose the render function which converts bitbucket objects (PullRequest, User, Ref, etc) directly to a JSON string, so there isn't much flexibility.

The other approach would be manually serializing these objects like how events are currently being handled to avoid incurring this redundant operation.

src/main/java/com/sourcegraph/webhook/WebhookListener.java Outdated Show resolved Hide resolved
src/main/java/com/sourcegraph/webhook/WebhookRouter.java Outdated Show resolved Hide resolved
src/main/java/com/sourcegraph/webhook/WebhookRouter.java Outdated Show resolved Hide resolved
src/main/java/com/sourcegraph/webhook/WebhookRouter.java Outdated Show resolved Hide resolved
@nicksnyder nicksnyder removed their request for review November 6, 2019 15:28
@kzh
Copy link
Contributor Author

kzh commented Nov 6, 2019

Thanks for the review! I'll ping to request the second round :)

Copy link
Contributor

@mrnugget mrnugget left a comment

Choose a reason for hiding this comment

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

Leaving this comment here to say: Thorsten was here 😄 I don't know enough about Bitbucket Server's plugin architecture to spot foundational mistakes and my knowledge of Java is superficial, but, that being said, I can't spot obvious mistakes here.

I'm wondering whether we should add some developer/debug experience features such as logging of succesful deliveries, sending a test even for a given PR?

Copy link

@tsenart tsenart left a comment

Choose a reason for hiding this comment

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

Approving to unblock this PR but left some additional feedback, some of which can definitely be addressed in follow up PRs.

A more general question I have: How has all of this been tested? I think we'd truly benefit from at least one high level system level integration test to make our future lives easier in changing this code.

src/main/java/com/sourcegraph/webhook/Dispatcher.java Outdated Show resolved Hide resolved

@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response put(String raw) throws WebhookException {
Copy link

Choose a reason for hiding this comment

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

We'll need some basic documentation on how to use this API (but that can come in a follow up PR).

Copy link

Choose a reason for hiding this comment

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

Question that comes to mind: Are requests authenticated in the same way as the normal Bitbucket Server API?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep!

Authentication
Any authentication that works against Bitbucket will work against the REST API. The preferred authentication methods are HTTP Basic (when using SSL) and OAuth. Other supported methods include: HTTP Cookies and Trusted Applications.

You can find OAuth code samples in several programming languages at bitbucket.org/atlassian_tutorial/atlassian-oauth-examples.

The log-in page uses cookie-based authentication, so if you are using Bitbucket in a browser you can call REST from JavaScript on the page and rely on the authentication that the browser has established.

(https://docs.atlassian.com/bitbucket-server/rest/5.16.0/bitbucket-rest.html)

Copy link

Choose a reason for hiding this comment

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

This talks about the REST API. Does this plugin "extend" the same REST API? Or is it served under a different path? I guess all of these usage questions need to be documented.

Copy link
Contributor Author

@kzh kzh Nov 13, 2019

Choose a reason for hiding this comment

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

Yes, I believe the plugin extend the native bitbucket REST API. They're both served under the /rest/ path which requires authentication (else 401s).

Copy link

@nicksnyder nicksnyder left a comment

Choose a reason for hiding this comment

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

No need to block on my review. I glanced through the code and here are some notes. My biggest ask is to add a lot of documentation.

import java.util.concurrent.ExecutorService;

@Component
public class Dispatcher {

Choose a reason for hiding this comment

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

Please add class and method doc comments here and everywhere else.

src/main/java/com/sourcegraph/webhook/Dispatcher.java Outdated Show resolved Hide resolved
src/main/java/com/sourcegraph/webhook/Dispatcher.java Outdated Show resolved Hide resolved
src/main/java/com/sourcegraph/webhook/Dispatcher.java Outdated Show resolved Hide resolved
src/main/java/com/sourcegraph/webhook/Dispatcher.java Outdated Show resolved Hide resolved
private JsonObject payload;

private static JsonElement render(Object o) {
String raw = renderer.render(o, new HashMap<>());

Choose a reason for hiding this comment

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

I had a similar question. Can you assign this to a variable so it is clearer?

Suggested change
String raw = renderer.render(o, new HashMap<>());
HashMap<> options = new HashMap<>();
String raw = renderer.render(o, options);

Do we really have to indirect through a JSON string? There is no method to go directly from a regular object to a JsonElement?

src/main/java/com/sourcegraph/webhook/EventSerializer.java Outdated Show resolved Hide resolved
@tsenart
Copy link

tsenart commented Nov 13, 2019

@kzh: Can you please summarise the current state of this PR? What's left to do and what's your expected timeline?

@tsenart
Copy link

tsenart commented Nov 13, 2019

@christinaforney: By the way, since this plugin isn't tied to our release cycle, there isn't any hard requirement for us to ship this at the same time as 3.10. We have to implement support in Sourcegraph for receiving and processing these webhooks in 3.11 anyways, so no rush necessary here.

@kzh
Copy link
Contributor Author

kzh commented Nov 13, 2019

This PR is good to merge. Everything is fully functioning. I intend to include the flushed out docs (README.md for webhooks) + unit/integration tests + comments in a followup PR.

A more general question I have: How has all of this been tested? I think we'd truly benefit from at least one high level system level integration test to make our future lives easier in changing this code.

So far, the tests have been manual. I spun up a small webserver that dumps incoming http requests:

package main

import (
	"log"
	"net/http"
	"net/http/httputil"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		dump, err := httputil.DumpRequest(r, true)
		if err != nil {
			log.Println(err)
		} else {
			log.Println(string(dump))
		}
	})
	log.Fatal(http.ListenAndServe(":80", nil))
}

I registered a webhook that listens to all PR events and sends it to the webserver.
[{"id":1,"name":"test","scope":"global","identifier":"","events":["pr"],"endpoint":"<endpoint>","secret":"secret"}]

This is the output on the webserver when I trigger an event (ex. merging a pr).
Screen Shot 2019-11-13 at 6 21 57 AM

@tsenart
Copy link

tsenart commented Nov 13, 2019

This PR is good to merge. Everything is fully functioning. I intend to include the docs (README.md for webhooks) + unit/integration tests in a followup PR.

Great. Will that follow up PR also include Java docs that @nicksnyder asked for? If so, let's merge this!

@kzh kzh merged commit fe2491f into master Nov 13, 2019
@tsenart
Copy link

tsenart commented Nov 13, 2019

@kzh: I'd share that screenshot in our #progress channel! Thank you for working on this 🙇

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.

5 participants