Sunday, May 28, 2017

Create a Secured Restful API App with Grails 3, PostgreSQL and JMS - Step by Step: Part 4 of 5

Posts on this topic

Part 4: Secure end-points fully and cleanly

I left my last post with basic domain objects (Artist, ArtWork, Specification) created, and one of the core domain objects (Artistexposed as a RESTful resource by leveraging Grails provided @Resource annotation. Without writing any further code, I got all CRUD operations for Artist resource working in RESTful way. That's pretty neat out-of-the-box default implementation provided by Grails framework. I also ended my last post with a note about not-so-readable UUID and date formats. This post is a continuation of previous and is all about securing Artist resource end-point fully.

Environment: Grails 3.1.6, Java 1.8, IntelliJ 15 on Mac OS X 10.9.5

Step 1 First, let's make id, dateCreated and lastUpdated formats more readable
There are multiple ways to customize data formatting.

i) The easiest way is to simply register JSON marshallers as shown below in Bootstrap.groovy. Grails runs grails-app/init/*Bootstrap classes' init closure(s) at the startup of the application.

grails-app/init/Bootstrap.groovy
class BootStrap { def init = { //register JSON marshaller for Date grails.converters.JSON.registerObjectMarshaller(Date){ return it?.format('MM/dd/yyyy') } //register JSON marshaller for UUID grails.converters.JSON.registerObjectMarshaller(UUID){ return it?.toString() } ... } ... }

ii) Another way to register marshallers is by defining a Spring bean that registers all marshallers as shown below:

src/main/groovy/com/giri/marshallers/CustomObjectMarshaller.groovy
package com.giri.marshallers /** * Custom object marshaller trait for all custom object marshallers to implement. */ trait CustomObjectMarshaller { abstract void register() }

src/main/groovy/com/giri/marshallers/UUIDMarshaller.groovy
package com.giri.marshallers import grails.converters.JSON /** * UUID marshaller, registers a {@link JSON} marshaller to output the string representation of {@link UUID} */ class UUIDMarshaller implements CustomObjectMarshaller { @Override void register(){ JSON.registerObjectMarshaller(UUID){ UUID uuid-> return uuid.toString() } } }

src/main/groovy/com/giri/marshallers/DateMarshaller.groovy
package com.giri.marshallers import grails.converters.JSON /** * Date marshaller, registers a {@link JSON} marshaller to output the string representation of {@link Date} */ class DateMarshaller implements CustomObjectMarshaller { @Override void register() { JSON.registerObjectMarshaller(Date) {Date date -> return date.format('MM/dd/yyyy') } } }

src/main/groovy/com/giri/marshallers/CustomMarshallerRegistrar.groovy
package com.giri.marshallers import javax.annotation.PostConstruct /** * Custom Marshaller Registrar, registers custom object marshallers with spring. * Configured as a spring managed bean in resources.groovy * * @see resources.groovy */ class CustomMarshallerRegistrar { /** List of custom marshallers to be registered, initialized with bean configuration in resources.groovy */ List marshallers @PostConstruct void registerCustomMarshallers() { marshallers.each{ it.register() } } }

grails-app/conf/spring/resources.groovy
import com.giri.marshallers.CustomMarshallerRegistrar import com.giri.marshallers.DateMarshaller import com.giri.marshallers.UUIDMarshaller beans = { //JSON Marshallers customMarshallerRegistrar(CustomMarshallerRegistrar) { marshallers = [ new UUIDMarshaller(), new DateMarshaller() ] } }

With this, UUID and date formats in the response look like:
$ curl -i -X GET 'http://localhost:8080/api/artists' HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... [{"id":"8d6698a1-03db-4676-973b-bb374aa1381c","dateCreated":"05/27/2017","firstName":"Giri","lastName":"Pottepalem","lastUpdated":"05/27/2017"}]

iii) There is even a better way of customizing the response using Grails recent addition- JSON views, which is not covered in this post.

Step 2 Secure Resource end-point

Let's start securing Artist resource end-point.

When domain class is annotated with @Resource, Grails provides  RestfulController implementation for CRUD actions, maps them to appropriate HTTP method verbs and makes the resource accessible at the end-point specified through uri property of @Resource annotation in RESTful way.

