diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/InfoController.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/InfoController.groovy index b02f57e76..cd1c33b9e 100644 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/InfoController.groovy +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/InfoController.groovy @@ -19,6 +19,7 @@ package com.netflix.spinnaker.igor.build import com.netflix.spinnaker.igor.config.ConcourseProperties import com.netflix.spinnaker.igor.config.GitlabCiProperties +import com.netflix.spinnaker.igor.config.GoogleCloudBuildProperties import com.netflix.spinnaker.igor.config.JenkinsProperties import com.netflix.spinnaker.igor.config.TravisProperties import com.netflix.spinnaker.igor.config.WerckerProperties @@ -69,6 +70,9 @@ class InfoController { @Autowired(required = false) ConcourseProperties concourseProperties + @Autowired(required = false) + GoogleCloudBuildProperties gcbProperties + @RequestMapping(value = '/masters', method = RequestMethod.GET) @PostFilter("hasPermission(filterObject, 'BUILD_SERVICE', 'READ')") List listMasters(@RequestParam(value = "type", defaultValue = "") String type) { @@ -84,7 +88,13 @@ class InfoController { @RequestMapping(value = '/buildServices', method = RequestMethod.GET) List getAllBuildServices() { - buildServices.allBuildServices + List allBuildServices = new ArrayList<>(buildServices.allBuildServices) + // GCB accounts are not part of com.netflix.spinnaker.igor.service.BuildServices class. + if (gcbProperties != null) { + allBuildServices.addAll(gcbProperties.getGcbBuildServices()) + } + + return allBuildServices } @RequestMapping(value = '/jobs/{master:.+}', method = RequestMethod.GET) diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/model/BuildServiceProvider.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/model/BuildServiceProvider.groovy index 73b68b75f..e56f578b6 100644 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/model/BuildServiceProvider.groovy +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/model/BuildServiceProvider.groovy @@ -22,5 +22,6 @@ enum BuildServiceProvider { TRAVIS, CONCOURSE, GITLAB_CI, - WERCKER + WERCKER, + GCB } diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/config/GoogleCloudBuildProperties.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/config/GoogleCloudBuildProperties.java index 536556fbd..a0075939f 100644 --- a/igor-web/src/main/java/com/netflix/spinnaker/igor/config/GoogleCloudBuildProperties.java +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/config/GoogleCloudBuildProperties.java @@ -16,7 +16,11 @@ package com.netflix.spinnaker.igor.config; +import com.netflix.spinnaker.fiat.model.resources.Permissions; +import com.netflix.spinnaker.igor.model.BuildServiceProvider; +import com.netflix.spinnaker.igor.service.BuildService; import java.util.List; +import java.util.stream.Collectors; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -25,11 +29,26 @@ public class GoogleCloudBuildProperties { private List accounts; + public List getGcbBuildServices() { + return this.accounts.stream().map(BuildService::getView).collect(Collectors.toList()); + } + @Data - public static class Account { + public static class Account implements BuildService { private String name; private String project; private String subscriptionName; private String jsonKey; + private Permissions.Builder permissions = new Permissions.Builder(); + + @Override + public BuildServiceProvider getBuildServiceProvider() { + return BuildServiceProvider.GCB; + } + + @Override + public Permissions getPermissions() { + return this.permissions.build(); + } } } diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/gcb/GoogleCloudBuildController.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/gcb/GoogleCloudBuildController.java index 503add9cb..fb1959f0a 100644 --- a/igor-web/src/main/java/com/netflix/spinnaker/igor/gcb/GoogleCloudBuildController.java +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/gcb/GoogleCloudBuildController.java @@ -22,6 +22,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PostFilter; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import retrofit.http.Query; @@ -34,6 +36,7 @@ public class GoogleCloudBuildController { private final GoogleCloudBuildParser googleCloudBuildParser; @RequestMapping(value = "/accounts", method = RequestMethod.GET) + @PostFilter("hasPermission(filterObject, 'BUILD_SERVICE', 'READ')") List getAccounts() { return googleCloudBuildAccountRepository.getAccounts(); } @@ -42,6 +45,7 @@ List getAccounts() { value = "/builds/create/{account}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasPermission(#account, 'BUILD_SERVICE', 'WRITE')") Build createBuild(@PathVariable String account, @RequestBody String buildString) { Build build = googleCloudBuildParser.parse(buildString, Build.class); return googleCloudBuildAccountRepository.getGoogleCloudBuild(account).createBuild(build); @@ -62,11 +66,13 @@ void updateBuild( } @RequestMapping(value = "/builds/{account}/{buildId}", method = RequestMethod.GET) + @PreAuthorize("hasPermission(#account, 'BUILD_SERVICE', 'READ')") Build getBuild(@PathVariable String account, @PathVariable String buildId) { return googleCloudBuildAccountRepository.getGoogleCloudBuild(account).getBuild(buildId); } @RequestMapping(value = "/builds/{account}/{buildId}/artifacts", method = RequestMethod.GET) + @PreAuthorize("hasPermission(#account, 'BUILD_SERVICE', 'READ')") List getArtifacts(@PathVariable String account, @PathVariable String buildId) { return googleCloudBuildAccountRepository.getGoogleCloudBuild(account).getArtifacts(buildId); } diff --git a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/build/InfoControllerSpec.groovy b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/build/InfoControllerSpec.groovy index b893848f0..d018b9a08 100644 --- a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/build/InfoControllerSpec.groovy +++ b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/build/InfoControllerSpec.groovy @@ -19,6 +19,7 @@ package com.netflix.spinnaker.igor.build import com.netflix.spinnaker.fiat.model.Authorization import com.netflix.spinnaker.fiat.model.resources.Permissions import com.netflix.spinnaker.igor.config.GitlabCiProperties +import com.netflix.spinnaker.igor.config.GoogleCloudBuildProperties import com.netflix.spinnaker.igor.config.JenkinsConfig import com.netflix.spinnaker.igor.config.JenkinsProperties import com.netflix.spinnaker.igor.config.TravisProperties @@ -55,6 +56,7 @@ class InfoControllerSpec extends Specification { JenkinsProperties jenkinsProperties TravisProperties travisProperties GitlabCiProperties gitlabCiProperties + GoogleCloudBuildProperties gcbProperties @Shared JenkinsService service @@ -70,10 +72,14 @@ class InfoControllerSpec extends Specification { server = new MockWebServer() } - void createMocks(Map buildServices) { + void createMocks(Map buildServices, List gcbAccounts = null) { cache = Mock(BuildCache) this.buildServices = new BuildServices() this.buildServices.addServices(buildServices) + if (gcbAccounts != null) { + this.gcbProperties = new GoogleCloudBuildProperties() + this.gcbProperties.accounts = gcbAccounts + } jenkinsProperties = Mock(JenkinsProperties) travisProperties = Mock(TravisProperties) gitlabCiProperties = Mock(GitlabCiProperties) @@ -82,10 +88,18 @@ class InfoControllerSpec extends Specification { buildServices: this.buildServices, jenkinsProperties: jenkinsProperties, travisProperties: travisProperties, - gitlabCiProperties: gitlabCiProperties)) + gitlabCiProperties: gitlabCiProperties, + gcbProperties: gcbProperties)) .build() } + GoogleCloudBuildProperties.Account createGCBAccount(String name) { + GoogleCloudBuildProperties.Account account = new GoogleCloudBuildProperties.Account() + account.setName(name) + account.setProject('blah') + return account + } + void 'is able to get a list of jenkins buildMasters'() { given: createMocks(['master2': null, 'build.buildServices.blah': null, 'master1': null]) @@ -98,6 +112,51 @@ class InfoControllerSpec extends Specification { response.contentAsString == '["build.buildServices.blah","master1","master2"]' } + void 'is able to get a list of google cloud build accounts'() { + given: + createMocks([:], [createGCBAccount("account1"), createGCBAccount("account2")]) + + when: + MockHttpServletResponse response = mockMvc.perform(get('/buildServices') + .accept(MediaType.APPLICATION_JSON)).andReturn().response + + then: + def actualAccounts = new JsonSlurper().parseText(response.contentAsString).collect { it.name }; + ['account1', 'account2'] == actualAccounts + + } + + void 'buildServices return empty permissions list if no permisions have been defined'() { + given: + createMocks([:], [createGCBAccount("account1")]) + + when: + MockHttpServletResponse response = mockMvc.perform(get('/buildServices') + .accept(MediaType.APPLICATION_JSON)).andReturn().response + + then: + def actualAccounts = new JsonSlurper().parseText(response.contentAsString); + actualAccounts.size == 1 + actualAccounts[0].permissions == [:] + + } + + void 'buildServices returns correctly if gcb is not defined'() { + given: + JenkinsService jenkinsService1 = new JenkinsService('master2', null, false, Permissions.EMPTY) + createMocks(['master2': jenkinsService1]) + + when: + MockHttpServletResponse response = mockMvc.perform(get('/buildServices') + .accept(MediaType.APPLICATION_JSON)).andReturn().response + + then: + def actualAccounts = new JsonSlurper().parseText(response.contentAsString); + actualAccounts.size == 1 + actualAccounts[0].name == 'master2' + + } + void 'is able to get a list of buildServices with permissions'() { given: JenkinsService jenkinsService1 = new JenkinsService('jenkins-foo', null, false, Permissions.EMPTY) @@ -109,11 +168,18 @@ class InfoControllerSpec extends Specification { new Permissions.Builder() .add(Authorization.READ, ['group-3', 'group-4']) .add(Authorization.WRITE, 'group-3').build(), false) + + GoogleCloudBuildProperties.Account gcbAccount = createGCBAccount('gcbAccount'); + gcbAccount.setPermissions(new Permissions.Builder() + .add(Authorization.READ, ['group-5', 'group-6']) + .add(Authorization.WRITE, ['group-5']) + ) + createMocks([ 'jenkins-foo': jenkinsService1, 'jenkins-bar': jenkinsService2, 'travis-baz': travisService - ]) + ], [gcbAccount]) when: MockHttpServletResponse response = mockMvc.perform(get('/buildServices') @@ -156,6 +222,18 @@ class InfoControllerSpec extends Specification { "group-3" ] } + }, + { "name": "gcbAccount", + "buildServiceProvider": "GCB", + "permissions": { + "READ": [ + "group-5", + "group-6" + ], + "WRITE": [ + "group-5" + ] + } } ]