Skip to content

Instantly share code, notes, and snippets.

@RafPe
Forked from jkeiser/gist:7244532
Created August 4, 2016 11:44
Show Gist options
  • Save RafPe/2e3b706b82db74b22ea33678e0e9b2c3 to your computer and use it in GitHub Desktop.
Save RafPe/2e3b706b82db74b22ea33678e0e9b2c3 to your computer and use it in GitHub Desktop.
Adventures With Chef: Ghost #1

Installing My Blog

I want to install Ghost as my blog. I want the web server, ghost, and OS, all to receive security updates on a regular schedule without me having to muck with it. (I am willing for the blog to sometimes go down because of this.)

I'll eventually want monitoring and alerting, and backups of the blog entries; uploads of the static site to my web hosting company; automatic DNS configuration; updates of the base OS. But for now I'm modest, I just want Ghost and I want updates.

I want to do this all with Chef. I want to write the Ghost cookbook myself, not because the existing Ghost cookbook is bad, but because I want to get a real feel for how these get written.

Getting Started: Test Kitchen

First thing: I need a Linux machine, since that's what I'm eventually going to install. The right way to do this is to make a test-kitchen VM, so that I can dispose of it and recreate it at will, and so that I can write tests that verify my cookbooks work.

Let's head over to test-kitchen and follow instructions!

> gem install test-kitchen --pre
> kitchen init
> gem install kitchen-vagrant

So far, so good. Now, I want to run my empty run list and see what it does.

> kitchen test
...
!!!!! Berksfile, Cheffile, cookbooks/, or metadata.rb must exist in /Users/jkeiser/oc/code/jkeiser/mysite
>>>>>> ------Exception-------
>>>>>> Class: Kitchen::ActionFailed
>>>>>> Message: Failed to complete #converge action: [Cookbooks could not be found]
>>>>>> ----------------------
>>>>>> Please see .kitchen/logs/kitchen.log for more details

No go. Looks like I need to make a cookbooks/ directory before it will be happy. I guess I can oblige.

> mkdir cookbooks
> kitchen test
...
Compiling Cookbooks...
[2013-10-31T02:29:42+00:00] FATAL: No cookbook found in "/tmp/kitchen-chef-solo/cookbooks", make sure cookbook_path is set correctly.
[2013-10-31T02:29:42+00:00] ERROR: Running exception handlers
[2013-10-31T02:29:42+00:00] ERROR: Exception handlers complete
[2013-10-31T02:29:42+00:00] FATAL: Stacktrace dumped to /tmp/kitchen-chef-solo/cache/chef-stacktrace.out
Chef Client failed. 0 resources updated
[2013-10-31T02:29:42+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)
>>>>>> Converge failed on instance <default-ubuntu-1204>.
>>>>>> Please see .kitchen/logs/default-ubuntu-1204.log for more details
>>>>>> ------Exception-------
>>>>>> Class: Kitchen::ActionFailed
>>>>>> Message: SSH exited (1) for command: [sudo -E chef-solo --config /tmp/kitchen-chef-solo/solo.rb --json-attributes /tmp/kitchen-chef-solo/dna.json  --log_level info]
>>>>>> ----------------------

Now chef-client is unhappy, because there are no cookbooks?? Why does it care? My run list is empty!

> mkdir cookbooks/ghost
> kitchen test
...

Much better! Now, it created a ton of instances and I really only care about running on one, so I comment out everything in .kitchen.yml except ubuntu 12.04 and get to work.

node.js

I read the Ghost deploy instructions to get this going. The first thing that needs to go up is node.js. I will just use the nodejs community cookbook for this.

mysite> knife cookbook site install nodejs
WARNING: No knife configuration file found
Installing nodejs to /var/chef/cookbooks
ERROR: The cookbook repo path /var/chef/cookbooks does not exist or is not a directory

Right, I didn't use -z. This really needs to be the default for knife. Let's fix that so there's no future confusion. I create a .chef/knife.rb:

local_mode true

Now, we run again:

mysite> knife cookbook site install nodejs
Installing nodejs to /Users/jkeiser/oc/code/jkeiser/mysite/cookbooks
ERROR: The cookbook repo /Users/jkeiser/oc/code/jkeiser/mysite/cookbooks is not a git repository.
Use `git init` to initialize a git repo

Ah, an existing bug. Ah well, we'll use the workaround:

mysite> cd cookbooks
mysite/cookbooks> knife cookbook download nodejs
mysite/cookbooks> tar xzvf nodejs-1.3.0.tar.gz

Here is a shitty thing: nodejs depends on yum, apt, and build-essential. Downloading the one did not download its deps. I hear Berkshelf will fix that, but there is no reason the core product should not. Anyway, I download those too, and their dependencies.

But lo:

mysite/cookbooks> knife cookbook site download apt yum build-essential
ERROR: The object you are looking for could not be found
Response:

Really??? I have to type them individually? Well, OK.

mysite/cookbooks> knife cookbook site download apt
mysite/cookbooks> knife cookbook site download yum
mysite/cookbooks> knife cookbook site download build-essential

