dd-trace 6.13.0 → 6.14.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 (201) hide show
  1. package/LICENSE-3rdparty.csv +3 -1
  2. package/ci/init.js +8 -3
  3. package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
  4. package/ci/test-optimization-validation/ci-remediation.js +6 -6
  5. package/ci/test-optimization-validation/command-blocker.js +7 -5
  6. package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
  7. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  8. package/ci/test-optimization-validation/package-check.js +1 -1
  9. package/ci/test-optimization-validation/report-writer.js +7 -5
  10. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  11. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  12. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  13. package/package.json +13 -10
  14. package/packages/datadog-esbuild/index.js +3 -1
  15. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  16. package/packages/datadog-instrumentations/src/fs.js +24 -20
  17. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  18. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  19. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  20. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  21. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +265 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +251 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +11 -1
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  33. package/packages/datadog-instrumentations/src/jest.js +25 -20
  34. package/packages/datadog-instrumentations/src/knex.js +76 -0
  35. package/packages/datadog-instrumentations/src/mariadb.js +6 -0
  36. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  37. package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
  38. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  39. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  40. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  41. package/packages/datadog-instrumentations/src/net.js +14 -17
  42. package/packages/datadog-instrumentations/src/next.js +212 -62
  43. package/packages/datadog-instrumentations/src/openai.js +42 -40
  44. package/packages/datadog-instrumentations/src/pg.js +15 -0
  45. package/packages/datadog-instrumentations/src/playwright.js +109 -13
  46. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  47. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  48. package/packages/datadog-instrumentations/src/undici.js +53 -4
  49. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +37 -3
  50. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  51. package/packages/datadog-instrumentations/src/vitest-worker.js +71 -14
  52. package/packages/datadog-instrumentations/src/webdriverio.js +965 -6
  53. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  54. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  55. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  56. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  57. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  58. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  59. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  60. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  61. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  62. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  63. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  64. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  65. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  66. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  67. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  68. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  69. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  70. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  71. package/packages/datadog-plugin-jest/src/index.js +0 -6
  72. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  73. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  74. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  75. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  76. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  77. package/packages/datadog-plugin-next/src/index.js +63 -29
  78. package/packages/datadog-plugin-playwright/src/index.js +167 -27
  79. package/packages/datadog-plugin-undici/src/index.js +27 -41
  80. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  81. package/packages/dd-trace/src/aiguard/evaluation.js +2 -1
  82. package/packages/dd-trace/src/aiguard/messages/anthropic.js +8 -1
  83. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  84. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  85. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  86. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  87. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  88. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  89. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  90. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  91. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
  92. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  93. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  94. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  95. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  96. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  97. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +5 -173
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  100. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  101. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  102. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +394 -225
  103. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  104. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  105. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  106. package/packages/dd-trace/src/ci-visibility/final-flush.js +2 -1
  107. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  108. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  109. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  110. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
  111. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  112. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  114. package/packages/dd-trace/src/config/index.js +73 -13
  115. package/packages/dd-trace/src/config/parsers.js +11 -1
  116. package/packages/dd-trace/src/config/stable.js +3 -3
  117. package/packages/dd-trace/src/config/supported-configurations.json +20 -2
  118. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  119. package/packages/dd-trace/src/debugger/config.js +12 -1
  120. package/packages/dd-trace/src/debugger/constants.js +1 -0
  121. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  122. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
  123. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -40
  124. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  125. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  126. package/packages/dd-trace/src/debugger/index.js +23 -3
  127. package/packages/dd-trace/src/dogstatsd.js +510 -76
  128. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  129. package/packages/dd-trace/src/encode/0.4.js +5 -5
  130. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  131. package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
  132. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  133. package/packages/dd-trace/src/exporters/agent/writer.js +24 -7
  134. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  135. package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
  136. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  137. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  138. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  139. package/packages/dd-trace/src/exporters/common/request.js +31 -11
  140. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  141. package/packages/dd-trace/src/exporters/common/writer.js +9 -7
  142. package/packages/dd-trace/src/flush.js +6 -3
  143. package/packages/dd-trace/src/id.js +14 -0
  144. package/packages/dd-trace/src/index.js +5 -2
  145. package/packages/dd-trace/src/llmobs/experiments/dataset.js +2 -1
  146. package/packages/dd-trace/src/llmobs/index.js +32 -32
  147. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  148. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  149. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  150. package/packages/dd-trace/src/llmobs/util.js +0 -33
  151. package/packages/dd-trace/src/llmobs/writers/base.js +49 -32
  152. package/packages/dd-trace/src/log/index.js +7 -1
  153. package/packages/dd-trace/src/{ci-visibility/log-submission → log-submission}/log-submission-plugin.js +48 -19
  154. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  155. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  156. package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -24
  157. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  158. package/packages/dd-trace/src/opentelemetry/metrics/index.js +15 -22
  159. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +14 -4
  160. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +10 -0
  161. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  162. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  163. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  164. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  165. package/packages/dd-trace/src/opentracing/propagation/text_map.js +93 -18
  166. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  167. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  168. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  169. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  170. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  171. package/packages/dd-trace/src/plugins/util/test.js +83 -20
  172. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  173. package/packages/dd-trace/src/plugins/util/web.js +4 -9
  174. package/packages/dd-trace/src/profiling/config.js +1 -0
  175. package/packages/dd-trace/src/profiling/oom.js +7 -2
  176. package/packages/dd-trace/src/profiling/profiler.js +23 -13
  177. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  178. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  179. package/packages/dd-trace/src/proxy.js +75 -5
  180. package/packages/dd-trace/src/remote_config/index.js +319 -101
  181. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  182. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  183. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -1
  184. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -1
  185. package/packages/dd-trace/src/sampling_rule.js +36 -9
  186. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +4 -8
  187. package/packages/dd-trace/src/serverless/vercel.js +53 -20
  188. package/packages/dd-trace/src/serverless.js +10 -3
  189. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  190. package/packages/dd-trace/src/span_stats.js +24 -28
  191. package/packages/dd-trace/src/startup-log.js +3 -2
  192. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  193. package/packages/dd-trace/src/telemetry/send-data.js +18 -16
  194. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  195. package/packages/dd-trace/src/tracer.js +4 -5
  196. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  197. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  198. package/vendor/dist/protobufjs/index.js +1 -1
  199. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  200. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  201. package/packages/dd-trace/src/encode/agentless-json.js +0 -210