In addition, Spring security core plugin's @Secured annotation can be applied on the domain object to secure the resource. In my previous post's Step 9, I allowed everyone access to /api/artists end-point by annotating Artist domain object with @Secured(['permitAll']). With this all CRUD operations are allowed without a login. We need to secure this resource now.

Let's say we want to allow only Admin user to access Artist resource. This can easily be done by changing the annotation to @Secured(['ROLE_ADMIN']) or simply to @Secured('ROLE_ADMIN')

With that, the end-point /api/artists is now secured and is accessible to only users Admin role. Let's test it.

Get Artists
$ curl -i -X GET 'http://localhost:8080/api/artists' HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 Content-Type: application/json;charset=UTF-8 ...

Login as Admin
$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"username":"admin","password":"admin"}' http://localhost:8080/api/login HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Cache-Control: no-store Pragma: no-cache Content-Type: application/json;charset=UTF-8 Content-Length: 93 Date: Wed, 24 May 2017 22:14:07 GMT {"username":"admin","roles":["ROLE_ADMIN"],"access_token":"ucsbqbd3f26fjpb5b6794ph7cbu3fqq2"}

Get Artists as logged in Admin
$ curl -i -H "X-Auth-Token: ucsbqbd3f26fjpb5b6794ph7cbu3fqq2" http://localhost:8080/api/artists HTTP/1.1 200 OK Server: Apache-Coyote/1.1 X-Application-Context: application:development Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Wed, 24 May 2017 22:15:13 GMT []

Post an Artist
$ curl -i -X POST -H "Content-Type:application/json" -d '{ "firstName": "Giri", "lastName": "Pottepalem" }' 'http://localhost:8080/api/artists' HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Wed, 24 May 2017 22:25:52 GMT {"timestamp":1495664752925,"status":403,"error":"Forbidden","message":"Access Denied","path":"/api/artists"}

Post an Artist as logged in Admin
$ curl -i -X POST -H "X-Auth-Token: ucsbqbd3f26fjpb5b6794ph7cbu3fqq2" -H "Content-Type: application/json" -d '{ "firstName": "Giri", "lastName": "Pottepalem" }' 'http://localhost:8080/api/artists' HTTP/1.1 201 Created Server: Apache-Coyote/1.1 X-Application-Context: application:development Location: http://localhost:8080/api/artists/a0480de2-d5df-43eb-a919-196e34c40ab5 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Wed, 24 May 2017 22:53:07 GMT {"id":"a0480de2-d5df-43eb-a919-196e34c40ab5","dateCreated":"05/24/2017","firstName":"Giri","lastName":"Pottepalem","lastUpdated":"05/24/2017"}

Step 3 Secure Resource end-point properly and fully

Though @Secured('ROLE_ADMIN') makes the resource secured easily to the role specified, this may not meet the actual security requirements. Let's say, the right level of security we want to apply to the end-point: /api/artists is as follows:
  • Allow everyone to see the list of Artists
  • Only allow admin to create/delete an Artist
  • A logged in Artist can only see/update his/her details
We have now specific security logic that we need to apply to different actions on the resource. This level of customization is not possible with @Secured annotation applied at the resource-level. It requires some customization at the action level and this is where we can implement our own REST controller for the resource to achieve this. Grails comes with grails.rest.RestfulController base implementation that can be extended. This is not required but gives you some common base logic that can be leveraged.

Let's generate a REST controller now for the resource/domain object. Grails 3 offers create-restful-controller command for creating a RESTful controller.

$ grails create-restful-controller com.giri.Artist | Created grails-app/controllers/com/giri/ArtistController.groovy

The generated class looks like:
package com.giri import grails.rest.* import grails.converters.* class ArtistController extends RestfulController { static responseFormats = ['json', 'xml'] ArtistController() { super(Artist) } }

The generated controller is minimal with default implementation for all actions derived from the base RestfulController class provided by Grails. With this controller in place for our customization we no longer need @Resource and @Secured annotations on the domain class. Let's remove those and add URL mappings in UrlMappings.groovy for the resource.

