chef-provisioning 0.15

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 (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +207 -0
  3. data/LICENSE +201 -0
  4. data/README.md +260 -0
  5. data/Rakefile +6 -0
  6. data/lib/chef/provider/load_balancer.rb +77 -0
  7. data/lib/chef/provider/machine.rb +176 -0
  8. data/lib/chef/provider/machine_batch.rb +191 -0
  9. data/lib/chef/provider/machine_execute.rb +35 -0
  10. data/lib/chef/provider/machine_file.rb +54 -0
  11. data/lib/chef/provider/machine_image.rb +60 -0
  12. data/lib/chef/provisioning.rb +95 -0
  13. data/lib/chef/provisioning/action_handler.rb +68 -0
  14. data/lib/chef/provisioning/add_prefix_action_handler.rb +31 -0
  15. data/lib/chef/provisioning/chef_image_spec.rb +108 -0
  16. data/lib/chef/provisioning/chef_load_balancer_spec.rb +108 -0
  17. data/lib/chef/provisioning/chef_machine_spec.rb +84 -0
  18. data/lib/chef/provisioning/chef_provider_action_handler.rb +74 -0
  19. data/lib/chef/provisioning/chef_run_data.rb +139 -0
  20. data/lib/chef/provisioning/convergence_strategy.rb +28 -0
  21. data/lib/chef/provisioning/convergence_strategy/install_cached.rb +156 -0
  22. data/lib/chef/provisioning/convergence_strategy/install_msi.rb +58 -0
  23. data/lib/chef/provisioning/convergence_strategy/install_sh.rb +55 -0
  24. data/lib/chef/provisioning/convergence_strategy/no_converge.rb +39 -0
  25. data/lib/chef/provisioning/convergence_strategy/precreate_chef_objects.rb +183 -0
  26. data/lib/chef/provisioning/driver.rb +304 -0
  27. data/lib/chef/provisioning/image_spec.rb +72 -0
  28. data/lib/chef/provisioning/load_balancer_spec.rb +86 -0
  29. data/lib/chef/provisioning/machine.rb +112 -0
  30. data/lib/chef/provisioning/machine/basic_machine.rb +84 -0
  31. data/lib/chef/provisioning/machine/unix_machine.rb +278 -0
  32. data/lib/chef/provisioning/machine/windows_machine.rb +104 -0
  33. data/lib/chef/provisioning/machine_spec.rb +82 -0
  34. data/lib/chef/provisioning/recipe_dsl.rb +103 -0
  35. data/lib/chef/provisioning/transport.rb +95 -0
  36. data/lib/chef/provisioning/transport/ssh.rb +343 -0
  37. data/lib/chef/provisioning/transport/winrm.rb +151 -0
  38. data/lib/chef/provisioning/version.rb +5 -0
  39. data/lib/chef/resource/chef_data_bag_resource.rb +148 -0
  40. data/lib/chef/resource/load_balancer.rb +57 -0
  41. data/lib/chef/resource/machine.rb +124 -0
  42. data/lib/chef/resource/machine_batch.rb +78 -0
  43. data/lib/chef/resource/machine_execute.rb +28 -0
  44. data/lib/chef/resource/machine_file.rb +34 -0
  45. data/lib/chef/resource/machine_image.rb +35 -0
  46. data/lib/chef_metal.rb +1 -0
  47. metadata +217 -0
@@ -0,0 +1,108 @@
1
+ require 'chef/provisioning'
2
+ require 'cheffish'
3
+ require 'chef/provisioning/load_balancer_spec'
4
+
5
+ class Chef
6
+ module Provisioning
7
+ #
8
+ # Specification for a image. Sufficient information to find and contact it
9
+ # after it has been set up.
10
+ #
11
+ class ChefLoadBalancerSpec < LoadBalancerSpec
12
+ def initialize(node, chef_server)
13
+ super(node)
14
+ @chef_server = chef_server
15
+ end
16
+
17
+ #
18
+ # Get a ImageSpec from the chef server. If the node does not exist on the
19
+ # server, it returns nil.
20
+ #
21
+ def self.get(name, chef_server = Cheffish.default_chef_server)
22
+ chef_api = Cheffish.chef_server_api(chef_server)
23
+ begin
24
+ data = chef_api.get("/data/loadbalancers/#{name}")
25
+ data['load_balancer_options'] = strings_to_symbols(data['load_balancer_options'])
26
+ ChefLoadBalancerSpec.new(data, chef_server)
27
+ rescue Net::HTTPServerException => e
28
+ if e.response.code == '404'
29
+ nil
30
+ else
31
+ raise
32
+ end
33
+ end
34
+ end
35
+
36
+ # Creates a new empty ImageSpec with the given name.
37
+ def self.empty(id, chef_server = Cheffish.default_chef_server)
38
+ ChefLoadBalancerSpec.new({ 'id' => id }, chef_server)
39
+ end
40
+
41
+ #
42
+ # Globally unique identifier for this image. Does not depend on the image's
43
+ # location or existence.
44
+ #
45
+ def id
46
+ ChefLoadBalancerSpec.id_from(chef_server, name)
47
+ end
48
+
49
+ def self.id_from(chef_server, name)
50
+ "#{chef_server[:chef_server_url]}/data/loadbalancers/#{name}"
51
+ end
52
+
53
+ #
54
+ # Save this node to the server. If you have significant information that
55
+ # could be lost, you should do this as quickly as possible. Data will be
56
+ # saved automatically for you after allocate_image and ready_image.
57
+ #
58
+ def save(action_handler)
59
+ # Save the node to the server.
60
+ _self = self
61
+ _chef_server = _self.chef_server
62
+ Chef::Provisioning.inline_resource(action_handler) do
63
+ chef_data_bag_item _self.name do
64
+ data_bag 'loadbalancers'
65
+ chef_server _chef_server
66
+ raw_data _self.load_balancer_data
67
+ end
68
+ end
69
+ end
70
+
71
+ def delete(action_handler)
72
+ # Save the node to the server.
73
+ _self = self
74
+ _chef_server = _self.chef_server
75
+ Chef::Provisioning.inline_resource(action_handler) do
76
+ chef_data_bag_item _self.name do
77
+ data_bag 'loadbalancers'
78
+ chef_server _chef_server
79
+ action :destroy
80
+ end
81
+ end
82
+ end
83
+
84
+ protected
85
+
86
+ attr_reader :chef_server
87
+
88
+ #
89
+ # Chef API object for the given Chef server
90
+ #
91
+ def chef_api
92
+ Cheffish.server_api_for(chef_server)
93
+ end
94
+
95
+ def self.strings_to_symbols(data)
96
+ if data.is_a?(Hash)
97
+ result = {}
98
+ data.each_pair do |key, value|
99
+ result[key.to_sym] = strings_to_symbols(value)
100
+ end
101
+ result
102
+ else
103
+ data
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,84 @@
1
+ require 'pry'
2
+ require 'chef/provisioning'
3
+ require 'cheffish'
4
+ require 'chef/provisioning/machine_spec'
5
+
6
+ class Chef
7
+ module Provisioning
8
+ #
9
+ # Specification for a machine. Sufficient information to find and contact it
10
+ # after it has been set up.
11
+ #
12
+ class ChefMachineSpec < MachineSpec
13
+ def initialize(node, chef_server)
14
+ super(node)
15
+ @chef_server = chef_server
16
+ end
17
+
18
+ #
19
+ # Get a MachineSpec from the chef server. If the node does not exist on the
20
+ # server, it returns nil.
21
+ #
22
+ def self.get(name, chef_server = Cheffish.default_chef_server)
23
+ chef_api = Cheffish.chef_server_api(chef_server)
24
+ begin
25
+ ChefMachineSpec.new(chef_api.get("/nodes/#{name}"), chef_server)
26
+ rescue Net::HTTPServerException => e
27
+ if e.response.code == '404'
28
+ nil
29
+ else
30
+ raise
31
+ end
32
+ end
33
+ end
34
+
35
+ # Creates a new empty MachineSpec with the given name.
36
+ def self.empty(name, chef_server = Cheffish.default_chef_server)
37
+ ChefMachineSpec.new({ 'name' => name, 'normal' => {} }, chef_server)
38
+ end
39
+
40
+ #
41
+ # Globally unique identifier for this machine. Does not depend on the machine's
42
+ # location or existence.
43
+ #
44
+ def id
45
+ ChefMachineSpec.id_from(chef_server, name)
46
+ end
47
+
48
+ def self.id_from(chef_server, name)
49
+ "#{chef_server[:chef_server_url]}/nodes/#{name}"
50
+ end
51
+
52
+ #
53
+ # Save this node to the server. If you have significant information that
54
+ # could be lost, you should do this as quickly as possible. Data will be
55
+ # saved automatically for you after allocate_machine and ready_machine.
56
+ #
57
+ def save(action_handler)
58
+ if location && (!location.is_a?(Hash) || !location['driver_url'])
59
+ raise "Drivers must specify a canonical driver_url in machine_spec.location. Contact your driver's author."
60
+ end
61
+ # Save the node to the server.
62
+ _self = self
63
+ _chef_server = _self.chef_server
64
+ Chef::Provisioning.inline_resource(action_handler) do
65
+ chef_node _self.name do
66
+ chef_server _chef_server
67
+ raw_json _self.node
68
+ end
69
+ end
70
+ end
71
+
72
+ protected
73
+
74
+ attr_reader :chef_server
75
+
76
+ #
77
+ # Chef API object for the given Chef server
78
+ #
79
+ def chef_api
80
+ Cheffish.server_api_for(chef_server)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,74 @@
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/provisioning/action_handler'
20
+
21
+ # This is included in provisioning drivers to proxy from generic requests needed
22
+ # to specific driver actions
23
+ class Chef
24
+ module Provisioning
25
+ class ChefProviderActionHandler < ActionHandler
26
+ def initialize(provider)
27
+ @provider = provider
28
+ end
29
+
30
+ attr_reader :provider
31
+
32
+ def updated!
33
+ provider.new_resource.updated_by_last_action(true)
34
+ end
35
+
36
+ def should_perform_actions
37
+ !provider.run_context.config.why_run
38
+ end
39
+
40
+ def report_progress(description)
41
+ # TODO this seems wrong but Chef doesn't have another thing
42
+ provider.converge_by description do
43
+ # We already did the action, but we trust whoever told us that they did it.
44
+ end
45
+ end
46
+
47
+ def performed_action(description)
48
+ provider.converge_by description do
49
+ # We already did the action, but we trust whoever told us that they did it.
50
+ end
51
+ end
52
+
53
+ def perform_action(description, &block)
54
+ provider.converge_by(description, &block)
55
+ end
56
+
57
+ def open_stream(name, &block)
58
+ if provider.run_context.respond_to?(:open_stream)
59
+ provider.run_context.open_stream({ :name => name }, &block)
60
+ else
61
+ if block_given?
62
+ yield STDOUT
63
+ else
64
+ STDOUT
65
+ end
66
+ end
67
+ end
68
+
69
+ def host_node
70
+ "#{provider.run_context.config[:chef_server_url]}/nodes/#{provider.run_context.node['name']}"
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,139 @@
1
+ require 'chef/mixin/deep_merge'
2
+ require 'cheffish/merged_config'
3
+ require 'chef/provisioning/chef_machine_spec'
4
+
5
+ class Chef
6
+ module Provisioning
7
+ class ChefRunData
8
+
9
+ def initialize(config)
10
+ @config = config
11
+ @drivers = {}
12
+ end
13
+
14
+ attr_reader :config
15
+ attr_reader :drivers
16
+ attr_reader :current_driver
17
+ attr_accessor :current_machine_options
18
+ attr_accessor :current_load_balancer_options
19
+ attr_accessor :current_image_options
20
+ attr_accessor :current_data_center
21
+
22
+ def with_machine_options(value)
23
+ old_value = self.current_machine_options
24
+ self.current_machine_options = value
25
+ if block_given?
26
+ begin
27
+ yield
28
+ ensure
29
+ self.current_machine_options = old_value
30
+ end
31
+ end
32
+ end
33
+
34
+ def with_image_options(value)
35
+ old_value = self.current_image_options
36
+ self.current_image_options = value
37
+ if block_given?
38
+ begin
39
+ yield
40
+ ensure
41
+ self.current_image_options = old_value
42
+ end
43
+ end
44
+ end
45
+
46
+ def with_data_center(value)
47
+ old_value = self.current_data_center
48
+ self.current_data_center = value
49
+ if block_given?
50
+ begin
51
+ yield
52
+ ensure
53
+ self.current_data_center = old_value
54
+ end
55
+ end
56
+ end
57
+
58
+ def with_driver(driver, options = nil, &block)
59
+ if drivers[driver] && options
60
+ raise "Driver #{driver} has already been created, options #{options} would be ignored!"
61
+ end
62
+ @current_driver = driver
63
+ @current_driver_options = options
64
+ end
65
+
66
+ def current_driver
67
+ @current_driver || config[:driver]
68
+ end
69
+
70
+ def current_machine_options
71
+ if @current_machine_options
72
+ @current_machine_options
73
+ else
74
+ {}
75
+ end
76
+ end
77
+
78
+ def current_image_options
79
+ if @current_image_options
80
+ @current_image_options
81
+ else
82
+ {}
83
+ end
84
+ end
85
+
86
+ def add_machine_options(options, &block)
87
+ with_machine_options(Chef::Mixin::DeepMerge.hash_only_merge(current_machine_options, options), &block)
88
+ end
89
+
90
+ def driver_for(driver)
91
+ driver.is_a?(String) ? driver_for_url(driver) : driver
92
+ end
93
+
94
+ def connect_to_machine(name, chef_server = nil)
95
+ if name.is_a?(MachineSpec)
96
+ machine_spec = name
97
+ else
98
+ machine_spec = Chef::Provisioning::ChefMachineSpec.get(name, chef_server)
99
+ end
100
+ Chef::Provisioning.connect_to_machine(machine_spec, config)
101
+ end
102
+
103
+ private
104
+
105
+ def driver_for_url(driver_url)
106
+ drivers[driver_url] ||= begin
107
+ if driver_url == @current_driver && @current_driver_options
108
+ # Use the driver options if available
109
+ merged_config = Cheffish::MergedConfig.new({ :driver_options => @current_driver_options }, config)
110
+ driver = Chef::Provisioning.driver_for_url(driver_url, merged_config)
111
+ else
112
+ driver = Chef::Provisioning.driver_for_url(driver_url, config)
113
+ end
114
+ # Check the canonicalized driver_url from the driver
115
+ if driver.driver_url != driver_url
116
+ if drivers[driver.driver_url] && @current_driver_options
117
+ raise "Canonical driver #{driver.driver_url} for #{driver_url} has already been created! Current options #{@current_driver_options} would be ignored."
118
+ end
119
+ drivers[driver.driver_url] ||= driver
120
+ else
121
+ driver
122
+ end
123
+ end
124
+ end
125
+
126
+ def keys
127
+ result = (config.keys || {}).dup
128
+ Array(config.key_path) do |key_path|
129
+ Dir.entries(key_path).each do |key|
130
+ if File.extname(key) == '.pem'
131
+ result[File.basename(key)[0..-5]] ||= key
132
+ end
133
+ end
134
+ end
135
+ result
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,28 @@
1
+ class Chef
2
+ module Provisioning
3
+ class ConvergenceStrategy
4
+ # convergence_options - a freeform hash of options to the converger.
5
+ # config - a Chef::Config-like object with global config like :log_level
6
+ def initialize(convergence_options, config)
7
+ @convergence_options = convergence_options || {}
8
+ @config = config
9
+ end
10
+
11
+ attr_reader :convergence_options
12
+ attr_reader :config
13
+
14
+ # Get the machine ready to converge, but do not converge.
15
+ def setup_convergence(action_handler, machine)
16
+ raise "setup_convergence not overridden on #{self.class}"
17
+ end
18
+
19
+ def converge(action_handler, machine)
20
+ raise "converge not overridden on #{self.class}"
21
+ end
22
+
23
+ def cleanup_convergence(action_handler, machine_spec)
24
+ raise "cleanup_convergence not overridden on #{self.class}"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,156 @@
1
+ require 'chef/provisioning/convergence_strategy/precreate_chef_objects'
2
+ require 'pathname'
3
+ require 'fileutils'
4
+ require 'digest/md5'
5
+ require 'thread'
6
+ require 'chef/http/simple'
7
+
8
+ class Chef
9
+ module Provisioning
10
+ class ConvergenceStrategy
11
+ class InstallCached < PrecreateChefObjects
12
+ # convergence_options is a hash of setup convergence_options, including:
13
+ # - :chef_server
14
+ # - :allow_overwrite_keys
15
+ # - :source_key, :source_key_path, :source_key_pass_phrase
16
+ # - :private_key_options
17
+ # - :ohai_hints
18
+ # - :public_key_path, :public_key_format
19
+ # - :admin, :validator
20
+ # - :chef_client_timeout
21
+ # - :client_rb_path, :client_pem_path
22
+ # - :chef_version, :prerelease, :package_cache_path
23
+ def initialize(convergence_options, config)
24
+ convergence_options = Cheffish::MergedConfig.new(convergence_options, {
25
+ :client_rb_path => '/etc/chef/client.rb',
26
+ :client_pem_path => '/etc/chef/client.pem'
27
+ })
28
+ super(convergence_options, config)
29
+ @chef_version ||= convergence_options[:chef_version]
30
+ @prerelease ||= convergence_options[:prerelease]
31
+ @package_cache_path ||= convergence_options[:package_cache_path] || "#{ENV['HOME']}/.chef/package_cache"
32
+ @package_cache = {}
33
+ @tmp_dir = '/tmp'
34
+ @chef_client_timeout = convergence_options.has_key?(:chef_client_timeout) ? convergence_options[:chef_client_timeout] : 120*60 # Default: 2 hours
35
+ FileUtils.mkdir_p(@package_cache_path)
36
+ @package_cache_lock = Mutex.new
37
+ end
38
+
39
+ attr_reader :client_rb_path
40
+ attr_reader :client_pem_path
41
+
42
+ def setup_convergence(action_handler, machine)
43
+ super
44
+
45
+ # Install chef-client. TODO check and update version if not latest / not desired
46
+ if machine.execute_always('chef-client -v').exitstatus != 0
47
+ platform, platform_version, machine_architecture = machine.detect_os(action_handler)
48
+ package_file = download_package_for_platform(action_handler, machine, platform, platform_version, machine_architecture)
49
+ remote_package_file = "#{@tmp_dir}/#{File.basename(package_file)}"
50
+ machine.upload_file(action_handler, package_file, remote_package_file)
51
+ install_package(action_handler, machine, remote_package_file)
52
+ end
53
+ end
54
+
55
+ def converge(action_handler, machine)
56
+ super
57
+
58
+ action_handler.open_stream(machine.node['name']) do |stdout|
59
+ action_handler.open_stream(machine.node['name']) do |stderr|
60
+ command_line = "chef-client"
61
+ command_line << " -l #{config[:log_level].to_s}" if config[:log_level]
62
+ machine.execute(action_handler, command_line,
63
+ :stream_stdout => stdout,
64
+ :stream_stderr => stderr,
65
+ :timeout => @chef_client_timeout)
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def download_package_for_platform(action_handler, machine, platform, platform_version, machine_architecture)
73
+ @package_cache_lock.synchronize do
74
+ @package_cache ||= {}
75
+ @package_cache[platform] ||= {}
76
+ @package_cache[platform][platform_version] ||= {}
77
+ @package_cache[platform][platform_version][machine_architecture] ||= { :lock => Mutex.new }
78
+ end
79
+ @package_cache[platform][platform_version][machine_architecture][:lock].synchronize do
80
+ if !@package_cache[platform][platform_version][machine_architecture][:file]
81
+ #
82
+ # Grab metadata
83
+ #
84
+ metadata = download_metadata_for_platform(machine, platform, platform_version, machine_architecture)
85
+
86
+ # Download actual package desired by metadata
87
+ package_file = "#{@package_cache_path}/#{URI(metadata['url']).path.split('/')[-1]}"
88
+
89
+ Chef::Provisioning.inline_resource(action_handler) do
90
+ remote_file package_file do
91
+ source metadata['url']
92
+ checksum metadata['sha256']
93
+ end
94
+ end
95
+
96
+ @package_cache[platform][platform_version][machine_architecture][:file] = package_file
97
+ end
98
+ end
99
+ @package_cache[platform][platform_version][machine_architecture][:file]
100
+ end
101
+
102
+ def download_metadata_for_platform(machine, platform, platform_version, machine_architecture)
103
+ #
104
+ # Figure out the URL to the metadata
105
+ #
106
+ metadata_url="https://www.opscode.com/chef/metadata"
107
+ metadata_url << "?v=#{@chef_version}"
108
+ metadata_url << "&prerelease=#{@prerelease ? 'true' : 'false'}"
109
+ metadata_url << "&p=#{platform.strip}"
110
+ metadata_url << "&pv=#{platform_version.strip}"
111
+ metadata_url << "&m=#{machine_architecture.strip}"
112
+ use_ssl = true
113
+
114
+ # solaris 9 lacks openssl, solaris 10 lacks recent enough credentials - your base O/S is completely insecure, please upgrade
115
+ if platform == 'solaris2' && (platform_version == '5.9' || platform_version == '5.10')
116
+ metadata_url.sub(/^https/, 'http')
117
+ use_ssl = false
118
+ end
119
+
120
+ # Download and parse the metadata
121
+ Chef::Log.debug("Getting metadata for machine #{machine.node['name']}: #{metadata_url}")
122
+ uri = URI(metadata_url)
123
+ metadata_str = Chef::HTTP::Simple.new(uri).get(uri)
124
+ metadata = {}
125
+ metadata_str.each_line do |line|
126
+ key, value = line.split("\t", 2)
127
+ metadata[key] = value
128
+ end
129
+ metadata
130
+ end
131
+
132
+ def install_package(action_handler, machine, remote_package_file)
133
+ extension = File.extname(remote_package_file)
134
+ result = case extension
135
+ when '.rpm'
136
+ machine.execute(action_handler, "rpm -Uvh --oldpackage --replacepkgs \"#{remote_package_file}\"")
137
+ when '.deb'
138
+ machine.execute(action_handler, "dpkg -i \"#{remote_package_file}\"")
139
+ when '.solaris'
140
+ machine.write_file(action_handler, "#{@tmp_dir}/nocheck", <<EOM)
141
+ conflict=nocheck
142
+ action=nocheck
143
+ mail=
144
+ EOM
145
+ machine.execute(action_handler, "pkgrm -a \"#{@tmp_dir}/nocheck\" -n chef")
146
+ machine.execute(action_handler, "pkgadd -n -d \"#{remote_package_file}\" -a \"#{@tmp_dir}/nocheck\" chef")
147
+ when '.sh'
148
+ machine.execute(action_handler, "sh \"#{remote_package_file}\"")
149
+ else
150
+ raise "Unknown package extension '#{extension}' for file #{remote_package_file}"
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end