krill 0.9.0

Resource Public Key Infrastructure (RPKI) daemon
Documentation
# GitHub Actions workflow for building and testing Krill O/S packages.
# Uses GitHub Actions caching to avoid rebuilding Rust cargo-deb and
# Krill dependencies on every run.
#
# Note: at the time of writing the GH cache contents expire after a
# week if not used so the next build may be much slower as it will
# have to re-download/build/install lots of Rust crates.
#
# Packages are built inside Docker containers as GH Runners have extra libraries
# and packages installed which can cause package building to succeed but package
# installation on a real target O/S to fail, due to being built against too
# recent version of a package such as libssl or glibc.
#
# Packages are tested inside LXC/LXD containers because Docker containers don't
# by default support init managers such as systemd but we want to test systemd
# service unit installation and activation.

name: Packaging
on:
  push:
    branches:
    - main
    paths-ignore:
      - '.dockerignore'
      - '.github/workflow/pkg.yml'
      - 'Changelog.md'
      - 'Dockerfile'
      - 'doc/**'
      - 'docker/**'
      - 'LICENSE'
      - 'README.md'
      - 'tests/e2e/**'
  # Hmm, annoying, do we really have to duplicate this?
  pull_request:
    branches:
    - main
    paths-ignore:
      - '.dockerignore'
      - '.github/workflow/pkg.yml'
      - 'Changelog.md'
      - 'Dockerfile'
      - 'doc/**'
      - 'docker/**'
      - 'LICENSE'
      - 'README.md'
      - 'tests/e2e/**'

defaults:
  run:
    # see: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell
    shell: bash --noprofile --norc -eo pipefail -x {0}

