unoverse 0.1.89 → 0.1.91
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.
|
@@ -381,9 +381,15 @@ resource "digitalocean_project" "universe" {
|
|
|
381
381
|
description = "unoverse universe: ${var.name}"
|
|
382
382
|
purpose = "Web Application"
|
|
383
383
|
environment = var.size == "small" ? "Development" : "Production"
|
|
384
|
+
# EVERY resource this stack creates, or the dashboard lies. The Canvas load balancer was
|
|
385
|
+
# added without being listed here, so it stayed in the account's default project while
|
|
386
|
+
# the rest of the universe sat in this one: two views each reporting "LOAD BALANCERS (1)"
|
|
387
|
+
# and no single place showing what this universe actually is. Anything added below must
|
|
388
|
+
# be added here in the same commit.
|
|
384
389
|
resources = compact([
|
|
385
390
|
digitalocean_droplet.app.urn,
|
|
386
391
|
digitalocean_loadbalancer.public.urn,
|
|
392
|
+
local.has_domain && var.canvas_public ? digitalocean_loadbalancer.canvas[0].urn : "",
|
|
387
393
|
digitalocean_database_cluster.redis.urn,
|
|
388
394
|
local.provision_pg ? digitalocean_database_cluster.pg[0].urn : "",
|
|
389
395
|
])
|
package/operator/lib/deploy.sh
CHANGED
|
@@ -441,12 +441,26 @@ _ground_apply() {
|
|
|
441
441
|
|
|
442
442
|
if [ "$summary_rc" = "3" ]; then
|
|
443
443
|
local REPLY
|
|
444
|
+
# ENTER GOES AHEAD, ESC STOPS. This asked for a typed `y` and made Enter mean "stop",
|
|
445
|
+
# which is the opposite of every other prompt in this CLI — the developer who has just
|
|
446
|
+
# read the plan and decided has to break the habit of the whole tool to act on it. One
|
|
447
|
+
# meaning for Enter throughout, and the escape hatch is the key literally named for it.
|
|
444
448
|
echo -e " ${RED}This removes or rebuilds things that already exist.${NC}"
|
|
445
|
-
|
|
446
|
-
|
|
449
|
+
echo ""
|
|
450
|
+
echo -e " ${DIM}Enter to go ahead · Esc to stop${NC}"
|
|
451
|
+
printf " "
|
|
452
|
+
local key=""
|
|
453
|
+
if [ -t 0 ]; then
|
|
454
|
+
IFS= read -r -s -n 1 key
|
|
455
|
+
fi
|
|
456
|
+
# \e is Escape; anything else typed (n, q, a stray letter) also stops, because only a
|
|
457
|
+
# deliberate empty Enter should destroy something.
|
|
458
|
+
if [ -n "$key" ]; then
|
|
459
|
+
echo ""
|
|
447
460
|
info "Nothing was changed. Run ${BOLD}unoverse deploy${NC} when you are ready"
|
|
448
461
|
rm -rf "$tmp"; return 1
|
|
449
462
|
fi
|
|
463
|
+
echo ""
|
|
450
464
|
fi
|
|
451
465
|
|
|
452
466
|
echo ""
|
package/operator/lib/ground.sh
CHANGED
|
@@ -13,6 +13,31 @@ _ground_my_ip() {
|
|
|
13
13
|
curl -s --max-time 5 https://api.ipify.org 2>/dev/null || curl -s --max-time 5 https://ifconfig.me 2>/dev/null
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
# A GROUND WRITES SECRETS TO DISK, so the ignore rule ships with the ground itself.
|
|
17
|
+
#
|
|
18
|
+
# terraform.tfvars holds the database URL, the registry token and every service key; the
|
|
19
|
+
# state file holds those plus everything Terraform generated, in plain text. The platform
|
|
20
|
+
# repo has ignored them since the grounds landed. A universe scaffolded by the CLI did not,
|
|
21
|
+
# because its .gitignore comes from the starter — which only changes when the starter is
|
|
22
|
+
# re-synced and pushed, long after somebody's first `git init && git add .`.
|
|
23
|
+
#
|
|
24
|
+
# Fixing it here means the rule exists the moment the file it protects does, on every
|
|
25
|
+
# universe, without waiting for a starter release. Idempotent: it appends only what is
|
|
26
|
+
# missing, and never rewrites a line the developer put there.
|
|
27
|
+
_ground_protect_secrets() {
|
|
28
|
+
local gi="$ROOT/.gitignore" rule
|
|
29
|
+
[ -f "$gi" ] || : > "$gi"
|
|
30
|
+
local added=0
|
|
31
|
+
for rule in 'infra/**/.terraform/' 'infra/**/*.tfstate*' 'infra/**/terraform.tfvars' '.unoverse/'; do
|
|
32
|
+
grep -qxF "$rule" "$gi" 2>/dev/null && continue
|
|
33
|
+
[ "$added" = "0" ] && printf '\n# Terraform (infra/*) — state and vars carry secrets\n' >> "$gi"
|
|
34
|
+
printf '%s\n' "$rule" >> "$gi"
|
|
35
|
+
added=1
|
|
36
|
+
done
|
|
37
|
+
[ "$added" = "1" ] && ok "Added the terraform files to .gitignore ${DIM}(they hold your database URL and keys)${NC}"
|
|
38
|
+
return 0
|
|
39
|
+
}
|
|
40
|
+
|
|
16
41
|
# THE UNIVERSE HAS A NAME, so ask for one. This file used to write name = "universe-poc"
|
|
17
42
|
# as a literal, so every universe on every account carried the same placeholder — it names
|
|
18
43
|
# the droplet, the cache, the load balancer and the cloud project, and the developer first
|
|
@@ -132,6 +157,7 @@ _ground_do() {
|
|
|
132
157
|
echo ""
|
|
133
158
|
echo -e " ${DIM}Regions available to you: $regions${NC}"
|
|
134
159
|
fi
|
|
160
|
+
_ground_protect_secrets
|
|
135
161
|
_ground_identity "$regions"
|
|
136
162
|
|
|
137
163
|
cat > "$out" <<EOF
|
|
@@ -232,6 +258,7 @@ _ground_aws() {
|
|
|
232
258
|
fi
|
|
233
259
|
|
|
234
260
|
# Region already came from the AWS CLI's own configuration, so only the name is asked.
|
|
261
|
+
_ground_protect_secrets
|
|
235
262
|
_ground_identity "-"
|
|
236
263
|
|
|
237
264
|
cat > "$out" <<EOF
|
|
@@ -71,6 +71,17 @@ function describe(r) {
|
|
|
71
71
|
price: PRICE[a.size],
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
+
// NEVER "SUPPORTING". These were counted as plumbing, which is fair when they are being
|
|
75
|
+
// created alongside the cluster they live on — and badly wrong on a REPLACE, where a
|
|
76
|
+
// dropped and recreated database takes its contents with it. "plus 1 supporting
|
|
77
|
+
// resource (users, rules, keys)" is not something anyone reads as "your data is about
|
|
78
|
+
// to be deleted". Naming them costs one line and makes that impossible to miss.
|
|
79
|
+
case "digitalocean_database_db":
|
|
80
|
+
return { what: "Database", detail: a.name ? `${a.name} — its contents live here` : "" };
|
|
81
|
+
case "digitalocean_database_user":
|
|
82
|
+
return { what: "Database user", detail: a.name ?? "" };
|
|
83
|
+
case "digitalocean_database_connection_pool":
|
|
84
|
+
return { what: "Connection pool", detail: a.name ?? "" };
|
|
74
85
|
case "digitalocean_loadbalancer":
|
|
75
86
|
return { what: "Load balancer", detail: "the public way in", price: PRICE.lb };
|
|
76
87
|
case "digitalocean_firewall":
|
|
@@ -168,7 +179,11 @@ process.stdin.on("end", () => {
|
|
|
168
179
|
plan.planned_values?.outputs?.monthly_estimate?.value ??
|
|
169
180
|
plan.outputs?.monthly_estimate?.value;
|
|
170
181
|
const monthly = ground ?? Object.values(groups).flat().reduce((sum, d) => sum + (d.price ?? 0), 0);
|
|
182
|
+
// THE TOTAL, NOT THE PRICE OF THIS CHANGE. `monthly_estimate` is what the whole universe
|
|
183
|
+
// costs to run, and printing it under a change list read as the bill for that change:
|
|
184
|
+
// a one-line firewall update appeared to cost $135. Say which number this is.
|
|
171
185
|
if (monthly) {
|
|
186
|
+
out.push(` ${dim("Running total for the whole universe, not the cost of this change:")}`);
|
|
172
187
|
out.push(` ${bold("Roughly")} ${bold(`$${monthly}/month`)} ${dim("at your provider's current prices, billed by them, not by unoverse")}`);
|
|
173
188
|
out.push("");
|
|
174
189
|
}
|
package/package.json
CHANGED