aikido-zen 1.5.1-aarch64-linux → 1.6.0-aarch64-linux

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/benchmarks/ipc/benchmark.rb +149 -0
  3. data/benchmarks/rpc/benchmark.rb +57 -0
  4. data/docs/rails.md +22 -0
  5. data/lib/aikido/zen/actor.rb +10 -10
  6. data/lib/aikido/zen/agent.rb +61 -25
  7. data/lib/aikido/zen/api_cache.rb +61 -0
  8. data/lib/aikido/zen/api_stream.rb +6 -0
  9. data/lib/aikido/zen/attack.rb +14 -14
  10. data/lib/aikido/zen/attack_wave.rb +8 -8
  11. data/lib/aikido/zen/collector/event.rb +26 -26
  12. data/lib/aikido/zen/collector/routes.rb +4 -4
  13. data/lib/aikido/zen/collector/sink_stats.rb +10 -10
  14. data/lib/aikido/zen/collector/stats.rb +17 -17
  15. data/lib/aikido/zen/config.rb +50 -25
  16. data/lib/aikido/zen/errors.rb +0 -8
  17. data/lib/aikido/zen/event.rb +12 -12
  18. data/lib/aikido/zen/ipc/ipc.rb +506 -0
  19. data/lib/aikido/zen/ipc/rpc.rb +349 -0
  20. data/lib/aikido/zen/ipc.rb +4 -0
  21. data/lib/aikido/zen/middleware/fork_detector.rb +8 -2
  22. data/lib/aikido/zen/middleware/rack_throttler.rb +2 -4
  23. data/lib/aikido/zen/outbound_connection.rb +3 -3
  24. data/lib/aikido/zen/payload.rb +3 -3
  25. data/lib/aikido/zen/rate_limiter/result.rb +20 -0
  26. data/lib/aikido/zen/request.rb +14 -9
  27. data/lib/aikido/zen/route.rb +3 -3
  28. data/lib/aikido/zen/sinks/action_controller.rb +2 -4
  29. data/lib/aikido/zen/system_info.rb +13 -13
  30. data/lib/aikido/zen/version.rb +1 -1
  31. data/lib/aikido/zen/worker_process/agent/client.rb +141 -0
  32. data/lib/aikido/zen/worker_process/agent/server.rb +97 -0
  33. data/lib/aikido/zen/worker_process/agent.rb +4 -0
  34. data/lib/aikido/zen/worker_process.rb +3 -0
  35. data/lib/aikido/zen.rb +55 -29
  36. metadata +12 -6
  37. data/lib/aikido/zen/detached_agent/agent.rb +0 -79
  38. data/lib/aikido/zen/detached_agent/front_object.rb +0 -41
  39. data/lib/aikido/zen/detached_agent/server.rb +0 -78
  40. data/lib/aikido/zen/detached_agent.rb +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 419c4218ef399b4771e16a5dd23ff900d183cbbe68d27ce4ff5bb05911334ecf
4
- data.tar.gz: 0433d63e6456458c516f277e477f030b3359e486f897f711337b03e48b144789
3
+ metadata.gz: eba16aa05cb3f3506e487a3dfd7c03a1d4bbc186a397053aaf43562fad1ed4f4
4
+ data.tar.gz: b9815eab35c5f245eba4587c64bf4bfda422c1bcfea1d1d21eee747057723e41
5
5
  SHA512:
