chef 12.2.1 → 12.3.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/lib/chef.rb +1 -0
  3. data/lib/chef/application/apply.rb +5 -0
  4. data/lib/chef/application/client.rb +10 -0
  5. data/lib/chef/application/knife.rb +5 -1
  6. data/lib/chef/application/solo.rb +5 -0
  7. data/lib/chef/chef_class.rb +130 -0
  8. data/lib/chef/client.rb +15 -7
  9. data/lib/chef/config.rb +13 -0
  10. data/lib/chef/event_loggers/windows_eventlog.rb +11 -5
  11. data/lib/chef/http.rb +13 -3
  12. data/lib/chef/http/basic_client.rb +21 -4
  13. data/lib/chef/http/socketless_chef_zero_client.rb +207 -0
  14. data/lib/chef/knife.rb +3 -0
  15. data/lib/chef/knife/bootstrap.rb +1 -1
  16. data/lib/chef/knife/core/status_presenter.rb +12 -11
  17. data/lib/chef/knife/ssh.rb +3 -1
  18. data/lib/chef/knife/status.rb +32 -7
  19. data/lib/chef/local_mode.rb +13 -3
  20. data/lib/chef/mixin/provides.rb +32 -0
  21. data/lib/chef/platform/provider_priority_map.rb +16 -7
  22. data/lib/chef/platform/resource_priority_map.rb +37 -0
  23. data/lib/chef/policy_builder/expand_node_object.rb +14 -0
  24. data/lib/chef/policy_builder/policyfile.rb +0 -1
  25. data/lib/chef/provider.rb +5 -20
  26. data/lib/chef/provider/package/rubygems.rb +4 -1
  27. data/lib/chef/provider/service/macosx.rb +66 -30
  28. data/lib/chef/provider_resolver.rb +10 -5
  29. data/lib/chef/resource.rb +5 -39
  30. data/lib/chef/resource/gem_package.rb +5 -0
  31. data/lib/chef/resource/link.rb +1 -1
  32. data/lib/chef/resource/macosx_service.rb +59 -0
  33. data/lib/chef/resource/remote_file.rb +0 -4
  34. data/lib/chef/resource_resolver.rb +101 -0
  35. data/lib/chef/rest.rb +4 -5
  36. data/lib/chef/search/query.rb +1 -1
  37. data/lib/chef/server_api.rb +1 -0
  38. data/lib/chef/version.rb +1 -1
  39. data/spec/data/lwrp/providers/buck_passer.rb +2 -1
  40. data/spec/data/lwrp/resources/bar.rb +1 -1
  41. data/spec/data/{big_json.json → nested.json} +2 -2
  42. data/spec/functional/event_loggers/windows_eventlog_spec.rb +14 -0
  43. data/spec/functional/resource/execute_spec.rb +1 -1
  44. data/spec/integration/client/client_spec.rb +12 -1
  45. data/spec/integration/client/ipv6_spec.rb +1 -1
  46. data/spec/integration/knife/common_options_spec.rb +3 -3
  47. data/spec/integration/recipes/lwrp_inline_resources_spec.rb +1 -1
  48. data/spec/integration/solo/solo_spec.rb +7 -5
  49. data/spec/unit/application/client_spec.rb +10 -0
  50. data/spec/unit/chef_class_spec.rb +91 -0
  51. data/spec/unit/client_spec.rb +13 -0
  52. data/spec/unit/http/basic_client_spec.rb +43 -6
  53. data/spec/unit/http/socketless_chef_zero_client_spec.rb +174 -0
  54. data/spec/unit/http_spec.rb +14 -0
  55. data/spec/unit/json_compat_spec.rb +7 -20
  56. data/spec/unit/knife/ssh_spec.rb +18 -0
  57. data/spec/unit/knife/status_spec.rb +69 -3
  58. data/spec/unit/knife_spec.rb +5 -0
  59. data/spec/unit/provider/package/rubygems_spec.rb +19 -0
  60. data/spec/unit/provider/service/macosx_spec.rb +230 -203
  61. data/spec/unit/provider_resolver_spec.rb +1 -0
  62. data/spec/unit/recipe_spec.rb +48 -0
  63. data/spec/unit/resource/link_spec.rb +15 -0
  64. data/spec/unit/resource_spec.rb +6 -6
  65. data/spec/unit/rest_spec.rb +9 -0
  66. data/spec/unit/search/query_spec.rb +24 -0
  67. data/spec/unit/shell_spec.rb +3 -1
  68. metadata +16 -9
  69. data/spec/data/big_json_plus_one.json +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 91f1920d1c2e0863500a9e71845388856c621dc3
