Friday, October 30, 2020

Antora Quick Start Tutorial

Antora is a sophisticated documentation "static site generator" (SSG) for AsciiDoc/Asciidoctor. It's built for big projects with multiple sets of documentation, so just getting started can be a little intimidating. Here's a quick guide for how to get up and running with Antora, using Docker and some Makefiles on your local dev machine.

At minimum, you really should use at least three separate git repos with Antora:

  1. Your documentation content (AsciiDoc source files).
  2. Your customized UI theme/style (JavaScript, CSS, and Handlebars templates).
  3. Your Antora build and deployment configuration (antora-playbook.yml, and whatever additional scripts/config files you use to deploy to production).

In practice, you may in fact have several different repos for #1, like the docs directory from several different software products, or simply different repos for different pieces of documentation (like an Install Guide, Product Manual, API Reference, etc). And #3 might be part of other repos you use for devops configuration/deployment/infrastructure/etc.

But you're really going to want to have a new dedicated repo for #2 — this is where you customize your page header and footer content with links to your own websites, with your own logos, colors, and general look-and-feel.

So this guide is just going to focus on #2, with development-focused Antora config (#3) and initial documentation content (#1) snuck into the same repo. Before you begin, make sure you have Git, Make, and Docker installed on your local dev machine.

  1. UI Set Up
  2. UI Customization
  3. Documentation Set Up
  4. Documentation Customization
  5. Development Workflow

UI Set Up

Clone Antora Default UI

To start off, clone the Antora Default UI repo. We're going to save all our UI customizations, our initial doc content, and our dev Antora config in this new repo, which we'll call docs-ui. Run these commands in a terminal:

$ git clone https://gitlab.com/antora/antora-ui-default.git docs-ui $ cd docs-ui $ git remote rename origin antora-ui-default $ git branch --move antora

This will keep antora-ui-default as a remote source for your git repository, so you can easily diff and pull in core fixes to the UI from the Antora Default UI repo. The local copy of the Antora Default UI we'll keep in a branch called antora.

But you'll want to set the main remote source of your customized repo (called origin by convention) to a new remote source. For example, if you have a github account named my-account, create a new repo named docs-ui in it, and push the local content of this new repo to it. We'll call the local branch of the main source main:

$ git checkout -b main $ git remote add origin https://github.com/my-account/docs-ui.git $ git push -u origin main
Dockerized UI Build

Now we're ready to start making changes to the repo. To make it easy to preview and build those changes, we'll add three files to the root of the repo: ui.dockerfile, docker-compose.yml, and Makefile. Create these files:

# ui.dockerfile FROM node:12-buster WORKDIR /srv/docs-ui
# docker-compose.yml version: '3' services: ui: build: context: . dockerfile: ui.dockerfile ports: - 8052:5252 volumes: - .:/srv/docs-ui
# Makefile # help: @ Lists available make tasks help: @egrep -oh '[0-9a-zA-Z_\.\-]+:.*?@ .*' $(MAKEFILE_LIST) | \ awk 'BEGIN {FS = ":.*?@ "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' | sort # node_modules: @ Runs initial ui npm install node_modules: docker-compose run ui npm install # ui.build: @ Builds ui production output (to build/ui-bundle.zip) ui.build: node_modules docker-compose run -u $$(id -u) ui node_modules/.bin/gulp bundle # ui.lint: @ Runs ui linting ui.lint: node_modules docker-compose run -u $$(id -u) ui node_modules/.bin/gulp lint # ui.run: @ Runs ui server in preview mode (on port 8052) ui.run: node_modules docker-compose run -u $$(id -u) --service-ports ui node_modules/.bin/gulp preview # ui.shell: @ Opens bash shell in ui container ui.shell: CMD ?= /bin/bash ui.shell: docker-compose run -u $$(id -u) ui $(CMD)

Note that Makefiles require you to use tabs for indentation, instead of spaces, so make sure your editor has preserved the indentation in the above Makefile with tabs.

Run UI Preview

With those three files in place, run this command from the repo root:

make ui.run

This will build the Docker image defined by the ui.dockerfile file, launch it as a container, and run the Antora UI preview server in it, exposed on port 8052. Open up a browser, and navigate to:

http://localhost:8052/

You should see a test page for the Antora UI (titled "Hardware and Software Requirements") with a bunch of sample content.

UI Customization

Customize Header Content

Now open up the src/partials/header-content.hbs file from the repo. This is the template for the page header content. Change the line with the "Home" link to this:

