puppet 2.7.20 → 2.7.21

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (81) hide show
  1. data/CHANGELOG +226 -0
  2. data/conf/auth.conf +3 -3
  3. data/ext/packaging/README.md +191 -57
  4. data/ext/packaging/spec/spec_helper.rb +2 -2
  5. data/ext/packaging/spec/tasks/00_utils_spec.rb +63 -18
  6. data/ext/packaging/spec/tasks/build_object_spec.rb +171 -0
  7. data/ext/packaging/tasks/00_utils.rake +186 -33
  8. data/ext/packaging/tasks/10_setupvars.rake +94 -65
  9. data/ext/packaging/tasks/20_setupextravars.rake +45 -26
  10. data/ext/packaging/tasks/30_metrics.rake +41 -0
  11. data/ext/packaging/tasks/apple.rake +92 -36
  12. data/ext/packaging/tasks/build.rake +183 -0
  13. data/ext/packaging/tasks/deb.rake +45 -40
  14. data/ext/packaging/tasks/deb_repos.rake +103 -0
  15. data/ext/packaging/tasks/doc.rake +5 -5
  16. data/ext/packaging/tasks/fetch.rake +35 -10
  17. data/ext/packaging/tasks/gem.rake +38 -27
  18. data/ext/packaging/tasks/ips.rake +14 -14
  19. data/ext/packaging/tasks/jenkins.rake +337 -0
  20. data/ext/packaging/tasks/mock.rake +153 -72
  21. data/ext/packaging/tasks/pe_deb.rake +2 -2
  22. data/ext/packaging/tasks/pe_remote.rake +22 -19
  23. data/ext/packaging/tasks/pe_rpm.rake +5 -5
  24. data/ext/packaging/tasks/pe_ship.rake +31 -21
  25. data/ext/packaging/tasks/pe_sign.rake +20 -19
  26. data/ext/packaging/tasks/pe_sles.rake +40 -36
  27. data/ext/packaging/tasks/pe_tar.rake +5 -0
  28. data/ext/packaging/tasks/release.rake +32 -12
  29. data/ext/packaging/tasks/remote_build.rake +141 -83
  30. data/ext/packaging/tasks/retrieve.rake +23 -0
  31. data/ext/packaging/tasks/rpm.rake +11 -19
  32. data/ext/packaging/tasks/rpm_repos.rake +127 -0
  33. data/ext/packaging/tasks/ship.rake +68 -55
  34. data/ext/packaging/tasks/sign.rake +38 -10
  35. data/ext/packaging/tasks/tar.rake +25 -9
  36. data/ext/packaging/tasks/update.rake +2 -2
  37. data/ext/packaging/tasks/version.rake +34 -14
  38. data/ext/packaging/tasks/z_data_dump.rake +33 -0
  39. data/lib/puppet/indirector/catalog/compiler.rb +13 -2
  40. data/lib/puppet/indirector/certificate_status/file.rb +5 -0
  41. data/lib/puppet/indirector/errors.rb +5 -0
  42. data/lib/puppet/indirector/file_bucket_file/file.rb +4 -0
  43. data/lib/puppet/indirector/file_bucket_file/selector.rb +4 -0
  44. data/lib/puppet/indirector/indirection.rb +1 -0
  45. data/lib/puppet/indirector/resource/active_record.rb +3 -0
  46. data/lib/puppet/indirector/resource/ral.rb +4 -0
  47. data/lib/puppet/indirector/resource/store_configs.rb +3 -0
  48. data/lib/puppet/indirector/resource/validator.rb +8 -0
  49. data/lib/puppet/indirector/rest.rb +8 -0
  50. data/lib/puppet/indirector/run/local.rb +4 -0
  51. data/lib/puppet/indirector/terminus.rb +20 -0
  52. data/lib/puppet/network/formats.rb +3 -3
  53. data/lib/puppet/network/handler/master.rb +1 -1
  54. data/lib/puppet/network/handler/report.rb +1 -1
  55. data/lib/puppet/network/http/handler.rb +7 -1
  56. data/lib/puppet/network/http/rack/rest.rb +7 -2
  57. data/lib/puppet/network/http/webrick.rb +1 -0
  58. data/lib/puppet/network/rest_authconfig.rb +1 -1
  59. data/lib/puppet/parser/templatewrapper.rb +17 -17
  60. data/lib/puppet/util/monkey_patches.rb +58 -0
  61. data/lib/puppet/version.rb +1 -1
  62. data/spec/integration/indirector/catalog/compiler_spec.rb +1 -0
  63. data/spec/integration/indirector/catalog/queue_spec.rb +1 -1
  64. data/spec/integration/resource/catalog_spec.rb +1 -0
  65. data/spec/unit/indirector/catalog/compiler_spec.rb +29 -2
  66. data/spec/unit/indirector/indirection_spec.rb +18 -1
  67. data/spec/unit/indirector/terminus_spec.rb +191 -177
  68. data/spec/unit/network/formats_spec.rb +6 -6
  69. data/spec/unit/network/http/handler_spec.rb +25 -0
  70. data/spec/unit/network/http/rack/rest_spec.rb +17 -0
  71. data/spec/unit/network/http/webrick_spec.rb +4 -0
  72. data/spec/unit/network/http_pool_spec.rb +0 -1
  73. data/spec/unit/network/rest_authconfig_spec.rb +16 -1
  74. data/spec/unit/parser/functions/inline_template_spec.rb +13 -0
  75. data/spec/unit/parser/functions/template_spec.rb +15 -0
  76. data/spec/unit/parser/templatewrapper_spec.rb +19 -4
  77. data/spec/unit/ssl/certificate_request_spec.rb +2 -0
  78. data/spec/unit/ssl/host_spec.rb +1 -0
  79. data/spec/unit/util/monkey_patches_spec.rb +12 -0
  80. data/test/language/snippets.rb +1 -1
  81. metadata +13 -2
