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
@@ -0,0 +1,72 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Douglas Triggs (<doug@getchef.com>)
4
+ #
5
+ # Copyright (C) 2014, Chef, Inc.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'chef_metal/action_handler'
20
+
21
+ # This is included in the metal drivers to proxy from generic requests needed
22
+ # to specific driver actions
23
+ module ChefMetal
24
+ class ChefProviderActionHandler < ActionHandler
25
+ def initialize(provider)
26
+ @provider = provider
27
+ end
28
+
29
+ attr_reader :provider
30
+
31
+ def updated!
32
+ provider.new_resource.updated_by_last_action(true)
33
+ end
34
+
35
+ def should_perform_actions
36
+ !provider.run_context.config.why_run
37
+ end
38
+
39
+ def report_progress(description)
40
+ # TODO this seems wrong but Chef doesn't have another thing
41
+ provider.converge_by description do
42
+ # We already did the action, but we trust whoever told us that they did it.
43
+ end
44
+ end
45
+
46
+ def performed_action(description)
47
+ provider.converge_by description do
48
+ # We already did the action, but we trust whoever told us that they did it.
49
+ end
50
+ end
51
+
52
+ def perform_action(description, &block)
53
+ provider.converge_by(description, &block)
54
+ end
55
+
56
+ def open_stream(name, &block)
57
+ if provider.run_context.respond_to?(:open_stream)
58
+ provider.run_context.open_stream({ :name => name }, &block)
59
+ else
60
+ if block_given?
61
+ yield STDOUT
62
+ else
63
+ STDOUT
64
+ end
65
+ end
66
+ end
67
+
68
+ def host_node
69
+ "#{provider.run_context.config[:chef_server_url]}/nodes/#{provider.run_context.node['name']}"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,80 @@
1
+ require 'chef/mixin/deep_merge'
2
+ require 'cheffish/with_pattern'
3
+ require 'cheffish/merged_config'
4
+ require 'chef_metal/chef_machine_spec'
5
+
6
+ module ChefMetal
7
+ class ChefRunData
8
+ extend Cheffish::WithPattern
9
+ def initialize(config)
10
+ @config = config
11
+ @drivers = {}
12
+ end
13
+
14
+ attr_reader :config
15
+ attr_reader :drivers
16
+
17
+ with :driver
18
+ with :machine_options
19
+ with :machine_batch
20
+
21
+ def current_driver
22
+ @current_driver || config[:driver]
23
+ end
24
+
25
+ def current_machine_options
26
+ if @current_machine_options
27
+ @current_machine_options
28
+ elsif config[:drivers] && driver_for(current_driver) && config[:drivers][driver_for(current_driver).driver_url]
29
+ Cheffish::MergedConfig.new(config[:drivers][driver_for(current_driver).driver_url], config)[:machine_options] || {}
30
+ else
31
+ config[:machine_options] || {}
32
+ end
33
+ end
34
+
35
+ def add_machine_options(options, &block)
36
+ with_machine_options(Chef::Mixin::DeepMerge.hash_only_merge(current_machine_options, options), &block)
37
+ end
38
+
39
+ def driver_for(driver)
40
+ driver.is_a?(String) ? driver_for_url(driver) : driver
41
+ end
42
+
43
+ def driver_config_for(driver)
44
+ ChefMetal.config_for_url(driver_for(driver).driver_url, config)
45
+ end
46
+
47
+ def driver_for_url(driver_url)
48
+ drivers[driver_url] ||= begin
49
+ driver = ChefMetal.driver_for_url(driver_url, config)
50
+ # Check the canonicalized driver_url from the driver
51
+ if driver.driver_url != driver_url
52
+ drivers[driver.driver_url] ||= driver
53
+ else
54
+ driver
55
+ end
56
+ end
57
+ end
58
+
59
+ def connect_to_machine(name, chef_server = nil)
60
+ if name.is_a?(MachineSpec)
61
+ machine_spec = name
62
+ else
63
+ machine_spec = ChefMetal::ChefMachineSpec.get(name, chef_server)
64
+ end
65
+ ChefMetal.connect_to_machine(machine_spec, config)
66
+ end
67
+
68
+ def keys
69
+ result = (config.keys || {}).dup
70
+ Array(config.key_path) do |key_path|
71
+ Dir.entries(key_path).each do |key|
72
+ if File.extname(key) == '.pem'
73
+ result[File.basename(key)[0..-5]] ||= key
74
+ end
75
+ end
76
+ end
77
+ result
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,26 @@
1
+ module ChefMetal
2
+ class ConvergenceStrategy
3
+ # convergence_options - a freeform hash of options to the converger.
4
+ # config - a Chef::Config-like object with global config like :log_level
5
+ def initialize(convergence_options, config)
6
+ @convergence_options = convergence_options || {}
7
+ @config = config
8
+ end
9
+
10
+ attr_reader :convergence_options
11
+ attr_reader :config
12
+
13
+ # Get the machine ready to converge, but do not converge.
14
+ def setup_convergence(action_handler, machine)
15
+ raise "setup_convergence not overridden on #{self.class}"
16
+ end
17
+
18
+ def converge(action_handler, machine)
19
+ raise "converge not overridden on #{self.class}"
20
+ end
21
+
22
+ def cleanup_convergence(action_handler, machine_spec)
23
+ raise "cleanup_convergence not overridden on #{self.class}"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,157 @@
1
+ require 'chef_metal/convergence_strategy/precreate_chef_objects'
2
+ require 'pathname'
3
+ require 'fileutils'
4
+ require 'digest/md5'
5
+ require 'thread'
6
+
7
+ module ChefMetal
8
+ class ConvergenceStrategy
9
+ class InstallCached < PrecreateChefObjects
10
+ # convergence_options is a hash of setup convergence_options, including:
11
+ # - :chef_server
12
+ # - :allow_overwrite_keys
13
+ # - :source_key, :source_key_path, :source_key_pass_phrase
14
+ # - :private_key_options
15
+ # - :ohai_hints
16
+ # - :public_key_path, :public_key_format
17
+ # - :admin, :validator
18
+ # - :chef_client_timeout
19
+ # - :client_rb_path, :client_pem_path
20
+ # - :chef_version, :prerelease, :package_cache_path
21
+ def initialize(convergence_options, config)
22
+ convergence_options = Cheffish::MergedConfig.new(convergence_options, {
23
+ :client_rb_path => '/etc/chef/client.rb',
24
+ :client_pem_path => '/etc/chef/client.pem'
25
+ })
26
+ super(convergence_options, config)
27
+ @chef_version ||= convergence_options[:chef_version]
28
+ @prerelease ||= convergence_options[:prerelease]
29
+ @package_cache_path ||= convergence_options[:package_cache_path] || "#{ENV['HOME']}/.chef/package_cache"
30
+ @package_cache = {}
31
+ @tmp_dir = '/tmp'
32
+ @chef_client_timeout = convergence_options.has_key?(:chef_client_timeout) ? convergence_options[:chef_client_timeout] : 120*60 # Default: 2 hours
33
+ FileUtils.mkdir_p(@package_cache_path)
34
+ @package_cache_lock = Mutex.new
35
+ end
36
+
37
+ attr_reader :client_rb_path
38
+ attr_reader :client_pem_path
39
+
40
+ def setup_convergence(action_handler, machine)
41
+ super
42
+
43
+ # Install chef-client. TODO check and update version if not latest / not desired
44
+ if machine.execute_always('chef-client -v').exitstatus != 0
45
+ platform, platform_version, machine_architecture = machine.detect_os(action_handler)
46
+ package_file = download_package_for_platform(action_handler, machine, platform, platform_version, machine_architecture)
47
+ remote_package_file = "#{@tmp_dir}/#{File.basename(package_file)}"
48
+ machine.upload_file(action_handler, package_file, remote_package_file)
49
+ install_package(action_handler, machine, remote_package_file)
50
+ end
51
+ end
52
+
53
+ def converge(action_handler, machine)
54
+ super
55
+
56
+ action_handler.open_stream(machine.node['name']) do |stdout|
57
+ action_handler.open_stream(machine.node['name']) do |stderr|
58
+ command_line = "chef-client"
59
+ command_line << " -l #{config[:log_level].to_s}" if config[:log_level]
60
+ machine.execute(action_handler, command_line,
61
+ :stream_stdout => stdout,
62
+ :stream_stderr => stderr,
63
+ :timeout => @chef_client_timeout)
64
+ end
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def download_package_for_platform(action_handler, machine, platform, platform_version, machine_architecture)
71
+ @package_cache_lock.synchronize do
72
+ @package_cache ||= {}
73
+ @package_cache[platform] ||= {}
74
+ @package_cache[platform][platform_version] ||= {}
75
+ @package_cache[platform][platform_version][machine_architecture] ||= { :lock => Mutex.new }
76
+ end
77
+ @package_cache[platform][platform_version][machine_architecture][:lock].synchronize do
78
+ if !@package_cache[platform][platform_version][machine_architecture][:file]
79
+ #
80
+ # Grab metadata
81
+ #
82
+ metadata = download_metadata_for_platform(machine, platform, platform_version, machine_architecture)
83
+
84
+ # Download actual package desired by metadata
85
+ package_file = "#{@package_cache_path}/#{URI(metadata['url']).path.split('/')[-1]}"
86
+
87
+ ChefMetal.inline_resource(action_handler) do
88
+ remote_file package_file do
89
+ source metadata['url']
90
+ checksum metadata['sha256']
91
+ end
92
+ end
93
+
94
+ @package_cache[platform][platform_version][machine_architecture][:file] = package_file
95
+ end
96
+ end
97
+ @package_cache[platform][platform_version][machine_architecture][:file]
98
+ end
99
+
100
+ def download_metadata_for_platform(machine, platform, platform_version, machine_architecture)
101
+ #
102
+ # Figure out the URL to the metadata
103
+ #
104
+ metadata_url="https://www.opscode.com/chef/metadata"
105
+ metadata_url << "?v=#{@chef_version}"
106
+ metadata_url << "&prerelease=#{@prerelease ? 'true' : 'false'}"
107
+ metadata_url << "&p=#{platform.strip}"
108
+ metadata_url << "&pv=#{platform_version.strip}"
109
+ metadata_url << "&m=#{machine_architecture.strip}"
110
+ use_ssl = true
111
+
112
+ # solaris 9 lacks openssl, solaris 10 lacks recent enough credentials - your base O/S is completely insecure, please upgrade
113
+ if platform == 'solaris2' && (platform_version == '5.9' || platform_version == '5.10')
114
+ metadata_url.sub(/^https/, 'http')
115
+ use_ssl = false
116
+ end
117
+
118
+ # Download and parse the metadata
119
+ Chef::Log.debug("Getting metadata for machine #{machine.node['name']}: #{metadata_url}")
120
+ uri = URI(metadata_url)
121
+ http = Net::HTTP.new(uri.host, uri.port)
122
+ http.use_ssl = use_ssl
123
+ request = Net::HTTP::Get.new(uri.request_uri)
124
+ response = http.request(request)
125
+ metadata_str = response.body
126
+ metadata = {}
127
+ metadata_str.each_line do |line|
128
+ key, value = line.split("\t", 2)
129
+ metadata[key] = value
130
+ end
131
+ metadata
132
+ end
133
+
134
+ def install_package(action_handler, machine, remote_package_file)
135
+ extension = File.extname(remote_package_file)
136
+ result = case extension
137
+ when '.rpm'
138
+ machine.execute(action_handler, "rpm -Uvh --oldpackage --replacepkgs \"#{remote_package_file}\"")
139
+ when '.deb'
140
+ machine.execute(action_handler, "dpkg -i \"#{remote_package_file}\"")
141
+ when '.solaris'
142
+ machine.write_file(action_handler, "#{@tmp_dir}/nocheck", <<EOM)
143
+ conflict=nocheck
144
+ action=nocheck
145
+ mail=
146
+ EOM
147
+ machine.execute(action_handler, "pkgrm -a \"#{@tmp_dir}/nocheck\" -n chef")
148
+ machine.execute(action_handler, "pkgadd -n -d \"#{remote_package_file}\" -a \"#{@tmp_dir}/nocheck\" chef")
149
+ when '.sh'
150
+ machine.execute(action_handler, "sh \"#{remote_package_file}\"")
151
+ else
152
+ raise "Unknown package extension '#{extension}' for file #{remote_package_file}"
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,56 @@
1
+ require 'chef_metal/convergence_strategy/precreate_chef_objects'
2
+ require 'pathname'
3
+
4
+ module ChefMetal
5
+ class ConvergenceStrategy
6
+ class InstallMsi < PrecreateChefObjects
7
+ @@install_msi_cache = {}
8
+
9
+ def initialize(convergence_options, config)
10
+ super
11
+ @install_msi_url = convergence_options[:install_msi_url] || 'http://www.opscode.com/chef/install.msi'
12
+ @install_msi_path = convergence_options[:install_msi_path] || "%TEMP%\\#{File.basename(@install_msi_url)}"
13
+ @chef_client_timeout = convergence_options.has_key?(:chef_client_timeout) ? convergence_options[:chef_client_timeout] : 120*60 # Default: 2 hours
14
+ end
15
+
16
+ attr_reader :install_msi_url
17
+ attr_reader :install_msi_path
18
+
19
+ def setup_convergence(action_handler, machine)
20
+ if !convergence_options.has_key?(:client_rb_path) || !convergence_options.has_key?(:client_pem_path)
21
+ system_drive = machine.execute_always('$env:SystemDrive').stdout.strip
22
+ @convergence_options = Cheffish::MergedConfig.new(convergence_options, {
23
+ :client_rb_path => "#{system_drive}\\chef\\client.rb",
24
+ :client_pem_path => "#{system_drive}\\chef\\client.pem"
25
+ })
26
+ end
27
+
28
+ super
29
+
30
+ # Install chef-client. TODO check and update version if not latest / not desired
31
+ if machine.execute_always('chef-client -v').exitstatus != 0
32
+ # TODO ssh verification of install.msi before running arbtrary code would be nice?
33
+ # TODO find a way to cache this on the host like with the Unix stuff.
34
+ # Limiter is we don't know how to efficiently upload large files to
35
+ # the remote machine with WMI.
36
+ machine.execute(action_handler, "(New-Object System.Net.WebClient).DownloadFile(#{machine.escape(install_msi_url)}, #{machine.escape(install_msi_path)})")
37
+ machine.execute(action_handler, "msiexec /qn /i #{machine.escape(install_msi_path)}")
38
+ end
39
+ end
40
+
41
+ def converge(action_handler, machine)
42
+ super
43
+
44
+ # TODO For some reason I get a 500 back if I don't do -l debug
45
+ action_handler.open_stream(machine.node['name']) do |stdout|
46
+ action_handler.open_stream(machine.node['name']) do |stderr|
47
+ machine.execute(action_handler, "chef-client -l debug",
48
+ :stream_stdout => stdout,
49
+ :stream_stderr => stderr,
50
+ :timeout => @chef_client_timeout)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,51 @@
1
+ require 'chef_metal/convergence_strategy/precreate_chef_objects'
2
+ require 'pathname'
3
+
4
+ module ChefMetal
5
+ class ConvergenceStrategy
6
+ class InstallSh < PrecreateChefObjects
7
+ @@install_sh_cache = {}
8
+
9
+ def initialize(convergence_options, config)
10
+ convergence_options = Cheffish::MergedConfig.new(convergence_options, {
11
+ :client_rb_path => '/etc/chef/client.rb',
12
+ :client_pem_path => '/etc/chef/client.pem'
13
+ })
14
+ super(convergence_options, config)
15
+ @install_sh_url = convergence_options[:install_sh_url] || 'http://www.opscode.com/chef/install.sh'
16
+ @install_sh_path = convergence_options[:install_sh_path] || '/tmp/chef-install.sh'
17
+ @chef_client_timeout = convergence_options.has_key?(:chef_client_timeout) ? convergence_options[:chef_client_timeout] : 120*60 # Default: 2 hours
18
+ end
19
+
20
+ attr_reader :install_sh_url
21
+ attr_reader :install_sh_path
22
+
23
+ def setup_convergence(action_handler, machine)
24
+ super
25
+
26
+ # Install chef-client. TODO check and update version if not latest / not desired
27
+ if machine.execute_always('chef-client -v').exitstatus != 0
28
+ # TODO ssh verification of install.sh before running arbtrary code would be nice?
29
+ @@install_sh_cache[install_sh_url] ||= Net::HTTP.get(URI(install_sh_url))
30
+ machine.write_file(action_handler, install_sh_path, @@install_sh_cache[install_sh_url], :ensure_dir => true)
31
+ machine.execute(action_handler, "bash #{install_sh_path}")
32
+ end
33
+ end
34
+
35
+ def converge(action_handler, machine)
36
+ super
37
+
38
+ action_handler.open_stream(machine.node['name']) do |stdout|
39
+ action_handler.open_stream(machine.node['name']) do |stderr|
40
+ command_line = "chef-client"
41
+ command_line << " -l #{config[:log_level].to_s}" if config[:log_level]
42
+ machine.execute(action_handler, command_line,
43
+ :stream_stdout => stdout,
44
+ :stream_stderr => stderr,
45
+ :timeout => @chef_client_timeout)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end