hoodoo 1.19.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hoodoo/active/active_model/uuid_validator.rb +0 -1
  3. data/lib/hoodoo/active/active_record/dated.rb +2 -2
  4. data/lib/hoodoo/active/active_record/support.rb +2 -1
  5. data/lib/hoodoo/active/active_record/uuid.rb +2 -2
  6. data/lib/hoodoo/active/active_record/writer.rb +1 -1
  7. data/lib/hoodoo/generator.rb +52 -12
  8. data/lib/hoodoo/monkey/patch/datadog_traced_amqp.rb +11 -6
  9. data/lib/hoodoo/monkey/patch/newrelic_middleware_analytics.rb +17 -10
  10. data/lib/hoodoo/monkey/patch/newrelic_traced_amqp.rb +71 -33
  11. data/lib/hoodoo/services/discovery/discoverers/by_drb/by_drb.rb +13 -8
  12. data/lib/hoodoo/services/middleware/exception_reporting/reporters/airbrake_reporter.rb +8 -3
  13. data/lib/hoodoo/services/middleware/interaction.rb +1 -1
  14. data/lib/hoodoo/services/middleware/middleware.rb +52 -41
  15. data/lib/hoodoo/version.rb +2 -2
  16. data/spec/active/active_record/creator_spec.rb +1 -1
  17. data/spec/active/active_record/dated_spec.rb +7 -7
  18. data/spec/active/active_record/finder_spec.rb +953 -839
  19. data/spec/active/active_record/manually_dated_spec.rb +1 -1
  20. data/spec/active/active_record/search_helper_spec.rb +1 -1
  21. data/spec/active/active_record/secure_spec.rb +2 -2
  22. data/spec/active/active_record/support_spec.rb +3 -3
  23. data/spec/monkey/patch/datadog_traced_amqp_spec.rb +10 -2
  24. data/spec/monkey/patch/newrelic_traced_amqp_spec.rb +54 -21
  25. data/spec/new_relic/agent/logger.rb +24 -0
  26. data/spec/new_relic/agent/transaction.rb +32 -0
  27. data/spec/services/discovery/discoverers/by_drb/by_drb_spec.rb +48 -2
  28. data/spec/services/middleware/exception_reporting/reporters/airbrake_reporter_spec.rb +4 -4
  29. data/spec/services/middleware/middleware_multi_local_spec.rb +41 -13
  30. data/spec/services/middleware/middleware_multi_remote_spec.rb +93 -67
  31. data/spec/services/middleware/middleware_spec.rb +80 -7
  32. data/spec/services/services/interface_spec.rb +2 -2
  33. data/spec/transient_store/transient_store/mocks/redis_spec.rb +8 -6
  34. metadata +30 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 686b88cd1d3e169957a22f9bbd1b2e0680143344
4
- data.tar.gz: bbb4fe6fdfd02d447ca76c9972ede50aec481ba7
3
+ metadata.gz: dc0db0a009b321df89a2eb2e8a879c76bc875bb9
4
+ data.tar.gz: 29c85538f05080287181a67d3292b72f950f1872
5
5
  SHA512:
6
- metadata.gz: 02bde4ca9622f8b7ce1524341c4e997d54717cc8282addfb86d44ad87333942a6caa6d97bf8d28cd860e40cf50f34cb8c97a90d7015249625dd711e950375658
7
- data.tar.gz: c16e8ea17f27fb117816c6e1416ba65248e6d9e3b0b113e05f88f04499afb949d3a00c37412e5073385f1b6b5e3fd5b883173a110d5c9cf874ef717a2f442ea1
6
+ metadata.gz: 8519c31178c7439fc57888851eac50409e71c8225dfc0fd5421da540e370ab01ef92423f3a5753195efea9ca01f4954ee68d33d09fc083470a6ac5eade962f70
7
+ data.tar.gz: 45d7b58441e42e224624123c74f15f812880a074ea07648628f49741a3a3ba918456632b456a6b617b73d65935884ec16ad30e92176e19cc939af93fcc3437c0
@@ -26,7 +26,6 @@ module Hoodoo
26
26
  # Example:
