diff options
Diffstat (limited to 'tools/nm-in-container')
| -rwxr-xr-x | tools/nm-in-container | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/tools/nm-in-container b/tools/nm-in-container new file mode 100755 index 00000000..7ff073d0 --- /dev/null +++ b/tools/nm-in-container @@ -0,0 +1,520 @@ +#!/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 <<EOF +$0: build|run|exec|stop|reset|reexec|clean|journal [--no-cleanup] [--stop] [-- EXTRA_ARGS] +EOF + echo + awk '/^####*$/{ if(on) exit; on=1} { if (on) { if (on==2) print(substr($0,3)); on=2; } }' "$BASH_SOURCE" + echo +} + +############################################################################### + +die() { + ( + echo -n -e "\033[31m" + printf "%s" "$*" + echo -e "\033[0m" + ) >&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 <<EOF | tmp_file "$CONTAINERFILE" +FROM $BASE_IMAGE + +ENTRYPOINT ["/sbin/init"] + +RUN sed -i 's/^tsflags=.*/tsflags=/' /etc/dnf/dnf.conf + +RUN dnf install -y \\ + --skip-broken \\ + \\ + /usr/bin/python \\ + /usr/sbin/ausearch \\ + ModemManager-devel \\ + ModemManager-glib-devel \\ + NetworkManager \\ + audit-libs-devel \\ + bash-completion \\ + bind-utils \\ + black \\ + bluez-libs-devel \\ + ccache \\ + clang \\ + clang-tools-extra \\ + cscope \\ + dbus-devel \\ + dbus-x11 \\ + dhclient \\ + dnsmasq \\ + firewalld-filesystem \\ + gcc-c++ \\ + gdb \\ + gettext-devel \\ + git \\ + glib2-doc \\ + glibc-langpack-pl \\ + gnutls-devel \\ + gobject-introspection-devel \\ + gtk-doc \\ + intltool \\ + iproute \\ + iproute-tc \\ + iptables \\ + jansson-devel \\ + libasan \\ + libcurl-devel \\ + libndp-devel \\ + libpsl-devel \\ + libselinux-devel \\ + libtool \\ + libubsan \\ + libuuid-devel \\ + make \\ + meson \\ + mlocate \\ + mobile-broadband-provider-info-devel \\ + newt-devel \\ + nispor \\ + nmstate \\ + nss-devel \\ + polkit-devel \\ + ppp \\ + ppp-devel \\ + procps \\ + python3-behave \\ + python3-black \\ + python3-dbus \\ + python3-devel \\ + python3-gobject \\ + python3-pexpect \\ + python3-pip \\ + python3-pyte \\ + python3-pyyaml \\ + qt-devel \\ + radvd \\ + readline-devel \\ + rp-pppoe \\ + rpm-build \\ + strace \\ + systemd \\ + systemd-devel \\ + tcpdump \\ + teamd-devel \\ + vala \\ + vala-devel \\ + valgrind \\ + vim \\ + which \\ + \\ + 'dbus*' \\ + 'openvswitch2*' \\ + /usr/bin/pytest \\ + /usr/bin/debuginfo-install \\ + NetworkManager-openvpn \\ + NetworkManager-pptp \\ + NetworkManager-strongswan \\ + NetworkManager-vpnc \\ + NetworkManager-ovs \\ + NetworkManager-wifi \\ + NetworkManager-team \\ + NetworkManager-ppp \\ + cryptsetup \\ + dhcp-client \\ + dhcp-relay \\ + dhcp-server \\ + dnsmasq \\ + dracut-network \\ + ethtool \\ + firewalld \\ + gcc \\ + git \\ + hostapd \\ + iproute-tc \\ + ipsec-tools \\ + iputils \\ + iscsi-initiator-utils \\ + iw \\ + ldns \\ + libreswan \\ + libselinux-utils \\ + libyaml-devel \\ + logrotate \\ + lvm2 \\ + mdadm \\ + net-tools \\ + nfs-utils \\ + nmap-ncat \\ + nss-tools \\ + openvpn \\ + perl-IO-Pty-Easy \\ + perl-IO-Tty \\ + psmisc \\ + python3-dbus \\ + python3-gobject \\ + python3-netaddr \\ + qemu-kvm \\ + radvd \\ + rp-pppoe \\ + scsi-target-utils \\ + tcpdump \\ + tcpreplay \\ + tuned \\ + wireguard-tools \\ + wireshark-cli + +RUN dnf debuginfo-install --skip-broken NetworkManager \$(ldd /usr/sbin/NetworkManager | sed -n 's/.* => \\(.*\\) (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 sed 's/.*RateLimitBurst=.*/RateLimitBurst=0/' /etc/systemd/journald.conf -i + +$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[@]}" |