6
- metadata.gz: 7f229bf62bd38b3f5c33fba7fa03dd0b6420c3dc6b3186675d52fdb27cad3f56b6d7d743d6ebd2e9b8c5dc4e4777dd215f440d21ced57c620467ab68c474de7b
7
- data.tar.gz: e901f43ec597884c0db569ad753bb66d471d88b6aad11188129b6c4ffb267c8d6433e4f776792269fbb29678cf4d7b4b871ee18d86fcbeb6363a9f0e93532849
6
+ metadata.gz: 9c99f6276ce2d33f42d3b9ec7f5430bb647cd72cb146e583066d97d918cc0e59b21d82999dada088197627be1226579cbea675f7750c609346a2d7487f39b3e5
7
+ data.tar.gz: ad2e3595f4fee8181ba2083f05a2a15181cf62db472f47c112a3480dd6575846cf50d7e9c3ac420a5f3ddbd48bda9b98cff815de50a12a35e427ebbce7e68427
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "benchmark"
4
+ require "securerandom"
5
+ require "aikido-zen"
6
+
7
+ class BasicIO
8
+ def initialize(data, timeout: 5)
9
+ @data = data
10
+ @size = data.bytesize
11
+ @timeout = timeout
12
+ end
13
+
14
+ def read(socket)
15
+ buffer = String.new(encoding: Encoding::BINARY)
16
+
17
+ while buffer.bytesize < @size
18
+ case chunk = socket.read_nonblock(@size - buffer.bytesize, exception: false)
19
+ when :wait_readable
20
+ raise Errno::ETIMEDOUT, "read timed out" unless IO.select([socket], nil, nil, @timeout)
21
+ when nil
22
+ raise EOFError
23
+ else
24
+ buffer << chunk
25
+ end
26
+ end
27
+
28
+ buffer
29
+ end
30
+
31
+ def write(socket)
32
+ written = 0
33
+
34
+ while written < @data.bytesize
35
+ case n = socket.write_nonblock(@data.byteslice(written..), exception: false)
36
+ when :wait_writable
37
+ raise Errno::ETIMEDOUT, "write timed out" unless IO.select(nil, [socket], nil, @timeout)
38
+ else
39
+ written += n
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ class TimedIO
46
+ include Aikido::Zen::IPC::TimedIO
47
+
48
+ def initialize(data, timeout: 5)
49
+ @data = data
50
+ @size = data.bytesize
51
+ @timeout = timeout
52
+ end
53
+
54
+ def read(socket)
55
+ read_with_deadline(socket, @size, Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout)
56
+ end
57
+
58
+ def write(socket)
59
+ write_with_deadline(socket, @data, Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout)
60
+ end
61
+ end
62
+
63
+ class FramedIO
64
+ include Aikido::Zen::IPC::FramedIO
65
+
66
+ def initialize(data, timeout: 5)
67
+ @data = data
68
+ @timeout = timeout
69
+ @buffer = String.new(encoding: Encoding::BINARY)
70
+ end
71
+
72
+ def read(socket)
73
+ read_coalesced_frame_with_timeout(socket, @buffer, nil, @timeout)
74
+ end
75
+
76
+ def write(socket)
77
+ write_coalesced_frame_with_timeout(socket, @data, nil, @timeout)
78
+ end
79
+ end
80
+
81
+ def run(duration, io)
82
+ secret = SecureRandom.bytes(32)
83
+
84
+ server = Aikido::Zen::IPC::Server.start(secret) do |socket|
85
+ loop do
86
+ io.read(socket)
87
+ end
88
+ rescue EOFError, Errno::ECONNRESET, Errno::EPIPE
89
+ # disconnected
90
+ rescue IOError
91
+ # client stopped
92
+ end
93
+
94
+ client = Aikido::Zen::IPC::Client.new(secret, server.host, server.port)
95
+
96
+ sent = 0
97
+
98
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + duration
99
+
100
+ result = Benchmark.measure do
101
+ while Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline
102
+ io.write(client.socket)
103
+
104
+ sent += 1
105
+ end
106
+ end
107
+
108
+ [sent, result.real]
109
+ ensure
110
+ client.close
111
+ server.stop
112
+ end
113
+
114
+ def report(label, sent, real, data_size, baseline: nil)
115
+ rate = sent / real
116
+ mib_per_sec = rate * data_size / (1024 * 1024)
117
+
118
+ puts label
119
+ puts "#{rate.round} messages/sec, #{mib_per_sec.round(2)} MiB/sec"
120
+
121
+ if baseline
122
+ overhead = 100 - (100.0 * rate / baseline)
123
+ puts "#{overhead.round(1)}% fewer messages/sec than baseline"
124
+ end
125
+ end
126
+
127
+ if __FILE__ == $0
128
+ duration = (ENV["DURATION"] || 5).to_i
129
+ data_size = (ENV["DATA_SIZE"] || 1024).to_i
130
+
131
+ data = SecureRandom.bytes(data_size)
132
+
133
+ basic_io = BasicIO.new(data)
134
+ basic_io_sent, basic_io_real = run(duration, basic_io)
135
+ report("baseline", basic_io_sent, basic_io_real, data_size)
136
+ basic_io_rate = basic_io_sent / basic_io_real
137
+
138
+ puts
139
+
140
+ timed_io = TimedIO.new(data)
141
+ timed_io_sent, timed_io_real = run(duration, timed_io)
142
+ report("TimedIO", timed_io_sent, timed_io_real, data_size, baseline: basic_io_rate)
143
+
144
+ puts
145
+
146
+ framed_io = FramedIO.new(data)
147
+ framed_io_sent, framed_io_real = run(duration, framed_io)
148
+ report("FramedIO (coalesced)", framed_io_sent, framed_io_real, data_size, baseline: basic_io_rate)
149
+ end
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "benchmark"
4
+ require "securerandom"
5
+ require "logger"
6
+ require "aikido-zen"
7
+
8
+ def run(duration, concurrency)
9
+ secret = SecureRandom.bytes(32)
10
+ logger = Logger.new(File::NULL)
11
+
12
+ server = Aikido::Zen::RPC::Server.start(secret, logger: logger) do |server|
13
+ server.handle("echo") { |respond, value| respond.call(value) }
14
+ end
15
+
16
+ client = Aikido::Zen::RPC::Client.start(secret, server.host, server.port, logger: logger)
17
+
18
+ calls = Concurrent::AtomicFixnum.new(0)
19
+
20
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + duration
21
+
22
+ result = Benchmark.measure do
23
+ threads = concurrency.times.map do
24
+ Thread.new do
25
+ while Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline
26
+ client.invoke("echo", 5.0, "hello")
27
+
28
+ calls.increment
29
+ end
30
+ end
31
+ end
32
+
33
+ threads.each(&:join)
34
+ end
35
+
36
+ [calls.value, result.real]
37
+ ensure
38
+ client.stop
39
+ server.stop
40
+ end
41
+
42
+ def report(label, total_calls, real)
43
+ rate = total_calls / real
44
+ latency_ms = real / total_calls * 1000
45
+
46
+ puts label
47
+ puts "#{rate.round} calls/sec, #{latency_ms.round(3)} ms/call"
48
+ end
49
+
50
+ if __FILE__ == $0
51
+ duration = (ENV["DURATION"] || 5).to_i
52
+ concurrency = (ENV["CONCURRENCY"] || 1).to_i
53
+
54
+ calls, real = run(duration, concurrency)
55
+
56
+ report("RPC echo (concurrency: #{concurrency})", calls, real)
57
+ end
data/docs/rails.md CHANGED
@@ -27,6 +27,28 @@ Bundler.require(*Rails.groups)
27
27
  That's it! Zen will start to run inside your app when it starts getting