27
27
  #
28
28
  # class SomeModel < ActiveRecord::Base
29
- #
30
29
  # validates :somefield, uuid: true
31
30
  # end
32
31
  #
@@ -243,7 +243,7 @@ module Hoodoo
243
243
  # Rationalise and convert the date time to UTC.
244
244
 
245
245
  date_time = Hoodoo::Utilities.rationalise_datetime( date_time ).utc
246
- safe_date_time = self.sanitize( date_time ) # ActiveRecord provides #sanitize
246
+ safe_date_time = self.connection.quoted_date( date_time )
247
247
 
248
248
  # Create strings that specify the required attributes escaped and
249
249
  # joined by commas for use in a SQL query, for both main and history
@@ -269,7 +269,7 @@ module Hoodoo
269
269
  SELECT #{ safe_history_name_string },"effective_start","effective_end"
270
270
  FROM #{ dating_table_name }
271
271
  ) AS u
272
- WHERE "effective_start" <= #{ safe_date_time } AND ("effective_end" > #{ safe_date_time } OR "effective_end" IS NULL)
272
+ WHERE "effective_start" <= '#{ safe_date_time }' AND ("effective_end" > '#{ safe_date_time }' OR "effective_end" IS NULL)
273
273
  ) AS #{ self.table_name }
274
274
  }
275
275
 
@@ -70,7 +70,8 @@ module Hoodoo
70
70
  #
71
71
  mapping = {
72
72
  'created_after' => Hoodoo::ActiveRecord::Finder::SearchHelper.cs_gt( :created_at ),
73
- 'created_before' => Hoodoo::ActiveRecord::Finder::SearchHelper.cs_lt( :created_at )
73
+ 'created_before' => Hoodoo::ActiveRecord::Finder::SearchHelper.cs_lt( :created_at ),
74
+ 'created_by' => Hoodoo::ActiveRecord::Finder::SearchHelper.cs_match( :created_by )
74
75
  }
75
76
 
76
77
  if mapping.keys.length != ( mapping.keys | Hoodoo::Services::Middleware::FRAMEWORK_QUERY_DATA.keys ).length
@@ -38,7 +38,7 @@ module Hoodoo
38
38
  # # ...
39
39
  # end
40
40
  #
41
- # +model+:: The ActiveRecord::Base descendant class that is including
41
+ # +model+:: The ActiveRecord::Base descendant that is including
42
42
  # this module.
43
43
  #
44
44
  def self.included( model )
@@ -62,7 +62,7 @@ module Hoodoo
62
62
  #
63
63
  # change_column :model_table_name, :id, :string, :limit => 32
64
64
  #
65
- # +model+:: The ActiveRecord::Base descendant class that is including
65
+ # +model+:: The ActiveRecord::Base descendant that is including
66
66
  # this module.
67
67
  #
68
68
  def self.instantiate( model )
@@ -41,7 +41,7 @@ module Hoodoo
41
41
  # # ...
42
42
  # end
43
43
  #
44
- # +model+:: The ActiveRecord::Base descendant class that is including
44
+ # +model+:: The ActiveRecord::Base descendant that is including
45
45
  # this module.
46
46
  #
47
47
  def self.included( model )
@@ -10,6 +10,7 @@
10
10
 
11
11
  require 'singleton'
12
12
  require 'fileutils'
13
+ require 'pathname'
13
14
 
14
15
  module Hoodoo
15
16
 
@@ -45,25 +46,33 @@ module Hoodoo
45
46
 
46
47
  name = args.first
47
48
 
48
- return show_usage if name == '-h' || name == '--help'
49
+ return show_usage if name == '-h' || name == '--help' || name.nil? || name.empty?
50
+ return show_version if name == '-v' || name == '--version'
49
51
 
50
52
  return usage_and_warning( "SERVICE_NAME must match #{ NAME_REGEX.inspect }" ) if naughty_name?( name )
51
53
  return usage_and_warning( "'#{ name }' already exists" ) if File.exist?( "./#{ name }" )
52
54
 
