datadog 2.23.0 → 2.24.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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -2
  3. data/ext/datadog_profiling_native_extension/collectors_stack.c +17 -5
  4. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
  5. data/ext/datadog_profiling_native_extension/extconf.rb +4 -1
  6. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +12 -0
  7. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +4 -0
  8. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  9. data/lib/datadog/appsec/context.rb +2 -1
  10. data/lib/datadog/appsec/remote.rb +1 -9
  11. data/lib/datadog/appsec/security_engine/result.rb +2 -1
  12. data/lib/datadog/core/configuration/config_helper.rb +1 -1
  13. data/lib/datadog/core/configuration/deprecations.rb +2 -2
  14. data/lib/datadog/core/configuration/option_definition.rb +4 -2
  15. data/lib/datadog/core/configuration/options.rb +8 -5
  16. data/lib/datadog/core/configuration/settings.rb +14 -3
  17. data/lib/datadog/core/configuration/supported_configurations.rb +2 -1
  18. data/lib/datadog/core/environment/cgroup.rb +52 -25
  19. data/lib/datadog/core/environment/container.rb +140 -46
  20. data/lib/datadog/core/environment/ext.rb +1 -0
  21. data/lib/datadog/core/environment/process.rb +9 -1
  22. data/lib/datadog/core/rate_limiter.rb +9 -1
  23. data/lib/datadog/core/remote/client.rb +14 -6
  24. data/lib/datadog/core/remote/component.rb +6 -4
  25. data/lib/datadog/core/remote/configuration/content.rb +15 -2
  26. data/lib/datadog/core/remote/configuration/digest.rb +14 -7
  27. data/lib/datadog/core/remote/configuration/repository.rb +1 -1
  28. data/lib/datadog/core/remote/configuration/target.rb +13 -6
  29. data/lib/datadog/core/remote/transport/config.rb +3 -16
  30. data/lib/datadog/core/remote/transport/http/config.rb +4 -44
  31. data/lib/datadog/core/remote/transport/http/negotiation.rb +0 -39
  32. data/lib/datadog/core/remote/transport/http.rb +13 -24
  33. data/lib/datadog/core/remote/transport/negotiation.rb +7 -16
  34. data/lib/datadog/core/telemetry/component.rb +52 -13
  35. data/lib/datadog/core/telemetry/event/app_started.rb +34 -0
  36. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  37. data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
  38. data/lib/datadog/core/telemetry/request.rb +17 -3
  39. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +2 -32
  40. data/lib/datadog/core/telemetry/transport/http.rb +21 -16
  41. data/lib/datadog/core/telemetry/transport/telemetry.rb +3 -10
  42. data/lib/datadog/core/telemetry/worker.rb +88 -32
  43. data/lib/datadog/core/transport/ext.rb +2 -0
  44. data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
  45. data/lib/datadog/core/transport/http/api/instance.rb +4 -21
  46. data/lib/datadog/core/transport/http/builder.rb +9 -5
  47. data/lib/datadog/core/transport/http/client.rb +19 -8
  48. data/lib/datadog/core/transport/http.rb +22 -19
  49. data/lib/datadog/core/transport/response.rb +9 -0
  50. data/lib/datadog/core/transport/transport.rb +90 -0
  51. data/lib/datadog/core/utils/only_once_successful.rb +2 -0
  52. data/lib/datadog/core/utils/time.rb +1 -1
  53. data/lib/datadog/core/workers/async.rb +10 -1
  54. data/lib/datadog/core/workers/interval_loop.rb +44 -3
  55. data/lib/datadog/core/workers/polling.rb +2 -0
  56. data/lib/datadog/core/workers/queue.rb +100 -1
  57. data/lib/datadog/data_streams/processor.rb +1 -1
  58. data/lib/datadog/data_streams/transport/http/stats.rb +1 -36
  59. data/lib/datadog/data_streams/transport/http.rb +5 -6
  60. data/lib/datadog/data_streams/transport/stats.rb +3 -17
  61. data/lib/datadog/di/contrib/active_record.rb +31 -5
  62. data/lib/datadog/di/el/compiler.rb +8 -4
  63. data/lib/datadog/di/error.rb +5 -0
  64. data/lib/datadog/di/instrumenter.rb +17 -4
  65. data/lib/datadog/di/probe_builder.rb +2 -1
  66. data/lib/datadog/di/probe_manager.rb +37 -31
  67. data/lib/datadog/di/probe_notification_builder.rb +15 -2
  68. data/lib/datadog/di/remote.rb +89 -84
  69. data/lib/datadog/di/transport/diagnostics.rb +7 -35
  70. data/lib/datadog/di/transport/http/diagnostics.rb +1 -31
  71. data/lib/datadog/di/transport/http/input.rb +1 -31
  72. data/lib/datadog/di/transport/http.rb +28 -17
  73. data/lib/datadog/di/transport/input.rb +7 -34
  74. data/lib/datadog/di.rb +61 -5
  75. data/lib/datadog/open_feature/evaluation_engine.rb +2 -1
  76. data/lib/datadog/open_feature/remote.rb +3 -10
  77. data/lib/datadog/open_feature/transport.rb +9 -11
  78. data/lib/datadog/opentelemetry/api/baggage.rb +1 -1
  79. data/lib/datadog/opentelemetry/configuration/settings.rb +2 -2
  80. data/lib/datadog/opentelemetry/metrics.rb +21 -14
  81. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +5 -8
  82. data/lib/datadog/profiling/collectors/code_provenance.rb +27 -2
  83. data/lib/datadog/profiling/collectors/info.rb +2 -1
  84. data/lib/datadog/profiling/component.rb +12 -11
  85. data/lib/datadog/profiling/http_transport.rb +4 -1
  86. data/lib/datadog/tracing/contrib/extensions.rb +10 -2
  87. data/lib/datadog/tracing/contrib/karafka/patcher.rb +31 -32
  88. data/lib/datadog/tracing/contrib/status_range_matcher.rb +2 -1
  89. data/lib/datadog/tracing/contrib/utils/quantization/hash.rb +3 -1
  90. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +6 -3
  91. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
  92. data/lib/datadog/tracing/remote.rb +1 -9
  93. data/lib/datadog/tracing/span_event.rb +2 -2
  94. data/lib/datadog/tracing/span_operation.rb +9 -4
  95. data/lib/datadog/tracing/trace_operation.rb +44 -6
  96. data/lib/datadog/tracing/tracer.rb +42 -16
  97. data/lib/datadog/tracing/transport/http/traces.rb +2 -50
  98. data/lib/datadog/tracing/transport/http.rb +15 -9
  99. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  100. data/lib/datadog/tracing/transport/traces.rb +6 -66
  101. data/lib/datadog/tracing/workers/trace_writer.rb +5 -0
  102. data/lib/datadog/tracing/writer.rb +1 -0
  103. data/lib/datadog/version.rb +2 -2
  104. metadata +7 -13
  105. data/lib/datadog/core/remote/transport/http/api.rb +0 -53
  106. data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
  107. data/lib/datadog/core/transport/http/api/spec.rb +0 -36
  108. data/lib/datadog/data_streams/transport/http/api.rb +0 -33
  109. data/lib/datadog/data_streams/transport/http/client.rb +0 -21
  110. data/lib/datadog/di/transport/http/api.rb +0 -42
  111. data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
  112. data/lib/datadog/tracing/transport/http/api.rb +0 -44
