Automatically combine your scattered documentation with a few lines in GitLab CI
Tags: gitlab, productivity, devops, programming
Published: 2022-02-28 13:00:27

# Our documentation lives in scattered Readme's

At FinanzRitter, we have a lot of repositories. Some do small things, some do big things. All are documented in Readme files, written in Markdown. That works really well for us, because it allows us to keep the documentation where the code lives. However, when we're **onboarding new developers** we struggle to get them up to speed because:

1. all the **information is scattered** and we don't have much documentation that would explain where everything is and how it connects to work together.
2. We're also **missing pieces on why we structured things a certain way**.
3. And lastly, we have **no place for things that don't really belong anywhere**: code snippets, how-tos etc.

# We need a system to combine all Readme's and a little more

With this struggle, we set out to find a solution.

- We *don't* want to separate our documentation from our code base.
- We *don't* want to maintain the same documentation in two different places.
- We *don't* want to put a lot of extra work into it.
- We *do* want the ability to add documentation that does not belong to a specific repository but rather explains overarching concepts.
- We *do* want to have one tool to rule them all. One place where everything comes together.

# Why we chose Outline

*I'm not affiliated in any way with Outline.*

[Outline](https://www.getoutline.com/) is, as they put it: „Your team’s knowledge base“. It's an online documentation tool built on the Markdown syntax. We chose it over competitors like Confluence, Notion and Slite because Outline is:

- simple, easy to use and minimalistic in design. It does one thing and does it well.
- open source ([not strictly FOSS](https://github.com/outline/outline/blob/main/LICENSE)), [built in public](https://www.getoutline.com/changelog) and can be self-hosted.
- has a solid API.
- has a fair price structure starting at 10 USD / month for 10 users (not per user)

# Using the API to push Readme's to Outline

Outline supports collections (we have, for example a collection for frontend documentation, for backend documentation, infrastructure and onboarding). Collections contain pages and subpages. So you have plenty of options to organize things.

We integrated our Readme's into Outline using their API and a job in our GitLab pipelines. The pipeline will take the contents of the Readme and push them to the Outline API, overwriting a given page completely. With a little more work it should also be possible to only overwrite a certain section of a page, but we opted for completely overwriting them for ease-of-use and safety.

So for example, our 'Frontend' collection contains a page called 'Data Management', explaining the overall concept of handling data on the client. It has three sub-pages, one for every repository we have that plays a role in managing the data. Those are updated automatically to mirror the Readme's on GitLab:

![Structure of our Frontend collection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g7wycv8aqxhd3praj5ua.png) 

```
Frontend (Collection)
 → Data Management (Page, written in the Outline editor)
    → DataCollection (Subpage, synchronized from the repo's Readme on GitLab)
    → DataStore (Subpage, synchronized from the repo's Readme on GitLab)
    → DataVersion (Subpage, synchronized from the repo's Readme on GitLab)
```

## How it works

1. We add a **new `document` stage** to our GitLab CI pipeline, as well as a `document` job. This should run after a release job.
2. This job reads the `README.md` from your repository. And **replaces the headline** (which is expected to be `# Your Repository`) **with a warning**: *„This page automatically mirrors the documentation of *Your Repository*. Don’t make changes here or they will be lost with the next release.”* This marks the page as being automatically mirrored to Outline and prevents us from loosing manual changes. We don't have a two-way synchronization here!
3. Lastly, we **sent the modified Markdown** inside a JSON object to the Outline API.

![A warning in the mirrored documentation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f2ba5qjghntgwvsljx55.png) 

# Setting up the integration

## 1. Create a new page in Outline

Create a new page / subpage in Outline and collect its alias from the URL. For example, an alias might look like this:

`how-to-set-up-outline-integration-nkOq0DuntG`

## 2. Get an API Token

Create an API token in the settings: `https://your_organization.getoutline.com/settings/tokens` and save it to your GitLab repository or group as a new [CI environment variable](https://docs.gitlab.com/ee/ci/variables/#add-a-cicd-variable-to-a-project). We're naming it `OUTLINE_AUTH_TOKEN`. It will now be available to your job. You should also make it protected. If you do so, make sure the job is only running on protected branch- or tag-pipelines or it will fail because of the missing API token.

## 3. Add the job to your pipeline

Add the following job at the end of your `.gitlab-ci.yml`:

```yaml
document:
  stage: document
  before_script:
    # depending on your base image, you probably need to install jq
    - apt update && apt install -y jq
  script: |
    ORIGINAL=$(cat README.md)
    WARNING=':::info
    This page automatically mirrors the documentation of [Your Repository](https://gitlab.com/your_username/your_repository). Don’t make changes here or they **will be lost** with the next release.
    :::
    \'
    DOCUMENT=$(echo "${ORIGINAL/\# Your Repository/$WARNING}" | jq -Rs .)
    curl https://your_username.getoutline.com/api/documents.update \
      -X POST \
      -H "authorization: Bearer $OUTLINE_AUTH_TOKEN" \
      -H 'content-type: application/json' \
      -H 'accept: application/json' \
      --data-raw "{\"id\": \"your_page_alias\", \"text\": ${DOCUMENT}}"
```

Make sure to adapt the job to your repository. You need to replace:

- The name (`Your Repository`) and URL of your GitLab repository (`https://gitlab.com/your_username/your_repository`)
- The sub-domain or URL of your Outline instance (`https://your_username.getoutline.com`)
- The page alias (`your_page_alias`)
- Possibly the name of your Readme. We use `README.md`.


Also, the [rules](https://docs.gitlab.com/ee/ci/yaml/#rules) or [only/except](https://docs.gitlab.com/ee/ci/yaml/#only--except) (depending on what you are using) should match the configuration of your release job(s). So the documentation is only published when a release has been built.

You also might want to add `allow_failure: false` to the release job(s), if they have a manual trigger. Otherwise, the document stage will run regardless of their success.

## Caveats

- Outline has a feature that allows 'Collaborative editing' in real-time with multiple people. It is disabled by default (February 2022) and can be enabled in the *Features* section of the settings. If active, the integration does not work. I haven't digged deeper into this. Maybe someone has a solution?
- Using the script above, you can only publish one Markdown file. If you have multiple, you need to add multiple jobs or adapt the script accordingly. 
- This is currently GitLab-only. It sure would be nice to have A GitHub Action for other folks. If someone creates one, please let me know!

## Feedback for Outline

- Your API documentation is good, thank you! Maybe it can be improved regarding the collaborative editing?
- Your WYSIWYG editor often does not behave as I'm used to, especially in regards to lists. It's not buggy, but it could use some fine-tuning.
- It would be amazing to have an integration like this natively!
- Your design is really clean, I enjoy using Outline very much!