unoverse 0.1.103 → 0.1.105

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.
@@ -167,15 +167,28 @@
167
167
  # PACKAGES_PATH any more and docker-compose.yml has no such volume, so the check could
168
168
  # only ever report 0 and tell the operator to run a command that would not fix it.
169
169
 
170
+ # SINCE THE SERVICES RESTARTED, NOT SINCE FIVE MINUTES AGO.
171
+ #
172
+ # A first setup necessarily boots the services before the schema exists — migrations run
173
+ # THROUGH the unoverse container, so there is nothing to run them in until it is up — and
174
+ # db-setup restarts everything afterwards. A fixed five-minute window straddles that
175
+ # restart and reports the pre-migration errors as if they were current: every first
176
+ # deploy ended with 'relation "memories" does not exist' in red, describing a state that
177
+ # had already been fixed two minutes earlier by this very playbook.
178
+ #
179
+ # The question is "is anything wrong NOW", so the window starts when the services did.
170
180
  - name: "[12/14] Check for errors in service logs"
171
181
  shell: |
172
182
  cd /opt/gravity
173
- ERRORS=$(docker compose logs --tail=50 --since=5m 2>&1 | grep -iE 'error|fatal|panic|does not exist|ECONNREFUSED' | grep -v 'NOGROUP' | tail -10)
183
+ # Newest container start: everything before it belongs to a previous run.
184
+ SINCE=$(docker compose ps -q 2>/dev/null | xargs -r docker inspect -f '{{.State.StartedAt}}' 2>/dev/null | sort | tail -1)
185
+ [ -n "$SINCE" ] || SINCE=5m
186
+ ERRORS=$(docker compose logs --tail=50 --since="$SINCE" 2>&1 | grep -iE 'error|fatal|panic|does not exist|ECONNREFUSED' | grep -v 'NOGROUP' | tail -10)
174
187
  if [ -n "$ERRORS" ]; then
175
188
  echo "$ERRORS"
176
189
  exit 1
177
190
  else
178
- echo "No recent errors"
191
+ echo "No errors since the services started"
179
192
  fi
180
193
  register: log_errors
181
194
  ignore_errors: yes
@@ -141,26 +141,43 @@ process.stdin.on("end", () => {
141
141
 
142
142
  const groups = { create: [], update: [], delete: [], replace: [] };
143
143
  const supporting = { create: 0, update: 0, delete: 0, replace: 0 };
144
+ // LOSING SOMETHING IS NEVER ANONYMOUS. Counting the plumbing is right when things are
145
+ // being built — nobody needs eleven IAM attachments enumerated. It is wrong the moment
146
+ // anything is destroyed or replaced: "plus 1 supporting resource" was a security group
147
+ // about to be deleted, and the operator hit Ctrl-C when Terraform printed "Destroying"
148
+ // for something the summary had not named. Anything being taken away is listed.
149
+ const named = { delete: [], replace: [] };
144
150
 
145
151
  for (const r of plan.resource_changes ?? []) {
146
152
  const act = action(r.change ?? {});
147
153
  if (!act) continue;
148
154
  const d = describe(r);
149
155
  if (d) groups[act].push(d);
150
- else supporting[act]++;
156
+ else if (act === "delete" || act === "replace") {
157
+ // Turn the terraform type into something readable: aws_security_group → security group
158
+ const label = r.type.replace(/^(aws|digitalocean|random|time|archive)_/, "").replace(/_/g, " ");
159
+ const v = r.change?.before ?? r.change?.after ?? {};
160
+ named[act].push({ what: label.charAt(0).toUpperCase() + label.slice(1), detail: v.name ?? v.id ?? "" });
161
+ } else supporting[act]++;
151
162
  }
152
163
 
153
- const total = Object.values(groups).flat().length + Object.values(supporting).reduce((a, b) => a + b, 0);
164
+ const total = Object.values(groups).flat().length
165
+ + Object.values(supporting).reduce((a, b) => a + b, 0)
166
+ + named.delete.length + named.replace.length;
154
167
  const out = [];
155
168
  if (total === 0) {
156
169
  console.log(`\n ${green("●")} Your infrastructure already matches. Nothing to change.\n`);
157
170
  return;
158
171
  }
159
172
 
160
- const width = Math.max(...Object.values(groups).flat().map((d) => d.what.length), 0) + 2;
173
+ const width = Math.max(
174
+ ...Object.values(groups).flat().map((d) => d.what.length),
175
+ ...named.delete.map((d) => d.what.length),
176
+ ...named.replace.map((d) => d.what.length),
177
+ 0) + 2;
161
178
 
162
179
  for (const act of ["create", "update", "replace", "delete"]) {
163
- const list = groups[act];
180
+ const list = [...groups[act], ...(named[act] ?? [])];
164
181
  const extra = supporting[act];
165
182
  if (!list.length && !extra) continue;
166
183
  const colour = act === "delete" || act === "replace" ? red : act === "update" ? yellow : green;
@@ -194,6 +211,6 @@ process.stdin.on("end", () => {
194
211
  // something is not. `process.exitCode` rather than `process.exit`, which can cut off
195
212
  // the output above while it is still draining down a pipe.
196
213
  const destructive =
197
- groups.delete.length + groups.replace.length + supporting.delete + supporting.replace;
214
+ groups.delete.length + groups.replace.length + named.delete.length + named.replace.length;
198
215
  if (destructive) process.exitCode = 3;
199
216
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unoverse",
3
- "version": "0.1.103",
3
+ "version": "0.1.105",
4
4
  "description": "The Unoverse front door — create a Studio project, a universe, or a client app, and launch Studio.",
5
5
  "license": "SEE LICENSE IN README.md",
6
6
  "type": "module",