Skip to content

Commit

Permalink
Merge pull request #130 from jeyrschabu/application_api_v2
Browse files Browse the repository at this point in the history
Add V2 Application API endpoints
  • Loading branch information
jeyrschabu authored Sep 12, 2016
2 parents ed46b40 + a532765 commit 6d485a2
Show file tree
Hide file tree
Showing 9 changed files with 581 additions and 25 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter

@Configuration
Expand All @@ -53,6 +54,11 @@ public class Front50WebConfig extends WebMvcConfigurerAdapter {
)
}

@Override
void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseRegisteredSuffixPatternMatch(false)
}

@Bean
FilterRegistrationBean authenticatedRequestFilter() {
def frb = new FilterRegistrationBean(new AuthenticatedRequestFilter(true))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,14 @@ import org.springframework.http.HttpStatus
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.prepost.PostFilter
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.access.prepost.PreFilter
import org.springframework.validation.Errors
import org.springframework.validation.ObjectError
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.*

import javax.servlet.http.HttpServletResponse

@Slf4j
@RestController
@RestController("legacyApplicationsController")
@RequestMapping(["/default/applications", "/global/applications"])
@Api(value = "application", description = "Application API")
public class ApplicationsController {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@

package com.netflix.spinnaker.front50.controllers.exception

import groovy.transform.InheritConstructors
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus

@InheritConstructors
@ResponseStatus(value = HttpStatus.SERVICE_UNAVAILABLE, reason = "Exception, baby")
class ApplicationException extends RuntimeException {
public ApplicationException(Throwable cause) {
super(cause)
}
}
class ApplicationException extends RuntimeException {}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@

package com.netflix.spinnaker.front50.controllers.exception

import groovy.transform.InheritConstructors
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus

@InheritConstructors
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Application not found")
class ApplicationNotFoundException extends RuntimeException {
public ApplicationNotFoundException(Throwable cause) {
super(cause)
}
}
class ApplicationNotFoundException extends RuntimeException {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2016 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.front50.controllers.exception

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

@InheritConstructors
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class InvalidApplicationRequestException extends HystrixBadRequestException {}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@

package com.netflix.spinnaker.front50.controllers.exception

import groovy.transform.InheritConstructors
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus

@InheritConstructors
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No applications found")
class NoApplicationsFoundException extends RuntimeException {
public NoApplicationsFoundException(Throwable cause) {
super(cause)
}
}
class NoApplicationsFoundException extends RuntimeException {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package com.netflix.spinnaker.front50.controllers.v2

import com.netflix.spectator.api.Registry
import com.netflix.spinnaker.front50.events.ApplicationEventListener
import com.netflix.spinnaker.front50.controllers.exception.InvalidApplicationRequestException
import com.netflix.spinnaker.front50.model.application.Application
import com.netflix.spinnaker.front50.model.application.ApplicationDAO
import com.netflix.spinnaker.front50.model.notification.NotificationDAO
import com.netflix.spinnaker.front50.model.pipeline.PipelineDAO
import com.netflix.spinnaker.front50.model.pipeline.PipelineStrategyDAO
import com.netflix.spinnaker.front50.model.project.ProjectDAO
import com.netflix.spinnaker.front50.validator.ApplicationValidator
import groovy.util.logging.Slf4j
import io.swagger.annotations.Api
import io.swagger.annotations.ApiOperation
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.MessageSource
import org.springframework.context.i18n.LocaleContextHolder
import org.springframework.http.HttpStatus
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.prepost.PostFilter
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.validation.Errors
import org.springframework.validation.ObjectError
import org.springframework.web.bind.annotation.*

import javax.servlet.http.HttpServletResponse

@Slf4j
@RestController
@RequestMapping("/v2/applications")
@Api(value = "application", description = "Application API")
public class ApplicationsController {
@Autowired
MessageSource messageSource

@Autowired
ApplicationDAO applicationDAO

@Autowired
ProjectDAO projectDAO

@Autowired
NotificationDAO notificationDAO

@Autowired
PipelineDAO pipelineDAO

@Autowired
PipelineStrategyDAO pipelineStrategyDAO

@Autowired
List<ApplicationValidator> applicationValidators

@Autowired(required = false)
List<ApplicationEventListener> applicationEventListeners = []

@Autowired
Registry registry

@PreAuthorize("@fiatPermissionEvaluator.storeWholePermission()")
@PostFilter("hasPermission(filterObject.name, 'APPLICATION', 'READ')")
@ApiOperation(value = "", notes = """Fetch all applications.
Supports filtering by one or more attributes:
- [email protected]
- [email protected]&name=flex""")
@RequestMapping(method = RequestMethod.GET)
Set<Application> applications(@RequestParam Map<String, String> params) {
if (params.isEmpty()) {
return applicationDAO.all()
}

return applicationDAO.search(params)
}

@PreAuthorize("hasPermission(#app.name, 'APPLICATION', 'CREATE')")
@ApiOperation(value = "", notes = "Create an application")
@RequestMapping(method = RequestMethod.POST)
Application create(@RequestBody final Application app) {
return getApplication().initialize(app).withName(app.getName()).save()
}

@PreAuthorize("hasPermission(#applicationName, 'APPLICATION', 'WRITE')")
@ApiOperation(value = "", notes = "Delete an application")
@RequestMapping(method = RequestMethod.DELETE, value = "/{applicationName}")
void delete(@PathVariable String applicationName, HttpServletResponse response) {
getApplication().initialize(new Application().withName(applicationName)).delete()
response.setStatus(HttpStatus.NO_CONTENT.value())
}

@PreAuthorize("hasPermission(#app.name, 'APPLICATION', 'WRITE')")
@ApiOperation(value = "", notes = "Update an existing application")
@RequestMapping(method = RequestMethod.PATCH, value = "/{applicationName}")
Application update(@PathVariable String applicationName, @RequestBody final Application app) {
if (!applicationName.trim().equalsIgnoreCase(app.getName())) {
throw new InvalidApplicationRequestException("Application name '${app.getName()}' does not match path parameter '${applicationName}'")
}

def application = getApplication()
Application existingApplication = application.findByName(app.getName())
application.initialize(existingApplication).withName(app.getName()).update(app)
return app
}

@PreAuthorize("hasPermission(#applicationName, 'APPLICATION', 'READ')")
@ApiOperation(value = "", notes = "Fetch a single application by name")
@RequestMapping(method = RequestMethod.GET, value = "/{applicationName}")
Application get(@PathVariable final String applicationName) {
return applicationDAO.findByName(applicationName.toUpperCase())
}

@PreAuthorize("hasPermission(#applicationName, 'APPLICATION', 'READ')")
@RequestMapping(value = '{applicationName}/history', method = RequestMethod.GET)
Collection<Application> getHistory(@PathVariable String applicationName,
@RequestParam(value = "limit", defaultValue = "20") int limit) {
return applicationDAO.getApplicationHistory(applicationName, limit)
}

@PreAuthorize("@fiatPermissionEvaluator.isAdmin()")
@RequestMapping(method = RequestMethod.POST, value = "/batch/applications")
void batchUpdate(@RequestBody final Collection<Application> applications) {
applicationDAO.bulkImport(applications)
}

@ExceptionHandler(Application.ValidationException)
@ResponseStatus(HttpStatus.BAD_REQUEST)
Map handleValidationException(Application.ValidationException ex) {
def locale = LocaleContextHolder.locale
def errorStrings = []
ex.errors.each { Errors errors ->
errors.allErrors.each { ObjectError objectError ->
def message = messageSource.getMessage(objectError.code, objectError.arguments, objectError.defaultMessage, locale)
errorStrings << message
}
}
return [error: "Validation Failed.", errors: errorStrings, status: HttpStatus.BAD_REQUEST]
}

@ExceptionHandler(AccessDeniedException)
@ResponseStatus(HttpStatus.FORBIDDEN)
Map handleAccessDeniedException(AccessDeniedException ade) {
return [error: "Access is denied", status: HttpStatus.FORBIDDEN.value()]
}

private Application getApplication() {
return new Application(
dao: applicationDAO,
projectDao: projectDAO,
notificationDao: notificationDAO,
pipelineDao: pipelineDAO,
pipelineStrategyDao: pipelineStrategyDAO,
validators: applicationValidators,
applicationEventListeners: applicationEventListeners
)
}
}
Loading

0 comments on commit 6d485a2

Please sign in to comment.