4
- data.tar.gz: 1ded9282d859bca75cffd03654cfeec71a676b4e
3
+ metadata.gz: dab060d316f7e7df4ab46305e971695d6f8d7c99
4
+ data.tar.gz: f09a8ffb3137bdb1ba5bfcf5bff8969537c5e502
5
5
  SHA512:
6
- metadata.gz: f4f3f95775aba6c9a129db77b7ae7a1b708db9c16a553673a4c6275f02bba0fabae76dc5b259c1ee71085da2f842f0cd4dedcf6e45439e20334cac06e540715f
7
- data.tar.gz: ec7d0ad336743697fcb95f0d139ec8d8f7c364f324cf191311abece9ea1efada824504d3513117fa328d0671e6a5463bc217a097e153ad89ea264deedbcc8008
6
+ metadata.gz: 244c89673e62ec837b467ca6be7b8846012d17b8231b29f11365ba6044e044f281c34edd9ed4e71173350a0e7472d05e111145dda831198c5921234dab6da9fe
7
+ data.tar.gz: e79a4ef02c7e5bf2484f06a9bba694a1c5ea4001faa5c50de3702be3b65947a59746b665a685150cc6902ab8cbcc599b3d9ae164ade2ed5b3df81689c2f98273
@@ -32,3 +32,4 @@ require 'chef/run_status'
32
32
  require 'chef/handler'
33
33
  require 'chef/handler/json_file'
34
34
 
35
+ require 'chef/chef_class'
@@ -85,6 +85,11 @@ class Chef::Application::Apply < Chef::Application
85
85
  :default => !Chef::Platform.windows?,
86
86
  :description => "Use colored output, defaults to enabled"
87
87
 
88
+ option :minimal_ohai,
89
+ :long => "--minimal-ohai",
90
+ :description => "Only run the bare minimum ohai plugins chef needs to function",
91
+ :boolean => true
92
+
88
93
  attr_reader :json_attribs
89
94
 
90
95
  def initialize
@@ -253,6 +253,16 @@ class Chef::Application::Client < Chef::Application
253
253
  :description => "Enable audit-mode with `enabled`. Disable audit-mode with `disabled`. Skip converge and only perform audits with `audit-only`",
254
254
  :proc => lambda { |mo| mo.gsub("-", "_").to_sym }
255
255
 
256
+ option :minimal_ohai,
257
+ :long => "--minimal-ohai",
258
+ :description => "Only run the bare minimum ohai plugins chef needs to function",
259
+ :boolean => true
260
+
261
+ option :listen,
262
+ :long => "--[no-]listen",
263
+ :description => "Whether a local mode (-z) server binds to a port",
264
+ :boolean => true
265
+
256
266
  IMMEDIATE_RUN_SIGNAL = "1".freeze
257
267
 
258
268
  attr_reader :chef_client_json
@@ -121,6 +121,11 @@ class Chef::Application::Knife < Chef::Application
121
121
  :long => "--chef-zero-port PORT",
122
122
  :description => "Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works."
123
123
 
124
+ option :listen,
125
+ :long => "--[no-]listen",
126
+ :description => "Whether a local mode (-z) server binds to a port",
127
+ :boolean => true
128
+
124
129
  option :version,
125
130
  :short => "-v",
126
131
  :long => "--version",
@@ -129,7 +134,6 @@ class Chef::Application::Knife < Chef::Application
129
134
  :proc => lambda {|v| puts "Chef: #{::Chef::VERSION}"},
130
135
  :exit => 0
131
136
 
132
-
133
137
  # Run knife
134
138
  def run
135
139
  Mixlib::Log::Formatter.show_time = false
@@ -180,6 +180,11 @@ class Chef::Application::Solo < Chef::Application
180
180
  :description => "Set maximum duration to wait for another client run to finish, default is indefinitely.",
181
181
  :proc => lambda { |s| s.to_i }
182
182
 
183
+ option :minimal_ohai,
184
+ :long => "--minimal-ohai",
185
+ :description => "Only run the bare minimum ohai plugins chef needs to function",
186
+ :boolean => true
187
+
183
188
  attr_reader :chef_client_json
