unoverse 0.1.76 → 0.1.77
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 +33 -0
- package/operator/lib/deploy.sh +25 -10
- package/operator/lib/destroy.sh +8 -0
- package/package.json +1 -1
|
@@ -14,8 +14,41 @@ terraform {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
# ── One universe, one group ───────────────────────────────────────────────────
|
|
18
|
+
#
|
|
19
|
+
# AWS has no projects. Its equivalent is TAGS, and `default_tags` is the only honest way
|
|
20
|
+
# to apply them: it stamps every resource this configuration creates, so nothing is
|
|
21
|
+
# forgotten the day someone adds a resource and does not think about tagging. The console
|
|
22
|
+
# view comes from the Resource Group below, and the same tag drives cost allocation, which
|
|
23
|
+
# a DigitalOcean project cannot do.
|
|
24
|
+
#
|
|
25
|
+
# It touches ONLY what this stack creates. An adopted database keeps its own tags, in
|
|
26
|
+
# keeping with the rule that a universe never takes ownership of what it borrowed
|
|
27
|
+
# (INFRASTRUCTURE.md, "Borrowed resources are never owned").
|
|
17
28
|
provider "aws" {
|
|
18
29
|
region = var.region
|
|
30
|
+
|
|
31
|
+
default_tags {
|
|
32
|
+
tags = {
|
|
33
|
+
Universe = var.name
|
|
34
|
+
ManagedBy = "unoverse"
|
|
35
|
+
Size = var.size
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# The console's answer to "what does this universe own". A tag query rather than a
|
|
41
|
+
# membership list, so it stays correct as the ground grows without anyone maintaining it.
|
|
42
|
+
resource "aws_resourcegroups_group" "universe" {
|
|
43
|
+
name = var.name
|
|
44
|
+
description = "unoverse universe: ${var.name}"
|
|
45
|
+
|
|
46
|
+
resource_query {
|
|
47
|
+
query = jsonencode({
|
|
48
|
+
ResourceTypeFilters = ["AWS::AllSupported"]
|
|
49
|
+
TagFilters = [{ Key = "Universe", Values = [var.name] }]
|
|
50
|
+
})
|
|
51
|
+
}
|
|
19
52
|
}
|
|
20
53
|
|
|
21
54
|
# ── Network: default VPC + two security groups ─────────────────────────────────
|
package/operator/lib/deploy.sh
CHANGED
|
@@ -20,16 +20,23 @@
|
|
|
20
20
|
# trusted-sources list, so pointing it at a borrowed cluster deletes the operator's own
|
|
21
21
|
# IP, their other droplets and their apps. Read the rules, add ours if missing, write
|
|
22
22
|
# them back. Nothing else moves, and running it twice changes nothing.
|
|
23
|
-
|
|
23
|
+
# Add or remove THIS universe's droplet in an adopted cluster's trusted sources.
|
|
24
|
+
# _adopted_db_access <cloud> grant|revoke [droplet_id]
|
|
25
|
+
# Symmetric on purpose. The grant existed and nothing revoked it, so a teardown left a
|
|
26
|
+
# stale rule behind — and worse, terraform's own destroy of an authoritative firewall
|
|
27
|
+
# resource PUT an EMPTY list and locked the operator out of a database it had borrowed.
|
|
28
|
+
# Both directions now touch exactly one rule and never the list.
|
|
29
|
+
_adopted_db_access() {
|
|
24
30
|
local cloud="$1" dir="$ROOT/infra/$cloud" cluster droplet_id
|
|
31
|
+
local mode="$2"
|
|
25
32
|
cluster=$(grep -E '^existing_pg_cluster_name[[:space:]]*=' "$dir/terraform.tfvars" 2>/dev/null | sed -E 's/.*"([^"]+)".*/\1/')
|
|
26
33
|
[ -n "$cluster" ] || return 0
|
|
27
34
|
[ -n "${DIGITALOCEAN_TOKEN:-}" ] || return 0
|
|
28
|
-
droplet_id
|
|
35
|
+
droplet_id="${3:-$(terraform -chdir="$dir" state show digitalocean_droplet.app 2>/dev/null | awk -F'"' '/^ *id *=/{print $2; exit}')}"
|
|
29
36
|
[ -n "$droplet_id" ] || return 0
|
|
30
37
|
|
|
31
|
-
node - "$cluster" "$droplet_id" <<'NODE'
|
|
32
|
-
const [cluster, droplet] = process.argv.slice(2);
|
|
38
|
+
node - "$cluster" "$droplet_id" "$mode" <<'NODE'
|
|
39
|
+
const [cluster, droplet, mode] = process.argv.slice(2);
|
|
33
40
|
const T = process.env.DIGITALOCEAN_TOKEN;
|
|
34
41
|
const H = { Authorization: `Bearer ${T}`, "Content-Type": "application/json" };
|
|
35
42
|
const api = (p, o = {}) => fetch(`https://api.digitalocean.com/v2${p}`, { headers: H, ...o });
|
|
@@ -38,13 +45,21 @@ const api = (p, o = {}) => fetch(`https://api.digitalocean.com/v2${p}`, { header
|
|
|
38
45
|
const db = (list.databases || []).find((d) => d.name === cluster);
|
|
39
46
|
if (!db) return;
|
|
40
47
|
const fw = await (await api(`/databases/${db.id}/firewall`)).json();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
let rules = (fw.rules || []).map((r) => ({ type: r.type, value: r.value }));
|
|
49
|
+
const mine = (r) => r.type === "droplet" && String(r.value) === String(droplet);
|
|
50
|
+
const has = rules.some(mine);
|
|
51
|
+
if (mode === "revoke") {
|
|
52
|
+
if (!has) return;
|
|
53
|
+
rules = rules.filter((r) => !mine(r)); // ours only; every other rule survives
|
|
54
|
+
} else {
|
|
55
|
+
if (has) return;
|
|
56
|
+
rules.push({ type: "droplet", value: String(droplet) });
|
|
57
|
+
}
|
|
44
58
|
const res = await api(`/databases/${db.id}/firewall`, { method: "PUT", body: JSON.stringify({ rules }) });
|
|
59
|
+
const what = mode === "revoke" ? "no longer reaches" : "may reach";
|
|
45
60
|
console.log(res.ok
|
|
46
|
-
? ` \x1b[32m✓\x1b[0m This universe
|
|
47
|
-
: ` \x1b[33m!\x1b[0m Could not update ${cluster}'s trusted sources —
|
|
61
|
+
? ` \x1b[32m✓\x1b[0m This universe ${what} ${cluster} \x1b[2m(one rule changed, ${rules.length} left in place)\x1b[0m`
|
|
62
|
+
: ` \x1b[33m!\x1b[0m Could not update ${cluster}'s trusted sources — adjust droplet ${droplet} by hand`);
|
|
48
63
|
})().catch(() => {});
|
|
49
64
|
NODE
|
|
50
65
|
}
|
|
@@ -311,7 +326,7 @@ cmd_deploy() {
|
|
|
311
326
|
exit 1
|
|
312
327
|
fi
|
|
313
328
|
|
|
314
|
-
|
|
329
|
+
_adopted_db_access "$cloud" grant
|
|
315
330
|
|
|
316
331
|
|
|
317
332
|
# Read deploy target from .env.production
|
package/operator/lib/destroy.sh
CHANGED
|
@@ -91,6 +91,14 @@ cmd_destroy() {
|
|
|
91
91
|
return 1
|
|
92
92
|
fi
|
|
93
93
|
|
|
94
|
+
# Take our rule out of the borrowed cluster BEFORE the state goes, while the droplet id
|
|
95
|
+
# is still readable. One rule, ours, never the list.
|
|
96
|
+
if [ -n "$kept" ]; then
|
|
97
|
+
local dropped
|
|
98
|
+
dropped=$(terraform -chdir="$dir" state show digitalocean_droplet.app 2>/dev/null | awk -F'"' '/^ *id *=/{print $2; exit}')
|
|
99
|
+
[ -n "$dropped" ] && _adopted_db_access "$cloud" revoke "$dropped"
|
|
100
|
+
fi
|
|
101
|
+
|
|
94
102
|
echo ""
|
|
95
103
|
info "Taking it down..."
|
|
96
104
|
echo ""
|
package/package.json
CHANGED