gabyx / Githooks

🦎 Githooks: per-repo and shared Git hooks with version control and auto update. [✩Star] if you're using it!

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

feat: Run hooks over container images

gabyx opened this issue · comments

Running Hooks over Images

Benefits: No dependencies

Add .images.yaml to .githooks folders with

version: 1.0.0
images:

  - "myhooks-pre-commit-format:1.2.5":   # Image name: Can be local or remote...
  
     pull: # optional (otherwise the key is used)
        name: bla/bla/bla 
        tag: 1.2.0 # optional, otherwise tag `1.2.5` will get automatically used to pull this image.
        digest: sha@256:1231321 # optional, only for security
     
     build: # optional (otherwise the `pull` or the key is used)
        file: ".githooks/.images/pre-commit" # dockerfile
        target: "stage-1" # directly used as `--target`
        # Version `1.2.5` is passed as build arg to docker build, 
        # leeds to caching, if everything stays the same.

 - "..."

which will build/pull docker images if not existing on

  • git hooks shared update if githooks.buildImagesOnSharedUpdate = true (unset is true)

which then can be used to dispatch to run the whole hook inside an image.

Run Configuration

On a hook run, Githooks can check if it needs to build the image
if the hook is specified over e.g. .githooks/pre-commit/format.yaml

cmd: "./pre-commit/format.sh"
args:
    - "--do-it"
container: 
  image: "myhooks-pre-commit-format:1.2.5"
  interactive: false # -i flags in docker run
  

version: 1

If githooks.useImages is true, all hooks will run over specified images in the run configuration (if specified).
If the image myhooks-pre-commit-format:1.2.5 is not existing, Githooks will look into .images.yaml and try to build/pull the image with the matching myhooks-pre-commit-format:1.2.5. First good solution prints error and tells to git hooks shared update which will build/pull the images if not there.

Execute over Image

The repo is mounted under /home/githooks/workspace and the hook repository (shared or the same as the repo)
is mounted in /home/githooks/hooks. Environment variables are forwarded, for the ones which make sense.

The following in Go, but here in bash:

function handleExitDocker() {
    if [ "$1" -eq "125" ]; then
        printError "The docker daemon reported an error."
    elif [ "$1" -eq "126" ]; then
        printError "Could docker command could not be invoked."
    elif [ "$1" -eq "127" ]; then
        printError "The docker command could not be found."
    fi

    return 0
}

function runWithDocker() {

    local repo="$1"
    local githooksRoot
    githooksRoot="$(cd "$2" && pwd)"
    local interactive="$3"
    local script="${4##*githooks/}"
    shift 4

    local args=("$@")

    local dockerFile="$githooksRoot/docker/Dockerfile"

    local version
    version=$(grep -E "LABEL.*version=" "$dockerFile" | sed -E "s@.*version=(.*)@\1@") ||
        die "Could not extract docker file version in '$dockerFile'."
    local name
    name=$(grep -E "LABEL.*name=" "$dockerFile" | sed -E "s@.*name=(.*)@\1@") ||
        die "Could not extract docker image name in '$dockerFile'."

    imageName="$name:$version"

    if ! imageExists "$imageName"; then
        printInfo "Building docker file '$dockerFile'"
        out=$(
            DOCKER_BUILDKIT=0 \
                docker build \
                -t "$imageName" \
                -f "$dockerFile" \
                "$githooksRoot/docker" 2>&1
        ) || {
            printError "Could not build docker file:\n" \
                "Output:\n$out"
            return 1
        }

        printInfo "Build image '$imageName' successfully."
    fi

    dockerArgs=()
    # If we have a terminal attached, also attach one in docker.
    if [ -t 1 ]; then
        dockerArgs+=("-t")
    fi

    if [ "$interactive" = "true" ]; then
        dockerArgs+=("-i")
    fi

    script="/mnt/shared/$script"

    # --user "$(id -u):$(id -g)" \
    MSYS2_NO_PATHCONV=1 \
        docker run --rm \
        --user "$(id -u):$(id -g)" \
        -v "$repo:/mnt/workspace" \
        -v "$githooksRoot:/mnt/shared:ro" \
        -w "/mnt/workspace" \
        -e "STAGED_FILES=${STAGED_FILES:-}" \
        "${dockerArgs[@]}" \
        "$imageName" \
        "$script" "${args[@]}" || {

        local exitCode="$?"
        handleExitDocker "$exitCode"
        printError "Executing hook in docker failed."

        return 1
    }

    return 0
}

with

# syntax = docker/dockerfile:1.2

FROM alpine:3.17
LABEL version=1.0.16
LABEL name=githooks-general-shell

ARG SHELLCHECK_VERSION=0.9.0
ARG SHFMT_VERSION=3.6.0

ARG USER_UID=1001
ARG USER_GID=1001
ARG USER_NAME=githooks

ENV GITHOOKS_IN_DOCKER=true

RUN apk add git tar curl xz bash coreutils findutils grep sed parallel && \
    curl -fsSL https://github.com/koalaman/shellcheck/releases/download/v$SHELLCHECK_VERSION/shellcheck-v$SHELLCHECK_VERSION.linux.x86_64.tar.xz | tar -xJf - && \
    cp shellcheck-v$SHELLCHECK_VERSION/shellcheck /usr/local/bin && \
    rm -rf shellcheck-v$SHELLCHECK_VERSION && \
    curl -fsSL "https://github.com/mvdan/sh/releases/download/v$SHFMT_VERSION/shfmt_v${SHFMT_VERSION}_linux_amd64" -o /usr/local/bin/shfmt && \
    chmod +x /usr/local/bin/shfmt

# Install MatchHostFsOwner.
# See https://github.com/FooBarWidget/matchhostfsowner/releases
ADD https://github.com/FooBarWidget/matchhostfsowner/releases/download/v1.0.0/matchhostfsowner-1.0.0-x86_64-linux.gz /sbin/matchhostfsowner.gz
RUN gunzip /sbin/matchhostfsowner.gz && \
  chown root: /sbin/matchhostfsowner && \
  chmod +x,+s /sbin/matchhostfsowner

# Use 'githooks' for MatchHostFsOwner.
RUN mkdir -p /etc/matchhostfsowner && \
    echo -e "app_account: githooks\napp_group: githooks" > /etc/matchhostfsowner/config.yml && \
    cat /etc/matchhostfsowner/config.yml && \
    chown -R root: /etc/matchhostfsowner && \
    chmod 700 /etc/matchhostfsowner && \
    chmod 600 /etc/matchhostfsowner/*

RUN adduser "$USER_NAME" -s /bin/zsh \
    -D \
    -u "$USER_UID" -g "$USER_GID" \
    -h "/home/$USER_NAME"

USER "$USER_NAME"

RUN mkdir -p ~/.parallel && touch ~/.parallel/will-cite && \
    git config --global --add safe.directory /mnt/workspace
    
VOLUME [ "/mnt/workspace" ]
VOLUME [ "/mnt/shared" ]

ENTRYPOINT [ "/sbin/matchhostfsowner" ]
CMD [ "bash" ]

where ${cmd} is the command refering to the executable inside the image.
In the example: /home/githooks/hooks/.githooks/pre-commit/format.sh

Steps:

  • Implement yaml and run docker/podman build on pull.
  • Implement docker dispatch.

Solved in #111 , #113.