@@ -1,8 +1,13 @@
1
1
  'use strict'
2
2
 
3
+ const { hostname: getHostname } = require('node:os')
4
+
5
+ const { channel } = require('dc-polyfill')
6
+
3
7
  const uuid = require('../../../../vendor/dist/crypto-randomuuid')
4
8
  const tracerVersion = require('../../../../package.json').version
5
9
  const request = require('../exporters/common/request')
10
+ const { createSiteUrl } = require('../exporters/common/url')
6
11
  const log = require('../log')
7
12
  const { getExtraServices } = require('../service-naming/extra-services')
8
13
  const getGitMetadata = require('../git_metadata')
@@ -10,17 +15,39 @@ const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
10
15
  const tagger = require('../tagger')
11
16
  const processTags = require('../process-tags')
12
17
  const Scheduler = require('./scheduler')
18
+ const capabilities = require('./capabilities')
13
19
  const { UNACKNOWLEDGED, ACKNOWLEDGED, ERROR } = require('./apply_states')
14
20
 
15
- const clientId = uuid()
21
+ /**
22
+ * @typedef {object} RemoteConfigChange
23
+ * @property {'add' | 'update' | 'remove'} kind
24
+ * @property {string} path
25
+ * @property {string} product
26
+ * @property {string} configId
27
+ * @property {number} version
28
+ * @property {string} [contents]
29
+ * @property {number} [length]
30
+ * @property {Record<string, string>} [hashes]
31
+ */
32
+
33
+ let clientId = uuid()
34
+ /** @type {{ id: string, client_tracer: { runtime_id: string, tags: string[] } } | undefined} */
35
+ let client
36
+
37
+ channel('datadog:identity:update').subscribe(refreshIdentity)
16
38
 
17
39
  const DEFAULT_CAPABILITY = Buffer.alloc(1).toString('base64') // 0x00
40
+ const AGENTLESS_REQUEST_TIMEOUT_MS = 5000
41
+ const capabilityEntries = Object.entries(capabilities)
18
42
 
