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
ifgithooks.buildImagesOnSharedUpdate = true
(unset istrue
)
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.