datadog-ci 0.8.3 → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -0
  3. data/LICENSE-3rdparty.csv +1 -1
  4. data/README.md +40 -55
  5. data/ext/datadog_cov/datadog_cov.c +192 -0
  6. data/ext/datadog_cov/extconf.rb +18 -0
  7. data/lib/datadog/ci/configuration/components.rb +43 -9
  8. data/lib/datadog/ci/configuration/settings.rb +7 -1
  9. data/lib/datadog/ci/contrib/cucumber/configuration/settings.rb +0 -15
  10. data/lib/datadog/ci/contrib/cucumber/ext.rb +1 -5
  11. data/lib/datadog/ci/contrib/cucumber/formatter.rb +13 -18
  12. data/lib/datadog/ci/contrib/cucumber/integration.rb +1 -2
  13. data/lib/datadog/ci/contrib/cucumber/patcher.rb +3 -0
  14. data/lib/datadog/ci/contrib/cucumber/step.rb +27 -0
  15. data/lib/datadog/ci/contrib/minitest/configuration/settings.rb +0 -15
  16. data/lib/datadog/ci/contrib/minitest/ext.rb +1 -5
  17. data/lib/datadog/ci/contrib/minitest/helpers.rb +1 -2
  18. data/lib/datadog/ci/contrib/minitest/hooks.rb +4 -2
  19. data/lib/datadog/ci/contrib/minitest/integration.rb +1 -1
  20. data/lib/datadog/ci/contrib/rspec/configuration/settings.rb +0 -15
  21. data/lib/datadog/ci/contrib/rspec/example.rb +25 -23
  22. data/lib/datadog/ci/contrib/rspec/ext.rb +0 -4
  23. data/lib/datadog/ci/contrib/rspec/integration.rb +1 -2
  24. data/lib/datadog/ci/contrib/settings.rb +0 -3
  25. data/lib/datadog/ci/ext/environment/providers/base.rb +1 -1
  26. data/lib/datadog/ci/ext/environment/providers/bitbucket.rb +1 -1
  27. data/lib/datadog/ci/ext/environment/providers/local_git.rb +8 -79
  28. data/lib/datadog/ci/ext/environment.rb +11 -16
  29. data/lib/datadog/ci/ext/settings.rb +1 -0
  30. data/lib/datadog/ci/ext/test.rb +5 -0
  31. data/lib/datadog/ci/ext/transport.rb +12 -0
  32. data/lib/datadog/ci/git/local_repository.rb +238 -0
  33. data/lib/datadog/ci/git/packfiles.rb +70 -0
  34. data/lib/datadog/ci/git/search_commits.rb +77 -0
  35. data/lib/datadog/ci/git/tree_uploader.rb +90 -0
  36. data/lib/datadog/ci/git/upload_packfile.rb +66 -0
  37. data/lib/datadog/ci/git/user.rb +29 -0
  38. data/lib/datadog/ci/itr/coverage/ddcov.rb +14 -0
  39. data/lib/datadog/ci/itr/coverage/event.rb +81 -0
  40. data/lib/datadog/ci/itr/coverage/transport.rb +42 -0
  41. data/lib/datadog/ci/itr/coverage/writer.rb +108 -0
  42. data/lib/datadog/ci/itr/runner.rb +143 -6
  43. data/lib/datadog/ci/itr/skippable.rb +106 -0
  44. data/lib/datadog/ci/span.rb +9 -0
  45. data/lib/datadog/ci/test.rb +20 -14
  46. data/lib/datadog/ci/test_module.rb +2 -2
  47. data/lib/datadog/ci/test_session.rb +2 -2
  48. data/lib/datadog/ci/test_suite.rb +2 -2
  49. data/lib/datadog/ci/test_visibility/context/global.rb +1 -3
  50. data/lib/datadog/ci/test_visibility/null_recorder.rb +5 -2
  51. data/lib/datadog/ci/test_visibility/recorder.rb +63 -8
  52. data/lib/datadog/ci/test_visibility/serializers/base.rb +1 -1
  53. data/lib/datadog/ci/test_visibility/serializers/factories/test_level.rb +1 -1
  54. data/lib/datadog/ci/test_visibility/serializers/factories/test_suite_level.rb +1 -1
  55. data/lib/datadog/ci/test_visibility/transport.rb +11 -54
  56. data/lib/datadog/ci/transport/api/agentless.rb +8 -1
  57. data/lib/datadog/ci/transport/api/base.rb +23 -0
  58. data/lib/datadog/ci/transport/api/builder.rb +9 -1
  59. data/lib/datadog/ci/transport/api/evp_proxy.rb +8 -0
  60. data/lib/datadog/ci/transport/event_platform_transport.rb +88 -0
  61. data/lib/datadog/ci/transport/http.rb +43 -6
  62. data/lib/datadog/ci/transport/remote_settings_api.rb +12 -6
  63. data/lib/datadog/ci/utils/configuration.rb +2 -2
  64. data/lib/datadog/ci/utils/git.rb +6 -67
  65. data/lib/datadog/ci/utils/parsing.rb +16 -0
  66. data/lib/datadog/ci/utils/test_run.rb +13 -0
  67. data/lib/datadog/ci/version.rb +5 -5
  68. data/lib/datadog/ci/worker.rb +35 -0
  69. data/lib/datadog/ci.rb +4 -0
  70. metadata +36 -4
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "datadog/tracing"
4
+ require "datadog/tracing/contrib/component"
4
5
  require "datadog/tracing/trace_digest"
