diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 01e13b54dec..4d2d111bf81 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,7 +31,7 @@ default: parallel: matrix: # ADD NEW RUBIES HERE - - RUBY_VERSION: ["3.2.2", "3.1.4", "3.0.6", "2.7.8"] + - RUBY_VERSION: ["3.4", "3.3", "3.2", "3.1", "3.0", "2.7"] script: - > docker build @@ -60,7 +60,7 @@ promote-image: parallel: matrix: # ADD NEW RUBIES HERE - - RUBY_VERSION: ["3.2.2", "3.1.4", "3.0.6", "2.7.8"] + - RUBY_VERSION: ["3.4", "3.3", "3.2", "3.1", "3.0", "2.7"] ARCHITECTURE: ["amd64", "arm64"] script: - docker pull $RUBY_CUSTOM_IMAGE_BASE/$RUBY_VERSION-$ARCHITECTURE:$CI_PIPELINE_ID @@ -92,7 +92,8 @@ install-dependencies: tags: [ "arch:$ARCH" ] parallel: matrix: - - RUBY_VERSION: ["2.7.8", "3.0.6", "3.1.4", "3.2.2"] + # Promote again when adding 3.4 support + - RUBY_VERSION: ["3.3", "3.2", "3.1", "3.0", "2.7"] ARCH: [ "amd64", "arm64" ] stage: package needs: diff --git a/.gitlab/Dockerfile-2.7 b/.gitlab/Dockerfile-2.7 new file mode 100644 index 00000000000..099626411cd --- /dev/null +++ b/.gitlab/Dockerfile-2.7 @@ -0,0 +1 @@ +FROM ghcr.io/datadog/images-rb/engines/ruby:2.7-centos-gcc diff --git a/.gitlab/Dockerfile-2.7.8 b/.gitlab/Dockerfile-2.7.8 deleted file mode 100644 index 447c2bfa2df..00000000000 --- a/.gitlab/Dockerfile-2.7.8 +++ /dev/null @@ -1,94 +0,0 @@ -# This is copied from official Docker image -# but compile Ruby with `--disable-shared` and update gem version - -FROM registry.ddbuild.io/images/mirror/buildpack-deps:buster - -# skip installing gem documentation -RUN set -eux; \ - mkdir -p /usr/local/etc; \ - { \ - echo 'install: --no-document'; \ - echo 'update: --no-document'; \ - } >> /usr/local/etc/gemrc - -ENV LANG C.UTF-8 -ENV RUBY_MAJOR 2.7 -ENV RUBY_VERSION 2.7.8 -ENV RUBY_DOWNLOAD_SHA256 f22f662da504d49ce2080e446e4bea7008cee11d5ec4858fc69000d0e5b1d7fb - -# some of ruby's build scripts are written in ruby -# we purge system ruby later to make sure our final image uses what we just built -RUN set -eux; \ - \ - savedAptMark="$(apt-mark showmanual)"; \ - apt-get update; \ - apt-get install -y --no-install-recommends \ - bison \ - dpkg-dev \ - libgdbm-dev \ - ruby \ - ; \ - rm -rf /var/lib/apt/lists/*; \ - \ - wget -O ruby.tar.xz "https://cache.ruby-lang.org/pub/ruby/${RUBY_MAJOR%-rc}/ruby-$RUBY_VERSION.tar.xz"; \ - echo "$RUBY_DOWNLOAD_SHA256 *ruby.tar.xz" | sha256sum --check --strict; \ - \ - mkdir -p /usr/src/ruby; \ - tar -xJf ruby.tar.xz -C /usr/src/ruby --strip-components=1; \ - rm ruby.tar.xz; \ - \ - cd /usr/src/ruby; \ - \ -# hack in "ENABLE_PATH_CHECK" disabling to suppress: -# warning: Insecure world writable dir - { \ - echo '#define ENABLE_PATH_CHECK 0'; \ - echo; \ - cat file.c; \ - } > file.c.new; \ - mv file.c.new file.c; \ - \ - autoconf; \ - gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ - ./configure \ - --build="$gnuArch" \ - --disable-install-doc \ - --disable-shared \ - ; \ - make -j "$(nproc)"; \ - make install; \ - \ - apt-mark auto '.*' > /dev/null; \ - apt-mark manual $savedAptMark > /dev/null; \ - find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec ldd '{}' ';' \ - | awk '/=>/ { print $(NF-1) }' \ - | sort -u \ - | grep -vE '^/usr/local/lib/' \ - | xargs -r dpkg-query --search \ - | cut -d: -f1 \ - | sort -u \ - | xargs -r apt-mark manual \ - ; \ - apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ - \ - cd /; \ - rm -r /usr/src/ruby; \ -# verify we have no "ruby" packages installed - if dpkg -l | grep -i ruby; then exit 1; fi; \ - [ "$(command -v ruby)" = '/usr/local/bin/ruby' ]; \ -# update gem version - gem update --system 3.3.27;\ -# rough smoke test - ruby --version; \ - gem --version; \ - bundle --version - -# don't create ".bundle" in all our apps -ENV GEM_HOME /usr/local/bundle -ENV BUNDLE_SILENCE_ROOT_WARNING=1 \ - BUNDLE_APP_CONFIG="$GEM_HOME" -ENV PATH $GEM_HOME/bin:$PATH -# adjust permissions of a few directories for running "gem install" as an arbitrary user -RUN mkdir -p "$GEM_HOME" && chmod 1777 "$GEM_HOME" - -CMD [ "irb" ] diff --git a/.gitlab/Dockerfile-3.0 b/.gitlab/Dockerfile-3.0 new file mode 100644 index 00000000000..e342afc299a --- /dev/null +++ b/.gitlab/Dockerfile-3.0 @@ -0,0 +1 @@ +FROM ghcr.io/datadog/images-rb/engines/ruby:3.0-centos-gcc diff --git a/.gitlab/Dockerfile-3.0.6 b/.gitlab/Dockerfile-3.0.6 deleted file mode 100644 index 08b2947d849..00000000000 --- a/.gitlab/Dockerfile-3.0.6 +++ /dev/null @@ -1,94 +0,0 @@ -# This is copied from official Docker image -# but compile Ruby with `--disable-shared` and update gem version - -FROM registry.ddbuild.io/images/mirror/buildpack-deps:buster - -# skip installing gem documentation -RUN set -eux; \ - mkdir -p /usr/local/etc; \ - { \ - echo 'install: --no-document'; \ - echo 'update: --no-document'; \ - } >> /usr/local/etc/gemrc - -ENV LANG C.UTF-8 -ENV RUBY_MAJOR 3.0 -ENV RUBY_VERSION 3.0.6 -ENV RUBY_DOWNLOAD_SHA256 b5cbee93e62d85cfb2a408c49fa30a74231ae8409c2b3858e5f5ea254d7ddbd1 - -# some of ruby's build scripts are written in ruby -# we purge system ruby later to make sure our final image uses what we just built -RUN set -eux; \ - \ - savedAptMark="$(apt-mark showmanual)"; \ - apt-get update; \ - apt-get install -y --no-install-recommends \ - bison \ - dpkg-dev \ - libgdbm-dev \ - ruby \ - ; \ - rm -rf /var/lib/apt/lists/*; \ - \ - wget -O ruby.tar.xz "https://cache.ruby-lang.org/pub/ruby/${RUBY_MAJOR%-rc}/ruby-$RUBY_VERSION.tar.xz"; \ - echo "$RUBY_DOWNLOAD_SHA256 *ruby.tar.xz" | sha256sum --check --strict; \ - \ - mkdir -p /usr/src/ruby; \ - tar -xJf ruby.tar.xz -C /usr/src/ruby --strip-components=1; \ - rm ruby.tar.xz; \ - \ - cd /usr/src/ruby; \ - \ -# hack in "ENABLE_PATH_CHECK" disabling to suppress: -# warning: Insecure world writable dir - { \ - echo '#define ENABLE_PATH_CHECK 0'; \ - echo; \ - cat file.c; \ - } > file.c.new; \ - mv file.c.new file.c; \ - \ - autoconf; \ - gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ - ./configure \ - --build="$gnuArch" \ - --disable-install-doc \ - --disable-shared \ - ; \ - make -j "$(nproc)"; \ - make install; \ - \ - apt-mark auto '.*' > /dev/null; \ - apt-mark manual $savedAptMark > /dev/null; \ - find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec ldd '{}' ';' \ - | awk '/=>/ { print $(NF-1) }' \ - | sort -u \ - | grep -vE '^/usr/local/lib/' \ - | xargs -r dpkg-query --search \ - | cut -d: -f1 \ - | sort -u \ - | xargs -r apt-mark manual \ - ; \ - apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ - \ - cd /; \ - rm -r /usr/src/ruby; \ -# verify we have no "ruby" packages installed - if dpkg -l | grep -i ruby; then exit 1; fi; \ - [ "$(command -v ruby)" = '/usr/local/bin/ruby' ]; \ -# update gem version - gem update --system;\ -# rough smoke test - ruby --version; \ - gem --version; \ - bundle --version - -# don't create ".bundle" in all our apps -ENV GEM_HOME /usr/local/bundle -ENV BUNDLE_SILENCE_ROOT_WARNING=1 \ - BUNDLE_APP_CONFIG="$GEM_HOME" -ENV PATH $GEM_HOME/bin:$PATH -# adjust permissions of a few directories for running "gem install" as an arbitrary user -RUN mkdir -p "$GEM_HOME" && chmod 1777 "$GEM_HOME" - -CMD [ "irb" ] diff --git a/.gitlab/Dockerfile-3.1 b/.gitlab/Dockerfile-3.1 new file mode 100644 index 00000000000..b28521c11b9 --- /dev/null +++ b/.gitlab/Dockerfile-3.1 @@ -0,0 +1 @@ +FROM ghcr.io/datadog/images-rb/engines/ruby:3.1-centos-gcc diff --git a/.gitlab/Dockerfile-3.1.4 b/.gitlab/Dockerfile-3.1.4 deleted file mode 100644 index c27d01a0081..00000000000 --- a/.gitlab/Dockerfile-3.1.4 +++ /dev/null @@ -1,94 +0,0 @@ -# This is copied from official Docker image -# but compile Ruby with `--disable-shared` and update gem version - -FROM registry.ddbuild.io/images/mirror/buildpack-deps:buster - -# skip installing gem documentation -RUN set -eux; \ - mkdir -p /usr/local/etc; \ - { \ - echo 'install: --no-document'; \ - echo 'update: --no-document'; \ - } >> /usr/local/etc/gemrc - -ENV LANG C.UTF-8 -ENV RUBY_MAJOR 3.1 -ENV RUBY_VERSION 3.1.4 -ENV RUBY_DOWNLOAD_SHA256 1b6d6010e76036c937b9671f4752f065aeca800a6c664f71f6c9a699453af94f - -# some of ruby's build scripts are written in ruby -# we purge system ruby later to make sure our final image uses what we just built -RUN set -eux; \ - \ - savedAptMark="$(apt-mark showmanual)"; \ - apt-get update; \ - apt-get install -y --no-install-recommends \ - bison \ - dpkg-dev \ - libgdbm-dev \ - ruby \ - ; \ - rm -rf /var/lib/apt/lists/*; \ - \ - wget -O ruby.tar.xz "https://cache.ruby-lang.org/pub/ruby/${RUBY_MAJOR%-rc}/ruby-$RUBY_VERSION.tar.xz"; \ - echo "$RUBY_DOWNLOAD_SHA256 *ruby.tar.xz" | sha256sum --check --strict; \ - \ - mkdir -p /usr/src/ruby; \ - tar -xJf ruby.tar.xz -C /usr/src/ruby --strip-components=1; \ - rm ruby.tar.xz; \ - \ - cd /usr/src/ruby; \ - \ -# hack in "ENABLE_PATH_CHECK" disabling to suppress: -# warning: Insecure world writable dir - { \ - echo '#define ENABLE_PATH_CHECK 0'; \ - echo; \ - cat file.c; \ - } > file.c.new; \ - mv file.c.new file.c; \ - \ - autoconf; \ - gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ - ./configure \ - --build="$gnuArch" \ - --disable-install-doc \ - --disable-shared \ - ; \ - make -j "$(nproc)"; \ - make install; \ - \ - apt-mark auto '.*' > /dev/null; \ - apt-mark manual $savedAptMark > /dev/null; \ - find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec ldd '{}' ';' \ - | awk '/=>/ { print $(NF-1) }' \ - | sort -u \ - | grep -vE '^/usr/local/lib/' \ - | xargs -r dpkg-query --search \ - | cut -d: -f1 \ - | sort -u \ - | xargs -r apt-mark manual \ - ; \ - apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ - \ - cd /; \ - rm -r /usr/src/ruby; \ -# verify we have no "ruby" packages installed - if dpkg -l | grep -i ruby; then exit 1; fi; \ - [ "$(command -v ruby)" = '/usr/local/bin/ruby' ]; \ -# update gem version - gem update --system;\ -# rough smoke test - ruby --version; \ - gem --version; \ - bundle --version - -# don't create ".bundle" in all our apps -ENV GEM_HOME /usr/local/bundle -ENV BUNDLE_SILENCE_ROOT_WARNING=1 \ - BUNDLE_APP_CONFIG="$GEM_HOME" -ENV PATH $GEM_HOME/bin:$PATH -# adjust permissions of a few directories for running "gem install" as an arbitrary user -RUN mkdir -p "$GEM_HOME" && chmod 1777 "$GEM_HOME" - -CMD [ "irb" ] diff --git a/.gitlab/Dockerfile-3.2 b/.gitlab/Dockerfile-3.2 new file mode 100644 index 00000000000..9c18c8072ff --- /dev/null +++ b/.gitlab/Dockerfile-3.2 @@ -0,0 +1 @@ +FROM ghcr.io/datadog/images-rb/engines/ruby:3.2-centos-gcc diff --git a/.gitlab/Dockerfile-3.2.2 b/.gitlab/Dockerfile-3.2.2 deleted file mode 100644 index 5f0b67f3a0a..00000000000 --- a/.gitlab/Dockerfile-3.2.2 +++ /dev/null @@ -1,118 +0,0 @@ -# This is copied from official Docker image, but -# compile Ruby with `--disable-shared` and update gem version - -FROM registry.ddbuild.io/images/mirror/buildpack-deps:buster - -# skip installing gem documentation -RUN set -eux; \ - mkdir -p /usr/local/etc; \ - { \ - echo 'install: --no-document'; \ - echo 'update: --no-document'; \ - } >> /usr/local/etc/gemrc - -ENV LANG C.UTF-8 -ENV RUBY_MAJOR 3.2 -ENV RUBY_VERSION 3.2.2 -ENV RUBY_DOWNLOAD_SHA256 4b352d0f7ec384e332e3e44cdbfdcd5ff2d594af3c8296b5636c710975149e23 - -# some of ruby's build scripts are written in ruby -# we purge system ruby later to make sure our final image uses what we just built -RUN set -eux; \ - \ - savedAptMark="$(apt-mark showmanual)"; \ - apt-get update; \ - apt-get install -y --no-install-recommends \ - bison \ - dpkg-dev \ - libgdbm-dev \ - ruby \ - ; \ - rm -rf /var/lib/apt/lists/*; \ - \ - rustArch=; \ - dpkgArch="$(dpkg --print-architecture)"; \ - case "$dpkgArch" in \ - 'amd64') rustArch='x86_64-unknown-linux-gnu'; rustupUrl='https://static.rust-lang.org/rustup/archive/1.25.1/x86_64-unknown-linux-gnu/rustup-init'; rustupSha256='5cc9ffd1026e82e7fb2eec2121ad71f4b0f044e88bca39207b3f6b769aaa799c' ;; \ - 'arm64') rustArch='aarch64-unknown-linux-gnu'; rustupUrl='https://static.rust-lang.org/rustup/archive/1.25.1/aarch64-unknown-linux-gnu/rustup-init'; rustupSha256='e189948e396d47254103a49c987e7fb0e5dd8e34b200aa4481ecc4b8e41fb929' ;; \ - esac; \ - \ - if [ -n "$rustArch" ]; then \ - mkdir -p /tmp/rust; \ - \ - wget -O /tmp/rust/rustup-init "$rustupUrl"; \ - echo "$rustupSha256 */tmp/rust/rustup-init" | sha256sum --check --strict; \ - chmod +x /tmp/rust/rustup-init; \ - \ - export RUSTUP_HOME='/tmp/rust/rustup' CARGO_HOME='/tmp/rust/cargo'; \ - export PATH="$CARGO_HOME/bin:$PATH"; \ - /tmp/rust/rustup-init -y --no-modify-path --profile minimal --default-toolchain '1.66.0' --default-host "$rustArch"; \ - \ - rustc --version; \ - cargo --version; \ - fi; \ - \ - wget -O ruby.tar.xz "https://cache.ruby-lang.org/pub/ruby/${RUBY_MAJOR%-rc}/ruby-$RUBY_VERSION.tar.xz"; \ - echo "$RUBY_DOWNLOAD_SHA256 *ruby.tar.xz" | sha256sum --check --strict; \ - \ - mkdir -p /usr/src/ruby; \ - tar -xJf ruby.tar.xz -C /usr/src/ruby --strip-components=1; \ - rm ruby.tar.xz; \ - \ - cd /usr/src/ruby; \ - \ -# hack in "ENABLE_PATH_CHECK" disabling to suppress: -# warning: Insecure world writable dir - { \ - echo '#define ENABLE_PATH_CHECK 0'; \ - echo; \ - cat file.c; \ - } > file.c.new; \ - mv file.c.new file.c; \ - \ - autoconf; \ - gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ - ./configure \ - --build="$gnuArch" \ - --disable-install-doc \ - --disable-shared \ - ${rustArch:+--enable-yjit} \ - ; \ - make -j "$(nproc)"; \ - make install; \ - \ - rm -rf /tmp/rust; \ - apt-mark auto '.*' > /dev/null; \ - apt-mark manual $savedAptMark > /dev/null; \ - find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec ldd '{}' ';' \ - | awk '/=>/ { print $(NF-1) }' \ - | sort -u \ - | grep -vE '^/usr/local/lib/' \ - | xargs -r dpkg-query --search \ - | cut -d: -f1 \ - | sort -u \ - | xargs -r apt-mark manual \ - ; \ - apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ - \ - cd /; \ - rm -r /usr/src/ruby; \ -# verify we have no "ruby" packages installed - if dpkg -l | grep -i ruby; then exit 1; fi; \ - [ "$(command -v ruby)" = '/usr/local/bin/ruby' ]; \ -# update gem version - gem update --system;\ -# rough smoke test - ruby --version; \ - gem --version; \ - bundle --version - -# don't create ".bundle" in all our apps -ENV GEM_HOME /usr/local/bundle -ENV BUNDLE_SILENCE_ROOT_WARNING=1 \ - BUNDLE_APP_CONFIG="$GEM_HOME" -ENV PATH $GEM_HOME/bin:$PATH -# adjust permissions of a few directories for running "gem install" as an arbitrary user -RUN mkdir -p "$GEM_HOME" && chmod 1777 "$GEM_HOME" - -CMD [ "irb" ] diff --git a/.gitlab/Dockerfile-3.3 b/.gitlab/Dockerfile-3.3 new file mode 100644 index 00000000000..d854cb28efd --- /dev/null +++ b/.gitlab/Dockerfile-3.3 @@ -0,0 +1 @@ +FROM ghcr.io/datadog/images-rb/engines/ruby:3.3-centos-gcc diff --git a/.gitlab/Dockerfile-3.4 b/.gitlab/Dockerfile-3.4 new file mode 100644 index 00000000000..8159a223623 --- /dev/null +++ b/.gitlab/Dockerfile-3.4 @@ -0,0 +1 @@ +FROM ghcr.io/datadog/images-rb/engines/ruby:3.4-centos-gcc diff --git a/lib-injection/README.md b/lib-injection/README.md index 15da0b805e4..cc8478bdc32 100644 --- a/lib-injection/README.md +++ b/lib-injection/README.md @@ -41,9 +41,9 @@ Currently, we support | Environment| version | |---|---| -| Ruby | `2.7`, `3.0`, `3.1`, `3.2`| +| Ruby | `2.7`, `3.0`, `3.1`, `3.2`, `3.3`| | Arch | `amd64`, `arm64` | -| glibc | 2.28+ | +| 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. diff --git a/lib-injection/host_inject.rb b/lib-injection/host_inject.rb index 2db6a690e7d..dca4eb5b4af 100644 --- a/lib-injection/host_inject.rb +++ b/lib-injection/host_inject.rb @@ -87,7 +87,7 @@ 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'].freeze + 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 diff --git a/lib-injection/requirements.json b/lib-injection/requirements.json index c753ec54b45..14357abb53c 100644 --- a/lib-injection/requirements.json +++ b/lib-injection/requirements.json @@ -6,13 +6,13 @@ { "arch": "x64", "supported": true, - "min": "2.27", + "min": "2.17", "description": "libffi needs memfd_create" }, { "arch": "arm64", "supported": true, - "min": "2.27", + "min": "2.17", "description": "libffi needs memfd_create" } ], diff --git a/lib-injection/requirements.rb b/lib-injection/requirements.rb index e63e05b736f..109289d4d67 100755 --- a/lib-injection/requirements.rb +++ b/lib-injection/requirements.rb @@ -11,13 +11,13 @@ def requirements { 'arch' => 'x64', 'supported' => true, - 'min' => '2.27', + 'min' => '2.17', 'description' => 'libffi needs memfd_create', }, { 'arch' => 'arm64', 'supported' => true, - 'min' => '2.27', + 'min' => '2.17', 'description' => 'libffi needs memfd_create', }, ], diff --git a/lib-injection/test_allow.json b/lib-injection/test_allow.json index cdd2d082856..e9cbc895a29 100644 --- a/lib-injection/test_allow.json +++ b/lib-injection/test_allow.json @@ -1,6 +1,6 @@ [ - {"name": "✅ 2.27 glibc x86" , "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64" , "libc": "glibc:2.27"}}, - {"name": "✅ 2.27 glibc arm64", "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm64", "libc": "glibc:2.27"}}, + {"name": "✅ 2.17 glibc x86" , "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64" , "libc": "glibc:2.17"}}, + {"name": "✅ 2.17 glibc arm64", "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm64", "libc": "glibc:2.17"}}, {"name": "✅ ruby" , "filepath": "/some/path/ruby", "args": ["ruby", "/some/path/to/foo.rb"], "envars": [], "host": {"os": "linux", "arch": "arm64", "libc": "glibc:2.27"}}, {"name": "✅ ruby" , "filepath": "/some/path/ruby", "args": ["ruby", "some/path/to/foo.rb"], "envars": [], "host": {"os": "linux", "arch": "arm64", "libc": "glibc:2.27"}}, diff --git a/lib-injection/test_block.json b/lib-injection/test_block.json index a39fd80efd3..f3af160664d 100644 --- a/lib-injection/test_block.json +++ b/lib-injection/test_block.json @@ -4,11 +4,11 @@ {"name": "❌ musl x64" ,"filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64" , "libc": "musl:"}}, {"name": "❌ musl arm64" ,"filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm64" , "libc": "musl:"}}, - {"name": "❌ 2.27 glibc x86" , "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x86" , "libc": "glibc:2.27"}}, - {"name": "❌ 2.27 glibc arm" , "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm" , "libc": "glibc:2.27"}}, + {"name": "❌ 2.17 glibc x86" , "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x86" , "libc": "glibc:2.17"}}, + {"name": "❌ 2.17 glibc arm" , "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm" , "libc": "glibc:2.17"}}, - {"name": "❌ 2.26 glibc x64" , "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64" , "libc": "glibc:2.26"}}, - {"name": "❌ 2.26 glibc arm64", "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm64", "libc": "glibc:2.26"}}, + {"name": "❌ 2.16 glibc x64" , "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64" , "libc": "glibc:2.16"}}, + {"name": "❌ 2.16 glibc arm64", "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm64", "libc": "glibc:2.16"}}, {"name": "❌ gem", "filepath": "/path/to/ruby", "args": ["/path/to/ruby", "/path/to/gem" ], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.40"}}, {"name": "❌ gem install", "filepath": "/path/to/ruby", "args": ["/path/to/ruby", "/path/to/gem", "install" ], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.40"}}, diff --git a/lib/datadog/di/instrumenter.rb b/lib/datadog/di/instrumenter.rb index e9fea6b74e7..7f72574eb03 100644 --- a/lib/datadog/di/instrumenter.rb +++ b/lib/datadog/di/instrumenter.rb @@ -103,6 +103,7 @@ def hook_method(probe, &block) # target method here. end rate_limiter = probe.rate_limiter + settings = self.settings mod = Module.new do define_method(method_name) do |*args, **kwargs| # steep:ignore @@ -110,7 +111,9 @@ def hook_method(probe, &block) # Arguments may be mutated by the method, therefore # they need to be serialized prior to method invocation. entry_args = if probe.capture_snapshot? - serializer.serialize_args(args, kwargs) + serializer.serialize_args(args, kwargs, + depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth, + attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count) end rv = nil # Under Ruby 2.6 we cannot just call super(*args, **kwargs) diff --git a/lib/datadog/di/probe.rb b/lib/datadog/di/probe.rb index 6e03ca2e5b6..4f0ac2916b1 100644 --- a/lib/datadog/di/probe.rb +++ b/lib/datadog/di/probe.rb @@ -36,7 +36,9 @@ class Probe def initialize(id:, type:, file: nil, line_no: nil, type_name: nil, method_name: nil, - template: nil, capture_snapshot: false, max_capture_depth: nil, rate_limit: nil) + template: nil, capture_snapshot: false, max_capture_depth: nil, + max_capture_attribute_count: nil, + rate_limit: nil) # Perform some sanity checks here to detect unexpected attribute # combinations, in order to not do them in subsequent code. unless KNOWN_TYPES.include?(type) @@ -64,6 +66,7 @@ def initialize(id:, type:, @template = template @capture_snapshot = !!capture_snapshot @max_capture_depth = max_capture_depth + @max_capture_attribute_count = max_capture_attribute_count # These checks use instance methods that have more complex logic # than checking a single argument value. To avoid duplicating @@ -91,6 +94,10 @@ def initialize(id:, type:, # the global default will be used. attr_reader :max_capture_depth + # Configured maximum capture attribute count. Can be nil in which case + # the global default will be used. + attr_reader :max_capture_attribute_count + # Rate limit in effect, in invocations per second. Always present. attr_reader :rate_limit diff --git a/lib/datadog/di/probe_builder.rb b/lib/datadog/di/probe_builder.rb index 7c759fb0fd1..ce1ef4b14db 100644 --- a/lib/datadog/di/probe_builder.rb +++ b/lib/datadog/di/probe_builder.rb @@ -37,6 +37,7 @@ module ProbeBuilder template: config["template"], capture_snapshot: !!config["captureSnapshot"], max_capture_depth: config["capture"]&.[]("maxReferenceDepth"), + max_capture_attribute_count: config["capture"]&.[]("maxFieldCount"), rate_limit: config["sampling"]&.[]("snapshotsPerSecond"), ) rescue KeyError => exc diff --git a/lib/datadog/di/probe_notification_builder.rb b/lib/datadog/di/probe_notification_builder.rb index c8634ac4364..f9be3bea746 100644 --- a/lib/datadog/di/probe_notification_builder.rb +++ b/lib/datadog/di/probe_notification_builder.rb @@ -65,14 +65,18 @@ def build_snapshot(probe, rv: nil, snapshot: nil, path: nil, arguments: if serialized_entry_args serialized_entry_args else - (args || kwargs) && serializer.serialize_args(args, kwargs) + (args || kwargs) && serializer.serialize_args(args, kwargs, + depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth, + attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count) end, throwable: nil, # standard:enable all }, return: { arguments: { - "@return": serializer.serialize_value(rv), + "@return": serializer.serialize_value(rv, + depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth, + attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count), }, throwable: nil, }, @@ -80,7 +84,9 @@ def build_snapshot(probe, rv: nil, snapshot: nil, path: nil, elsif probe.line? { lines: snapshot && { - probe.line_no => {locals: serializer.serialize_vars(snapshot)}, + probe.line_no => {locals: serializer.serialize_vars(snapshot, + depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth, + attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count,)}, }, } end @@ -121,7 +127,7 @@ def build_snapshot(probe, rv: nil, snapshot: nil, path: nil, }, # In python tracer duration is under debugger.snapshot, # but UI appears to expect it here at top level. - duration: duration ? (duration * 10**9).to_i : nil, + duration: duration ? (duration * 10**9).to_i : 0, host: nil, logger: { name: probe.file, @@ -139,7 +145,7 @@ def build_snapshot(probe, rv: nil, snapshot: nil, path: nil, "dd.span_id": Datadog::Tracing.active_span&.id&.to_s, ddsource: 'dd_debugger', message: probe.template && evaluate_template(probe.template, - duration: duration ? duration * 1000 : nil), + duration: duration ? duration * 1000 : 0), timestamp: timestamp, } end diff --git a/lib/datadog/di/serializer.rb b/lib/datadog/di/serializer.rb index 511061e5dfc..953e6ec7205 100644 --- a/lib/datadog/di/serializer.rb +++ b/lib/datadog/di/serializer.rb @@ -82,7 +82,9 @@ def initialize(settings, redactor, telemetry: nil) # between positional and keyword arguments. We convert positional # arguments to keyword arguments ("arg1", "arg2", ...) and ensure # the positional arguments are listed first. - def serialize_args(args, kwargs) + def serialize_args(args, kwargs, + depth: settings.dynamic_instrumentation.max_capture_depth, + attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count) counter = 0 combined = args.each_with_object({}) do |value, c| counter += 1 @@ -90,16 +92,18 @@ def serialize_args(args, kwargs) # kwargs when they are merged below. c[:"arg#{counter}"] = value end.update(kwargs) - serialize_vars(combined) + serialize_vars(combined, depth: depth, attribute_count: attribute_count) end # Serializes variables captured by a line probe. # # These are normally local variables that exist on a particular line # of executed code. - def serialize_vars(vars) + def serialize_vars(vars, + depth: settings.dynamic_instrumentation.max_capture_depth, + attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count) vars.each_with_object({}) do |(k, v), agg| - agg[k] = serialize_value(v, name: k) + agg[k] = serialize_value(v, name: k, depth: depth, attribute_count: attribute_count) end end @@ -115,7 +119,11 @@ def serialize_vars(vars) # (integers, strings, arrays, hashes). # # Respects string length, collection size and traversal depth limits. - def serialize_value(value, name: nil, depth: settings.dynamic_instrumentation.max_capture_depth, type: nil) + def serialize_value(value, name: nil, + depth: settings.dynamic_instrumentation.max_capture_depth, + attribute_count: nil, + type: nil) + attribute_count ||= settings.dynamic_instrumentation.max_capture_attribute_count cls = type || value.class begin if redactor.redact_type?(value) @@ -203,7 +211,6 @@ def serialize_value(value, name: nil, depth: settings.dynamic_instrumentation.ma serialized.update(notCapturedReason: "depth") else fields = {} - max = settings.dynamic_instrumentation.max_capture_attribute_count cur = 0 # MRI and JRuby 9.4.5+ preserve instance variable definition @@ -229,7 +236,7 @@ def serialize_value(value, name: nil, depth: settings.dynamic_instrumentation.ma ivars = value.instance_variables ivars.each do |ivar| - if cur >= max + if cur >= attribute_count serialized.update(notCapturedReason: "fieldCount", fields: fields) break end diff --git a/sig/datadog/di/probe.rbs b/sig/datadog/di/probe.rbs index d492b650414..39db4113751 100644 --- a/sig/datadog/di/probe.rbs +++ b/sig/datadog/di/probe.rbs @@ -22,7 +22,7 @@ module Datadog @rate_limiter: Datadog::Core::RateLimiter def initialize: (id: String, type: Symbol, ?file: String?, ?line_no: Integer?, ?type_name: String?, ?method_name: String?, ?template: String?, ?capture_snapshot: bool, - ?max_capture_depth: Integer, ?rate_limit: Integer) -> void + ?max_capture_depth: Integer, ?max_capture_attribute_count: Integer?, ?rate_limit: Integer) -> void attr_reader id: String @@ -35,6 +35,10 @@ module Datadog attr_reader type_name: String? attr_reader method_name: String? + + attr_reader max_capture_depth: Integer? + + attr_reader max_capture_attribute_count: Integer? attr_reader template: String attr_reader rate_limiter: Datadog::Core::RateLimiter diff --git a/sig/datadog/di/serializer.rbs b/sig/datadog/di/serializer.rbs index acaacece168..733501ff328 100644 --- a/sig/datadog/di/serializer.rbs +++ b/sig/datadog/di/serializer.rbs @@ -15,12 +15,12 @@ module Datadog attr_reader telemetry: Core::Telemetry::Component - def serialize_args: (untyped args, untyped kwargs) -> untyped - def serialize_vars: (untyped vars) -> untyped - def serialize_value: (untyped value, ?name: String, ?depth: Integer) -> untyped + def serialize_args: (untyped args, untyped kwargs, ?depth: Integer, ?attribute_count: Integer?) -> untyped + def serialize_vars: (untyped vars, ?depth: Integer, ?attribute_count: Integer?) -> untyped + def serialize_value: (untyped value, ?name: String, ?depth: Integer, ?attribute_count: Integer?) -> untyped def self.register: (?condition: Proc) { - (serializer: Serializer, value: untyped, name: Symbol, depth: Integer) -> untyped } -> void + (serializer: Serializer, value: untyped, name: Symbol, depth: Integer, ?attribute_count: Integer?) -> untyped } -> void private def class_name: (untyped cls) -> untyped diff --git a/spec/datadog/core/environment/execution_spec.rb b/spec/datadog/core/environment/execution_spec.rb index 78fc9f2df16..8f035c3a54a 100644 --- a/spec/datadog/core/environment/execution_spec.rb +++ b/spec/datadog/core/environment/execution_spec.rb @@ -112,6 +112,15 @@ def test_it_does_something_useful let(:script) do <<-RUBY + # Under Ruby 3.0 through 3.2 there is a weird error that occurs + # in CI where two copies of psych get loaded in the same process, + # and even more strangely the first version is a newer one from + # gem and the second one is the older one from Ruby standard + # library. Try to work around this situation by forcing psych + # to be loaded from (some) gem. + # We still don't know exactly what is causing the original issue. + gem 'psych' + require 'bundler/inline' gemfile(true) do @@ -213,18 +222,16 @@ class << Gem it 'returns true' do Dir.mktmpdir do |dir| Dir.chdir(dir) do - Bundler.with_unbundled_env do - FileUtils.mkdir_p('features/support') - - # Add our script to `env.rb`, which is always run before any feature is executed. - File.write('features/support/env.rb', repl_script) + FileUtils.mkdir_p('features/support') - out, err, = Bundler.with_unbundled_env do - Open3.capture3('ruby', stdin_data: script) - end + # Add our script to `env.rb`, which is always run before any feature is executed. + File.write('features/support/env.rb', repl_script) - expect("--OUT.\n#{out}\n--ERR\n#{err}").to include('ACTUAL:true') + _, err, = Bundler.with_unbundled_env do + Open3.capture3('ruby', stdin_data: script) end + + expect(err).to include('ACTUAL:true') end end end diff --git a/spec/datadog/di/instrumenter_spec.rb b/spec/datadog/di/instrumenter_spec.rb index cc416d8f628..02ec4acc191 100644 --- a/spec/datadog/di/instrumenter_spec.rb +++ b/spec/datadog/di/instrumenter_spec.rb @@ -12,6 +12,7 @@ allow(settings.dynamic_instrumentation).to receive(:enabled).and_return(true) allow(settings.dynamic_instrumentation.internal).to receive(:untargeted_trace_points).and_return(false) allow(settings.dynamic_instrumentation).to receive(:max_capture_depth).and_return(2) + allow(settings.dynamic_instrumentation).to receive(:max_capture_attribute_count).and_return(2) allow(settings.dynamic_instrumentation).to receive(:redacted_type_names).and_return([]) allow(settings.dynamic_instrumentation).to receive(:redacted_identifiers).and_return([]) end diff --git a/spec/datadog/di/integration/probe_notification_builder_spec.rb b/spec/datadog/di/integration/probe_notification_builder_spec.rb index 48a408c3fac..5b1a9a0f993 100644 --- a/spec/datadog/di/integration/probe_notification_builder_spec.rb +++ b/spec/datadog/di/integration/probe_notification_builder_spec.rb @@ -28,6 +28,7 @@ allow(settings).to receive(:enabled).and_return(true) allow(settings).to receive(:untargeted_trace_points).and_return(false) allow(settings).to receive(:max_capture_depth).and_return(2) + allow(settings).to receive(:max_capture_attribute_count).and_return(2) allow(settings).to receive(:max_capture_string_length).and_return(20) allow(settings).to receive(:max_capture_collection_size).and_return(20) allow(settings).to receive(:redacted_type_names).and_return([]) diff --git a/spec/datadog/di/probe_builder_spec.rb b/spec/datadog/di/probe_builder_spec.rb index c2d1738ff80..3dcbbb5d9d0 100644 --- a/spec/datadog/di/probe_builder_spec.rb +++ b/spec/datadog/di/probe_builder_spec.rb @@ -22,7 +22,7 @@ "captureSnapshot" => false, # Use a value different from our library default to ensure that # it is correctly processed. - "capture" => {"maxReferenceDepth" => 33}, + "capture" => {"maxReferenceDepth" => 33, 'maxFieldCount' => 34}, # Use a value different from our library default to ensure that # it is correctly processed. "sampling" => {"snapshotsPerSecond" => 4500}, @@ -37,6 +37,7 @@ expect(probe.type_name).to be nil expect(probe.method_name).to be nil expect(probe.max_capture_depth).to eq 33 + expect(probe.max_capture_attribute_count).to eq 34 expect(probe.rate_limit).to eq 4500 expect(probe.line?).to be true diff --git a/spec/datadog/di/probe_notification_builder_spec.rb b/spec/datadog/di/probe_notification_builder_spec.rb index 7d1b0804703..5f25bf2dcdc 100644 --- a/spec/datadog/di/probe_notification_builder_spec.rb +++ b/spec/datadog/di/probe_notification_builder_spec.rb @@ -42,32 +42,140 @@ end describe '#build_received' do - it 'returns a hash' do - expect(builder.build_received(probe)).to be_a(Hash) + let(:payload) do + builder.build_received(probe) + end + + let(:expected) do + { + ddsource: 'dd_debugger', + debugger: { + diagnostics: { + parentId: nil, + probeId: '123', + probeVersion: 0, + runtimeId: String, + status: 'RECEIVED', + }, + }, + message: "Probe 123 has been received correctly", + service: 'test service', + timestamp: Integer, + } + end + + it 'returns a hash with expected contents' do + expect(payload).to be_a(Hash) + expect(payload).to match(expected) end end describe '#build_installed' do - it 'returns a hash' do - expect(builder.build_installed(probe)).to be_a(Hash) + let(:payload) do + builder.build_installed(probe) + end + + let(:expected) do + { + ddsource: 'dd_debugger', + debugger: { + diagnostics: { + parentId: nil, + probeId: '123', + probeVersion: 0, + runtimeId: String, + status: 'INSTALLED', + }, + }, + message: "Probe 123 has been instrumented correctly", + service: 'test service', + timestamp: Integer, + } + end + + it 'returns a hash with expected contents' do + expect(payload).to be_a(Hash) + expect(payload).to match(expected) end end describe '#build_emitting' do - it 'returns a hash' do - expect(builder.build_emitting(probe)).to be_a(Hash) + let(:payload) do + builder.build_emitting(probe) + end + + let(:expected) do + { + ddsource: 'dd_debugger', + debugger: { + diagnostics: { + parentId: nil, + probeId: '123', + probeVersion: 0, + runtimeId: String, + status: 'EMITTING', + }, + }, + message: "Probe 123 is emitting", + service: 'test service', + timestamp: Integer, + } + end + + it 'returns a hash with expected contents' do + expect(payload).to be_a(Hash) + expect(payload).to match(expected) end end describe '#build_executed' do + let(:payload) { builder.build_executed(probe) } + context 'with template' do let(:probe) do Datadog::DI::Probe.new(id: '123', type: :log, file: 'X', line_no: 1, template: 'hello world') end - it 'returns a hash' do - expect(builder.build_executed(probe)).to be_a(Hash) + let(:expected) do + { + ddsource: 'dd_debugger', + "dd.span_id": nil, + "dd.trace_id": nil, + "debugger.snapshot": { + captures: nil, + evaluationErrors: [], + id: String, + language: 'ruby', + probe: { + id: '123', + location: { + file: nil, + lines: [1], + }, + version: 0, + }, + stack: nil, + timestamp: Integer, + }, + message: "hello world", + service: 'test service', + timestamp: Integer, + logger: { + method: 'no_method', + name: 'X', + thread_id: nil, + thread_name: 'Thread.main', + version: 2, + }, + duration: 0, + host: nil, + } + end + + it 'returns a hash with expected contents' do + expect(payload).to be_a(Hash) + expect(payload).to match(expected) end end @@ -77,8 +185,45 @@ capture_snapshot: false) end - it 'returns a hash' do - expect(builder.build_executed(probe)).to be_a(Hash) + let(:expected) do + { + ddsource: 'dd_debugger', + "dd.span_id": nil, + "dd.trace_id": nil, + "debugger.snapshot": { + captures: nil, + evaluationErrors: [], + id: String, + language: 'ruby', + probe: { + id: '123', + location: { + file: nil, + lines: [1], + }, + version: 0, + }, + stack: nil, + timestamp: Integer, + }, + message: nil, + service: 'test service', + timestamp: Integer, + logger: { + method: 'no_method', + name: 'X', + thread_id: nil, + thread_name: 'Thread.main', + version: 2, + }, + duration: 0, + host: nil, + } + end + + it 'returns a hash with expected contents' do + expect(payload).to be_a(Hash) + expect(payload).to match(expected) end end @@ -91,13 +236,92 @@ let(:trace_point) do instance_double(TracePoint).tap do |tp| # Returns an empty binding - expect(tp).to receive(:binding).and_return(binding) + expect(tp).to receive(:binding).and_return(get_binding) expect(tp).to receive(:path).and_return('/foo.rb') end end - it 'returns a hash' do - expect(builder.build_executed(probe, trace_point: trace_point)).to be_a(Hash) + let(:get_binding) do + x = 1 + binding + end + + let(:payload) do + builder.build_executed(probe, trace_point: trace_point) + end + + let(:expected) do + { + ddsource: 'dd_debugger', + "dd.span_id": nil, + "dd.trace_id": nil, + "debugger.snapshot": { + captures: { + lines: { + 1 => { + locals: local_captures, + }, + }, + }, + evaluationErrors: [], + id: String, + language: 'ruby', + probe: { + id: '123', + location: { + file: '/foo.rb', + lines: [1], + }, + version: 0, + }, + stack: nil, + timestamp: Integer, + }, + message: nil, + service: 'test service', + timestamp: Integer, + logger: { + method: 'no_method', + name: 'X', + thread_id: nil, + thread_name: 'Thread.main', + version: 2, + }, + duration: 0, + host: nil, + } + end + + shared_examples 'returns a hash with expected contents' do + it 'returns a hash with expected contents' do + expect(payload).to be_a(Hash) + expect(payload).to match(expected) + end + end + + context 'when binding is empty' do + let(:get_binding) do + binding + end + + let(:local_captures) do + {} + end + + include_examples 'returns a hash with expected contents' + end + + context 'when binding is not empty' do + let(:get_binding) do + x = 1 + binding + end + + let(:local_captures) do + {x: {type: 'Integer', value: '1'}} + end + + include_examples 'returns a hash with expected contents' end end end