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,332 +1,332 @@
1
- require_relative "rest_request"
2
- require_relative "rest_error_response"
3
- require_relative "data_store/data_not_found_error"
4
- require_relative "chef_data/acl_path"
5
-
6
- module ChefZero
7
- class RestBase
8
- DEFAULT_REQUEST_VERSION = 0
9
- DEFAULT_RESPONSE_VERSION = 0
10
-
11
- def initialize(server)
12
- @server = server
13
- end
14
-
15
- attr_reader :server
16
-
17
- def data_store
18
- server.data_store
19
- end
20
-
21
- def check_api_version(request)
22
- version = request.api_version
23
-
24
- if version > MAX_API_VERSION || version < MIN_API_VERSION
25
- response = {
26
- "error" => "invalid-x-ops-server-api-version",
27
- "message" => "Specified version #{version} not supported",
28
- "min_api_version" => MIN_API_VERSION,
29
- "max_api_version" => MAX_API_VERSION,
30
- }
31
-
32
- return json_response(406,
33
- response,
34
- request_version: version, response_version: -1)
35
- end
36
- rescue ArgumentError
37
- json_response(406,
38
- { "username" => request.requestor },
39
- request_version: -1, response_version: -1)
40
- end
41
-
42
- def call(request)
43
- response = check_api_version(request)
44
- return response unless response.nil?
45
-
46
- method = request.method.downcase.to_sym
47
- unless respond_to?(method)
48
- accept_methods = %i{get put post delete}.select { |m| respond_to?(m) }
49
- accept_methods_str = accept_methods.map { |m| m.to_s.upcase }.join(", ")
50
- return [405, { "Content-Type" => "text/plain", "Allow" => accept_methods_str }, "Bad request method for '#{request.env["REQUEST_PATH"]}': #{request.env["REQUEST_METHOD"]}"]
51
- end
52
- if json_only && !accepts?(request, "application", "json")
53
- return [406, { "Content-Type" => "text/plain" }, "Must accept application/json"]
54
- end
55
-
56
- # Dispatch to get()/post()/put()/delete()
57
- begin
58
- send(method, request)
59
- rescue RestErrorResponse => e
60
- ChefZero::Log.debug("#{e.inspect}\n#{e.backtrace.join("\n")}")
61
- error(e.response_code, e.error)
62
- end
63
- end
64
-
65
- def json_only
66
- true
67
- end
68
-
69
- def accepts?(request, category, type)
70
- # If HTTP_ACCEPT is not sent at all, assume it accepts anything
71
- # This parses as per https://datatracker.ietf.org/doc/html/rfc7231#section-5.3
72
- return true unless request.env["HTTP_ACCEPT"]
73
-
74
- accepts = request.env["HTTP_ACCEPT"].split(/,\s*/).map { |x| x.split(";", 2)[0].strip }
75
- accepts.include?("#{category}/#{type}") || accepts.include?("#{category}/*") || accepts.include?("*/*")
76
- end
77
-
78
- def get_data(request, rest_path = nil, *options)
79
- rest_path ||= request.rest_path
80
- rest_path = rest_path.map { |v| self.class.rfc2396_parser.unescape(v) }
81
- begin
82
- data_store.get(rest_path, request)
83
- rescue DataStore::DataNotFoundError
84
- if options.include?(:nil)
85
- nil
86
- elsif options.include?(:data_store_exceptions)
87
- raise
88
- else
89
- raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
90
- end
91
- end
92
- end
93
-
94
- def list_data(request, rest_path = nil, *options)
95
- rest_path ||= request.rest_path
96
- begin
97
- data_store.list(rest_path)
98
- rescue DataStore::DataNotFoundError
99
- if options.include?(:data_store_exceptions)
100
- raise
101
- else
102
- raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
103
- end
104
- end
105
- end
106
-
107
- def delete_data(request, rest_path = nil, *options)
108
- rest_path ||= request.rest_path
109
- begin
110
- data_store.delete(rest_path, *options)
111
- rescue DataStore::DataNotFoundError
112
- if options.include?(:data_store_exceptions)
113
- raise
114
- else
115
- raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
116
- end
117
- end
118
-
119
- begin
120
- acl_path = ChefData::AclPath.get_acl_data_path(rest_path)
121
- data_store.delete(acl_path) if acl_path
122
- rescue DataStore::DataNotFoundError
123
- end
124
- end
125
-
126
- def delete_data_dir(request, rest_path, *options)
127
- rest_path ||= request.rest_path
128
- begin
129
- data_store.delete_dir(rest_path, *options)
130
- rescue DataStore::DataNotFoundError
131
- if options.include?(:data_store_exceptions)
132
- raise
133
- else
134
- raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
135
- end
136
- end
137
-
138
- begin
139
- acl_path = ChefData::AclPath.get_acl_data_path(rest_path)
140
- data_store.delete(acl_path) if acl_path
141
- rescue DataStore::DataNotFoundError
142
- end
143
- end
144
-
145
- def set_data(request, rest_path, data, *options)
146
- rest_path ||= request.rest_path
147
- begin
148
- data_store.set(rest_path, data, *options, requestor: request.requestor)
149
- rescue DataStore::DataNotFoundError
150
- if options.include?(:data_store_exceptions)
151
- raise
152
- else
153
- raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
154
- end
155
- end
156
- end
157
-
158
- def create_data_dir(request, rest_path, name, *options)
159
- rest_path ||= request.rest_path
160
- begin
161
- data_store.create_dir(rest_path, name, *options, requestor: request.requestor)
162
- rescue DataStore::DataNotFoundError
163
- if options.include?(:data_store_exceptions)
164
- raise
165
- else
166
- raise RestErrorResponse.new(404, "Parent not found: #{build_uri(request.base_uri, rest_path)}")
167
- end
168
- rescue DataStore::DataAlreadyExistsError
169
- if options.include?(:data_store_exceptions)
170
- raise
171
- else
172
- raise RestErrorResponse.new(409, "Object already exists: #{build_uri(request.base_uri, rest_path + [name])}")
173
- end
174
- end
175
- end
176
-
177
- def create_data(request, rest_path, name, data, *options)
178
- rest_path ||= request.rest_path
179
- begin
180
- data_store.create(rest_path, name, data, *options, requestor: request.requestor)
181
- rescue DataStore::DataNotFoundError
182
- if options.include?(:data_store_exceptions)
183
- raise
184
- else
185
- raise RestErrorResponse.new(404, "Parent not found: #{build_uri(request.base_uri, rest_path)}")
186
- end
187
- rescue DataStore::DataAlreadyExistsError
188
- if options.include?(:data_store_exceptions)
189
- raise
190
- else
191
- raise RestErrorResponse.new(409, "Object already exists: #{build_uri(request.base_uri, rest_path + [name])}")
192
- end
193
- end
194
- end
195
-
196
- def exists_data?(request, rest_path = nil)
197
- rest_path ||= request.rest_path
198
- data_store.exists?(rest_path)
199
- end
200
-
201
- def exists_data_dir?(request, rest_path = nil)
202
- rest_path ||= request.rest_path
203
- data_store.exists_dir?(rest_path)
204
- end
205
-
206
- def error(response_code, error, opts = {})
207
- json_response(response_code, { "error" => [ error ] }, opts)
208
- end
209
-
210
- # Serializes `data` to JSON and returns an Array with the
211
- # response code, HTTP headers and JSON body.
212
- #
213
- # @param [Fixnum] response_code HTTP response code
214
- # @param [Hash] data The data for the response body as a Hash
215
- # @param [Hash] options
216
- # @option options [Hash] :headers (see #already_json_response)
217
- # @option options [Boolean] :pretty (true) Pretty-format the JSON
218
- # @option options [Fixnum] :request_version (see #already_json_response)
219
- # @option options [Fixnum] :response_version (see #already_json_response)
220
- #
221
- # @return (see #already_json_response)
222
- #
223
- def json_response(response_code, data, options = {})
224
- options = { pretty: true }.merge(options)
225
- do_pretty_json = !!options.delete(:pretty) # make sure we have a proper Boolean.
226
- json = FFI_Yajl::Encoder.encode(data, pretty: do_pretty_json)
227
- already_json_response(response_code, json, options)
228
- end
229
-
230
- def text_response(response_code, text)
231
- [response_code, { "Content-Type" => "text/plain" }, text]
232
- end
233
-
234
- # rfc090 returns 404 error or 200 with an emtpy body
235
- # @param [ChefZero::RestRequest] request The HTTP request object
236
- #
237
- # @return (see #json_response)
238
- #
239
- def head_request(request)
240
- get_data(request) # will raise 404 if non-existant
241
- json_response(200, nil)
242
- end
243
-
244
- # Returns an Array with the response code, HTTP headers, and JSON body.
245
- #
246
- # @param [Fixnum] response_code The HTTP response code
247
- # @param [String] json_text The JSON body for the response
248
- # @param [Hash] options
249
- # @option options [Hash] :headers ({}) HTTP headers (may override default headers)
250
- # @option options [Fixnum] :request_version (0) Request API version
251
- # @option options [Fixnum] :response_version (0) Response API version
252
- #
253
- # @return [Array(Fixnum, Hash{String => String}, String)]
254
- #
255
- def already_json_response(response_code, json_text, options = {})
256
- version_header = FFI_Yajl::Encoder.encode(
257
- "min_version" => MIN_API_VERSION.to_s,
258
- "max_version" => MAX_API_VERSION.to_s,
259
- "request_version" => options[:request_version] || DEFAULT_REQUEST_VERSION.to_s,
260
- "response_version" => options[:response_version] || DEFAULT_RESPONSE_VERSION.to_s
261
- )
262
-
263
- headers = {
264
- "Content-Type" => "application/json",
265
- "X-Ops-Server-API-Version" => version_header,
266
- }
267
- headers.merge!(options[:headers]) if options[:headers]
268
-
269
- [ response_code, headers, json_text ]
270
- end
271
-
272
- # To be called from inside rest endpoints
273
- def build_uri(base_uri, rest_path)
274
- if server.options[:single_org]
275
- # Strip off /organizations/chef if we are in single org mode
276
- if rest_path[0..1] != [ "organizations", server.options[:single_org] ]
277
- raise "Unexpected URL #{rest_path[0..1]} passed to build_uri in single org mode"
278
- end
279
-
280
- return self.class.build_uri(base_uri, rest_path[2..-1])
281
- end
282
-
283
- self.class.build_uri(base_uri, rest_path)
284
- end
285
-
286
- def self.build_uri(base_uri, rest_path)
287
- "#{base_uri}/#{rest_path.map { |v| rfc2396_parser.escape(v) }.join("/")}"
288
- end
289
-
290
- def populate_defaults(request, response)
291
- response
292
- end
293
-
294
- def parse_json(json)
295
- FFI_Yajl::Parser.parse(json)
296
- end
297
-
298
- def to_json(data)
299
- FFI_Yajl::Encoder.encode(data, pretty: true)
300
- end
301
-
302
- def get_data_or_else(request, path, or_else_value)
303
- if exists_data?(request, path)
304
- parse_json(get_data(request, path))
305
- else
306
- or_else_value
307
- end
308
- end
309
-
310
- def list_data_or_else(request, path, or_else_value)
311
- if exists_data_dir?(request, path)
312
- list_data(request, path)
313
- else
314
- or_else_value
315
- end
316
- end
317
-
318
- def hashify_list(list)
319
- list.reduce({}) { |acc, obj| acc.merge( obj => {} ) }
320
- end
321
-
322
- def policy_name_invalid?(name)
323
- !name.is_a?(String) ||
324
- name.size > 255 ||
325
- name =~ /[+ !]/
326
- end
327
-
328
- def self.rfc2396_parser
329
- @parser ||= URI::RFC2396_Parser.new
330
- end
331
- end
332
- end
1
+ require_relative "rest_request"
2
+ require_relative "rest_error_response"
3
+ require_relative "data_store/data_not_found_error"
4
+ require_relative "chef_data/acl_path"
5
+
6
+ module ChefZero
7
+ class RestBase
8
+ DEFAULT_REQUEST_VERSION = 0
9
+ DEFAULT_RESPONSE_VERSION = 0
10
+
11
+ def initialize(server)
12
+ @server = server
13
+ end
14
+
15
+ attr_reader :server
16
+
17
+ def data_store
18
+ server.data_store
19
+ end
20
+
21
+ def check_api_version(request)
22
+ version = request.api_version
23
+
24
+ if version > MAX_API_VERSION || version < MIN_API_VERSION
25
+ response = {
26
+ "error" => "invalid-x-ops-server-api-version",
27
+ "message" => "Specified version #{version} not supported",
28
+ "min_api_version" => MIN_API_VERSION,
29
+ "max_api_version" => MAX_API_VERSION,
30
+ }
31
+
32
+ json_response(406,
33
+ response,
34
+ request_version: version, response_version: -1)
35
+ end
36
+ rescue ArgumentError
37
+ json_response(406,
38
+ { "username" => request.requestor },
39
+ request_version: -1, response_version: -1)
40
+ end
41
+
42
+ def call(request)
43
+ response = check_api_version(request)
44
+ return response unless response.nil?
45
+
46
+ method = request.method.downcase.to_sym
47
+ unless respond_to?(method)
48
+ accept_methods = %i{get put post delete}.select { |m| respond_to?(m) }
49
+ accept_methods_str = accept_methods.map { |m| m.to_s.upcase }.join(", ")
50
+ return [405, { "Content-Type" => "text/plain", "Allow" => accept_methods_str }, "Bad request method for '#{request.env["REQUEST_PATH"]}': #{request.env["REQUEST_METHOD"]}"]
51
+ end
52
+ if json_only && !accepts?(request, "application", "json")
53
+ return [406, { "Content-Type" => "text/plain" }, "Must accept application/json"]
54
+ end
55
+
56
+ # Dispatch to get()/post()/put()/delete()
57
+ begin
58
+ send(method, request)
59
+ rescue RestErrorResponse => e
60
+ ChefZero::Log.debug("#{e.inspect}\n#{e.backtrace.join("\n")}")
61
+ error(e.response_code, e.error)
62
+ end
63
+ end
64
+
65
+ def json_only
66
+ true
67
+ end
68
+
69
+ def accepts?(request, category, type)
70
+ # If HTTP_ACCEPT is not sent at all, assume it accepts anything
71
+ # This parses as per https://datatracker.ietf.org/doc/html/rfc7231#section-5.3
72
+ return true unless request.env["HTTP_ACCEPT"]
73
+
74
+ accepts = request.env["HTTP_ACCEPT"].split(/,\s*/).map { |x| x.split(";", 2)[0].strip }
75
+ accepts.include?("#{category}/#{type}") || accepts.include?("#{category}/*") || accepts.include?("*/*")
76
+ end
77
+
78
+ def get_data(request, rest_path = nil, *options)
79
+ rest_path ||= request.rest_path
80
+ rest_path = rest_path.map { |v| self.class.rfc2396_parser.unescape(v) }
81
+ begin
82
+ data_store.get(rest_path, request)
83
+ rescue DataStore::DataNotFoundError
84
+ if options.include?(:nil)
85
+ nil
86
+ elsif options.include?(:data_store_exceptions)
87
+ raise
88
+ else
89
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
90
+ end
91
+ end
92
+ end
93
+
94
+ def list_data(request, rest_path = nil, *options)
95
+ rest_path ||= request.rest_path
96
+ begin
97
+ data_store.list(rest_path)
98
+ rescue DataStore::DataNotFoundError
99
+ if options.include?(:data_store_exceptions)
100
+ raise
101
+ else
102
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
103
+ end
104
+ end
105
+ end
106
+
107
+ def delete_data(request, rest_path = nil, *options)
108
+ rest_path ||= request.rest_path
109
+ begin
110
+ data_store.delete(rest_path, *options)
111
+ rescue DataStore::DataNotFoundError
112
+ if options.include?(:data_store_exceptions)
113
+ raise
114
+ else
115
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
116
+ end
117
+ end
118
+
119
+ begin
120
+ acl_path = ChefData::AclPath.get_acl_data_path(rest_path)
121
+ data_store.delete(acl_path) if acl_path
122
+ rescue DataStore::DataNotFoundError
123
+ end
124
+ end
125
+
126
+ def delete_data_dir(request, rest_path, *options)
127
+ rest_path ||= request.rest_path
128
+ begin
129
+ data_store.delete_dir(rest_path, *options)
130
+ rescue DataStore::DataNotFoundError
131
+ if options.include?(:data_store_exceptions)
132
+ raise
133
+ else
134
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
135
+ end
136
+ end
137
+
138
+ begin
139
+ acl_path = ChefData::AclPath.get_acl_data_path(rest_path)
140
+ data_store.delete(acl_path) if acl_path
141
+ rescue DataStore::DataNotFoundError
142
+ end
143
+ end
144
+
145
+ def set_data(request, rest_path, data, *options)
146
+ rest_path ||= request.rest_path
147
+ begin
148
+ data_store.set(rest_path, data, *options, requestor: request.requestor)
149
+ rescue DataStore::DataNotFoundError
150
+ if options.include?(:data_store_exceptions)
151
+ raise
152
+ else
153
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
154
+ end
155
+ end
156
+ end
157
+
158
+ def create_data_dir(request, rest_path, name, *options)
159
+ rest_path ||= request.rest_path
160
+ begin
161
+ data_store.create_dir(rest_path, name, *options, requestor: request.requestor)
162
+ rescue DataStore::DataNotFoundError
163
+ if options.include?(:data_store_exceptions)
164
+ raise
165
+ else
166
+ raise RestErrorResponse.new(404, "Parent not found: #{build_uri(request.base_uri, rest_path)}")
167
+ end
168
+ rescue DataStore::DataAlreadyExistsError
169
+ if options.include?(:data_store_exceptions)
170
+ raise
171
+ else
172
+ raise RestErrorResponse.new(409, "Object already exists: #{build_uri(request.base_uri, rest_path + [name])}")
173
+ end
174
+ end
175
+ end
176
+
177
+ def create_data(request, rest_path, name, data, *options)
178
+ rest_path ||= request.rest_path
179
+ begin
180
+ data_store.create(rest_path, name, data, *options, requestor: request.requestor)
181
+ rescue DataStore::DataNotFoundError
182
+ if options.include?(:data_store_exceptions)
183
+ raise
184
+ else
185
+ raise RestErrorResponse.new(404, "Parent not found: #{build_uri(request.base_uri, rest_path)}")
186
+ end
187
+ rescue DataStore::DataAlreadyExistsError
188
+ if options.include?(:data_store_exceptions)
189
+ raise
190
+ else
191
+ raise RestErrorResponse.new(409, "Object already exists: #{build_uri(request.base_uri, rest_path + [name])}")
192
+ end
193
+ end
194
+ end
195
+
196
+ def exists_data?(request, rest_path = nil)
197
+ rest_path ||= request.rest_path
198
+ data_store.exists?(rest_path)
199
+ end
200
+
201
+ def exists_data_dir?(request, rest_path = nil)
202
+ rest_path ||= request.rest_path
203
+ data_store.exists_dir?(rest_path)
204
+ end
205
+
206
+ def error(response_code, error, opts = {})
207
+ json_response(response_code, { "error" => [ error ] }, opts)
208
+ end
209
+
210
+ # Serializes `data` to JSON and returns an Array with the
211
+ # response code, HTTP headers and JSON body.
212
+ #
213
+ # @param [Fixnum] response_code HTTP response code
214
+ # @param [Hash] data The data for the response body as a Hash
215
+ # @param [Hash] options
216
+ # @option options [Hash] :headers (see #already_json_response)
217
+ # @option options [Boolean] :pretty (true) Pretty-format the JSON
218
+ # @option options [Fixnum] :request_version (see #already_json_response)
219
+ # @option options [Fixnum] :response_version (see #already_json_response)
220
+ #
221
+ # @return (see #already_json_response)
222
+ #
223
+ def json_response(response_code, data, options = {})
224
+ options = { pretty: true }.merge(options)
225
+ do_pretty_json = !!options.delete(:pretty) # make sure we have a proper Boolean.
226
+ json = FFI_Yajl::Encoder.encode(data, pretty: do_pretty_json)
227
+ already_json_response(response_code, json, options)
228
+ end
229
+
230
+ def text_response(response_code, text)
231
+ [response_code, { "Content-Type" => "text/plain" }, text]
232
+ end
233
+
234
+ # rfc090 returns 404 error or 200 with an emtpy body
235
+ # @param [ChefZero::RestRequest] request The HTTP request object
236
+ #
237
+ # @return (see #json_response)
238
+ #
239
+ def head_request(request)
240
+ get_data(request) # will raise 404 if non-existant
241
+ json_response(200, nil)
242
+ end
243
+
244
+ # Returns an Array with the response code, HTTP headers, and JSON body.
245
+ #
246
+ # @param [Fixnum] response_code The HTTP response code
247
+ # @param [String] json_text The JSON body for the response
248
+ # @param [Hash] options
249
+ # @option options [Hash] :headers ({}) HTTP headers (may override default headers)
250
+ # @option options [Fixnum] :request_version (0) Request API version
251
+ # @option options [Fixnum] :response_version (0) Response API version
252
+ #
253
+ # @return [Array(Fixnum, Hash{String => String}, String)]
254
+ #
255
+ def already_json_response(response_code, json_text, options = {})
256
+ version_header = FFI_Yajl::Encoder.encode(
257
+ "min_version" => MIN_API_VERSION.to_s,
258
+ "max_version" => MAX_API_VERSION.to_s,
259
+ "request_version" => options[:request_version] || DEFAULT_REQUEST_VERSION.to_s,
260
+ "response_version" => options[:response_version] || DEFAULT_RESPONSE_VERSION.to_s
261
+ )
262
+
263
+ headers = {
264
+ "Content-Type" => "application/json",
265
+ "X-Ops-Server-API-Version" => version_header,
266
+ }
267
+ headers.merge!(options[:headers]) if options[:headers]
268
+
269
+ [ response_code, headers, json_text ]
270
+ end
271
+
272
+ # To be called from inside rest endpoints
273
+ def build_uri(base_uri, rest_path)
274
+ if server.options[:single_org]
275
+ # Strip off /organizations/chef if we are in single org mode
276
+ if rest_path[0..1] != [ "organizations", server.options[:single_org] ]
277
+ raise "Unexpected URL #{rest_path[0..1]} passed to build_uri in single org mode"
278
+ end
279
+
280
+ return self.class.build_uri(base_uri, rest_path[2..-1])
281
+ end
282
+
283
+ self.class.build_uri(base_uri, rest_path)
284
+ end
285
+
286
+ def self.build_uri(base_uri, rest_path)
287
+ "#{base_uri}/#{rest_path.map { |v| rfc2396_parser.escape(v) }.join("/")}"
288
+ end
289
+
290
+ def populate_defaults(request, response)
291
+ response
292
+ end
293
+
294
+ def parse_json(json)
295
+ FFI_Yajl::Parser.parse(json)
296
+ end
297
+
298
+ def to_json(data)
299
+ FFI_Yajl::Encoder.encode(data, pretty: true)
300
+ end
301
+
302
+ def get_data_or_else(request, path, or_else_value)
303
+ if exists_data?(request, path)
304
+ parse_json(get_data(request, path))
305
+ else
306
+ or_else_value
307
+ end
308
+ end
309
+
310
+ def list_data_or_else(request, path, or_else_value)
311
+ if exists_data_dir?(request, path)
312
+ list_data(request, path)
313
+ else
314
+ or_else_value
315
+ end
316
+ end
317
+
318
+ def hashify_list(list)
319
+ list.reduce({}) { |acc, obj| acc.merge( obj => {} ) }
320
+ end
321
+
322
+ def policy_name_invalid?(name)
323
+ !name.is_a?(String) ||
324
+ name.size > 255 ||
325
+ name =~ /[+ !]/
326
+ end
327
+
328
+ def self.rfc2396_parser
329
+ @parser ||= URI::RFC2396_Parser.new
330
+ end
331
+ end
332
+ end