Skip to content

chore: Bump Microsoft.AspNetCore.Mvc.Testing and 5 others #220

chore: Bump Microsoft.AspNetCore.Mvc.Testing and 5 others

chore: Bump Microsoft.AspNetCore.Mvc.Testing and 5 others #220

Workflow file for this run

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