Marcus Ramberg <insert something deeply moving and profound>

Building Ansible Modules with Perl and Mojolicious

When setting up Output, we have automated all our infrastructure using Ansible. This lets us easily do zero downtime releases and create new workers as needed to scale. We've got roles for haproxy, postgresql, nginx, our app servers and a lot more. One thing we've been handling manually so far is DNS. We're happy Cloudflare users, and they have one of the best DNS admin tools I've used, but still I felt like we could do better.

My friend Jan Henning also felt this way, and had already written Mojo::Cloudflare, a simple API client for Cloudflare. Even tho Ansible is Python-based, you can write your plugins in whatever language you like, so I decided to glue the two together. This turned out to be quite easy. First you need to tell ansible you can handle json:

#!/usr/bin/env perl
# WANT_JSON

Then we get the module argument file from the args, and parse the JSON:

use Mojo::JSON;
use Mojo::Util qw/slurp/;

my $json = Mojo::JSON->new;
my $args = $json->decode(slurp($ARGV[0]));

And then you just process those arguments and do your thing. Lets say you want to check for required arguments:

for (qw/email api_key zone name/) {
  fail("$_ is a required argument") unless exists $args->{$_};
}

My fail method looks like this:

sub fail {
  my $msg = shift;
  print $json->encode({failed => 1, message => $msg});
  exit 1;
}

The important thing here is to exit 1, the json response is icing on the cake. For success you could could do something like this:

print $json->encode({changed => \$changed, record => $current->id});

Note the bool reference to make Mojo::JSON return true/false. $changed will make the difference between OK and CHANGED output in your ansible run.

Put your module in a library/ sub directory of your playbook folder, and you can just use it in your playbook like this:

local_action: cloudflare zone=theoutput.co name=sentry type=CNAME 
content=sentry.nordaaker.com service_mode=1 email= 
api_key=

I store my Cloudflare credentials in an ansible-vault and just include that in my playbook. Note that using this module will require having the latest version of Mojo::Cloudflare installed. If you want to run it on the remote server rather than as a local_action, that server must also have the module.

Anyways, if you want to try it yourself, you can find the latest version of my cloudflare ansible module here.

Convos 0.8 - Spit and polish

This fall we released Convos 0.8, with the latest update 0.83 hitting CPAN today. As we're inching closer to 1.0, we've been focusing on polishing the user interface, fixing bugs and other rough patches in the user experience. One of the most important improvements in this round is how you switch between conversations.

Previously, when you pressed shift+enter, we shifted focus to the conversation list on the top, to let you tab through your most recent conversations . Now, shift enter will open up a panel on your right hand, where you can tab through all known channels and usernames, or you can start typing to filter the list. You can even join a new channel by typing in a unknown channel name and selecting the server to join on (if you have more than one).

Change channel dialog

The same list is available by hitting the hamburger menu to the right of the channel list, and of course this works just as well on mobile.

Change on mobile

We've also replaced the dropdowns on the top with slide in panels that works more smoothly both on the desktop and mobile.

Another new feature in the 0.8 series that I really enjoy is the recent activity line. We now track whenever a channel goes out of focus, either because you switch to another tab, or to another channel. When you return, there's a line showing you what's new since you switching away.

screenshot of activity line

Desktop notifications, which was broken in 0.7, has been fixed and improved a lot as well. Now, we ask you if you want desktop notifications like this - it works in all major browsers.

screenshot of notification
question

We've also changed the way web sockets are handled, now we always load new messages on web socket connect, significantly improving stability and performance. We've also added some more irc commands. You can now /kick people, and /names will show you who's op and voiced in the current channel at any time.

There's alot of other bugfixes and improvements as well, if you're interested in learning more you can check out the full changelog, try our demo or follow our Installation instructions to run your own.

Our main focus for the next release is cleaning up the Connection Manager, and there's already solid progress on this work. I'll leave you with this sneak peak of the new channel join dialog.

Mojolicious::Plugin::RenderSteps

This weekend I attended the MojoConf hackathon, which was great fun. I had some interesting talks with the rest of the core team, and I collaborated with Joel Berger on Mojo::PG, an adaptor for the Mojo::IOLoop for Postgres. Joel is almost done with a Pool implementation as well, and we'll probably be on CPAN sometime this week.

I also wrote a simple plugin-helper, which I think greatly simplify working with async controllers in Mojolicious. This what you have to do to setup async actions in mojolicious at the moment:

#!/usr/bin/env perl
use Mojolicious::Lite;

get '/foo' => sub {
  my $self = shift;
  $self->render_later;
  # Concurrent requests
  my $delay=Mojo::IOLoop->delay(
    sub {
      my $delay = shift;
      my $url   = Mojo::URL->new('api.metacpan.org/v0/module/_search');
      $url->query({sort => 'date:desc'});
      $self->ua->get($url->clone->query({q => 'mojo'})  => $delay->begin);
      $self->ua->get($url->clone->query({q => 'mango'}) => $delay->begin);
    },

    # Delayed rendering
    sub {
      my ($delay, $mojo, $mango) = @_;
      $self->stash (
        mojo  => $mojo->res->json('/hits/hits/0/_source/release'),
        mango => $mango->res->json('/hits/hits/0/_source/release')
      );
      $self->render;
    }
  )->catch(sub { shift->render_exception(shift) });
  $delay->wait unless Mojo::IOLoop->is_running;

With the help of my new helper, that can be turned into this:

#!/usr/bin/env perl
use Mojolicious::Lite;

plugin 'RenderSteps';

get '/foo' => sub {
  # Concurrent requests
  my $self=shift;
  $self->render_steps(
    sub {
      my $delay = shift;
      my $url   = Mojo::URL->new('api.metacpan.org/v0/module/_search');
      $url->query({sort => 'date:desc'});
      $self->ua->get($url->clone->query({q => 'mojo'})  => $delay->begin);
      $self->ua->get($url->clone->query({q => 'mango'}) => $delay->begin);
    },
    # Automatic rendering at after last step
    sub {
      my ($delay, $mojo, $mango) = @_;
      $self->stash (
        mojo  => $mojo->res->json('/hits/hits/0/_source/release'),
        mango => $mango->res->json('/hits/hits/0/_source/release')
      );
    }
  );
};

PS. We are looking for someone to host Mojoconf 2015, and we've donated 2000 EUR to get the next host kickstarted. Contact Oslo.pm if you're interested.

Monit notifications in slack #

A quick ruby script that lets you recieve monit notifications in Slack when a process is in trouble on your server.

Improved init.d script for unicorn

I went looking for a init.d script for Unicorn today, and Google seemed to give me various links to this one - It's a nice and simple script, but it has a couple of issues. For one, it's missing the LSB header, and more importantly it is missing status.

This does not make Ansible very happy, as it is using the status to check if the service is running before enforcing state=stopped or state=running. I spent some time tracking this down, so I wanted to share a fixed version with LSB headers and status implemented, in case someone else has the same problem.