add blog post about concoruse apprise notifier
This commit is contained in:
parent
5381da47a4
commit
b6f580c522
3 changed files with 194 additions and 0 deletions
|
@ -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 <<EOF
|
||||||
|
{
|
||||||
|
"body": ${alert_body},
|
||||||
|
"title": ${alert_title},
|
||||||
|
"type": ${alert_type},
|
||||||
|
"tag": ${alert_tag},
|
||||||
|
"format": ${alert_format}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Before sending it just yet, we compact the JSON and remove any values that are `null`:
|
||||||
|
```bash
|
||||||
|
compact_body="$(echo "${body}" | jq -c '.')"
|
||||||
|
echo "$compact_body" | jq 'del(..|nulls)' > /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.
|
BIN
jekyll/_posts/concourse-apprise-notifier/ntfy.png
Normal file
BIN
jekyll/_posts/concourse-apprise-notifier/ntfy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 202 KiB |
BIN
jekyll/_posts/concourse-apprise-notifier/pipeline.png
Normal file
BIN
jekyll/_posts/concourse-apprise-notifier/pipeline.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
Reference in a new issue