anbaric-cloud-hosting 1.68.2 → 1.69.0
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/package.json +7 -7
- package/src/app-management/BaseBuildLayer.ts +30 -8
- package/src/app-management/BuildLayer.ts +12 -3
- package/src/app-management/FargateBuildLayer.ts +83 -2
- package/src/data-store/AppSettingsStore.ts +30 -0
- package/src/data-store/InMemoryAppSettingsStore.ts +27 -0
- package/src/data-store/PlatformMigrations.ts +11 -0
- package/src/data-store/PostgresAppSettingsStore.ts +40 -0
- package/src/hosting/HostingServer.ts +4 -2
- package/src/hosting/handlers/AppsHandler.ts +33 -5
- package/src/hosting/pages/platform-ui.html +1 -1
- package/src/main.ts +17 -3
- package/src/usage/UsageReporter.ts +10 -7
package/src/main.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {FargateBuildLayer} from "./app-management/FargateBuildLayer";
|
|
|
26
26
|
import {DocGenerator} from "./docs/DocGenerator";
|
|
27
27
|
import {GatewayDocModel} from "./docs/GatewayDocModel";
|
|
28
28
|
import {PostgresAppDocsStore} from "./data-store/PostgresAppDocsStore";
|
|
29
|
+
import {PostgresAppSettingsStore} from "./data-store/PostgresAppSettingsStore";
|
|
29
30
|
import {PostgresEntitlementStore} from "./data-store/PostgresEntitlementStore";
|
|
30
31
|
import {PostgresUserDirectory} from "./data-store/PostgresUserDirectory";
|
|
31
32
|
import {HttpMembershipService} from "./auth/HttpMembershipService";
|
|
@@ -41,6 +42,12 @@ import {CentralBilling} from "./billing/CentralBilling";
|
|
|
41
42
|
|
|
42
43
|
new FatalErrors().install();
|
|
43
44
|
|
|
45
|
+
// "key=value,key=value", as the hosting module writes it; nothing means untagged.
|
|
46
|
+
const resourceTags = (spec? : string) : Record<string, string> | undefined => {
|
|
47
|
+
const entries = (spec ?? "").split(",").map(pair => pair.split("=")).filter(pair => pair.length === 2 && pair[0].trim() && pair[1].trim());
|
|
48
|
+
return entries.length === 0 ? undefined : Object.fromEntries(entries.map(([key, value]) => [key.trim(), value.trim()]));
|
|
49
|
+
};
|
|
50
|
+
|
|
44
51
|
const pool = new Pool({ connectionString: process.env.ANBARIC_DATABASE_URL });
|
|
45
52
|
await ensureSchema(pool);
|
|
46
53
|
|
|
@@ -54,6 +61,7 @@ const appsDir = process.env.ANBARIC_APPS_DIR ?? "/tmp/anbaric-apps";
|
|
|
54
61
|
// Generates user docs from an app's source on deploy; no-ops without an AI
|
|
55
62
|
// gateway token. The build layer calls it fire-and-forget off the deploy path.
|
|
56
63
|
const docGenerator = new DocGenerator(new GatewayDocModel(), new PostgresAppDocsStore(pool));
|
|
64
|
+
const appSettings = new PostgresAppSettingsStore(pool);
|
|
57
65
|
|
|
58
66
|
const buildLayer = process.env.ANBARIC_BUILD_LAYER === "docker"
|
|
59
67
|
? new DockerBuildLayer(appsDir, {
|
|
@@ -85,6 +93,7 @@ const buildLayer = process.env.ANBARIC_BUILD_LAYER === "docker"
|
|
|
85
93
|
sqlDatabaseUrlSecretArn: process.env.ANBARIC_AWS_APP_SQL_URL_SECRET,
|
|
86
94
|
sqlSchema: process.env.ANBARIC_SQL_SCHEMA,
|
|
87
95
|
docGenerator,
|
|
96
|
+
tags: resourceTags(process.env.ANBARIC_RESOURCE_TAGS),
|
|
88
97
|
})
|
|
89
98
|
: undefined;
|
|
90
99
|
|
|
@@ -147,7 +156,7 @@ const server = new HostingServer(jobs, queue, registry, buildLayer,
|
|
|
147
156
|
tokenAuthenticator, process.env.ANBARIC_TENANT, auditRecords, plugins,
|
|
148
157
|
new PostgresJobRunSchedulePersistence(pool), await loadNotifier(process.env.ANBARIC_NOTIFIER_MODULE),
|
|
149
158
|
new PostgresEntitlementStore(pool), new PostgresUserDirectory(pool), memberships,
|
|
150
|
-
(appId) => new PostgresPromptManager(pool, appId), fileStorageFor, subdomains, billing);
|
|
159
|
+
(appId) => new PostgresPromptManager(pool, appId), fileStorageFor, subdomains, billing, appSettings);
|
|
151
160
|
const port = await server.listen(hostingPort);
|
|
152
161
|
const internal = await server.listenInternal(internalPort);
|
|
153
162
|
|
|
@@ -163,9 +172,14 @@ if (buildLayer && process.env.ANBARIC_TENANT && process.env.ANBARIC_CLI_KEY_LOOK
|
|
|
163
172
|
process.env.ANBARIC_CLI_KEY_LOOKUP_URL,
|
|
164
173
|
process.env.ANBARIC_CLI_KEY_LOOKUP_SECRET ?? "",
|
|
165
174
|
process.env.ANBARIC_TENANT,
|
|
166
|
-
() => {
|
|
175
|
+
async () => {
|
|
167
176
|
const live = buildLayer.list().filter(app => app.status === "running" || app.status === "draining");
|
|
168
|
-
|
|
177
|
+
const zeroDowntime = new Set((await appSettings.list()).filter(each => each.zeroDowntime).map(each => each.appName));
|
|
178
|
+
return {
|
|
179
|
+
apps: live.length,
|
|
180
|
+
large: live.filter(app => app.size === "large").length,
|
|
181
|
+
zeroDowntime: live.filter(app => zeroDowntime.has(app.appName)).length,
|
|
182
|
+
};
|
|
169
183
|
},
|
|
170
184
|
).start();
|
|
171
185
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
type FetchFn = (url : string, init : RequestInit) => Promise<Response>;
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/* How many apps are running, how many of those are on a large instance, and
|
|
4
|
+
how many have zero-downtime deploys turned on. Counted afresh each report,
|
|
5
|
+
and allowed to look something up on the way. */
|
|
6
|
+
type AppCounts = { apps : number, large : number, zeroDowntime? : number };
|
|
7
|
+
type CountApps = () => AppCounts | Promise<AppCounts>;
|
|
5
8
|
|
|
6
9
|
const REPORT_INTERVAL_MS = 60 * 60_000;
|
|
7
10
|
|
|
@@ -37,7 +40,7 @@ class UsageReporter {
|
|
|
37
40
|
const response = await this.fetchFn(`${this.centralUrl}/usage`, {
|
|
38
41
|
method: "POST",
|
|
39
42
|
headers: { "content-type": "application/json", "x-anbaric-central-key": this.secret },
|
|
40
|
-
body: JSON.stringify({ slug: this.tenantSlug, ...this.counted(), day: this.today() }),
|
|
43
|
+
body: JSON.stringify({ slug: this.tenantSlug, ...(await this.counted()), day: this.today() }),
|
|
41
44
|
});
|
|
42
45
|
if (! response.ok) console.warn(`[usage] central answered ${response.status} to the app count`);
|
|
43
46
|
return response.ok;
|
|
@@ -47,9 +50,9 @@ class UsageReporter {
|
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
private counted() : { apps : number, largeApps : number } {
|
|
51
|
-
const { apps, large } = this.countApps();
|
|
52
|
-
return { apps, largeApps: large };
|
|
53
|
+
private async counted() : Promise<{ apps : number, largeApps : number, zeroDowntimeApps : number }> {
|
|
54
|
+
const { apps, large, zeroDowntime } = await this.countApps();
|
|
55
|
+
return { apps, largeApps: large, zeroDowntimeApps: zeroDowntime ?? 0 };
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
// The day in UTC, so every tenant's day starts at the same moment however
|
|
@@ -66,4 +69,4 @@ class UsageReporter {
|
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
export { UsageReporter };
|
|
69
|
-
export type { CountApps };
|
|
72
|
+
export type { CountApps, AppCounts };
|