Skip to content
This repository has been archived by the owner on Jan 8, 2020. It is now read-only.

Commit

Permalink
feat(gitlab): Add Gitlab SCM integration (spinnaker/spinnaker#2047)
Browse files Browse the repository at this point in the history
  • Loading branch information
Maarten Dirkse committed Nov 20, 2017
1 parent 8f9504e commit c5ed3de
Show file tree
Hide file tree
Showing 9 changed files with 523 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2017 bol.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.netflix.spinnaker.igor.config

import com.netflix.spinnaker.igor.scm.gitlab.client.GitLabClient
import com.netflix.spinnaker.igor.scm.gitlab.client.GitLabMaster
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import retrofit.Endpoints
import retrofit.RequestInterceptor
import retrofit.RestAdapter
import retrofit.client.OkClient
import retrofit.converter.JacksonConverter

import javax.validation.Valid

@Configuration
@ConditionalOnProperty('gitlab.baseUrl')
@Slf4j
@CompileStatic
@EnableConfigurationProperties(GitLabProperties)
class GitLabConfig {
@Bean
GitLabMaster gitLabMasters(@Valid GitLabProperties gitLabProperties) {
log.info "bootstrapping ${gitLabProperties.baseUrl} as gitlab"
new GitLabMaster(gitLabClient: gitLabClient(gitLabProperties.baseUrl, gitLabProperties.privateToken), baseUrl: gitLabProperties.baseUrl)
}

GitLabClient gitLabClient(String address, String accessToken) {
new RestAdapter.Builder()
.setEndpoint(Endpoints.newFixedEndpoint(address))
.setRequestInterceptor(new PrivateTokenRequestInterceptor(accessToken))
.setClient(new OkClient())
.setConverter(new JacksonConverter())
.build()
.create(GitLabClient)
}


static class PrivateTokenRequestInterceptor implements RequestInterceptor {
private final String privateToken

PrivateTokenRequestInterceptor(String privateToken) {
this.privateToken = privateToken
}

@Override
void intercept(RequestInterceptor.RequestFacade request) {
request.addHeader("Private-Token", privateToken)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2017 bol.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.netflix.spinnaker.igor.config

import groovy.transform.CompileStatic
import org.hibernate.validator.constraints.NotEmpty
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.ConfigurationProperties

import javax.validation.constraints.NotNull

/**
* Helper class to map masters in properties file into a validated property map
*/
@ConditionalOnProperty('gitlab.baseUrl')
@CompileStatic
@ConfigurationProperties(prefix = 'gitlab')
class GitLabProperties {
@NotEmpty
String baseUrl

@NotEmpty
String privateToken

@NotNull
Integer commitDisplayLength
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2017 bol.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.netflix.spinnaker.igor.scm.gitlab

import com.netflix.spinnaker.igor.config.GitLabProperties
import com.netflix.spinnaker.igor.scm.AbstractCommitController
import com.netflix.spinnaker.igor.scm.gitlab.client.model.CompareCommitsResponse
import com.netflix.spinnaker.igor.scm.gitlab.client.GitLabMaster
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import retrofit.RetrofitError

@Slf4j
@RestController(value = "GitLabCommitController")
@ConditionalOnProperty('gitlab.baseUrl')
@RequestMapping("/gitlab")
class CommitController extends AbstractCommitController {
@Autowired
GitLabMaster master

@Autowired
GitLabProperties gitLabProperties

@RequestMapping(value = '/{projectKey}/{repositorySlug}/compareCommits')
List compareCommits(@PathVariable(value = 'projectKey') String projectKey, @PathVariable(value='repositorySlug') repositorySlug, @RequestParam Map<String, String> requestParams) {
super.compareCommits(projectKey, repositorySlug, requestParams)
CompareCommitsResponse commitsResponse
List result = []

try {
commitsResponse = master.gitLabClient.getCompareCommits(projectKey, repositorySlug, [to: requestParams.to, from: requestParams.from])
} catch (RetrofitError e) {
if(e.getKind() == RetrofitError.Kind.NETWORK) {
throw new RuntimeException("Could not find the server ${master.baseUrl}")
} else if(e.response.status == 404) {
return getNotFoundCommitsResponse(projectKey, repositorySlug, requestParams.to, requestParams.from, master.baseUrl)
}
log.error("Unhandled error response, acting like commit response was not found", e)
return getNotFoundCommitsResponse(projectKey, repositorySlug, requestParams.to, requestParams.from, master.baseUrl)
}

commitsResponse.commits.each {
def commitUrl = getCommitUrl(gitLabProperties.baseUrl, projectKey, repositorySlug, it?.id)
result << [displayId: it?.id?.substring(0,gitLabProperties.commitDisplayLength), id: it?.id, authorDisplayName: it?.author_name,
timestamp: it?.authored_date, message : it?.message, commitUrl: commitUrl]
}
return result
}

static String getCommitUrl(String baseUrl, String projectKey, String repositorySlug, String commitId) {
return "${baseUrl}/${projectKey}/${repositorySlug}/commit/${commitId}"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.netflix.spinnaker.igor.scm.gitlab.client

import com.netflix.spinnaker.igor.scm.gitlab.client.model.CompareCommitsResponse
import retrofit.http.GET
import retrofit.http.Path
import retrofit.http.QueryMap

/**
* Interface for interacting with a Gitab REST API
* https://docs.gitlab.com/ce/api/README.html
*/
interface GitLabClient {

/**
* https://docs.gitlab.com/ce/api/repositories.html#compare-branches-tags-or-commits
*/
@GET('/api/v4/projects/{projectKey}%2F{repositorySlug}/repository/compare')
CompareCommitsResponse getCompareCommits(
@Path('projectKey') String projectKey,
@Path('repositorySlug') String repositorySlug,
@QueryMap Map queryMap)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2015 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.netflix.spinnaker.igor.scm.gitlab.client

import com.netflix.spinnaker.igor.config.GitLabProperties
import groovy.util.logging.Slf4j
import org.springframework.context.annotation.Bean
import retrofit.Endpoints
import retrofit.RequestInterceptor
import retrofit.RestAdapter
import retrofit.client.OkClient
import retrofit.converter.SimpleXMLConverter

import javax.validation.Valid

/**
* Wrapper class for a collection of GitLab clients
*/
@Slf4j
class GitLabMaster {
GitLabClient gitLabClient
String baseUrl

@Bean
GitLabMaster gitLabMasters(@Valid GitLabProperties gitLabProperties) {
new GitLabMaster(gitLabClient : gitLabClient(gitLabProperties.baseUrl, gitLabProperties.accessToken), baseUrl: gitLabProperties.baseUrl)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.netflix.spinnaker.igor.scm.gitlab.client.model

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import org.joda.time.DateTime
import org.joda.time.format.ISODateTimeFormat

@JsonIgnoreProperties(ignoreUnknown = true)
class Commit {
String id
String author_name
Date authored_date
String message

@JsonProperty(value = "authored_date")
void setDate(String utcTimestamp) {
authored_date = DateTime.parse(utcTimestamp, ISODateTimeFormat.dateTime()).toDate()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.netflix.spinnaker.igor.scm.gitlab.client.model

import com.fasterxml.jackson.annotation.JsonIgnoreProperties

@JsonIgnoreProperties(ignoreUnknown = true)
class CompareCommitsResponse {
List<Commit> commits
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright 2017 bol.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.netflix.spinnaker.igor.scm.gitlab

import com.netflix.spinnaker.igor.config.GitLabProperties
import com.netflix.spinnaker.igor.scm.AbstractCommitController
import com.netflix.spinnaker.igor.scm.gitlab.client.GitLabClient
import com.netflix.spinnaker.igor.scm.gitlab.client.GitLabMaster
import com.netflix.spinnaker.igor.scm.gitlab.client.model.Commit
import com.netflix.spinnaker.igor.scm.gitlab.client.model.CompareCommitsResponse
import retrofit.RetrofitError
import retrofit.client.Response
import spock.lang.Specification
import spock.lang.Subject

import java.util.concurrent.Executors

/**
* Tests for GitLab CommitController
*/
class CommitControllerSpec extends Specification {

@Subject
CommitController controller

GitLabClient client = Mock(GitLabClient)

final GITLAB_ADDRESS = "https://api.gitlab.com"

void setup() {
controller = new CommitController(executor: Executors.newSingleThreadExecutor(), master: new GitLabMaster(gitLabClient: client, baseUrl: GITLAB_ADDRESS), gitLabProperties: new GitLabProperties(baseUrl: GITLAB_ADDRESS, commitDisplayLength: 8))
}

void 'missing query params'() {
when:
controller.compareCommits(projectKey, repositorySlug, queryParams)

then:
thrown(AbstractCommitController.MissingParametersException)

where:
projectKey = 'key'
repositorySlug = 'slug'
queryParams | _
['to': "abcdef"] | _
['from': "ghijk"] | _
}

void 'get 404 from client and return one commit'() {
when:
1 * client.getCompareCommits(projectKey, repositorySlug, [to: queryParams.to, from: queryParams.from]) >> {
throw new RetrofitError(null, null, new Response("http://foo.com", 404, "test reason", [], null), null, null, null, null)
}
def result = controller.compareCommits(projectKey, repositorySlug, queryParams)

then:
result.size() == 1
result[0].id == "NOT_FOUND"

where:
projectKey = 'key'
repositorySlug = 'slug'
queryParams | _
['to': "abcdef", 'from': 'ghijk'] | _
}

void 'compare commits'() {
given:
1 * client.getCompareCommits(projectKey, repositorySlug, [to: toCommit, from: fromCommit]) >>
new CompareCommitsResponse(commits:
[new Commit(id: "1234512345123451234512345", author_name: "Joe Coder", authored_date: new Date(1433192015000), message: "my commit"),
new Commit(id: "67890678906789067890", author_name: "Jane Coder", authored_date: new Date(1432078663000), message: "bug fix")])

when:
List commitsResponse = controller.compareCommits(projectKey, repositorySlug, ['to': toCommit, 'from': fromCommit])

then:
commitsResponse.size == 2

with(commitsResponse[0]) {
displayId == "12345123"
id == "1234512345123451234512345"
authorDisplayName == "Joe Coder"
message == "my commit"
commitUrl == "https://api.gitlab.com/${projectKey}/${repositorySlug}/commit/1234512345123451234512345"
timestamp == new Date(1433192015000)
}

with(commitsResponse[1]) {
displayId == "67890678"
id == "67890678906789067890"
authorDisplayName == "Jane Coder"
message == "bug fix"
commitUrl == "https://api.gitlab.com/${projectKey}/${repositorySlug}/commit/67890678906789067890"
timestamp == new Date(1432078663000)
}

where:
projectKey = 'key'
repositorySlug = 'slug'
toCommit = 'abcd'
fromCommit = 'efgh'
}
}

0 comments on commit c5ed3de

Please sign in to comment.