atlassian-cli はコミュニティによる独立したオープンソースプロジェクトです。Atlassian と提携・関連しておらず、Atlassian による承認、推奨、後援のいずれも受けておらず、Atlassian が提供する公式 CLI(acli)でもありません。製品名は互換性を示す目的でのみ使用しています。

MkDocs から Confluence へ公開するには、Markdown のソースファイルを 1 つずつ Confluence のストレージ形式 HTML に変換し、atlassian-cli でページに送り込みます。あとはそのスクリプトを CI で動かせば、マージのたびに Confluence が更新されます。インストールが必要な MkDocs プラグインも、手作業のコピー&ペーストもありません。Git リポジトリが唯一の情報源であり続け、Confluence は関係者が読み、コメントし、検索できるレンダリング済みのミラーになります。

これは docs-as-code のパターンを wiki に適用したものです。エンジニアはすでに、対象のコードの隣で Markdown を書き、プルリクエストでレビューし、Git でバージョン管理しています。摩擦が生まれるのは、エンジニア以外の人たちが Confluence を主戦場にしているときです。人手に任せていると、2 つのコピーは 1 スプリントのうちに食い違います。パイプラインは公開を雑務ではなくビルドの一工程にすることで、この食い違いをなくします。

acli との違い: atlassian-cli はコミュニティによる独立したオープンソースプロジェクトです。Atlassian が提供する公式 CLI(acli)ではなく、Atlassian と提携・関連しておらず、Atlassian による承認、推奨、後援、保守のいずれも受けていません。ベンダーによる一次サポートが必要であれば公式の acli を、Jira、Confluence、Bitbucket、JSM を横断し、ドキュメントのパイプラインにきれいに組み込める無料の Rust バイナリがひとつ欲しい場合は atlassian-cli を使ってください。

Confluence における docs-as-code とは

docs-as-code は、ドキュメントをソースコードと同じように扱う考え方です。プレーンテキストのファイル、バージョン管理の履歴、コードレビュー、そして自動公開。MkDocs はこの形と相性がよく、入力は docs/ ディレクトリ配下の Markdown ファイルと、ナビゲーションツリーを宣言する 1 つの mkdocs.yml だけです。そこから静的な HTML サイトを生成するところまでは、すでにできているはずです。ここでの目標は、2 つ目の出力先を用意することです。Confluence がその出力先になります。

設計上の重要な選択は、Confluence に何を送るかです。mkdocs build が生成する、ナビゲーションバーとテーマの CSS に包まれたテーマ適用済みの HTML ページ全体は送りたくありません。Confluence には Confluence 自身の外枠があります。必要なのは各ドキュメントの本文を、API が受け付ける制限された HTML のサブセットである Confluence のストレージ形式で表したものです。そのためパイプラインはビルド済みのサイトではなく生の Markdown ソースを起点にし、ソースファイル 1 つを Confluence ページ 1 つの本文へ変換します。

組み立てを始める前に、対象スペースに対して認証情報が有効であることを確認します。

# Verify the profile and that the space exists
atlassian-cli auth test --profile prod
atlassian-cli confluence space get DOCS --profile prod

MkDocs プロジェクトをスペースに対応させる

MkDocs は構造を mkdocs.ymlnav: ブロックで記述します。このツリーがそのまま Confluence への対応表になります。最上位のセクションが親になり、その下にぶら下がるページが子になります。Confluence はこれをページの親子関係と、v0.4 以降は専用のフォルダーで表現します。入れ物の階層は一度作っておき、あとはそこにページを吊り下げていきます。

# A section in mkdocs.yml nav -> a Confluence folder
atlassian-cli confluence folder create \
  --space DOCS \
  --title "Guides" \
  --parent 3302031761

# A page created under a parent keeps the tree shape
atlassian-cli confluence page create \
  --space DOCS \
  --title "Getting Started" \
  --parent 3302031761 \
  --body "<p>placeholder</p>"

実務では、各 Markdown のパスから親 ID への小さな対応表(テキストファイル 1 枚か、数行のシェル)を持っておきます。この対応表が、維持すべき唯一の Confluence 固有の状態であり、ドキュメントを再編成したときだけ変わります。それ以外はすべて Markdown 自体から導かれます。ページタイトルは各ファイルの最初の # 見出しから取り、本文は次の変換工程から得られます。

1 ページを変換して公開する