28
28
  requests.
29
29
 
30
+ ## Running with multiple worker processes (Puma)
31
+
32
+ Zen can share rate limiting state, runtime settings, and runtime stats
33
+ across all of an app's worker processes. This only works if Rails is booted
34
+ once and then forked into workers, which on Puma requires
35
+ [`preload_app!`][puma-preload]:
36
+
37
+ ```ruby
38
+ # config/puma.rb
39
+ preload_app!
40
+ ```
41
+
42
+ Without `preload_app!`, each worker boots Rails independently, so Zen
43
+ tracks rate limits, settings, and stats separately per worker instead
44
+ of sharing them.
45
+
46
+ > [!NOTE]
47
+ > This only matters for multi-process setups; it doesn't apply to
48
+ > single-process or threaded-only deployments.
49
+
50
+ [puma-preload]: https://github.com/puma/puma#cluster-mode
51
+
30
52
  ## Rate limiting and user blocking
31
53
 
32
54
  If you want to add the rate limiting feature to your app, modify your code like this:
@@ -40,11 +40,11 @@ module Aikido::Zen
40
40
  class Actor
41
41
  def self.from_json(data)
42
42
  new(
43
- id: data[:id],
44
- name: data[:name],
45
- ip: data[:lastIpAddress],
46
- first_seen_at: Time.at(data[:firstSeenAt] / 1000),
47
- last_seen_at: Time.at(data[:lastSeenAt] / 1000)
43
+ id: data["id"],
44
+ name: data["name"],
45
+ ip: data["lastIpAddress"],
46
+ first_seen_at: Time.at(data["firstSeenAt"] / 1000),
47
+ last_seen_at: Time.at(data["lastSeenAt"] / 1000)
48
48
  )
49
49
  end
50
50
 
@@ -135,11 +135,11 @@ module Aikido::Zen
135
135
 
136
136
  def as_json
137
137
  {
138
- id: id,
139
- name: name,
140
- lastIpAddress: ip,
141
- firstSeenAt: first_seen_at.to_i * 1000,
142
- lastSeenAt: last_seen_at.to_i * 1000
138
+ "id" => id,
139
+ "name" => name,
140
+ "lastIpAddress" => ip,
141
+ "firstSeenAt" => first_seen_at.to_i * 1000,
142
+ "lastSeenAt" => last_seen_at.to_i * 1000
143
143
  }.compact
