ランブック

Markdown ファイルを Confluence ページに同期

Markdown から Confluence へつながるドキュメントパイプラインを構築します。Markdown ファイルを Confluence のストレージ形式に変換してページとしてインポートし、ドライランと自動ラベル付けにも対応します。

この処理の内容: Markdown を Confluence にインポート

このランブックは、Markdown を Confluence にインポートする作業をスクリプト化したものです。ローカルのディレクトリから Markdown ファイルを探し、変換ツールとして pandoc を使って各ファイルを Confluence 互換の HTML に変換し、対応する Confluence ページを作成または更新します。ページタイトルは各ファイルの最初の # heading から取り出し、追跡用のラベルを自動で付けます。Git で管理するドキュメントを、手作業のインポートなしで Confluence と同期させ続けたい場合に最適です。pandoc のフラグやストレージ形式の詳細を含む、変換そのものの手順を順を追って確認したい場合は、Markdown から Confluence へのガイドから始めてください。このページでは、そのワークフローをすぐ実行できるスクリプトにまとめています。

これは、ドキュメントの乖離という繰り返し起きる問題を解決します。開発チームはコードの隣で Markdown を書き、関係者は Confluence でそのドキュメントを読むため、2 つのコピーは次第にずれていきます。この Markdown から Confluence へのスクリプトを、ドキュメント変更の最後の手順として実行してください。リリース前にローカルで実行しても、ドキュメント用ブランチへのマージのたびに CI で自動実行しても構いません。そうすれば Confluence は常にバージョン管理の内容を反映します。ページタイトルで検索し、重複を作らずに本文をその場で上書きするため、繰り返し実行しても冪等です。内容が変わっていないファイルはバイト単位で同一のストレージ形式の HTML を生成するので、再公開は何も変えない安全な操作になります。ここで使うコマンド一式は、コマンドリファレンス、より広範な Confluence CLI のリファレンス、手順を追って解説する Confluence CLI ガイドをご覧ください。

前提条件

クイックスタート

# Dry-run: see which pages would be created/updated
DRY_RUN=true ./doc-pipeline.sh DOCS prod

# Sync docs directory to DOCS space
./doc-pipeline.sh DOCS prod

# Sync a custom docs directory
DOCS_DIR="./my-docs" ./doc-pipeline.sh DOCS prod

ランブックスクリプト全文

doc-pipeline.sh

#!/bin/bash
# Automated Documentation Pipeline: Markdown -> Confluence
#
# This script syncs markdown documentation from a Git repository to Confluence.
# It converts markdown files to Confluence storage format and creates/updates pages.
#
# Usage:
#   ./doc-pipeline.sh --space DOCS --profile prod
#
# Requirements:
#   - atlassian-cli installed and configured
#   - pandoc (for markdown -> HTML conversion)
#   - jq (for JSON processing)

set -euo pipefail

# Configuration
SPACE_KEY="${1:-DOCS}"
PROFILE="${2:-default}"
DOCS_DIR="./docs"
DRY_RUN="${DRY_RUN:-false}"

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

log() {
    echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $*"
}

error() {
    echo -e "${RED}[ERROR]${NC} $*" >&2
}

warn() {
    echo -e "${YELLOW}[WARN]${NC} $*"
}

# Check dependencies
check_dependencies() {
    local missing=0

    for cmd in atlassian-cli pandoc jq; do
        if ! command -v "$cmd" &> /dev/null; then
            error "Required command not found: $cmd"
            missing=$((missing + 1))
        fi
    done

    if [ $missing -gt 0 ]; then
        error "$missing required dependencies missing. Please install them first."
        exit 1
    fi
}

# Convert markdown to Confluence storage format
md_to_confluence() {
    local md_file="$1"

    # Use pandoc to convert markdown to HTML
    pandoc -f markdown -t html "$md_file" | \
        # Basic cleanup for Confluence
        sed 's/<h1>/<h1 style="margin-top: 20px;">/g' | \
        sed 's/<code>/<code class="code-inline">/g'
}

# Get or create page by title
get_or_create_page() {
    local space_key="$1"
    local title="$2"
    local parent_id="${3:-}"

    # Search for existing page
    local page_id
    page_id=$(atlassian-cli confluence search cql \
        --output json \
        "space = $space_key AND title = \"$title\"" 2>/dev/null | \
        jq -r '.results[0].content.id // empty')

    if [ -n "$page_id" ]; then
        echo "$page_id"
        return
    fi

    # Create new page if not found
    if [ "$DRY_RUN" = "true" ]; then
        warn "[DRY-RUN] Would create page: $title"
        echo "DRY_RUN_PAGE_ID"
        return
    fi

    log "Creating new page: $title"

    local create_args=(
        "confluence" "page" "create"
        "--profile" "$PROFILE"
        "--space" "$space_key"
        "--title" "$title"
    )

    if [ -n "$parent_id" ]; then
        create_args+=("--parent" "$parent_id")
    fi

    atlassian-cli "${create_args[@]}" --output json | jq -r '.id'
}