grails-app/contrllers/giri/api/UrlMappings.groovy
package giri.api class UrlMappings { static mappings = { ... "/api/artists"(resources: 'artist') }

With this we can run grails url-mappings-report command to check url mappings for the resource end-point. It should look same as it was with @Resource applied on the Artist domain class. Now we can provide necessary action-methods implementations and secure each action-method with @Secured annotation and achieve custom security that we wanted for the end-point's each HTTP verb mapped to a specific action-method.

The customized ArtistController class looks like(with custom security highlighted):
package com.giri import grails.plugin.springsecurity.annotation.Secured import grails.rest.RestfulController import grails.transaction.Transactional /** * Customized Artists RestfulController. * * @author Gpottepalem * Created on May 26, 2017 */ class ArtistController extends RestfulController { static responseFormats = ['json', 'xml'] ArtistController() { super(Artist) } @Secured('permitAll') @Override def index(Integer max) { super.index(max) } @Secured('ROLE_USER') @Override def show() { super.show() } @Secured('ROLE_ADMIN') @Override def save() { super.save() } @Secured('ROLE_USER') @Override def update() { super.update() } @Secured('ROLE_ADMIN') @Override def delete() { super.delete() } }
Not much customization is done to achieve this other than overwriting just needed action-methods and annotating them properly per security requirements. All overwritten methods simple delegate the implementation to the super class.
Gotcha: There is no need for @Transactional annotation for methods like save(), update() and delete() as they all simply call corresponding super methods and all super methods are annotated appropriately for transactionality. In fact, annotating these methods again in this kind of implementation results into exception ;)

Step 4 Test fully customized Resource end-point

With the required customization done, let's take a spin and test it. Note that I have bootstrapped an admin user and a me user as specified in Step 4 of my earlier post.

# GET Artists (index) $ curl -i -X GET 'http://localhost:8080/api/artists' HTTP/1.1 200 OK ... []

# Login as Admin $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"username":"admin","password":"admin"}' http://localhost:8080/api/login HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... {"username":"admin","roles":["ROLE_ADMIN"],"access_token":"h1tdbs1cc8e7qt1bt7ohpsar57nt8car"}

# Login as me user $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"username":"me","password":"password"}' http://localhost:8080/api/login HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... {"username":"me","roles":["ROLE_USER"],"access_token":"ci9ct5hocreljl5pbqga60npsi8ol03f"}

# POST Artist as user (save) $ curl -i -X POST -H "X-Auth-Token: ci9ct5hocreljl5pbqga60npsi8ol03f" -H "Content-Type: application/json" -d '{ "firstName": "Giri", "lastName": "Potte" }' 'http://localhost:8080/api/artists' HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 ... {"timestamp":1495891839637,"status":403,"error":"Forbidden","message":"Access is denied","path":"/api/artists"}

# POST Artist as admin (save) $ curl -i -X POST -H "X-Auth-Token: h1tdbs1cc8e7qt1bt7ohpsar57nt8car" -H "Content-Type: application/json" -d '{ "firstName": "Giri", "lastName": "Potte" }' 'http://localhost:8080/api/artists' HTTP/1.1 201 Created Server: Apache-Coyote/1.1 ... {"id":"8d6698a1-03db-4676-973b-bb374aa1381c","dateCreated":"05/27/2017","firstName":"Giri","lastName":"Potte","lastUpdated":"05/27/2017"}

# GET Artists (index) $ curl -i -X GET 'http://localhost:8080/api/artists' HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... [{"id":"8d6698a1-03db-4676-973b-bb374aa1381c","dateCreated":"05/27/2017","firstName":"Giri","lastName":"Potte","lastUpdated":"05/27/2017"}]

# GET an Artist (show) $ curl -i -X GET 'http://localhost:8080/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c' HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 ... {"timestamp":1495893259443,"status":403,"error":"Forbidden","message":"Access Denied","path":"/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c"}

# GET an Artist (show) as admin - secured for ROLE_USER $ curl -i -X GET -H "X-Auth-Token: h1tdbs1cc8e7qt1bt7ohpsar57nt8car" 'http://localhost:8080/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c' HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 ... {"timestamp":1495893471587,"status":403,"error":"Forbidden","message":"Access is denied","path":"/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c"}

# GET an Artist (show) as me user - secured for ROLE_USER $ curl -i -X GET -H "X-Auth-Token: ci9ct5hocreljl5pbqga60npsi8ol03f" 'http://localhost:8080/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c' HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... {"id":"8d6698a1-03db-4676-973b-bb374aa1381c","dateCreated":"05/27/2017","firstName":"Giri","lastName":"Potte","lastUpdated":"05/27/2017"}