184
189
 
185
190
  def initialize
@@ -0,0 +1,130 @@
1
+ #
2
+ # Author:: Lamont Granquist (<lamont@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ # NOTE: This class is not intended for internal use by the chef-client code. Classes in
19
+ # chef-client should still have objects like the node and run_context injected into them
20
+ # via their initializers. This class is still global state and will complicate writing
21
+ # unit tests for internal Chef objects. It is intended to be used only by recipe code.
22
+
23
+ # NOTE: When adding require lines here you are creating tight coupling on a global that may be
24
+ # included in many different situations and ultimately that ends in tears with circular requires.
25
+ # Note the way that the run_context, provider_priority_map and resource_priority_map are "dependency
26
+ # injected" into this class by other objects and do not reference the class symbols in those files
27
+ # directly and we do not need to require those files here.
28
+
29
+ class Chef
30
+ class << self
31
+
32
+ #
33
+ # Public API
34
+ #
35
+
36
+ # Get the node object
37
+ #
38
+ # @return [Chef::Node] node object of the chef-client run
39
+ attr_reader :node
40
+
41
+ # Get the run context
42
+ #
43
+ # @return [Chef::RunContext] run_context of the chef-client run
44
+ attr_reader :run_context
45
+
46
+ # Get the array of providers associated with a resource_name for the current node
47
+ #
48
+ # @param resource_name [Symbol] name of the resource as a symbol
49
+ # @return [Array<Class>] Priority Array of Provider Classes to use for the resource_name on the node
50
+ def get_provider_priority_array(resource_name)
51
+ @provider_priority_map.get_priority_array(node, resource_name).dup
52
+ end
53
+
54
+ # Get the array of resources associated with a resource_name for the current node
55
+ #
56
+ # @param resource_name [Symbol] name of the resource as a symbol
57
+ # @return [Array<Class>] Priority Array of Resource Classes to use for the resource_name on the node
58
+ def get_resource_priority_array(resource_name)
59
+ @resource_priority_map.get_priority_array(node, resource_name).dup
60
+ end
61
+
62
+ # Set the array of providers associated with a resource_name for the current node
63
+ #
64
+ # @param resource_name [Symbol] name of the resource as a symbol
65
+ # @param priority_array [Array<Class>] Array of Classes to set as the priority for resource_name on the node
66
+ # @param filter [Hash] Chef::Nodearray-style filter
67
+ # @return [Array<Class>] Modified Priority Array of Provider Classes to use for the resource_name on the node
68
+ def set_provider_priority_array(resource_name, priority_array, *filter)
69
+ @provider_priority_map.set_priority_array(resource_name, priority_array, *filter).dup
70
+ end
71
+
72
+ # Get the array of resources associated with a resource_name for the current node
73
+ #
74
+ # @param resource_name [Symbol] name of the resource as a symbol
75
+ # @param priority_array [Array<Class>] Array of Classes to set as the priority for resource_name on the node
76
+ # @param filter [Hash] Chef::Nodearray-style filter
77
+ # @return [Array<Class>] Modified Priority Array of Resource Classes to use for the resource_name on the node
78
+ def set_resource_priority_array(resource_name, priority_array, *filter)
79
+ @resource_priority_map.set_priority_array(resource_name, priority_array, *filter).dup
80
+ end
81
+
82
+ #
83
+ # Dependency Injection API (Private not Public)
84
+ # [ in the ruby sense these have to be public methods, but they are
85
+ # *NOT* for public consumption ]
86
+ #
87
+
88
+ # Sets the resource_priority_map
89
+ #
90
+ # @api private
91
+ # @param resource_priority_map [Chef::Platform::ResourcePriorityMap]
92
+ def set_resource_priority_map(resource_priority_map)
93
+ @resource_priority_map = resource_priority_map
94
+ end
95
+
96
+ # Sets the provider_priority_map
97
+ #
98
+ # @api private
99
+ # @param provider_priority_map [Chef::Platform::providerPriorityMap]
100
+ def set_provider_priority_map(provider_priority_map)
101
+ @provider_priority_map = provider_priority_map
102
+ end
103
+
104
+ # Sets the node object
105
+ #
106
+ # @api private
107
+ # @param node [Chef::Node]
108
+ def set_node(node)
109
+ @node = node
110
+ end
111
+
112
+ # Sets the run_context object
113
+ #
114
+ # @api private
115
+ # @param run_context [Chef::RunContext]
116
+ def set_run_context(run_context)
117
+ @run_context = run_context
118
+ end
119
+
120
+ # Resets the internal state
121
+ #
122
+ # @api private
123
+ def reset!
124
+ @run_context = nil
125
+ @node = nil
126
+ @provider_priority_map = nil
127
+ @resource_priority_map = nil
128
+ end
129
+ end
130
+ end
@@ -166,6 +166,13 @@ class Chef
166
166
  if new_runlist = args.delete(:runlist)
