iron_chef 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|