diff --git a/jekyll/_posts/concourse-apprise-notifier/2023-06-14-concourse-apprise-notifier.md b/jekyll/_posts/concourse-apprise-notifier/2023-06-14-concourse-apprise-notifier.md new file mode 100644 index 0000000..9889795 --- /dev/null +++ b/jekyll/_posts/concourse-apprise-notifier/2023-06-14-concourse-apprise-notifier.md @@ -0,0 +1,194 @@ +--- +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.pim.kunis.nl/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.pim.kunis.nl/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.pim.kunis.nl/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.pim.kunis.nl/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.pim.kunis.nl/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. diff --git a/jekyll/_posts/concourse-apprise-notifier/ntfy.png b/jekyll/_posts/concourse-apprise-notifier/ntfy.png new file mode 100644 index 0000000..3b47f51 Binary files /dev/null and b/jekyll/_posts/concourse-apprise-notifier/ntfy.png differ diff --git a/jekyll/_posts/concourse-apprise-notifier/pipeline.png b/jekyll/_posts/concourse-apprise-notifier/pipeline.png new file mode 100644 index 0000000..68d0d14 Binary files /dev/null and b/jekyll/_posts/concourse-apprise-notifier/pipeline.png differ