Skip to content

Commit

Permalink
[examples] Add a webhook example.
Browse files Browse the repository at this point in the history
  • Loading branch information
shizunge committed Sep 28, 2024
1 parent cf327aa commit fcf8c66
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 3 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ docker service create \
shizunge/gantry
```

The [examples folder](examples/README.md) contains example docker compose files, and more methods to launch *Gantry*, like [at a given time](examples/cronjob).
The [examples folder](examples/README.md) contains example docker compose files, and more methods to launch *Gantry*, like [at a specific time](examples/cronjob) and [via webhook](examples/webhook).

You can also run *Gantry* as a script directly on the host outside the container
```
Expand Down
5 changes: 4 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

## [cronjob](./cronjob)

Run *gantry* at the specific time.
Run *Gantry* at a specific time.

## [prune-and-watchtower](./prune-and-watchtower)

Remove unused containers, networks and images. Update standalone docker containers. Update docker swarm services.

## [webhook](./webhook)

Launch *Gantry* via webhook.
2 changes: 1 addition & 1 deletion examples/cronjob/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# cronjob

Use [*swarm-cronjob*](https://github.com/crazy-max/swarm-cronjob) to launch *gantry* at a given time.
Use [*swarm-cronjob*](https://github.com/crazy-max/swarm-cronjob) to launch [*Gantry*](https://github.com/shizunge/gantry) at a specific time.

31 changes: 31 additions & 0 deletions examples/webhook/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# webhook

This example describes how to launch [*Gantry*](https://github.com/shizunge/gantry) via [adnanh/webhook](https://github.com/adnanh/webhook).

## Setup

We leverage a dockerized webhook image [lwlook/webhook](https://hub.docker.com/r/lwlook/webhook) which is based on the offical Docker image. This allows us to launch the *Gantry* service with simple docker commands.

[hooks.json](./hooks.json) defines the webhook's behavior. It parses incoming payloads and transforms them into environment variables like `GANTRY_SERVICES_EXCLUDED`, `GANTRY_SERVICES_EXCLUDED_FILTERS` and `GANTRY_SERVICES_FILTERS`. These variables are then used by [run_gantry.sh](./run_gantry.sh) to control *Gantry* behaviors. which means you can update different services by passing different payloads to the webhook. Refer to the [adnanh/webhook](https://github.com/adnanh/webhook) repository for more advanced webhook configurations, including securing the webhook with `trigger-rule`.

[run_gantry.sh](./run_gantry.sh) is responsible for launching the *Gantry* service.

## Test

Use the following command to deploy the Docker Compose stack that includes the webhook service.

```
docker stack deploy --detach=true --prune --with-registry-auth --compose-file ./docker-compose.yml webhook
```

Use curl to send a POST request to the webhook endpoint. This request tells the *Gantry* to only update the service named "webhook_webhook".

```
curl -X POST localhost:9000/hooks/run-gantry -H "Content-Type: application/json" -d '{"GANTRY_SERVICES_FILTERS":"name=webhook_webhook"}'
```

Check the webhook service logs to confirm if the webhook was triggered correctly.

```
docker service logs webhook_webhook
```
60 changes: 60 additions & 0 deletions examples/webhook/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
version: "3.8"

services:
webhook:
image: lwlook/webhook:latest
command:
- -verbose
- -hooks=/hooks.json
- -hotreload
ports:
- "9000:9000"
configs:
- source: hooks_json
target: /hooks.json
- source: run_gantry
target: /run_gantry.sh
mode: 0550
volumes:
- /var/run/docker.sock:/var/run/docker.sock
deploy:
placement:
constraints:
- node.role==manager

# Note: run_gantry.sh does not use this service by default.
# This service is left here to demonstrate a potential approach for reusing the same service
# by scaling its replicas instead of starting a new service each webhook request.
# See function resume_gantry in run_gantry.sh.
# Pros:
# * This approach can work together with other launching methods like crazymax/swarm-cronjob.
# Cons:
# * Concurrency Issues: Sending webhook requests too frequently can increase the chance of the
# webhook failing to launch Gantry correctly for some requests due to the existing service
# potentially handling a previous command.
gantry:
image: shizunge/gantry:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- "GANTRY_NODE_NAME={{.Node.Hostname}}"
- "GANTRY_SLEEP_SECONDS=0"
deploy:
replicas: 0
placement:
constraints:
- node.role==manager
restart_policy:
condition: none
labels:
# The label can be used to find this service.
# This can be used together with resume_gantry function in the run_gantry.sh
- webhook.run-gantry=true

configs:
hooks_json:
name: hooks_json
file: ./hooks.json
run_gantry:
name: run_gantry
file: ./run_gantry.sh
25 changes: 25 additions & 0 deletions examples/webhook/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[
{
"id": "run-gantry",
"execute-command": "/run_gantry.sh",
"command-working-directory": "/",
"pass-environment-to-command":
[
{
"source": "payload",
"name": "GANTRY_SERVICES_EXCLUDED",
"envname": "GANTRY_SERVICES_EXCLUDED"
},
{
"source": "payload",
"name": "GANTRY_SERVICES_EXCLUDED_FILTERS",
"envname": "GANTRY_SERVICES_EXCLUDED_FILTERS"
},
{
"source": "payload",
"name": "GANTRY_SERVICES_FILTERS",
"envname": "GANTRY_SERVICES_FILTERS"
}
]
}
]
92 changes: 92 additions & 0 deletions examples/webhook/run_gantry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/bin/sh
# Copyright (C) 2024 Shizun Ge
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#

_docker_start_replicated_job() {
local ARGS="${@}"
if [ -z "${ARGS}" ]; then
echo "No services set."
echo "Services that are not running:"
docker service ls | grep "0/"
return 0
fi
for S in ${ARGS}; do
echo -n "Set replicas to 0 to ${S}: "
docker service update --replicas=0 "${S}"
echo -n "Set replicas to 1 to ${S}: "
docker service update --detach --replicas=1 "${S}"
done
}

_get_number_of_running_tasks() {
local FILTER="${1}"
local REPLICAS=
if ! REPLICAS=$(docker service ls --filter "${FILTER}" --format '{{.Replicas}}' | head -n 1); then
return 1
fi
# https://docs.docker.com/engine/reference/commandline/service_ls/#examples
# The REPLICAS is like "5/5" or "1/1 (3/5 completed)"
# Get the number before the first "/".
local NUM_RUNS=
NUM_RUNS=$(echo "${REPLICAS}/" | cut -d '/' -f 1)
echo "${NUM_RUNS}"
}

resume_gantry() {
local FILTER="label=webhook.run-gantry=true"
local SERVICE_NAME=$(docker service ls --filter "${FILTER}" --format "{{.Name}}" | head -n 1)
if [ -z "${SERVICE_NAME}" ]; then
echo "Cannot find a service from ${FILTER}."
return 1
fi
local replicas=
if ! replicas=$(_get_number_of_running_tasks "${FILTER}"); then
echo "Failed to obtain task states of service from ${FILTER}."
return 1
fi
if [ "${replicas}" != "0" ]; then
echo "${SERVICE_NAME} is still running. There are ${replicas} running tasks."
return 1
fi
docker service update --detach --env-add "GANTRY_SERVICES_EXCLUDED=${GANTRY_SERVICES_EXCLUDED}" "${SERVICE_NAME}"
docker service update --detach --env-add "GANTRY_SERVICES_EXCLUDED_FILTERS=${GANTRY_SERVICES_EXCLUDED_FILTERS}" "${SERVICE_NAME}"
docker service update --detach --env-add "GANTRY_SERVICES_FILTERS=${GANTRY_SERVICES_FILTERS}" "${SERVICE_NAME}"
_docker_start_replicated_job "${SERVICE_NAME}"
}

launch_new_gantry() {
local service_name="gantry-$(date +%s)"
docker service create \
--name "${service_name}" \
--mode replicated-job \
--constraint "node.role==manager" \
--env "GANTRY_SERVICES_EXCLUDED=${GANTRY_SERVICES_EXCLUDED}" \
--env "GANTRY_SERVICES_EXCLUDED_FILTERS=${GANTRY_SERVICES_EXCLUDED_FILTERS}" \
--env "GANTRY_SERVICES_FILTERS=${GANTRY_SERVICES_FILTERS}" \
--label "from-webhook=true" \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
shizunge/gantry
local return_value=$?
docker service logs --raw "${service_name}"
docker service rm "${service_name}"
return "${return_value}"
}

main() {
launch_new_gantry ${*}
}

main "${@}"

0 comments on commit fcf8c66

Please sign in to comment.