Skip to content

Commit

Permalink
Update endpoints to have job names at the end. Jobs named with the fo…
Browse files Browse the repository at this point in the history
…lders plugin can contain forward slashes.
  • Loading branch information
AMeng committed Dec 17, 2015
1 parent 7022127 commit b98f293
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 16 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,16 +19,22 @@ 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")
@RequestMapping("/v2/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

Expand All @@ -42,18 +48,21 @@ class BuildController {
buildService.getJobsForBuildMaster(buildMaster)
}

@RequestMapping(value = "/{buildMaster}/jobs/{job:.+}", method = RequestMethod.GET)
Map getJobConfig(@PathVariable("buildMaster") String buildMaster, @PathVariable("job") String job) {
@RequestMapping(value = "/{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 = "/{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 = "/{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)
}
}
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,114 @@
/*
* 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_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}}"
}
}

0 comments on commit b98f293

Please sign in to comment.