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