19
43
  const kSupportsAckCallback = Symbol('kSupportsAckCallback')
20
44
 
21
45
  // There MUST NOT exist separate instances of RC clients in a tracer making separate ClientGetConfigsRequest
22
46
  // with their own separated Client.ClientState.
23
47
  class RemoteConfig {
48
+ #agentless
49
+ /** @type {import('@datadog/libdatadog/remote-config').RemoteConfigFetcher | undefined} */
50
+ #fetcher
24
51
  #handlers = new Map()
25
52
  #products = new Set()
26
53
  #batchHandlers = new Map()
@@ -31,6 +58,7 @@ class RemoteConfig {
31
58
  constructor (config) {
32
59
  const pollInterval = Math.floor(config.remoteConfig.pollInterval * 1000)
33
60
 
61
+ this.#agentless = config.DD_AGENTLESS_ENABLED
34
62
  this.url = config.url
35
63
 
36
64
  tagger.add(config.tags, {
@@ -38,13 +66,7 @@ class RemoteConfig {
38
66
  })
39
67
 
40
68
  const { commitSHA, repositoryUrl } = getGitMetadata(config)
41
- const tags = repositoryUrl
42
- ? {
43
- ...config.tags,
44
- [GIT_REPOSITORY_URL]: repositoryUrl,
45
- [GIT_COMMIT_SHA]: commitSHA,
46
- }
47
- : config.tags
69
+ const tracerTags = getTagsString(config, repositoryUrl, commitSHA)
48
70
 
49
71
  const appliedConfigs = this.appliedConfigs = new Map()
50
72
 
@@ -84,13 +106,43 @@ class RemoteConfig {
84
106
  env: config.env,
85
107
  app_version: config.version,
86
108
  extra_services: /** @type {string[]} */ ([]),
87
- tags: Object.entries(tags).map((pair) => pair.join(':')),
109
+ tags: tracerTags,
88
110
  [processTags.REMOTE_CONFIG_FIELD_NAME]: processTags.tagsArray,
89
111
  },
90
112
  capabilities: DEFAULT_CAPABILITY, // updated by `updateCapabilities()`
91
113
  },
92
114
  cached_target_files: /** @type {RcCachedTargetFile[]} */ ([]), // updated by `parseConfig()`
93
115
  }
116
+
117
+ client = this.state.client
118
+
119
+ if (this.#agentless) {
120
+ if (config.DD_API_KEY === undefined) {
121
+ log.error('[RC] DD_API_KEY is required for agentless Remote Config; Remote Config is disabled')
122
+ return
123
+ }
124
+ const siteUrl = createSiteUrl(config.site)
125
+ if (siteUrl === undefined) {
126
+ log.error('[RC] Invalid DD_SITE for agentless Remote Config: %s; Remote Config is disabled', config.site)
127
+ return
128
+ }
129
+
130
+ this.#fetcher = createAgentlessFetcher({
131
+ clientId,
132
+ runtimeId: config.tags['runtime-id'],
133
+ service: config.service,
134
+ env: config.env ?? '',
135
+ appVersion: config.version ?? '',
136
+ tags: tracerTags,
137
+ processTags: processTags.tagsArray,
138
+ language: 'node',
139
+ tracerVersion,
140
+ url: siteUrl.origin,
141
+ timeoutMs: AGENTLESS_REQUEST_TIMEOUT_MS,
142
+ apiKey: config.DD_API_KEY,
143
+ hostname: getHostname(),
144
+ })
145
+ }
94
146
  }
95
147
 
96
148
  /**
@@ -99,20 +151,15 @@ class RemoteConfig {
99
151
  */
100
152
  updateCapabilities (mask, value) {
101
153
  const hex = Buffer.from(this.state.client.capabilities, 'base64').toString('hex')
154
+ let capabilities = BigInt(`0x${hex}`)
102
155
 
103
- let num = BigInt(`0x${hex}`)
104
-
105
- if (value) {
106
- num |= mask
107
- } else {
108
- num &= ~mask
109
- }
110
-
111
- let str = num.toString(16)
156
+ capabilities = value ? capabilities | mask : capabilities & ~mask
157
+ let str = capabilities.toString(16)
112
158
 
113
159
  if (str.length % 2) str = `0${str}`
114
160
 
115
161
  this.state.client.capabilities = Buffer.from(str, 'hex').toString('base64')
162
+ this.#updateProductCapabilities()
116
163
  }
