Runbook

Sync Markdown Files to Confluence Pages

Build a documentation pipeline that converts markdown files to Confluence storage format and publishes them as pages -- with dry-run support and automatic labeling.

What This Does

This runbook scans a local directory for markdown files, converts each one to Confluence-compatible HTML using pandoc, and creates or updates the corresponding Confluence page. It extracts page titles from the first heading in each file and applies tracking labels automatically. Ideal for keeping Git-based docs in sync with Confluence.

Prerequisites

Quick Start

# 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

Full Runbook Script

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 "$@"

How It Works

1

Check dependencies. Validates that atlassian-cli, pandoc, and jq are all available on the system PATH before proceeding.

2

Discover markdown files. Recursively scans the configured docs directory (default ./docs) for all .md files. Each file becomes one Confluence page.

3

Convert to Confluence format. Pipes each markdown file through pandoc -f markdown -t html, then applies basic cleanup for Confluence storage format compatibility.

4

Create or update pages. Searches Confluence by title to find existing pages. If found, updates the content. If not, creates a new page in the target space. Titles are extracted from the first # heading in each file.

5

Apply tracking labels. Tags each synced page with auto-generated and documentation labels so you can easily identify and filter machine-synced content in Confluence.

Related Runbooks

Copied!