jobs:
  # Use the cargo-deb Rust crate to build a Debian package for installing
  # Krill. See: https://github.com/mmstick/cargo-deb
  deb-pkg:
    strategy:
      matrix:
        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
          - 'debian:stretch'  # debian/9
          - 'debian:buster'   # debian/10
          - 'debian:bullseye' # debian/11
    env:
      CARGO_DEB_VER: 1.28.0
      # A Krill version of the form 'x.y.z-plus' denotes a dev build that is
      # newer than the released x.y.z version but is not yet a new release.
      NEXT_VER_LABEL: bis
    name: deb-pkg
    runs-on: ubuntu-latest
    # Build on the oldest platform we are targeting in order to avoid
    # https://github.com/rust-lang/rust/issues/57497. Specifying container
    # causes all of the steps in this job to run inside a Docker container.
    container: ${{ matrix.image }}

    steps:
    - name: Set vars
      id: setvars
      shell: bash
      run: |
        # Get the operating system and release name (e.g. ubuntu and xenial) from
        # the image name (e.g. ubuntu:xenial) by extracting only the parts before
        # and after but not including the colon:
        echo "OS_NAME=${MATRIX_IMAGE%:*}" >> $GITHUB_ENV
        echo "OS_REL=${MATRIX_IMAGE#*:}" >> $GITHUB_ENV
      env:
        MATRIX_IMAGE: ${{ matrix.image }}

    # Git clone the Krill code in the branch we were invoked on.
    - name: Checkout repository
      uses: actions/checkout@v1

    # Install Rust the hard way rather than using a GH Action because the action
    # doesn't work inside a Docker container.
    - name: Install Rust
      run: |
        apt-get update
        apt-get install -y curl
        curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --profile minimal -y
        echo "$HOME/.cargo/bin" >> $GITHUB_PATH
      env:
        DEBIAN_FRONTEND: noninteractive

    - name: Install compilation and other dependencies
      run: |
        apt-get install -y build-essential jq libssl-dev lintian pkg-config
      env:
        DEBIAN_FRONTEND: noninteractive

    # Speed up Krill Rust builds by caching unchanged built dependencies.
    # See: https://github.com/actions/cache/blob/master/examples.md#rust---cargo
    - name: Cache Dot Cargo
      uses: actions/cache@v2
      with:
        path: |
          ~/.cargo/registry
          ~/.cargo/git
          target
        key: ${{ matrix.image }}-cargo-${{ hashFiles('**/Cargo.lock') }}

    # Speed up cargo-deb installation by only re-downloading and re-building its
    # dependent crates if we change the version of cargo-deb that we are using.
    - name: Cache Cargo Deb binary
      id: cache-cargo-deb
      uses: actions/cache@v2
      with:
        path: ~/.cargo/bin/cargo-deb
        key: ${{ matrix.image }}-cargo-deb-${{ env.CARGO_DEB_VER }}

    # Only install cargo-deb if not already fetched from the cache.
    - name: Install Cargo Deb
      if: steps.cache-cargo-deb.outputs.cache-hit != 'true'
      run: |
        cargo install cargo-deb --version $CARGO_DEB_VER

    # Instruct cargo-deb to build the Debian package using the config section
    # in Cargo.toml for the specified "variant".
    - name: Create the DEB package
      run: |
        # Packages for different distributions (e.g. Stretch, Buster) of the same
        # O/S (e.g. Debian) when served from a single package repository MUST have
        # unique package_ver_architecture triples. Cargo deb can vary the name based
        # on the 'variant' config section in use, but doesn't do so according to
        # Debian policy (as it modifies the package name, not the package version).
        #   Format: package_ver_architecture
        #   Where ver has format: [epoch:]upstream_version[-debian_revision]
        #   And debian_version should be of the form: 1<xxx>
        #   Where it is common to set <xxx> to the O/S name.
        # See:
        #   - https://unix.stackexchange.com/a/190899
        #   - https://www.debian.org/doc/debian-policy/ch-controlfields.html#version
        #   - https://readme.phys.ethz.ch/documentation/debian_version_numbers/
        # Therefore we generate the version ourselves.
        #
        # In addition, Semantic Versioning and Debian version policy cannot
        # express a pre-release label in the same way. For example 0.8.0-rc.1
        # is a valid Cargo.toml [package].version value but when used as a
        # Debian package version 0.8.0-rc.1 would be considered _NEWER_ than
        # the final 0.8.0 release. To express this in a Debian compatible way we
        # must replace the dash '-' with a tilda '~'.
        #
        # Finally, sometimes we want a version to be NEWER than the latest
        # release but without having to decide what higher semver number to bump
        # to. In this case we do NOT want dash '-' to become '~' because `-`
        # is treated as higher and tilda is treated as lower.
        KRILL_VER=$(cargo read-manifest | jq -r '.version' | tr '-' '~')
        DEB_KRILL_VER=$(echo $KRILL_VER | sed -e "s/~$NEXT_VER_LABEL/-$NEXT_VER_LABEL/")

        case ${{ github.event_name }} in
          pull_request) MAINTAINER="${{ github.actor }} <unknown@email.address>" ;;
          push)         MAINTAINER="${{ github.event.pusher.name }} <${{ github.event.pusher.email }}>" ;;
          *)            echo 2>&1 "ERROR: Unexpected GitHub Actions event"; exit 1 ;;
        esac

        # Generate the RFC 5322 format date by hand instead of using date --rfc-email
        # because that option doesn't exist on Ubuntu 16.04 and Debian 9
        RFC5322_TS=$(LC_TIME=en_US.UTF-8 date +'%a, %d %b %Y %H:%M:%S %z')

        # Generate the changelog file that Debian packages are required to have.
        # See: https://www.debian.org/doc/manuals/maint-guide/dreq.en.html#changelog
        echo "krill (${DEB_KRILL_VER}) unstable; urgency=medium" >debian/changelog
        echo "  * See: https://github.com/NLnetLabs/krill/releases/tag/v${KRILL_VER}" >>debian/changelog
        echo " -- maintainer ${MAINTAINER}  ${RFC5322_TS}" >>debian/changelog

        DEB_VER="${DEB_KRILL_VER}-1${OS_REL}"
        cargo deb --variant ${OS_NAME}-${OS_REL} --deb-version ${DEB_VER} -v -- --locked

    # See what Lintian thinks of our package.
    - name: Verify the DEB package
      run: |
        lintian -v target/debian/*.deb

    # Upload the produced DEB package. The artifact will be available
    # via the GH Actions job summary and build log pages, but only to
    # users logged in to GH with sufficient rights in this project. The
    # uploaded artifact is also downloaded by the next job (see below)
    # to sanity check that it can be installed and results in a working
    # Krill installation.
    - name: Upload DEB package
      uses: actions/upload-artifact@v2
      with:
        name: ${{ env.OS_NAME }}_${{ env.OS_REL }}
        path: target/debian/*.deb

  # Download and sanity check on target operating systems the packages created
  # by previous jobs (see above). Don't test on GH runners as they come with
  # lots of software and libraries pre-installed and thus are not representative
  # of the actual deployment targets, nor do GH runners support all targets that
  # we want to test. Don't test in Docker containers as they do not support
  # systemd.
  deb-pkg-test:
    name: deb-pkg-test
    needs: deb-pkg
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        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
          - 'debian:stretch'  # debian/9
          - 'debian:buster'   # debian/10
          - 'debian:bullseye' # debian/11
        mode:
          - 'fresh-install'
          - 'upgrade-from-published'
        exclude:
          - image: 'debian:bullseye'
            mode: 'upgrade-from-published'
    steps:
    # Set some environment variables that will be available to "run" steps below
    # in this job, and some output variables that will be available in GH Action
    # step definitions below.
    - name: Set vars
      id: setvars
      shell: bash
      run: |
        # Get the operating system and release name (e.g. ubuntu and xenial) from
        # the image name (e.g. ubuntu:xenial) by extracting only the parts before
        # and after but not including the colon:
        OS_NAME=${MATRIX_IMAGE%:*}
        OS_REL=${MATRIX_IMAGE#*:}

        echo "OS_NAME=${OS_NAME}" >> $GITHUB_ENV
        echo "OS_REL=${OS_REL}" >> $GITHUB_ENV
        echo "LXC_IMAGE=images:${OS_NAME}/${OS_REL}/cloud" >> $GITHUB_ENV
      env:
        MATRIX_IMAGE: ${{ matrix.image }}

    - name: Download DEB package
      uses: actions/download-artifact@v2
      with:
        name: ${{ env.OS_NAME }}_${{ env.OS_REL }}

    - name: Add current user to LXD group
      run: |
        sudo usermod --append --groups lxd $(whoami)

    - name: Initialize LXD
      run: |
        sudo lxd init --auto

    - name: Check LXD configuration
      run: |
        sg lxd -c "lxc info"

    - name: Launch LXC container
      run: |

        # security.nesting=true is needed to avoid error "Failed to set up mount
        # namespacing: Permission denied" in a Debian 10 container.
        sg lxd -c "lxc launch ${LXC_IMAGE} -c security.nesting=true testcon"

    # Run apt-get update and install man and sudo support (missing in some LXC/LXD
    # O/S images) but first wait for cloud-init to finish otherwise the network
    # isn't yet ready. Don't use cloud-init status --wait as that isn't supported
    # on older O/S's like Ubuntu 16.04 and Debian 9. Use the sudo package provided
    # configuration files otherwise when using sudo we get an error that the root
    # user isn't allowed to use sudo.
    - name: Prepare container
      shell: bash
      run: |
        echo "Waiting for cloud-init.."
        while ! sudo lxc exec testcon -- ls -la /var/lib/cloud/data/result.json; do
          sleep 1s
        done
        sg lxd -c "lxc exec testcon -- apt-get update"
        sg lxd -c "lxc exec testcon -- apt-get install -y -o Dpkg::Options::=\"--force-confnew\" apt-transport-https gnupg2 man sudo wget"

    - name: Copy DEB into LXC container
      run: |
        DEB_FILE=$(ls -1 *.deb)
        sg lxd -c "lxc file push ${DEB_FILE} testcon/tmp/"
        echo "DEB_FILE=${DEB_FILE}" >> $GITHUB_ENV

    - name: Install published DEB package
      if: ${{ matrix.mode == 'upgrade-from-published' }}
      run: |
        echo "deb [arch=amd64] https://packages.nlnetlabs.nl/linux/${OS_NAME}/ ${OS_REL} main" >$HOME/nlnetlabs.list
        sg lxd -c "lxc file push $HOME/nlnetlabs.list testcon/etc/apt/sources.list.d/"
        sg lxd -c "lxc exec testcon -- wget -q https://packages.nlnetlabs.nl/aptkey.asc"
        sg lxd -c "lxc exec testcon -- apt-key add ./aptkey.asc"
        sg lxd -c "lxc exec testcon -- apt update"
        sg lxd -c "lxc exec testcon -- apt install -y krill"

    - name: Install new DEB package
      if: ${{ matrix.mode == 'fresh-install' }}
      run: |
        sg lxd -c "lxc exec testcon -- apt-get -y install /tmp/${DEB_FILE}"

    - name: Test installed packages
      run: |
        echo -e "\nKRILLC VERSION:"
        sg lxd -c "lxc exec testcon -- krillc --version"

        echo -e "\nKRILL VERSION:"
        sg lxd -c "lxc exec testcon -- krill --version"

        echo -e "\nKRILL CONF:"
        sg lxd -c "lxc exec testcon -- cat /etc/krill.conf"

        echo -e "\nKRILL DATA DIR:"
        sg lxd -c "lxc exec testcon -- ls -la /var/lib/krill"

        echo -e "\nKRILL SERVICE STATUS BEFORE ENABLE:"
        sg lxd -c "lxc exec testcon -- systemctl status krill || true"

        echo -e "\nENABLE KRILL SERVICE:"
        sg lxd -c "lxc exec testcon -- systemctl enable krill"

        echo -e "\nKRILL SERVICE STATUS AFTER ENABLE:"
        sg lxd -c "lxc exec testcon -- systemctl status krill || true"

        echo -e "\nSTART KRILL SERVICE:"
        sg lxd -c "lxc exec testcon -- systemctl start krill"
        
        echo -e "\nKRILL SERVICE STATUS AFTER START:"
        sleep 1s
        sg lxd -c "lxc exec testcon -- systemctl status krill"

        echo -e "\nKRILL MAN PAGE:"
        sg lxd -c "lxc exec testcon -- man -P cat krill"

    - name: Install new DEB package
      if: ${{ matrix.mode == 'upgrade-from-published' }}
      run: |
        sg lxd -c "lxc exec testcon -- apt-get -y install /tmp/${DEB_FILE}"

    - name: Test installed packages
      if: ${{ matrix.mode == 'upgrade-from-published' }}
      run: |
        echo -e "\nKRILLC VERSION:"
        sg lxd -c "lxc exec testcon -- krillc --version"

        echo -e "\nKRILL VERSION:"
        sg lxd -c "lxc exec testcon -- krill --version"

        echo -e "\nKRILL CONF:"
        sg lxd -c "lxc exec testcon -- cat /etc/krill.conf"

        echo -e "\nKRILL DATA DIR:"
        sg lxd -c "lxc exec testcon -- ls -la /var/lib/krill"

        echo -e "\nKRILL SERVICE STATUS:"
        sg lxd -c "lxc exec testcon -- systemctl status krill || true"

        echo -e "\nKRILL MAN PAGE:"
        sg lxd -c "lxc exec testcon -- man -P cat krill"