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

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.
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