📜 ⬆️ ⬇️

We write deploy-script for Grails

Why do I need a deploy script


Grails applications are very easy to assemble in a WAR. This is done like this:

grails war 

In addition to the fact that WAR is going, I really want this WAR to be installed on the server. In our case, this is Tomcat. Manual installation requires some fuss:
  1. Stop the server. Kill the process if he did not stop himself.
  2. Delete old application files (just in case)
  3. Copy the new WAR to the server. Sometimes it needs to be renamed (say, in ROOT.war)
In Maven, for example, cargo plugin can do this work. But there are a lot of adventures and settings with him, and he doesn’t really take into account the particularities of the server.

We can also use a shell script. But why write in the inconvenient shell language when there is a great cross-platform language Groovy?
')

Writing a Groovy Script


Grails makes it easy to create scripts on Groovy command-line that are added to the scripts folder of our application. Any of these scripts can then be run using the grails console command . We will write a script called Deploy.groovy .

It's nice that inside the script you can use the configuration data of our Grails-application. For example, the server name and user name are environment-specific. For Grails 1.3.7, we can access the configuration like this:

 depends(compile) depends(createConfig) /*   Gant,   Config.groovy */ def host = ConfigurationHolder.config?.deploy.host def username = ConfigurationHolder.config?.deploy.username 

It is assumed that where inside grails-app / conf / Config.groovy there will be approximately the following lines:

 ... environments { production { ... deploy { host = 'www1.shards.intra' username = 'deployer' } } } 

A little trick is that in order to load the configuration, you first need to compile the Config.groovy file. To do this, we declared depends (compile) , where compile is the already known Gant command (task) to compile the project.

Using SSH


We need to perform many operations on the server with SSH access. For simplicity, I took the free JSch library and limited myself to the password access option. Therefore, our script starts like this:

 @GrabResolver(name='jcraft', root='http://jsch.sourceforge.net/maven2') @Grab(group='com.jcraft', module='jsch', version='0.1.44') import com.jcraft.jsch.* 

Further we will do some magic manipulations with JSch. We need two things:
We have the Groovy language at our disposal, so we will try to make mini DSL with the functions we need. Add a couple of new methods to the Session object (from JSch), which is an SSH session. First, the exec method to execute the command on the server:

 Session.metaClass.exec = { String cmd -> Channel channel = this.openChannel("exec") channel.command = cmd channel.inputStream = null channel.errStream = System.err InputStream inp = channel.inputStream channel.connect() int exitStatus = -1 StringBuilder output = new StringBuilder() try { while (true) { output << inp if (channel.closed) { exitStatus = channel.exitStatus break } try { sleep(1000) } catch (Exception ee) { } } } finally { channel.disconnect() } if (exitStatus != 0) { println output throw new RuntimeException("Command [${cmd}] returned exit-status ${exitStatus}") } output.toString() } 

For reasons of brevity, I made it so that if successful, the method displays nothing, and when errors occur, prints the entire output stream of the executed command.

Now I would also write the file to the server:

 Session.metaClass.scp = { sourceFile, dst -> ChannelSftp channel = (ChannelSftp) openChannel("sftp") channel.connect() println "${sourceFile.path} => ${dst}" try { channel.put(new FileInputStream(sourceFile), dst, new SftpProgressMonitor() { private int max = 1 private int points = 0 private int current = 0 void init(int op, String src, String dest, long max) { this.max = max this.current = 0 } boolean count(long count) { current += count int newPoints = (current * 20 / max) as int if (newPoints > points) { print '.' } points = newPoints true } void end() { println '' } }) } finally { channel.disconnect() } } 

Actually, all the main stuffing of this method is an indicator of progress.

Finally, to make a full-fledged DSL, we need a closure to which we attach our structures. This is done, for example, as follows (we attach to the doRemote method):

 Session.metaClass.doRemote = { Closure closure -> connect() try { closure.delegate = delegate closure.call() } finally { disconnect() } } 

The doRemote method forms “brackets” within which we can use the exec and scp methods.

Finally, we write the procedure itself deploy


Actually, the body of our script will look something like this:

 //   .    grails help. target(main: " WAR-  .") { ..  JSch JSch jsch = new JSch() Properties config = new Properties() config.put("StrictHostKeyChecking", "no") config.put("HashKnownHosts", "yes") jsch.config = config //    ,  host  username  . ... String password = new String(System.console() .readPassword("Enter password for ${username}@${host}: ")) Session session = jsch.getSession(username, host, 22) session.setPassword(password) session.doRemote { exec "- " ... scp warFile, '/opt/tomcat/latest/webapps/ROOT.war' ... } } 

Now, in fact, you need to understand where the WAR file is and how to tell the system that it would be good to assemble it before the deployment procedure.

This is done by the already known command Gant called depends :

 depends(clean) depends(war) 

First we clean the project, then we assemble the WAR. Regarding access to a WAR file, nothing is impossible. All scripts have access to the grailsSettings variable, from which, among other things, you can find out where it lies:

 File warFile = grailsSettings.projectWarFile 

Read more about grailsSettings in the Grails documentation.

Actually, everything is ready, there was a final touch. We have only one task declared in the script (main), let's assign it to run by default:

 setDefaultTarget(main) 

In addition, we use the built-in scripts of Grails, such as: compile , war , etc. To import them into our script (so that they can be referenced by the depends command), add the following to the top of the script:

 includeTargets << grailsScript("Clean") includeTargets << grailsScript("Init") includeTargets << grailsScript("War") 

To save space, I do not publish the final script in its entirety. View the finished script for Tomcat here .

Conclusion


We have compiled a small mini-framework for writing deploy-scripts with the ability to access servers over SSH. We can start it like this:

 grails deploy 
Running the same
 grails help deploy 
we can even get instructions on how to use the script :)

Compared to shell scripts, this gives us the following advantages:

Source: https://habr.com/ru/post/124035/


All Articles