From 30eb44eae6a1f0d81b7bcc16a7e5281ca77bdcab Mon Sep 17 00:00:00 2001 From: Matt Chu Date: Sun, 20 Apr 2014 12:02:00 -0700 Subject: [PATCH] wip: cleanup and documentation --- .kitchen.yml | 9 ++- Gemfile | 5 +- README.md | 63 +++++++++++++++++-- Vagrantfile | 20 +++--- attributes/default.rb | 9 ++- metadata.rb | 2 + recipes/default.rb | 34 +++++----- script/cibuild | 14 +++++ spec/default_spec.rb | 46 +------------- ...conda-debconf.erb => installer_config.erb} | 0 10 files changed, 123 insertions(+), 79 deletions(-) create mode 100755 script/cibuild rename templates/default/{anaconda-debconf.erb => installer_config.erb} (100%) diff --git a/.kitchen.yml b/.kitchen.yml index e439ec5..9e05925 100644 --- a/.kitchen.yml +++ b/.kitchen.yml @@ -6,9 +6,9 @@ provisioner: name: chef_solo platforms: - #- name: ubuntu-12.04 - #- name: ubuntu-12.10 - #- name: ubuntu-13.04 + - name: ubuntu-12.04 + - name: ubuntu-12.10 + - name: ubuntu-13.04 - name: ubuntu-13.10 suites: @@ -16,3 +16,6 @@ suites: run_list: - recipe[chef-continuum-anaconda::default] attributes: + anaconda: + version: '1.8.0' + flavor: 'x86' diff --git a/Gemfile b/Gemfile index 7d41304..81e40e6 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,7 @@ gem 'chefspec' gem 'test-kitchen' gem 'kitchen-vagrant' -#gem 'serverspec' -gem 'psych', '2.0.5' +gem 'foodcritic' + +#gem 'psych', '2.0.5' diff --git a/README.md b/README.md index bcb45d5..6415105 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,72 @@ # chef-continuum-anaconda cookbook +Chef cookbook for installing [Continuum Analytic](http://continuum.io/)'s +[Anaconda](https://store.continuum.io/cshop/anaconda/): "completely free Python +distribution for large-scale data processing, predictive analytics, and +scientific computing". + +This also serves as a example of the most up-to-date best practices for +writing, maintaining, and testing Chef cookbooks: + +- Berkshelf 3 for dependency resolution +- Vagrant for development +- Chefspec for rapid testing +- Test Kitchen for comprehensive testing across multiple platforms +- Foodcritic for style checking + # Requirements -# Usage +# Usage, recipes, and attributes -# Attributes +This cookbook only has one recipe: `chef-continuum-anaconda::default`. Include +it in your runlist, and it will install the package as well as any necessary +dependencies. -# Recipes +The following are user-configurable attributes. Check for default values. -# Author +- anaconda + - version: the version to install + - flavor: either 'x86' (32-bit) or 'x86_64' (64-bit) + - install_root: the parent directory of all anaconda installs. note that installs go into `#{install_root}/#{version}` + - add_to_shell_path: TODO + - owner: the user who owns the install + - group: the group who owns the install + +# Tests + +Run the full test suite: + +```bash +# this will take a long time +$> script/cibuild +... + +# check the final result; bash return codes: 0 is good, anything else is not +$> echo $? +``` + +Run just the [chefspec](https://github.com/sethvargo/chefspec)s: + +```bash +$> rspec +``` + +Run just the [test kitchen](https://github.com/test-kitchen/test-kitchen) full integration tests: + +```bash +$> rspec +``` + +Check the style with [Foodcritic](http://acrmp.github.io/foodcritic/): + +```bash +$> foodcritic +``` # TODO - autodetect 64-bit versus 32 +# Author + Author:: Matt Chu (matt.chu@gmail.com) diff --git a/Vagrantfile b/Vagrantfile index 2262b86..01f44a7 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,11 +1,11 @@ # -*- mode: ruby -*- # vi: set ft=ruby : -Vagrant.configure("2") do |config| - config.vm.hostname = "chef-continuum-anaconda-berkshelf" - config.vm.box = "precise32" - config.vm.box_url = "http://files.vagrantup.com/precise32.box" - config.vm.network :private_network, ip: "33.33.33.123" +Vagrant.configure('2') do |config| + config.vm.hostname = 'chef-continuum-anaconda-berkshelf' + config.vm.box = 'precise32' + config.vm.box_url = 'http://files.vagrantup.com/precise32.box' + config.vm.network :private_network, ip: '33.33.33.123' # ssh config.ssh.forward_x11 = true @@ -17,7 +17,8 @@ Vagrant.configure("2") do |config| # provisioning - # anaconda's big, so put it in the cache for development + # dev optimization: anaconda's big, so put it in the cache for development if + # it's already been downloaded [ 'Anaconda-1.8.0-Linux-x86.sh', 'Anaconda-1.8.0-Linux-x86_64.sh', @@ -25,9 +26,8 @@ Vagrant.configure("2") do |config| 'Anaconda-1.9.2-Linux-x86_64.sh', ].each do |f| if File.exists?(f) - #config.trigger.before [ :provision ], :execute => "bash -c 'cp /vagrant/Anaconda-1.8.0-Linux-x86.sh /var/chef/cache'", :stdout => true config.vm.provision :shell do |shell| - shell.inline = "if [[ ! -f $1 ]]; then cp $1 $2; fi" + shell.inline = 'if [[ ! -f $1 ]]; then cp $1 $2; fi' shell.args = [ "/vagrant/#{f}", '/var/chef/cache' ] end end @@ -36,13 +36,13 @@ Vagrant.configure("2") do |config| config.vm.provision :chef_solo do |chef| chef.json = { :anaconda => { - :version => '1.8.0', + :version => '1.9.2', :flavor => 'x86', } } chef.run_list = [ - "recipe[chef-continuum-anaconda::default]" + 'recipe[chef-continuum-anaconda::default]', ] end end diff --git a/attributes/default.rb b/attributes/default.rb index 4f5220e..7d1a9d1 100644 --- a/attributes/default.rb +++ b/attributes/default.rb @@ -1,4 +1,4 @@ -default.anaconda.version = '1.8.0' +default.anaconda.version = '1.9.2' default.anaconda.flavor = 'x86' default.anaconda.installer = { '1.8.0' => { @@ -11,5 +11,10 @@ }, } +# specific versions are installed _under_ this directory default.anaconda.install_root = '/opt/anaconda' -default.anaconda.add_to_shell_path = true +# setting this to true is not recommended; if the installation dir changes the path won't change +default.anaconda.add_to_shell_path = false + +default.anaconda.owner = 'vagrant' +default.anaconda.group = 'vagrant' diff --git a/metadata.rb b/metadata.rb index e042b96..e22171b 100644 --- a/metadata.rb +++ b/metadata.rb @@ -7,6 +7,8 @@ version '0.1.0' supports 'ubuntu', '= 12.04' +supports 'ubuntu', '= 13.04' +supports 'ubuntu', '= 13.10' depends 'apt' depends 'python' diff --git a/recipes/default.rb b/recipes/default.rb index 14163e8..01bedd8 100644 --- a/recipes/default.rb +++ b/recipes/default.rb @@ -10,27 +10,26 @@ include_recipe 'apt::default' include_recipe 'python::default' -anaconda_install_dir = node.anaconda.install_root -add_to_shell_path = node.anaconda.add_to_shell_path version = node.anaconda.version flavor = node.anaconda.flavor + +anaconda_install_dir = "#{node.anaconda.install_root}/#{version}" +add_to_shell_path = node.anaconda.add_to_shell_path installer = "Anaconda-#{version}-Linux-#{flavor}.sh" +installer_path = "#{Chef::Config[:file_cach_path]}/#{installer}" +installer_config = 'installer_config' +installer_config_path = "#{Chef::Config[:file_cache_path]}/#{installer_config}" + Chef::Log.debug "installer = #{installer}" -debconf_template = "anaconda-debconf" -debconf_template_path = "#{Chef::Config[:file_cache_path]}/#{debconf_template}" -remote_file "#{Chef::Config[:file_cache_path]}/#{installer}" do +remote_file installer_path do source "http://09c8d0b2229f813c1b93-c95ac804525aac4b6dba79b00b39d1d3.r79.cf1.rackcdn.com/#{installer}" checksum node.anaconda.installer[version][flavor] notifies :run, 'bash[run anaconda installer]', :delayed end -#template "#{Chef::Config[:file_cache_path]}/#{installer}.debconf" do -template debconf_template_path do - source "#{debconf_template}.erb" - #owner - #group - #mode +template installer_config_path do + source "#{installer_config}.erb" variables({ :version => version, :flavor => flavor, @@ -39,9 +38,16 @@ }) end +directory node.anaconda.install_root do + owner node.anaconda.owner + group node.anaconda.group + recursive true +end + bash 'run anaconda installer' do - code "cat #{debconf_template_path} | bash #{Chef::Config[:file_cache_path]}/#{installer}" - #action :run - action :nothing + code "cat #{installer_config_path} | bash #{installer_path}" + user node.anaconda.owner + group node.anaconda.group + action :run not_if { File.directory?(anaconda_install_dir) } end diff --git a/script/cibuild b/script/cibuild new file mode 100755 index 0000000..d4e72b8 --- /dev/null +++ b/script/cibuild @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +rspec +rspec_result=$? + +# this will take a long time +kitchen verify +kitchen_result=$? +kitchen destroy + +if [[ ${rspec_result} -eq 0 ]] && [[ ${kitchen_result} -eq 0 ]]; then + exit 0 +fi +exit 1 diff --git a/spec/default_spec.rb b/spec/default_spec.rb index 8d401c8..d359cb2 100644 --- a/spec/default_spec.rb +++ b/spec/default_spec.rb @@ -2,72 +2,30 @@ require 'chefspec/berkshelf' #require 'chefspec/server' require 'spec_helper.rb' -#require 'aws-sdk' - -=begin -describe 'continuum-anaconda:default' do - let(:log) { - Logger.new(STDOUT).tap { |l| l.level = Logger::DEBUG } - } - - before do - stub_command("bash -c \"source /etc/profile && type rvm | cat | head -1 | grep -q '^rvm is a function$'\"").and_return(true) - # on the ci server it looks like this - stub_command("bash -c \"source /etc/profile.d/rvm.sh && type rvm | cat | head -1 | grep -q '^rvm is a function$'\"").and_return(true) - - # stub out chef-client: https://github.com/sethvargo/chefspec/issues/364 - Chef::Recipe.any_instance.stub(:include_recipe).and_call_original - Chef::Recipe.any_instance.stub(:include_recipe).with('chef-client::delete_validation').and_return(true) - end - - let(:chef_run) do - ChefSpec::Runner.new - end - - it 'configures xlate backups' do - chef_run.converge(described_recipe) - - expect(chef_run).to create_cookbook_file('/home/distribution/backup-xlate.sh') - # TODO we use cron.d everywhere but there's no matcher for it out of the box - #expect(chef_run).to create_cron('backup-xlate') - end -end -=end shared_examples 'general tests' do |platform, version| context "on #{platform} #{version}" do - #let(:users) { %w[user1 user2] } let(:chef_run) do ChefSpec::Runner.new(platform: platform, version: version) do |node| #node.set['foo']['users'] = users - #end.converge('foo::default') end end - #subject { chef_run } it 'runs without errors. see test-kitchen tests for more comprehensive tests not possible here' do - #default.anaconda.install_root = '/opt/anaconda' chef_run.converge(described_recipe) - #should install_package 'foo' expect(chef_run).to include_recipe 'python::default' - #expect(chef_run).to create_directory chef_run.node.anaconda.install_root end it 'generates the installer template correctly' do chef_run.converge(described_recipe) # must be exactly 4 lines - debconf_template = "anaconda-debconf" - debconf_template_path = "#{Chef::Config[:file_cache_path]}/#{debconf_template}" - expect(chef_run).to render_file(debconf_template_path).with_content(/.*\n.*\n.*\n.*/) + installer_config_path = "#{Chef::Config[:file_cache_path]}/installer_config" + expect(chef_run).to render_file(installer_config_path).with_content(/.*\n.*\n.*\n.*/) end - #it "creates specified users" do - ##users.each { |u| expect(chef_run).to create_user u } - #end - end end diff --git a/templates/default/anaconda-debconf.erb b/templates/default/installer_config.erb similarity index 100% rename from templates/default/anaconda-debconf.erb rename to templates/default/installer_config.erb