144
144
  end
145
145
  end
@@ -19,14 +19,12 @@ module Aikido::Zen
19
19
  def initialize(
20
20
  config: Aikido::Zen.config,
21
21
  collector: Aikido::Zen.collector,
22
- detached_agent: Aikido::Zen.detached_agent,
23
22
  worker: Aikido::Zen::Worker.new(config: config),
24
23
  api_client: Aikido::Zen::APIClient.new(config: config),
25
24
  api_stream: Aikido::Zen::APIStream.new(config: config)
26
25
  )
27
26
  @config = config
28
27
  @collector = collector
29
- @detached_agent = detached_agent
30
28
  @worker = worker
31
29
  @api_client = api_client
32
30
  @api_stream = api_stream
@@ -61,26 +59,36 @@ module Aikido::Zen
61
59
  return
62
60
  end
63
61
 
62
+ # Code coverage is disabled here because the block is only called when
63
+ # the process exits.
64
+ # :nocov:
64
65
  at_exit { stop! if started? }
66
+ # :nocov:
65
67
 
66
68
  report(Events::Started.new(time: @started_at)) do |response|
67
- if update_settings_from_runtime_config!(response)
69
+ if update_settings_from_runtime_config!(response, reason: "after start")
68
70
  updated_settings!
69
- @config.logger.info("Updated runtime settings")
71
+ # :nocov:
72
+ else
73
+ # empty
70
74
  end
75
+ # :nocov:
71
76
  rescue => err
72
77
  @config.logger.error(err.message)
73
78
  end
74
79
 
75
80
  begin
76
- update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
77
- @config.logger.info("Updated runtime firewall list")
81
+ update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after start")
78
82
  rescue => err
79
83
  @config.logger.error(err.message)
80
84
  end
81
85
 
82
86
  if @config.realtime_settings_updates_enabled?
83
- @api_stream.handle("config-updated") { |event| settings_updated(event) }
87
+ @api_stream.handle("config-updated") do |event|
88
+ @config.logger.debug("Received server-sent event: config-updated")
89
+ settings_updated(event)
90
+ end
91
+
84
92
  @api_stream.start!
85
93
  end
86
94
 
@@ -169,13 +177,15 @@ module Aikido::Zen
169
177
 
170
178
  heartbeat = @collector.flush
171
179
  report(heartbeat) do |response|
172
- if update_settings_from_runtime_config!(response)
180
+ if update_settings_from_runtime_config!(response, reason: "after heartbeat")
173
181
  updated_settings!
174
- @config.logger.info("Updated runtime settings after heartbeat")
175
182
 
176
- update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
177
- @config.logger.info("Updated runtime firewall list after heartbeat")
183
+ update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after heartbeat")
184
+ # :nocov:
185
+ else
186
+ # empty
178
187
  end
188
+ # :nocov:
179
189
  end
180
190
  end
181
191
 
@@ -189,13 +199,15 @@ module Aikido::Zen
189
199
  def poll_for_setting_updates
190
200
  @worker.every(@config.polling_interval) do
191
201
  if @api_client.should_fetch_settings?
192
- if update_settings_from_runtime_config!(@api_client.fetch_runtime_config)
202
+ if update_settings_from_runtime_config!(@api_client.fetch_runtime_config, reason: "after polling")
193
203
  updated_settings!
194
- @config.logger.info("Updated runtime settings after polling")
204
+ # :nocov:
205
+ else
206
+ # empty
195
207
  end
208
+ # :nocov:
196
209
 
197
- update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
198
- @config.logger.info("Updated runtime firewall list after polling")
210
+ update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after polling")
199
211
  end
200
212
  end
201
213
  end
@@ -206,13 +218,15 @@ module Aikido::Zen
206
218
  updated_at = Time.at(event[:data]["configUpdatedAt"].to_i)
207
219
 
208
220
  if should_fetch_settings?(updated_at)
209
- if update_settings_from_runtime_config!(@api_client.fetch_runtime_config)
221
+ if update_settings_from_runtime_config!(@api_client.fetch_runtime_config, reason: "after server-sent event")
210
222
  updated_settings!
211
- @config.logger.info("Updated runtime settings after server-side event")
212
223
 
