iron_chef 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +33 -0
- data/Rakefile +24 -0
- data/bin/ironchef +151 -0
- data/iron_chef.gemspec +29 -0
- data/lib/iron_chef.rb +14 -0
- data/lib/iron_chef/async_enumerable.rb +19 -0
- data/lib/iron_chef/erb.rb +19 -0
- data/lib/iron_chef/plugin.rb +271 -0
- data/lib/iron_chef/rsync.rb +166 -0
- data/lib/iron_chef/tasks.rb +181 -0
- data/lib/iron_chef/thread_pool.rb +39 -0
- data/lib/iron_chef/util.rb +23 -0
- data/lib/iron_chef/version.rb +3 -0
- data/lib/iron_chef/writer/batched.rb +22 -0
- data/lib/iron_chef/writer/file.rb +23 -0
- data/lib/iron_chef/writer/streaming.rb +16 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MzczZmZjMTgxNjNlMWQwOGViOGMzNjkxOWFmNmMyNTMyM2FiNTQ3Mw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MmQ2MzQ0ZWIwMjE5YmE3MTYxY2YyMDVjYTQ5OTQ4NDk4Y2IwN2QzMw==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YWUxNjA3OTUyYWI5NGZjYTc2ODhkMzJiZDQ3NDgwM2MyNDY5MDNkYWNmNTVl
|
10
|
+
YzAyZTZmNmVkM2RkYmVhODc4NGEyZWVhOGM1NDRhODJlYTgxOWJlNjllYWYw
|
11
|
+
OGUxZjQ5MTczOTU2ZTQ5NTBjMWRmM2ZhZTgwYTY0YWNiNWMyZjU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZmUxNzA0ZDdhNGMxODdmYWYzMjVlYTJmNzZjMDU5MWVhN2ZhZDI1MzA0ODAz
|
14
|
+
YWZlOWI3YmE3OGI1ZTkwOGZiNjYyOGUzOTQ4ZjkxMmNkZTk0NmIzMzdiMDFl
|
15
|
+
NmJiZWJiMjJhZDI4YTc0M2Y1Y2MxYTYxODI0MDAyMzU3ZThmYzU=
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
iron_chef (0.0.10)
|
5
|
+
capistrano (>= 2.15.4)
|
6
|
+
json
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
capistrano (2.15.4)
|
12
|
+
highline
|
13
|
+
net-scp (>= 1.0.0)
|
14
|
+
net-sftp (>= 2.0.0)
|
15
|
+
net-ssh (>= 2.0.14)
|
16
|
+
net-ssh-gateway (>= 1.1.0)
|
17
|
+
highline (1.6.19)
|
18
|
+
json (1.8.0)
|
19
|
+
net-scp (1.1.1)
|
20
|
+
net-ssh (>= 2.6.5)
|
21
|
+
net-sftp (2.1.2)
|
22
|
+
net-ssh (>= 2.6.5)
|
23
|
+
net-ssh (2.6.7)
|
24
|
+
net-ssh-gateway (1.2.0)
|
25
|
+
net-ssh (>= 2.6.5)
|
26
|
+
rake (10.1.0)
|
27
|
+
|
28
|
+
PLATFORMS
|
29
|
+
ruby
|
30
|
+
|
31
|
+
DEPENDENCIES
|
32
|
+
iron_chef!
|
33
|
+
rake
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
Rake::TestTask.new do |t|
|
4
|
+
t.test_files = FileList['test/*_test.rb']
|
5
|
+
end
|
6
|
+
|
7
|
+
task :default => :test
|
8
|
+
|
9
|
+
desc "clean"
|
10
|
+
task :clean do
|
11
|
+
rm_f Dir.glob("*.gem")
|
12
|
+
end
|
13
|
+
|
14
|
+
namespace :gem do
|
15
|
+
desc "build the gem"
|
16
|
+
task :build => :clean do
|
17
|
+
sh "gem build iron_chef.gemspec"
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "push the gem"
|
21
|
+
task :push => :build do
|
22
|
+
sh "gem push #{Dir.glob("*.gem").first}"
|
23
|
+
end
|
24
|
+
end
|
data/bin/ironchef
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
OptionParser.new do |opts|
|
7
|
+
opts.banner = "Usage: #{File.basename($0)} [project-name-devops]"
|
8
|
+
|
9
|
+
opts.on("-h", "--help", "Displays this help info") do
|
10
|
+
puts opts
|
11
|
+
exit 0
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
opts.parse!(ARGV)
|
16
|
+
rescue OptionParser::ParseError => e
|
17
|
+
warn e.message
|
18
|
+
puts opts
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
if ARGV.empty?
|
25
|
+
abort "Please specify the DevOps project directory , e.g. '#{File.basename($0)}-devops'"
|
26
|
+
elsif ARGV.length > 1
|
27
|
+
abort "Too many arguments; please specify only the DevOps project directory."
|
28
|
+
end
|
29
|
+
|
30
|
+
def unindent(string)
|
31
|
+
indentation = string[/\A\s*/]
|
32
|
+
string.strip.gsub(/^#{indentation}/, "")
|
33
|
+
end
|
34
|
+
|
35
|
+
base = ARGV.shift
|
36
|
+
|
37
|
+
FileUtils.mkdir_p(base)
|
38
|
+
|
39
|
+
FileUtils.mkdir_p("#{base}/environments")
|
40
|
+
FileUtils.mkdir_p("#{base}/nodes")
|
41
|
+
FileUtils.mkdir_p("#{base}/data_bags/environments")
|
42
|
+
|
43
|
+
files = {
|
44
|
+
".gitignore" => unindent(<<-FILE),
|
45
|
+
/cookbooks
|
46
|
+
/tmp
|
47
|
+
|
48
|
+
FILE
|
49
|
+
|
50
|
+
"Gemfile" => unindent(<<-FILE),
|
51
|
+
source 'https://rubygems.org'
|
52
|
+
|
53
|
+
gem 'iron_chef'
|
54
|
+
|
55
|
+
FILE
|
56
|
+
|
57
|
+
"Capfile" => unindent(<<-FILE),
|
58
|
+
require 'iron_chef'
|
59
|
+
load 'config/deploy'
|
60
|
+
|
61
|
+
FILE
|
62
|
+
|
63
|
+
"config/deploy.rb" => unindent(<<-FILE),
|
64
|
+
set :chef_environments, %w(staging production)
|
65
|
+
|
66
|
+
# don't forget to run locally
|
67
|
+
# chmod 600 /your-full/local-path-to-ssh-key/xxxxx_keypair_ec2.pem
|
68
|
+
# ssh-add /your-full/local-path-to-ssh-key/xxxxx_keypair_ec2.pem
|
69
|
+
|
70
|
+
ssh_options[:keys] = File.expand_path('~/your-local-path-to-ssh-key/xxxxx_keypair_ec2.pem')
|
71
|
+
default_run_options[:pty] = true
|
72
|
+
|
73
|
+
set :user, 'ec2-user'
|
74
|
+
set :use_sudo, false
|
75
|
+
|
76
|
+
FILE
|
77
|
+
|
78
|
+
"roles/base_server.rb" => unindent(<<-FILE),
|
79
|
+
name "base_server"
|
80
|
+
description "Base Server Role"
|
81
|
+
# run_list
|
82
|
+
# default_attributes
|
83
|
+
FILE
|
84
|
+
|
85
|
+
"roles/app_server.rb" => unindent(<<-FILE),
|
86
|
+
name "app_server"
|
87
|
+
description "Application Server Role"
|
88
|
+
# run_list
|
89
|
+
# default_attributes
|
90
|
+
FILE
|
91
|
+
|
92
|
+
"roles/db_server.rb" => unindent(<<-FILE),
|
93
|
+
name "db_server"
|
94
|
+
description "Database Server Role"
|
95
|
+
# run_list
|
96
|
+
# default_attributes
|
97
|
+
FILE
|
98
|
+
|
99
|
+
"roles/web_server.rb" => unindent(<<-FILE),
|
100
|
+
name "web_server"
|
101
|
+
description "Web Server Role"
|
102
|
+
# run_list
|
103
|
+
# default_attributes
|
104
|
+
FILE
|
105
|
+
|
106
|
+
"cookbooks/.gitkeep" => '',
|
107
|
+
|
108
|
+
"site-cookbooks/commons/recipes/default.rb" => unindent(<<-FILE),
|
109
|
+
# retrieve environment specific data bag item
|
110
|
+
env = Chef::DataBagItem.load('environments', node['chef_environment'])
|
111
|
+
Chef::Log.info("Loaded environments information from DataBagItem env[\#{env['id']}]")
|
112
|
+
Chef::Log.info("Loaded environments information from DataBagItem env[\#{env['description']}]")
|
113
|
+
|
114
|
+
# retrieve global settings specific data bag item
|
115
|
+
gs_default = Chef::DataBagItem.load('global_settings', 'default')
|
116
|
+
Chef::Log.info("Loaded global settings information from DataBagItem gs_default[\#{gs_default['id']}]")
|
117
|
+
Chef::Log.info("Loaded global settings information from DataBagItem gs_default[\#{gs_default['description']}]")
|
118
|
+
FILE
|
119
|
+
|
120
|
+
"site-cookbooks/commons/attributes/default.rb" => '',
|
121
|
+
|
122
|
+
"data_bags/global_settings/default.json" => "{\n\"id\": \"default\",\n\"description\": \"global settings default data bag item\"\n}",
|
123
|
+
|
124
|
+
"README.md" => "\# #{base}"
|
125
|
+
}
|
126
|
+
|
127
|
+
files.each do |file, content|
|
128
|
+
file = File.join(base, file)
|
129
|
+
if File.exists?(file)
|
130
|
+
warn "[skip] '#{file}' already exists"
|
131
|
+
elsif File.exists?(file.downcase)
|
132
|
+
warn "[skip] '#{file.downcase}' exists, which could conflict with `#{file}'"
|
133
|
+
else
|
134
|
+
unless File.exists?(File.dirname(file))
|
135
|
+
puts "[add] making directory '#{File.dirname(file)}'"
|
136
|
+
FileUtils.mkdir_p(File.dirname(file))
|
137
|
+
end
|
138
|
+
puts "[add] writing '#{file}'"
|
139
|
+
File.open(file, "w") { |f| f.write(content) }
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
Dir.chdir(base) do
|
144
|
+
puts 'Installing gem dependencies'
|
145
|
+
system 'bundle'
|
146
|
+
|
147
|
+
system 'cap env:prepare'
|
148
|
+
|
149
|
+
puts "Created Iron Chef DevOps project '#{base}'"
|
150
|
+
|
151
|
+
end
|
data/iron_chef.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'iron_chef/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "iron_chef"
|
8
|
+
gem.platform = Gem::Platform::RUBY
|
9
|
+
gem.version = IronChef::VERSION
|
10
|
+
gem.license = "MIT"
|
11
|
+
|
12
|
+
gem.required_ruby_version = '>= 1.9.3'
|
13
|
+
gem.required_rubygems_version = '>= 1.8.11'
|
14
|
+
|
15
|
+
gem.authors = ["Scott Rosenthal"]
|
16
|
+
gem.email = ["sr7575@gmail.com"]
|
17
|
+
gem.description = %q{Iron Chef is a lightweight Chef Solo wrapper built as a capistrano plugin}
|
18
|
+
gem.summary = %q{Iron Chef makes cloud server provisioning with Chef Solo powerful and easy.}
|
19
|
+
gem.homepage = "https://github.com/scottvrosenthal/iron_chef"
|
20
|
+
gem.license = "MIT"
|
21
|
+
gem.files = `git ls-files | grep -vE '(jenkins|.travis.yml|.gitmodules|.gitignore|.ruby-version|.ruby-gemset|README.md)'`.split("\n")
|
22
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
23
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
24
|
+
gem.require_paths = ["lib"]
|
25
|
+
|
26
|
+
gem.add_dependency 'capistrano', '>= 2.15.4'
|
27
|
+
gem.add_dependency 'json'
|
28
|
+
|
29
|
+
end
|
data/lib/iron_chef.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'iron_chef/rsync'
|
4
|
+
require 'iron_chef/async_enumerable'
|
5
|
+
require 'iron_chef/erb'
|
6
|
+
require 'iron_chef/plugin'
|
7
|
+
require 'iron_chef/thread_pool'
|
8
|
+
require 'iron_chef/util'
|
9
|
+
require 'iron_chef/writer/batched'
|
10
|
+
require 'iron_chef/writer/file'
|
11
|
+
require 'iron_chef/writer/streaming'
|
12
|
+
require 'iron_chef/tasks'
|
13
|
+
|
14
|
+
Capistrano.plugin :iron_chef, IronChef::Plugin
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module IronChef
|
2
|
+
module AsyncEnumerable
|
3
|
+
def each(&block)
|
4
|
+
pool = IronChef::ThreadPool.new(IronChef::Util.thread_pool_size)
|
5
|
+
super do |item|
|
6
|
+
pool.schedule(item, &block)
|
7
|
+
end
|
8
|
+
pool.shutdown
|
9
|
+
end
|
10
|
+
|
11
|
+
def map(&block)
|
12
|
+
pool = IronChef::ThreadPool.new(IronChef::Util.thread_pool_size)
|
13
|
+
super do |item|
|
14
|
+
pool.schedule(item, &block)
|
15
|
+
end
|
16
|
+
pool.shutdown
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'yaml'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module IronChef
|
6
|
+
module ERB
|
7
|
+
def self.read_erb(path)
|
8
|
+
::ERB.new(File.read(path)).result binding
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.read_erb_yaml(path)
|
12
|
+
YAML::load(read_erb path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.read_erb_json(path)
|
16
|
+
JSON::parse(read_erb path)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
module IronChef
|
2
|
+
module Plugin
|
3
|
+
|
4
|
+
def fetch_chef_environment_dir
|
5
|
+
fetch(:chef_environment_dir, 'environments')
|
6
|
+
end
|
7
|
+
|
8
|
+
def fetch_chef_nodes_dir
|
9
|
+
fetch(:chef_nodes_dir, 'nodes')
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_node(node_path)
|
13
|
+
raise "Node YAML file #{node_path} not found" unless node_path && File.exists?(node_path)
|
14
|
+
|
15
|
+
node_name = File.basename(node_path).gsub('.yml','')
|
16
|
+
node_config = IronChef::ERB.read_erb_yaml(node_path)
|
17
|
+
|
18
|
+
node_config['node_name'] = node_name
|
19
|
+
|
20
|
+
node_config
|
21
|
+
end
|
22
|
+
|
23
|
+
def node(node_name)
|
24
|
+
nodes_location = fetch_chef_nodes_dir
|
25
|
+
unless node_path = Dir.glob("./#{nodes_location}/**/#{node_name}.yml")[0]
|
26
|
+
abort "Node '#{node_name}' is unknown. Known nodes are #{nodes_list.join(', ')}."
|
27
|
+
end
|
28
|
+
find_node(node_path)
|
29
|
+
end
|
30
|
+
|
31
|
+
def nodes_list
|
32
|
+
nodes_location = fetch_chef_nodes_dir
|
33
|
+
nodes_available = Dir.glob("./#{nodes_location}/**/*.yml").map { |f| File.basename(f, '.*') }
|
34
|
+
nodes_available.sort
|
35
|
+
end
|
36
|
+
|
37
|
+
def env_nodes_list
|
38
|
+
nodes_location = fetch_chef_nodes_dir
|
39
|
+
nodes_available = Dir.glob("./#{nodes_location}/**/*.yml").map { |f| File.basename(f, '.*') }
|
40
|
+
env_nodes_available = []
|
41
|
+
nodes_available.each do |node_name|
|
42
|
+
node_config = node(node_name)
|
43
|
+
if node_config['json']
|
44
|
+
if node_config['json']['chef_environment']
|
45
|
+
node_env = node_config['json']['chef_environment']
|
46
|
+
if "#{chef_environment}" == "#{node_env}"
|
47
|
+
env_nodes_available << node_config['node_name']
|
48
|
+
end
|
49
|
+
else
|
50
|
+
puts "Node '#{node_config['node_name']}' ['json']['chef_environment'] attribute is empty or missing."
|
51
|
+
end
|
52
|
+
else
|
53
|
+
puts "Node '#{node_config['node_name']}' ['json'] attribute is empty or missing."
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
if env_nodes_available.size == 0
|
58
|
+
abort "No nodes found for chef environment '#{chef_environment}'. Known nodes are #{nodes_available.join(', ')}."
|
59
|
+
end
|
60
|
+
env_nodes_available.sort
|
61
|
+
end
|
62
|
+
|
63
|
+
def tasks_for_env(nodes_names)
|
64
|
+
|
65
|
+
servers = []
|
66
|
+
|
67
|
+
nodes_names.each do |node_name|
|
68
|
+
node_config = node(node_name)
|
69
|
+
if node_config['server']
|
70
|
+
if node_config['server']['host']
|
71
|
+
|
72
|
+
servers << [node_config['server']['host'], node_config['node_name']]
|
73
|
+
|
74
|
+
task(node_config['node_name']) do
|
75
|
+
role :server, node_config['server']['host'], { node_name: node_config['node_name'] }
|
76
|
+
end
|
77
|
+
else
|
78
|
+
puts "Node '#{node_config['node_name']}' ['server']['host'] attribute is empty or missing."
|
79
|
+
end
|
80
|
+
else
|
81
|
+
puts "Node '#{node_config['node_name']}' ['server'] attribute is empty or missing."
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
if nodes_names
|
86
|
+
task(:all_nodes) do
|
87
|
+
servers.each do |server|
|
88
|
+
host_name, node_name = server
|
89
|
+
role :server, host_name, { node_name: node_name }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
def upload_node_json(node_name)
|
97
|
+
|
98
|
+
node_config = node(node_name)
|
99
|
+
|
100
|
+
node_dna = {
|
101
|
+
:run_list => node_run_list(node_config)
|
102
|
+
}.merge(node_config['json'])
|
103
|
+
|
104
|
+
put node_dna.to_json, "#{chef_destination}/node.json", :via => :scp
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
def dump_nodes_json
|
109
|
+
|
110
|
+
nodes_available = nodes_list
|
111
|
+
|
112
|
+
nodes_location = fetch_chef_nodes_dir
|
113
|
+
|
114
|
+
FileUtils.mkdir_p("./tmp/#{nodes_location}")
|
115
|
+
|
116
|
+
nodes_available.each do |node_name|
|
117
|
+
node_config = node(node_name)
|
118
|
+
|
119
|
+
node_dna = {
|
120
|
+
:run_list => node_run_list(node_config)
|
121
|
+
}.merge(node_config['json'])
|
122
|
+
|
123
|
+
node_dna_json_file = "./tmp/#{nodes_location}/#{node_config['node_name']}.json"
|
124
|
+
File.open(node_dna_json_file, "w") do |f|
|
125
|
+
f.puts node_dna.to_json
|
126
|
+
end
|
127
|
+
|
128
|
+
puts "Created #{node_dna_json_file}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def node_run_list(node_config)
|
133
|
+
run_list = []
|
134
|
+
run_list += node_config['roles'].map { |r| "role[#{r}]" } if node_config['roles']
|
135
|
+
run_list += node_config['recipes'].map { |r| "recipe[#{r}]" } if node_config['recipes']
|
136
|
+
|
137
|
+
run_list
|
138
|
+
end
|
139
|
+
|
140
|
+
def cookbooks
|
141
|
+
Array(fetch(:chef_cookbooks) { (:chef_cookbooks).select { |path| File.exist?(path) } })
|
142
|
+
end
|
143
|
+
|
144
|
+
def upload_solo_rb
|
145
|
+
cookbook_paths = cookbooks.map { |c| "File.join(chef_root, #{c.to_s.inspect})" }.join(', ')
|
146
|
+
solo_rb = <<-RUBY
|
147
|
+
solo true
|
148
|
+
chef_root = File.expand_path(File.dirname(__FILE__))
|
149
|
+
file_cache_path chef_root
|
150
|
+
cookbook_path [ #{cookbook_paths} ]
|
151
|
+
role_path File.join(chef_root, "roles")
|
152
|
+
data_bag_path File.join(chef_root, "data_bags")
|
153
|
+
json_attribs File.join(chef_root, "node.json")
|
154
|
+
log_level "#{chef_log_level}".to_sym
|
155
|
+
RUBY
|
156
|
+
put solo_rb, "#{chef_destination}/solo.rb", :via => :scp
|
157
|
+
end
|
158
|
+
|
159
|
+
def rsync
|
160
|
+
IronChef::Util.thread_pool_size = chef_parallel_rsync_pool_size
|
161
|
+
servers = IronChef::Util.optionally_async(find_servers_for_task(current_task), chef_parallel_rsync)
|
162
|
+
overrides = {}
|
163
|
+
overrides[:user] = fetch(:user, ENV['USER'])
|
164
|
+
overrides[:port] = fetch(:port) if exists?(:port)
|
165
|
+
|
166
|
+
failed_servers = servers.map do |server|
|
167
|
+
rsync_cmd = IronChef::Rsync.command(
|
168
|
+
chef_source,
|
169
|
+
IronChef::Rsync.remote_address(server.user || fetch(:user, ENV['USER']), server.host, chef_destination),
|
170
|
+
:delete => true,
|
171
|
+
:excludes => chef_excludes,
|
172
|
+
:ssh => ssh_options.merge(server.options[:ssh_options]||{}).merge(overrides)
|
173
|
+
)
|
174
|
+
logger.debug rsync_cmd
|
175
|
+
|
176
|
+
server.host unless system rsync_cmd
|
177
|
+
end.compact
|
178
|
+
|
179
|
+
raise "rsync failed on #{failed_servers.join(',')}" if failed_servers.any?
|
180
|
+
|
181
|
+
upload_chef_solo_config(servers)
|
182
|
+
end
|
183
|
+
|
184
|
+
def upload_chef_solo_config(servers)
|
185
|
+
|
186
|
+
servers.map do |server|
|
187
|
+
# allows use to use node aliases for Iron Chef
|
188
|
+
node_name = server.options[:node_name]
|
189
|
+
|
190
|
+
raise "upload_chef_solo_config failed on #{server.host} with missing node name on role" unless node_name
|
191
|
+
upload_node_json(node_name)
|
192
|
+
|
193
|
+
upload_solo_rb
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
def prepare
|
199
|
+
run "mkdir -p #{chef_destination}"
|
200
|
+
release_chef_client_lock
|
201
|
+
run "chown -R $USER: #{chef_destination}"
|
202
|
+
end
|
203
|
+
|
204
|
+
def why_run
|
205
|
+
chef(:why_run)
|
206
|
+
end
|
207
|
+
|
208
|
+
def apply
|
209
|
+
chef(:apply)
|
210
|
+
end
|
211
|
+
|
212
|
+
def lock
|
213
|
+
if should_lock?
|
214
|
+
run <<-CHECK_LOCK
|
215
|
+
if [ -f #{chef_lock_file} ]; then
|
216
|
+
stat -c "#{red_text("Chef in progress, #{chef_lock_file} owned by %U since %x")}" #{chef_lock_file} >&2;
|
217
|
+
exit 1;
|
218
|
+
fi
|
219
|
+
CHECK_LOCK
|
220
|
+
|
221
|
+
run "touch #{chef_lock_file}"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def unlock
|
226
|
+
run prepare_sudo_cmd("rm -f #{chef_lock_file}; true") if should_lock?
|
227
|
+
end
|
228
|
+
|
229
|
+
def release_chef_client_lock
|
230
|
+
run prepare_sudo_cmd("rm -f #{chef_destination}/chef-client-running.pid; true")
|
231
|
+
end
|
232
|
+
|
233
|
+
def prepare_sudo_cmd(cmd)
|
234
|
+
user == 'root' ? cmd : "sudo -- sh -c '#{cmd}'"
|
235
|
+
end
|
236
|
+
|
237
|
+
private
|
238
|
+
|
239
|
+
def should_lock?
|
240
|
+
chef_lock_file && !ENV['NO_CHEF_LOCK']
|
241
|
+
end
|
242
|
+
|
243
|
+
def chef(command = :why_run)
|
244
|
+
chef_cmd = "cd #{chef_destination} && #{chef_command} #{chef_parameters}"
|
245
|
+
flag = command == :why_run ? '--why-run' : ''
|
246
|
+
|
247
|
+
writer = if chef_stream_output
|
248
|
+
IronChef::Writer::Streaming.new(logger)
|
249
|
+
else
|
250
|
+
IronChef::Writer::Batched.new(logger)
|
251
|
+
end
|
252
|
+
|
253
|
+
writer = IronChef::Writer::File.new(writer, chef_write_to_file) unless chef_write_to_file.nil?
|
254
|
+
|
255
|
+
prepared_chef_cmd = prepare_sudo_cmd("#{chef_cmd} #{flag}")
|
256
|
+
begin
|
257
|
+
run prepared_chef_cmd do |channel, stream, data|
|
258
|
+
writer.collect_output(channel[:host], data)
|
259
|
+
end
|
260
|
+
logger.debug "Chef #{command} complete."
|
261
|
+
ensure
|
262
|
+
writer.all_output_collected
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def red_text(text)
|
267
|
+
"\033[0;31m#{text}\033[0m"
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module IronChef
|
2
|
+
class Rsync
|
3
|
+
class << self
|
4
|
+
def command(from, to, options={})
|
5
|
+
flags = ['-az']
|
6
|
+
flags << '--delete' if options[:delete]
|
7
|
+
flags << excludes(options[:excludes]) if options.has_key?(:excludes)
|
8
|
+
flags << ssh_options(options[:ssh]) if options.has_key?(:ssh)
|
9
|
+
|
10
|
+
"rsync #{flags.compact.join(' ')} #{from} #{to}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def remote_address(user, host, path)
|
14
|
+
user_with_host = [user, host].compact.join('@')
|
15
|
+
[user_with_host, path].join(':')
|
16
|
+
end
|
17
|
+
|
18
|
+
def excludes(patterns)
|
19
|
+
[patterns].flatten.map { |p| "--exclude=#{p}" }
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
#
|
24
|
+
# Convert Net::SSH options into OpenSSH options.
|
25
|
+
#
|
26
|
+
# For a list of the options normally support by Net::SSH (and thus Capistrano), see
|
27
|
+
# http://net-ssh.github.com/net-ssh/classes/Net/SSH.html#method-c-start
|
28
|
+
#
|
29
|
+
# Also, to see how Net::SSH does the opposite of the conversion we are doing here, check out:
|
30
|
+
# https://github.com/net-ssh/net-ssh/blob/master/lib/net/ssh/config.rb
|
31
|
+
#
|
32
|
+
# API mismatch:
|
33
|
+
#
|
34
|
+
# * many OpenSSH options not supported
|
35
|
+
# * some options only make sense for Net::SSH
|
36
|
+
# * compression: for Net::SSH, this option is supposed to accept true, false, or algorithm. OpenSSH accepts 'yes' or 'no'
|
37
|
+
#
|
38
|
+
def ssh_options(options)
|
39
|
+
mapped_options = options.map do |key, value|
|
40
|
+
next unless value
|
41
|
+
|
42
|
+
case key
|
43
|
+
when :auth_methods then opt_auth_methods(value)
|
44
|
+
when :bind_address then opt('BindAddress', value)
|
45
|
+
when :compression then opt('Compression', value ? 'yes' : 'no')
|
46
|
+
when :compression_level then opt('CompressionLevel', value.to_i)
|
47
|
+
when :config then "-F '#{value}'"
|
48
|
+
when :encryption then opt('Ciphers', [value].flatten.join(','))
|
49
|
+
when :forward_agent then opt('ForwardAgent', value)
|
50
|
+
when :global_known_hosts_file then opt('GlobalKnownHostsFile', value)
|
51
|
+
when :hmac then opt('MACs', [value].flatten.join(','))
|
52
|
+
when :host_key then opt('HostKeyAlgorithms', [value].flatten.join(','))
|
53
|
+
when :host_key_alias then opt('HostKeyAlias', value)
|
54
|
+
when :host_name then opt('HostName', value)
|
55
|
+
when :kex then opt('KexAlgorithms', [value].flatten.join(','))
|
56
|
+
when :key_data then nil # not supported
|
57
|
+
when :keys then [value].flatten.select { |k| File.exist?(k) }.map { |k| "-i '#{k}'" }
|
58
|
+
when :keys_only then opt('IdentitiesOnly', value ? 'yes' : 'no')
|
59
|
+
when :languages then nil # not applicable
|
60
|
+
when :logger then nil # not applicable
|
61
|
+
when :paranoid then opt('StrictHostKeyChecking', value ? 'yes' : 'no')
|
62
|
+
when :passphrase then nil # not supported
|
63
|
+
when :password then nil # not supported
|
64
|
+
when :port then "-p #{value.to_i}"
|
65
|
+
when :properties then nil # not applicable
|
66
|
+
when :proxy then nil # not applicable
|
67
|
+
when :rekey_blocks_limit then nil # not supported
|
68
|
+
when :rekey_limit then opt('RekeyLimit', reverse_interpret_size(value))
|
69
|
+
when :rekey_packet_limit then nil # not supported
|
70
|
+
when :timeout then opt('ConnectTimeout', value.to_i)
|
71
|
+
when :user then "-l #{value}"
|
72
|
+
when :user_known_hosts_file then multi_opt('UserKnownHostsFile', value)
|
73
|
+
when :verbose then opt('LogLevel', interpret_log_level(value))
|
74
|
+
end
|
75
|
+
end.compact
|
76
|
+
|
77
|
+
%[-e "ssh #{mapped_options.join(' ')}"] unless mapped_options.empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def opt(option_name, option_value)
|
83
|
+
"-o #{option_name}='#{option_value}'"
|
84
|
+
end
|
85
|
+
|
86
|
+
def multi_opt(option_name, option_values)
|
87
|
+
[option_values].flatten.map do |value|
|
88
|
+
opt(option_name, value)
|
89
|
+
end.join(' ')
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# In OpenSSH, password and pubkey default to 'yes', hostbased defaults to 'no'.
|
94
|
+
# Regardless, if :auth_method is configured, then we explicitly disable methods not included.
|
95
|
+
#
|
96
|
+
def opt_auth_methods(value)
|
97
|
+
value = [value].flatten
|
98
|
+
opts = []
|
99
|
+
if value.any?
|
100
|
+
if value.include? 'password'
|
101
|
+
opts << opt('PasswordAuthentication', 'yes')
|
102
|
+
else
|
103
|
+
opts << opt('PasswordAuthentication', 'no')
|
104
|
+
end
|
105
|
+
if value.include? 'publickey'
|
106
|
+
opts << opt('PubkeyAuthentication', 'yes')
|
107
|
+
else
|
108
|
+
opts << opt('PubkeyAuthentication', 'no')
|
109
|
+
end
|
110
|
+
if value.include? 'hostbased'
|
111
|
+
opts << opt('HostbasedAuthentication', 'yes')
|
112
|
+
else
|
113
|
+
opts << opt('HostbasedAuthentication', 'no')
|
114
|
+
end
|
115
|
+
end
|
116
|
+
if opts.any?
|
117
|
+
return opts.join(' ')
|
118
|
+
else
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
#
|
124
|
+
# Converts the given integer size in bytes into a string with 'K', 'M', 'G' suffix, as appropriate.
|
125
|
+
#
|
126
|
+
# reverse of interpret_size in https://github.com/net-ssh/net-ssh/blob/master/lib/net/ssh/config.rb
|
127
|
+
#
|
128
|
+
def reverse_interpret_size(size)
|
129
|
+
size = size.to_i
|
130
|
+
if size < 1024
|
131
|
+
"#{size}"
|
132
|
+
elsif size < 1024 * 1024
|
133
|
+
"#{size/1024}K"
|
134
|
+
elsif size < 1024 * 1024 * 1024
|
135
|
+
"#{size/(1024*1024)}M"
|
136
|
+
else
|
137
|
+
"#{size/(1024*1024*1024)}G"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def interpret_log_level(level)
|
142
|
+
if level.is_a? Symbol
|
143
|
+
case level
|
144
|
+
when :debug then "DEBUG"
|
145
|
+
when :info then "INFO"
|
146
|
+
when :warn then "ERROR"
|
147
|
+
when :error then "ERROR"
|
148
|
+
when :fatal then "FATAL"
|
149
|
+
else "INFO"
|
150
|
+
end
|
151
|
+
elsif level.is_a? Integer
|
152
|
+
case level
|
153
|
+
when Logger::DEBUG then "DEBUG"
|
154
|
+
when Logger::INFO then "INFO"
|
155
|
+
when Logger::WARN then "ERROR"
|
156
|
+
when Logger::ERROR then "ERROR"
|
157
|
+
when Logger::FATAL then "FATAL"
|
158
|
+
else "INFO"
|
159
|
+
end
|
160
|
+
else
|
161
|
+
"INFO"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
Capistrano::Configuration.instance.load do
|
5
|
+
|
6
|
+
set :chef_source, '.'
|
7
|
+
set :chef_destination, '/tmp/chef'
|
8
|
+
set :chef_cookbooks, %w(cookbooks site-cookbooks)
|
9
|
+
set :chef_log_level, 'info'
|
10
|
+
set :chef_command, '/opt/chef/embedded/bin/ruby /opt/chef/bin/chef-solo -c /tmp/chef/solo.rb'
|
11
|
+
set :chef_parameters, '--color'
|
12
|
+
set :chef_excludes, %w(.git .svn nodes)
|
13
|
+
set :chef_stream_output, false
|
14
|
+
set :chef_parallel_rsync, true
|
15
|
+
set :chef_parallel_rsync_pool_size, 10
|
16
|
+
set :chef_syntax_check, false
|
17
|
+
set :chef_write_to_file, nil
|
18
|
+
set :chef_runner, nil
|
19
|
+
set :chef_lock_file, '/tmp/chef.lock'
|
20
|
+
set :chef_nodes_dir, 'nodes'
|
21
|
+
set :chef_data_bags_dir, 'data_bags'
|
22
|
+
set :chef_environment_dir, 'environments'
|
23
|
+
|
24
|
+
namespace :chef do
|
25
|
+
|
26
|
+
desc "Pushes the current chef configuration to the server."
|
27
|
+
task :update_code, :except => { :nochef => true } do
|
28
|
+
iron_chef.rsync
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "Runs chef with --why-run flag to to understand the decisions it makes."
|
32
|
+
task :why_run, :except => { :nochef => true } do
|
33
|
+
iron_chef.lock
|
34
|
+
transaction do
|
35
|
+
on_rollback { iron_chef.unlock }
|
36
|
+
iron_chef.prepare
|
37
|
+
update_code
|
38
|
+
iron_chef.why_run
|
39
|
+
iron_chef.unlock
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Applies the current chef config to the server."
|
44
|
+
task :apply, :except => { :nochef => true } do
|
45
|
+
iron_chef.lock
|
46
|
+
transaction do
|
47
|
+
on_rollback { iron_chef.unlock }
|
48
|
+
iron_chef.prepare
|
49
|
+
update_code
|
50
|
+
iron_chef.apply
|
51
|
+
iron_chef.unlock
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Clears the chef lockfile on the server."
|
56
|
+
task :unlock, :except => { :nochef => true } do
|
57
|
+
iron_chef.unlock
|
58
|
+
end
|
59
|
+
|
60
|
+
desc "Clears the chef destination folder on the server."
|
61
|
+
task :clear, :except => { :nochef => true } do
|
62
|
+
run iron_chef.prepare_sudo_cmd("rm -rf #{chef_destination}/*")
|
63
|
+
iron_chef.unlock
|
64
|
+
end
|
65
|
+
|
66
|
+
desc "Shows all nodes available for chef config."
|
67
|
+
task :nodes, :except => { :nochef => true } do
|
68
|
+
puts iron_chef.nodes_list
|
69
|
+
end
|
70
|
+
|
71
|
+
desc "Dump each node's dynamically generated node.json file to local ./tmp/#{chef_nodes_dir} directory."
|
72
|
+
task :dump_nodes_json, :except => { :nochef => true } do
|
73
|
+
iron_chef.dump_nodes_json
|
74
|
+
end
|
75
|
+
|
76
|
+
end ## end chef namespace ##
|
77
|
+
|
78
|
+
## begin env tasks
|
79
|
+
|
80
|
+
unless exists?(:chef_environments)
|
81
|
+
set :chef_environments, Dir["./#{chef_environment_dir}/*.rb"].map { |f| File.basename(f, ".rb") }
|
82
|
+
end
|
83
|
+
|
84
|
+
desc "Target individual nodes."
|
85
|
+
task :nodes, :except => { :nochef => true } do
|
86
|
+
|
87
|
+
nodes_available = iron_chef.nodes_list
|
88
|
+
|
89
|
+
iron_chef.tasks_for_env(nodes_available)
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
chef_environments.each do |name|
|
94
|
+
desc "Set the target chef environment to '#{name}'."
|
95
|
+
task name, :except => { :nochef => true } do
|
96
|
+
set :chef_environment, name.to_sym
|
97
|
+
if File.exist?(File.join(chef_environment_dir, "#{chef_environment}.rb"))
|
98
|
+
load "./#{chef_environment_dir}/#{chef_environment}"
|
99
|
+
|
100
|
+
iron_chef.tasks_for_env(iron_chef.env_nodes_list)
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
on :load do
|
107
|
+
if chef_environments.include?(ARGV.first)
|
108
|
+
# Execute the specified chef environment so that recipes required in environment can contribute to task list
|
109
|
+
find_and_execute_task(ARGV.first) if ARGV.any?{ |option| option =~ /-T|--tasks|-e|--explain/ }
|
110
|
+
else
|
111
|
+
# Execute the default chef environment so that recipes required in environment can contribute tasks
|
112
|
+
find_and_execute_task('nodes')
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
namespace :env do
|
117
|
+
|
118
|
+
desc "Stub out the chef environment config files."
|
119
|
+
task :prepare, :except => { :nochef => true } do
|
120
|
+
FileUtils.mkdir_p(chef_environment_dir)
|
121
|
+
chef_environments.each do |name|
|
122
|
+
rb_env_file = File.join(chef_environment_dir, "#{name}.rb")
|
123
|
+
unless File.exists?(rb_env_file)
|
124
|
+
File.open(rb_env_file, "w") do |f|
|
125
|
+
f.puts "# #{name.upcase}-specific chef environment configuration"
|
126
|
+
f.puts "# please put general chef environment config in config/deploy.rb"
|
127
|
+
end
|
128
|
+
yml_env_node_file = File.join(chef_nodes_dir, "#{name}-server1.yml")
|
129
|
+
unless File.exists?(yml_env_node_file)
|
130
|
+
File.open(yml_env_node_file, "w") do |f|
|
131
|
+
f.puts "json:\n chef_environment: #{name}\n\nroles:\n - app_server\n\nrecipes:\n - commons\n\nserver:\n host: ec2-xxx-xxx-xxx-xxx.us-west-2.compute.amazonaws.com"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
json_env_data_bag_file = File.join("#{chef_data_bags_dir}/environments", "#{name}.json")
|
135
|
+
unless File.exists?(json_env_data_bag_file)
|
136
|
+
File.open(json_env_data_bag_file, "w") do |f|
|
137
|
+
f.puts "{\n\"id\": \"#{name}\",\n\"description\": \"environments #{name} data_bag_item\"\n}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
puts "Created chef environment config files for: #{name}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
desc "Shows chef environment nodes available for chef apply config."
|
146
|
+
task :nodes, :except => { :nochef => true } do
|
147
|
+
if chef_environments.include?(ARGV.first)
|
148
|
+
puts iron_chef.env_nodes_list
|
149
|
+
else
|
150
|
+
puts iron_chef.nodes_list
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
## end env tasks
|
157
|
+
|
158
|
+
## begin bootstrap namespace ##
|
159
|
+
|
160
|
+
namespace :bootstrap do
|
161
|
+
|
162
|
+
desc "Installs chef via omnibus on host."
|
163
|
+
task :chef, :except => { :nochef => true } do
|
164
|
+
run "mkdir -p #{chef_destination}"
|
165
|
+
|
166
|
+
script = <<-BASH
|
167
|
+
#!/bin/sh
|
168
|
+
|
169
|
+
curl -s -o /tmp/chef-omnibus-install.sh https://www.opscode.com/chef/install.sh
|
170
|
+
chmod +x /tmp/chef-omnibus-install.sh
|
171
|
+
/tmp/chef-omnibus-install.sh > /tmp/chef-omnibus-install.log
|
172
|
+
|
173
|
+
BASH
|
174
|
+
|
175
|
+
put script, "/tmp/chef-install.sh", :via => :scp
|
176
|
+
run iron_chef.prepare_sudo_cmd("chmod +x /tmp/chef-install.sh")
|
177
|
+
run iron_chef.prepare_sudo_cmd("/tmp/chef-install.sh > /tmp/chef-install.log")
|
178
|
+
end
|
179
|
+
|
180
|
+
end ## end bootstrap namespace ##
|
181
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module IronChef
|
4
|
+
class ThreadPool
|
5
|
+
def initialize(size)
|
6
|
+
@size = size
|
7
|
+
@jobs = Queue.new
|
8
|
+
@retvals = []
|
9
|
+
|
10
|
+
@pool = Array.new(@size) do |i|
|
11
|
+
Thread.new do
|
12
|
+
Thread.current[:id] = i
|
13
|
+
|
14
|
+
catch(:exit) do
|
15
|
+
loop do
|
16
|
+
job, args = @jobs.pop
|
17
|
+
@retvals << job.call(*args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def schedule(*args, &block)
|
26
|
+
@jobs << [block, args]
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def shutdown
|
31
|
+
@size.times do
|
32
|
+
schedule { throw :exit }
|
33
|
+
end
|
34
|
+
|
35
|
+
@pool.map(&:join)
|
36
|
+
@retvals
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module IronChef
|
2
|
+
module Util
|
3
|
+
DEFAULT_THREAD_POOL_SIZE = 10
|
4
|
+
|
5
|
+
def self.thread_pool_size
|
6
|
+
@thread_pool_size ||= DEFAULT_THREAD_POOL_SIZE
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.thread_pool_size=(size)
|
10
|
+
@thread_pool_size = size
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.optionally_async(collection, async)
|
14
|
+
if async
|
15
|
+
async_collection = collection.clone
|
16
|
+
async_collection.extend IronChef::AsyncEnumerable
|
17
|
+
async_collection
|
18
|
+
else
|
19
|
+
collection
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module IronChef
|
2
|
+
module Writer
|
3
|
+
class Batched
|
4
|
+
def initialize(logger)
|
5
|
+
@outputs = {}
|
6
|
+
@logger = logger
|
7
|
+
end
|
8
|
+
|
9
|
+
def collect_output(host, data)
|
10
|
+
@outputs[host] ||= ""
|
11
|
+
@outputs[host] << data
|
12
|
+
end
|
13
|
+
|
14
|
+
def all_output_collected
|
15
|
+
@outputs.keys.sort.each do |host|
|
16
|
+
@logger.info "Chef output for #{host}"
|
17
|
+
@logger.debug @outputs[host], host
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module IronChef
|
2
|
+
module Writer
|
3
|
+
class File
|
4
|
+
def initialize(writer, file)
|
5
|
+
@wrapped_writer = writer
|
6
|
+
@logger = Capistrano::Logger.new(:output => file)
|
7
|
+
@logger.level = Capistrano::Logger::TRACE
|
8
|
+
@file_writer = Batched.new(@logger)
|
9
|
+
end
|
10
|
+
|
11
|
+
def collect_output(host, data)
|
12
|
+
@wrapped_writer.collect_output(host, data)
|
13
|
+
@file_writer.collect_output(host, data)
|
14
|
+
end
|
15
|
+
|
16
|
+
def all_output_collected
|
17
|
+
@wrapped_writer.all_output_collected
|
18
|
+
@file_writer.all_output_collected
|
19
|
+
@logger.close
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: iron_chef
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.10
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Scott Rosenthal
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-07-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: capistrano
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.15.4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.15.4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Iron Chef is a lightweight Chef Solo wrapper built as a capistrano plugin
|
42
|
+
email:
|
43
|
+
- sr7575@gmail.com
|
44
|
+
executables:
|
45
|
+
- ironchef
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- Gemfile
|
50
|
+
- Gemfile.lock
|
51
|
+
- Rakefile
|
52
|
+
- bin/ironchef
|
53
|
+
- iron_chef.gemspec
|
54
|
+
- lib/iron_chef.rb
|
55
|
+
- lib/iron_chef/async_enumerable.rb
|
56
|
+
- lib/iron_chef/erb.rb
|
57
|
+
- lib/iron_chef/plugin.rb
|
58
|
+
- lib/iron_chef/rsync.rb
|
59
|
+
- lib/iron_chef/tasks.rb
|
60
|
+
- lib/iron_chef/thread_pool.rb
|
61
|
+
- lib/iron_chef/util.rb
|
62
|
+
- lib/iron_chef/version.rb
|
63
|
+
- lib/iron_chef/writer/batched.rb
|
64
|
+
- lib/iron_chef/writer/file.rb
|
65
|
+
- lib/iron_chef/writer/streaming.rb
|
66
|
+
homepage: https://github.com/scottvrosenthal/iron_chef
|
67
|
+
licenses:
|
68
|
+
- MIT
|
69
|
+
metadata: {}
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: 1.9.3
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 1.8.11
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 2.0.3
|
87
|
+
signing_key:
|
88
|
+
specification_version: 4
|
89
|
+
summary: Iron Chef makes cloud server provisioning with Chef Solo powerful and easy.
|
90
|
+
test_files: []
|