5
6
 
6
7
  require "rbconfig"
@@ -12,13 +13,14 @@ require_relative "../codeowners/parser"
12
13
  require_relative "../ext/app_types"
13
14
  require_relative "../ext/test"
14
15
  require_relative "../ext/environment"
15
- require_relative "../utils/git"
16
+ require_relative "../git/local_repository"
16
17
 
17
18
  require_relative "../span"
18
19
  require_relative "../test"
19
20
  require_relative "../test_session"
20
21
  require_relative "../test_module"
21
22
  require_relative "../test_suite"
23
+ require_relative "../worker"
22
24
 
23
25
  module Datadog
24
26
  module CI
@@ -29,8 +31,11 @@ module Datadog
29
31
  attr_reader :environment_tags, :test_suite_level_visibility_enabled
30
32
 
31
33
  def initialize(
32
- itr:, remote_settings_api:, test_suite_level_visibility_enabled: false,
33
- codeowners: Codeowners::Parser.new(Utils::Git.root).parse
34
+ itr:,
35
+ remote_settings_api:,
36
+ git_tree_upload_worker: DummyWorker.new,
37
+ test_suite_level_visibility_enabled: false,
38
+ codeowners: Codeowners::Parser.new(Git::LocalRepository.root).parse
34
39
  )
35
40
  @test_suite_level_visibility_enabled = test_suite_level_visibility_enabled
36
41
 
@@ -42,6 +47,11 @@ module Datadog
42
47
 
43
48
  @itr = itr
44
49
  @remote_settings_api = remote_settings_api
50
+ @git_tree_upload_worker = git_tree_upload_worker
51
+ end
52
+
53
+ def shutdown!
54
+ @git_tree_upload_worker.stop
45
55
  end
46
56
 
47
57
  def start_test_session(service: nil, tags: {})
@@ -55,6 +65,7 @@ module Datadog
55
65
 
56
66
  test_session = build_test_session(tracer_span, tags)
57
67
 
68
+ @git_tree_upload_worker.perform(test_session.git_repository_url)
58
69
  configure_library(test_session)
59
70
 
60
71
  test_session
@@ -116,14 +127,19 @@ module Datadog
116
127
  test = build_test(tracer_span, tags)
117
128
 
118
129
  @local_context.activate_test(test) do
119
- block.call(test)
130
+ on_test_started(test)
131
+ res = block.call(test)
132
+ on_test_finished(test)
133
+ res
120
134
  end
121
135
  end
122
136
  else
123
137
  tracer_span = start_datadog_tracer_span(test_name, span_options)
124
-
125
138
  test = build_test(tracer_span, tags)
139
+
126
140
  @local_context.activate_test(test)
141
+ on_test_started(test)
142
+
127
143
  test
128
144
  end
129
145
  end
@@ -168,10 +184,16 @@ module Datadog
168
184
  end
169
185
 
170
186
  def deactivate_test
187
+ test = active_test
188
+ on_test_finished(test) if test
189
+
171
190
  @local_context.deactivate_test
172
191
  end
173
192
 
174
193
  def deactivate_test_session
194
+ test_session = active_test_session
195
+ on_test_session_finished(test_session) if test_session
196
+
175
197
  @global_context.deactivate_test_session!
176
198
  end