@@ -1,9 +1,9 @@
1
1
  namespace :package do
2
2
  desc "Create a source tar archive"
3
3
  task :tar => [ :clean ] do
4
- Rake::Task["package:doc"].invoke if @build_doc
4
+ Rake::Task["package:doc"].invoke if @build.build_doc
5
5
  tar = ENV['TAR'] || 'tar'
6
- workdir = "pkg/#{@name}-#{@version}"
6
+ workdir = "pkg/#{@build.project}-#{@build.version}"
7
7
  mkdir_p(workdir)
8
8
 
9
9
  # The list of files to install in the tarball
@@ -13,13 +13,13 @@ namespace :package do
13
13
  # to support a mode where a space-separated string was used. Support both
14
14
  # to allow a gentle migration to a modern style...
15
15
  patterns =
16
- case @files
16
+ case @build.files
17
17
  when String
18
18
  STDERR.puts "warning: `files` should be an array, not a string"
19
- @files.split(' ')
19
+ @build.files.split(' ')
20
20
 
21
21
  when Array
22
- @files
22
+ @build.files
23
23
 
24
24
  else
25
25
  raise "`files` must be a string or an array!"
@@ -55,14 +55,30 @@ namespace :package do
55
55
  end
56
56
  end
57
57
 
58
- tar_excludes = @tar_excludes.nil? ? [] : @tar_excludes.split(' ')
59
- tar_excludes << "ext/#{@packaging_repo}"
58
+ tar_excludes = @build.tar_excludes.nil? ? [] : @build.tar_excludes.split(' ')
59
+ tar_excludes << "ext/#{@build.packaging_repo}"
60
60
  Rake::Task["package:template"].invoke(workdir)
61
+
62
+ # This is to support packages that only burn-in the version number in the
63
+ # release artifact, rather than storing it two (or more) times in the
64
+ # version control system. Razor is a good example of that; see
65
+ # https://github.com/puppetlabs/Razor/blob/master/lib/project_razor/version.rb
66
+ # for an example of that this looks like.
67
+ #
68
+ # If you set this the version will only be modified in the temporary copy,
69
+ # with the intent that it never change the official source tree.
70
+ ENV['NEW_STYLE_PACKAGE'] and Rake::Task["package:versionbump"].invoke(workdir)
71
+
61
72
  cd "pkg" do
62
- sh "#{tar} --exclude #{tar_excludes.join(" --exclude ")} -zcf #{@name}-#{@version}.tar.gz #{@name}-#{@version}"
73
+ sh "#{tar} --exclude #{tar_excludes.join(" --exclude ")} -zcf #{@build.project}-#{@build.version}.tar.gz #{@build.project}-#{@build.version}"
63
74
  end
