gusteau 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/.travis.yml +4 -0
- data/Gemfile +7 -0
- data/LICENSE.md +8 -0
- data/README.md +91 -0
- data/Rakefile +7 -0
- data/bin/gusteau +46 -0
- data/bootstrap/centos.sh +15 -0
- data/bootstrap/gentoo.sh +21 -0
- data/bootstrap/redhat.sh +15 -0
- data/bootstrap/solo.rb +6 -0
- data/bootstrap/ubuntu.sh +8 -0
- data/gusteau.gemspec +28 -0
- data/lib/gusteau.rb +6 -0
- data/lib/gusteau/bureau.rb +57 -0
- data/lib/gusteau/chef.rb +39 -0
- data/lib/gusteau/compressed_tar_stream.rb +22 -0
- data/lib/gusteau/log.rb +45 -0
- data/lib/gusteau/node.rb +55 -0
- data/lib/gusteau/server.rb +38 -0
- data/lib/gusteau/ssh.rb +50 -0
- data/lib/gusteau/version.rb +3 -0
- data/spec/lib/gusteau/chef_spec.rb +38 -0
- data/spec/lib/gusteau/node_spec.rb +37 -0
- data/spec/lib/gusteau/server_spec.rb +69 -0
- data/spec/nodes/example.yml +17 -0
- data/spec/spec_helper.rb +10 -0
- data/template/.gitignore +2 -0
- data/template/Cheffile +8 -0
- data/template/Gemfile +7 -0
- data/template/Vagrantfile +8 -0
- data/template/data_bags/users/user.json.erb +6 -0
- data/template/nodes/example.yml.erb +19 -0
- data/template/roles/platform.rb +8 -0
- data/template/site-cookbooks/cowsay/attributes/default.rb +1 -0
- data/template/site-cookbooks/cowsay/recipes/default.rb +9 -0
- metadata +201 -0
    
        data/.gitignore
    ADDED
    
    
    
        data/.travis.yml
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE.md
    ADDED
    
    | @@ -0,0 +1,8 @@ | |
| 1 | 
            +
            Copyright (c) 2013 Locomote Pty Ltd
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
         | 
| 6 | 
            +
            2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,91 @@ | |
| 1 | 
            +
            Gusteau
         | 
