From a8a7e6665637605412920921be87d583c57007a3 Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Fri, 20 Oct 2023 11:19:30 +0200 Subject: [PATCH] Upgrade to Plotous v7. (#90) This PR upgrades the packaging workflow and Dockerfile to allow for cross-compilation. --- .github/workflows/pkg.yml | 13 +-- Cargo.toml | 2 +- Dockerfile | 168 +++++++++++++++++++++++---- pkg/debian/postrm | 17 +++ pkg/rpm/scriptlets.toml | 43 +++++-- pkg/rules/cross-targets.yml | 4 - pkg/rules/docker-images-to-build.yml | 16 +++ pkg/rules/packages-to-build.yml | 59 ++++++---- pkg/rules/packages-to-test.yml | 17 --- 9 files changed, 255 insertions(+), 84 deletions(-) create mode 100644 pkg/debian/postrm delete mode 100644 pkg/rules/cross-targets.yml delete mode 100644 pkg/rules/packages-to-test.yml diff --git a/.github/workflows/pkg.yml b/.github/workflows/pkg.yml index afb79b4..3844e02 100644 --- a/.github/workflows/pkg.yml +++ b/.github/workflows/pkg.yml @@ -1,6 +1,8 @@ name: Packaging on: + # Since this workflow is quite costly, we only run it automatically on + # release tags. Otherwise trigger it manually. push: tags: - v* @@ -10,20 +12,17 @@ on: jobs: package: - uses: NLnetLabs/ploutos/.github/workflows/pkg-rust.yml@v1 + uses: NLnetLabs/ploutos/.github/workflows/pkg-rust.yml@v7 secrets: DOCKER_HUB_ID: ${{ secrets.DOCKER_HUB_ID }} DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }} with: docker_org: nlnetlabs docker_repo: rtrtr + docker_build_rules: pkg/rules/docker-images-to-build.yml + docker_sanity_check_command: --version - cross_build_rules_path: pkg/rules/cross-targets.yml - docker_build_rules_path: pkg/rules/docker-images-to-build.yml - package_build_rules_path: pkg/rules/packages-to-build.yml - package_test_rules_path: pkg/rules/packages-to-test.yml + package_build_rules: pkg/rules/packages-to-build.yml package_test_scripts_path: pkg/test-scripts/test-.sh - deb_maintainer: The NLnet Labs RPKI Team - docker_sanity_check_command: --version rpm_scriptlets_path: pkg/rpm/scriptlets.toml diff --git a/Cargo.toml b/Cargo.toml index d970de8..acef6e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,7 +88,7 @@ license = "BSD" assets = [ { source = "target/release/rtrtr", dest = "/usr/bin/rtrtr", mode = "755" }, { source = "target/rpm/rtrtr.service", dest = "/lib/systemd/system/rtrtr.service", mode = "644" }, - { source = "README.md", dest = "/usr/share/doc/rtrtr/README.md", mode = "644" }, + { source = "README.md", dest = "/usr/share/doc/rtrtr/README.md", mode = "644", doc = true }, { source = "doc/rtrtr.1", dest = "/usr/share/man/man1/rtrtr.1", mode = "644", doc = true }, { source = "etc/rtrtr.conf.system-service", dest = "/etc/rtrtr.conf", mode = "644", config = true }, { source = "pkg/common/service.preset", dest = "/lib/systemd/system-preset/50-rtrtr.preset", mode = "644" }, diff --git a/Dockerfile b/Dockerfile index 099c5d0..76a2bab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,44 +1,166 @@ -# -- stage 1: build static rtrtr with musl libc for alpine -FROM alpine:3.15 as build +# This is a multi-stage Dockerfile, with a selectable first stage. With this +# approach we get: +# +# 1. Separation of dependencies needed to build our app in the 'build' stage +# and those needed to run our app in the 'final' stage, as we don't want +# the build-time dependencies to be included in the final Docker image. +# +# 2. Support for either building our app for the architecture of the base +# image using MODE=build (the default) or for externally built app +# binaries (e.g. cross-compiled) using MODE=copy. +# +# In total there are four stages consisting of: +# - Two possible first stages: 'build' or 'copy'. +# - A special 'source' stage which selects either 'build' or 'copy' as the +# source of binaries to be used by ... +# - The 'final' stage. -RUN apk add rust cargo -WORKDIR /tmp/rtrtr +### +### ARG DEFINITIONS ########################################################### +### + +# This section defines arguments that can be overriden on the command line +# when invoking `docker build` using the argument form: +# +# `--build-arg =`. + +# MODE +# ==== +# Supported values: build (default), copy +# +# By default this Dockerfile will build our app from sources. If the sources +# have already been (cross) compiled by some external process and you wish to +# use the resulting binaries from that process, then: +# +# 1. Create a directory on the host called 'dockerbin/$TARGETPLATFORM' +# containing the already compiled app binaries (where $TARGETPLATFORM +# is a special variable set by Docker BuiltKit). +# 2. Supply arguments `--build-arg MODE=copy` to `docker build`. +ARG MODE=build + + +# BASE_IMG +# ======== +# +# Only used when MODE=build. +ARG BASE_IMG=alpine:3.18 + + +# CARGO_ARGS +# ========== +# +# Only used when MODE=build. +# +# This ARG can be used to control the features enabled when compiling the app +# or other compilation settings as necessary. +ARG CARGO_ARGS + + +### +### BUILD STAGES ############################################################## +### + + +# ----------------------------------------------------------------------------- +# Docker stage: build +# ----------------------------------------------------------------------------- +# +# Builds our app binaries from sources. +FROM ${BASE_IMG} AS build +ARG CARGO_ARGS + +RUN apk add --no-cache rust cargo + +WORKDIR /tmp/build COPY . . -RUN cargo build \ - --target x86_64-alpine-linux-musl \ - --release \ - --locked +# `CARGO_HTTP_MULTIPLEXING` forces Cargo to use HTTP/1.1 without pipelining +# instead of HTTP/2 with multiplexing. This seems to help with various +# "spurious network error" warnings when Cargo attempts to fetch from crates.io +# when building this image on Docker Hub and GitHub Actions build machines. +# +# `cargo install` is used instead of `cargo build` because it places just the +# binaries we need into a predictable output directory. We can't control this +# with arguments to cargo build as `--out-dir` is unstable and contentious and +# `--target-dir` still requires us to know which profile and target the +# binaries were built for. By using `cargo install` we can also avoid needing +# to hard-code the set of binary names to copy so that if we add or remove +# built binaries in future this will "just work". Note that `--root /tmp/out` +# actually causes the binaries to be placed in `/tmp/out/bin/`. `cargo install` +# will create the output directory for us. +RUN CARGO_HTTP_MULTIPLEXING=false cargo install \ + --locked \ + --path . \ + --root /tmp/out/ \ + ${CARGO_ARGS} -# -- stage 2: create alpine-based container with the static rtrtr -# executable -FROM alpine:3.15 -COPY --from=build /tmp/rtrtr/target/x86_64-alpine-linux-musl/release/rtrtr /usr/local/bin/ + +# ----------------------------------------------------------------------------- +# Docker stage: copy +# ----------------------------------------------------------------------------- +# Only used when MODE=copy. +# +# Copy binaries from the host directory 'dockerbin/$TARGETPLATFORM' directory +# into this build stage to the same predictable location that binaries would be +# in if MODE were 'build'. +# +# Requires that `docker build` be invoked with variable `DOCKER_BUILDKIT=1` set +# in the environment. This is necessary so that Docker will skip the unused +# 'build' stage and so that the magic $TARGETPLATFORM ARG will be set for us. +FROM ${BASE_IMG} AS copy +ARG TARGETPLATFORM +ONBUILD COPY dockerbin/$TARGETPLATFORM /tmp/out/bin/ + + +# ----------------------------------------------------------------------------- +# Docker stage: source +# ----------------------------------------------------------------------------- +# This is a "magic" build stage that "labels" a chosen prior build stage as the +# one that the build stage after this one should copy application binaries +# from. It also causes the ONBUILD COPY command from the 'copy' stage to be run +# if needed. Finally, we ensure binaries have the executable flag set because +# when copied in from outside they may not have the flag set, especially if +# they were uploaded as a GH actions artifact then downloaded again which +# causes file permissions to be lost. +# See: https://github.com/actions/upload-artifact#permission-loss +FROM ${MODE} AS source +RUN chmod a+x /tmp/out/bin/* + + +# ----------------------------------------------------------------------------- +# Docker stage: final +# ----------------------------------------------------------------------------- +# Create an image containing just the binaries, configs & scripts needed to run +# our app, and not the things needed to build it. +# +# The previous build stage from which binaries are copied is controlled by the +# MODE ARG (see above). +FROM ${BASE_IMG} AS final + +# Copy binaries from the 'source' build stage into the image we are building +COPY --from=source /tmp/out/bin/* /usr/local/bin/ # Build variables for uid and guid of user to run container ARG RUN_USER=rtrtr ARG RUN_USER_UID=1012 ARG RUN_USER_GID=1012 -# Install rsync as rtrtr depends on it -RUN apk add --no-cache rsync libgcc - -# Use Tini to ensure that Routinator responds to CTRL-C when run in the -# foreground without the Docker argument "--init" (which is actually another -# way of activating Tini, but cannot be enabled from inside the Docker image). -RUN apk add --no-cache tini -# Tini is now available at /sbin/tini +# Install required runtime dependencies +RUN apk add --no-cache libgcc tini RUN addgroup -g ${RUN_USER_GID} ${RUN_USER} && \ adduser -D -u ${RUN_USER_UID} -G ${RUN_USER} ${RUN_USER} +# Switch to our applications user USER $RUN_USER_UID -# Expose the default metrics port +# Hint to operators the TCP port that the application in this image listens on +# (by default). EXPOSE 8080/tcp - -# Expose the default data serving port EXPOSE 9001/tcp +# Use Tini to ensure that our application responds to CTRL-C when run in the +# foreground without the Docker argument "--init" (which is actually another +# way of activating Tini, but cannot be enabled from inside the Docker image). ENTRYPOINT ["/sbin/tini", "--", "rtrtr"] diff --git a/pkg/debian/postrm b/pkg/debian/postrm new file mode 100644 index 0000000..a124ccc --- /dev/null +++ b/pkg/debian/postrm @@ -0,0 +1,17 @@ +#!/bin/sh -e + +RTRTR_ETC="/etc/rtrtr.conf" + +case "$1" in +purge) + # Per https://www.debian.org/doc/debian-policy/ch-files.html#behavior + # "configuration files must be preserved when the package is removed, and + # only deleted when the package is purged." + if [ -f ${RTRTR_ETC} ]; then + rm ${RTRTR_ETC} + fi + ;; +esac + +#DEBHELPER# + diff --git a/pkg/rpm/scriptlets.toml b/pkg/rpm/scriptlets.toml index 5609cd4..d6e0613 100644 --- a/pkg/rpm/scriptlets.toml +++ b/pkg/rpm/scriptlets.toml @@ -1,8 +1,6 @@ post_install_script = ''' #!/bin/bash -e -# Script based on the RPM %systemd_post scriptlet. See: -# - https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/#_systemd -# - https://cgit.freedesktop.org/systemd/systemd/tree/src/core/macros.systemd.in +#RPM_SYSTEMD_MACROS# if [ $EUID -ne 0 ]; then echo >&2 "ERROR: RTRTR postinst script must be run as root" @@ -16,10 +14,13 @@ if [ $1 -eq 1 ] ; then R_HOME_DIR=/var/lib/rtrtr R_HOME_DIR_PERMS=700 - # According to the CentOS 7 useradd man page: - # --user-group causes a group by the same name as the user to be created - # --create-home should force creation of a home dir even for a system account. - useradd --system --home-dir ${R_HOME_DIR} --system --create-home --user-group ${R_USER} + # https://github.com/NLnetLabs/routinator/issues/774 + if ! id ${R_USER} > /dev/null 2>&1; then + # According to the CentOS 7 useradd man page: + # --user-group causes a group by the same name as the user to be created + # --create-home should force creation of a home dir even for a system account. + useradd --system --home-dir ${R_HOME_DIR} --system --create-home --user-group ${R_USER} + fi # Ensure that the home directory has the correct ownership chown -R ${R_USER}:${R_GROUP} ${R_HOME_DIR} @@ -27,6 +28,32 @@ if [ $1 -eq 1 ] ; then # Ensure that the home directory has the correct permissions chmod ${R_HOME_DIR_PERMS} ${R_HOME_DIR} - systemctl preset rtrtr.service 2>&1 || : + # Run commands equivalent to what the RPM systemd macros would do + systemd_post rtrtr.service + systemd_triggers fi ''' + +pre_uninstall_script = ''' +#!/bin/bash -e +#RPM_SYSTEMD_MACROS# + +if [ $1 -eq 0 ] ; then + # Package removal, not upgrade + # Run commands equivalent to what the RPM systemd macros would do + systemd_preun rtrtr.service + systemd_triggers +fi +''' + +post_uninstall_script = ''' +#!/bin/bash -e +#RPM_SYSTEMD_MACROS# + +if [ $1 -ge 1 ] ; then + # Run commands equivalent to what the RPM systemd macros would do + systemd_postun_with_restart rtrtr.service + systemd_triggers +fi +''' + diff --git a/pkg/rules/cross-targets.yml b/pkg/rules/cross-targets.yml deleted file mode 100644 index db090ce..0000000 --- a/pkg/rules/cross-targets.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -# must be one of: https://github.com/cross-rs/cross#supported-targets -- 'armv7-unknown-linux-musleabihf' -- 'aarch64-unknown-linux-musl' \ No newline at end of file diff --git a/pkg/rules/docker-images-to-build.yml b/pkg/rules/docker-images-to-build.yml index 5be4960..5432b9a 100644 --- a/pkg/rules/docker-images-to-build.yml +++ b/pkg/rules/docker-images-to-build.yml @@ -19,3 +19,19 @@ include: - platform: 'linux/amd64' shortname: 'amd64' mode: 'build' + + - platform: 'linux/arm/v6' + shortname: 'armv6' + crosstarget: 'arm-unknown-linux-musleabihf' + mode: 'copy' + + - platform: 'linux/arm/v7' + shortname: 'armv7' + crosstarget: 'armv7-unknown-linux-musleabihf' + mode: 'copy' + + - platform: 'linux/arm64' + shortname: 'arm64' + crosstarget: 'aarch64-unknown-linux-musl' + mode: 'copy' + diff --git a/pkg/rules/packages-to-build.yml b/pkg/rules/packages-to-build.yml index 4be36fa..9528d4e 100644 --- a/pkg/rules/packages-to-build.yml +++ b/pkg/rules/packages-to-build.yml @@ -1,23 +1,9 @@ +# This matrix definition is used as both the package_build_rules and the +# package_test_rules Ploutos packaging workflow inputs. --- -# matrix field notes: -# platform: used by Docker to use the right architecture base image. -# the set of supported values can be seen at: -# https://go.dev/doc/install/source#environment -# from: https://github.com/docker-library/official-images#architectures-other-than-amd64 -# from: https://docs.docker.com/desktop/multi-arch/ -# one must also take any "normalization" into account, e.g. arm64v8 -> arm64, see: -# https://github.com/containerd/containerd/blob/v1.4.3/platforms/database.go#L83 -# see also: -# https://stackoverflow.com/a/70889505 -# shortname: used by us to tag the architecture specific "manifest" image. -# crosstarget: (optional) used to download the correct cross-compiled binary that was produced earlier by the -# 'cross' job above. -# mode: (optional) set to 'copy' for cross-compiled targets. -# cargo_args: (optional) can be used when testing, e.g. set to '--no-default-features' to speed up the Krill -# build. pkg: - "rtrtr" -image: # can't use complex values here, only primitive values are allowed +image: - "ubuntu:xenial" # ubuntu/16.04 - "ubuntu:bionic" # ubuntu/18.04 - "ubuntu:focal" # ubuntu/20.04 @@ -25,26 +11,51 @@ image: # can't use complex values here, only primitive values are allowed - "debian:stretch" # debian/9 - "debian:buster" # debian/10 - "debian:bullseye" # debian/11 - - "centos:7" + - "debian:bookworm" # debian/12 + - 'centos:7' - 'rockylinux:8' # compatible with EOL centos:8 + - 'rockylinux:9' target: - 'x86_64' +test-image: + - "" include: - image: "centos:7" systemd_service_unit_file: pkg/common/rtrtr-minimal.rtrtr.service - image: 'rockylinux:8' systemd_service_unit_file: pkg/common/rtrtr.rtrtr.service - os: 'centos:8' - # package for the Raspberry Pi 4b as an ARMv7 cross compiled variant of the Debian Bullseye upon which - # Raspbian 11 is based. + - image: 'rockylinux:9' + systemd_service_unit_file: pkg/common/rtrtr.rtrtr.service + - pkg: "rtrtr" image: 'debian:bullseye' target: 'armv7-unknown-linux-musleabihf' - # package for the ROCK64 as an AARCH64 cross compiled variant of Debian Buster upon which Armbian 21 is based. - - pkg: "rtrtr" + - pkg: 'rtrtr' image: 'debian:buster' - target: 'aarch64-unknown-linux-musl' + target: 'arm-unknown-linux-musleabihf' + + - pkg: 'rtrtr' + image: 'rockylinux:9' + target: 'x86_64' + test-image: 'almalinux:9' + + - pkg: 'rtrtr' + image: 'rockylinux:9' + target: 'x86_64' + test-image: 'centos:9-Stream' + +test-mode: + - 'fresh-install' + - 'upgrade-from-published' + +test-exclude: + - pkg: 'rtrtr' + image: 'rockylinux:9' + mode: 'upgrade-from-published' + - pkg: 'rtrtr' + image: 'debian:bookworm' + mode: 'upgrade-from-published' diff --git a/pkg/rules/packages-to-test.yml b/pkg/rules/packages-to-test.yml deleted file mode 100644 index 2c6677f..0000000 --- a/pkg/rules/packages-to-test.yml +++ /dev/null @@ -1,17 +0,0 @@ ---- -pkg: - - 'rtrtr' -image: # can't use complex values here, only primitive values are allowed - - "ubuntu:xenial" # ubuntu/16.04 - - "ubuntu:bionic" # ubuntu/18.04 - - "ubuntu:focal" # ubuntu/20.04 - - "ubuntu:jammy" # ubuntu/22.04 - #- "debian:stretch" # debian/9 - - "debian:buster" # debian/10 (disabled, see issue #26) - - "debian:bullseye" # debian/11 - - "centos:7" - - "centos:8" -mode: - - 'fresh-install' -target: - - 'x86_64'