64
75
  rm_rf(workdir)
65
76
  puts
66
- puts "Wrote #{`pwd`.strip}/pkg/#{@name}-#{@version}.tar.gz"
77
+ puts "Wrote #{`pwd`.strip}/pkg/#{@build.project}-#{@build.version}.tar.gz"
67
78
  end
68
79
  end
80
+
81
+ namespace :pl do
82
+ task :tar => ["package:tar"]
83
+ end
84
+
@@ -2,8 +2,8 @@ namespace :package do
2
2
  desc "Update your clone of the packaging repo with `git pull`"
3
3
  task :update do
4
4
  cd 'ext/packaging' do
5
- remote = @packaging_url.split(' ')[0]
6
- branch = @packaging_url.split(' ')[1].split('=')[1]
5
+ remote = @build.packaging_url.split(' ')[0]
6
+ branch = @build.packaging_url.split(' ')[1].split('=')[1]
7
7
  if branch.nil? or remote.nil?
8
8
  STDERR.puts "Couldn't parse the packaging repo URL from 'ext/build_defaults.yaml'."
9
9
  STDERR.puts "Normally this is a string in the format git@github.com:<User>/<packaging_repo> --branch=<branch>"
@@ -12,32 +12,52 @@
12
12
  # revisiting this task and improving it substantially,
13
13
  # and/or standardizing the expected version file format.
14
14
  namespace :package do
15
- desc "Update the version in #{@version_file} to current and commit."
16
- task :versionbump do
17
- version = ENV['VERSION'] || @version.to_s.strip
18
- old_version = get_version_file_version
19
- contents = IO.read(@version_file)
15
+ desc "Update the version in #{@build.version_file} to current and commit."
16
+ task :versionbump, :workdir do |t, args|
17
+ version = ENV['VERSION'] || @build.version.to_s.strip
20
18
  new_version = '"' + version + '"'
21
- puts "Updating #{old_version} to #{new_version} in #{@version_file}"
19
+
20
+ version_file = "#{args.workdir ? args.workdir + '/' : ''}#{@build.version_file}"
21
+
22
+ # Read the previous version file in...
23
+ contents = IO.read(version_file)
24
+
25
+ # Match version files containing 'VERSION = "x.x.x"' and just x.x.x
26
+ if version_string = contents.match(/VERSION =.*/)
27
+ old_version = version_string.to_s.split()[-1]
28
+ else
29
+ old_version = contents
30
+ end
31
+
32
+ puts "Updating #{old_version} to #{new_version} in #{version_file}"
22
33
  if contents.match("@DEVELOPMENT_VERSION@")
23
34
  contents.gsub!("@DEVELOPMENT_VERSION@", version)