53
- git = args[ 2 ] if args[ 1 ] == '--from'
55
+ path = args[ 2 ] if args[ 1 ] == '--path'
56
+ git = args[ 2 ] if args[ 1 ] == '--from'
54
57
  git ||= 'git@github.com:LoyaltyNZ/service_shell.git'
55
58
 
56
- return create_service( name, git )
59
+ return create_service( name, git, path )
57
60
  end
58
61
 
59
62
  private
60
63
 
61
- def create_service( name, git )
62
- if create_dir( name ) &&
63
- clone_service_shell( name, git ) &&
64
- remove_dot_git( name, git ) &&
65
- replace_strings( name )
66
-
64
+ # Name of new service, mandatory GitHub repo path of the shell to start
65
+ # with, or override local filesystem path to copy from (pass "nil" to
66
+ # not do that).
67
+ #
68
+ def create_service( name, git, path )
69
+ ok = create_dir( name )
70
+ ok = clone_service_shell( name, git ) if ok && path.nil?
71
+ ok = copy_service_shell( name, path ) if ok && ! path.nil?
72
+ ok = remove_dot_git( name, git ) if ok
73
+ ok = replace_strings( name ) if ok
74
+
75
+ if ok
67
76
  puts "Success! ./#{name} created."
68
77
  Kernel::exit( KERNEL_EXIT_SUCCESS )
69
78
  else
@@ -81,6 +90,14 @@ module Hoodoo
81
90
  $?.to_i == 0
82
91
  end
83
92
 
93
+ def copy_service_shell( name, path )
94
+ source_path = Pathname.new( path ).to_s << '/.'
95
+ dest_path = File.join( '.', name )
96
+
97
+ FileUtils.cp_r( source_path, dest_path, verbose: true )
98
+ $?.to_i == 0
99
+ end
100
+
84
101
  def remove_dot_git( name, git )
85
102
  git_folder = "./#{ name }/.git"
86
103
  git_config = "#{ git_folder }/config"
@@ -123,9 +140,32 @@ module Hoodoo
123
140
  end
124
141
 
125
142
  def show_usage
126
- puts "Usage: hoodoo SERVICE_NAME [--from <git-repository>]"
127
- puts " e.g. hoodoo service_cron"
128
- puts " hoodoo service_person --from git@github.com:YOURNAME/service_shell_fork.git"
143
+ puts
144
+ puts "Creates a service shell at the PWD, customised with the given service name."
145
+ puts
146
+ puts " hoodoo <service-name> [--from <git-repository> | --path <full-pathname>]"
147
+ puts
148
+ puts "For example:"
149
+ puts
150
+ puts " hoodoo service_cron"
151
+ puts " hoodoo service_person --from git@github.com:YOURNAME/service_shell_fork.git"
152
+ puts " hoodoo service_product --path /path/to/local/service/shell/container"
153
+ puts
154
+ puts "See also:"
155
+ puts
156
+ puts " hoodoo --help shows this help"
157
+ puts " hoodoo --version shows the require-able gem version"
158
+ puts
159
+
160
+ Kernel::exit( KERNEL_EXIT_FAILURE )
161
+ end
162
+
163
+ def show_version
164
+ require 'hoodoo/version'
165
+
166
+ puts
167
+ puts "Accessible Hoodoo gem is #{ Hoodoo::VERSION } (#{ Hoodoo::DATE })"
168
+ puts
129
169
 
130
170
  Kernel::exit( KERNEL_EXIT_FAILURE )
131
171
  end
@@ -37,22 +37,27 @@ module Hoodoo
37
37
  # the response. It calls the original implementation via +super+.
38
38
  #
39
39
  # +http_message+:: Hash describing the message to send.
40
- #
41
40
  # +full_uri+:: URI being sent to. This is somewhat meaningless
42
- # when using AMQP but NewRelic requires it.
41
+ # when using AMQP but useful for analytics data.
43
42
  #
44
43
  def monkey_send_request( http_message, full_uri )
45
44
  amqp_response = nil
46
45
 
