chore: Bump Microsoft.AspNetCore.Mvc.Testing and 5 others #220
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: CI/CD Pipeline | |
| on: | |
| push: | |
| branches: [main, develop] | |
| pull_request: | |
| branches: [main, develop] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} | |
| env: | |
| DOTNET_VERSION: '10.0.x' | |
| COVERAGE_THRESHOLD: 80 | |
| DOTNET_NOLOGO: true | |
| DOTNET_CLI_TELEMETRY_OPTOUT: true | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| build-cache-key: ${{ steps.cache-key.outputs.key }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 # Shallow clones should be disabled for better analysis | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - name: Generate cache key | |
| id: cache-key | |
| run: | | |
| echo "key=${{ runner.os }}-build-${{ hashFiles('**/*.csproj', '**/*.sln') }}-${{ github.sha }}" >> $GITHUB_OUTPUT | |
| - name: Cache NuGet packages | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nuget- | |
| - name: Cache build output | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| **/bin | |
| **/obj | |
| key: ${{ steps.cache-key.outputs.key }} | |
| restore-keys: | | |
| ${{ runner.os }}-build-${{ hashFiles('**/*.csproj', '**/*.sln') }}- | |
| - name: Restore dependencies | |
| run: dotnet restore UserApi.sln | |
| - name: Build solution | |
| run: dotnet build UserApi.sln --no-restore --configuration Release | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: build-artifacts | |
| path: | | |
| **/bin/Release | |
| **/obj/Release | |
| UserApi.sln | |
| **/*.csproj | |
| **/*.props | |
| **/*.targets | |
| Directory.Build.props | |
| global.json | |
| retention-days: 1 | |
| code-quality: | |
| runs-on: ubuntu-latest | |
| needs: build | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: build-artifacts | |
| - name: Cache NuGet packages | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nuget- | |
| - name: Restore dependencies | |
| run: dotnet restore UserApi.sln | |
| - name: Run code formatting check | |
| run: dotnet format UserApi.sln --verify-no-changes --verbosity diagnostic | |
| - name: Run linting | |
| run: dotnet build UserApi.sln --verbosity normal --configuration Release /p:TreatWarningsAsErrors=true | |
| test: | |
| runs-on: ubuntu-latest | |
| needs: build | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: build-artifacts | |
| - name: Cache NuGet packages | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nuget- | |
| - name: Restore dependencies | |
| run: dotnet restore UserApi.sln | |
| - name: Run tests | |
| run: dotnet test UserApi.sln --no-build --configuration Release --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage --settings coverlet.runsettings | |
| - name: Generate coverage report | |
| run: | | |
| dotnet tool install -g dotnet-reportgenerator-globaltool | |
| reportgenerator -reports:"coverage/**/coverage.cobertura.xml" -targetdir:"coverage/report" -reporttypes:"Html;Cobertura;SonarQube" | |
| # Copy the generated Cobertura file to the expected location | |
| cp coverage/report/Cobertura.xml coverage/coverage.cobertura.xml | |
| - name: Upload coverage artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: coverage-reports | |
| path: | | |
| coverage/ | |
| retention-days: 30 | |
| - name: Upload coverage reports to Codecov | |
| uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de | |
| with: | |
| files: ./coverage/coverage.cobertura.xml | |
| flags: unittests | |
| name: codecov-umbrella | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| continue-on-error: true | |
| - name: Check coverage threshold | |
| run: | | |
| if [ -f "coverage/coverage.cobertura.xml" ]; then | |
| # Extract line-rate from Cobertura XML root element | |
| LINE_RATE=$(python3 -c " | |
| import xml.etree.ElementTree as ET | |
| tree = ET.parse('coverage/coverage.cobertura.xml') | |
| print(tree.getroot().attrib.get('line-rate', '0')) | |
| ") | |
| COVERAGE=$(python3 -c "print(int(float('${LINE_RATE}') * 100))") | |
| echo "Line coverage: ${COVERAGE}%" | |
| if [ "$COVERAGE" -lt "${{ env.COVERAGE_THRESHOLD }}" ]; then | |
| echo "::error::Coverage ${COVERAGE}% is below threshold ${{ env.COVERAGE_THRESHOLD }}%" | |
| exit 1 | |
| else | |
| echo "Coverage ${COVERAGE}% meets threshold ${{ env.COVERAGE_THRESHOLD }}%" | |
| fi | |
| else | |
| echo "::error::Coverage report not found at coverage/coverage.cobertura.xml" | |
| exit 1 | |
| fi | |
| security-scan: | |
| runs-on: ubuntu-latest | |
| needs: build | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: build-artifacts | |
| - name: Cache NuGet packages | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nuget- | |
| - name: Restore dependencies | |
| run: dotnet restore UserApi.sln | |
| - name: Run security scan | |
| run: | | |
| dotnet list package --vulnerable --include-transitive | |
| dotnet list package --deprecated | |
| sonar-analysis: | |
| runs-on: ubuntu-latest | |
| needs: [build, test] | |
| if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: build-artifacts | |
| - name: Download coverage artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: coverage-reports | |
| path: coverage | |
| - name: Install SonarCloud scanner | |
| run: | | |
| dotnet tool install --global dotnet-sonarscanner | |
| dotnet tool install --global dotnet-reportgenerator-globaltool | |
| - name: Cache NuGet packages | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nuget- | |
| - name: Restore dependencies | |
| run: dotnet restore UserApi.sln | |
| - name: Begin SonarCloud analysis | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| PR_NUMBER: ${{ github.event.number }} | |
| BRANCH_NAME: ${{ github.head_ref }} | |
| BASE_BRANCH: ${{ github.base_ref }} | |
| run: | | |
| if [ "${{ github.event_name }}" == "pull_request" ]; then | |
| dotnet sonarscanner begin \ | |
| /k:"devops-thiago_otel-core-example" \ | |
| /o:"devops-thiago" \ | |
| /n:"UserApi" \ | |
| /v:"1.0.0" \ | |
| /d:sonar.host.url="https://sonarcloud.io" \ | |
| /d:sonar.token="$SONAR_TOKEN" \ | |
| /d:sonar.pullrequest.key="$PR_NUMBER" \ | |
| /d:sonar.pullrequest.branch="$BRANCH_NAME" \ | |
| /d:sonar.pullrequest.base="$BASE_BRANCH" \ | |
| /d:sonar.coverageReportPaths="coverage/report/SonarQube.xml" \ | |
| /d:sonar.coverage.exclusions="**/Program.cs,**/Models/**,**/DTOs/**,**/Migrations/**,**/bin/**,**/obj/**,**/*Tests*/**" \ | |
| /d:sonar.exclusions="**/bin/**,**/obj/**,**/Migrations/**,**/coverage/**,**/test-coverage/**,**/TestResults/**,**/*.coverage,**/*.coveragexml,**/*.cobertura.xml" | |
| else | |
| dotnet sonarscanner begin \ | |
| /k:"devops-thiago_otel-core-example" \ | |
| /o:"devops-thiago" \ | |
| /n:"UserApi" \ | |
| /v:"1.0.0" \ | |
| /d:sonar.host.url="https://sonarcloud.io" \ | |
| /d:sonar.token="$SONAR_TOKEN" \ | |
| /d:sonar.branch.name="${{ github.ref_name }}" \ | |
| /d:sonar.coverageReportPaths="coverage/report/SonarQube.xml" \ | |
| /d:sonar.coverage.exclusions="**/Program.cs,**/Models/**,**/DTOs/**,**/Migrations/**,**/bin/**,**/obj/**,**/*Tests*/**" \ | |
| /d:sonar.exclusions="**/bin/**,**/obj/**,**/Migrations/**,**/coverage/**,**/test-coverage/**,**/TestResults/**,**/*.coverage,**/*.coveragexml,**/*.cobertura.xml" | |
| fi | |
| - name: Build solution for SonarQube analysis | |
| run: dotnet build UserApi.sln --no-restore --configuration Release | |
| - name: List available coverage files | |
| run: | | |
| echo "=== Available coverage files ===" | |
| find . -name "*.xml" -type f | grep -i coverage | head -10 | |
| echo "=== End coverage files ===" | |
| - name: Prepare coverage for SonarCloud | |
| run: | | |
| echo "=== Verifying coverage files for SonarCloud ===" | |
| # List all coverage files found | |
| echo "Coverage files in subdirectories:" | |
| find coverage -name "coverage.cobertura.xml" -type f | head -5 | |
| # Check if consolidated files exist | |
| if [ -f "coverage/coverage.cobertura.xml" ]; then | |
| echo "✓ Found consolidated Cobertura file: coverage/coverage.cobertura.xml" | |
| else | |
| echo "✗ Consolidated Cobertura file not found" | |
| echo "Will use individual coverage files: coverage/**/coverage.cobertura.xml" | |
| fi | |
| if [ -f "coverage/report/SonarQube.xml" ]; then | |
| echo "✓ Found SonarQube coverage file: coverage/report/SonarQube.xml" | |
| else | |
| echo "✗ SonarQube coverage file not found - reportgenerator may have failed" | |
| echo "Attempting to regenerate reports..." | |
| dotnet tool install -g dotnet-reportgenerator-globaltool || echo "ReportGenerator already installed" | |
| reportgenerator -reports:"coverage/**/coverage.cobertura.xml" -targetdir:"coverage/report" -reporttypes:"Html;Cobertura;SonarQube" || echo "Report generation failed" | |
| if [ -f "coverage/report/Cobertura.xml" ]; then | |
| cp coverage/report/Cobertura.xml coverage/coverage.cobertura.xml || echo "Failed to copy consolidated report" | |
| echo "✓ Successfully generated consolidated coverage files" | |
| fi | |
| fi | |
| echo "=== Final coverage file verification ===" | |
| ls -la coverage/coverage.cobertura.xml coverage/report/SonarQube.xml 2>/dev/null || echo "Some consolidated files missing" | |
| - name: End SonarCloud analysis | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| run: dotnet sonarscanner end /d:sonar.token="$SONAR_TOKEN" | |
| docker-build: | |
| runs-on: ubuntu-latest | |
| needs: [build, test, code-quality] | |
| if: github.event_name == 'pull_request' | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - name: Build Docker image for PR validation | |
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 | |
| with: | |
| context: . | |
| push: false | |
| tags: otel-core-example:pr-${{ github.event.pull_request.number }} | |
| load: true | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Verify Docker image was built | |
| run: | | |
| echo "Listing Docker images:" | |
| docker images | |
| echo "Checking if our image exists:" | |
| docker inspect otel-core-example:pr-${{ github.event.pull_request.number }} | |
| docker-publish: | |
| runs-on: ubuntu-latest | |
| needs: [build, test, code-quality] | |
| if: github.ref == 'refs/heads/main' | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - name: Login to Docker Hub | |
| uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 | |
| with: | |
| username: ${{ secrets.DOCKER_USERNAME }} | |
| password: ${{ secrets.DOCKER_PASSWORD }} | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 | |
| with: | |
| context: . | |
| push: true | |
| tags: | | |
| ${{ secrets.DOCKER_USERNAME }}/userapi:latest | |
| ${{ secrets.DOCKER_USERNAME }}/userapi:${{ github.sha }} | |
| ${{ secrets.DOCKER_USERNAME }}/otel-crud-api-net-core:v${{ github.run_number }} | |
| labels: | | |
| org.opencontainers.image.title=OpenTelemetry .NET Core CRUD API | |
| org.opencontainers.image.description=.NET Core REST API with OpenTelemetry integration and Alloy observability | |
| org.opencontainers.image.url=https://github.com/devops-thiago/otel-core-example | |
| org.opencontainers.image.source=https://github.com/devops-thiago/otel-core-example | |
| org.opencontainers.image.version=v${{ github.run_number }} | |
| org.opencontainers.image.created=${{ github.event.head_commit.timestamp }} | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| org.opencontainers.image.licenses=MIT | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| deploy: | |
| runs-on: ubuntu-latest | |
| needs: [docker-publish] | |
| if: github.ref == 'refs/heads/main' | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Deploy to staging | |
| run: | | |
| echo "Deploying to staging environment..." | |
| # Add your deployment steps here |