gusteau 0.3.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![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
|
+
|
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
|