167
167
  @json_attribs["run_list"] = new_runlist
168
168
  end
169
+
170
+ # these slurp in the resource+provider world, so be exceedingly lazy about requiring them
171
+ require 'chef/platform/provider_priority_map' unless defined? Chef::Platform::ProviderPriorityMap
172
+ require 'chef/platform/resource_priority_map' unless defined? Chef::Platform::ResourcePriorityMap
173
+
174
+ Chef.set_provider_priority_map(Chef::Platform::ProviderPriorityMap.instance)
175
+ Chef.set_resource_priority_map(Chef::Platform::ResourcePriorityMap.instance)
169
176
  end
170
177
 
171
178
  def configure_formatters
@@ -224,21 +231,21 @@ class Chef
224
231
  end
225
232
 
226
233
  # Instantiates a Chef::Node object, possibly loading the node's prior state
227
- # when using chef-client. Delegates to policy_builder
228
- #
234
+ # when using chef-client. Delegates to policy_builder. Injects the built node
235
+ # into the Chef class.
229
236
  #
230
- # === Returns
231
- # Chef::Node:: The node object for this chef run
237
+ # @return [Chef::Node] The node object for this Chef run
232
238
  def load_node
233
239
  policy_builder.load_node
234
240
  @node = policy_builder.node
241
+ Chef.set_node(@node)
242
+ node
235
243
  end
236
244
 
237
245
  # Mutates the `node` object to prepare it for the chef run. Delegates to
238
246
  # policy_builder
239
247
  #
240
- # === Returns
241
- # Chef::Node:: The updated node object
248
+ # @return [Chef::Node] The updated node object
242
249
  def build_node
243
250
  policy_builder.build_node
244
251
  @run_status = Chef::RunStatus.new(node, events)
@@ -272,7 +279,8 @@ class Chef
272
279
  end
273
280
 
274
281
  def run_ohai
275
- ohai.all_plugins
282
+ filter = Chef::Config[:minimal_ohai] ? %w[fqdn machinename hostname platform platform_version os os_version] : nil
283
+ ohai.all_plugins(filter)
276
284
  @events.ohai_completed(node)
277
285
  end
278
286
 
@@ -305,6 +305,14 @@ class Chef
305
305
 
306
306
  default :pid_file, nil
307
307
 
308
+ # Whether Chef Zero local mode should bind to a port. All internal requests
309
+ # will go through the socketless code path regardless, so the socket is
310
+ # only needed if other processes will connect to the local mode server.
311
+ #
312
+ # For compatibility this is set to true but it will be changed to false in
313
+ # the future.
314
+ default :listen, true
315
+
308
316
  config_context :chef_zero do
309
317
  config_strict_mode true
310
318
  default(:enabled) { Chef::Config.local_mode }
@@ -333,6 +341,11 @@ class Chef
333
341
  # This can be removed when audit-mode is enabled by default.
334
342
  default :audit_mode, :disabled
335
343
 
344
+ # Chef only needs ohai to run the hostname plugin for the most basic
345
+ # functionality. If the rest of the ohai plugins are not needed (like in
346
+ # most of our testing scenarios)
347
+ default :minimal_ohai, false
348
+
336
349
  # Policyfile is an experimental feature where a node gets its run list and
337
350
  # cookbook version set from a single document on the server instead of
338
351
  # expanding the run list and having the server compute the cookbook version
@@ -88,15 +88,21 @@ class Chef
88
88
  #Exception message: %4
89
89
  #Exception backtrace: %5
90
90
  def run_failed(e)
