feat: Add support for instrumenting Hangfire background jobs. #7283
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: All Solutions Build | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| - "feature/**" | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| inputs: | |
| aggregate_payload_data: | |
| description: 'Collect and aggregate payload data' | |
| required: false | |
| type: boolean | |
| default: false | |
| parallelize_integration_tests: | |
| description: 'Enable parallel integration test execution (requires pre-built test apps)' | |
| required: false | |
| type: boolean | |
| default: true | |
| schedule: | |
| - cron: "0 9 * * 1-5" | |
| # only allow one instance of this workflow to be running per PR or branch, cancels any that are already running | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| env: | |
| scripts_path: ${{ github.workspace }}\build\scripts | |
| tools_path: ${{ github.workspace }}\build\Tools | |
| DOTNET_NOLOGO: true | |
| jobs: | |
| check-modified-files: | |
| name: Check if source files were modified, skip remaining jobs if not | |
| # skip if the PR source branch starts with "release-please" | |
| if: github.event_name != 'pull_request' || !startsWith(github.head_ref, 'release-please') | |
| uses: ./.github/workflows/check_modified_files.yml | |
| secrets: inherit | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| shellcheck: | |
| name: Validate shell scripts | |
| needs: check-modified-files | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 | |
| with: | |
| disable-sudo: true | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Run Shellcheck | |
| run: | | |
| find ${{ github.workspace }} -name "*.sh" -exec shellcheck --severity=error {} + | |
| # This builds both FullAgent and MSIInstaller since MSIInstaller requires FullAgent artifacts. | |
| build-fullagent-msi: | |
| name: Build FullAgent and MSIInstaller | |
| runs-on: windows-2022 | |
| needs: | |
| - check-modified-files | |
| - shellcheck | |
| # don't run this job if triggered by Dependabot, will cause all other jobs to be skipped as well | |
| # run this job if source files were modified, or if triggered by a release, a manual execution or schedule | |
| if: github.actor != 'dependabot[bot]' && (needs.check-modified-files.outputs.source-files-changed == 'true' || github.event.release || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule') | |
| env: | |
| fullagent_solution_path: ${{ github.workspace }}\FullAgent.sln | |
| msi_solution_path: ${{ github.workspace }}\src\Agent\MsiInstaller\MsiInstaller.sln | |
| outputs: | |
| agentVersion: ${{ steps.agentVersion.outputs.version }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| filter: blob:none | |
| - name: Cache NuGet packages | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }} | |
| restore-keys: ${{ runner.os }}-nuget- | |
| - name: Install latest .NET 10 SDK | |
| uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 | |
| with: | |
| dotnet-version: '10.0.x' | |
| dotnet-quality: 'ga' | |
| - name: Build FullAgent.sln | |
| run: | | |
| Write-Host "dotnet build --force --configuration Release -p:AllowUnsafeBlocks=true ${{ env.fullagent_solution_path }}" | |
| dotnet build --force --configuration Release -p:AllowUnsafeBlocks=true ${{ env.fullagent_solution_path }} | |
| shell: powershell | |
| - name: Create agentVersion | |
| id: agentVersion | |
| run: | | |
| $agentVersion = (Get-Item "${{ github.workspace }}\src\_build\AnyCPU-Release\NewRelic.Agent.Core\net462\NewRelic.Agent.Core.dll").VersionInfo.FileVersion | |
| echo "version=$agentVersion" >> $env:GITHUB_OUTPUT | |
| shell: powershell | |
| - name: Archive FullAgent Home folders | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: homefolders | |
| path: | | |
| ${{ github.workspace }}\src\Agent\newrelichome_x64 | |
| ${{ github.workspace }}\src\Agent\newrelichome_x64_coreclr | |
| ${{ github.workspace }}\src\Agent\newrelichome_x64_coreclr_linux | |
| ${{ github.workspace }}\src\Agent\newrelichome_arm64_coreclr_linux | |
| ${{ github.workspace }}\src\Agent\newrelichome_x86 | |
| ${{ github.workspace }}\src\Agent\newrelichome_x86_coreclr | |
| if-no-files-found: error | |
| - name: Convert Code Signing Certificate Into File | |
| id: write_cert | |
| run: | | |
| $filePath = '${{ github.workspace }}\newrelic_code_sign_cert.pfx' | |
| $bytes = [Convert]::FromBase64String('${{ secrets.SIGNING_CERT }}') | |
| [IO.File]::WriteAllBytes($filePath, $bytes) | |
| echo "filePath=$filePath" >> $env:GITHUB_OUTPUT | |
| shell: powershell | |
| - name: Install Code Signing Certificate | |
| run: | | |
| Write-Host "certutil.exe -f -user -p <passphrase> -importPFX ${{ steps.write_cert.outputs.filePath }} NoRoot" | |
| certutil.exe -f -user -p ${{ secrets.CERT_PASSPHRASE }} -importPFX ${{ steps.write_cert.outputs.filePath }} NoRoot | |
| shell: powershell | |
| - name: Add msbuild to PATH (required for MsiInstaller build) | |
| uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0 | |
| - name: Build MsiInstaller.sln x86 | |
| run: | | |
| Write-Host "MSBuild.exe -restore -m -p:Configuration=Release -p:AllowUnsafeBlocks=true -p:Platform=x86 ${{ env.msi_solution_path }}" | |
| MSBuild.exe -restore -m -p:Configuration=Release -p:AllowUnsafeBlocks=true -p:Platform=x86 ${{ env.msi_solution_path }} | |
| shell: powershell | |
| - name: Build MsiInstaller.sln x64 | |
| run: | | |
| Write-Host "MSBuild.exe -restore -m -p:Configuration=Release -p:AllowUnsafeBlocks=true -p:Platform=x64 ${{ env.msi_solution_path }}" | |
| MSBuild.exe -restore -m -p:Configuration=Release -p:AllowUnsafeBlocks=true -p:Platform=x64 ${{ env.msi_solution_path }} | |
| shell: powershell | |
| - name: Archive msi _build Artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: msi-build-folder-artifacts | |
| path: ${{ github.workspace }}\src\_build | |
| if-no-files-found: error | |
| run-linux-container-tests: | |
| name: Run Linux Container Tests | |
| needs: | |
| - build-fullagent-msi | |
| uses: ./.github/workflows/linux_container_tests.yml | |
| with: | |
| aggregate_payload_data: ${{ inputs.aggregate_payload_data == true || github.event_name == 'schedule' }} | |
| secrets: inherit | |
| permissions: | |
| actions: write | |
| contents: read | |
| # --------------------------------------------------------------------------- | |
| # Compute the set of test namespaces to run, after applying exclusions from | |
| # GitHub repo variables. To skip a namespace, add it to the JSON-array | |
| # variable in Settings > Secrets and variables > Actions > Variables: | |
| # INTEGRATION_EXCLUDE_NAMESPACES (e.g. ["LLM","OpenTelemetry"]) | |
| # UNBOUNDED_EXCLUDE_NAMESPACES (e.g. ["Couchbase"]) | |
| # --------------------------------------------------------------------------- | |
| get-test-namespaces: | |
| name: Get Test Namespaces | |
| needs: check-modified-files | |
| if: github.actor != 'dependabot[bot]' && (needs.check-modified-files.outputs.source-files-changed == 'true' || github.event.release || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule') | |
| runs-on: ubuntu-latest | |
| outputs: | |
| integration-namespaces: ${{ steps.compute.outputs.integration }} | |
| unbounded-namespaces: ${{ steps.compute.outputs.unbounded }} | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 | |
| with: | |
| disable-sudo: true | |
| egress-policy: audit | |
| - name: Compute namespace lists | |
| id: compute | |
| env: | |
| INTEGRATION_EXCLUDE: ${{ vars.INTEGRATION_EXCLUDE_NAMESPACES }} | |
| UNBOUNDED_EXCLUDE: ${{ vars.UNBOUNDED_EXCLUDE_NAMESPACES }} | |
| run: | | |
| # ── Canonical namespace lists ────────────────────────────────── | |
| # maintain alphabetical order, please! | |
| integration_all='[ | |
| "AgentFeatures", | |
| "AgentLogs", | |
| "AgentMetrics", | |
| "Api", | |
| "AppDomainCaching", | |
| "AspNetCore", | |
| "AwsLambda.AutoInstrumentation", | |
| "AwsLambda.CloudWatch", | |
| "AwsLambda.Custom", | |
| "AwsLambda.DynamoDb", | |
| "AwsLambda.General", | |
| "AwsLambda.Kinesis", | |
| "AwsLambda.S3", | |
| "AwsLambda.Ses", | |
| "AwsLambda.Sns", | |
| "AwsLambda.Sqs", | |
| "AwsLambda.WebRequest", | |
| "AwsSdk", | |
| "AzureFunction", | |
| "BasicInstrumentation", | |
| "Blazor", | |
| "CatInbound", | |
| "CatOutbound", | |
| "CodeLevelMetrics", | |
| "Configuration", | |
| "CSP", | |
| "CustomAttributes", | |
| "CustomInstrumentation", | |
| "DataTransmission", | |
| "DistributedTracing", | |
| "Errors", | |
| "Grpc", | |
| "Hangfire", | |
| "HttpClientInstrumentation", | |
| "HybridHttpContextStorage", | |
| "InfiniteTracing", | |
| "LLM", | |
| "Logging.AuditLog", | |
| "Logging.ContextData", | |
| "Logging.HsmAndCsp", | |
| "Logging.Labels", | |
| "Logging.LocalDecoration", | |
| "Logging.LogLevelDetection", | |
| "Logging.MaxSamplesStored", | |
| "Logging.MetricsAndForwarding", | |
| "Logging.StructuredLogArgContextData", | |
| "Logging.ZeroMaxSamplesStored", | |
| "MassTransit", | |
| "OpenTelemetry", | |
| "Owin", | |
| "ReJit.NetCore", | |
| "ReJit.NetFramework", | |
| "RequestHandling", | |
| "RequestHeadersCapture.AspNet", | |
| "RequestHeadersCapture.AspNetCore", | |
| "RequestHeadersCapture.EnvironmentVariables", | |
| "RequestHeadersCapture.Owin", | |
| "RequestHeadersCapture.WCF", | |
| "RestSharp", | |
| "WCF.Client.IIS.ASPDisabled", | |
| "WCF.Client.IIS.ASPEnabled", | |
| "WCF.Client.Self", | |
| "WCF.Service.IIS.ASPDisabled", | |
| "WCF.Service.IIS.ASPEnabled", | |
| "WCF.Service.Self" | |
| ]' | |
| unbounded_all='[ | |
| "AzureServiceBus", | |
| "CosmosDB", | |
| "Couchbase", | |
| "Elasticsearch", | |
| "MongoDB", | |
| "Msmq", | |
| "MsSql", | |
| "MySql", | |
| "NServiceBus", | |
| "NServiceBus5", | |
| "OpenSearch", | |
| "Oracle", | |
| "Postgres", | |
| "RabbitMq", | |
| "Redis" | |
| ]' | |
| # ── Read exclusion variables (default to empty array) ───────── | |
| integration_exclude="${INTEGRATION_EXCLUDE:-[]}" | |
| unbounded_exclude="${UNBOUNDED_EXCLUDE:-[]}" | |
| # ── Apply exclusions ────────────────────────────────────────── | |
| integration=$(jq -c '. - $ex' --argjson ex "$integration_exclude" <<< "$integration_all") | |
| unbounded=$(jq -c '. - $ex' --argjson ex "$unbounded_exclude" <<< "$unbounded_all") | |
| # ── Log what was excluded ───────────────────────────────────── | |
| int_removed=$(jq -c '. as $all | $all - ($all - $ex)' --argjson ex "$integration_exclude" <<< "$integration_all") | |
| unb_removed=$(jq -c '. as $all | $all - ($all - $ex)' --argjson ex "$unbounded_exclude" <<< "$unbounded_all") | |
| echo "::group::Integration test namespaces" | |
| echo "Excluded: $int_removed" | |
| echo "Running: $integration" | |
| echo "::endgroup::" | |
| echo "::group::Unbounded test namespaces" | |
| echo "Excluded: $unb_removed" | |
| echo "Running: $unbounded" | |
| echo "::endgroup::" | |
| echo "integration=$integration" >> "$GITHUB_OUTPUT" | |
| echo "unbounded=$unbounded" >> "$GITHUB_OUTPUT" | |
| shell: bash | |
| build-integration-tests: | |
| needs: build-fullagent-msi | |
| name: Build IntegrationTests | |
| runs-on: windows-2025-vs2026 | |
| env: | |
| integration_solution_path: ${{ github.workspace }}\tests\Agent\IntegrationTests\IntegrationTests.sln | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| filter: blob:none | |
| - name: Cache NuGet packages | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }} | |
| restore-keys: ${{ runner.os }}-nuget- | |
| - name: Install latest .NET 10 SDK | |
| uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 | |
| with: | |
| dotnet-version: '10.0.x' | |
| dotnet-quality: 'ga' | |
| - name: Add msbuild to PATH | |
| uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0 | |
| # if needed for future .NET preview builds: | |
| #with: | |
| # vs-prerelease: true | |
| - name: List SDKS | |
| run: dotnet --list-sdks | |
| shell: powershell | |
| - name: Build IntegrationTests.sln | |
| run: | | |
| Write-Host "List NuGet Sources" | |
| dotnet nuget list source # For unknown reasons, this step is necessary to avoid subsequent problems with NuGet package restore | |
| Write-Host "MSBuild.exe -restore -m -p:Configuration=Release -p:DeployOnBuild=true -p:PublishProfile=LocalDeploy ${{ env.integration_solution_path }}" | |
| MSBuild.exe -restore -m -p:Configuration=Release -p:DeployOnBuild=true -p:PublishProfile=LocalDeploy ${{ env.integration_solution_path }} | |
| shell: powershell | |
| - name: Pre-publish .NET Core test apps | |
| run: | | |
| # Pre-publish .NET Core test apps so test fixtures can copy pre-built output | |
| # instead of invoking dotnet publish at runtime. This eliminates file-lock | |
| # collisions on shared dependency obj/ directories during parallel test execution. | |
| # We skip class libraries and Azure Function apps (func.exe requires the original | |
| # build output structure, not RID-specific publish output). | |
| $runtime = "win-x64" | |
| $searchPaths = @("Applications", "SharedApplications") | |
| $basePath = "${{ github.workspace }}\tests\Agent\IntegrationTests" | |
| # Azure Function apps are incompatible with pre-publish: func.exe start --no-build | |
| # expects framework-dependent build output, not RID-specific publish output | |
| $skipProjects = @("AzureFunctionApplication", "AzureFunctionInProcApplication") | |
| $published = 0; $skipped = 0; $failed = 0 | |
| foreach ($searchPath in $searchPaths) { | |
| $fullPath = Join-Path $basePath $searchPath | |
| if (-not (Test-Path $fullPath)) { continue } | |
| $projects = Get-ChildItem -Path $fullPath -Filter "*.csproj" -Recurse | |
| foreach ($project in $projects) { | |
| $content = Get-Content $project.FullName -Raw | |
| # Skip explicit class libraries and excluded projects | |
| if ($content -match '<OutputType>\s*Library\s*</OutputType>') { $skipped++; continue } | |
| if ($skipProjects -contains $project.BaseName) { $skipped++; continue } | |
| if ($content -match '<TargetFrameworks?>(.*?)</TargetFrameworks?>') { | |
| $tfms = $Matches[1] -split ';' | |
| foreach ($tfm in $tfms) { | |
| if ($tfm -match '^net\d+\.\d+$') { | |
| Write-Host "Pre-publishing $($project.Name) for $tfm/$runtime" | |
| $output = dotnet publish --configuration Release --runtime $runtime --framework $tfm $project.FullName 2>&1 | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Warning "Failed to pre-publish $($project.Name) for $tfm/$runtime" | |
| $output | Where-Object { $_ -match "error" } | ForEach-Object { Write-Host " $_" } | |
| $failed++ | |
| } else { | |
| $published++ | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| Write-Host "Pre-publish complete: $published succeeded, $skipped skipped (libraries), $failed failed" | |
| # Clean up intermediate RID-specific build artifacts to reduce artifact size. | |
| # dotnet publish --runtime creates large intermediate files alongside the publish/ | |
| # subdirectory (including self-contained runtime copies). Only publish/ is needed. | |
| $cleaned = 0 | |
| foreach ($searchPath in $searchPaths) { | |
| $fullPath = Join-Path $basePath $searchPath | |
| if (-not (Test-Path $fullPath)) { continue } | |
| Get-ChildItem -Path $fullPath -Directory -Recurse -Filter $runtime | ForEach-Object { | |
| Get-ChildItem -Path $_.FullName -Exclude "publish" | Remove-Item -Recurse -Force | |
| $cleaned++ | |
| } | |
| } | |
| Write-Host "Cleaned intermediate build artifacts from $cleaned RID directories" | |
| shell: powershell | |
| - name: Archive Artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: integrationtests | |
| path: | | |
| ${{ github.workspace }}\test.runsettings # Force the artifacts to use repo root as root of package. | |
| ${{ github.workspace }}\tests\Agent\IntegrationTests\**\bin\**\* | |
| ${{ github.workspace }}\tests\Agent\IntegrationTests\**\Deploy\**\* | |
| !${{ github.workspace }}\tests\Agent\IntegrationTests\**\obj\**\* | |
| if-no-files-found: error | |
| build-unbounded-tests: | |
| needs: build-fullagent-msi | |
| name: Build UnboundedIntegrationTests | |
| runs-on: windows-2025-vs2026 | |
| env: | |
| unbounded_solution_path: ${{ github.workspace }}\tests\Agent\IntegrationTests\UnboundedIntegrationTests.sln | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| filter: blob:none | |
| - name: Cache NuGet packages | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }} | |
| restore-keys: ${{ runner.os }}-nuget- | |
| - name: Install latest .NET 10 SDK | |
| uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 | |
| with: | |
| dotnet-version: '10.0.x' | |
| dotnet-quality: 'ga' | |
| - name: Add msbuild to PATH | |
| uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0 | |
| # if needed for future .NET preview builds: | |
| #with: | |
| # vs-prerelease: true | |
| - name: Build UnboundedIntegrationTests.sln | |
| run: | | |
| Write-Host "List NuGet Sources" | |
| dotnet nuget list source # For unknown reasons, this step is necessary to avoid subsequent problems with NuGet package restore | |
| Write-Host "MSBuild.exe -restore -m -p:Configuration=Release -p:DeployOnBuild=true -p:PublishProfile=LocalDeploy ${{ env.unbounded_solution_path }}" | |
| MSBuild.exe -restore -m -p:Configuration=Release -p:DeployOnBuild=true -p:PublishProfile=LocalDeploy ${{ env.unbounded_solution_path }} | |
| shell: powershell | |
| - name: Pre-publish .NET Core test apps | |
| run: | | |
| # Pre-publish .NET Core test apps so test fixtures can copy pre-built output | |
| # instead of invoking dotnet publish at runtime. This eliminates file-lock | |
| # collisions on shared dependency obj/ directories during parallel test execution. | |
| # We skip class libraries (OutputType=Library or netstandard-only targets). | |
| $runtime = "win-x64" | |
| $searchPaths = @("UnboundedApplications", "SharedApplications") | |
| $basePath = "${{ github.workspace }}\tests\Agent\IntegrationTests" | |
| $published = 0; $skipped = 0; $failed = 0 | |
| foreach ($searchPath in $searchPaths) { | |
| $fullPath = Join-Path $basePath $searchPath | |
| if (-not (Test-Path $fullPath)) { continue } | |
| $projects = Get-ChildItem -Path $fullPath -Filter "*.csproj" -Recurse | |
| foreach ($project in $projects) { | |
| $content = Get-Content $project.FullName -Raw | |
| # Skip explicit class libraries | |
| if ($content -match '<OutputType>\s*Library\s*</OutputType>') { $skipped++; continue } | |
| if ($content -match '<TargetFrameworks?>(.*?)</TargetFrameworks?>') { | |
| $tfms = $Matches[1] -split ';' | |
| foreach ($tfm in $tfms) { | |
| if ($tfm -match '^net\d+\.\d+$') { | |
| Write-Host "Pre-publishing $($project.Name) for $tfm/$runtime" | |
| $output = dotnet publish --configuration Release --runtime $runtime --framework $tfm $project.FullName 2>&1 | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Warning "Failed to pre-publish $($project.Name) for $tfm/$runtime" | |
| $output | Where-Object { $_ -match "error" } | ForEach-Object { Write-Host " $_" } | |
| $failed++ | |
| } else { | |
| $published++ | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| Write-Host "Pre-publish complete: $published succeeded, $skipped skipped (libraries), $failed failed" | |
| # Clean up intermediate RID-specific build artifacts to reduce artifact size. | |
| $cleaned = 0 | |
| foreach ($searchPath in $searchPaths) { | |
| $fullPath = Join-Path $basePath $searchPath | |
| if (-not (Test-Path $fullPath)) { continue } | |
| Get-ChildItem -Path $fullPath -Directory -Recurse -Filter $runtime | ForEach-Object { | |
| Get-ChildItem -Path $_.FullName -Exclude "publish" | Remove-Item -Recurse -Force | |
| $cleaned++ | |
| } | |
| } | |
| Write-Host "Cleaned intermediate build artifacts from $cleaned RID directories" | |
| shell: powershell | |
| - name: Archive Artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: unboundedintegrationtests | |
| path: | | |
| ${{ github.workspace }}\test.runsettings # Force the artifacts to use repo root as root of package. | |
| ${{ github.workspace }}\tests\Agent\IntegrationTests\**\bin\**\* | |
| ${{ github.workspace }}\tests\Agent\IntegrationTests\**\Deploy\**\* | |
| !${{ github.workspace }}\tests\Agent\IntegrationTests\**\obj\**\* | |
| if-no-files-found: error | |
| run-integration-tests: | |
| needs: [build-integration-tests, get-test-namespaces] | |
| name: Run IntegrationTests | |
| runs-on: windows-2025-vs2026 | |
| strategy: | |
| matrix: | |
| namespace: ${{ fromJson(needs.get-test-namespaces.outputs.integration-namespaces) }} | |
| fail-fast: false # we don't want one test failure in one namespace to kill the other runs | |
| env: | |
| integration_tests_shared_project: ${{ github.workspace }}/tests/Agent/IntegrationTests/Shared | |
| integration_tests_path: ${{ github.workspace }}/tests/Agent/IntegrationTests/IntegrationTests/bin/Release/net10.0 | |
| # Make this variable true to enable extra data-gathering and logging to help troubleshoot test failures, at the cost of additional time and resources | |
| enhanced_logging: false | |
| NR_DOTNET_TEST_SAVE_WORKING_DIRECTORY: 1 | |
| NR_DOTNET_TEST_PREBUILT_APPS: 1 # use pre-published test app output instead of dotnet publish at runtime | |
| azure_func_exe_path: C:\ProgramData\chocolatey\lib\azure-functions-core-tools\tools\func.exe | |
| NEW_RELIC_AZURE_FUNCTION_LOG_LEVEL_OVERRIDE: 1 # enables profiler debug logs when testing an azure function | |
| # Set an environment variable that the tests will use to set the application name. | |
| CI_NEW_RELIC_APP_NAME: ${{ github.event_name == 'schedule' && 'DotNetIngestTracking' || '' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| filter: blob:none | |
| - name: Disable TLS 1.3 | |
| run: | | |
| $registryPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client" | |
| if(!(Test-Path $registryPath)) { | |
| New-Item -Path $registryPath -Force | |
| } | |
| New-ItemProperty -Path $registryPath -Name "DisabledByDefault" -Value "1" -PropertyType DWORD -Force | |
| New-ItemProperty -Path $registryPath -Name "Enabled" -Value "0" -PropertyType DWORD -Force | |
| shell: powershell | |
| - name: Create and trust .NET development SSL certificate | |
| run: | | |
| dotnet dev-certs https --clean | |
| dotnet dev-certs https --export-path ./devcert.pfx --password "password1" | |
| $pwd = ConvertTo-SecureString -String "password1" -Force -AsPlainText | |
| Import-PfxCertificate -FilePath ./devcert.pfx -CertStoreLocation Cert:\LocalMachine\Root -Password $pwd | |
| dotnet dev-certs https --check --trust | |
| shell: powershell | |
| - name: Set up secrets | |
| env: | |
| INTEGRATION_TEST_SECRETS: ${{ secrets.TEST_SECRETS }} | |
| run: | | |
| "$Env:INTEGRATION_TEST_SECRETS" | dotnet user-secrets set --project ${{ env.integration_tests_shared_project }} | |
| shell: pwsh #this doesn't work with normal powershell due to UTF-8 BOM handling | |
| - name: Download Agent Home Folders | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: homefolders | |
| path: src/Agent | |
| - name: Download Integration Test Artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: integrationtests | |
| # Should not need a path because the integration test artifacts are archived with the full directory structure | |
| - name: Install HostableWebCore Feature | |
| if: | # only install for the required namespaces | |
| matrix.namespace == 'AgentFeatures' || matrix.namespace == 'AgentLogs' || matrix.namespace == 'AgentMetrics' || matrix.namespace == 'BasicInstrumentation' || | |
| matrix.namespace == 'CatInbound' || matrix.namespace == 'CatOutbound' || matrix.namespace == 'CodeLevelMetrics' || matrix.namespace == 'CSP' || | |
| matrix.namespace == 'CustomAttributes' || matrix.namespace == 'CustomInstrumentation' || matrix.namespace == 'DataTransmission' || | |
| matrix.namespace == 'DistributedTracing' || matrix.namespace == 'Errors' || matrix.namespace == 'HttpClientInstrumentation' || matrix.namespace == 'HybridHttpContextStorage' || | |
| matrix.namespace == 'Rejit.NetFramework' || matrix.namespace == 'RequestHandling' || matrix.namespace == 'RequestHeadersCapture.AspNet' || | |
| matrix.namespace == 'RequestHeadersCapture.AspNetCore' || matrix.namespace == 'RequestHeadersCapture.EnvironmentVariables' || | |
| matrix.namespace == 'RequestHeadersCapture.WCF' || matrix.namespace == 'WCF.Client.IIS.ASPDisabled' || | |
| matrix.namespace == 'WCF.Client.IIS.ASPEnabled' || matrix.namespace == 'WCF.Service.IIS.ASPDisabled' || | |
| matrix.namespace == 'WCF.Service.IIS.ASPEnabled' | |
| run: | | |
| Enable-WindowsOptionalFeature -Online -FeatureName IIS-HostableWebCore | |
| shell: powershell | |
| - name: Install aiohttp | |
| if: matrix.namespace == 'DistributedTracing' | |
| run: | | |
| pip install aiohttp | |
| shell: powershell | |
| - name: Install Azure Functions Core Tools | |
| if: matrix.namespace == 'AzureFunction' | |
| run: | | |
| choco install azure-functions-core-tools -y --params "'/x64'" | |
| shell: powershell | |
| - name: Install latest .NET 10 SDK | |
| uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 | |
| with: | |
| dotnet-version: '10.0.x' | |
| dotnet-quality: 'ga' | |
| - name: Run Integration Tests | |
| run: | | |
| if ($Env:enhanced_logging -eq $True) { | |
| Write-Host "List ports in use" | |
| netstat -no | |
| } | |
| Write-Host "Run tests" | |
| # Most integration tests can run in parallel with pre-built test apps. | |
| # Some namespaces must remain serial because they use shared external tools | |
| # that don't support concurrent instances (e.g., Azure Functions Core Tools | |
| # func.exe crashes when multiple instances launch simultaneously due to shared | |
| # state in storage emulator/lock files). To parallelize these, the shared tool | |
| # contention would need to be resolved. | |
| $noParallelNamespaces = @("AzureFunction") | |
| $parallelize = "${{ inputs.parallelize_integration_tests }}" | |
| if ($parallelize -eq "" -or $parallelize -eq "true") { $parallelize = $true } else { $parallelize = $false } | |
| if ($noParallelNamespaces -contains "${{ matrix.namespace }}") { $parallelize = $false } | |
| $json = Get-Content "${{ env.integration_tests_path }}/xunit.runner.json" | ConvertFrom-Json | |
| $json | Add-Member -Name "parallelizeAssembly" -Value $parallelize -MemberType NoteProperty | |
| $json | Add-Member -Name "parallelizeTestCollections" -Value $parallelize -MemberType NoteProperty | |
| $json | ConvertTo-Json | Out-File "${{ env.integration_tests_path }}/xunit.runner.json" | |
| ${{ env.integration_tests_path }}/NewRelic.Agent.IntegrationTests.exe -namespace NewRelic.Agent.IntegrationTests.${{ matrix.namespace }} -trx "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_testResults.trx" | |
| if ($Env:enhanced_logging -eq $True) { | |
| Write-Host "Get HostableWebCore errors (if any)" | |
| Get-EventLog -LogName Application -Source HostableWebCore -ErrorAction:Ignore | |
| Write-Host "Get .NET Runtime errors (if any)" | |
| Get-EventLog -LogName Application -Source ".NET Runtime" -EntryType "Error","Warning" -ErrorAction:Ignore | |
| } | |
| shell: powershell | |
| - name: Extract payload bytes log from TRX | |
| if: inputs.aggregate_payload_data == true || github.event_name == 'schedule' | |
| run: | | |
| $trxFile = "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_testResults.trx" | |
| $payloadLog = "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_payload_bytes.json" | |
| Write-Host "Checking for TRX file at: $trxFile" | |
| if (Test-Path $trxFile) { | |
| $fileSize = (Get-Item $trxFile).Length | |
| Write-Host "TRX file found. Size: $fileSize bytes" | |
| # Read TRX file and extract lines containing payload information from Message elements | |
| [xml]$trxContent = Get-Content $trxFile | |
| $messageElements = $trxContent.SelectNodes("//*[local-name()='Output']/*[local-name()='TextMessages']/*[local-name()='Message']") | |
| $payloadData = @{} | |
| foreach ($message in $messageElements) { | |
| $text = $message.InnerText | |
| if ($text -match "^(.+?):\s*Total payload bytes sent:\s*(\d+)") { | |
| $className = $Matches[1].Trim() | |
| $byteCount = [int]$Matches[2] | |
| $payloadData[$className] = $byteCount | |
| Write-Host "Found: $className = $byteCount bytes" | |
| } | |
| } | |
| Write-Host "Found $($payloadData.Count) payload log entries" | |
| if ($payloadData.Count -gt 0) { | |
| $payloadData | ConvertTo-Json | Out-File -FilePath $payloadLog -Encoding UTF8 | |
| Write-Host "Extracted payload logs to $payloadLog" | |
| Write-Host "Content:" | |
| Get-Content $payloadLog | ForEach-Object { Write-Host $_ } | |
| } else { | |
| Write-Host "No payload byte logs found in TRX file" | |
| @{} | ConvertTo-Json | Out-File -FilePath $payloadLog -Encoding UTF8 | |
| } | |
| } else { | |
| Write-Host "TRX file not found" | |
| @{} | ConvertTo-Json | Out-File -FilePath $payloadLog -Encoding UTF8 | |
| } | |
| shell: powershell | |
| - name: Upload payload bytes log | |
| if: inputs.aggregate_payload_data == true || github.event_name == 'schedule' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: integration-payload-bytes-${{ matrix.namespace }} | |
| path: C:\IntegrationTestWorkingDirectory\TestResults\*_payload_bytes.json | |
| if-no-files-found: warn | |
| - name: Archive integration test results on failure | |
| if: ${{ failure() }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: integration-test-results-${{ matrix.namespace }} | |
| path: | | |
| C:\IntegrationTestWorkingDirectory\**\*.log | |
| C:\IntegrationTestWorkingDirectory\**\*.config | |
| C:\IntegrationTestWorkingDirectory\TestResults\**\*TestResults.trx | |
| if-no-files-found: error | |
| run-unbounded-tests: | |
| needs: [build-unbounded-tests, get-test-namespaces] | |
| name: Run Unbounded Tests | |
| runs-on: windows-2025-vs2026 | |
| strategy: | |
| matrix: | |
| namespace: ${{ fromJson(needs.get-test-namespaces.outputs.unbounded-namespaces) }} | |
| fail-fast: false # we don't want one test failure in one namespace to kill the other runs | |
| env: | |
| integration_tests_shared_project: ${{ github.workspace }}/tests/Agent/IntegrationTests/Shared | |
| unbounded_tests_path: ${{ github.workspace }}/tests/Agent/IntegrationTests/UnboundedIntegrationTests/bin/Release/net10.0 | |
| NR_DOTNET_TEST_SAVE_WORKING_DIRECTORY: 1 | |
| NR_DOTNET_TEST_PREBUILT_APPS: 1 # use pre-published test app output instead of dotnet publish at runtime | |
| # Make this variable true to enable extra data-gathering and logging to help troubleshoot test failures, at the cost of additional time and resources | |
| enhanced_logging: false | |
| # Set an environment variable that the tests will use to set the application name. | |
| CI_NEW_RELIC_APP_NAME: ${{ github.event_name == 'schedule' && 'DotNetIngestTracking' || '' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| filter: blob:none | |
| - name: Download Agent Home Folders | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: homefolders | |
| path: src/Agent | |
| - name: Download Unbounded Integration Test Artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: unboundedintegrationtests | |
| # Should not need a path because the integration test artifacts are archived with the full directory structure | |
| - name: Disable TLS 1.3 | |
| run: | | |
| $registryPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client" | |
| if(!(Test-Path $registryPath)) { | |
| New-Item -Path $registryPath -Force | |
| } | |
| New-ItemProperty -Path $registryPath -Name "DisabledByDefault" -Value "1" -PropertyType DWORD -Force | |
| New-ItemProperty -Path $registryPath -Name "Enabled" -Value "0" -PropertyType DWORD -Force | |
| shell: powershell | |
| - name: Install HostableWebCore Feature | |
| if: | # only install for the required namespaces | |
| matrix.namespace == 'MongoDB' || matrix.namespace == 'MsSql' || matrix.namespace == 'Oracle' | |
| run: | | |
| Enable-WindowsOptionalFeature -Online -FeatureName IIS-HostableWebCore | |
| shell: powershell | |
| - name: Install MSMQ dependencies | |
| if: matrix.namespace == 'Msmq' | |
| run: | | |
| Write-Host "Installing Msmq Features" | |
| Enable-WindowsOptionalFeature -Online -FeatureName MSMQ-Server -All | |
| Enable-WindowsOptionalFeature -Online -FeatureName MSMQ-HTTP -All | |
| Enable-WindowsOptionalFeature -Online -FeatureName MSMQ-Triggers -All | |
| shell: powershell | |
| - name: Install MsSql dependencies | |
| if: matrix.namespace == 'MsSql' | |
| run: | | |
| Write-Host "Installing MSSQL CLI" | |
| msiexec /i "${{ github.workspace }}\build\Tools\sqlncli.msi" IACCEPTSQLNCLILICENSETERMS=YES /quiet /qn /norestart | |
| Start-Sleep 20 # Need to wait for install to finish -- takes only a few seconds, but we need to be sure. | |
| shell: powershell | |
| - name: Set up secrets | |
| env: | |
| INTEGRATION_TEST_SECRETS: ${{ secrets.TEST_SECRETS }} | |
| run: | | |
| "$Env:INTEGRATION_TEST_SECRETS" | dotnet user-secrets set --project ${{ env.integration_tests_shared_project }} | |
| shell: pwsh #this doesn't work with normal powershell due to UTF-8 BOM handling | |
| # save in case we move back to using the emulator | |
| # - name: Start Local CosmosDB Emulator for CosmosDB Tests | |
| # if: matrix.namespace == 'CosmosDB' | |
| # run: | | |
| # Write-Host "Launching Cosmos DB Emulator" | |
| # Import-Module "$env:ProgramFiles\Azure Cosmos DB Emulator\PSModules\Microsoft.Azure.CosmosDB.Emulator" | |
| # Start-CosmosDbEmulator | |
| # shell: pwsh | |
| - name: Install latest .NET 10 SDK | |
| uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 | |
| with: | |
| dotnet-version: '10.0.x' | |
| dotnet-quality: 'ga' | |
| - name: Run Unbounded Integration Tests | |
| run: | | |
| if ($Env:enhanced_logging -eq $True) { | |
| Write-Host "List ports in use" | |
| netstat -no | |
| } | |
| # Most unbounded tests can run in parallel with pre-built test apps. | |
| # Some namespaces must remain serial because they use shared external infrastructure | |
| # with hard-coded object names (e.g., MsSql tests all query NewRelic.dbo.TeamMembers | |
| # with constant values, NServiceBus5 tests share MSMQ queues). To parallelize these, | |
| # each test fixture would need isolated resources (per-test databases/tables/queues). | |
| $noParallelNamespaces = @("MsSql", "NServiceBus", "NServiceBus5") | |
| $parallelize = $noParallelNamespaces -notcontains "${{ matrix.namespace }}" | |
| $json = Get-Content "${{ env.unbounded_tests_path }}/xunit.runner.json" | ConvertFrom-Json | |
| $json | Add-Member -Name "parallelizeAssembly" -Value $parallelize -MemberType NoteProperty | |
| $json | Add-Member -Name "parallelizeTestCollections" -Value $parallelize -MemberType NoteProperty | |
| $json | ConvertTo-Json | Out-File "${{ env.unbounded_tests_path }}/xunit.runner.json" | |
| ${{ env.unbounded_tests_path }}/NewRelic.Agent.UnboundedIntegrationTests.exe -namespace NewRelic.Agent.UnboundedIntegrationTests.${{ matrix.namespace }} -trx "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_testResults.trx" | |
| if ($Env:enhanced_logging -eq $True) { | |
| Write-Host "Get HostableWebCore errors (if any)" | |
| Get-EventLog -LogName Application -Source HostableWebCore -ErrorAction:Ignore | |
| Write-Host "Get .NET Runtime errors (if any)" | |
| Get-EventLog -LogName Application -Source ".NET Runtime" -EntryType "Error","Warning" -ErrorAction:Ignore | |
| } | |
| shell: powershell | |
| - name: Extract payload bytes log from TRX | |
| if: inputs.aggregate_payload_data == true || github.event_name == 'schedule' | |
| run: | | |
| $trxFile = "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_testResults.trx" | |
| $payloadLog = "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_payload_bytes.json" | |
| Write-Host "Checking for TRX file at: $trxFile" | |
| if (Test-Path $trxFile) { | |
| $fileSize = (Get-Item $trxFile).Length | |
| Write-Host "TRX file found. Size: $fileSize bytes" | |
| # Read TRX file and extract lines containing payload information from Message elements | |
| [xml]$trxContent = Get-Content $trxFile | |
| $messageElements = $trxContent.SelectNodes("//*[local-name()='Output']/*[local-name()='TextMessages']/*[local-name()='Message']") | |
| $payloadData = @{} | |
| foreach ($message in $messageElements) { | |
| $text = $message.InnerText | |
| if ($text -match "^(.+?):\s*Total payload bytes sent:\s*(\d+)") { | |
| $className = $Matches[1].Trim() | |
| $byteCount = [int]$Matches[2] | |
| $payloadData[$className] = $byteCount | |
| Write-Host "Found: $className = $byteCount bytes" | |
| } | |
| } | |
| Write-Host "Found $($payloadData.Count) payload log entries" | |
| if ($payloadData.Count -gt 0) { | |
| $payloadData | ConvertTo-Json | Out-File -FilePath $payloadLog -Encoding UTF8 | |
| Write-Host "Extracted payload logs to $payloadLog" | |
| Write-Host "Content:" | |
| Get-Content $payloadLog | ForEach-Object { Write-Host $_ } | |
| } else { | |
| Write-Host "No payload byte logs found in TRX file" | |
| @{} | ConvertTo-Json | Out-File -FilePath $payloadLog -Encoding UTF8 | |
| } | |
| } else { | |
| Write-Host "TRX file not found" | |
| @{} | ConvertTo-Json | Out-File -FilePath $payloadLog -Encoding UTF8 | |
| } | |
| shell: powershell | |
| - name: Upload payload bytes log | |
| if: inputs.aggregate_payload_data == true || github.event_name == 'schedule' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: unbounded-payload-bytes-${{ matrix.namespace }} | |
| path: C:\IntegrationTestWorkingDirectory\TestResults\*_payload_bytes.json | |
| if-no-files-found: warn | |
| - name: Archive unbounded test results on failure | |
| if: ${{ failure() }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: unbounded-test-working-directory-${{ matrix.namespace }} | |
| path: | | |
| C:\IntegrationTestWorkingDirectory\**\*.log | |
| C:\IntegrationTestWorkingDirectory\**\*.config | |
| C:\IntegrationTestWorkingDirectory\TestResults\**\*TestResults.trx | |
| if-no-files-found: error | |
| create-package-rpm: | |
| needs: build-fullagent-msi | |
| name: Create RPM Package | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 | |
| with: | |
| disable-sudo: true | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Download msi _build Artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: msi-build-folder-artifacts | |
| path: src/_build | |
| - name: Download Agent Home Folders | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: homefolders | |
| path: src/Agent | |
| - name: Convert GPG Private Key Into File | |
| id: write_gpgkey | |
| run: | | |
| filePath="/tmp/private_gpg_key.gpg" | |
| echo "${{ secrets.NEW_GPG_KEY }}" | base64 -d > $filePath | |
| echo "filePath=$filePath" >> $GITHUB_OUTPUT | |
| shell: bash | |
| - name: Copy GPG Key to keys | |
| run: | | |
| mkdir ${{ github.workspace }}/build/Linux/keys | |
| cp -f ${{ steps.write_gpgkey.outputs.filePath }} ${{ github.workspace }}/build/Linux/keys/private_gpg_key.gpg | |
| shell: bash | |
| - name: Build RPM | |
| run: | | |
| agentVersion=${{ needs.build-fullagent-msi.outputs.agentVersion }} | |
| if [[ "$agentVersion" =~ [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ ]]; then | |
| major=$(echo $agentVersion | cut -d'.' -f1) | |
| minor=$(echo $agentVersion | cut -d'.' -f2) | |
| patch=$(echo $agentVersion | cut -d'.' -f3) | |
| agentVersion="${major}.${minor}.${patch}" | |
| echo "agentVersion is simplified to $agentVersion" | |
| fi | |
| cd ${{ github.workspace }}/build/Linux | |
| docker compose build build_rpm | |
| docker compose run -e AGENT_VERSION=$agentVersion -e GPG_KEY=/keys/private_gpg_key.gpg -e GPG_KEY_PASSPHRASE=${{ secrets.NEW_GPG_KEY_PASSPHRASE }} build_rpm | |
| shell: bash | |
| - name: Archive RPM Package Artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: rpm-build-artifacts | |
| path: ${{ github.workspace }}/src/_build/CoreArtifacts | |
| if-no-files-found: error | |
| create-package-deb: | |
| needs: build-fullagent-msi | |
| name: Create Debian package | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 | |
| with: | |
| disable-sudo: true | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Download Agent Home Folders | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: homefolders | |
| path: src/Agent | |
| - name: Download msi _build Artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: msi-build-folder-artifacts | |
| path: src/_build | |
| - name: Build Debian Package | |
| run: | | |
| agentVersion=${{ needs.build-fullagent-msi.outputs.agentVersion }} | |
| if [[ "$agentVersion" =~ [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ ]]; then | |
| major=$(echo $agentVersion | cut -d'.' -f1) | |
| minor=$(echo $agentVersion | cut -d'.' -f2) | |
| patch=$(echo $agentVersion | cut -d'.' -f3) | |
| agentVersion="${major}.${minor}.${patch}" | |
| echo "agentVersion is simplified to $agentVersion" | |
| fi | |
| cd ${{ github.workspace }}/build/Linux | |
| docker compose build build_deb | |
| docker compose run -e AGENT_VERSION=$agentVersion build_deb | |
| shell: bash | |
| - name: Archive Debian Package Artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: debian-build-artifacts | |
| path: ${{ github.workspace }}/src/_build/CoreArtifacts | |
| if-no-files-found: error | |
| run-artifactbuilder: | |
| needs: [create-package-rpm, create-package-deb] | |
| name: Run ArtifactBuilder | |
| runs-on: windows-2022 | |
| steps: | |
| - name: Install latest .NET 10 SDK | |
| uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 | |
| with: | |
| dotnet-version: '10.0.x' | |
| dotnet-quality: 'ga' | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| filter: blob:none | |
| - name: Download Agent Home Folders | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: homefolders | |
| path: src/Agent | |
| - name: Download msi _build Artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: msi-build-folder-artifacts | |
| path: src/_build | |
| - name: Download Debian _build Artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: debian-build-artifacts | |
| path: src/_build/CoreArtifacts | |
| - name: Download RPM _build Artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: rpm-build-artifacts | |
| path: src/_build/CoreArtifacts | |
| - name: Add msbuild to PATH | |
| uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0 | |
| - name: Build NewRelic.NuGetHelper | |
| run: | | |
| MSBuild.exe -restore -m -p:Configuration=Release ${{ github.workspace }}\build\NewRelic.NuGetHelper\NewRelic.NuGetHelper.csproj | |
| shell: powershell | |
| - name: Run ArtifactBuilder | |
| run: | | |
| ${{ github.workspace }}\build\package.ps1 -configuration Release -IncludeDownloadSite | |
| shell: powershell | |
| - name: Archive Deploy Artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: deploy-artifacts | |
| path: | | |
| ${{ github.workspace }}\build\BuildArtifacts | |
| if-no-files-found: error | |
| # This job is necessary in order for us to have a branch protection rule for tests with a matrix | |
| # if any of the matrix tests fail, this job fails and the branch protection rule keeps the PR from merging | |
| integration-test-status: | |
| name: Check Test Matrix Status | |
| runs-on: ubuntu-latest | |
| needs: [run-linux-container-tests, run-integration-tests, run-unbounded-tests] | |
| if: always() | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 | |
| with: | |
| disable-sudo: true | |
| egress-policy: audit | |
| - name: Successful test run | |
| if: ${{ !(contains(needs.*.result, 'failure')) }} | |
| run: exit 0 | |
| - name: Failing test run | |
| if: ${{ contains(needs.*.result, 'failure') }} | |
| run: exit 1 | |
| aggregate-integration-payload-logs: | |
| name: Aggregate Integration Test Payload Logs | |
| needs: [integration-test-status] | |
| runs-on: ubuntu-latest | |
| if: inputs.aggregate_payload_data == true || github.event_name == 'schedule' | |
| permissions: | |
| actions: write | |
| contents: read | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 | |
| with: | |
| disable-sudo: true | |
| egress-policy: audit | |
| - name: Download all integration payload artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: integration-payload-bytes-* | |
| path: payload-logs/integration | |
| merge-multiple: true | |
| - name: Combine payload logs | |
| run: | | |
| mkdir -p combined-logs | |
| if [ -d "payload-logs/integration" ]; then | |
| echo "[" > combined-logs/integration_payload_bytes_combined.json | |
| first=true | |
| grand_total=0 | |
| # Process JSON files and merge into array | |
| while IFS= read -r file; do | |
| echo "Processing: $(basename "$file")" | |
| # Read JSON and extract key-value pairs | |
| while IFS= read -r line; do | |
| # Skip lines that are just braces or empty | |
| if [[ "$line" =~ ^[[:space:]]*\{[[:space:]]*$ ]] || [[ "$line" =~ ^[[:space:]]*\}[[:space:]]*$ ]] || [[ -z "$line" ]]; then | |
| continue | |
| fi | |
| # Extract class name and byte count from JSON property | |
| if [[ "$line" =~ \"([^\"]+)\":[[:space:]]*([0-9]+) ]]; then | |
| className="${BASH_REMATCH[1]}" | |
| byteCount="${BASH_REMATCH[2]}" | |
| grand_total=$((grand_total + byteCount)) | |
| if [ "$first" = true ]; then | |
| first=false | |
| else | |
| echo "," >> combined-logs/integration_payload_bytes_combined.json | |
| fi | |
| echo " {\"className\": \"$className\", \"payloadBytes\": $byteCount}" >> combined-logs/integration_payload_bytes_combined.json | |
| fi | |
| done < "$file" | |
| done < <(find payload-logs/integration -name "*_payload_bytes.json" -type f | sort) | |
| echo "" >> combined-logs/integration_payload_bytes_combined.json | |
| echo "]" >> combined-logs/integration_payload_bytes_combined.json | |
| # Create summary metadata file | |
| echo "{" > combined-logs/integration_payload_bytes_summary.json | |
| echo " \"testType\": \"Integration Tests\"," >> combined-logs/integration_payload_bytes_summary.json | |
| echo " \"generatedAt\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"," >> combined-logs/integration_payload_bytes_summary.json | |
| echo " \"grandTotalBytes\": $grand_total" >> combined-logs/integration_payload_bytes_summary.json | |
| echo "}" >> combined-logs/integration_payload_bytes_summary.json | |
| echo "Combined payload JSON created with grand total: $grand_total bytes" | |
| echo "Payload data:" | |
| cat combined-logs/integration_payload_bytes_combined.json | |
| echo "" | |
| echo "Summary:" | |
| cat combined-logs/integration_payload_bytes_summary.json | |
| else | |
| echo "No integration payload logs found" | |
| echo "[]" > combined-logs/integration_payload_bytes_combined.json | |
| echo "{\"testType\": \"Integration Tests\", \"grandTotalBytes\": 0}" > combined-logs/integration_payload_bytes_summary.json | |
| fi | |
| shell: bash | |
| - name: Upload combined integration payload log | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: combined-integration-payload-bytes | |
| path: combined-logs/integration_payload_bytes_*.json | |
| if-no-files-found: warn | |
| - name: Delete individual integration payload artifacts | |
| run: | | |
| # Get all artifacts from this run matching the pattern and delete them | |
| gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \ | |
| --jq '.artifacts[] | select(.name | startswith("integration-payload-bytes-")) | "\(.id) \(.name)"' \ | |
| | while read -r artifact_id artifact_name; do | |
| echo "Deleting artifact: $artifact_name (ID: $artifact_id)" | |
| gh api --method DELETE repos/${{ github.repository }}/actions/artifacts/$artifact_id \ | |
| && echo "Successfully deleted $artifact_name" \ | |
| || echo "Failed to delete $artifact_name" | |
| done | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: bash | |
| continue-on-error: true | |
| aggregate-unbounded-payload-logs: | |
| name: Aggregate Unbounded Test Payload Logs | |
| needs: [integration-test-status] | |
| runs-on: ubuntu-latest | |
| if: inputs.aggregate_payload_data == true || github.event_name == 'schedule' | |
| permissions: | |
| actions: write | |
| contents: read | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 | |
| with: | |
| disable-sudo: true | |
| egress-policy: audit | |
| - name: Download all unbounded payload artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: unbounded-payload-bytes-* | |
| path: payload-logs/unbounded | |
| merge-multiple: true | |
| - name: Combine payload logs | |
| run: | | |
| mkdir -p combined-logs | |
| if [ -d "payload-logs/unbounded" ]; then | |
| echo "[" > combined-logs/unbounded_payload_bytes_combined.json | |
| first=true | |
| grand_total=0 | |
| # Process JSON files and merge into array | |
| while IFS= read -r file; do | |
| echo "Processing: $(basename "$file")" | |
| # Read JSON and extract key-value pairs | |
| while IFS= read -r line; do | |
| # Skip lines that are just braces or empty | |
| if [[ "$line" =~ ^[[:space:]]*\{[[:space:]]*$ ]] || [[ "$line" =~ ^[[:space:]]*\}[[:space:]]*$ ]] || [[ -z "$line" ]]; then | |
| continue | |
| fi | |
| # Extract class name and byte count from JSON property | |
| if [[ "$line" =~ \"([^\"]+)\":[[:space:]]*([0-9]+) ]]; then | |
| className="${BASH_REMATCH[1]}" | |
| byteCount="${BASH_REMATCH[2]}" | |
| grand_total=$((grand_total + byteCount)) | |
| if [ "$first" = true ]; then | |
| first=false | |
| else | |
| echo "," >> combined-logs/unbounded_payload_bytes_combined.json | |
| fi | |
| echo " {\"className\": \"$className\", \"payloadBytes\": $byteCount}" >> combined-logs/unbounded_payload_bytes_combined.json | |
| fi | |
| done < "$file" | |
| done < <(find payload-logs/unbounded -name "*_payload_bytes.json" -type f | sort) | |
| echo "" >> combined-logs/unbounded_payload_bytes_combined.json | |
| echo "]" >> combined-logs/unbounded_payload_bytes_combined.json | |
| # Create summary metadata file | |
| echo "{" > combined-logs/unbounded_payload_bytes_summary.json | |
| echo " \"testType\": \"Unbounded Integration Tests\"," >> combined-logs/unbounded_payload_bytes_summary.json | |
| echo " \"generatedAt\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"," >> combined-logs/unbounded_payload_bytes_summary.json | |
| echo " \"grandTotalBytes\": $grand_total" >> combined-logs/unbounded_payload_bytes_summary.json | |
| echo "}" >> combined-logs/unbounded_payload_bytes_summary.json | |
| echo "Combined payload JSON created with grand total: $grand_total bytes" | |
| echo "Payload data:" | |
| cat combined-logs/unbounded_payload_bytes_combined.json | |
| echo "" | |
| echo "Summary:" | |
| cat combined-logs/unbounded_payload_bytes_summary.json | |
| else | |
| echo "No unbounded payload logs found" | |
| echo "[]" > combined-logs/unbounded_payload_bytes_combined.json | |
| echo "{\"testType\": \"Unbounded Integration Tests\", \"grandTotalBytes\": 0}" > combined-logs/unbounded_payload_bytes_summary.json | |
| fi | |
| shell: bash | |
| - name: Upload combined unbounded payload log | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: combined-unbounded-payload-bytes | |
| path: combined-logs/unbounded_payload_bytes_*.json | |
| if-no-files-found: warn | |
| - name: Delete individual unbounded payload artifacts | |
| run: | | |
| # Get all artifacts from this run matching the pattern and delete them | |
| gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \ | |
| --jq '.artifacts[] | select(.name | startswith("unbounded-payload-bytes-")) | "\(.id) \(.name)"' \ | |
| | while read -r artifact_id artifact_name; do | |
| echo "Deleting artifact: $artifact_name (ID: $artifact_id)" | |
| gh api --method DELETE repos/${{ github.repository }}/actions/artifacts/$artifact_id \ | |
| && echo "Successfully deleted $artifact_name" \ | |
| || echo "Failed to delete $artifact_name" | |
| done | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: bash | |
| continue-on-error: true | |
| display-all-payload-summaries: | |
| name: Display All Payload Summaries | |
| needs: [aggregate-integration-payload-logs, aggregate-unbounded-payload-logs, run-linux-container-tests] | |
| runs-on: ubuntu-latest | |
| if: inputs.aggregate_payload_data == true || github.event_name == 'schedule' | |
| permissions: | |
| actions: write | |
| contents: read | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 | |
| with: | |
| disable-sudo: true | |
| egress-policy: audit | |
| - name: Download combined integration payload log | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: combined-integration-payload-bytes | |
| path: payload-summaries | |
| continue-on-error: true | |
| - name: Download combined unbounded payload log | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: combined-unbounded-payload-bytes | |
| path: payload-summaries | |
| continue-on-error: true | |
| - name: Download combined Linux container payload log | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: combined-linux-container-payload-bytes | |
| path: payload-summaries | |
| continue-on-error: true | |
| - name: Create consolidated JSON output | |
| run: | | |
| if [ -d "payload-summaries" ]; then | |
| echo "{" > final_payload_summary.json | |
| first_type=true | |
| overall_total=0 | |
| # Process each test type's combined data | |
| for data_file in payload-summaries/*_combined.json; do | |
| if [ -f "$data_file" ]; then | |
| filename=$(basename "$data_file" _payload_bytes_combined.json) | |
| # Determine test type name | |
| case "$filename" in | |
| integration) | |
| test_type="integrationTests" | |
| ;; | |
| unbounded) | |
| test_type="unboundedTests" | |
| ;; | |
| linux_container) | |
| test_type="containerTests" | |
| ;; | |
| *) | |
| test_type="$filename" | |
| ;; | |
| esac | |
| # Get corresponding summary file for grand total and timestamp | |
| summary_file="payload-summaries/${filename}_payload_bytes_summary.json" | |
| grand_total=0 | |
| generated_at="" | |
| if [ -f "$summary_file" ]; then | |
| if grep -q "grandTotalBytes" "$summary_file"; then | |
| grand_total=$(grep -oP '"grandTotalBytes":\s*\K\d+' "$summary_file") | |
| overall_total=$((overall_total + grand_total)) | |
| fi | |
| if grep -q "generatedAt" "$summary_file"; then | |
| generated_at=$(grep -oP '"generatedAt":\s*"\K[^"]+' "$summary_file") | |
| fi | |
| fi | |
| # Add comma separator if not first entry | |
| if [ "$first_type" = false ]; then | |
| echo "," >> final_payload_summary.json | |
| fi | |
| first_type=false | |
| # Add test type section | |
| echo " \"$test_type\": {" >> final_payload_summary.json | |
| echo " \"generatedAt\": \"$generated_at\"," >> final_payload_summary.json | |
| echo " \"bytes\": $grand_total," >> final_payload_summary.json | |
| echo " \"details\": [" >> final_payload_summary.json | |
| # Read and merge test class data from combined file, converting payloadBytes to bytes | |
| first_test=true | |
| while IFS= read -r line; do | |
| # Skip array brackets and empty lines | |
| if [[ "$line" =~ ^[[:space:]]*\[[[:space:]]*$ ]] || [[ "$line" =~ ^[[:space:]]*\][[:space:]]*$ ]] || [[ -z "$line" ]]; then | |
| continue | |
| fi | |
| # Extract className and payloadBytes, convert to new format | |
| if [[ "$line" =~ \"className\":[[:space:]]*\"([^\"]+)\".*\"payloadBytes\":[[:space:]]*([0-9]+) ]]; then | |
| className="${BASH_REMATCH[1]}" | |
| byteCount="${BASH_REMATCH[2]}" | |
| # Add comma separator if not first test entry | |
| if [ "$first_test" = false ]; then | |
| echo "," >> final_payload_summary.json | |
| fi | |
| first_test=false | |
| echo " {\"className\": \"$className\", \"bytes\": $byteCount}" >> final_payload_summary.json | |
| fi | |
| done < "$data_file" | |
| echo "" >> final_payload_summary.json | |
| echo " ]" >> final_payload_summary.json | |
| echo -n " }" >> final_payload_summary.json | |
| fi | |
| done | |
| echo "," >> final_payload_summary.json | |
| echo " \"bytes\": $overall_total" >> final_payload_summary.json | |
| echo "}" >> final_payload_summary.json | |
| echo "==========================================" | |
| echo "FINAL CONSOLIDATED PAYLOAD SUMMARY" | |
| echo "==========================================" | |
| cat final_payload_summary.json | |
| echo "" | |
| echo "==========================================" | |
| echo "OVERALL TOTAL: $overall_total bytes" | |
| echo "==========================================" | |
| else | |
| echo "No payload summary files found" | |
| echo '{"bytes": 0}' > final_payload_summary.json | |
| fi | |
| shell: bash | |
| - name: Upload final consolidated summary | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: final-payload-summary | |
| path: final_payload_summary.json | |
| if-no-files-found: warn | |
| - name: Delete combined payload artifacts | |
| run: | | |
| # Get combined artifacts from this run and delete them | |
| gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \ | |
| --jq '.artifacts[] | select(.name | startswith("combined-")) | select(.name | endswith("-payload-bytes")) | "\(.id) \(.name)"' \ | |
| | while read -r artifact_id artifact_name; do | |
| echo "Deleting artifact: $artifact_name (ID: $artifact_id)" | |
| gh api --method DELETE repos/${{ github.repository }}/actions/artifacts/$artifact_id \ | |
| && echo "Successfully deleted $artifact_name" \ | |
| || echo "Failed to delete $artifact_name" | |
| done | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: bash | |
| continue-on-error: true |