# PUT Artist as admin (update) $ curl -i -X PUT -H "X-Auth-Token: h1tdbs1cc8e7qt1bt7ohpsar57nt8car" -H "Content-Type: application/json" -d '{ "lastName": "Pottepalem" }' 'http://localhost:8080/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c' HTTP/1.1 403 Forbidden ... {"timestamp":1495892176757,"status":403,"error":"Forbidden","message":"Access is denied","path":"/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c"}

# PUT Artist as me user (update) $ curl -i -X PUT -H "X-Auth-Token: ci9ct5hocreljl5pbqga60npsi8ol03f" -H "Content-Type: application/json" -d '{ "lastName": "Pottepalem" }' 'http://localhost:8080/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c' HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... {"id":"8d6698a1-03db-4676-973b-bb374aa1381c","dateCreated":"05/27/2017","firstName":"Giri","lastName":"Pottepalem","lastUpdated":"05/27/2017"}

# GET Artists (index) $ curl -i -X GET 'http://localhost:8080/api/artists' HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... [{"id":"8d6698a1-03db-4676-973b-bb374aa1381c","dateCreated":"05/27/2017","firstName":"Giri","lastName":"Pottepalem","lastUpdated":"05/27/2017"}]

# DELETE Artist as user (delete) $ curl -i -X DELETE -H "X-Auth-Token: ci9ct5hocreljl5pbqga60npsi8ol03f" 'http://localhost:8080/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c' HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 ... {"timestamp":1495892582172,"status":403,"error":"Forbidden","message":"Access is denied","path":"/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c"}

# DELETE Artist as admin (delete) $ curl -i -X DELETE -H "X-Auth-Token: h1tdbs1cc8e7qt1bt7ohpsar57nt8car" 'http://localhost:8080/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c' HTTP/1.1 204 No Content Server: Apache-Coyote/1.1 ...

# GET Artists (index) $ curl -i -X GET 'http://localhost:8080/api/artists' HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... []

# Logout admin $ curl -i -X POST -H "X-Auth-Token: h1tdbs1cc8e7qt1bt7ohpsar57nt8car" http://localhost:8080/api/logout HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ...

# Logout user $ curl -i -X POST -H "X-Auth-Token: ci9ct5hocreljl5pbqga60npsi8ol03f" http://localhost:8080/api/logout HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ...

