clc-fork-chef-metal 0.11.beta.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +106 -0
  3. data/LICENSE +201 -0
  4. data/README.md +201 -0
  5. data/Rakefile +6 -0
  6. data/bin/metal +276 -0
  7. data/lib/chef/provider/machine.rb +147 -0
  8. data/lib/chef/provider/machine_batch.rb +130 -0
  9. data/lib/chef/provider/machine_execute.rb +30 -0
  10. data/lib/chef/provider/machine_file.rb +49 -0
  11. data/lib/chef/resource/machine.rb +95 -0
  12. data/lib/chef/resource/machine_batch.rb +20 -0
  13. data/lib/chef/resource/machine_execute.rb +22 -0
  14. data/lib/chef/resource/machine_file.rb +28 -0
  15. data/lib/chef_metal.rb +62 -0
  16. data/lib/chef_metal/action_handler.rb +63 -0
  17. data/lib/chef_metal/add_prefix_action_handler.rb +29 -0
  18. data/lib/chef_metal/chef_machine_spec.rb +64 -0
  19. data/lib/chef_metal/chef_provider_action_handler.rb +72 -0
  20. data/lib/chef_metal/chef_run_data.rb +80 -0
  21. data/lib/chef_metal/convergence_strategy.rb +26 -0
  22. data/lib/chef_metal/convergence_strategy/install_cached.rb +157 -0
  23. data/lib/chef_metal/convergence_strategy/install_msi.rb +56 -0
  24. data/lib/chef_metal/convergence_strategy/install_sh.rb +51 -0
  25. data/lib/chef_metal/convergence_strategy/no_converge.rb +38 -0
  26. data/lib/chef_metal/convergence_strategy/precreate_chef_objects.rb +180 -0
  27. data/lib/chef_metal/driver.rb +267 -0
  28. data/lib/chef_metal/machine.rb +110 -0
  29. data/lib/chef_metal/machine/basic_machine.rb +82 -0
  30. data/lib/chef_metal/machine/unix_machine.rb +276 -0
  31. data/lib/chef_metal/machine/windows_machine.rb +102 -0
  32. data/lib/chef_metal/machine_spec.rb +78 -0
  33. data/lib/chef_metal/recipe_dsl.rb +84 -0
  34. data/lib/chef_metal/transport.rb +87 -0
  35. data/lib/chef_metal/transport/ssh.rb +235 -0
  36. data/lib/chef_metal/transport/winrm.rb +109 -0
  37. data/lib/chef_metal/version.rb +3 -0
  38. metadata +223 -0
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
3
+
4
+ task :spec do
5
+ require File.expand_path('spec/run')
6
+ end
data/bin/metal ADDED
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
5
+ require 'chef_metal'
6
+ require 'chef/rest'
7
+ require 'chef/application'
8
+ require 'chef/knife'
9
+ require 'chef/run_context'
10
+ require 'chef/server_api'
11
+ require 'chef_metal/action_handler'
12
+ require 'chef_metal/version'
13
+ require 'chef_metal/chef_machine_spec'
14
+
15
+ class ChefMetal::Application < Chef::Application
16
+
17
+ # Mimic self_pipe sleep from Unicorn to capture signals safely
18
+ SELF_PIPE = []
19
+
20
+ option :config_file,
21
+ :short => "-c CONFIG",
22
+ :long => "--config CONFIG",
23
+ :description => "The configuration file to use"
24
+
25
+ option :log_level,
26
+ :short => "-l LEVEL",
27
+ :long => "--log_level LEVEL",
28
+ :description => "Set the log level (debug, info, warn, error, fatal)",
29
+ :proc => lambda { |l| l.to_sym }
30
+
31
+ option :log_location,
32
+ :short => "-L LOGLOCATION",
33
+ :long => "--logfile LOGLOCATION",
34
+ :description => "Set the log file location, defaults to STDOUT - recommended for daemonizing",
35
+ :proc => nil
36
+
37
+ option :node_name,
38
+ :short => "-N NODE_NAME",
39
+ :long => "--node-name NODE_NAME",
40
+ :description => "The node name for this client",
41
+ :proc => nil
42
+
43
+ option :chef_server_url,
44
+ :short => "-S CHEFSERVERURL",
45
+ :long => "--server CHEFSERVERURL",
46
+ :description => "The chef server URL",
47
+ :proc => nil
48
+
49
+ option :client_key,
50
+ :short => "-k KEY_FILE",
51
+ :long => "--client_key KEY_FILE",
52
+ :description => "Set the client key file location",
53
+ :proc => nil
54
+
55
+ option :local_mode,
56
+ :short => "-z",
57
+ :long => "--local-mode",
58
+ :description => "Point chef-client at local repository",
59
+ :boolean => true
60
+
61
+ option :chef_repo_path,
62
+ :long => '--chef-repo-path=PATH',
63
+ :description => "Path to Chef repository"
64
+
65
+ option :chef_zero_port,
66
+ :long => "--chef-zero-port PORT",
67
+ :description => "Port to start chef-zero on"
68
+
69
+ option :read_only,
70
+ :long => "--[no-]read-only",
71
+ :description => "Promise that execution will not modify the machine (helps with Docker in particular)"
72
+
73
+ option :stream,
74
+ :long => "--[no-]stream",
75
+ :default => true,
76
+ :boolean => true,
77
+ :description => "Whether to stream output from the machine (default: true)"
78
+
79
+ option :timeout,
80
+ :long => "--timeout=TIMEOUT",
81
+ :default => 15*60,
82
+ :description => "Time to wait for program to execute, or 0 for no timeout (default: 15 minutes)"
83
+
84
+ def reconfigure
85
+ super
86
+
87
+ Chef::Config.chef_server_url = config[:chef_server_url] if config.has_key? :chef_server_url
88
+
89
+ Chef::Config.chef_repo_path = config[:chef_repo_path] if config.has_key? :chef_repo_path
90
+
91
+ Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode)
92
+ if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path)
93
+ Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd)
94
+ end
95
+ Chef::Config.chef_zero.port = config[:chef_zero_port] if config[:chef_zero_port]
96
+ end
97
+
98
+ def setup_application
99
+ end
100
+
101
+ def load_config_file
102
+ if !config.has_key?(:config_file)
103
+ require 'chef/knife'
104
+ config[:config_file] = Chef::Knife.locate_config_file
105
+ end
106
+ super
107
+ end
108
+
109
+ def run_application
110
+ exit_code = 0
111
+
112
+ Cheffish.honor_local_mode do
113
+ command = cli_arguments.shift
114
+ case command
115
+ when 'execute'
116
+ connect_to_machines(cli_arguments.shift) do |machine|
117
+ machine.execute(action_handler, cli_arguments.join(' '), :read_only => config[:read_only], :stream => config[:stream], :timeout => config[:timeout].to_f)
118
+ puts result.stdout if result.stdout != '' && !config[:stream] && Chef::Config.log_level != :debug
119
+ STDERR.puts result.stderr if result.stderr != '' && !config[:stream] && Chef::Config.log_level != :debug
120
+ exit_code = result.exitstatus if result.exitstatus != 0
121
+ end
122
+ when 'destroy'
123
+ each_current_machine(cli_arguments.shift) do |driver, specs_and_options|
124
+ driver.destroy_machines(action_handler, specs_and_options, parallelizer)
125
+ end
126
+ when 'allocate'
127
+ each_new_machine(cli_arguments.shift) do |driver, specs_and_options|
128
+ driver.allocate_machines(action_handler, specs_and_options, parallelizer)
129
+ end
130
+ when 'ready'
131
+ each_new_machine(cli_arguments.shift) do |driver, specs_and_options|
132
+ driver.allocate_machines(action_handler, specs_and_options, parallelizer)
133
+ driver.ready_machines(action_handler, specs_and_options, parallelizer)
134
+ end
135
+ when 'setup'
136
+ each_new_machine(cli_arguments.shift) do |driver, specs_and_options|
137
+ driver.allocate_machines(action_handler, specs_and_options, parallelizer)
138
+ driver.ready_machines(action_handler, specs_and_options, parallelizer) do |machine|
139
+ machine.setup_convergence(action_handler)
140
+ end
141
+ end
142
+ when 'converge'
143
+ each_new_machine(cli_arguments.shift) do |driver, specs_and_options|
144
+ driver.allocate_machines(action_handler, specs_and_options, parallelizer)
145
+ driver.ready_machines(action_handler, specs_and_options, parallelizer) do |machine|
146
+ # TODO upload files? Maybe they should be in machine_options?
147
+ machine.setup_convergence(action_handler)
148
+ machine.converge(action_handler)
149
+ end
150
+ end
151
+ when 'reconverge'
152
+ connect_to_machines(cli_arguments.shift) do |machine|
153
+ machine.converge(action_handler)
154
+ end
155
+ when 'stop'
156
+ each_current_machine(cli_arguments.shift) do |driver, specs_and_options|
157
+ driver.stop_machines(action_handler, specs_and_options, parallelizer)
158
+ end
159
+ when 'cat'
160
+ connect_to_machines(cli_arguments.shift) do |machine|
161
+ cli_arguments.each do |remote_path|
162
+ puts machine.read_file(remote_path)
163
+ end
164
+ end
165
+ when 'cp'
166
+ machines = {}
167
+ to = cli_arguments.pop
168
+ if to =~ /^([^\/:]+):(.+)$/
169
+ to_server = $1
170
+ machines[to_server] ||= ChefMetal.connect_to_machine(to_server)
171
+ to_path = $2
172
+ to_is_directory = machines[to_server].is_directory?(to_path)
173
+ else
174
+ to_server = nil
175
+ to_path = File.absolute_path(to)
176
+ end
177
+
178
+ cli_arguments.each do |from|
179
+ if from =~ /^([^\/:]+):(.+)$/
180
+ from_server = $1
181
+ from_path = $2
182
+ if to_server
183
+ raise "Cannot copy from one server to another, or intraserver (from=#{from}, to=#{to})"
184
+ end
185
+
186
+ machines[from_server] ||= connect_to_machine(from_server)
187
+ if File.directory?(to_path)
188
+ machines[from_server].download_file(action_handler, from_path, "#{to_path}/#{File.basename(from_path)}")
189
+ else
190
+ machines[from_server].download_file(action_handler, from_path, to_path)
191
+ end
192
+ else
193
+ from_server = nil
194
+ from_path = File.absolute_path(from)
195
+ if !to_server
196
+ raise "Cannot copy two local files. One of the arguments must be MACHINE:PATH. (from=#{from}, to=#{to})"
197
+ end
198
+
199
+ if to_is_directory
200
+ machines[to_server].upload_file(action_handler, from_path, "#{to_path}/#{File.basename(from_path)}")
201
+ else
202
+ machines[to_server].upload_file(action_handler, from_path, to_path)
203
+ end
204
+ end
205
+ end
206
+ else
207
+ Chef::Log.error("Command '#{command}' unrecognized")
208
+ end
209
+ end
210
+
211
+ exit(exit_code) if exit_code != 0
212
+ end
213
+
214
+ private
215
+
216
+ def rest
217
+ @rest ||= Chef::ServerAPI.new()
218
+ end
219
+
220
+ def machine_specs(*specs)
221
+ names = specs.collect_concat { |spec| spec.split(',') }.uniq
222
+ parallelizer.parallelize(names) { |name| ChefMetal::ChefMachineSpec.get(name) }.to_a
223
+ end
224
+
225
+ def each_new_machine(spec)
226
+ driver = ChefMetal.default_driver
227
+ specs_and_options = {}
228
+ machine_specs(spec).each do |machine_spec|
229
+ specs_and_options[machine_spec] = driver.config[:machine_options]
230
+ end
231
+ [ [ driver, specs_and_options ] ]
232
+ end
233
+
234
+ def each_current_machine(spec)
235
+ grouped = machine_specs(spec).group_by { |machine_spec| machine_spec.driver_url }
236
+ parallelizer.parallelize(grouped) do |driver_url, machine_specs|
237
+ if driver_url
238
+ driver = ChefMetal.driver_for_url(driver_url)
239
+ specs_and_options = {}
240
+ machine_specs(spec).each do |machine_spec|
241
+ specs_and_options[machine_spec] = driver.config[:machine_options]
242
+ end
243
+ yield driver, specs_and_options
244
+ end
245
+ end.to_a
246
+ end
247
+
248
+ def connect_to_machines(spec)
249
+ grouped = machine_specs(spec).group_by { |machine_spec| machine_spec.driver_url }
250
+ grouped.collect_concat do |driver_url, machine_specs|
251
+ machine_specs.map { |machine_spec| ChefMetal.connect_to_machine(machine_spec) }
252
+ end
253
+ end
254
+
255
+ def parallelizer
256
+ Chef::ChefFS::Parallelizer
257
+ end
258
+
259
+ def action_handler
260
+ @action_handler ||= ActionHandler.new
261
+ end
262
+
263
+ class ActionHandler < ChefMetal::ActionHandler
264
+ def recipe_context
265
+ # TODO: somehow remove this code; should context really always be needed?
266
+ node = Chef::Node.new
267
+ node.name 'nothing'
268
+ node.automatic[:platform] = 'metal'
269
+ node.automatic[:platform_version] = ChefMetal::VERSION
270
+ Chef::RunContext.new(node, {},
271
+ Chef::EventDispatch::Dispatcher.new(Chef::Formatters::Doc.new(STDOUT,STDERR)))
272
+ end
273
+ end
274
+ end
275
+
276
+ ChefMetal::Application.new.run
@@ -0,0 +1,147 @@
1
+ require 'chef/provider/lwrp_base'
2
+ require 'chef/provider/chef_node'
3
+ require 'openssl'
4
+ require 'chef_metal/chef_provider_action_handler'
5
+ require 'chef_metal/chef_machine_spec'
6
+
7
+ class Chef::Provider::Machine < Chef::Provider::LWRPBase
8
+
9
+ def action_handler
10
+ @action_handler ||= ChefMetal::ChefProviderActionHandler.new(self)
11
+ end
12
+
13
+ use_inline_resources
14
+
15
+ def whyrun_supported?
16
+ true
17
+ end
18
+
19
+ action :allocate do
20
+ new_driver.allocate_machine(action_handler, machine_spec, machine_options)
21
+ machine_spec.save(action_handler)
22
+ end
23
+
24
+ action :ready do
25
+ action_allocate
26
+ machine = current_driver.ready_machine(action_handler, machine_spec, machine_options)
27
+ machine_spec.save(action_handler)
28
+ machine
29
+ end
30
+
31
+ action :setup do
32
+ machine = action_ready
33
+ begin
34
+ machine.setup_convergence(action_handler)
35
+ machine_spec.save(action_handler)
36
+ upload_files(machine)
37
+ ensure
38
+ machine.disconnect
39
+ end
40
+ end
41
+
42
+ action :converge do
43
+ machine = action_ready
44
+ begin
45
+ machine.setup_convergence(action_handler)
46
+ machine_spec.save(action_handler)
47
+ upload_files(machine)
48
+ # If we were asked to converge, or anything changed, or if a converge has never succeeded, converge.
49
+ if new_resource.converge || (new_resource.converge.nil? && resource_updated?) ||
50
+ !machine_spec.node['automatic'] || machine_spec.node['automatic'].size == 0
51
+ machine.converge(action_handler)
52
+ end
53
+ ensure
54
+ machine.disconnect
55
+ end
56
+ end
57
+
58
+ action :converge_only do
59
+ machine = run_context.chef_metal.connect_to_machine(machine_spec, machine_options)
60
+ begin
61
+ machine.converge(action_handler)
62
+ ensure
63
+ machine.disconnect
64
+ end
65
+ end
66
+
67
+ action :stop do
68
+ if current_driver
69
+ current_driver.stop_machine(action_handler, machine_spec, machine_options)
70
+ end
71
+ end
72
+
73
+ action :destroy do
74
+ if current_driver
75
+ current_driver.destroy_machine(action_handler, machine_spec, machine_options)
76
+ end
77
+ end
78
+
79
+ def new_driver
80
+ run_context.chef_metal.driver_for(new_resource.driver)
81
+ end
82
+
83
+ def new_driver_config
84
+ run_context.chef_metal.driver_config_for(new_resource.driver)
85
+ end
86
+
87
+ def current_driver
88
+ run_context.chef_metal.current_driver || (
89
+ if machine_spec.driver_url
90
+ run_context.chef_metal.driver_for_url(machine_spec.driver_url)
91
+ end
92
+ )
93
+ end
94
+
95
+ attr_reader :machine_spec
96
+
97
+ def machine_options
98
+ configs = []
99
+ configs << {
100
+ :convergence_options =>
101
+ [ :chef_server,
102
+ :allow_overwrite_keys,
103
+ :source_key, :source_key_path, :source_key_pass_phrase,
104
+ :private_key_options,
105
+ :ohai_hints,
106
+ :public_key_path, :public_key_format,
107
+ :admin, :validator
108
+ ].inject({}) do |result, key|
109
+ result[key] = new_resource.send(key)
110
+ result
111
+ end
112
+ }
113
+ configs << new_resource.machine_options if new_resource.machine_options
114
+ configs << new_driver_config[:machine_options] if new_driver_config[:machine_options]
115
+ Cheffish::MergedConfig.new(*configs)
116
+ end
117
+
118
+ def load_current_resource
119
+ node_driver = Chef::Provider::ChefNode.new(new_resource, run_context)
120
+ node_driver.load_current_resource
121
+ json = node_driver.new_json
122
+ json['normal']['metal'] = node_driver.current_json['normal']['metal']
123
+ @machine_spec = ChefMetal::ChefMachineSpec.new(json, new_resource.chef_server)
124
+ end
125
+
126
+ def self.upload_files(action_handler, machine, files)
127
+ if files
128
+ files.each_pair do |remote_file, local|
129
+ if local.is_a?(Hash)
130
+ if local[:local_path]
131
+ machine.upload_file(action_handler, local[:local_path], remote_file)
132
+ else
133
+ machine.write_file(action_handler, remote_file, local[:content])
134
+ end
135
+ else
136
+ machine.upload_file(action_handler, local, remote_file)
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ private
143
+
144
+ def upload_files(machine)
145
+ Machine.upload_files(action_handler, machine, new_resource.files)
146
+ end
147
+ end
@@ -0,0 +1,130 @@
1
+ require 'chef/chef_fs/parallelizer'
2
+ require 'chef/provider/lwrp_base'
3
+ require 'chef/provider/machine'
4
+ require 'chef_metal/chef_provider_action_handler'
5
+ require 'chef_metal/add_prefix_action_handler'
6
+
7
+ class Chef::Provider::MachineBatch < Chef::Provider::LWRPBase
8
+
9
+ def action_handler
10
+ @action_handler ||= ChefMetal::ChefProviderActionHandler.new(self)
11
+ end
12
+
13
+ use_inline_resources
14
+
15
+ def whyrun_supported?
16
+ true
17
+ end
18
+
19
+ def parallelizer
20
+ @parallelizer ||= Chef::ChefFS::Parallelizer.new(new_resource.max_simultaneous || 100)
21
+ end
22
+
23
+ action :allocate do
24
+ by_new_driver.each do |driver, specs_and_options|
25
+ driver.allocate_machines(action_handler, specs_and_options, parallelizer) do |machine_spec|
26
+ machine_spec.save(action_handler)
27
+ end
28
+ end
29
+ end
30
+
31
+ action :ready do
32
+ with_ready_machines
33
+ end
34
+
35
+ action :setup do
36
+ with_ready_machines do |m|
37
+ prefixed_handler = ChefMetal::AddPrefixActionHandler.new(action_handler, "[#{m[:resource].name}] ")
38
+ machine[:machine].setup_convergence(prefixed_handler)
39
+ m[:spec].save(action_handler)
40
+ Chef::Provider::Machine.upload_files(prefixed_handler, m[:machine], m[:resource].files)
41
+ end
42
+ end
43
+
44
+ action :converge do
45
+ with_ready_machines do |m|
46
+ prefixed_handler = ChefMetal::AddPrefixActionHandler.new(action_handler, "[#{m[:resource].name}] ")
47
+ m[:machine].setup_convergence(prefixed_handler)
48
+ m[:spec].save(action_handler)
49
+ Chef::Provider::Machine.upload_files(prefixed_handler, m[:machine], m[:resource].files)
50
+ m[:machine].converge(prefixed_handler)
51
+ m[:spec].save(action_handler)
52
+ end
53
+ end
54
+
55
+ action :stop do
56
+ parallel_do(by_current_driver) do |driver, specs_and_options|
57
+ driver.stop_machines(action_handler, specs_and_options, parallelizer)
58
+ end
59
+ end
60
+
61
+ action :destroy do
62
+ parallel_do(by_current_driver) do |driver, specs_and_options|
63
+ driver.destroy_machines(action_handler, specs_and_options, parallelizer)
64
+ end
65
+ end
66
+
67
+ def with_ready_machines
68
+ action_allocate
69
+ by_id = @machines.inject({}) { |hash,m| hash[m[:spec].id] = m; hash }
70
+ parallel_do(by_new_driver) do |driver, specs_and_options|
71
+ driver.ready_machines(action_handler, specs_and_options, parallelizer) do |machine|
72
+ machine.machine_spec.save(action_handler)
73
+
74
+ m = by_id[machine.machine_spec.id]
75
+
76
+ m[:machine] = machine
77
+ begin
78
+ yield m if block_given?
79
+ ensure
80
+ machine.disconnect
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ # TODO in many of these cases, the order of the results only matters because you
87
+ # want to match it up with the input. Make a parallelize method that doesn't
88
+ # care about order and spits back results as quickly as possible.
89
+ def parallel_do(enum, options = {}, &block)
90
+ parallelizer.parallelize(enum, options, &block).to_a
91
+ end
92
+
93
+ def by_new_driver
94
+ result = {}
95
+ @machines.each do |m|
96
+ if m[:resource].driver
97
+ driver = run_context.chef_metal.driver_for(m[:resource].driver)
98
+ result[driver] ||= {}
99
+ result[driver][m[:spec]] = m[:options]
100
+ end
101
+ end
102
+ result
103
+ end
104
+
105
+ def by_current_driver
106
+ result = {}
107
+ @machines.each do |m|
108
+ if m[:spec].driver_url
109
+ driver = run_context.chef_metal.driver_for_url(m[:spec].driver_url)
110
+ result[driver] ||= {}
111
+ result[driver][m[:spec]] = m[:options]
112
+ end
113
+ end
114
+ result
115
+ end
116
+
117
+ def load_current_resource
118
+ # Load nodes in parallel
119
+ @machines = parallel_do(new_resource.machines) do |machine_resource|
120
+ provider = Chef::Provider::Machine.new(machine_resource, machine_resource.run_context)
121
+ provider.load_current_resource
122
+ {
123
+ :resource => machine_resource,
124
+ :spec => provider.machine_spec,
125
+ :options => provider.machine_options
126
+ }
127
+ end.to_a
128
+ end
129
+
130
+ end