diff --git a/.github/workflows/lib-injection.yml b/.github/workflows/lib-injection.yml new file mode 100644 index 00000000000..f5ec7211950 --- /dev/null +++ b/.github/workflows/lib-injection.yml @@ -0,0 +1,59 @@ +name: "Library Injection" +on: + # Build each branch for testing + push: + +jobs: + build-and-publish-test-image: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + - name: Login to Docker + run: docker login -u publisher -p ${{ secrets.GITHUB_TOKEN }} ghcr.io + - name: Docker Build + uses: docker/build-push-action@v3 + with: + push: true + tags: ghcr.io/datadog/dd-trace-rb/dd-lib-ruby-init:${{ github.sha }} + platforms: 'linux/amd64,linux/arm64/v8' + build-args: DDTRACE_RUBY_SHA=${{ github.sha }} + context: ./lib-injection + + test: + needs: + - build-and-publish-test-image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + strategy: + matrix: + lib-injection-connection: ['network'] + lib-injection-use-admission-controller: ['', 'use-admission-controller'] + weblog-variant: + - dd-lib-ruby-init-test-rails + - dd-lib-ruby-init-test-rails-explicit + - dd-lib-ruby-init-test-rails-gemsrb + fail-fast: false + env: + TEST_LIBRARY: ruby + WEBLOG_VARIANT: ${{ matrix.weblog-variant }} + LIBRARY_INJECTION_CONNECTION: ${{ matrix.lib-injection-connection }} + LIBRARY_INJECTION_ADMISSION_CONTROLLER: ${{ matrix.lib-injection-use-admission-controller }} + DOCKER_REGISTRY_IMAGES_PATH: ghcr.io/datadog + DOCKER_IMAGE_TAG: ${{ github.sha }} + BUILDX_PLATFORMS: linux/amd64,linux/arm64/v8 + steps: + - name: lib-injection test runner + id: lib-injection-test-runner + uses: DataDog/system-tests/lib-injection/runner@1af3241d5b6a928199528a8cbfc5698564f5d260 + with: + docker-registry: ghcr.io + docker-registry-username: ${{ github.repository_owner }} + docker-registry-password: ${{ secrets.GITHUB_TOKEN }} + test-script: ./lib-injection/run-manual-lib-injection.sh diff --git a/.github/workflows/release-lib-injection.yml b/.github/workflows/release-lib-injection.yml new file mode 100644 index 00000000000..43d3708d166 --- /dev/null +++ b/.github/workflows/release-lib-injection.yml @@ -0,0 +1,33 @@ + +name: "Release Library Injection" +on: + push: + tags: + - 'v*.*.*' + +jobs: + build-and-publish-release-image: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set version + id: version + run: echo "version=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + - name: Get version + run: echo "The selected version is ${{ steps.version.outputs.version }}" + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + - name: Login to Docker + run: docker login -u publisher -p ${{ secrets.GITHUB_TOKEN }} ghcr.io + - name: Docker Build + uses: docker/build-push-action@v3 + with: + push: true + tags: ghcr.io/datadog/dd-trace-rb/dd-lib-ruby-init:${{ steps.version.outputs.version }} + platforms: 'linux/amd64,linux/arm64/v8' + build-args: DDTRACE_RUBY_VERSION=${{ steps.version.outputs.version }} + context: ./lib-injection + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5f47cbd28a8..4ae7235475c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,3 +25,40 @@ deploy_to_reliability_env: UPSTREAM_COMMIT_SHA: $CI_COMMIT_SHA FORCE_TRIGGER: $FORCE_TRIGGER +deploy_to_docker_registries: + stage: deploy + rules: + - if: '$POPULATE_CACHE' + when: never + - if: '$CI_COMMIT_TAG =~ /^v.*/' + when: delayed + start_in: 1 day + - when: manual + allow_failure: true + trigger: + project: DataDog/public-images + branch: main + strategy: depend + variables: + IMG_SOURCES: ghcr.io/datadog/dd-trace-rb/dd-lib-ruby-init:$CI_COMMIT_TAG + IMG_DESTINATIONS: dd-lib-ruby-init:$CI_COMMIT_TAG + IMG_SIGNING: "false" + +deploy_latest_tag_to_docker_registries: + stage: deploy + rules: + - if: '$POPULATE_CACHE' + when: never + - if: '$CI_COMMIT_TAG =~ /^v.*/' + when: delayed + start_in: 1 day + - when: manual + allow_failure: true + trigger: + project: DataDog/public-images + branch: main + strategy: depend + variables: + IMG_SOURCES: ghcr.io/datadog/dd-trace-rb/dd-lib-ruby-init:$CI_COMMIT_TAG + IMG_DESTINATIONS: dd-lib-ruby-init:latest + IMG_SIGNING: "false" diff --git a/lib-injection/Dockerfile b/lib-injection/Dockerfile new file mode 100644 index 00000000000..1f65949ac86 --- /dev/null +++ b/lib-injection/Dockerfile @@ -0,0 +1,24 @@ +# This image provides the files needed to install the dd-trace-rb +# and auto instrument Ruby applications in containerized environments. +FROM busybox + +# Set high UID to prevent possible conflict with existing users: http://www.linfo.org/uid.html +ARG UID=10000 + +ARG DDTRACE_RUBY_VERSION +ENV DDTRACE_RUBY_VERSION=$DDTRACE_RUBY_VERSION + +ARG DDTRACE_RUBY_SHA +ENV DDTRACE_RUBY_SHA=$DDTRACE_RUBY_SHA + +RUN addgroup -g 10000 -S datadog && \ + adduser -u ${UID} -S datadog -G datadog + +USER ${UID} +WORKDIR /datadog-init +ADD auto_inject.rb /datadog-init/auto_inject.rb + +RUN sed -i "s~~${DDTRACE_RUBY_SHA}~g" /datadog-init/auto_inject.rb +RUN sed -i "s~~${DDTRACE_RUBY_VERSION}~g" /datadog-init/auto_inject.rb + +ADD copy-lib.sh /datadog-init/copy-lib.sh diff --git a/lib-injection/auto_inject.rb b/lib-injection/auto_inject.rb new file mode 100644 index 00000000000..a548fb200e6 --- /dev/null +++ b/lib-injection/auto_inject.rb @@ -0,0 +1,77 @@ +return if ENV['DD_TRACE_SKIP_LIB_INJECTION'] == 'true' + +begin + require 'open3' + + failure_prefix = 'Datadog lib injection failed:' + support_message = 'For help solving this issue, please contact Datadog support at https://docs.datadoghq.com/help/.' + + _, status = Open3.capture2e({'DD_TRACE_SKIP_LIB_INJECTION' => 'true'}, 'bundle show ddtrace') + + if status.success? + STDOUT.puts '[ddtrace] ddtrace already installed... skipping auto-injection' if ENV['DD_TRACE_DEBUG'] == 'true' + return + end + + require 'bundler' + require 'shellwords' + + if Bundler.frozen_bundle? + STDERR.puts "[ddtrace] #{failure_prefix} Cannot inject with frozen Gemfile, run `bundle config unset deployment` to allow lib injection. To learn more about bundler deployment, check https://bundler.io/guides/deploying.html#deploying-your-application. #{support_message}" + return + end + + # `version` and `sha` should be replaced by docker build arguments + version = "" + sha = "" + + bundle_add_ddtrace_cmd = + if !version.empty? + # For public release + "bundle add ddtrace --require ddtrace/auto_instrument --version #{version.gsub(/^v/, '').shellescape}" + elsif !sha.empty? + # For internal testing + "bundle add ddtrace --require ddtrace/auto_instrument --github datadog/dd-trace-rb --ref #{sha.shellescape}" + end + + unless bundle_add_ddtrace_cmd + STDERR.puts "[ddtrace] #{failure_prefix} Missing version specification. #{support_message}" + return + end + + STDOUT.puts "[ddtrace] Performing lib injection with `#{bundle_add_ddtrace_cmd}`" if ENV['DD_TRACE_DEBUG'] == 'true' + + gemfile = Bundler::SharedHelpers.default_gemfile + lockfile = Bundler::SharedHelpers.default_lockfile + + datadog_gemfile = gemfile.dirname + "datadog-Gemfile" + datadog_lockfile = lockfile.dirname + "datadog-Gemfile.lock" + + require 'fileutils' + + begin + # Copies for trial + FileUtils.cp gemfile, datadog_gemfile + FileUtils.cp lockfile, datadog_lockfile + + output, status = Open3.capture2e( + { 'DD_TRACE_SKIP_LIB_INJECTION' => 'true', 'BUNDLE_GEMFILE' => datadog_gemfile.to_s }, + bundle_add_ddtrace_cmd + ) + + if status.success? + STDOUT.puts '[ddtrace] Datadog lib injection successfully added ddtrace to the application.' + + FileUtils.cp datadog_gemfile, gemfile + FileUtils.cp datadog_lockfile, lockfile + else + STDERR.puts "[ddtrace] #{failure_prefix} Unable to add ddtrace. Error output:\n#{output.split("\n").map {|l| "[ddtrace] #{l}"}.join("\n")}\n#{support_message}" + end + ensure + # Remove the copies + FileUtils.rm datadog_gemfile + FileUtils.rm datadog_lockfile + end +rescue Exception => e + STDERR.puts "[ddtrace] #{failure_prefix} #{e.class.name} #{e.message}\nBacktrace: #{e.backtrace.join("\n")}\n#{support_message}" +end diff --git a/lib-injection/copy-lib.sh b/lib-injection/copy-lib.sh new file mode 100644 index 00000000000..fa74f8292d7 --- /dev/null +++ b/lib-injection/copy-lib.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +# This script is used by the admission controller to install the library from the +# init container into the application container. +cp auto_inject.rb "$1/auto_inject.rb" diff --git a/spec/ddtrace/release_gem_spec.rb b/spec/ddtrace/release_gem_spec.rb index 090c4381ddc..09577ee616d 100644 --- a/spec/ddtrace/release_gem_spec.rb +++ b/spec/ddtrace/release_gem_spec.rb @@ -35,7 +35,7 @@ /x directories_excluded = %r{ - ^(sig|spec|docs|\.circleci|\.github|benchmarks|gemfiles|integration|tasks|yard|vendor/rbs)/ + ^(sig|spec|docs|\.circleci|\.github|lib-injection|benchmarks|gemfiles|integration|tasks|yard|vendor/rbs)/ }x expect(files)