117
164
 
118
165
  /**
@@ -155,7 +202,7 @@ class RemoteConfig {
155
202
  this.#products.add(product)
156
203
  }
157
204
  this.updateProducts()
158
- if (!hadProducts && this.#products.size > 0) {
205
+ if ((!this.#agentless || this.#fetcher) && !hadProducts && this.#products.size > 0) {
159
206
  this.scheduler.start()
160
207
  }
161
208
  }
@@ -181,6 +228,25 @@ class RemoteConfig {
181
228
 
182
229
  updateProducts () {
183
230
  this.state.client.products = [...this.#products]
231
+ this.#updateProductCapabilities()
232
+ }
233
+
234
+ /**
235
+ * Updates the native client with the current products and capabilities.
236
+ *
237
+ * @returns {void}
238
+ */
239
+ #updateProductCapabilities () {
240
+ const fetcher = this.#fetcher
241
+ if (fetcher === undefined) return
242
+
243
+ const unrecognizedNames = fetcher.setProductCapabilities(
244
+ this.state.client.products,
245
+ decodeCapabilities(this.state.client.capabilities)
246
+ )
247
+ if (unrecognizedNames.length !== 0) {
248
+ log.error('[RC] Unrecognized remote config products or capabilities: %s', unrecognizedNames.join(', '))
249
+ }
184
250
  }
185
251
 
186
252
  /**
@@ -214,6 +280,11 @@ class RemoteConfig {
214
280
  }
215
281
 
216
282
  poll (cb) {
283
+ if (this.#agentless) {
284
+ this.#pollAgentless(cb)
285
+ return
286
+ }
287
+
217
288
  const options = {
218
289
  url: this.url,
219
290
  method: 'POST',
@@ -253,6 +324,111 @@ class RemoteConfig {
253
324
  })
254
325
  }
255
326
 
327
+ /**
328
+ * @param {() => void} cb
329
+ */
330
+ #pollAgentless (cb) {
331
+ const fetcher = /** @type {import('@datadog/libdatadog/remote-config').RemoteConfigFetcher} */ (this.#fetcher)
332
+
333
+ fetcher.setExtraServices(getExtraServices())
334
+
335
+ fetcher.fetchChanges().then(
336
+ (changes) => {
337
+ if (changes.length !== 0) this.#applyChanges(changes)
338
+ cb()
339
+ },
340
+ (error) => {
341
+ log.errorWithoutTelemetry('[RC] Error in request', error)
342
+ cb()
343
+ }
344
+ )
345
+ }
346
+
347
+ /**
348
+ * @param {RemoteConfigChange[]} changes
349
+ */
350
+ #applyChanges (changes) {
351
+ const toUnapply = /** @type {RcConfigState[]} */ ([])
352
+ const toApply = /** @type {RcConfigState[]} */ ([])
353
+ const toModify = /** @type {RcConfigState[]} */ ([])
354
+ const transactionByPath = new Map()
355
+ const transactionOutcomes = new Map()
356
+
357
+ for (const change of changes) {
358
+ const { path } = change
359
+ if (transactionByPath.has(path)) continue
360
+
361
+ const current = this.appliedConfigs.get(path)
362
+ if (change.kind === 'remove') {
363
+ if (current === undefined) continue
364
+ toUnapply.push(current)
365
+ transactionByPath.set(path, current)
366
+ continue
367
+ }
368
+
369
+ let file
370
+ try {
371
+ file = change.contents === '' ? null : JSON.parse(change.contents)
372
+ } catch (error) {
373
+ if (this.#fetcher === undefined) throw error
374
+
375
+ log.error('[RC] Could not parse the config file at path %s', path, error)
376
+ this.#fetcher.setConfigState(path, ERROR, error.toString())
377
+ continue
378
+ }
379
+
380
+ const config = /** @type {RcConfigState} */ ({
381
+ path,
382
+ product: change.product,
383
+ id: change.configId,
384
+ version: change.version,
385
+ apply_state: UNACKNOWLEDGED,
386
+ apply_error: '',
387
+ file,
388
+ })
389
+ if (change.hashes !== undefined) {
390
+ config.length = change.length
391
+ config.hashes = change.hashes
392
+ }
393
+ transactionByPath.set(path, config)
394
+
395
+ if (change.kind === 'update' && current !== undefined) {
396
+ toModify.push(config)
397
+ } else {
398
+ toApply.push(config)
399
+ }
400
+ }
401
+
402
+ if (transactionByPath.size === 0) return
403
+
404
+ const transaction = createUpdateTransaction(
405
+ { toUnapply, toApply, toModify },
406
+ transactionOutcomes
407
+ )
408
+
409
+ if (this.#batchHandlers.size) {
410
+ for (const [handler, products] of this.#batchHandlers) {
411
+ const transactionView = filterTransactionByProducts(transaction, products)
412
+ if (transactionView.toUnapply.length || transactionView.toApply.length || transactionView.toModify.length) {
413
+ handler(transactionView)
414
+ }
415
+ }
416
+ }
417
+
418
+ for (const [path, outcome] of transactionOutcomes) {
419
+ const item = transactionByPath.get(path)
420
+ if (item !== undefined) {
421
+ item.apply_state = outcome.state
422
+ item.apply_error = outcome.error
423
+ this.#fetcher?.setConfigState(path, outcome.state, outcome.error)
424
+ }
425
+ }
426
+
427
+ this.dispatch(toUnapply, 'unapply', transactionOutcomes)
428
+ this.dispatch(toApply, 'apply', transactionOutcomes)
429
+ this.dispatch(toModify, 'modify', transactionOutcomes)
430
+ }
431
+
256
432
  // `client_configs` is the list of config paths to have applied