177
199
 
@@ -194,16 +216,34 @@ module Datadog
194
216
  return unless itr_enabled?
195
217
 
196
218
  remote_configuration = @remote_settings_api.fetch_library_settings(test_session)
197
- @itr.configure(remote_configuration.payload, test_session)
219
+ # sometimes we can skip code coverage for default branch if there are no changes in the repository
220
+ # backend needs git metadata uploaded for this test session to check if we can skip code coverage
221
+ if remote_configuration.require_git?
222
+ Datadog.logger.debug { "Library configuration endpoint requires git upload to be finished, waiting..." }
223
+ @git_tree_upload_worker.wait_until_done
224
+
225
+ Datadog.logger.debug { "Requesting library configuration again..." }
226
+ remote_configuration = @remote_settings_api.fetch_library_settings(test_session)
227
+
228
+ if remote_configuration.require_git?
229
+ Datadog.logger.debug { "git metadata upload did not complete in time when configuring library" }
230
+ end
231
+ end
232
+
233
+ @itr.configure(
234
+ remote_configuration.payload,
235
+ test_session: test_session,
236
+ git_tree_upload_worker: @git_tree_upload_worker
237
+ )
198
238
  end
199
239
 
200
240
  def skip_tracing(block = nil)
201
- block.call(nil) if block
241
+ block&.call(nil)
202
242
  end
203
243
 
204
244
  # Sets trace's origin to ciapp-test
205
245
  def set_trace_origin(trace)
206
- trace.origin = Ext::Test::CONTEXT_ORIGIN if trace
246
+ trace&.origin = Ext::Test::CONTEXT_ORIGIN
207
247
  end
208
248
 
209
249
  def build_test_session(tracer_span, tags)
@@ -357,6 +397,21 @@ module Datadog
357
397
  end
358
398
  end
359
399
  end
400
+
401
+ # TODO: use kind of event system to notify about test finished?
402
+ def on_test_finished(test)
403
+ @itr.stop_coverage(test)
404
+ @itr.count_skipped_test(test)
405
+ end
406
+
407
+ def on_test_started(test)
408
+ @itr.mark_if_skippable(test)
409
+ @itr.start_coverage(test)
410
+ end
411
+
412
+ def on_test_session_finished(test_session)
413
+ @itr.write_test_session_tags(test_session)
414
+ end
360
415
  end
361
416
  end
362
417
  end
@@ -241,7 +241,7 @@ module Datadog
241
241
  end
242
242
 
243
243
  def to_integer(value)
244
- value.to_i if value
244
+ value&.to_i
245
245
  end
246
246
  end
247
247
  end
@@ -8,7 +8,7 @@ module Datadog
8
8
  module TestVisibility
9
9
  module Serializers
10
10
  module Factories
11
- # This factory takes care of creating citestcycle serializers when test-level visibility is enabled
11
+ # This factory takes care of creating msgpack serializers when test-level visibility is enabled
12
12
  # NOTE: citestcycle is a protocol Datadog uses to submit test execution tracing information to CI visibility
13
13
  # backend
14
14
  module TestLevel
@@ -11,7 +11,7 @@ module Datadog
11
11
  module TestVisibility
12
12
  module Serializers
13
13
  module Factories
14
- # This factory takes care of creating citestcycle serializers when test-suite-level visibility is enabled
14
+ # This factory takes care of creating msgpack serializers when test-suite-level visibility is enabled
15
15
  module TestSuiteLevel
16
16
  module_function
17
17
 
@@ -1,64 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "msgpack"
4
- require "uri"
5
-
6
- require "datadog/core/encoding"
7
3
  require "datadog/core/environment/identity"
8
- require "datadog/core/chunker"
9
4
 
10
5
  require_relative "serializers/factories/test_level"
11
6
  require_relative "../ext/transport"
7
+ require_relative "../transport/event_platform_transport"
12
8
 
13
9
  module Datadog
14
10
  module CI
15
11
  module TestVisibility
16
- class Transport
17
- # CI test cycle intake's limit is 5.1MB uncompressed
18
- # We will use a bit more conservative value 5MB
19
- DEFAULT_MAX_PAYLOAD_SIZE = 5 * 1024 * 1024
20
-
21
- attr_reader :serializers_factory,
22
- :api,
23
- :max_payload_size,
24
- :dd_env
12
+ class Transport < Datadog::CI::Transport::EventPlatformTransport
13
+ attr_reader :serializers_factory, :dd_env
25
14
 
