Skip to content

Commit

Permalink
Merge pull request #161 from AMeng/jenkins-folders
Browse files Browse the repository at this point in the history
Update endpoints to support Jenkins folder plugin
  • Loading branch information
tomaslin committed Dec 23, 2015
2 parents 90851f0 + a85fdf7 commit 3568d4a
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 18 deletions.
2 changes: 2 additions & 0 deletions gate-web/gate-web.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ dependencies {
compile('org.springframework.session:spring-session-data-redis:1.0.1.RELEASE')
compile('org.opensaml:opensaml:2.6.4')

testCompile 'com.squareup.okhttp:mockwebserver:2.1.0'

//this brings in the jetty GzipFilter which boot will autoconfigure
runtime 'org.eclipse.jetty:jetty-servlets:9.2.11.v20150529'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,76 @@ package com.netflix.spinnaker.gate.controllers

import com.netflix.spinnaker.gate.services.BuildService
import groovy.transform.CompileStatic
import javax.servlet.http.HttpServletRequest
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.HandlerMapping

@CompileStatic
@RequestMapping("/builds")
@RestController
class BuildController {
/*
* Job names can have '/' in them if using the Jenkins Folder plugin.
* Because of this, always put the job name at the end of the URL.
*/
@Autowired
BuildService buildService

@RequestMapping(method = RequestMethod.GET)
@RequestMapping(value = "v2/builds", method = RequestMethod.GET)
List<String> getBuildMasters() {
buildService.getBuildMasters()
}

@RequestMapping(value = "/{buildMaster}/jobs", method = RequestMethod.GET)
@RequestMapping(value = "/v2/builds/{buildMaster}/jobs", method = RequestMethod.GET)
List<String> getJobsForBuildMaster(@PathVariable("buildMaster") String buildMaster) {
buildService.getJobsForBuildMaster(buildMaster)
}

@RequestMapping(value = "/{buildMaster}/jobs/{job:.+}", method = RequestMethod.GET)
Map getJobConfig(@PathVariable("buildMaster") String buildMaster, @PathVariable("job") String job) {
@RequestMapping(value = "/v2/builds/{buildMaster}/jobs/**", method = RequestMethod.GET)
Map getJobConfig(@PathVariable("buildMaster") String buildMaster, HttpServletRequest request) {
def job = request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString().split('/').drop(5).join('/')
buildService.getJobConfig(buildMaster, job)
}

@RequestMapping(value = "/{buildMaster}/jobs/{job}/builds", method = RequestMethod.GET)
List getBuilds(@PathVariable("buildMaster") String buildMaster, @PathVariable("job") String job) {
@RequestMapping(value = "/v2/builds/{buildMaster}/builds/**", method = RequestMethod.GET)
List getBuilds(@PathVariable("buildMaster") String buildMaster, HttpServletRequest request) {
def job = request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)toString().split('/').drop(5).join('/')
buildService.getBuilds(buildMaster, job)
}

@RequestMapping(value = "/{buildMaster}/jobs/{job}/builds/{number}", method = RequestMethod.GET)
Map getBuilds(@PathVariable("buildMaster") String buildMaster, @PathVariable("job") String job, @PathVariable("number") String number) {
@RequestMapping(value = "/v2/builds/{buildMaster}/build/{number}/**", method = RequestMethod.GET)
Map getBuild(@PathVariable("buildMaster") String buildMaster, @PathVariable("number") String number, HttpServletRequest request) {
def job = request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)toString().split('/').drop(6).join('/')
buildService.getBuild(buildMaster, job, number)
}

// LEGACY ENDPOINTS:

@RequestMapping(value = "/builds", method = RequestMethod.GET)
List<String> getBuildMastersLegacy() {
buildService.getBuildMasters()
}

@RequestMapping(value = "/builds/{buildMaster}/jobs", method = RequestMethod.GET)
List<String> getJobsForBuildMasterLegacy(@PathVariable("buildMaster") String buildMaster) {
buildService.getJobsForBuildMaster(buildMaster)
}

@RequestMapping(value = "/builds/{buildMaster}/jobs/{job:.+}", method = RequestMethod.GET)
Map getJobConfigLegacy(@PathVariable("buildMaster") String buildMaster, @PathVariable("job") String job) {
buildService.getJobConfig(buildMaster, job)
}

@RequestMapping(value = "/builds/{buildMaster}/jobs/{job}/builds", method = RequestMethod.GET)
List getBuildsLegacy(@PathVariable("buildMaster") String buildMaster, @PathVariable("job") String job) {
buildService.getBuilds(buildMaster, job)
}

@RequestMapping(value = "/builds/{buildMaster}/jobs/{job}/builds/{number}", method = RequestMethod.GET)
Map getBuildsLegacy(@PathVariable("buildMaster") String buildMaster, @PathVariable("job") String job, @PathVariable("number") String number) {
buildService.getBuild(buildMaster, job, number)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.util.UriUtils
import retrofit.RetrofitError

@CompileStatic
Expand All @@ -36,6 +37,10 @@ class BuildService {
@Autowired(required = false)
IgorService igorService

private String encode(uri) {
return UriUtils.encodeFragment(uri.toString(), "UTF-8")
}

List<String> getBuildMasters() {
if (!igorService) {
return []
Expand Down Expand Up @@ -69,7 +74,7 @@ class BuildService {
}
HystrixFactory.newMapCommand(GROUP, "jobConfig") {
try {
igorService.getJobConfig(buildMaster, job)
igorService.getJobConfig(buildMaster, encode(job))
} catch (RetrofitError e) {
if (e.response?.status == 404) {
throw new BuildMasterNotFound("Build master '${buildMaster}' not found")
Expand All @@ -86,7 +91,7 @@ class BuildService {
}
HystrixFactory.newListCommand(GROUP, "buildsForJob") {
try {
igorService.getBuilds(buildMaster, job)
igorService.getBuilds(buildMaster, encode(job))
} catch (RetrofitError e) {
if (e.response?.status == 404) {
throw new BuildMasterNotFound("Build master '${buildMaster}' not found")
Expand All @@ -103,7 +108,7 @@ class BuildService {
}
HystrixFactory.newMapCommand(GROUP, "buildDetails") {
try {
igorService.getBuild(buildMaster, job, number)
igorService.getBuild(buildMaster, encode(job), number)
} catch (RetrofitError e) {
if (e.response?.status == 404) {
throw new BuildMasterNotFound("Build master '${buildMaster}' not found")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,28 @@

package com.netflix.spinnaker.gate.services.internal

import retrofit.http.EncodedPath
import retrofit.http.GET
import retrofit.http.Path

interface IgorService {
/*
* Job names can have '/' in them if using the Jenkins Folder plugin.
* Because of this, always put the job name at the end of the URL.
*/

@GET('/masters')
List<String> getBuildMasters()

@GET('/jobs/{buildMaster}')
List<String> getJobsForBuildMaster(@Path("buildMaster") String buildMaster)

@GET('/jobs/{buildMaster}/{job}')
Map getJobConfig(@Path("buildMaster") String buildMaster, @Path("job") String job)

@GET('/jobs/{buildMaster}/{job}/builds')
List<Map> getBuilds(@Path("buildMaster") String buildMaster, @Path("job") String job)
Map getJobConfig(@Path("buildMaster") String buildMaster, @EncodedPath("job") String job)

@GET('/jobs/{buildMaster}/{job}/{number}')
Map getBuild(@Path("buildMaster") String buildMaster, @Path("job") String job, @Path("number") String number)
@GET('/builds/all/{buildMaster}/{job}')
List<Map> getBuilds(@Path("buildMaster") String buildMaster, @EncodedPath("job") String job)

@GET('/builds/status/{number}/{buildMaster}/{job}')
Map getBuild(@Path("buildMaster") String buildMaster, @EncodedPath("job") String job, @Path("number") String number)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* 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.gate.controllers

import com.netflix.spinnaker.gate.services.BuildService
import com.netflix.spinnaker.gate.services.internal.IgorService
import com.squareup.okhttp.mockwebserver.MockWebServer
import org.springframework.http.MediaType
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import spock.lang.Shared
import spock.lang.Specification

class BuildControllerSpec extends Specification {

MockMvc mockMvc
BuildService buildService
IgorService igorService

@Shared
MockWebServer server

final MASTER = 'MASTER'
final BUILD_NUMBER = 123
final JOB_NAME = "name/with/slashes and spaces"
final JOB_NAME_LEGACY = "job"
final JOB_NAME_ENCODED = "name/with/slashes%20and%20spaces"

void cleanup() {
server.shutdown()
}

void setup() {
igorService = Mock(IgorService)
buildService = new BuildService(igorService: igorService)
server = new MockWebServer()
mockMvc = MockMvcBuilders.standaloneSetup(new BuildController(buildService: buildService)).build()
}

void 'should get a list of masters'() {
given:
1 * igorService.getBuildMasters() >> [MASTER, "master2"]

when:
MockHttpServletResponse response = mockMvc.perform(get("/v2/builds")
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
response.contentAsString == "[\"${MASTER}\",\"master2\"]"
}

void 'should get a list of jobs for a master'() {
given:
1 * igorService.getJobsForBuildMaster(MASTER) >> [JOB_NAME, "another_job"]

when:
MockHttpServletResponse response = mockMvc.perform(get("/v2/builds/${MASTER}/jobs")
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
response.contentAsString == "[\"${JOB_NAME}\",\"another_job\"]"
}

void 'should get a list of builds for a job'() {
given:
1 * igorService.getBuilds(MASTER, JOB_NAME_ENCODED) >> [["building":false, "number":111], ["building":false, "number":222]]

when:
MockHttpServletResponse response = mockMvc.perform(get("/v2/builds/${MASTER}/builds/${JOB_NAME}")
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
response.contentAsString == "[{\"building\":false,\"number\":111},{\"building\":false,\"number\":222}]"
}

void 'should get a job config'() {
given:
1 * igorService.getJobConfig(MASTER, JOB_NAME_ENCODED) >> ['name': JOB_NAME, 'url': "http://test.com/job/${JOB_NAME}".toString()]

when:
MockHttpServletResponse response = mockMvc.perform(get("/v2/builds/${MASTER}/jobs/${JOB_NAME}")
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
response.contentAsString == "{\"name\":\"${JOB_NAME}\",\"url\":\"http://test.com/job/${JOB_NAME}\"}"
}

void 'should get a build'() {
given:
1 * igorService.getBuild(MASTER, JOB_NAME_ENCODED, BUILD_NUMBER.toString()) >> ["building":false, "number":BUILD_NUMBER]

when:
MockHttpServletResponse response = mockMvc.perform(get("/v2/builds/${MASTER}/build/${BUILD_NUMBER}/${JOB_NAME}")
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
response.contentAsString == "{\"building\":false,\"number\":${BUILD_NUMBER}}"
}

// LEGACY ENDPOINT TESTS:

void 'should get a list of masters LEGACY'() {
given:
1 * igorService.getBuildMasters() >> [MASTER, "master2"]

when:
MockHttpServletResponse response = mockMvc.perform(get("/builds")
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
response.contentAsString == "[\"${MASTER}\",\"master2\"]"
}

void 'should get a list of jobs for a master LEGACY'() {
given:
1 * igorService.getJobsForBuildMaster(MASTER) >> [JOB_NAME_LEGACY, "another_job"]

when:
MockHttpServletResponse response = mockMvc.perform(get("/builds/${MASTER}/jobs")
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
response.contentAsString == "[\"${JOB_NAME_LEGACY}\",\"another_job\"]"
}

void 'should get a list of builds for a job LEGACY'() {
given:
1 * igorService.getBuilds(MASTER, JOB_NAME_LEGACY) >> [["building":false, "number":111], ["building":false, "number":222]]

when:
MockHttpServletResponse response = mockMvc.perform(get("/builds/${MASTER}/jobs/${JOB_NAME_LEGACY}/builds")
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
response.contentAsString == "[{\"building\":false,\"number\":111},{\"building\":false,\"number\":222}]"
}

void 'should get a job config LEGACY'() {
given:
1 * igorService.getJobConfig(MASTER, JOB_NAME_LEGACY) >> ['name': JOB_NAME_LEGACY, 'url': "http://test.com/job/${JOB_NAME_LEGACY}".toString()]

when:
MockHttpServletResponse response = mockMvc.perform(get("/builds/${MASTER}/jobs/${JOB_NAME_LEGACY}")
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
response.contentAsString == "{\"name\":\"${JOB_NAME_LEGACY}\",\"url\":\"http://test.com/job/${JOB_NAME_LEGACY}\"}"
}

void 'should get a build LEGACY'() {
given:
1 * igorService.getBuild(MASTER, JOB_NAME_LEGACY, BUILD_NUMBER.toString()) >> ["building":false, "number":BUILD_NUMBER]

when:
MockHttpServletResponse response = mockMvc.perform(get("/builds/${MASTER}/jobs/${JOB_NAME_LEGACY}/builds/${BUILD_NUMBER}/")
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
response.contentAsString == "{\"building\":false,\"number\":${BUILD_NUMBER}}"
}
}

0 comments on commit 3568d4a

Please sign in to comment.