| 2 | 
            +
            =======
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            *"Anyone can cook."*
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            [](http://magnum.travis-ci.com/locomote/gusteau)
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            Introduction
         | 
| 9 | 
            +
            ------------
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            Gusteau is here to make servers provisioning simple and enjoyable. It provides an efficient interface to Chef Solo as well as some nice features:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            * Uses YAML for readable server configuration definitions
         | 
| 14 | 
            +
            * Uses a single SSH connection to stream compressed files and commands
         | 
| 15 | 
            +
            * Allows you to use normal Chef flags:
         | 
| 16 | 
            +
              * `-W` or `--why-run` (dry run mode)
         | 
| 17 | 
            +
              * `-l` for setting a log level and `-F` for setting an output formatter
         | 
| 18 | 
            +
            * Is able to bootstrap CentOS, RHEL, Ubuntu and Gentoo systems with chef-solo.
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            Gettings started
         | 
| 21 | 
            +
            ----------------
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            A typical Gusteau node configuration looks like this:
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            ```YAML
         | 
| 26 | 
            +
            json:
         | 
| 27 | 
            +
              mysql:
         | 
| 28 | 
            +
                server_root_password: ASahiweqwqe2
         | 
| 29 | 
            +
              rvm:
         | 
| 30 | 
            +
                default_ruby: 1.9.3-p362
         | 
| 31 | 
            +
              users:
         | 
| 32 | 
            +
               - linguini
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            roles:
         | 
| 35 | 
            +
              - platform
         | 
| 36 | 
            +
              - rails
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            recipes:
         | 
| 39 | 
            +
              - mysql::server
         | 
| 40 | 
            +
              - iptables
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            server:
         | 
| 43 | 
            +
              host: 33.33.33.20
         | 
| 44 | 
            +
              platform: ubuntu
         | 
| 45 | 
            +
              password: vagrant
         | 
| 46 | 
            +
            ```
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            Gusteau only needs a node definition to run, but you'll need a few cookbooks to actually cook something :)
         | 
| 49 | 
            +
            The following command generates an example configuration to get you started:
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            ```
         | 
| 52 | 
            +
            gusteau generate project-name
         | 
| 53 | 
            +
            ```
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            Next, `cd project-name` and see `nodes/example.yml`.
         | 
| 56 | 
            +
             | 
| 57 | 
            +
             | 
| 58 | 
            +
            Provisioning a server
         | 
| 59 | 
            +
            ----------
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            The following command will run all roles and recipes from node's YAML file.
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            ```
         | 
| 64 | 
            +
            gusteau node-name provision
         | 
| 65 | 
            +
            ```
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            Use the `--bootstrap` or `-b` flag to bootstrap chef-solo (for the first time run).
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            Running recipes
         | 
| 70 | 
            +
            -----------
         | 
| 71 | 
            +
            You may choose to run a few recipes instead of full provisioning.
         | 
| 72 | 
            +
             | 
| 73 | 
            +
            ```
         | 
| 74 | 
            +
            gusteau node-name run redis::server ntp unicorn
         | 
| 75 | 
            +
            ```
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            Using with Vagrant
         | 
| 78 | 
            +
            ------------------
         | 
| 79 | 
            +
            At the moment Gusteau doesn't come with Vagrant integration. However, using it with Vagrant is easy, just make sure that you provide the correct IP address of the VM in node's YAML file.
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            ```
         | 
| 82 | 
            +
            vagrant up
         | 
| 83 | 
            +
            gusteau node-name provision
         | 
| 84 | 
            +
            ```
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            Notes
         | 
| 87 | 
            +
            -----
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            * Feel free to contribute a [bootstrap script](https://github.com/locomote/gusteau/tree/master/bootstrap) for your platform.
         | 
| 90 | 
            +
            * Gusteau uploads  both `./cookbooks` and `./site-cookbooks` so that you can use [librarian-chef](https://github.com/applicationsonline/librarian) to include third party cookbooks.
         | 
| 91 | 
            +
             | 
    
        data/Rakefile
    ADDED
    
    
    
        data/bin/gusteau
    ADDED
    
    | @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'rubygems'
         | 
| 4 | 
            +
            require 'optitron'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            $LOAD_PATH << File.expand_path("../..", __FILE__)
         | 
| 7 | 
            +
            require 'lib/gusteau'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            class Gusteau::CLI < Optitron::CLI
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              class_opt 'bootstrap', 'Install chef-solo'
         | 
| 12 | 
            +
              class_opt 'format',    'Output format to use', :short_name => 'F', :type => :string
         | 
| 13 | 
            +
              class_opt 'log_level', 'Set the log level',    :in => %w{debug info warn error fatal}
         | 
| 14 | 
            +
              class_opt 'why-run',   'Enable whyrun mode',   :short_name => 'W'
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              desc 'Fully provision a node'
         | 
| 17 | 
            +
              def provision(node_name)
         | 
| 18 | 
            +
                node(node_name).provision(params)
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              desc 'Run recipe(s)'
         | 
| 22 | 
            +
              def run(node_name, *recipe)
         | 
| 23 | 
            +
                node(node_name).run(params, recipe)
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              desc 'Generate an example project (a bureau)'
         | 
| 27 | 
            +
              def generate(bureau_name)
         | 
| 28 | 
            +
                Gusteau::Bureau.new(bureau_name)
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              private
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              def node(node_name)
         | 
| 34 | 
            +
                unless node_path = Dir.glob("./nodes/**/#{node_name}.yml")[0]
         | 
| 35 | 
            +
                  abort "Node '#{node_name}' is unknown. Known nodes are #{nodes_list.join(', ')}."
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
                Gusteau::Node.new(node_path)
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              def nodes_list
         | 
| 41 | 
            +
                Dir.glob("./nodes/**/*.yml").map { |f| File.basename(f, '.*') }
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            Gusteau::CLI.dispatch
         | 
    
        data/bootstrap/centos.sh
    ADDED
    
    | @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            #!/bin/sh
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            if type -p chef-solo > /dev/null; then
         | 
| 4 | 
            +
              echo "Using chef-solo at `which chef-solo`"
         | 
| 5 | 
            +
            else
         | 
| 6 | 
            +
              rpm -Uvh http://rbel.frameos.org/rbel6
         | 
| 7 | 
            +
              yum-config-manager --enable rhel-6-server-optional-rpms
         | 
| 8 | 
            +
              yum install -y ruby ruby-devel ruby-ri ruby-rdoc ruby-shadow gcc gcc-c++ automake autoconf make curl dmidecode
         | 
| 9 | 
            +
              cd /tmp
         | 
| 10 | 
            +
              curl -O http://production.cf.rubygems.org/rubygems/rubygems-1.8.10.tgz
         | 
| 11 | 
            +
              tar zxf rubygems-1.8.10.tgz
         | 
| 12 | 
            +
              cd rubygems-1.8.10
         | 
| 13 | 
            +
              ruby setup.rb --no-format-executable
         | 
| 14 | 
            +
              gem install chef --no-ri --no-rdoc
         | 
| 15 | 
            +
            fi
         | 
    
        data/bootstrap/gentoo.sh
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            #!/bin/sh
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            if type -p chef-solo > /dev/null; then
         | 
| 4 | 
            +
              echo "Using chef-solo at `which chef-solo`"
         | 
| 5 | 
            +
            else
         | 
| 6 | 
            +
              emerge --sync
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              echo 'RUBY_TARGETS="ruby19"' >> /etc/make.conf
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              CONFIG_PROTECT_MASK="/etc/portage/" emerge --autounmask-write ruby:1.9
         | 
| 11 | 
            +
              emerge -uDN ruby:1.9
         | 
| 12 | 
            +
              revdep-rebuild
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              gem install chef ruby-shadow --no-ri --no-rdoc
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              # Make non-interactive SSH sessions see environment variables
         | 
| 17 | 
            +
              if [[ ! `which chef-solo` ]]; then
         | 
| 18 | 
            +
                echo 'source /etc/profile' >> ~/.bashrc
         | 
| 19 | 
            +
                source ~/.bashrc
         | 
| 20 | 
            +
              fi
         | 
| 21 | 
            +
            fi
         | 
    
        data/bootstrap/redhat.sh
    ADDED
    
    | @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            #!/bin/sh
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            if type -p chef-solo > /dev/null; then
         | 
| 4 | 
            +
              echo "Using chef-solo at `which chef-solo`"
         | 
| 5 | 
            +
            else
         | 
| 6 | 
            +
              rpm -Uvh http://rbel.frameos.org/rbel6
         | 
| 7 | 
            +
              yum-config-manager --enable rhel-6-server-optional-rpms
         | 
| 8 | 
            +
              yum install -y ruby ruby-devel ruby-ri ruby-rdoc ruby-shadow gcc gcc-c++ automake autoconf make curl dmidecode
         | 
| 9 | 
            +
              cd /tmp
         | 
| 10 | 
            +
              curl -O http://production.cf.rubygems.org/rubygems/rubygems-1.8.10.tgz
         | 
| 11 | 
            +
              tar zxf rubygems-1.8.10.tgz
         | 
| 12 | 
            +
              cd rubygems-1.8.10
         | 
| 13 | 
            +
              ruby setup.rb --no-format-executable
         | 
| 14 | 
            +
              gem install chef --no-ri --no-rdoc
         | 
| 15 | 
            +
            fi
         | 
    
        data/bootstrap/solo.rb
    ADDED
    
    
    
        data/bootstrap/ubuntu.sh
    ADDED
    
    
    
        data/gusteau.gemspec
    ADDED
    
    | @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            +
            lib = File.expand_path('../lib', __FILE__)
         | 
| 3 | 
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 | 
            +
            require 'gusteau/version'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Gem::Specification.new do |gem|
         | 
| 7 | 
            +
              gem.name          = "gusteau"
         | 
| 8 | 
            +
              gem.version       = Gusteau::VERSION
         | 
| 9 | 
            +
              gem.authors       = ["Vasily Mikhaylichenko", "Chris"]
         | 
| 10 | 
            +
              gem.email         = ["vasily@locomote.com", "chris@locomote.com"]
         | 
| 11 | 
            +
              gem.description   = %q{A fine Chef Solo wrapper}
         | 
| 12 | 
            +
              gem.summary       = %q{Making servers provisioning enjoyable since 2013.}
         | 
| 13 | 
            +
              gem.homepage      = "http://github.com/locomote/gusteau"
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              gem.files         = `git ls-files | grep -vE '(jenkins|.gitmodules|.ruby-version)'`.split("\n")
         | 
| 16 | 
            +
              gem.executables   = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
         | 
| 17 | 
            +
              gem.test_files    = gem.files.grep(%r{^(test|spec|features)/})
         | 
| 18 | 
            +
              gem.require_paths = ["lib"]
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              gem.add_dependency 'optitron'
         | 
| 21 | 
            +
              gem.add_dependency 'inform'
         | 
| 22 | 
            +
              gem.add_dependency 'json'
         | 
| 23 | 
            +
              gem.add_dependency 'net-ssh', '~> 2.2.2'
         | 
| 24 | 
            +
              gem.add_dependency 'archive-tar-minitar', '~> 0.5.2'
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              gem.add_development_dependency 'minitest'
         | 
| 27 | 
            +
              gem.add_development_dependency 'mocha'
         | 
| 28 | 
            +
            end
         | 
    
        data/lib/gusteau.rb
    ADDED
    
    
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            require 'etc'
         | 
| 2 | 
            +
            require 'erb'
         | 
| 3 | 
            +
            require 'yaml'
         | 
| 4 | 
            +
            require 'json'
         | 
| 5 | 
            +
            require 'fileutils'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Gusteau
         | 
| 8 | 
            +
              class Bureau
         | 
| 9 | 
            +
                def initialize(name)
         | 
| 10 | 
            +
                  template_path = File.expand_path('../../../template', __FILE__)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  @login   = Etc.getlogin
         | 
| 13 | 
            +
                  @ssh_key = File.read(File.expand_path '~/.ssh/id_rsa.pub').chomp rescue 'Your SSH key'
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  abort "Directory #{name} already exists" if Dir.exists?(name)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  FileUtils.cp_r(template_path, name)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  File.open(File.join(name, 'nodes', 'example.yml'), 'w+') do |f|
         | 
| 20 | 
            +
                    read_erb(File.join(template_path, 'nodes', 'example.yml.erb')).tap do |node|
         | 
| 21 | 
            +
                      f.write node
         | 
| 22 | 
            +
                      f.close
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    FileUtils.rm(File.join(name, 'nodes', 'example.yml.erb'))
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  File.open(File.join(name, 'data_bags', 'users', "#{@login}.json"), 'w+') do |f|
         | 
| 29 | 
            +
                    read_erb_json(File.join(template_path, 'data_bags', 'users', 'user.json.erb')).tap do |user|
         | 
| 30 | 
            +
                      f.write JSON::pretty_generate user
         | 
| 31 | 
            +
                      f.close
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    FileUtils.rm(File.join(name, 'data_bags', 'users', 'user.json.erb'))
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  puts "Created bureau '#{name}'"
         | 
| 38 | 
            +
                  Dir.chdir(name) do
         | 
| 39 | 
            +
                    puts   'Installing gem dependencies'
         | 
| 40 | 
            +
                    system 'bundle'
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    puts   'Installing cookbooks'
         | 
| 43 | 
            +
                    system 'librarian-chef install'
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                private
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def read_erb(path)
         | 
| 50 | 
            +
                  ERB.new(File.read(path)).result binding
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def read_erb_json(path)
         | 
| 54 | 
            +
                  JSON::parse(read_erb path)
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
    
        data/lib/gusteau/chef.rb
    ADDED
    
    | @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            module Gusteau
         | 
| 2 | 
            +
              class Chef
         | 
| 3 | 
            +
                def initialize(server, platform, dest_dir = '/etc/chef')
         | 
| 4 | 
            +
                  @server   = server
         | 
| 5 | 
            +
                  @platform = platform
         | 
| 6 | 
            +
                  @dest_dir = dest_dir
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def run(opts, dna)
         | 
| 10 | 
            +
                  @server.run "rm -rf #{@dest_dir} && mkdir #{@dest_dir} && mkdir -p /tmp/chef"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  @server.upload(src_files(dna[:path]), @dest_dir, :exclude => '.git/')
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  # move bootstrap directory to the top level
         | 
| 15 | 
            +
                  @server.run "cd #{@dest_dir} && mv `find . -type d -name bootstrap` #{@dest_dir}/"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  @server.run "sh /etc/chef/bootstrap/#{@platform}.sh" if opts['bootstrap']
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  cmd  = "chef-solo -c #{@dest_dir}/bootstrap/solo.rb -j #{@dest_dir + dna[:path]} --color"
         | 
| 20 | 
            +
                  cmd << " -F #{opts['format']}"    if opts['format']
         | 
| 21 | 
            +
                  cmd << " -l #{opts['log_level']}" if opts['log_level']
         | 
| 22 | 
            +
                  cmd << " -W"                      if opts['why-run']
         | 
| 23 | 
            +
                  @server.run cmd
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                private
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def src_files(dna_path)
         | 
| 29 | 
            +
                  %W(
         | 
| 30 | 
            +
                    #{dna_path}
         | 
| 31 | 
            +
                    #{File.expand_path("../../../bootstrap", __FILE__)}
         | 
| 32 | 
            +
                    ./cookbooks
         | 
| 33 | 
            +
                    ./site-cookbooks
         | 
| 34 | 
            +
                    ./roles
         | 
| 35 | 
            +
                    ./data_bags
         | 
| 36 | 
            +
                  ).select { |file| File.exists? file }
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            require 'zlib'
         | 
| 2 | 
            +
            require 'archive/tar/minitar'
         | 
| 3 | 
            +
            require 'stringio'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Gusteau
         | 
| 6 | 
            +
              module CompressedTarStream
         | 
| 7 | 
            +
                private
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def compressed_tar_stream(files, opts={})
         | 
| 10 | 
            +
                  using = opts[:using] || Zlib::GzipWriter
         | 
| 11 | 
            +
                  log "#compressing #{files.size} files for upload (using #{using}): " do
         | 
| 12 | 
            +
                    tar = Archive::Tar::Minitar::Output.new(using.new(StringIO.new('')))
         | 
| 13 | 
            +
                    files.each do |f|
         | 
| 14 | 
            +
                      print '.'
         | 
| 15 | 
            +
                      Archive::Tar::Minitar.pack_file(f, tar)
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
                    tar.close.string.force_encoding('ASCII-8BIT')
         | 
| 18 | 
            +
                    .tap { |data| puts " (compressed down to #{data.size} bytes)" }
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
    
        data/lib/gusteau/log.rb
    ADDED
    
    | @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            require 'inform'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Gusteau
         | 
| 4 | 
            +
              module Log
         | 
| 5 | 
            +
                private
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def timestamp
         | 
| 8 | 
            +
                  Time.now.strftime('%Y-%m-%dT%H:%M:%S')
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def indent
         | 
| 12 | 
            +
                  @level = (@level || 0) + 1
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def unindent
         | 
| 16 | 
            +
                  @level = (@level || 0) - 1
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def prompt
         | 
| 20 | 
            +
                  "[#{timestamp}] GUSTEAU: #{'  ' * (@level || 0)}"
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def log(msg, opts={})
         | 
| 24 | 
            +
                  info "%{prompt}#{msg}", opts.merge(:prompt => prompt)
         | 
| 25 | 
            +
                  if block_given?
         | 
| 26 | 
            +
                    indent
         | 
| 27 | 
            +
                    start_time = Time.now
         | 
| 28 | 
            +
                    yield.tap do
         | 
| 29 | 
            +
                      unindent
         | 
| 30 | 
            +
                      Inform.info "%{prompt}DONE (in #{'%0.2f' % (Time.now - start_time)}s)", :prompt => prompt
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                  else
         | 
| 33 | 
            +
                    puts "\n"
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def log_error(msg, opts={})
         | 
| 38 | 
            +
                  Inform.error "%{prompt}#{msg}", opts.merge(:prompt => prompt)
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def info(str, opts={})
         | 
| 42 | 
            +
                  Inform.info str, opts
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
    
        data/lib/gusteau/node.rb
    ADDED
    
    | @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            require 'yaml'
         | 
| 2 | 
            +
            require 'json'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'gusteau/server'
         | 
| 5 | 
            +
            require 'gusteau/chef'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Gusteau
         | 
| 8 | 
            +
              class Node
         | 
| 9 | 
            +
                attr_reader :server
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def initialize(path)
         | 
| 12 | 
            +
                  raise "Node YAML file #{path} not found" unless path && File.exists?(path)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  @name   = File.basename(path).gsub('.yml','')
         | 
| 15 | 
            +
                  @config = YAML::load_file path
         | 
| 16 | 
            +
                  @dna_path = '/tmp/dna.json'
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  @server = Server.new(@config['server'])
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def provision(opts = {})
         | 
| 22 | 
            +
                  @server.chef.run opts, dna(true)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def run(opts = {}, *recipes)
         | 
| 26 | 
            +
                  @server.chef.run opts, dna(false, recipes.flatten)
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                private
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def dna(include_all, recipes = [])
         | 
| 32 | 
            +
                  node_dna = {
         | 
| 33 | 
            +
                    :path => @dna_path,
         | 
| 34 | 
            +
                    :hash => {
         | 
| 35 | 
            +
                      :instance_role => @name,
         | 
| 36 | 
            +
                      :run_list      => run_list(include_all, recipes)
         | 
| 37 | 
            +
                    }.merge(@config['json'])
         | 
| 38 | 
            +
                  }
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  File.open(node_dna[:path], 'w+') { |f| f.puts node_dna[:hash].to_json }
         | 
| 41 | 
            +
                  node_dna
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def run_list(include_all, recipes)
         | 
| 45 | 
            +
                  if include_all
         | 
| 46 | 
            +
                    list = []
         | 
| 47 | 
            +
                    list += @config['roles'].map   { |r| "role[#{r}]"   } if @config['roles']
         | 
| 48 | 
            +
                    list += @config['recipes'].map { |r| "recipe[#{r}]" } if @config['recipes']
         | 
| 49 | 
            +
                    list
         | 
| 50 | 
            +
                  else
         | 
| 51 | 
            +
                    recipes.map { |r| "recipe[#{r}]" }
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
            end
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            require 'gusteau/log'
         | 
| 2 | 
            +
            require 'gusteau/ssh'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Gusteau
         | 
| 5 | 
            +
              class Server
         | 
| 6 | 
            +
                include Gusteau::Log
         | 
| 7 | 
            +
                include Gusteau::SSH
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                attr_reader :host, :port, :user, :password, :chef
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def initialize(config, opts={})
         | 
| 12 | 
            +
                  @host     = config['host']
         | 
| 13 | 
            +
                  @port     = (config['port'] || '22').to_i
         | 
| 14 | 
            +
                  @user     = config['user'] || 'root'
         | 
| 15 | 
            +
                  @password = config['password']
         | 
| 16 | 
            +
                  @chef = Gusteau::Chef.new(self, config['platform'])
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def upload(files_and_dirs, dest_dir, opts={})
         | 
| 20 | 
            +
                  log "#uploading #{files_and_dirs.join(' ')} to #{@host}:#{dest_dir}" do
         | 
| 21 | 
            +
                    files = Find.find(*files_and_dirs).select { |f| f unless opts[:exclude] && f.include?(opts[:exclude]) }
         | 
| 22 | 
            +
                    send_files(files, dest_dir)
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def run(*cmds)
         | 
| 27 | 
            +
                  cmds.each do |cmd|
         | 
| 28 | 
            +
                    log("%{host}> #{prepared_cmd cmd}", :host => host) do
         | 
| 29 | 
            +
                      unless send_command(cmd)
         | 
| 30 | 
            +
                        log_error("%{host}> #{prepared_cmd cmd}", :host => host)
         | 
| 31 | 
            +
                        raise
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                  true
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
            end
         | 
    
        data/lib/gusteau/ssh.rb
    ADDED
    
    | @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            require 'net/ssh'
         | 
| 2 | 
            +
            require 'gusteau/compressed_tar_stream'
         | 
| 3 | 
            +
            require 'gusteau/log'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Gusteau
         | 
| 6 | 
            +
              module SSH
         | 
| 7 | 
            +
                include Gusteau::CompressedTarStream
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def ssh
         | 
| 10 | 
            +
                  @ssh ||= begin
         | 
| 11 | 
            +
                    opts = { :port => port }
         | 
| 12 | 
            +
                    opts.update(:password => password) if @password
         | 
| 13 | 
            +
                    log "#setting up ssh connection #{@user}@#{host}, #{opts.inspect})" do
         | 
| 14 | 
            +
                      Net::SSH.start(host, @user, opts)
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def send_command(cmd)
         | 
| 20 | 
            +
                  exit_code = -1
         | 
| 21 | 
            +
                  ssh.open_channel do |ch|
         | 
| 22 | 
            +
                    ch.exec(prepared_cmd cmd) do |_, success|
         | 
| 23 | 
            +
                      if success
         | 
| 24 | 
            +
                        ch.on_data { |_,data| puts data }
         | 
| 25 | 
            +
                        ch.on_extended_data { |_,_,data| $stderr.puts data }
         | 
| 26 | 
            +
                        ch.on_request("exit-status") { |_,data| exit_code = data.read_long }
         | 
| 27 | 
            +
                      else
         | 
| 28 | 
            +
                        raise "FAILED to execute command: #{cmd.inspect}"
         | 
| 29 | 
            +
                      end
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                  ssh.loop
         | 
| 33 | 
            +
                  exit_code == 0
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def send_files(files, dest_dir)
         | 
| 37 | 
            +
                  ssh.open_channel { |ch|
         | 
| 38 | 
            +
                    ch.exec(prepared_cmd "tar zxf - -C #{dest_dir}")
         | 
| 39 | 
            +
                    ch.send_data(compressed_tar_stream(files))
         | 
| 40 | 
            +
                    ch.eof!
         | 
| 41 | 
            +
                  }.wait
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                private
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def prepared_cmd(cmd)
         | 
| 47 | 
            +
                  user == 'root' ? cmd : "sudo -- sh -c '#{cmd}'"
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
            end
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            require './spec/spec_helper.rb'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Gusteau::Chef do
         | 
| 4 | 
            +
              let(:platform)  { 'centos' }
         | 
| 5 | 
            +
              let(:server)    { Gusteau::Server.new('host' => 'example.com', 'platform' => platform) }
         | 
| 6 | 
            +
              let(:chef)      { Gusteau::Chef.new(server, platform) }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              describe "#run" do
         | 
| 9 | 
            +
                let(:bootstrap) { false }
         | 
| 10 | 
            +
                let(:opts)      { { 'bootstrap' => bootstrap, 'why-run' => false } }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def expects_run_chef_solo
         | 
| 13 | 
            +
                  server.expects(:run).with { |p1| p1 =~ /chef-solo/ }
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                before do
         | 
| 17 | 
            +
                  server.expects(:upload)
         | 
| 18 | 
            +
                  server.expects(:run).times(2)
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                context "bootstrap option is not specified" do
         | 
| 22 | 
            +
                  it "should run chef solo" do
         | 
| 23 | 
            +
                    expects_run_chef_solo
         | 
| 24 | 
            +
                    chef.run(opts, { :path => '/tmp/node.json' })
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                context "bootstrap option is specified" do
         | 
| 29 | 
            +
                  let(:bootstrap) { true }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  it "should run the bootstrap script and chef solo" do
         | 
| 32 | 
            +
                    server.expects(:run).with('sh /etc/chef/bootstrap/centos.sh')
         | 
| 33 | 
            +
                    expects_run_chef_solo
         | 
| 34 | 
            +
                    chef.run(opts, { :path => '/tmp/node.json' })
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
            end
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            require './spec/spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Gusteau::Node do
         | 
| 4 | 
            +
              it "should raise if node yaml doesn't exist" do
         | 
| 5 | 
            +
                proc { Gusteau::Node.new('spec/nodes/void.yml') }.must_raise RuntimeError
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              describe "#dna" do
         | 
| 9 | 
            +
                let(:node) { Gusteau::Node.new('spec/nodes/example.yml') }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                let(:dna)         { node.send(:dna, include_all, recipes) }
         | 
| 12 | 
            +
                let(:include_all) { true }
         | 
| 13 | 
            +
                let(:recipes)     { [] }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                let(:path) { '/tmp/testdna.json' }
         | 
| 16 | 
            +
                let(:json) { JSON::parse(File.read dna[:path]) }
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                it "should create a new dna file" do
         | 
| 19 | 
            +
                  File.exists?(dna[:path]).must_equal true
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                context "recipes not specified" do
         | 
| 23 | 
            +
                  it "should contain a full run_list" do
         | 
| 24 | 
            +
                    json['run_list'].must_equal ["role[redhat]", "recipe[rvm]", "recipe[ntp]", "recipe[rails::apps]"]
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                context "recipes specified" do
         | 
| 29 | 
            +
                  let(:include_all) { false }
         | 
| 30 | 
            +
                  let(:recipes)     { ['rvm', 'rails::apps'] }
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  it "should only include the specified item into run_list" do
         | 
| 33 | 
            +
                    json['run_list'].must_equal ["recipe[rvm]", "recipe[rails::apps]"]
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
| @@ -0,0 +1,69 @@ | |
| 1 | 
            +
            require './spec/spec_helper.rb'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Gusteau::Server do
         | 
| 4 | 
            +
              let(:config) do
         | 
| 5 | 
            +
                {
         | 
| 6 | 
            +
                  'host'     => 'demo.com',
         | 
| 7 | 
            +
                  'port'     => '2222',
         | 
| 8 | 
            +
                  'platform' => 'ubuntu'
         | 
| 9 | 
            +
                }
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              let(:server) { Gusteau::Server.new(config) }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              before do
         | 
| 15 | 
            +
                def server.log(msg, opts={})
         | 
| 16 | 
            +
                  yield if block_given?
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def server.log_error(msg, opts={})
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              describe "Default user" do
         | 
| 24 | 
            +
                subject { server.user }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                context "User not specified in node config" do
         | 
| 27 | 
            +
                  it { subject.must_equal 'root' }
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                context "User specified in node config" do
         | 
| 31 | 
            +
                  let(:config) do
         | 
| 32 | 
            +
                  {
         | 
| 33 | 
            +
                    'host'     => 'demo.com',
         | 
| 34 | 
            +
                    'port'     => '2222',
         | 
| 35 | 
            +
                    'user'     => 'oneiric',
         | 
| 36 | 
            +
                    'platform' => 'ubuntu',
         | 
| 37 | 
            +
                  }
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                  it { subject.must_equal 'oneiric' }
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              describe "#run" do
         | 
| 44 | 
            +
                it "should raise if one of the commands fails" do
         | 
| 45 | 
            +
                  server.stub(:send_command, false) do
         | 
| 46 | 
            +
                    proc { server.run('failure') }.must_raise RuntimeError
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                it "should return true if all command succeed" do
         | 
| 51 | 
            +
                  server.stub(:send_command, true) do
         | 
| 52 | 
            +
                    server.run('uname').must_equal true
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              describe "#prepared_cmd" do
         | 
| 58 | 
            +
                subject { server.send(:prepared_cmd, 'cd /etc/chef && touch test') }
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                context "user is root" do
         | 
| 61 | 
            +
                  it { subject.must_equal 'cd /etc/chef && touch test' }
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                context "user is not root" do
         | 
| 65 | 
            +
                  before { server.stubs(:user).returns('vaskas') }
         | 
| 66 | 
            +
                  it     { subject.must_equal "sudo -- sh -c 'cd /etc/chef && touch test'" }
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
            end
         | 
    
        data/spec/spec_helper.rb
    ADDED
    
    
    
        data/template/.gitignore
    ADDED
    
    
    
        data/template/Cheffile
    ADDED
    
    
    
        data/template/Gemfile
    ADDED
    
    
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            json:
         | 
| 2 | 
            +
              net:
         | 
| 3 | 
            +
                IP: 33.33.33.10
         | 
| 4 | 
            +
              users:
         | 
| 5 | 
            +
                - <%= @login %>
         | 
| 6 | 
            +
              cowsay:
         | 
| 7 | 
            +
                greeting: "Good job, <%= @login %>!"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            roles:
         | 
| 10 | 
            +
              - platform
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            recipes:
         | 
| 13 | 
            +
              - cowsay
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            server:
         | 
| 16 | 
            +
              host: 33.33.33.10
         | 
| 17 | 
            +
              user: vagrant
         | 
| 18 | 
            +
              password: vagrant
         | 
| 19 | 
            +
              platform: ubuntu
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            default['cowsay']['greeting'] = 'Hey welcome!'
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,201 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: gusteau
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.3.8
         | 
| 5 | 
            +
              prerelease: 
         | 
| 6 | 
            +
            platform: ruby
         | 
| 7 | 
            +
            authors:
         | 
| 8 | 
            +
            - Vasily Mikhaylichenko
         | 
| 9 | 
            +
            - Chris
         | 
| 10 | 
            +
            autorequire: 
         | 
| 11 | 
            +
            bindir: bin
         | 
| 12 | 
            +
            cert_chain: []
         | 
| 13 | 
            +
            date: 2013-01-23 00:00:00.000000000 Z
         | 
| 14 | 
            +
            dependencies:
         | 
| 15 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 16 | 
            +
              name: optitron
         | 
| 17 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 18 | 
            +
                none: false
         | 
| 19 | 
            +
                requirements:
         | 
| 20 | 
            +
                - - ! '>='
         | 
| 21 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 22 | 
            +
                    version: '0'
         | 
| 23 | 
            +
              type: :runtime
         | 
| 24 | 
            +
              prerelease: false
         | 
| 25 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 26 | 
            +
                none: false
         | 
| 27 | 
            +
                requirements:
         | 
| 28 | 
            +
                - - ! '>='
         | 
| 29 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 30 | 
            +
                    version: '0'
         | 
| 31 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 32 | 
            +
              name: inform
         | 
| 33 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 34 | 
            +
                none: false
         | 
| 35 | 
            +
                requirements:
         | 
| 36 | 
            +
                - - ! '>='
         | 
| 37 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 38 | 
            +
                    version: '0'
         | 
| 39 | 
            +
              type: :runtime
         | 
| 40 | 
            +
              prerelease: false
         | 
| 41 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 42 | 
            +
                none: false
         | 
| 43 | 
            +
                requirements:
         | 
| 44 | 
            +
                - - ! '>='
         | 
| 45 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 46 | 
            +
                    version: '0'
         | 
| 47 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 48 | 
            +
              name: json
         | 
| 49 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 50 | 
            +
                none: false
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - ! '>='
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '0'
         | 
| 55 | 
            +
              type: :runtime
         | 
| 56 | 
            +
              prerelease: false
         | 
| 57 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                none: false
         | 
| 59 | 
            +
                requirements:
         | 
| 60 | 
            +
                - - ! '>='
         | 
| 61 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 62 | 
            +
                    version: '0'
         | 
| 63 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 64 | 
            +
              name: net-ssh
         | 
| 65 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 66 | 
            +
                none: false
         | 
| 67 | 
            +
                requirements:
         | 
| 68 | 
            +
                - - ~>
         | 
| 69 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 70 | 
            +
                    version: 2.2.2
         | 
| 71 | 
            +
              type: :runtime
         | 
| 72 | 
            +
              prerelease: false
         | 
| 73 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 74 | 
            +
                none: false
         | 
| 75 | 
            +
                requirements:
         | 
| 76 | 
            +
                - - ~>
         | 
| 77 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 78 | 
            +
                    version: 2.2.2
         | 
| 79 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 80 | 
            +
              name: archive-tar-minitar
         | 
| 81 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 82 | 
            +
                none: false
         | 
| 83 | 
            +
                requirements:
         | 
| 84 | 
            +
                - - ~>
         | 
| 85 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 86 | 
            +
                    version: 0.5.2
         | 
| 87 | 
            +
              type: :runtime
         | 
| 88 | 
            +
              prerelease: false
         | 
| 89 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 90 | 
            +
                none: false
         | 
| 91 | 
            +
                requirements:
         | 
| 92 | 
            +
                - - ~>
         | 
| 93 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 94 | 
            +
                    version: 0.5.2
         | 
| 95 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 96 | 
            +
              name: minitest
         | 
| 97 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 98 | 
            +
                none: false
         | 
| 99 | 
            +
                requirements:
         | 
| 100 | 
            +
                - - ! '>='
         | 
| 101 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 102 | 
            +
                    version: '0'
         | 
| 103 | 
            +
              type: :development
         | 
| 104 | 
            +
              prerelease: false
         | 
| 105 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 106 | 
            +
                none: false
         | 
| 107 | 
            +
                requirements:
         | 
| 108 | 
            +
                - - ! '>='
         | 
| 109 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 110 | 
            +
                    version: '0'
         | 
| 111 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 112 | 
            +
              name: mocha
         | 
| 113 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 114 | 
            +
                none: false
         | 
| 115 | 
            +
                requirements:
         | 
| 116 | 
            +
                - - ! '>='
         | 
| 117 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 118 | 
            +
                    version: '0'
         | 
| 119 | 
            +
              type: :development
         | 
| 120 | 
            +
              prerelease: false
         | 
| 121 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 122 | 
            +
                none: false
         | 
| 123 | 
            +
                requirements:
         | 
| 124 | 
            +
                - - ! '>='
         | 
| 125 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 126 | 
            +
                    version: '0'
         | 
| 127 | 
            +
            description: A fine Chef Solo wrapper
         | 
| 128 | 
            +
            email:
         | 
| 129 | 
            +
            - vasily@locomote.com
         | 
| 130 | 
            +
            - chris@locomote.com
         | 
| 131 | 
            +
            executables:
         | 
| 132 | 
            +
            - gusteau
         | 
| 133 | 
            +
            extensions: []
         | 
| 134 | 
            +
            extra_rdoc_files: []
         | 
| 135 | 
            +
            files:
         | 
| 136 | 
            +
            - .gitignore
         | 
| 137 | 
            +
            - .travis.yml
         | 
| 138 | 
            +
            - Gemfile
         | 
| 139 | 
            +
            - LICENSE.md
         | 
| 140 | 
            +
            - README.md
         | 
| 141 | 
            +
            - Rakefile
         | 
| 142 | 
            +
            - bin/gusteau
         | 
| 143 | 
            +
            - bootstrap/centos.sh
         | 
| 144 | 
            +
            - bootstrap/gentoo.sh
         | 
| 145 | 
            +
            - bootstrap/redhat.sh
         | 
| 146 | 
            +
            - bootstrap/solo.rb
         | 
| 147 | 
            +
            - bootstrap/ubuntu.sh
         | 
| 148 | 
            +
            - gusteau.gemspec
         | 
| 149 | 
            +
            - lib/gusteau.rb
         | 
| 150 | 
            +
            - lib/gusteau/bureau.rb
         | 
| 151 | 
            +
            - lib/gusteau/chef.rb
         | 
| 152 | 
            +
            - lib/gusteau/compressed_tar_stream.rb
         | 
| 153 | 
            +
            - lib/gusteau/log.rb
         | 
| 154 | 
            +
            - lib/gusteau/node.rb
         | 
| 155 | 
            +
            - lib/gusteau/server.rb
         | 
| 156 | 
            +
            - lib/gusteau/ssh.rb
         | 
| 157 | 
            +
            - lib/gusteau/version.rb
         | 
| 158 | 
            +
            - spec/lib/gusteau/chef_spec.rb
         | 
| 159 | 
            +
            - spec/lib/gusteau/node_spec.rb
         | 
| 160 | 
            +
            - spec/lib/gusteau/server_spec.rb
         | 
| 161 | 
            +
            - spec/nodes/example.yml
         | 
| 162 | 
            +
            - spec/spec_helper.rb
         | 
| 163 | 
            +
            - template/.gitignore
         | 
| 164 | 
            +
            - template/Cheffile
         | 
| 165 | 
            +
            - template/Gemfile
         | 
| 166 | 
            +
            - template/Vagrantfile
         | 
| 167 | 
            +
            - template/data_bags/users/user.json.erb
         | 
| 168 | 
            +
            - template/nodes/example.yml.erb
         | 
| 169 | 
            +
            - template/roles/platform.rb
         | 
| 170 | 
            +
            - template/site-cookbooks/cowsay/attributes/default.rb
         | 
| 171 | 
            +
            - template/site-cookbooks/cowsay/recipes/default.rb
         | 
| 172 | 
            +
            homepage: http://github.com/locomote/gusteau
         | 
| 173 | 
            +
            licenses: []
         | 
| 174 | 
            +
            post_install_message: 
         | 
| 175 | 
            +
            rdoc_options: []
         | 
| 176 | 
            +
            require_paths:
         | 
| 177 | 
            +
            - lib
         | 
| 178 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 179 | 
            +
              none: false
         | 
| 180 | 
            +
              requirements:
         | 
| 181 | 
            +
              - - ! '>='
         | 
| 182 | 
            +
                - !ruby/object:Gem::Version
         | 
| 183 | 
            +
                  version: '0'
         | 
| 184 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 185 | 
            +
              none: false
         | 
| 186 | 
            +
              requirements:
         | 
| 187 | 
            +
              - - ! '>='
         | 
| 188 | 
            +
                - !ruby/object:Gem::Version
         | 
| 189 | 
            +
                  version: '0'
         | 
| 190 | 
            +
            requirements: []
         | 
| 191 | 
            +
            rubyforge_project: 
         | 
| 192 | 
            +
            rubygems_version: 1.8.23
         | 
| 193 | 
            +
            signing_key: 
         | 
| 194 | 
            +
            specification_version: 3
         | 
| 195 | 
            +
            summary: Making servers provisioning enjoyable since 2013.
         | 
| 196 | 
            +
            test_files:
         | 
| 197 | 
            +
            - spec/lib/gusteau/chef_spec.rb
         | 
| 198 | 
            +
            - spec/lib/gusteau/node_spec.rb
         | 
| 199 | 
            +
            - spec/lib/gusteau/server_spec.rb
         | 
| 200 | 
            +
            - spec/nodes/example.yml
         | 
| 201 | 
            +
            - spec/spec_helper.rb
         |