Everything looks good except that any logged-in user can see/update any other user as we only secured update method to ROLE_USER. We can easily add some custom logic to show() and update() action-methods to lock-down these actions further so that a logged-in user can only see/update his/her own user. Grails Spring Security core plugin provides SpringSecurityService class that can be leveraged to achieve this. Since the basic domain model currently I have has not evolved enough for making this check, I am only showing pseudo-coding-steps here:
class ArtistController extends RestfulController { SpringSecurityService springSecurityService ... def update() { AppUser currentUser = springSecurityService.currentUser as AppUser AppUser updateArtist = //Find user account of the Artist's id (params.id) being updated if(currentUser != updateArtist) { respond([message: 'Access Denied'], status: HttpStatus.FORBIDDEN) return } else { ... } }

Gotchas

Grails Spring Security Core plugin's login form
The default Grails Spring Security Core plugin provided login action url: /login/auth when accessed runs into an exception upon not finding an associated view resulting into Internal Server Error response. This is available due to "/$controller/$action?/$id?(.$format)?" mapping in UrlMappings.groovy and /login/auth is mapped to LoginController's auth() action-method provided by Grails Spring Security Core plugin. There is no point in having this wide-open anymore as it provides a form-based login for web application which is not used with Grails Spring Security REST plugin. So, let's lock it down.

When /login/auth is accessed, it runs into the following exception:
javax.servlet.ServletException: Could not resolve view with name '/login/auth' in servlet with name 'grailsDispatcherServlet'

And the response looks like:
$ curl -i -X GET 'http://localhost:8080/login/auth' HTTP/1.1 500 Internal Server Error Server: Apache-Coyote/1.1 ... Connection: close {"message":"Internal server error","error":500}

Lock it down by adding the following pattern to staticRules in application.groovy
grails.plugin.springsecurity.controllerAnnotations.staticRules = [ [pattern: '/login/auth', access: ['denyAll']] //lock down spring security login form url ... ] //Spring Security REST API plugin config String statelessFilters = 'JOINED_FILTERS, -exceptionTranslationFilter, -authenticationProcessingFilter, -securityContextPersistenceFilter, -rememberMeAuthenticationFilter' //common def filterChainChainMaps = [ //Stateless chain [pattern: '/api/**', filters: statelessFilters], [pattern: '/**', filters: statelessFilters] //Traditional stateful chain - We are stateless, no stateful chain is required ] grails.plugin.springsecurity.filterChain.chainMap = filterChainChainMaps

With this, when we access /login/auth, we get the following response:
$ curl -i -X GET 'http://localhost:8080/login/auth' HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 ... {"timestamp":1495926839720,"status":403,"error":"Forbidden","message":"Access Denied","path":"/login/auth"}

Using Custom subclass of RestfulController with @Resource annotation
If you prefer to annotate your custom RestfulController with @Resource instead of mapping the resource in UrlMappings.groovy, there is a small section in Grails docs that describes how to get this done. However, it has some limitations at the time of my exploration as I had to place the controller under src/main/groovy instead of under grails-app/controllers.

References

Saturday, May 13, 2017

Running Tests - differences between Grails 2 and Grails 3 . . .

Grails - A highly-productive and rapid application development framework for developing modern applications on JVM which not only promotes several good design & development practices but also promotes testing. It takes testing as an integral part of development by making many hard things simple. It also makes writing & running tests as fun as development ;)

Grails 3 got so much better than Grails 2. However, there are several differences between Grails 2 and Grails 3 applications and testing is no exception. Due to these differences, confusions arise running tests while switching between Grails 2 and Grails 3 applications. Grails documentation covers this in detail, but it's handy to have all these at one place, and hence I write this blog post ;)

Grails 2 way

Usage grails [environment] test-app [SpecificTest ...] [test-phase:[spock]...]

[environment] is optional, defaults to test, when specified it can be just dev (shortcut for development) or -Dgrails.env=<environment> where <environment> can be development, test or custom-env .

[SpecificTest ...] is optional and when specified, it can take one or more SpecificTests to run separated by a space, each SpecificTest can also take wild card * at either class-level or package-level.

[test-phase:[spock] ...] is optional, defaults to all test phases, unit:, integration: and functional:test phases.  When specified it can be one, any two or all three of test phases separated by space.  The extra spock is only needed for running spock specifications.

Examples # run all tests: unit, integration and functional in test environment grails test-app # run all tests: unit, integration and functional in development environment grails dev test-app grails -Dgrails.env=development test-app # run all tests in 'specific-env' grails -Dgrails.env=<specific-env> test-app # run all unit tests in test environment grails test-app unit: # run all unit and functional tests in test environment grails test-app unit: functional: # run all unit tests in a specific package in test environment grails test-app com.giri.* unit: # run all unit & integration tests in a specific package in test environment grails test-app com.giri.* unit: integration: # run selected unit tests in test environment grails test-app Test1 Test2 unit: # run all unit tests in a package, particular test method of a test-case & specific test-case in test environment grails test-app com.giri.* Test1.testMethod1 MyTest2 unit: # run all spock unit test specifications in test environment grails test-app unit:spock

Grails 3 - Grails way

Grails 3 changed it's build system from GANT (Groovy ANT) to Gradle. It provides Grails commands for Gradle tasks. Gradle doesn't distinguish integration and functional tests and hence in Grails 3 both integration and functional test-phases are combined into integration test phase. In other words, both integration and functional tests are run as part of integration test-phase. Unlike Grails 2 which takes test-phase as test-phase:, Grails 3 takes test-phase as -test-phase.

Note the difference: at the end of test-phase vs. - in the beginning of the test-phase. Also, there is no need for giving any special indication for Spock specifications.

Usage grails [environment] test-app [SpecificTest ...] [-test-phase...]

[environment] is optional, defaults to test, when specified it can be dev (shortcut for development) or -Dgrails.env=environment where environment can be development, test or custom-env .

[SpecificTest ...] is optional and when specified, it can take one or more SpecificTests to run separated by space, each SpecificTest can also take wild card * at either class-level or package-level

[-test-phase ...] is optional, defaults to all: -unit and -integration. When specified it can be one, or both test phases separated by space.

Examples (with grails command) # run all tests: unit, integration and functional in test environment grails test-app # run all tests: unit, integration and functional in development environment grails dev test-app grails -Dgrails.env=development test-app # run all tests in 'specific-env' grails -Dgrails.env=<specific-env> test-app # run all unit tests grails test-app -unit # run all unit, integration & functional tests. Equivalent to not specifying test-phases at all grails test-app -unit -integration # run all unit tests in a specific package grails test-app com.giri.* -unit # run all unit, integration and functional tests in a specific package grails test-app com.giri.* -unit -integration # run selected unit tests grails test-app Test1 Test2 -unit # run all unit tests in a package, particular test method of a test-case & specific test-case grails test-app com.giri.* Test1.testMethod1 MyTest2 -unit

Grails 3 - Gradle way

Grails 3 has Gradle as it's build system and hence one can also use gradle tasks directly to run tests. There are only two test-phases: unit(test) and integration (integrationTest). Integration test-phase covers both integration and functional. Following are examples of running tests with gradle command:

Examples (with gradle command, it's recommended to use gradle wrapper: gradlew) # run all unit tests, units tests don't need to be run in a specific environment ./gradlew test # run all unit tests in a specific package ./gradlew test --tests com.giri.* # run all unit tests in a specific package matching the specific test class name pattern ./gradlew test --tests com.giri.My*Spec # run specific unit test spec ./gradlew test --tests com.giri.Test1Spec # run specific unit test-spec and a specific feature method (when method name is in JUnit style) ./gradlew test --tests com.giri.Test1Spec.testFeatureMethod # run specific unit test-spec and a specific feature method (method name is in Spock style) ./gradlew test --tests com.giri.Test1Spec."test feature method" # run all integration and functional tests in development environment ./gradlew -Dgrails.env=development integrationTest #run integration and functional tests in 'specific-env' ./gradlew -Dgrails.env=<specific-env> integrationTest # clean & run all integration and functional tests in development environment. Continue on failing unit tests ./gradlew -Dgrails.env=development clean test integrationTest --continue # assemble but skip running tests (assemble depends on integrationTest) ./gradlew -Dgrails.env=development assemble -x integrationTest NOTE # run all unit tests in a package, a particular test method of a test-case and a specific test-case I haven't found a way to get this... #./gradlew test --tests com.giri.* Test1.testMethod1 MyTest2

Knowing all these differences and possible ways of running tests will definitely be a time saver in a developer's day-to-day development.

My Other posts on Grails Testing

References

Sunday, January 01, 2017

Get release details into your Grails-3 application . . .

It is always a good practice to make application release details like: commit id, version number and build tag etc. available within the application for display as a reference to know the current deployed code state of the application. Typically, these details can be taken into the application during build, specifically during the assembling phase of creating a deployable artifact (war or jar archive file). In an integrated development environment, Continuous Integration (CI) platforms like Jenkins, Bamboo can pass in release details to the build job which is setup to run application's build task(s).

In Grails-3 applications, this can simply be achieved by hooking into the underlying Gradle build system's assemble task. This involves writing customizing processResources task in build.gradle build file something like as shown below:

build.gradle
... //filters, populates BUILD_TAG and GIT_REVISION in the resource file: application.yml with their //actual values passed through -P flag when 'assemble' task is run processResources { println "Processing resources..." def buildTag = project.properties['BUILD_TAG'] ?: 'undefined' def gitRevision = project.properties['GIT_REVISION'] ?: 'undefined' filesMatching("**/application.yml") { println "Replacing BUILD_TAG and GIT_REVISION in application.yml with ${buildTag} and ${gitRevision}..." expand( [ 'BUILD_TAG' : buildTag, 'GIT_REVISION': gitRevision ] ) } } ...

That's no big deal, but if you have more than one Grails-3 application in your organization, with this strategy, you end up copying such customized build code snippet across all projects.

Grails framework applies and promotes DRY (Don't Repeat Yourself) principle. Gradle offers a plugin architecture for sharing reusable build logic across many projects'  build scripts. Gradle's plugin architecture can be leveraged to put a DRY solution to this problem. So, an ideal DRY solution to this problem in a Grails-3 environment is to make this piece of build logic packaged into as a simple custom Gradle Plugin.

I recently created a simple Gradle plugin as a DRY solution to this problem and hosted it on our organization's Bintray repository. The source code is hosted and available on Github.

Plugin Source Code - https://github.com/gpottepalem/gradle-release-info
Plugin repository - https://bintray.com/goodstartgenetics/gradle-plugins/release-info

References