chef-zero 15.0.17 → 15.0.21

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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +36 -31
  3. data/LICENSE +201 -201
  4. data/Rakefile +73 -68
  5. data/bin/chef-zero +111 -111
  6. data/chef-zero.gemspec +34 -33
  7. data/lib/chef_zero/chef_data/acl_path.rb +140 -140
  8. data/lib/chef_zero/chef_data/cookbook_data.rb +237 -237
  9. data/lib/chef_zero/chef_data/data_normalizer.rb +276 -276
  10. data/lib/chef_zero/chef_data/default_creator.rb +476 -476
  11. data/lib/chef_zero/data_store/data_already_exists_error.rb +29 -29
  12. data/lib/chef_zero/data_store/data_error.rb +32 -32
  13. data/lib/chef_zero/data_store/data_not_found_error.rb +29 -29
  14. data/lib/chef_zero/data_store/default_facade.rb +143 -147
  15. data/lib/chef_zero/data_store/interface_v1.rb +67 -67
  16. data/lib/chef_zero/data_store/interface_v2.rb +18 -18
  17. data/lib/chef_zero/data_store/memory_store.rb +33 -33
  18. data/lib/chef_zero/data_store/memory_store_v2.rb +159 -159
  19. data/lib/chef_zero/data_store/raw_file_store.rb +143 -143
  20. data/lib/chef_zero/data_store/v1_to_v2_adapter.rb +150 -150
  21. data/lib/chef_zero/data_store/v2_to_v1_adapter.rb +105 -105
  22. data/lib/chef_zero/dist.rb +9 -9
  23. data/lib/chef_zero/endpoints/acl_endpoint.rb +39 -39
  24. data/lib/chef_zero/endpoints/acls_endpoint.rb +41 -41
  25. data/lib/chef_zero/endpoints/actor_default_key_endpoint.rb +78 -78
  26. data/lib/chef_zero/endpoints/actor_endpoint.rb +184 -184
  27. data/lib/chef_zero/endpoints/actor_key_endpoint.rb +62 -62
  28. data/lib/chef_zero/endpoints/actor_keys_endpoint.rb +129 -129
  29. data/lib/chef_zero/endpoints/actors_endpoint.rb +104 -104
  30. data/lib/chef_zero/endpoints/authenticate_user_endpoint.rb +32 -32
  31. data/lib/chef_zero/endpoints/container_endpoint.rb +22 -22
  32. data/lib/chef_zero/endpoints/containers_endpoint.rb +25 -25
  33. data/lib/chef_zero/endpoints/controls_endpoint.rb +16 -16
  34. data/lib/chef_zero/endpoints/cookbook_artifact_endpoint.rb +24 -24
  35. data/lib/chef_zero/endpoints/cookbook_artifact_identifier_endpoint.rb +68 -68
  36. data/lib/chef_zero/endpoints/cookbook_artifacts_endpoint.rb +34 -34
  37. data/lib/chef_zero/endpoints/cookbook_endpoint.rb +39 -39
  38. data/lib/chef_zero/endpoints/cookbook_version_endpoint.rb +136 -136
  39. data/lib/chef_zero/endpoints/cookbooks_base.rb +80 -80
  40. data/lib/chef_zero/endpoints/cookbooks_endpoint.rb +19 -19
  41. data/lib/chef_zero/endpoints/data_bag_endpoint.rb +45 -45
  42. data/lib/chef_zero/endpoints/data_bag_item_endpoint.rb +25 -25
  43. data/lib/chef_zero/endpoints/data_bags_endpoint.rb +23 -23
  44. data/lib/chef_zero/endpoints/dummy_endpoint.rb +29 -29
  45. data/lib/chef_zero/endpoints/environment_cookbook_endpoint.rb +24 -24
  46. data/lib/chef_zero/endpoints/environment_cookbook_versions_endpoint.rb +126 -126
  47. data/lib/chef_zero/endpoints/environment_cookbooks_endpoint.rb +22 -22
  48. data/lib/chef_zero/endpoints/environment_endpoint.rb +33 -33
  49. data/lib/chef_zero/endpoints/environment_nodes_endpoint.rb +23 -23
  50. data/lib/chef_zero/endpoints/environment_recipes_endpoint.rb +22 -22
  51. data/lib/chef_zero/endpoints/environment_role_endpoint.rb +36 -36
  52. data/lib/chef_zero/endpoints/file_store_file_endpoint.rb +22 -22
  53. data/lib/chef_zero/endpoints/group_endpoint.rb +20 -20
  54. data/lib/chef_zero/endpoints/groups_endpoint.rb +13 -13
  55. data/lib/chef_zero/endpoints/license_endpoint.rb +25 -25
  56. data/lib/chef_zero/endpoints/node_endpoint.rb +34 -34
  57. data/lib/chef_zero/endpoints/node_identifiers_endpoint.rb +22 -22
  58. data/lib/chef_zero/endpoints/nodes_endpoint.rb +34 -34
  59. data/lib/chef_zero/endpoints/not_found_endpoint.rb +11 -11
  60. data/lib/chef_zero/endpoints/organization_association_request_endpoint.rb +22 -22
  61. data/lib/chef_zero/endpoints/organization_association_requests_endpoint.rb +30 -30
  62. data/lib/chef_zero/endpoints/organization_authenticate_user_endpoint.rb +26 -26
  63. data/lib/chef_zero/endpoints/organization_endpoint.rb +47 -47
  64. data/lib/chef_zero/endpoints/organization_user_base.rb +15 -15
  65. data/lib/chef_zero/endpoints/organization_user_default_key_endpoint.rb +16 -16
  66. data/lib/chef_zero/endpoints/organization_user_endpoint.rb +26 -26
  67. data/lib/chef_zero/endpoints/organization_user_key_endpoint.rb +17 -17
  68. data/lib/chef_zero/endpoints/organization_user_keys_endpoint.rb +17 -17
  69. data/lib/chef_zero/endpoints/organization_users_endpoint.rb +43 -43
  70. data/lib/chef_zero/endpoints/organization_validator_key_endpoint.rb +20 -20
  71. data/lib/chef_zero/endpoints/organizations_endpoint.rb +61 -61
  72. data/lib/chef_zero/endpoints/policies_endpoint.rb +26 -26
  73. data/lib/chef_zero/endpoints/policy_endpoint.rb +24 -24
  74. data/lib/chef_zero/endpoints/policy_group_endpoint.rb +46 -46
  75. data/lib/chef_zero/endpoints/policy_group_policy_endpoint.rb +83 -83
  76. data/lib/chef_zero/endpoints/policy_groups_endpoint.rb +38 -38
  77. data/lib/chef_zero/endpoints/policy_revision_endpoint.rb +66 -66
  78. data/lib/chef_zero/endpoints/policy_revisions_endpoint.rb +15 -15
  79. data/lib/chef_zero/endpoints/principal_endpoint.rb +55 -55
  80. data/lib/chef_zero/endpoints/rest_list_endpoint.rb +42 -42
  81. data/lib/chef_zero/endpoints/rest_object_endpoint.rb +78 -78
  82. data/lib/chef_zero/endpoints/role_endpoint.rb +16 -16
  83. data/lib/chef_zero/endpoints/role_environments_endpoint.rb +14 -14
  84. data/lib/chef_zero/endpoints/sandbox_endpoint.rb +27 -27
  85. data/lib/chef_zero/endpoints/sandboxes_endpoint.rb +51 -51
  86. data/lib/chef_zero/endpoints/search_endpoint.rb +208 -208
  87. data/lib/chef_zero/endpoints/searches_endpoint.rb +18 -18
  88. data/lib/chef_zero/endpoints/server_api_version_endpoint.rb +14 -14
  89. data/lib/chef_zero/endpoints/system_recovery_endpoint.rb +30 -30
  90. data/lib/chef_zero/endpoints/universe_endpoint.rb +15 -15
  91. data/lib/chef_zero/endpoints/user_association_request_endpoint.rb +41 -41
  92. data/lib/chef_zero/endpoints/user_association_requests_count_endpoint.rb +19 -19
  93. data/lib/chef_zero/endpoints/user_association_requests_endpoint.rb +19 -19
  94. data/lib/chef_zero/endpoints/user_organizations_endpoint.rb +22 -22
  95. data/lib/chef_zero/endpoints/version_endpoint.rb +13 -13
  96. data/lib/chef_zero/log.rb +7 -7
  97. data/lib/chef_zero/rest_base.rb +332 -332
  98. data/lib/chef_zero/rest_error_response.rb +11 -11
  99. data/lib/chef_zero/rest_request.rb +84 -88
  100. data/lib/chef_zero/rest_router.rb +72 -72
  101. data/lib/chef_zero/rspec.rb +355 -355
  102. data/lib/chef_zero/server.rb +730 -730
  103. data/lib/chef_zero/socketless_server_map.rb +92 -93
  104. data/lib/chef_zero/solr/query/binary_operator.rb +52 -52
  105. data/lib/chef_zero/solr/query/phrase.rb +23 -23
  106. data/lib/chef_zero/solr/query/range_query.rb +46 -46
  107. data/lib/chef_zero/solr/query/regexpable_query.rb +30 -30
  108. data/lib/chef_zero/solr/query/subquery.rb +37 -37
  109. data/lib/chef_zero/solr/query/term.rb +45 -45
  110. data/lib/chef_zero/solr/query/unary_operator.rb +41 -41
  111. data/lib/chef_zero/solr/solr_doc.rb +53 -53
  112. data/lib/chef_zero/solr/solr_parser.rb +208 -208
  113. data/lib/chef_zero/version.rb +3 -3
  114. data/lib/chef_zero.rb +10 -10
  115. data/spec/run_oc_pedant.rb +226 -226
  116. data/spec/search_spec.rb +36 -36
  117. data/spec/server_spec.rb +96 -96
  118. data/spec/socketless_server_map_spec.rb +74 -74
  119. data/spec/support/oc_pedant.rb +149 -149
  120. data/spec/support/secrets.json +6 -6
  121. data/spec/support/stickywicket.pem +27 -27
  122. metadata +35 -18
