Thursday, August 7, 2014

A Simple Gradle plugin for Wildfly (and probably jboss)

When doing EE prototype work, I like convenient ways to deploy and redeploy archives to my target app-server.   I use Netbeans, primarily, and its native build system has a pretty good set of features and plugins integrating IDE and app-server work.

For work that I plan to share, I  like to use a non-IDE based build system and gradle is my weapon of choice these days.   Gradle works well with most of the IDEs out there and it is great LCD for sharing work with out forcing my IDE preferences upon others.   It also has lots of plugins for all sorts of things; including app-server integration plugins.

Finally, I have been using Wildfly 8.x for much of my prototyping work and for my latest POC I wanted some gradle tasks do some basic local app-server control stuff  (start, stop, deploy, undeploy).  

Cargo has a nice gradle plugin that seems to handle much of those tasks for wildfly, as well as many other app-servers.     However, it seems to lack a simple local re-deploy feature.    So I decided to take on a new POC:  build my own simple gradle/wildfly plugin.

Where To Start?

Well, here is a great place to start.   Really, just do some of the samples in this chapter; it will provide the basics.  From there I browsed the source of some of the countless gradle plugins on github.   Since this was more about getting my feet wet writing a gradle plugin, I adopted a simple set of design elements and compromises:
  • Only going to concern myself with local wildfly and the 'standalone' configuration
  • Only going to allow for some configuration of wildfly parameters, in the fist iteration
  • Only going to execute 'standalone.sh' and ‘jboss-cli.sh’ to provide plugin implementation.
  • Only pay attention to deployment or EARs and WARS, right now and ignore deployments or redeployments of data sources or other configuration elements.
  • Probably going to consider making it work for JBOSS AS 7x, and JBOSS EAP 6x, too; since simple administration for all of these is just about the same.

How Does it Work?

So the gradle custom plugin documentation lays out much of what you need to know; so I will not rehash that stuff here. But here are some general steps:Extend the plugin class
  1. Choose the plugin implementation language. I chose to implement this plugin in Groovy. Gradle plugins can also be implemented in Java or Scala (or perhaps other VM languages).
  2. Build a simple project area.   This project area pretty much follows normal gradle directory conventions but groovy code goes into a root called ‘groovy’:
  3. Make an exceedingly simple build.gradle to build the plugin code:
  4. apply plugin: 'groovy'
    
    dependencies {
        compile gradleApi()
        compile localGroovy()
    }
    
  5. Extend the gradle Plugin class in a groovy class.
  6. The plugin extension has an 'apply' lifecycle method to override. This is where tasks are registered and the extension is added to a project. Other initialization activities can go in the apply method too. Sample:
  7. package org.fhw.gradle.wildfly
    
    import org.gradle.api.Project
    import org.gradle.api.Plugin
    
    class WildFlyPlugin implements Plugin {
            
        @Override
        void apply(Project project) {        
                    
            project.extensions.create("wildfly", WildFlyPluginExtension)        
            project.task('start', type: StartWildFlyTask)
            project.task('stop', type: StopWildFlyTask)        
            project.task('deploy', type: DeployWildFlyTask)                
            project.task('undeploy', type: UndeployWildFlyTask)
        }       
    }
    
  8. Make an extension class to serves as a template of what properties can be configured in a closure of the build file that uses the plugin. Like this:
  9. package org.fhw.gradle.wildfly
    
    class WildFlyPluginExtension {
        def String wildfly_home = '/opt/wildfly'
        def String start_script = 'standalone.sh'    
        def String cli_script = 'jboss-cli.sh'
        def String start_regex = '^.*started in.*- Started.*$'    
        def String path_to_deployable = null 
        def String deployment_name = null 
    }  
    
  10. Provide implementation of the registered tasks. Here is a sample:
  11. package org.fhw.gradle.wildfly
    
    import org.gradle.api.tasks.TaskAction
    import org.gradle.api.tasks.Input
    import org.gradle.api.DefaultTask
    import org.apache.tools.ant.taskdefs.condition.Os
    
    class StartWildFlyTask extends BaseWildFlyTask {
                 
        @TaskAction
        def start() {
            def binDir = getWildFlyBinDir()
            if( ! isUp() )
            {        
                ProcessBuilder builder = new ProcessBuilder(getStarterScript())
                builder.directory(new File(binDir))                    
                builder.redirectErrorStream(true)
                Process process = builder.start()            
                InputStream stdout = process.getInputStream()
                BufferedReader reader = new BufferedReader(new InputStreamReader(stdout)) 
                def reggie = getStartRegex()
                def line
                while ((line = reader.readLine()) != null) {
                    if (line.matches(reggie)) {
                        println line
                        break
                    }
                }            
            }
            else
            {
                println "seems like wildfly is already started."
            }
        }
    }
    
  12. Wrap all of the above into a jar with a gradle-plugins properties file used to indicate the class(es) that provide the new plugin. Here is a sample plugin properties file:
  13. implementation-class= org.fhw.gradle.wildfly.WildFlyPlugin
    
  14. Apply the plugin in a gradle build file that needs basic start/stop/deploy/undeploy tasks for Wildfly. Here is a sample:
  15. apply plugin: 'ear'
    apply plugin: 'wildfly'
    
    buildscript {         //this bit points to a dir that has the jar containing the built plugin. 
        repositories {
            flatDir {
                dirs '../gradle-wildfly-plugin/build/libs'   
            }
        }    
        dependencies {
            classpath name: 'gradle-wildfly-plugin'
        }
    }
    
    // other stuff….
    
    wildfly    //this bit is a closure where this build can customize the task behaviour some. 
    {
       wildfly_home = '/opt/wildfly-8.1.0.Final'
       deployment_name = 'Schlepp'
    }
    

That is the very basic anatomy of a gradle plugin! At this point, in the ‘client’ build file has new tasks are available:
$ gradle tasks
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.

//snip for brevity 

Other tasks
-----------
deploy
start
stop
undeploy
wrapper - Creates and deploys the Gradle wrapper to the current directory.

//snip for brevity 

BUILD SUCCESSFUL
The code for this post is github at https://github.com/fwelland/gradle-wildfly-plugin.

What's Next?

This example hopefully shows that it is pretty easy to create a custom plugin for gradle.  As mentioned earlier, this plugin really is just for interacting with local Wildfly instance.  It will probably also work with most JBOSS 7+ versions (and its variants) too.  In the near future I plan to (or may consider doing):
  • add in more configuration properties about the target wildfly install
  • put a copy of it somewhere to get rid of funky directory reference for plugin implementation
  • build some unit-like tests to exercise the plugin
  • shore up deploy tasks so that other archive types can be deployed
  • maybe de-brand it from wildfly and make more generic to cover jboss too
  • others?

No comments:

Post a Comment