Friday, January 19, 2018

Perl for DevOps: IO::All

A stupidly common task to perform is file and directory IO. In the Perl world, the IO::All module has wrapped up nearly every common IO task I can think of into a very expressive interface. With the many options available to perform the same task, it can fit into scripts in many different ways.

For example - as a sort of "hello world" of file IO - if I wanted to read the contents of a file, do some basic processing and then output to a new file, here is a very simple solution:

use IO::All;

my $contents < io("foo.txt");
$contents =~ s{foo}{bar}g;
$contents > io("bar.txt");

Or, if you're not a fan of operator overloading, that's cool too! Here's the same script, with a more explicit usage:

use IO::All;

my $contents = io("foo.txt")->slurp;
$contents =~ s{foo}{bar}g;
io("bar.txt")->print($contents);

And there are a bunch more options to do similar things in the documentation.

What about reading a file backwards? This is sometimes useful to look for the last instance of an event in a log file:

use v5.10;
use IO::All;

my $io = io("/var/log/maillog");
$io->backwards;
while (my $line = $io->getline) {
  if ($line =~ m{ dsn = 4\.5\.0 }xms) {
    say "last success: $line";
    last;
  }
}

What About Directories?

Perhaps we wanted to traverse /var/log recursively and list out anything that's gzip compressed:

use v5.10;
use IO::All;

my @files = io('/var/log')->deep->all_files;
foreach my $file (grep { $_->ext eq 'gz' } @files) {
  say $file->name;
}

Something I've had to do on more than one occasion - when bringing up and initialising a new VM - is create a directory structure and all of the parent directories with it:

use IO::All;

foreach my $a ('0' .. '9', 'a' .. 'f') {
  foreach my $b ('0' .. '9', 'a' .. 'f')
    io->dir("/var/my-application/tmp/$a/$b")->mkpath;
  }
}

So What?

The tendency is to just use bash scripts for a lot of these tasks. But bash scripts become unwieldy when the scope of a tiny script creeps, and it now needs to compress files, encrypt data, maybe upload stuff to S3, logging everything it does along the way to a centralised location, perhaps logging all errors to a Slack channel, or maybe just sending a notification to a Slack channel when the job is done. Perl is more than ready to handle those tasks.

I'll tackle some of these modules and tasks in more detail in future posts.

Worthy Mention: Path::Tiny

Although more can be accomplished with IO::All, the Path::Tiny module is also worth knowing about. There have been certain times where I've needed more specific control that IO::All doesn't provide. In those cases, Path::Tiny usually does what I want, so it's a handy backup tool and worth knowing about.

Between these two modules, pretty much all filesystem IO needs should be taken care of.

So Much More

I'd encourage anyone to look through the docs. There are tons of examples for all kinds of tasks that I haven't touched on at all in this post, even as far as being able to send emails via a plugin.

Unless - or until - you need to use the low-level IO functions for fine-grained control and/or better performance for a critical piece of functionality, the IO::All module (and Path::Tiny as its companion) should be more than enough almost all of the time.

Friday, January 12, 2018

Handy Skills: Start a Campfire

It's been a while since I've done one of these! I thought this was a good one to post, having just returned from a camping trip last week and already dying to go on the next one.

Being able to start a fire is a super handy skill to have, more so when camping, but even when at someone's house with a fire pit of some sort. Most people who've never really started one think it's dead simple, only to fail miserably when they have to actually do it on their own. I know I did, at least.

Even if you've got fire starters and a deep fire pit to protect from any wind, if you don't do it right, it can take a lot more effort to get the fire going than is necessary. Ideally, I wanted to get to a point where I could start a fire with just a lighter (or matches) and not have to rely on anything else that I may either forget to take camping, or that I may run out of. When you go camping with friends who smoke, you're never short on lighters :)

The video that gave me all of the info that I needed was The Bird Nest and Tinder Bundle from Dave Canterbury (below). This is a great video about creating a bird nest to start a fire. From this video, you can skip the use of the char cloth to ignite the bundle and just use a Bic lighter, and I don't necessarily create my bird nest as big as in the video, but the practice of gathering or creating a bunch of fine, dry material for the bird nest - that'll catch fire quickly and easily - and gathering small sticks for kindling to then build on top of with larger logs is probably 95% of what I needed to reliably and consistently get a fire going. Another quick example can be seen in the Basic Camp Overnighter series too.

The Upside Down Fire is another good idea for getting a campfire going without needing much attention to maintain (while you go do other things), and I've started these a couple of times with great success. The biggest benefit to using this style of fire in winter is that the fire starts on a dry piece of wood, rather than on the ground, which may be wet or damp, and the above method of starting a fire with a bird nest is still relevant.