213
- update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
214
- @config.logger.info("Updated runtime firewall list after server-side event")
224
+ update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after server-sent event")
225
+ # :nocov:
226
+ else
227
+ # empty
215
228
  end
229
+ # :nocov:
216
230
  end
217
231
  end
218
232
 
@@ -231,22 +245,44 @@ module Aikido::Zen
231
245
  end
232
246
 
233
247
  # @param data [Hash]
234
- # @return [Boolean]
235
- def update_settings_from_runtime_config!(data)
248
+ # @param reason [String] context appended to the log message when logged
249
+ # @return [Boolean] whether the settings were updated; or nil if another
250
+ # thread is already applying an update.
251
+ def update_settings_from_runtime_config!(data, reason:)
252
+ # :nocov:
236
253
  return unless @runtime_config_update_mutex.try_lock
254
+ # :nocov:
237
255
  begin
238
- Aikido::Zen.runtime_settings.update_from_runtime_config_json(data)
256
+ return false unless Aikido::Zen.api_cache.update_runtime_config(data)
257
+
258
+ if Aikido::Zen.runtime_settings.update_from_runtime_config_json(data)
259
+ @config.logger.info("Updated runtime settings #{reason}")
260
+ true
261
+ else
262
+ false
263
+ end
239
264
  ensure
240
265
  @runtime_config_update_mutex.unlock
241
266
  end
242
267
  end
243
268
 
244
269
  # @param data [Hash]
245
- # @return [Boolean]
246
- def update_settings_from_runtime_firewall_lists!(data)
270
+ # @param reason [String] context appended to the log message when logged
271
+ # @return [Boolean] whether the settings were updated; or nil if another
272
+ # thread is already applying an update.
273
+ def update_settings_from_runtime_firewall_lists!(data, reason:)
274
+ # :nocov:
247
275
  return unless @runtime_firewall_lists_update_mutex.try_lock
276
+ # :nocov:
248
277
  begin
249
- Aikido::Zen.runtime_settings.update_from_runtime_firewall_lists_json(data)
278
+ return false unless Aikido::Zen.api_cache.update_runtime_firewall_lists(data)
279
+
280
+ if Aikido::Zen.runtime_settings.update_from_runtime_firewall_lists_json(data)
281
+ @config.logger.info("Updated runtime firewall list #{reason}")
282
+ true
283
+ else
284
+ false
285
+ end
250
286
  ensure
251
287
  @runtime_firewall_lists_update_mutex.unlock
252
288
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido::Zen
4
+ class APICache
5
+ attr_reader :runtime_config
6
+ attr_reader :runtime_firewall_lists
7
+ attr_reader :runtime_config_generation
8
+ attr_reader :runtime_firewall_lists_generation
9
+
10
+ def initialize
11
+ @runtime_config_generation = 0
12
+ @runtime_firewall_lists_generation = 0
13
+ end
14
+
15
+ def runtime_config=(value)
16
+ update_runtime_config(value)
17
+ end
18
+
19
+ def runtime_firewall_lists=(value)
20
+ update_runtime_firewall_lists(value)
21
+ end
22
+
23
+ # @param value [Hash, nil]
24
+ # @return [Boolean] whether the value actually changed
25
+ def update_runtime_config(value)
26
+ return false if value == @runtime_config
27
+
28
+ @runtime_config_generation += 1
29
+ @runtime_config = value
30
+
31
+ true
32
+ end
33
+
34
+ # @param value [Hash, nil]
35
+ # @return [Boolean] whether the value actually changed
36
+ def update_runtime_firewall_lists(value)
37
+ return false if value == @runtime_firewall_lists
38
+
39
+ @runtime_firewall_lists_generation += 1
40
+ @runtime_firewall_lists = value
41
+
42
+ true
43
+ end
44
+
45
+ # @param known_generation [Integer, nil]
46
+ # @return [Array(Object, Integer), nil] the current value and generation,
47
+ # or nil if the known generation is already current.
48
+ def config_if_changed(known_generation)
49
+ return nil if known_generation == runtime_config_generation
50
+ [runtime_config, runtime_config_generation]
51
+ end
52
+
53
+ # @param known_generation [Integer, nil]
54
+ # @return [Array(Object, Integer), nil] the current value and generation,
55
+ # or nil if the known generation is already current.
56
+ def firewall_lists_if_changed(known_generation)
57
+ return nil if known_generation == runtime_firewall_lists_generation
58
+ [runtime_firewall_lists, runtime_firewall_lists_generation]
59
+ end
60
+ end
61
+ end
@@ -116,12 +116,18 @@ module Aikido::Zen
116
116
  http.open_timeout = @open_timeout