# Update page content
update_page() {
    local page_id="$1"
    local title="$2"
    local content="$3"

    if [ "$DRY_RUN" = "true" ]; then
        warn "[DRY-RUN] Would update page $page_id: $title"
        return
    fi

    # Save content to temp file
    local temp_file
    temp_file=$(mktemp)
    echo "$content" > "$temp_file"

    log "Updating page $page_id: $title"

    atlassian-cli confluence page update \
        --profile "$PROFILE" \
        "$page_id" \
        --title "$title" \
        --body "$temp_file"

    rm -f "$temp_file"
}

# Add labels to page
add_labels() {
    local page_id="$1"
    shift
    local labels=("$@")

    if [ "$DRY_RUN" = "true" ]; then
        warn "[DRY-RUN] Would add labels to $page_id: ${labels[*]}"
        return
    fi

    for label in "${labels[@]}"; do
        log "Adding label '$label' to page $page_id"
        atlassian-cli confluence page add-label \
            --profile "$PROFILE" \
            "$page_id" \
            "$label" || warn "Failed to add label: $label"
    done
}

# Main pipeline
main() {
    log "Starting documentation pipeline"
    log "Space: $SPACE_KEY | Profile: $PROFILE | Dry-run: $DRY_RUN"

    check_dependencies

    # Find all markdown files
    if [ ! -d "$DOCS_DIR" ]; then
        error "Documentation directory not found: $DOCS_DIR"
        exit 1
    fi

    local processed=0
    local failed=0

    # Process each markdown file
    while IFS= read -r md_file; do
        log "Processing: $md_file"

        # Extract title from first heading
        local title
        title=$(grep -m 1 '^# ' "$md_file" | sed 's/^# //' || echo "$(basename "$md_file" .md)")

        # Convert to Confluence format
        local content
        content=$(md_to_confluence "$md_file")

        # Get or create page
        local page_id
        page_id=$(get_or_create_page "$SPACE_KEY" "$title")

        if [ -z "$page_id" ]; then
            error "Failed to get/create page for: $title"
            failed=$((failed + 1))
            continue
        fi

        # Update page content
        update_page "$page_id" "$title" "$content"

        # Add auto-generated label
        add_labels "$page_id" "auto-generated" "documentation"

        processed=$((processed + 1))

    done < <(find "$DOCS_DIR" -name "*.md" -type f)

    log "Pipeline complete: $processed processed, $failed failed"

    if [ $failed -gt 0 ]; then
        exit 1
    fi
}

main "$@"

仕組み

このパイプラインは意図的にステートレスです。どのページがどのファイルに対応するかを記録するローカルのデータベースはありません。代わりに、Markdown ファイルの最初の H1 が正式なページタイトルになり、Confluence の CQL クエリ(space = KEY AND title = "...")が検索キーになります。つまりスクリプトが行う「差分」は行単位の比較ではなく、アップサートです。一致するタイトルが見つかれば、変換した本文が confluence page update で既存のページ内容を置き換え、見つからなければ confluence page create が新しいページを作成します。変換そのものは pandoc -f markdown -t html が担当し、その後 2 つの軽量な sed 処理が Confluence のストレージ形式に合わせて出力を調整します(h1 要素に上マージンを追加し、インラインの code にクラスを付けます)。pandoc はフェンス付きコードブロック、リンク、ほとんどの表をきれいに処理しますが、複雑な GitHub 形式の Markdown の表や生の HTML は手作業での確認が必要になることがあります。DRY_RUN=true を設定すると、作成、更新、ラベル付けの各手順は API を呼び出さずに実行内容を表示するだけになるため、新しいドキュメントディレクトリを安全に検証できます。以下の番号付きの手順は、ドキュメントツリーを 1 回走査する流れをたどったものです。

1

依存関係の確認。atlassian-clipandocjq のすべてがシステムの PATH で利用できることを、処理を進める前に確認します。

2

Markdown ファイルの検出。設定したドキュメントディレクトリ(既定は ./docs)を再帰的に走査し、すべての .md ファイルを探します。各ファイルが 1 つの Confluence ページになります。

3

Confluence 形式への変換。各 Markdown ファイルを pandoc -f markdown -t html に通し、Confluence のストレージ形式に合わせて基本的な整形を行います。

4

ページの作成または更新。タイトルで Confluence を検索し、既存のページを探します。見つかった場合は内容を更新し、見つからない場合は対象スペースに新しいページを作成します。タイトルは各ファイルの最初の # heading から取り出します。

