{"id":21479284,"url":"https://github.com/rycus86/webhook-proxy","last_synced_at":"2025-07-15T11:31:36.952Z","repository":{"id":24413212,"uuid":"101429044","full_name":"rycus86/webhook-proxy","owner":"rycus86","description":"Simple web server for JSON webhooks","archived":false,"fork":false,"pushed_at":"2024-11-02T03:47:15.000Z","size":128,"stargazers_count":26,"open_issues_count":6,"forks_count":7,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-09T03:24:03.766Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rycus86.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-08-25T17:56:48.000Z","updated_at":"2025-04-02T22:21:47.000Z","dependencies_parsed_at":"2023-02-12T21:00:36.526Z","dependency_job_id":"3958ebbf-c5d5-4b4a-84dc-6a905a1561ec","html_url":"https://github.com/rycus86/webhook-proxy","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rycus86/webhook-proxy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fwebhook-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fwebhook-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fwebhook-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fwebhook-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rycus86","download_url":"https://codeload.github.com/rycus86/webhook-proxy/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fwebhook-proxy/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265431566,"owners_count":23764031,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-23T11:24:29.924Z","updated_at":"2025-07-15T11:31:36.702Z","avatar_url":"https://github.com/rycus86.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Webhook Proxy\n\nA simple `Python` [Flask](http://flask.pocoo.org) *REST* server to\naccept *JSON* webhooks and run actions as a result.\n\n[![Build Status](https://travis-ci.org/rycus86/webhook-proxy.svg?branch=master)](https://travis-ci.org/rycus86/webhook-proxy)\n[![Build Status](https://img.shields.io/docker/build/rycus86/webhook-proxy.svg)](https://hub.docker.com/r/rycus86/webhook-proxy)\n[![Coverage Status](https://coveralls.io/repos/github/rycus86/webhook-proxy/badge.svg?branch=master)](https://coveralls.io/github/rycus86/webhook-proxy?branch=master)\n[![Code Climate](https://codeclimate.com/github/rycus86/webhook-proxy/badges/gpa.svg)](https://codeclimate.com/github/rycus86/webhook-proxy)\n\n## Usage\n\nTo start the server, run:\n\n```shell\npython app.py [server.yml]\n```\n\nIf the parameter is omitted, the configuration file is expected to be `server.yml`\nin the current directory (see configuration details below).\n\nThe application can be run using Python 2 or 3.\n\n## Configuration\n\nThe configuration for the server and its endpoints is described in a *YAML* file.\n\nA short example:\n\n```yaml\nserver:\n  host: '127.0.0.1'\n  port: '5000'\n\nendpoints:\n  - /endpoint/path:\n      method: 'POST'\n\n      headers:\n        X-Sender: 'regex for X-Sender HTTP header'\n\n      body:\n        project:\n          name: 'regex for project.name in the JSON payload'\n          items:\n            name: '^example_[0-9]+'\n      \n      actions:\n        - log:\n            message: 'Processing {{ request.path }} ...'\n```\n\n### server\n\nThe `server` section defines settings for the HTTP server receiving the webhook requests.\n\n| key | description | default | required |\n| --- | ----------- | ------- | -------- |\n| host | The host name or address for the server to listen on  | `127.0.0.1` | no |\n| port | The port number to accept incoming connections on     | `5000`      | no |\n| imports | Python modules (as list of file paths) to import for registering additional actions | `None` | no |\n\nSet the `host` to `0.0.0.0` to accept connections from any hosts.\n\nThe `imports` property has to be a `list` and should point to the `.py` files.\nThey will be copied temporarily into the `TMP_IMPORT_DIR` folder (`/tmp` by default,\noverride with the environment variable) then renamed to a random filename and\nfinally imported as a module so that we can load multiple modules with the same\nfilename from different paths.\nAlso note that because of this, we cannot rely on the module `__name__`.\n\n### endpoints\n\nThe `endpoints` section configures the list of endpoints exposed on the server.\n\nEach endpoint supports the following configuration (all optional):\n\n| key | description | default |\n| --- | ----------- | ------- |\n| method   | HTTP method supported on the endpoint           | `POST`  |\n| headers  | HTTP header validation rules as a dictionary of names to regular expressions  | `empty` |\n| body     | Validation rules for the JSON payload in the request body                     | `empty` |\n| async    | Execute the action asynchronously               | `False` |\n| actions  | List of actions to execute for valid requests.  | `empty` |\n\nThe message body validation supports lists too, the `project.item.name` in the example would accept\n`{\"project\": {\"name\": \"...\", \"items\": [{\"name\": \"example_12\"}]}}` as an incoming body.\n\n### actions\n\nAction definitions support variables for most properties using _Jinja2_ templates.\nBy default, these receive the following objects in their context:\n\n- `request`   : the incoming _Flask_ request being handled\n- `timestamp` : the Epoch timestamp as `time.time()`\n- `datetime`  : human-readable timestamp as `time.ctime()`\n- `own_container_id`: the ID of the container the app is running in or otherwise `None`\n- `read_config`: helper for reading configuration parameters from key-value files\n  or environment variables and also full configuration files (certificates for example),\n  see [docker_helper](https://github.com/rycus86/docker_helper) for more information and usage\n- `error(..)` : a function with an optional `message` argument to raise errors when evaluating templates\n- `context`   : a thread-local object for passing information from one action to another\n\n_Jinja2_ does not let you execute code in the templates directly, so to use\nthe `error` and `context` objects you need to do something like this:\n\n```\n{% if 'something' is 'wrong' %}\n  \n  {# treat it as literal (will display None) #}\n  {{ error('Something is not right }}\n\n  {# or use the assignment block with a dummy variable #}\n  {% set _ = error() %}\n\n{% else %}\n\n  {% set _ = context.set('verdict', 'All good') %}\n\n{% endif %}\n\n## In another action's template:\n\n  Previously we said {{ context.verdict }}\n```\n\nThe following actions are supported (given their dependencies are met).\n\n#### log\n\nThe `log` action prints a message on the standard output. \n\n| key | description | default | templated | required |\n| --- | ----------- | ------- | --------- | -------- |\n| message | The log message template | `Processing {{ request.path }} ...` | yes | no |\n\n#### eval\n\nThe `eval` action evaluates a _Jinja2_ template block.\nThis can be useful to work with objects passed through from previous actions using\nthe `context` for example.\n\n| key | description | default | templated | required |\n| --- | ----------- | ------- | --------- | -------- |\n| block | The template block to evaluate | | yes | yes |\n\n#### execute\n\nThe `execute` action executes an external command using `subprocess.check_output`.\nThe output (string) of the invocation is passed to the _Jinja2_ template as `result`.\n\n| key | description | default | templated | required |\n| --- | ----------- | ------- | --------- | -------- |\n| command | The command to execute as a string or list                     |                | no  | yes |\n| shell   | Configuration for the shell used (see below)                   | `True`         | no  | no  |\n| output  | Output template for printing the result on the standard output | `{{ result }}` | yes | no  |\n\nThe `shell` parameter accepts:\n\n- boolean : whether to use the default shell or run the command directly\n- string  : a shell command that supports `-c`\n- list    : for the complete shell prefix, like `['bash', '-c']`\n\n#### http\n\nThe `http` action sends an HTTP request to a target and requires the __requests__ Python module.\nThe HTTP response object (from the _requests_ module) is available to the\n_Jinja2_ template as `response`.\n\n| key | description | default | templated | required |\n| --- | ----------- | ------- | --------- | -------- |\n| target  | The target endpoint as `\u003cscheme\u003e://\u003chost\u003e[:\u003cport\u003e][/\u003cpath\u003e]`                        |    | no  | yes |\n| method  | The HTTP method to use for the request                                              | `POST`  | no  | no  |\n| headers | The HTTP headers (as dictionary) to add to the request                              | `empty` | yes | no  |\n| json    | Whether to dump `body` YAML subtree as json                                         | `False` | no  | no  |\n| body    | The HTTP body to send with the request. String (or YAML tree, if `json` is `True`)  | `empty` | yes | no  |\n| output  | Output template for printing the response on the standard output                    | `HTTP {{ response.status_code }} : {{ response.content }}` | yes | no |\n| verify | SSL certificate check behavior, set to `False` to ignore certificate errors (not recomended) or provide path to custom CA | `True` | no | no\n\n#### github-verify\n\nThe `github-verify` is a convenience action to validate incoming _GitHub_ webhooks.\nIt requires the webhook to be signed with a secret.\n\n| key | description | default | templated | required |\n| --- | ----------- | ------- | --------- | -------- |\n| secret | The webhook secret configured in _GitHub_ | | yes | yes |\n| output  | Output template for printing a message on the standard output | `{{ result }}` | yes | no |\n\nThe action will raise an `ActionInvocationException` on failure.\nIf that happens, the actions defined after this one will not be executed.\n\n#### docker\n\nThe `docker` action interacts with the _Docker_ daemon and requires the __docker__ Python module.\nIt also needs access to the _Docker_ UNIX socket at `/var/run/docker.sock`.\n\nThe action supports _exactly one_ invocation on the _Docker_ client (per action).\nInvocations (or properties) are keys starting with `$` in the configuration,\nfor example listing the containers would use `$containers` with `$list` as a sub-item.\nThe result of the invocation (as an object from the _Docker_ client) is available to the\n_Jinja2_ templates as `result`.\n\n| key | description | default | templated | required |\n| --- | ----------- | ------- | --------- | -------- |\n| `$invocation` | Exactly one invocation supported by the _Docker_ client (see examples below) |                | yes (for values) | yes |\n| output        | Output template for printing the result on the standard output               | `{{ result }}` | yes              | no  |\n\nExamples:\n\n```yaml\n...\n  actions:\n    - docker:\n        $containers:\n          $list:\n            filters:\n              name: '{{ request.json.repo.name }}'\n        output: |\n          Containers matching \"{{ request.json.name }}\":\n          {% for container in result %}\n           - {{ container.name }} @ {{ container.short_id }}\n          {% endfor %}\n\n    - docker:\n        $info:\n        output: 'Docker version: {{ result.ServerVersion }} on {{ result.OperatingSystem }}'\n\n    - docker:\n        $images:\n          $pull:\n            repository: '{{ request.json.namespace }}/{{ request.json.name }}'\n            tag: '{{ request.json.get('tag', 'latest') }}'\n\n    - docker:\n        $containers:\n          $run:\n            image: 'alpine'\n            command: 'echo \"Hello {{ request.json.message }}!\"'\n            remove: true\n```\n\n#### docker-compose\n\nThe `docker-compose` action interacts with _Docker Compose_ and requires the `docker-compose` Python module.\n\nThe action supports _exactly one_ invocation on the _Docker Compose_ project (per action).\nThe invocations are in the same format as with the `docker` action and the\nresult is available for _Jinja2_ templates as `result` that is the return object\nfrom the _Docker Compose_ invocation.\n\n| key | description | default | templated | required |\n| --- | ----------- | ------- | --------- | -------- |\n| project\\_name | The _Compose_ project name             | | no | yes |\n| directory     | The directory of the _Compose_ project | | no | yes |\n| composefile   | The filename of the _Composefile_ within the directory          | `docker-compose.yml` | no  | no |\n| `$invocation` | Exactly one invocation supported by the _Docker Compose_ client (see examples below) | | yes (for values) | yes |\n| output | Output template for printing the result on the standard output         | `{{ result }}`       | yes | no |\n\nExamples:\n\n```yaml\n...\n  actions:\n    - docker-compose:\n        project_name: 'web'\n        directory: '/opt/projects/web'\n        $get_services:\n        output: |\n          Compose services:\n          {% for service in result %}\n           - service: {{ service.name }}\n          {% endfor %}\n\n    - docker-compose:\n        project_name: 'backend'\n        directory: '/opt/projects/compose_project'\n        $up:\n          detached: true\n        output: |\n          Containers started:\n          {% for container in result %}\n           - {{ container.name }}\n          {% endfor %}\n\n    - docker-compose:\n        project_name: 'backend'\n        directory: '/opt/projects/compose_project'\n        $down:\n          remove_image_type: false\n          include_volumes: true\n        output: 'Compose project stopped'\n```\n\n#### docker-swarm (deprecated)\n\n*Since the merge of [docker-py#1807](https://github.com/docker/docker-py/pull/1807),\nthis convenience action is no longer necessary.\nThe official Docker SDK can handle Swarm service updates nicely.*\n\nThe `docker-swarm` action exposes convenience _Docker_ actions for _Swarm_ related operations\nthat might require quite a bit of manual work to replicate with the `docker` action.\n\nThe action supports _exactly one_ invocation (per action) on its own action object.\nThe invocations are in the same format as with the `docker` action and the available\nones are:\n\n- `$restart`: restarts (force updates) a _Swarm_ service matching the `service_id` parameter\n  (this can be a service name or ID)\n- `$scale`: updates a __replicated__ service matched by `service_id` to have `replicas` number\n  of instances\n- `$update`: updates a service matched by `service_id`\n\nThe update invocation uses the current service spec and updates them with the following\nparameters if they are present:\n\n- `image`, `command`, `args`, `hostname`, `env`, `dir`, `user`, `mounts`, `stop_grace_period`, `tty`\n  for the container specification (see `docker.types.services.ContainerSpec`)\n- `container_labels` for container labels\n- `secrets` for secret references as a list of dictionaries\n  (see `docker.types.services.SecretReference`) \n- `resources`, `restart_policy`, `placement` for the task template specification\n  (see `docker.types.services.TaskTemplate`)\n- `labels` for service labels\n- `replicas` for number of instances for __replicated__ services\n- `update_config` for the service update configuration\n  (see `docker.types.services.UpdateConfig`)\n- `networks` as a list of network IDs or names\n- `endpoint_spec` for the endpoint specification\n  (see `docker.types.services.EndpointSpec`)\n\nThe result of the invocations will be the service object if the service update was successful. \n\n| key | description | default | templated | required |\n| --- | ----------- | ------- | --------- | -------- |\n| `$invocation` | Exactly one invocation supported by the action (see examples below) | | yes (for values) | yes |\n| output | Output template for printing the result on the standard output | `{{ result }}` | yes | no |\n\nExamples:\n\n```yaml\n...\n  actions:\n    - docker-swarm:\n        $restart:\n          service_id: '{{ request.json.service }}'\n        output: \u003e\n          Service restarted: {{ result.name }}\n\n    - docker-swarm:\n        $scale:\n          service_id: '{{ request.json.service }}'\n          replicas: '{{ request.json.replicas }}'\n\n    - docker-swarm:\n        $update:\n          service_id: '{{ request.json.service }}'\n          command: '{{ request.json.command }}'\n          labels:\n            label_1: 'sample'\n            label_2: '{{ request.json.label }}'\n```\n\n#### sleep\n\nThe `sleep` action waits for a given time period.\nIt may be useful if an action has executed something asynchronous and another action\nrelies on the outcome that would only happen a little bit later.\n\n| key | description | default | templated | required |\n| --- | ----------- | ------- | --------- | -------- |\n| seconds | Number of seconds to sleep for | | yes | yes | \n| message | The message template to print on the standard output | `Waiting {{ seconds }} seconds before continuing ...` | yes | no |\n\n#### metrics\n\nThe application exposes [Prometheus](https://prometheus.io/) metrics\nabout the number of calls and the execution times of the endpoints.\n\nThe `metrics` action registers a new metric in addition that\ntracks the entire execution of the endpoint (not only the action).\nApart from the optional `output` configuration it has to contain\none metric registration from the table below.\n\n| key | description | default | templated | required |\n| --- | ----------- | ------- | --------- | -------- |\n| histogram | Registers a Histogram | | yes (labels) | yes (one) | \n| summary   | Registers a Summary   | | yes (labels) | yes (one) |\n| gauge     | Registers a Gauge     | | yes (labels) | yes (one) |\n| counter   | Registers a Counter   | | yes (labels) | yes (one) |\n| message | The message template to print on the standard output | `Waiting {{ seconds }} seconds before continuing ...` | yes | no |\n| output | Output template for printing the result on the standard output         | `Tracking metrics: {{ metric }}`       | yes | no |\n\nNote that the `name` configuration is mandatory for metrics.\nAlso note that metric labels are accepted as a dictionary where\nthe value can be templated and will be evaluated within\nthe Flask request context.\nThe templates also have access to the Flask `response` object\n(with the `gauge` being the exception as it is also evaluated\nbefore the request to track in-progress executions).\n\nFor example:\n\n```yaml\n...\n  actions:\n    - metrics:\n        gauge:\n          name: requests_in_progress\n          help: Tracks current requests in progress\n          \n    - metrics:\n        summary:\n          name: request_summary\n          labels:\n            path: '{{ request.path }}'\n...\n```\n\n## Docker\n\nThe application can be run in *Docker* containers using images based on *Alpine Linux*\nfor 3 processor architectures with the following tags:\n\n- `latest`: for *x86* hosts  \n  [![Layers](https://images.microbadger.com/badges/image/rycus86/webhook-proxy.svg)](https://microbadger.com/images/rycus86/webhook-proxy \"Get your own image badge on microbadger.com\")\n- `armhf`: for *32-bits ARM* hosts  \n  [![Layers](https://images.microbadger.com/badges/image/rycus86/webhook-proxy:armhf.svg)](https://microbadger.com/images/rycus86/webhook-proxy:armhf \"Get your own image badge on microbadger.com\")\n- `aarch64`: for *64-bits ARM* hosts  \n  [![Layers](https://images.microbadger.com/badges/image/rycus86/webhook-proxy:aarch64.svg)](https://microbadger.com/images/rycus86/webhook-proxy:aarch64 \"Get your own image badge on microbadger.com\")\n\n`latest` is auto-built on [Docker Hub](https://hub.docker.com/r/rycus86/webhook-proxy)\nwhile the *ARM* builds are uploaded from [Travis](https://travis-ci.org/rycus86/webhook-proxy).\n\nThe containers run as a non-root user.\n\nTo start the server:\n\n```shell\ndocker run -d --name=webhook-proxy -p 5000:5000      \\\n    -v $PWD/server.yml:/etc/conf/webhook-server.yml  \\\n    rycus86/webhook-proxy:latest                     \\\n        /etc/conf/webhook-server.yml\n```\n\nOr put the configuration file at the default location:\n\n```shell\ndocker run -d --name=webhook-proxy -p 5000:5000  \\\n    -v $PWD/server.yml:/app/server.yml           \\\n    rycus86/webhook-proxy:latest\n```\n\nThere are 3 more tags available for images that can use the `docker` and `docker-compose`\nactions which are running as `root` user:\n\n- `docker`: for *x86* hosts  \n  [![Layers](https://images.microbadger.com/badges/image/rycus86/webhook-proxy:docker.svg)](https://microbadger.com/images/rycus86/webhook-proxy:docker \"Get your own image badge on microbadger.com\")\n- `armhf-docker`: for *32-bits ARM* hosts  \n  [![Layers](https://images.microbadger.com/badges/image/rycus86/webhook-proxy:armhf-docker.svg)](https://microbadger.com/images/rycus86/webhook-proxy:armhf-docker \"Get your own image badge on microbadger.com\")\n- `aarch64-docker`: for *64-bits ARM* hosts  \n  [![Layers](https://images.microbadger.com/badges/image/rycus86/webhook-proxy:aarch64-docker.svg)](https://microbadger.com/images/rycus86/webhook-proxy:aarch64-docker \"Get your own image badge on microbadger.com\")\n\nEach of these are built on [Travis](https://travis-ci.org/rycus86/webhook-proxy) and\npushed to [Docker Hub](https://hub.docker.com/r/rycus86/webhook-proxy).\n\nTo run these, the _Docker_ daemon's UNIX socket needs to be mounted into the container\ntoo apart from the configuration file:\n\n```shell\ndocker run -d --name=webhook-proxy -p 5000:5000      \\\n    -v $PWD/server.yml:/app/server.yml               \\\n    -v /var/run/docker.sock:/var/run/docker.sock:ro  \\\n    rycus86/webhook-proxy:docker\n```\n\nIn _Docker Compose_ on a 64-bit ARM machine the service definition could look like this:\n\n```yaml\nversion: '2'\nservices:\n\n  webhooks:\n    image: rycus86/webhook-proxy:aarch64\n    ports:\n      - 8080:5000\n    volumes:\n      - ./webhook-server.yml:/app/server.yml:ro\n```\n\n## Examples\n\nHave a look at the [sample.yml](https://github.com/rycus86/webhook-proxy/blob/master/sample.yml) included in this repo to get\na better idea of the configuration.\n\nYou can also find some examples with short explanation below.\n\n- An externally available server listening on port `7000` and printing\n  details about a _GitHub_ push webhook\n\n```yaml\nserver:\n  host: '0.0.0.0'\n  port: '7000'\n\nendpoints:\n  - /github:\n      method: 'POST'\n\n      headers:\n        X-GitHub-Delivery: '^[0-9a-f\\-]+$'\n        X-GitHub-Event: 'push'\n\n      body:\n        ref: 'refs/heads/.+'\n        before: '^[0-9a-f]{40}'\n        after: '^[0-9a-f]{40}'\n        repository:\n          id: '^[0-9]+$'\n          full_name: 'sample/.+'\n          owner:\n            email: '.+@.+\\..+'\n        commits:\n          id: '^[0-9a-f]{40}'\n          message: '.+'\n          author:\n            name: '.+'\n          added: '^(src/.+)?'\n          removed: '^(src/.+)?'\n        pusher:\n          name: '.+'\n          email: '.+@.+\\..+'\n\n      actions:\n        - log:\n            message: |\n              Received a GitHub push from the {{ request.json.repository.full_name }} repo:\n              - Pushed by {{ request.json.pusher.name }} \u003c{{ request.json.pusher.email }}\u003e\n              - Commits included:\n              {% for commit in request.json.commits %}\n              +   {{ commit.id }}\n              +   {{ commit.committer.name }} at {{ commit.timestamp }}\n              +   {{ commit.message }}\n              \n              {% endfor %}\n              Check this change out at {{ request.json.compare }}\n\n        # verify the webhook signature\n        - github-verify:\n            secret: '{{ read_config(\"GITHUB_SECRET\", \"/var/run/secrets/github\") }}'\n```\n\nThe validators for the `/github` endpoint require that\nthe `X-GitHub-Delivery` header is hexadecimal separated by dashes and\nthe `X-GitHub-Event` header has the `push` value.\nThe event also has to come from one of the repos under the `sample` namespace.\nSome of the commit hashes are checked that they are 40 character long\nhexadecimal values and the commit author's name has to be non-empty.\nThe `commits` field is actually a list in the _GitHub_ webhook so\nthe validation is applied to each commit data individually.\nThe `added` and `removed` checks for example accept if the commit has\nnot added or removed anything but if it did it has to be in the `src` folder.\n\nFor valid webhooks the repository's name, the pushers name and emails are\nprinted to the standard output followed by the ID, committer name, timestamp\nand message of each commit in the push.\nThe last line displays the URL for the _GitHub_ compare page for the change.\n\n\nFor more information about using the _Jinja2_ templates have a look\nat the [official documentation](http://jinja.pocoo.org).\n\nThe `github-verify` action will make sure that the webhook is signed as appropriate.\nThe _secret_ for this is read either from the `/var/run/secrets/github` file or\nthe `GITHUB_SECRET` environment variable.\n\n\u003e In case it is in a file, that file should contain key-value pairs, like `GITHUB_SECRET=TopSecret`\n\n- Update a _Docker Compose_ project on image changes\n\nLet's assume we have a _Compose_ project with a few services.\nWhen their image is updated in _Docker Hub_ we want to pull it\nand get _Compose_ to restart the related containers.\n\n```yaml\nserver:\n  host: '0.0.0.0'\n  port: '5000'\n\nendpoints:\n  - /webhook/dockerhub:\n      method: 'POST'\n\n      body:\n        repository:\n          repo_name: 'somebody/.+'\n          owner: 'somebody'\n        push_data:\n          tag: 'latest'\n      \n      actions:\n        - docker:\n            $containers:\n              $list:\n            output: |\n              {% for container in result if request.json.repository.repo_name in container.image.tags %}\n                Found {{ container.name }} with {{ container.image }}\n              {% else %}\n                {% set _ = error('No containers found using %s'|filter(request.json.repo_name)) %}\n              {% endfor %}\n        - docker:\n            $images:\n              $pull:\n                repository: '{{ request.json.repo_name }}'\n                tag: '{{ request.json.tag }}'\n        - docker-compose:\n            project_name: 'autoupdate'\n            directory: '/var/compose/project'\n            $up:\n              detached: true\n            output: |\n              Containers affected:\n              {% for container in result %}\n              {{ container.name }} \u003c{{ container.short_id }}\u003e\n```\n\nThe `/webhook/dockerhub` endpoint will accept webhooks from `somebody/*` repos\nwhen an image's `latest` tag is updated.\nFirst a `docker` action checks that we already have containers running that\nuse the image then another `docker` action pulls the updated image and\nfinally the `docker-compose` action applies the changes by restarting\nany related containers.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frycus86%2Fwebhook-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frycus86%2Fwebhook-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frycus86%2Fwebhook-proxy/lists"}