117
117
  http.write_timeout = @write_timeout
118
118
  http.read_timeout = @read_timeout
119
+ # Working around Net::HTTP cleverness; retries GET requests once by default.
119
120
  http.max_retries = 0
120
121
 
121
122
  request = Net::HTTP::Get.new("/api/runtime/stream")
122
123
  request["Authorization"] = @token
123
124
  request["Accept"] = "text/event-stream"
124
125
  request["Cache-Control"] = "no-cache"
126
+ request["X-Agent-Platform"] = "ruby"
127
+ request["X-Agent-Version"] = Aikido::Zen::VERSION
128
+ # Working around Net::HTTP cleverness; auto-negotiates gzip and buffers
129
+ # decompressed output internally.
130
+ request["Accept-Encoding"] = "identity"
125
131
 
126
132
  @config.logger.debug("API stream connecting")
127
133
  http.start
@@ -44,11 +44,11 @@ module Aikido::Zen
44
44
 
45
45
  def as_json
46
46
  {
47
- kind: kind,
48
- blocked: blocked?,
49
- metadata: metadata,
50
- operation: @operation,
51
- stack: @stack
47
+ "kind" => kind,
48
+ "blocked" => blocked?,
49
+ "metadata" => metadata,
50
+ "operation" => @operation,
51
+ "stack" => @stack
52
52
  }.compact.merge(input.as_json)
53
53
  end
54
54
 
@@ -70,7 +70,7 @@ module Aikido::Zen
70
70
 
71
71
  def metadata
72
72
  {
73
- filename: filepath
73
+ "filename" => filepath
74
74
  }
75
75
  end
76
76
 
@@ -107,7 +107,7 @@ module Aikido::Zen
107
107
 
108
108
  def metadata
109
109
  {
110
- command: @command
110
+ "command" => @command
111
111
  }
112
112
  end
113
113
 
@@ -139,9 +139,9 @@ module Aikido::Zen
139
139
 
140
140
  def metadata
141
141
  {
142
- sql: @query,
143
- dialect: @dialect.name,
144
- failedToTokenize: @failed_to_tokenize || nil
142
+ "sql" => @query,
143
+ "dialect" => @dialect.name,
144
+ "failedToTokenize" => @failed_to_tokenize || nil
145
145
  }.compact
146
146
  end
147
147
 
@@ -174,8 +174,8 @@ module Aikido::Zen
174
174
 
175
175
  def metadata
176
176
  {
177
- hostname: @request.uri.hostname,
178
- port: @request.uri.port.to_s
177
+ "hostname" => @request.uri.hostname,
178
+ "port" => @request.uri.port.to_s
179
179
  }
180
180
  end
181
181
  end
@@ -212,8 +212,8 @@ module Aikido::Zen
212
212
 
213
213
  def metadata
214
214
  {
215
- hostname: @hostname,
216
- privateIP: @address
215
+ "hostname" => @hostname,
216
+ "privateIP" => @address
217
217
  }
218
218
  end
219
219
  end
@@ -69,9 +69,9 @@ module Aikido::Zen
69
69
 
70
70
  def as_json
71
71
  {
72
- ipAddress: @ip_address,
73
- userAgent: @user_agent,
74
- source: @source
72
+ "ipAddress" => @ip_address,
73
+ "userAgent" => @user_agent,
74
+ "source" => @source
75
75
  }.compact
76
76
  end
77
77
 
@@ -101,10 +101,10 @@ module Aikido::Zen
101
101
 
102
102
  def as_json
103
103
  {
104
- metadata: {
105
- samples: @samples.as_json.to_json # The API only accepts string values in metadata
104
+ "metadata" => {
105
+ "samples" => @samples.as_json.to_json # The API only accepts string values in metadata
106
106
  },
107
- user: @user.as_json
107
+ "user" => @user.as_json
108
108
  }.compact
109
109
  end
110
110
 
@@ -130,8 +130,8 @@ module Aikido::Zen
130
130
 
131
131
  def as_json
132
132
  {
133
- method: @verb.as_json,
134
- url: @path.as_json
133
+ "method" => @verb.as_json,
134
+ "url" => @path.as_json
135
135
  }.compact
136
136
  end
137
137