Documentation pipeline with Antora

The big picture: shift left and automate

user-journey
  • The downstream documentation receives content contributions to the upstream repository, source for the upstream documentation.

  • The downstream repository has only the required tooling and downstream specific content to publish to the Customer portal.

  • Everything that can be automated is automated.

Understanding the downstreaming workflow

Downstreaming workflow
Figure 1. Downstreaming workflow

Step by step downstreaming workflow

  1. Writers and engineers contribute content to the upstream repository.

  2. On commit, upstream CI publishes the upstream documentation website.

  3. On release, engineers create a tag in the upstream repository.

  4. Writers update the downstream repository content and metadata.

  5. On schedule, downstream CI single-sources content from the upstream repository tag to the downstream repository.

  6. On push, downstream CI publishes the downstream documentation preview.

  7. On release, writers create a release branch in the downstream repository.

  8. Customer Portal CI publishes the downstream documentation to the Customer Portal.

Handling the content variations

user-journey

Understanding the downstream repository structure

Repository content structure
.
├── docs
│   ├── getting_started
│   │   └── modules
│   │       └── getting_started
│   │           └── examples (1)
│   ├── release_notes
│   │   └── modules
│   │       └── release_notes
│   │           ├── pages (2)
│   │           └── partials (2)
│   └── shared (3)
├── publication (4)
└── tools (5)
1Editable Getting started downstream specific content: Antora examples, and metadata.
2Editable Release notes content: Antora module, and metadata.
3Editable shared downstream specific content: AsciiDoc attributes, shared content.
4Versioned build artifacts required for publication. Do not edit manually.
5Automation scripts.

Understanding the CI pipeline

Downstreaming workflow
Figure 2. Downstreaming workflow
Single-source

Single-source content from the upstream repository.

Convert

Convert to HTML multi-page, HTML single-page, EPUB, and PDF.

Test

Test language, links.

Push

Commit back changes after verification

AsciiDoc features

asciidoc

Modifying inline content with AsciiDoc attributes

Upstream
  1. Content

    Configuring {prod}
  2. Attributes

    :prod: Project
  3. Output

    Configuring Project

Downstream
  1. Attributes

    :prod: Product
  2. Output

    Configuring Product

Benefits

Modifying block content with AsciiDoc include

Upstream
  1. Content

    include::{context}-snip.adoc[]
    . Run.
  2. Partial

    . Download.
    . Build.
  3. Output

    1. Download.

    2. Build.

    3. Run.

Downstream
  1. Partial

    . Install.
  2. Output

    1. Install.

    2. Run.

Benefits

Antora features

antora

Collecting multiple content sources with Antora

Upstream
content:
  sources:
    - url: ./ (1)
      branches: HEAD (1)
      start_path: docs (1)
1Upstream
Downstream
content:
  sources:
    - url: https://github.com/crc-org/crc (1)
      tags: v2.23.0 (1)
      start_path: docs (1)
    - url: ./ (2)
      branches: HEAD (2)
      start_path: docs (2)
1Upstream
2Downstream
Benefits

Combining sources with Distributed Component Versions

Upstream
name: getting_started (1)
title: Getting started (2)
version: ~ (1)
start_page: getting_started:introducing.adoc (2)
nav: (2)
    - modules/getting_started/nav.adoc (2)
1Same as downstream.
2Metadata only upstream
Downstream
name: getting_started (1)
version: ~ (1)
1Same as upstream.

Modifying inline content with Antora

Upstream
asciidoc:
  attributes:
    prod: Project
    version: 1.42.0
Downstream
asciidoc:
  attributes:
    prod: Product
    version: 2.23
Benefits

Modifying block content with Antora

Upstream
File tree
docs/getting_started/modules/getting_started/
└── examples
    └── project_installing.adoc
└── pages
    └── installing.adoc
installing.adoc
include::example${project-context}_installing.adoc[]
. Run.
project_installing.adoc
. Download.
. Build.
Output
  1. Download.

  2. Build.

  3. Run.

Downstream
File tree
docs/getting_started/modules/getting_started/
└── examples
    └── product_installing.adoc
product_installing.adoc
. Install.
Output
  1. Install.

  2. Run.

Benefits

Antora extensions features

antora_extensions

Exporting content with Antora Assembler

Downstream
antora-assembler.yml
root_level: 1
component_versions: '*'
section_merge_strategy: fuse
build:
  command: ./tools/assembler2portal.sh
  dir: .cache/assembler
  keep_aggregate_source: true
  process_limit: 1
assembler2portal.sh (simplified)
# Get source file name
while getopts "a:o:f:" opt; do
  case "$opt" in
  o) source=${OPTARG/pdf/adoc}
    ;;
  esac
done
# Get destination file name
destination="portal/$(basename $source)"
# Copy files
cp --recursive "$source" "$destination"
Benefits

Generating content with Antora Collector

Upstream
Antora playbook
antora:
  extensions:
    - '@antora/collector-extension'
Antora component
ext:
  collector:
  - run:
      command: ./tools/validate_language.sh
  - run:
      command: ./tools/generate_reference.sh
    scan:
      base: modules/reference/examples/reference     #
      dir: build/collector/reference
      files: reference.adoc
Downstream
Antora playbook
antora:
  extensions:
    - '@antora/collector-extension'   #
Benefits
  • This extension delegates to external commands the ability to generate content from external sources.

  • Crafting reference guides from source code.

  • Modifying the outline: removing upstream only content and injecting downstream only content.

  • https://gitlab.com/antora/antora-collector-extension

Enabling search in Antora website with Antora Lunr

Upstream
Antora playbook
antora:
  extensions:
    - require: '@antora/lunr-extension'   #
      index_latest_only: true
      snippet_length: 142
Benefits

Writing your own extension

antora_custom_extensions

Antora provides an event-based extension facility you can tap into to augment or influence the functionality of the generator. This extension facility is designed for users of all experience levels.

Testing built site with a custom Antora extension

Downstream
Antora playbook
antora:
  extensions:
    - require: ./extensions/htmltest.js
extensions/htmltest.js
'use strict'
module.exports.register = function () {
    this.on('sitePublished', () => { (1)
        require('child_process').execFile('htmltest', (error, stdout, stderr) => { (2)
            if (error) {
                console.log(stdout + stderr);
                return;
                }
            console.log(stdout);
        })
    })
}
1Run after building the site.
2Run the external command htmltest.

Inject collector configuration

Downstream
extensions/inject-collector-configuration.js
'use strict'
const ADDITIONAL_COLLECTOR = [
  { run: { command: 'sed --in-place -e="/locally/d" modules/guide/nav.adoc' }}, (1)
  { run: { command: 'rm --force modules/guide/pages/installing-locally.adoc' }}, (2)
  { run: { command: 'mkdir --parents build/collector/downstream' }},(3)
  { run: { command: 'cp --recursive modules/guide/ build/collector/downstream/' }, (4)
    scan: { base: 'modules/guide/', dir: 'build/collector/downstream/guide' }} (4)
]
module.exports.register = function () {
  this.once('contentAggregated', ({ contentAggregate }) => {
    for (const { origins } of contentAggregate) {
      for (const origin of origins) {
        if (origin.descriptor.ext?.collector) {
          origin.descriptor.ext = { collector: ADDITIONAL_COLLECTOR.concat(origin.descriptor.ext?.collector) }
        }
      }
    }
  })
}
1Remove pages from the navigation.
2Delete the pages from the module.
3Create the collector directory.
4Copy files to the collector directory, and scan the entire modified module.

For the future?

antora_new_extensions

Demo

Credits