0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🚀 Rails + PostgreSQLで、ERD(SchemaSpy)を、GitHub Actions + Cloudflare Pagesを自動デプロイ!Basic 認証付きで安全に公開する方法

Posted at

🔰 はじめに

Rails + PostgreSQL のデータベーススキーマを可視化する SchemaSpy を使って、自動でスキーマのドキュメントを生成し、 Cloudflare Pages にデプロイする方法を紹介します。

💡 この方法のメリット

  • GitHub Actions を活用して完全自動化
  • SchemaSpy を使って ER 図を生成
  • Cloudflare Pages にホスティング
  • Cloudflare Functions で Basic 認証を追加し、公開範囲を制限
  • GitHub Pages の制約を回避(Enterprise でなくても非公開運用が可能!)

🌍 使用するサービスの説明

GitHub

GitHub はソースコードを管理するプラットフォームです。今回のワークフローでは、GitHub Actions を使ってデータベースのスキーマを取得し、Cloudflare Pages に自動デプロイします。

GitHub Actions

GitHub Actions は CI/CD(継続的インテグレーション / 継続的デリバリー) を実現するための仕組みです。
リポジトリの変更をトリガーにして、スクリプトを自動実行できます。

GitHub Pages

GitHub Pages は GitHub が提供する静的サイトのホスティングサービスです。
ただし、無料プランでは「公開」しかできない(組織以下のリポジトリで、組織がエンタープライズプラン出ないと一般公開しかできないらしい)ため、非公開での利用は不可
そのため、今回は GitHub Pages ではなく Cloudflare Pages にデプロイします。

Cloudflare Pages + Functions

Cloudflare Pages は、GitHub リポジトリと連携してデプロイできるホスティングサービスです。
また、Cloudflare Functions を活用すると function機能を利用すれば Basic 認証をつけることが可能 なので、一応セキュアにスキーマ情報を管理できます。


📌 GitHub Actions の設定

以下の GitHub Actions ワークフローを .github/workflows/schemaspy.yml に作成します。

.github/workflows/schemaspy.yml
name: Generate SchemaSpy Docs

on:
  push:
    branches:
      - main  # main ブランチに push された時
    paths:
      - 'db/schema.rb'  # db/schema.rb が変更された時のみ実行
  workflow_dispatch:  # 手動実行も可能

jobs:
  generate-docs:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:latest
        env:
          POSTGRES_DB: my_database
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        ports:
          - 5432:5432
        options: >-
          --health-cmd "pg_isready -U postgres"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Wait for PostgreSQL to be ready
        run: sleep 10s

      - name: Setup Ruby and Bundler
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: .ruby-version # .ruby-version がない場合には、3.4.1 とか記載してください
          bundler-cache: true

      - name: Install dependencies
        run: bundle install

      - name: Set up database schema
        env:
          RAILS_ENV: test
          DATABASE_URL: postgres://postgres:postgres@localhost:5432/my_database
        run: |
          bundle exec rails db:create
          bundle exec rails db:migrate

      - name: Install Graphviz (for ER diagrams)
        run: sudo apt-get update && sudo apt-get install -y graphviz

      - name: Install SchemaSpy and JDBC Driver
        run: |
          wget https://github.com/schemaspy/schemaspy/releases/latest/download/schemaspy-6.2.4.jar -O schemaspy.jar
          wget https://jdbc.postgresql.org/download/postgresql-42.6.0.jar -O postgresql-jdbc.jar

      - name: Generate SchemaSpy documentation
        run: |
          mkdir -p schemaspy-output
          java -jar schemaspy.jar \
            -t pgsql \
            -host localhost \
            -port 5432 \
            -db my_database \
            -u postgres \
            -p postgres \
            -s public \
            -o schemaspy-output \
            -dp postgresql-jdbc.jar

      - name: Copy _middleware.js to Cloudflare Functions
        run: |
          mkdir -p schemaspy-output/functions
          cp cloudflare/_middleware.js schemaspy-output/functions/_middleware.js

      - name: Deploy to GitHub Pages
        uses: JamesIves/github-pages-deploy-action@v4
        with:
          branch: gh-pages
          folder: schemaspy-output
          clean: true

🔐 Cloudflare Functions で Basic 認証を追加

