TheCatLady/docker-webhook

OS world vs Container world

rafsaf opened this issue · 3 comments

Hi, this is a nice image, i would love to use it instead of hard coding webhook directly on server's OS, BUT there is a problem you didn't mentioned in README and I suppose it makes the image at least not useful in many cases and at worst not useful at all for some situations.

This was discussed in more detail here for example:
https://stackoverflow.com/questions/32163955/how-to-run-shell-script-on-host-from-docker-container

Short: If you want to execute shell scripts from the inside of a container, you need to use hacks or even worse hacks (this is not designed this way, but the opposite), and after doing so, encounter problems like container restart, server restart which may (or may not) mess up things, in sense of messing those hacks. I didn't find yet any clean and STABLE solution to this problem (disclaimer: didn't spend much time trying), you probably don't want to run your scripts inside of the container with Webhook (which actually happens by reasonable default after using this image), but directly in OS, and this is different matter.

To sum up (it's late nigh here in PL :D, hope above text is understandable), if you are aware of any clean and stable solution, please share, cheers ;)

That's out of the scope of this repo, which exists solely to publish a Docker image for webhook.

Just to share what I've done;

  • I expect the processes inside the container to run as root so things like permissions need to be accounted for.
  • I expect the image to include sh (shell) so I can execute commands/scripts (it does not include bash)

With those expectations, my mentality is for my execute-command to be a shell script that checks if any of the commands I plan to use exist, and if not it installs them. Check and then install is useful because it will check and install when the first webhook request is received after creating the container, but not reinstall every time a webhook is received.


My docker-compose.yml

services:
  webhook:
    image: ghcr.io/thecatlady/webhook:latest
    container_name: webhook
    command: -verbose -hooks=hooks.json -hotreload
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /path/to/config:/config:ro
      - /path/to/parent/git_folder:/opt/git
      - /path/to/.ssh:/root/.ssh:ro
    ports:
      - 9000:9000
    restart: unless-stopped

In the above:

  • /path/to/config is hopefully self explanatory
  • /path/to/parent/git_folder is one level above where I put all my git repos (ex: /home/myuser/git/ which contains multiple repos), you can mount your git repos however works for you (it's not really relevant to answering this issue)
  • /path/to/.ssh for me is /home/myuser/.ssh which contains my id_ed25519 (deploy key for github) (again, not really relevant to answering this issue)

My hooks.json (placed at /path/to/config/hooks.json)

[
  {
    "id": "my-hook-name",
    "execute-command": "/config/run/git-checkout-force.sh",
    "command-working-directory": "/opt/git/myrepo",
    "include-command-output-in-response": true,
    "include-command-output-in-response-on-error": true,
    "pass-arguments-to-command": [
      { "source": "payload", "name": "head_commit.id", "comment": "GIT_REF" },
      {
        "source": "string",
        "name": "/opt/git/myrepo",
        "comment": "GIT_DIR"
      },
      { "source": "string", "name": "1000", "comment": "PUID" },
      { "source": "string", "name": "1000", "comment": "PGID" }
    ],
    "trigger-rule": {
      "and": [
        {
          "match": {
            "type": "payload-hmac-sha1",
            "secret": "<YOUR_GITHUB_WEBHOOK_SECRET>",
            "parameter": { "source": "header", "name": "X-Hub-Signature" }
          }
        },
        {
          "match": {
            "type": "value",
            "value": "refs/heads/main",
            "parameter": { "source": "payload", "name": "ref" }
          }
        }
      ]
    }
  }
]

I'm not going to go too much into the details of this, it's all covered in https://github.com/adnanh/webhook/tree/master/docs
I will note that I'm expecting the branch to be main, you might need it to be master or something else.
Also, command-working-directory is pointed at where the container will see my repo folder (mounted inside the container).


My git-checkout-force.sh (placed at /path/to/config/run/git-checkout-force.sh)

#!/usr/bin/env sh

# variables
GIT_REF=${1}
GIT_DIR=${2}
PUID=${3}
PGID=${4}

# log date
date

# install git
if ! command -v git > /dev/null 2>&1; then
    apk add --no-cache \
        git
fi

# install openssh
if ! command -v ssh > /dev/null 2>&1; then
    apk add --no-cache \
        openssh
fi

# allow git with different ownership
git config --global --add safe.directory ${GIT_DIR}

# fetch from git
git fetch --all

# checkout git reference
git checkout --force ${GIT_REF}

# set ownership
chown -R ${PUID}:${PGID} ${GIT_DIR}

In the above:

  • GIT_REF is the first argument, passed in when webhook runs the script. It should be the commit id (see hooks.json above)
  • PUID and PGID are the second/third arguments, passed in, later used to chown. Why not hard code? So I can setup multiple hooks with the end result allowing different file ownership in each working directory.
  • Log the date, just for the sake of it
  • Check if the git command exists. If not, install it using apk (the alpine package manager included in the base OS of the image)
  • Check if the ssh command exists. If not, install it
  • git config --global --add safe.directory ${GIT_DIR} newer versions of git started caring about who owns the files in the repository, so tell git this directory is safe to run the rest of the commands (since root is the user running webhook in the container). This can result in root being the owner of newly added or changed files, which we will handle below
  • git fetch --all self explanatory
  • git checkout --force ${GIT_REF} force checkout the referenced commit (passed in argument)
  • chown -R ${PUID}:${PGID} ${GIT_DIR} set the ownership of all the files in the repo using the PUID and PGID that are passed to the script from hooks.json

The important takeaways:

  • webhook runs in the container as root
  • all commands webhook executes via your hooks.json execute as root
  • you (may) need to chown at the end of your script so that root is not the owner of your files
  • the image does not include much tooling (ex: git or ssh) but you can install the tools you need in your script
  • when installing tooling, check if it exists before installing, so that you're not reinstalling every time you receive a webhook request

The image itself does not need to change. It just requires some clever configuration. It may be reasonable to document some of this information in the README.

The image itself does not need to change. It just requires some clever configuration. It may be reasonable to document some of this information in the README.

@nemchik Thanks for the writeup! Feel free to submit a PR to update the README. 🙂