47
46
  Datadog.tracer.trace( 'alchemy.request' ) do |span|
48
- span.service = 'alchemy'
47
+ span.service = 'alchemy'
49
48
  span.span_type = 'alchemy'
50
- span.resource = http_message[ 'verb' ]
49
+ span.resource = http_message[ 'verb' ]
51
50
  span.set_tag( 'target.path', http_message[ 'path'] )
52
51
 
53
- # Add Datadog trace IDs to the HTTP message
52
+ # Add Datadog trace IDs to the HTTP message. For compatibility
53
+ # with Hoodoo V1 services using a fork of DDTrace, we send both
54
+ # old headers ("X-DDTrace...") and new ("X-DataDog-...")
55
+
56
+ http_message[ 'headers' ][ 'X_DATADOG_TRACE_ID' ] = span.trace_id.to_s
57
+ http_message[ 'headers' ][ 'X_DATADOG_PARENT_ID' ] = span.span_id.to_s
58
+
54
59
  http_message[ 'headers' ][ 'X_DDTRACE_PARENT_TRACE_ID' ] = span.trace_id.to_s
55
- http_message[ 'headers' ][ 'X_DDTRACE_PARENT_SPAN_ID' ] = span.span_id.to_s
60
+ http_message[ 'headers' ][ 'X_DDTRACE_PARENT_SPAN_ID' ] = span.span_id.to_s
56
61
 
57
62
  amqp_response = super( http_message, full_uri )
58
63
  end
@@ -13,7 +13,10 @@
13
13
  ########################################################################
14
14
 
15
15
  begin
16
+
16
17
  # Raises LoadError if NewRelic is absent
18
+ #
19
+ require 'newrelic_rpm'
17
20
  require 'new_relic/agent/method_tracer'
18
21
 
19
22
  # Add a method tracer on the dispatch method so that the time spent
@@ -29,6 +32,7 @@ begin
29
32
  end
30
33
  end
31
34
  end
35
+
32
36
  rescue LoadError; end
33
37
 
34
38
  module Hoodoo
@@ -55,18 +59,19 @@ module Hoodoo
55
59
  # Add custom attributes to the NewRelic transaction. The original
56
60
  # implementation is called via +super+.
57
61
  #
58
- # +interaction+:: Hoodoo::Services::Interaction describing the
59
- # inbound request. The +interaction_id+,
60
- # +rack_request+ and +session+ data is used (the
61
- # latter being optional). If +target_interface+ and
62
- # +requested_action+ are available, body data
63
- # _might_ be logged according to secure log settings
64
- # in the interface; if these values are unset, body
65
- # data is _not_ logged.
62
+ # +interaction+:: Hoodoo::Services::Middleware::Interaction
63
+ # instance describing the inbound request. The
64
+ # +interaction_id+, +rack_request+ and +session+
65
+ # data is used (the latter being optional). If
66
+ # +target_interface+ and +requested_action+ are
67
+ # available, body data _might_ be logged according
68
+ # to secure log settings in the interface; if these
69
+ # values are unset, body data is _not_ logged.
66
70
  #
67
71
  def monkey_log_inbound_request( interaction )
68
72
 
69
73
  # Add custom attributes to the NewRelic transaction.
