Creating an API with Sinatra and wrapping Puppet Code around it
Another week, another commute to a different country. You do have to love Europe!.
Anyway, with an hour on my hands, I starting thinking back to, well, probably the question I get asked the most: “What can you do with Puppet?”.
There is probably no short or straight answer to it, so I tend to start enumerating anything that might be relevant, and lately I’ve been finishing up with, “and also, if you have anything that expose resources through an API, well you can wrap Puppet Code around it!”. An excellent example of this, could be Gareth’s AWS module. It is also, too complicated to illustrate a principle, since it’s a fairly complex module, with quite a bit of Ruby code. For those who don’t know me, I don’t do Ruby… well… kind of.
Ruby does have a number of brilliant gems (see what I did there?), and since I have been hearing about Ruby, the one that always caught my eye, was Sinatra.
It is one of those things that look amazingly simple, that are really powerful, and of course, I always wanted to play around with. With all these in mind, I decided to write an extremely simple REST API, and a Puppet Module around it with very simple curl calls, to demo how this would look like, and of course, I did it my way.
The absolutely basic Sinatra syntax is:
So let’s look at a commented example:
So with a few bits and bolts of Ruby code here and there, here’s my full API example, with three basic verbs.
So I’m sure there will be a lot of Ruby experts criticizing how that code is written, to be honest, and taking into account is the first piece of Ruby code I wrote, and that I lifted some examples from everywhere, I’m quite happy with my Frankenstein API.
So let’s curl around to see how my API works.
If I PUT an item there, it saves it on the file, but of course if the item exists, it won’t allow me to do it again:
Note that when I try to PUT the item the second time, it returns an HTTP code of 422. By the way, I spent a bit of time researching which should be the right http code to return in this case. I think I got it right but if anyone has a table documenting how to map these, please do send it over.
By the way, depending on the sinatra / webrick version, you may need to specify a Content-Length (even if its zero) or you might get an HTTP/1.1 411 Length Required response. It would look like this
Now how about a couple of GETs
Please note that GET, of course, if the default action, but I’m specifying it just for documentation purposes. A GET to /items is returning the whole contents of the file, while a GET to /items/specificitem, checks if the items (a string in this case) exists on the file. A GET to a non-existent item returns 404.
Finally, let’s delete something.
For all intent and purposes, that’s a great API, it even has error checking!.
But the best is yet to come, now is time to actually wrap a Puppet module around it. As I’ll be using exec, I’ve to be spot on, around managing errors. Now here’s the kicker… in order for curl to return an HTTP error code >= 400 as an exit code > 0, you have to use the -f parameter in curl.
So in this case, I basically created a module (which is available along with the full API code in https://github.com/ncorrare/ncorrare-itemsapi). The key here, is the resource definition:
That’s it! You know have a new type, to manage an API, which is fully idempotent, so you can basically:
By the way, can you find the references to Sinatra (the actual one) in the blog post? There are two (I think!)