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.
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
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.
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}
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}
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}
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
andpageTitleSuffix
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
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.
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.
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.
More articles
fromChristoph Knauf
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
More articles in this subject area
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.
Blog author
Christoph Knauf
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.