74
+ #
70
75
  ::NewRelic::Agent.add_custom_attributes(
71
76
  {
72
77
  :target_action => interaction.requested_action,
@@ -75,6 +80,7 @@ module Hoodoo
75
80
  )
76
81
 
77
82
  # Call the original logging method.
83
+ #
78
84
  super( interaction )
79
85
 
80
86
  end
@@ -85,16 +91,17 @@ module Hoodoo
85
91
  if defined?( Hoodoo::Services ) &&
86
92
  defined?( Hoodoo::Services::Middleware )
87
93
 
88
- ::Hoodoo::Monkey.register(
94
+ Hoodoo::Monkey.register(
89
95
  target_unit: Hoodoo::Services::Middleware,
90
96
  extension_module: Hoodoo::Monkey::Patch::NewRelicMiddlewareAnalytics
91
97
  )
92
98
 
93
- ::Hoodoo::Monkey.enable( extension_module: Hoodoo::Monkey::Patch::NewRelicMiddlewareAnalytics )
99
+ Hoodoo::Monkey.enable( extension_module: Hoodoo::Monkey::Patch::NewRelicMiddlewareAnalytics )
94
100
  end
95
101
 
96
102
  rescue LoadError
97
103
  # No NewRelic => do nothing
104
+
98
105
  end
99
106
 
100
107
  end # module Patch
@@ -16,7 +16,11 @@ module Hoodoo
16
16
  module Patch
17
17
 
18
18
  begin
19
- require 'newrelic_rpm' # Raises LoadError if NewRelic is absent
19
+
20
+ # Raises LoadError if NewRelic is absent
21
+ #
22
+ require 'newrelic_rpm'
23
+ require 'new_relic/agent/transaction'
20
24
 
21
25
  # Wrap Hoodoo::Client::Endpoint::AMQP using NewRelic transaction
22
26
  # tracing so that over-queue inter-resource calls get connected
@@ -36,36 +40,47 @@ module Hoodoo
36
40
  # This adds headers to the request and extracts header data from
37
41
  # the response. It calls the original implementation via +super+.
38
42
  #
39
- # +http_message+:: Hash describing the message to send.
43
+ # +http_message+:: Hash describing the message to send. See e.g.
44
+ # Hoodoo::Client::Endpoint::AMQP#do_amqp. Note
45
+ # that the header names inside this Hash are the
46
+ # mixed case, HTTP specification style ones like
47
+ # <tt>X-Interaction-ID</tt> and _not_ the Rack
48
+ # names like <tt>HTTP_X_INTERACTION_ID</tt>.
40
49
  #
41
50
  # +full_uri+:: URI being sent to. This is somewhat meaningless
42
51
  # when using AMQP but NewRelic requires it.
43
52
  #
44
53
  def monkey_send_request( http_message, full_uri )
45
- amqp_response = nil
46
- newrelic_request = ::Hoodoo::Monkey::Patch::NewRelicTracedAMQP::AMQPNewRelicRequestWrapper.new(
54
+ amqp_response = nil
55
+ wrapped_request = AlchemyFluxHTTPRequestWrapper.new(
47
56
  http_message,
48
57
  full_uri
49
58
  )
50
59
 
51
- ::NewRelic::Agent::CrossAppTracing.tl_trace_http_request( newrelic_request ) do
60
+ segment = ::NewRelic::Agent::Transaction.start_external_request_segment(
61
+ wrapped_request.type,
62
+ wrapped_request.uri,
63
+ wrapped_request.method
64
+ )
65
+
66
+ begin
67
+ segment.add_request_headers( wrapped_request )
68
+
69
+ amqp_response = super( http_message, full_uri )
52
70
 
53
- # Disable further tracing in request to avoid double counting
54
- # if connection wasn't started (which calls request again).
71
+ # The outer block extracts required information from the
72
+ # object returned by this block. Need to wrap it match the
73
+ # expected interface.
55
74
  #
56
- ::NewRelic::Agent.disable_all_tracing do
75
+ wrapped_response = AlchemyFluxHTTPResponseWrapper.new(
76
+ amqp_response
77
+ )
57
78
 
58
- amqp_response = super( http_message, full_uri )
79
+ segment.read_response_headers( wrapped_response )
59
80
 
60
- # The outer block extracts required information from the
61
- # object returned by this block. Need to wrap it match the
62
- # expected interface.
63
- #
64
- ::Hoodoo::Monkey::Patch::NewRelicTracedAMQP::AMQPNewRelicResponseWrapper.new(
65
- amqp_response
66
- )
81
+ ensure
82
+ segment.finish()
67
83
 
68
- end
69
84
  end
70
85
 
71
86
  return amqp_response
@@ -75,12 +90,18 @@ module Hoodoo
75
90
  # Wrapper class for an AMQP request which conforms to the API that
76
91
  # NewRelic expects.
77
92
  #
78
- class AMQPNewRelicRequestWrapper
93
+ class AlchemyFluxHTTPRequestWrapper
79
94
 
80
95
  # Wrap the Alchemy Flux +http_message+ aimed at the specified
81
96
  # +full_uri+.
82
97
  #
83
- # +http_message+:: Hash describing the request for Alchemy Flux.
98
+ # +http_message+:: Hash describing the message to send. See e.g.
99
+ # Hoodoo::Client::Endpoint::AMQP#do_amqp. Note
100
+ # that the header names inside this Hash are the
101
+ # mixed case, HTTP specification style ones like
102
+ # <tt>X-Interaction-ID</tt> and _not_ the Rack
103
+ # names like <tt>HTTP_X_INTERACTION_ID</tt>.
104
+ #
84
105
  # +full_uri+:: Full target URI, as a String.
85
106
  #
86
107
  def initialize( http_message, full_uri )
@@ -91,13 +112,28 @@ module Hoodoo
91
112
  # String describing what kind of request this is.
92
113
  #
93
114
  def type
94
- self.class.to_s()
115
+ 'AlchemyFlux'
116
+ end
117
+
118
+ # String descrbing this request's intended host, according to the
119
+ # +Host+ header. May return +nil+ if none is found.
120
+ #
121
+ # See also: #host.
122
+ #
123
+ def host_from_header
124
+ begin
125
+ @http_message[ 'headers' ][ 'host' ] || @http_message[ 'headers' ][ 'Host' ]
126
+ rescue
127
+ nil
128
+ end
95
129
  end
96
130
 
97
131
  # String describing this request's intended host.
98
132
  #
133
+ # See also: #host_from_header.
134
+ #
99
135
  def host
100
- @http_message[ 'host' ]
136
+ self.host_from_header() || @http_message[ 'host' ]
101
137
  end
102
138
 
103
139
  # String describing this request's HTTP verb (GET, POST and
@@ -127,10 +163,10 @@ module Hoodoo
127
163
  @http_message[ 'headers' ][ key ] = value
128
164
  end
129
165
 
130
- # String describing the full request URI.
166
+ # URI object describing the full request URI.
131
167
  #
132
168
  def uri
133
- @full_uri
169
+ URI.parse( @full_uri.to_s )
134
170
  end
135
171
 
136
172
  end
@@ -138,29 +174,30 @@ module Hoodoo
138
174
  # Wrapper class for an AMQP request which conforms to the API that
139
175
  # NewRelic expects.
140
176
  #
141
- class AMQPNewRelicResponseWrapper
177
+ class AlchemyFluxHTTPResponseWrapper
142
178
 
143
179
  # The +response_hash+ to be wrapped.
144
180
  #
145
181
  # +response_hash+:: Hash describing the response returned from
146
- # Alchemy Flux.
182
+ # Alchemy Flux. See that gem for details.
147
183
  #
148
184
  def initialize( response_hash )
149
185
  @response_hash = response_hash
150
186
  end
151
187
 
152
- # If the NewRelic cross-app tracing header is the +key+, return the
153
- # value of the header that matches that key. Otherwise look up the
154
- # key like normal.
188
+ # Look up a key in the headers Hash first, but if absent try the
189
+ # top-level response Hash instead.
155
190
  #
156
191
  # +key+:: Hash key to look up.
157
192
  #
158
193
  def []( key )
159
- if key == ::NewRelic::Agent::CrossAppTracing::NR_APPDATA_HEADER
160
- @response_hash[ 'headers' ][ key ]
161
- else
162
- @response_hash[ key ]
163
- end
194
+ @response_hash[ 'headers' ][ key ] || @response_hash[ key ]
195
+ end
196
+
197
+ # Return the HTTP headers for this response as a Hash.
198
+ #
199
+ def to_hash
200
+ @response_hash[ 'headers' ].dup()
164
201
  end
165
202
  end
166
203
  end
@@ -185,6 +222,7 @@ module Hoodoo
185
222
 
186
223
  rescue LoadError
187
224
  # No NewRelic => do nothing
225
+
188
226
  end
189
227
 
190
228
  end # module Patch