chef-provisioning 0.15

Sign up to get free protection for your applications and to get access to all the features.
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