hoodoo 1.19.0 → 2.0.0

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 (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