unoverse 0.1.98 → 0.1.100
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/infra/aws/main.tf +8 -2
- package/operator/lib/check.sh +82 -0
- package/package.json +1 -1
|
@@ -41,7 +41,11 @@ provider "aws" {
|
|
|
41
41
|
# membership list, so it stays correct as the ground grows without anyone maintaining it.
|
|
42
42
|
resource "aws_resourcegroups_group" "universe" {
|
|
43
43
|
name = var.name
|
|
44
|
-
description
|
|
44
|
+
# AWS Resource Groups allow ONLY [\sa-zA-Z0-9_.-] in a description — no colon, no
|
|
45
|
+
# parentheses. "unoverse universe: name" failed CreateGroup after four minutes of ALB
|
|
46
|
+
# provisioning, and terraform validate cannot see it: the rule lives in the AWS API, not
|
|
47
|
+
# in the schema.
|
|
48
|
+
description = "unoverse universe ${var.name}"
|
|
45
49
|
|
|
46
50
|
resource_query {
|
|
47
51
|
query = jsonencode({
|
|
@@ -149,7 +153,9 @@ resource "aws_security_group" "app" {
|
|
|
149
153
|
vpc_id = data.aws_vpc.default.id
|
|
150
154
|
|
|
151
155
|
ingress {
|
|
152
|
-
|
|
156
|
+
# Security group descriptions forbid angle brackets. "api.<domain>" reads naturally in
|
|
157
|
+
# prose and is rejected by the API.
|
|
158
|
+
description = "Platform api host, from the ALB only"
|
|
153
159
|
from_port = 4105
|
|
154
160
|
to_port = 4105
|
|
155
161
|
protocol = "tcp"
|
package/operator/lib/check.sh
CHANGED
|
@@ -1,4 +1,56 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# Resources this universe is BILLED FOR that terraform does not know it owns.
|
|
4
|
+
# _cloud_orphans <ground> <universe-name>
|
|
5
|
+
# Echoes nothing when everything is tracked, "unknown" when the provider could not be
|
|
6
|
+
# reached (never a failure — an offline laptop is not an orphaned resource), or one line
|
|
7
|
+
# per untracked resource.
|
|
8
|
+
#
|
|
9
|
+
# It works because every resource is labelled with the universe it belongs to: AWS through
|
|
10
|
+
# default_tags (Universe=<name>), DigitalOcean through project membership. Without that
|
|
11
|
+
# label there is no question to ask, which is why the Canvas load balancer landing in the
|
|
12
|
+
# wrong DO project was worth fixing rather than tidying.
|
|
13
|
+
_cloud_orphans() {
|
|
14
|
+
local g="$1" uname_="$2" dir="$ROOT/infra/$1"
|
|
15
|
+
[ -n "$uname_" ] || { echo unknown; return 0; }
|
|
16
|
+
|
|
17
|
+
local state
|
|
18
|
+
state=$(terraform -chdir="$dir" show -json 2>/dev/null) || { echo unknown; return 0; }
|
|
19
|
+
[ -n "$state" ] || { echo unknown; return 0; }
|
|
20
|
+
|
|
21
|
+
local live
|
|
22
|
+
if [ "$g" = "aws" ]; then
|
|
23
|
+
command -v aws >/dev/null 2>&1 || { echo unknown; return 0; }
|
|
24
|
+
live=$(aws resourcegroupstaggingapi get-resources \
|
|
25
|
+
--tag-filters "Key=Universe,Values=$uname_" \
|
|
26
|
+
--query 'ResourceTagMappingList[].ResourceARN' --output text 2>/dev/null | tr '\t' '\n')
|
|
27
|
+
else
|
|
28
|
+
[ -n "${DIGITALOCEAN_TOKEN:-}" ] || { echo unknown; return 0; }
|
|
29
|
+
local pid
|
|
30
|
+
pid=$(doctl projects list --format ID,Name --no-header 2>/dev/null | awk -v n="$uname_" '$2==n{print $1}')
|
|
31
|
+
[ -n "$pid" ] || { echo unknown; return 0; }
|
|
32
|
+
live=$(doctl projects resources list "$pid" --format URN --no-header 2>/dev/null)
|
|
33
|
+
fi
|
|
34
|
+
[ -n "$live" ] || { echo ""; return 0; }
|
|
35
|
+
|
|
36
|
+
printf '%s' "$state" | node -e '
|
|
37
|
+
let s=""; process.stdin.on("data",d=>s+=d).on("end",()=>{
|
|
38
|
+
const live=(process.argv[1]||"").split("\n").map(x=>x.trim()).filter(Boolean);
|
|
39
|
+
let ids=new Set();
|
|
40
|
+
try {
|
|
41
|
+
const st=JSON.parse(s);
|
|
42
|
+
const walk=(m)=>{ (m.resources||[]).forEach(r=>{ if(!r.values) return;
|
|
43
|
+
["id","arn","urn"].forEach(k=>r.values[k]&&ids.add(String(r.values[k]))); });
|
|
44
|
+
(m.child_modules||[]).forEach(walk); };
|
|
45
|
+
if (st.values && st.values.root_module) walk(st.values.root_module);
|
|
46
|
+
} catch { process.exit(0); } // unreadable state is not an orphan claim
|
|
47
|
+
const known=[...ids];
|
|
48
|
+
const orphans=live.filter(a=>!ids.has(a) && !known.some(i=>i && a.includes(i)));
|
|
49
|
+
orphans.forEach(o=>console.log(o));
|
|
50
|
+
});
|
|
51
|
+
' "$live"
|
|
52
|
+
}
|
|
53
|
+
|
|
2
54
|
# unoverse check
|
|
3
55
|
|
|
4
56
|
cmd_check() {
|
|
@@ -86,6 +138,36 @@ cmd_check() {
|
|
|
86
138
|
fail "Canvas ${DIM}http://localhost:3001 → $canvas_code${NC}"
|
|
87
139
|
fi
|
|
88
140
|
|
|
141
|
+
# 6. NOTHING BILLING THAT TERRAFORM HAS LOST SIGHT OF.
|
|
142
|
+
#
|
|
143
|
+
# Terraform records what it creates as it creates it, so an interrupted apply does not
|
|
144
|
+
# duplicate anything — the next run continues from state. Two cases break that, and both
|
|
145
|
+
# cost money silently: a crash between the API call and the state write, and a lost or
|
|
146
|
+
# deleted state file, which orphans everything at once and makes it invisible to destroy.
|
|
147
|
+
#
|
|
148
|
+
# This is answerable because every resource is labelled with the universe it belongs to:
|
|
149
|
+
# AWS through default_tags, DigitalOcean through project membership. Ask the provider
|
|
150
|
+
# what it thinks is ours, ask terraform what it knows it made, and compare. A number on
|
|
151
|
+
# the health check is the difference between trusting that and verifying it.
|
|
152
|
+
local _g
|
|
153
|
+
for _g in aws digitalocean; do
|
|
154
|
+
[ -f "$ROOT/infra/$_g/terraform.tfvars" ] || continue
|
|
155
|
+
[ -d "$ROOT/infra/$_g/.terraform" ] || continue
|
|
156
|
+
total=$((total + 1))
|
|
157
|
+
local uname_ orphans
|
|
158
|
+
uname_=$(grep -E '^name[[:space:]]*=' "$ROOT/infra/$_g/terraform.tfvars" 2>/dev/null | sed -E 's/.*"([^"]+)".*/\1/')
|
|
159
|
+
orphans=$(_cloud_orphans "$_g" "$uname_")
|
|
160
|
+
case "$orphans" in
|
|
161
|
+
"") ok "Cloud resources ${DIM}($_g — every resource is tracked)${NC}"; pass=$((pass + 1)) ;;
|
|
162
|
+
unknown) ok "Cloud resources ${DIM}($_g — could not reach the provider, skipped)${NC}"; pass=$((pass + 1)) ;;
|
|
163
|
+
*)
|
|
164
|
+
fail "Cloud resources ${DIM}($_g — billing but NOT tracked by terraform)${NC}"
|
|
165
|
+
echo "$orphans" | while read -r o; do [ -n "$o" ] && echo -e " ${DIM}$o${NC}"; done
|
|
166
|
+
info " These will not be removed by ${BOLD}unoverse destroy $_g${NC}. Delete them in the console"
|
|
167
|
+
;;
|
|
168
|
+
esac
|
|
169
|
+
done
|
|
170
|
+
|
|
89
171
|
# Summary
|
|
90
172
|
echo ""
|
|
91
173
|
if [ "$pass" -eq "$total" ]; then
|
package/package.json
CHANGED