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