unoverse 0.1.124 → 0.1.126
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/operator/lib/destroy.sh +107 -10
- package/package.json +1 -1
package/operator/lib/destroy.sh
CHANGED
|
@@ -18,6 +18,84 @@
|
|
|
18
18
|
# It never touches the developer's terraform.tfvars, their .env, or their code. Only the
|
|
19
19
|
# cloud resources this ground owns.
|
|
20
20
|
|
|
21
|
+
# ── Resources a previous run created and did not write down ───────────────────────────────
|
|
22
|
+
#
|
|
23
|
+
# `destroy` removes what STATE records. A deploy killed mid-apply leaves AWS holding
|
|
24
|
+
# resources terraform never recorded, and they do not merely linger: a database or a load
|
|
25
|
+
# balancer keeps a security group alive, so the part terraform CAN do fails on
|
|
26
|
+
#
|
|
27
|
+
# DependencyViolation: resource sg-... has a dependent object
|
|
28
|
+
#
|
|
29
|
+
# after fifteen minutes of retrying, and the teardown reports only that it did not finish.
|
|
30
|
+
# The operator is then left deleting a database, a Redis cluster and a load balancer by hand
|
|
31
|
+
# before a "one command" teardown will complete. That happened on 2026-08-05.
|
|
32
|
+
#
|
|
33
|
+
# IT IMPORTS. IT NEVER DELETES.
|
|
34
|
+
#
|
|
35
|
+
# Adopting an orphan into state is a state write and touches no cloud API. The deletion is
|
|
36
|
+
# still terraform's, from the plan the operator sees and confirms by name. That matters twice
|
|
37
|
+
# over: terraform already knows the order to take this stack apart (load balancer before its
|
|
38
|
+
# security group, database before the group holding its interface), and a second deletion
|
|
39
|
+
# engine written in bash would not, and would drift from main.tf the day a resource was
|
|
40
|
+
# added.
|
|
41
|
+
#
|
|
42
|
+
# NOTHING WE DID NOT CREATE, enforced four ways rather than trusted:
|
|
43
|
+
#
|
|
44
|
+
# 1. TAGGED AS OURS. `_cloud_orphans` lists only what carries `Universe=<name>`, and
|
|
45
|
+
# `default_tags` stamps that on resources THIS configuration creates. A borrowed
|
|
46
|
+
# database keeps its own tags and can never appear here (main.tf, "Borrowed resources
|
|
47
|
+
# are never owned").
|
|
48
|
+
# 2. A KNOWN ADDRESS. The ARN must map to one of the handful of resources main.tf declares.
|
|
49
|
+
# Anything else is left alone, whatever it is tagged.
|
|
50
|
+
# 3. NAMED AFTER THIS UNIVERSE. The ARN must contain the universe's own name, which is why
|
|
51
|
+
# `aws_instance.app` is deliberately absent below: an EC2 ARN carries only an opaque
|
|
52
|
+
# instance id, so it cannot be verified this way, and an unverifiable match is not one
|
|
53
|
+
# worth making. An untracked server is reported instead.
|
|
54
|
+
# 4. NOT ALREADY TRACKED. An address present in state is skipped, so this can only ever add
|
|
55
|
+
# what is missing, never re-point something the operator already owns.
|
|
56
|
+
#
|
|
57
|
+
# Failure is not fatal anywhere in here: the worst case is the teardown behaving exactly as
|
|
58
|
+
# it did before.
|
|
59
|
+
_adopt_orphans() {
|
|
60
|
+
local dir="$1" uname_="$2" orphans arn addr id adopted=0 unclaimed=""
|
|
61
|
+
|
|
62
|
+
command -v aws >/dev/null 2>&1 || return 0
|
|
63
|
+
orphans=$(_cloud_orphans aws "$uname_" 2>/dev/null)
|
|
64
|
+
[ -n "$orphans" ] && [ "$orphans" != "unknown" ] || return 0
|
|
65
|
+
|
|
66
|
+
while IFS= read -r arn; do
|
|
67
|
+
[ -n "$arn" ] || continue
|
|
68
|
+
addr=""; id="$arn"
|
|
69
|
+
case "$arn" in
|
|
70
|
+
*:rds:*:db:"$uname_"-pg) addr="aws_db_instance.postgres"; id="$uname_-pg" ;;
|
|
71
|
+
*:elasticache:*:replicationgroup:"$uname_"-redis) addr="aws_elasticache_replication_group.redis"; id="$uname_-redis" ;;
|
|
72
|
+
*:loadbalancer/app/"$uname_"-alb/*) addr="aws_lb.public" ;;
|
|
73
|
+
*:targetgroup/"$uname_"-app/*) addr="aws_lb_target_group.app" ;;
|
|
74
|
+
*:targetgroup/"$uname_"-canvas/*) addr="aws_lb_target_group.canvas[0]" ;;
|
|
75
|
+
*) unclaimed="$unclaimed $arn"$'\n' ; continue ;;
|
|
76
|
+
esac
|
|
77
|
+
terraform -chdir="$dir" state list 2>/dev/null | grep -qxF "$addr" && continue
|
|
78
|
+
if terraform -chdir="$dir" import -input=false "$addr" "$id" >/dev/null 2>&1; then
|
|
79
|
+
adopted=$((adopted + 1))
|
|
80
|
+
else
|
|
81
|
+
unclaimed="$unclaimed $arn"$'\n'
|
|
82
|
+
fi
|
|
83
|
+
done <<EOF
|
|
84
|
+
$orphans
|
|
85
|
+
EOF
|
|
86
|
+
|
|
87
|
+
[ "$adopted" -gt 0 ] && \
|
|
88
|
+
info "Adopted $adopted resource(s) an interrupted run left untracked. They are in the plan below"
|
|
89
|
+
# SAID OUT LOUD, never silently swept. Anything here is billing and this teardown will not
|
|
90
|
+
# remove it, which the operator has to know BEFORE they are told the universe is gone.
|
|
91
|
+
if [ -n "$unclaimed" ]; then
|
|
92
|
+
warn "these carry this universe's tag but are not resources this teardown can remove:"
|
|
93
|
+
printf '%s' "$unclaimed"
|
|
94
|
+
echo " Check them yourself before assuming the bill has stopped."
|
|
95
|
+
fi
|
|
96
|
+
return 0
|
|
97
|
+
}
|
|
98
|
+
|
|
21
99
|
cmd_destroy() {
|
|
22
100
|
# NAME THE GROUND. This took the first configured one, and digitalocean is first in the
|
|
23
101
|
# list — so an operator with both grounds who typed `unoverse destroy` meaning AWS would
|
|
@@ -46,10 +124,20 @@ cmd_destroy() {
|
|
|
46
124
|
echo -e " ${RED}${BOLD}⬡ Taking down your $pretty universe${NC}"
|
|
47
125
|
echo ""
|
|
48
126
|
|
|
49
|
-
local tmp planfile rc
|
|
127
|
+
local tmp planfile rc name
|
|
50
128
|
tmp=$(mktemp -d); planfile="$tmp/plan"
|
|
51
129
|
|
|
52
|
-
#
|
|
130
|
+
# The universe's name, read once. It is the tag every resource carries, so it is what
|
|
131
|
+
# decides which untracked resources are this universe's (_adopt_orphans) as well as the
|
|
132
|
+
# word the operator types to confirm.
|
|
133
|
+
name=$(grep -E '^name[[:space:]]*=' "$dir/terraform.tfvars" 2>/dev/null | sed -E 's/.*"([^"]+)".*/\1/')
|
|
134
|
+
name="${name:-universe}"
|
|
135
|
+
|
|
136
|
+
# BEFORE THE PLAN, so what an interrupted run left behind is in the list the operator
|
|
137
|
+
# confirms, rather than something they discover by hand an hour later.
|
|
138
|
+
[ "$cloud" = "aws" ] && _adopt_orphans "$dir" "$name"
|
|
139
|
+
|
|
140
|
+
# APPLY THE CONFIGURATION TO THE DATABASE FIRST, because destroy does not.
|
|
53
141
|
#
|
|
54
142
|
# `terraform destroy` deletes a resource as the last apply left it, so an attribute that
|
|
55
143
|
# matters only AT DELETE — RDS's `skip_final_snapshot` — comes from STATE, not from
|
|
@@ -57,10 +145,23 @@ cmd_destroy() {
|
|
|
57
145
|
# already applied. Applying the config to the database writes those settings into state
|
|
58
146
|
# and calls no API; the resource is deleted seconds later, so nothing else here matters.
|
|
59
147
|
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
148
|
+
# ONLY IF IT IS ALREADY IN STATE. Without that test this is not an update, it is a CREATE:
|
|
149
|
+
# `apply -target` on a resource the configuration declares and the state does not have
|
|
150
|
+
# builds it. So a teardown of a universe whose database had already gone — deleted by
|
|
151
|
+
# hand, or removed from state — silently provisioned a NEW one, with -auto-approve, on the
|
|
152
|
+
# way to being told to destroy everything. In state means the only possible change is to
|
|
153
|
+
# the resource's own attributes.
|
|
154
|
+
#
|
|
155
|
+
# ANNOUNCED, because it runs before the first progress line and terraform can sit on an
|
|
156
|
+
# RDS refresh for a while. Sending it to a log with nothing on screen made `unoverse
|
|
157
|
+
# destroy` look frozen at the banner.
|
|
158
|
+
if [ "$cloud" = "aws" ] \
|
|
159
|
+
&& terraform -chdir="$dir" state list 2>/dev/null | grep -qx "aws_db_instance.postgres"; then
|
|
160
|
+
info "Checking the database's delete settings..."
|
|
161
|
+
terraform -chdir="$dir" apply -input=false -auto-approve \
|
|
162
|
+
-target=aws_db_instance.postgres >"$tmp/sync.log" 2>&1 \
|
|
163
|
+
|| warn "could not update them; the teardown may stop on the database"
|
|
164
|
+
fi
|
|
64
165
|
|
|
65
166
|
info "Working out what exists..."
|
|
66
167
|
terraform -chdir="$dir" plan -destroy -input=false -detailed-exitcode -out="$planfile" >"$tmp/log" 2>&1
|
|
@@ -85,10 +186,6 @@ cmd_destroy() {
|
|
|
85
186
|
info "Staying: ${BOLD}your own database${NC} ${DIM}(byo_postgres_url — this stack never owned it)${NC}"
|
|
86
187
|
fi
|
|
87
188
|
|
|
88
|
-
local name
|
|
89
|
-
name=$(grep -E '^name[[:space:]]*=' "$dir/terraform.tfvars" 2>/dev/null | sed -E 's/.*"([^"]+)".*/\1/')
|
|
90
|
-
name="${name:-universe}"
|
|
91
|
-
|
|
92
189
|
echo ""
|
|
93
190
|
# Say what is actually lost. With a reused cluster the DATABASE survives, and claiming
|
|
94
191
|
# otherwise makes the warning untrue in the commonest case — which teaches people to
|
package/package.json
CHANGED