Cloudflare Pages で、Basic 認証を追加するための functions/_middleware.js を作成します。

      - name: Copy _middleware.js to Cloudflare Functions
        run: |
          mkdir -p schemaspy-output/functions
          cp cloudflare/_middleware.js schemaspy-output/functions/_middleware.js

この部分で、SchemaSpyが作成した schemaspy-output ディレクトリ以下にfunctions/_middleware.jsをコピーしてます。

中身は↓

functions/_middleware.js
/**
 * Shows how to restrict access using the HTTP Basic schema.
 * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication
 * @see https://tools.ietf.org/html/rfc7617
 *
 * A user-id containing a colon (":") character is invalid, as the
 * first colon in a user-pass string separates user and password.
 */
 
const BASIC_USER = 'admin'; // そのまま使わないで!
const BASIC_PASS = 'aBcDeFgHiJ'; // そのまま使わないで!

async function errorHandling(context) {
  try {
    return await context.next()
  } catch (err) {
    return new Response(`${err.message}\n${err.stack}`, { status: 500 })
  }
}

async function handleRequest({ next, request }) {
  // The "Authorization" header is sent when authenticated.
  if (request.headers.has("Authorization")) {
    const Authorization = request.headers.get('Authorization')
    // Throws exception when authorization fails.
    const [scheme, encoded] = Authorization.split(' ')
    // The Authorization header must start with Basic, followed by a space.
    if (!encoded || scheme !== 'Basic') {
      return new Response(`The Authorization header must start with Basic`, {
        status: 400,
      })
    }
    // Decodes the base64 value and performs unicode normalization.
    // @see https://datatracker.ietf.org/doc/html/rfc7613#section-3.3.2 (and #section-4.2.2)
    // @see https://dev.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/normalize
    const buffer = Uint8Array.from(atob(encoded), (character) =>
      character.charCodeAt(0)
    )
    const decoded = new TextDecoder().decode(buffer).normalize()
    // The username & password are split by the first colon.
    //=> example: "username:password"
    const index = decoded.indexOf(':')
    // The user & password are split by the first colon and MUST NOT contain control characters.
    // @see https://tools.ietf.org/html/rfc5234#appendix-B.1 (=> "CTL = %x00-1F / %x7F")
    if (index === -1 || /[\0-\x1F\x7F]/.test(decoded)) {
      return new Response('Invalid authorization value.', { status: 400 })
    }
    const user = decoded.substring(0, index);
    const pass = decoded.substring(index + 1);
    if (BASIC_USER !== user) {
      return new Response('Invalid credentials.', { status: 401 })
    }
    if (BASIC_PASS !== pass) {
      return new Response('Invalid credentials.', { status: 401 })
    }
    // Only returns this response when no exception is thrown.
    return await next()
  }
  // Not authenticated.
  return new Response('You need to login.', {
    status: 401,
    headers: {
      // Prompts the user for credentials.
      'WWW-Authenticate': 'Basic realm="my scope", charset="UTF-8"',
    },
  })
}
export const onRequest = [errorHandling, handleRequest]

参考:
https://www.to-r.net/media/cloudflare-pages-basic/

🚀 GitHub Actions → Cloudflare Pages のデプロイの流れ

  1. mainブランチで、db/schema.rb が変更されたら GitHub Actions が実行される。
  2. SchemaSpy でデータベーススキーマの HTML を生成。
  3. schemaspy-output/functions/_middleware.js をコピー(Basic 認証を追加)。
  4. GitHub Pages に push → Cloudflare Pages がビルドを検知し、自動でデプロイ。

Cloudflare Pageの設定方法

こちらのページを参考にさせていただきました。
https://qiita.com/RyoWakabayashi/items/2d40b6e8ae7ddee092db

ちなみ 対象ブランチに関しては、 action で、指定している、 gh-pages を指定します。

      - name: Deploy to GitHub Pages
        uses: JamesIves/github-pages-deploy-action@v4
        with:
          branch: gh-pages
          folder: schemaspy-output
          clean: true

ちなみに、仮に今後Githubエンタープライズプランになれた時に、そのまま使えるので、 uses: JamesIves/github-pages-deploy-action@v4 を使わせていただいております。Github Page の設定で、対象ブランチに gh-pages を設定しないでください。(一般公開されちゃいます)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?