#!/usr/bin/env bash
# The CI half of nx-pulumi's deploy, preview, refresh and cancel targets, run
# in place of the laptop command when GITHUB_ACTIONS is set. `nx run-many` runs
# many at once, all sharing $HOME/.pulumi.
#
#   ci.sh <up|preview|refresh|cancel> <project-root> <project-name> <stack>
#
# PULUMI_CLEAR_PENDING_CREATES=true adds `--clear-pending-creates` to a refresh.
set -uo pipefail

action="$1"
dir="$2/pulumi"
project="$3"
stack="$4"

logs="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/pulumi-logs"
log="$logs/$project-$stack.log"
mkdir -p "$logs"
: > "$log"

# A job previewing both stacks at once writes a profile per account; the stack
# picks its own, and ambient keys are dropped so neither account can leak in.
if [ -n "${CFX_CI_AWS_PROFILE_PREFIX:-}" ]; then
  export AWS_PROFILE="$CFX_CI_AWS_PROFILE_PREFIX-$stack"
  unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
fi

# Not `pulumi login`: concurrent logins race on ~/.pulumi/credentials.json.
export PULUMI_BACKEND_URL="s3://cfx-pulumi-iac-backend-$stack?region=us-west-2"
secrets_provider="awskms://alias/cfx-secrets?region=us-west-2"

# Only an apply or a preview creates a missing stack; a refresh or cancel of
# one that does not exist is a mistake to report.
create=(--create)
if [ "$action" = "up" ]; then
  flags=(--yes --skip-preview)
elif [ "$action" = "preview" ]; then
  flags=(--diff)
elif [ "$action" = "refresh" ]; then
  flags=(--yes --skip-preview --run-program)
  [ "${PULUMI_CLEAR_PENDING_CREATES:-}" = "true" ] && flags+=(--clear-pending-creates)
  create=()
else
  flags=(--yes)
  create=()
fi

# `stack select --create` so a stack file added in this change gets its
# backend stack; a no-op for one that exists.
attempt() {
  pulumi stack select "${create[@]}" "$stack" -C "$dir" \
    --secrets-provider "$secrets_provider" --non-interactive &&
    pulumi "$action" "${flags[@]}" --non-interactive --color always \
      -C "$dir" --stack "$stack"
}

# Backgrounded and waited on so the trap below can run mid-apply; bash holds a
# trap until a foreground command returns.
pid=""
run() {
  attempt >> "$log" 2>&1 &
  pid=$!
  wait "$pid"
}

# A cancel or timeout lands here. SIGINT is pulumi's graceful cancel, which
# releases the state lock; the log is printed or nothing shows what was running.
# The wait is bounded because the runner kills the step ~10s after it signals.
interrupted() {
  trap '' INT TERM
  pkill -INT -x pulumi 2>/dev/null || true
  for _ in 1 2 3 4 5; do
    kill -0 "$pid" 2>/dev/null || break
    sleep 1
  done
  echo 130 > "$logs/$project-$stack.rc"
  cat "$log"
  echo "::error::$project ($stack) interrupted mid-$action; check the stack for a stranded lock"
  exit 130
}
trap interrupted INT TERM

rc=0
run || rc=$?

# Two concurrent tasks can materialise the same bridged Terraform provider and
# one execs a binary the other is still writing. Only that is retried: any
# other failure is real and must stay loud. The pause lets the sibling finish
# writing, since the retry is not serialised behind it.
if [ "$rc" != "0" ] && grep -q 'text file busy' "$log"; then
  echo "── retry: provider binary was busy ──" >> "$log"
  sleep 10 &
  wait $!
  rc=0
  run || rc=$?
fi

# Read by tools/scripts/pulumi-comment.ts, which puts failed stacks first.
echo "$rc" > "$logs/$project-$stack.rc"
cat "$log"
exit "$rc"