Now I add nodejs to the runlist in .kitchen.yml:

suites:
- name: default
  run_list:
  - recipe[nodejs]
  attributes: {}

And it's time for fun!

mysite> kitchen test

It worked! Running it twice in a row now:

mysite> kitchen converge
mysite> kitchen converge
Chef Client finished, 1 resources updated
...

A tiny bit disappointing--I expect convergent resources not to do anything the second time. But not the end of the world.

Now let's just log in and see if node is really there:

mysite> kitchen login
ERROR: kitchen login was called with no arguments
Usage: "kitchen login (['REGEX']|[INSTANCE])".

Oops. There's only one instance, can I please just log in? Oh well:

mysite> kitchen list
Instance             Driver   Provisioner  Last Action
default-ubuntu-1204  Vagrant  Chef Solo    Converged
mysite> kitchen login ubuntu
Welcome to Ubuntu 12.04.2 LTS (GNU/Linux 3.5.0-23-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

101 packages can be updated.
51 updates are security updates.

Last login: Thu Oct 31 03:24:35 2013 from 10.0.2.2
vagrant@default-ubuntu-1204:~$ node -v
v0.10.15

Success!

Downloading Ghost

Next, it says to download Ghost. Time to make our ghost recipe! First, let's set it up so it depends on nodejs: edit cookbooks/ghost/metadata.rb to read:

name 'ghost'
depends 'nodejs'

Now we make our first recipe. Edit cookbooks/ghost/recipes/default.rb:

include_recipe 'nodejs'
include_recipe 'ghost::server'

Now for the fun part. What I really want is to scrape https://ghost.org/download/ looking for the ghost url. In fact, screw it, let's do that:

And cookbooks/ghost/recipes/server.rb:

# Find ghost URL at https://ghost.org/download/
def get_https(url)
  uri = URI(url)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  request = Net::HTTP::Get.new(uri.request_uri)
  response = http.request(request)
  response.error! if response.code != '200'
  response.body
end

ghost_org_html = get_https('https://ghost.org/download/')
if ghost_org_html !~ /\/zip\/(ghost-[0-9.]+.zip)/
  raise "Could not find download URL inside https://ghost.org/download/"
end
ghost_zip_url = "https://ghost.org/zip/#{$1}"
ghost_zip_filename = "#{Chef::Config[:file_cache_path]}/#{$1}"

# Download zip file
remote_file ghost_zip_filename do
  source ghost_zip_url
end

Note: it sucks I have to decide to put the file_cache_path in there. Can't I just have it decide something like that for me and tell me the path somehow?

Note 2: it doesn't TOTALLY hurt, but we really should make an easier way to get an https URL since this is a fairly common thing (I've done it twice in my last two cookbooks).

Finally, we set the run list to just Ghost (it will pick up node.js inside):

suites:
- name: default
  run_list:
  - recipe[ghost]
  attributes: {}

To save time, I just do another kitchen converge instead of a full kitchen test (which destroys the VM):

mysite> kitchen converge

Woo! We have a file. Let's unpack it. Chef has a nice little package of definitions for that called ark:

mysite> knife cookbook site download ark

Now we add depends 'ark' to cookbooks/ghost/metadata.rb, and replace everything in cookbooks/ghost/recipes/server.rb from # Download zip file with:

ark 'ghost' do
  path Chef::Config.file_cache_path
  url ghost_zip_url
  action :put
end

This is not entirely intuitive. I would have expected the path to be the full target path, but instead it unpacks to cache_path/ghost (the name of the ark resource).

mysite> kitchen converge
...
Mixlib::ShellOut::ShellCommandFailed
------------------------------------
execute[unpack /tmp/kitchen-chef-solo/cache/ghost.zip] (/tmp/kitchen-chef-solo/cookbooks/ark/providers/default.rb line 54) had an error: Mixlib::ShellOut::ShellCommandFailed: Expected process to exit with [0], but received '127'
       ---- Begin output of unzip -q -u -o /tmp/kitchen-chef-solo/cache/ghost.zip -d /tmp/d20131031-27899-1n9gke&& rsync -a /tmp/d20131031-27899-1n9gke/*/ /usr/local/ghost-1&& rm -rf  /tmp/d20131031-27899-1n9gke ----
       STDOUT:
       STDERR: sh: 1: unzip: not found
...

Doh! No unzip, really? Turns out the 'ark' default recipe takes care of that, so we may as well add include_recipe 'ark' at the top of cookbooks/ghost/recipes/server.rb.

mysite> kitchen converge

Success! A second converge re-unzips it, but ah well, such is life.

Install Ghost

The install looks straightforward. Run npm install --production from the unzipped ghost dir. Let's add that to the server recipe cookbooks/ghost/recipes/server.rb:

execute 'npm install --production' do
  cwd "#{Chef::Config.file_cache_path}/ghost"
end
mysite> kitchen converge

Well, we're getting what appear to be errors with the Ghost install procedure now. Good place to stop for the night. Actual progress, not bad!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment