├── gitlab-jira-integration-api ├── src │ ├── test │ │ ├── resources │ │ │ └── fixtures │ │ │ │ ├── jira │ │ │ │ ├── comment.json │ │ │ │ ├── transition.json │ │ │ │ ├── response │ │ │ │ │ ├── comment.json │ │ │ │ │ └── transition.json │ │ │ │ └── input │ │ │ │ │ └── transition.json │ │ │ │ └── gitlab │ │ │ │ ├── author.json │ │ │ │ ├── user.json │ │ │ │ ├── commit.json │ │ │ │ ├── repository.json │ │ │ │ └── event.json │ │ └── java │ │ │ └── fr │ │ │ └── mmarie │ │ │ └── api │ │ │ ├── Utils.java │ │ │ ├── gitlab │ │ │ ├── UserTest.java │ │ │ ├── AuthorTest.java │ │ │ ├── EventTest.java │ │ │ ├── CommitTest.java │ │ │ └── RepositoryTest.java │ │ │ └── jira │ │ │ ├── CommentTest.java │ │ │ ├── TransitionTest.java │ │ │ ├── response │ │ │ ├── CommentResponseTest.java │ │ │ └── TransitionResponseTest.java │ │ │ └── input │ │ │ └── TransitionInputTest.java │ └── main │ │ └── java │ │ └── fr │ │ └── mmarie │ │ └── api │ │ ├── jira │ │ ├── Comment.java │ │ ├── Transition.java │ │ ├── response │ │ │ ├── CommentResponse.java │ │ │ └── TransitionResponse.java │ │ └── input │ │ │ └── TransitionInput.java │ │ └── gitlab │ │ ├── Author.java │ │ ├── User.java │ │ ├── Commit.java │ │ ├── Repository.java │ │ └── Event.java └── pom.xml ├── gitlab-jira-integration-application ├── src │ ├── test │ │ ├── resources │ │ │ ├── fixtures │ │ │ │ ├── jira │ │ │ │ │ └── comment.json │ │ │ │ └── gitlab │ │ │ │ │ └── user.json │ │ │ └── properties-test.yml │ │ └── java │ │ │ └── fr │ │ │ └── mmarie │ │ │ ├── core │ │ │ ├── auth │ │ │ │ ├── AuthResource.java │ │ │ │ ├── GitLabAuthenticatorTest.java │ │ │ │ └── GitLabAuthFilterTestIT.java │ │ │ ├── gitlab │ │ │ │ └── GitLabServiceTestIT.java │ │ │ ├── jira │ │ │ │ ├── JiraServiceTest.java │ │ │ │ └── JiraServiceTestIT.java │ │ │ └── IntegrationServiceTest.java │ │ │ ├── GitLabJiraConfigurationTest.java │ │ │ ├── resources │ │ │ └── HookResourceTestIT.java │ │ │ └── GitLabJiraApplicationTestIT.java │ └── main │ │ └── java │ │ └── fr │ │ └── mmarie │ │ ├── utils │ │ └── Common.java │ │ ├── core │ │ ├── auth │ │ │ ├── GitLabCredentials.java │ │ │ ├── GitLabAuthenticator.java │ │ │ └── GitLabAuthFilter.java │ │ ├── gitlab │ │ │ ├── GitLabEndPoints.java │ │ │ ├── GitLabConfiguration.java │ │ │ └── GitLabService.java │ │ ├── jira │ │ │ ├── TransitionConfiguration.java │ │ │ ├── JiraEndPoints.java │ │ │ ├── JiraConfiguration.java │ │ │ └── JiraService.java │ │ └── IntegrationService.java │ │ ├── GitLabJiraConfiguration.java │ │ ├── guice │ │ ├── ConfigurationModule.java │ │ └── AuthenticationModule.java │ │ ├── health │ │ ├── JiraHealthCheck.java │ │ └── GitLabHealthCheck.java │ │ ├── GitLabJiraApplication.java │ │ └── resources │ │ └── HookResource.java ├── properties.yml └── pom.xml ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md └── pom.xml /gitlab-jira-integration-api/src/test/resources/fixtures/jira/comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": "This is a comment" 3 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/resources/fixtures/jira/transition.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "15", 3 | "name": "Close" 4 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/test/resources/fixtures/jira/comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": "This is a comment" 3 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/resources/fixtures/gitlab/author.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "akraxx", 3 | "email": "contact@mmarie.fr" 4 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/resources/fixtures/gitlab/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "username": "john_smith", 4 | "name": "John Smith" 5 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/resources/fixtures/jira/response/comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | {"body": "This is a comment"}, 4 | {"body": "This is an other comment"} 5 | ] 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | *.iml 10 | /.idea 11 | deploy-settings.xml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | script: travis_retry mvn jacoco:prepare-agent install 3 | jdk: 4 | - oraclejdk8 5 | after_success: 6 | - mvn jacoco:report coveralls:report 7 | 8 | cache: 9 | directories: 10 | - $HOME/.m2 11 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/test/resources/fixtures/gitlab/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "username": "john_smith", 4 | "name": "John Smith", 5 | "state": "active", 6 | "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg" 7 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/resources/fixtures/jira/response/transition.json: -------------------------------------------------------------------------------- 1 | { 2 | "transitions": [ 3 | { 4 | "id": "15", 5 | "name": "Close" 6 | }, 7 | { 8 | "id": "18", 9 | "name": "Open" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/resources/fixtures/gitlab/commit.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", 3 | "message": "Update Catalan translation to e38cb41.", 4 | "timestamp": 1323692851000, 5 | "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327" 6 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/resources/fixtures/jira/input/transition.json: -------------------------------------------------------------------------------- 1 | { 2 | "update": { 3 | "comment": [ 4 | { 5 | "add": { 6 | "body": "Bug has been fixed." 7 | } 8 | } 9 | ] 10 | }, 11 | "transition": { 12 | "id": "15", 13 | "name": "Close" 14 | } 15 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/utils/Common.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.utils; 2 | 3 | public final class Common { 4 | 5 | private Common() { 6 | } 7 | 8 | public static String sanitizeURL(String url) { 9 | if(!url.endsWith("/")) { 10 | url = url+"/"; 11 | } 12 | 13 | return url; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/resources/fixtures/gitlab/repository.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Diaspora", 3 | "url": "git@example.com:mike/diasporadiaspora.git", 4 | "description": "", 5 | "homepage": "http://example.com/mike/diaspora", 6 | "git_http_url":"http://example.com/mike/diaspora.git", 7 | "git_ssh_url":"git@example.com:mike/diaspora.git", 8 | "visibility_level":0 9 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/resources/fixtures/gitlab/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "object_kind": "push", 3 | "before": "95790bf891e76fee5e1747ab589903a6a1f80f22", 4 | "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", 5 | "ref": "refs/heads/master", 6 | "user_id": 4, 7 | "user_name": "John Smith", 8 | "user_email": "john@example.com", 9 | "project_id": 15, 10 | "total_commits_count": 4 11 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/java/fr/mmarie/api/Utils.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | 7 | public class Utils { 8 | 9 | public static Date getDateFromString(String date) throws ParseException { 10 | return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").parse(date); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/core/auth/GitLabCredentials.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.auth; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import lombok.ToString; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @ToString(of = "service") 13 | public class GitLabCredentials { 14 | private String service; 15 | @JsonIgnore 16 | private String password; 17 | } 18 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/core/gitlab/GitLabEndPoints.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.gitlab; 2 | 3 | import fr.mmarie.api.gitlab.User; 4 | import retrofit.Call; 5 | import retrofit.http.GET; 6 | import retrofit.http.Path; 7 | import retrofit.http.Query; 8 | 9 | public interface GitLabEndPoints { 10 | @GET("api/v3/users/{id}") 11 | Call getUser(@Path("id") Long id, @Query("private_token") String privateToken); 12 | 13 | @GET("api/v3/user") 14 | Call getLoggedUser(@Query("private_token") String privateToken); 15 | } 16 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/test/resources/properties-test.yml: -------------------------------------------------------------------------------- 1 | server: 2 | applicationConnectors: 3 | - type: http 4 | port: 19090 5 | 6 | adminConnectors: 7 | - type: http 8 | port: 19091 9 | 10 | password: test-password 11 | 12 | jira: 13 | username: gitlab 14 | password: gitlab 15 | url: http://localhost:1338 16 | transitions: 17 | - name: Close 18 | # Case insensitive 19 | keywords: 20 | - close 21 | - fix 22 | 23 | gitlab: 24 | private_token: N1bJ4n8-rbFAEf8Syrh2 25 | url: http://localhost:1339 -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/main/java/fr/mmarie/api/jira/Comment.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.ToString; 9 | 10 | @Data 11 | @ToString(of = "body") 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @JsonIgnoreProperties(ignoreUnknown = true) 15 | public class Comment { 16 | 17 | @JsonProperty("body") 18 | private String body; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/main/java/fr/mmarie/api/gitlab/Author.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.gitlab; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.ToString; 9 | 10 | @Data 11 | @ToString(of = "name") 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @JsonIgnoreProperties(ignoreUnknown = true) 15 | public class Author { 16 | @JsonProperty("name") 17 | private String name; 18 | 19 | @JsonProperty("email") 20 | private String email; 21 | } 22 | -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/main/java/fr/mmarie/api/jira/Transition.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.ToString; 9 | 10 | @Data 11 | @ToString(of = {"id", "name"}) 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @JsonIgnoreProperties(ignoreUnknown = true) 15 | public class Transition { 16 | 17 | @JsonProperty("id") 18 | private Long id; 19 | 20 | @JsonProperty("name") 21 | private String name; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/main/java/fr/mmarie/api/jira/response/CommentResponse.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.jira.response; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.google.common.annotations.VisibleForTesting; 5 | import fr.mmarie.api.jira.Comment; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.List; 10 | 11 | @NoArgsConstructor 12 | @Getter 13 | @JsonIgnoreProperties(ignoreUnknown = true) 14 | public class CommentResponse { 15 | 16 | private List comments; 17 | 18 | @VisibleForTesting 19 | public CommentResponse(List comments) { 20 | this.comments = comments; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/main/java/fr/mmarie/api/gitlab/User.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.gitlab; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.ToString; 9 | 10 | @Data 11 | @ToString(of = "username") 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @JsonIgnoreProperties(ignoreUnknown = true) 15 | public class User { 16 | @JsonProperty("id") 17 | private Long id; 18 | 19 | @JsonProperty("username") 20 | private String username; 21 | 22 | @JsonProperty("name") 23 | private String name; 24 | } 25 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/test/java/fr/mmarie/core/auth/AuthResource.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.auth; 2 | 3 | 4 | import io.dropwizard.auth.Auth; 5 | 6 | import javax.ws.rs.GET; 7 | import javax.ws.rs.Path; 8 | import javax.ws.rs.Produces; 9 | import javax.ws.rs.core.MediaType; 10 | import java.security.Principal; 11 | 12 | @Path("/test/") 13 | @Produces(MediaType.TEXT_PLAIN) 14 | public class AuthResource { 15 | 16 | @GET 17 | @Path("noauth") 18 | public String hello() { 19 | return "hello"; 20 | } 21 | 22 | @GET 23 | @Path("protected") 24 | public String protectedEndPoint(@Auth Principal principal) { 25 | return "'" + principal.getName() + "' has user privileges"; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/main/java/fr/mmarie/api/jira/response/TransitionResponse.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.jira.response; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.google.common.annotations.VisibleForTesting; 5 | import fr.mmarie.api.jira.Comment; 6 | import fr.mmarie.api.jira.Transition; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.util.List; 11 | 12 | @NoArgsConstructor 13 | @Getter 14 | @JsonIgnoreProperties(ignoreUnknown = true) 15 | public class TransitionResponse { 16 | 17 | private List transitions; 18 | 19 | @VisibleForTesting 20 | public TransitionResponse(List transitions) { 21 | this.transitions = transitions; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/properties.yml: -------------------------------------------------------------------------------- 1 | server: 2 | applicationConnectors: 3 | - type: http 4 | port: 9090 5 | 6 | adminConnectors: 7 | - type: http 8 | port: 9091 9 | 10 | logging: 11 | appenders: 12 | - type: console 13 | logFormat: "%-5p [%d{ISO8601,UTC}] [%thread] [%c{5}] [hookId:%X{hookId:--}] : %m%n%rEx" 14 | 15 | password: gitlabpwd 16 | 17 | jira: 18 | username: gitlab 19 | password: gitlab 20 | url: http://localhost:8090 21 | transitions: 22 | - name: Closed 23 | # Case insensitive 24 | keywords: 25 | - closed 26 | - closes 27 | - close 28 | - fixed 29 | - fixes 30 | - fix 31 | 32 | gitlab: 33 | private_token: RXRpin15wEZkTSt4FfH1 34 | url: http://192.168.99.101:8080 -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/test/java/fr/mmarie/GitLabJiraConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | public class GitLabJiraConfigurationTest { 8 | 9 | @Test 10 | public void jiraConfigurationShouldBeNullByDefault() { 11 | GitLabJiraConfiguration gitLabJiraConfiguration = new GitLabJiraConfiguration(); 12 | assertThat(gitLabJiraConfiguration.getJiraConfiguration()).isNull(); 13 | } 14 | 15 | @Test 16 | public void gitLabConfigurationShouldBeNullByDefault() { 17 | GitLabJiraConfiguration gitLabJiraConfiguration = new GitLabJiraConfiguration(); 18 | assertThat(gitLabJiraConfiguration.getGitLabConfiguration()).isNull(); 19 | } 20 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/GitLabJiraConfiguration.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import fr.mmarie.core.gitlab.GitLabConfiguration; 5 | import fr.mmarie.core.jira.JiraConfiguration; 6 | import io.dropwizard.Configuration; 7 | import lombok.Getter; 8 | import org.hibernate.validator.constraints.Length; 9 | 10 | import javax.validation.constraints.NotNull; 11 | 12 | @Getter 13 | public class GitLabJiraConfiguration extends Configuration { 14 | @NotNull 15 | @JsonProperty("jira") 16 | private JiraConfiguration jiraConfiguration; 17 | 18 | @NotNull 19 | @JsonProperty("gitlab") 20 | private GitLabConfiguration gitLabConfiguration; 21 | 22 | @NotNull 23 | @JsonProperty("password") 24 | @Length(min = 5) 25 | private String password; 26 | } 27 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/guice/ConfigurationModule.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.guice; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.google.inject.Provides; 5 | import fr.mmarie.GitLabJiraConfiguration; 6 | import fr.mmarie.core.gitlab.GitLabConfiguration; 7 | import fr.mmarie.core.jira.JiraConfiguration; 8 | 9 | public class ConfigurationModule extends AbstractModule { 10 | @Override 11 | protected void configure() { 12 | 13 | } 14 | 15 | @Provides 16 | public GitLabConfiguration providesGitLabJiraApplication(GitLabJiraConfiguration configuration) { 17 | return configuration.getGitLabConfiguration(); 18 | } 19 | 20 | @Provides 21 | public JiraConfiguration providesJiraConfiguration(GitLabJiraConfiguration configuration) { 22 | return configuration.getJiraConfiguration(); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/core/gitlab/GitLabConfiguration.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.gitlab; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.google.common.annotations.VisibleForTesting; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import org.hibernate.validator.constraints.NotEmpty; 8 | 9 | import javax.validation.constraints.NotNull; 10 | 11 | @Getter 12 | @NoArgsConstructor 13 | public class GitLabConfiguration { 14 | @NotEmpty 15 | @NotNull 16 | @JsonProperty("private_token") 17 | private String privateToken; 18 | 19 | @NotEmpty 20 | @NotNull 21 | @JsonProperty 22 | private String url; 23 | 24 | @VisibleForTesting 25 | public GitLabConfiguration(String privateToken, String url) { 26 | this.privateToken = privateToken; 27 | this.url = url; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/core/jira/TransitionConfiguration.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.jira; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.google.common.annotations.VisibleForTesting; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import org.hibernate.validator.constraints.NotEmpty; 9 | 10 | import javax.validation.constraints.NotNull; 11 | import java.util.List; 12 | 13 | @Getter 14 | @NoArgsConstructor 15 | public class TransitionConfiguration { 16 | @NotEmpty 17 | @NotNull 18 | @JsonProperty 19 | private String name; 20 | 21 | @NotEmpty 22 | @NotNull 23 | @JsonProperty 24 | private List keywords; 25 | 26 | @VisibleForTesting 27 | public TransitionConfiguration(String name, List keywords) { 28 | this.name = name; 29 | this.keywords = keywords; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/main/java/fr/mmarie/api/gitlab/Commit.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.gitlab; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import lombok.ToString; 10 | 11 | import java.util.Date; 12 | 13 | @Data 14 | @ToString(of = "id") 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @Builder 18 | @JsonIgnoreProperties(ignoreUnknown = true) 19 | public class Commit { 20 | @JsonProperty("id") 21 | private String id; 22 | 23 | @JsonProperty("message") 24 | private String message; 25 | 26 | @JsonProperty("timestamp") 27 | private Date timestamp; 28 | 29 | @JsonProperty("url") 30 | private String url; 31 | 32 | @JsonProperty("author") 33 | private Author author; 34 | } 35 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/core/auth/GitLabAuthenticator.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.auth; 2 | 3 | import com.google.common.base.Optional; 4 | import io.dropwizard.auth.AuthenticationException; 5 | import io.dropwizard.auth.Authenticator; 6 | import lombok.NonNull; 7 | 8 | import java.security.Principal; 9 | 10 | public class GitLabAuthenticator implements Authenticator { 11 | 12 | private final String password; 13 | 14 | public GitLabAuthenticator(@NonNull String password) { 15 | this.password = password; 16 | } 17 | 18 | @Override 19 | public Optional authenticate(GitLabCredentials credentials) throws AuthenticationException { 20 | if(password.contentEquals(credentials.getPassword())) { 21 | return Optional.of(credentials::getService); 22 | } else { 23 | return Optional.absent(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gitlab-jira-integration-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | gitlab-jira-integration 5 | fr.mmarie 6 | 0.5.0-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | gitlab-jira-integration-api 11 | 12 | 13 | 14 | com.fasterxml.jackson.core 15 | jackson-annotations 16 | ${jackson.version} 17 | 18 | 19 | io.dropwizard 20 | dropwizard-validation 21 | ${dropwizard.version} 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/main/java/fr/mmarie/api/gitlab/Repository.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.gitlab; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import lombok.ToString; 10 | 11 | @Data 12 | @ToString(of = "name") 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @Builder 16 | @JsonIgnoreProperties(ignoreUnknown = true) 17 | public class Repository { 18 | @JsonProperty("name") 19 | private String name; 20 | 21 | @JsonProperty("url") 22 | private String url; 23 | 24 | @JsonProperty("description") 25 | private String description; 26 | 27 | @JsonProperty("homepage") 28 | private String homepage; 29 | 30 | @JsonProperty("git_http_url") 31 | private String gitHttpUrl; 32 | 33 | @JsonProperty("git_ssh_url") 34 | private String gitSshUrl; 35 | 36 | @JsonProperty("visibility_level") 37 | private Long visibilityLevel; 38 | } 39 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/health/JiraHealthCheck.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.health; 2 | 3 | import com.google.inject.Inject; 4 | import fr.mmarie.core.jira.JiraService; 5 | import ru.vyarus.dropwizard.guice.module.installer.feature.health.NamedHealthCheck; 6 | 7 | import javax.ws.rs.core.Response; 8 | 9 | public class JiraHealthCheck extends NamedHealthCheck { 10 | 11 | private final JiraService jiraService; 12 | 13 | @Inject 14 | public JiraHealthCheck(JiraService jiraService) { 15 | this.jiraService = jiraService; 16 | } 17 | 18 | @Override 19 | protected Result check() throws Exception { 20 | int code = jiraService.serverInfo().code(); 21 | if(code == Response.Status.OK.getStatusCode()) { 22 | return Result.healthy("Jira server has been contacted successfully"); 23 | } else { 24 | return Result.unhealthy("Unable to contact JIRA server, HTTP code received <"+code+">"); 25 | } 26 | } 27 | 28 | @Override 29 | public String getName() { 30 | return "jira"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/health/GitLabHealthCheck.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.health; 2 | 3 | import com.google.inject.Inject; 4 | import fr.mmarie.core.gitlab.GitLabService; 5 | import ru.vyarus.dropwizard.guice.module.installer.feature.health.NamedHealthCheck; 6 | 7 | import javax.ws.rs.core.Response; 8 | 9 | public class GitLabHealthCheck extends NamedHealthCheck { 10 | 11 | private final GitLabService gitLabService; 12 | 13 | @Inject 14 | public GitLabHealthCheck(GitLabService gitLabService) { 15 | this.gitLabService = gitLabService; 16 | } 17 | 18 | @Override 19 | protected Result check() throws Exception { 20 | int code = gitLabService.getLoggedUser().code(); 21 | if(code == Response.Status.OK.getStatusCode()) { 22 | return Result.healthy("GitLab server has been contacted successfully"); 23 | } else { 24 | return Result.unhealthy("Unable to contact GitLab server, HTTP code received <"+code+">"); 25 | } 26 | } 27 | 28 | @Override 29 | public String getName() { 30 | return "gitlab"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Maximilien Marie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/GitLabJiraApplication.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie; 2 | 3 | import fr.mmarie.guice.AuthenticationModule; 4 | import fr.mmarie.guice.ConfigurationModule; 5 | import io.dropwizard.Application; 6 | import io.dropwizard.setup.Bootstrap; 7 | import io.dropwizard.setup.Environment; 8 | import ru.vyarus.dropwizard.guice.GuiceBundle; 9 | 10 | public class GitLabJiraApplication extends Application { 11 | 12 | @Override 13 | public void initialize(Bootstrap bootstrap) { 14 | bootstrap.addBundle(GuiceBundle.builder() 15 | .enableAutoConfig(getClass().getPackage().getName()) 16 | .modules(new ConfigurationModule(), new AuthenticationModule()) 17 | .searchCommands(true) 18 | .build() 19 | ); 20 | } 21 | 22 | @Override 23 | public void run(GitLabJiraConfiguration configuration, Environment environment) throws Exception { 24 | } 25 | 26 | public static void main(String[] args) throws Exception { 27 | new GitLabJiraApplication().run(args); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/java/fr/mmarie/api/gitlab/UserTest.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.gitlab; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.dropwizard.jackson.Jackson; 5 | import org.assertj.core.api.StrictAssertions; 6 | import org.junit.Test; 7 | 8 | import static fr.mmarie.api.gitlab.UserAssert.assertThat; 9 | import static io.dropwizard.testing.FixtureHelpers.fixture; 10 | 11 | public class UserTest { 12 | 13 | private static final ObjectMapper MAPPER = Jackson.newObjectMapper(); 14 | 15 | @Test 16 | public void serializesToJSON() throws Exception { 17 | final User user = new User(1L, "john_smith", "John Smith"); 18 | 19 | final String expected = MAPPER.writeValueAsString( 20 | MAPPER.readValue(fixture("fixtures/gitlab/user.json"), User.class)); 21 | 22 | StrictAssertions.assertThat(MAPPER.writeValueAsString(user)).isEqualTo(expected); 23 | } 24 | 25 | @Test 26 | public void deserializesFromJSON() throws Exception { 27 | final User user = MAPPER.readValue(fixture("fixtures/gitlab/user.json"), User.class); 28 | 29 | assertThat(user) 30 | .hasId(1L) 31 | .hasUsername("john_smith") 32 | .hasName("John Smith"); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/core/jira/JiraEndPoints.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.jira; 2 | 3 | import fr.mmarie.api.jira.Comment; 4 | import fr.mmarie.api.jira.input.TransitionInput; 5 | import fr.mmarie.api.jira.response.CommentResponse; 6 | import fr.mmarie.api.jira.response.TransitionResponse; 7 | import retrofit.Call; 8 | import retrofit.http.Body; 9 | import retrofit.http.GET; 10 | import retrofit.http.POST; 11 | import retrofit.http.Path; 12 | import rx.Observable; 13 | 14 | import java.util.Map; 15 | 16 | public interface JiraEndPoints { 17 | @GET("rest/api/2/issue/{issue}") 18 | Call getIssue(@Path("issue") String issue); 19 | 20 | @POST("rest/api/2/issue/{issue}/comment") 21 | Call commentIssue(@Path("issue") String issue, @Body Comment comment); 22 | 23 | @GET("rest/api/2/issue/{issue}/comment") 24 | Call getCommentsOfIssue(@Path("issue") String issue); 25 | 26 | @GET("rest/api/2/issue/{issue}/transitions") 27 | Observable getTransitionsOfIssue(@Path("issue") String issue); 28 | 29 | @POST("rest/api/2/issue/{issue}/transitions") 30 | Call transitionsOnIssue(@Path("issue") String issue, @Body TransitionInput transitionInput); 31 | 32 | @GET("rest/api/2/serverInfo") 33 | Call> serverInfo(); 34 | } 35 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/core/jira/JiraConfiguration.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.google.common.annotations.VisibleForTesting; 5 | import com.google.common.collect.Lists; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import org.hibernate.validator.constraints.NotEmpty; 9 | 10 | import javax.validation.constraints.NotNull; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | @Getter 15 | @NoArgsConstructor 16 | public class JiraConfiguration { 17 | @NotEmpty 18 | @NotNull 19 | @JsonProperty 20 | private String username; 21 | 22 | @NotEmpty 23 | @NotNull 24 | @JsonProperty 25 | private String password; 26 | 27 | @NotEmpty 28 | @NotNull 29 | @JsonProperty 30 | private String url; 31 | 32 | @NotNull 33 | @JsonProperty 34 | private List transitions = Lists.newArrayList(); 35 | 36 | @VisibleForTesting 37 | public JiraConfiguration(String username, 38 | String password, 39 | String url, 40 | List transitions) { 41 | this.username = username; 42 | this.password = password; 43 | this.url = url; 44 | this.transitions = transitions; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/test/java/fr/mmarie/core/auth/GitLabAuthenticatorTest.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.auth; 2 | 3 | import com.google.common.base.Optional; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import java.security.Principal; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | public class GitLabAuthenticatorTest { 12 | 13 | private GitLabAuthenticator gitLabAuthenticator; 14 | 15 | private final String password = "test-password"; 16 | 17 | @Before 18 | public void setUp() throws Exception { 19 | gitLabAuthenticator = new GitLabAuthenticator(password); 20 | } 21 | 22 | @Test 23 | public void authenticateWithBadCredentialsShouldReturnAnAbsentPrincipal() throws Exception { 24 | Optional principalOptional = gitLabAuthenticator.authenticate( 25 | new GitLabCredentials("bad-svc", "bad-pwd")); 26 | 27 | assertThat(principalOptional.isPresent()).isFalse(); 28 | } 29 | 30 | @Test 31 | public void authenticateWithGoodCredentialsShouldReturnAPrincipal() throws Exception { 32 | Optional principalOptional = gitLabAuthenticator.authenticate( 33 | new GitLabCredentials("good-svc", password)); 34 | 35 | assertThat(principalOptional.isPresent()).isTrue(); 36 | assertThat(principalOptional.get().getName()).isEqualTo("good-svc"); 37 | } 38 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/java/fr/mmarie/api/jira/CommentTest.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.jira; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.dropwizard.jackson.Jackson; 5 | import org.junit.Test; 6 | 7 | import java.io.IOException; 8 | 9 | import static fr.mmarie.api.jira.CommentAssert.assertThat; 10 | import static io.dropwizard.testing.FixtureHelpers.fixture; 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | public class CommentTest { 14 | 15 | private static final ObjectMapper MAPPER = Jackson.newObjectMapper(); 16 | 17 | @Test 18 | public void serializesToJSON() throws IOException { 19 | final Comment comment = new Comment("This is a comment"); 20 | 21 | final String expected = MAPPER.writeValueAsString( 22 | MAPPER.readValue(fixture("fixtures/jira/comment.json"), Comment.class)); 23 | 24 | assertThat(MAPPER.writeValueAsString(comment)).isEqualTo(expected); 25 | } 26 | 27 | @Test 28 | public void deserializesFromJSON() throws IOException { 29 | final Comment comment = MAPPER.readValue(fixture("fixtures/jira/comment.json"), Comment.class); 30 | 31 | assertThat(comment).hasBody("This is a comment"); 32 | } 33 | 34 | @Test 35 | public void testToString() throws Exception { 36 | final Comment comment = new Comment("This is a comment"); 37 | 38 | assertThat(comment.toString()).isEqualTo("Comment(body=This is a comment)"); 39 | } 40 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/java/fr/mmarie/api/gitlab/AuthorTest.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.gitlab; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.dropwizard.jackson.Jackson; 5 | import org.junit.Test; 6 | 7 | import java.io.IOException; 8 | 9 | import static fr.mmarie.api.gitlab.AuthorAssert.assertThat; 10 | import static io.dropwizard.testing.FixtureHelpers.fixture; 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | public class AuthorTest { 14 | 15 | private static final ObjectMapper MAPPER = Jackson.newObjectMapper(); 16 | 17 | @Test 18 | public void serializesToJSON() throws IOException { 19 | final Author author = new Author("akraxx", "contact@mmarie.fr"); 20 | 21 | final String expected = MAPPER.writeValueAsString( 22 | MAPPER.readValue(fixture("fixtures/gitlab/author.json"), Author.class)); 23 | 24 | assertThat(MAPPER.writeValueAsString(author)).isEqualTo(expected); 25 | } 26 | 27 | @Test 28 | public void deserializesFromJSON() throws IOException { 29 | final Author author = MAPPER.readValue(fixture("fixtures/gitlab/author.json"), Author.class); 30 | 31 | assertThat(author) 32 | .hasName("akraxx") 33 | .hasEmail("contact@mmarie.fr"); 34 | } 35 | 36 | @Test 37 | public void testToString() throws Exception { 38 | final Author author = new Author("akraxx", "contact@mmarie.fr"); 39 | 40 | assertThat(author.toString()).isEqualTo("Author(name=akraxx)"); 41 | } 42 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/java/fr/mmarie/api/jira/TransitionTest.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.jira; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.dropwizard.jackson.Jackson; 5 | import org.junit.Test; 6 | 7 | import java.io.IOException; 8 | 9 | import static fr.mmarie.api.jira.TransitionAssert.assertThat; 10 | import static io.dropwizard.testing.FixtureHelpers.fixture; 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | public class TransitionTest { 14 | 15 | private static final ObjectMapper MAPPER = Jackson.newObjectMapper(); 16 | 17 | @Test 18 | public void serializesToJSON() throws IOException { 19 | final Transition transition = new Transition(15L, "Close"); 20 | 21 | final String expected = MAPPER.writeValueAsString( 22 | MAPPER.readValue(fixture("fixtures/jira/transition.json"), Transition.class)); 23 | 24 | assertThat(MAPPER.writeValueAsString(transition)).isEqualTo(expected); 25 | } 26 | 27 | @Test 28 | public void deserializesFromJSON() throws IOException { 29 | final Transition transition = MAPPER.readValue(fixture("fixtures/jira/transition.json"), Transition.class); 30 | 31 | assertThat(transition) 32 | .hasId(15L) 33 | .hasName("Close"); 34 | } 35 | 36 | @Test 37 | public void testToString() throws Exception { 38 | final Transition transition = new Transition(15L, "Close"); 39 | 40 | assertThat(transition.toString()).isEqualTo("Transition(id=15, name=Close)"); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/core/gitlab/GitLabService.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.gitlab; 2 | 3 | import com.google.inject.Inject; 4 | import fr.mmarie.api.gitlab.User; 5 | import lombok.NonNull; 6 | import retrofit.JacksonConverterFactory; 7 | import retrofit.Response; 8 | import retrofit.Retrofit; 9 | 10 | import java.io.IOException; 11 | 12 | import static fr.mmarie.utils.Common.sanitizeURL; 13 | 14 | public class GitLabService { 15 | 16 | private final GitLabEndPoints gitLabEndPoints; 17 | 18 | private final GitLabConfiguration gitLabConfiguration; 19 | 20 | @Inject 21 | public GitLabService(@NonNull GitLabConfiguration gitLabConfiguration) { 22 | this.gitLabConfiguration = gitLabConfiguration; 23 | 24 | this.gitLabEndPoints = new Retrofit.Builder() 25 | .baseUrl(sanitizeURL(gitLabConfiguration.getUrl())) 26 | .addConverterFactory(JacksonConverterFactory.create()) 27 | .build() 28 | .create(GitLabEndPoints.class); 29 | } 30 | 31 | public Response getUser(Long id) throws IOException { 32 | return gitLabEndPoints.getUser(id, gitLabConfiguration.getPrivateToken()).execute(); 33 | } 34 | 35 | public Response getLoggedUser() throws IOException { 36 | return gitLabEndPoints.getLoggedUser(gitLabConfiguration.getPrivateToken()).execute(); 37 | } 38 | 39 | public String getUserUrl(String username) { 40 | String baseUrl = gitLabConfiguration.getUrl(); 41 | 42 | return sanitizeURL(baseUrl).concat("u/" + username); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/java/fr/mmarie/api/jira/response/CommentResponseTest.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.jira.response; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.google.common.collect.ImmutableList; 5 | import fr.mmarie.api.jira.Comment; 6 | import io.dropwizard.jackson.Jackson; 7 | import org.junit.Test; 8 | 9 | import java.io.IOException; 10 | 11 | import static io.dropwizard.testing.FixtureHelpers.fixture; 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | public class CommentResponseTest { 15 | 16 | private static final ObjectMapper MAPPER = Jackson.newObjectMapper(); 17 | 18 | @Test 19 | public void serializesToJSON() throws IOException { 20 | final CommentResponse commentResponse = new CommentResponse( 21 | ImmutableList.of(new Comment("This is a comment"), new Comment("This is an other comment")) 22 | ); 23 | 24 | final String expected = MAPPER.writeValueAsString( 25 | MAPPER.readValue(fixture("fixtures/jira/response/comment.json"), CommentResponse.class)); 26 | 27 | assertThat(MAPPER.writeValueAsString(commentResponse)).isEqualTo(expected); 28 | } 29 | 30 | @Test 31 | public void deserializesFromJSON() throws IOException { 32 | final CommentResponse commentResponse = MAPPER.readValue(fixture("fixtures/jira/response/comment.json"), CommentResponse.class); 33 | 34 | assertThat(commentResponse.getComments()) 35 | .hasSameElementsAs(ImmutableList.of(new Comment("This is a comment"), new Comment("This is an other comment"))); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/java/fr/mmarie/api/jira/response/TransitionResponseTest.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.jira.response; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.google.common.collect.ImmutableList; 5 | import fr.mmarie.api.jira.Transition; 6 | import io.dropwizard.jackson.Jackson; 7 | import org.junit.Test; 8 | 9 | import java.io.IOException; 10 | 11 | import static io.dropwizard.testing.FixtureHelpers.fixture; 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | public class TransitionResponseTest { 15 | 16 | private static final ObjectMapper MAPPER = Jackson.newObjectMapper(); 17 | 18 | @Test 19 | public void serializesToJSON() throws IOException { 20 | final TransitionResponse transitionResponse = new TransitionResponse( 21 | ImmutableList.of(new Transition(15L, "Close"), new Transition(18L, "Open")) 22 | ); 23 | 24 | final String expected = MAPPER.writeValueAsString( 25 | MAPPER.readValue(fixture("fixtures/jira/response/transition.json"), TransitionResponse.class)); 26 | 27 | assertThat(MAPPER.writeValueAsString(transitionResponse)).isEqualTo(expected); 28 | } 29 | 30 | @Test 31 | public void deserializesFromJSON() throws IOException { 32 | final TransitionResponse transition = MAPPER.readValue(fixture("fixtures/jira/response/transition.json"), TransitionResponse.class); 33 | 34 | assertThat(transition.getTransitions()) 35 | .hasSameElementsAs(ImmutableList.of(new Transition(15L, "Close"), new Transition(18L, "Open"))); 36 | 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/main/java/fr/mmarie/api/jira/input/TransitionInput.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.jira.input; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.google.common.annotations.VisibleForTesting; 5 | import fr.mmarie.api.jira.Comment; 6 | import fr.mmarie.api.jira.Transition; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | import lombok.ToString; 11 | 12 | import java.util.List; 13 | 14 | @NoArgsConstructor 15 | @EqualsAndHashCode(of = "update") 16 | @ToString(of = "transition") 17 | @Getter 18 | public class TransitionInput { 19 | 20 | @EqualsAndHashCode(of = "comments") 21 | @NoArgsConstructor 22 | @Getter 23 | public static class Update { 24 | @JsonProperty("comment") 25 | private List comments; 26 | 27 | @VisibleForTesting 28 | public Update(List comments) { 29 | this.comments = comments; 30 | } 31 | } 32 | 33 | @EqualsAndHashCode(of = "comment") 34 | @NoArgsConstructor 35 | @Getter 36 | public static class CommentWrapper { 37 | @JsonProperty("add") 38 | private Comment comment; 39 | 40 | @VisibleForTesting 41 | public CommentWrapper(Comment comment) { 42 | this.comment = comment; 43 | } 44 | } 45 | 46 | @JsonProperty("update") 47 | private Update update; 48 | 49 | @JsonProperty("transition") 50 | private Transition transition; 51 | 52 | @VisibleForTesting 53 | public TransitionInput(Update update, Transition transition) { 54 | this.update = update; 55 | this.transition = transition; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/main/java/fr/mmarie/api/gitlab/Event.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.gitlab; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.google.common.annotations.VisibleForTesting; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | import lombok.ToString; 11 | 12 | import javax.validation.constraints.NotNull; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | @Data 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | @Builder 20 | @ToString(of = "type") 21 | @JsonIgnoreProperties(ignoreUnknown = true) 22 | public class Event { 23 | 24 | public enum Type { 25 | PUSH, 26 | TAG_PUSH, 27 | ISSUE, 28 | NOTE, 29 | MERGE_REQUEST 30 | } 31 | 32 | @JsonProperty("object_kind") 33 | @NotNull 34 | private Type type; 35 | 36 | @JsonProperty("before") 37 | private String before; 38 | 39 | @JsonProperty("after") 40 | private String after; 41 | 42 | @JsonProperty("ref") 43 | private String ref; 44 | 45 | @JsonProperty("user_id") 46 | private Long userId; 47 | 48 | @JsonProperty("user_name") 49 | private String userName; 50 | 51 | @JsonProperty("user_email") 52 | private String userEmail; 53 | 54 | @JsonProperty("project_id") 55 | private Long projectId; 56 | 57 | @JsonProperty("repository") 58 | private Repository repository; 59 | 60 | @JsonProperty("commits") 61 | private List commits; 62 | 63 | @JsonProperty("object_attributes") 64 | private Map objectAttributes; 65 | 66 | @JsonProperty("total_commits_count") 67 | private Long totalCommitsCount; 68 | 69 | @VisibleForTesting 70 | public Event(Type type) { 71 | this.type = type; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/guice/AuthenticationModule.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.guice; 2 | 3 | import com.google.inject.AbstractModule; 4 | import fr.mmarie.GitLabJiraConfiguration; 5 | import fr.mmarie.core.auth.GitLabAuthFilter; 6 | import fr.mmarie.core.auth.GitLabAuthenticator; 7 | import io.dropwizard.auth.AuthDynamicFeature; 8 | import io.dropwizard.auth.AuthValueFactoryProvider; 9 | import io.dropwizard.setup.Environment; 10 | import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature; 11 | import ru.vyarus.dropwizard.guice.module.support.ConfigurationAwareModule; 12 | import ru.vyarus.dropwizard.guice.module.support.EnvironmentAwareModule; 13 | 14 | import javax.ws.rs.core.Response; 15 | import java.security.Principal; 16 | 17 | public class AuthenticationModule extends AbstractModule implements EnvironmentAwareModule, 18 | ConfigurationAwareModule { 19 | 20 | private Environment environment; 21 | private GitLabJiraConfiguration configuration; 22 | 23 | @Override 24 | protected void configure() { 25 | environment.jersey().register(new AuthDynamicFeature(new GitLabAuthFilter.Builder() 26 | .setAuthenticator(new GitLabAuthenticator(configuration.getPassword())) 27 | .setUnauthorizedHandler((s, s1) -> Response.status(Response.Status.UNAUTHORIZED).build()) 28 | .setRealm("GitLab HOOK") 29 | .buildAuthFilter())); 30 | 31 | environment.jersey().register(RolesAllowedDynamicFeature.class); 32 | environment.jersey().register(new AuthValueFactoryProvider.Binder<>(Principal.class)); 33 | } 34 | 35 | @Override 36 | public void setEnvironment(Environment environment) { 37 | this.environment = environment; 38 | } 39 | 40 | @Override 41 | public void setConfiguration(GitLabJiraConfiguration configuration) { 42 | this.configuration = configuration; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/resources/HookResource.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.resources; 2 | 3 | import com.codahale.metrics.MetricRegistry; 4 | import com.codahale.metrics.annotation.Timed; 5 | import com.google.inject.Inject; 6 | import fr.mmarie.api.gitlab.Event; 7 | import fr.mmarie.core.IntegrationService; 8 | import io.dropwizard.auth.Auth; 9 | import io.dropwizard.setup.Environment; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | import javax.validation.Valid; 13 | import javax.ws.rs.HeaderParam; 14 | import javax.ws.rs.POST; 15 | import javax.ws.rs.Path; 16 | import javax.ws.rs.Produces; 17 | import javax.ws.rs.core.MediaType; 18 | import java.security.Principal; 19 | import java.util.Date; 20 | 21 | @Path("/hook") 22 | @Slf4j 23 | public class HookResource { 24 | 25 | public static final String GITLAB_HEADER = "X-Gitlab-Event"; 26 | 27 | private final IntegrationService service; 28 | 29 | private final MetricRegistry metricRegistry; 30 | 31 | @Inject 32 | public HookResource(IntegrationService service, 33 | Environment environment) { 34 | this.service = service; 35 | this.metricRegistry = environment.metrics(); 36 | } 37 | 38 | @POST 39 | @Produces(MediaType.APPLICATION_JSON) 40 | @Timed 41 | public void hook(@Auth Principal principal, 42 | @HeaderParam(GITLAB_HEADER) String gitLabHeader, 43 | @Valid Event event) { 44 | new Thread(() -> { 45 | setThreadName(principal); 46 | metricRegistry.counter(principal.getName()).inc(); 47 | 48 | log.info("Hook received > {}", event); 49 | switch (event.getType()) { 50 | case PUSH: 51 | case TAG_PUSH: 52 | service.performPushEvent(event); 53 | break; 54 | } 55 | }).start(); 56 | } 57 | 58 | private void setThreadName(Principal principal) { 59 | final String requestId = String.format("%s-%d", 60 | principal.getName(), 61 | new Date().getTime()); 62 | Thread.currentThread().setName(requestId); 63 | 64 | log.info("setThreadName(): initialized new requestId <{}>", requestId); 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/java/fr/mmarie/api/gitlab/EventTest.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.gitlab; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.dropwizard.jackson.Jackson; 5 | import org.junit.Test; 6 | 7 | import static fr.mmarie.api.gitlab.EventAssert.assertThat; 8 | import static io.dropwizard.testing.FixtureHelpers.fixture; 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | public class EventTest { 12 | 13 | private static final ObjectMapper MAPPER = Jackson.newObjectMapper(); 14 | 15 | @Test 16 | public void serializesToJSON() throws Exception { 17 | final Event event = Event.builder() 18 | .type(Event.Type.PUSH) 19 | .before("95790bf891e76fee5e1747ab589903a6a1f80f22") 20 | .after("da1560886d4f094c3e6c9ef40349f7d38b5d27d7") 21 | .ref("refs/heads/master") 22 | .userId(4L) 23 | .userName("John Smith") 24 | .userEmail("john@example.com") 25 | .projectId(15L) 26 | .totalCommitsCount(4L) 27 | .build(); 28 | 29 | final String expected = MAPPER.writeValueAsString( 30 | MAPPER.readValue(fixture("fixtures/gitlab/event.json"), Event.class)); 31 | 32 | assertThat(MAPPER.writeValueAsString(event)).isEqualTo(expected); 33 | } 34 | 35 | @Test 36 | public void deserializesFromJSON() throws Exception { 37 | final Event event = MAPPER.readValue(fixture("fixtures/gitlab/event.json"), Event.class); 38 | 39 | assertThat(event) 40 | .hasType(Event.Type.PUSH) 41 | .hasBefore("95790bf891e76fee5e1747ab589903a6a1f80f22") 42 | .hasAfter("da1560886d4f094c3e6c9ef40349f7d38b5d27d7") 43 | .hasRef("refs/heads/master") 44 | .hasUserId(4L) 45 | .hasUserName("John Smith") 46 | .hasUserEmail("john@example.com") 47 | .hasProjectId(15L) 48 | .hasTotalCommitsCount(4L); 49 | } 50 | 51 | @Test 52 | public void testToString() throws Exception { 53 | final Event event = new Event(Event.Type.PUSH); 54 | 55 | assertThat(event.toString()).isEqualTo("Event(type=PUSH)"); 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/java/fr/mmarie/api/gitlab/CommitTest.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.gitlab; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.dropwizard.jackson.Jackson; 5 | import org.assertj.core.api.StrictAssertions; 6 | import org.junit.Test; 7 | 8 | import static fr.mmarie.api.Utils.getDateFromString; 9 | import static fr.mmarie.api.gitlab.CommitAssert.assertThat; 10 | import static io.dropwizard.testing.FixtureHelpers.fixture; 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | public class CommitTest { 14 | 15 | private static final ObjectMapper MAPPER = Jackson.newObjectMapper(); 16 | 17 | @Test 18 | public void serializesToJSON() throws Exception { 19 | final Commit commit = Commit.builder() 20 | .id("b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327") 21 | .message("Update Catalan translation to e38cb41.") 22 | .timestamp(getDateFromString("2011-12-12T14:27:31+02:00")) 23 | .url("http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327") 24 | .build(); 25 | 26 | final String expected = MAPPER.writeValueAsString( 27 | MAPPER.readValue(fixture("fixtures/gitlab/commit.json"), Commit.class)); 28 | 29 | StrictAssertions.assertThat(MAPPER.writeValueAsString(commit)).isEqualTo(expected); 30 | } 31 | 32 | @Test 33 | public void deserializesFromJSON() throws Exception { 34 | final Commit commit = MAPPER.readValue(fixture("fixtures/gitlab/commit.json"), Commit.class); 35 | 36 | assertThat(commit) 37 | .hasId("b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327") 38 | .hasMessage("Update Catalan translation to e38cb41.") 39 | .hasUrl("http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327"); 40 | } 41 | 42 | @Test 43 | public void testToString() throws Exception { 44 | final Commit commit = Commit.builder() 45 | .id("b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327") 46 | .message("Update Catalan translation to e38cb41.") 47 | .timestamp(getDateFromString("2011-12-12T14:27:31+02:00")) 48 | .url("http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327") 49 | .build(); 50 | 51 | assertThat(commit.toString()).isEqualTo("Commit(id=b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327)"); 52 | } 53 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/java/fr/mmarie/api/jira/input/TransitionInputTest.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.jira.input; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.google.common.collect.ImmutableList; 5 | import fr.mmarie.api.jira.Comment; 6 | import fr.mmarie.api.jira.Transition; 7 | import fr.mmarie.api.jira.response.TransitionResponse; 8 | import io.dropwizard.jackson.Jackson; 9 | import org.junit.Test; 10 | 11 | import java.io.IOException; 12 | 13 | import static io.dropwizard.testing.FixtureHelpers.fixture; 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | 16 | public class TransitionInputTest { 17 | 18 | private static final ObjectMapper MAPPER = Jackson.newObjectMapper(); 19 | 20 | @Test 21 | public void serializesToJSON() throws IOException { 22 | TransitionInput.CommentWrapper commentWrapper = new TransitionInput.CommentWrapper( 23 | new Comment("Bug has been fixed.")); 24 | 25 | final TransitionInput transitionResponse = new TransitionInput( 26 | new TransitionInput.Update(ImmutableList.of(commentWrapper)), 27 | new Transition(15L, "Close") 28 | ); 29 | 30 | final String expected = MAPPER.writeValueAsString( 31 | MAPPER.readValue(fixture("fixtures/jira/input/transition.json"), TransitionInput.class)); 32 | 33 | assertThat(MAPPER.writeValueAsString(transitionResponse)).isEqualTo(expected); 34 | } 35 | 36 | @Test 37 | public void deserializesFromJSON() throws IOException { 38 | TransitionInput.CommentWrapper commentWrapper = new TransitionInput.CommentWrapper( 39 | new Comment("Bug has been fixed.")); 40 | 41 | final TransitionInput expected = new TransitionInput( 42 | new TransitionInput.Update(ImmutableList.of(commentWrapper)), 43 | new Transition(15L, "Close") 44 | ); 45 | 46 | final TransitionInput transition = MAPPER.readValue(fixture("fixtures/jira/input/transition.json"), TransitionInput.class); 47 | 48 | assertThat(transition) 49 | .isEqualTo(expected); 50 | 51 | } 52 | 53 | @Test 54 | public void testToString() throws Exception { 55 | TransitionInput.CommentWrapper commentWrapper = new TransitionInput.CommentWrapper( 56 | new Comment("Bug has been fixed.")); 57 | 58 | final TransitionInput expected = new TransitionInput( 59 | new TransitionInput.Update(ImmutableList.of(commentWrapper)), 60 | new Transition(15L, "Close") 61 | ); 62 | 63 | assertThat(expected.toString()) 64 | .isEqualTo("TransitionInput(transition=Transition(id=15, name=Close))"); 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-api/src/test/java/fr/mmarie/api/gitlab/RepositoryTest.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.api.gitlab; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.dropwizard.jackson.Jackson; 5 | import org.assertj.core.api.StrictAssertions; 6 | import org.junit.Test; 7 | 8 | import java.io.IOException; 9 | 10 | import static fr.mmarie.api.gitlab.RepositoryAssert.assertThat; 11 | import static io.dropwizard.testing.FixtureHelpers.fixture; 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | public class RepositoryTest { 15 | private static final ObjectMapper MAPPER = Jackson.newObjectMapper(); 16 | 17 | @Test 18 | public void serializesToJSON() throws IOException { 19 | final Repository repository = Repository.builder() 20 | .name("Diaspora") 21 | .url("git@example.com:mike/diasporadiaspora.git") 22 | .description("") 23 | .homepage("http://example.com/mike/diaspora") 24 | .gitHttpUrl("http://example.com/mike/diaspora.git") 25 | .gitSshUrl("git@example.com:mike/diaspora.git") 26 | .visibilityLevel(0L) 27 | .build(); 28 | 29 | final String expected = MAPPER.writeValueAsString( 30 | MAPPER.readValue(fixture("fixtures/gitlab/repository.json"), Repository.class)); 31 | 32 | StrictAssertions.assertThat(MAPPER.writeValueAsString(repository)).isEqualTo(expected); 33 | } 34 | 35 | @Test 36 | public void deserializesFromJSON() throws IOException { 37 | final Repository repository = MAPPER.readValue(fixture("fixtures/gitlab/repository.json"), Repository.class); 38 | 39 | assertThat(repository) 40 | .hasName("Diaspora") 41 | .hasUrl("git@example.com:mike/diasporadiaspora.git") 42 | .hasDescription("") 43 | .hasHomepage("http://example.com/mike/diaspora") 44 | .hasGitHttpUrl("http://example.com/mike/diaspora.git") 45 | .hasGitSshUrl("git@example.com:mike/diaspora.git") 46 | .hasVisibilityLevel(0L); 47 | } 48 | 49 | @Test 50 | public void testToString() throws Exception { 51 | final Repository repository = Repository.builder() 52 | .name("Diaspora") 53 | .url("git@example.com:mike/diasporadiaspora.git") 54 | .description("") 55 | .homepage("http://example.com/mike/diaspora") 56 | .gitHttpUrl("http://example.com/mike/diaspora.git") 57 | .gitSshUrl("git@example.com:mike/diaspora.git") 58 | .visibilityLevel(0L) 59 | .build(); 60 | 61 | assertThat(repository.toString()).isEqualTo("Repository(name=Diaspora)"); 62 | } 63 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/test/java/fr/mmarie/core/gitlab/GitLabServiceTestIT.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.gitlab; 2 | 3 | import com.github.tomakehurst.wiremock.junit.WireMockRule; 4 | import fr.mmarie.api.gitlab.User; 5 | import org.junit.Before; 6 | import org.junit.Rule; 7 | import org.junit.Test; 8 | import retrofit.Response; 9 | 10 | import java.util.List; 11 | 12 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; 13 | import static com.github.tomakehurst.wiremock.client.WireMock.get; 14 | import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; 15 | import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; 16 | import static fr.mmarie.Assertions.assertThat; 17 | import static io.dropwizard.testing.FixtureHelpers.fixture; 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | public class GitLabServiceTestIT { 21 | 22 | private GitLabService gitLabService; 23 | 24 | public static final int PORT = 1339; 25 | 26 | @Rule 27 | public WireMockRule wireMockRule = new WireMockRule(PORT); 28 | 29 | private String privateToken = "N1bJ4n8-rbFAEf8Syrh2"; 30 | private GitLabConfiguration gitLabConfiguration = new GitLabConfiguration(privateToken, 31 | String.format("http://localhost:%d", PORT)); 32 | 33 | @Before 34 | public void setUp() throws Exception { 35 | gitLabService = new GitLabService(gitLabConfiguration); 36 | } 37 | 38 | @Test 39 | public void testGetUser() throws Exception { 40 | Long userId = 1L; 41 | String mockedUser = fixture("fixtures/gitlab/user.json"); 42 | 43 | wireMockRule.stubFor(get(urlEqualTo("/api/v3/users/" + userId + "?private_token=" + privateToken)) 44 | .willReturn(aResponse() 45 | .withStatus(200) 46 | .withBody(mockedUser))); 47 | 48 | Response response = gitLabService.getUser(userId); 49 | 50 | assertThat(response.code()) 51 | .isEqualTo(200); 52 | 53 | assertThat(response.body()) 54 | .hasId(1L) 55 | .hasUsername("john_smith") 56 | .hasName("John Smith"); 57 | 58 | wireMockRule.verify(getRequestedFor(urlEqualTo("/api/v3/users/" + userId + "?private_token=" + privateToken))); 59 | } 60 | 61 | @Test 62 | public void testGetLoggedUser() throws Exception { 63 | String mockedUser = fixture("fixtures/gitlab/user.json"); 64 | 65 | wireMockRule.stubFor(get(urlEqualTo("/api/v3/user?private_token=" + privateToken)) 66 | .willReturn(aResponse() 67 | .withStatus(200) 68 | .withBody(mockedUser))); 69 | 70 | Response response = gitLabService.getLoggedUser(); 71 | 72 | assertThat(response.code()) 73 | .isEqualTo(200); 74 | 75 | assertThat(response.body()) 76 | .hasId(1L) 77 | .hasUsername("john_smith") 78 | .hasName("John Smith"); 79 | 80 | wireMockRule.verify(getRequestedFor(urlEqualTo("/api/v3/user?private_token=" + privateToken))); 81 | } 82 | 83 | @Test 84 | public void getUserUrl() throws Exception { 85 | String username = "akraxx"; 86 | String url = gitLabService.getUserUrl(username); 87 | 88 | assertThat(url).isEqualTo(gitLabConfiguration.getUrl() + "/u/" + username); 89 | } 90 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/core/auth/GitLabAuthFilter.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.auth; 2 | 3 | import com.google.common.base.Optional; 4 | import com.google.common.io.BaseEncoding; 5 | import io.dropwizard.auth.AuthFilter; 6 | import io.dropwizard.auth.AuthenticationException; 7 | import io.dropwizard.auth.Authenticator; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import javax.ws.rs.InternalServerErrorException; 11 | import javax.ws.rs.WebApplicationException; 12 | import javax.ws.rs.container.ContainerRequestContext; 13 | import javax.ws.rs.core.SecurityContext; 14 | import java.io.IOException; 15 | import java.nio.charset.StandardCharsets; 16 | import java.security.Principal; 17 | 18 | @Slf4j 19 | public class GitLabAuthFilter extends AuthFilter { 20 | 21 | private GitLabAuthFilter() { 22 | } 23 | 24 | @Override 25 | public void filter(ContainerRequestContext requestContext) throws IOException { 26 | final String token = requestContext.getUriInfo().getQueryParameters().getFirst("token"); 27 | try { 28 | if (token != null) { 29 | final String decoded = new String( 30 | BaseEncoding.base64().decode(token), 31 | StandardCharsets.UTF_8); 32 | final int i = decoded.indexOf(':'); 33 | if (i > 0) { 34 | final String username = decoded.substring(0, i); 35 | final String password = decoded.substring(i + 1); 36 | try { 37 | GitLabCredentials gitLabCredentials = new GitLabCredentials(username, password); 38 | final Optional principal = authenticator.authenticate(gitLabCredentials); 39 | if (principal.isPresent()) { 40 | requestContext.setSecurityContext(new SecurityContext() { 41 | @Override 42 | public Principal getUserPrincipal() { 43 | return principal.get(); 44 | } 45 | 46 | @Override 47 | public boolean isUserInRole(String role) { 48 | return authorizer.authorize(principal.get(), role); 49 | } 50 | 51 | @Override 52 | public boolean isSecure() { 53 | return requestContext.getSecurityContext().isSecure(); 54 | } 55 | 56 | @Override 57 | public String getAuthenticationScheme() { 58 | return "GitLab Auth"; 59 | } 60 | }); 61 | return; 62 | } 63 | } catch (AuthenticationException e) { 64 | log.warn("Error authenticating credentials", e); 65 | throw new InternalServerErrorException(); 66 | } 67 | } 68 | } 69 | } catch (IllegalArgumentException e) { 70 | log.warn("Error decoding credentials", e); 71 | } 72 | 73 | throw new WebApplicationException(unauthorizedHandler.buildResponse(prefix, realm)); 74 | } 75 | 76 | /** 77 | * Builder for {@link GitLabAuthFilter}. 78 | *

An {@link Authenticator} must be provided during the building process.

79 | */ 80 | public static class Builder extends 81 | AuthFilterBuilder { 82 | 83 | @Override 84 | protected GitLabAuthFilter newInstance() { 85 | return new GitLabAuthFilter(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/test/java/fr/mmarie/resources/HookResourceTestIT.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.resources; 2 | 3 | import com.codahale.metrics.MetricRegistry; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import fr.mmarie.api.gitlab.Event; 6 | import fr.mmarie.core.IntegrationService; 7 | import fr.mmarie.core.auth.GitLabAuthFilter; 8 | import fr.mmarie.core.auth.GitLabAuthenticator; 9 | import io.dropwizard.auth.AuthDynamicFeature; 10 | import io.dropwizard.auth.AuthValueFactoryProvider; 11 | import io.dropwizard.setup.Environment; 12 | import io.dropwizard.testing.junit.ResourceTestRule; 13 | import org.junit.After; 14 | import org.junit.ClassRule; 15 | import org.junit.Test; 16 | 17 | import javax.validation.Validation; 18 | import javax.ws.rs.client.Entity; 19 | import javax.ws.rs.core.Response; 20 | import java.security.Principal; 21 | import java.util.Base64; 22 | 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | import static org.mockito.Mockito.mock; 25 | import static org.mockito.Mockito.reset; 26 | import static org.mockito.Mockito.timeout; 27 | import static org.mockito.Mockito.times; 28 | import static org.mockito.Mockito.verify; 29 | 30 | public class HookResourceTestIT { 31 | 32 | private static final IntegrationService INTEGRATION_SERVICE = mock(IntegrationService.class); 33 | 34 | private static final String PASSWORD = "test-password"; 35 | 36 | private static final Environment ENVIRONMENT = new Environment("mocked-env", 37 | new ObjectMapper(), 38 | Validation.buildDefaultValidatorFactory().getValidator(), 39 | new MetricRegistry(), 40 | ClassLoader.getSystemClassLoader()); 41 | 42 | @ClassRule 43 | public static final ResourceTestRule resources = ResourceTestRule.builder() 44 | .addResource(new HookResource(INTEGRATION_SERVICE, ENVIRONMENT)) 45 | .addProvider(new AuthDynamicFeature(new GitLabAuthFilter.Builder() 46 | .setAuthenticator(new GitLabAuthenticator(PASSWORD)) 47 | .setUnauthorizedHandler((s, s1) -> Response.status(Response.Status.UNAUTHORIZED).build()) 48 | .setRealm("GitLab HOOK") 49 | .buildAuthFilter())) 50 | .addProvider(new AuthValueFactoryProvider.Binder<>(Principal.class)) 51 | .build(); 52 | 53 | @After 54 | public void tearDown() throws Exception { 55 | reset(INTEGRATION_SERVICE); 56 | } 57 | 58 | @Test 59 | public void hook_WithPushEvent_ShouldPerformIt() throws Exception { 60 | Event event = new Event(Event.Type.PUSH); 61 | Response response = resources.client().target("/hook") 62 | .queryParam("token", Base64.getEncoder().encodeToString(String.format("%s:%s", "test-svc", PASSWORD).getBytes())) 63 | .request() 64 | .post(Entity.json(event)); 65 | 66 | verify(INTEGRATION_SERVICE, timeout(100)).performPushEvent(event); 67 | assertThat(response).isNotNull(); 68 | assertThat(response.getStatus()).isEqualTo(204); 69 | } 70 | 71 | @Test 72 | public void hook_WithTagPushEvent_ShouldPerformIt() throws Exception { 73 | Event event = new Event(Event.Type.TAG_PUSH); 74 | Response response = resources.client().target("/hook") 75 | .queryParam("token", Base64.getEncoder().encodeToString(String.format("%s:%s", "test-svc", PASSWORD).getBytes())) 76 | .request() 77 | .post(Entity.json(event)); 78 | 79 | verify(INTEGRATION_SERVICE, timeout(100)).performPushEvent(event); 80 | assertThat(response).isNotNull(); 81 | assertThat(response.getStatus()).isEqualTo(204); 82 | } 83 | 84 | @Test 85 | public void hook_WithMergeEvent_ShouldDoNothing() throws Exception { 86 | Event event = new Event(Event.Type.MERGE_REQUEST); 87 | Response response = resources.client().target("/hook") 88 | .queryParam("token", Base64.getEncoder().encodeToString(String.format("%s:%s", "test-svc", PASSWORD).getBytes())) 89 | .request() 90 | .post(Entity.json(event)); 91 | 92 | verify(INTEGRATION_SERVICE, times(0)).performPushEvent(event); 93 | assertThat(response).isNotNull(); 94 | assertThat(response.getStatus()).isEqualTo(204); 95 | } 96 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/test/java/fr/mmarie/GitLabJiraApplicationTestIT.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie; 2 | 3 | import com.github.tomakehurst.wiremock.junit.WireMockRule; 4 | import io.dropwizard.testing.ResourceHelpers; 5 | import io.dropwizard.testing.junit.DropwizardAppRule; 6 | import org.junit.ClassRule; 7 | import org.junit.Rule; 8 | import org.junit.Test; 9 | 10 | import javax.ws.rs.client.Client; 11 | import javax.ws.rs.client.ClientBuilder; 12 | import javax.ws.rs.core.Response; 13 | 14 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; 15 | import static com.github.tomakehurst.wiremock.client.WireMock.get; 16 | import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; 17 | import static com.github.tomakehurst.wiremock.client.WireMock.matching; 18 | import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; 19 | import static org.assertj.core.api.StrictAssertions.assertThat; 20 | 21 | public class GitLabJiraApplicationTestIT { 22 | public static final int PORT_JIRA = 1338; 23 | public static final int PORT_GITLAB = 1339; 24 | 25 | @Rule 26 | public WireMockRule wireMockRuleJira = new WireMockRule(PORT_JIRA); 27 | 28 | @Rule 29 | public WireMockRule wireMockRuleGitLab = new WireMockRule(PORT_GITLAB); 30 | 31 | @ClassRule 32 | public static final DropwizardAppRule RULE = 33 | new DropwizardAppRule<>(GitLabJiraApplication.class, ResourceHelpers.resourceFilePath("properties-test.yml")); 34 | 35 | @Test 36 | public void testHealthyHealthChecks() { 37 | wireMockRuleJira.stubFor(get(urlEqualTo("/rest/api/2/serverInfo")) 38 | .withHeader("Authorization", matching("Basic .*")) 39 | .willReturn(aResponse() 40 | .withStatus(200) 41 | .withBody("{}"))); 42 | 43 | wireMockRuleGitLab.stubFor(get(urlEqualTo("/api/v3/user?private_token=" 44 | + RULE.getConfiguration().getGitLabConfiguration().getPrivateToken())) 45 | .willReturn(aResponse() 46 | .withStatus(200) 47 | .withBody("{}"))); 48 | 49 | Client client = ClientBuilder.newClient(); 50 | 51 | Response healthcheck = client.target(String.format("http://localhost:%d/", RULE.getAdminPort())) 52 | .path("healthcheck") 53 | .request() 54 | .get(); 55 | 56 | assertThat(healthcheck.getStatus()) 57 | .isEqualTo(Response.Status.OK.getStatusCode()); 58 | 59 | assertThat(healthcheck.readEntity(String.class)) 60 | .contains("\"jira\":{\"healthy\":true,\"message\":\"Jira server has been contacted successfully\"", 61 | "\"gitlab\":{\"healthy\":true,\"message\":\"GitLab server has been contacted successfully\"}"); 62 | 63 | wireMockRuleJira.verify(getRequestedFor(urlEqualTo("/rest/api/2/serverInfo")) 64 | .withHeader("Authorization", matching("Basic .*"))); 65 | 66 | wireMockRuleGitLab.verify(getRequestedFor(urlEqualTo("/api/v3/user?private_token=" 67 | + RULE.getConfiguration().getGitLabConfiguration().getPrivateToken()))); 68 | } 69 | 70 | @Test 71 | public void testUnhealthyHealthChecks() { 72 | wireMockRuleJira.stubFor(get(urlEqualTo("/rest/api/2/serverInfo")) 73 | .withHeader("Authorization", matching("Basic .*")) 74 | .willReturn(aResponse() 75 | .withStatus(500) 76 | .withBody("{}"))); 77 | 78 | wireMockRuleGitLab.stubFor(get(urlEqualTo("/api/v3/user?private_token=" 79 | + RULE.getConfiguration().getGitLabConfiguration().getPrivateToken())) 80 | .willReturn(aResponse() 81 | .withStatus(500) 82 | .withBody("{}"))); 83 | 84 | Client client = ClientBuilder.newClient(); 85 | 86 | Response healthcheck = client.target(String.format("http://localhost:%d/", RULE.getAdminPort())) 87 | .path("healthcheck") 88 | .request() 89 | .get(); 90 | 91 | assertThat(healthcheck.getStatus()) 92 | .isEqualTo(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); 93 | 94 | assertThat(healthcheck.readEntity(String.class)) 95 | .contains("\"jira\":{\"healthy\":false,\"message\":\"Unable to contact JIRA server, HTTP code received <500>\"", 96 | "\"gitlab\":{\"healthy\":false,\"message\":\"Unable to contact GitLab server, HTTP code received <500>\""); 97 | 98 | wireMockRuleJira.verify(getRequestedFor(urlEqualTo("/rest/api/2/serverInfo")) 99 | .withHeader("Authorization", matching("Basic .*"))); 100 | 101 | wireMockRuleGitLab.verify(getRequestedFor(urlEqualTo("/api/v3/user?private_token=" 102 | + RULE.getConfiguration().getGitLabConfiguration().getPrivateToken()))); 103 | } 104 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gitlab-jira-integration 2 | ======================= 3 | 4 | [![Build Status](https://travis-ci.org/akraxx/gitlab-jira-integration.svg)](https://travis-ci.org/akraxx/gitlab-jira-integration) [![Coverage Status](https://coveralls.io/repos/akraxx/gitlab-jira-integration/badge.svg?branch=master&service=github)](https://coveralls.io/github/akraxx/gitlab-jira-integration?branch=master) [![License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://www.opensource.org/licenses/MIT) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/fr.mmarie/gitlab-jira-integration/badge.svg?style=plastic)](https://maven-badges.herokuapp.com/maven-central/fr.mmarie/gitlab-jira-integration) [![Issue Stats](http://issuestats.com/github/akraxx/gitlab-jira-integration/badge/issue?style=flat)](http://issuestats.com/github/akraxx/gitlab-jira-integration) 5 | 6 | If you are using *GitLab Community Edition* and you want to fully integrate gitlab with JIRA (ie. comment issues), you can use this project. It's a standalone java service which provides endpoints to intercepts hook events from gitlab and use JIRA REST api to interacts with issues. 7 | 8 | Requirements 9 | ============ 10 | 11 | * A JVM (jdk 8) 12 | * An account on JIRA which can comment issues on projects 13 | * An account on gitlab to have a private_token (admin rights are not required) 14 | 15 | How to use 16 | ========== 17 | 18 | * Download the fatjar **gitlab-jira-integration-application** from maven central 19 | * Create a yaml file as follow and configure it 20 | 21 | ```yaml 22 | 23 | server: 24 | applicationConnectors: 25 | - type: http 26 | port: 9090 27 | 28 | adminConnectors: 29 | - type: http 30 | port: 9091 31 | 32 | logging: 33 | appenders: 34 | - type: console 35 | logFormat: "%-5p [%d{ISO8601,UTC}] [%thread] [%c{5}] %m%n%rEx" 36 | 37 | password: test-password 38 | 39 | jira: 40 | username: gitlab 41 | password: gitlab 42 | url: http://localhost:8090 43 | 44 | gitlab: 45 | private_token: N1bJ4n8-rbFAEf8Syrh2 46 | url: http://192.168.59.104:8080 47 | 48 | ``` 49 | 50 | * Launch your JAR like this : ```java -jar gitlab-jira-integration-application.jar server properties.yml``` 51 | 52 | If everything is ok you should have something like this in your console : 53 | 54 | ``` 55 | INFO [2015-10-10 17:48:43,361] [main] [i.d.j.DropwizardResourceConfig] [hookId:-] : The following paths were found for the configured resources: 56 | 57 | POST /hook (fr.mmarie.resources.HookResource) 58 | 59 | INFO [2015-10-10 17:48:43,365] [main] [o.e.j.s.h.ContextHandler] [hookId:-] : Started i.d.j.MutableServletContextHandler@43a0a32d{/,null,AVAILABLE} 60 | INFO [2015-10-10 17:48:43,371] [main] [i.d.s.AdminEnvironment] [hookId:-] : tasks = 61 | 62 | POST /tasks/log-level (io.dropwizard.servlets.tasks.LogConfigurationTask) 63 | POST /tasks/gc (io.dropwizard.servlets.tasks.GarbageCollectionTask) 64 | 65 | INFO [2015-10-10 17:48:43,376] [main] [o.e.j.s.h.ContextHandler] [hookId:-] : Started i.d.j.MutableServletContextHandler@5aaaa446{/,null,AVAILABLE} 66 | INFO [2015-10-10 17:48:43,394] [main] [o.e.j.s.ServerConnector] [hookId:-] : Started application@794b435f{HTTP/1.1}{0.0.0.0:9090} 67 | INFO [2015-10-10 17:48:43,398] [main] [o.e.j.s.ServerConnector] [hookId:-] : Started admin@38f2e97e{HTTP/1.1}{0.0.0.0:9091} 68 | INFO [2015-10-10 17:48:43,399] [main] [o.e.j.s.Server] [hookId:-] : Started @3812ms 69 | ``` 70 | 71 | You need to generate a token to authenticate your hook, format of the token is : 72 | [service:pwd] encoded in Base64, where service is any value you want to identify your gitlab hook, and password is the one defined in the YAML configuration file. 73 | 74 | * Add a new WebHook service in gitlab settings to : ```http://[IP/HOSTNAME]:9090/hook?token=[see above]``` 75 | 76 | * Commit messages with a JIRA issue prefixed by **#** will be mentionnd in issue comments. (For example : **#TESTGIT-1**) 77 | 78 | Transitions 79 | =========== 80 | 81 | Since version **0.5.0** it's possible to perfom transition on JIRA issues (ie Close, Fix...) : 82 | 83 | To do that, you need to add this part to your configuration : 84 | 85 | ``` yaml 86 | jira: 87 | # Username, pwd... 88 | transitions: 89 | - name: Closed 90 | # Case insensitive 91 | keywords: 92 | - closed 93 | - closes 94 | - close 95 | - fixed 96 | - fixes 97 | - fix 98 | ``` 99 | 100 | Example of commit messages working with previous configuration : 101 | 102 | `` Test Closes #TESGITLAB-3 `` 103 | 104 | `` Hello World FIX #TESGITLAB-15 `` 105 | 106 | This message will close both issues : 107 | 108 | `` Hello World FIX #TESGITLAB-25 and Close #TESGITLAB-47 `` 109 | 110 | To edit your transitions in JIRA, take a look at the [jira documentation](https://confluence.atlassian.com/jira/configuring-workflow-185729632.html) 111 | 112 | The change will be also commented in the jira issue activity to notify in which commit the issue has been closed (or any other transition) 113 | 114 | *Note :* The default resolution will be taken, it only manage **transition** 115 | 116 | Dependencies 117 | ============ 118 | 119 | * [Dropwizard](http://www.dropwizard.io/) 120 | * [Retrofit](http://square.github.io/retrofit/) 121 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/test/java/fr/mmarie/core/auth/GitLabAuthFilterTestIT.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.auth; 2 | 3 | import com.codahale.metrics.MetricRegistry; 4 | import io.dropwizard.auth.AuthDynamicFeature; 5 | import io.dropwizard.auth.AuthValueFactoryProvider; 6 | import io.dropwizard.jersey.DropwizardResourceConfig; 7 | import io.dropwizard.logging.BootstrapLogging; 8 | import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature; 9 | import org.glassfish.jersey.servlet.ServletProperties; 10 | import org.glassfish.jersey.test.DeploymentContext; 11 | import org.glassfish.jersey.test.JerseyTest; 12 | import org.glassfish.jersey.test.ServletDeploymentContext; 13 | import org.glassfish.jersey.test.TestProperties; 14 | import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; 15 | import org.glassfish.jersey.test.spi.TestContainerException; 16 | import org.glassfish.jersey.test.spi.TestContainerFactory; 17 | import org.junit.Test; 18 | 19 | import javax.ws.rs.WebApplicationException; 20 | import javax.ws.rs.core.Response; 21 | import java.security.Principal; 22 | import java.util.Base64; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | import static org.assertj.core.api.StrictAssertions.failBecauseExceptionWasNotThrown; 26 | 27 | public class GitLabAuthFilterTestIT extends JerseyTest { 28 | 29 | private static final String PASSWORD = "test-password"; 30 | 31 | public static class AuthTestResourceConfig extends DropwizardResourceConfig { 32 | public AuthTestResourceConfig() { 33 | super(true, new MetricRegistry()); 34 | 35 | register(new AuthDynamicFeature(new GitLabAuthFilter.Builder() 36 | .setAuthenticator(new GitLabAuthenticator(PASSWORD)) 37 | .setUnauthorizedHandler((s, s1) -> Response.status(Response.Status.UNAUTHORIZED).build()) 38 | .setRealm("GitLab HOOK") 39 | .buildAuthFilter())); 40 | 41 | register(new AuthValueFactoryProvider.Binder<>(Principal.class)); 42 | register(RolesAllowedDynamicFeature.class); 43 | register(AuthResource.class); 44 | } 45 | } 46 | 47 | static { 48 | BootstrapLogging.bootstrap(); 49 | } 50 | 51 | @Override 52 | protected TestContainerFactory getTestContainerFactory() 53 | throws TestContainerException { 54 | return new GrizzlyWebTestContainerFactory(); 55 | } 56 | 57 | @Override 58 | protected DeploymentContext configureDeployment() { 59 | forceSet(TestProperties.CONTAINER_PORT, "0"); 60 | return ServletDeploymentContext.builder(new AuthTestResourceConfig()) 61 | .initParam(ServletProperties.JAXRS_APPLICATION_CLASS, AuthTestResourceConfig.class.getName()) 62 | .build(); 63 | } 64 | 65 | @Test 66 | public void resourceWithoutAuthShouldReturn200() { 67 | assertThat(target("/test/noauth").request() 68 | .get(String.class)) 69 | .isEqualTo("hello"); 70 | } 71 | 72 | @Test 73 | public void resourceWithAuthenticationWithCorrectCredentialsReturn200() { 74 | String service = "good-svc"; 75 | assertThat(target("/test/protected") 76 | .queryParam("token", Base64.getEncoder().encodeToString(String.format("%s:%s", service, PASSWORD).getBytes())) 77 | .request() 78 | .get(String.class)) 79 | .isEqualTo("'" + service +"' has user privileges"); 80 | } 81 | 82 | @Test 83 | public void resourceWithAuthenticationWithBadTokenParamReturn401() { 84 | String service = "bad-svc"; 85 | try { 86 | target("/test/protected") 87 | .queryParam("token", Base64.getEncoder().encodeToString(String.format("%s:%s", service, "bad-pwd").getBytes())) 88 | .request() 89 | .get(String.class); 90 | failBecauseExceptionWasNotThrown(WebApplicationException.class); 91 | } catch (WebApplicationException e) { 92 | assertThat(e.getResponse().getStatus()).isEqualTo(401); 93 | } 94 | } 95 | 96 | @Test 97 | public void resourceWithAuthenticationWithTokenPatternParamReturn401() { 98 | String service = "bad-svc"; 99 | try { 100 | target("/test/protected") 101 | .queryParam("token", Base64.getEncoder().encodeToString(String.format("%s %s", service, PASSWORD).getBytes())) 102 | .request() 103 | .get(String.class); 104 | failBecauseExceptionWasNotThrown(WebApplicationException.class); 105 | } catch (WebApplicationException e) { 106 | assertThat(e.getResponse().getStatus()).isEqualTo(401); 107 | } 108 | } 109 | 110 | @Test 111 | public void resourceWithAuthenticationWithoutTokenParamReturn401() { 112 | try { 113 | target("/test/protected") 114 | .request() 115 | .get(String.class); 116 | failBecauseExceptionWasNotThrown(WebApplicationException.class); 117 | } catch (WebApplicationException e) { 118 | assertThat(e.getResponse().getStatus()).isEqualTo(401); 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-application/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | gitlab-jira-integration 5 | fr.mmarie 6 | 0.5.0-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | gitlab-jira-integration-application 11 | 12 | 13 | 2.0.0-beta2 14 | 3.1.0 15 | 16 | 17 | 1.57 18 | 19 | 20 | 2.4.1 21 | 1.4.0 22 | 23 | 24 | 25 | 26 | fr.mmarie 27 | gitlab-jira-integration-api 28 | ${project.version} 29 | 30 | 31 | com.squareup.retrofit 32 | retrofit 33 | ${retrofit.version} 34 | 35 | 36 | com.squareup.retrofit 37 | adapter-rxjava 38 | ${retrofit.version} 39 | 40 | 41 | com.squareup.retrofit 42 | converter-jackson 43 | ${retrofit.version} 44 | 45 | 46 | com.fasterxml.jackson.core 47 | jackson-databind 48 | 49 | 50 | 51 | 52 | io.dropwizard 53 | dropwizard-core 54 | ${dropwizard.version} 55 | 56 | 57 | io.dropwizard 58 | dropwizard-auth 59 | ${dropwizard.version} 60 | 61 | 62 | ru.vyarus 63 | dropwizard-guicey 64 | ${dropwizard-guicey.version} 65 | 66 | 67 | 68 | com.github.tomakehurst 69 | wiremock 70 | ${wiremock.version} 71 | test 72 | 73 | 74 | org.glassfish.jersey.test-framework 75 | jersey-test-framework-core 76 | ${jersey.version} 77 | test 78 | 79 | 80 | org.glassfish.jersey.test-framework.providers 81 | jersey-test-framework-provider-grizzly2 82 | ${jersey.version} 83 | test 84 | 85 | 86 | 87 | 88 | 89 | 90 | org.apache.maven.plugins 91 | maven-shade-plugin 92 | ${maven-shade-plugin.version} 93 | 94 | 95 | package 96 | 97 | shade 98 | 99 | 100 | 101 | 102 | 103 | fr.mmarie.GitLabJiraApplication 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | org.codehaus.mojo 112 | exec-maven-plugin 113 | ${exec-maven-plugin.version} 114 | 115 | 116 | 117 | java 118 | 119 | 120 | 121 | 122 | fr.mmarie.GitLabJiraApplication 123 | 124 | server 125 | properties.yml 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/core/IntegrationService.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.base.Strings; 5 | import com.google.common.collect.ArrayListMultimap; 6 | import com.google.common.collect.Multimap; 7 | import com.google.inject.Inject; 8 | import fr.mmarie.api.gitlab.Commit; 9 | import fr.mmarie.api.gitlab.Event; 10 | import fr.mmarie.api.gitlab.User; 11 | import fr.mmarie.api.jira.Comment; 12 | import fr.mmarie.core.gitlab.GitLabService; 13 | import fr.mmarie.core.jira.JiraService; 14 | import lombok.extern.slf4j.Slf4j; 15 | import retrofit.Response; 16 | 17 | import java.io.IOException; 18 | import java.text.DateFormat; 19 | import java.text.SimpleDateFormat; 20 | import java.util.Collection; 21 | import java.util.Date; 22 | 23 | @Slf4j 24 | public class IntegrationService { 25 | public static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd-yyyy HH:mm:ss"); 26 | 27 | private final GitLabService gitLabService; 28 | private final JiraService jiraService; 29 | 30 | @Inject 31 | public IntegrationService(GitLabService gitLabService, JiraService jiraService) { 32 | this.gitLabService = gitLabService; 33 | this.jiraService = jiraService; 34 | } 35 | 36 | public String getDateAsString(Date date) { 37 | if (date == null) { 38 | return "unknown date of commit"; 39 | } else { 40 | return DATE_FORMAT.format(date); 41 | } 42 | } 43 | 44 | public String buildComment(User user, String repositoryName, Commit commit) { 45 | if(Strings.isNullOrEmpty(user.getUsername())) { 46 | return String.format("%s mentioned this issue in [a commit of %s|%s] \r\n " 47 | + "*%s* : %s", 48 | user.getName(), repositoryName, commit.getUrl(), 49 | getDateAsString(commit.getTimestamp()), commit.getMessage()); 50 | } else { 51 | return String.format("[%s|%s] mentioned this issue in [a commit of %s|%s] \r\n" 52 | + "*%s* : %s", 53 | user.getName(), gitLabService.getUserUrl(user.getUsername()), repositoryName, commit.getUrl(), 54 | getDateAsString(commit.getTimestamp()), commit.getMessage()); 55 | } 56 | } 57 | 58 | public String buildCommentForTransition(User user, String repositoryName, Commit commit) { 59 | if(Strings.isNullOrEmpty(user.getUsername())) { 60 | return String.format("%s changed status of this issue in [a commit of %s|%s] to *%s* \r\n\r\n " 61 | + "*%s* : %s", 62 | user.getName(), repositoryName, commit.getUrl(), 63 | JiraService.TRANSITION_HOLDER, 64 | getDateAsString(commit.getTimestamp()), commit.getMessage()); 65 | } else { 66 | return String.format("[%s|%s] changed status of this issue in [a commit of %s|%s] to *%s* \r\n\r\n" 67 | + "*%s* : %s", 68 | user.getName(), gitLabService.getUserUrl(user.getUsername()), repositoryName, commit.getUrl(), 69 | JiraService.TRANSITION_HOLDER, 70 | getDateAsString(commit.getTimestamp()), commit.getMessage()); 71 | } 72 | } 73 | 74 | public void commentIssue(String repositoryName, User user, Collection commits, String issue) { 75 | if(jiraService.isExistingIssue(issue)) { 76 | commits.forEach(commit -> { 77 | if(!jiraService.isIssueAlreadyCommented(issue, commit.getId())) { 78 | try { 79 | log.info("Comment issue <{}> for commit <{}>", issue, commit); 80 | if(!jiraService.performTransition(commit.getMessage(), issue, 81 | buildCommentForTransition(user, 82 | repositoryName, 83 | commit))) { 84 | jiraService.commentIssue(issue, 85 | new Comment(buildComment(user, 86 | repositoryName, 87 | commit))); 88 | } 89 | } catch (IOException e) { 90 | log.error("Unable to comment issue <{}>", issue, e); 91 | } 92 | } else { 93 | log.warn("Issue <{}> has already been commented for commit <{}>", 94 | issue, 95 | commit.getId()); 96 | } 97 | }); 98 | } else { 99 | log.warn("Issue <{}> has been mentioned, but does not exists", issue); 100 | } 101 | } 102 | 103 | public void performPushEvent(Event event) { 104 | Preconditions.checkNotNull(event.getCommits(), "commits array can not be null"); 105 | // For each commit, extract jira issues 106 | Multimap jiraIssues = ArrayListMultimap.create(); 107 | event.getCommits().forEach(commit -> 108 | jiraService.extractIssuesFromMessage(commit.getMessage()) 109 | .forEach(issue -> jiraIssues.put(issue, commit))); 110 | 111 | if(jiraIssues.size() > 0) { 112 | User user = getUser(event); 113 | 114 | jiraIssues.asMap().forEach((issue, commits) -> commentIssue(event.getRepository().getName(), user, commits, issue)); 115 | } 116 | } 117 | 118 | public User getUser(Event event) { 119 | try { 120 | Response userResponse = gitLabService.getUser(event.getUserId()); 121 | Preconditions.checkArgument(userResponse.code() == 200); 122 | 123 | return userResponse.body(); 124 | } catch (Exception e) { 125 | log.error("Unable to get gitlab user with id <{}>, comment issue with pusher username <{}>", 126 | event.getUserId(), 127 | event.getUserName()); 128 | return new User(event.getUserId(), null, event.getUserName()); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/test/java/fr/mmarie/core/jira/JiraServiceTest.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.jira; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import fr.mmarie.api.jira.Comment; 5 | import fr.mmarie.api.jira.Transition; 6 | import fr.mmarie.api.jira.input.TransitionInput; 7 | import fr.mmarie.api.jira.response.TransitionResponse; 8 | import org.assertj.core.data.MapEntry; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.mockito.ArgumentCaptor; 12 | 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Optional; 16 | 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | import static org.mockito.Matchers.any; 19 | import static org.mockito.Matchers.eq; 20 | import static org.mockito.Mockito.doReturn; 21 | import static org.mockito.Mockito.spy; 22 | import static org.mockito.Mockito.times; 23 | import static org.mockito.Mockito.verify; 24 | import static org.mockito.Mockito.when; 25 | 26 | public class JiraServiceTest { 27 | 28 | public static final int PORT = 1520; 29 | 30 | public JiraConfiguration jiraConfiguration = new JiraConfiguration("username", 31 | "password", 32 | String.format("http://localhost:%d", PORT), 33 | ImmutableList.of(new TransitionConfiguration("Close", ImmutableList.of("close", "fix")), 34 | new TransitionConfiguration("Start Progress", ImmutableList.of("starts", "starting")))); 35 | 36 | public JiraService jiraService; 37 | 38 | @Before 39 | public void setUp() throws Exception { 40 | jiraService = new JiraService(jiraConfiguration); 41 | } 42 | 43 | @Test 44 | public void extractIssuesFromMessageWithoutIssue() throws Exception { 45 | String message = "test: no issue"; 46 | 47 | final List issues = jiraService.extractIssuesFromMessage(message); 48 | 49 | assertThat(issues) 50 | .hasSize(0); 51 | } 52 | 53 | @Test 54 | public void extractIssuesFromMessageWithOneIssue() throws Exception { 55 | String message = "test(#TEST-1): single issue"; 56 | 57 | final List issues = jiraService.extractIssuesFromMessage(message); 58 | 59 | assertThat(issues) 60 | .hasSize(1) 61 | .containsExactly("TEST-1"); 62 | } 63 | 64 | @Test 65 | public void extractIssuesFromMessageWithMoreThanOneIssue() throws Exception { 66 | String message = "test(#TEST-1): issue related to #TEST-15289"; 67 | 68 | final List issues = jiraService.extractIssuesFromMessage(message); 69 | 70 | assertThat(issues) 71 | .hasSize(2) 72 | .containsExactly("TEST-1", "TEST-15289"); 73 | } 74 | 75 | @Test 76 | public void extractMatchingTransitionsFromMessageWithoutTransition() throws Exception { 77 | String message = "test: no issue"; 78 | 79 | final Map matchingTransitions = jiraService.extractMatchingTransitionsFromMessage(message); 80 | 81 | assertThat(matchingTransitions) 82 | .isEmpty(); 83 | } 84 | 85 | @Test 86 | public void extractMatchingTransitionsFromMessageWithOneTransition() throws Exception { 87 | String message = "test: close #TEST-15289"; 88 | 89 | final Map matchingTransitions = jiraService.extractMatchingTransitionsFromMessage(message); 90 | 91 | assertThat(matchingTransitions) 92 | .hasSize(1); 93 | assertThat(matchingTransitions) 94 | .containsOnly(MapEntry.entry("TEST-15289", "Close")); 95 | } 96 | 97 | @Test 98 | public void extractMatchingTransitionsFromMessageCaseInsensitive() throws Exception { 99 | String message = "test: FIX #TEST-15289"; 100 | 101 | final Map matchingTransitions = jiraService.extractMatchingTransitionsFromMessage(message); 102 | 103 | assertThat(matchingTransitions) 104 | .hasSize(1); 105 | assertThat(matchingTransitions) 106 | .containsOnly(MapEntry.entry("TEST-15289", "Close")); 107 | } 108 | 109 | @Test 110 | public void extractMatchingTransitionsFromMessageWithTwoTransitionResturnTheFirstOne() throws Exception { 111 | String message = "test: Close #TEST-15289 and FIX #TEST-52"; 112 | 113 | final Map matchingTransitions = jiraService.extractMatchingTransitionsFromMessage(message); 114 | 115 | assertThat(matchingTransitions) 116 | .hasSize(2); 117 | assertThat(matchingTransitions) 118 | .containsOnly(MapEntry.entry("TEST-15289", "Close"), MapEntry.entry("TEST-52", "Close")); 119 | } 120 | 121 | @Test 122 | public void performTransitionWithoutKeyword() throws Exception { 123 | jiraService = spy(jiraService); 124 | 125 | String message = "dummy"; 126 | String issue = "TESGITLAB-1"; 127 | 128 | doReturn(Optional.empty()).when(jiraService).extractMatchingTransitionsFromMessage(message, issue); 129 | 130 | jiraService.performTransition(message, issue, "Hello"); 131 | 132 | verify(jiraService, times(1)).extractMatchingTransitionsFromMessage(message, issue); 133 | verify(jiraService, times(0)).transitionOnIssue(eq(issue), any(TransitionInput.class)); 134 | } 135 | 136 | @Test 137 | public void performTransitionWithAnUnknownTransition() throws Exception { 138 | jiraService = spy(jiraService); 139 | 140 | String issue = "TESGITLAB-1"; 141 | String message = "dummy closes #" + issue; 142 | 143 | String transitionName = "close"; 144 | doReturn(Optional.of(transitionName)).when(jiraService).extractMatchingTransitionsFromMessage(message, issue); 145 | doReturn(Optional.empty()).when(jiraService).getTransition(issue, transitionName); 146 | 147 | jiraService.performTransition(message, issue, "Hello"); 148 | 149 | verify(jiraService, times(1)).extractMatchingTransitionsFromMessage(message, issue); 150 | verify(jiraService, times(0)).transitionOnIssue(eq(issue), any(TransitionInput.class)); 151 | } 152 | 153 | @Test 154 | public void performTransitionWithARightTransition() throws Exception { 155 | jiraService = spy(jiraService); 156 | 157 | ArgumentCaptor transitionInputArgumentCaptor = ArgumentCaptor.forClass(TransitionInput.class); 158 | 159 | String issue = "TESGITLAB-1"; 160 | String message = "dummy closes #" + issue; 161 | 162 | String transitionName = "close"; 163 | doReturn(Optional.of(transitionName)).when(jiraService).extractMatchingTransitionsFromMessage(message, issue); 164 | Transition transition = new Transition(15L, "Close"); 165 | doReturn(Optional.of(transition)).when(jiraService).getTransition(issue, transitionName); 166 | 167 | jiraService.performTransition(message, issue, "Hello " + JiraService.TRANSITION_HOLDER); 168 | 169 | verify(jiraService, times(1)).extractMatchingTransitionsFromMessage(message, issue); 170 | verify(jiraService, times(1)).transitionOnIssue(eq(issue), transitionInputArgumentCaptor.capture()); 171 | 172 | TransitionInput transitionInput = transitionInputArgumentCaptor.getValue(); 173 | 174 | assertThat(transitionInput.getTransition()).isEqualTo(transition); 175 | assertThat(transitionInput.getUpdate().getComments().size()).isEqualTo(1); 176 | 177 | assertThat(transitionInput.getUpdate().getComments().get(0).getComment()) 178 | .isEqualTo(new Comment("Hello " + transitionName)); 179 | } 180 | 181 | @Test 182 | public void performTransitionWithMultipleTransitions() throws Exception { 183 | jiraService = spy(jiraService); 184 | 185 | ArgumentCaptor transitionInputArgumentCaptor = ArgumentCaptor.forClass(TransitionInput.class); 186 | 187 | String issue = "TESGITLAB-1"; 188 | Transition startProgressTransition = new Transition(2L, "Start Progress"); 189 | Transition closeTransition = new Transition(1L, "Close"); 190 | doReturn(new TransitionResponse(ImmutableList.of(closeTransition, startProgressTransition))) 191 | .when(jiraService).getTransitionsOfIssue(issue); 192 | String message = "dummy starts #" + issue; 193 | 194 | String transitionName = "Start Progress"; 195 | doReturn(Optional.of(transitionName)).when(jiraService).extractMatchingTransitionsFromMessage(message, issue); 196 | 197 | jiraService.performTransition(message, issue, "Hello " + JiraService.TRANSITION_HOLDER); 198 | 199 | verify(jiraService, times(1)).extractMatchingTransitionsFromMessage(message, issue); 200 | verify(jiraService, times(1)).transitionOnIssue(eq(issue), transitionInputArgumentCaptor.capture()); 201 | 202 | TransitionInput transitionInput = transitionInputArgumentCaptor.getValue(); 203 | 204 | assertThat(transitionInput.getTransition()).isEqualTo(startProgressTransition); 205 | assertThat(transitionInput.getUpdate().getComments().size()).isEqualTo(1); 206 | 207 | assertThat(transitionInput.getUpdate().getComments().get(0).getComment()) 208 | .isEqualTo(new Comment("Hello " + transitionName)); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/test/java/fr/mmarie/core/IntegrationServiceTest.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core; 2 | 3 | import fr.mmarie.api.gitlab.Commit; 4 | import fr.mmarie.api.gitlab.Event; 5 | import fr.mmarie.api.gitlab.Repository; 6 | import fr.mmarie.api.gitlab.User; 7 | import fr.mmarie.api.jira.Comment; 8 | import fr.mmarie.core.gitlab.GitLabService; 9 | import fr.mmarie.core.jira.JiraService; 10 | import org.junit.After; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.mockito.ArgumentCaptor; 15 | import org.mockito.Mock; 16 | import org.mockito.runners.MockitoJUnitRunner; 17 | import retrofit.Response; 18 | 19 | import java.io.IOException; 20 | import java.util.Arrays; 21 | import java.util.Collections; 22 | import java.util.Date; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | import static org.mockito.Matchers.any; 26 | import static org.mockito.Matchers.eq; 27 | import static org.mockito.Mockito.reset; 28 | import static org.mockito.Mockito.spy; 29 | import static org.mockito.Mockito.times; 30 | import static org.mockito.Mockito.verify; 31 | import static org.mockito.Mockito.when; 32 | 33 | @RunWith(MockitoJUnitRunner.class) 34 | public class IntegrationServiceTest { 35 | 36 | @Mock 37 | private GitLabService gitLabService; 38 | @Mock 39 | private JiraService jiraService; 40 | 41 | private User user = new User(1L, "John Smith", "john.smith@mocked.com"); 42 | 43 | private Response mockedUserResponse = Response.success(user); 44 | 45 | private IntegrationService service; 46 | 47 | @Before 48 | public void setUp() throws Exception { 49 | service = new IntegrationService(gitLabService, jiraService); 50 | } 51 | 52 | @After 53 | public void tearDown() throws Exception { 54 | reset(gitLabService, jiraService); 55 | } 56 | 57 | @Test 58 | public void commentExistingIssue() throws Exception { 59 | String issue = "TESTGITLAB-1"; 60 | String repository = "test-repo"; 61 | String commitId = "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327"; 62 | 63 | ArgumentCaptor commentArgumentCaptor = ArgumentCaptor.forClass(Comment.class); 64 | when(jiraService.isExistingIssue(issue)).thenReturn(true); 65 | when(jiraService.isIssueAlreadyCommented(issue, commitId)).thenReturn(false); 66 | 67 | service.commentIssue(repository, 68 | new User(1L, "John Smith", "john.smith@mocked.com"), 69 | Collections.singletonList(Commit.builder().id(commitId).build()), 70 | issue); 71 | 72 | verify(jiraService, times(1)).commentIssue(eq(issue), commentArgumentCaptor.capture()); 73 | Comment comment = commentArgumentCaptor.getValue(); 74 | assertThat(comment.getBody()) 75 | .contains("unknown date of commit"); 76 | } 77 | 78 | @Test 79 | public void commentExistingIssueWithoutUsername() throws Exception { 80 | String issue = "TESTGITLAB-1"; 81 | String repository = "test-repo"; 82 | String commitId = "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327"; 83 | 84 | ArgumentCaptor commentArgumentCaptor = ArgumentCaptor.forClass(Comment.class); 85 | when(jiraService.isExistingIssue(issue)).thenReturn(true); 86 | when(jiraService.isIssueAlreadyCommented(issue, commitId)).thenReturn(false); 87 | 88 | String message = "Very nice commit !"; 89 | String url = "http://test.gitlab.fr/"; 90 | Date date = new Date(); 91 | 92 | service.commentIssue(repository, 93 | new User(1L, null, "john.smith@mocked.com"), 94 | Collections.singletonList(Commit.builder() 95 | .id(commitId) 96 | .message(message) 97 | .url(url) 98 | .timestamp(date) 99 | .build()), 100 | issue); 101 | 102 | verify(jiraService, times(1)).commentIssue(eq(issue), commentArgumentCaptor.capture()); 103 | Comment comment = commentArgumentCaptor.getValue(); 104 | assertThat(comment.getBody()) 105 | .contains("john.smith@mocked.com", repository, message, url, IntegrationService.DATE_FORMAT.format(date), message); 106 | } 107 | 108 | @Test 109 | public void commentNonExistingIssue() throws Exception { 110 | String issue = "TESTGITLAB-1"; 111 | String repository = "test-repo"; 112 | String commitId = "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327"; 113 | 114 | when(jiraService.isExistingIssue(issue)).thenReturn(false); 115 | when(jiraService.isIssueAlreadyCommented(issue, commitId)).thenReturn(false); 116 | 117 | service.commentIssue(repository, 118 | new User(1L, "John Smith", "john.smith@mocked.com"), 119 | Collections.singletonList(Commit.builder().id(commitId).build()), 120 | issue); 121 | 122 | verify(jiraService, times(0)).commentIssue(eq(issue), any(Comment.class)); 123 | } 124 | 125 | @Test 126 | public void commentExistingIssueWithUnavailableJira() throws Exception { 127 | String issue = "TESTGITLAB-1"; 128 | String repository = "test-repo"; 129 | String commitId = "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327"; 130 | 131 | when(jiraService.isExistingIssue(issue)).thenReturn(true); 132 | when(jiraService.isIssueAlreadyCommented(issue, commitId)).thenReturn(false); 133 | when(jiraService.commentIssue(eq(issue), any(Comment.class))) 134 | .thenThrow(new IOException()); 135 | 136 | service.commentIssue(repository, 137 | new User(1L, "John Smith", "john.smith@mocked.com"), 138 | Collections.singletonList(Commit.builder().id(commitId).build()), 139 | issue); 140 | 141 | verify(jiraService, times(1)).commentIssue(eq(issue), any(Comment.class)); 142 | } 143 | 144 | @Test 145 | public void issueShouldNotBeCommentedWhenItsAlreadyIs() throws IOException { 146 | String issue = "TESTGITLAB-1"; 147 | String repository = "test-repo"; 148 | String commitId = "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327"; 149 | 150 | when(jiraService.isExistingIssue(issue)).thenReturn(true); 151 | when(jiraService.isIssueAlreadyCommented(issue, commitId)).thenReturn(true); 152 | 153 | service.commentIssue(repository, 154 | new User(1L, "John Smith", "john.smith@mocked.com"), 155 | Collections.singletonList(Commit.builder().id(commitId).build()), 156 | issue); 157 | 158 | verify(jiraService, times(0)).commentIssue(eq(issue), any(Comment.class)); 159 | } 160 | 161 | @Test 162 | public void performPushEventShouldCheckCommit() throws IOException { 163 | service = spy(service); 164 | 165 | String commitId1 = "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327qgh"; 166 | Commit commit1 = Commit.builder() 167 | .message("#GITLAB-1") 168 | .id(commitId1) 169 | .build(); 170 | String commitId2 = "b6iib8db1bc1doqzjbj4d5a946b0b91f9dacd73qzd5"; 171 | Commit commit2 = Commit.builder() 172 | .message("#GITLAB-1,#GITLAB-2") 173 | .id(commitId2) 174 | .build(); 175 | 176 | when(jiraService.extractIssuesFromMessage("#GITLAB-1")).thenReturn(Collections.singletonList("GITLAB-1")); 177 | when(jiraService.extractIssuesFromMessage("#GITLAB-1,#GITLAB-2")).thenReturn(Arrays.asList("GITLAB-1", "GITLAB-2")); 178 | 179 | long userId = 1L; 180 | String repositoryName = "test-repo"; 181 | Event event = Event.builder() 182 | .type(Event.Type.PUSH) 183 | .userId(userId) 184 | .repository(Repository.builder().name(repositoryName).build()) 185 | .commits(Arrays.asList(commit1, commit2)) 186 | .build(); 187 | 188 | service.performPushEvent(event); 189 | 190 | verify(service, times(1)).commentIssue(eq(repositoryName), any(User.class), eq(Arrays.asList(commit1, commit2)), eq("GITLAB-1")); 191 | verify(service, times(1)).commentIssue(eq(repositoryName), any(User.class), eq(Collections.singletonList(commit2)), eq("GITLAB-2")); 192 | } 193 | 194 | @Test 195 | public void performPushEventWithoutMentionedIssuesShouldNotCallGetUserMethod() throws IOException { 196 | service = spy(service); 197 | String repositoryName = "test-repo"; 198 | 199 | long userId = 1L; 200 | Event event = Event.builder() 201 | .type(Event.Type.PUSH) 202 | .userId(userId) 203 | .repository(Repository.builder().name(repositoryName).build()) 204 | .commits(Collections.emptyList()) 205 | .build(); 206 | 207 | service.performPushEvent(event); 208 | 209 | verify(service, times(0)).getUser(event); 210 | } 211 | 212 | @Test 213 | public void getUserWhenGitLabServiceIsAvailable() throws Exception { 214 | Long userId = 10L; 215 | User user = new User(userId, "John Smith", "john.smith@mocked.com"); 216 | when(gitLabService.getUser(userId)).thenReturn(Response.success(user)); 217 | 218 | assertThat(service.getUser(Event.builder().userId(userId).build())).isEqualTo(user); 219 | 220 | verify(gitLabService, times(1)).getUser(userId); 221 | } 222 | } -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/main/java/fr/mmarie/core/jira/JiraService.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.jira; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.Lists; 5 | import com.google.common.collect.Maps; 6 | import com.google.inject.Inject; 7 | import com.squareup.okhttp.OkHttpClient; 8 | import com.squareup.okhttp.Request; 9 | import fr.mmarie.api.jira.Comment; 10 | import fr.mmarie.api.jira.Transition; 11 | import fr.mmarie.api.jira.input.TransitionInput; 12 | import fr.mmarie.api.jira.response.CommentResponse; 13 | import fr.mmarie.api.jira.response.TransitionResponse; 14 | import lombok.NonNull; 15 | import lombok.extern.slf4j.Slf4j; 16 | import retrofit.Callback; 17 | import retrofit.JacksonConverterFactory; 18 | import retrofit.Response; 19 | import retrofit.Retrofit; 20 | import retrofit.RxJavaCallAdapterFactory; 21 | 22 | import java.io.IOException; 23 | import java.util.Base64; 24 | import java.util.List; 25 | import java.util.Map; 26 | import java.util.Optional; 27 | import java.util.regex.Matcher; 28 | import java.util.regex.Pattern; 29 | 30 | import static fr.mmarie.utils.Common.sanitizeURL; 31 | 32 | @Slf4j 33 | public class JiraService { 34 | public static final String TRANSITION_HOLDER = "{transition}"; 35 | 36 | /** 37 | * Matches issues with name like : #TEST-1, does not support special characters in 38 | * project name, #TEST-TEST-1 won't match. 39 | */ 40 | public static final Pattern ISSUE_PATTERN = Pattern.compile("#\\s*(\\w+-\\d+)"); 41 | 42 | private final JiraConfiguration jiraConfiguration; 43 | 44 | private final JiraEndPoints jiraEndPoints; 45 | 46 | @Inject 47 | public JiraService(@NonNull JiraConfiguration jiraConfiguration) { 48 | this.jiraConfiguration = jiraConfiguration; 49 | 50 | OkHttpClient httpClient = new OkHttpClient(); 51 | httpClient.interceptors().add(chain -> { 52 | String credentials = jiraConfiguration.getUsername() + ":" + jiraConfiguration.getPassword(); 53 | String encodedHeader = "Basic " + new String(Base64.getEncoder().encode(credentials.getBytes())); 54 | 55 | Request requestWithAuthorization = chain.request().newBuilder().addHeader("Authorization", encodedHeader).build(); 56 | return chain.proceed(requestWithAuthorization); 57 | }); 58 | 59 | this.jiraEndPoints = new Retrofit.Builder() 60 | .baseUrl(sanitizeURL(jiraConfiguration.getUrl())) 61 | .addConverterFactory(JacksonConverterFactory.create()) 62 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 63 | .client(httpClient) 64 | .build() 65 | .create(JiraEndPoints.class); 66 | } 67 | 68 | public Response getIssue(String issue) throws IOException { 69 | return jiraEndPoints.getIssue(issue).execute(); 70 | } 71 | 72 | public Response getCommentsOfIssue(String issue) throws IOException { 73 | return jiraEndPoints.getCommentsOfIssue(issue).execute(); 74 | } 75 | 76 | public Response commentIssue(String issue, Comment comment) throws IOException { 77 | return jiraEndPoints.commentIssue(issue, comment).execute(); 78 | } 79 | 80 | public TransitionResponse getTransitionsOfIssue(String issue) { 81 | return jiraEndPoints.getTransitionsOfIssue(issue).toBlocking().firstOrDefault(null); 82 | } 83 | 84 | public boolean transitionOnIssue(String issue, TransitionInput transitionInput) { 85 | try { 86 | Response response = jiraEndPoints.transitionsOnIssue(issue, transitionInput).execute(); 87 | if(response.isSuccess()) { 88 | log.info("Transition {} has been made on {}", transitionInput, issue); 89 | return true; 90 | } else { 91 | log.error("Bad response received <" + response.code() + ">, " 92 | + "unable to make the transition <{}> on <{}>, received message : " + response.message(), 93 | issue, 94 | transitionInput); 95 | 96 | try { 97 | log.error("Response body received : {}", 98 | response.errorBody().string()); 99 | } catch (IOException e) { 100 | log.error("Unable to read response body", e); 101 | } 102 | } 103 | } catch (IOException e) { 104 | log.error("Unable to perform transition <{}> on <{}>", transitionInput, issue); 105 | } 106 | 107 | return false; 108 | } 109 | 110 | public Response> serverInfo() throws IOException { 111 | return jiraEndPoints.serverInfo().execute(); 112 | } 113 | 114 | /** 115 | * Extracts issues names from given {@code message}. 116 | * 117 | * @param message Commit message 118 | * @return Matching issues name 119 | */ 120 | public List extractIssuesFromMessage(String message) { 121 | List issues = Lists.newArrayList(); 122 | 123 | Matcher matcher = ISSUE_PATTERN.matcher(message); 124 | 125 | while (matcher.find()) { 126 | issues.add(matcher.group(1)); 127 | } 128 | 129 | return issues; 130 | } 131 | 132 | /** 133 | * Extracts all transitions from given {@code message}. 134 | * 135 | * @param message Commit message 136 | * @return Matching [ISSUE, TRANSITION] 137 | */ 138 | public Map extractMatchingTransitionsFromMessage(String message) { 139 | Map matchingTransition = Maps.newHashMap(); 140 | 141 | List issues = extractIssuesFromMessage(message); 142 | 143 | issues.forEach(issue -> { 144 | Optional optionalTransition = extractMatchingTransitionsFromMessage(message, issue); 145 | if(optionalTransition.isPresent()) { 146 | matchingTransition.put(issue, optionalTransition.get()); 147 | } 148 | }); 149 | 150 | return matchingTransition; 151 | } 152 | 153 | /** 154 | * Extracts first matching transition from given {@code message} related to the given issue. 155 | * 156 | * @param message Commit message 157 | * @return Matching transition if it exists 158 | */ 159 | public Optional extractMatchingTransitionsFromMessage(String message, String issue) { 160 | List transitions = jiraConfiguration.getTransitions(); 161 | 162 | for (TransitionConfiguration transition : transitions) { 163 | for (String keyword : transition.getKeywords()) { 164 | String regex = keyword.toLowerCase() + " #" + issue.toLowerCase(); 165 | Matcher matcher = Pattern.compile(regex).matcher(message.toLowerCase()); 166 | 167 | if(matcher.find()) { 168 | return Optional.of(transition.getName()); 169 | } 170 | } 171 | } 172 | 173 | return Optional.empty(); 174 | } 175 | 176 | public boolean performTransition(String message, String issue, String comment) { 177 | Optional optionalTransitionName = extractMatchingTransitionsFromMessage(message, issue); 178 | 179 | if(optionalTransitionName.isPresent()) { 180 | String transitionName = optionalTransitionName.get(); 181 | Optional optionalTransition = getTransition(issue, transitionName); 182 | 183 | if(optionalTransition.isPresent()) { 184 | log.info("Performing transition <{}> for issue <{}>", transitionName, issue); 185 | TransitionInput.CommentWrapper commentWrapper = 186 | new TransitionInput.CommentWrapper(new Comment(comment.replace(TRANSITION_HOLDER, transitionName))); 187 | 188 | final TransitionInput transitionInput = new TransitionInput( 189 | new TransitionInput.Update(ImmutableList.of(commentWrapper)), 190 | optionalTransition.get() 191 | ); 192 | 193 | return transitionOnIssue(issue, transitionInput); 194 | } else { 195 | log.warn("Transaction <{}> does not exists, issue <{}> can not be edited", 196 | transitionName, 197 | issue); 198 | } 199 | } 200 | 201 | return false; 202 | } 203 | 204 | public boolean isExistingIssue(String issue) { 205 | try { 206 | return (getIssue(issue).code() == 200); 207 | } catch (Exception e) { 208 | log.error("Unable to get issue <{}>", issue, e); 209 | return false; 210 | } 211 | } 212 | 213 | public Optional getTransition(String issue, String name) { 214 | return getTransitionsOfIssue(issue) 215 | .getTransitions() 216 | .stream() 217 | .filter(transition -> transition.getName().equalsIgnoreCase(name)) 218 | .findFirst(); 219 | } 220 | 221 | public boolean isExistingTransition(String issue, String name) { 222 | return getTransition(issue, name).isPresent(); 223 | } 224 | 225 | public boolean isIssueAlreadyCommented(String issue, String commitId) { 226 | try { 227 | List comments = getCommentsOfIssue(issue).body().getComments(); 228 | return comments.stream().anyMatch(comment -> comment.getBody().contains(commitId)); 229 | } catch (IOException e) { 230 | return true; 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /gitlab-jira-integration-application/src/test/java/fr/mmarie/core/jira/JiraServiceTestIT.java: -------------------------------------------------------------------------------- 1 | package fr.mmarie.core.jira; 2 | 3 | import com.github.tomakehurst.wiremock.http.Fault; 4 | import com.github.tomakehurst.wiremock.junit.WireMockRule; 5 | import com.google.common.base.Optional; 6 | import com.google.common.collect.ImmutableList; 7 | import fr.mmarie.api.jira.Comment; 8 | import fr.mmarie.api.jira.input.TransitionInput; 9 | import org.assertj.core.data.MapEntry; 10 | import org.junit.Before; 11 | import org.junit.Rule; 12 | import org.junit.Test; 13 | import retrofit.Response; 14 | 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; 19 | import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; 20 | import static com.github.tomakehurst.wiremock.client.WireMock.get; 21 | import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; 22 | import static com.github.tomakehurst.wiremock.client.WireMock.matching; 23 | import static com.github.tomakehurst.wiremock.client.WireMock.post; 24 | import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; 25 | import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; 26 | import static fr.mmarie.Assertions.assertThat; 27 | import static io.dropwizard.testing.FixtureHelpers.fixture; 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | 30 | public class JiraServiceTestIT { 31 | 32 | public static final int PORT = 1520; 33 | 34 | @Rule 35 | public WireMockRule wireMockRule = new WireMockRule(PORT); 36 | 37 | public JiraConfiguration jiraConfiguration = new JiraConfiguration("username", 38 | "password", 39 | String.format("http://localhost:%d", PORT), 40 | ImmutableList.of(new TransitionConfiguration("Close", ImmutableList.of("close", "fix")))); 41 | 42 | public JiraService jiraService; 43 | 44 | @Before 45 | public void setUp() throws Exception { 46 | jiraService = new JiraService(jiraConfiguration); 47 | } 48 | 49 | @Test 50 | public void testGetIssue() throws Exception { 51 | String issue = "TEST-1"; 52 | wireMockRule.stubFor(get(urlEqualTo("/rest/api/2/issue/" + issue)) 53 | .withHeader("Authorization", matching("Basic .*")) 54 | .willReturn(aResponse() 55 | .withStatus(200) 56 | .withBody("{}"))); 57 | 58 | Response response = jiraService.getIssue(issue); 59 | 60 | assertThat(response.code()) 61 | .isEqualTo(200); 62 | 63 | wireMockRule.verify(getRequestedFor(urlEqualTo("/rest/api/2/issue/" + issue)) 64 | .withHeader("Authorization", matching("Basic .*"))); 65 | } 66 | 67 | @Test 68 | public void testCommentIssue() throws Exception { 69 | String issue = "TEST-1"; 70 | String body = "This is a comment"; 71 | Comment comment = new Comment(body); 72 | String mockedComment = fixture("fixtures/jira/comment.json"); 73 | 74 | wireMockRule.stubFor(post(urlEqualTo("/rest/api/2/issue/" + issue + "/comment")) 75 | .withHeader("Authorization", matching("Basic .*")) 76 | .withRequestBody(equalToJson(mockedComment)) 77 | .willReturn(aResponse() 78 | .withStatus(200) 79 | .withBody(mockedComment))); 80 | 81 | Response response = jiraService.commentIssue(issue, comment); 82 | 83 | assertThat(response.code()) 84 | .isEqualTo(200); 85 | 86 | assertThat(response.body()) 87 | .hasBody(body); 88 | 89 | wireMockRule.verify(postRequestedFor(urlEqualTo("/rest/api/2/issue/" + issue + "/comment")) 90 | .withHeader("Authorization", matching("Basic .*")) 91 | .withRequestBody(equalToJson(mockedComment))); 92 | } 93 | 94 | @Test 95 | public void testServerInfo() throws Exception { 96 | wireMockRule.stubFor(get(urlEqualTo("/rest/api/2/serverInfo")) 97 | .withHeader("Authorization", matching("Basic .*")) 98 | .willReturn(aResponse() 99 | .withStatus(200) 100 | .withBody("{}"))); 101 | 102 | Response> response = jiraService.serverInfo(); 103 | 104 | assertThat(response.code()) 105 | .isEqualTo(200); 106 | 107 | wireMockRule.verify(getRequestedFor(urlEqualTo("/rest/api/2/serverInfo")) 108 | .withHeader("Authorization", matching("Basic .*"))); 109 | } 110 | 111 | @Test 112 | public void isExistingIssueWithAGoodOne() throws Exception { 113 | String issue = "TEST-1"; 114 | wireMockRule.stubFor(get(urlEqualTo("/rest/api/2/issue/" + issue)) 115 | .withHeader("Authorization", matching("Basic .*")) 116 | .willReturn(aResponse() 117 | .withStatus(200) 118 | .withBody("{}"))); 119 | 120 | assertThat(jiraService.isExistingIssue(issue)).isTrue(); 121 | 122 | wireMockRule.verify(getRequestedFor(urlEqualTo("/rest/api/2/issue/" + issue)) 123 | .withHeader("Authorization", matching("Basic .*"))); 124 | } 125 | 126 | @Test 127 | public void isExistingIssueWithABadOne() throws Exception { 128 | String issue = "TEST-1"; 129 | wireMockRule.stubFor(get(urlEqualTo("/rest/api/2/issue/" + issue)) 130 | .withHeader("Authorization", matching("Basic .*")) 131 | .willReturn(aResponse() 132 | .withStatus(404) 133 | .withBody("{}"))); 134 | 135 | assertThat(jiraService.isExistingIssue(issue)).isFalse(); 136 | 137 | wireMockRule.verify(getRequestedFor(urlEqualTo("/rest/api/2/issue/" + issue)) 138 | .withHeader("Authorization", matching("Basic .*"))); 139 | } 140 | 141 | @Test 142 | public void isExistingIssueWithAServerError() throws Exception { 143 | String issue = "TEST-1"; 144 | wireMockRule.stubFor(get(urlEqualTo("/rest/api/2/issue/" + issue)) 145 | .withHeader("Authorization", matching("Basic .*")) 146 | .willReturn(aResponse() 147 | .withFault(Fault.EMPTY_RESPONSE))); 148 | 149 | assertThat(jiraService.isExistingIssue(issue)).isFalse(); 150 | 151 | wireMockRule.verify(getRequestedFor(urlEqualTo("/rest/api/2/issue/" + issue)) 152 | .withHeader("Authorization", matching("Basic .*"))); 153 | } 154 | 155 | @Test 156 | public void isIssueAlreadyCommentedWithAServerError() throws Exception { 157 | String issue = "TEST-1"; 158 | wireMockRule.stubFor(get(urlEqualTo("/rest/api/2/issue/" + issue + "/comment")) 159 | .withHeader("Authorization", matching("Basic .*")) 160 | .willReturn(aResponse() 161 | .withFault(Fault.EMPTY_RESPONSE))); 162 | 163 | assertThat(jiraService.isIssueAlreadyCommented(issue, "commitId")).isTrue(); 164 | 165 | wireMockRule.verify(getRequestedFor(urlEqualTo("/rest/api/2/issue/" + issue + "/comment")) 166 | .withHeader("Authorization", matching("Basic .*"))); 167 | } 168 | 169 | @Test 170 | public void alreadyCommentedIssueShouldReturnTrue() throws Exception { 171 | String issue = "TEST-1"; 172 | wireMockRule.stubFor(get(urlEqualTo("/rest/api/2/issue/" + issue + "/comment")) 173 | .withHeader("Authorization", matching("Basic .*")) 174 | .willReturn(aResponse() 175 | .withStatus(200) 176 | .withBody("{ \"comments\" : [{\"body\":\"commit is : commitId\"}] }"))); 177 | 178 | assertThat(jiraService.isIssueAlreadyCommented(issue, "commitId")).isTrue(); 179 | 180 | wireMockRule.verify(getRequestedFor(urlEqualTo("/rest/api/2/issue/" + issue + "/comment")) 181 | .withHeader("Authorization", matching("Basic .*"))); 182 | } 183 | 184 | @Test 185 | public void notCommentedIssueShouldReturnFalse() throws Exception { 186 | String issue = "TEST-1"; 187 | wireMockRule.stubFor(get(urlEqualTo("/rest/api/2/issue/" + issue + "/comment")) 188 | .withHeader("Authorization", matching("Basic .*")) 189 | .willReturn(aResponse() 190 | .withStatus(200) 191 | .withBody("{ \"comments\" : [{\"body\":\"commitId\"}, {\"body\":\"commitId2\"}] }"))); 192 | 193 | assertThat(jiraService.isIssueAlreadyCommented(issue, "newOne")).isFalse(); 194 | 195 | wireMockRule.verify(getRequestedFor(urlEqualTo("/rest/api/2/issue/" + issue + "/comment")) 196 | .withHeader("Authorization", matching("Basic .*"))); 197 | } 198 | 199 | @Test 200 | public void isExistingTransitionWithNonExistingTransaction() { 201 | String issue = "TEST-1"; 202 | wireMockRule.stubFor(get(urlEqualTo("/rest/api/2/issue/" + issue + "/transitions")) 203 | .withHeader("Authorization", matching("Basic .*")) 204 | .willReturn(aResponse() 205 | .withStatus(200) 206 | .withBody("{ \"transitions\" : [{\"id\":\"15\", \"name\":\"Close\"}] }"))); 207 | 208 | assertThat(jiraService.isExistingTransition(issue, "test")).isFalse(); 209 | 210 | wireMockRule.verify(getRequestedFor(urlEqualTo("/rest/api/2/issue/" + issue + "/transitions")) 211 | .withHeader("Authorization", matching("Basic .*"))); 212 | } 213 | 214 | @Test 215 | public void isExistingTransitionWithExistingTransaction() { 216 | String issue = "TEST-1"; 217 | wireMockRule.stubFor(get(urlEqualTo("/rest/api/2/issue/" + issue + "/transitions")) 218 | .withHeader("Authorization", matching("Basic .*")) 219 | .willReturn(aResponse() 220 | .withStatus(200) 221 | .withBody("{ \"transitions\" : [{\"id\":\"15\", \"name\":\"Close\"}, {\"id\":\"16\", \"name\":\"Open\"}] }"))); 222 | 223 | assertThat(jiraService.isExistingTransition(issue, "close")).isTrue(); 224 | 225 | wireMockRule.verify(getRequestedFor(urlEqualTo("/rest/api/2/issue/" + issue + "/transitions")) 226 | .withHeader("Authorization", matching("Basic .*"))); 227 | } 228 | 229 | @Test 230 | public void transitionOnIssueWithBadResponseStatus() throws Exception { 231 | String issue = "TEST-1"; 232 | wireMockRule.stubFor(post(urlEqualTo("/rest/api/2/issue/" + issue + "/transitions")) 233 | .withHeader("Authorization", matching("Basic .*")) 234 | .willReturn(aResponse() 235 | .withStatus(400) 236 | .withBody("Bad request !"))); 237 | 238 | jiraService.transitionOnIssue(issue, new TransitionInput()); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | fr.mmarie 6 | gitlab-jira-integration 7 | pom 8 | 0.5.0-SNAPSHOT 9 | 10 | GitLab JIRA Integration 11 | WebService to intercepts hook events from gitlab and use JIRA REST api to interacts with issues. 12 | https://github.com/akraxx/gitlab-jira-integration 13 | 14 | 15 | 16 | gitlab-jira-integration-api 17 | gitlab-jira-integration-application 18 | 19 | 20 | 21 | scm:git:git://github.com/akraxx/gitlab-jira-integration.git 22 | scm:git:git@github.com:akraxx/gitlab-jira-integration.git 23 | https://github.com/akraxx/gitlab-jira-integration.git 24 | HEAD 25 | 26 | 27 | 28 | GitHub 29 | https://github.com/akraxx/gitlab-jira-integration/issues 30 | 31 | 32 | 33 | Travis CI 34 | https://travis-ci.org/akraxx/gitlab-jira-integration 35 | 36 | 37 | 38 | 39 | MIT 40 | http://opensource.org/licenses/MIT 41 | repo 42 | 43 | 44 | 45 | 46 | 47 | Maximilien Marie 48 | contact@mmarie.fr 49 | Europe/Paris 50 | 51 | master 52 | 53 | 54 | 55 | 56 | 57 | 58 | ossrh 59 | Sonatype Nexus Snapshots 60 | http://oss.sonatype.org/content/repositories/snapshots 61 | 62 | 63 | ossrh 64 | Nexus Release Repository 65 | http://oss.sonatype.org/service/local/staging/deploy/maven2/ 66 | 67 | 68 | gitlab-jira-integration-wiki 69 | GitLab Jira Integration 70 | https://github.com/akraxx/gitlab-jira-integration/wiki 71 | 72 | 73 | 74 | 75 | 76 | release 77 | 78 | 79 | 80 | org.apache.maven.plugins 81 | maven-source-plugin 82 | ${maven-source-plugin.version} 83 | 84 | 85 | attach-sources 86 | 87 | jar 88 | 89 | 90 | 91 | 92 | 93 | org.apache.maven.plugins 94 | maven-gpg-plugin 95 | 1.5 96 | 97 | 98 | sign-artifacts 99 | verify 100 | 101 | sign 102 | 103 | 104 | 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-javadoc-plugin 109 | ${maven-javadoc-plugin.version} 110 | 111 | ${javadoc.doclint.none} 112 | true 113 | 114 | 115 | 116 | attach-javadocs 117 | 118 | jar 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 1.8 131 | UTF-8 132 | UTF-8 133 | 134 | 135 | 0.9.0-rc4 136 | 1.16.6 137 | 2.6.0 138 | 2.21 139 | 140 | 141 | 4.11 142 | 143 | 144 | 3.1 145 | 3.0.1 146 | 2.18.1 147 | 2.18.1 148 | 3.1.0 149 | 2.0.0 150 | 2.8.2 151 | 2.10.1 152 | 2.4 153 | 2.5.2 154 | 2.5 155 | 156 | 157 | ${project.build.directory}/jacoco-it.exec 158 | ${project.build.directory}/jacoco.exec 159 | 2.15 160 | 2.5 161 | reuseReports 162 | jacoco 163 | 1.6.3 164 | 165 | 166 | 167 | 168 | org.projectlombok 169 | lombok 170 | ${lombok.version} 171 | provided 172 | 173 | 174 | 175 | 176 | junit 177 | junit 178 | ${junit.version} 179 | test 180 | 181 | 182 | io.dropwizard 183 | dropwizard-testing 184 | ${dropwizard.version} 185 | test 186 | 187 | 188 | org.assertj 189 | assertj-core 190 | ${assertj-core.version} 191 | test 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | org.apache.maven.plugins 200 | maven-deploy-plugin 201 | ${maven-deploy-plugin.version} 202 | 203 | 204 | 205 | 206 | 207 | org.apache.maven.plugins 208 | maven-compiler-plugin 209 | ${maven-compiler-plugin.version} 210 | 211 | ${java.version} 212 | ${java.version} 213 | ${project.build.sourceEncoding} 214 | 215 | 216 | 217 | org.jacoco 218 | jacoco-maven-plugin 219 | 0.7.4.201502262128 220 | 221 | 222 | pre-unit-test 223 | 224 | prepare-agent 225 | 226 | 227 | surefireArgLine 228 | ${sonar.jacoco.reportPath} 229 | 230 | 231 | 232 | post-unit-test 233 | 234 | report 235 | 236 | 237 | ${sonar.jacoco.reportPath} 238 | ${project.build.directory} 239 | 240 | 241 | 242 | pre-integration-test 243 | pre-integration-test 244 | 245 | prepare-agent 246 | 247 | 248 | ${sonar.jacoco.itReportPath} 249 | failsafe.argLine 250 | 251 | 252 | 253 | post-integration-test 254 | post-integration-test 255 | 256 | report 257 | 258 | 259 | ${sonar.jacoco.itReportPath} 260 | 261 | 262 | 263 | 264 | 265 | org.eluder.coveralls 266 | coveralls-maven-plugin 267 | ${coveralls-maven-plugin.version} 268 | 269 | 270 | org.apache.maven.plugins 271 | maven-failsafe-plugin 272 | ${maven-failsafe-plugin.version} 273 | 274 | 275 | 276 | integration-test 277 | verify 278 | 279 | 280 | false 281 | 10 282 | 10 283 | 284 | 285 | 286 | 287 | 288 | org.apache.maven.plugins 289 | maven-surefire-plugin 290 | ${maven-surefire-plugin.version} 291 | 292 | 293 | org.assertj 294 | assertj-assertions-generator-maven-plugin 295 | ${assertj-assertions-generator-maven-plugin.version} 296 | 297 | 298 | 299 | generate-assertions 300 | 301 | 302 | 303 | 304 | 305 | fr.mmarie 306 | 307 | 308 | 309 | 310 | 311 | org.apache.maven.plugins 312 | maven-release-plugin 313 | ${maven-release-plugin.version} 314 | 315 | true 316 | false 317 | release 318 | deploy 319 | 320 | 321 | 322 | 323 | org.sonatype.plugins 324 | nexus-staging-maven-plugin 325 | ${nexus-staging-maven-plugin.version} 326 | true 327 | 328 | ossrh 329 | https://oss.sonatype.org/ 330 | true 331 | 332 | 333 | 334 | 335 | --------------------------------------------------------------------------------