datadog-ci 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14828e8c64b8eeb9c04d4ae2563e568b3b920b587f8e97e628896b143219daf0
4
- data.tar.gz: b538d180acd0c18e1d8070adfdcd9f44dad65331048619de40db04cd876dfe01
3
+ metadata.gz: ae9b964bc83f82487cc72d99641bcd08a575c104ec311327d49715d88852d02d
4
+ data.tar.gz: 7f787507a51166a49e95c9b55049fc68f3b1d0952fa49d7974f857b37b10e56f
5
5
  SHA512:
6
- metadata.gz: 60f8fda5f530404027669902b194c7e056af834c882adbc2b7d350eb62f7f75c2d63dae23bf6e98a8bebea0cc8ec34d4f5416fe667d1c30dff1fdb7ebb42854d
7
- data.tar.gz: 1b2ad405dee8a1dd2ce24c3d5738ddcbc751f85d5aa5c29a287b614dcfc0a4764230a3b53e59967247fd0f64711fd81fc3863e49895098e7664aa81a3bf1b8e6
6
+ metadata.gz: 5947c6dd42ab0d629d1e7eecd2f116ea2c28e89b657e47e80eb0b127caf21e25b68e182ba5f3039671491f9335b0dda917091cbd43f18f64c9136e3e38ef1239
7
+ data.tar.gz: 7790297ed83c4bfedc4cfb9c2b9116a540c8f7c3cdc78ccd9b5ece5ecb506be427765851834f8abca4594ff5908ca99c4e7f4cd8ab601fe5c0228cf2c62f8971
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.8.0] - 2024-03-08
4
+
5
+ ### Added
6
+
7
+ * gzip agent payloads support via evp_proxy/v4 ([#123][])
8
+
9
+ ### Changed
10
+
11
+ * Add note to README on using VCR ([#122][])
12
+
13
+ ### Fixed
14
+
15
+ * use framework name as test module name to make test fingerprints stable ([#131][])
16
+
3
17
  ## [0.7.0] - 2024-01-26
4
18
 
5
19
  ### Added
@@ -150,7 +164,8 @@ Currently test suite level visibility is not used by our instrumentation: it wil
150
164
 
151
165
  * Ruby versions < 2.7 no longer supported ([#8][])
152
166
 
153
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v0.7.0...main
167
+ [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v0.8.0...main
168
+ [0.8.0]: https://github.com/DataDog/datadog-ci-rb/compare/v0.7.0...v0.8.0
154
169
  [0.7.0]: https://github.com/DataDog/datadog-ci-rb/compare/v0.6.0...v0.7.0
155
170
  [0.6.0]: https://github.com/DataDog/datadog-ci-rb/compare/v0.5.1...v0.6.0
156
171
  [0.5.1]: https://github.com/DataDog/datadog-ci-rb/compare/v0.5.0...v0.5.1
@@ -213,3 +228,6 @@ Currently test suite level visibility is not used by our instrumentation: it wil
213
228
  [#113]: https://github.com/DataDog/datadog-ci-rb/issues/113
214
229
  [#114]: https://github.com/DataDog/datadog-ci-rb/issues/114
215
230
  [#115]: https://github.com/DataDog/datadog-ci-rb/issues/115
231
+ [#122]: https://github.com/DataDog/datadog-ci-rb/issues/122
232
+ [#123]: https://github.com/DataDog/datadog-ci-rb/issues/123
233
+ [#131]: https://github.com/DataDog/datadog-ci-rb/issues/131
data/README.md CHANGED
@@ -175,7 +175,7 @@ Webmock accordingly.
175
175
  ```ruby
176
176
  # when using agentless mode
177
177
  # note to use the correct datadog site (e.g. datadoghq.eu, etc)
178
- WebMock.disable_net_connect!(:allow => "citestcycle-intake.datadoghq.com")
178
+ WebMock.disable_net_connect!(:allow => /datadoghq.com/)
179
179
 
180
180
  # when using agent
181
181
  WebMock.disable_net_connect!(:allow_localhost => true)
@@ -184,6 +184,25 @@ WebMock.disable_net_connect!(:allow_localhost => true)
184
184
  WebMock.disable_net_connect!(:allow => "localhost:8126")
185
185
  ```
186
186
 
187
+ ### VCR
188
+
189
+ [VCR](https://github.com/vcr/vcr) is another popular testing library for HTTP interactions.
190
+
191
+ It requires additional configuration to correctly work with datadog-ci:
192
+
193
+ ```ruby
194
+ VCR.configure do |config|
195
+ # ... your usual configuration here ...
196
+
197
+ # when using agent
198
+ config.ignore_hosts "127.0.0.1", "localhost"
199
+
200
+ # when using agentless mode
201
+ # note to use the correct datadog site (e.g. datadoghq.eu, etc)
202
+ config.ignore_hosts "citestcycle-intake.datadoghq.com", "api.datadoghq.com"
203
+ end
204
+ ```
205
+
187
206
  ### Disabling startup logs
188
207
 
189
208
  Startup logs produce a report of tracing state when the application is initially configured.
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "datadog/core/configuration/agent_settings_resolver"
4
- require "datadog/core/remote/negotiation"
5
-
6
- require_relative "../ext/transport"
7
3
  require_relative "../ext/settings"
4
+ require_relative "../itr/runner"
8
5
  require_relative "../test_visibility/flush"
9
6
  require_relative "../test_visibility/recorder"
10
7
  require_relative "../test_visibility/null_recorder"
@@ -12,6 +9,7 @@ require_relative "../test_visibility/serializers/factories/test_level"
12
9
  require_relative "../test_visibility/serializers/factories/test_suite_level"
13
10
  require_relative "../test_visibility/transport"
14
11
  require_relative "../transport/api/builder"
12
+ require_relative "../transport/remote_settings_api"
15
13
 
16
14
  module Datadog
17
15
  module CI
@@ -32,18 +30,7 @@ module Datadog
32
30
  end
33
31
 
34
32
  def activate_ci!(settings)
35
- test_visibility_transport = nil
36
- agent_settings = Datadog::Core::Configuration::AgentSettingsResolver.call(settings)
37
-
38
- if settings.ci.agentless_mode_enabled
39
- check_dd_site(settings)
40
- test_visibility_transport = build_agentless_transport(settings)
41
- elsif can_use_evp_proxy?(settings, agent_settings)
42
- test_visibility_transport = build_evp_proxy_transport(settings, agent_settings)
43
- else
44
- settings.ci.force_test_level_visibility = true
45
- end
46
-
33
+ # Configure ddtrace library for CI visibility mode
47
34
  # Deactivate telemetry
48
35
  settings.telemetry.enabled = false
49
36
 
@@ -60,62 +47,77 @@ module Datadog
60
47
  # Choose user defined TraceFlush or default to CI TraceFlush
61
48
  settings.tracing.test_mode.trace_flush = settings.ci.trace_flush || CI::TestVisibility::Flush::Partial.new
62
49
 
50
+ # transport creation
63
51
  writer_options = settings.ci.writer_options
64
- if test_visibility_transport
65
- writer_options[:transport] = test_visibility_transport
52
+ test_visibility_api = build_test_visibility_api(settings)
53
+
54
+ if test_visibility_api
55
+ writer_options[:transport] = Datadog::CI::TestVisibility::Transport.new(
56
+ api: test_visibility_api,
57
+ serializers_factory: serializers_factory(settings),
58
+ dd_env: settings.env
59
+ )
66
60
  writer_options[:shutdown_timeout] = 60
67
61
  writer_options[:buffer_size] = 10_000
68
62
 
69
63
  settings.tracing.test_mode.async = true
64
+ else
65
+ # only legacy APM protocol is supported, so no test suite level visibility
66
+ settings.ci.force_test_level_visibility = true
67
+
68
+ # ITR is not supported with APM protocol
69
+ settings.ci.itr_enabled = false
70
70
  end
71
71
 
72
72
  settings.tracing.test_mode.writer_options = writer_options
73
73
 
74
- @ci_recorder = TestVisibility::Recorder.new(
75
- test_suite_level_visibility_enabled: !settings.ci.force_test_level_visibility
74
+ itr = Datadog::CI::ITR::Runner.new(
75
+ enabled: settings.ci.enabled && settings.ci.itr_enabled
76
76
  )
77
- end
78
77
 
79
- def can_use_evp_proxy?(settings, agent_settings)
80
- Datadog::Core::Remote::Negotiation.new(settings, agent_settings).endpoint?(
81
- Ext::Transport::EVP_PROXY_PATH_PREFIX
78
+ remote_settings_api = Transport::RemoteSettingsApi.new(
79
+ api: test_visibility_api,
80
+ dd_env: settings.env
81
+ )
82
+
83
+ # CI visibility recorder global instance
84
+ @ci_recorder = TestVisibility::Recorder.new(
85
+ test_suite_level_visibility_enabled: !settings.ci.force_test_level_visibility,
86
+ itr: itr,
87
+ remote_settings_api: remote_settings_api
82
88
  )
83
89
  end
84
90
 
85
- def build_agentless_transport(settings)
86
- if settings.api_key.nil?
87
- # agentless mode is requested but no API key is provided -
88
- # we cannot continue and log an error
89
- # Tests are running without CI visibility enabled
91
+ def build_test_visibility_api(settings)
92
+ if settings.ci.agentless_mode_enabled
93
+ check_dd_site(settings)
90
94
 
91
- Datadog.logger.error(
92
- "DATADOG CONFIGURATION - CI VISIBILITY - ATTENTION - " \
93
- "Agentless mode was enabled but DD_API_KEY is not set: CI visibility is disabled. " \
94
- "Please make sure to set valid api key in DD_API_KEY environment variable"
95
- )
95
+ Datadog.logger.debug("CI visibility configured to use agentless transport")
96
96
 
97
- settings.ci.enabled = false
97
+ api = Transport::Api::Builder.build_agentless_api(settings)
98
+ if api.nil?
99
+ Datadog.logger.error do
100
+ "DATADOG CONFIGURATION - CI VISIBILITY - ATTENTION - " \
101
+ "Agentless mode was enabled but DD_API_KEY is not set: CI visibility is disabled. " \
102
+ "Please make sure to set valid api key in DD_API_KEY environment variable"
103
+ end
98
104
 
99
- nil
100
- else
101
- Datadog.logger.debug("CI visibility configured to use agentless transport")
105
+ # Tests are running without CI visibility enabled
106
+ settings.ci.enabled = false
107
+ end
102
108
 
103
- Datadog::CI::TestVisibility::Transport.new(
104
- api: Transport::Api::Builder.build_ci_test_cycle_api(settings),
105
- serializers_factory: serializers_factory(settings),
106
- dd_env: settings.env
107
- )
109
+ else
110
+ Datadog.logger.debug("CI visibility configured to use agent transport via EVP proxy")
111
+
112
+ api = Transport::Api::Builder.build_evp_proxy_api(settings)
113
+ if api.nil?
114
+ Datadog.logger.debug(
115
+ "Old agent version detected, no evp_proxy support. Forcing test level visibility mode"
116
+ )
117
+ end
108
118
  end
109
- end
110
-
111
- def build_evp_proxy_transport(settings, agent_settings)
112
- Datadog.logger.debug("CI visibility configured to use agent transport via EVP proxy")
113
119
 
114
- Datadog::CI::TestVisibility::Transport.new(
115
- api: Transport::Api::Builder.build_evp_proxy_api(agent_settings),
116
- serializers_factory: serializers_factory(settings),
117
- dd_env: settings.env
118
- )
120
+ api
119
121
  end
120
122
 
121
123
  def serializers_factory(settings)
@@ -130,11 +132,11 @@ module Datadog
130
132
  return if settings.site.nil?
131
133
  return if Ext::Settings::DD_SITE_ALLOWLIST.include?(settings.site)
132
134
 
133
- Datadog.logger.warn(
135
+ Datadog.logger.warn do
134
136
  "CI VISIBILITY CONFIGURATION " \
135
137
  "Agentless mode was enabled but DD_SITE is not set to one of the following: #{Ext::Settings::DD_SITE_ALLOWLIST.join(", ")}. " \
136
138
  "Please make sure to set valid site in DD_SITE environment variable"
137
- )
139
+ end
138
140
  end
139
141
  end
140
142
  end
@@ -55,6 +55,12 @@ module Datadog
55
55
  end
56
56
  end
57
57
 
58
+ option :itr_enabled do |o|
59
+ o.type :bool
60
+ o.env CI::Ext::Settings::ENV_ITR_ENABLED
61
+ o.default false
62
+ end
63
+
58
64
  define_method(:instrument) do |integration_name, options = {}, &block|
59
65
  return unless enabled
60
66
 
@@ -34,14 +34,14 @@ module Datadog
34
34
  end
35
35
 
36
36
  def on_test_run_started(event)
37
- test_session = CI.start_test_session(
37
+ CI.start_test_session(
38
38
  tags: {
39
39
  CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
40
40
  CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Cucumber::Integration.version.to_s
41
41
  },
42
42
  service: configuration[:service_name]
43
43
  )
44
- CI.start_test_module(test_session.name) if test_session
44
+ CI.start_test_module(Ext::FRAMEWORK)
45
45
  end
46
46
 
47
47
  def on_test_run_finished(event)
@@ -18,14 +18,14 @@ module Datadog
18
18
 
19
19
  return unless datadog_configuration[:enabled]
20
20
 
21
- test_session = CI.start_test_session(
21
+ CI.start_test_session(
22
22
  tags: {
23
23
  CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
24
24
  CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Minitest::Integration.version.to_s
25
25
  },
26
26
  service: datadog_configuration[:service_name]
27
27
  )
28
- CI.start_test_module(test_session.name) if test_session
28
+ CI.start_test_module(Ext::FRAMEWORK)
29
29
  end
30
30
 
31
31
  private
@@ -25,13 +25,12 @@ module Datadog
25
25
  service: datadog_configuration[:service_name]
26
26
  )
27
27
 
28
- test_module = CI.start_test_module(test_session.name) if test_session
28
+ test_module = CI.start_test_module(Ext::FRAMEWORK)
29
29
 
30
30
  result = super
31
31
  return result unless test_module && test_session
32
32
 
33
33
  if result != 0
34
- # TODO: repeating this twice feels clunky, we need to remove test_module API before GA
35
34
  test_module.failed!
36
35
  test_session.failed!
37
36
  else
@@ -10,6 +10,7 @@ module Datadog
10
10
  ENV_AGENTLESS_URL = "DD_CIVISIBILITY_AGENTLESS_URL"
11
11
  ENV_EXPERIMENTAL_TEST_SUITE_LEVEL_VISIBILITY_ENABLED = "DD_CIVISIBILITY_EXPERIMENTAL_TEST_SUITE_LEVEL_VISIBILITY_ENABLED"
12
12
  ENV_FORCE_TEST_LEVEL_VISIBILITY = "DD_CIVISIBILITY_FORCE_TEST_LEVEL_VISIBILITY"
13
+ ENV_ITR_ENABLED = "DD_CIVISIBILITY_ITR_ENABLED"
13
14
 
14
15
  # Source: https://docs.datadoghq.com/getting_started/site/
15
16
  DD_SITE_ALLOWLIST = [
@@ -23,6 +23,13 @@ module Datadog
23
23
  TAG_CODEOWNERS = "test.codeowners"
24
24
  TAG_PARAMETERS = "test.parameters"
25
25
 
26
+ # ITR tags
27
+ TAG_ITR_TEST_SKIPPING_ENABLED = "test.itr.tests_skipping.enabled"
28
+ TAG_ITR_TEST_SKIPPING_TYPE = "test.itr.tests_skipping.type"
29
+
30
+ # Code coverage tags
31
+ TAG_CODE_COVERAGE_ENABLED = "test.code_coverage.enabled"
32
+
26
33
  # those tags are special and used to correlate tests with the test sessions, suites, and modules
27
34
  # they are transient and not sent to the backend
28
35
  TAG_TEST_SESSION_ID = "_test.session_id"
@@ -43,6 +50,10 @@ module Datadog
43
50
  TAG_SPAN_KIND = "span.kind"
44
51
  SPAN_KIND_TEST = "test"
45
52
 
53
+ # could be either "test" or "suite" depending on whether we skip individual tests or whole suites
54
+ # we use test skipping for Ruby
55
+ ITR_TEST_SKIPPING_MODE = "test"
56
+
46
57
  # test status as recognized by Datadog
47
58
  module Status
48
59
  PASS = "pass"
@@ -12,11 +12,29 @@ module Datadog
12
12
  HEADER_EVP_SUBDOMAIN = "X-Datadog-EVP-Subdomain"
13
13
  HEADER_CONTAINER_ID = "Datadog-Container-ID"
14
14
 
15
- EVP_PROXY_PATH_PREFIX = "/evp_proxy/v2/"
15
+ EVP_PROXY_V2_PATH_PREFIX = "/evp_proxy/v2/"
16
+ EVP_PROXY_V4_PATH_PREFIX = "/evp_proxy/v4/"
17
+ EVP_PROXY_PATH_PREFIXES = [EVP_PROXY_V4_PATH_PREFIX, EVP_PROXY_V2_PATH_PREFIX].freeze
18
+ EVP_PROXY_COMPRESSION_SUPPORTED = {
19
+ EVP_PROXY_V4_PATH_PREFIX => true,
20
+ EVP_PROXY_V2_PATH_PREFIX => false
21
+ }
22
+
16
23
  TEST_VISIBILITY_INTAKE_HOST_PREFIX = "citestcycle-intake"
17
24
  TEST_VISIBILITY_INTAKE_PATH = "/api/v2/citestcycle"
18
25
 
26
+ DD_API_HOST_PREFIX = "api"
27
+ DD_API_SETTINGS_PATH = "/api/v2/libraries/tests/services/setting"
28
+ DD_API_SETTINGS_TYPE = "ci_app_test_service_libraries_settings"
29
+ DD_API_SETTINGS_RESPONSE_DIG_KEYS = %w[data attributes].freeze
30
+ DD_API_SETTINGS_RESPONSE_ITR_ENABLED_KEY = "itr_enabled"
31
+ DD_API_SETTINGS_RESPONSE_CODE_COVERAGE_KEY = "code_coverage"
32
+ DD_API_SETTINGS_RESPONSE_TESTS_SKIPPING_KEY = "tests_skipping"
33
+ DD_API_SETTINGS_RESPONSE_REQUIRE_GIT_KEY = "require_git"
34
+ DD_API_SETTINGS_RESPONSE_DEFAULT = {DD_API_SETTINGS_RESPONSE_ITR_ENABLED_KEY => false}.freeze
35
+
19
36
  CONTENT_TYPE_MESSAGEPACK = "application/msgpack"
37
+ CONTENT_TYPE_JSON = "application/json"
20
38
  CONTENT_ENCODING_GZIP = "gzip"
21
39
  end
22
40
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../ext/test"
4
+ require_relative "../ext/transport"
5
+
6
+ module Datadog
7
+ module CI
8
+ module ITR
9
+ # Intelligent test runner implementation
10
+ # Integrates with backend to provide test impact analysis data and
11
+ # skip tests that are not impacted by the changes
12
+ class Runner
13
+ def initialize(
14
+ enabled: false
15
+ )
16
+ @enabled = enabled
17
+ @test_skipping_enabled = false
18
+ @code_coverage_enabled = false
19
+
20
+ Datadog.logger.debug("ITR Runner initialized with enabled: #{@enabled}")
21
+ end
22
+
23
+ def configure(remote_configuration, test_session)
24
+ Datadog.logger.debug("Configuring ITR Runner with remote configuration: #{remote_configuration}")
25
+
26
+ @enabled = convert_to_bool(
27
+ remote_configuration.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_ITR_ENABLED_KEY, false)
28
+ )
29
+ @test_skipping_enabled = @enabled && convert_to_bool(
30
+ remote_configuration.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_TESTS_SKIPPING_KEY, false)
31
+ )
32
+ @code_coverage_enabled = @enabled && convert_to_bool(
33
+ remote_configuration.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_CODE_COVERAGE_KEY, false)
34
+ )
35
+
36
+ test_session.set_tag(Ext::Test::TAG_ITR_TEST_SKIPPING_ENABLED, @test_skipping_enabled)
37
+ # currently we set this tag when ITR requires collecting code coverage
38
+ # this will change as soon as we implement total code coverage support in this library
39
+ test_session.set_tag(Ext::Test::TAG_CODE_COVERAGE_ENABLED, @code_coverage_enabled)
40
+
41
+ # we skip tests, not suites
42
+ test_session.set_tag(Ext::Test::TAG_ITR_TEST_SKIPPING_TYPE, Ext::Test::ITR_TEST_SKIPPING_MODE)
43
+
44
+ Datadog.logger.debug("Configured ITR Runner with enabled: #{@enabled}, skipping_tests: #{@test_skipping_enabled}, code_coverage: #{@code_coverage_enabled}")
45
+ end
46
+
47
+ def enabled?
48
+ @enabled
49
+ end
50
+
51
+ def skipping_tests?
52
+ @test_skipping_enabled
53
+ end
54
+
55
+ def code_coverage?
56
+ @code_coverage_enabled
57
+ end
58
+
59
+ private
60
+
61
+ def convert_to_bool(value)
62
+ value.to_s == "true"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -121,6 +121,48 @@ module Datadog
121
121
  tracer_span.set_tags(tags)
122
122
  end
123
123
 
124
+ # Returns the git repository URL extracted from the environment.
125
+ # @return [String] the repository URL.
126
+ def git_repository_url
127
+ tracer_span.get_tag(Ext::Git::TAG_REPOSITORY_URL)
128
+ end
129
+
130
+ # Returns the latest commit SHA extracted from the environment.
131
+ # @return [String] the commit SHA of the last commit.
132
+ def git_commit_sha
133
+ tracer_span.get_tag(Ext::Git::TAG_COMMIT_SHA)
134
+ end
135
+
136
+ # Returns the git branch name extracted from the environment.
137
+ # @return [String] the branch.
138
+ def git_branch
139
+ tracer_span.get_tag(Ext::Git::TAG_BRANCH)
140
+ end
141
+
142
+ # Returns the OS architecture extracted from the environment.
143
+ # @return [String] OS arch.
144
+ def os_architecture
145
+ tracer_span.get_tag(Ext::Test::TAG_OS_ARCHITECTURE)
146
+ end
147
+
148
+ # Returns the OS platform extracted from the environment.
149
+ # @return [String] OS platform.
150
+ def os_platform
151
+ tracer_span.get_tag(Ext::Test::TAG_OS_PLATFORM)
152
+ end
153
+
154
+ # Returns the runtime name extracted from the environment.
155
+ # @return [String] runtime name.
156
+ def runtime_name
157
+ tracer_span.get_tag(Ext::Test::TAG_RUNTIME_NAME)
158
+ end
159
+
160
+ # Returns the runtime version extracted from the environment.
161
+ # @return [String] runtime version.
162
+ def runtime_version
163
+ tracer_span.get_tag(Ext::Test::TAG_RUNTIME_VERSION)
164
+ end
165
+
124
166
  def set_environment_runtime_tags
125
167
  tracer_span.set_tag(Ext::Test::TAG_OS_ARCHITECTURE, ::RbConfig::CONFIG["host_cpu"])
126
168
  tracer_span.set_tag(Ext::Test::TAG_OS_PLATFORM, ::RbConfig::CONFIG["host_os"])
@@ -26,6 +26,14 @@ module Datadog
26
26
  get_tag(Ext::Test::TAG_COMMAND)
27
27
  end
28
28
 
29
+ def skipping_tests?
30
+ get_tag(Ext::Test::TAG_ITR_TEST_SKIPPING_ENABLED) == "true"
31
+ end
32
+
33
+ def code_coverage?
34
+ get_tag(Ext::Test::TAG_CODE_COVERAGE_ENABLED) == "true"
35
+ end
36
+
29
37
  # Return the test session tags that could be inherited by sub-spans
30
38
  # @return [Hash] the tags to be inherited by sub-spans.
31
39
  def inheritable_tags
@@ -29,7 +29,7 @@ module Datadog
29
29
  attr_reader :environment_tags, :test_suite_level_visibility_enabled
30
30
 
31
31
  def initialize(
32
- test_suite_level_visibility_enabled: false,
32
+ itr:, remote_settings_api:, test_suite_level_visibility_enabled: false,
33
33
  codeowners: Codeowners::Parser.new(Utils::Git.root).parse
34
34
  )
35
35
  @test_suite_level_visibility_enabled = test_suite_level_visibility_enabled
@@ -37,7 +37,11 @@ module Datadog
37
37
  @environment_tags = Ext::Environment.tags(ENV).freeze
38
38
  @local_context = Context::Local.new
39
39
  @global_context = Context::Global.new
40
+
40
41
  @codeowners = codeowners
42
+
43
+ @itr = itr
44
+ @remote_settings_api = remote_settings_api
41
45
  end
42
46
 
43
47
  def start_test_session(service: nil, tags: {})
@@ -49,7 +53,11 @@ module Datadog
49
53
  )
50
54
  set_session_context(tags, tracer_span)
51
55
 
52
- build_test_session(tracer_span, tags)
56
+ test_session = build_test_session(tracer_span, tags)
57
+
58
+ configure_library(test_session)
59
+
60
+ test_session
53
61
  end
54
62
  end
55
63
 
@@ -175,8 +183,20 @@ module Datadog
175
183
  @global_context.deactivate_test_suite!(test_suite_name)
176
184
  end
177
185
 
186
+ def itr_enabled?
187
+ @itr.enabled?
188
+ end
189
+
178
190
  private
179
191
 
192
+ def configure_library(test_session)
193
+ # this will change when EFD is implemented
194
+ return unless itr_enabled?
195
+
196
+ remote_configuration = @remote_settings_api.fetch_library_settings(test_session)
197
+ @itr.configure(remote_configuration.payload, test_session)
198
+ end
199
+
180
200
  def skip_tracing(block = nil)
181
201
  block.call(nil) if block
182
202
  end
@@ -55,10 +55,6 @@ module Datadog
55
55
 
56
56
  response = send_payload(encoded_payload)
57
57
 
58
- Datadog.logger.debug do
59
- "Received server response: #{response.inspect}"
60
- end
61
-
62
58
  responses << response
63
59
  end
64
60
 
@@ -68,7 +64,7 @@ module Datadog
68
64
  private
69
65
 
70
66
  def send_payload(encoded_payload)
71
- api.request(
67
+ api.citestcycle_request(
72
68
  path: Datadog::CI::Ext::Transport::TEST_VISIBILITY_INTAKE_PATH,
73
69
  payload: encoded_payload
74
70
  )
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../../ext/transport"
5
+
6
+ module Datadog
7
+ module CI
8
+ module Transport
9
+ module Api
10
+ class Agentless < Base
11
+ attr_reader :api_key
12
+
13
+ def initialize(api_key:, citestcycle_url:, api_url:)
14
+ @api_key = api_key
15
+ @citestcycle_http = build_http_client(citestcycle_url, compress: true)
16
+ @api_http = build_http_client(api_url, compress: false)
17
+ end
18
+
19
+ def citestcycle_request(path:, payload:, headers: {}, verb: "post")
20
+ super
21
+
22
+ perform_request(@citestcycle_http, path: path, payload: payload, headers: headers, verb: verb)
23
+ end
24
+
25
+ def api_request(path:, payload:, headers: {}, verb: "post")
26
+ super
27
+
28
+ perform_request(@api_http, path: path, payload: payload, headers: headers, verb: verb)
29
+ end
30
+
31
+ private
32
+
33
+ def perform_request(http_client, path:, payload:, headers:, verb:)
34
+ http_client.request(
35
+ path: path,
36
+ payload: payload,
37
+ headers: headers_with_default(headers),
38
+ verb: verb
39
+ )
40
+ end
41
+
42
+ def build_http_client(url, compress:)
43
+ uri = URI.parse(url)
44
+ raise "Invalid agentless mode URL: #{url}" if uri.host.nil?
45
+
46
+ Datadog::CI::Transport::HTTP.new(
47
+ host: uri.host,
48
+ port: uri.port,
49
+ ssl: uri.scheme == "https" || uri.port == 443,
50
+ compress: compress
51
+ )
52
+ end
53
+
54
+ def default_headers
55
+ headers = super
56
+ headers[Ext::Transport::HEADER_DD_API_KEY] = api_key
57
+ headers
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -7,27 +7,23 @@ module Datadog
7
7
  module Transport
8
8
  module Api
9
9
  class Base
10
- attr_reader :http
10
+ def api_request(path:, payload:, headers: {}, verb: "post")
11
+ headers[Ext::Transport::HEADER_CONTENT_TYPE] ||= Ext::Transport::CONTENT_TYPE_JSON
12
+ end
11
13
 
12
- def initialize(http:)
13
- @http = http
14
+ def citestcycle_request(path:, payload:, headers: {}, verb: "post")
15
+ headers[Ext::Transport::HEADER_CONTENT_TYPE] ||= Ext::Transport::CONTENT_TYPE_MESSAGEPACK
14
16
  end
15
17
 
16
- def request(path:, payload:, verb: "post")
17
- http.request(
18
- path: path,
19
- payload: payload,
20
- verb: verb,
21
- headers: headers
22
- )
18
+ def headers_with_default(headers)
19
+ request_headers = default_headers
20
+ request_headers.merge!(headers)
23
21
  end
24
22
 
25
23
  private
26
24
 
27
- def headers
28
- {
29
- Ext::Transport::HEADER_CONTENT_TYPE => Ext::Transport::CONTENT_TYPE_MESSAGEPACK
30
- }
25
+ def default_headers
26
+ {}
31
27
  end
32
28
  end
33
29
  end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "ci_test_cycle"
3
+ require "datadog/core/configuration/agent_settings_resolver"
4
+ require "datadog/core/remote/negotiation"
5
+
6
+ require_relative "agentless"
4
7
  require_relative "evp_proxy"
5
8
  require_relative "../http"
6
9
  require_relative "../../ext/transport"
@@ -10,34 +13,34 @@ module Datadog
10
13
  module Transport
11
14
  module Api
12
15
  module Builder
13
- def self.build_ci_test_cycle_api(settings)
16
+ def self.build_agentless_api(settings)
17
+ return nil if settings.api_key.nil?
18
+
14
19
  dd_site = settings.site || Ext::Transport::DEFAULT_DD_SITE
15
- url = settings.ci.agentless_url ||
16
- "https://#{Ext::Transport::TEST_VISIBILITY_INTAKE_HOST_PREFIX}.#{dd_site}:443"
17
20
 
18
- uri = URI.parse(url)
19
- raise "Invalid agentless mode URL: #{url}" if uri.host.nil?
21
+ citestcycle_url = settings.ci.agentless_url ||
22
+ "https://#{Ext::Transport::TEST_VISIBILITY_INTAKE_HOST_PREFIX}.#{dd_site}:443"
20
23
 
21
- http = Datadog::CI::Transport::HTTP.new(
22
- host: uri.host,
23
- port: uri.port,
24
- ssl: uri.scheme == "https" || uri.port == 443,
25
- compress: true
26
- )
24
+ api_url = settings.ci.agentless_url ||
25
+ "https://#{Ext::Transport::DD_API_HOST_PREFIX}.#{dd_site}:443"
27
26
 
28
- CiTestCycle.new(api_key: settings.api_key, http: http)
27
+ Agentless.new(api_key: settings.api_key, citestcycle_url: citestcycle_url, api_url: api_url)
29
28
  end
30
29
 
31
- def self.build_evp_proxy_api(agent_settings)
32
- http = Datadog::CI::Transport::HTTP.new(
33
- host: agent_settings.hostname,
34
- port: agent_settings.port,
35
- ssl: agent_settings.ssl,
36
- timeout: agent_settings.timeout_seconds,
37
- compress: false
38
- )
30
+ def self.build_evp_proxy_api(settings)
31
+ agent_settings = Datadog::Core::Configuration::AgentSettingsResolver.call(settings)
32
+ negotiation = Datadog::Core::Remote::Negotiation.new(settings, agent_settings)
33
+
34
+ # temporary, remove this when patch will be accepted in Core to make logging configurable
35
+ negotiation.instance_variable_set(:@logged, {no_config_endpoint: true})
36
+
37
+ evp_proxy_path_prefix = Ext::Transport::EVP_PROXY_PATH_PREFIXES.find do |path_prefix|
38
+ negotiation.endpoint?(path_prefix)
39
+ end
40
+
41
+ return nil if evp_proxy_path_prefix.nil?
39
42
 
40
- EvpProxy.new(http: http)
43
+ EvpProxy.new(agent_settings: agent_settings, path_prefix: evp_proxy_path_prefix)
41
44
  end
42
45
  end
43
46
  end
@@ -10,17 +10,48 @@ module Datadog
10
10
  module Transport
11
11
  module Api
12
12
  class EvpProxy < Base
13
- def request(path:, payload:, verb: "post")
14
- path = "#{Ext::Transport::EVP_PROXY_PATH_PREFIX}#{path.sub(/^\//, "")}"
13
+ def initialize(agent_settings:, path_prefix: Ext::Transport::EVP_PROXY_V2_PATH_PREFIX)
14
+ @agent_intake_http = build_http_client(
15
+ agent_settings,
16
+ compress: Ext::Transport::EVP_PROXY_COMPRESSION_SUPPORTED[path_prefix]
17
+ )
18
+
19
+ @agent_api_http = build_http_client(agent_settings, compress: false)
20
+
21
+ path_prefix = "#{path_prefix}/" unless path_prefix.end_with?("/")
22
+ @path_prefix = path_prefix
23
+ end
24
+
25
+ def citestcycle_request(path:, payload:, headers: {}, verb: "post")
26
+ super
27
+
28
+ headers[Ext::Transport::HEADER_EVP_SUBDOMAIN] = Ext::Transport::TEST_VISIBILITY_INTAKE_HOST_PREFIX
29
+
30
+ perform_request(@agent_intake_http, path: path, payload: payload, headers: headers, verb: verb)
31
+ end
32
+
33
+ def api_request(path:, payload:, headers: {}, verb: "post")
34
+ super
35
+
36
+ headers[Ext::Transport::HEADER_EVP_SUBDOMAIN] = Ext::Transport::DD_API_HOST_PREFIX
15
37
 
16
- super(
17
- path: path,
38
+ perform_request(@agent_api_http, path: path, payload: payload, headers: headers, verb: verb)
39
+ end
40
+
41
+ private
42
+
43
+ def perform_request(http_client, path:, payload:, headers:, verb:)
44
+ http_client.request(
45
+ path: path_with_prefix(path),
18
46
  payload: payload,
47
+ headers: headers_with_default(headers),
19
48
  verb: verb
20
49
  )
21
50
  end
22
51
 
23
- private
52
+ def path_with_prefix(path)
53
+ "#{@path_prefix}#{path.sub(/^\//, "")}"
54
+ end
24
55
 
25
56
  def container_id
26
57
  return @container_id if defined?(@container_id)
@@ -28,15 +59,24 @@ module Datadog
28
59
  @container_id = Datadog::Core::Environment::Container.container_id
29
60
  end
30
61
 
31
- def headers
62
+ def default_headers
32
63
  headers = super
33
- headers[Ext::Transport::HEADER_EVP_SUBDOMAIN] = Ext::Transport::TEST_VISIBILITY_INTAKE_HOST_PREFIX
34
64
 
35
65
  c_id = container_id
36
66
  headers[Ext::Transport::HEADER_CONTAINER_ID] = c_id unless c_id.nil?
37
67
 
38
68
  headers
39
69
  end
70
+
71
+ def build_http_client(agent_settings, compress:)
72
+ Datadog::CI::Transport::HTTP.new(
73
+ host: agent_settings.hostname,
74
+ port: agent_settings.port,
75
+ ssl: agent_settings.ssl,
76
+ timeout: agent_settings.timeout_seconds,
77
+ compress: compress
78
+ )
79
+ end
40
80
  end
41
81
  end
42
82
  end
@@ -40,11 +40,17 @@ module Datadog
40
40
  "compression_enabled=#{compress}; path=#{path}; payload_size=#{payload.size}"
41
41
  end
42
42
 
43
- ResponseDecorator.new(
43
+ response = ResponseDecorator.new(
44
44
  adapter.call(
45
45
  build_env(path: path, payload: payload, headers: headers, verb: verb)
46
46
  )
47
47
  )
48
+
49
+ Datadog.logger.debug do
50
+ "Received server response: #{response.inspect}"
51
+ end
52
+
53
+ response
48
54
  end
49
55
 
50
56
  private
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ require "datadog/core/environment/identity"
6
+
7
+ require_relative "../ext/transport"
8
+
9
+ module Datadog
10
+ module CI
11
+ module Transport
12
+ # Datadog API client
13
+ # Calls settings endpoint to fetch library settings for given service and env
14
+ class RemoteSettingsApi
15
+ class Response
16
+ def initialize(http_response)
17
+ @http_response = http_response
18
+ @json = nil
19
+ end
20
+
21
+ def ok?
22
+ resp = @http_response
23
+ !resp.nil? && resp.ok?
24
+ end
25
+
26
+ def payload
27
+ cached = @json
28
+ return cached unless cached.nil?
29
+
30
+ resp = @http_response
31
+ return @json = default_payload if resp.nil? || !resp.ok?
32
+
33
+ begin
34
+ @json = JSON.parse(resp.payload).dig(*Ext::Transport::DD_API_SETTINGS_RESPONSE_DIG_KEYS) ||
35
+ default_payload
36
+ rescue JSON::ParserError => e
37
+ Datadog.logger.error("Failed to parse settings response payload: #{e}. Payload was: #{resp.payload}")
38
+ @json = default_payload
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def default_payload
45
+ Ext::Transport::DD_API_SETTINGS_RESPONSE_DEFAULT
46
+ end
47
+ end
48
+
49
+ def initialize(api: nil, dd_env: nil)
50
+ @api = api
51
+ @dd_env = dd_env
52
+ end
53
+
54
+ def fetch_library_settings(test_session)
55
+ api = @api
56
+ return Response.new(nil) unless api
57
+
58
+ request_payload = payload(test_session)
59
+ Datadog.logger.debug("Fetching library settings with request: #{request_payload}")
60
+
61
+ http_response = api.api_request(
62
+ path: Ext::Transport::DD_API_SETTINGS_PATH,
63
+ payload: request_payload
64
+ )
65
+
66
+ Response.new(http_response)
67
+ end
68
+
69
+ private
70
+
71
+ def payload(test_session)
72
+ {
73
+ "data" => {
74
+ "id" => Datadog::Core::Environment::Identity.id,
75
+ "type" => Ext::Transport::DD_API_SETTINGS_TYPE,
76
+ "attributes" => {
77
+ "service" => test_session.service,
78
+ "env" => @dd_env,
79
+ "repository_url" => test_session.git_repository_url,
80
+ "branch" => test_session.git_branch,
81
+ "sha" => test_session.git_commit_sha,
82
+ "test_level" => Ext::Test::ITR_TEST_SKIPPING_MODE,
83
+ "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
88
+ }
89
+ }
90
+ }
91
+ }.to_json
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -4,7 +4,7 @@ module Datadog
4
4
  module CI
5
5
  module VERSION
6
6
  MAJOR = "0"
7
- MINOR = "7"
7
+ MINOR = "8"
8
8
  PATCH = "0"
9
9
  PRE = nil
10
10
  BUILD = nil
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datadog-ci
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-26 00:00:00.000000000 Z
11
+ date: 2024-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -98,6 +98,7 @@ files:
98
98
  - lib/datadog/ci/ext/settings.rb
99
99
  - lib/datadog/ci/ext/test.rb
100
100
  - lib/datadog/ci/ext/transport.rb
101
+ - lib/datadog/ci/itr/runner.rb
101
102
  - lib/datadog/ci/span.rb
102
103
  - lib/datadog/ci/test.rb
103
104
  - lib/datadog/ci/test_module.rb
@@ -118,12 +119,13 @@ files:
118
119
  - lib/datadog/ci/test_visibility/serializers/test_v1.rb
119
120
  - lib/datadog/ci/test_visibility/serializers/test_v2.rb
120
121
  - lib/datadog/ci/test_visibility/transport.rb
122
+ - lib/datadog/ci/transport/api/agentless.rb
121
123
  - lib/datadog/ci/transport/api/base.rb
122
124
  - lib/datadog/ci/transport/api/builder.rb
123
- - lib/datadog/ci/transport/api/ci_test_cycle.rb
124
125
  - lib/datadog/ci/transport/api/evp_proxy.rb
125
126
  - lib/datadog/ci/transport/gzip.rb
126
127
  - lib/datadog/ci/transport/http.rb
128
+ - lib/datadog/ci/transport/remote_settings_api.rb
127
129
  - lib/datadog/ci/utils/configuration.rb
128
130
  - lib/datadog/ci/utils/git.rb
129
131
  - lib/datadog/ci/utils/test_run.rb
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base"
4
- require_relative "../../ext/transport"
5
-
6
- module Datadog
7
- module CI
8
- module Transport
9
- module Api
10
- class CiTestCycle < Base
11
- attr_reader :api_key
12
-
13
- def initialize(api_key:, http:)
14
- @api_key = api_key
15
-
16
- super(http: http)
17
- end
18
-
19
- private
20
-
21
- def headers
22
- headers = super
23
- headers[Ext::Transport::HEADER_DD_API_KEY] = api_key
24
- headers
25
- end
26
- end
27
- end
28
- end
29
- end
30
- end