26
15
  def initialize(
27
16
  api:,
28
- dd_env: nil,
17
+ dd_env:,
29
18
  serializers_factory: Datadog::CI::TestVisibility::Serializers::Factories::TestLevel,
30
19
  max_payload_size: DEFAULT_MAX_PAYLOAD_SIZE
31
20
  )
21
+ super(api: api, max_payload_size: max_payload_size)
22
+
32
23
  @serializers_factory = serializers_factory
33
- @max_payload_size = max_payload_size
34
24
  @dd_env = dd_env
35
- @api = api
36
25
  end
37
26
 
27
+ # this method is needed for compatibility with Datadog::Tracing::Writer that uses this Transport
38
28
  def send_traces(traces)
39
- return [] if traces.nil? || traces.empty?
40
-
41
- Datadog.logger.debug { "Sending #{traces.count} traces..." }
42
-
43
- encoded_events = encode_traces(traces)
44
- if encoded_events.empty?
45
- Datadog.logger.debug { "Empty encoded events list, skipping send" }
46
- return []
47
- end
48
-
49
- responses = []
50
- Datadog::Core::Chunker.chunk_by_size(encoded_events, max_payload_size).map do |chunk|
51
- encoded_payload = pack_events(chunk)
52
- Datadog.logger.debug do
53
- "Send chunk of #{chunk.count} events; payload size #{encoded_payload.size}"
54
- end
55
-
56
- response = send_payload(encoded_payload)
57
-
58
- responses << response
59
- end
60
-
61
- responses
29
+ send_events(traces)
62
30
  end
63
31
 
64
32
  private
@@ -70,15 +38,9 @@ module Datadog
70
38
  )
71
39
  end
72
40
 
73
- def encode_traces(traces)
41
+ def encode_events(traces)
74
42
  traces.flat_map do |trace|
75
- spans = trace.spans
76
- # TODO: remove condition when 1.0 is released
77
- if spans.respond_to?(:filter_map)
78
- spans.filter_map { |span| encode_span(trace, span) }
79
- else
80
- spans.map { |span| encode_span(trace, span) }.reject(&:nil?)
81
- end
43
+ trace.spans.filter_map { |span| encode_span(trace, span) }
82
44
  end
83
45
  end
84
46
 
@@ -107,9 +69,7 @@ module Datadog
107
69
  Datadog::Core::Encoding::MsgpackEncoder
108
70
  end
109
71
 
110
- def pack_events(encoded_events)
111
- packer = MessagePack::Packer.new
112
-
72
+ def write_payload_header(packer)
113
73
  packer.write_map_header(3) # Set header with how many elements in the map
114
74
 
115
75
  packer.write("version")
@@ -137,9 +97,6 @@ module Datadog
137
97
  packer.write(Datadog::CI::VERSION::STRING)
138
98
 
139
99
  packer.write("events")
140
- packer.write_array_header(encoded_events.size)
141
-
142
- (packer.buffer.to_a + encoded_events).join
143
100
  end
144
101
  end
145
102
  end
@@ -10,10 +10,11 @@ module Datadog
10
10
  class Agentless < Base
11
11
  attr_reader :api_key
12
12
 
13
- def initialize(api_key:, citestcycle_url:, api_url:)
13
+ def initialize(api_key:, citestcycle_url:, api_url:, citestcov_url:)
14
14
  @api_key = api_key
15
15
  @citestcycle_http = build_http_client(citestcycle_url, compress: true)
16
16
  @api_http = build_http_client(api_url, compress: false)
17
+ @citestcov_http = build_http_client(citestcov_url, compress: true)
17
18
  end
18
19
 
19
20
  def citestcycle_request(path:, payload:, headers: {}, verb: "post")
@@ -28,6 +29,12 @@ module Datadog
28
29
  perform_request(@api_http, path: path, payload: payload, headers: headers, verb: verb)
29
30
  end
30
31
 
32
+ def citestcov_request(path:, payload:, headers: {}, verb: "post")
33
+ super(path: path, payload: payload, headers: headers, verb: verb)
34
+
35
+ perform_request(@citestcov_http, path: path, payload: @citestcov_payload, headers: headers, verb: verb)
36
+ end
37
+
31
38
  private
32
39
 
33
40
  def perform_request(http_client, path:, payload:, headers:, verb:)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "securerandom"