35
+ elsif contents.match('version\s*=\s*[\'"]DEVELOPMENT[\'"]')
36
+ contents.gsub!(/version\s*=\s*['"]DEVELOPMENT['"]/, "version = '#{version}'")
24
37
  elsif contents.match("VERSION = #{old_version}")
25
38
  contents.gsub!("VERSION = #{old_version}", "VERSION = #{new_version}")
26
- elsif contents.match("#{@name.upcase}VERSION = #{old_version}")
27
- contents.gsub!("#{@name.upcase}VERSION = #{old_version}", "#{@name.upcase}VERSION = #{new_version}")
39
+ elsif contents.match("#{@build.project.upcase}VERSION = #{old_version}")
40
+ contents.gsub!("#{@build.project.upcase}VERSION = #{old_version}", "#{@build.project.upcase}VERSION = #{new_version}")
28
41
  else
29
- contents.gsub!(old_version, @version)
42
+ contents.gsub!(old_version, @build.version)
30
43
  end
31
- file = File.open(@version_file, 'w')
32
- file.write contents
33
- file.close
44
+
45
+ # ...and write it back on out.
46
+ File.open(version_file, 'w') {|f| f.write contents }
34
47
  end
35
48
 
36
- desc "Set and commit the version in #{@version_file}, requires VERSION."
49
+ desc "Set and commit the version in #{@build.version_file}, requires VERSION."
37
50
  task :versionset do
38
51
  check_var('VERSION', ENV['VERSION'])
39
52
  Rake::Task["package:versionbump"].invoke
40
- git_commit_file(@version_file, "update to #{ENV['VERSION']}")
53
+ git_commit_file(@build.version_file, "update to #{ENV['VERSION']}")
54
+ end
55
+
56
+ # A set of tasks for printing the version
57
+ [:version, :rpmversion, :rpmrelease, :debversion, :release].each do |task|
58
+ task "#{task}" do
59
+ STDOUT.puts self.instance_variable_get("@#{task}")
60
+ end
41
61
  end
42
62
  end
43
63
 
@@ -0,0 +1,33 @@
1
+ ##
2
+ # These tasks are just wrappers for the build objects capabilities, exposed
3
+ # for our debugging purposes. This file is prepended with `z_` to ensure it is
4
+ # loaded last, so that any variable manipulations that occur in the rake tasks
5
+ # happen prior to printing (although ideally all variables have been set after
6
+ # loading `20_setupextrasvars.rake`).
7
+ #
8
+ namespace :pl do
9
+ ##
10
+ # Utility rake task that will dump all current build parameters and variables
11
+ # to a yaml file to a temporary location and print the path. Given the
12
+ # environment variable 'OUTPUT_DIR', output file at 'OUTPUT_DIR'. The
13
+ # environment variable TASK sets the task instance variable of the build to the
14
+ # supplied args, allowing us to use this file for later builds.
15
+ #
16
+ desc "Write all package build parameters to a yaml file, pass OUTPUT_DIR to specify outut location"
17
+ task :write_build_params do
18
+ if ENV['TASK']
19
+ task_args = ENV['TASK'].split(' ')
20
+ @build.task = { :task => task_args[0], :args => task_args[1..-1] }
21
+ end
22
+ @build.params_to_yaml(ENV['OUTPUT_DIR'])
23
+ end
24
+
25
+ ##
26
+ # Print all build parameters to STDOUT.
27
+ #
28
+ desc "Print all package build parameters"
29
+ task :print_build_params do
30
+ @build.print_params
31
+ end
32
+ end
33
+
@@ -13,7 +13,9 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code
13
13
 
14
14
  def extract_facts_from_request(request)
15
15
  return unless text_facts = request.options[:facts]
16
- raise ArgumentError, "Facts but no fact format provided for #{request.name}" unless format = request.options[:facts_format]
16
+ unless format = request.options[:facts_format]
17
+ raise ArgumentError, "Facts but no fact format provided for #{request.key}"
18
+ end
17
19
 
18
20
  # If the facts were encoded as yaml, then the param reconstitution system
19
21
  # in Network::HTTP::Handler will automagically deserialize the value.
@@ -22,6 +24,11 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code
22
24
  else
23
25
  facts = Puppet::Node::Facts.convert_from(format, text_facts)
24
26
  end
27
+
28
+ unless facts.name == request.key
29
+ raise Puppet::Error, "Catalog for #{request.key.inspect} was requested with fact definition for the wrong node (#{facts.name.inspect})."
30
+ end
31
+
25
32
  facts.add_timestamp
26
33
  Puppet::Node::Facts.indirection.save(facts)
27
34
  end
@@ -104,7 +111,11 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code
104
111
  # to find the node.
105
112
  def node_from_request(request)
106
113
  if node = request.options[:use_node]
107
- return node
114
+ if request.remote?
115
+ raise Puppet::Error, "Invalid option use_node for a remote request"
116
+ else
117
+ return node
118
+ end
108
119
  end
109
120
 
110
121
  # We rely on our authorization system to determine whether the connected
@@ -79,4 +79,9 @@ class Puppet::Indirector::CertificateStatus::File < Puppet::Indirector::Code
79
79
  nil
80
80
  end
81
81
  end
82
+
83
+ def validate_key(request)
84
+ # We only use desired_state from the instance and use request.key
85
+ # otherwise, so the name does not need to match
86
+ end
82
87
  end
@@ -0,0 +1,5 @@
1
+ require 'puppet/error'
2
+
3
+ module Puppet::Indirector
4
+ class ValidationError < Puppet::Error; end
5
+ end
@@ -48,6 +48,10 @@ module Puppet::FileBucketFile
48
48
  instance.to_s
49
49
  end
50
50
 
51
+ def validate_key(request)
52
+ # There are no ACLs on filebucket files so validating key is not important
53
+ end
54
+
51
55
  private
52
56
 
53
57
  def path_match(dir_path, files_original_path)
@@ -44,6 +44,10 @@ module Puppet::FileBucketFile
44
44
  true
45
45
  end
46
46
  end
47
+
48
+ def validate_key(request)
49
+ get_terminus(request).validate(request)
50
+ end
47
51
  end
48
52
  end
49
53
 
@@ -308,6 +308,7 @@ class Puppet::Indirector::Indirection
308
308
 
309
309
  dest_terminus = terminus(terminus_name)
310
310
  check_authorization(request, dest_terminus)
311
+ dest_terminus.validate(request)
311
312
 
312
313
  dest_terminus
313
314
  end
@@ -1,6 +1,9 @@
1
1
  require 'puppet/indirector/active_record'
2
+ require 'puppet/indirector/resource/validator'
2
3
 
3
4
  class Puppet::Resource::ActiveRecord < Puppet::Indirector::ActiveRecord
5
+ include Puppet::Resource::Validator
6
+
4
7
  def search(request)
5
8
  type = request_to_type_name(request)
6
9
  host = request.options[:host]
@@ -1,4 +1,8 @@
1
+ require 'puppet/indirector/resource/validator'
2
+
1
3
  class Puppet::Resource::Ral < Puppet::Indirector::Code
4
+ include Puppet::Resource::Validator
5
+
2
6
  def find( request )
3
7
  # find by name
4
8
  res = type(request).instances.find { |o| o.name == resource_name(request) }
@@ -1,3 +1,6 @@
1
1
  require 'puppet/indirector/store_configs'
2
+ require 'puppet/indirector/resource/validator'
3
+
2
4
  class Puppet::Resource::StoreConfigs < Puppet::Indirector::StoreConfigs
5
+ include Puppet::Resource::Validator
3
6
  end
@@ -0,0 +1,8 @@
1
+ module Puppet::Resource::Validator
2
+ def validate_key(request)
3
+ type, title = request.key.split('/', 2)
4
+ unless type.downcase == request.instance.type.downcase and title == request.instance.title
5
+ raise Puppet::Indirector::ValidationError, "Resource instance does not match request key"
6
+ end
7
+ end
8
+ end
@@ -108,6 +108,10 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus
108
108
  msg = valid_certnames.length > 1 ? "one of #{valid_certnames.join(', ')}" : valid_certnames.first
109
109
 
110
110
  raise Puppet::Error, "Server hostname '#{http_connection.address}' did not match server certificate; expected #{msg}"
111
+ elsif error.message.empty?
112
+ # This may be because the server is speaking SSLv2 and we
113
+ # monkey patch OpenSSL::SSL:SSLContext to reject SSLv2.
114
+ raise error.exception("#{error.class} with no message")
111
115
  else
112
116
  raise
113
117
  end
@@ -158,6 +162,10 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus
158
162
  deserialize http_put(request, indirection2uri(request), request.instance.render, headers.merge({ "Content-Type" => request.instance.mime }))
159
163
  end
160
164
 
165
+ def validate_key(request)
166
+ # Validation happens on the remote end
167
+ end
168
+
161
169
  private
162
170
 
163
171
  def environment
@@ -5,4 +5,8 @@ class Puppet::Run::Local < Puppet::Indirector::Code
5
5
  def save( request )
6
6
  request.instance.run
7
7
  end
8
+
9
+ def validate_key(request)
10
+ # No key is necessary for kick
11
+ end
8
12
  end
@@ -1,4 +1,5 @@
1
1
  require 'puppet/indirector'
2
+ require 'puppet/indirector/errors'
2
3
  require 'puppet/indirector/indirection'
3
4
  require 'puppet/util/instance_loader'
4
5
 
@@ -142,4 +143,23 @@ class Puppet::Indirector::Terminus
142
143
  def terminus_type
143
144
  self.class.terminus_type
144
145
  end
146
+
147
+ def validate(request)
148
+ if request.instance
149
+ validate_model(request)
150
+ validate_key(request)
151
+ end
152
+ end
153
+
154
+ def validate_key(request)
155
+ unless request.key == request.instance.name
156
+ raise Puppet::Indirector::ValidationError, "Instance name #{request.instance.name.inspect} does not match requested key #{request.key.inspect}"
157
+ end
158
+ end
159
+
160
+ def validate_model(request)
161
+ unless model === request.instance
162
+ raise Puppet::Indirector::ValidationError, "Invalid instance type #{request.instance.class.inspect}, expected #{model.inspect}"
163
+ end
164
+ end
145
165
  end
@@ -3,12 +3,12 @@ require 'puppet/network/format_handler'
3
3
  Puppet::Network::FormatHandler.create_serialized_formats(:yaml) do
4
4
  # Yaml doesn't need the class name; it's serialized.
5
5
  def intern(klass, text)
6
- YAML.load(text)
6
+ YAML.safely_load(text)
7
7
  end
8
8
 
9
9
  # Yaml doesn't need the class name; it's serialized.
10
10
  def intern_multiple(klass, text)
11
- YAML.load(text)
11
+ YAML.safely_load(text)
12
12
  end
13
13
 
14
14
  def render(instance)
@@ -72,7 +72,7 @@ Puppet::Network::FormatHandler.create_serialized_formats(:b64_zlib_yaml) do
72
72
 
73
73
  def decode(yaml)
74
74
  requiring_zlib do
75
- YAML.load(Zlib::Inflate.inflate(Base64.decode64(yaml)))
75
+ YAML.safely_load(Zlib::Inflate.inflate(Base64.decode64(yaml)))
76
76
  end
77
77
  end
78
78
  end
@@ -69,7 +69,7 @@ class Puppet::Network::Handler
69
69
  Puppet.debug "Our client is remote"
70
70
 
71
71
  begin
72
- facts = YAML.load(CGI.unescape(facts))
72
+ facts = YAML.safely_load(CGI.unescape(facts))
73
73
  rescue => detail
74
74
  raise XMLRPC::FaultException.new(
75
75
  1, "Could not rebuild facts"
@@ -45,7 +45,7 @@ class Puppet::Network::Handler
45
45
 
46
46
  # First convert the report to real objects
47
47
  begin
48
- report = YAML.load(yaml)
48
+ report = YAML.safely_load(yaml)
49
49
  rescue => detail
50
50
  Puppet.warning "Could not load report: #{detail}"
51
51
  return
@@ -70,6 +70,8 @@ module Puppet::Network::HTTP::Handler
70
70
  raise
71
71
  rescue Exception => e
72
72
  return do_exception(response, e)
73
+ ensure
74
+ cleanup(request)
73
75
  end
74
76
 
75
77
  # Set the response up, with the body and status.
@@ -220,6 +222,10 @@ module Puppet::Network::HTTP::Handler
220
222
  raise NotImplementedError
221
223
  end
222
224
 
225
+ def cleanup(request)
226
+ # By default, there is nothing to cleanup.
227
+ end
228
+
223
229
  def decode_params(params)
224
230
  params.inject({}) do |result, ary|
225
231
  param, value = ary
@@ -233,7 +239,7 @@ module Puppet::Network::HTTP::Handler
233
239
  next result if param == :ip
234
240
  value = CGI.unescape(value)
235
241
  if value =~ /^---/
236
- value = YAML.load(value)
242
+ value = YAML.safely_load(value)
237
243
  else
238
244
  value = true if value == "true"
239
245
  value = false if value == "false"
@@ -73,12 +73,17 @@ class Puppet::Network::HTTP::RackREST < Puppet::Network::HTTP::RackHttpHandler
73
73
  end
74
74
 
75
75
  # return the request body
76
- # request.body has some limitiations, so we need to concat it back
77
- # into a regular string, which is something puppet can use.
78
76
  def body(request)
79
77
  request.body.read
80
78
  end
81
79
 
80
+ # Passenger freaks out if we finish handling the request without reading any
81
+ # part of the body, so make sure we have.
82
+ def cleanup(request)
83
+ request.body.read(1)
84
+ nil
85
+ end
86
+
82
87
  def extract_client_info(request)
83
88
  result = {}
84
89
  result[:ip] = request.ip