mofa 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +8 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/Guardfile +12 -0
- data/LICENSE +202 -0
- data/README.md +2 -0
- data/Rakefile +7 -0
- data/bin/mofa +12 -0
- data/config.yml.erb +33 -0
- data/lib/mofa.rb +14 -0
- data/lib/mofa/cli.rb +125 -0
- data/lib/mofa/config.rb +24 -0
- data/lib/mofa/cookbook.rb +87 -0
- data/lib/mofa/hostlist.rb +61 -0
- data/lib/mofa/mofa_cmd.rb +246 -0
- data/lib/mofa/released_cookbook.rb +47 -0
- data/lib/mofa/runlist_map.rb +54 -0
- data/lib/mofa/source_cookbook.rb +140 -0
- data/lib/mofa/version.rb +3 -0
- data/mofa.gemspec +29 -0
- metadata +258 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'thor/base'
|
3
|
+
require 'thor/actions'
|
4
|
+
|
5
|
+
class Cookbook
|
6
|
+
include Thor::Base
|
7
|
+
include Thor::Actions
|
8
|
+
include FileUtils
|
9
|
+
|
10
|
+
attr_accessor :name
|
11
|
+
attr_accessor :version
|
12
|
+
attr_accessor :type
|
13
|
+
attr_accessor :pkg_uri
|
14
|
+
attr_accessor :source_uri
|
15
|
+
attr_accessor :cookbooks_url
|
16
|
+
attr_accessor :token
|
17
|
+
|
18
|
+
def self.create(cookbook_name_or_path='.', token=nil)
|
19
|
+
cb = nil
|
20
|
+
begin
|
21
|
+
case
|
22
|
+
when cookbook_name_or_path.match(/:/)
|
23
|
+
fail "Did not find released Cookbook #{cookbook_name_or_path}!" unless ReleasedCookbook.exists?(cookbook_name_or_path)
|
24
|
+
fail "Did not find Version #{cookbook_version} of released Cookbook #{cookbook_name_or_path}!" unless ReleasedCookbook.exists?(cookbook_name_or_path, cookbook_version)
|
25
|
+
|
26
|
+
cb = ReleasedCookbook.new(cookbook_name_or_path)
|
27
|
+
|
28
|
+
else
|
29
|
+
cb = SourceCookbook.new(cookbook_name_or_path)
|
30
|
+
end
|
31
|
+
rescue RuntimeError => e
|
32
|
+
error e.message
|
33
|
+
raise "Cookbook not found/detected!"
|
34
|
+
end
|
35
|
+
cb.token = token
|
36
|
+
cb.autodetect_type
|
37
|
+
|
38
|
+
cb
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def autodetect_type
|
43
|
+
env_indicator = Mofa::Config.config['cookbook_type_indicator']['env']
|
44
|
+
wrapper_indicator = Mofa::Config.config['cookbook_type_indicator']['wrapper']
|
45
|
+
base_indicator = Mofa::Config.config['cookbook_type_indicator']['base']
|
46
|
+
|
47
|
+
say "Autodetecting Cookbook Architectural Type... "
|
48
|
+
case
|
49
|
+
when @name.match(env_indicator)
|
50
|
+
@type = 'env'
|
51
|
+
when @name.match(wrapper_indicator)
|
52
|
+
@type = 'wrapper'
|
53
|
+
when @name.match(base_indicator)
|
54
|
+
@type = 'base'
|
55
|
+
else
|
56
|
+
@type = 'application'
|
57
|
+
end
|
58
|
+
say "#{type.capitalize} Cookbook"
|
59
|
+
end
|
60
|
+
|
61
|
+
def say(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)$/))
|
62
|
+
color ||= :green
|
63
|
+
super
|
64
|
+
end
|
65
|
+
|
66
|
+
def ok(detail=nil)
|
67
|
+
text = detail ? "OK, #{detail}." : "OK."
|
68
|
+
say text, :green
|
69
|
+
end
|
70
|
+
|
71
|
+
def error(detail)
|
72
|
+
say detail, :red
|
73
|
+
end
|
74
|
+
|
75
|
+
# Enforce silent system calls, unless the --verbose option is passed.
|
76
|
+
# One may either pass -v, --verbose or --[v|verbose]=[true|t|yes|y|1].
|
77
|
+
#
|
78
|
+
def run(cmd, *args)
|
79
|
+
args = args.empty? ? {} : args.pop
|
80
|
+
verbose = (Mofa::CLI::option_debug) ? true : false
|
81
|
+
#verbose = !!(options[:verbose] && options[:verbose].to_s.match(/(verbose|true|t|yes|y|1)$/i))
|
82
|
+
exit_code = super(cmd, args.merge(:verbose => verbose))
|
83
|
+
fail "Failed to run #{cmd.inspect}!" unless exit_code == true
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'net/ping'
|
3
|
+
|
4
|
+
class Hostlist
|
5
|
+
attr_accessor :list
|
6
|
+
attr_accessor :filter
|
7
|
+
attr_accessor :service_host
|
8
|
+
attr_accessor :service_url
|
9
|
+
attr_accessor :filter
|
10
|
+
attr_accessor :api_key
|
11
|
+
|
12
|
+
def self.create(filter = nil)
|
13
|
+
hl = Hostlist.new
|
14
|
+
filter ||= Mofa::Config.config['service_hostlist_default_filter']
|
15
|
+
hl.filter = filter
|
16
|
+
hl.service_host = Mofa::Config.config['service_hostlist_url'].gsub(/^http:\/\//, '').gsub(/\/.*$/, '').gsub(/:.*$/, '')
|
17
|
+
hl.service_url = Mofa::Config.config['service_hostlist_url']
|
18
|
+
hl.api_key = Mofa::Config.config['service_hostlist_api_key']
|
19
|
+
hl
|
20
|
+
end
|
21
|
+
|
22
|
+
def retrieve
|
23
|
+
fail "Hostlist Service not reachable! (cannot ping #{service_host})" unless up?
|
24
|
+
response = RestClient.get(@service_url, { :params => {:key => api_key}})
|
25
|
+
hosts_list_json = JSON.parse response.body
|
26
|
+
@list = hosts_list_json['data'].collect { |i| i['cname'] }
|
27
|
+
|
28
|
+
apply_filter
|
29
|
+
sort_by_domainname
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def up?
|
34
|
+
p = Net::Ping::TCP.new(@service_host, 'http')
|
35
|
+
p.ping?
|
36
|
+
end
|
37
|
+
|
38
|
+
def apply_filter
|
39
|
+
# building matcher
|
40
|
+
regex = @filter.gsub(/\*/, '__ASTERISK__')
|
41
|
+
regex = Regexp.escape(regex).gsub(/__ASTERISK__/, '.*')
|
42
|
+
regex = '^' + regex + '$'
|
43
|
+
|
44
|
+
puts "regex=#{regex}"
|
45
|
+
|
46
|
+
@list.select! {|hostname| hostname.match(regex) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def sort_by_domainname
|
50
|
+
sortable = {}
|
51
|
+
@list.each do |hostname|
|
52
|
+
sortable.store(hostname.split(/\./).reverse.join('.'), hostname)
|
53
|
+
end
|
54
|
+
@list = sortable.keys.sort.collect { |s| sortable[s] }
|
55
|
+
end
|
56
|
+
|
57
|
+
def filter_by_runlist_map(runlist_map)
|
58
|
+
@list.select! { |hostname| runlist_map.mp.key?(hostname)}
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'net/sftp'
|
3
|
+
|
4
|
+
class MofaCmd
|
5
|
+
attr_accessor :token
|
6
|
+
attr_accessor :cookbook
|
7
|
+
attr_accessor :hostlist
|
8
|
+
attr_accessor :runlist_map
|
9
|
+
|
10
|
+
def self.generate_token
|
11
|
+
Digest::SHA1.hexdigest([Time.now, rand].join)[0..10]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.create(cookbook, hostlist, runlist_map, token)
|
15
|
+
mofa_cmd = MofaCmd.new
|
16
|
+
mofa_cmd.token = token
|
17
|
+
mofa_cmd.cookbook = cookbook
|
18
|
+
mofa_cmd.hostlist = hostlist
|
19
|
+
mofa_cmd.runlist_map = runlist_map
|
20
|
+
mofa_cmd
|
21
|
+
end
|
22
|
+
|
23
|
+
def prepare
|
24
|
+
cookbook.prepare
|
25
|
+
fail "Hostlist Service not reachable! (cannot ping #{hostlist.service_url})" unless hostlist.up?
|
26
|
+
end
|
27
|
+
|
28
|
+
def execute
|
29
|
+
cookbook.execute
|
30
|
+
hostlist.retrieve
|
31
|
+
runlist_map.generate
|
32
|
+
|
33
|
+
puts "Runlist Map: #{runlist_map.mp.inspect}"
|
34
|
+
puts "Hostlist before runlist filtering: #{hostlist.list.inspect}"
|
35
|
+
|
36
|
+
hostlist.filter_by_runlist_map(runlist_map)
|
37
|
+
|
38
|
+
puts "Hostlist after runlist filtering: #{hostlist.list.inspect}"
|
39
|
+
|
40
|
+
exit_code = run_chef_solo_on_hosts
|
41
|
+
|
42
|
+
exit_code
|
43
|
+
end
|
44
|
+
|
45
|
+
def cleanup
|
46
|
+
cookbook.cleanup
|
47
|
+
end
|
48
|
+
|
49
|
+
# FIXME
|
50
|
+
# This Code is Copy'n'Pasted from the old mofa tooling. Only to make the MVP work in time!!
|
51
|
+
# This needs to be refactored ASAP.
|
52
|
+
|
53
|
+
def run_chef_solo_on_hosts
|
54
|
+
time = Time.new
|
55
|
+
puts 'Chef-Solo Run started at ' + time.strftime('%Y-%m-%d %H:%M:%S')
|
56
|
+
puts "Will use ssh_user #{Mofa::Config.config['ssh_user']} and ssh_key_file #{Mofa::Config.config['ssh_keyfile']}"
|
57
|
+
at_least_one_chef_solo_run_failed = false
|
58
|
+
chef_solo_runs = {}
|
59
|
+
host_index = 0
|
60
|
+
hostlist.list.each do |hostname|
|
61
|
+
host_index = host_index + 1
|
62
|
+
puts
|
63
|
+
puts "----------------------------------------------------------------------"
|
64
|
+
puts "Chef-Solo on Host #{hostname} (#{host_index}/#{hostlist.list.length.to_s})"
|
65
|
+
puts "----------------------------------------------------------------------"
|
66
|
+
chef_solo_runs.store(hostname, {})
|
67
|
+
|
68
|
+
# do only one for faster dev-cycle...
|
69
|
+
#next unless hostname.match(/^dash/)
|
70
|
+
|
71
|
+
puts "Pinging host #{hostname}..."
|
72
|
+
exit_status = system("ping -q -c 1 #{hostname} >/dev/null 2>&1")
|
73
|
+
unless exit_status then
|
74
|
+
puts " --> Host #{hostname} is unavailable!"
|
75
|
+
chef_solo_runs[hostname].store('status', 'UNAVAIL')
|
76
|
+
chef_solo_runs[hostname].store('status_msg', "Host #{hostname} unreachable.")
|
77
|
+
else
|
78
|
+
puts " --> Host #{hostname} is available."
|
79
|
+
prerequesits_met = true
|
80
|
+
# Create a temp working dir on the target host
|
81
|
+
solo_dir = '/var/tmp/' + time.strftime('%Y-%m-%d_%H%M%S')
|
82
|
+
Net::SSH.start(hostname, Mofa::Config.config['ssh_user'], :keys => [Mofa::Config.config['ssh_keyfile']], :verbose => :error) do |ssh|
|
83
|
+
puts "Remotely creating solo_dir \"#{solo_dir}\" on host #{hostname}"
|
84
|
+
# remotely create the temp folder
|
85
|
+
out = ssh_exec!(ssh, "[ -d #{solo_dir} ] || mkdir #{solo_dir}")
|
86
|
+
puts "ERROR (#{out[0]}): #{out[2]}" if out[0] != 0
|
87
|
+
|
88
|
+
# remotely create a data_bags folder structure on the target host
|
89
|
+
if File.directory?("#{cookbook.source_dir}/data_bags")
|
90
|
+
Dir.entries("#{cookbook.source_dir}/data_bags").select{|f| !f.match(/^\.\.?$/)}.each do |data_bag|
|
91
|
+
puts "Remotely creating data_bags dir \"#{solo_dir}/data_bags/#{data_bag}\""
|
92
|
+
out = ssh_exec!(ssh, "[ -d #{solo_dir}/data_bags/#{data_bag} ] || mkdir -p #{solo_dir}/data_bags/#{data_bag}")
|
93
|
+
puts "ERROR (#{out[0]}): #{out[2]}" if out[0] != 0
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# skip the rest if prerequesits are not met
|
100
|
+
next unless prerequesits_met
|
101
|
+
|
102
|
+
|
103
|
+
Net::SFTP.start(hostname, Mofa::Config.config['ssh_user'], :keys => [Mofa::Config.config['ssh_keyfile']], :verbose => :error) do |sftp|
|
104
|
+
|
105
|
+
# remotely creating solo.rb
|
106
|
+
puts "Remotely creating \"#{solo_dir}/solo.rb\""
|
107
|
+
sftp.file.open("#{solo_dir}/solo.rb", "w") do |file|
|
108
|
+
solo_rb = <<-"EOF"
|
109
|
+
cookbook_path [ "#{solo_dir}/cookbooks" ]
|
110
|
+
data_bag_path "#{solo_dir}/data_bags"
|
111
|
+
log_level :info
|
112
|
+
log_location "#{solo_dir}/log"
|
113
|
+
verify_api_cert true
|
114
|
+
EOF
|
115
|
+
|
116
|
+
file.write(solo_rb)
|
117
|
+
end
|
118
|
+
|
119
|
+
# remotely creating node.json
|
120
|
+
puts "Remotely creating \"#{solo_dir}/node.json\""
|
121
|
+
node_json = {}
|
122
|
+
node_json.store('run_list', runlist_map.mp[hostname])
|
123
|
+
|
124
|
+
sftp.file.open("#{solo_dir}/node.json", "w") do |file|
|
125
|
+
file.write(JSON.pretty_generate(node_json))
|
126
|
+
end
|
127
|
+
|
128
|
+
# remotely create data_bag items
|
129
|
+
if File.directory?("#{cookbook.source_dir}/data_bags")
|
130
|
+
Dir.entries("#{cookbook.source_dir}/data_bags").select{|f| !f.match(/^\.\.?$/)}.each do |data_bag|
|
131
|
+
Dir.entries("#{cookbook.source_dir}/data_bags/#{data_bag}").select{|f| f.match(/\.json$/)}.each do |data_bag_item|
|
132
|
+
puts "Uploading data_bag_item #{data_bag_item}... "
|
133
|
+
sftp.upload!("#{cookbook.source_dir}/data_bags/#{data_bag}/#{data_bag_item}", "#{solo_dir}/data_bags/#{data_bag}/#{data_bag_item}")
|
134
|
+
puts "OK."
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
if cookbook.instance_of?(SourceCookbook)
|
140
|
+
puts "Cookbook is a SourceCookbook! Uploading Snapshot Package #{cookbook.pkg_name}... "
|
141
|
+
sftp.upload!("#{cookbook.pkg_dir}/#{cookbook.pkg_name}", "#{solo_dir}/#{cookbook.pkg_name}")
|
142
|
+
puts "OK."
|
143
|
+
end
|
144
|
+
|
145
|
+
# Do it -> Execute the chef-solo run!
|
146
|
+
Net::SSH.start(hostname, Mofa::Config::config['ssh_user'], :keys => [Mofa::Config::config['ssh_keyfile']], :verbose => :error) do |ssh|
|
147
|
+
|
148
|
+
if cookbook.instance_of?(SourceCookbook)
|
149
|
+
puts "Remotely unpacking Snapshot Package #{cookbook.pkg_name}... "
|
150
|
+
out = ssh_exec!(ssh, "cd #{solo_dir}; tar xvfz #{cookbook.pkg_name}")
|
151
|
+
if out[0] != 0
|
152
|
+
puts "ERROR (#{out[0]}): #{out[2]}"
|
153
|
+
puts out[1]
|
154
|
+
else
|
155
|
+
puts "OK."
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
puts "Remotely running chef-solo -c #{solo_dir}/solo.rb -j #{solo_dir}/node.json"
|
160
|
+
out = ssh_exec!(ssh, "sudo chef-solo -c #{solo_dir}/solo.rb -j #{solo_dir}/node.json")
|
161
|
+
if out[0] != 0
|
162
|
+
puts "ERROR (#{out[0]}): #{out[2]}"
|
163
|
+
out = ssh_exec!(ssh, "sudo cat #{solo_dir}/log")
|
164
|
+
puts "ERROR (#{out[0]}): #{out[2]}" if out[0] != 0
|
165
|
+
puts out[1]
|
166
|
+
chef_solo_runs[hostname].store('status', 'FAIL')
|
167
|
+
chef_solo_runs[hostname].store('status_msg', out[1])
|
168
|
+
else
|
169
|
+
unless Mofa::CLI::option_debug
|
170
|
+
out = ssh_exec!(ssh, "sudo grep 'Chef Run' #{solo_dir}/log")
|
171
|
+
puts "ERROR (#{out[0]}): #{out[2]}" if out[0] != 0
|
172
|
+
puts "Done."
|
173
|
+
else
|
174
|
+
out = ssh_exec!(ssh, "sudo cat #{solo_dir}/log")
|
175
|
+
puts "ERROR (#{out[0]}): #{out[2]}" if out[0] != 0
|
176
|
+
puts out[1]
|
177
|
+
end
|
178
|
+
chef_solo_runs[hostname].store('status', 'SUCCESS')
|
179
|
+
chef_solo_runs[hostname].store('status_msg', '')
|
180
|
+
end
|
181
|
+
out = ssh_exec!(ssh, "sudo chown -R #{Mofa::Config.config['ssh_user']}.#{Mofa::Config.config['ssh_user']} #{solo_dir}")
|
182
|
+
puts "ERROR (#{out[0]}): #{out[2]}" if out[0] != 0
|
183
|
+
end
|
184
|
+
end
|
185
|
+
at_least_one_chef_solo_run_failed = true if chef_solo_runs[hostname]['status'] == 'FAIL'
|
186
|
+
end
|
187
|
+
|
188
|
+
# ------- print out report
|
189
|
+
puts
|
190
|
+
puts "----------------------------------------------------------------------"
|
191
|
+
puts "Chef-Solo Run REPORT"
|
192
|
+
puts "----------------------------------------------------------------------"
|
193
|
+
puts "Chef-Solo has been run on #{chef_solo_runs.keys.length.to_s} hosts."
|
194
|
+
|
195
|
+
chef_solo_runs.each do |hostname, content|
|
196
|
+
status_msg = ''
|
197
|
+
status_msg = "(#{content['status_msg']})" if content['status'] == 'FAIL'
|
198
|
+
puts "#{content['status']}: #{hostname} #{status_msg}"
|
199
|
+
end
|
200
|
+
|
201
|
+
exit_code = 0
|
202
|
+
if at_least_one_chef_solo_run_failed
|
203
|
+
exit_code = 1
|
204
|
+
end
|
205
|
+
|
206
|
+
puts "Exiting with exit code #{exit_code}."
|
207
|
+
exit_code
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
def ssh_exec!(ssh, command)
|
212
|
+
stdout_data = ""
|
213
|
+
stderr_data = ""
|
214
|
+
exit_code = nil
|
215
|
+
exit_signal = nil
|
216
|
+
ssh.open_channel do |channel|
|
217
|
+
channel.exec(command) do |ch, success|
|
218
|
+
unless success
|
219
|
+
abort "FAILED: couldn't execute command (ssh.channel.exec)"
|
220
|
+
end
|
221
|
+
channel.on_data do |ch, data|
|
222
|
+
stdout_data+=data
|
223
|
+
end
|
224
|
+
|
225
|
+
channel.on_extended_data do |ch, type, data|
|
226
|
+
stderr_data+=data
|
227
|
+
end
|
228
|
+
|
229
|
+
channel.on_request("exit-status") do |ch, data|
|
230
|
+
exit_code = data.read_long
|
231
|
+
end
|
232
|
+
|
233
|
+
channel.on_request("exit-signal") do |ch, data|
|
234
|
+
exit_signal = data.read_long
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
ssh.loop
|
239
|
+
[exit_code, stdout_data, stderr_data, exit_signal]
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
private
|
244
|
+
|
245
|
+
end
|
246
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class ReleasedCookbook < Cookbook
|
2
|
+
attr_accessor :pkg_dir
|
3
|
+
attr_accessor :pkg_name
|
4
|
+
|
5
|
+
def initialize(cookbook_name_or_path)
|
6
|
+
super()
|
7
|
+
# TODO: this needs proper vaidation!
|
8
|
+
@name = cookbook_name_or_path.split(/:/).first
|
9
|
+
@version = cookbook_name_or_path.split(/:/).last
|
10
|
+
end
|
11
|
+
|
12
|
+
# ------------- Interface Methods
|
13
|
+
|
14
|
+
def prepare
|
15
|
+
@pkg_name = "#{name}-#{version}.tar.gz"
|
16
|
+
@pkg_dir = "#{Mofa::Config.config['tmp_dir']}/.mofa/#{token}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute
|
20
|
+
# TODO: Download & unpack released cookbook
|
21
|
+
# Important for guessing role runlists (when cookbook is an env-cookbook)
|
22
|
+
end
|
23
|
+
|
24
|
+
def cleanup
|
25
|
+
say "Removing folder #{pkg_dir}...#{nl}"
|
26
|
+
run "rm -r #{pkg_dir}"
|
27
|
+
ok
|
28
|
+
end
|
29
|
+
|
30
|
+
# ------------- /Interface Methods
|
31
|
+
|
32
|
+
def cleanup!
|
33
|
+
unless (Dir.entries("#{Mofa::Config.config['tmp_dir']}/.mofa") - %w{ . .. }).empty?
|
34
|
+
say "Removing content of folder #{Mofa::Config.config['tmp_dir']}/.mofa"
|
35
|
+
run "rm -r #{Mofa::Config.config['tmp_dir']}/.mofa/*"
|
36
|
+
else
|
37
|
+
say "Folder #{Mofa::Config.config['tmp_dir']}/.mofa is (already) clean."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def nl
|
44
|
+
return (Mofa::CLI::option_verbose) ? '' : ' '
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class RunlistMap
|
2
|
+
attr_accessor :mp
|
3
|
+
attr_accessor :cookbook
|
4
|
+
attr_accessor :hostlist
|
5
|
+
attr_accessor :token
|
6
|
+
attr_accessor :option_runlist
|
7
|
+
attr_accessor :default_runlist
|
8
|
+
|
9
|
+
def self.create(cookbook, hostlist, token, option_runlist = nil)
|
10
|
+
rl = RunlistMap.new
|
11
|
+
rl.cookbook = cookbook
|
12
|
+
rl.hostlist = hostlist
|
13
|
+
rl.token = token
|
14
|
+
rl.default_runlist = (!option_runlist.nil?) ? option_runlist : nil
|
15
|
+
rl
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@mp = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate
|
23
|
+
@default_runlist ||= "recipe[#{cookbook.name}::default]"
|
24
|
+
|
25
|
+
case cookbook.type
|
26
|
+
when 'env'
|
27
|
+
guess_runlists_by_hostnames
|
28
|
+
else
|
29
|
+
set_default_runlist_for_every_host
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def guess_runlists_by_hostnames
|
34
|
+
# recipes/jkmaster.rb --> runlist[<env_cookbook_name>::jkmaster] for all hosts with shortname jkmaster
|
35
|
+
# recipes/jkslave.rb --> runlist[<env_cookbook_name>::jkslave] for all hosts with shortname jkslave[0-9]
|
36
|
+
# and so on
|
37
|
+
hostlist.list.each do |hostname|
|
38
|
+
cookbook.recipes.each do |recipe|
|
39
|
+
recipe_regex = "^#{recipe}[0-9]*\."
|
40
|
+
if hostname.match(recipe_regex)
|
41
|
+
@mp.store(hostname, "recipe[#{cookbook.name}::#{recipe}]")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_default_runlist_for_every_host
|
49
|
+
hostlist.list.each do |hostname|
|
50
|
+
@mp.store(hostname, @default_runlist)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|