Skip to content

Commit

Permalink
feat(gcb): Ability to invoke existing GCB triggers (#956)
Browse files Browse the repository at this point in the history
* feat(gcb): Ability to invoke existing GCB triggers

* style(gcb): Extracted some inner classes to their own file

* fix(gcb): Renamed json parameter to match igor api
  • Loading branch information
afcastano authored and mergify[bot] committed Nov 13, 2019
1 parent eb73cbe commit 3c58ab7
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@

package com.netflix.spinnaker.gate.controllers;

import com.netflix.spinnaker.gate.services.BuildService;
import com.netflix.spinnaker.gate.services.internal.GoogleCloudBuildTrigger;
import com.netflix.spinnaker.gate.services.internal.IgorService;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -30,15 +33,26 @@
@RequestMapping("/gcb")
public class GoogleCloudBuildController {
private IgorService igorService;
private BuildService buildService;

@Autowired
public GoogleCloudBuildController(IgorService igorService) {
public GoogleCloudBuildController(IgorService igorService, BuildService buildService) {
this.igorService = igorService;
this.buildService = buildService;
}

@ApiOperation(value = "Retrieve the list of Google Cloud Build accounts", response = List.class)
@GetMapping(value = "/accounts")
List<String> getAccounts() {
return igorService.getGoogleCloudBuildAccounts();
}

@ApiOperation(
value = "Retrieve the list of Google Cloud Build triggers for a given account",
response = List.class)
@GetMapping(value = "/triggers/{account}")
List<GoogleCloudBuildTrigger> getGoogleCloudBuildTriggers(
@PathVariable("account") String account) {
return buildService.getGoogleCloudBuildTriggersForAccount(account);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2019 Andres Castano.
*
* 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.services;

import com.netflix.hystrix.exception.HystrixBadRequestException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class BuildMasterNotFound extends HystrixBadRequestException {
public BuildMasterNotFound(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,12 @@

package com.netflix.spinnaker.gate.services

import com.netflix.hystrix.exception.HystrixBadRequestException
import com.netflix.spinnaker.gate.services.commands.HystrixFactory
import com.netflix.spinnaker.gate.services.internal.GoogleCloudBuildTrigger
import com.netflix.spinnaker.gate.services.internal.IgorService
import groovy.transform.CompileStatic
import groovy.transform.InheritConstructors
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

Expand Down Expand Up @@ -82,6 +79,23 @@ class BuildService {
} execute()
}

List<GoogleCloudBuildTrigger> getGoogleCloudBuildTriggersForAccount(String account) {
if (!igorService) {
return []
}
HystrixFactory.newListCommand(GROUP, "triggersForGcbAccount") {
try {
igorService.getGoogleCloudBuildTriggers(account)
} catch (RetrofitError e) {
if (e.response?.status == 404) {
throw new GCBAccountNotFound("Account '${account}' not found")
}

throw e
}
} execute()
}

Map getJobConfig(String buildMaster, String job) {
if (!igorService) {
return [:]
Expand Down Expand Up @@ -133,7 +147,4 @@ class BuildService {
} execute()
}

@ResponseStatus(HttpStatus.NOT_FOUND)
@InheritConstructors
static class BuildMasterNotFound extends HystrixBadRequestException {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2019 Andres Castano.
*
* 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.services;

import com.netflix.hystrix.exception.HystrixBadRequestException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class GCBAccountNotFound extends HystrixBadRequestException {
public GCBAccountNotFound(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2019 Andres Castano
*
* 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.services.internal;

import com.fasterxml.jackson.annotation.JsonProperty;

public class GoogleCloudBuildTrigger {
GoogleCloudBuildTrigger() {}

public GoogleCloudBuildTrigger(String id, String name, String description) {
this.id = id;
this.name = name;
this.description = description;
}

@JsonProperty private String id;

@JsonProperty private String name;

@JsonProperty private String description;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

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


import com.fasterxml.jackson.annotation.JsonProperty
import retrofit.http.EncodedPath
import retrofit.http.GET
import retrofit.http.Path
Expand Down Expand Up @@ -71,6 +71,9 @@ interface IgorService {
@GET('/gcb/accounts')
List<String> getGoogleCloudBuildAccounts();

@GET('/gcb/triggers/{account}')
List<GoogleCloudBuildTrigger> getGoogleCloudBuildTriggers(@Path("account") String account);

@GET('/artifacts/{provider}/{packageName}')
List<String> getArtifactVersions(@Path("provider") String provider,
@Path("packageName") String packageName,
Expand All @@ -83,4 +86,5 @@ interface IgorService {

@GET('/concourse/{buildMaster}/teams/{team}/pipelines/{pipeline}/resources')
List<String> getConcourseResources(@Path("buildMaster") String buildMaster, @Path("team") String team, @Path("pipeline") String pipeline);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright 2019 Andres Castano
*
* 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.fasterxml.jackson.databind.ObjectMapper
import com.netflix.spinnaker.gate.services.BuildService
import com.netflix.spinnaker.gate.services.internal.GoogleCloudBuildTrigger
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 retrofit.RetrofitError
import retrofit.client.Response
import spock.lang.Shared
import spock.lang.Specification

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get

class GoogleCloudBuildControllerSpec extends Specification {

MockMvc mockMvc
BuildService buildService
IgorService igorService

def server = new MockWebServer()

@Shared def objectMapper = new ObjectMapper()
@Shared def ACCOUNT = 'myAccount'

void cleanup() {
server.shutdown()
}

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

void 'should get a list of triggers for a given account'() {
def triggers = [
new GoogleCloudBuildTrigger("trigger1", "myTrigger1", "My desc 1"),
new GoogleCloudBuildTrigger("trigger2", "myTrigger2", "My desc 2")
]
given:
1 * igorService.getGoogleCloudBuildTriggers(ACCOUNT) >> triggers

when:
MockHttpServletResponse response = mockMvc.perform(get("/gcb/triggers/${ACCOUNT}")
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
response.status == 200
response.contentAsString == objectMapper.writeValueAsString(triggers)
}

void 'should get an empty list when no triggers defined for a given account'() {
given:
1 * igorService.getGoogleCloudBuildTriggers(ACCOUNT) >> []

when:
MockHttpServletResponse response = mockMvc.perform(get("/gcb/triggers/${ACCOUNT}")
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
response.status == 200
response.contentAsString == '[]'
}

void 'Should return correct message when account not found in downstream service'() {
def downstreamResponse = new Response("blah", 404, "Not found", [], null)
given:
1 * igorService.getGoogleCloudBuildTriggers(ACCOUNT) >> {String account ->
throw RetrofitError.httpError("blah", downstreamResponse, null, null)
}

when:
MockHttpServletResponse response = mockMvc.perform(get("/gcb/triggers/${ACCOUNT}")
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
response.status == 404
}

}

0 comments on commit 3c58ab7

Please sign in to comment.