#!/usr/bin/env bash
#
# case.sh — move the guest's base to a variant and report what became of the
# layered package.
#
#   case.sh n2            upgrade with rpm-ostree, reboot, read the state back
#   case.sh n2 --bootc    use bootc upgrade instead, which is a separate answer
#   case.sh n4 --drain    drop our layers first, then upgrade
#
# --drain is the candidate answer to the wedge the first run measured: a base
# that moves ahead of a layered hotfix stops the machine updating, forever,
# and that is the normal life of a hotfix rather than an edge case. The rule
# it tests is that our own layers are disposable — never carried across an
# upgrade, dropped before it and re-applied after if the base still needs
# them. If that holds, the depsolve conflict cannot arise at all, because
# there is never a layer and a base copy in the same transaction.
#
# Chained into one transaction on purpose. rpm-ostree stages a deployment per
# command, and what matters is whether a user pays one reboot or two, so the
# uninstall and the upgrade go in a single invocation and the deployment count
# is part of the result.
#
# Retagging is how the base moves. The installed system points at :latest in
# the host registry, so pushing a different variant to that tag is the whole
# mechanism; nothing in the guest has to be reconfigured between cases.
#
# The reboot is not optional. rpm-ostree stages a deployment and the running
# system keeps running the old one, so reading `alloy-demo` before the reboot
# reports the previous answer and looks like a failure to apply.
set -euo pipefail

# shellcheck source=build/layertest/common.sh
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"

# shellcheck source=build/privilege.sh
. "$HERE/../privilege.sh"

MARK="${1:-}"
[ -n "$MARK" ] || die "usage: case.sh <mark> [--bootc|--drain]"
VERB="rpm-ostree upgrade"
DRAIN=no
case "${2:-}" in
  --bootc) VERB="bootc upgrade" ;;
  --drain) DRAIN=yes ;;
  "") ;;
  *) die "unknown option ${2}" ;;
esac

privc podman push --tls-verify=false "$IMAGE:$MARK" \
  "127.0.0.1:$REGISTRY_PORT/alloy-layertest:latest" >/dev/null 2>&1

# Drop our own layers before the upgrade rather than after, and by the exact
# string the request was recorded under. A bare package name does not match a
# request made by NEVRA, and rpm-ostree says so in a way that reads as "there
# was nothing to do" rather than as a failure — which is how a drain silently
# does not drain and the machine wedges anyway.
#
# Enumerated rather than `rpm-ostree reset`, which would work here and is
# wrong as a model: reset drops every layered package including ones the user
# layered themselves, and those are not ours to remove. Production has to be
# this precise, so the harness is too.
if [ "$DRAIN" = yes ]; then
  say "=== drain: dropping our layers before the upgrade ==="
  ours=$("$HERE/sshx" 'rpm-ostree status --json' \
    | python3 "$HERE/readstate.py" --requested alloy-demo)
  if [ -z "$ours" ]; then
    say "nothing of ours is layered"
  else
    for package in $ours; do
      say "uninstalling $package"
      "$HERE/sshx" "rpm-ostree uninstall $package" 2>&1 | tail -3
    done
  fi
fi

say "=== $VERB onto $MARK ==="
# Not fatal. A refusal is a result, and the whole point of --bootc is to see
# one, so the script has to survive the command it is measuring.
"$HERE/sshx" "$VERB" 2>&1 | tail -6 || true

"$HERE/sshx" 'systemctl reboot' >/dev/null 2>&1 || true
sleep 45

say "--- booted ---"
# `|| true` on the last command: `rpm -q` exits 1 for a package that is not
# installed, and that is a result rather than a failure. Without it a case that
# legitimately ends with nothing layered reports "guest did not come back"
# about a guest that came back and answered the question.
"$HERE/sshx" 'echo -n "base-mark: "; cat /usr/share/base-mark; \
  echo -n "binary:    "; alloy-demo 2>&1 || echo "(absent)"; \
  echo -n "rpm -q:    "; rpm -q alloy-demo 2>&1 || true' \
  || die "guest did not come back"
"$HERE/sshx" 'rpm-ostree status --json' | python3 "$HERE/readstate.py"

# Whether the machine can still take the next one. A wedge does not show up in
# the state above — it reports a perfectly ordinary deployment — and only
# announces itself the next time somebody tries to update. Both verbs, because
# the whole point of dropping the layer is to hand `bootc upgrade` back a
# deployment it will consent to work on.
say "--- can it still update? ---"
"$HERE/sshx" 'rpm-ostree upgrade --check 2>&1 | tail -3' || true
"$HERE/sshx" 'bootc upgrade --check 2>&1 | tail -3' || true