4
+
3
5
  require_relative "../../ext/transport"
4
6
 
5
7
  module Datadog
@@ -15,6 +17,27 @@ module Datadog
15
17
  headers[Ext::Transport::HEADER_CONTENT_TYPE] ||= Ext::Transport::CONTENT_TYPE_MESSAGEPACK
16
18
  end
17
19
 
20
+ def citestcov_request(path:, payload:, headers: {}, verb: "post")
21
+ citestcov_request_boundary = ::SecureRandom.uuid
22
+
23
+ headers[Ext::Transport::HEADER_CONTENT_TYPE] ||=
24
+ "#{Ext::Transport::CONTENT_TYPE_MULTIPART_FORM_DATA}; boundary=#{citestcov_request_boundary}"
25
+
26
+ @citestcov_payload = [
27
+ "--#{citestcov_request_boundary}",
28
+ 'Content-Disposition: form-data; name="event"; filename="event.json"',
29
+ "Content-Type: application/json",
30
+ "",
31
+ '{"dummy":true}',
32
+ "--#{citestcov_request_boundary}",
33
+ 'Content-Disposition: form-data; name="coverage1"; filename="coverage1.msgpack"',
34
+ "Content-Type: application/msgpack",
35
+ "",
36
+ payload,
37
+ "--#{citestcov_request_boundary}--"
38
+ ].join("\r\n")
39
+ end
40
+
18
41
  def headers_with_default(headers)
19
42
  request_headers = default_headers
20
43
  request_headers.merge!(headers)
@@ -24,7 +24,15 @@ module Datadog
24
24
  api_url = settings.ci.agentless_url ||
25
25
  "https://#{Ext::Transport::DD_API_HOST_PREFIX}.#{dd_site}:443"
26
26
 
27
- Agentless.new(api_key: settings.api_key, citestcycle_url: citestcycle_url, api_url: api_url)
27
+ citestcov_url = settings.ci.agentless_url ||
28
+ "https://#{Ext::Transport::TEST_COVERAGE_INTAKE_HOST_PREFIX}.#{dd_site}:443"
29
+
30
+ Agentless.new(
31
+ api_key: settings.api_key,
32
+ citestcycle_url: citestcycle_url,
33
+ api_url: api_url,
34
+ citestcov_url: citestcov_url
35
+ )
28
36
  end
29
37
 
30
38
  def self.build_evp_proxy_api(settings)
@@ -38,6 +38,14 @@ module Datadog
38
38
  perform_request(@agent_api_http, path: path, payload: payload, headers: headers, verb: verb)
39
39
  end
40
40
 
41
+ def citestcov_request(path:, payload:, headers: {}, verb: "post")
42
+ super(path: path, payload: payload, headers: headers, verb: verb)
43
+
44
+ headers[Ext::Transport::HEADER_EVP_SUBDOMAIN] = Ext::Transport::TEST_COVERAGE_INTAKE_HOST_PREFIX
45
+
46
+ perform_request(@agent_intake_http, path: path, payload: @citestcov_payload, headers: headers, verb: verb)
47
+ end
48
+
41
49
  private
42
50
 
