//

Architecture docs as code with Structurizr & Asciidoctor. Part 4: Publishing

28.10.2022 | 6 minutes of reading time

You are reading the fourth part of this article series about architecture documentation as code. If you worked through the previous articles, you already automated the generation of your architecture documents using Asciidoctor and integrated the diagrams generated with Structurizr.

Now, let's publish the resulting documentation. The related part of our workflow is displayed on a gray background in the following figure.

The workflow we will implement in this article. See part one of this series.

The code can be found on GitHub. The architecture documentation we create in this article can be found on GitHub Pages.

Publishing pipeline

Like for our code, we will build and publish the documentation continuously and in an automated way as part of our build pipeline. Therefore, we will define two build jobs using GitHub Actions. One build job will publish the document to GitHub Pages, the other one will publish it to Atlassian Confluence. Therefore, we create a file named docs-pipeline.yml under .github/workflows.

GitHub Pages

Before we start defining the pipeline, we need to tell GitHub that we want to use GitHub Actions as deployment method for GitHub Pages. Therefore, we open the settings of our repository, navigate to the Pages section and choose GitHub Actions as source for build and deployment.

Setting GitHub Actions as source for GitHub Pages.

Afterwards, we can configure the build job as shown in the following listing, running each time a developer pushes to the main branch.

1name: Docs pipeline
2
3# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
4permissions:
5  contents: read
6  pages: write
7
8# Allow one concurrent deployment
9concurrency:
10  group: "docs_pages"
11  cancel-in-progress: true
12
13on:
14  push:
15    branches: [main]
16
17jobs:
18  docs_pages:
19    environment:
20      name: github-pages
21      url: ${{ steps.deployment.outputs.page_url }}
22    runs-on: ubuntu-latest
23    steps:
24      - name: Checkout Repo
25        uses: actions/checkout@v3
26      # We need to install GraphViz to convert PlantUML files to images
27      - name: Setup Graphviz
28        uses: ts-graphviz/setup-graphviz@v1
29      # Run Asciidoctor
30      - name: Asciidoctor
31        uses: gradle/gradle-build-action@v2
32        with:
33          arguments: asciidoctor
34      # Upload the HTML docs generated with Asciidoctor
35      - name: Upload artifact
36        uses: actions/upload-pages-artifact@v1
37        with:
38          path: ./build/docs
39      # Deploy the uploaded docs using GitHub Pages action
40      - name: Deploy to GitHub Pages
41        id: deployment
42        uses: actions/deploy-pages@v1
43

To publish GitHub Pages, we need to set permissions for the GitHub token. Furthermore, we allow only one concurrent page deployment to avoid race conditions when multiple pipelines are running in parallel. In such cases, the job started earlier will be cancelled.

To be able to deploy to GitHub Pages, we need to use the GitHub Pages environment for our job. Furthermore, we have to install Graphviz for the PlantUML to SVG conversions. Therefore, we use a predefined action.

Afterwards, we run the Asciidoctor Gradle task and upload the resulting HTML documents using the upload-pages-artifact action. Finally, we deploy the uploaded artefacts using the deploy-pages action.

After pushing the workflow file to the main branch, it will be executed. When the workflow is done, you should see the following result in the workflow view.

The GitHub workflow view after the docs_pages job was finished.

A click on the link will open the architecture documentation hosted on GitHub Pages.

Atlassian Confluence

Some companies I worked for had the policy that all documentation has to be published in Confluence. Let's fulfil this policy and allow non-technical reader to access the documentation in Confluence.

Therefore, we use the official Confluence Publisher provided by Atlassian. The publisher will convert our AsciiDoc files to Confluence-compatible XHTML and will upload the resulting pages using the Confluence REST API.

Since the publisher is only available as a Maven plugin, we can't directly use it. Instead, we will use a Gradle plugin wrapping the Confluence Publisher. Therefore, we add the plugin to the plugins section of the build.gradle.kts file.

1plugins {
2    ...
3    id("ch.nomisp.confluence.publisher") version "0.2.0"
4}
5

Afterwards, we configure the publisher as shown in the following listing.