91
+ data =
92
+ if @run_status
93
+ [@run_status.run_id,
94
+ @run_status.elapsed_time.to_s]
95
+ else
96
+ ["UNKNOWN", "UNKNOWN"]
97
+ end
98
+
91
99
  @eventlog.report_event(
92
100
  :event_type => ::Win32::EventLog::ERROR_TYPE,
93
101
  :source => SOURCE,
94
102
  :event_id => RUN_FAILED_EVENT_ID,
95
- :data => [@run_status.run_id,
96
- @run_status.elapsed_time.to_s,
97
- e.class.name,
98
- e.message,
99
- e.backtrace.join("\n")]
103
+ :data => data + [e.class.name,
104
+ e.message,
105
+ e.backtrace.join("\n")]
100
106
  )
101
107
  end
102
108
 
@@ -25,6 +25,7 @@ require 'tempfile'
25
25
  require 'net/https'
26
26
  require 'uri'
27
27
  require 'chef/http/basic_client'
28
+ require 'chef/http/socketless_chef_zero_client'
28
29
  require 'chef/monkey_patches/net_http'
29
30
  require 'chef/config'
30
31
  require 'chef/platform/query_helpers'
@@ -196,14 +197,18 @@ class Chef
196
197
 
197
198
  def http_client(base_url=nil)
198
199
  base_url ||= url
199
- BasicClient.new(base_url)
200
+ if chef_zero_uri?(base_url)
201
+ SocketlessChefZeroClient.new(base_url)
202
+ else
203
+ BasicClient.new(base_url, :ssl_policy => Chef::HTTP::APISSLPolicy)
204
+ end
200
205
  end
201
206
 
202
207
  protected
203
208
 
204
209
  def create_url(path)
205
210
  return path if path.is_a?(URI)
206
- if path =~ /^(http|https):\/\//i
211
+ if path =~ /^(http|https|chefzero):\/\//i
207
212
  URI.parse(path)
208
213
  elsif path.nil? or path.empty?
209
214
  URI.parse(@url)
@@ -292,7 +297,7 @@ class Chef
292
297
  http_attempts += 1
293
298
  response, request, return_value = yield
294
299
  # handle HTTP 50X Error
295
- if response.kind_of?(Net::HTTPServerError)
300
+ if response.kind_of?(Net::HTTPServerError) && !Chef::Config.local_mode
296
301
  if http_retry_count - http_attempts + 1 > 0
297
302
  sleep_time = 1 + (2 ** http_attempts) + rand(2 ** http_attempts)
298
303
  Chef::Log.error("Server returned error #{response.code} for #{url}, retrying #{http_attempts}/#{http_retry_count} in #{sleep_time}s")
@@ -351,6 +356,11 @@ class Chef
351
356
 
352
357
  private
353
358
 
359
+ def chef_zero_uri?(uri)
360
+ uri = URI.parse(uri) unless uri.respond_to?(:scheme)
361
+ uri.scheme == "chefzero"
362
+ end
363
+
354
364
  def redirected_to(response)
355
365
  return nil unless response.kind_of?(Net::HTTPRedirection)
356
366
  # Net::HTTPNotModified is undesired subclass of Net::HTTPRedirection so test for this
@@ -97,7 +97,9 @@ class Chef
97
97
 
98
98
  #adapted from buildr/lib/buildr/core/transports.rb
99
99
  def proxy_uri
100
- proxy = Chef::Config["#{url.scheme}_proxy"]
100
+ proxy = Chef::Config["#{url.scheme}_proxy"] ||
101
+ env["#{url.scheme.upcase}_PROXY"] || env["#{url.scheme}_proxy"]
102
+
101
103
  # Check if the proxy string contains a scheme. If not, add the url's scheme to the
102
104
  # proxy before parsing. The regex /^.*:\/\// matches, for example, http://.
