diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4a57c0c5c6a..c926d66d32c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,7 +32,7 @@ default: parallel: matrix: # ADD NEW RUBIES HERE - - RUBY_VERSION: ["3.4", "3.3", "3.2", "3.1", "3.0", "2.7"] + - RUBY_VERSION: ["3.4", "3.3", "3.2", "3.1", "3.0", "2.7", "2.6"] script: - > docker build @@ -67,7 +67,7 @@ promote-image: parallel: matrix: # ADD NEW RUBIES HERE - - RUBY_VERSION: ["3.4", "3.3", "3.2", "3.1", "3.0", "2.7"] + - RUBY_VERSION: ["3.4", "3.3", "3.2", "3.1", "3.0", "2.7", "2.6"] ARCHITECTURE: ["amd64", "arm64"] script: - docker pull $RUBY_CUSTOM_IMAGE_BASE/$RUBY_VERSION-$ARCHITECTURE:$CI_PIPELINE_ID @@ -105,8 +105,7 @@ install-dependencies: tags: ["arch:$ARCH"] parallel: matrix: - # Promote again when adding 3.4 support - - RUBY_VERSION: ["3.3", "3.2", "3.1", "3.0", "2.7"] + - RUBY_VERSION: ["3.4", "3.3", "3.2", "3.1", "3.0", "2.7", "2.6"] ARCH: ["amd64", "arm64"] stage: package needs: @@ -180,7 +179,7 @@ vaccine: script: - export GITHUB_TOKEN=$(cat token.txt) - - .gitlab/scripts/vaccine.sh master "${CI_COMMIT_SHA}" "glci:${CI_PIPELINE_ID}" + - .gitlab/scripts/vaccine.sh lloeki/ssi "${CI_COMMIT_SHA}" "glci:${CI_PIPELINE_ID}" after_script: # Revoke the token after usage diff --git a/.gitlab/Dockerfile-2.6 b/.gitlab/Dockerfile-2.6 new file mode 100644 index 00000000000..88f862b7a97 --- /dev/null +++ b/.gitlab/Dockerfile-2.6 @@ -0,0 +1 @@ +FROM ghcr.io/datadog/images-rb/engines/ruby:2.6-centos-gcc diff --git a/.gitlab/prepare-oci-package.sh b/.gitlab/prepare-oci-package.sh index df40ee2ed45..8b6e4af1b21 100755 --- a/.gitlab/prepare-oci-package.sh +++ b/.gitlab/prepare-oci-package.sh @@ -1,20 +1,45 @@ -#!/bin/bash +#!/usr/bin/env bash -set -e +## This script is called by `package-oci` in `one-pipeline.yml` -if [ "$OS" != "linux" ]; then +set -euo pipefail + +## Early checks + +if [[ "$OS" != "linux" ]]; then echo "Only linux packages are supported. Exiting" exit 0 fi +## Obtain injector source + +injector_repo="https://github.com/DataDog/datadog-injector-rb.git" +injector_ref="v1.0.0" +injector_path="${HOME}/datadog-injector-rb" + +git clone "${injector_repo}" --branch "${injector_ref}" "${injector_path}" + +## Prepare package structure as expected by shared pipeline + mkdir sources -cp ../lib-injection/host_inject.rb sources -cp ../lib-injection/host_inject_main.rb sources +# Copy injector runtime source +cp -Rv "${injector_path}/src"/* sources + +# host injection expects a specific name +ln -rs sources/injector.rb sources/host_inject.rb + +# Kubernetes injection expects a specific name +ln -rs sources/injector.rb sources/auto_inject.rb + +## Copy system injector rules + cp ../lib-injection/requirements.json sources/requirements.json -# Kubernetes injection expects a different path -ln -rs sources/host_inject.rb sources/auto_inject.rb -cp -r ../tmp/${ARCH}/* sources +## Copy arch-specific content, a.k.a per-version `GEM_HOME` + +cp -r "../tmp/${ARCH}"/* sources + +## Add `datadog` gem version information cp ../tmp/version sources diff --git a/lib-injection/README.md b/lib-injection/README.md deleted file mode 100644 index cc8478bdc32..00000000000 --- a/lib-injection/README.md +++ /dev/null @@ -1,52 +0,0 @@ -### Injection - -Injection is Datadog's strategy to [instrument application without touching application code](https://docs.datadoghq.com/tracing/trace_collection/library_injection_local/?tab=kubernetes). Currently, This strategy is implemented by adding `datadog` to your application's `Gemfile` to [instrument your application](https://docs.datadoghq.com/tracing/trace_collection/dd_libraries/ruby/#rails-or-hanami-applications)). - -* Supports `Ruby on Rails` and `Hanami` application -* Requires [bundler](https://bundler.io/) version 2.3 or above -* Does not support frozen `Gemfile` or vendoring gems ([Bundler's Deployment Mode](https://www.bundler.cn/man/bundle-install.1.html#DEPLOYMENT-MODE) or setting `BUNDLE_PATH`) - -Examples: - -Bundler vendors gems from a specific location, instead of system default. -```bash -bundle config path vendor/cache -# or -BUNDLE_PATH=vendor/cache bundle install -# or -bundle install --path=vendor/cache -``` - -Bundler freezes the `Gemfile` to prevent the `Gemfile.lock` to be updated after this install. -```bash -bundle config set frozen true -# or -bundle install --frozen -``` - -[Bundler's Deployment Mode](https://www.bundler.cn/man/bundle-install.1.html#DEPLOYMENT-MODE) would freeze the `Gemfile` and vendor gems from `vendor/cache`. - -```bash -bundle config set deployment true -# or -bundle install --deployment -``` - - -### Packaging - -There's an internal gitlab build pipeline ships pre-installed `datadog` deb and rpm packages. - -Currently, we support - -| Environment| version | -|---|---| -| Ruby | `2.7`, `3.0`, `3.1`, `3.2`, `3.3`| -| Arch | `amd64`, `arm64` | -| glibc | 2.17+ | - -In order to ship `datadog` and its dependencies as a pre-install package, we need a few tweaks in our build pipeline. - -* Use multiple custom built Ruby images to build native extensions. Those images are based on Debian `buster` to support older distribution and Ruby is compiled as a static library with `--disable-shared` option which disables the creation of shared libraries (also known as dynamic libraries or DLLs). -* Install `ffi` gem with its built-in `libffi` native extension instead of using system's `libffi`. -* After gem installation, the native extensions would be store in `extensions/x86_64-linux/3.2.0-static/`(see `Gem.extension_api_version`). We symlink those directories to remove the `-static` suffix so user’s ruby can detect those `.so` files and make sure files have read permission. diff --git a/lib-injection/host_inject.rb b/lib-injection/host_inject.rb deleted file mode 100644 index 1dbf4d683d8..00000000000 --- a/lib-injection/host_inject.rb +++ /dev/null @@ -1,7 +0,0 @@ -# This file's intent is to be parseable and executable by all ruby versions -# to call into the main one only for versions for which it is known-compatible -# with at the language level. - -if RUBY_VERSION >= '2.3.' - require File.expand_path(File.join(File.dirname(__FILE__), 'host_inject_main.rb')) -end diff --git a/lib-injection/host_inject_main.rb b/lib-injection/host_inject_main.rb deleted file mode 100644 index aa4d8812efe..00000000000 --- a/lib-injection/host_inject_main.rb +++ /dev/null @@ -1,251 +0,0 @@ -if ENV['DD_TRACE_SKIP_LIB_INJECTION'] == 'true' - # Skip -else - utils = Module.new do - module_function - - def debug(msg) - $stdout.puts "[datadog][#{pid}][#{$0}] #{msg}" if ENV['DD_TRACE_DEBUG'] == 'true' - end - - def error(msg) - warn "[datadog][#{pid}][#{$0}] #{msg}" - end - - def pid - Process.respond_to?(:pid) ? Process.pid : 0 - end - - def root - File.expand_path(File.join(File.dirname(__FILE__), '.')) - end - - def path - major, minor, = RUBY_VERSION.split('.') - ruby_api_version = "#{major}.#{minor}.0" - - "#{root}/#{ruby_api_version}" - end - - def version - File.exist?("#{root}/version") ? File.read("#{root}/version").chomp : 'unknown' - end - end - - telemetry = Module.new do - module_function - - def emit(pid, version, events) - payload = { - metadata: { - language_name: 'ruby', - language_version: RUBY_VERSION, - runtime_name: RUBY_ENGINE, - runtime_version: RUBY_VERSION, - tracer_version: version, - pid: pid - }, - points: events - } - - fowarder = ENV['DD_TELEMETRY_FORWARDER_PATH'] - - if fowarder && !fowarder.empty? - require 'open3' - require 'json' - - Open3.capture2e(fowarder, 'library_entrypoint', stdin_data: payload.to_json) - end - end - end - - pid = utils.pid - - if Process.respond_to?(:fork) - utils.debug 'Starts injection' - - require 'rubygems' - - read, write = IO.pipe - - fork do - read.close - - require 'open3' - require 'bundler' - require 'bundler/cli' - require 'fileutils' - - precheck = Module.new do - module_function - - def in_bundle? - Bundler::SharedHelpers.in_bundle? - end - - def runtime_supported? - major, minor, = RUBY_VERSION.split('.') - ruby_api_version = "#{major}.#{minor}.0" - - supported_ruby_api_versions = ['2.7.0', '3.0.0', '3.1.0', '3.2.0', '3.3.0'].freeze - - RUBY_ENGINE == 'ruby' && supported_ruby_api_versions.any? { |v| ruby_api_version == v } - end - - def platform_supported? - platform_support_matrix = { - cpu: ['x86_64', 'aarch64'].freeze, - os: ['linux'].freeze, - version: ['gnu', nil].freeze # nil is equivalent to `gnu` for local platform - } - local_platform = Gem::Platform.local - - platform_support_matrix.fetch(:cpu).any? { |v| local_platform.cpu == v } && - platform_support_matrix.fetch(:os).any? { |v| local_platform.os == v } && - platform_support_matrix.fetch(:version).any? { |v| local_platform.version == v } - end - - def already_installed? - ['ddtrace', 'datadog'].any? do |gem| - fork do - $stdout = File.new('/dev/null', 'w') - $stderr = File.new('/dev/null', 'w') - Bundler::CLI::Common.select_spec(gem) - end - _, status = Process.wait2 - status.success? - end - end - - def frozen_bundle? - Bundler.frozen_bundle? - end - - def bundler_supported? - Bundler::CLI.commands['add'] && Bundler::CLI.commands['add'].options.key?('require') - end - end - - if !precheck.in_bundle? - utils.debug 'Not in bundle... skipping injection' - exit!(1) - elsif !precheck.runtime_supported? - utils.debug "Runtime not supported: #{RUBY_DESCRIPTION}" - telemetry.emit( - pid, - utils.version, - [{ name: 'library_entrypoint.abort', tags: ['reason:incompatible_runtime'] }, - { name: 'library_entrypoint.abort.runtime' }] - ) - exit!(1) - elsif !precheck.platform_supported? - utils.debug "Platform not supported: #{local_platform}" - telemetry.emit(pid, utils.version, [{ name: 'library_entrypoint.abort', tags: ['reason:incompatible_platform'] }]) - exit!(1) - elsif precheck.already_installed? - utils.debug 'Skip injection: already installed' - elsif precheck.frozen_bundle? - utils.error "Skip injection: bundler is configured with 'deployment' or 'frozen'" - telemetry.emit(pid, utils.version, [{ name: 'library_entrypoint.abort', tags: ['reason:bundler'] }]) - exit!(1) - elsif !precheck.bundler_supported? - utils.error "Skip injection: bundler version #{Bundler::VERSION} is not supported, please upgrade to >= 2.3." - telemetry.emit(pid, utils.version, [{ name: 'library_entrypoint.abort', tags: ['reason:bundler_version'] }]) - exit!(1) - else - # Injection - path = utils.path - utils.debug "Loading from #{path}" - lock_file_parser = Bundler::LockfileParser.new(Bundler.read_file("#{path}/Gemfile.lock")) - gem_version_mapping = lock_file_parser.specs.each_with_object({}) do |spec, hash| - hash[spec.name] = spec.version.to_s - hash - end - - gemfile = Bundler::SharedHelpers.default_gemfile - lockfile = Bundler::SharedHelpers.default_lockfile - - datadog_gemfile = gemfile.dirname + '.datadog-Gemfile' - datadog_lockfile = lockfile.dirname + '.datadog-Gemfile.lock' - - # Copies for trial - ::FileUtils.cp gemfile, datadog_gemfile - ::FileUtils.cp lockfile, datadog_lockfile - - injection_failure = false - - # This is order dependent - # ADD NEW DEPENDENCIES HERE - [ - 'msgpack', - 'ffi', - 'datadog-ruby_core_source', - 'libdatadog', - 'libddwaf', - 'logger', - 'datadog' - ].each do |gem| - fork do - $stdout = File.new('/dev/null', 'w') - $stderr = File.new('/dev/null', 'w') - Bundler::CLI::Common.select_spec(gem) - end - - _, status = Process.wait2 - if status.success? - utils.debug "#{gem} already installed... skipping..." - next - end - - bundle_add_cmd = "bundle add #{gem} --skip-install --version #{gem_version_mapping[gem]} " - bundle_add_cmd << ' --verbose ' if ENV['DD_TRACE_DEBUG'] == 'true' - bundle_add_cmd << '--require datadog/single_step_instrument' if gem == 'datadog' - - utils.debug "Injection with `#{bundle_add_cmd}`" - - env = { 'BUNDLE_GEMFILE' => datadog_gemfile.to_s, - 'DD_TRACE_SKIP_LIB_INJECTION' => 'true', - 'GEM_PATH' => utils.path } - add_output, add_status = Open3.capture2e(env, bundle_add_cmd) - - if add_status.success? - utils.debug "Successfully injected #{gem} into the application." - else - injection_failure = true - utils.error "Injection failed: Unable to add datadog. Error output: #{add_output}" - end - end - - if injection_failure - ::FileUtils.rm datadog_gemfile - ::FileUtils.rm datadog_lockfile - telemetry.emit(pid, utils.version, [{ name: 'library_entrypoint.error', tags: ['error_type:injection_failure'] }]) - exit!(1) - else - write.puts datadog_gemfile.to_s - telemetry.emit(pid, utils.version, [{ name: 'library_entrypoint.complete', tags: ['injection_forced:false'] }]) - end - end - end - - write.close - gemfile = read.read.to_s.chomp - - _, status = Process.wait2 - ENV['DD_TRACE_SKIP_LIB_INJECTION'] = 'true' - - if status.success? - dd_lib_injection_path = utils.path - - Gem.paths = { 'GEM_PATH' => "#{dd_lib_injection_path}:#{ENV['GEM_PATH']}" } - ENV['GEM_PATH'] = Gem.path.join(':') - ENV['BUNDLE_GEMFILE'] = gemfile - utils.debug "Fork success: Using Gemfile `#{gemfile}`" - else - utils.debug 'Fork abort' - end - else - utils.debug 'Fork not supported... skipping injection' - telemetry.emit(pid, utils.version, [{ name: 'library_entrypoint.abort', tags: ['reason:fork_not_supported'] }]) - end -end