43
51
  def perform_request(http_client, path:, payload:, headers:, verb:)
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "msgpack"
4
+
5
+ require "datadog/core/encoding"
6
+ require "datadog/core/chunker"
7
+
8
+ module Datadog
9
+ module CI
10
+ module Transport
11
+ class EventPlatformTransport
12
+ DEFAULT_MAX_PAYLOAD_SIZE = 5 * 1024 * 1024
13
+
14
+ attr_reader :api,
15
+ :max_payload_size
16
+
17
+ def initialize(api:, max_payload_size: DEFAULT_MAX_PAYLOAD_SIZE)
18
+ @api = api
19
+ @max_payload_size = max_payload_size
20
+ end
21
+
22
+ def send_events(events)
23
+ return [] if events.nil? || events.empty?
24
+
25
+ Datadog.logger.debug { "[#{self.class.name}] Sending #{events.count} events..." }
26
+
27
+ encoded_events = encode_events(events)
28
+ if encoded_events.empty?
29
+ Datadog.logger.debug { "[#{self.class.name}] Empty encoded events list, skipping send" }
30
+ return []
31
+ end
32
+
33
+ responses = []
34
+
35
+ Datadog::Core::Chunker.chunk_by_size(encoded_events, max_payload_size).map do |chunk|
36
+ encoded_payload = pack_events(chunk)
37
+ Datadog.logger.debug do
38
+ "[#{self.class.name}] Send chunk of #{chunk.count} events; payload size #{encoded_payload.size}"
39
+ end
40
+
41
+ response = send_payload(encoded_payload)
42
+
43
+ responses << response
44
+ end
45
+
46
+ responses
47
+ end
48
+
49
+ private
50
+
51
+ def encoder
52
+ Datadog::Core::Encoding::MsgpackEncoder
53
+ end
54
+
55
+ def pack_events(encoded_events)
56
+ packer = MessagePack::Packer.new
57
+
58
+ write_payload_header(packer)
59
+
60
+ packer.write_array_header(encoded_events.count)
61
+ (packer.buffer.to_a + encoded_events).join
62
+ end
63
+
64
+ def event_too_large?(event, encoded_event)
65
+ return false unless encoded_event.size > max_payload_size
66
+
67
+ # This single event is too large, we can't flush it
68
+ Datadog.logger.warn("[#{self.class.name}] Dropping coverage event. Payload too large: '#{event.inspect}'")
69
+ Datadog.logger.warn(encoded_event)
70
+
71
+ true
72
+ end
73
+
74
+ def send_payload(encoded_payload)
75
+ raise NotImplementedError
76
+ end
77
+
78
+ def encode_events(events)
79
+ raise NotImplementedError
80
+ end
81
+
82
+ def write_payload_header(packer)
83
+ raise NotImplementedError
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -4,6 +4,7 @@ require "delegate"
4
4
  require "datadog/core/transport/http/adapters/net"
5
5
  require "datadog/core/transport/http/env"
6
6
  require "datadog/core/transport/request"
7
+ require "socket"
7
8
 
8
9
  require_relative "gzip"
9
10
  require_relative "../ext/transport"
@@ -20,6 +21,8 @@ module Datadog
20
21
  :compress
21
22
 
22
23
  DEFAULT_TIMEOUT = 30
24
+ MAX_RETRIES = 3
25
+ INITIAL_BACKOFF = 1
23
26
 
24
27
  def initialize(host:, timeout: DEFAULT_TIMEOUT, port: nil, ssl: true, compress: false)
25
28
  @host = host
@@ -29,7 +32,7 @@ module Datadog
29
32
  @compress = compress.nil? ? false : compress
30
33
  end
31
34
 
32
- def request(path:, payload:, headers:, verb: "post")
35
+ def request(path:, payload:, headers:, verb: "post", retries: MAX_RETRIES, backoff: INITIAL_BACKOFF)
33
36
  if compress
34
37
  headers[Ext::Transport::HEADER_CONTENT_ENCODING] = Ext::Transport::CONTENT_ENCODING_GZIP
35
38
  payload = Gzip.compress(payload)
@@ -41,9 +44,7 @@ module Datadog
41
44
  end
42
45
 
43
46
  response = ResponseDecorator.new(
44
- adapter.call(
45
- build_env(path: path, payload: payload, headers: headers, verb: verb)
46
- )
47
+ perform_http_call(path: path, payload: payload, headers: headers, verb: verb, retries: retries, backoff: backoff)
47
48
  )
48
49
 
49
50
  Datadog.logger.debug do
@@ -55,6 +56,25 @@ module Datadog
55
56
 
56
57
  private
57
58
 
59
+ def perform_http_call(path:, payload:, headers:, verb:, retries: MAX_RETRIES, backoff: INITIAL_BACKOFF)
60
+ adapter.call(
61
+ build_env(path: path, payload: payload, headers: headers, verb: verb)
62
+ )
63
+ rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, SocketError, Net::HTTPBadResponse => e
64
+ Datadog.logger.debug("Failed to send request with #{e} (#{e.message})")
65
+
66
+ if retries.positive?
67
+ sleep(backoff)
68
+
69
+ perform_http_call(
70
+ path: path, payload: payload, headers: headers, verb: verb, retries: retries - 1, backoff: backoff * 2
71
+ )
72
+ else
73
+ Datadog.logger.error("Failed to send request after #{MAX_RETRIES} retries")
74
+ raise e
75
+ end
76
+ end
77
+
58
78
  def build_env(path:, payload:, headers:, verb:)
