One cool thing about Ansible is that you can use it to provision your OS X
boxes using the homebrew module (which I co-wrote with Andrew Dunham). Today,
I’m going to share with you how I fully automate the provisioning of new OS X
boxes. (Note: this blog entry assumes the use of Ansible 1.6, which is
currently still in development)
I have a private repository with a playbook that looks like this:
I use two separate roles here:
dotfiles. Note that these
roles have been created for my own unique needs, so if anything, they should
only be used as a base to point you in the right direction.
SRC_DIRECTORY="$HOME/src"ANSIBLE_DIRECTORY="$SRC_DIRECTORY/ansible"ANSIBLE_CONFIGURATION_DIRECTORY="$HOME/.ansible.d"# Download and install Command Line Toolsif[[ ! -x /usr/bin/gcc ]]; thenecho"Info | Install | xcode" xcode-select --install
fi# Download and install Homebrewif[[ ! -x /usr/local/bin/brew ]]; thenecho"Info | Install | homebrew" ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"fi# Modify the PATHexport PATH=/usr/local/bin:$PATH# Download and install zshif[[ ! -x /usr/local/bin/zsh ]]; thenecho"Info | Install | zsh" brew install zsh
fi# Download and install gitif[[ ! -x /usr/local/bin/git ]]; thenecho"Info | Install | git" brew install git
fi# Download and install pythonif[[ ! -x /usr/local/bin/python ]]; thenecho"Info | Install | python" brew install python --framework --with-brewed-openssl
fi# Download and install hgif[[ ! -x /usr/local/bin/hg ]]; thenpip install mercurial
fi# Download and install Ansibleif[[ ! -x /usr/local/bin/ansible ]]; thenbrew install ansible
fi# Make the code directorymkdir -p $SRC_DIRECTORY# Clone down ansibleif[[ ! -d $ANSIBLE_DIRECTORY]]; thengit clone -b devel git://github.com/danieljaouen/ansible.git $ANSIBLE_DIRECTORYfi# Use the forked Ansiblesource$ANSIBLE_DIRECTORY/hacking/env-setup > /dev/null
# Clone down the Ansible repoif[[ ! -d $ANSIBLE_CONFIGURATION_DIRECTORY]]; thengit clone email@example.com:danieljaouen/ansible-base-box.git $ANSIBLE_CONFIGURATION_DIRECTORY(cd$ANSIBLE_CONFIGURATION_DIRECTORY&& git submodule init && git submodule update)fi# Provision the boxansible-playbook --ask-sudo-pass -i $ANSIBLE_CONFIGURATION_DIRECTORY/inventories/osx $ANSIBLE_CONFIGURATION_DIRECTORY/site.yml --connection=local# Link the casks.~/.bin/link-casks
Note that at the moment, OS X actually comes with a gcc program that just
spits out an error telling you to install Command Line Utilities, so the first
block where I check for the existence of gcc is not completely correct.
How to make it better
As of this writing, the only thing that I can’t automate is App Store
downloads. Hopefully Apple will fix this in the near future :)
After spending a bit of time experimenting with Erlang, I am conviced that it
is “The Next Big Thing”. Not only is it extremely performant (just check out
a light-weight HTTP server written in Erlang), it also avoids the kind of
callback mess commonly seen in node.js applications. Erlang web apps are the
best of both worlds.
How does Erlang manage to do this? The answer is OTP.
What is OTP?
OTP is a set of behaviours that form a “contract” between your code and the
external world. Scalable web apps tend to have certain properties, and these
properties have been codified using Erlang’s OTP library.
Let’s dig in with an example.
One commonly seen pattern in scalable web apps is the “supervisor” pattern. We
set up one process to watch running code, perhaps restarting processes that
have died. Erlang’s OTP library codifies this pattern into a “behaviour”.
Essentially, you create an Erlang module and specify that the module will use
the supervisor behaviour. For example:
Ansible is a radically simple configuration management tool comparable to
Puppet or Chef. Today, I would like to share with you how I structure my
Ansible roles and playbooks so as to facilitate both sane defaults and maximum
Role Directory Structure
At the root level, I have a roles directory that contains each of my roles. I
tend to structure my roles like so:
What’s nice about this structure is that it contains defaults (instead of vars)
that can be overridden at the group level (which I will discuss later). The
use of defaults allows things to just work out of the box.
Role Variable/Default Structure
Each role has namespaced variables. The main.yml file under the defaults
directory looks something like this:
The top-level dict key (i.e., base_osx) is the name of the role, while
immediately below that (i.e., common) is the task filename. This helps to
avoid variable naming conflicts.
Role Task Structure
The role task structure looks something like this:
# file: roles/base_osx/tasks/common.yml----name:base_osx | common | brew updatehomebrew_class:update_homebrew=yes-name:base_osx | common | brew tap base_osx_common.tapshomebrew_tap:name=$item state=presentwith_items:base_osx.common.taps
Notice how my with_items tasks refer to namespaced variables.
Role Group Variables
One thing I do to keep things nice and decoupled is to create a separate
playbook and inventory group for each role. For example: