modules.gitlab-runner: accept space in names

when you register a runner with spaces in its name (possible if you use 'description' option) then the runners never get unregistered because our bash scripts assume no space in names.

This solves the issue

Retreiving the fullname of the runner via `gitlab-runner list` got surprisingly hard between lazy-capture issues and `gitlab-runner list` displaying invisible (CSI) characters that break the regex etc.
Which is why I fell back on the pseudo-json format.

This PR adds the hash in the name, which allows to keep both the
stateless aspect of the module while allowing for a freeform name.

I found using bash associative arrays easier to use/debug than the current
approach.
This commit is contained in:
Matthieu Coudron 2022-10-21 15:18:10 +02:00 committed by Matthieu Coudron
parent da98602aa9
commit c61f554c1a

View file

@ -4,24 +4,41 @@ with lib;
let
cfg = config.services.gitlab-runner;
hasDocker = config.virtualisation.docker.enable;
/* The whole logic of this module is to diff the hashes of the desired vs existing runners
The hash is recorded in the runner's name because we can't do better yet
See https://gitlab.com/gitlab-org/gitlab-runner/-/issues/29350 for more details
*/
genRunnerName = service: let
hash = substring 0 12 (hashString "md5" (unsafeDiscardStringContext (toJSON service)));
in if service ? description
then "${hash} ${service.description}"
else "${name}_${config.networking.hostName}_${hash}";
hashedServices = mapAttrs'
(name: service: nameValuePair
"${name}_${config.networking.hostName}_${
substring 0 12
(hashString "md5" (unsafeDiscardStringContext (toJSON service)))}"
service)
cfg.services;
configPath = "$HOME/.gitlab-runner/config.toml";
configureScript = pkgs.writeShellScriptBin "gitlab-runner-configure" (
if (cfg.configFile != null) then ''
mkdir -p $(dirname ${configPath})
(name: service: nameValuePair (genRunnerName service) service) cfg.services;
configPath = ''"$HOME"/.gitlab-runner/config.toml'';
configureScript = pkgs.writeShellApplication {
name = "gitlab-runner-configure";
runtimeInputs = with pkgs; [
bash
gawk
jq
moreutils
remarshal
util-linux
cfg.package
perl
python3
];
text = if (cfg.configFile != null) then ''
cp ${cfg.configFile} ${configPath}
# make config file readable by service
chown -R --reference=$HOME $(dirname ${configPath})
'' else ''
export CONFIG_FILE=${configPath}
mkdir -p $(dirname ${configPath})
mkdir -p "$(dirname "${configPath}")"
touch ${configPath}
# update global options
@ -34,22 +51,43 @@ let
# remove no longer existing services
gitlab-runner verify --delete
# current and desired state
NEEDED_SERVICES=$(echo ${concatStringsSep " " (attrNames hashedServices)} | tr " " "\n")
REGISTERED_SERVICES=$(gitlab-runner list 2>&1 | grep 'Executor' | awk '{ print $1 }')
${toShellVar "NEEDED_SERVICES" (lib.mapAttrs (name: value: 1) hashedServices)}
declare -A REGISTERED_SERVICES
while IFS="," read -r name token;
do
REGISTERED_SERVICES["$name"]="$token"
done < <(gitlab-runner --log-format json list 2>&1 | grep Token | jq -r '.msg +"," + .Token')
echo "NEEDED_SERVICES: " "''${!NEEDED_SERVICES[@]}"
echo "REGISTERED_SERVICES:" "''${!REGISTERED_SERVICES[@]}"
# difference between current and desired state
NEW_SERVICES=$(grep -vxF -f <(echo "$REGISTERED_SERVICES") <(echo "$NEEDED_SERVICES") || true)
OLD_SERVICES=$(grep -vxF -f <(echo "$NEEDED_SERVICES") <(echo "$REGISTERED_SERVICES") || true)
declare -A NEW_SERVICES
for name in "''${!NEEDED_SERVICES[@]}"; do
if [ ! -v 'REGISTERED_SERVICES[$name]' ]; then
NEW_SERVICES[$name]=1
fi
done
declare -A OLD_SERVICES
# shellcheck disable=SC2034
for name in "''${!REGISTERED_SERVICES[@]}"; do
if [ ! -v 'NEEDED_SERVICES[$name]' ]; then
OLD_SERVICES[$name]=1
fi
done
# register new services
${concatStringsSep "\n" (mapAttrsToList (name: service: ''
if echo "$NEW_SERVICES" | grep -xq "${name}"; then
# TODO so here we should mention NEW_SERVICES
if [ -v 'NEW_SERVICES["${name}"]' ] ; then
bash -c ${escapeShellArg (concatStringsSep " \\\n " ([
"set -a && source ${service.registrationConfigFile} &&"
"gitlab-runner register"
"--non-interactive"
(if service.description != null then "--description \"${service.description}\"" else "--name '${name}'")
"--name '${name}'"
"--executor ${service.executor}"
"--limit ${toString service.limit}"
"--request-concurrency ${toString service.requestConcurrency}"
@ -92,22 +130,26 @@ let
fi
'') hashedServices)}
# check key is in array https://stackoverflow.com/questions/30353951/how-to-check-if-dictionary-contains-a-key-in-bash
echo "NEW_SERVICES: ''${NEW_SERVICES[*]}"
echo "OLD_SERVICES: ''${OLD_SERVICES[*]}"
# unregister old services
for NAME in $(echo "$OLD_SERVICES")
for NAME in "''${!OLD_SERVICES[@]}"
do
[ ! -z "$NAME" ] && gitlab-runner unregister \
[ -n "$NAME" ] && gitlab-runner unregister \
--name "$NAME" && sleep 1
done
# make config file readable by service
chown -R --reference=$HOME $(dirname ${configPath})
'');
chown -R --reference="$HOME" "$(dirname ${configPath})"
'';
};
startScript = pkgs.writeShellScriptBin "gitlab-runner-start" ''
export CONFIG_FILE=${configPath}
exec gitlab-runner run --working-directory $HOME
'';
in
{
in {
options.services.gitlab-runner = {
enable = mkEnableOption (lib.mdDoc "Gitlab Runner");
configFile = mkOption {