59
79
  env = Datadog::Core::Transport::HTTP::Env.new(
60
80
  Datadog::Core::Transport::Request.new
@@ -67,16 +87,33 @@ module Datadog
67
87
  end
68
88
 
69
89
  def adapter
70
- @adapter ||= Datadog::Core::Transport::HTTP::Adapters::Net.new(host, port, timeout: timeout, ssl: ssl)
90
+ settings = AdapterSettings.new(hostname: host, port: port, ssl: ssl, timeout_seconds: timeout)
91
+ @adapter ||= Datadog::Core::Transport::HTTP::Adapters::Net.new(settings)
71
92
  end
72
93
 
73
94
  # this is needed because Datadog::Tracing::Writer is not fully compatiple with Datadog::Core::Transport
74
- # TODO: remove before 1.0 when CI implements its own worker
95
+ # TODO: remove when CI implements its own worker
75
96
  class ResponseDecorator < ::SimpleDelegator
76
97
  def trace_count
77
98
  0
78
99
  end
79
100
  end
101
+
102
+ class AdapterSettings
103
+ attr_reader :hostname, :port, :ssl, :timeout_seconds
104
+
105
+ def initialize(hostname:, port: nil, ssl: true, timeout_seconds: nil)
106
+ @hostname = hostname
107
+ @port = port
108
+ @ssl = ssl
109
+ @timeout_seconds = timeout_seconds
110
+ end
111
+
112
+ def ==(other)
113
+ hostname == other.hostname && port == other.port && ssl == other.ssl &&
114
+ timeout_seconds == other.timeout_seconds
115
+ end
116
+ end
80
117
  end
81
118
  end
82
119
  end
@@ -5,6 +5,7 @@ require "json"
5
5
  require "datadog/core/environment/identity"
6
6
 
7
7
  require_relative "../ext/transport"
8
+ require_relative "../utils/parsing"
8
9
 
9
10
  module Datadog
10
11
  module CI
@@ -28,7 +29,7 @@ module Datadog
28
29
  return cached unless cached.nil?
29
30
 
30
31
  resp = @http_response
31
- return @json = default_payload if resp.nil? || !resp.ok?
32
+ return @json = default_payload if resp.nil? || !ok?
32
33
 
33
34
  begin
34
35
  @json = JSON.parse(resp.payload).dig(*Ext::Transport::DD_API_SETTINGS_RESPONSE_DIG_KEYS) ||
@@ -39,6 +40,10 @@ module Datadog
39
40
  end
40
41
  end
41
42
 
43
+ def require_git?
44
+ Utils::Parsing.convert_to_bool(payload[Ext::Transport::DD_API_SETTINGS_RESPONSE_REQUIRE_GIT_KEY])
45
+ end
46
+
42
47
  private
43
48
 
44
49
  def default_payload
@@ -46,7 +51,7 @@ module Datadog
46
51
  end
47
52
  end
48
53
 
49
- def initialize(api: nil, dd_env: nil)
54
+ def initialize(dd_env:, api: nil)
50
55
  @api = api
51
56
  @dd_env = dd_env
52
57
  end
@@ -81,10 +86,11 @@ module Datadog
81
86
  "sha" => test_session.git_commit_sha,
82
87
  "test_level" => Ext::Test::ITR_TEST_SKIPPING_MODE,
83
88
  "configurations" => {
84
- "os.platform" => test_session.os_platform,
85
- "os.arch" => test_session.os_architecture,
86
- "runtime.name" => test_session.runtime_name,
87
- "runtime.version" => test_session.runtime_version
89
+ Ext::Test::TAG_OS_PLATFORM => test_session.os_platform,
90
+ Ext::Test::TAG_OS_ARCHITECTURE => test_session.os_architecture,
91
+ Ext::Test::TAG_OS_VERSION => test_session.os_version,
92
+ Ext::Test::TAG_RUNTIME_NAME => test_session.runtime_name,
93
+ Ext::Test::TAG_RUNTIME_VERSION => test_session.runtime_version
88
94
  }
89
95
  }
90
96
  }
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "git"
3
+ require_relative "../git/local_repository"
4
4
 
5
5
  module Datadog
6
6
  module CI
7
7
  module Utils
8
8
  module Configuration
9
9
  def self.fetch_service_name(default)
10
- Datadog.configuration.service_without_fallback || Git.repository_name || default
10
+ Datadog.configuration.service_without_fallback || CI::Git::LocalRepository.repository_name || default
11
11
  end
12
12
  end
13
13
  end