103
105
  proxy = if proxy.match(/^.*:\/\//)
@@ -105,7 +107,8 @@ class Chef
105
107
  else
106
108
  URI.parse("#{url.scheme}://#{proxy}")
107
109
  end if String === proxy
108
- excludes = Chef::Config[:no_proxy].to_s.split(/\s*,\s*/).compact
110
+ no_proxy = Chef::Config[:no_proxy] || env['NO_PROXY'] || env['no_proxy']
111
+ excludes = no_proxy.to_s.split(/\s*,\s*/).compact
109
112
  excludes = excludes.map { |exclude| exclude =~ /:\d+$/ ? exclude : "#{exclude}:*" }
110
113
  return proxy unless excludes.any? { |exclude| File.fnmatch(exclude, "#{host}:#{port}") }
111
114
  end
@@ -126,18 +129,32 @@ class Chef
126
129
  Chef::Config
127
130
  end
128
131
 
132
+ def env
133
+ ENV
134
+ end
135
+
129
136
  def http_client_builder
130
137
  http_proxy = proxy_uri
131
138
  if http_proxy.nil?
132
139
  Net::HTTP
133
140
  else
134
141
  Chef::Log.debug("Using #{http_proxy.host}:#{http_proxy.port} for proxy")
135
- user = Chef::Config["#{url.scheme}_proxy_user"]
136
- pass = Chef::Config["#{url.scheme}_proxy_pass"]
142
+ user = http_proxy_user(http_proxy)
143
+ pass = http_proxy_pass(http_proxy)
137
144
  Net::HTTP.Proxy(http_proxy.host, http_proxy.port, user, pass)
138
145
  end
139
146
  end
140
147
 
148
+ def http_proxy_user(http_proxy)
149
+ http_proxy.user || Chef::Config["#{url.scheme}_proxy_user"] ||
150
+ env["#{url.scheme.upcase}_PROXY_USER"] || env["#{url.scheme}_proxy_user"]
151
+ end
152
+
153
+ def http_proxy_pass(http_proxy)
154
+ http_proxy.password || Chef::Config["#{url.scheme}_proxy_pass"] ||
155
+ env["#{url.scheme.upcase}_PROXY_PASS"] || env["#{url.scheme}_proxy_pass"]
156
+ end
157
+
141
158
  def configure_ssl(http_client)
142
159
  http_client.use_ssl = true
143
160
  ssl_policy.apply_to(http_client)
@@ -0,0 +1,207 @@
1
+ #--
2
+ # Author:: Daniel DeLeo (<dan@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # ---
19
+ # Some portions of the code in this file are verbatim copies of code from the
20
+ # fakeweb project: https://github.com/chrisk/fakeweb
21
+ #
22
+ # fakeweb is distributed under the MIT license, which is copied below:
23
+ # ---
24
+ #
25
+ # Copyright 2006-2010 Blaine Cook, Chris Kampmeier, and other contributors
26
+ #
27
+ # Permission is hereby granted, free of charge, to any person obtaining
28
+ # a copy of this software and associated documentation files (the
29
+ # "Software"), to deal in the Software without restriction, including
30
+ # without limitation the rights to use, copy, modify, merge, publish,
31
+ # distribute, sublicense, and/or sell copies of the Software, and to
32
+ # permit persons to whom the Software is furnished to do so, subject to
33
+ # the following conditions:
34
+
35
+ # The above copyright notice and this permission notice shall be
36
+ # included in all copies or substantial portions of the Software.
37
+
38
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
39
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
40
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
41
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
42
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
43
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
44
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
45
+
46
+ require 'chef_zero/server'
47
+
48
+ class Chef
49
+ class HTTP
50
+
51
+ # HTTP Client class that talks directly to Zero via the Rack interface.
52
+ class SocketlessChefZeroClient
53
+
54
+ # This module is extended into Net::HTTP Response objects created from
55
+ # Socketless Chef Zero responses.
56
+ module ResponseExts
57
+
58
+ # Net::HTTP raises an error if #read_body is called with a block or
59
+ # file argument after the body has already been read from the network.
60
+ #
61
+ # Since we always set the body to the string response from Chef Zero
62
+ # and set the `@read` indicator variable, we have to patch this method
63
+ # or else streaming-style responses won't work.
64
+ def read_body(dest = nil, &block)
65
+ if dest
66
+ raise "responses from socketless chef zero can't be written to specific destination"
67
+ end
68
+
69
+ if block_given?
70
+ block.call(@body)
71
+ else
72
+ super
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ attr_reader :url
79
+
80
+ # copied verbatim from webrick (2-clause BSD License)
81
+ #
82
+ # HTTP status codes and descriptions
83
+ STATUS_MESSAGE = {
84
+ 100 => 'Continue',
85
+ 101 => 'Switching Protocols',
86
+ 200 => 'OK',
87
+ 201 => 'Created',
88
+ 202 => 'Accepted',
89
+ 203 => 'Non-Authoritative Information',
90
+ 204 => 'No Content',
91
+ 205 => 'Reset Content',
92
+ 206 => 'Partial Content',
93
+ 207 => 'Multi-Status',
94
+ 300 => 'Multiple Choices',
95
+ 301 => 'Moved Permanently',
96
+ 302 => 'Found',
97
+ 303 => 'See Other',
98
+ 304 => 'Not Modified',
99
+ 305 => 'Use Proxy',
100
+ 307 => 'Temporary Redirect',
101
+ 400 => 'Bad Request',
102
+ 401 => 'Unauthorized',
103
+ 402 => 'Payment Required',
104
+ 403 => 'Forbidden',
105
+ 404 => 'Not Found',
106
+ 405 => 'Method Not Allowed',
107
+ 406 => 'Not Acceptable',
108
+ 407 => 'Proxy Authentication Required',
109
+ 408 => 'Request Timeout',
110
+ 409 => 'Conflict',
111
+ 410 => 'Gone',
112
+ 411 => 'Length Required',
113
+ 412 => 'Precondition Failed',
114
+ 413 => 'Request Entity Too Large',
115
+ 414 => 'Request-URI Too Large',
116
+ 415 => 'Unsupported Media Type',
117
+ 416 => 'Request Range Not Satisfiable',
118
+ 417 => 'Expectation Failed',
119
+ 422 => 'Unprocessable Entity',
120
+ 423 => 'Locked',
121
+ 424 => 'Failed Dependency',
122
+ 426 => 'Upgrade Required',
123
+ 428 => 'Precondition Required',
124
+ 429 => 'Too Many Requests',
125
+ 431 => 'Request Header Fields Too Large',
126
+ 500 => 'Internal Server Error',
127
+ 501 => 'Not Implemented',
128
+ 502 => 'Bad Gateway',
129
+ 503 => 'Service Unavailable',
130
+ 504 => 'Gateway Timeout',
131
+ 505 => 'HTTP Version Not Supported',
132
+ 507 => 'Insufficient Storage',
133
+ 511 => 'Network Authentication Required',
134
+ }
135
+
136
+ STATUS_MESSAGE.values.each {|v| v.freeze }
137
+ STATUS_MESSAGE.freeze
138
+
139
+ def initialize(base_url)
140
+ @url = base_url
141
+ end
142
+
143
+ def host
144
+ @url.hostname
145
+ end
146
+
147
+ def port
148
+ @url.port
149
+ end
150
+
151
+ def request(method, url, body, headers, &handler_block)
152
+ request = req_to_rack(method, url, body, headers)
153
+ res = ChefZero::SocketlessServerMap.request(port, request)
154
+
155
+ net_http_response = to_net_http(res[0], res[1], res[2])
156
+
157
+ yield net_http_response if block_given?
158
+
159
+ [self, net_http_response]
160
+ end
161
+
162
+ def req_to_rack(method, url, body, headers)
163
+ body_str = body || ""
164
+ {
165
+ "SCRIPT_NAME" => "",
166
+ "SERVER_NAME" => "localhost",
167
+ "REQUEST_METHOD" => method.to_s.upcase,
168
+ "PATH_INFO" => url.path,
169
+ "QUERY_STRING" => url.query,
170
+ "SERVER_PORT" => url.port,
171
+ "HTTP_HOST" => "localhost:#{url.port}",
172
+ "rack.url_scheme" => "chefzero",
173
+ "rack.input" => StringIO.new(body_str),
174
+ }
175
+ end
176
+
177
+ def to_net_http(code, headers, chunked_body)
178
+ body = chunked_body.join('')
179
+ msg = STATUS_MESSAGE[code] or raise "Cannot determine HTTP status message for code #{code}"
180
+ response = Net::HTTPResponse.send(:response_class, code.to_s).new("1.0", code.to_s, msg)
181
+ response.instance_variable_set(:@body, body)
182
+ headers.each do |name, value|
183
+ if value.respond_to?(:each)
184
+ value.each { |v| response.add_field(name, v) }
185
+ else
186
+ response[name] = value
187
+ end
188
+ end
189
+
190
+ response.instance_variable_set(:@read, true)
191
+ response.extend(ResponseExts)
192
+ response
193
+ end
194
+
195
+ private
196
+
197
+ def headers_extracted_from_options
198
+ options.reject {|name, _| KNOWN_OPTIONS.include?(name) }.map { |name, value|
199
+ [name.to_s.split("_").map { |segment| segment.capitalize }.join("-"), value]
200
+ }
201
+ end
202
+
203
+
204
+ end
205
+
206
+ end
207
+ end