Testcontainers labels #204
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 | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| pull_request: | |
| branches: [ main, develop ] | |
| workflow_dispatch: | |
| env: | |
| DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer | |
| jobs: | |
| # docker-host is designed to stay alive while macOS jobs run, providing a tunnel to the Docker daemon on the Linux runner. It also pre-pulls and caches common images to speed up tests. | |
| docker-host: | |
| name: Docker Host (Tunnel) | |
| runs-on: ubuntu-latest | |
| if: github.event_name != 'schedule' # Only run on explicit triggers | |
| steps: | |
| - name: Setup SSH and bore tunnel | |
| run: | | |
| sudo systemctl start docker || true | |
| sudo chmod 666 /var/run/docker.sock | |
| # Setup SSH | |
| sudo apt-get update && sudo apt-get install -y openssh-server | |
| sudo mkdir -p /run/sshd | |
| sudo ssh-keygen -A | |
| sudo sed -i 's/^#*AllowStreamLocalForwarding.*/AllowStreamLocalForwarding yes/' /etc/ssh/sshd_config | |
| sudo sed -i 's/^#*AllowTcpForwarding.*/AllowTcpForwarding yes/' /etc/ssh/sshd_config | |
| sudo systemctl restart ssh || sudo /usr/sbin/sshd | |
| # Setup Keys | |
| ssh-keygen -t ed25519 -f ./id_ed25519 -N "" | |
| mkdir -p ~/.ssh | |
| cat ./id_ed25519.pub >> ~/.ssh/authorized_keys | |
| chmod 700 ~/.ssh | |
| chmod 600 ~/.ssh/authorized_keys | |
| # Start Tunnel — download, verify SHA256, then extract | |
| BORE_URL="https://github.com/ekzhang/bore/releases/download/v0.5.1/bore-v0.5.1-x86_64-unknown-linux-musl.tar.gz" | |
| BORE_SHA256="f73f3c608fcb926cadeecd7615302e452eea60998abe7204c4383980f1e2912e" | |
| curl -fsSL -o bore.tar.gz "$BORE_URL" | |
| echo "${BORE_SHA256} bore.tar.gz" | sha256sum -c - | |
| tar zxf bore.tar.gz | |
| rm bore.tar.gz | |
| ./bore local 22 --to bore.pub > bore.log 2>&1 & | |
| sleep 5 | |
| PORT=$(grep -oP 'listening at bore.pub:\K\d+' bore.log | head -1) | |
| if [ -z "$PORT" ]; then cat bore.log; exit 1; fi | |
| echo "$PORT" > docker-port.txt | |
| - name: Cache Docker images | |
| uses: actions/cache@v4 | |
| id: cache-docker | |
| with: | |
| path: ~/docker-images | |
| key: ${{ runner.os }}-docker-images-${{ hashFiles('.github/workflows/ci.yml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-docker-images- | |
| - name: Load cached Docker images | |
| if: steps.cache-docker.outputs.cache-hit == 'true' | |
| run: | | |
| if [ -d ~/docker-images ]; then | |
| for img in ~/docker-images/*.tar; do | |
| if [ -f "$img" ]; then | |
| sudo docker load -i "$img" || true | |
| fi | |
| done | |
| fi | |
| - name: Pre-pull and save Docker images | |
| if: steps.cache-docker.outputs.cache-hit != 'true' | |
| run: | | |
| IMAGES=( | |
| "postgres:15" | |
| "rabbitmq:3.11" | |
| "redis:7" | |
| "mongo:7" | |
| "confluentinc/cp-kafka:7.4.0" | |
| "mysql:8" | |
| "nginx:latest" | |
| ) | |
| mkdir -p ~/docker-images | |
| for img in "${IMAGES[@]}"; do | |
| sudo docker pull "$img" || true | |
| safe_name=$(echo "$img" | tr '/:' '_') | |
| sudo docker save "$img" -o ~/docker-images/${safe_name}.tar || true | |
| done | |
| sudo chown -R $USER:$USER ~/docker-images | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: docker-port | |
| retention-days: 1 | |
| path: | | |
| docker-port.txt | |
| id_ed25519 | |
| - name: Keep alive | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| for i in {1..180}; do | |
| # Match any macOS job | |
| STATUS=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs | jq -r '[.jobs[] | select(.name | test("macos")) | .status] | if length == 0 then "running" elif all(. == "completed") then "completed" else "running" end') | |
| if [ "$STATUS" = "completed" ]; then | |
| echo "macOS tests completed. Shutting down tunnel." | |
| break | |
| fi | |
| sleep 10 | |
| done | |
| # Build and test across multiple platforms and Swift versions | |
| build-and-test: | |
| name: Build and Test | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| swift: '6.2' | |
| - os: ubuntu-latest | |
| swift: '6.1' | |
| - os: macos-latest | |
| swift: '6.1' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install system dependencies (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libsqlite3-dev llvm curl | |
| - name: Setup Docker (macOS) | |
| if: runner.os == 'macOS' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| for i in {1..20}; do | |
| gh run download ${{ github.run_id }} --repo ${{ github.repository }} -n docker-port --dir . && break || sleep 5 | |
| done | |
| PORT=$(cat docker-port.txt) | |
| chmod 600 id_ed25519 | |
| brew install docker | |
| export DOCKER_HOST="unix:///tmp/docker.sock" | |
| echo "DOCKER_HOST=$DOCKER_HOST" >> $GITHUB_ENV | |
| echo "Starting SSH tunnel to bore.pub:$PORT..." | |
| ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ | |
| -i ./id_ed25519 -N -L /tmp/docker.sock:/var/run/docker.sock \ | |
| runner@bore.pub -p $PORT & | |
| # Wait for socket to appear | |
| for i in {1..30}; do | |
| if [ -S /tmp/docker.sock ]; then | |
| echo "Socket is locally available." | |
| break | |
| fi | |
| sleep 1 | |
| done | |
| # Wait for readiness | |
| for i in {1..30}; do | |
| if docker system info > /dev/null 2>&1; then | |
| echo "docker is ready." | |
| break | |
| fi | |
| echo "Waiting for docker... (attempt $i)" | |
| sleep 2 | |
| done | |
| if ! docker system info > /dev/null 2>&1; then | |
| echo "Docker never became ready after 30 attempts" | |
| exit 1 | |
| fi | |
| - name: Setup Swift | |
| uses: swift-actions/setup-swift@v2 | |
| with: | |
| swift-version: ${{ matrix.swift }} | |
| - name: Cache Swift dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| .build | |
| .swiftpm | |
| key: ${{ matrix.os }}-swift-${{ matrix.swift }}-${{ hashFiles('Package.resolved') }} | |
| restore-keys: | | |
| ${{ matrix.os }}-swift-${{ matrix.swift }}- | |
| - name: Build | |
| run: swift build --configuration release --verbose | |
| - name: Run tests | |
| run: swift test --configuration release --verbose --enable-code-coverage | |
| - name: Upload test results | |
| uses: actions/upload-artifact@v4 | |
| if: failure() | |
| with: | |
| name: test-results-${{ matrix.os }}-swift-${{ matrix.swift }} | |
| path: | | |
| .build/**/*.xctest | |
| test_output.txt | |
| - name: Generate coverage report | |
| run: | | |
| # Use xcrun on macOS, plain llvm-cov on Linux | |
| if [ "${{ runner.os }}" = "macOS" ]; then | |
| LLVM_COV="xcrun llvm-cov" | |
| else | |
| LLVM_COV="llvm-cov" | |
| fi | |
| # Find the profdata file | |
| PROFDATA=$(find .build -name "default.profdata" -type f | head -1) | |
| echo "Found profdata: $PROFDATA" | |
| # Find the test binary (macOS bundles vs Linux plain binary) | |
| if [ "${{ runner.os }}" = "macOS" ]; then | |
| XCTEST_BUNDLE=$(find .build -name "*.xctest" -type d | head -1) | |
| if [ -n "$XCTEST_BUNDLE" ]; then | |
| TEST_NAME=$(basename "$XCTEST_BUNDLE" .xctest) | |
| BINARY="$XCTEST_BUNDLE/Contents/MacOS/$TEST_NAME" | |
| fi | |
| # Fallback: if no bundle was found or the derived binary is not executable, | |
| # search for alternative binary locations similar to the Linux branch. | |
| if [ -z "$BINARY" ] || [ ! -x "$BINARY" ]; then | |
| BINARY=$(find .build -name "*.xctest" -type f -perm /111 | head -1) | |
| if [ -z "$BINARY" ]; then | |
| BINARY=$(find .build -type f -name "*PackageTests" -perm /111 | head -1) | |
| fi | |
| fi | |
| else | |
| BINARY=$(find .build -name "*.xctest" -type f -executable | head -1) | |
| if [ -z "$BINARY" ]; then | |
| BINARY=$(find .build -type f -name "*PackageTests" -executable | head -1) | |
| fi | |
| fi | |
| echo "Found binary: $BINARY" | |
| # Generate LCOV report | |
| if [ -n "$PROFDATA" ] && [ -n "$BINARY" ] && [ -f "$PROFDATA" ] && [ -f "$BINARY" ]; then | |
| $LLVM_COV export \ | |
| -format="lcov" \ | |
| -instr-profile="$PROFDATA" \ | |
| "$BINARY" \ | |
| -ignore-filename-regex=".build|Tests" \ | |
| > coverage.lcov | |
| echo "Coverage report generated" | |
| # Print summary | |
| echo "--- Coverage Summary ---" | |
| $LLVM_COV report \ | |
| -instr-profile="$PROFDATA" \ | |
| "$BINARY" \ | |
| -ignore-filename-regex=".build|Tests" || true | |
| else | |
| echo "Could not find coverage files" | |
| echo "Available files:" | |
| find .build -name "*.profdata" -o -name "*.xctest" 2>/dev/null | head -20 | |
| touch coverage.lcov | |
| fi | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: ./coverage.lcov | |
| flags: swift,unittests | |
| name: testcontainers-swift-coverage | |
| fail_ci_if_error: false | |
| verbose: true | |
| - name: Upload coverage artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| path: coverage.lcov | |
| retention-days: 30 | |
| if-no-files-found: warn | |
| name: coverage-report-${{ matrix.os }}-${{ matrix.swift }} | |
| # Code quality checks | |
| code-quality: | |
| name: Code Quality | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Swift | |
| uses: swift-actions/setup-swift@v2 | |
| with: | |
| swift-version: '6.1' | |
| - name: Cache Swift dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: .build | |
| key: ubuntu-latest-swift-6.1-${{ hashFiles('Package.resolved') }} | |
| - name: Set up Homebrew | |
| id: set-up-homebrew | |
| uses: Homebrew/actions/setup-homebrew@main | |
| - name: Install SwiftFormat and SwiftLint | |
| run: | | |
| brew install swiftformat | |
| brew install swiftlint | |
| - name: Check code formatting | |
| run: swiftformat --lint --verbose . --config .swiftformat --reporter github-actions-log | |
| - name: Run SwiftLint | |
| run: swiftlint lint --strict --config .swiftlint.yml || true | |
| # Documentation checks | |
| documentation: | |
| name: Documentation | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Swift | |
| uses: swift-actions/setup-swift@v2 | |
| with: | |
| swift-version: '6.1' | |
| - name: Check documentation files | |
| run: | | |
| required_files=("README.md" "LICENSE" "Package.swift") | |
| for file in "${required_files[@]}"; do | |
| if [ ! -f "$file" ]; then | |
| echo "Missing required file: $file" | |
| exit 1 | |
| fi | |
| done | |
| - name: Generate documentation | |
| run: | | |
| swift package --allow-writing-to-directory docs generate-documentation --target Testcontainers --output-path docs | |
| continue-on-error: true | |
| - name: Upload documentation | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: documentation | |
| path: docs/ | |
| if: success() | |
| # Security and dependency checks | |
| security: | |
| name: Security Scan | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Swift | |
| uses: swift-actions/setup-swift@v2 | |
| with: | |
| swift-version: '6.1' | |
| - name: Run Swift Package Security Check | |
| run: | | |
| swift package resolve | |
| swift package show-dependencies --format json > dependencies.json | |
| - name: Check for known vulnerabilities | |
| run: | | |
| # Check for common security issues in dependencies | |
| if command -v safety &> /dev/null; then | |
| echo "Safety not available, skipping dependency vulnerability check" | |
| else | |
| echo "Dependency security check would go here" | |
| fi | |
| continue-on-error: true | |
| - name: CodeQL Analysis | |
| uses: github/codeql-action/init@v3 | |
| with: | |
| languages: swift | |
| continue-on-error: true | |
| - name: Perform CodeQL Analysis | |
| uses: github/codeql-action/analyze@v3 | |
| continue-on-error: true | |
| # License compliance | |
| license-check: | |
| name: License Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Check license headers | |
| run: | | |
| # Check that source files have license headers | |
| find Sources Tests -name "*.swift" -exec grep -L "Copyright" {} \; | head -10 | |
| echo "License header check completed" | |
| - name: Check dependency licenses | |
| run: | | |
| swift package resolve | |
| swift package show-dependencies --format json | jq -r '.dependencies[].name' > dependencies.txt | |
| echo "Dependencies listed in dependencies.txt" | |
| continue-on-error: true | |
| # Release preparation | |
| release-check: | |
| name: Release Check | |
| runs-on: ubuntu-latest | |
| if: github.ref == 'refs/heads/main' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Check version consistency | |
| run: | | |
| # Check if version in Package.swift matches git tags | |
| version=$(grep 'let package = Package' Package.swift | sed 's/.*name: "\([^"]*\)".*/\1/') | |
| echo "Package name: $version" | |
| echo "Release checks passed" | |
| - name: Check changelog | |
| run: | | |
| if [ -f CHANGELOG.md ]; then | |
| echo "Changelog exists" | |
| else | |
| echo "No CHANGELOG.md found" | |
| fi | |
| # Final status check | |
| ci-success: | |
| name: CI Success | |
| runs-on: ubuntu-latest | |
| needs: [build-and-test, code-quality, documentation, security, license-check] | |
| if: always() | |
| steps: | |
| - name: Check all jobs | |
| run: | | |
| if [[ "${{ needs.build-and-test.result }}" == "success" && \ | |
| "${{ needs.code-quality.result }}" == "success" && \ | |
| "${{ needs.documentation.result }}" != "failure" && \ | |
| "${{ needs.security.result }}" != "failure" && \ | |
| "${{ needs.license-check.result }}" != "failure" ]]; then | |
| echo "All critical checks passed!" | |
| exit 0 | |
| else | |
| echo "Some checks failed. Please review the job outputs." | |
| exit 1 | |
| fi |