@@ -10,40 +10,67 @@ module Datadog
10
10
  # about the current Linux container identity.
11
11
  # @see https://man7.org/linux/man-pages/man7/cgroups.7.html
12
12
  module Cgroup
13
- LINE_REGEX = /^(\d+):([^:]*):(.+)$/.freeze
14
-
15
- Descriptor = Struct.new(
16
- :id,
17
- :groups,
13
+ # A parsed cgroup entry from /proc/<pid>/cgroup
14
+ Entry = Struct.new(
15
+ :hierarchy,
16
+ :controllers,
18
17
  :path,
19
- :controllers
18
+ :inode
20
19
  )
21
20
 
22
21
  module_function
23
22
 
24
- def descriptors(process = 'self')
25
- [].tap do |descriptors|
26
- filepath = "/proc/#{process}/cgroup"
27
-
28
- if File.exist?(filepath)
29
- File.foreach("/proc/#{process}/cgroup") do |line|
30
- line = line.strip
31
- descriptors << parse(line) unless line.empty?
32
- end
33
- end
34
- rescue => e
35
- Datadog.logger.error(
36
- "Error while parsing cgroup. Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
37
- )
23
+ # Parses the /proc/self/cgroup file,
24
+ # @return [Array<Entry>] one entry for each valid cgroup line
25
+ def entries
26
+ filepath = '/proc/self/cgroup'
27
+ return [] unless File.exist?(filepath)
28
+
29
+ ret = []
30
+ File.foreach(filepath) do |entry_line|
31
+ ret << parse(entry_line) unless entry_line.empty?
38
32
  end
33
+ ret
39
34
  end
40
35
 
41
- def parse(line)
42
- id, groups, path = line.scan(LINE_REGEX).first
36
+ # Parses a single cgroup entry from /proc/<pid>/cgroup.
37
+ #
38
+ # Files can have cgroup v1 and v2 entries mixed. Their format is the same.
39
+ #
40
+ # Each entry has 3 colon-separated fields:
41
+ # hierarchy-ID:controllers:path
42
+ # Examples:
43
+ # 10:memory:/docker/1234567890abcdef (cgroup v1)
44
+ # 0::/docker/1234567890abcdef (cgroup v2)
45
+ #
46
+ # @see https://man7.org/linux/man-pages/man7/cgroups.7.html#:~:text=%2Fproc%2Fpid%2Fcgroup
47
+ # @return [Entry]
48
+ def parse(entry_line)
49
+ hierarchy, controllers, path = entry_line.split(':', 3)
43
50
 
44
- Descriptor.new(id, groups, path).tap do |descriptor|
45
- descriptor.controllers = groups.split(',') unless groups.nil?
46
- end
51
+ Entry.new(
52
+ hierarchy,
53
+ controllers,
54
+ path,
55
+ inode_for(controllers, path)
56
+ )
57
+ end
58
+
59
+ # We can find the container inode by running a file stat on the cgroup filesystem path.
60
+ # Example:
61
+ # For the entry `0:cpu:/docker/abc123`,
62
+ # we read `stat -c '%i' /sys/fs/cgroup/cpu/docker/abc123`
63
+ def inode_for(controllers, path)
64
+ return if controllers.nil? || path.nil?
65
+
66
+ # In cgroup v1, when multiple controllers are co-mounted, the controllers
67
+ # becomes part of the directory name (with commas preserved).
68
+ # Example entry:
69
+ # For the line "10:cpu,cpuacct:/docker/abc123", the path is
70
+ # "/sys/fs/cgroup/cpu,cpuacct/docker/abc123"
71
+ inode_path = File.join('/sys/fs/cgroup', controllers, path)
72
+
73
+ File.stat(inode_path).ino if File.exist?(inode_path)
47
74
  end
48
75
  end
49
76
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'cgroup'
4
+ require_relative 'ext'
4
5
 
5
6
  module Datadog
6
7
  module Core
@@ -15,73 +16,166 @@ module Datadog
15
16
  CONTAINER_REGEX = /(?<container>#{UUID_PATTERN}|#{CONTAINER_PATTERN})(?:.scope)?$/.freeze
16
17
  FARGATE_14_CONTAINER_REGEX = /(?<container>[0-9a-f]{32}-[0-9]{1,10})/.freeze
17
18
 
18
- Descriptor = Struct.new(
19
+ # From https://github.com/torvalds/linux/blob/5859a2b1991101d6b978f3feb5325dad39421f29/include/linux/proc_ns.h#L41-L49
20
+ # Currently, the host namespace inode number is hardcoded.
21
+ # We use it to determine if we're running in the host namespace.
22
+ # This detection approach does not work when running in
23
+ # ["Docker-in-Docker"](https://www.docker.com/resources/docker-in-docker-containerized-ci-workflows-dockercon-2023/).
24
+ HOST_CGROUP_NAMESPACE_INODE = 0xEFFFFFFB
25
+
26
+ Entry = Struct.new(
19
27
  :platform,
28
+ :task_uid,
20
29
  :container_id,
21
- :task_uid
30
+ :inode
22
31
  )
23
32
 
24
33
  module_function
25
34
 
35
+ # Returns HTTP headers representing container information.
36
+ # These can used in any Datadog request that requires origin detection.
37
+ # This is the recommended method to call to get container information.
38
+ def to_headers
39
+ headers = {}
40
+ headers["Datadog-Container-ID"] = container_id if container_id
41
+ headers["Datadog-Entity-ID"] = entity_id if entity_id
42
+ headers["Datadog-External-Env"] = external_env if external_env
43
+ headers
44
+ end
45
+
46
+ # Container ID, prefixed with "ci-" or Inode, prefixed with "in-".
47
+ def entity_id
48
+ if container_id
49
+ "ci-#{container_id}"
50
+ elsif inode
51
+ "in-#{inode}"
52
+ end
53
+ end
54
+
55
+ # External data supplied by the Datadog Cluster Agent Admission Controller.
56
+ # @see {Ext::ENV_EXTERNAL_ENV} for more details.
57
+ def external_env
58
+ Datadog.configuration.container.external_env
59
+ end
60
+
61
+ # The container orchestration platform or runtime environment.
62
+ #
63
+ # Examples: Docker, Kubernetes, AWS Fargate, LXC, etc.
64
+ #
65
+ # @return [String, nil] The platform name (e.g., "docker", "kubepods", "fargate"), or nil if not containerized
26
66
  def platform
27
- descriptor.platform
67
+ entry.platform
28
68
  end
29
69
 
70
+ # The unique identifier of the current container in the container environment.
71
+ #
72
+ # @return [String, nil] The container ID, or nil if not running in a containerized environment
30
73
  def container_id
31
- descriptor.container_id
74
+ entry.container_id
32
75
  end
33
76
 
77
+ # The unique identifier of the task or pod containing this container.
78
+ #
79
+ # In Kubernetes, this is the Pod UID; in AWS ECS/Fargate, the task ID.
80
+ # Used to identify higher-level workloads beyond this container,
81
+ # enabling correlation across container restarts and multi-container applications.
82
+ #
83
+ # @return [String, nil] The task/pod UID, or nil if not available in the current environment
34
84
  def task_uid
35
- descriptor.task_uid
85
+ entry.task_uid
36
86
  end
37
87
 
38
- def descriptor
39
- @descriptor ||= Descriptor.new.tap do |descriptor|
40
- Cgroup.descriptors.each do |cgroup_descriptor|
41
- # Parse container data from cgroup descriptor
42
- path = cgroup_descriptor.path
43
- next if path.nil?
44
-
45
- # Split path into parts
46
- parts = path.split('/')
47
- parts.shift # Remove leading empty part
48
-
49
- # Read info from path
50
- next if parts.empty?
51
-
52
- platform = parts[0][PLATFORM_REGEX, :platform]
53
- container_id, task_uid = nil
54
-
55
- case parts.length
56
- when 0..1
57
- next
58
- when 2
59
- container_id = parts[-1][CONTAINER_REGEX, :container] \
60
- || parts[-1][FARGATE_14_CONTAINER_REGEX, :container]
61
- else
62
- if (container_id = parts[-1][CONTAINER_REGEX, :container])
88
+ # A unique identifier for the execution context (container or host namespace).
89
+ #
90
+ # Used as a fallback identifier when {#container_id} is unavailable.
91
+ #
92
+ # @return [Integer, nil] The namespace inode, or nil if unavailable
93
+ def inode
94
+ entry.inode
95
+ end
96
+
97
+ # Checks if the current process is running on the host cgroup namespace.
98
+ # This indicates that the process is not running inside a container.
99
+ # When unsure, we return `false` (not running on host).
100
+ def running_on_host?
101
+ return @running_on_host if defined?(@running_on_host)
102
+
103
+ @running_on_host = begin
104
+ if File.exist?('/proc/self/ns/cgroup')
105
+ File.stat('/proc/self/ns/cgroup').ino == HOST_CGROUP_NAMESPACE_INODE
106
+ else
107
+ false
108
+ end
109
+ rescue => e
110
+ Datadog.logger.debug(
111
+ "Error while checking cgroup namespace. Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
112
+ )
113
+ false
114
+ end
115
+ end
116
+
117
+ # All cgroup entries have the same container identity.
118
+ # The first valid one is sufficient.
119
+ # v2 entries are preferred over v1.
120
+ def entry
121
+ return @entry if defined?(@entry)
122
+
123
+ # Scan all v2 entries first, only then falling back to v1 entries.
124
+ #
125
+ # To do this, we {Enumerable#partition} the list between v1 and v2,
126
+ # with a `true` predicate for v2 entries, making v2 first
127
+ # partition returned.
128
+ #
129
+ # All v2 entries have the `hierarchy` set to zero.
130
+ # v1 entries have a non-zero `hierarchy`.
131
+ entries = Cgroup.entries.partition { |d| d.hierarchy == '0' }.flatten(1)
132
+ entries.each do |entry_obj|
133
+ path = entry_obj.path
134
+ next unless path
135
+
136
+ # To ease handling, remove the emtpy leading "",
137
+ # as `path` starts with a "/".
138
+ path.delete_prefix!('/')
139
+ parts = path.split('/')
140
+
141
+ # With not path information, we can still use the inode
142
+ if parts.empty? && entry_obj.inode && !running_on_host?
143
+ return @entry = Entry.new(nil, nil, nil, entry_obj.inode)
144
+ end
145
+
146
+ platform = parts[0][PLATFORM_REGEX, :platform]
147
+
148
+ # Extract container_id and task_uid based on path structure
149
+ container_id = task_uid = nil
150
+ if parts.length >= 2
151
+ # Try standard container regex first
152
+ if (container_id = parts[-1][CONTAINER_REGEX, :container])
153
+ # For 3+ parts, also extract task_uid
154
+ if parts.length > 2
63
155
  task_uid = parts[-2][POD_REGEX, :pod] || parts[1][POD_REGEX, :pod]
64
- else
65
- container_id = parts[-1][FARGATE_14_CONTAINER_REGEX, :container]
66
156
  end
157
+ else
158
+ # Fall back to Fargate regex
159
+ container_id = parts[-1][FARGATE_14_CONTAINER_REGEX, :container]
67
160
  end
161
+ end
68
162
 
69
- # If container ID wasn't found, ignore.
70
- # Path might describe a non-container environment.
71
- next if container_id.nil?
72
-
73
- descriptor.platform = platform
74
- descriptor.container_id = container_id
75
- descriptor.task_uid = task_uid
76
-
77
- break
163
+ # container_id is a better container identifier than inode.
164
+ # We MUST only populate one of them, to avoid container identification ambiguity.
165
+ if container_id
166
+ return @entry = Entry.new(platform, task_uid, container_id)
167
+ elsif entry_obj.inode && !running_on_host?
168
+ return @entry = Entry.new(platform, task_uid, nil, entry_obj.inode)
78
169
  end
79
- rescue => e
80
- Datadog.logger.error(
81
- "Error while parsing container info. Cause: #{e.class.name} #{e.message} " \
82
- "Location: #{Array(e.backtrace).first}"
83
- )
84
170
  end
171
+
172
+ @entry = Entry.new # Empty entry if no valid cgroup entry is found
173
+ rescue => e
174
+ Datadog.logger.debug(
175
+ "Error while reading container entry. Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
176
+ )
177
+ @entry = Entry.new unless defined?(@entry)
178
+ @entry
85
179
  end
86
180
  end
87
181
  end
@@ -17,6 +17,7 @@ module Datadog
17
17
 
18
18
  ENV_API_KEY = 'DD_API_KEY'
19
19
  ENV_ENVIRONMENT = 'DD_ENV'
20
+ ENV_EXTERNAL_ENV = 'DD_EXTERNAL_ENV'
20
21
  ENV_SERVICE = 'DD_SERVICE'
21
22
  ENV_SITE = 'DD_SITE'
22
23
  ENV_TAGS = 'DD_TAGS'
@@ -14,6 +14,14 @@ module Datadog
14
14
  # @return [String] comma-separated normalized key:value pairs
15
15
  def self.serialized
16
16
  return @serialized if defined?(@serialized)
17
+
18
+ @serialized = tags.join(',').freeze
19
+ end
20
+
21
+ # This method returns an array in the format ["k1:v1","k2:v2","k3:v3"]
22
+ # @return [Array<String>] array of normalized key:value pairs
23
+ def self.tags
24
+ return @tags if defined?(@tags)
17
25
  tags = []
18
26
 
19
27
  workdir = TagNormalizer.normalize_process_value(entrypoint_workdir.to_s)
@@ -27,7 +35,7 @@ module Datadog
27
35
 
28
36
  tags << "#{Environment::Ext::TAG_ENTRYPOINT_TYPE}:#{TagNormalizer.normalize(entrypoint_type, remove_digit_start_char: false)}"
29
37
 
30
- @serialized = tags.join(',').freeze
38
+ @tags = tags.freeze
31
39
  end
32
40
 
33
41
  # Returns the last segment of the working directory of the process
@@ -81,6 +81,10 @@ module Datadog
81
81
 
82
82
  return current_window_rate if @prev_conforming_messages.nil? || @prev_total_messages.nil?
83
83
 
84
+ # Steep: Due to https://github.com/soutaro/steep/issues/477,
85
+ # the previous nil check does not narrow type to Integer
86
+ # The annotation fixes the typing error, but it takes effect in the method
87
+ # @type ivar @prev_total_messages: Integer
84
88
  (@conforming_messages.to_f + @prev_conforming_messages.to_f) / (@total_messages + @prev_total_messages)
85
89
  end
86
90
 
@@ -154,7 +158,11 @@ module Datadog
154
158
  if @current_window.nil?
155
159
  @current_window = now
156
160
  # If more than 1 second has past since last window, reset
157
- elsif now - @current_window >= 1
161
+ #
162
+ # Steep: @current_window is a Float, but for some reason annotations does not work here
163
+ # Once a fix will be out for nil checks on instance variables, we can remove the steep:ignore
164
+ # https://github.com/soutaro/steep/issues/477
165
+ elsif now - @current_window >= 1 # steep:ignore UnresolvedOverloading
158
166
  @prev_conforming_messages = @conforming_messages
159
167
  @prev_total_messages = @total_messages
160
168
  @conforming_messages = 0
@@ -14,10 +14,11 @@ module Datadog
14
14
 
15
15
  class SyncError < StandardError; end
16
16
 
17
- attr_reader :transport, :repository, :id, :dispatcher, :logger
17
+ attr_reader :transport, :repository, :id, :dispatcher, :settings, :logger
18
18
 
19
- def initialize(transport, capabilities, logger: Datadog.logger, repository: Configuration::Repository.new)
19
+ def initialize(transport, capabilities, settings:, logger:, repository: Configuration::Repository.new)
20
20
  @transport = transport
21
+ @settings = settings
21
22
  @logger = logger
22
23
 
23
24
  @repository = repository
@@ -97,7 +98,9 @@ module Datadog
97
98
  content = contents.find_content(path, target)
98
99
 
99
100
  # abort entirely if matching content not found
100
- raise SyncError, "no valid content for target at path '#{path}'" if content.nil?
101
+ if content.nil?
102
+ raise SyncError, "no valid content for target at path '#{path}'"
103
+ end
101
104
 
102
105
  # to be added or updated << config
103
106
  # TODO: metadata (hash, version, etc...)
@@ -153,14 +156,19 @@ module Datadog
153
156
  language: Core::Environment::Identity.lang,
154
157
  tracer_version: tracer_version,
155
158
  service: service_name,
156
- env: Datadog.configuration.env,
159
+ env: settings.env,
157
160
  tags: client_tracer_tags,
158
161
  }
159
162
 
160
- app_version = Datadog.configuration.version
163
+ app_version = settings.version
161
164
 
162
165
  client_tracer[:app_version] = app_version if app_version
163
166
 
167
+ if settings.experimental_propagate_process_tags_enabled
168
+ process_tags = Core::Environment::Process.tags
169
+ client_tracer[:process_tags] = process_tags if process_tags.any?
170
+ end
171
+
164
172
  {
165
173
  client: {
166
174
  state: {
@@ -184,7 +192,7 @@ module Datadog
184
192
  end
185
193
 
186
194
  def service_name
187
- Datadog.configuration.remote.service || Datadog.configuration.service
195
+ settings.remote.service || settings.service
188
196
  end
189
197
 
190
198
  def tracer_version
@@ -11,9 +11,11 @@ module Datadog
11
11
  module Core
12
12
  module Remote
13
13
  # Configures the HTTP transport to communicate with the agent
14
- # to fetch and sync the remote configuration
14
+ # to fetch and sync the remote configuration.
15
+ #
16
+ # @api private
15
17
  class Component
16
- attr_reader :logger, :client, :healthy
18
+ attr_reader :logger, :client, :healthy, :worker
17
19
 
18
20
  def initialize(settings, capabilities, agent_settings, logger:)
19
21
  @logger = logger
@@ -23,7 +25,7 @@ module Datadog
23
25
 
24
26
  @barrier = Barrier.new(settings.remote.boot_timeout_seconds)
25
27
 
26
- @client = Client.new(transport_v7, capabilities, logger: logger)
28
+ @client = Client.new(transport_v7, capabilities, settings: settings, logger: logger)
27
29
  @healthy = false
28
30
  logger.debug { "new remote configuration client: #{@client.id}" }
29
31
 
@@ -55,7 +57,7 @@ module Datadog
55
57
  end
56
58
 
57
59
  # client state is unknown, state might be corrupted
58
- @client = Client.new(transport_v7, capabilities, logger: logger)
60
+ @client = Client.new(transport_v7, capabilities, settings: settings, logger: logger)
59
61
  @healthy = false
60
62
  logger.debug { "new remote configuration client: #{@client.id}" }
61
63
 
@@ -22,6 +22,18 @@ module Datadog
22
22
  attr_accessor :version
23
23
 
24
24
  def initialize(path:, data:)
25
+ if data.nil?
26
+ # +data+ is passed to Digest calculation and also is
27
+ # unconditionally taken length of by +length+ method.
28
+ # As such, the class is not written to expect +data+ to be nil.
29
+ # Detect bad incoming values here to provide earlier diagnostics
30
+ # when developing tests, for example.
31
+ raise ArgumentError, 'data must not be nil'
32
+ end
33
+ unless String === data
34
+ raise ArgumentError, "Invalid type for data: #{data.class}: expected String"
35
+ end
36
+
25
37
  @path = path
26
38
  @data = data
27
39
  @apply_state = ApplyState::UNACKNOWLEDGED
@@ -72,8 +84,9 @@ module Datadog
72
84
  private_class_method :new
73
85
  end
74
86
 
75
- # ContentList stores a list of Conetnt instances
76
- # It provides convinient methods for finding content base on Configuration::Path and Configuration::Target
87
+ # ContentList stores a list of Content instances.
88
+ # It provides convenient methods for finding content based on
89
+ # Configuration::Path and Configuration::Target.
77
90
  class ContentList < Array
78
91
  class << self
79
92
  def parse(array)
@@ -24,10 +24,21 @@ module Datadog
24
24
  class InvalidHashTypeError < StandardError; end
25
25
  attr_reader :type, :hexdigest
26
26
 
27
- DIGEST_CHUNK = 1024
28
-
29
27
  class << self
30
28
  def hexdigest(type, data)
29
+ unless String === data
30
+ # This class (Digest) passes +data+ to the Ruby standard
31
+ # library Digest routines without validating its type.
32
+ # The stdlib Digest requires a String, and the previous
33
+ # implementation of this class that used StringIO
34
+ # unconditionally read from +data+ without validating the
35
+ # type. Meaning, passing +nil+ as +data+ has never worked.
36
+ # It still doesn't work in the present implementation.
37
+ # Flag the nil data now to get earlier diagnostics when
38
+ # developing tests for example.
39
+ raise ArgumentError, "Invalid type for data: #{data.class}: expected String"
40
+ end
41
+
31
42
  d = case type
32
43
  when :sha256
33
44
  ::Digest::SHA256.new
@@ -37,13 +48,9 @@ module Datadog
37
48
  raise InvalidHashTypeError, type
38
49
  end
39
50
 
40
- while (buf = data.read(DIGEST_CHUNK))
41
- d.update(buf)
42
- end
51
+ d.update(data)
43
52
 
44
53
  d.hexdigest
45
- ensure
46
- data.rewind
47
54
  end
48
55
  end
49
56
 
@@ -185,7 +185,7 @@ module Datadog
185
185
  end
186
186
  end
187
187
 
188
- # Update existimng repository's contents
188
+ # Update existing repository's contents
189
189
  class Update
190
190
  attr_reader :path, :target, :content
191
191
 
@@ -11,8 +11,15 @@ module Datadog
11
11
  class TargetMap < Hash
12
12
  class << self
13
13
  def parse(hash)
14
- opaque_backend_state = hash['signed']['custom']['opaque_backend_state']
15
- version = hash['signed']['version']
14
+ signed = hash.fetch('signed')
15
+ # Note that the +dig+ call permits +hash['signed']+ to be
16
+ # missing the +custom+ subtree entirely.
17
+ # Previously the subtree was required but +opaque_backend_state+
18
+ # could still be missing (and obtained here as nil).
19
+ opaque_backend_state = signed.dig('custom', 'opaque_backend_state')
20
+ # The version appears to be optional to the rest of this class,
21
+ # and we have tests that do not provide it.
22
+ version = signed['version']
16
23
 
17
24
  map = new
18
25
 
@@ -21,7 +28,7 @@ module Datadog
21
28
  @version = version
22
29
  end
23
30
 
24
- hash['signed']['targets'].each_with_object(map) do |(p, t), m|
31
+ signed.fetch('targets').each_with_object(map) do |(p, t), m|
25
32
  path = Configuration::Path.parse(p)
26
33
  target = Configuration::Target.parse(t)
27
34
 
@@ -46,9 +53,9 @@ module Datadog
46
53
  class Target
47
54
  class << self
48
55
  def parse(hash)
49
- length = Integer(hash['length'])
50
- digests = Configuration::DigestList.parse(hash['hashes'])
51
- version = Integer(hash['custom']['v'])
56
+ length = Integer(hash.fetch('length'))
57
+ digests = Configuration::DigestList.parse(hash.fetch('hashes'))
58
+ version = Integer(hash.dig('custom', 'v'))
52
59
 
53
60
  new(digests: digests, length: length, version: version)
54
61
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative '../../../core/transport/request'
4
4
  require_relative '../../../core/transport/parcel'
5
+ require_relative '../../../core/transport/transport'
5
6
  require_relative 'http/config'
6
7
 
7
8
  module Datadog
@@ -23,27 +24,13 @@ module Datadog
23
24
  end
24
25
 
25
26
  # Config transport
26
- class Transport
27
- attr_reader :client, :apis, :default_api, :current_api_id, :logger
28
-
29
- def initialize(apis, default_api, logger: Datadog.logger)
30
- @apis = apis
31
- @logger = logger
32
-
33
- @client = Remote::Transport::HTTP::Config::Client.new(current_api, logger: logger)
34
- end
35
-
36
- ##### there is only one transport! it's negotiation!
27
+ class Transport < Core::Transport::Transport
37
28
  def send_config(payload)
38
29
  json = JSON.dump(payload)
39
30
  parcel = EncodedParcel.new(json)
40
31
  request = Request.new(parcel)
41
32
 
42
- @client.send_config_payload(request)
43
- end
44
-
45
- def current_api
46
- @apis[HTTP::API::V7]
33
+ @client.send_request(:config, request)
47
34
  end
48
35
  end
49
36
  end