gusteau 0.3.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .ruby-version
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.8.7
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :test do
4
+ gem 'rake'
5
+ end
6
+
7
+ gemspec
@@ -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.
@@ -0,0 +1,91 @@
1
+ Gusteau
2
+ =======
3
+
4
+ *"Anyone can cook."*
5
+
6
+ [![Build Status](https://magnum.travis-ci.com/locomote/gusteau.png?token=sLrGziB4HXtaF5mwnaxi)](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
+
@@ -0,0 +1,7 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.pattern = "spec/**/*_spec.rb"
5
+ end
6
+
7
+ task :default => :test
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,6 @@
1
+ file_cache_path "/tmp/chef"
2
+ cookbook_path "/etc/chef/cookbooks", "/etc/chef/site-cookbooks"
3
+ role_path "/etc/chef/roles"
4
+ data_bag_path "/etc/chef/data_bags"
5
+
6
+ log_level :info
@@ -0,0 +1,8 @@
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
+ apt-get install -y ruby ruby-dev libopenssl-ruby rdoc ri irb build-essential wget ssl-cert curl rubygems
7
+ gem install chef --no-ri --no-rdoc
8
+ fi
@@ -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
@@ -0,0 +1,6 @@
1
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
2
+
3
+ module Gusteau
4
+ require 'gusteau/node'
5
+ require 'gusteau/bureau'
6
+ end
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,3 @@
1
+ module Gusteau
2
+ VERSION = "0.3.8"
3
+ 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
@@ -0,0 +1,17 @@
1
+ json:
2
+ apps:
3
+ enabled:
4
+ - profile
5
+ - travel_management_platform
6
+
7
+ roles:
8
+ - redhat
9
+
10
+ recipes:
11
+ - rvm
12
+ - ntp
13
+ - rails::apps
14
+
15
+ server:
16
+ host: server.example.com
17
+ platform: redhat
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
2
+ require 'lib/gusteau'
3
+ require 'minitest/autorun'
4
+ require 'mocha/setup'
5
+
6
+ class MiniTest::Spec
7
+ class << self
8
+ alias :context :describe
9
+ end
10
+ end
@@ -0,0 +1,2 @@
1
+ /cookbooks
2
+ /tmp
@@ -0,0 +1,8 @@
1
+ # vi:ft=ruby:
2
+
3
+ site 'http://community.opscode.com/api/v1'
4
+
5
+ cookbook 'apt'
6
+ cookbook 'build-essential'
7
+
8
+ cookbook 'user', :github => 'fnichol/chef-user'
@@ -0,0 +1,7 @@
1
+ # vi:ft=ruby:
2
+
3
+ source 'http://rubygems.org'
4
+
5
+ gem 'librarian'
6
+ gem 'gusteau'
7
+ gem 'vagrant', '~> 1.0.5'
@@ -0,0 +1,8 @@
1
+ # vi:ft=ruby:
2
+
3
+ Vagrant::Config.run do |config|
4
+ config.vm.box_url = "http://files.vagrantup.com/precise64.box"
5
+ config.vm.box = "precise64"
6
+
7
+ config.vm.network :hostonly, "33.33.33.10"
8
+ end
@@ -0,0 +1,6 @@
1
+ {
2
+ "id" : "<%= @login %>",
3
+ "home" : "/home/<%= @login %>",
4
+ "groups" : ["<%= @login %>"],
5
+ "ssh_keys": ["<%= @ssh_key %>"]
6
+ }
@@ -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,8 @@
1
+ name "platform"
2
+ description "A basic Ubuntu system"
3
+
4
+ run_list(%w(
5
+ recipe[apt]
6
+ recipe[build-essential]
7
+ recipe[user::data_bag]
8
+ ))
@@ -0,0 +1 @@
1
+ default['cowsay']['greeting'] = 'Hey welcome!'
@@ -0,0 +1,9 @@
1
+ package 'cowsay' do
2
+ action :install
3
+ end
4
+
5
+ ruby_block 'Greet the user' do
6
+ block do
7
+ puts Mixlib::ShellOut.new("cowsay '#{node['cowsay']['greeting']}'").run_command.stdout
8
+ end
9
+ end
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