Skip to content

Commit 16dc906

Browse files
Merge pull request #149 from Prsna23/master
Adding support for Gitlab
2 parents 04593d7 + 74453bf commit 16dc906

File tree

10 files changed

+350
-0
lines changed

10 files changed

+350
-0
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This is a GoCD SCM plugin for Git feature branch support. [Discussion Thread](ht
66
Supported (as separate plugins):
77
* Git repository for branches
88
* Github repository for Pull Requests
9+
* Gitlab repository for Merge Requests
910
* Stash repository for Pull Requests
1011
* Gerrit repository for Change Sets
1112

@@ -100,6 +101,16 @@ Note: Their name will vary depending on your material's name. For example, if yo
100101
| `GO_SCM_*_PR_TITLE` | Title of the Pull Request |
101102
| `GO_SCM_*_PR_DESCRIPTION` | Description of the Pull Request |
102103

104+
### Gitlab / Gitlab Enterprise
105+
106+
**Authentication:**
107+
- You can create a file `~/.gitlab` with the following contents: (Note: `~/.gitlab` needs to be available on Go Server)
108+
```
109+
login=johndoe
110+
accessToken=personalaccesstoken
111+
```
112+
Please refer [this link](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#creating-a-personal-access-token) for more details on how to create a personal access token.
113+
103114
### Stash
104115
**Authentication**
105116

pom.xml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@
4040
<artifactId>github-api</artifactId>
4141
<version>1.68</version>
4242
</dependency>
43+
<dependency>
44+
<groupId>org.gitlab4j</groupId>
45+
<artifactId>gitlab4j-api</artifactId>
46+
<version>4.15.6</version>
47+
</dependency>
48+
<dependency>
49+
<groupId>com.fasterxml.jackson.core</groupId>
50+
<artifactId>jackson-core</artifactId>
51+
<version>2.9.3</version>
52+
</dependency>
53+
<dependency>
54+
<groupId>com.fasterxml.jackson.core</groupId>
55+
<artifactId>jackson-databind</artifactId>
56+
<version>2.9.3</version>
57+
</dependency>
58+
<dependency>
59+
<groupId>com.fasterxml.jackson.core</groupId>
60+
<artifactId>jackson-annotations</artifactId>
61+
<version>2.9.3</version>
62+
</dependency>
63+
4364

4465
<dependency>
4566
<groupId>org.mockito</groupId>
@@ -85,6 +106,16 @@
85106
<resource.directory>src/main/resources/github</resource.directory>
86107
</properties>
87108
</profile>
109+
<profile>
110+
<id>gitlab.pr</id>
111+
<activation>
112+
<activeByDefault>true</activeByDefault>
113+
</activation>
114+
<properties>
115+
<plugin.name>gitlab-pr-poller</plugin.name>
116+
<resource.directory>src/main/resources/gitlab</resource.directory>
117+
</properties>
118+
</profile>
88119
<profile>
89120
<id>git.fb</id>
90121
<properties>

release.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ cd ../gocd-build-github-pull-requests
88
mvn clean install -DskipTests -P github.pr
99
cp target/github-pr-poller*.jar dist/
1010

11+
mvn clean install -DskipTests -P gitlab.pr
12+
cp target/gitlab-pr-poller*.jar dist/
13+
1114
mvn clean install -DskipTests -P git.fb
1215
cp target/git-fb-poller*.jar dist/
1316

src/main/java/in/ashwanthkumar/gocd/github/provider/github/model/PullRequestStatus.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@ public PullRequestStatus(int id, String lastHead, String mergedSHA, String prBra
3232
this.title = title;
3333
}
3434

35+
public PullRequestStatus(int id, String mergeRefPattern, String lastHead, String mergedSHA, String prBranch,
36+
String toBranch, String url, String author, String authorEmail, String description, String title) {
37+
this.id = id;
38+
this.mergeRef = String.format("%s%d", mergeRefPattern, getId());
39+
this.lastHead = lastHead;
40+
this.mergeSHA = mergedSHA;
41+
this.prBranch = prBranch;
42+
this.toBranch = toBranch;
43+
this.url = url;
44+
this.author = author;
45+
this.authorEmail = authorEmail;
46+
this.description = description;
47+
this.title = title;
48+
}
49+
3550
private PullRequestStatus() {
3651
}
3752

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package in.ashwanthkumar.gocd.github.provider.gitlab;
2+
3+
import com.thoughtworks.go.plugin.api.GoPluginIdentifier;
4+
import com.tw.go.plugin.model.GitConfig;
5+
import com.tw.go.plugin.util.StringUtil;
6+
import in.ashwanthkumar.gocd.github.provider.Provider;
7+
import in.ashwanthkumar.gocd.github.provider.github.GHUtils;
8+
import in.ashwanthkumar.gocd.github.provider.github.model.PullRequestStatus;
9+
import in.ashwanthkumar.gocd.github.settings.general.DefaultGeneralPluginConfigurationView;
10+
import in.ashwanthkumar.gocd.github.settings.general.GeneralPluginConfigurationView;
11+
import in.ashwanthkumar.gocd.github.settings.scm.DefaultScmPluginConfigurationView;
12+
import in.ashwanthkumar.gocd.github.settings.scm.ScmPluginConfigurationView;
13+
import in.ashwanthkumar.gocd.github.util.URLUtils;
14+
import in.ashwanthkumar.utils.func.Function;
15+
import in.ashwanthkumar.utils.lang.StringUtils;
16+
import org.gitlab4j.api.GitLabApi;
17+
import org.gitlab4j.api.GitLabApiException;
18+
import org.gitlab4j.api.models.Author;
19+
import org.gitlab4j.api.models.MergeRequest;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
23+
import java.util.Arrays;
24+
import java.util.Map;
25+
import java.util.Properties;
26+
27+
public class GitLabProvider implements Provider {
28+
private static final Logger LOG = LoggerFactory.getLogger(GitLabProvider.class);
29+
public static final String REF_SPEC = "+refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*";
30+
public static final String REF_PATTERN = "refs/remotes/origin/merge-requests/";
31+
private static String LOGIN = "login";
32+
private static String ACCESS_TOKEN = "accessToken";
33+
34+
@Override
35+
public GoPluginIdentifier getPluginId() {
36+
return new GoPluginIdentifier("gitlab.pr", Arrays.asList("1.0"));
37+
}
38+
39+
@Override
40+
public String getName() {
41+
return "Gitlab";
42+
}
43+
44+
@Override
45+
public void addConfigData(GitConfig gitConfig) {
46+
try {
47+
Properties props = GitLabUtils.readPropertyFile();
48+
if (StringUtil.isEmpty(gitConfig.getUsername())) {
49+
gitConfig.setUsername(props.getProperty(LOGIN));
50+
}
51+
if (StringUtil.isEmpty(gitConfig.getPassword())) {
52+
gitConfig.setPassword(props.getProperty(ACCESS_TOKEN));
53+
}
54+
} catch (Exception e) {
55+
LOG.error(String.format("Failed to add config data. %s", e.getMessage()), e);
56+
throw new RuntimeException(String.format("Failed to add config data. %s", e.getMessage()), e);
57+
}
58+
}
59+
60+
@Override
61+
public boolean isValidURL(String url) {
62+
return new URLUtils().isValidURL(url);
63+
}
64+
65+
@Override
66+
public void checkConnection(GitConfig gitConfig) {
67+
try {
68+
loginWith(gitConfig).getProjectApi().getProject(GHUtils.parseGithubUrl(gitConfig.getEffectiveUrl()));
69+
} catch (Exception e) {
70+
LOG.error(String.format("Check connection failed. %s", e.getMessage()), e);
71+
throw new RuntimeException(String.format("Check connection failed. %s", e.getMessage()), e);
72+
}
73+
}
74+
75+
@Override
76+
public String getRefSpec() {
77+
return REF_SPEC;
78+
}
79+
80+
@Override
81+
public String getRefPattern() {
82+
return REF_PATTERN;
83+
}
84+
85+
@Override
86+
public void populateRevisionData(GitConfig gitConfig, String prId, String prSHA, Map<String, String> data) {
87+
data.put("PR_ID", prId);
88+
89+
PullRequestStatus prStatus = null;
90+
boolean isDisabled = System.getProperty("go.plugin.gitlab.pr.populate-details", "Y").equals("N");
91+
if (isDisabled) {
92+
LOG.debug("Populating PR details is disabled");
93+
} else {
94+
prStatus = getPullRequestStatus(gitConfig, prId, prSHA);
95+
}
96+
97+
if (prStatus != null) {
98+
data.put("PR_BRANCH", String.valueOf(prStatus.getPrBranch()));
99+
data.put("TARGET_BRANCH", String.valueOf(prStatus.getToBranch()));
100+
data.put("PR_URL", String.valueOf(prStatus.getUrl()));
101+
data.put("PR_AUTHOR", prStatus.getAuthor());
102+
data.put("PR_AUTHOR_EMAIL", prStatus.getAuthorEmail());
103+
data.put("PR_DESCRIPTION", prStatus.getDescription());
104+
data.put("PR_TITLE", prStatus.getTitle());
105+
}
106+
}
107+
108+
@Override
109+
public ScmPluginConfigurationView getScmConfigurationView() {
110+
return new DefaultScmPluginConfigurationView();
111+
}
112+
113+
@Override
114+
public GeneralPluginConfigurationView getGeneralConfigurationView() {
115+
return new DefaultGeneralPluginConfigurationView();
116+
}
117+
118+
private PullRequestStatus getPullRequestStatus(GitConfig gitConfig, String prId, String prSHA) {
119+
try {
120+
MergeRequest currentPR = pullRequestFrom(gitConfig, Integer.parseInt(prId));
121+
return transformMergeRequestToPullRequestStatus(prSHA).apply(currentPR);
122+
} catch (Exception e) {
123+
LOG.error(String.format("Failed to fetch PR status. %s", e.getMessage()), e);
124+
throw new RuntimeException(String.format("Failed to fetch PR status. %s", e.getMessage()), e);
125+
}
126+
}
127+
128+
private MergeRequest pullRequestFrom(GitConfig gitConfig, int currentPullRequestID) throws GitLabApiException {
129+
return loginWith(gitConfig)
130+
.getMergeRequestApi()
131+
.getMergeRequest(GHUtils.parseGithubUrl(gitConfig.getEffectiveUrl()), currentPullRequestID);
132+
}
133+
134+
private Function<MergeRequest, PullRequestStatus> transformMergeRequestToPullRequestStatus(final String mergedSHA) {
135+
return new Function<MergeRequest, PullRequestStatus>() {
136+
@Override
137+
public PullRequestStatus apply(MergeRequest input) {
138+
int prID = input.getId();
139+
Author user = input.getAuthor();
140+
return new PullRequestStatus(prID, GitLabProvider.REF_PATTERN, input.getSha(), mergedSHA,
141+
input.getSourceBranch(), input.getTargetBranch(), input.getWebUrl(), user.getName(),
142+
user.getEmail(), input.getDescription(), input.getTitle());
143+
}
144+
};
145+
}
146+
147+
private GitLabApi loginWith(GitConfig gitConfig) throws RuntimeException {
148+
if (hasCredentials(gitConfig))
149+
return new GitLabApi(GitLabUtils.getServerUrl(gitConfig.getEffectiveUrl()),
150+
gitConfig.getPassword());
151+
else {
152+
LOG.error("No gitlab credentials found");
153+
throw new RuntimeException("No gitlab credentials found");
154+
}
155+
}
156+
157+
private boolean hasCredentials(GitConfig gitConfig) {
158+
return StringUtils.isNotEmpty(gitConfig.getUsername()) && StringUtils.isNotEmpty(gitConfig.getPassword());
159+
}
160+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package in.ashwanthkumar.gocd.github.provider.gitlab;
2+
3+
import org.apache.commons.io.IOUtils;
4+
5+
import java.io.File;
6+
import java.io.FileInputStream;
7+
import java.io.IOException;
8+
import java.util.Properties;
9+
10+
public class GitLabUtils {
11+
public static final String HTTP_PROTOCOL = "http";
12+
public static final String HTTPS_PROTOCOL = "https";
13+
public static final String URL_SEPARATOR = "/";
14+
15+
public static Properties readPropertyFile() throws IOException {
16+
File propertyFile = new File(System.getProperty("user.home"), ".gitlab");
17+
Properties props = new Properties();
18+
FileInputStream in = null;
19+
try {
20+
in = new FileInputStream(propertyFile);
21+
props.load(in);
22+
} finally {
23+
IOUtils.closeQuietly(in);
24+
}
25+
return props;
26+
}
27+
28+
public static Boolean isSSHUrl(String url) {
29+
return url.startsWith("git@");
30+
}
31+
32+
public static String getProtocol(String url) {
33+
if(url.startsWith(HTTPS_PROTOCOL) || isSSHUrl(url)) {
34+
return HTTPS_PROTOCOL;
35+
} else {
36+
return HTTP_PROTOCOL;
37+
}
38+
}
39+
40+
public static String getServerUrl(String url) {
41+
if(isSSHUrl(url)) {
42+
return String.format("%s://%s", getProtocol(url), url.split(":")[0].substring(4));
43+
} else {
44+
String[] urlParts = url.split(URL_SEPARATOR);
45+
return String.format("%s://%s", getProtocol(url), urlParts[2]);
46+
}
47+
}
48+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
provider=in.ashwanthkumar.gocd.github.provider.gitlab.GitLabProvider
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<go-plugin id="gitlab.pr" version="1">
3+
<about>
4+
<name>Gitlab Pull Requests Builder</name>
5+
<version>${version}</version>
6+
<target-go-version>19.8.0</target-go-version>
7+
<description>Plugin that polls a Gitlab repository for merge requests and triggers a build for each of them</description>
8+
<vendor>
9+
<name>Prasanna Jagathesan</name>
10+
<url>https://github.com/ashwanthkumar/gocd-build-github-pull-requests</url>
11+
</vendor>
12+
</about>
13+
</go-plugin>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package in.ashwanthkumar.gocd.github.provider.gitlab;
2+
3+
import in.ashwanthkumar.gocd.github.provider.AbstractProviderTest;
4+
import in.ashwanthkumar.gocd.github.provider.Provider;
5+
import in.ashwanthkumar.gocd.github.settings.scm.PluginConfigurationView;
6+
import org.junit.Test;
7+
8+
import static org.hamcrest.CoreMatchers.hasItems;
9+
import static org.hamcrest.CoreMatchers.is;
10+
import static org.junit.Assert.assertThat;
11+
12+
public class GitLabProviderTest extends AbstractProviderTest {
13+
@Test
14+
public void shouldReturnCorrectScmSettingsTemplate() throws Exception {
15+
PluginConfigurationView scmConfigurationView = getScmView();
16+
17+
assertThat(scmConfigurationView.templateName(), is("/views/scm.template.html"));;
18+
}
19+
20+
@Test
21+
public void shouldReturnCorrectScmSettingsFields() throws Exception {
22+
PluginConfigurationView scmConfigurationView = getScmView();
23+
24+
assertThat(scmConfigurationView.fields().keySet(),
25+
hasItems("url", "username", "password", "defaultBranch", "shallowClone")
26+
);
27+
assertThat(scmConfigurationView.fields().size(), is(5));
28+
}
29+
30+
@Test
31+
public void shouldReturnCorrectGeneralSettingsTemplate() throws Exception {
32+
PluginConfigurationView generalConfigurationView = getGeneralView();
33+
34+
assertThat(generalConfigurationView.templateName(), is(""));
35+
assertThat(generalConfigurationView.hasConfigurationView(), is(false));
36+
}
37+
38+
@Override
39+
protected Provider getProvider() {
40+
return new GitLabProvider();
41+
}
42+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package in.ashwanthkumar.gocd.github.provider.gitlab;
2+
3+
import org.junit.Test;
4+
5+
import static in.ashwanthkumar.gocd.github.provider.gitlab.GitLabUtils.*;
6+
import static org.hamcrest.CoreMatchers.is;
7+
import static org.junit.Assert.assertThat;
8+
9+
public class GitLabUtilsTest {
10+
@Test
11+
public void shouldIdentifySSHUrl() {
12+
assertThat(isSSHUrl("[email protected]:ashwanthkumar/gocd-build-github-pull-requests.git"), is(true));
13+
assertThat(isSSHUrl("https://gitlab.com/ashwanthkumar/gocd-build-github-pull-requests.git"), is(false));
14+
assertThat(isSSHUrl("[email protected]:ashwanthkumar/gocd-build-github-pull-requests"), is(true));
15+
assertThat(isSSHUrl("[email protected]:username/repo"), is(true));
16+
}
17+
18+
@Test
19+
public void shouldGetServerUrl() {
20+
assertThat(getServerUrl("https://gitlab.com/ashwanthkumar/gocd-build-github-pull-requests.git"), is("https://gitlab.com"));
21+
assertThat(getServerUrl("[email protected]:username/repo"), is("https://code.corp.yourcompany.com"));
22+
assertThat(getServerUrl("https://Github.Com/ashwanthkumar/gocd-build-github-pull-requests"),
23+
is("https://Github.Com"));
24+
assertThat(getServerUrl("https://gitlab.company.com/user/test-repo.git"), is("https://gitlab.company.com"));
25+
}
26+
}

0 commit comments

Comments
 (0)