--- layout: post title: Sending Apprise Notifications from Concourse CI date: 2023-06-14 23:39:00 Europe/Amsterdam categories: concourse apprise --- Recently, I deployed [Concourse CI](https://concourse-ci.org/) because I wanted to get my feet wet with a CI/CD pipeline. However, I had a practical use case lying around for a long time: automatically compiling my static website and deploying it to my docker Swarm. This took some time getting right, but the result works like a charm ([source code](https://git.kun.is/pim/static)). It's comforting to know I don't have move a finger and my website is automatically deployed. However, I would still like to receive some indication of what's happening. And what's a better way to do that, than using my [Apprise](https://github.com/caronc/apprise) service to keep me up to date. There's a little snag though: I could not find any Concourse resource that does this. That's when I decided to just create it myself. # The Plagiarism Hunt As any good computer person, I am lazy. I'd rather just copy someone's work, so that's what I did. I found [this](https://github.com/mockersf/concourse-slack-notifier) GitHub repository that does the same thing but for Slack notifications. For some reason it's archived, but it seemed like it should work. I actually noticed lots of repositories for Concourse resource types are archived, so not sure what's going on there. # Getting to know Concourse Let's first understand what we need to do reach our end goal of sending Apprise notifications from Concourse. A Concourse pipeline takes some inputs, performs some operations on them which result in some outputs. These inputs and outputs are called _resources_ in Concourse. For example, a Git repository could be a resource. Each resource is an instance of a _resource type_. A resource type therefore is simply a blueprint that can create multiple resources. To continue the example, a resource type could be "Git repository". We therefore need to create our own resource type that can send Apprise notifications. A resource type is simply a container that includes three scripts: - `check`: check for a new version of a resource - `in`: retrieve a version of the resource - `out`: create a version of the resource As Apprise notifications are basically fire-and-forget, we will only implement the `out` script. # Writing the `out` script The whole script can be found [here](https://git.kun.is/pim/concourse-apprise-notifier/src/branch/master/out), but I will explain the most important bits of it. Note that I only use Apprise's persistent storage solution, and not its stateless solution. Concourse provides us with the working directory, which we `cd` to: ```bash cd "${1}" ``` We create a timestamp, formatted in JSON, which we will use for the resource's new version later. Concourse requires us to set a version for the resource, but since Apprise notifications don't have that, we use the timestamp: ```bash timestamp="$(jq -n "{version:{timestamp:\"$(date +%s)\"}}")" ``` First some black magic Bash to redirect file descriptors. Not sure why this is needed, but I copied it anyways. After that, we create a temporary file holding resource's parameters. ```bash exec 3>&1 exec 1>&2 payload=$(mktemp /tmp/resource-in.XXXXXX) cat > "${payload}" <&0 ``` We then extract the individual parameters. The `source` key contains values how the resource type was specified, while the `params` key specifies parameters for this specific resource. ```bash apprise_host="$(jq -r '.source.host' < "${payload}")" apprise_key="$(jq -r '.source.key' < "${payload}")" alert_body="$(jq -r '.params.body' < "${payload}")" alert_title="$(jq -r '.params.title // null' < "${payload}")" alert_type="$(jq -r '.params.type // null' < "${payload}")" alert_tag="$(jq -r '.params.tag // null' < "${payload}")" alert_format="$(jq -r '.params.format // null' < "${payload}")" ``` We then format the different parameters using JSON: ```bash alert_body="$(eval "printf \"${alert_body}\"" | jq -R -s .)" [ "${alert_title}" != "null" ] && alert_title="$(eval "printf \"${alert_title}\"" | jq -R -s .)" [ "${alert_type}" != "null" ] && alert_type="$(eval "printf \"${alert_type}\"" | jq -R -s .)" [ "${alert_tag}" != "null" ] && alert_tag="$(eval "printf \"${alert_tag}\"" | jq -R -s .)" [ "${alert_format}" != "null" ] && alert_format="$(eval "printf \"${alert_format}\"" | jq -R -s .)" ``` Next, from the individual parameters we construct the final JSON message body we send to the Apprise endpoint. ```bash body="$(cat < /tmp/compact_body.json ``` Here is the most important line, where we send the payload to the Apprise endpoint. It's quite straight-forward. ```bash curl -v -X POST -T /tmp/compact_body.json -H "Content-Type: application/json" "${apprise_host}/notify/${apprise_key}" ``` Finally, we print the timestamp (fake version) in order to appease the Concourse gods. ```bash echo "${timestamp}" >&3 ``` # Building the Container As said earlier, to actually use this script, we need to add it to a image. I won't be explaining this whole process, but the source can be found [here](https://git.kun.is/pim/concourse-apprise-notifier/src/branch/master/pipeline.yml). The most important take-aways are these: - Use `concourse/oci-build-task` to build a image from a Dockerfile. - Use `registry-image` to push the image to an image registry. # Using the Resource Type Using our newly created resource type is surprisingly simple. I use it for the blog you are reading right now and the pipeline definition can be found [here](https://git.kun.is/pim/static/src/branch/main/pipeline.yml). Here we specify the resource type in a Concourse pipeline: ```yaml resource_types: - name: apprise type: registry-image source: repository: git.kun.is/pim/concourse-apprise-notifier tag: "1.1.1" ``` We simply have to tell Concourse where to find the image, and which tag we want. Next, we instantiate the resource type to create a resource: ```yaml resources: - name: apprise-notification type: apprise source: host: https://apprise.pim.kunis.nl:444 key: concourse icon: bell ``` We simply specify the host to send Apprise notifications to. Yeah, I even gave it a little bell because it's cute. All that's left to do, is actually send the notification. Let's see how that is done: ```yaml - name: deploy-static-website plan: - task: deploy-site config: ... on_success: put: apprise-notification params: title: "Static website deployed!" body: "New version: $(cat version/version)" no_get: true ``` As can be seen, the Apprise notification can be triggered when a task is executed successfully. We do this using the `put` command, which execute the `out` script underwater. We set the notification's title and body, and send it! The result is seen below in my Ntfy app, which Apprise forwards the message to: ![picture showing my Ntfy app with the Apprise notification](ntfy.png) And to finish this off, here is what it looks like in the Concourse web UI: ![the concourse web gui showing the pipeline of my static website including the the apprise notification resources](pipeline.png) # Conclusion Concourse's way of representing everything as an image/container is really interesting in my opinion. A resource type is quite easily implemented as well, although Bash might not be the optimal way to do this. I've seen some people implement it in Rust, which might be a good excuse to finally learn that language :) Apart from Apprise notifications, I'm planning on creating a resource type to deploy to a Docker swarm eventually. This seems like a lot harder than simply sending notifications though.