#!/bin/bash set -e ############################################################################### # Script to create a podman container for testing NetworkManager. # # Commands: # - build: build a new image, named "$CONTAINER_NAME_REPOSITORY:$CONTAINER_NAME_TAG" ("nm:nm"). # - run: start the container and tag it "$CONTAINER_NAME_NAME" ("nm"). # - exec: run bash inside the container (this is the default). # - journal|j: print the journal from inside the container. # - stop: stop the container. # - reset: stop and delete the container. # - clean: stop and delete the container and the image. # # Options: # --no-cleanup: don't delete the CONTAINERFILE and other artifacts # --stop: only has effect with "run". It will stop the container afterwards. # -- [EXTRA_ARGS]: # - with command "exec", provide a command and arguments to run in the container. # Defaults to "bash". # - with command "journal", additional arguments that are passed to journalctl. # # It bind mounts the current working directory inside the container. # You can run `make install` and run tests. # There is a script nm-env-prepare.sh to generate a net1 interface for testing. # # This will bind-mount the NetworkManager working tree inside the container (and symlink # from /NetworkManager). Create a file ".git/nm-in-container-host" to bind mount the host's # "/" to "/Host". # # Create a symlink ./.git/NetworkManager-ci, to also bind-mount the CI directory. # Create additional symlinks ./.git/nm-guest-link-*, to bind mount additional # directories. # # Currently NM-ci requires a working eth1. # Hence call `nm-env-prepare.sh --prefix eth -i 1 && sleep 1 && nmcli device connect eth1` before # running a CI test. ############################################################################### if [ -z "$BASE_IMAGE" ]; then if grep -q "^ID=fedora$" /etc/os-release 2>/dev/null ; then BASE_IMAGE="$(sed -n 's/^VERSION_ID=\([0-9]\+\)$/fedora:\1/p' /etc/os-release)" fi fi if [ -z "$BASE_IMAGE" ]; then BASE_IMAGE=fedora:latest fi BASEDIR_NM="$(readlink -f "$(dirname "$(readlink -f "$0")")/..")" BASEDIR_DATA="$BASEDIR_NM/tools/nm-guest-data" SYMLINK_NAME=() SYMLINK_TARGET=() for d in $(ls -1d "$BASEDIR_NM/.git/NetworkManager-ci" "$BASEDIR_NM/.git/nm-guest-link-"* 2>/dev/null) ; do NAME="${d##*/}" NAME="${NAME##nm-guest-link-}" TARGET="$(readlink -f "$d")" test -e "$TARGET" SYMLINK_NAME+=("$NAME") SYMLINK_TARGET+=("$TARGET") done CONTAINER_NAME_REPOSITORY=${CONTAINER_NAME_REPOSITORY:-nm} CONTAINER_NAME_TAG=${CONTAINER_NAME_TAG:-nm} CONTAINER_NAME_NAME=${CONTAINER_NAME_NAME:-nm} EXEC_ENV=() ############################################################################### usage() { cat <&2 exit 1 } ############################################################################### CLEANUP_FILES=() DO_CLEANUP=1 cleanup() { test "$DO_CLEANUP" = 1 || return 0 for f in "${CLEANUP_FILES[@]}" ; do rm -rf "$f" done } trap cleanup EXIT ############################################################################### tmp_file() { cat > "$1" CLEANUP_FILES+=( "$1" ) test -z "$2" || chmod "$2" "$1" } gen_file() { local PERMS [[ $1 =~ bin-* ]] && PERMS=755 || PERMS=644 sed "s|{{BASEDIR_NM}}|$BASEDIR_NM|g" "$BASEDIR_DATA/$1.in" \ | tmp_file "$BASEDIR_DATA/data-$1" $PERMS } bind_files() { VARIABLE_NAME="$1" ARR=() H=~ ARR+=( -v "$BASEDIR_NM:$BASEDIR_NM" ) if [ -e "$BASEDIR_NM/.git/nm-in-container-host" ] ; then ARR+=( -v /:/Host ) fi for i in $(seq 1 ${#SYMLINK_TARGET[@]}) ; do j=$((i - 1)) ARR+=( -v "${SYMLINK_TARGET[$j]}:${SYMLINK_TARGET[$j]}" ) done for f in ~/.gitconfig* ~/.vim* ; do test -e "$f" || continue f2="${f#$H/}" [[ "$f2" = .viminf* ]] && continue [[ "$f2" = *.tmp ]] && continue [[ "$f2" = *~ ]] && continue f2="/root/$f2" ARR+=( -v "$f:$f2" ) done eval "$VARIABLE_NAME=( \"\${ARR[@]}\" )" } create_dockerfile() { local CONTAINERFILE="$1" local BASE_IMAGE="$2" local GEN_FILES="bin-nm-env-prepare.sh bin-nm-deploy.sh bin-_nm-in-container-setup.sh etc-rc.local etc-motd-container etc-bashrc.my etc-bashrc-motd.my nm-90-my.conf nm-95-user.conf home-bash_history home-gdbinit home-gdb_history home-behaverc systemd-20-nm.override" cp "$BASEDIR_NM/contrib/scripts/NM-log" "$BASEDIR_DATA/data-bin-NM-log" CLEANUP_FILES+=( "$BASEDIR_DATA/data-NM-log" ) for f in $GEN_FILES; do gen_file "$f" done chmod 755 "$BASEDIR_DATA/data-etc-rc.local" RUN_LN_BASEDIR_NM= if [ -n "$BASEDIR_NM" -a "$BASEDIR_NM" != "/NetworkManager" ] ; then RUN_LN_BASEDIR_NM="RUN ln -snf \"$BASEDIR_NM\" /NetworkManager" fi RUN_LN_SYMLINK_CMDS="" for i in $(seq 1 ${#SYMLINK_NAME[@]}) ; do j=$((i - 1)) if [ -d "${SYMLINK_TARGET[$j]}" ] ; then RUN_LN_SYMLINK_CMDS="$RUN_LN_SYMLINK_CMDS"$'\n'"RUN ln -snf \"${SYMLINK_TARGET[$j]}\" \"/${SYMLINK_NAME[$j]}\"" fi done cat < \\(.*\\) (0x[0-9A-Fa-f]*)$/\1/p' | xargs -n1 readlink -f) -y RUN dnf clean all RUN pip3 install --user behave_html_formatter || true RUN mkdir -p /etc/systemd/system/NetworkManager.service.d COPY data-bin-NM-log "/usr/bin/NM-log" COPY data-bin-nm-env-prepare.sh "/usr/bin/nm-env-prepare.sh" COPY data-bin-nm-deploy.sh "/usr/bin/nm-deploy.sh" COPY data-bin-_nm-in-container-setup.sh "/usr/bin/_nm-in-container-setup.sh" COPY data-etc-rc.local "/etc/rc.d/rc.local" COPY data-etc-motd-container /etc/motd COPY data-etc-bashrc.my /etc/bashrc.my COPY data-etc-bashrc-motd.my /etc/bashrc-motd.my COPY data-nm-90-my.conf /etc/NetworkManager/conf.d/90-my.conf COPY data-nm-95-user.conf /etc/NetworkManager/conf.d/95-user.conf COPY data-home-bash_history /root/.bash_history COPY data-home-gdbinit /root/.gdbinit COPY data-home-gdb_history /root/.gdb_history COPY data-home-behaverc /root/.behaverc COPY data-systemd-20-nm.override /etc/systemd/system/NetworkManager.service.d/20-nm.override RUN systemctl enable NetworkManager # Generate a stable machine id. RUN echo "10001000100010001000100010001000" > /etc/machine-id RUN echo -e "# Default from the container image\nnameserver 8.8.8.8" > /etc/resolv.conf # Generate a fixed (version 1) secret key. RUN mkdir -p /var/lib/NetworkManager RUN chmod 700 /var/lib/NetworkManager RUN echo -n "nm-in-container-secret-key" > /var/lib/NetworkManager/secret_key RUN chmod 600 /var/lib/NetworkManager/secret_key RUN mkdir -p /etc/systemd/journald.conf.d/ && \ echo "RateLimitBurst=0" > /etc/systemd/journald.conf.d/no-rate-limit.conf $RUN_LN_BASEDIR_NM $RUN_LN_SYMLINK_CMDS RUN rm -rf /etc/NetworkManager/system-connections/* RUN echo -e '\n. /etc/bashrc.my\n' >> /etc/bashrc RUN echo -e '\n. /etc/bashrc-motd.my\n' >> /etc/bashrc RUN updatedb EOF } ############################################################################### container_image_exists() { podman image exists "$1" || return 1 } container_exists() { podman container exists "$1" || return 1 } container_is_running() { test "$(podman ps --format "{{.ID}} {{.Names}}" | sed -n "s/ $1\$/\0/p")" != "" || return 1 } ############################################################################### do_reset() { podman stop "$CONTAINER_NAME_NAME" || : podman rm "$CONTAINER_NAME_NAME" || : } do_clean() { do_reset podman rmi "$CONTAINER_NAME_REPOSITORY:$CONTAINER_NAME_TAG" || : } do_build() { container_image_exists "$CONTAINER_NAME_REPOSITORY:$CONTAINER_NAME_TAG" && return 0 CONTAINERFILE="$BASEDIR_DATA/containerfile" create_dockerfile "$CONTAINERFILE" "$BASE_IMAGE" podman build --squash-all --tag "$CONTAINER_NAME_REPOSITORY:$CONTAINER_NAME_TAG" -f "$CONTAINERFILE" } do_run() { do_build if container_is_running "$CONTAINER_NAME_NAME" ; then return 0 fi if container_exists "$CONTAINER_NAME_NAME" ; then podman start "$CONTAINER_NAME_NAME" return 0 fi bind_files BIND_FILES podman run --privileged \ --name "$CONTAINER_NAME_NAME" \ --dns=none \ --no-hosts \ -d \ "${BIND_FILES[@]}" \ "$CONTAINER_NAME_REPOSITORY:$CONTAINER_NAME_TAG" } do_exec() { do_run local e local EXTRA_ARGS=("$@") if [ "${#EXTRA_ARGS[@]}" = 0 ]; then EXTRA_ARGS=('bash') fi local ENV=() for e in "${EXEC_ENV[@]}" ; do ENV+=(-e "$e") done podman exec "${ENV[@]}" --workdir "$BASEDIR_NM" -it "$CONTAINER_NAME_NAME" "${EXTRA_ARGS[@]}" if [ "$DO_STOP" = 1 ]; then do_stop fi } do_reexec() { do_reset do_exec "$@" } do_journal() { EXEC_ENV+=( "SYSTEMD_COLORS=0" ) do_exec "journalctl" --no-pager "$@" } do_stop() { container_is_running "$CONTAINER_NAME_NAME" || return 0 podman stop "$CONTAINER_NAME_NAME" } ############################################################################### DO_STOP=0 CMD=exec EXTRA_ARGS=() for (( i=1 ; i<="$#" ; )) ; do c="${@:$i:1}" i=$((i+1)) case "$c" in --no-cleanup) DO_CLEANUP=0 ;; --stop) DO_STOP=1 ;; j) CMD=journal ;; build|run|exec|stop|reset|reexec|clean|journal) CMD=$c ;; --) EXTRA_ARGS=( "${@:$i}" ) break ;; -h|--help) usage exit 0 ;; *) if [ "$CMD" = "journal" ]; then EXTRA_ARGS=( "${@:$((i-1))}" ) break; else usage die "invalid argument: $c" fi ;; esac done ############################################################################### test "$UID" != 0 || die "cannot run as root" if test "$CMD" != exec -a "$CMD" != journal -a "$CMD" != reexec -a "${#EXTRA_ARGS[@]}" != 0 ; then die "Extra arguments are only allowed with exec|journal|reexec command" fi ############################################################################### do_$CMD "${EXTRA_ARGS[@]}"