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,151 @@
1
+ require 'chef/provisioning/transport'
2
+ require 'base64'
3
+ require 'timeout'
4
+
5
+ class Chef
6
+ module Provisioning
7
+ class Transport
8
+ # Transport to handle the WinRM connection protocol.
9
+ class WinRM < Chef::Provisioning::Transport
10
+ #
11
+ # Create a new WinRM transport.
12
+ #
13
+ # == Arguments
14
+ # - endpoint: the WinRM endpoint, e.g. http://145.14.51.45:5985/wsman.
15
+ # - type: the connection type, e.g. :plaintext.
16
+ # - options: options hash, including both WinRM options and transport options.
17
+ # For transport options, see the Transport.options definition. WinRM
18
+ # options include :user, :pass, :disable_sspi => true, among others.
19
+ # - global_config: an options hash that looks suspiciously similar to
20
+ # Chef::Config, containing at least the key :log_level.
21
+ #
22
+ # The actual connection is made as ::WinRM::WinRMWebService.new(endpoint, type, options)
23
+ #
24
+ def initialize(endpoint, type, options, global_config)
25
+ @endpoint = endpoint
26
+ @type = type
27
+ @options = options
28
+ @config = global_config
29
+ end
30
+
31
+ attr_reader :endpoint
32
+ attr_reader :type
33
+ attr_reader :options
34
+ attr_reader :config
35
+
36
+ def execute(command, execute_options = {})
37
+ output = with_execute_timeout(execute_options) do
38
+ session.set_timeout(execute_timeout(execute_options))
39
+ session.run_powershell_script(command) do |stdout, stderr|
40
+ stream_chunk(execute_options, stdout, stderr)
41
+ end
42
+ end
43
+ WinRMResult.new(command, execute_options, config, output)
44
+ end
45
+
46
+ def read_file(path)
47
+ result = execute("[Convert]::ToBase64String((Get-Content #{escape(path)} -Encoding byte -ReadCount 0))")
48
+ if result.exitstatus == 0
49
+ Base64.decode64(result.stdout)
50
+ else
51
+ nil
52
+ end
53
+ end
54
+
55
+ def write_file(path, content)
56
+ chunk_size = options[:chunk_size] || 1024
57
+ # TODO if we could marshal this data directly, we wouldn't have to base64 or do this godawful slow stuff :(
58
+ index = 0
59
+ execute("
60
+ $ByteArray = [System.Convert]::FromBase64String(#{escape(Base64.encode64(content[index..index+chunk_size-1]))})
61
+ $file = [System.IO.File]::Open(#{escape(path)}, 'Create', 'Write')
62
+ $file.Write($ByteArray, 0, $ByteArray.Length)
63
+ $file.Close
64
+ ").error!
65
+ index += chunk_size
66
+ while index < content.length
67
+ execute("
68
+ $ByteArray = [System.Convert]::FromBase64String(#{escape(Base64.encode64(content[index..index+chunk_size-1]))})
69
+ $file = [System.IO.File]::Open(#{escape(path)}, 'Append', 'Write')
70
+ $file.Write($ByteArray, 0, $ByteArray.Length)
71
+ $file.Close
72
+ ").error!
73
+ index += chunk_size
74
+ end
75
+ end
76
+
77
+ def disconnect
78
+ #
79
+ end
80
+
81
+ def escape(string)
82
+ "\"#{string.gsub("\"", "`\"")}\""
83
+ end
84
+
85
+ def available?
86
+ # If you can't pwd within 10 seconds, you can't pwd
87
+ execute('pwd', :timeout => 10)
88
+ true
89
+ rescue Timeout::Error, Errno::EHOSTUNREACH, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::ECONNRESET, ::WinRM::WinRMHTTPTransportError, ::WinRM::WinRMWebServiceError, ::WinRM::WinRMWSManFault
90
+ Chef::Log.debug("unavailable: network connection failed or broke: #{$!.inspect}")
91
+ disconnect
92
+ false
93
+ rescue ::WinRM::WinRMAuthorizationError
94
+ Chef::Log.debug("unavailable: winrm authentication error: #{$!.inspect} ")
95
+ disconnect
96
+ false
97
+ end
98
+
99
+ def make_url_available_to_remote(local_url)
100
+ uri = URI(local_url)
101
+ host = Socket.getaddrinfo(uri.host, uri.scheme, nil, :STREAM)[0][3]
102
+ if host == '127.0.0.1' || host == '::1'
103
+ raise 'Unable to converge locally via winrm. Local converge is currently only supported with SSH. You may only converge with winrm against a chef-server.'
104
+ end
105
+ local_url
106
+ end
107
+
108
+ protected
109
+
110
+ def session
111
+ @session ||= begin
112
+ require 'kconv' # Really, people? *sigh*
113
+ require 'winrm'
114
+ ::WinRM::WinRMWebService.new(endpoint, type, options)
115
+ end
116
+ end
117
+
118
+ class WinRMResult
119
+ def initialize(command, options, config, output)
120
+ @command = command
121
+ @options = options
122
+ @config = config
123
+ @exitstatus = output[:exitcode]
124
+ @stdout = ''
125
+ @stderr = ''
126
+ output[:data].each do |data|
127
+ @stdout << data[:stdout] if data[:stdout]
128
+ @stderr << data[:stderr] if data[:stderr]
129
+ end
130
+ end
131
+
132
+ attr_reader :stdout
133
+ attr_reader :stderr
134
+ attr_reader :exitstatus
135
+ attr_reader :command
136
+ attr_reader :options
137
+ attr_reader :config
138
+
139
+ def error!
140
+ if exitstatus != 0
141
+ msg = "Error: command '#{command}' exited with code #{exitstatus}.\n"
142
+ msg << "STDOUT: #{stdout}" if !options[:stream] && !options[:stream_stdout] && config[:log_level] != :debug
143
+ msg << "STDERR: #{stderr}" if !options[:stream] && !options[:stream_stderr] && config[:log_level] != :debug
144
+ raise msg
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,5 @@
1
+ class Chef
2
+ module Provisioning
3
+ VERSION = '0.15'
4
+ end
5
+ end
@@ -0,0 +1,148 @@
1
+ require 'chef/resource/lwrp_base'
2
+ require 'cheffish'
3
+
4
+ # A resource that is backed by a data bag in a Chef server somewhere
5
+ class Chef::Resource::ChefDataBagResource < Chef::Resource::LWRPBase
6
+
7
+ # The key to store this thing under (/data/bag/<<name>>).
8
+ attr_reader :name
9
+
10
+ class << self
11
+ # The name of the databag to store the item in.
12
+ attr_reader :databag_name
13
+ # A list of attributes to be persisted into the databag.
14
+ attr_reader :stored_attributes
15
+ end
16
+
17
+ def initialize(name, run_context=nil)
18
+ super
19
+ Chef::Log.debug("Re-hydrating #{name} from #{self.class.databag_name}...")
20
+ self.hydrate
21
+ end
22
+
23
+ # @return [Array] List of attributes that are stored in the databag
24
+ def self.stored_attributes
25
+ @stored_attributes || []
26
+ end
27
+
28
+ # Set databag name
29
+ # @return [Void]
30
+ def self.databag_name= name
31
+ Chef::Log.debug("Setting databag name to #{name}")
32
+ @databag_name = name
33
+ end
34
+
35
+ # Mark an attribute as stored by adding it to the internal tracking list {stored_attributes}
36
+ # and then delegating to {Chef::Resource::LWRPBase#attribute}
37
+ # @param attr_name [Symbol] Name of the attribute as a symbol
38
+ # @return [Void]
39
+ def self.stored_attribute(attr_name)
40
+ @stored_attributes ||= []
41
+ @stored_attributes << attr_name
42
+ self.attribute attr_name
43
+ end
44
+
45
+ # Load persisted data from the server's databag. If the databag does not exist on the
46
+ # server, returns nil.
47
+ #
48
+ # @param chef_server [Hash] A hash representing which Chef server to talk to
49
+ # @option chef_server [String] :chef_server_url URL to the Chef server
50
+ # @option chef_server [Hash] :options Options for when talking to the Chef server
51
+ # @option options [String] :client_name The node name making the call
52
+ # @option options [String] :signing_key_filename Path to the signing key
53
+ # @return [Object] an instance of this class re-hydrated from the data hash stored in the
54
+ # databag.
55
+ def hydrate(chef_server = Cheffish.default_chef_server)
56
+ chef_api = Cheffish.chef_server_api(chef_server)
57
+ begin
58
+ data = chef_api.get("/data/#{self.class.databag_name}/#{name}")
59
+ load_from_hash(data)
60
+ Chef::Log.debug("Rehydrating resource from #{self.class.databag_name}/#{name}: #{data}")
61
+ rescue Net::HTTPServerException => e
62
+ if e.response.code == '404'
63
+ nil
64
+ else
65
+ raise
66
+ end
67
+ end
68
+ end
69
+
70
+ # Load instance variable data from a hash. For each key,value pair, set @<key> to value
71
+ # @param hash [Hash] Hash containing the instance variable data
72
+ # @return [Object] self after having been populated with data.
73
+ def load_from_hash hash
74
+ hash.each do |k,v|
75
+ self.instance_variable_set("@#{k}", v)
76
+ end
77
+ self
78
+ end
79
+
80
+ # Convert the values in {stored_attributes} to a hash for storing in a databag
81
+ # @return [Hash] a hash of (k,v) pairs where k is each record in {stored_attributes}
82
+ def storage_hash
83
+ ignored = []
84
+
85
+ hash = {}
86
+ (self.class.stored_attributes - ignored).each do |attr_name|
87
+ varname = "@#{attr_name.to_s.gsub('@', '')}"
88
+ key = varname.gsub('@', '')
89
+ hash[key] = self.instance_variable_get varname
90
+ end
91
+
92
+ hash
93
+ end
94
+
95
+
96
+ # Save this entity to the server. If you have significant information that
97
+ # could be lost, you should do this as quickly as possible.
98
+ # @return [Void]
99
+ def save
100
+
101
+ create_databag_if_needed self.class.databag_name
102
+
103
+ # Clone for inline_resource
104
+ _databag_name = self.class.databag_name
105
+ _hash = self.storage_hash
106
+ _name = self.name
107
+
108
+ Cheffish.inline_resource(self, @action) do
109
+ chef_data_bag_item _name do
110
+ data_bag _databag_name
111
+ raw_data _hash
112
+ action :create
113
+ end
114
+ end
115
+ end
116
+
117
+ # Delete this entity from the server
118
+ # @return [Void]
119
+ def delete
120
+ # Clone for inline_resource
121
+ _name = self.name
122
+ _databag_name = self.class.databag_name
123
+
124
+ Cheffish.inline_resource(self, @action) do
125
+ chef_data_bag_item _name do
126
+ data_bag _databag_name
127
+ action :delete
128
+ end
129
+ end
130
+ end
131
+
132
+ def new_resource
133
+ self
134
+ end
135
+
136
+ private
137
+ # Create the databag with Cheffish if required
138
+ # @return [Void]
139
+ def create_databag_if_needed databag_name
140
+ _databag_name = databag_name
141
+ Cheffish.inline_resource(self, @action) do
142
+ chef_data_bag _databag_name do
143
+ action :create
144
+ end
145
+ end
146
+ end
147
+ end
148
+
@@ -0,0 +1,57 @@
1
+ require 'chef/resource/lwrp_base'
2
+ require 'cheffish'
3
+ require 'chef_metal'
4
+ require 'cheffish/merged_config'
5
+
6
+ class Chef
7
+ class Resource
8
+ class LoadBalancer < Chef::Resource::LWRPBase
9
+
10
+ self.resource_name = 'load_balancer'
11
+
12
+ def initialize(*args)
13
+ super
14
+ @chef_environment = run_context.cheffish.current_environment
15
+ @chef_server = run_context.cheffish.current_chef_server
16
+ @driver = run_context.chef_metal.current_driver
17
+ @load_balancer_options = run_context.chef_metal.current_load_balancer_options
18
+ end
19
+
20
+ actions :create, :destroy
21
+ default_action :create
22
+
23
+ # Driver attributes
24
+ attribute :driver
25
+ attribute :load_balancer_options
26
+ attribute :name, :kind_of => String, :name_attribute => true
27
+ attribute :machines
28
+
29
+ def add_load_balancer_options(options)
30
+ @load_balancer_options = Cheffish::MergedConfig.new(options, @load_balancer_options)
31
+ end
32
+
33
+
34
+ # This is here because metal users will probably want to do things like:
35
+ # machine "foo"
36
+ # action :destroy
37
+ # end
38
+ #
39
+ # with_load_balancer_options :bootstrap_options => {...}
40
+ # machine "foo"
41
+ # converge true
42
+ # end
43
+ #
44
+ # Without this, the first resource's machine options will obliterate the second
45
+ # resource's machine options, and then unexpected (and undesired) things happen.
46
+ def load_prior_resource
47
+ Chef::Log.debug "Overloading #{self.resource_name} load_prior_resource with NOOP"
48
+ end
49
+
50
+ # chef client version and omnibus
51
+ # chef-zero boot method?
52
+ # chef-client -z boot method?
53
+ # pushy boot method?
54
+ end
55
+ end
56
+ end
57
+
@@ -0,0 +1,124 @@
1
+ require 'chef/resource/lwrp_base'
2
+ require 'cheffish'
3
+ require 'chef/provisioning'
4
+ require 'cheffish/merged_config'
5
+
6
+ class Chef
7
+ class Resource
8
+ class Machine < Chef::Resource::LWRPBase
9
+
10
+ self.resource_name = 'machine'
11
+
12
+ def initialize(*args)
13
+ super
14
+ @chef_environment = run_context.cheffish.current_environment
15
+ @chef_server = run_context.cheffish.current_chef_server
16
+ @driver = run_context.chef_provisioning.current_driver
17
+ @machine_options = run_context.chef_provisioning.current_machine_options
18
+ end
19
+
20
+ actions :allocate, :ready, :setup, :converge, :converge_only, :destroy, :stop
21
+ default_action :converge
22
+
23
+ # Driver attributes
24
+ attribute :driver
25
+
26
+ # Machine options
27
+ attribute :machine_options
28
+
29
+ # Node attributes
30
+ Cheffish.node_attributes(self)
31
+
32
+ # Client keys
33
+ # Options to generate private key (size, type, etc.) when the server doesn't have it
34
+ attribute :private_key_options, :kind_of => Hash
35
+ attribute :allow_overwrite_keys, :kind_of => [TrueClass, FalseClass]
36
+
37
+ # Optionally pull the public key out to a file
38
+ attribute :public_key_path, :kind_of => String
39
+ attribute :public_key_format, :kind_of => String
40
+
41
+ # If you really want to force the private key to be a certain key, pass these
42
+ attribute :source_key
43
+ attribute :source_key_path, :kind_of => String
44
+ attribute :source_key_pass_phrase
45
+
46
+ # Client attributes
47
+ attribute :admin, :kind_of => [TrueClass, FalseClass]
48
+ attribute :validator, :kind_of => [TrueClass, FalseClass]
49
+
50
+ # Client Ohai hints, allows machine to enable hints
51
+ # e.g. ohai_hint 'ec2' => { 'a' => 'b' } creates file ec2.json with json contents { 'a': 'b' }
52
+ attribute :ohai_hints, :kind_of => Hash
53
+
54
+ # Allows you to turn convergence off in the :create action by writing "converge false"
55
+ # or force it with "true"
56
+ attribute :converge, :kind_of => [TrueClass, FalseClass]
57
+
58
+ # A list of files to upload, in the format REMOTE_PATH => LOCAL_PATH|HASH.
59
+ # == Examples
60
+ # files '/remote/path.txt' => '/local/path.txt'
61
+ # files '/remote/path.txt' => { :local_path => '/local/path.txt' }
62
+ # files '/remote/path.txt' => { :content => 'woo' }
63
+ attribute :files, :kind_of => Hash
64
+
65
+ # The named machine_image to start from. Specify the name of a machine_image
66
+ # object and the default machine_options will be set to use that image.
67
+ # == Examples
68
+ # from_image 'company_base_image'
69
+ attribute :from_image, :kind_of => String
70
+
71
+ # A single file to upload, in the format REMOTE_PATH, LOCAL_PATH|HASH.
72
+ # This directive may be passed multiple times, and multiple files will be uploaded.
73
+ # == Examples
74
+ # file '/remote/path.txt', '/local/path.txt'
75
+ # file '/remote/path.txt', { :local_path => '/local/path.txt' }
76
+ # file '/remote/path.txt', { :content => 'woo' }
77
+ def file(remote_path, local = nil)
78
+ @files ||= {}
79
+ if remote_path.is_a?(Hash)
80
+ if local
81
+ raise "file(Hash, something) does not make sense. Either pass a hash, or pass a pair, please."
82
+ end
83
+ remote_path.each_pair do |remote, local|
84
+ @files[remote] = local
85
+ end
86
+ elsif remote_path.is_a?(String)
87
+ if !local
88
+ raise "Must pass both a remote path and a local path to file directive"
89
+ end
90
+ @files[remote_path] = local
91
+ else
92
+ raise "file remote_path must be a String, but is a #{remote_path.class}"
93
+ end
94
+ end
95
+
96
+ def add_machine_options(options)
97
+ @machine_options = Cheffish::MergedConfig.new(options, @machine_options)
98
+ end
99
+
100
+
101
+ # This is here because provisioning users will probably want to do things like:
102
+ # machine "foo"
103
+ # action :destroy
104
+ # end
105
+ #
106
+ # @example
107
+ # with_machine_options :bootstrap_options => { ... }
108
+ # machine "foo"
109
+ # converge true
110
+ # end
111
+ #
112
+ # Without this, the first resource's machine options will obliterate the second
113
+ # resource's machine options, and then unexpected (and undesired) things happen.
114
+ def load_prior_resource
115
+ Chef::Log.debug "Overloading #{self.resource_name} load_prior_resource with NOOP"
116
+ end
117
+
118
+ # chef client version and omnibus
119
+ # chef-zero boot method?
120
+ # chef-client -z boot method?
121
+ # pushy boot method?
122
+ end
123
+ end
124
+ end