まずは 1 ページだけで試し、動く部品をはっきりさせてからループで包みます。これを確実に動かせる理由は 2 つあります。1 つ目は、atlassian-cli confluence page createpage update のどちらも --body で Confluence のストレージ形式 HTML を受け取れるため、任意の Markdown 変換ツールの出力をそのまま渡せることです。2 つ目は、CQL 検索でタイトルの完全一致から既存ページを引けることです。これが、再公開を重複ではなく冪等な操作にしてくれます。

# 1. Convert one MkDocs source file to storage HTML
BODY=$(pandoc -f markdown -t html docs/getting-started.md)

# 2. Look the page up by title (empty if it does not exist yet)
PAGE_ID=$(atlassian-cli confluence search cql \
  "space = DOCS AND title = \"Getting Started\"" \
  --format json | jq -r '.results[0].content.id // empty')

# 3. Create the first time, update every time after
if [ -z "$PAGE_ID" ]; then
  atlassian-cli confluence page create \
    --space DOCS --title "Getting Started" \
    --parent 3302031761 --body "$BODY" --profile prod
else
  atlassian-cli confluence page update "$PAGE_ID" \
    --title "Getting Started" --body "$BODY" --profile prod
fi

pandoc はあくまで一例です。Confluence が受け付ける HTML を出力できるツールならどれでも構いませんし、変換を CLI の外に置いておけば、公開のロジックに触れずに変換ツールを差し替えたり、HTML を後処理したり(注意書き、コードブロック、Confluence のマクロなどのために)できます。変換の詳細や書式の細かな落とし穴はMarkdown から Confluence へのガイドで扱っています。この記事は、その 1 ページ分の操作をパイプラインに仕立てるところに集中します。

一般化するには、MkDocs がすでに把握しているファイルをループで回します。パスは mkdocs.yml から直接読んでもよいですし、docs/ ディレクトリを走査するだけでも構いません。タイトルはソースと同期させるために、最初の見出しから取り出します。

# Title from the first "# heading", fallback to the filename
TITLE=$(grep -m1 '^# ' "$md" | sed 's/^# //' \
  || basename "$md" .md)

CI からサイト全体を公開する

本当の価値は、手作業ではなくマージのたびに実行されることにあります。CI では認証情報をコミットしません。atlassian-cli は保存済みのトークンを ATLASSIAN_API_TOKEN 環境変数で上書きするため、セットアップ工程で最小限のプロファイルを設定し、実行時にシークレットストアから本物のトークンを注入します。ジョブはドキュメントが変わったときだけ起動させ、無関係なコミットで再公開が走らないようにします。

# .github/workflows/publish-docs.yml
name: publish-docs
on:
  push:
    branches: [main]
    paths: ['docs/**', 'mkdocs.yml']

jobs:
  confluence:
    runs-on: ubuntu-latest
    env:
      ATLASSIAN_API_TOKEN: ${{ secrets.ATLASSIAN_API_TOKEN }}
    steps:
      - uses: actions/checkout@v4
      - name: Install tools
        run: sudo apt-get update && sudo apt-get install -y pandoc jq
      - name: Configure profile
        run: |
          atlassian-cli auth login --profile ci \
            --base-url https://mycompany.atlassian.net \
            --email bot@mycompany.com --default
      - name: Publish to Confluence
        run: ./scripts/publish-to-confluence.sh DOCS ci

auth login の工程が記録するのはプロファイルのメタデータ(ベース URL、メールアドレス、デフォルト指定)だけです。実際のシークレットは環境変数から渡り、CLI はディスク上の値よりこちらを優先します。Bitbucket Pipelines でも考え方は同じです。トークンをリポジトリ変数として設定し、同じツールをインストールする pipelines のステップを追加して、まったく同じスクリプトを実行します。必要なのは 1 つの静的バイナリと pandocjq だけなので、ジョブにランタイムも言語のツールチェーンも要りません。環境変数でトークンを渡すこの形は、CI で動かす他の Atlassian 自動化にもそのまま使えます。

冪等性、ラベル、バックアップ

マージのたびに走るパイプラインは、繰り返し実行しても安全でなければなりません。よい意味で退屈な状態を保つ習慣が 3 つあります。

タイトル検索による冪等性。 上の手順 2 が書き込み前にページを検索しているため、再実行しても 2 つ目のページができるのではなく、同じページが更新されます。内容が変わっていないファイルはバイト単位で同一のストレージ形式 HTML に変換されるので、再公開しても何も起きません。おかげでサイト全体を何度でも安心して流し直せます。

パイプラインが持ち物とするページにはラベルを付ける。 生成されたページに印を付けておけば、機械が公開した内容と手書きのページを人が見分けられ、あとから棚卸しや掃除もできます。実行中にページごとのラベルを付け、スペース全体は一括でそろえます。

