codemachine

Sandboxing Chef-solo

I’m in the early stages of a team project. Our goal is to build a rails app that uses chef-solo to automate the creation and provisioning of a DigitalOcean droplet, and deploys to it using Capistrano. The idea is to let a user deploy their own app to a DO server using our app to do the heavy lifting.

We’re new to chef-solo, and initially I tried plowing through documentation, hoping to build a comprehensive understanding before diving into the project. But we really needed to get our hands dirty to get a feel for chef, so we started sandboxing tiny prototypes. We were flying blind at times, but the small successes kept us moving forward.

The zeroth step was to install some necessary tools, namely knife-solo and berkshelf. knife-solo is the command line tool for chef-solo, and berkshelf plays a role similar to Bundler, managing dependencies within chef.

For our first sandbox venture, we took an existing, chef-ready rails server template and used it to provision an existing server.

Next we created a project directory from scratch, initialized a chef repo within it, and cloned an existing cookbook into the cookbooks directory. Easier said than done, but the ascii-art made it all worth while.

production

These little exercises were helpful, but we were still bogged down trying to map out the components and configurations encompassed by Chef. We backed up, got some helpful input from the TA’s here at Flatiron School, and started mapping out the components necessary to our project, pinpointing the core functionality we needed to start with.

sketch

Step 1: Get a local chef repo communicating with DigitalOcean

It turns out there’s a plugin just for that: knife-digital_ocean. We had to put our API-credentials from DigitalOcean into knife.rb, a configuration file for chef-solo, and add our SSH key to the DO account. Then we were able to create a new DO droplet with the desired OS and our SSH key, all in a single console command. Thank you knife-digital_ocean!

There’s plenty more work ahead. Our rails app needs to:

  • automate knife-digital_ocean commands
  • take user input
    • Github url for the app they want to deploy
    • necessary credentials (handle the SSH keys)
  • run provisioning as a background process (it takes a while)
  • deploy with Capistrano

It feels much more manageable with step 1 behind us and the skeleton of the project sketched out. Still, I’m sure I’ll be spending some more quality time in the Chef documentation.

Other People’s Dotfiles (OPD)

After getting familiar with Git and the concept of version control, I got interested in backing up my dotfiles (.bash_profile, .vimrc and the like), most of which were in ~. Now, turning my entire home directory into a Git repository would be asking for trouble, but a classmate suggested I might use symlinks to get around this.

The basic idea is this: move your dotfiles into their own directory, link to them from their original locations, ensuring they’re still available to the applications that need them, and run git init with a little less trepidation.

I did some googling and found I’m not the first to think of this. There are some pretty advanced scripts out there for backing up your dotfiles this way, but I wanted to do it myself since I wasn’t backing up that many files and I wanted to understand how it worked. The consensus seems to be to use symlinks, which begs the question: what’s the difference between a symlink (or “soft link”), and a hard link?

What we think of as a filename is actually a pointer to a specific location in memory. If we have a file ~/goblet, pointing at memory location-X, we could create a hard link to the same location with a different name, say ~/Stockpile/golden_cup. Both ~/goblet and ~/Stockpile/treasure/golden_cup point directly at the same location in memory. We could instead create a symlink ~/Cloud/chalice pointing at ~/goblet, which in turn points to location-X in memory.

It’s a subtle distinction, but one that carries real repercussions. If we move or delete the original ~/goblet, then the hard link ~/Stockpile/golden_cup will still point at the same data (moving another pointer has no effect on this pointer), but the symlink will be broken. It just pointed at the original path (~/goblet), and now it’s connection to the memory location is severed.

Despite this apparent disadvantage to symlinks, the general consensus seems to be to prefer them for linking to dotfiles. They do offer the ability to link across separate filesystems, and besides, I’m not planning on moving them around, so I went ahead and made some symlinks to serve my purpose.

Take, for example, my ~/.bash_profile. I created a directory for my dotfiles at ~/Development/resources/dotfiles/, moved the file to that directory, and linked to it with a symlink from its original location in ~, allowing my shell to pick it up at the expected location (~).

$ mkdir -p ~/Development/resources/dotfiles/
$ mv ~/.bash_profile ~/Development/resources/dotfiles/bash_profile
$ ln -s ~/Development/resources/dotfiles/bash_profile ~/.bash_profile

I removed the . from the target filename both for convenience (so it shows up in Finder), and to distinguish it from the symlink I created at ~/.bash_profile. Then I initialized a git repository in the dotfiles directory, putting my precious dotfiles under version control and making it easier to share them.

Yield the Weird

I’m messing around with yield to get a handle on the various closures in Ruby, and I built a method that returns a rotated-mapped array. You could of course do this with the built-in #rotate and #map methods, but what fun is that?

1
2
3
4
5
6
7
8
def rotomap(arr, n)
  arr.each_index.inject([]) do |roto, i|
    roto << yield( arr[ (i+n) % (arr.count) ] )
  end
end

rotomap([1,2,3,4,5], 3) { |x| "#{x * 5} merge requests" }
# => ["20 merge requests", "25 merge requests", "5 merge requests", "10 merge requests", "15 merge requests"]

I don’t imagine there’s a lot of rotomapping going on out there, but it was fun to slap this together.

Modules, Classes, Pterosaurs

Pterosaur, bat, and bird wings