5

追跡用ラベルの付与。同期した各ページに auto-generateddocumentation のラベルを付け、Confluence 上で機械的に同期されたコンテンツを見分けて絞り込めるようにします。

変換、インポート、貼り付けの使い分け

Markdown を Confluence に取り込む方法は 3 つあり、このランブックはそのうち規模に耐えられる方法を自動化します。コンテンツの更新頻度に合った方法を選んでください。

# Import: convert one markdown file and create the page
pandoc -f markdown -t html notes.md > notes.html
atlassian-cli confluence page create --space DOCS --title "Release notes" --body notes.html

# Export: pull a Confluence page back out as markdown
atlassian-cli confluence page get 12345 --format markdown > notes.md

すべてのファイルに同じ変換ツールを使うため、一括での Markdown インポートは一貫性を保てます。見出し、フェンス付きコードブロック、リンクはいずれも同じストレージ形式に収まります。各コマンドの正確なフラグはコマンドリファレンスで確認してください。

よくあるエラーと対処

CI/CD への組み込み

この同期は冪等でドライランにも対応しているため、CI でも問題なく実行できます。docs/ 配下の変更を条件にし、マージのたびに公開すれば、Confluence がリポジトリから遅れることはありません。API トークンは暗号化されたシークレットとして保存し、認証プロファイル経由で渡してください。以下の GitHub Actions のワークフローは、main ブランチで docs/ 配下のファイルが変更されるたびにランブックを実行します。

# .github/workflows/confluence-sync.yml
name: Sync docs to Confluence
on:
  push:
    branches: [main]
    paths:
      - 'docs/**'
jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        run: sudo apt-get update && sudo apt-get install -y pandoc jq
      - name: Install atlassian-cli
        run: pipx install atlassian-cli
      - name: Sync markdown to Confluence
        env:
          ATLASSIAN_API_TOKEN: ${{ secrets.ATLASSIAN_API_TOKEN }}
        run: ./doc-pipeline.sh DOCS prod

GitLab CI では、on.push.paths トリガーと Actions のシークレットの代わりに、only: changes: ["docs/**"] ルールとマスクされた CI/CD 変数をトークンに使うジョブで、同じ結果を得られます。

よくある質問

Markdown ファイルを Confluence にインポートするには? pandoc(pandoc -f markdown -t html)でファイルを Confluence のストレージ形式に変換し、atlassian-cli confluence page create --space KEY --title "Page title" --body file.html で公開します。ランブックのスクリプトはこの 2 つの手順をつなげているため、1 ページずつではなく Markdown ディレクトリ全体を一度に Confluence へインポートできます。Confluence CLI ガイドでは、同じコマンドをさらに詳しく解説しています。

Markdown を Confluence 形式に変換しているのは何ですか? 変換ツールは pandoc です。パイプラインは pandoc -f markdown -t html を実行し、その後 2 つの小さな sed 処理が HTML を Confluence のストレージ形式に合わせて調整してから、atlassian-cli がページを作成または更新します。

atlassian-cli は Confluence ページを Markdown に書き出せますか? はい。すべてのコマンドは --format フラグを受け付け、markdown も対応する出力形式の 1 つです。そのため atlassian-cli confluence page get 12345 --format markdown を実行するとページが Markdown として返り、内容を Git リポジトリへ戻すラウンドトリップができます。

代わりに Markdown を Confluence ページへ直接貼り付けてもよいですか? Confluence のエディターでは、単発の編集であればページに Markdown を貼り付けられます。ただし手作業であり、規模には耐えられません。このランブックは同じ Markdown から Confluence への変換を自動化するため、数十個のファイルをコピーと貼り付けなしで同期し続けられます。

Confluence ページを削除できますか? いいえ。スクリプトは新しいページを作成するか、既存のページの内容を更新するだけです。削除エンドポイントを呼び出すことはないため、元の Markdown ファイルを削除しても、同期によって Confluence ページが消えることはありません。削除には一括クリーンアップランブックをご利用ください。

ネストしたページや複数のスペースに対応していますか? ドキュメントディレクトリ配下で見つかったすべての Markdown ファイルを、1 つの対象スペースへフラットなページ群として同期します。ネストは親ページの ID を指定した場合にのみ対応し、複数スペースへの同期にはスペースキーごとにスクリプトを実行する必要があります。

変更はどのように検出しますか? 実行のたびに CQL 検索でタイトルからページを探します。ページが存在すれば、その本文を変換したての HTML で上書きし、存在しなければページを作成します。同じ Markdown で再実行しても同じストレージ形式の出力になるため、この操作は実質的に冪等です。大きな同期の前には、Confluence バックアップランブックでオフラインのコピーを残しておいてください。

関連ランブック

コピーしました