1confluencePublisher {
2    asciiDocRootFolder.set(tasks.asciidoctor.get().sourceDir)
3    setAttributes(tasks.asciidoctor.get().attributes)
4    rootConfluenceUrl.set("YOUR_CONFLUENCE_URL")
5    spaceKey.set("YOUR_SPACE_KEY")
6    ancestorId.set("ID_OF_PARENT_PAGE")
7    // set username or password or use an api token as password with empty username
8    username.set("")
9    password.set(System.getenv("CONFLUENCE_TOKEN"))
10}
11

To make sure the output of the Confluence Publisher task is the same as the one produced by the Asciidoctor task, we reuse the sourceDir and the attributes configured in the Asciidoctor task. The attributes confluenceUrl, spaceKey and ancestorId are needed to tell the publisher where to upload the documentation. The ancestorId is the ID of the parent page of our documentation. It’s a good idea to create a dedicated parent page for the architecture documentation. For the authentication against Confluence, we can set username and password. However, using a Confluence API token is recommended. To do so, the username has to be empty, and the token must be used as password. More properties can be configured. The complete list can be found in the documentation.

To make sure the diagrams included in the documentation are up to date, we let the PublishToConfluenceTask depend on the writeDiagrams task.

1tasks.withType(PublishToConfluenceTask::class) {
2    dependsOn("writeDiagrams")
3}
4

The Confluence Publisher by convention creates a Confluence page for each AsciiDoc file which does not start with an underscore. This means our documentation would end up in Confluence as one combined file based on the index.adoc and additionally a separate file for each included section file. To avoid this, we have to rename the included section files and let each file name start with an underscore. As a result, only one Confluence page based on our index.adoc file is created.

Note: Another convention is that each AsciiDoc file to be uploaded as a Confluence page must contain a top-level document title (e.g. "= Some title"). This title will be used as the Confluence page title. Due to limitations of the Confluence REST API, these titles must be unique per Confluence space. Therefore, if you want to upload multiple pages with the same title in the same space, you must pre- or suffix them. This can be done using the pageTitlePrefix and pageTitleSuffix attributes provided by the Confluence publisher.

To automate the Confluence publishing, we add the following job to the docs-pipeline.yml file.

1docs_confluence:
2  runs-on: ubuntu-latest
3  steps:
4    - name: Checkout Repo
5      uses: actions/checkout@v3
6    # We need to install GraphViz to convert PlantUML files to images
7    - name: Setup Graphviz
8      uses: ts-graphviz/setup-graphviz@v1
9    # Run publishToConfluence task which runs Asciidoctor 
10    # and publishes the resulting file to Confluence
11    - name: Publish To Confluence
12      uses: gradle/gradle-build-action@v2
13      # Add the Confluence token provided as Action secret
14      # to the environment 
15      env:
16        CONFLUENCE_TOKEN: ${{secrets.CONFLUENCE_TOKEN}}
17      with:
18        arguments: publishToConfluence
19

Again, we check out the repository and install Graphviz. Afterwards, we run the publishToConfluence Gradle task. The Confluence API token needed by the task must be part of the step environment. It can be stored as action secret in the security settings of the repository as shown in the following figure. The complete workflow can be found in the example repository.

Configuring an repository secret for GitHub Actions.

The resulting Confluence page can be found in the Confluence space you configured beneath the page you configured as ancestor. The following figure shows an excerpt of the Confluence page produced by the example project.

Excerpt of the Confluence page produced by the example project.

Summary and outlook

In this article we finished the implementation of our architecture documents as code workflow. We learned how to publish our architecture documentation automatically to GitHub Pages and Atlassian Confluence as part of a build pipeline using GitHub Actions.

As described in part one, the goal of this workflow is to reduce the efforts for maintaining long-living architecture documentation, keep it up to date, and ensure consistency. We already reduced the maintenance efforts by automating document generation and deployment. Furthermore, Structurizr with its central model saves us maintenance efforts when changing our diagrams and ensures that our diagrams are consistent.

The upcoming final part of this series is about further automating the workflow by extracting Structurizr model elements from application code, configurations and API descriptions.

share post

Likes

1

//

More articles in this subject area\n

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen

Wir helfen Deinem Unternehmen

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.