@@ -1,730 +1,730 @@
1
- #
2
- # Author:: John Keiser (<jkeiser@opscode.com>)
3
- # Copyright:: Copyright (c) 2012-2017, 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
- # https://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
- require "openssl" unless defined?(OpenSSL)
20
- require "open-uri" unless defined?(OpenURI)
21
- require "rubygems" unless defined?(Gem)
22
- require "timeout" unless defined?(Timeout)
23
- require "stringio" unless defined?(StringIO)
24
-
25
- require "rack" unless defined?(Rack)
26
- require "rackup" unless defined?(Rackup)
27
- require "webrick" unless defined?(WEBrick)
28
- require "webrick/https"
29
-
30
- require_relative "../chef_zero"
31
- require_relative "socketless_server_map"
32
- require_relative "chef_data/cookbook_data"
33
- require_relative "chef_data/acl_path"
34
- require_relative "rest_router"
35
- require_relative "data_store/memory_store_v2"
36
- require_relative "data_store/v1_to_v2_adapter"
37
- require_relative "data_store/default_facade"
38
- require_relative "version"
39
- require "chef_zero/dist.rb"
40
-
41
- require_relative "endpoints/rest_list_endpoint"
42
- require_relative "endpoints/authenticate_user_endpoint"
43
- require_relative "endpoints/acls_endpoint"
44
- require_relative "endpoints/acl_endpoint"
45
- require_relative "endpoints/actor_endpoint"
46
- require_relative "endpoints/actors_endpoint"
47
- require_relative "endpoints/actor_key_endpoint"
48
- require_relative "endpoints/organization_user_key_endpoint"
49
- require_relative "endpoints/organization_user_default_key_endpoint"
50
- require_relative "endpoints/organization_user_keys_endpoint"
51
- require_relative "endpoints/actor_default_key_endpoint"
52
- require_relative "endpoints/actor_keys_endpoint"
53
- require_relative "endpoints/cookbooks_endpoint"
54
- require_relative "endpoints/cookbook_endpoint"
55
- require_relative "endpoints/cookbook_version_endpoint"
56
- require_relative "endpoints/cookbook_artifacts_endpoint"
57
- require_relative "endpoints/cookbook_artifact_endpoint"
58
- require_relative "endpoints/cookbook_artifact_identifier_endpoint"
59
- require_relative "endpoints/containers_endpoint"
60
- require_relative "endpoints/container_endpoint"
61
- require_relative "endpoints/controls_endpoint"
62
- require_relative "endpoints/dummy_endpoint"
63
- require_relative "endpoints/data_bags_endpoint"
64
- require_relative "endpoints/data_bag_endpoint"
65
- require_relative "endpoints/data_bag_item_endpoint"
66
- require_relative "endpoints/groups_endpoint"
67
- require_relative "endpoints/group_endpoint"
68
- require_relative "endpoints/environment_endpoint"
69
- require_relative "endpoints/environment_cookbooks_endpoint"
70
- require_relative "endpoints/environment_cookbook_endpoint"
71
- require_relative "endpoints/environment_cookbook_versions_endpoint"
72
- require_relative "endpoints/environment_nodes_endpoint"
73
- require_relative "endpoints/environment_recipes_endpoint"
74
- require_relative "endpoints/environment_role_endpoint"
75
- require_relative "endpoints/license_endpoint"
76
- require_relative "endpoints/node_endpoint"
77
- require_relative "endpoints/nodes_endpoint"
78
- require_relative "endpoints/node_identifiers_endpoint"
79
- require_relative "endpoints/organizations_endpoint"
80
- require_relative "endpoints/organization_endpoint"
81
- require_relative "endpoints/organization_association_requests_endpoint"
82
- require_relative "endpoints/organization_association_request_endpoint"
83
- require_relative "endpoints/organization_authenticate_user_endpoint"
84
- require_relative "endpoints/organization_users_endpoint"
85
- require_relative "endpoints/organization_user_endpoint"
86
- require_relative "endpoints/organization_validator_key_endpoint"
87
- require_relative "endpoints/policies_endpoint"
88
- require_relative "endpoints/policy_endpoint"
89
- require_relative "endpoints/policy_revisions_endpoint"
90
- require_relative "endpoints/policy_revision_endpoint"
91
- require_relative "endpoints/policy_groups_endpoint"
92
- require_relative "endpoints/policy_group_endpoint"
93
- require_relative "endpoints/policy_group_policy_endpoint"
94
- require_relative "endpoints/principal_endpoint"
95
- require_relative "endpoints/role_endpoint"
96
- require_relative "endpoints/role_environments_endpoint"
97
- require_relative "endpoints/sandboxes_endpoint"
98
- require_relative "endpoints/sandbox_endpoint"
99
- require_relative "endpoints/searches_endpoint"
100
- require_relative "endpoints/search_endpoint"
101
- require_relative "endpoints/system_recovery_endpoint"
102
- require_relative "endpoints/user_association_requests_endpoint"
103
- require_relative "endpoints/user_association_requests_count_endpoint"
104
- require_relative "endpoints/user_association_request_endpoint"
105
- require_relative "endpoints/user_organizations_endpoint"
106
- require_relative "endpoints/file_store_file_endpoint"
107
- require_relative "endpoints/not_found_endpoint"
108
- require_relative "endpoints/version_endpoint"
109
- require_relative "endpoints/server_api_version_endpoint"
110
- require_relative "endpoints/universe_endpoint"
111
-
112
- module ChefZero
113
-
114
- class Server
115
-
116
- DEFAULT_OPTIONS = {
117
- host: ["127.0.0.1"],
118
- port: 8889,
119
- log_level: :warn,
120
- generate_real_keys: true,
121
- single_org: "chef",
122
- ssl: false,
123
- }.freeze
124
-
125
- GLOBAL_ENDPOINTS = [
126
- "/license",
127
- "/version",
128
- "/server_api_version",
129
- ].freeze
130
-
131
- def initialize(options = {})
132
- @options = DEFAULT_OPTIONS.merge(options)
133
- if @options[:single_org] && !@options.key?(:osc_compat)
134
- @options[:osc_compat] = true
135
- end
136
- @options.freeze
137
- ChefZero::Log.level = @options[:log_level].to_sym
138
- @app = nil
139
- end
140
-
141
- # @return [Hash]
142
- attr_reader :options
143
-
144
- # @return [Integer]
145
- def port
146
- if @port
147
- @port
148
- # If options[:port] is not an Array or an Enumerable, it is just an Integer.
149
- elsif !options[:port].respond_to?(:each)
150
- options[:port]
151
- else
152
- raise "port cannot be determined until server is started"
153
- end
154
- end
155
-
156
- # @return [WEBrick::HTTPServer]
157
- attr_reader :server
158
-
159
- include ChefZero::Endpoints
160
-
161
- #
162
- # The URL for this Chef Zero server. If the given host is an IPV6 address,
163
- # it is escaped in brackets according to RFC-2732.
164
- #
165
- # @see http://www.ietf.org/rfc/rfc2732.txt RFC-2732
166
- #
167
- # @return [String]
168
- #
169
- def url
170
- sch = @options[:ssl] ? "https" : "http"
171
- hosts = Array(@options[:host])
172
- @url ||= if hosts.first.include?(":")
173
- URI("#{sch}://[#{hosts.first}]:#{port}").to_s
174
- else
175
- URI("#{sch}://#{hosts.first}:#{port}").to_s
176
- end
177
- end
178
-
179
- def local_mode_url
180
- raise "Port not yet set, cannot generate URL" unless port.is_a?(Integer)
181
-
182
- "chefzero://localhost:#{port}"
183
- end
184
-
185
- #
186
- # The data store for this server (default is in-memory).
187
- #
188
- # @return [ChefZero::DataStore]
189
- #
190
- def data_store
191
- @data_store ||= begin
192
- result = @options[:data_store] || DataStore::DefaultFacade.new(DataStore::MemoryStoreV2.new, options[:single_org], options[:osc_compat])
193
- if options[:single_org]
194
-
195
- if !result.respond_to?(:interface_version) || result.interface_version == 1
196
- result = ChefZero::DataStore::V1ToV2Adapter.new(result, options[:single_org])
197
- result = ChefZero::DataStore::DefaultFacade.new(result, options[:single_org], options[:osc_compat])
198
- end
199
-
200
- else
201
- if !result.respond_to?(:interface_version) || result.interface_version == 1
202
- raise "Multi-org not supported by data store #{result}!"
203
- end
204
- end
205
-
206
- result
207
- end
208
- end
209
-
210
- #
211
- # Boolean method to determine if real Public/Private keys should be
212
- # generated.
213
- #
214
- # @return [Boolean]
215
- # true if real keys should be created, false otherwise
216
- #
217
- def generate_real_keys?
218
- !!@options[:generate_real_keys]
219
- end
220
-
221
- #
222
- # Start a Chef Zero server in the current thread. You can stop this server
223
- # by canceling the current thread.
224
- #
225
- # @param [Boolean|IO] publish
226
- # publish the server information to the publish parameter or to STDOUT if it's "true"
227
- #
228
- # @return [nil]
229
- # this method will block the main thread until interrupted
230
- #
231
- def start(publish = true)
232
- publish = publish[:publish] if publish.is_a?(Hash) # Legacy API
233
-
234
- if publish
235
- output = publish.respond_to?(:puts) ? publish : STDOUT
236
- output.puts <<-EOH.gsub(/^ {10}/, "")
237
- >> Starting #{ChefZero::Dist::PRODUCT} (v#{ChefZero::VERSION})...
238
- EOH
239
- end
240
-
241
- thread = start_background
242
-
243
- if publish
244
- output = publish.respond_to?(:puts) ? publish : STDOUT
245
- output.puts <<-EOH.gsub(/^ {10}/, "")
246
- >> WEBrick (v#{WEBrick::VERSION}) on Rack (v#{Rack.release}) is listening at #{url}
247
- >> Press CTRL+C to stop
248
-
249
- EOH
250
- end
251
-
252
- %w{INT TERM}.each do |signal|
253
- Signal.trap(signal) do
254
- puts "\n>> Stopping #{ChefZero::Dist::PRODUCT}..."
255
- @server.shutdown
256
- end
257
- end
258
-
259
- # Move the background process to the main thread
260
- thread.join
261
- end
262
-
263
- #
264
- # Start a Chef Zero server in a forked process. This method returns the PID
265
- # to the forked process.
266
- #
267
- # @param [Fixnum] wait
268
- # the number of seconds to wait for the server to start
269
- #
270
- # @return [Thread]
271
- # the thread the background process is running in
272
- #
273
- def listen(hosts, port)
274
- hosts.each do |host|
275
- @server.listen(host, port)
276
- end
277
- true
278
- rescue Errno::EADDRINUSE
279
- ChefZero::Log.warn("Port #{port} not available")
280
- @server.listeners.each(&:close)
281
- @server.listeners.clear
282
- false
283
- end
284
-
285
- def start_background(wait = 5)
286
- @server = WEBrick::HTTPServer.new(
287
- DoNotListen: true,
288
- AccessLog: [],
289
- Logger: WEBrick::Log.new(StringIO.new, 7),
290
- RequestTimeout: 300,
291
- SSLEnable: options[:ssl],
292
- SSLOptions: ssl_opts,
293
- SSLCertName: [ [ "CN", WEBrick::Utils.getservername ] ],
294
- StartCallback: proc do
295
- @running = true
296
- end
297
- )
298
- ENV["HTTPS"] = "on" if options[:ssl]
299
- @server.mount("/", Rackup::Handler::WEBrick, app)
300
-
301
- # Pick a port
302
- # If options[:port] can be an Enumerator, an Array, or an Integer,
303
- # we need something that can respond to .each (Enum and Array can already).
304
- Array(options[:port]).each do |port|
305
- if listen(Array(options[:host]), port)
306
- @port = port
307
- break
308
- end
309
- end
310
- unless @port
311
- raise Errno::EADDRINUSE,
312
- "No port in :port range #{options[:port]} is available"
313
- end
314
-
315
- # Start the server in the background
316
- @thread = Thread.new do
317
- begin
318
- Thread.current.abort_on_exception = true
319
- @server.start
320
- ensure
321
- @port = nil
322
- @running = false
323
- end
324
- end
325
-
326
- # Do not return until the web server is genuinely started.
327
- sleep(0.01) while !@running && @thread.alive?
328
-
329
- SocketlessServerMap.instance.register_port(@port, self)
330
-
331
- @thread
332
- end
333
-
334
- def start_socketless
335
- @port = SocketlessServerMap.instance.register_no_listen_server(self)
336
- end
337
-
338
- def handle_socketless_request(request_env)
339
- app.call(request_env)
340
- end
341
-
342
- #
343
- # Boolean method to determine if the server is currently ready to accept
344
- # requests. This method will attempt to make an HTTP request against the
345
- # server. If this method returns true, you are safe to make a request.
346
- #
347
- # @return [Boolean]
348
- # true if the server is accepting requests, false otherwise
349
- #
350
- def running?
351
- !@server.nil? && @running && @server.status == :Running
352
- end
353
-
354
- #
355
- # Gracefully stop the Chef Zero server.
356
- #
357
- # @param [Fixnum] wait
358
- # the number of seconds to wait before raising force-terminating the
359
- # server
360
- #
361
- def stop(wait = 5)
362
- if @running
363
- @server.shutdown if @server
364
- @thread.join(wait) if @thread
365
- end
366
- rescue Timeout::Error
367
- if @thread
368
- ChefZero::Log.error("#{ChefZero::Dist::PRODUCT} did not stop within #{wait} seconds! Killing...")
369
- @thread.kill
370
- SocketlessServerMap.deregister(port)
371
- end
372
- ensure
373
- @server = nil
374
- @thread = nil
375
- end
376
-
377
- def gen_key_pair
378
- if generate_real_keys?
379
- private_key = OpenSSL::PKey::RSA.new(2048)
380
- public_key = private_key.public_key.to_s
381
- public_key.sub!(/^-----BEGIN RSA PUBLIC KEY-----/, "-----BEGIN PUBLIC KEY-----")
382
- public_key.sub!(/-----END RSA PUBLIC KEY-----(\s+)$/, '-----END PUBLIC KEY-----\1')
383
- [private_key.to_s, public_key]
384
- else
385
- [PRIVATE_KEY, PUBLIC_KEY]
386
- end
387
- end
388
-
389
- def on_request(&block)
390
- @on_request_proc = block
391
- end
392
-
393
- def on_response(&block)
394
- @on_response_proc = block
395
- end
396
-
397
- # Load data in a nice, friendly form:
398
- # {
399
- # 'roles' => {
400
- # 'desert' => '{ "description": "Hot and dry"' },
401
- # 'rainforest' => { "description" => 'Wet and humid' }
402
- # },
403
- # 'cookbooks' => {
404
- # 'apache2-1.0.1' => {
405
- # 'templates' => { 'default' => { 'blah.txt' => 'hi' }}
406
- # 'recipes' => { 'default.rb' => 'template "blah.txt"' }
407
- # 'metadata.rb' => 'depends "mysql"'
408
- # },
409
- # 'apache2-1.2.0' => {
410
- # 'templates' => { 'default' => { 'blah.txt' => 'lo' }}
411
- # 'recipes' => { 'default.rb' => 'template "blah.txt"' }
412
- # 'metadata.rb' => 'depends "mysql"'
413
- # },
414
- # 'mysql' => {
415
- # 'recipes' => { 'default.rb' => 'file { contents "hi" }' },
416
- # 'metadata.rb' => 'version "1.0.0"'
417
- # }
418
- # }
419
- # }
420
- def load_data(contents, org_name = nil)
421
- org_name ||= options[:single_org]
422
- if org_name.nil? && contents.keys != [ "users" ]
423
- raise "Must pass an org name to load_data or run in single_org mode"
424
- end
425
-
426
- %w{clients containers environments groups nodes roles sandboxes}.each do |data_type|
427
- if contents[data_type]
428
- dejsonize_children(contents[data_type]).each_pair do |name, data|
429
- data_store.set(["organizations", org_name, data_type, name], data, :create)
430
- end
431
- end
432
- end
433
-
434
- if contents["users"]
435
- dejsonize_children(contents["users"]).each_pair do |name, data|
436
- if options[:osc_compat]
437
- data_store.set(["organizations", org_name, "users", name], data, :create)
438
- else
439
- # Create the user and put them in the org
440
- data_store.set(["users", name], data, :create)
441
- if org_name
442
- data_store.set(["organizations", org_name, "users", name], "{}", :create)
443
- end
444
- end
445
- end
446
- end
447
-
448
- if contents["members"]
449
- contents["members"].each do |name|
450
- data_store.set(["organizations", org_name, "users", name], "{}", :create)
451
- end
452
- end
453
-
454
- if contents["invites"]
455
- contents["invites"].each do |name|
456
- data_store.set(["organizations", org_name, "association_requests", name], "{}", :create)
457
- end
458
- end
459
-
460
- if contents["acls"]
461
- dejsonize_children(contents["acls"]).each do |path, acl|
462
- path = [ "organizations", org_name ] + path.split("/")
463
- path = ChefData::AclPath.get_acl_data_path(path)
464
- ChefZero::RSpec.server.data_store.set(path, acl)
465
- end
466
- end
467
-
468
- if contents["data"]
469
- contents["data"].each_pair do |key, data_bag|
470
- data_store.create_dir(["organizations", org_name, "data"], key, :recursive)
471
- dejsonize_children(data_bag).each do |item_name, item|
472
- data_store.set(["organizations", org_name, "data", key, item_name], item, :create)
473
- end
474
- end
475
- end
476
-
477
- if contents["policies"]
478
- contents["policies"].each_pair do |policy_name, policy_struct|
479
- # data_store.create_dir(['organizations', org_name, 'policies', policy_name], "revisions", :recursive)
480
- dejsonize_children(policy_struct).each do |revision, policy_data|
481
- data_store.set(["organizations", org_name, "policies", policy_name,
482
- "revisions", revision], policy_data, :create, :create_dir)
483
- end
484
- end
485
- end
486
-
487
- if contents["policy_groups"]
488
- contents["policy_groups"].each_pair do |group_name, group|
489
- group["policies"].each do |policy_name, policy_revision|
490
- data_store.set(["organizations", org_name, "policy_groups", group_name, "policies", policy_name], FFI_Yajl::Encoder.encode(policy_revision["revision_id"], pretty: true), :create, :create_dir)
491
- end
492
- end
493
- end
494
-
495
- %w{cookbooks cookbook_artifacts}.each do |cookbook_type|
496
- if contents[cookbook_type]
497
- contents[cookbook_type].each_pair do |name_version, cookbook|
498
- if cookbook_type == "cookbook_artifacts"
499
- name, _, identifier = name_version.rpartition("-")
500
- cookbook_data = ChefData::CookbookData.to_hash(cookbook, name, identifier)
501
- elsif name_version =~ /(.+)-(\d+\.\d+\.\d+)$/
502
- cookbook_data = ChefData::CookbookData.to_hash(cookbook, $1, $2)
503
- else
504
- cookbook_data = ChefData::CookbookData.to_hash(cookbook, name_version)
505
- end
506
- raise "No version specified" unless cookbook_data[:version]
507
-
508
- data_store.create_dir(["organizations", org_name, cookbook_type], cookbook_data[:cookbook_name], :recursive)
509
- data_store.set(["organizations", org_name, cookbook_type, cookbook_data[:cookbook_name], cookbook_data[:version]], FFI_Yajl::Encoder.encode(cookbook_data, pretty: true), :create)
510
- cookbook_data.values.each do |files|
511
- next unless files.is_a? Array
512
-
513
- files.each do |file|
514
- data_store.set(["organizations", org_name, "file_store", "checksums", file[:checksum]], get_file(cookbook, file[:path]), :create)
515
- end
516
- end
517
- end
518
- end
519
- end
520
- end
521
-
522
- def clear_data
523
- data_store.clear
524
- end
525
-
526
- def request_handler(&block)
527
- @request_handler = block
528
- end
529
-
530
- def to_s
531
- "#<#{self.class} #{url}>"
532
- end
533
-
534
- def inspect
535
- "#<#{self.class} @url=#{url.inspect}>"
536
- end
537
-
538
- private
539
-
540
- def endpoints
541
- result = if options[:osc_compat]
542
- # OSC-only
543
- [
544
- [ "/organizations/*/users", ActorsEndpoint.new(self) ],
545
- [ "/organizations/*/users/*", ActorEndpoint.new(self) ],
546
- [ "/organizations/*/authenticate_user", OrganizationAuthenticateUserEndpoint.new(self) ],
547
- ]
548
- else
549
- # EC-only
550
- [
551
- [ "/organizations/*/users", OrganizationUsersEndpoint.new(self) ],
552
- [ "/organizations/*/users/*", OrganizationUserEndpoint.new(self) ],
553
- [ "/users", ActorsEndpoint.new(self, "username") ],
554
- [ "/users/*", ActorEndpoint.new(self, "username") ],
555
- [ "/users/*/_acl", AclsEndpoint.new(self) ],
556
- [ "/users/*/_acl/*", AclEndpoint.new(self) ],
557
- [ "/users/*/association_requests", UserAssociationRequestsEndpoint.new(self) ],
558
- [ "/users/*/association_requests/count", UserAssociationRequestsCountEndpoint.new(self) ],
559
- [ "/users/*/association_requests/*", UserAssociationRequestEndpoint.new(self) ],
560
- [ "/users/*/keys", ActorKeysEndpoint.new(self) ],
561
- [ "/users/*/keys/default", ActorDefaultKeyEndpoint.new(self) ],
562
- [ "/users/*/keys/*", ActorKeyEndpoint.new(self) ],
563
- [ "/users/*/organizations", UserOrganizationsEndpoint.new(self) ],
564
- [ "/authenticate_user", AuthenticateUserEndpoint.new(self) ],
565
- [ "/system_recovery", SystemRecoveryEndpoint.new(self) ],
566
- [ "/license", LicenseEndpoint.new(self) ],
567
- [ "/organizations", OrganizationsEndpoint.new(self) ],
568
- [ "/organizations/*", OrganizationEndpoint.new(self) ],
569
- [ "/organizations/*/_validator_key", OrganizationValidatorKeyEndpoint.new(self) ],
570
- [ "/organizations/*/association_requests", OrganizationAssociationRequestsEndpoint.new(self) ],
571
- [ "/organizations/*/association_requests/*", OrganizationAssociationRequestEndpoint.new(self) ],
572
- [ "/organizations/*/containers", ContainersEndpoint.new(self) ],
573
- [ "/organizations/*/containers/*", ContainerEndpoint.new(self) ],
574
- [ "/organizations/*/groups", GroupsEndpoint.new(self) ],
575
- [ "/organizations/*/groups/*", GroupEndpoint.new(self) ],
576
- [ "/organizations/*/organization/_acl", AclsEndpoint.new(self) ],
577
- [ "/organizations/*/organizations/_acl", AclsEndpoint.new(self) ],
578
- [ "/organizations/*/*/*/_acl", AclsEndpoint.new(self) ],
579
- [ "/organizations/*/organization/_acl/*", AclEndpoint.new(self) ],
580
- [ "/organizations/*/organizations/_acl/*", AclEndpoint.new(self) ],
581
- [ "/organizations/*/*/*/_acl/*", AclEndpoint.new(self) ],
582
- ]
583
- end
584
- result + [
585
- # Both
586
- [ "/dummy", DummyEndpoint.new(self) ],
587
- [ "/organizations/*/clients", ActorsEndpoint.new(self) ],
588
- [ "/organizations/*/clients/*", ActorEndpoint.new(self) ],
589
- [ "/organizations/*/clients/*/keys", ActorKeysEndpoint.new(self) ],
590
- [ "/organizations/*/clients/*/keys/default", ActorDefaultKeyEndpoint.new(self) ],
591
- [ "/organizations/*/clients/*/keys/*", ActorKeyEndpoint.new(self) ],
592
- [ "/organizations/*/users/*/keys", OrganizationUserKeysEndpoint.new(self) ],
593
- [ "/organizations/*/users/*/keys/default", OrganizationUserDefaultKeyEndpoint.new(self) ],
594
- [ "/organizations/*/users/*/keys/*", OrganizationUserKeyEndpoint.new(self) ],
595
- [ "/organizations/*/controls", ControlsEndpoint.new(self) ],
596
- [ "/organizations/*/cookbooks", CookbooksEndpoint.new(self) ],
597
- [ "/organizations/*/cookbooks/*", CookbookEndpoint.new(self) ],
598
- [ "/organizations/*/cookbooks/*/*", CookbookVersionEndpoint.new(self) ],
599
- [ "/organizations/*/cookbook_artifacts", CookbookArtifactsEndpoint.new(self) ],
600
- [ "/organizations/*/cookbook_artifacts/*", CookbookArtifactEndpoint.new(self) ],
601
- [ "/organizations/*/cookbook_artifacts/*/*", CookbookArtifactIdentifierEndpoint.new(self) ],
602
- [ "/organizations/*/data", DataBagsEndpoint.new(self) ],
603
- [ "/organizations/*/data/*", DataBagEndpoint.new(self) ],
604
- [ "/organizations/*/data/*/*", DataBagItemEndpoint.new(self) ],
605
- [ "/organizations/*/environments", RestListEndpoint.new(self) ],
606
- [ "/organizations/*/environments/*", EnvironmentEndpoint.new(self) ],
607
- [ "/organizations/*/environments/*/cookbooks", EnvironmentCookbooksEndpoint.new(self) ],
608
- [ "/organizations/*/environments/*/cookbooks/*", EnvironmentCookbookEndpoint.new(self) ],
609
- [ "/organizations/*/environments/*/cookbook_versions", EnvironmentCookbookVersionsEndpoint.new(self) ],
610
- [ "/organizations/*/environments/*/nodes", EnvironmentNodesEndpoint.new(self) ],
611
- [ "/organizations/*/environments/*/recipes", EnvironmentRecipesEndpoint.new(self) ],
612
- [ "/organizations/*/environments/*/roles/*", EnvironmentRoleEndpoint.new(self) ],
613
- [ "/organizations/*/nodes", NodesEndpoint.new(self) ],
614
- [ "/organizations/*/nodes/*", NodeEndpoint.new(self) ],
615
- [ "/organizations/*/nodes/*/_identifiers", NodeIdentifiersEndpoint.new(self) ],
616
- [ "/organizations/*/policies", PoliciesEndpoint.new(self) ],
617
- [ "/organizations/*/policies/*", PolicyEndpoint.new(self) ],
618
- [ "/organizations/*/policies/*/revisions", PolicyRevisionsEndpoint.new(self) ],
619
- [ "/organizations/*/policies/*/revisions/*", PolicyRevisionEndpoint.new(self) ],
620
- [ "/organizations/*/policy_groups", PolicyGroupsEndpoint.new(self) ],
621
- [ "/organizations/*/policy_groups/*", PolicyGroupEndpoint.new(self) ],
622
- [ "/organizations/*/policy_groups/*/policies/*", PolicyGroupPolicyEndpoint.new(self) ],
623
- [ "/organizations/*/principals/*", PrincipalEndpoint.new(self) ],
624
- [ "/organizations/*/roles", RestListEndpoint.new(self) ],
625
- [ "/organizations/*/roles/*", RoleEndpoint.new(self) ],
626
- [ "/organizations/*/roles/*/environments", RoleEnvironmentsEndpoint.new(self) ],
627
- [ "/organizations/*/roles/*/environments/*", EnvironmentRoleEndpoint.new(self) ],
628
- [ "/organizations/*/sandboxes", SandboxesEndpoint.new(self) ],
629
- [ "/organizations/*/sandboxes/*", SandboxEndpoint.new(self) ],
630
- [ "/organizations/*/search", SearchesEndpoint.new(self) ],
631
- [ "/organizations/*/search/*", SearchEndpoint.new(self) ],
632
- [ "/organizations/*/universe", UniverseEndpoint.new(self) ],
633
- [ "/version", VersionEndpoint.new(self) ],
634
- [ "/server_api_version", ServerAPIVersionEndpoint.new(self) ],
635
-
636
- # Internal
637
- [ "/organizations/*/file_store/**", FileStoreFileEndpoint.new(self) ],
638
- ]
639
- end
640
-
641
- def global_endpoint?(ep)
642
- GLOBAL_ENDPOINTS.any? do |g_ep|
643
- ep.start_with?(g_ep)
644
- end
645
- end
646
-
647
- def app
648
- return @app if @app
649
-
650
- router = RestRouter.new(endpoints)
651
- router.not_found = NotFoundEndpoint.new
652
-
653
- if options[:single_org]
654
- rest_base_prefix = [ "organizations", options[:single_org] ]
655
- else
656
- rest_base_prefix = []
657
- end
658
- @app = proc do |env|
659
- begin
660
- prefix = global_endpoint?(env["PATH_INFO"]) ? [] : rest_base_prefix
661
-
662
- request = RestRequest.new(env, prefix)
663
- if @on_request_proc
664
- @on_request_proc.call(request)
665
- end
666
- response = nil
667
- if @request_handler
668
- response = @request_handler.call(request)
669
- end
670
- unless response
671
- response = router.call(request)
672
- end
673
- if @on_response_proc
674
- @on_response_proc.call(request, response)
675
- end
676
-
677
- # Insert Server header
678
- response[1]["Server"] = "chef-zero"
679
-
680
- # Add CORS header
681
- response[1]["Access-Control-Allow-Origin"] = "*"
682
-
683
- # Puma expects the response to be an array (chunked responses). Since
684
- # we are statically generating data, we won't ever have said chunked
685
- # response, so fake it.
686
- response[-1] = Array(response[-1])
687
-
688
- response
689
- rescue
690
- if options[:log_level] == :debug
691
- STDERR.puts "Request Error: #{$!}"
692
- STDERR.puts $!.backtrace.join("\n")
693
- end
694
- end
695
- end
696
- @app
697
- end
698
-
699
- def dejsonize_children(hash)
700
- result = {}
701
- hash.each_pair do |key, value|
702
- result[key] = dejsonize(value)
703
- end
704
- result
705
- end
706
-
707
- def dejsonize(value)
708
- value.is_a?(Hash) ? FFI_Yajl::Encoder.encode(value, pretty: true) : value
709
- end
710
-
711
- def get_file(directory, path)
712
- value = directory
713
- path.split("/").each do |part|
714
- value = value[part]
715
- end
716
- value
717
- end
718
-
719
- ## Disable unsecure ssl
720
- ## Ref: https://www.ruby-lang.org/en/news/2014/10/27/changing-default-settings-of-ext-openssl/
721
- def ssl_opts
722
- ssl_opts = OpenSSL::SSL::OP_ALL
723
- ssl_opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS)
724
- ssl_opts |= OpenSSL::SSL::OP_NO_COMPRESSION if defined?(OpenSSL::SSL::OP_NO_COMPRESSION)
725
- ssl_opts |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2)
726
- ssl_opts |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3)
727
- ssl_opts
728
- end
729
- end
730
- end
1
+ #
2
+ # Author:: John Keiser (<jkeiser@opscode.com>)
3
+ # Copyright:: Copyright (c) 2012-2017, 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
+ # https://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
+ require "openssl" unless defined?(OpenSSL)
20
+ require "open-uri" unless defined?(OpenURI)
21
+ require "rubygems" unless defined?(Gem)
22
+ require "timeout" unless defined?(Timeout)
23
+ require "stringio" unless defined?(StringIO)
24
+
25
+ require "rack" unless defined?(Rack)
26
+ require "rackup" unless defined?(Rackup)
27
+ require "webrick" unless defined?(WEBrick)
28
+ require "webrick/https"
29
+
30
+ require_relative "../chef_zero"
31
+ require_relative "socketless_server_map"
32
+ require_relative "chef_data/cookbook_data"
33
+ require_relative "chef_data/acl_path"
34
+ require_relative "rest_router"
35
+ require_relative "data_store/memory_store_v2"
36
+ require_relative "data_store/v1_to_v2_adapter"
37
+ require_relative "data_store/default_facade"
38
+ require_relative "version"
39
+ require "chef_zero/dist"
40
+
41
+ require_relative "endpoints/rest_list_endpoint"
42
+ require_relative "endpoints/authenticate_user_endpoint"
43
+ require_relative "endpoints/acls_endpoint"
44
+ require_relative "endpoints/acl_endpoint"
45
+ require_relative "endpoints/actor_endpoint"
46
+ require_relative "endpoints/actors_endpoint"
47
+ require_relative "endpoints/actor_key_endpoint"
48
+ require_relative "endpoints/organization_user_key_endpoint"
49
+ require_relative "endpoints/organization_user_default_key_endpoint"
50
+ require_relative "endpoints/organization_user_keys_endpoint"
51
+ require_relative "endpoints/actor_default_key_endpoint"
52
+ require_relative "endpoints/actor_keys_endpoint"
53
+ require_relative "endpoints/cookbooks_endpoint"
54
+ require_relative "endpoints/cookbook_endpoint"
55
+ require_relative "endpoints/cookbook_version_endpoint"
56
+ require_relative "endpoints/cookbook_artifacts_endpoint"
57
+ require_relative "endpoints/cookbook_artifact_endpoint"
58
+ require_relative "endpoints/cookbook_artifact_identifier_endpoint"
59
+ require_relative "endpoints/containers_endpoint"
60
+ require_relative "endpoints/container_endpoint"
61
+ require_relative "endpoints/controls_endpoint"
62
+ require_relative "endpoints/dummy_endpoint"
63
+ require_relative "endpoints/data_bags_endpoint"
64
+ require_relative "endpoints/data_bag_endpoint"
65
+ require_relative "endpoints/data_bag_item_endpoint"
66
+ require_relative "endpoints/groups_endpoint"
67
+ require_relative "endpoints/group_endpoint"
68
+ require_relative "endpoints/environment_endpoint"
69
+ require_relative "endpoints/environment_cookbooks_endpoint"
70
+ require_relative "endpoints/environment_cookbook_endpoint"
71
+ require_relative "endpoints/environment_cookbook_versions_endpoint"
72
+ require_relative "endpoints/environment_nodes_endpoint"
73
+ require_relative "endpoints/environment_recipes_endpoint"
74
+ require_relative "endpoints/environment_role_endpoint"
75
+ require_relative "endpoints/license_endpoint"
76
+ require_relative "endpoints/node_endpoint"
77
+ require_relative "endpoints/nodes_endpoint"
78
+ require_relative "endpoints/node_identifiers_endpoint"
79
+ require_relative "endpoints/organizations_endpoint"
80
+ require_relative "endpoints/organization_endpoint"
81
+ require_relative "endpoints/organization_association_requests_endpoint"
82
+ require_relative "endpoints/organization_association_request_endpoint"
83
+ require_relative "endpoints/organization_authenticate_user_endpoint"
84
+ require_relative "endpoints/organization_users_endpoint"
85
+ require_relative "endpoints/organization_user_endpoint"
86
+ require_relative "endpoints/organization_validator_key_endpoint"
87
+ require_relative "endpoints/policies_endpoint"
88
+ require_relative "endpoints/policy_endpoint"
89
+ require_relative "endpoints/policy_revisions_endpoint"
90
+ require_relative "endpoints/policy_revision_endpoint"
91
+ require_relative "endpoints/policy_groups_endpoint"
92
+ require_relative "endpoints/policy_group_endpoint"
93
+ require_relative "endpoints/policy_group_policy_endpoint"
94
+ require_relative "endpoints/principal_endpoint"
95
+ require_relative "endpoints/role_endpoint"
96
+ require_relative "endpoints/role_environments_endpoint"
97
+ require_relative "endpoints/sandboxes_endpoint"
98
+ require_relative "endpoints/sandbox_endpoint"
99
+ require_relative "endpoints/searches_endpoint"
100
+ require_relative "endpoints/search_endpoint"
101
+ require_relative "endpoints/system_recovery_endpoint"
102
+ require_relative "endpoints/user_association_requests_endpoint"
103
+ require_relative "endpoints/user_association_requests_count_endpoint"
104
+ require_relative "endpoints/user_association_request_endpoint"
105
+ require_relative "endpoints/user_organizations_endpoint"
106
+ require_relative "endpoints/file_store_file_endpoint"
107
+ require_relative "endpoints/not_found_endpoint"
108
+ require_relative "endpoints/version_endpoint"
109
+ require_relative "endpoints/server_api_version_endpoint"
110
+ require_relative "endpoints/universe_endpoint"
111
+
112
+ module ChefZero
113
+
114
+ class Server
115
+
116
+ DEFAULT_OPTIONS = {
117
+ host: ["127.0.0.1"],
118
+ port: 8889,
119
+ log_level: :warn,
120
+ generate_real_keys: true,
121
+ single_org: "chef",
122
+ ssl: false,
123
+ }.freeze
124
+
125
+ GLOBAL_ENDPOINTS = [
126
+ "/license",
127
+ "/version",
128
+ "/server_api_version",
129
+ ].freeze
130
+
131
+ def initialize(options = {})
132
+ @options = DEFAULT_OPTIONS.merge(options)
133
+ if @options[:single_org] && !@options.key?(:osc_compat)
134
+ @options[:osc_compat] = true
135
+ end
136
+ @options.freeze
137
+ ChefZero::Log.level = @options[:log_level].to_sym
138
+ @app = nil
139
+ end
140
+
141
+ # @return [Hash]
142
+ attr_reader :options
143
+
144
+ # @return [Integer]
145
+ def port
146
+ if @port
147
+ @port
148
+ # If options[:port] is not an Array or an Enumerable, it is just an Integer.
149
+ elsif !options[:port].respond_to?(:each)
150
+ options[:port]
151
+ else
152
+ raise "port cannot be determined until server is started"
153
+ end
154
+ end
155
+
156
+ # @return [WEBrick::HTTPServer]
157
+ attr_reader :server
158
+
159
+ include ChefZero::Endpoints
160
+
161
+ #
162
+ # The URL for this Chef Zero server. If the given host is an IPV6 address,
163
+ # it is escaped in brackets according to RFC-2732.
164
+ #
165
+ # @see http://www.ietf.org/rfc/rfc2732.txt RFC-2732
166
+ #
167
+ # @return [String]
168
+ #
169
+ def url
170
+ sch = @options[:ssl] ? "https" : "http"
171
+ hosts = Array(@options[:host])
172
+ @url ||= if hosts.first.include?(":")
173
+ URI("#{sch}://[#{hosts.first}]:#{port}").to_s
174
+ else
175
+ URI("#{sch}://#{hosts.first}:#{port}").to_s
176
+ end
177
+ end
178
+
179
+ def local_mode_url
180
+ raise "Port not yet set, cannot generate URL" unless port.is_a?(Integer)
181
+
182
+ "chefzero://localhost:#{port}"
183
+ end
184
+
185
+ #
186
+ # The data store for this server (default is in-memory).
187
+ #
188
+ # @return [ChefZero::DataStore]
189
+ #
190
+ def data_store
191
+ @data_store ||= begin
192
+ result = @options[:data_store] || DataStore::DefaultFacade.new(DataStore::MemoryStoreV2.new, options[:single_org], options[:osc_compat])
193
+ if options[:single_org]
194
+
195
+ if !result.respond_to?(:interface_version) || result.interface_version == 1
196
+ result = ChefZero::DataStore::V1ToV2Adapter.new(result, options[:single_org])
197
+ result = ChefZero::DataStore::DefaultFacade.new(result, options[:single_org], options[:osc_compat])
198
+ end
199
+
200
+ else
201
+ if !result.respond_to?(:interface_version) || result.interface_version == 1
202
+ raise "Multi-org not supported by data store #{result}!"
203
+ end
204
+ end
205
+
206
+ result
207
+ end
208
+ end
209
+
210
+ #
211
+ # Boolean method to determine if real Public/Private keys should be
212
+ # generated.
213
+ #
214
+ # @return [Boolean]
215
+ # true if real keys should be created, false otherwise
216
+ #
217
+ def generate_real_keys?
218
+ !!@options[:generate_real_keys]
219
+ end
220
+
221
+ #
222
+ # Start a Chef Zero server in the current thread. You can stop this server
223
+ # by canceling the current thread.
224
+ #
225
+ # @param [Boolean|IO] publish
226
+ # publish the server information to the publish parameter or to STDOUT if it's "true"
227
+ #
228
+ # @return [nil]
229
+ # this method will block the main thread until interrupted
230
+ #
231
+ def start(publish = true)
232
+ publish = publish[:publish] if publish.is_a?(Hash) # Legacy API
233
+
234
+ if publish
235
+ output = publish.respond_to?(:puts) ? publish : STDOUT
236
+ output.puts <<-EOH.gsub(/^ {10}/, "")
237
+ >> Starting #{ChefZero::Dist::PRODUCT} (v#{ChefZero::VERSION})...
238
+ EOH
239
+ end
240
+
241
+ thread = start_background
242
+
243
+ if publish
244
+ output = publish.respond_to?(:puts) ? publish : STDOUT
245
+ output.puts <<-EOH.gsub(/^ {10}/, "")
246
+ >> WEBrick (v#{WEBrick::VERSION}) on Rack (v#{Rack.release}) is listening at #{url}
247
+ >> Press CTRL+C to stop
248
+
249
+ EOH
250
+ end
251
+
252
+ %w{INT TERM}.each do |signal|
253
+ Signal.trap(signal) do
254
+ puts "\n>> Stopping #{ChefZero::Dist::PRODUCT}..."
255
+ @server.shutdown
256
+ end
257
+ end
258
+
259
+ # Move the background process to the main thread
260
+ thread.join
261
+ end
262
+
263
+ #
264
+ # Start a Chef Zero server in a forked process. This method returns the PID
265
+ # to the forked process.
266
+ #
267
+ # @param [Fixnum] wait
268
+ # the number of seconds to wait for the server to start
269
+ #
270
+ # @return [Thread]
271
+ # the thread the background process is running in
272
+ #
273
+ def listen(hosts, port)
274
+ hosts.each do |host|
275
+ @server.listen(host, port)
276
+ end
277
+ true
278
+ rescue Errno::EADDRINUSE
279
+ ChefZero::Log.warn("Port #{port} not available")
280
+ @server.listeners.each(&:close)
281
+ @server.listeners.clear
282
+ false
283
+ end
284
+
285
+ def start_background(wait = 5)
286
+ @server = WEBrick::HTTPServer.new(
287
+ DoNotListen: true,
288
+ AccessLog: [],
289
+ Logger: WEBrick::Log.new(StringIO.new, 7),
290
+ RequestTimeout: 300,
291
+ SSLEnable: options[:ssl],
292
+ SSLOptions: ssl_opts,
293
+ SSLCertName: [ [ "CN", WEBrick::Utils.getservername ] ],
294
+ StartCallback: proc do
295
+ @running = true
296
+ end
297
+ )
298
+ ENV["HTTPS"] = "on" if options[:ssl]
299
+ @server.mount("/", Rackup::Handler::WEBrick, app)
300
+
301
+ # Pick a port
302
+ # If options[:port] can be an Enumerator, an Array, or an Integer,
303
+ # we need something that can respond to .each (Enum and Array can already).
304
+ Array(options[:port]).each do |port|
305
+ if listen(Array(options[:host]), port)
306
+ @port = port
307
+ break
308
+ end
309
+ end
310
+ unless @port
311
+ raise Errno::EADDRINUSE,
312
+ "No port in :port range #{options[:port]} is available"
313
+ end
314
+
315
+ # Start the server in the background
316
+ @thread = Thread.new do
317
+
318
+ Thread.current.abort_on_exception = true
319
+ @server.start
320
+ ensure
321
+ @port = nil
322
+ @running = false
323
+
324
+ end
325
+
326
+ # Do not return until the web server is genuinely started.
327
+ sleep(0.01) while !@running && @thread.alive?
328
+
329
+ SocketlessServerMap.instance.register_port(@port, self)
330
+
331
+ @thread
332
+ end
333
+
334
+ def start_socketless
335
+ @port = SocketlessServerMap.instance.register_no_listen_server(self)
336
+ end
337
+
338
+ def handle_socketless_request(request_env)
339
+ app.call(request_env)
340
+ end
341
+
342
+ #
343
+ # Boolean method to determine if the server is currently ready to accept
344
+ # requests. This method will attempt to make an HTTP request against the
345
+ # server. If this method returns true, you are safe to make a request.
346
+ #
347
+ # @return [Boolean]
348
+ # true if the server is accepting requests, false otherwise
349
+ #
350
+ def running?
351
+ !@server.nil? && @running && @server.status == :Running
352
+ end
353
+
354
+ #
355
+ # Gracefully stop the Chef Zero server.
356
+ #
357
+ # @param [Fixnum] wait
358
+ # the number of seconds to wait before raising force-terminating the
359
+ # server
360
+ #
361
+ def stop(wait = 5)
362
+ if @running
363
+ @server.shutdown if @server
364
+ @thread.join(wait) if @thread
365
+ end
366
+ rescue Timeout::Error
367
+ if @thread
368
+ ChefZero::Log.error("#{ChefZero::Dist::PRODUCT} did not stop within #{wait} seconds! Killing...")
369
+ @thread.kill
370
+ SocketlessServerMap.deregister(port)
371
+ end
372
+ ensure
373
+ @server = nil
374
+ @thread = nil
375
+ end
376
+
377
+ def gen_key_pair
378
+ if generate_real_keys?
379
+ private_key = OpenSSL::PKey::RSA.new(2048)
380
+ public_key = private_key.public_key.to_s
381
+ public_key.sub!(/^-----BEGIN RSA PUBLIC KEY-----/, "-----BEGIN PUBLIC KEY-----")
382
+ public_key.sub!(/-----END RSA PUBLIC KEY-----(\s+)$/, '-----END PUBLIC KEY-----\1')
383
+ [private_key.to_s, public_key]
384
+ else
385
+ [PRIVATE_KEY, PUBLIC_KEY]
386
+ end
387
+ end
388
+
389
+ def on_request(&block)
390
+ @on_request_proc = block
391
+ end
392
+
393
+ def on_response(&block)
394
+ @on_response_proc = block
395
+ end
396
+
397
+ # Load data in a nice, friendly form:
398
+ # {
399
+ # 'roles' => {
400
+ # 'desert' => '{ "description": "Hot and dry"' },
401
+ # 'rainforest' => { "description" => 'Wet and humid' }
402
+ # },
403
+ # 'cookbooks' => {
404
+ # 'apache2-1.0.1' => {
405
+ # 'templates' => { 'default' => { 'blah.txt' => 'hi' }}
406
+ # 'recipes' => { 'default.rb' => 'template "blah.txt"' }
407
+ # 'metadata.rb' => 'depends "mysql"'
408
+ # },
409
+ # 'apache2-1.2.0' => {
410
+ # 'templates' => { 'default' => { 'blah.txt' => 'lo' }}
411
+ # 'recipes' => { 'default.rb' => 'template "blah.txt"' }
412
+ # 'metadata.rb' => 'depends "mysql"'
413
+ # },
414
+ # 'mysql' => {
415
+ # 'recipes' => { 'default.rb' => 'file { contents "hi" }' },
416
+ # 'metadata.rb' => 'version "1.0.0"'
417
+ # }
418
+ # }
419
+ # }
420
+ def load_data(contents, org_name = nil)
421
+ org_name ||= options[:single_org]
422
+ if org_name.nil? && contents.keys != [ "users" ]
423
+ raise "Must pass an org name to load_data or run in single_org mode"
424
+ end
425
+
426
+ %w{clients containers environments groups nodes roles sandboxes}.each do |data_type|
427
+ if contents[data_type]
428
+ dejsonize_children(contents[data_type]).each_pair do |name, data|
429
+ data_store.set(["organizations", org_name, data_type, name], data, :create)
430
+ end
431
+ end
432
+ end
433
+
434
+ if contents["users"]
435
+ dejsonize_children(contents["users"]).each_pair do |name, data|
436
+ if options[:osc_compat]
437
+ data_store.set(["organizations", org_name, "users", name], data, :create)
438
+ else
439
+ # Create the user and put them in the org
440
+ data_store.set(["users", name], data, :create)
441
+ if org_name
442
+ data_store.set(["organizations", org_name, "users", name], "{}", :create)
443
+ end
444
+ end
445
+ end
446
+ end
447
+
448
+ if contents["members"]
449
+ contents["members"].each do |name|
450
+ data_store.set(["organizations", org_name, "users", name], "{}", :create)
451
+ end
452
+ end
453
+
454
+ if contents["invites"]
455
+ contents["invites"].each do |name|
456
+ data_store.set(["organizations", org_name, "association_requests", name], "{}", :create)
457
+ end
458
+ end
459
+
460
+ if contents["acls"]
461
+ dejsonize_children(contents["acls"]).each do |path, acl|
462
+ path = [ "organizations", org_name ] + path.split("/")
463
+ path = ChefData::AclPath.get_acl_data_path(path)
464
+ ChefZero::RSpec.server.data_store.set(path, acl)
465
+ end
466
+ end
467
+
468
+ if contents["data"]
469
+ contents["data"].each_pair do |key, data_bag|
470
+ data_store.create_dir(["organizations", org_name, "data"], key, :recursive)
471
+ dejsonize_children(data_bag).each do |item_name, item|
472
+ data_store.set(["organizations", org_name, "data", key, item_name], item, :create)
473
+ end
474
+ end
475
+ end
476
+
477
+ if contents["policies"]
478
+ contents["policies"].each_pair do |policy_name, policy_struct|
479
+ # data_store.create_dir(['organizations', org_name, 'policies', policy_name], "revisions", :recursive)
480
+ dejsonize_children(policy_struct).each do |revision, policy_data|
481
+ data_store.set(["organizations", org_name, "policies", policy_name,
482
+ "revisions", revision], policy_data, :create, :create_dir)
483
+ end
484
+ end
485
+ end
486
+
487
+ if contents["policy_groups"]
488
+ contents["policy_groups"].each_pair do |group_name, group|
489
+ group["policies"].each do |policy_name, policy_revision|
490
+ data_store.set(["organizations", org_name, "policy_groups", group_name, "policies", policy_name], FFI_Yajl::Encoder.encode(policy_revision["revision_id"], pretty: true), :create, :create_dir)
491
+ end
492
+ end
493
+ end
494
+
495
+ %w{cookbooks cookbook_artifacts}.each do |cookbook_type|
496
+ if contents[cookbook_type]
497
+ contents[cookbook_type].each_pair do |name_version, cookbook|
498
+ if cookbook_type == "cookbook_artifacts"
499
+ name, _, identifier = name_version.rpartition("-")
500
+ cookbook_data = ChefData::CookbookData.to_hash(cookbook, name, identifier)
501
+ elsif name_version =~ /(.+)-(\d+\.\d+\.\d+)$/
502
+ cookbook_data = ChefData::CookbookData.to_hash(cookbook, $1, $2)
503
+ else
504
+ cookbook_data = ChefData::CookbookData.to_hash(cookbook, name_version)
505
+ end
506
+ raise "No version specified" unless cookbook_data[:version]
507
+
508
+ data_store.create_dir(["organizations", org_name, cookbook_type], cookbook_data[:cookbook_name], :recursive)
509
+ data_store.set(["organizations", org_name, cookbook_type, cookbook_data[:cookbook_name], cookbook_data[:version]], FFI_Yajl::Encoder.encode(cookbook_data, pretty: true), :create)
510
+ cookbook_data.values.each do |files|
511
+ next unless files.is_a? Array
512
+
513
+ files.each do |file|
514
+ data_store.set(["organizations", org_name, "file_store", "checksums", file[:checksum]], get_file(cookbook, file[:path]), :create)
515
+ end
516
+ end
517
+ end
518
+ end
519
+ end
520
+ end
521
+
522
+ def clear_data
523
+ data_store.clear
524
+ end
525
+
526
+ def request_handler(&block)
527
+ @request_handler = block
528
+ end
529
+
530
+ def to_s
531
+ "#<#{self.class} #{url}>"
532
+ end
533
+
534
+ def inspect
535
+ "#<#{self.class} @url=#{url.inspect}>"
536
+ end
537
+
538
+ private
539
+
540
+ def endpoints
541
+ result = if options[:osc_compat]
542
+ # OSC-only
543
+ [
544
+ [ "/organizations/*/users", ActorsEndpoint.new(self) ],
545
+ [ "/organizations/*/users/*", ActorEndpoint.new(self) ],
546
+ [ "/organizations/*/authenticate_user", OrganizationAuthenticateUserEndpoint.new(self) ],
547
+ ]
548
+ else
549
+ # EC-only
550
+ [
551
+ [ "/organizations/*/users", OrganizationUsersEndpoint.new(self) ],
552
+ [ "/organizations/*/users/*", OrganizationUserEndpoint.new(self) ],
553
+ [ "/users", ActorsEndpoint.new(self, "username") ],
554
+ [ "/users/*", ActorEndpoint.new(self, "username") ],
555
+ [ "/users/*/_acl", AclsEndpoint.new(self) ],
556
+ [ "/users/*/_acl/*", AclEndpoint.new(self) ],
557
+ [ "/users/*/association_requests", UserAssociationRequestsEndpoint.new(self) ],
558
+ [ "/users/*/association_requests/count", UserAssociationRequestsCountEndpoint.new(self) ],
559
+ [ "/users/*/association_requests/*", UserAssociationRequestEndpoint.new(self) ],
560
+ [ "/users/*/keys", ActorKeysEndpoint.new(self) ],
561
+ [ "/users/*/keys/default", ActorDefaultKeyEndpoint.new(self) ],
562
+ [ "/users/*/keys/*", ActorKeyEndpoint.new(self) ],
563
+ [ "/users/*/organizations", UserOrganizationsEndpoint.new(self) ],
564
+ [ "/authenticate_user", AuthenticateUserEndpoint.new(self) ],
565
+ [ "/system_recovery", SystemRecoveryEndpoint.new(self) ],
566
+ [ "/license", LicenseEndpoint.new(self) ],
567
+ [ "/organizations", OrganizationsEndpoint.new(self) ],
568
+ [ "/organizations/*", OrganizationEndpoint.new(self) ],
569
+ [ "/organizations/*/_validator_key", OrganizationValidatorKeyEndpoint.new(self) ],
570
+ [ "/organizations/*/association_requests", OrganizationAssociationRequestsEndpoint.new(self) ],
571
+ [ "/organizations/*/association_requests/*", OrganizationAssociationRequestEndpoint.new(self) ],
572
+ [ "/organizations/*/containers", ContainersEndpoint.new(self) ],
573
+ [ "/organizations/*/containers/*", ContainerEndpoint.new(self) ],
574
+ [ "/organizations/*/groups", GroupsEndpoint.new(self) ],
575
+ [ "/organizations/*/groups/*", GroupEndpoint.new(self) ],
576
+ [ "/organizations/*/organization/_acl", AclsEndpoint.new(self) ],
577
+ [ "/organizations/*/organizations/_acl", AclsEndpoint.new(self) ],
578
+ [ "/organizations/*/*/*/_acl", AclsEndpoint.new(self) ],
579
+ [ "/organizations/*/organization/_acl/*", AclEndpoint.new(self) ],
580
+ [ "/organizations/*/organizations/_acl/*", AclEndpoint.new(self) ],
581
+ [ "/organizations/*/*/*/_acl/*", AclEndpoint.new(self) ],
582
+ ]
583
+ end
584
+ result + [
585
+ # Both
586
+ [ "/dummy", DummyEndpoint.new(self) ],
587
+ [ "/organizations/*/clients", ActorsEndpoint.new(self) ],
588
+ [ "/organizations/*/clients/*", ActorEndpoint.new(self) ],
589
+ [ "/organizations/*/clients/*/keys", ActorKeysEndpoint.new(self) ],
590
+ [ "/organizations/*/clients/*/keys/default", ActorDefaultKeyEndpoint.new(self) ],
591
+ [ "/organizations/*/clients/*/keys/*", ActorKeyEndpoint.new(self) ],
592
+ [ "/organizations/*/users/*/keys", OrganizationUserKeysEndpoint.new(self) ],
593
+ [ "/organizations/*/users/*/keys/default", OrganizationUserDefaultKeyEndpoint.new(self) ],
594
+ [ "/organizations/*/users/*/keys/*", OrganizationUserKeyEndpoint.new(self) ],
595
+ [ "/organizations/*/controls", ControlsEndpoint.new(self) ],
596
+ [ "/organizations/*/cookbooks", CookbooksEndpoint.new(self) ],
597
+ [ "/organizations/*/cookbooks/*", CookbookEndpoint.new(self) ],
598
+ [ "/organizations/*/cookbooks/*/*", CookbookVersionEndpoint.new(self) ],
599
+ [ "/organizations/*/cookbook_artifacts", CookbookArtifactsEndpoint.new(self) ],
600
+ [ "/organizations/*/cookbook_artifacts/*", CookbookArtifactEndpoint.new(self) ],
601
+ [ "/organizations/*/cookbook_artifacts/*/*", CookbookArtifactIdentifierEndpoint.new(self) ],
602
+ [ "/organizations/*/data", DataBagsEndpoint.new(self) ],
603
+ [ "/organizations/*/data/*", DataBagEndpoint.new(self) ],
604
+ [ "/organizations/*/data/*/*", DataBagItemEndpoint.new(self) ],
605
+ [ "/organizations/*/environments", RestListEndpoint.new(self) ],
606
+ [ "/organizations/*/environments/*", EnvironmentEndpoint.new(self) ],
607
+ [ "/organizations/*/environments/*/cookbooks", EnvironmentCookbooksEndpoint.new(self) ],
608
+ [ "/organizations/*/environments/*/cookbooks/*", EnvironmentCookbookEndpoint.new(self) ],
609
+ [ "/organizations/*/environments/*/cookbook_versions", EnvironmentCookbookVersionsEndpoint.new(self) ],
610
+ [ "/organizations/*/environments/*/nodes", EnvironmentNodesEndpoint.new(self) ],
611
+ [ "/organizations/*/environments/*/recipes", EnvironmentRecipesEndpoint.new(self) ],
612
+ [ "/organizations/*/environments/*/roles/*", EnvironmentRoleEndpoint.new(self) ],
613
+ [ "/organizations/*/nodes", NodesEndpoint.new(self) ],
614
+ [ "/organizations/*/nodes/*", NodeEndpoint.new(self) ],
615
+ [ "/organizations/*/nodes/*/_identifiers", NodeIdentifiersEndpoint.new(self) ],
616
+ [ "/organizations/*/policies", PoliciesEndpoint.new(self) ],
617
+ [ "/organizations/*/policies/*", PolicyEndpoint.new(self) ],
618
+ [ "/organizations/*/policies/*/revisions", PolicyRevisionsEndpoint.new(self) ],
619
+ [ "/organizations/*/policies/*/revisions/*", PolicyRevisionEndpoint.new(self) ],
620
+ [ "/organizations/*/policy_groups", PolicyGroupsEndpoint.new(self) ],
621
+ [ "/organizations/*/policy_groups/*", PolicyGroupEndpoint.new(self) ],
622
+ [ "/organizations/*/policy_groups/*/policies/*", PolicyGroupPolicyEndpoint.new(self) ],
623
+ [ "/organizations/*/principals/*", PrincipalEndpoint.new(self) ],
624
+ [ "/organizations/*/roles", RestListEndpoint.new(self) ],
625
+ [ "/organizations/*/roles/*", RoleEndpoint.new(self) ],
626
+ [ "/organizations/*/roles/*/environments", RoleEnvironmentsEndpoint.new(self) ],
627
+ [ "/organizations/*/roles/*/environments/*", EnvironmentRoleEndpoint.new(self) ],
628
+ [ "/organizations/*/sandboxes", SandboxesEndpoint.new(self) ],
629
+ [ "/organizations/*/sandboxes/*", SandboxEndpoint.new(self) ],
630
+ [ "/organizations/*/search", SearchesEndpoint.new(self) ],
631
+ [ "/organizations/*/search/*", SearchEndpoint.new(self) ],
632
+ [ "/organizations/*/universe", UniverseEndpoint.new(self) ],
633
+ [ "/version", VersionEndpoint.new(self) ],
634
+ [ "/server_api_version", ServerAPIVersionEndpoint.new(self) ],
635
+
636
+ # Internal
637
+ [ "/organizations/*/file_store/**", FileStoreFileEndpoint.new(self) ],
638
+ ]
639
+ end
640
+
641
+ def global_endpoint?(ep)
642
+ GLOBAL_ENDPOINTS.any? do |g_ep|
643
+ ep.start_with?(g_ep)
644
+ end
645
+ end
646
+
647
+ def app
648
+ return @app if @app
649
+
650
+ router = RestRouter.new(endpoints)
651
+ router.not_found = NotFoundEndpoint.new
652
+
653
+ if options[:single_org]
654
+ rest_base_prefix = [ "organizations", options[:single_org] ]
655
+ else
656
+ rest_base_prefix = []
657
+ end
658
+ @app = proc do |env|
659
+
660
+ prefix = global_endpoint?(env["PATH_INFO"]) ? [] : rest_base_prefix
661
+
662
+ request = RestRequest.new(env, prefix)
663
+ if @on_request_proc
664
+ @on_request_proc.call(request)
665
+ end
666
+ response = nil
667
+ if @request_handler
668
+ response = @request_handler.call(request)
669
+ end
670
+ unless response
671
+ response = router.call(request)
672
+ end
673
+ if @on_response_proc
674
+ @on_response_proc.call(request, response)
675
+ end
676
+
677
+ # Insert Server header
678
+ response[1]["Server"] = "chef-zero"
679
+
680
+ # Add CORS header
681
+ response[1]["Access-Control-Allow-Origin"] = "*"
682
+
683
+ # Puma expects the response to be an array (chunked responses). Since
684
+ # we are statically generating data, we won't ever have said chunked
685
+ # response, so fake it.
686
+ response[-1] = Array(response[-1])
687
+
688
+ response
689
+ rescue
690
+ if options[:log_level] == :debug
691
+ STDERR.puts "Request Error: #{$!}"
692
+ STDERR.puts $!.backtrace.join("\n")
693
+ end
694
+
695
+ end
696
+ @app
697
+ end
698
+
699
+ def dejsonize_children(hash)
700
+ result = {}
701
+ hash.each_pair do |key, value|
702
+ result[key] = dejsonize(value)
703
+ end
704
+ result
705
+ end
706
+
707
+ def dejsonize(value)
708
+ value.is_a?(Hash) ? FFI_Yajl::Encoder.encode(value, pretty: true) : value
709
+ end
710
+
711
+ def get_file(directory, path)
712
+ value = directory
713
+ path.split("/").each do |part|
714
+ value = value[part]
715
+ end
716
+ value
717
+ end
718
+
719
+ ## Disable unsecure ssl
720
+ ## Ref: https://www.ruby-lang.org/en/news/2014/10/27/changing-default-settings-of-ext-openssl/
721
+ def ssl_opts
722
+ ssl_opts = OpenSSL::SSL::OP_ALL
723
+ ssl_opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS)
724
+ ssl_opts |= OpenSSL::SSL::OP_NO_COMPRESSION if defined?(OpenSSL::SSL::OP_NO_COMPRESSION)
725
+ ssl_opts |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2)
726
+ ssl_opts |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3)
727
+ ssl_opts
728
+ end
729
+ end
730
+ end