diff --git a/.ci/snapshoty.yml b/.ci/snapshoty.yml deleted file mode 100644 index ef49bdf10..000000000 --- a/.ci/snapshoty.yml +++ /dev/null @@ -1,53 +0,0 @@ - ---- - -# Version of configuration to use -version: '1.0' - -# You can define a Google Cloud Account to use -account: - # Project id of the service account - project: '${GCS_PROJECT}' - # Private key id of the service account - private_key_id: '${GCS_PRIVATE_KEY_ID}' - # Private key of the service account - private_key: '${GCS_PRIVATE_KEY}' - # Email of the service account - client_email: '${GCS_CLIENT_EMAIL}' - # URI token - token_uri: 'https://oauth2.googleapis.com/token' - -x-metadata: &metadata - # Define static custom metadata - - name: 'custom' - data: - project: 'apm-agent-dotnet' - component: 'agent' - # Add git metadata - - name: 'git' - # Add github_actions metadata - - name: 'github_actions' - - -# List of artifacts -artifacts: - # Path to use for artifacts discovery - - path: './build/output' - # Files pattern to match - files_pattern: 'elastic_apm_profiler_(?P\d+\.\d+\.\d+)-(?P[\w\.]+)-(?P\w+)-(?P\w+)\.zip' - # File layout on GCS bucket - output_pattern: '{project}/{github_branch_name}/elastic-apm-dotnet-profiler-{app_version}-{app_version}-{os}-{arch}-{github_sha_short}.jar' - # List of metadata processors to use. - metadata: *metadata - - path: './build/output' - files_pattern: 'ElasticApmAgent_(?P\d+\.\d+\.\d+)-(?P[\w\.]+)\.zip' - output_pattern: '{project}/{github_branch_name}/elastic-apm-dotnet-agent-{app_version}-{revision}-{github_sha_short}.zip' - metadata: *metadata - - path: './build/output/_packages' - files_pattern: 'Elastic\.Apm\.(?P[\w\.]*)\.(?P\d+\.\d+\.\d+)-(?P[\w\.]+)(-(?P\w+)-(?P[\d-]+))?\.nupkg' - output_pattern: '{project}/{github_branch_name}/elastic-apm-dotnet-{component}-{app_version}-{revision}-{github_sha_short}.nupkg' - metadata: *metadata - - path: './build/output/_packages' - files_pattern: 'Elastic\.Apm\.(?P\d+\.\d+\.\d+)-(?P[\w\.]+)(-(?P\w+)-(?P[\d-]+))?\.nupkg' - output_pattern: '{project}/{github_branch_name}/elastic-apm-dotnet-{app_version}-{revision}-{github_sha_short}.nupkg' - metadata: *metadata diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 2d8ddf3b0..9ec06ba2d 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -7,10 +7,6 @@ There are 4 main stages that run on GitHub actions: * Test * Release -There are some other stages that run for every push on the main branches: - -* [Snapshoty](./snapshoty.yml) - ### Scenarios * Tests should be triggered on branch, tag and PR basis. @@ -39,7 +35,7 @@ The tag release follows the naming convention: `v...`, wher ### OpenTelemetry -There is a GitHub workflow in charge to populate what the workflow run in terms of jobs and steps. Those details can be seen in [here](https://ela.st/oblt-ci-cd-stats) (**NOTE**: only available for Elasticians). +Every workflow and its logs are exported to OpenTelemetry traces/logs/metrics. Those details can be seen [here](https://ela.st/oblt-ci-cd-stats) (**NOTE**: only available for Elasticians). ## Bump automation diff --git a/.github/workflows/bootstrap/action.yml b/.github/workflows/bootstrap/action.yml index 4446c068d..88c6f40a0 100644 --- a/.github/workflows/bootstrap/action.yml +++ b/.github/workflows/bootstrap/action.yml @@ -11,6 +11,10 @@ inputs: description: 'Install azure functions tool chain ("true" or "false")' required: false default: "false" + tc-cloud: + description: 'Bootstrap TestContainers Cloud (TOKEN or "false")' + required: false + default: "false" outputs: agent-version: description: "The current agent version number" @@ -18,6 +22,7 @@ outputs: major-version: description: "The current major version number, semver" value: ${{ steps.dotnet.outputs.major-version }} + runs: using: "composite" @@ -27,7 +32,7 @@ runs: run: | git fetch --prune --unshallow --tags git tag --list - + - uses: actions/cache@v4 with: path: ~/.nuget/packages @@ -56,10 +61,11 @@ runs: # Setup git config - uses: elastic/apm-pipeline-library/.github/actions/setup-git@current - + # install common dependencies - name: Install common dependencies uses: ./.github/workflows/install-dependencies with: rust: '${{ inputs.rust }}' azure: '${{ inputs.azure }}' + tc-cloud: '${{ inputs.tc-cloud }}' diff --git a/.github/workflows/install-dependencies/action.yml b/.github/workflows/install-dependencies/action.yml index cc3e300fe..10552af91 100644 --- a/.github/workflows/install-dependencies/action.yml +++ b/.github/workflows/install-dependencies/action.yml @@ -8,9 +8,13 @@ inputs: required: false default: "false" azure: - description: 'Install azure functions tool chain ("true" or "false")' - required: false - default: "false" + description: 'Install azure functions tool chain ("true" or "false")' + required: false + default: "false" + tc-cloud: + description: 'Bootstrap TestContainers Cloud (TOKEN or "false")' + required: false + default: "false" runs: using: "composite" @@ -55,3 +59,20 @@ runs: shell: cmd run: choco install azure-functions-core-tools -y --no-progress -r --version 4.0.4829 + # TEST CONTAINERS CLOUD + # If no PR event or if a PR event that's caused by a non-fork and non dependabot actor + - name: Setup TestContainers Cloud Client + if: | + inputs.tc-cloud != 'false' + && (github.event_name != 'pull_request' + || (github.event_name == 'pull_request' + && github.event.pull_request.head.repo.fork == false + && github.actor != 'dependabot[bot]' + ) + ) + uses: atomicjar/testcontainers-cloud-setup-action@c335bdbb570ec7c48f72c7d450c077f0a002293e # v1.3.0 + with: + token: ${{ inputs.tc-cloud }} + + + diff --git a/.github/workflows/release-main.yml b/.github/workflows/release-main.yml index 391980b04..6e911b488 100644 --- a/.github/workflows/release-main.yml +++ b/.github/workflows/release-main.yml @@ -28,37 +28,19 @@ jobs: - name: Package with canary suffix run: ./build.sh pack - - - name: Prepare feedz.io - uses: hashicorp/vault-action@v3.0.0 - with: - url: ${{ secrets.VAULT_ADDR }} - method: approle - roleId: ${{ secrets.VAULT_ROLE_ID }} - secretId: ${{ secrets.VAULT_SECRET_ID }} - secrets: | - secret/apm-team/ci/elastic-observability-feedz.io apiKey | REPO_API_KEY ; - secret/apm-team/ci/elastic-observability-feedz.io url | REPO_API_URL - - - name: generate build provenance - uses: github-early-access/generate-build-provenance@main - with: - subject-path: "${{ github.workspace }}/build/output/_packages/*.nupkg" - + # Github packages requires authentication, this is likely going away in the future so for now we publish to feedz.io - name: publish canary packages to feedz.io - run: dotnet nuget push 'build/output/_packages/*.nupkg' -k ${REPO_API_KEY} -s ${REPO_API_URL} --skip-duplicate --no-symbols + run: dotnet nuget push 'build/output/_packages/*.nupkg' -k ${{ secrets.FEEDZ_IO_API_KEY }} -s ${{ secrets.FEEDZ_IO_API_URL }} --skip-duplicate --no-symbols - name: publish canary packages github package repository run: dotnet nuget push 'build/output/_packages/*.nupkg' -k ${{secrets.GITHUB_TOKEN}} -s https://nuget.pkg.github.com/elastic/index.json --skip-duplicate --no-symbols - if: ${{ failure() }} - uses: elastic/apm-pipeline-library/.github/actions/slack-message@current + uses: elastic/oblt-actions/slack/send@v1.7.0 with: - url: ${{ secrets.VAULT_ADDR }} - roleId: ${{ secrets.VAULT_ROLE_ID }} - secretId: ${{ secrets.VAULT_SECRET_ID }} - channel: "#apm-agent-dotnet" + bot-token: ${{ secrets.SLACK_BOT_TOKEN }} + channel-id: "#apm-agent-dotnet" message: | :large_yellow_circle: [${{ github.repository }}] Snapshot could not be published to feedz.io. Build: (<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|here>) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1bc7ae93e..418a9fa34 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,27 +38,15 @@ jobs: - name: Package run: ./build.sh pack - - name: Prepare Nuget - uses: hashicorp/vault-action@v3.0.0 - with: - url: ${{ secrets.VAULT_ADDR }} - method: approle - roleId: ${{ secrets.VAULT_ROLE_ID }} - secretId: ${{ secrets.VAULT_SECRET_ID }} - secrets: | - secret/apm-team/ci/elastic-observability-nuget apiKey | REPO_API_KEY ; - secret/apm-team/ci/elastic-observability-nuget url | REPO_API_URL - - name: Release to Nuget - run: .ci/linux/deploy.sh ${REPO_API_KEY} ${REPO_API_URL} + run: .ci/linux/deploy.sh ${{ secrets.NUGET_API_KEY }} ${{ secrets.NUGET_API_URL }} - - uses: elastic/apm-pipeline-library/.github/actions/docker-login@current + - name: Log in to the Elastic Container registry + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: - registry: docker.elastic.co - secret: secret/observability-team/ci/docker-registry/prod - url: ${{ secrets.VAULT_ADDR }} - roleId: ${{ secrets.VAULT_ROLE_ID }} - secretId: ${{ secrets.VAULT_SECRET_ID }} + registry: ${{ secrets.ELASTIC_DOCKER_REGISTRY }} + username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} + password: ${{ secrets.ELASTIC_DOCKER_PASSWORD }} - name: Build and Push Profiler Docker Image id: docker-push @@ -99,24 +87,20 @@ jobs: gh release upload ${{ github.ref_name }} "${{ env.PREFIX_APM_AGENT }}${{ steps.bootstrap.outputs.agent-version }}${{ env.SUFFIX_APM_AGENT }}" "${{ env.PREFIX_APM_PROFILER }}${{ steps.bootstrap.outputs.agent-version }}${{ env.SUFFIX_APM_PROFILER }}" - if: ${{ success() }} - uses: elastic/apm-pipeline-library/.github/actions/slack-message@current + uses: elastic/oblt-actions/slack/send@v1.7.0 with: - url: ${{ secrets.VAULT_ADDR }} - roleId: ${{ secrets.VAULT_ROLE_ID }} - secretId: ${{ secrets.VAULT_SECRET_ID }} - channel: ${{ env.SLACK_CHANNEL }} + bot-token: ${{ secrets.SLACK_BOT_TOKEN }} + channel-id: ${{ env.SLACK_CHANNEL }} message: | :large_green_circle: [${{ github.repository }}] Release *${{ github.ref_name }}* published. Build: (<${{ env.JOB_URL }}|here>) Release URL: () - if: ${{ failure() }} - uses: elastic/apm-pipeline-library/.github/actions/slack-message@current + uses: elastic/oblt-actions/slack/send@v1.7.0 with: - url: ${{ secrets.VAULT_ADDR }} - roleId: ${{ secrets.VAULT_ROLE_ID }} - secretId: ${{ secrets.VAULT_SECRET_ID }} - channel: ${{ env.SLACK_CHANNEL }} + bot-token: ${{ secrets.SLACK_BOT_TOKEN }} + channel-id: ${{ env.SLACK_CHANNEL }} message: | :large_yellow_circle: [${{ github.repository }}] Release *${{ github.ref_name }}* could not be published. Build: (<${{ env.JOB_URL }}|here>) diff --git a/.github/workflows/snapshoty.yml b/.github/workflows/snapshoty.yml deleted file mode 100644 index d16216ba3..000000000 --- a/.github/workflows/snapshoty.yml +++ /dev/null @@ -1,43 +0,0 @@ ---- -# Publish a snapshot. A "snapshot" is a packaging of the latest *unreleased* APM agent, -# published to a known GCS bucket for use in edge demo/test environments. -name: snapshoty - -on: - workflow_run: - workflows: ['test-linux'] - types: - - completed - branches: - - main - -permissions: - contents: read - -jobs: - upload: - runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'success' }} - steps: - - uses: actions/checkout@v4 - - # used by opbeans .NET to reference the on commit nuget packages - # TODO: update opbeans to use our feedz.io packages - - name: Retrieve the artifact uploaded from `test-linux.yml` workflow - uses: actions/download-artifact@v4 - with: - name: snapshoty-linux - path: build/output - github-token: ${{ secrets.GITHUB_TOKEN }} - run-id: ${{ github.event.workflow_run.id }} - - - name: Display structure of downloaded files - run: find build -type f - - - name: Publish snaphosts - uses: elastic/apm-pipeline-library/.github/actions/snapshoty-simple@current - with: - config: '.ci/snapshoty.yml' - vaultUrl: ${{ secrets.VAULT_ADDR }} - vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} - vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index 837f7607f..73b291dad 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -18,16 +18,38 @@ permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: '${{ github.workflow }}-${{ github.ref }}' cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +defaults: + run: + shell: bash env: NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages + +# 'pack' & 'tests' are required checks in this workflow. + +# To not burn unneeded CI cycles: +# - Our required checks will always succeed if doc only changes are detected. +# - all jobs depend on 'format' to not waste cycles on quickly fixable errors. + jobs: + + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Bootstrap Action Workspace + uses: ./.github/workflows/bootstrap + - name: Format + run: ./build.sh format + + #required step pack: runs-on: ubuntu-latest - + needs: [ 'format' ] steps: - uses: actions/checkout@v4 - name: Bootstrap Action Workspace @@ -35,38 +57,26 @@ jobs: with: rust: 'true' - - name: Format - run: ./build.sh format - - name: Package run: ./build.sh pack - - - uses: actions/upload-artifact@v4 - if: github.event_name == 'push' && startswith(github.ref, 'refs/heads') - with: - name: snapshoty-linux - path: build/output/* - retention-days: 1 + #required step tests: runs-on: ubuntu-latest + needs: [ 'format' ] timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Bootstrap Action Workspace uses: ./.github/workflows/bootstrap - + - name: 'Tests: Unit' - shell: bash run: ./build.sh test --test-suite unit - - name: 'Tests: Integrations' - shell: bash - run: ./build.sh test --test-suite integrations - azure-tests: runs-on: ubuntu-latest + needs: [ 'format', 'tests' ] if: ${{ false }} #if: | # github.event_name != 'pull_request' @@ -98,7 +108,6 @@ jobs: echo "AZURE_RESOURCE_GROUP_PREFIX=ci-dotnet-${GITHUB_RUN_ID}" >> ${GITHUB_ENV} - name: 'Tests: Azure' - shell: bash run: ./build.sh test --test-suite azure - name: 'Teardown tests infra' @@ -107,22 +116,32 @@ jobs: for group in $(az group list --query "[?name | starts_with(@,'${{ env.AZURE_RESOURCE_GROUP_PREFIX }}')]" --out json | jq .[].name --raw-output); do az group delete --name "${group}" --no-wait --yes done + + integration-tests: + runs-on: ubuntu-latest + needs: [ 'format', 'tests' ] + steps: + - uses: actions/checkout@v4 + - name: Bootstrap Action Workspace + uses: ./.github/workflows/bootstrap + - name: 'Tests: Integrations' + run: ./build.sh test --test-suite integrations + startup-hook-tests: runs-on: ubuntu-latest - + needs: [ 'format', 'tests' ] steps: - uses: actions/checkout@v4 - name: Bootstrap Action Workspace uses: ./.github/workflows/bootstrap - name: 'Tests: StartupHooks' - shell: bash run: ./build.sh test --test-suite startuphooks profiler-tests: runs-on: ubuntu-latest - + needs: [ 'format', 'tests' ] steps: - uses: actions/checkout@v4 - name: Bootstrap Action Workspace @@ -132,7 +151,6 @@ jobs: rust: 'true' - name: 'Tests: Profiler' - shell: bash run: ./build.sh test --test-suite profiler - name: Build Profiler Docker Image diff --git a/.github/workflows/test-windows-iis.yml b/.github/workflows/test-windows-iis.yml deleted file mode 100644 index cc799068e..000000000 --- a/.github/workflows/test-windows-iis.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: test-windows-iis - -on: - push: - branches: - - main - - 1.* - paths-ignore: - - '*.md' - - '*.asciidoc' - - 'docs/**' - pull_request: - paths-ignore: - - '*.md' - - '*.asciidoc' - - 'docs/**' - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} - -env: - ELASTIC_STACK_VERSION: '8.4.0' - NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages - -jobs: - - test-iis: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v4 - - name: Bootstrap Action Workspace - uses: ./.github/workflows/bootstrap - - - uses: actions/cache@v4 - with: - path: ~/.nuget/packages - key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.[cf]sproj*') }} - - - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v2 - - - - name: Clean the application - shell: cmd - run: msbuild /t:Clean /p:Configuration=Release - - - name: Restore the application - shell: cmd - run: msbuild /t:Restore /p:Configuration=Release - - - name: Build the application - shell: cmd - run: | - set INCLUDE_CSHARP_TARGETS=true - msbuild ^ - /p:EnforceCodeStyleInBuild=false /p:_SkipUpgradeNetAnalyzersNuGetWarning=true /p:EnableNETAnalyzers=false ^ - -clp:ForceConsoleColor -clp:Summary -verbosity:minimal ^ - /t:Build /p:Configuration=Release /restore - - #- name: Discover Windows Features - # shell: cmd - # run: | - # DISM /online /get-features /format:table - - # TODO See if this really needed - - name: Enable Windows Features - shell: cmd - run: | - DISM /online /enable-feature /featurename:IIS-HttpErrors - DISM /online /enable-feature /featurename:IIS-HttpRedirect - - - name: Ensure AppPool Permissions - shell: cmd - run: | - REM enable permissions for the Application Pool Identity group - icacls C:\Windows\Temp /grant "IIS_IUSRS:(OI)(CI)F" /T - icacls "c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files" /grant "IIS_IUSRS:(OI)(CI)F" /T - icacls %cd% /t /q /grant "IIS_IUSRS:(OI)(CI)(IO)(RX)" - REM enable permissions for the anonymous access group - icacls %cd% /t /q /grant "IUSR:(OI)(CI)(IO)(RX)" - icacls C:\Windows\Temp /grant "IUSR:(OI)(CI)F" /T - icacls "c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files" /grant "IUSR:(OI)(CI)F" /T - - - name: Run tests - shell: cmd - run: | - set ELASTIC_APM_TESTS_FULL_FRAMEWORK_ENABLED=true - set sample_app_log_dir=C:\Elastic_APM_TEMP - if not exist "%sample_app_log_dir%" mkdir "%sample_app_log_dir%" - icacls %sample_app_log_dir% /t /q /grant Everyone:F - set ELASTIC_APM_ASP_NET_FULL_FRAMEWORK_SAMPLE_APP_LOG_FILE=%sample_app_log_dir%\Elastic.Apm.AspNetFullFramework.Tests.SampleApp.log - - dotnet test -c Release test\iis\Elastic.Apm.AspNetFullFramework.Tests --no-build ^ - --verbosity normal ^ - --results-directory build/output ^ - --diag build/output/diag-iis.log ^ - --filter "FullyQualifiedName=Elastic.Apm.AspNetFullFramework.Tests.CustomServiceNodeNameSetViaSettings.Test" ^ - --logger:"junit;LogFilePath=%cd%\build\output\junit-{framework}-{assembly}.xml;MethodFormat=Class;FailureBodyFormat=Verbose" - - - name: Store test results - if: success() || failure() - uses: actions/upload-artifact@v3 - with: - name: test-results-iis - path: build/output/junit-*.xml \ No newline at end of file diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index b7cadf4f6..64ed6fbe5 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -18,7 +18,7 @@ permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: '${{ github.workflow }}-${{ github.ref }}' cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} defaults: @@ -28,32 +28,56 @@ defaults: env: NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages +# 'tests' is a required check in this workflow. + +# To not burn unneeded CI cycles: +# - Our required checks will always succeed if doc only changes are detected. +# - all jobs depend on 'format' to not waste cycles on quickly fixable errors. + jobs: + + format: + runs-on: windows-2022 + steps: + - uses: actions/checkout@v4 + - name: Bootstrap Action Workspace + uses: ./.github/workflows/bootstrap + - name: Format + run: ./build.bat format + + #required step tests: runs-on: windows-2022 + needs: [ 'format' ] timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Bootstrap Action Workspace uses: ./.github/workflows/bootstrap - - - name: Setup Testcontainers Cloud Client - if: github.event_name != 'pull_request' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false - uses: atomicjar/testcontainers-cloud-setup-action@v1 - with: - token: ${{ secrets.TC_CLOUD_TOKEN }} + with: + tc-cloud: ${{ secrets.TC_CLOUD_TOKEN }} - name: 'Tests: Unit' run: ./build.bat test --test-suite unit - name: 'Tests: Integrations' - shell: bash run: ./build.bat test --test-suite integrations + integrations-tests: + runs-on: windows-2022 + needs: [ 'format', 'tests' ] + steps: + - uses: actions/checkout@v4 + - name: Bootstrap Action Workspace + uses: ./.github/workflows/bootstrap + + - name: 'Tests: Integrations' + run: ./build.bat test --test-suite integrations + startup-hook-tests: runs-on: windows-2022 - + needs: [ 'format', 'tests' ] steps: - uses: actions/checkout@v4 - name: Bootstrap Action Workspace @@ -64,20 +88,89 @@ jobs: profiler-tests: runs-on: windows-2022 - # Disable profiler tests for now - if: ${{ false }} + needs: [ 'format', 'tests' ] steps: - uses: actions/checkout@v4 - name: Bootstrap Action Workspace uses: ./.github/workflows/bootstrap with: rust: 'true' + tc-cloud: ${{ secrets.TC_CLOUD_TOKEN }} + + - name: 'Tests: Profiler' + run: ./build.bat test --test-suite profiler + + test-iis: + runs-on: windows-latest + needs: [ 'format', 'tests' ] + + steps: + - uses: actions/checkout@v4 + - name: Bootstrap Action Workspace + uses: ./.github/workflows/bootstrap - - name: Setup Testcontainers Cloud Client - if: github.event_name != 'pull_request' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false - uses: atomicjar/testcontainers-cloud-setup-action@v1 + - uses: actions/cache@v4 with: - token: ${{ secrets.TC_CLOUD_TOKEN }} + path: ~/.nuget/packages + key: "${{ runner.os }}-nuget-${{ hashFiles('**/*.[cf]sproj*') }}" - - name: 'Tests: Profiler' - run: ./build.bat test --test-suite profiler \ No newline at end of file + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2 + + + - name: Clean the application + run: msbuild /t:Clean /p:Configuration=Release + + - name: Restore the application + run: msbuild /t:Restore /p:Configuration=Release + + - name: Build the application + run: | + set INCLUDE_CSHARP_TARGETS=true + msbuild ^ + /p:EnforceCodeStyleInBuild=false /p:_SkipUpgradeNetAnalyzersNuGetWarning=true /p:EnableNETAnalyzers=false ^ + -clp:ForceConsoleColor -clp:Summary -verbosity:minimal ^ + /t:Build /p:Configuration=Release /restore + + #- name: Discover Windows Features + # run: | + # DISM /online /get-features /format:table + + # TODO See if this really needed + - name: Enable Windows Features + run: | + DISM /online /enable-feature /featurename:IIS-HttpErrors + DISM /online /enable-feature /featurename:IIS-HttpRedirect + + - name: Ensure AppPool Permissions + run: | + REM enable permissions for the Application Pool Identity group + icacls C:\Windows\Temp /grant "IIS_IUSRS:(OI)(CI)F" /T + icacls "c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files" /grant "IIS_IUSRS:(OI)(CI)F" /T + icacls %cd% /t /q /grant "IIS_IUSRS:(OI)(CI)(IO)(RX)" + REM enable permissions for the anonymous access group + icacls %cd% /t /q /grant "IUSR:(OI)(CI)(IO)(RX)" + icacls C:\Windows\Temp /grant "IUSR:(OI)(CI)F" /T + icacls "c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files" /grant "IUSR:(OI)(CI)F" /T + + - name: Run tests + run: | + set ELASTIC_APM_TESTS_FULL_FRAMEWORK_ENABLED=true + set sample_app_log_dir=C:\Elastic_APM_TEMP + if not exist "%sample_app_log_dir%" mkdir "%sample_app_log_dir%" + icacls %sample_app_log_dir% /t /q /grant Everyone:F + set ELASTIC_APM_ASP_NET_FULL_FRAMEWORK_SAMPLE_APP_LOG_FILE=%sample_app_log_dir%\Elastic.Apm.AspNetFullFramework.Tests.SampleApp.log + + dotnet test -c Release test\iis\Elastic.Apm.AspNetFullFramework.Tests --no-build ^ + --verbosity normal ^ + --results-directory build/output ^ + --diag build/output/diag-iis.log ^ + --filter "FullyQualifiedName=Elastic.Apm.AspNetFullFramework.Tests.CustomServiceNodeNameSetViaSettings.Test" ^ + --logger:"junit;LogFilePath=%cd%\build\output\junit-{framework}-{assembly}.xml;MethodFormat=Class;FailureBodyFormat=Verbose" + + - name: Store test results + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + name: test-results-iis + path: build/output/junit-*.xml diff --git a/build/scripts/Build.fs b/build/scripts/Build.fs index 9a5982294..58af804bf 100644 --- a/build/scripts/Build.fs +++ b/build/scripts/Build.fs @@ -164,8 +164,8 @@ module Build = let logger = match BuildServer.isGitHubActionsBuild with - | true -> Some "--logger:\"GitHubActions;summary.includePassedTests=false\"" - | fase -> None + | true -> Some "--logger:\"GitHubActions;summary.includePassedTests=false;summary.includeNotFoundTests=false\"" + | _ -> None let filter = match suite with @@ -183,6 +183,7 @@ module Build = @ (match filter with None -> [] | Some f -> ["--filter"; f]) @ (match framework with None -> [] | Some f -> ["-f"; f]) @ (match logger with None -> [] | Some l -> [l]) + @ ["--"; "RunConfiguration.CollectSourceInformation=true"] DotNet.ExecWithTimeout command (TimeSpan.FromMinutes 30) diff --git a/src/Elastic.Apm/Extensions/TransactionExtensions.cs b/src/Elastic.Apm/Extensions/TransactionExtensions.cs index f40038a8d..ef2127eba 100644 --- a/src/Elastic.Apm/Extensions/TransactionExtensions.cs +++ b/src/Elastic.Apm/Extensions/TransactionExtensions.cs @@ -33,17 +33,20 @@ internal static void CollectRequestBody(this ITransaction transaction, bool isFo // Is request body already captured? // We check transaction.IsContextCreated to avoid creating empty Context (that accessing transaction.Context directly would have done). - var hasContext = (transaction is Transaction t && t.IsContextCreated) || transaction.Context != null; + var hasContext = transaction is Transaction { IsContextCreated: true } || transaction.Context != null; if (hasContext && transaction.Context.Request.Body != null && !ReferenceEquals(transaction.Context.Request.Body, Consts.Redacted)) return; + if (transaction.Configuration.CaptureBody.Equals(ConfigConsts.SupportedValues.CaptureBodyOff)) + body = Consts.Redacted; + if (transaction.IsCaptureRequestBodyEnabled(isForError) && IsCaptureRequestBodyEnabledForContentType(transaction, httpRequest?.ContentType, logger)) body = httpRequest.ExtractBody(logger, transaction.Configuration); - // According to the documentation - the default value of 'body' is '[Redacted]' - transaction.Context.Request.Body = body ?? Consts.Redacted; + if (transaction.Context != null) + transaction.Context.Request.Body = body; } internal static bool IsCaptureRequestBodyEnabled(this ITransaction transaction, bool isForError) => diff --git a/src/Elastic.Apm/Helpers/SystemInfoHelper.cs b/src/Elastic.Apm/Helpers/SystemInfoHelper.cs index b5647da9b..6c1371aa1 100644 --- a/src/Elastic.Apm/Helpers/SystemInfoHelper.cs +++ b/src/Elastic.Apm/Helpers/SystemInfoHelper.cs @@ -5,6 +5,7 @@ using System; using System.Data.Common; using System.IO; +using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Text.RegularExpressions; @@ -28,6 +29,28 @@ internal class SystemInfoHelper public SystemInfoHelper(IApmLogger logger) => _logger = logger.Scoped(nameof(SystemInfoHelper)); + + //3997 3984 253:1 /var/lib/docker/containers/6548c6863fb748e72d1e2a4f824fde92f720952d062dede1318c2d6219a672d6/hostname /etc/hostname rw,relatime shared:1877 - ext4 /dev/mapper/vgubuntu-root rw,errors=remount-ro + internal void ParseMountInfo(Api.System system, string reportedHostName, string line) + { + + var fields = line.Split(' '); + if (fields.Length <= 3) + return; + + var path = fields[3]; + foreach (var folder in path.Split('/')) + { + //naive implementation to check for guid. + if (folder.Length != 64) + continue; + system.Container = new Container { Id = folder }; + } + + } + + // "1:name=systemd:/ecs/03752a671e744971a862edcee6195646/03752a671e744971a862edcee6195646-4015103728" + // "0::/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod121157b5_c67d_4c3e_9052_cb27bbb711fb.slice/cri-containerd-1cd3449e930b8a28c7595240fa32ba20c84f36d059e5fbe63104ad40057992d1.scope" internal void ParseContainerId(Api.System system, string reportedHostName, string line) { var fields = line.Split(':'); @@ -43,11 +66,13 @@ internal void ParseContainerId(Api.System system, string reportedHostName, strin if (string.IsNullOrWhiteSpace(idPart)) return; - // Legacy, e.g.: /system.slice/docker-.scope + // Legacy, e.g.: /system.slice/docker-.scope or cri-containerd-.scope if (idPart.EndsWith(".scope")) { - idPart = idPart.Substring(0, idPart.Length - ".scope".Length) - .Substring(idPart.IndexOf("-", StringComparison.Ordinal) + 1); + var idParts = idPart.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries); + var containerIdWithScope = idParts.Last(); + + idPart = containerIdWithScope.Substring(0, containerIdWithScope.Length - ".scope".Length); } // Looking for kubernetes info @@ -173,8 +198,10 @@ static string NormalizeHostName(string hostName) => private void ParseContainerInfo(Api.System system, string reportedHostName) { + //0::/ try { + var fallBackToMountInfo = false; using var sr = GetCGroupAsStream(); if (sr is null) { @@ -183,12 +210,34 @@ private void ParseContainerInfo(Api.System system, string reportedHostName) return; } + var i = 0; string line; while ((line = sr.ReadLine()) != null) { + if (line == "0::/" && i == 0) + fallBackToMountInfo = true; ParseContainerId(system, reportedHostName, line); if (system.Container != null) return; + i++; + } + if (!fallBackToMountInfo) + return; + + using var mi = GetMountInfoAsStream(); + if (mi is null) + { + _logger.Debug()?.Log("No /proc/self/mountinfo found - no information to fallback to"); + return; + } + + while ((line = mi.ReadLine()) != null) + { + if (!line.Contains("/etc/hostname")) + continue; + ParseMountInfo(system, reportedHostName, line); + if (system.Container != null) + return; } } catch (Exception e) @@ -201,8 +250,12 @@ private void ParseContainerInfo(Api.System system, string reportedHostName) "Failed parsing container id - the agent will not report container id. Likely the application is not running within a container"); } - protected virtual StreamReader GetCGroupAsStream() - => File.Exists("/proc/self/cgroup") ? new StreamReader("/proc/self/cgroup") : null; + protected virtual StreamReader GetCGroupAsStream() => + File.Exists("/proc/self/cgroup") ? new StreamReader("/proc/self/cgroup") : null; + + protected virtual StreamReader GetMountInfoAsStream() => + File.Exists("/proc/self/mountinfo") ? new StreamReader("/proc/self/mountinfo") : null; + internal const string Namespace = "KUBERNETES_NAMESPACE"; internal const string PodName = "KUBERNETES_POD_NAME"; diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 1e0539a9b..03a58c389 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -22,7 +22,7 @@ - + diff --git a/test/Elastic.Apm.Tests.Utilities/CGroupTestCasesAttribute.cs b/test/Elastic.Apm.Tests.Utilities/CGroupTestCasesAttribute.cs new file mode 100644 index 000000000..e73eaa5f7 --- /dev/null +++ b/test/Elastic.Apm.Tests.Utilities/CGroupTestCasesAttribute.cs @@ -0,0 +1,78 @@ +// Licensed to Elasticsearch B.V under +// one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Elastic.Apm.Libraries.Newtonsoft.Json.Linq; +using Xunit.Sdk; + +namespace Elastic.Apm.Tests.Utilities; + +public struct CgroupFiles +{ + public string ProcSelfCgroup; + public string[] MountInfo; +} + +public struct CGroupTestData +{ + public CgroupFiles Files; + public string ContainerId; + public string PodId; +} + + +public class CGroupTestCasesAttribute : DataAttribute +{ + private readonly string _fileName = "./TestResources/json-specs//container_metadata_discovery.json"; + + public override IEnumerable GetData(MethodInfo testMethod) + { + if (!File.Exists(_fileName)) + throw new ArgumentException($"JSON input file {_fileName} does not exist"); + + var jToken = JToken.Parse(File.ReadAllText(_fileName), new JsonLoadSettings + { + CommentHandling = CommentHandling.Ignore + }); + + foreach (var kvp in (JObject)jToken) + { + var name = kvp.Key; + var data = ParseTestData(kvp.Value as JObject); + yield return [name, data]; + } + } + + private static CGroupTestData ParseTestData(JObject jToken) + { + var testData = new CGroupTestData { Files = new CgroupFiles() }; + + foreach (var kvp in jToken) + { + switch (kvp.Key) + { + case "containerId": + testData.ContainerId = kvp.Value?.Value(); + break; + case "podId": + testData.PodId = kvp.Value?.Value(); + break; + case "files": + var o = (JObject)kvp.Value; + var cgroupA = o.Property("/proc/self/cgroup")?.Value as JArray; + testData.Files.ProcSelfCgroup = cgroupA?.Values().FirstOrDefault(); + + var mountInfoA = o.Property("/proc/self/mountinfo")?.Value as JArray; + testData.Files.MountInfo = mountInfoA?.Values().ToArray(); + break; + } + } + return testData; + } +} diff --git a/test/Elastic.Apm.Tests.Utilities/TestResources/json-specs/cgroup_parsing.json b/test/Elastic.Apm.Tests.Utilities/TestResources/json-specs/cgroup_parsing.json deleted file mode 100644 index f28d87d4c..000000000 --- a/test/Elastic.Apm.Tests.Utilities/TestResources/json-specs/cgroup_parsing.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "testUnderscores": { - "groupLine": "1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod90d81341_92de_11e7_8cf2_507b9d4141fa.slice/crio-2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63.scope", - "containerId": "2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63", - "podId": "90d81341-92de-11e7-8cf2-507b9d4141fa" - }, - "testOpenshiftForm": { - "groupLine": "9:freezer:/kubepods.slice/kubepods-pod22949dce_fd8b_11ea_8ede_98f2b32c645c.slice/docker-b15a5bdedd2e7645c3be271364324321b908314e4c77857bbfd32a041148c07f.scope", - "containerId": "b15a5bdedd2e7645c3be271364324321b908314e4c77857bbfd32a041148c07f", - "podId": "22949dce-fd8b-11ea-8ede-98f2b32c645c" - }, - "testUbuntuCGroup": { - "groupLine": "1:name=systemd:/user.slice/user-1000.slice/user@1000.service/apps.slice/apps-org.gnome.Terminal.slice/vte-spawn-75bc72bd-6642-4cf5-b62c-0674e11bfc84.scope", - "containerId": null, - "podId": null - }, - "testAwsEcsCGroup": { - "groupLine": "1:name=systemd:/ecs/03752a671e744971a862edcee6195646/03752a671e744971a862edcee6195646-4015103728", - "containerId": "03752a671e744971a862edcee6195646-4015103728", - "podId": null - } -} diff --git a/test/Elastic.Apm.Tests/SystemInfoHelperTests.cs b/test/Elastic.Apm.Tests/SystemInfoHelperTests.cs index 204edfdb2..a20f76d84 100644 --- a/test/Elastic.Apm.Tests/SystemInfoHelperTests.cs +++ b/test/Elastic.Apm.Tests/SystemInfoHelperTests.cs @@ -4,12 +4,14 @@ using System; using System.Collections.Generic; +using System.Linq; using Elastic.Apm.Api.Kubernetes; using Elastic.Apm.Features; using Elastic.Apm.Helpers; using Elastic.Apm.Logging; using Elastic.Apm.Tests.Utilities; using FluentAssertions; +using Newtonsoft.Json; using Xunit; namespace Elastic.Apm.Tests; @@ -61,30 +63,33 @@ public void ParseKubernetesInfo_ShouldReturnNull_WhenNoEnvironmentVariablesAreSe system.Kubernetes.Should().BeNull(); } - public struct CGroupTestData - { - public string GroupLine; - public string ContainerId; - public string PodId; - } - // Remove warning about unused test parameter "name" #pragma warning disable xUnit1026 [Theory] - [JsonFileData("./TestResources/json-specs/cgroup_parsing.json", typeof(CGroupTestData))] + [CGroupTestCases] public void ParseKubernetesInfo_FromCGroupLine(string name, CGroupTestData data) { - var line = data.GroupLine; + data.Files.ProcSelfCgroup.Should().NotBeNull(); + var line = data.Files.ProcSelfCgroup; var containerId = data.ContainerId; var podId = data.PodId; var system = new Api.System(); - _systemInfoHelper.ParseContainerId(system, "hostname", line); + if (line == "0::/") + { + line = data.Files.MountInfo.FirstOrDefault(l => l.Contains("/etc/hostname")); + _systemInfoHelper.ParseMountInfo(system, "hostname", line); + } + else + _systemInfoHelper.ParseContainerId(system, "hostname", line); if (containerId is null) system.Container.Should().BeNull(); else + { + system.Container.Should().NotBeNull("{0}", line); system.Container.Id.Should().Be(containerId); + } if (podId is null) system.Kubernetes.Should().BeNull();