dbmcp 0.15.0

A single-binary MCP server for MySQL, MariaDB, PostgreSQL, and SQLite
name: CI

on:
  push:
  pull_request:

env:
  CARGO_TERM_COLOR: always

jobs:
  commits:
    name: Commits
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0
      - uses: cocogitto/cocogitto-action@v4
        with:
          command: check
          args: --from-latest-tag

  fmt:
    name: Format
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v7
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt
      - run: cargo fmt --all --check

  taplo:
    name: Taplo
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v7
      - uses: uncenter/setup-taplo@v2
        with:
          version: "0.9.3"
      - run: taplo fmt --check --diff

  clippy:
    name: Clippy
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v7
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy
      - uses: Swatinem/rust-cache@v2
      - run: cargo clippy --workspace --tests -- -D warnings

  test:
    name: Unit Tests (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-24.04, windows-2025]
    steps:
      - uses: actions/checkout@v7
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - run: cargo test --workspace --lib --bins

  mariadb:
    name: MariaDB
    runs-on: ubuntu-24.04
    timeout-minutes: 15
    services:
      mariadb:
        image: mariadb:12
        env:
          MARIADB_ROOT_HOST: '%'
          MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: "yes"
        ports:
          - 3306:3306
        options: >-
          --health-cmd="mariadb-admin ping -u root --silent"
          --health-interval=5s
          --health-timeout=3s
          --health-retries=30
      sshd:
        image: lscr.io/linuxserver/openssh-server:10.3_p1-r1-ls239
        env:
          PASSWORD_ACCESS: "true"
          USER_NAME: dbmcp
          USER_PASSWORD: dbmcp
          DOCKER_MODS: linuxserver/mods:openssh-server-ssh-tunnel
        ports:
          - 2222:2222
        options: >-
          --health-cmd="nc -z 127.0.0.1 2222"
          --health-interval=2s
          --health-timeout=2s
          --health-retries=60
    steps:
      - uses: actions/checkout@v7
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Seed database
        run: docker exec -i ${{ job.services.mariadb.id }} mariadb -u root < tests/seed/mysql.sql
      - name: Approval tests
        run: cargo test --test approval_mysql -- --test-threads=1
        env:
          DB_HOST: 127.0.0.1
          DB_PORT: 3306
      - name: Functional tests
        run: cargo test --test functional_mysql -- --test-threads=1
        env:
          DB_HOST: 127.0.0.1
          DB_PORT: 3306
      - name: Authorize SSH test keys
        run: docker exec -i ${{ job.services.sshd.id }} sh -c 'cat >> /config/.ssh/authorized_keys' < tests/ssh/authorized_keys
      - name: SSH tunnel tests
        run: cargo test --test functional_ssh -- --test-threads=1
        env:
          DB_BACKEND: mysql
          DB_HOST: mariadb
          DB_PORT: 3306
          SSH_HOST: 127.0.0.1
          SSH_PORT: 2222
          SSH_USER: dbmcp
          SSH_PASSWORD: dbmcp
          SSH_STRICT_HOST_KEY_CHECKING: "no"
          SSHD_CONTAINER: ${{ job.services.sshd.id }}

  mysql:
    name: MySQL
    runs-on: ubuntu-24.04
    timeout-minutes: 15
    services:
      mysql:
        image: mysql:9
        env:
          MYSQL_ROOT_HOST: '%'
          MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
        ports:
          - 3306:3306
        options: >-
          --health-cmd="mysqladmin ping -u root --silent"
          --health-interval=5s
          --health-timeout=3s
          --health-retries=30
      sshd:
        image: lscr.io/linuxserver/openssh-server:10.3_p1-r1-ls239
        env:
          PASSWORD_ACCESS: "true"
          USER_NAME: dbmcp
          USER_PASSWORD: dbmcp
          DOCKER_MODS: linuxserver/mods:openssh-server-ssh-tunnel
        ports:
          - 2222:2222
        options: >-
          --health-cmd="nc -z 127.0.0.1 2222"
          --health-interval=2s
          --health-timeout=2s
          --health-retries=60
    steps:
      - uses: actions/checkout@v7
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Seed database
        run: docker exec -i ${{ job.services.mysql.id }} mysql -u root < tests/seed/mysql.sql
      - name: Approval tests
        run: cargo test --test approval_mysql -- --test-threads=1
        env:
          DB_HOST: 127.0.0.1
          DB_PORT: 3306
      - name: Functional tests
        run: cargo test --test functional_mysql -- --test-threads=1
        env:
          DB_HOST: 127.0.0.1
          DB_PORT: 3306
      - name: Authorize SSH test keys
        run: docker exec -i ${{ job.services.sshd.id }} sh -c 'cat >> /config/.ssh/authorized_keys' < tests/ssh/authorized_keys
      - name: Export MySQL CA certificate
        run: docker exec ${{ job.services.mysql.id }} cat /var/lib/mysql/ca.pem > "$RUNNER_TEMP/mysql_ca.pem"
      - name: SSH tunnel tests
        run: cargo test --test functional_ssh -- --test-threads=1
        env:
          TLS_TEST_CA: ${{ runner.temp }}/mysql_ca.pem
          DB_BACKEND: mysql
          DB_HOST: mysql
          DB_PORT: 3306
          SSH_HOST: 127.0.0.1
          SSH_PORT: 2222
          SSH_USER: dbmcp
          SSH_PASSWORD: dbmcp
          SSH_STRICT_HOST_KEY_CHECKING: "no"
          SSHD_CONTAINER: ${{ job.services.sshd.id }}

  postgres:
    name: PostgreSQL
    runs-on: ubuntu-24.04
    timeout-minutes: 15
    services:
      postgres:
        image: postgres:18-alpine
        env:
          POSTGRES_USER: postgres
          POSTGRES_HOST_AUTH_METHOD: trust
        ports:
          - 5432:5432
        options: >-
          --health-cmd="pg_isready -U postgres"
          --health-interval=3s
          --health-timeout=3s
          --health-retries=15
      sshd:
        image: lscr.io/linuxserver/openssh-server:10.3_p1-r1-ls239
        env:
          PASSWORD_ACCESS: "true"
          USER_NAME: dbmcp
          USER_PASSWORD: dbmcp
          DOCKER_MODS: linuxserver/mods:openssh-server-ssh-tunnel
        ports:
          - 2222:2222
        options: >-
          --health-cmd="nc -z 127.0.0.1 2222"
          --health-interval=2s
          --health-timeout=2s
          --health-retries=60
    steps:
      - uses: actions/checkout@v7
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Seed database
        run: docker exec -i ${{ job.services.postgres.id }} psql -U postgres < tests/seed/postgres.sql
      - name: Approval tests
        run: cargo test --test approval_postgres -- --test-threads=1
        env:
          DB_HOST: 127.0.0.1
          DB_PORT: 5432
      - name: Functional tests
        run: cargo test --test functional_postgres -- --test-threads=1
        env:
          DB_HOST: 127.0.0.1
          DB_PORT: 5432
      - name: Authorize SSH test keys
        run: docker exec -i ${{ job.services.sshd.id }} sh -c 'cat >> /config/.ssh/authorized_keys' < tests/ssh/authorized_keys
      - name: SSH tunnel tests
        run: cargo test --test functional_ssh -- --test-threads=1
        env:
          DB_BACKEND: postgres
          DB_HOST: postgres
          DB_PORT: 5432
          SSH_HOST: 127.0.0.1
          SSH_PORT: 2222
          SSH_USER: dbmcp
          SSH_PASSWORD: dbmcp
          SSH_STRICT_HOST_KEY_CHECKING: "no"
          SSHD_CONTAINER: ${{ job.services.sshd.id }}

  sqlite:
    name: SQLite
    runs-on: ubuntu-24.04
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v7
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Seed database
        run: sqlite3 /tmp/app.db < tests/seed/sqlite.sql
      - name: Approval tests
        run: cargo test --test approval_sqlite -- --test-threads=1
        env:
          DB_PATH: /tmp/app.db
      - name: Functional tests
        run: cargo test --test functional_sqlite -- --test-threads=1
        env:
          DB_PATH: /tmp/app.db