# Per page, during the loop
atlassian-cli confluence page add-label "$PAGE_ID" mkdocs

# Reconcile labels across the space (preview first)
atlassian-cli confluence bulk add-labels \
  --cql "space = DOCS" \
  --labels mkdocs,generated \
  --dry-run

大規模な再公開の前にバックアップを取る。 大量に流すときや初回の実行前には、スペースのスナップショットを取っておき、変換に失敗しても戻せるようにします。一括エクスポートのコマンドは、条件に一致するすべてのページを 1 つの JSON ファイルに書き出します。

# Snapshot the space before republishing
atlassian-cli confluence bulk export \
  --cql "space = DOCS" \
  --output confluence-backup.json \
  --format json

依存関係のチェック、タイトルの抽出、ドライラン対応まで含めた完全なスクリプトは、この記事の下敷きになっているConfluence Markdown 同期ランブックが参照実装です。逆方向に内容を引き戻したくなったときは、Confluence から Markdown へのエクスポートガイドが扱っています。

Markdown を Confluence へ持ち込む方法の比較

Markdown から Confluence ページに至る道はひとつではありません。違いは主に、無人で実行できるかどうかと、再実行が安全かどうかにあります。

方法 CI で実行 再実行が冪等 階層を保てる ドライランで確認
手作業のコピー&ペースト 不可 不可 手作業 不可
Confluence の UI インポート 不可 不可 一部のみ 不可
atlassian-cli のパイプライン 可(タイトル検索) 可(親 / フォルダー) 可(一括処理のドライラン)

手作業や UI からの取り込みは、単発のページなら十分です。スプリントごとに変わり続ける生きたドキュメントでは、繰り返しの手作業を増やさずに Confluence を正しい状態に保てるのはスクリプト化されたパイプラインだけです。上で使ったフラグの詳細は、スクリプトを書きながらコマンドリファレンスを開いておくと確認できます。

ドキュメントのパイプラインを動かす

単一の Rust バイナリをインストールすれば、MkDocs から Confluence への CI 連携は半日で組めます。

atlassian-cli を試す

よくある質問

MkDocs を Confluence へ自動で公開するにはどうすればよいですか?

MkDocs のソースとなる Markdown ファイルを Confluence のストレージ形式 HTML に変換し(pandoc が扱いやすいです)、対応する Confluence ページを atlassian-cli confluence page create/update で作成または更新します。これをスクリプトにまとめ、ドキュメント用ブランチへマージするたびに CI で実行します。スクリプトはページをタイトルで探して本文をその場で上書きするため、何度実行しても同じページが更新されるだけで、重複ページは作られません。

CI から Markdown のドキュメントを Confluence へ同期できますか?

できます。CI では認証情報をコミットせず、環境変数(ATLASSIAN_API_TOKEN)経由で API トークンを渡し、atlassian-cli auth login で最小限のプロファイルを設定します。docs/** の変更で起動する GitHub Actions や Bitbucket Pipelines のジョブが公開スクリプトを実行するため、Confluence は常にバージョン管理された内容を反映します。

MkDocs から Confluence への同期でページが重複しませんか?

書き込む前にタイトルでページを引いていれば重複しません。パイプラインは space = KEY AND title = "..." という CQL 検索を実行し、ページが存在すれば更新し、存在しないときだけ作成します。これにより再公開は冪等になります。内容が変わっていないファイルは同じストレージ形式 HTML を生成するため、実行し直しても何も起きません。

Markdown を Confluence へ変換するのにプラグインは必要ですか?

MkDocs のプラグインは不要です。atlassian-cli は page の create と update コマンドで Confluence のストレージ形式 HTML を送るため、すでに使い慣れたツール(pandoc がよく選ばれます)で Markdown を HTML に変換し、その結果を --body に渡すだけで済みます。変換の工程はビルドプラグインの内側に隠れず、透明で差し替え可能なままになります。

この用途では atlassian-cli と Atlassian の acli のどちらを使うべきですか?

atlassian-cli はコミュニティによる独立したオープンソースプロジェクトです。Atlassian が提供する公式 CLI(acli)ではなく、Atlassian と提携・関連しておらず、Atlassian による承認、推奨、後援、保守のいずれも受けていません。ベンダーによる一次サポートが必要であれば公式の acli を使ってください。Confluence、Jira、Bitbucket、JSM を 1 つでカバーし、ドキュメントのパイプラインにきれいに組み込める無料の Rust バイナリが欲しい場合は atlassian-cli を使ってください。

関連リソース