Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion bin/fm-azure-cell-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,15 @@ test -x "\$(command -v pi)"
installed=\$(node -p "require('/usr/lib/node_modules/@earendil-works/pi-coding-agent/package.json').version")
test "\$installed" = 0.84.1
apt-get clean
waagent -deprovision+user -force
# The marker goes out BEFORE deprovision, and deprovision is not run here at
# all. Deprovisioning stops the guest agent, and the guest agent is what carries
# a run command's output back to the caller: doing it inside this script means
# the marker can never be delivered, so the invoking CLI blocks until Azure's
# extension timeout no matter how well the bake went. That is not theoretical. It is why a bake that had
# installed everything correctly still hung for 90 minutes and captured
# nothing; it stayed hidden while an earlier bug killed this script on line 1,
# because a script that dies instantly never reaches deprovision. The host runs
# deprovision as its own fire-and-forget call once this one has returned.
echo FM-BAKE-COMPLETE
EOF
# RunCommandLinux is a VM EXTENSION, so it cannot provision until the guest
Expand Down Expand Up @@ -169,6 +177,16 @@ case "$BAKE_MESSAGE" in
;;
esac

# Deprovision now, as a SEPARATE call whose output nobody waits for: it stops
# the guest agent, so it can never report its own completion. Azure requires it
# before generalize.
echo "fm-azure-cell-image: deprovisioning the builder" >&2
az vm run-command invoke --resource-group "$RESOURCE_GROUP" --name "$BUILDER" \
--command-id RunShellScript --scripts "waagent -deprovision+user -force" \
--no-wait --output none 2>/dev/null || true
# Give the agent time to act on it before deallocating out from under it.
sleep 60

echo "fm-azure-cell-image: capturing image $IMAGE" >&2
az vm deallocate --resource-group "$RESOURCE_GROUP" --name "$BUILDER" --output none
BUILDER_LIVE=0
Expand Down
32 changes: 32 additions & 0 deletions tests/fm-azure-cell-image.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,37 @@ run_host_waits_for_the_guest_agent() {
pass "the bake waits for the builder guest agent before invoking a VM extension"
}

run_guest_script_never_deprovisions_itself() {
# Deprovisioning stops the guest agent, and the guest agent is what carries a
# run command's output back to the caller. Doing it inside the bake script
# means the completion marker can never be delivered, so the invoking CLI
# blocks until Azure's extension timeout NO MATTER HOW WELL THE BAKE WENT.
# That is exactly what happened: a bake that had installed everything
# correctly hung for 90 minutes and captured nothing. It stayed hidden while
# an earlier bug killed the script on line 1, because a script that dies
# instantly never reaches deprovision.
local guest
guest=$(awk '/^cat >"\$BAKE" <<EOF/,/^EOF$/' "$SCRIPT")
printf '%s' "$guest" | grep -vE '^\s*#' | grep -q 'deprovision' \
&& fail "the guest bake script deprovisions itself, so its marker can never be delivered"
printf '%s' "$guest" | grep -q 'FM-BAKE-COMPLETE' \
|| fail "the guest bake script no longer emits its completion marker"

# The host still has to deprovision before generalize, and must not wait on a
# call whose reporter it is killing.
local deprov
deprov=$(grep -n 'waagent -deprovision' "$SCRIPT" | head -1 | cut -d: -f1)
[ -n "$deprov" ] || fail "nothing deprovisions the builder, so generalize will refuse"
sed -n "${deprov}p;$((deprov+1))p;$((deprov-1))p" "$SCRIPT" | grep -q -- '--no-wait' \
|| fail "the host waits on the deprovision call that kills the agent reporting it"

local generalize
generalize=$(grep -n 'az vm generalize' "$SCRIPT" | head -1 | cut -d: -f1)
[ -n "$generalize" ] && [ "$deprov" -lt "$generalize" ] \
|| fail "deprovision does not precede generalize"
pass "the bake emits its marker before deprovision, and deprovisions without waiting"
}

run_failed_bake_does_not_leak_the_builder() {
# The builder is billable compute. A bake that refuses to capture used to
# leave a running D4as_v6 behind with nothing owning it, reclaimed only
Expand Down Expand Up @@ -138,6 +169,7 @@ PROBE
run_guest_script_survives_dash
run_guest_waits_for_egress_before_downloading
run_host_waits_for_the_guest_agent
run_guest_script_never_deprovisions_itself
run_failed_bake_does_not_leak_the_builder

echo "# fm-azure-cell-image.test.sh: all assertions passed"
Loading