Skip to content

Commit

Permalink
Merge pull request #1 from querydsl/initial-version
Browse files Browse the repository at this point in the history
Add initial version of Review Window
  • Loading branch information
johnktims committed Nov 10, 2015
2 parents 488f415 + 0dbe675 commit d7a843b
Show file tree
Hide file tree
Showing 7 changed files with 400 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target
nb-configuration.xml
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
language: java

jdk:
- oraclejdk8

install: mvn -B -q install -DskipTests=true

script: mvn -B test

cache:
directories:
- $HOME/.m2
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Github Review Window

A GitHub Webhook Implementation

[![Build Status](https://travis-ci.org/querydsl/gh-review-window.svg)](https://travis-ci.org/querydsl/gh-review-window)

To run it via Maven and the spring-boot plugin:
```sh
$ mvn spring-boot:run [OPTIONS]
```

To run it on the command line:
```sh
$ java [OPTIONS] -jar gh-review-window-(version)-full.jar
```

Where `OPTIONS` include:

| option | description |
|------------------|----------------------------------------------------------------------------|
| duration | **required** This is the default window duration |
| duration.`LABEL` | Additional durations for specific labels |
| secret | The secret that will be used to [compute the HMAC][securing your webhooks] |

For the syntax of period strings, see the [`java.time.Duration` javadoc][javadoc duration].

This is a subset of Spring Boot's autoconfiguration,
see the list of [common application properties][properties] for other supported configuration options.

Example:
```sh
$ mvn spring-boot:run -Dduration=P2D
```

## Oauth

In order to give Review Window the ability to add commit statuses, you need to specify
credentials that it can use to access those.

- Generate an Oauth token that gives `repo:status` access.
- Add it to your environment or `~/.github` file as `github_oauth`.


[javadoc duration]: http://docs.oracle.com/javase/8/docs/api/java/time/Duration.html
[properties]: http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
[securing your webhooks]: https://developer.github.com/webhooks/securing/
107 changes: 107 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-parent</artifactId>
<version>0.1.0</version>
</parent>

<name>GitHub Review Window</name>

<description>A GitHub Wehook implementation for blocking Pull Requests until the review period has passed</description>

<groupId>com.querydsl.webhooks</groupId>
<artifactId>gh-review-window</artifactId>
<version>0.1.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>

<url>https://github.com/querydsl/gh-review-window</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<spring.boot.version>1.2.7.RELEASE</spring.boot.version>

<project.checkout>https://github.com/querydsl/gh-review-window</project.checkout>
<project.githubpage>https://github.com/querydsl/gh-review-window</project.githubpage>

<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<scm>
<connection>${project.checkout}</connection>
<developerConnection>${project.checkout}</developerConnection>
<url>${project.githubpage}</url>
</scm>

<dependencies>
<dependency>
<groupId>com.github.shredder121</groupId>
<artifactId>gh-event-api</artifactId>
<version>0.2</version>
</dependency>
<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>github-api</artifactId>
<version>1.70</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.restassured</groupId>
<artifactId>rest-assured</artifactId>
<version>2.5.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<classifier>full</classifier>
</configuration>
</plugin>
</plugins>
</build>

<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
154 changes: 154 additions & 0 deletions src/main/java/com/querydsl/webhooks/GithubReviewWindow.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright 2015 The Querydsl Team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.querydsl.webhooks;

import static java.time.ZonedDateTime.now;

import java.io.IOException;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ScheduledFuture;

import org.kohsuke.github.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;

import com.github.shredder121.gh_event_api.GHEventApiServer;
import com.github.shredder121.gh_event_api.handler.pull_request.*;
import com.github.shredder121.gh_event_api.model.PullRequest;
import com.github.shredder121.gh_event_api.model.Ref;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;

/**
* GitHub Review Window - A GitHub Webhook Implementation.
*
* <p>
* When using Pull Requests, it's often necessary to review their contents.
*
* <p>
* This piece of software adds a commit status to the PR's head commit, essentially blocking
* The mergability until the duration of the review window has passed.
*
* <p>
* usage:
* {@code java -Dduration=(defaultDurationString) [-Dduration.(labelName)=(durationString)] -jar gh-review-window-(version)-full.jar }
*
* @author Shredder121
*/
@SpringBootApplication
public class GithubReviewWindow {

private static final Logger logger = LoggerFactory.getLogger(GithubReviewWindow.class);

private static final GitHub github;

private final TaskScheduler taskScheduler = new ConcurrentTaskScheduler();

static {
try {
github = GitHub.connect();
} catch (IOException ex) {
throw Throwables.propagate(ex);
}
}

public static void main(String... args) {
GHEventApiServer.start(GithubReviewWindow.class, args);
}

@Bean
public PullRequestHandler reviewWindowHandler(Environment environment) {
Duration defaultReviewWindow = Duration.parse(environment.getRequiredProperty("duration")); //duration is the default window
Map<String, ScheduledFuture<?>> asyncTasks = Maps.newConcurrentMap();

return payload -> {
PullRequest pullRequest = payload.getPullRequest();
Ref head = pullRequest.getHead();

try {
GHRepository repository = github.getRepository(payload.getRepository().getFullName());
Collection<GHLabel> labels = repository.getIssue(pullRequest.getNumber()).getLabels();

Duration reviewTime = labels.stream().map(label -> "duration." + label.getName()) //for all duration.[label] properties
.map(environment::getProperty).filter(Objects::nonNull) //look for a Duration
.findFirst().map(Duration::parse).orElse(defaultReviewWindow); //if none found, use the default window

ZonedDateTime creationTime = pullRequest.getCreated_at();
ZonedDateTime windowCloseTime = creationTime.plus(reviewTime);

boolean windowPassed = now().isAfter(windowCloseTime);
logger.info("creationTime({}) + reviewTime({}) = windowCloseTime({}), so windowPassed = {}",
creationTime, reviewTime, windowCloseTime, windowPassed);

if (windowPassed) {
completeAndCleanUp(asyncTasks, repository, head);
} else {
createPendingMessage(repository, head);

ScheduledFuture<?> scheduledTask = taskScheduler.schedule(
() -> completeAndCleanUp(asyncTasks, repository, head),
Date.from(windowCloseTime.toInstant()));

replaceCompletionTask(asyncTasks, scheduledTask, head);
}
} catch (IOException ex) {
throw Throwables.propagate(ex);
}
};
}

private static void completeAndCleanUp(Map<String, ?> tasks, GHRepository repo, Ref head) {
createSuccessMessage(repo, head);
tasks.remove(head.getSha());
}

private static void replaceCompletionTask(Map<String, ScheduledFuture<?>> tasks,
ScheduledFuture<?> completionTask, Ref head) {

boolean interrupt = false;
tasks.merge(head.getSha(), completionTask, (oldTask, newTask) -> {
oldTask.cancel(interrupt);
return newTask;
});
}

private static void createSuccessMessage(GHRepository repo, Ref commit) {
createStatusMessage(repo, commit, GHCommitState.SUCCESS, "The review window has passed");
}

private static void createPendingMessage(GHRepository repo, Ref commit) {
createStatusMessage(repo, commit, GHCommitState.PENDING, "The review window has not passed");
}

private static void createStatusMessage(GHRepository repo, Ref commit, GHCommitState state, String message) {
try {
repo.createCommitStatus(commit.getSha(), state, null, message, "review-window");
} catch (IOException ex) {
logger.warn("Exception updating status", ex);
}
}

}
5 changes: 5 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# the default review window duration is 2 days (see java.time.Duration#parse for details on the pattern used)
duration=P2D

# uncomment this line for GitHub MAC checking
#secret=[secret]
Loading

0 comments on commit d7a843b

Please sign in to comment.