257
433
  // `targets` is the signed index with metadata for config files
258
434
  // `target_files` is the list of config files containing the actual config data
@@ -261,17 +437,17 @@ class RemoteConfig {
261
437
  targets,
262
438
  target_files: targetFiles = [],
263
439
  }) {
264
- const toUnapply = /** @type {RcConfigState[]} */ ([])
265
- const toApply = /** @type {RcConfigState[]} */ ([])
266
- const toModify = /** @type {RcConfigState[]} */ ([])
267
- const transactionByPath = new Map()
268
- const transactionHandledPaths = new Set()
269
- const transactionOutcomes = new Map()
440
+ const changes = /** @type {RemoteConfigChange[]} */ ([])
270
441
 
271
442
  for (const appliedConfig of this.appliedConfigs.values()) {
272
443
  if (!clientConfigs.includes(appliedConfig.path)) {
273
- toUnapply.push(appliedConfig)
274
- transactionByPath.set(appliedConfig.path, appliedConfig)
444
+ changes.push({
445
+ kind: 'remove',
446
+ path: appliedConfig.path,
447
+ product: appliedConfig.product,
448
+ configId: appliedConfig.id,
449
+ version: appliedConfig.version,
450
+ })
275
451
  }
276
452
  }
277
453
 
@@ -284,15 +460,7 @@ class RemoteConfig {
284
460
 
285
461
  const current = this.appliedConfigs.get(path)
286
462
 
287
- const newConf = /** @type {RcConfigState} */ ({})
288
-
289
- if (current) {
290
- if (current.hashes.sha256 === meta.hashes.sha256) continue
291
-
292
- toModify.push(newConf)
293
- } else {
294
- toApply.push(newConf)
295
- }
463
+ if (current?.hashes.sha256 === meta.hashes.sha256) continue
296
464
 
297
465
  const file = targetFiles.find(file => file.path === path)
298
466
  if (!file) throw new Error(`Unable to find file for path ${path}`)
@@ -305,45 +473,21 @@ class RemoteConfig {
305
473
 
306
474
  const { product, id } = parseConfigPath(path)
307
475
 
308
- Object.assign(newConf, {
476
+ changes.push({
477
+ kind: current === undefined ? 'add' : 'update',
309
478
  path,
310
479
  product,
311
- id,
480
+ configId: id,
312
481
  version: meta.custom.v,
313
- apply_state: UNACKNOWLEDGED,
314
- apply_error: '',
482
+ contents: Buffer.from(file.raw, 'base64').toString(),
315
483
  length: meta.length,
316
484
  hashes: meta.hashes,
317
- file: fromBase64JSON(file.raw),
318
485
  })
319
- transactionByPath.set(path, newConf)
320
486
  }
321
-
322
- this.state.client.state.targets_version = targets.signed.version
323
- this.state.client.state.backend_client_state = targets.signed.custom.opaque_backend_state
324
487
  }
325
488
 
326
- if (toUnapply.length || toApply.length || toModify.length) {
327
- const transaction = createUpdateTransaction(
328
- { toUnapply, toApply, toModify },
329
- transactionHandledPaths,
330
- transactionOutcomes
331
- )
332
-
333
- if (this.#batchHandlers.size) {
334
- for (const [handler, products] of this.#batchHandlers) {
335
- const transactionView = filterTransactionByProducts(transaction, products)
336
- if (transactionView.toUnapply.length || transactionView.toApply.length || transactionView.toModify.length) {
337
- handler(transactionView)
338
- }
339
- }
340
- }
341
-
342
- applyOutcomes(transactionByPath, transactionOutcomes)
343
-
344
- this.dispatch(toUnapply, 'unapply', transactionHandledPaths)
345
- this.dispatch(toApply, 'apply', transactionHandledPaths)
346
- this.dispatch(toModify, 'modify', transactionHandledPaths)
489
+ if (changes.length !== 0) {
490
+ this.#applyChanges(changes)
347
491
 
348
492
  this.state.cached_target_files = /** @type {RcCachedTargetFile[]} */ ([])
349
493
 
@@ -359,6 +503,11 @@ class RemoteConfig {
359
503
  })
360
504
  }
361
505
  }
506
+
507
+ if (targets) {
508
+ this.state.client.state.targets_version = targets.signed.version
509
+ this.state.client.state.backend_client_state = targets.signed.custom.opaque_backend_state
510
+ }
362
511
  }
363
512
 
364
513
  /**
@@ -367,22 +516,41 @@ class RemoteConfig {
367
516
  *
368
517
  * @param {RcConfigState[]} list
369
518
  * @param {'apply' | 'modify' | 'unapply'} action
370
- * @param {Set<string>} handledPaths
519
+ * @param {Map<string, {state: number, error: string}>} outcomes
371
520
  */
372
- dispatch (list, action, handledPaths) {
521
+ dispatch (list, action, outcomes) {
373
522
  for (const item of list) {
374
- if (!handledPaths.has(item.path)) {
523
+ if (action !== 'unapply') {
524
+ this.appliedConfigs.set(item.path, item)
525
+ }
526
+
527
+ if (!outcomes.has(item.path)) {
375
528
  this.#callHandlerFor(action, item)
376
529
  }
377
530
 
378
531
  if (action === 'unapply') {
379
532
  this.appliedConfigs.delete(item.path)
380
- } else {
381
- this.appliedConfigs.set(item.path, item)
533
+ } else if (item.apply_state === UNACKNOWLEDGED) {
534
+ this.#fetcher?.setConfigState(item.path, UNACKNOWLEDGED, '')
382
535
  }
383
536
  }
384
537
  }
385
538
 
539
+ /**
540
+ * @param {RcConfigState} item
541
+ * @param {'apply' | 'modify' | 'unapply'} action
542
+ * @param {number} applyState
543
+ * @param {string} applyError
544
+ */
545
+ #setApplyState (item, action, applyState, applyError) {
546
+ const current = this.appliedConfigs.get(item.path)
547
+ if (current !== item && (action !== 'unapply' || current !== undefined)) return
548
+
549
+ item.apply_state = applyState
550
+ item.apply_error = applyError
551
+ this.#fetcher?.setConfigState(item.path, applyState, applyError)
552
+ }
553
+
386
554
  /**
387
555
  * @param {'apply' | 'modify' | 'unapply'} action
388
556
  * @param {RcConfigState} item
@@ -399,12 +567,11 @@ class RemoteConfig {
399
567
  if (supportsAckCallback(handler)) {
400
568
  // If the handler accepts an `ack` callback, expect that to be called and set `apply_state` accordingly
401
569
  // TODO: do we want to pass old and new config ?
402
- handler(action, item.file, item.id, (err) => {
403
- if (err) {
404
- item.apply_state = ERROR
405
- item.apply_error = err.toString()
570
+ handler(action, item.file, item.id, (error) => {
571
+ if (error) {
572
+ this.#setApplyState(item, action, ERROR, error.toString())
406
573
  } else if (item.apply_state !== ERROR) {
407
- item.apply_state = ACKNOWLEDGED
574
+ this.#setApplyState(item, action, ACKNOWLEDGED, '')
408
575
  }
409
576
  })
410
577
  } else {
@@ -414,19 +581,15 @@ class RemoteConfig {
414
581
  const result = handler(action, item.file, item.id)
415
582
  if (result instanceof Promise) {
416
583
  result.then(
417
- () => { item.apply_state = ACKNOWLEDGED },
418
- (err) => {
419
- item.apply_state = ERROR
420
- item.apply_error = err.toString()
421
- }
584
+ () => this.#setApplyState(item, action, ACKNOWLEDGED, ''),
585
+ (error) => this.#setApplyState(item, action, ERROR, error.toString())
422
586
  )
423
587
  } else {
424
- item.apply_state = ACKNOWLEDGED
588
+ this.#setApplyState(item, action, ACKNOWLEDGED, '')
425
589
  }
426
590
  }
427
- } catch (err) {
428
- item.apply_state = ERROR
429
- item.apply_error = err.toString()
591
+ } catch (error) {
592
+ this.#setApplyState(item, action, ERROR, error.toString())
430
593
  }
431
594
  }
432
595
  }
@@ -443,8 +606,8 @@ class RemoteConfig {
443
606
  * @property {unknown} file
444
607
  * @property {number} apply_state
445
608
  * @property {string} apply_error
446
- * @property {number} length
447
- * @property {Record<string, string>} hashes
609
+ * @property {number} [length]
610
+ * @property {Record<string, string>} [hashes]
448
611
  */
449
612
 
450
613
  /**
@@ -472,22 +635,19 @@ class RemoteConfig {
472
635
  * Create an immutable "view" of the batch changes and attach explicit outcome reporting.
473
636
  *
474
637
  * @param {{toUnapply: RcConfigState[], toApply: RcConfigState[], toModify: RcConfigState[]}} changes
475
- * @param {Set<string>} handledPaths
476
638
  * @param {Map<string, {state: number, error: string}>} outcomes
477
639
  * @returns {RcBatchUpdateTransaction}
478
640
  */
479
- function createUpdateTransaction ({ toUnapply, toApply, toModify }, handledPaths, outcomes) {
641
+ function createUpdateTransaction ({ toUnapply, toApply, toModify }, outcomes) {
480
642
  return {
481
643
  toUnapply,
482
644
  toApply,
483
645
  toModify,
484
646
  ack (path) {
485
647
  outcomes.set(path, { state: ACKNOWLEDGED, error: '' })
486
- handledPaths.add(path)
487
648
  },
488
649
  error (path, err) {
489
650
  outcomes.set(path, { state: ERROR, error: err ? err.toString() : 'Error' })
490
- handledPaths.add(path)
491
651
  },
492
652
  }
493
653
  }
@@ -526,16 +686,6 @@ function filterTransactionByProducts (transaction, products) {
526
686
  }
527
687
  }
528
688
 
529
- function applyOutcomes (byPath, outcomes) {
530
- for (const [path, outcome] of outcomes) {
531
- const item = byPath.get(path)
532
- if (item) {
533
- item.apply_state = outcome.state
534
- item.apply_error = outcome.error
535
- }
536
- }
537
- }
538
-
539
689
  function fromBase64JSON (str) {
540
690
  if (!str) return null
541
691
 
@@ -575,4 +725,72 @@ function supportsAckCallback (handler) {
575
725
  return result
576
726
  }
577
727
 
728
+ /**
729
+ * @param {import('@datadog/libdatadog/remote-config').RemoteConfigFetcherOptions} options
730
+ * @returns {import('@datadog/libdatadog/remote-config').RemoteConfigFetcher}
731
+ */
732
+ function createAgentlessFetcher (options) {
733
+ const { RemoteConfigFetcher, setStorage } = require('@datadog/libdatadog/remote-config')
734
+ const { storage } = require('../../../datadog-core')
735
+ const legacyStorage = storage('legacy')
736
+
737
+ /** @param {() => void} callback */
738
+ function runWithoutTracing (callback) {
739
+ legacyStorage.run({ noop: true }, callback)
740
+ }
741
+
742
+ setStorage(runWithoutTracing)
743
+ return new RemoteConfigFetcher(options)
744
+ }
745
+
746
+ /**
747
+ * @param {string} encodedCapabilities
748
+ * @returns {string[]}
749
+ */
750
+ function decodeCapabilities (encodedCapabilities) {
751
+ const hex = Buffer.from(encodedCapabilities, 'base64').toString('hex')
752
+ const mask = BigInt(`0x${hex}`)
753
+ const names = []
754
+
755
+ for (const [name, capability] of capabilityEntries) {
756
+ if ((mask & capability) !== 0n) names.push(name)
757
+ }
758
+
759
+ return names
760
+ }
761
+
762
+ /**
763
+ * @param {import('../config/config-base')} config
764
+ * @param {string} repositoryUrl
765
+ * @param {string} commitSHA
766
+ * @returns {string[]}
767
+ */
768
+ function getTagsString (config, repositoryUrl, commitSHA) {
769
+ const tags = repositoryUrl
770
+ ? {
771
+ ...config.tags,
772
+ [GIT_REPOSITORY_URL]: repositoryUrl,
773
+ [GIT_COMMIT_SHA]: commitSHA,
774
+ }
775
+ : config.tags
776
+ return Object.entries(tags).map((pair) => pair.join(':'))
777
+ }
778
+
779
+ /**
780
+ * Regenerates the RC client ID and rebuilds the RC tag list, so subsequent RC polls report the
781
+ * clone's identity. No-ops on the tags before the first `RemoteConfig` is constructed.
782
+ *
783
+ * @param {import('../config/config-base')} config
784
+ */
785
+ function refreshIdentity (config) {
786
+ clientId = uuid()
787
+ if (client !== undefined) {
788
+ config.tags['_dd.rc.client_id'] = clientId
789
+ client.id = clientId
790
+ client.client_tracer.runtime_id = config.tags['runtime-id']
791
+ const { commitSHA, repositoryUrl } = getGitMetadata(config)
792
+ client.client_tracer.tags = getTagsString(config, repositoryUrl, commitSHA)
793
+ }
794
+ }
795
+
578
796
  module.exports = RemoteConfig
@@ -1,29 +1,54 @@
1
1
  'use strict'
2
2
 
3
3
  class Scheduler {
4
- _timer = null
5
-
4
+ #active = false
5
+ #callback
6
+ #generation = 0
7
+ #inFlight = false
8
+ #interval
9
+ #timer
10
+
11
+ /**
12
+ * @param {(done: () => void) => void} callback
13
+ * @param {number} interval
14
+ */
6
15
  constructor (callback, interval) {
7
- this._callback = callback
8
- this._interval = interval
16
+ this.#callback = callback
17
+ this.#interval = interval
9
18
  }
10
19
 
11
20
  start () {
12
- if (this._timer) return
21
+ if (this.#active) return
13
22
 
14
- this.runAfterDelay(0)
23
+ this.#active = true
24
+ this.#generation++
25
+ if (!this.#inFlight) this.#runAfterDelay(0)
15
26
  }
16
27
 
17
- runAfterDelay (interval = this._interval) {
18
- this._timer = setTimeout(this._callback, interval, () => this.runAfterDelay())
19
-
20
- this._timer.unref?.()
28
+ /**
29
+ * @param {number} interval
30
+ */
31
+ #runAfterDelay (interval) {
32
+ const generation = this.#generation
33
+ this.#timer = setTimeout(() => {
34
+ this.#timer = undefined
35
+ this.#inFlight = true
36
+ this.#callback(() => {
37
+ this.#inFlight = false
38
+ if (!this.#active) return
39
+
40
+ this.#runAfterDelay(this.#generation === generation ? this.#interval : 0)
41
+ })
42
+ }, interval)
43
+
44
+ this.#timer.unref?.()
21
45
  }
22
46
 
23
47
  stop () {
24
- clearTimeout(this._timer)
48
+ this.#active = false
49
+ clearTimeout(this.#timer)
25
50
 
26
- this._timer = null
51
+ this.#timer = undefined
27
52
  }
28
53
  }
29
54