Pterosaurs, bats, and birds can/could all fly, but each evolved the ability independent of the others. This is an example of convergent evolution, the “independent evolution of similar features in species of different lineages”. The wings of bats, pterosaurs, and birds, the body-plans of marsupials and mammals, and the eyes of vertebrates and cephalopods are just some examples of this.

In Ruby, classes allow objects to be arranged into a hierarchical lineage, using inheritence to pass features from a parent class to all it’s descendants. But we often want to share functionality across unrelated classes. Modules allow us to do just that. Let’s consider the case of pterosaurs and bats.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
module Flyable
  def fly
    puts "A #{self.class} is flying."
  end
end

class Bat
  include Flyable

  def echolocate
    puts "A #{self.class} can see in the dark."
  end
end

class Pterosaur
  include Flyable
end

bert = Bat.new
bert.fly
# => "A Bat is flying."

phil = Pterosaur.new
phil.fly
# => "A Pterosaur is flying."

Contrast this with sharing functionality via classes. Let’s create a couple species of bat by defining descendants of the Bat class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class SeminoleBat < Bat
  def migrate
    puts "A #{self.class} is migrating."
  end
end

class CanyonBat < Bat
  def hibernate
    puts "A #{self.class} hibernating."
  end
end

sam = SeminoleBat.new
sam.fly
# => "A SeminoleBat is flying."
sam.echolocate
# => "A SeminoleBat can see in the dark."

candice = CanyonBat.new
candice.fly
# => "A CanyonBat is flying."
candice.echolocate
# => "A CanyonBat can see in the dark."

Both sam and candice can echolocate and fly, since the SeminoleBat and CanyonBat classes are descendants of the Bat class. The SeminoleBat and CanyonBat classes also each have unique functionality that distinguishes them, and is not shared between their two classes. Only instances of SeminoleBat can migrate, and only those of CanyonBat can hibernate.

1
2
3
4
5
6
7
8
9
sam.migrate
# => "A SeminoleBat is migrating."
sam.hibernate
# => NoMethodError: undefined method `hibernate' for #<SeminoleBat:0x007f88fe006228>

candice.hibernate
# => "A CanyonBat is hibernating."
candice.migrate
# => NoMethodError: undefined method `migrate' for #<CanyonBat:0x007f88fe807c38>

And while Pterosaur, like Bat and all its descendants, can fly, it can’t echolocate, migrate, or hibernate.

1
2
3
4
5
6
phil.echolocate
# => NoMethodError: undefined method `echolocate' for #<Pterosaur:0x007fdcbc082eb0>
phil.migrate
# => NoMethodError: undefined method `migrate' for #<Pterosaur:0x007fdcbc082eb0>
phil.hibernate
# => NoMethodError: undefined method `hibernate' for #<Pterosaur:0x007fdcbc082eb0>

There is a bit more to Modules (they can be used to define class methods as well as instance methods, and they can include constants), but the general purpose they serve is to enable sharing functionality across unrelated classes.

Enumerables

Ruby provides lots of built-in methods for working with arrays, but at first glance, some seem to be missing from the Array documentation. A good example is the #find method, which returns the first element satisfying the criteria you provide in a block.

1
[1,2,3,4,5].find { |x| x > 2 }  # => 3

The fact is, these methods are mixed in from the Enumerable module, a collection of useful methods that can be applied to Arrays, Ranges, and Sets, among other Ruby classes. A simple check whatever_object.is_a? Enumerable will confirm whether whatever object your dealing with includes the Enumerable module.

One of these methods, #zip, has been calling out to me since I started learning Ruby. It seemed like an alchemical process that merged arrays in a mysterious way. In practice, it simply merges the corresponding elements of each array, returning an array of arrays.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
i_got   = ["I got the style",
           "I got the clothes",
           "I got the bread",
           "I got the winda"]

but_not = ["but not the grace",
           "but not the face",
           "but not the butter",
           "but not the shutter"]

i_got.zip(but_not)
# => [["I got the style", "but not the grace"],
#     ["I got the clothes", "but not the face"],
#     ["I got the bread", "but not the butter"],
#     ["I got the winda", "but not the shutter"]]

Pretty nice, but why don’t we do Tom Waits proud and join those phrases?

1
2
i_got.zip(but_not) {|got_not| got_not.join(", ")}
# => nil

Whoops! Passing #zip a block will invoke the block for each output array, but return nil at the end of the day. A call to #map to will do the trick.

1
2
3
4
5
i_got.zip(but_not).map {|got_not| got_not.join(", ")}
# => ["I got the style, but not the grace",
#     "I got the clothes, but not the face",
#     "I got the bread, but not the butter",
#     "I got the winda, but not the shutter"]

In fact, #zip can merge any number of arrays…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bagels  = ["sesame bagel",
           "plain bagel",
           "poppy bagel",
           "pumpernickel bagel"]

spreads = ["cream cheese",
           "butter",
           "peanut-butter",
           "jam"]

extras  = ["lox",
           "tomato",
           "chives",
           "lettuce"]

bagels.zip(spreads, extras).map do |bgl, spd, xtr|
  "#{bgl} with #{spd} and #{xtr}"
end
# => ["sesame bagel with cream cheese and lox",
#     "plain bagel with butter and tomato",
#     "poppy bagel with peanut-butter and chives",
#     "pumpernickel bagel with jam and lettuce"]

Not all the tastiest combinations, but that’s how #zip works, it just matches up the elements in whatever order they appeared in the original arrays.