Deploying Perl5 Applications by Sparrowdo
Tags: perl, deployment, devops, perl6
Published: 2017-11-14 13:26:09

---
title: Deploying Perl5 Applications by Sparrowdo
published: true
description: How to use Sparrowdo to deploy Perl5 applications
tags: Perl, Deployment, Devops, Perl6
---


[Sparrowdo](https://github.com/melezhik/sparrowdo) - is a configuration management tool written on Perl6. It's an efficient tool to automate servers deployments. Have you written a Perl5 application? Don't bother yourself with finding proper deployment tool. Sparrowdo to the rescue!


In the rest of the post I am going to give a step by step instruction on how to use Sparrowdo to deploy [Mojolicious](http://mojolicious.org/) application with a source code fetched from Git. What we're going to do is:

* Fetch [the source](https://github.com/melezhik/my-app) code from GitHub repository.
* Install CPAN dependencies by using [Carton/cpanfile](https://metacpan.org/pod/Carton).
* Keep all the files and run the processes under dedicated system user.
* Set up systemd script to run web application as a daemon.
* Set up a file with passwords to be used in application's [digest access authentication](https://en.wikipedia.org/wiki/Digest_access_authentication).

Are you ready? Off we go.

# Sparrowdo Install

Sparrowdo is Perl6 module so is installed by zef manager:

```
$ zef install Sparrowdo

```

Once Sparrowdo is installed we should start writing Sparrowdo scenario, let's create a file named `sparrowfile`, located in the current working directory:


```
$ nano sparrowfile
```


We run sparrowdo scenario remotely on the target host like this:


```
$ sparrowdo --host=$remote-host

```

Sparrowdo console client supports many options, follow [documentation](https://github.com/melezhik/sparrowdo#sparrowdo-client-command-line-parameters) for details. 

The minimum of options is a remote host being deployed or `--localhost_mode` if we run deploy at the localhost.

# Application source code

This is a simple Mojolicios [application](https://github.com/melezhik/my-app) with a single endpoint which if a user is successfully authenticated returns "hello world":


```
#!/usr/bin/perl

use Mojolicious::Lite;
use strict;

plugin 'digest_auth', 
  realm   => 'app',
  expires => 120,
  allow   => '.htdigest'
;

get '/' => sub {
  my $c = shift;
  return unless $c->digest_auth;
  $c->render( text => 'hello world' );
};

app->start;

```   

# Dedicated system user

We're going to keep all the data and run our application under a dedicated system user, so let's create it:


```
user "web-application";
```

# Fetching the source code from SCM

It is simple just add this:

```
git-scm 'git@github.com:melezhik/my-app.git', %( 
  to   => '/home/web-application/projects', 
  user => "web-application"
);
```

To make this work we also need to create a directory `~/projects`, so let's add this line beforehand:


```
directory "/home/web-application/projects", %(
    owner => "web-application",
    group => "web-application"
)
```



# Installing dependencies

All the dependencies are declared via cpanfile:


```
$ cat cpanfile
requires 'Mojolicious::Lite';
requires 'Mojolicious::Plugin::DigestAuth';

```

So just run a carton installer:

```
bash "cd /home/web-application/projects/my-app && carton", %(
  description => "update cpan dependencies by carton",
  user => "web-application"
);
```

To install all declared CPAN dependencies into `./local` directory.


# Populating a file with passwords

We use digest access authentication in our application. This, first of all we need to create a local file with credentials:

```
$ htdigest -c .htdigest $realm $username
```

Where $real - authentication realm is specific for an application, and the real command to create a login/password entry for login `user` would be:

```
$ htdigest -c .htdigest app user # let's set password as `1`
```

Don't forget to "hide" `.htdigest` from Git, so not to commit it occasionally:

```
$ echo .htdigest >> .gitignore
```

All is left to do is to populate password file to remote server by sparrowdo, 
we also have to store the file under the application root:

```
file '/home/web-application/projects/.htdigest', %( 
  content => slurp '.htdigest', 
  owner => 'web-application', 
  group => 'web-application' 
);
``` 

We are almost all set, the last thing we need to is set up systemd service

# Set up systemd script and run application service


With sparrowdo it's just as simple as adding few lines of code:

```
systemd-service "my-app", %(
  user => "my-app",
  workdir => "/home/web-application/projects/my-app",
  command => "/bin/bash --login -c 'cd /home/web-application/projects/my-app && carton exec ./app.pl daemon --listen http://0.0.0.0:3000'"
);

service-restart "my-app";
```

This code ensures that our application will be accessible as 0.0.0.0:3000.

# Let's give it a run

Here is an example of sparrowdo report for the localhost deployment:


```
$ sparrowdo --local_mode  --format=production

```

The output:

```
running sparrow tasks on 127.0.0.1 ... 
target OS is - ubuntu
push [task] create user web-application OK
push [task] create directory /home/web-application/projects OK
push [task] fetch from git source: https://github.com/melezhik ... OK
push [task] create file /home/web-application/projects/my-app/.htdigest OK
push [task] update cpan dependencies by carton ... OK
push [task] create template /etc/systemd/system/my-app.service OK
push [task] restart service my-app OK
SPL file /opt/sparrow/sparrow.list is empty
get index updates from SparrowHub ... OK
set up task box file - /home/melezhik/.sparrowdo//opt/sparrow/task-box.json - OK
public@user is uptodate (0.2.1)
public@directory is uptodate (0.1.5)
public@bash is uptodate (0.1.7)
public@file is uptodate (0.0.6)
public@templater is uptodate (0.0.11)
Installing modules using /opt/sparrow/plugins/public/templater/cpanfile
Complete! Modules were installed into /opt/sparrow/plugins/public/templater/local
public@service is uptodate (0.1.15)
running task box from /opt/sparrow/sparrow-cache/task-box.json ... 
2017-11-14 16:11:03 : [task] create user web-application [path] modules/change/
2017-11-14 16:11:03 : [task] create directory /home/web-application/projects [path] modules/create/
2017-11-14 16:11:03 : [task] fetch from git source: https://github.com/melezhik ... [path] modules/bash-command/ [params] envvars:
2017-11-14 16:11:04 : [task] create file /home/web-application/projects/my-app/.htdigest [path] /
2017-11-14 16:11:05 : [task] update cpan dependencies by carton ... [path] modules/bash-command/ [params] envvars:
2017-11-14 16:11:05 : [task] create template /etc/systemd/system/my-app.service [path] modules/generate-content/
2017-11-14 16:11:05 : [task] create template /etc/systemd/system/my-app.service [path] /
2017-11-14 16:11:06 : [task] restart service my-app [path] modules/stop/ [params] os:debian service:my-app
2017-11-14 16:11:06 : [task] restart service my-app [path] modules/start/ [params] os:debian service:my-app
```


# Checks



Now lets check our web app, it should be visible as process:

```
$ sudo ps aux | grep  web-application
web-app+ 29642  0.0  0.0  17808  7180 ?        Ss   16:11   0:00 /bin/bash --login -c cd /home/web-application/projects/my-app && carton exec ./app.pl daemon --listen http://0.0.0.0:3000
```

And is accessible by http:


```
$ curl --digest -uuser:1 127.0.0.1:3000
hello world
```


Thank you for reading. I hope this post was useful.