<a class="navbar-item" href="https://example.com/">Example Co Home</a>

Save the file, go back to your browser, and refresh the page. The "Home" link in the page header should now read "Example Co Home" (and link to https://example.com/).

Customize Header Color

Next open up the src/css/vars.css file. This file defines (via CSS variables, which begin with the -- prefix) the basic colors and sizes of the UI. Change the --navbar-background variable to this:

--navbar-background: #39f;

Save the file, go back to your browser, and refresh the page. The background of the page header should now be a medium blue.

Build UI Bundle

Kill the make ui.run task (by pressing control-C in the terminal it's running in), and run this command in it's place:

make ui.build

This will build an Antora UI bundle as a zip file called ui-bundle.zip in the repo's build directory. We'll use this bundle in the next step, when we set up our basic Antora documentation build.

Documentation Set Up

Now we're ready to actually build some documentation. The Antora Default UI repo includes an Antora module in its docs directory, so we'll start by building it.

Create Antora Playbook

The first thing to do is create an antora-playbook.yml file. This file defines the configuration for the Antora build process. Add the following antora-playbook.yml to the root of the repo:

# antora-playbook.yml site: robots: allow start_page: antora-ui-default::index.adoc title: Example Documentation url: https://docs.example.com content: sources: - url: ./ branches: HEAD start_path: docs runtime: cache_dir: ./build/cache ui: bundle: url: ./build/ui-bundle.zip urls: html_extension_style: indexify

This will configure Antora to use "author mode", where it pulls its documentation content from local files instead of remote git repos. Your production build scripts will probably configure Antora to pull documentation content from specific release branches of various product or documentation repos, but while you're developing the documentation, you'll just want to build the latest docs from your working copy.

The Antora documentation covers all playbook settings thoroughly in its Antora Playbook section, but let's briefly touch on each setting of this antora-playbook.yml:

site.robots: Setting this to allow directs Antora to generate a robots.txt file which allows bots to spider your site; setting it to disallow generates a robots.txt that disallows bots. While you can omit this setting in your dev build (and not generate a robots.txt file), it's nice to see what Antora will generate.

site.start_page: This is the page ID of the page to redirect to when a visitor navigates to the root of your site (eg https://docs.example.com/). A value of antora-ui-default::index.adoc sets it to redirect to the index.adoc page of the ROOT module in the antora-ui-default component (https://docs.example.com/antora-ui-default/ under this configuration). This source file for this page is located at docs/modules/ROOT/pages/index.adoc in our repo.

site.title: This is the site-wide title that will be displayed in the header of every page, as well as the browser titlebar.

site.url: This is the base URL of the site, when deployed, minus the trailing slash. For deployments where the documentation site has its own domain name (eg docs.example.com), the value of this setting should just be the URL scheme plus domain name (eg https://docs.example.com); for deployments where the documentation lives as a sub-directory of a larger site, the value should also include the sub-directory under which the documentation will live (eg https://www.example.com/docs). While you can safely omit this setting for dev builds, it's nice to include just so you can see how this URL will be used in production.

content.sources: This is a list of sources (each separate item in a YAML list is denoted by a - sign); initially, we'll just have one source: the docs directory that came with the Antora Default UI repo.

content.sources[0].url: With your production configuration, you'd usually specify a full URL to a git repo with this setting; but with our dev config, we'll just use this local repo, indicated by ./. Note that this setting has to be the path to the root of a git repo — it can't specify a sub-directory of the repo itself (like say ./docs).

content.sources[0].branches: This specifies which branch of the repo to use. With your production config, you'd probably want to specify specific branches that correspond to releases of your product (eg [1.0, 1.1, 1.2]); but for development, we just want whatever your current working branch is (HEAD in git parlance).

content.sources[0].start_path: This specifies the path to the content source root of each Antora component in the repo you want to include. We just have one to start with, located in the docs directory. Each component must have an antora.yml file in it's source root; this file defines its component name, display title, version string, and navigation structure (and it can also include AsciiDoc setting customizations that apply to just the particular component).

runtime.cache_dir: This specifies Antora's internal cache of files. The default location (~/.cache/antora) would put it inside our Docker container, which is fine except that it means that Antora has to rebuild the cache again every time we run one of our Makefile commands. So for a moderately improved experience, we'll move it to the build/cache directory inside the repo itself (the build directory is already conveniently listed in the .gitignore file we cloned from the Antora Default UI).

ui.bundle.url: This specifies the location where Antora should fetch your customized UI files from. We want to use the UI built by this very repo, so we'll specify the path to to the UI bundle built by our make ui.build command, ./build/ui-bundle.zip. We prefix the path with ./ to indicate to Antora that this is a local file path — usually this would be the full URL to the location where your build system has stored the latest build of your customized UI; the build system for the Antora project saves the latest stable build of the Antora Default UI to https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/master/raw/build/ui-bundle.zip?job=bundle-stable.

urls.html_extension_style: Setting this to indexify directs Antora to generate each page as an index.html file within a directory named for the page — eg https://docs.example.com/antora-ui-default/index.html for our start page — and to drop index.html from the page URL when linking to it — eg https://docs.example.com/antora-ui-default/. This is the style you'd usually use when hosting static content with NGINX, Apache, and many static hosting services. Antora allows for several different options, however, and while you don't need to set this in your dev config, it's nice to just so you can see what your URLs will look like in production.

Dockerized Docs Build

To make it easy to run the build, we'll add another Dockerfile, and add to our existing docker-compose configuration and Makefile. Add this antora.dockerfile (substituting 2.3.4 in the file for whatever Antora's latest stable version number is):

# antora.dockerfile FROM antora/antora:2.3.4 RUN yarn global add http-server onchange WORKDIR /srv/docs

Then add an antora service to our existing docker-compose.yml file:

# docker-compose.yml version: '3' services: antora: build: context: . dockerfile: antora.dockerfile environment: CI: 'true' ports: - 8051:8080 volumes: - .:/srv/docs ui: build: context: . dockerfile: ui.dockerfile ports: - 8052:5252 volumes: - .:/srv/docs-ui

Note that the CI: 'true' environment variable will suppress the "Edit this Page" link that otherwise would be displayed in the top right of each page (making the output generated by Antora in our Docker containers more similar to what we'd see in production).

Finally, add some antora.* tasks to our Makefile:

# Makefile # help: @ Lists available make tasks help: @egrep -oh '[0-9a-zA-Z_\.\-]+:.*?@ .*' $(MAKEFILE_LIST) | \ awk 'BEGIN {FS = ":.*?@ "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' | sort # antora.build: @ Builds documentation production output (to build/site) antora.build: docker-compose run -u $$(id -u) antora antora generate --clean antora-playbook.yml # antora.run: @ Serves documentation output (on port 8051) antora.run: docker-compose run --service-ports antora http-server build/site -c-1 # antora.watch: @ Watches for documentation changes and rebuilds (to build/site) antora.watch: docker-compose run -u $$(id -u) -T antora onchange \ -i antora-playbook.yml 'docs/**' \ -- antora generate antora-playbook.yml # antora.shell: @ Opens bash shell in antora container antora.shell: CMD ?= /bin/sh antora.shell: docker-compose run -u $$(id -u) antora $(CMD) # node_modules: @ Runs initial ui npm install node_modules: docker-compose run ui npm install # ui.build: @ Builds ui production output (to build/ui-bundle.zip) ui.build: node_modules docker-compose run -u $$(id -u) ui node_modules/.bin/gulp bundle # ui.lint: @ Runs ui linting ui.lint: node_modules docker-compose run -u $$(id -u) ui node_modules/.bin/gulp lint # ui.run: @ Runs ui server in preview mode (port 8052) ui.run: node_modules docker-compose run -u $$(id -u) --service-ports ui node_modules/.bin/gulp preview # ui.shell: @ Opens bash shell in ui container ui.shell: CMD ?= /bin/bash ui.shell: docker-compose run -u $$(id -u) ui $(CMD)

This antora.dockerfile takes the base Antora image produced by the Antora project, and adds the http-server and onchange Node.js modules to it. Our Makefile's antora.run task uses the http-server module to serve the content Antora generates, and our antora.watch task uses the onchange module to automatically rebuild that content whenever we change a documentation source file.

Build Documentation

With those file changes in place, run this command from the repo root:

make antora.build

This will build the Docker image defined by the antora.dockerfile file, launch it as a container, and run Antora using the antora-playbook.yml playbook we just wrote. Antora will generate the built documentation files to the build/site directory in our repo.

Run Documentation Preview

Now let's take a look at that documentation — run this command from the repo root:

make antora.run

This will serve the built files via the http-server Node.js module on port 8051. Open up a browser, and navigate to:

http://localhost:8051/

You should be redirected to http://localhost:8051/antora-ui-default/, and see the start page of the Antora Default UI documentation.

Rebuild on Documentation Changes

Finally, in another terminal (while the make antora.run command is still running in the first terminal), run this command:

make antora.watch

This will use the onchange Node.js module to watch for changes to the documentation source, and automatically trigger Antora to rebuild whenever you make a change. We'll rely on this functionality with the next step.

Documentation Customization

Create New Component

Now, finally, we're ready to start writing our own documentation! We'll start with a "User Guide" component, initially consisting of a "root" module with one page. Create a content directory, and a user-guide directory within it — this is where our "User Guide" component will live.

Within the user-guide directory, create a new antora.yml file. This file will contain the basic metadata about our "User Guide" component. Create it with this content:

# content/user-guide/antora.yml name: example-user-guide title: Example User Guide version: master

Note that master is a special keyword in Antora that means a component with no version. Usually Antora includes the version number of a component in all the URLs it generates for that component, but it will omit the version number if it is master.

Create First Page

Also within the user-guide directory, create a modules directory; within the modules directory, create an ROOT directory; and within the ROOT directory, create a pages directory. Within the ROOT directory, create our first page, called index.adoc:

# content/user-guide/modules/ROOT/pages/index.adoc = User Guide == Welcome Welcome to our product! This is the user guide.
Add Watch Path

Update our make antora.watch command in our Makefile to the following, adding components/** as a path to watch for changes:

# Makefile antora.watch: docker-compose run -u $$(id -u) -T antora onchange \ -i antora-playbook.yml 'components/**' 'docs/**' \ -- antora generate antora-playbook.yml

If you still have the make antora.watch command running in a terminal, kill it (by pressing control-C in the terminal it's running in), and re-run make antora.watch again.

Register Component

Now we'll register the component in the antora-playbook.yml at the root of our repo, so that we can have Antora build it. We could add it as a second item to our sources list, but since we already have the current repo listed as a source, we can just change the existing single start_path setting to be a multiple start_paths setting (note the "s" on the end of the setting), and direct Antora to include any sub-directory of the content directory in our repo as a component:

# antora-playbook.yml site: robots: allow start_page: antora-ui-default::index.adoc title: Example Documentation url: https://docs.example.com content: sources: - url: ./ branches: HEAD start_path: docs start_paths: - docs - content/* runtime: cache_dir: ./build/cache ui: bundle: url: ./build/ui-bundle.zip urls: html_extension_style: indexify

With our make antora.watch command running, as soon as you save this change to antora-playbook.yml, Antora will rebuild all its content. So make the change, and refresh the browser window you have opened at http://localhost:8051.

The page will look exactly the same as it did before — but click the "Antora Default UI" label in the bottom-left corner of the page. The bottom of the left navigation should expand to show two items: "Antora Default UI" and "Example User Guide". Click the "master" label directly below "Example User Guide", and you will navigate to your new "Example User Guide" index page at http://localhost:8051/example-user-guide/.

The component is listed as "Example User Guide", because that is the title we gave it in the content/user-guide/antora.yml file. And its URL path is /example-user-guide/ because we set its name to example-user-guide in that same file.

The title of the page is "User Guide", as displayed in the browser titlebar and the main title of the page's body, because that's the title we gave the page in its AsciiDoc source at content/user-guide/modules/ROOT/pages/index.adoc. And because we added a "Welcome" section to that page, we see "Welcome" as a section title in the page's body, as well as its right navigation.

You'll also want to change your antora-playbook.yml to make the new component the start page for the site (so that whenever someone navigates to https://docs.example.com/, they'll be redirected to https://docs.example.com/example-user-guide/ instead of https://docs.example.com/antora-ui-default/). Just replace antora-ui-default in the start_page setting of your antora-playbook.yml with example-user-guide:

# antora-playbook.yml start_page: antora-ui-default::index.adoc start_page: example-user-guide::index.adoc
Create Navigation

We don't have any left navigation for our new component, however, so let's fix that. Create a navigation file at content/user-guide/modules/ROOT/nav.adoc with this content:

# content/user-guide/modules/ROOT/nav.adoc * xref:index.adoc#_welcome[Welcome!]

Then configure Antora to use this navigation file by updating the content/user-guide/modules/antora.yml file like so:

# content/user-guide/antora.yml name: example-user-guide title: Example User Guide version: master nav: - modules/ROOT/nav.adoc

Go back to your browser and refresh the page, and you will see that Antora added a navigation list for the user-guide component to the left navigation, with one item beneath the root. This item links to the "Welcome" section of the "root" module's index page, displaying the link text as "Welcome!".

Create Another Page

Now create a second page for our component at content/user-guide/modules/ROOT/pages/how-it-works.adoc with this content:

# content/user-guide/modules/ROOT/pages/how-it-works.adoc = This Is How It Works :navtitle: But How Does It Work? Well, to be honest, we're not exactly sure how any of this works.

Update the navigation file at content/user-guide/modules/ROOT/nav.adoc to add an item for our new page:

# content/user-guide/modules/ROOT/nav.adoc * xref:index.adoc#_welcome[Welcome!] * xref:how-it-works.adoc[]

Go back to your browser and refresh the page again, and you will see that Antora added another item to the left navigation. The item label is "But How Does It Work?", matching the navtitle attribute set under the title of the how-it-works.adoc page. The URL it links to is http://localhost:8051/example-user-guide/how-it-works/ — the first path segment comes from the component name defined in the antora.yml file of the component, and the last path segment comes from the file name of the page itself. If the file was part of any module other than the "root" module for the component, it would have another path segment between the component name and page name for the module name (taken from the directory name of the module).

When you click the link, you'll see that the page title is "This Is How It Works", as displayed in the browser titlebar and the main title of the page's body, matching the page title from the how-it-works.adoc file.

Development Workflow

Work on the UI

With your initial UI and documentation content now set up, whenever you want to make changes to the look-and-feel of your documentation, you would follow these steps:

  1. Fire up the Antora UI preview server with make ui.run
  2. View the Antora UI preview in your web browser at http://localhost:8052/
  3. Iteratively make changes to the CSS, Handlebars, and other UI files in the src directory of your project
Build the UI

Once your changes look good with the Antora UI preview content, follow these steps to incorporate them into the local version of your documentation:

  1. Build the UI bundle with make ui.build
  2. Pull the new UI bundle into your local doc build with make antora.build
  3. Fire up a web server for your local docs with make antora.run
  4. View your UI changes with your local copy of the docs at http://localhost:8051/
Write Documentation

And whenever you get the hankering to write some documentation, follow these steps:

  1. Fire up a web server for your local docs with make antora.run
  2. View your local copy of the docs at http://localhost:8051/
  3. Fire up the onchange watcher for the docs in a separate terminal with make antora.watch (to automatically re-build the docs whenever you make a change)
  4. Iteratively make changes to AsciiDoc files in the content directory of your project

5 comments:

  1. Hi Justin! Sorry to hijack your unrelated blog post. I saw a post you made regarding firmware flashing a router. I live also in the Seattle area. I have a router but am nervous to flash it myself. Do you happen to know anyone willing to do this for me as a service for like 1 hour of labor?

    ReplyDelete
    Replies
    1. Sorry, I don't have any recommendations for that.

      Delete
  2. [root@localhost docs-ui]# make ui.run
    docker-compose run -u $(id -u) --service-ports ui node_modules/.bin/gulp preview
    Creating docs-ui_ui_run ... done
    internal/modules/cjs/loader.js:818
    throw err;
    ^

    Error: Cannot find module '/srv/docs-ui/node_modules/.bin/gulp'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:815:15)
    at Function.Module._load (internal/modules/cjs/loader.js:667:27)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)
    at internal/main/run_main_module.js:17:47 {
    code: 'MODULE_NOT_FOUND',
    requireStack: []
    }
    ERROR: 1
    make: *** [ui.run] Error 1


    How can I resolve this error?

    ReplyDelete
    Replies
    1. That error means the executable /srv/docs-ui/node_modules/.bin/gulp does not exist in your docker container. Why doesn't it exist?

      1. Is /srv/docs-ui in the docker container mapped to your local docs-ui folder? (This is done in the article via the "volumes" setting for the ui container in the docker-compose.yml file.)
      2. Did you run "npm install" in the docker container? (This is done in the article by making the "ui.run" make task dependent on the "node_modules" make task -- if the "node_modules" directory does not already exist, the "node_modules" make task will be run before the "ui.run" make task.)

      If you've got everything set up as described in the article, just delete the node_modules directory and re-run the "ui.run" make task -- the makefile will automatically run "npm install", which will install gulp and all the project's other node dependencies.

      Delete
    2. Thank you! I got it,the problem is that docker container does not have permission.I close SELinux,and it will be solved.

      Delete