appmap 0.31.0 → 0.32.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +30 -2
- data/lib/appmap.rb +1 -0
- data/lib/appmap/class_map.rb +7 -6
- data/lib/appmap/config.rb +11 -13
- data/lib/appmap/event.rb +25 -16
- data/lib/appmap/hook.rb +16 -66
- data/lib/appmap/hook/method.rb +78 -0
- data/lib/appmap/rails/action_handler.rb +7 -7
- data/lib/appmap/rails/sql_handler.rb +10 -8
- data/lib/appmap/trace.rb +7 -7
- data/lib/appmap/util.rb +19 -0
- data/lib/appmap/version.rb +1 -1
- data/spec/abstract_controller4_base_spec.rb +1 -1
- data/spec/abstract_controller_base_spec.rb +9 -2
- data/spec/hook_spec.rb +5 -37
- data/spec/record_sql_rails_pg_spec.rb +56 -33
- data/test/cli_test.rb +2 -2
- data/test/test_helper.rb +1 -0
- metadata +3 -3
- data/spec/fixtures/hook/openssl_sign.rb +0 -87
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f6becd9530caf01e14b8a7db69e74b40297d41e4724f25315cd7b44ea6ad3e02
|
4
|
+
data.tar.gz: 2863e714f0651d73fc9f2f87d0ea2c5f9fb7f9f553d1f109d9d8f10fff35f5be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5574478fee68b2531449df2b9ec135a60110950cdcd518cf5b85d10e6d0d756b3e22740a4ce0905d7fccb2e6e7d684ee38ac61fd11ad6fdfcff4355a7ae7b36
|
7
|
+
data.tar.gz: d15827a809ffde6b6ff98bfb62057f759cdc03e7e24452c1a8273801f94e37d61bab265ddb863ce8f60d83c639875fcf2d247f27abe9b27164817f0739b7c699
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -33,7 +33,7 @@ There are several ways to record AppMaps of your Ruby program using the `appmap`
|
|
33
33
|
|
34
34
|
Once you have recorded some AppMaps (for example, by running RSpec tests), you use the `appland upload` command
|
35
35
|
to upload them to the AppLand server. This command, and some others, is provided
|
36
|
-
by the [AppLand CLI](https://github.com/applandinc/appland-cli/releases)
|
36
|
+
by the [AppLand CLI](https://github.com/applandinc/appland-cli/releases).
|
37
37
|
Then, on the [AppLand website](https://app.land), you can
|
38
38
|
visualize the design of your code and share links with collaborators.
|
39
39
|
|
@@ -87,12 +87,25 @@ Each entry in the `packages` list is a YAML object which has the following keys:
|
|
87
87
|
|
88
88
|
To record RSpec tests, follow these additional steps:
|
89
89
|
|
90
|
-
1) Require `appmap/rspec` in your `spec_helper.rb
|
90
|
+
1) Require `appmap/rspec` in your `spec_helper.rb` before any other classes are loaded.
|
91
91
|
|
92
92
|
```ruby
|
93
93
|
require 'appmap/rspec'
|
94
94
|
```
|
95
95
|
|
96
|
+
Note that `spec_helper.rb` in a Rails project typically loads the application's classes this way:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
require File.expand_path("../../config/environment", __FILE__)
|
100
|
+
```
|
101
|
+
|
102
|
+
and `appmap/rspec` must be required before this:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
require 'appmap/rspec'
|
106
|
+
require File.expand_path("../../config/environment", __FILE__)
|
107
|
+
```
|
108
|
+
|
96
109
|
2) *Optional* Add `feature: '<feature name>'` and `feature_group: '<feature group name>'` annotations to your
|
97
110
|
examples.
|
98
111
|
|
@@ -136,6 +149,19 @@ To record Minitest tests, follow these additional steps:
|
|
136
149
|
require 'appmap/minitest'
|
137
150
|
```
|
138
151
|
|
152
|
+
Note that `test_helper.rb` in a Rails project typically loads the application's classes this way:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
require_relative '../config/environment'
|
156
|
+
```
|
157
|
+
|
158
|
+
and `appmap/rspec` must be required before this:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
require 'appmap/rspec'
|
162
|
+
require_relative '../config/environment'
|
163
|
+
```
|
164
|
+
|
139
165
|
2) Run the tests with the environment variable `APPMAP=true`:
|
140
166
|
|
141
167
|
```sh-session
|
@@ -159,6 +185,8 @@ To record Cucumber tests, follow these additional steps:
|
|
159
185
|
require 'appmap/cucumber'
|
160
186
|
```
|
161
187
|
|
188
|
+
Be sure to require it before `config/environment` is required.
|
189
|
+
|
162
190
|
2) Create an `Around` hook in `support/hooks.rb` to record the scenario:
|
163
191
|
|
164
192
|
|
data/lib/appmap.rb
CHANGED
data/lib/appmap/class_map.rb
CHANGED
@@ -80,10 +80,6 @@ module AppMap
|
|
80
80
|
protected
|
81
81
|
|
82
82
|
def add_function(root, package, method)
|
83
|
-
location = method.source_location
|
84
|
-
location_file, lineno = location
|
85
|
-
location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
|
86
|
-
|
87
83
|
static = method.static
|
88
84
|
|
89
85
|
object_infos = [
|
@@ -101,12 +97,17 @@ module AppMap
|
|
101
97
|
function_info = {
|
102
98
|
name: method.name,
|
103
99
|
type: 'function',
|
104
|
-
location: [ location_file, lineno ].join(':'),
|
105
100
|
static: static
|
106
101
|
}
|
102
|
+
location = method.source_location
|
103
|
+
if location
|
104
|
+
location_file, lineno = location
|
105
|
+
location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
|
106
|
+
function_info[:location] = [ location_file, lineno ].join(':')
|
107
|
+
end
|
107
108
|
function_info[:labels] = package.labels if package.labels
|
108
109
|
object_infos << function_info
|
109
|
-
|
110
|
+
|
110
111
|
parent = root
|
111
112
|
object_infos.each do |info|
|
112
113
|
parent = find_or_create parent.children, info do
|
data/lib/appmap/config.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AppMap
|
4
|
-
Package = Struct.new(:path, :exclude, :labels) do
|
5
|
-
def initialize(path, exclude, labels = nil)
|
4
|
+
Package = Struct.new(:path, :package_name, :exclude, :labels) do
|
5
|
+
def initialize(path, package_name, exclude, labels = nil)
|
6
6
|
super
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
def to_h
|
10
10
|
{
|
11
11
|
path: path,
|
12
|
+
package_name: package_name,
|
12
13
|
exclude: exclude.blank? ? nil : exclude,
|
13
14
|
labels: labels.blank? ? nil : labels
|
14
15
|
}.compact
|
@@ -20,10 +21,7 @@ module AppMap
|
|
20
21
|
# package and labels that should be applied to them.
|
21
22
|
HOOKED_METHODS = {
|
22
23
|
'ActiveSupport::SecurityUtils' => {
|
23
|
-
secure_compare: Package.new('active_support', nil, ['security'])
|
24
|
-
},
|
25
|
-
'OpenSSL::X509::Certificate' => {
|
26
|
-
sign: Package.new('openssl', nil, ['security'])
|
24
|
+
secure_compare: Package.new('active_support', nil, nil, ['security'])
|
27
25
|
}
|
28
26
|
}
|
29
27
|
|
@@ -43,7 +41,7 @@ module AppMap
|
|
43
41
|
# Loads configuration from a Hash.
|
44
42
|
def load(config_data)
|
45
43
|
packages = (config_data['packages'] || []).map do |package|
|
46
|
-
Package.new(package['path'], package['exclude'] || [])
|
44
|
+
Package.new(package['path'], nil, package['exclude'] || [])
|
47
45
|
end
|
48
46
|
Config.new config_data['name'], packages
|
49
47
|
end
|
@@ -57,14 +55,14 @@ module AppMap
|
|
57
55
|
end
|
58
56
|
|
59
57
|
def package_for_method(method)
|
58
|
+
defined_class, _, method_name = Hook.qualify_method_name(method)
|
59
|
+
hooked_method = find_hooked_method(defined_class, method_name)
|
60
|
+
return hooked_method if hooked_method
|
61
|
+
|
60
62
|
location = method.source_location
|
61
63
|
location_file, = location
|
62
64
|
return unless location_file
|
63
65
|
|
64
|
-
defined_class,_,method_name = Hook.qualify_method_name(method)
|
65
|
-
hooked_method = find_hooked_method(defined_class, method_name)
|
66
|
-
return hooked_method if hooked_method
|
67
|
-
|
68
66
|
location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
|
69
67
|
packages.find do |pkg|
|
70
68
|
(location_file.index(pkg.path) == 0) &&
|
@@ -83,7 +81,7 @@ module AppMap
|
|
83
81
|
def find_hooked_method(defined_class, method_name)
|
84
82
|
find_hooked_class(defined_class)[method_name]
|
85
83
|
end
|
86
|
-
|
84
|
+
|
87
85
|
def find_hooked_class(defined_class)
|
88
86
|
HOOKED_METHODS[defined_class] || {}
|
89
87
|
end
|
data/lib/appmap/event.rb
CHANGED
@@ -15,21 +15,15 @@ module AppMap
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
MethodEventStruct = Struct.new(:id, :event, :
|
18
|
+
MethodEventStruct = Struct.new(:id, :event, :thread_id)
|
19
19
|
|
20
20
|
class MethodEvent < MethodEventStruct
|
21
21
|
LIMIT = 100
|
22
22
|
|
23
23
|
class << self
|
24
|
-
def build_from_invocation(me, event_type
|
24
|
+
def build_from_invocation(me, event_type)
|
25
25
|
me.id = AppMap::Event.next_id_counter
|
26
26
|
me.event = event_type
|
27
|
-
me.defined_class = defined_class
|
28
|
-
me.method_id = method.name.to_s
|
29
|
-
path = method.source_location[0]
|
30
|
-
path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
|
31
|
-
me.path = path
|
32
|
-
me.lineno = method.source_location[1]
|
33
27
|
me.thread_id = Thread.current.object_id
|
34
28
|
end
|
35
29
|
|
@@ -58,15 +52,25 @@ module AppMap
|
|
58
52
|
(value_string||'')[0...LIMIT].encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
|
59
53
|
end
|
60
54
|
end
|
61
|
-
|
62
55
|
end
|
63
56
|
|
64
57
|
class MethodCall < MethodEvent
|
65
|
-
attr_accessor :parameters, :receiver, :static
|
58
|
+
attr_accessor :defined_class, :method_id, :path, :lineno, :parameters, :receiver, :static
|
66
59
|
|
67
60
|
class << self
|
68
61
|
def build_from_invocation(mc = MethodCall.new, defined_class, method, receiver, arguments)
|
69
62
|
mc.tap do
|
63
|
+
static = receiver.is_a?(Module)
|
64
|
+
mc.defined_class = defined_class
|
65
|
+
mc.method_id = method.name.to_s
|
66
|
+
if method.source_location
|
67
|
+
path = method.source_location[0]
|
68
|
+
path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
|
69
|
+
mc.path = path
|
70
|
+
mc.lineno = method.source_location[1]
|
71
|
+
else
|
72
|
+
mc.path = [ defined_class, static ? '.' : '#', method.name ].join
|
73
|
+
end
|
70
74
|
mc.parameters = method.parameters.map.with_index do |method_param, idx|
|
71
75
|
param_type, param_name = method_param
|
72
76
|
param_name ||= 'arg'
|
@@ -84,17 +88,22 @@ module AppMap
|
|
84
88
|
object_id: receiver.__id__,
|
85
89
|
value: display_string(receiver)
|
86
90
|
}
|
87
|
-
mc.static =
|
88
|
-
MethodEvent.build_from_invocation(mc, :call
|
91
|
+
mc.static = static
|
92
|
+
MethodEvent.build_from_invocation(mc, :call)
|
89
93
|
end
|
90
94
|
end
|
91
95
|
end
|
92
96
|
|
93
97
|
def to_h
|
94
98
|
super.tap do |h|
|
99
|
+
h[:defined_class] = defined_class
|
100
|
+
h[:method_id] = method_id
|
101
|
+
h[:path] = path
|
102
|
+
h[:lineno] = lineno
|
95
103
|
h[:static] = static
|
96
104
|
h[:parameters] = parameters
|
97
105
|
h[:receiver] = receiver
|
106
|
+
h.delete_if { |_, v| v.nil? }
|
98
107
|
end
|
99
108
|
end
|
100
109
|
|
@@ -105,11 +114,11 @@ module AppMap
|
|
105
114
|
attr_accessor :parent_id, :elapsed
|
106
115
|
|
107
116
|
class << self
|
108
|
-
def build_from_invocation(mr = MethodReturnIgnoreValue.new,
|
117
|
+
def build_from_invocation(mr = MethodReturnIgnoreValue.new, parent_id, elapsed)
|
109
118
|
mr.tap do |_|
|
110
119
|
mr.parent_id = parent_id
|
111
120
|
mr.elapsed = elapsed
|
112
|
-
MethodEvent.build_from_invocation(mr, :return
|
121
|
+
MethodEvent.build_from_invocation(mr, :return)
|
113
122
|
end
|
114
123
|
end
|
115
124
|
end
|
@@ -126,7 +135,7 @@ module AppMap
|
|
126
135
|
attr_accessor :return_value, :exceptions
|
127
136
|
|
128
137
|
class << self
|
129
|
-
def build_from_invocation(mr = MethodReturn.new,
|
138
|
+
def build_from_invocation(mr = MethodReturn.new, parent_id, elapsed, return_value, exception)
|
130
139
|
mr.tap do |_|
|
131
140
|
if return_value
|
132
141
|
mr.return_value = {
|
@@ -152,7 +161,7 @@ module AppMap
|
|
152
161
|
|
153
162
|
mr.exceptions = exceptions
|
154
163
|
end
|
155
|
-
MethodReturnIgnoreValue.build_from_invocation(mr,
|
164
|
+
MethodReturnIgnoreValue.build_from_invocation(mr, parent_id, elapsed)
|
156
165
|
end
|
157
166
|
end
|
158
167
|
end
|
data/lib/appmap/hook.rb
CHANGED
@@ -4,9 +4,7 @@ require 'English'
|
|
4
4
|
|
5
5
|
module AppMap
|
6
6
|
class Hook
|
7
|
-
LOG =
|
8
|
-
|
9
|
-
HOOK_DISABLE_KEY = 'AppMap::Hook.disable'
|
7
|
+
LOG = (ENV['DEBUG'] == 'true')
|
10
8
|
|
11
9
|
class << self
|
12
10
|
# Return the class, separator ('.' or '#'), and method name for
|
@@ -17,7 +15,7 @@ module AppMap
|
|
17
15
|
# #<Class:Foo> or
|
18
16
|
# #<Class:#<Bar:0x0123ABC>>. Retrieve the name of
|
19
17
|
# the class from the string.
|
20
|
-
#
|
18
|
+
#
|
21
19
|
# (There really isn't a better way to do this. The
|
22
20
|
# singleton's reference to the class it was created
|
23
21
|
# from is stored in an instance variable named
|
@@ -39,89 +37,41 @@ module AppMap
|
|
39
37
|
|
40
38
|
# Observe class loading and hook all methods which match the config.
|
41
39
|
def enable &block
|
42
|
-
|
43
|
-
require 'appmap/event'
|
44
|
-
call_event = AppMap::Event::MethodCall.build_from_invocation(defined_class, method, receiver, args)
|
45
|
-
AppMap.tracing.record_event call_event, defined_class: defined_class, method: method
|
46
|
-
[ call_event, Time.now ]
|
47
|
-
end
|
48
|
-
|
49
|
-
after_hook = lambda do |call_event, defined_class, method, start_time, return_value, exception|
|
50
|
-
require 'appmap/event'
|
51
|
-
elapsed = Time.now - start_time
|
52
|
-
return_event = AppMap::Event::MethodReturn.build_from_invocation \
|
53
|
-
defined_class, method, call_event.id, elapsed, return_value, exception
|
54
|
-
AppMap.tracing.record_event return_event
|
55
|
-
end
|
56
|
-
|
57
|
-
with_disabled_hook = lambda do |&fn|
|
58
|
-
# Don't record functions, such as to_s and inspect, that might be called
|
59
|
-
# by the fn. Otherwise there can be a stack overflow.
|
60
|
-
Thread.current[HOOK_DISABLE_KEY] = true
|
61
|
-
begin
|
62
|
-
fn.call
|
63
|
-
ensure
|
64
|
-
Thread.current[HOOK_DISABLE_KEY] = false
|
65
|
-
end
|
66
|
-
end
|
40
|
+
require 'appmap/hook/method'
|
67
41
|
|
68
|
-
tp = TracePoint.new(:end) do |
|
69
|
-
|
70
|
-
cls = tp.self
|
42
|
+
tp = TracePoint.new(:end) do |trace_point|
|
43
|
+
cls = trace_point.self
|
71
44
|
|
72
45
|
instance_methods = cls.public_instance_methods(false)
|
73
46
|
class_methods = cls.singleton_class.public_instance_methods(false) - instance_methods
|
74
47
|
|
75
|
-
|
48
|
+
hook = lambda do |hook_cls|
|
76
49
|
lambda do |method_id|
|
77
50
|
next if method_id.to_s =~ /_hooked_by_appmap$/
|
78
51
|
|
79
|
-
method =
|
52
|
+
method = hook_cls.public_instance_method(method_id)
|
53
|
+
hook_method = Hook::Method.new(hook_cls, method)
|
54
|
+
|
55
|
+
warn "AppMap: Examining #{hook_method.method_display_name}" if LOG
|
56
|
+
|
80
57
|
disasm = RubyVM::InstructionSequence.disasm(method)
|
81
58
|
# Skip methods that have no instruction sequence, as they are obviously trivial.
|
82
59
|
next unless disasm
|
83
60
|
|
84
|
-
defined_class, method_symbol, method_name = Hook.qualify_method_name(method)
|
85
|
-
method_display_name = [defined_class, method_symbol, method_name].join
|
86
|
-
|
87
61
|
# Don't try and trace the AppMap methods or there will be
|
88
62
|
# a stack overflow in the defined hook method.
|
89
|
-
next if /\AAppMap[:\.]/.match?(method_display_name)
|
63
|
+
next if /\AAppMap[:\.]/.match?(hook_method.method_display_name)
|
90
64
|
|
91
65
|
next unless \
|
92
|
-
config.always_hook?(defined_class,
|
66
|
+
config.always_hook?(hook_method.defined_class, method.name) ||
|
93
67
|
config.included_by_location?(method)
|
94
68
|
|
95
|
-
|
96
|
-
|
97
|
-
cls.define_method method_id do |*args, &block|
|
98
|
-
base_method = method.bind(self).to_proc
|
99
|
-
|
100
|
-
hook_disabled = Thread.current[HOOK_DISABLE_KEY]
|
101
|
-
enabled = true if !hook_disabled && AppMap.tracing.enabled?
|
102
|
-
return base_method.call(*args, &block) unless enabled
|
103
|
-
|
104
|
-
call_event, start_time = with_disabled_hook.call do
|
105
|
-
before_hook.call(defined_class, method, self, args)
|
106
|
-
end
|
107
|
-
return_value = nil
|
108
|
-
exception = nil
|
109
|
-
begin
|
110
|
-
return_value = base_method.call(*args, &block)
|
111
|
-
rescue
|
112
|
-
exception = $ERROR_INFO
|
113
|
-
raise
|
114
|
-
ensure
|
115
|
-
with_disabled_hook.call do
|
116
|
-
after_hook.call(call_event, defined_class, method, start_time, return_value, exception)
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
69
|
+
hook_method.activate
|
120
70
|
end
|
121
71
|
end
|
122
72
|
|
123
|
-
instance_methods.each(&
|
124
|
-
class_methods.each(&
|
73
|
+
instance_methods.each(&hook.(cls))
|
74
|
+
class_methods.each(&hook.(cls.singleton_class))
|
125
75
|
end
|
126
76
|
|
127
77
|
tp.enable(&block)
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module AppMap
|
2
|
+
class Hook
|
3
|
+
class Method
|
4
|
+
attr_reader :hook_class, :hook_method, :defined_class, :method_display_name
|
5
|
+
|
6
|
+
HOOK_DISABLE_KEY = 'AppMap::Hook.disable'
|
7
|
+
private_constant :HOOK_DISABLE_KEY
|
8
|
+
|
9
|
+
def initialize(hook_class, hook_method)
|
10
|
+
@hook_class = hook_class
|
11
|
+
@hook_method = hook_method
|
12
|
+
@defined_class, method_symbol = Hook.qualify_method_name(@hook_method)
|
13
|
+
@method_display_name = [@defined_class, method_symbol, @hook_method.name].join
|
14
|
+
end
|
15
|
+
|
16
|
+
def activate
|
17
|
+
warn "AppMap: Hooking #{method_display_name}" if Hook::LOG
|
18
|
+
|
19
|
+
hook_method = self.hook_method
|
20
|
+
before_hook = self.method(:before_hook)
|
21
|
+
after_hook = self.method(:after_hook)
|
22
|
+
with_disabled_hook = self.method(:with_disabled_hook)
|
23
|
+
|
24
|
+
hook_class.define_method hook_method.name do |*args, &block|
|
25
|
+
instance_method = hook_method.bind(self).to_proc
|
26
|
+
|
27
|
+
hook_disabled = Thread.current[HOOK_DISABLE_KEY]
|
28
|
+
enabled = true if !hook_disabled && AppMap.tracing.enabled?
|
29
|
+
return instance_method.call(*args, &block) unless enabled
|
30
|
+
|
31
|
+
call_event, start_time = with_disabled_hook.() do
|
32
|
+
before_hook.(self, args)
|
33
|
+
end
|
34
|
+
return_value = nil
|
35
|
+
exception = nil
|
36
|
+
begin
|
37
|
+
return_value = instance_method.(*args, &block)
|
38
|
+
rescue
|
39
|
+
exception = $ERROR_INFO
|
40
|
+
raise
|
41
|
+
ensure
|
42
|
+
with_disabled_hook.() do
|
43
|
+
after_hook.(call_event, start_time, return_value, exception)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def before_hook(receiver, args)
|
52
|
+
require 'appmap/event'
|
53
|
+
call_event = AppMap::Event::MethodCall.build_from_invocation(defined_class, hook_method, receiver, args)
|
54
|
+
AppMap.tracing.record_event call_event, defined_class: defined_class, method: hook_method
|
55
|
+
[ call_event, Time.now ]
|
56
|
+
end
|
57
|
+
|
58
|
+
def after_hook(call_event, start_time, return_value, exception)
|
59
|
+
require 'appmap/event'
|
60
|
+
elapsed = Time.now - start_time
|
61
|
+
return_event = \
|
62
|
+
AppMap::Event::MethodReturn.build_from_invocation call_event.id, elapsed, return_value, exception
|
63
|
+
AppMap.tracing.record_event return_event
|
64
|
+
end
|
65
|
+
|
66
|
+
def with_disabled_hook(&fn)
|
67
|
+
# Don't record functions, such as to_s and inspect, that might be called
|
68
|
+
# by the fn. Otherwise there can be a stack overflow.
|
69
|
+
Thread.current[HOOK_DISABLE_KEY] = true
|
70
|
+
begin
|
71
|
+
fn.call
|
72
|
+
ensure
|
73
|
+
Thread.current[HOOK_DISABLE_KEY] = false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -16,11 +16,11 @@ module AppMap
|
|
16
16
|
class HTTPServerRequest
|
17
17
|
include ContextKey
|
18
18
|
|
19
|
-
class Call < AppMap::Event::
|
19
|
+
class Call < AppMap::Event::MethodCall
|
20
20
|
attr_accessor :payload
|
21
21
|
|
22
|
-
def initialize(
|
23
|
-
super AppMap::Event.next_id_counter, :call,
|
22
|
+
def initialize(payload)
|
23
|
+
super AppMap::Event.next_id_counter, :call, Thread.current.object_id
|
24
24
|
|
25
25
|
self.payload = payload
|
26
26
|
end
|
@@ -47,7 +47,7 @@ module AppMap
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def call(_, started, finished, _, payload) # (name, started, finished, unique_id, payload)
|
50
|
-
event = Call.new(
|
50
|
+
event = Call.new(payload)
|
51
51
|
Thread.current[context_key] = Context.new(event.id, Time.now)
|
52
52
|
AppMap.tracing.record_event(event)
|
53
53
|
end
|
@@ -59,8 +59,8 @@ module AppMap
|
|
59
59
|
class Call < AppMap::Event::MethodReturnIgnoreValue
|
60
60
|
attr_accessor :payload
|
61
61
|
|
62
|
-
def initialize(
|
63
|
-
super AppMap::Event.next_id_counter, :return,
|
62
|
+
def initialize(payload, parent_id, elapsed)
|
63
|
+
super AppMap::Event.next_id_counter, :return, Thread.current.object_id
|
64
64
|
|
65
65
|
self.payload = payload
|
66
66
|
self.parent_id = parent_id
|
@@ -82,7 +82,7 @@ module AppMap
|
|
82
82
|
context = Thread.current[context_key]
|
83
83
|
Thread.current[context_key] = nil
|
84
84
|
|
85
|
-
event = Call.new(
|
85
|
+
event = Call.new(payload, context.id, Time.now - context.start_time)
|
86
86
|
AppMap.tracing.record_event(event)
|
87
87
|
end
|
88
88
|
end
|
@@ -5,11 +5,11 @@ require 'appmap/event'
|
|
5
5
|
module AppMap
|
6
6
|
module Rails
|
7
7
|
class SQLHandler
|
8
|
-
class SQLCall < AppMap::Event::
|
8
|
+
class SQLCall < AppMap::Event::MethodCall
|
9
9
|
attr_accessor :payload
|
10
10
|
|
11
|
-
def initialize(
|
12
|
-
super AppMap::Event.next_id_counter, :call,
|
11
|
+
def initialize(payload)
|
12
|
+
super AppMap::Event.next_id_counter, :call, Thread.current.object_id
|
13
13
|
|
14
14
|
self.payload = payload
|
15
15
|
end
|
@@ -20,7 +20,7 @@ module AppMap
|
|
20
20
|
sql: payload[:sql],
|
21
21
|
database_type: payload[:database_type]
|
22
22
|
}.tap do |sql_query|
|
23
|
-
%i[server_version
|
23
|
+
%i[server_version].each do |attribute|
|
24
24
|
sql_query[attribute] = payload[attribute] if payload[attribute]
|
25
25
|
end
|
26
26
|
end
|
@@ -29,8 +29,8 @@ module AppMap
|
|
29
29
|
end
|
30
30
|
|
31
31
|
class SQLReturn < AppMap::Event::MethodReturnIgnoreValue
|
32
|
-
def initialize(
|
33
|
-
super AppMap::Event.next_id_counter, :return,
|
32
|
+
def initialize(parent_id, elapsed)
|
33
|
+
super AppMap::Event.next_id_counter, :return, Thread.current.object_id
|
34
34
|
|
35
35
|
self.parent_id = parent_id
|
36
36
|
self.elapsed = elapsed
|
@@ -76,6 +76,8 @@ module AppMap
|
|
76
76
|
case database_type
|
77
77
|
when :postgres
|
78
78
|
ActiveRecord::Base.connection.postgresql_version
|
79
|
+
when :sqlite
|
80
|
+
ActiveRecord::Base.connection.database_version.to_s
|
79
81
|
else
|
80
82
|
warn "Unable to determine database version for #{database_type.inspect}"
|
81
83
|
end
|
@@ -133,9 +135,9 @@ module AppMap
|
|
133
135
|
|
134
136
|
SQLExaminer.examine payload, sql: sql
|
135
137
|
|
136
|
-
call = SQLCall.new(
|
138
|
+
call = SQLCall.new(payload)
|
137
139
|
AppMap.tracing.record_event(call)
|
138
|
-
AppMap.tracing.record_event(SQLReturn.new(
|
140
|
+
AppMap.tracing.record_event(SQLReturn.new(call.id, finished - started))
|
139
141
|
ensure
|
140
142
|
Thread.current[reentry_key] = nil
|
141
143
|
end
|
data/lib/appmap/trace.rb
CHANGED
@@ -14,34 +14,34 @@ module AppMap
|
|
14
14
|
|
15
15
|
class Tracing
|
16
16
|
def initialize
|
17
|
-
@
|
17
|
+
@tracing = []
|
18
18
|
end
|
19
19
|
|
20
20
|
def empty?
|
21
|
-
@
|
21
|
+
@tracing.empty?
|
22
22
|
end
|
23
23
|
|
24
24
|
def trace(enable: true)
|
25
25
|
Tracer.new.tap do |tracer|
|
26
|
-
@
|
26
|
+
@tracing << tracer
|
27
27
|
tracer.enable if enable
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
31
|
def enabled?
|
32
|
-
@
|
32
|
+
@tracing.any?(&:enabled?)
|
33
33
|
end
|
34
34
|
|
35
35
|
def record_event(event, defined_class: nil, method: nil)
|
36
|
-
@
|
36
|
+
@tracing.each do |tracer|
|
37
37
|
tracer.record_event(event, defined_class: defined_class, method: method)
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
41
|
def delete(tracer)
|
42
|
-
return unless @
|
42
|
+
return unless @tracing.member?(tracer)
|
43
43
|
|
44
|
-
@
|
44
|
+
@tracing.delete(tracer)
|
45
45
|
tracer.disable
|
46
46
|
end
|
47
47
|
end
|
data/lib/appmap/util.rb
CHANGED
@@ -35,6 +35,25 @@ module AppMap
|
|
35
35
|
|
36
36
|
[ fname, extension ].join
|
37
37
|
end
|
38
|
+
|
39
|
+
# sanitize_event removes ephemeral values from an event, making
|
40
|
+
# events easier to compare across runs.
|
41
|
+
def sanitize_event(event, &block)
|
42
|
+
event.delete(:thread_id)
|
43
|
+
event.delete(:elapsed)
|
44
|
+
delete_object_id = ->(obj) { (obj || {}).delete(:object_id) }
|
45
|
+
delete_object_id.call(event[:receiver])
|
46
|
+
delete_object_id.call(event[:return_value])
|
47
|
+
(event[:parameters] || []).each(&delete_object_id)
|
48
|
+
(event[:exceptions] || []).each(&delete_object_id)
|
49
|
+
|
50
|
+
case event[:event]
|
51
|
+
when :call
|
52
|
+
event[:path] = event[:path].gsub(Gem.dir + '/', '')
|
53
|
+
end
|
54
|
+
|
55
|
+
event
|
56
|
+
end
|
38
57
|
end
|
39
58
|
end
|
40
59
|
end
|
data/lib/appmap/version.rb
CHANGED
@@ -48,11 +48,11 @@ describe 'AbstractControllerBase' do
|
|
48
48
|
|
49
49
|
expect(appmap).to match(<<-CREATE_CALL.strip)
|
50
50
|
event: call
|
51
|
+
thread_id: .*
|
51
52
|
defined_class: Api::UsersController
|
52
53
|
method_id: build_user
|
53
54
|
path: app/controllers/api/users_controller.rb
|
54
55
|
lineno: 23
|
55
|
-
thread_id: .*
|
56
56
|
static: false
|
57
57
|
parameters:
|
58
58
|
- name: params
|
@@ -47,17 +47,17 @@ describe 'AbstractControllerBase' do
|
|
47
47
|
SERVER_REQUEST
|
48
48
|
end
|
49
49
|
|
50
|
-
it '
|
50
|
+
it 'properly captures method parameters in the appmap' do
|
51
51
|
expect(File).to exist(appmap_json)
|
52
52
|
appmap = JSON.parse(File.read(appmap_json)).to_yaml
|
53
53
|
|
54
54
|
expect(appmap).to match(<<-CREATE_CALL.strip)
|
55
55
|
event: call
|
56
|
+
thread_id: .*
|
56
57
|
defined_class: Api::UsersController
|
57
58
|
method_id: build_user
|
58
59
|
path: app/controllers/api/users_controller.rb
|
59
60
|
lineno: 23
|
60
|
-
thread_id: .*
|
61
61
|
static: false
|
62
62
|
parameters:
|
63
63
|
- name: params
|
@@ -68,5 +68,12 @@ describe 'AbstractControllerBase' do
|
|
68
68
|
receiver:
|
69
69
|
CREATE_CALL
|
70
70
|
end
|
71
|
+
|
72
|
+
it 'returns a minimal event' do
|
73
|
+
expect(File).to exist(appmap_json)
|
74
|
+
appmap = JSON.parse(File.read(appmap_json))
|
75
|
+
event = appmap['events'].find { |event| event['event'] == 'return' && event['return_value'] }
|
76
|
+
expect(event.keys).to eq(%w[id event thread_id parent_id elapsed return_value])
|
77
|
+
end
|
71
78
|
end
|
72
79
|
end
|
data/spec/hook_spec.rb
CHANGED
@@ -16,36 +16,18 @@ end
|
|
16
16
|
Psych::Visitors::YAMLTree.prepend(ShowYamlNulls)
|
17
17
|
|
18
18
|
describe 'AppMap class Hooking', docker: false do
|
19
|
+
require 'appmap/util'
|
19
20
|
def collect_events(tracer)
|
20
21
|
[].tap do |events|
|
21
22
|
while tracer.event?
|
22
23
|
events << tracer.next_event.to_h
|
23
24
|
end
|
24
|
-
end.map
|
25
|
-
event.delete(:thread_id)
|
26
|
-
event.delete(:elapsed)
|
27
|
-
delete_object_id = ->(obj) { (obj || {}).delete(:object_id) }
|
28
|
-
delete_object_id.call(event[:receiver])
|
29
|
-
delete_object_id.call(event[:return_value])
|
30
|
-
(event[:parameters] || []).each(&delete_object_id)
|
31
|
-
(event[:exceptions] || []).each(&delete_object_id)
|
32
|
-
|
33
|
-
case event[:event]
|
34
|
-
when :call
|
35
|
-
event[:path] = event[:path].gsub(Gem.dir + '/', '')
|
36
|
-
when :return
|
37
|
-
# These should be removed from the appmap spec
|
38
|
-
%i[defined_class method_id path lineno static].each do |obsolete_field|
|
39
|
-
event.delete(obsolete_field)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
event
|
43
|
-
end.to_yaml
|
25
|
+
end.map(&AppMap::Util.method(:sanitize_event)).to_yaml
|
44
26
|
end
|
45
27
|
|
46
28
|
def invoke_test_file(file, setup: nil, &block)
|
47
29
|
AppMap.configuration = nil
|
48
|
-
package = AppMap::Package.new(file, [])
|
30
|
+
package = AppMap::Package.new(file, nil, [])
|
49
31
|
config = AppMap::Config.new('hook_spec', [ package ])
|
50
32
|
AppMap.configuration = config
|
51
33
|
tracer = nil
|
@@ -99,7 +81,7 @@ describe 'AppMap class Hooking', docker: false do
|
|
99
81
|
:class: String
|
100
82
|
:value: default
|
101
83
|
YAML
|
102
|
-
|
84
|
+
test_hook_behavior 'spec/fixtures/hook/instance_method.rb', events_yaml do
|
103
85
|
expect(InstanceMethod.new.say_default).to eq('default')
|
104
86
|
end
|
105
87
|
end
|
@@ -456,20 +438,6 @@ describe 'AppMap class Hooking', docker: false do
|
|
456
438
|
end
|
457
439
|
end
|
458
440
|
|
459
|
-
context 'OpenSSL::X509::Certificate.sign' do
|
460
|
-
# OpenSSL::X509 is not being hooked.
|
461
|
-
# This might be because the class is being loaded before AppMap, and so the TracePoint
|
462
|
-
# set by AppMap doesn't see it.
|
463
|
-
xit 'is hooked' do
|
464
|
-
events_yaml = <<~YAML
|
465
|
-
---
|
466
|
-
YAML
|
467
|
-
test_hook_behavior 'spec/fixtures/hook/openssl_sign.rb', events_yaml do
|
468
|
-
expect(OpenSSLExample.example).to be_truthy
|
469
|
-
end
|
470
|
-
end
|
471
|
-
end
|
472
|
-
|
473
441
|
context 'ActiveSupport::SecurityUtils.secure_compare' do
|
474
442
|
it 'is hooked' do
|
475
443
|
events_yaml = <<~YAML
|
@@ -560,7 +528,7 @@ describe 'AppMap class Hooking', docker: false do
|
|
560
528
|
:labels:
|
561
529
|
- security
|
562
530
|
YAML
|
563
|
-
|
531
|
+
|
564
532
|
config, tracer = invoke_test_file 'spec/fixtures/hook/compare.rb' do
|
565
533
|
expect(Compare.compare('string', 'string')).to be_truthy
|
566
534
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'rails_spec_helper'
|
2
2
|
|
3
|
-
describe '
|
3
|
+
describe 'SQL events' do
|
4
4
|
before(:all) { @fixture_dir = 'spec/fixtures/rails_users_app' }
|
5
5
|
include_context 'Rails app pg database'
|
6
6
|
|
@@ -14,54 +14,77 @@ describe 'Record SQL queries in a Rails app' do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
let(:tmpdir) { "tmp/spec/record_sql_rails_pg_spec" }
|
17
|
-
let(:appmap) { JSON.parse(File.read(appmap_json)).to_yaml }
|
18
17
|
|
19
|
-
|
18
|
+
describe 'fields' do
|
20
19
|
let(:test_line_number) { 8 }
|
21
20
|
let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json') }
|
21
|
+
let(:orm_module) { 'sequel' }
|
22
|
+
let(:appmap) { JSON.parse(File.read(appmap_json)) }
|
23
|
+
describe 'on a call event' do
|
24
|
+
let(:event) do
|
25
|
+
appmap['events'].find do |event|
|
26
|
+
event['event'] == 'call' &&
|
27
|
+
event.keys.include?('sql_query')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
it 'do not include function-only fields' do
|
31
|
+
expect(event.keys).to_not include('defined_class')
|
32
|
+
expect(event.keys).to_not include('method_id')
|
33
|
+
expect(event.keys).to_not include('path')
|
34
|
+
expect(event.keys).to_not include('lineno')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'in a Rails app' do
|
40
|
+
let(:appmap) { JSON.parse(File.read(appmap_json)).to_yaml }
|
41
|
+
context 'while creating a new record' do
|
42
|
+
let(:test_line_number) { 8 }
|
43
|
+
let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json') }
|
22
44
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
45
|
+
context 'using Sequel ORM' do
|
46
|
+
let(:orm_module) { 'sequel' }
|
47
|
+
it 'detects the sql INSERT' do
|
48
|
+
expect(appmap).to include(<<-SQL_QUERY.strip)
|
27
49
|
sql_query:
|
28
50
|
sql: INSERT INTO "users" ("login") VALUES ('alice') RETURNING *
|
29
|
-
|
51
|
+
SQL_QUERY
|
52
|
+
end
|
30
53
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
expect(appmap).to include(<<-SQL_QUERY.strip)
|
54
|
+
context 'using ActiveRecord ORM' do
|
55
|
+
let(:orm_module) { 'activerecord' }
|
56
|
+
it 'detects the sql INSERT' do
|
57
|
+
expect(appmap).to include(<<-SQL_QUERY.strip)
|
36
58
|
sql_query:
|
37
59
|
sql: INSERT INTO "users" ("login") VALUES ($1) RETURNING "id"
|
38
|
-
|
60
|
+
SQL_QUERY
|
61
|
+
end
|
39
62
|
end
|
40
63
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
expect(appmap).to include(<<-SQL_QUERY.strip)
|
64
|
+
|
65
|
+
context 'while listing records' do
|
66
|
+
let(:test_line_number) { 23 }
|
67
|
+
let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_GET_api_users_lists_the_users.appmap.json') }
|
68
|
+
|
69
|
+
context 'using Sequel ORM' do
|
70
|
+
let(:orm_module) { 'sequel' }
|
71
|
+
it 'detects the sql SELECT' do
|
72
|
+
expect(appmap).to include(<<-SQL_QUERY.strip)
|
51
73
|
sql_query:
|
52
74
|
sql: SELECT * FROM "users"
|
53
|
-
|
54
|
-
|
55
|
-
|
75
|
+
SQL_QUERY
|
76
|
+
|
77
|
+
expect(appmap).to include('sql:')
|
78
|
+
end
|
56
79
|
end
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
expect(appmap).to include(<<-SQL_QUERY.strip)
|
80
|
+
context 'using ActiveRecord ORM' do
|
81
|
+
let(:orm_module) { 'activerecord' }
|
82
|
+
it 'detects the sql SELECT' do
|
83
|
+
expect(appmap).to include(<<-SQL_QUERY.strip)
|
62
84
|
sql_query:
|
63
85
|
sql: SELECT "users".* FROM "users"
|
64
|
-
|
86
|
+
SQL_QUERY
|
87
|
+
end
|
65
88
|
end
|
66
89
|
end
|
67
90
|
end
|
data/test/cli_test.rb
CHANGED
@@ -55,7 +55,7 @@ class CLITest < Minitest::Test
|
|
55
55
|
assert_equal <<~OUTPUT.strip, output.strip
|
56
56
|
Class frequency:
|
57
57
|
----------------
|
58
|
-
|
58
|
+
1 Main
|
59
59
|
|
60
60
|
Method frequency:
|
61
61
|
----------------
|
@@ -79,7 +79,7 @@ class CLITest < Minitest::Test
|
|
79
79
|
"class_frequency": [
|
80
80
|
{
|
81
81
|
"name": "Main",
|
82
|
-
"count":
|
82
|
+
"count": 1
|
83
83
|
}
|
84
84
|
],
|
85
85
|
"method_frequency": [
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: appmap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.32.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Gilpin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -273,6 +273,7 @@ files:
|
|
273
273
|
- lib/appmap/cucumber.rb
|
274
274
|
- lib/appmap/event.rb
|
275
275
|
- lib/appmap/hook.rb
|
276
|
+
- lib/appmap/hook/method.rb
|
276
277
|
- lib/appmap/metadata.rb
|
277
278
|
- lib/appmap/middleware/remote_recording.rb
|
278
279
|
- lib/appmap/minitest.rb
|
@@ -311,7 +312,6 @@ files:
|
|
311
312
|
- spec/fixtures/hook/constructor.rb
|
312
313
|
- spec/fixtures/hook/exception_method.rb
|
313
314
|
- spec/fixtures/hook/instance_method.rb
|
314
|
-
- spec/fixtures/hook/openssl_sign.rb
|
315
315
|
- spec/fixtures/hook/singleton_method.rb
|
316
316
|
- spec/fixtures/rack_users_app/.dockerignore
|
317
317
|
- spec/fixtures/rack_users_app/.gitignore
|
@@ -1,87 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# From the manual page https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL.html
|
4
|
-
|
5
|
-
require 'openssl'
|
6
|
-
|
7
|
-
module OpenSSLExample
|
8
|
-
def OpenSSLExample.example
|
9
|
-
ca_key = OpenSSL::PKey::RSA.new 2048
|
10
|
-
pass_phrase = 'my secure pass phrase goes here'
|
11
|
-
|
12
|
-
cipher = OpenSSL::Cipher.new 'AES-256-CBC'
|
13
|
-
|
14
|
-
open 'tmp/ca_key.pem', 'w', 0644 do |io|
|
15
|
-
io.write ca_key.export(cipher, pass_phrase)
|
16
|
-
end
|
17
|
-
|
18
|
-
ca_name = OpenSSL::X509::Name.parse '/CN=ca/DC=example'
|
19
|
-
|
20
|
-
ca_cert = OpenSSL::X509::Certificate.new
|
21
|
-
ca_cert.serial = 0
|
22
|
-
ca_cert.version = 2
|
23
|
-
ca_cert.not_before = Time.now
|
24
|
-
ca_cert.not_after = Time.now + 86400
|
25
|
-
|
26
|
-
ca_cert.public_key = ca_key.public_key
|
27
|
-
ca_cert.subject = ca_name
|
28
|
-
ca_cert.issuer = ca_name
|
29
|
-
|
30
|
-
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
31
|
-
extension_factory.subject_certificate = ca_cert
|
32
|
-
extension_factory.issuer_certificate = ca_cert
|
33
|
-
|
34
|
-
ca_cert.add_extension extension_factory.create_extension('subjectKeyIdentifier', 'hash')
|
35
|
-
ca_cert.add_extension extension_factory.create_extension('basicConstraints', 'CA:TRUE', true)
|
36
|
-
|
37
|
-
ca_cert.add_extension extension_factory.create_extension(
|
38
|
-
'keyUsage', 'cRLSign,keyCertSign', true)
|
39
|
-
|
40
|
-
ca_cert.sign ca_key, OpenSSL::Digest::SHA1.new
|
41
|
-
|
42
|
-
open 'tmp/ca_cert.pem', 'w' do |io|
|
43
|
-
io.write ca_cert.to_pem
|
44
|
-
end
|
45
|
-
|
46
|
-
csr = OpenSSL::X509::Request.new
|
47
|
-
csr.version = 0
|
48
|
-
csr.subject = OpenSSL::X509::Name.new([ ['CN', 'the name to sign', OpenSSL::ASN1::UTF8STRING] ])
|
49
|
-
csr.public_key = ca_key.public_key
|
50
|
-
csr.sign ca_key, OpenSSL::Digest::SHA1.new
|
51
|
-
|
52
|
-
open 'tmp/csr.pem', 'w' do |io|
|
53
|
-
io.write csr.to_pem
|
54
|
-
end
|
55
|
-
|
56
|
-
csr = OpenSSL::X509::Request.new File.read 'tmp/csr.pem'
|
57
|
-
|
58
|
-
raise 'CSR can not be verified' unless csr.verify csr.public_key
|
59
|
-
|
60
|
-
csr_cert = OpenSSL::X509::Certificate.new
|
61
|
-
csr_cert.serial = 0
|
62
|
-
csr_cert.version = 2
|
63
|
-
csr_cert.not_before = Time.now
|
64
|
-
csr_cert.not_after = Time.now + 600
|
65
|
-
|
66
|
-
csr_cert.subject = csr.subject
|
67
|
-
csr_cert.public_key = csr.public_key
|
68
|
-
csr_cert.issuer = ca_cert.subject
|
69
|
-
|
70
|
-
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
71
|
-
extension_factory.subject_certificate = csr_cert
|
72
|
-
extension_factory.issuer_certificate = ca_cert
|
73
|
-
|
74
|
-
csr_cert.add_extension extension_factory.create_extension('basicConstraints', 'CA:FALSE')
|
75
|
-
|
76
|
-
csr_cert.add_extension extension_factory.create_extension(
|
77
|
-
'keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature')
|
78
|
-
|
79
|
-
csr_cert.add_extension extension_factory.create_extension('subjectKeyIdentifier', 'hash')
|
80
|
-
|
81
|
-
csr_cert.sign ca_key, OpenSSL::Digest::SHA1.new
|
82
|
-
|
83
|
-
open 'tmp/csr_cert.pem', 'w' do |io|
|
84
|
-
io.write csr_cert.to_pem
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|