Skip to content

Commit e91980e

Browse files
authored
Merge pull request #4140 from DataDog/lloeki/guard-injection-against-old-rubies
Guard injection against old rubies
2 parents 33ca49d + f9aec1a commit e91980e

File tree

3 files changed

+255
-247
lines changed

3 files changed

+255
-247
lines changed

.gitlab/prepare-oci-package.sh

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ set -e
55
mkdir sources
66

77
cp ../lib-injection/host_inject.rb sources
8+
cp ../lib-injection/host_inject_main.rb sources
89
cp ../lib-injection/requirements.json sources/requirements.json
910
# Kubernetes injection expects a different path
1011
ln -rs sources/host_inject.rb sources/auto_inject.rb

lib-injection/host_inject.rb

+5-247
Original file line numberDiff line numberDiff line change
@@ -1,249 +1,7 @@
1-
if ENV['DD_TRACE_SKIP_LIB_INJECTION'] == 'true'
2-
# Skip
3-
else
4-
utils = Module.new do
5-
module_function
1+
# This file's intent is to be parseable and executable by all ruby versions
2+
# to call into the main one only for versions for which it is known-compatible
3+
# with at the language level.
64

7-
def debug(msg)
8-
$stdout.puts "[datadog][#{pid}][#{$0}] #{msg}" if ENV['DD_TRACE_DEBUG'] == 'true'
9-
end
10-
11-
def error(msg)
12-
warn "[datadog][#{pid}][#{$0}] #{msg}"
13-
end
14-
15-
def pid
16-
Process.respond_to?(:pid) ? Process.pid : 0
17-
end
18-
19-
def root
20-
File.expand_path(File.join(File.dirname(__FILE__), '.'))
21-
end
22-
23-
def path
24-
major, minor, = RUBY_VERSION.split('.')
25-
ruby_api_version = "#{major}.#{minor}.0"
26-
27-
"#{root}/#{ruby_api_version}"
28-
end
29-
30-
def version
31-
File.exist?("#{root}/version") ? File.read("#{root}/version").chomp : 'unknown'
32-
end
33-
end
34-
35-
telemetry = Module.new do
36-
module_function
37-
38-
def emit(pid, version, events)
39-
payload = {
40-
metadata: {
41-
language_name: 'ruby',
42-
language_version: RUBY_VERSION,
43-
runtime_name: RUBY_ENGINE,
44-
runtime_version: RUBY_VERSION,
45-
tracer_version: version,
46-
pid: pid
47-
},
48-
points: events
49-
}
50-
51-
fowarder = ENV['DD_TELEMETRY_FORWARDER_PATH']
52-
53-
if fowarder && !fowarder.empty?
54-
require 'open3'
55-
require 'json'
56-
57-
Open3.capture2e(fowarder, 'library_entrypoint', stdin_data: payload.to_json)
58-
end
59-
end
60-
end
61-
62-
pid = utils.pid
63-
64-
if Process.respond_to?(:fork)
65-
utils.debug 'Starts injection'
66-
67-
require 'rubygems'
68-
69-
read, write = IO.pipe
70-
71-
fork do
72-
read.close
73-
74-
require 'open3'
75-
require 'bundler'
76-
require 'bundler/cli'
77-
require 'fileutils'
78-
79-
precheck = Module.new do
80-
module_function
81-
82-
def in_bundle?
83-
Bundler::SharedHelpers.in_bundle?
84-
end
85-
86-
def runtime_supported?
87-
major, minor, = RUBY_VERSION.split('.')
88-
ruby_api_version = "#{major}.#{minor}.0"
89-
90-
supported_ruby_api_versions = ['2.7.0', '3.0.0', '3.1.0', '3.2.0', '3.3.0'].freeze
91-
92-
RUBY_ENGINE == 'ruby' && supported_ruby_api_versions.any? { |v| ruby_api_version == v }
93-
end
94-
95-
def platform_supported?
96-
platform_support_matrix = {
97-
cpu: ['x86_64', 'aarch64'].freeze,
98-
os: ['linux'].freeze,
99-
version: ['gnu', nil].freeze # nil is equivalent to `gnu` for local platform
100-
}
101-
local_platform = Gem::Platform.local
102-
103-
platform_support_matrix.fetch(:cpu).any? { |v| local_platform.cpu == v } &&
104-
platform_support_matrix.fetch(:os).any? { |v| local_platform.os == v } &&
105-
platform_support_matrix.fetch(:version).any? { |v| local_platform.version == v }
106-
end
107-
108-
def already_installed?
109-
['ddtrace', 'datadog'].any? do |gem|
110-
fork do
111-
$stdout = File.new('/dev/null', 'w')
112-
$stderr = File.new('/dev/null', 'w')
113-
Bundler::CLI::Common.select_spec(gem)
114-
end
115-
_, status = Process.wait2
116-
status.success?
117-
end
118-
end
119-
120-
def frozen_bundle?
121-
Bundler.frozen_bundle?
122-
end
123-
124-
def bundler_supported?
125-
Bundler::CLI.commands['add'] && Bundler::CLI.commands['add'].options.key?('require')
126-
end
127-
end
128-
129-
if !precheck.in_bundle?
130-
utils.debug 'Not in bundle... skipping injection'
131-
exit!(1)
132-
elsif !precheck.runtime_supported?
133-
utils.debug "Runtime not supported: #{RUBY_DESCRIPTION}"
134-
telemetry.emit(
135-
pid,
136-
utils.version,
137-
[{ name: 'library_entrypoint.abort', tags: ['reason:incompatible_runtime'] },
138-
{ name: 'library_entrypoint.abort.runtime' }]
139-
)
140-
exit!(1)
141-
elsif !precheck.platform_supported?
142-
utils.debug "Platform not supported: #{local_platform}"
143-
telemetry.emit(pid, utils.version, [{ name: 'library_entrypoint.abort', tags: ['reason:incompatible_platform'] }])
144-
exit!(1)
145-
elsif precheck.already_installed?
146-
utils.debug 'Skip injection: already installed'
147-
elsif precheck.frozen_bundle?
148-
utils.error "Skip injection: bundler is configured with 'deployment' or 'frozen'"
149-
telemetry.emit(pid, utils.version, [{ name: 'library_entrypoint.abort', tags: ['reason:bundler'] }])
150-
exit!(1)
151-
elsif !precheck.bundler_supported?
152-
utils.error "Skip injection: bundler version #{Bundler::VERSION} is not supported, please upgrade to >= 2.3."
153-
telemetry.emit(pid, utils.version, [{ name: 'library_entrypoint.abort', tags: ['reason:bundler_version'] }])
154-
exit!(1)
155-
else
156-
# Injection
157-
path = utils.path
158-
utils.debug "Loading from #{path}"
159-
lock_file_parser = Bundler::LockfileParser.new(Bundler.read_file("#{path}/Gemfile.lock"))
160-
gem_version_mapping = lock_file_parser.specs.each_with_object({}) do |spec, hash|
161-
hash[spec.name] = spec.version.to_s
162-
hash
163-
end
164-
165-
gemfile = Bundler::SharedHelpers.default_gemfile
166-
lockfile = Bundler::SharedHelpers.default_lockfile
167-
168-
datadog_gemfile = gemfile.dirname + '.datadog-Gemfile'
169-
datadog_lockfile = lockfile.dirname + '.datadog-Gemfile.lock'
170-
171-
# Copies for trial
172-
::FileUtils.cp gemfile, datadog_gemfile
173-
::FileUtils.cp lockfile, datadog_lockfile
174-
175-
injection_failure = false
176-
177-
# This is order dependent
178-
[
179-
'msgpack',
180-
'ffi',
181-
'datadog-ruby_core_source',
182-
'libdatadog',
183-
'libddwaf',
184-
'datadog'
185-
].each do |gem|
186-
fork do
187-
$stdout = File.new('/dev/null', 'w')
188-
$stderr = File.new('/dev/null', 'w')
189-
Bundler::CLI::Common.select_spec(gem)
190-
end
191-
192-
_, status = Process.wait2
193-
if status.success?
194-
utils.debug "#{gem} already installed... skipping..."
195-
next
196-
end
197-
198-
bundle_add_cmd = "bundle add #{gem} --skip-install --version #{gem_version_mapping[gem]} "
199-
bundle_add_cmd << ' --verbose ' if ENV['DD_TRACE_DEBUG'] == 'true'
200-
bundle_add_cmd << '--require datadog/single_step_instrument' if gem == 'datadog'
201-
202-
utils.debug "Injection with `#{bundle_add_cmd}`"
203-
204-
env = { 'BUNDLE_GEMFILE' => datadog_gemfile.to_s,
205-
'DD_TRACE_SKIP_LIB_INJECTION' => 'true',
206-
'GEM_PATH' => utils.path }
207-
add_output, add_status = Open3.capture2e(env, bundle_add_cmd)
208-
209-
if add_status.success?
210-
utils.debug "Successfully injected #{gem} into the application."
211-
else
212-
injection_failure = true
213-
utils.error "Injection failed: Unable to add datadog. Error output: #{add_output}"
214-
end
215-
end
216-
217-
if injection_failure
218-
::FileUtils.rm datadog_gemfile
219-
::FileUtils.rm datadog_lockfile
220-
telemetry.emit(pid, utils.version, [{ name: 'library_entrypoint.error', tags: ['error_type:injection_failure'] }])
221-
exit!(1)
222-
else
223-
write.puts datadog_gemfile.to_s
224-
telemetry.emit(pid, utils.version, [{ name: 'library_entrypoint.complete', tags: ['injection_forced:false'] }])
225-
end
226-
end
227-
end
228-
229-
write.close
230-
gemfile = read.read.to_s.chomp
231-
232-
_, status = Process.wait2
233-
ENV['DD_TRACE_SKIP_LIB_INJECTION'] = 'true'
234-
235-
if status.success?
236-
dd_lib_injection_path = utils.path
237-
238-
Gem.paths = { 'GEM_PATH' => "#{dd_lib_injection_path}:#{ENV['GEM_PATH']}" }
239-
ENV['GEM_PATH'] = Gem.path.join(':')
240-
ENV['BUNDLE_GEMFILE'] = gemfile
241-
utils.debug "Fork success: Using Gemfile `#{gemfile}`"
242-
else
243-
utils.debug 'Fork abort'
244-
end
245-
else
246-
utils.debug 'Fork not supported... skipping injection'
247-
telemetry.emit(pid, utils.version, [{ name: 'library_entrypoint.abort', tags: ['reason:fork_not_supported'] }])
248-
end
5+
if RUBY_VERSION >= '2.3.'
6+
require File.expand_path(File.join(File.dirname(__FILE__), 'host_inject_main.rb'))
2497
end

0 commit comments

Comments
 (0)