@rulebricks/cli 2.1.7 → 2.3.1

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.
Files changed (117) hide show
  1. package/README.md +51 -16
  2. package/cluster-setup/aws/README.md +96 -47
  3. package/cluster-setup/aws/check-aws-access.sh +216 -52
  4. package/cluster-setup/aws/parameters.json +13 -0
  5. package/cluster-setup/aws/rulebricks-cluster.cfn.yaml +355 -0
  6. package/cluster-setup/azure/README.md +103 -55
  7. package/cluster-setup/azure/check-aks-prereqs.sh +236 -56
  8. package/cluster-setup/azure/parameters.json +30 -0
  9. package/cluster-setup/azure/rulebricks-cluster.bicep +546 -0
  10. package/cluster-setup/gcp/README.md +51 -34
  11. package/cluster-setup/gcp/check-gke-prereqs.sh +222 -60
  12. package/dist/commands/backup.d.ts +5 -0
  13. package/dist/commands/backup.js +104 -0
  14. package/dist/commands/deploy.d.ts +3 -1
  15. package/dist/commands/deploy.js +226 -326
  16. package/dist/commands/destroy.d.ts +1 -1
  17. package/dist/commands/destroy.js +73 -123
  18. package/dist/commands/init.d.ts +5 -1
  19. package/dist/commands/init.js +78 -54
  20. package/dist/commands/list.d.ts +1 -0
  21. package/dist/commands/list.js +74 -0
  22. package/dist/commands/open.d.ts +1 -1
  23. package/dist/commands/open.js +4 -12
  24. package/dist/commands/redeploy.d.ts +6 -0
  25. package/dist/commands/redeploy.js +310 -0
  26. package/dist/commands/restore.d.ts +5 -0
  27. package/dist/commands/restore.js +338 -0
  28. package/dist/commands/status.js +62 -49
  29. package/dist/commands/upgrade.js +74 -51
  30. package/dist/components/DNSWaitScreen.d.ts +5 -1
  31. package/dist/components/DNSWaitScreen.js +47 -41
  32. package/dist/components/Wizard/WizardContext.d.ts +157 -36
  33. package/dist/components/Wizard/WizardContext.js +872 -160
  34. package/dist/components/Wizard/steps/CloudProviderStep.js +192 -107
  35. package/dist/components/Wizard/steps/DomainStep.js +5 -24
  36. package/dist/components/Wizard/steps/ExternalServicesStep.d.ts +6 -0
  37. package/dist/components/Wizard/steps/ExternalServicesStep.js +645 -0
  38. package/dist/components/Wizard/steps/FeatureConfigStep.d.ts +2 -1
  39. package/dist/components/Wizard/steps/FeatureConfigStep.js +739 -425
  40. package/dist/components/Wizard/steps/FeaturesStep.js +31 -35
  41. package/dist/components/Wizard/steps/ObservabilityStep.d.ts +6 -0
  42. package/dist/components/Wizard/steps/ObservabilityStep.js +137 -0
  43. package/dist/components/Wizard/steps/ReviewStep.d.ts +2 -1
  44. package/dist/components/Wizard/steps/ReviewStep.js +56 -12
  45. package/dist/components/Wizard/steps/StorageStep.d.ts +9 -0
  46. package/dist/components/Wizard/steps/StorageStep.js +592 -0
  47. package/dist/components/Wizard/steps/SupabaseCredentialsStep.js +20 -21
  48. package/dist/components/Wizard/steps/VersionStep.js +45 -23
  49. package/dist/components/Wizard/steps/index.d.ts +3 -3
  50. package/dist/components/Wizard/steps/index.js +3 -3
  51. package/dist/components/common/CommandApproval.d.ts +12 -0
  52. package/dist/components/common/CommandApproval.js +91 -0
  53. package/dist/components/common/DeploymentPicker.d.ts +14 -0
  54. package/dist/components/common/DeploymentPicker.js +16 -0
  55. package/dist/components/common/index.d.ts +2 -0
  56. package/dist/components/common/index.js +2 -0
  57. package/dist/index.js +94 -62
  58. package/dist/lib/cloudCli.d.ts +134 -63
  59. package/dist/lib/cloudCli.js +512 -220
  60. package/dist/lib/clusterSetupDefaults.d.ts +30 -0
  61. package/dist/lib/clusterSetupDefaults.js +64 -0
  62. package/dist/lib/commandApproval.d.ts +26 -0
  63. package/dist/lib/commandApproval.js +114 -0
  64. package/dist/lib/config.d.ts +12 -10
  65. package/dist/lib/config.js +91 -33
  66. package/dist/lib/configFixtures.d.ts +5 -0
  67. package/dist/lib/configFixtures.js +513 -0
  68. package/dist/lib/deploymentHealth.d.ts +32 -0
  69. package/dist/lib/deploymentHealth.js +157 -0
  70. package/dist/lib/dns.d.ts +1 -1
  71. package/dist/lib/dns.js +19 -1
  72. package/dist/lib/dns.test.d.ts +1 -0
  73. package/dist/lib/dns.test.js +27 -0
  74. package/dist/lib/dockerHub.d.ts +12 -1
  75. package/dist/lib/dockerHub.js +18 -8
  76. package/dist/lib/helm.d.ts +4 -0
  77. package/dist/lib/helm.js +16 -0
  78. package/dist/lib/helmValues.d.ts +25 -0
  79. package/dist/lib/helmValues.js +1762 -289
  80. package/dist/lib/helmValues.test.d.ts +1 -0
  81. package/dist/lib/helmValues.test.js +966 -0
  82. package/dist/lib/htpasswd.d.ts +1 -0
  83. package/dist/lib/htpasswd.js +15 -0
  84. package/dist/lib/kubernetes.d.ts +124 -17
  85. package/dist/lib/kubernetes.js +576 -145
  86. package/dist/lib/secrets.d.ts +23 -0
  87. package/dist/lib/secrets.js +158 -0
  88. package/dist/lib/validateValues.d.ts +31 -0
  89. package/dist/lib/validateValues.js +253 -0
  90. package/dist/lib/versions.d.ts +82 -11
  91. package/dist/lib/versions.js +131 -31
  92. package/dist/lib/versions.test.d.ts +1 -0
  93. package/dist/lib/versions.test.js +81 -0
  94. package/dist/lib/wizardSteps.d.ts +14 -0
  95. package/dist/lib/wizardSteps.js +23 -0
  96. package/dist/lib/workloadIdentity.d.ts +26 -0
  97. package/dist/lib/workloadIdentity.js +323 -0
  98. package/dist/lib/workloadIdentity.test.d.ts +1 -0
  99. package/dist/lib/workloadIdentity.test.js +57 -0
  100. package/dist/types/index.d.ts +1860 -164
  101. package/dist/types/index.js +518 -295
  102. package/package.json +9 -4
  103. package/schema/values.schema.json +1934 -0
  104. package/cluster-setup/aws/cluster.yaml +0 -33
  105. package/cluster-setup/azure/main.bicep +0 -282
  106. package/cluster-setup/azure/main.parameters.json +0 -21
  107. package/dist/components/Wizard/steps/CredentialsStep.d.ts +0 -6
  108. package/dist/components/Wizard/steps/CredentialsStep.js +0 -22
  109. package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +0 -5
  110. package/dist/components/Wizard/steps/DeploymentModeStep.js +0 -26
  111. package/dist/components/Wizard/steps/TierStep.d.ts +0 -6
  112. package/dist/components/Wizard/steps/TierStep.js +0 -29
  113. package/dist/lib/terraform.d.ts +0 -66
  114. package/dist/lib/terraform.js +0 -754
  115. package/terraform/aws/main.tf +0 -355
  116. package/terraform/azure/main.tf +0 -371
  117. package/terraform/gcp/main.tf +0 -407
@@ -0,0 +1,513 @@
1
+ /**
2
+ * A representative matrix of deployment configurations used to verify that
3
+ * buildHelmValues() always produces schema-valid, chart-consumable values.
4
+ *
5
+ * Shared between the unit harness (src/lib/helmValues.test.ts) and the
6
+ * end-to-end chart check (scripts/verify-against-chart.mjs) so both exercise the
7
+ * exact same scenarios.
8
+ */
9
+ const JWT_SECRET = "test-jwt-secret-with-at-least-32-characters-long";
10
+ function s3Storage() {
11
+ return {
12
+ provider: "s3",
13
+ cloudAuthMode: "workload-identity",
14
+ awsIamRoleArn: "arn:aws:iam::123456789012:role/rulebricks-cluster-rulebricks",
15
+ bucket: "rulebricks-cluster-data-123456789012",
16
+ region: "us-east-1",
17
+ paths: { decisionLogs: "decision-logs", dbBackups: "db-backups" },
18
+ };
19
+ }
20
+ function gcsStorage() {
21
+ return {
22
+ provider: "gcs",
23
+ cloudAuthMode: "workload-identity",
24
+ gcpServiceAccountEmail: "rulebricks@my-project.iam.gserviceaccount.com",
25
+ bucket: "rulebricks-cluster-data",
26
+ region: "us-central1",
27
+ paths: { decisionLogs: "decision-logs", dbBackups: "db-backups" },
28
+ };
29
+ }
30
+ function azureStorage(mode) {
31
+ return {
32
+ provider: "azure-blob",
33
+ cloudAuthMode: mode,
34
+ azureBlobClientId: mode === "workload-identity"
35
+ ? "11111111-1111-1111-1111-111111111111"
36
+ : undefined,
37
+ azureBlobTenantId: mode === "workload-identity"
38
+ ? "22222222-2222-2222-2222-222222222222"
39
+ : undefined,
40
+ azureBlobConnectionStringSecretRef: mode === "secret"
41
+ ? { name: "azure-storage", key: "connection-string" }
42
+ : undefined,
43
+ bucket: "rbstorageacct",
44
+ region: "eastus",
45
+ azureBlobContainer: "rulebricks-cluster-data",
46
+ paths: { decisionLogs: "decision-logs", dbBackups: "db-backups" },
47
+ };
48
+ }
49
+ function storageForProvider(provider) {
50
+ switch (provider) {
51
+ case "gcp":
52
+ return gcsStorage();
53
+ case "azure":
54
+ return azureStorage("workload-identity");
55
+ default:
56
+ return s3Storage();
57
+ }
58
+ }
59
+ function build(options) {
60
+ const provider = options.provider ?? "aws";
61
+ const database = options.database ?? "self-hosted";
62
+ const storage = options.storage ?? storageForProvider(provider);
63
+ const region = provider === "gcp" ? "us-central1" : provider === "azure" ? "eastus" : "us-east-1";
64
+ const databaseConfig = database === "self-hosted"
65
+ ? {
66
+ type: "self-hosted",
67
+ supabaseJwtSecret: JWT_SECRET,
68
+ supabaseDbPassword: "db-password-1234",
69
+ supabaseDashboardUser: "supabase",
70
+ supabaseDashboardPass: "dashboard-pass-1234",
71
+ }
72
+ : {
73
+ type: "supabase-cloud",
74
+ supabaseUrl: "https://abcdefghijkl.supabase.co",
75
+ supabaseAnonKey: "anon-key-value",
76
+ supabaseServiceKey: "service-key-value",
77
+ supabaseAccessToken: "sbp_access_token_value",
78
+ supabaseProjectRef: "abcdefghijkl",
79
+ };
80
+ // Matches the wizard default: in-cluster Prometheus only, no destination,
81
+ // unless metrics export (remoteWrite) or an explicit destination
82
+ // (e.g. config-file-only "local-grafana") is configured.
83
+ const monitoringDestination = options.monitoringDestination ?? options.remoteWrite?.destination;
84
+ const clickStackEnabled = options.clickStackEnabled ??
85
+ !(options.remoteWrite || options.tracing || options.appLogs);
86
+ return {
87
+ name: options.name,
88
+ infrastructure: {
89
+ mode: "existing",
90
+ provider,
91
+ region,
92
+ clusterName: "rulebricks-cluster",
93
+ nodeArchitecture: "amd64",
94
+ arm64TolerationRequired: false,
95
+ storageClass: provider === "gcp"
96
+ ? "pd-balanced"
97
+ : provider === "azure"
98
+ ? "managed-premium"
99
+ : "gp3",
100
+ },
101
+ domain: "rb.example.com",
102
+ adminEmail: "admin@example.com",
103
+ tlsEmail: "tls@example.com",
104
+ dns: { provider: "route53", autoManage: false },
105
+ smtp: {
106
+ host: "smtp.example.com",
107
+ port: 587,
108
+ user: "smtp-user",
109
+ pass: "smtp-pass",
110
+ from: "no-reply@example.com",
111
+ fromName: "Rulebricks",
112
+ },
113
+ database: databaseConfig,
114
+ storage,
115
+ externalServices: options.externalServices,
116
+ backup: database === "self-hosted"
117
+ ? {
118
+ enabled: options.backupEnabled ?? false,
119
+ schedule: "0 2 * * *",
120
+ retentionDays: 7,
121
+ }
122
+ : undefined,
123
+ features: {
124
+ ai: options.ai
125
+ ? { enabled: true, openaiApiKey: "sk-test-openai-key" }
126
+ : { enabled: false },
127
+ sso: options.sso
128
+ ? {
129
+ enabled: true,
130
+ provider: "okta",
131
+ url: "https://example.okta.com",
132
+ clientId: "sso-client-id",
133
+ clientSecret: "sso-client-secret",
134
+ }
135
+ : { enabled: false },
136
+ monitoring: {
137
+ enabled: true,
138
+ destination: monitoringDestination,
139
+ remoteWrite: options.remoteWrite,
140
+ },
141
+ observability: {
142
+ clickstack: {
143
+ enabled: clickStackEnabled,
144
+ },
145
+ },
146
+ tracing: options.tracing,
147
+ cache: options.cache,
148
+ logging: { sink: "console", appLogs: options.appLogs },
149
+ customEmails: options.customEmails
150
+ ? {
151
+ enabled: true,
152
+ subjects: {
153
+ invite: "Join your team",
154
+ confirmation: "Confirm Your Email",
155
+ recovery: "Reset Your Password",
156
+ emailChange: "Confirm Email Change",
157
+ },
158
+ templates: {
159
+ invite: "https://example.com/invite.html",
160
+ confirmation: "https://example.com/confirm.html",
161
+ recovery: "https://example.com/recovery.html",
162
+ emailChange: "https://example.com/change.html",
163
+ },
164
+ }
165
+ : undefined,
166
+ },
167
+ licenseKey: "test-license-key",
168
+ version: options.version ?? "1.8.17",
169
+ };
170
+ }
171
+ export function buildConfigMatrix() {
172
+ const cases = [
173
+ { name: "aws-self-hosted-minimal", provider: "aws" },
174
+ { name: "aws-backup-enabled", provider: "aws", backupEnabled: true },
175
+ {
176
+ name: "aws-all-features",
177
+ provider: "aws",
178
+ backupEnabled: true,
179
+ ai: true,
180
+ sso: true,
181
+ customEmails: true,
182
+ },
183
+ {
184
+ name: "aws-valkey-admin-ingress",
185
+ provider: "aws",
186
+ cache: {
187
+ valkeyAdmin: {
188
+ enabled: true,
189
+ exposure: "ingress",
190
+ basicAuthUsers: ["admin:$2a$10$abcdefghijklmnopqrstuv"],
191
+ allowedIPs: ["203.0.113.0/24"],
192
+ },
193
+ redisExporter: { enabled: true },
194
+ kafkaExporter: { enabled: true },
195
+ },
196
+ },
197
+ {
198
+ name: "aws-external-redis",
199
+ provider: "aws",
200
+ externalServices: {
201
+ redis: {
202
+ mode: "external",
203
+ external: { host: "redis.example.com", port: 6379, tls: true },
204
+ },
205
+ kafka: { mode: "embedded" },
206
+ },
207
+ },
208
+ {
209
+ name: "aws-external-kafka-msk",
210
+ provider: "aws",
211
+ externalServices: {
212
+ redis: { mode: "embedded" },
213
+ kafka: {
214
+ mode: "external",
215
+ external: {
216
+ preset: "aws-msk-iam",
217
+ brokers: "b-1.msk.example:9098,b-2.msk.example:9098",
218
+ topicPrefix: "com.rulebricks.",
219
+ ssl: true,
220
+ sasl: { mechanism: "aws-iam", region: "us-east-1" },
221
+ identity: {
222
+ awsRoleArn: "arn:aws:iam::123456789012:role/msk-access",
223
+ },
224
+ },
225
+ },
226
+ },
227
+ },
228
+ {
229
+ name: "aws-external-postgres",
230
+ provider: "aws",
231
+ externalServices: {
232
+ redis: { mode: "embedded" },
233
+ kafka: { mode: "embedded" },
234
+ postgres: {
235
+ mode: "external",
236
+ external: {
237
+ provider: "aws",
238
+ host: "db.cluster-xxxx.us-east-1.rds.amazonaws.com",
239
+ port: 5432,
240
+ database: "postgres",
241
+ bootstrap: {
242
+ enabled: true,
243
+ masterUsername: "postgres",
244
+ masterPassword: "master-pw-change-me",
245
+ appRole: "postgres",
246
+ },
247
+ },
248
+ },
249
+ },
250
+ },
251
+ {
252
+ name: "azure-external-postgres",
253
+ provider: "azure",
254
+ externalServices: {
255
+ redis: { mode: "embedded" },
256
+ kafka: { mode: "embedded" },
257
+ postgres: {
258
+ mode: "external",
259
+ external: {
260
+ provider: "azure",
261
+ host: "myserver.postgres.database.azure.com",
262
+ port: 5432,
263
+ database: "postgres",
264
+ bootstrap: {
265
+ enabled: true,
266
+ masterUsername: "pgadmin",
267
+ masterPassword: "master-pw-change-me",
268
+ appRole: "pgadmin",
269
+ },
270
+ },
271
+ },
272
+ },
273
+ },
274
+ {
275
+ name: "aws-remote-write-amp",
276
+ provider: "aws",
277
+ remoteWrite: {
278
+ destination: "aws-amp",
279
+ url: "https://aps-workspaces.us-east-1.amazonaws.com/workspaces/ws-123/api/v1/remote_write",
280
+ awsRegion: "us-east-1",
281
+ awsRoleArn: "arn:aws:iam::123456789012:role/rulebricks-cluster-metrics",
282
+ },
283
+ },
284
+ { name: "aws-supabase-cloud", provider: "aws", database: "supabase-cloud" },
285
+ {
286
+ name: "aws-local-grafana",
287
+ provider: "aws",
288
+ monitoringDestination: "local-grafana",
289
+ },
290
+ { name: "gcp-self-hosted", provider: "gcp" },
291
+ {
292
+ name: "gcp-external-kafka",
293
+ provider: "gcp",
294
+ externalServices: {
295
+ redis: { mode: "embedded" },
296
+ kafka: {
297
+ mode: "external",
298
+ external: {
299
+ preset: "gcp-managed",
300
+ brokers: "bootstrap.managedkafka.example:9092",
301
+ topicPrefix: "com.rulebricks.",
302
+ ssl: true,
303
+ sasl: {
304
+ mechanism: "plain",
305
+ username: "sa@project.iam.gserviceaccount.com",
306
+ password: "token",
307
+ },
308
+ },
309
+ },
310
+ },
311
+ },
312
+ { name: "azure-workload-identity", provider: "azure" },
313
+ {
314
+ name: "azure-storage-secret",
315
+ provider: "azure",
316
+ storage: azureStorage("secret"),
317
+ },
318
+ {
319
+ name: "azure-remote-write-managed",
320
+ provider: "azure",
321
+ remoteWrite: {
322
+ destination: "azure-monitor",
323
+ url: "https://example.eastus.metrics.ingest.monitor.azure.com/dataCollectionRules/dcr-1/streams/Microsoft-PrometheusMetrics/api/v1/write?api-version=2023-04-24",
324
+ authType: "managed-identity",
325
+ clientId: "33333333-3333-3333-3333-333333333333",
326
+ },
327
+ },
328
+ {
329
+ name: "azure-remote-write-workload",
330
+ provider: "azure",
331
+ remoteWrite: {
332
+ destination: "azure-monitor",
333
+ url: "https://example.eastus.metrics.ingest.monitor.azure.com/dataCollectionRules/dcr-1/streams/Microsoft-PrometheusMetrics/api/v1/write?api-version=2023-04-24",
334
+ authType: "workload-identity",
335
+ clientId: "33333333-3333-3333-3333-333333333333",
336
+ tenantId: "22222222-2222-2222-2222-222222222222",
337
+ },
338
+ },
339
+ {
340
+ name: "azure-remote-write-oauth",
341
+ provider: "azure",
342
+ remoteWrite: {
343
+ destination: "azure-monitor",
344
+ url: "https://example.eastus.metrics.ingest.monitor.azure.com/dataCollectionRules/dcr-1/streams/Microsoft-PrometheusMetrics/api/v1/write?api-version=2023-04-24",
345
+ authType: "oauth",
346
+ clientId: "33333333-3333-3333-3333-333333333333",
347
+ tenantId: "22222222-2222-2222-2222-222222222222",
348
+ clientSecretRef: { name: "azure-monitor", key: "client-secret" },
349
+ },
350
+ },
351
+ {
352
+ name: "remote-write-grafana-cloud",
353
+ provider: "aws",
354
+ remoteWrite: {
355
+ destination: "grafana-cloud",
356
+ url: "https://prometheus-prod-01.grafana.net/api/prom/push",
357
+ authType: "basic",
358
+ usernameSecretRef: { name: "grafana-cloud", key: "username" },
359
+ passwordSecretRef: { name: "grafana-cloud", key: "password" },
360
+ },
361
+ },
362
+ {
363
+ name: "remote-write-generic-bearer",
364
+ provider: "aws",
365
+ remoteWrite: {
366
+ destination: "generic",
367
+ url: "https://metrics.example.com/api/v1/write",
368
+ authType: "bearer",
369
+ bearerTokenSecretRef: { name: "metrics", key: "token" },
370
+ },
371
+ },
372
+ {
373
+ name: "aws-tracing-elastic",
374
+ provider: "aws",
375
+ tracing: {
376
+ enabled: true,
377
+ samplingRatio: 1,
378
+ elastic: {
379
+ endpoint: "https://rb-deployment.apm.us-east-1.aws.elastic-cloud.com:443",
380
+ authMode: "secret-token",
381
+ secretToken: "elastic-apm-secret-token",
382
+ },
383
+ },
384
+ },
385
+ {
386
+ name: "aws-app-logs-elasticsearch",
387
+ provider: "aws",
388
+ appLogs: {
389
+ enabled: true,
390
+ destination: "elasticsearch",
391
+ elasticsearch: {
392
+ endpoint: "https://rb-deployment.es.us-east-1.aws.elastic-cloud.com:9243",
393
+ index: "rulebricks-app-logs",
394
+ authMode: "basic",
395
+ username: "elastic",
396
+ password: "elastic-password",
397
+ },
398
+ },
399
+ },
400
+ {
401
+ name: "aws-app-logs-loki",
402
+ provider: "aws",
403
+ appLogs: {
404
+ enabled: true,
405
+ destination: "loki",
406
+ loki: {
407
+ endpoint: "https://loki.example.com/loki/api/v1/push",
408
+ labels: { app: "rulebricks", source: "app-logs" },
409
+ },
410
+ },
411
+ },
412
+ {
413
+ name: "aws-app-logs-generic-http",
414
+ provider: "aws",
415
+ appLogs: {
416
+ enabled: true,
417
+ destination: "generic",
418
+ generic: {
419
+ endpoint: "https://logs.example.com/ingest",
420
+ authHeader: "Bearer generic-log-token",
421
+ },
422
+ },
423
+ },
424
+ {
425
+ name: "aws-valkey-admin-internal",
426
+ provider: "aws",
427
+ cache: {
428
+ valkeyAdmin: { enabled: true, exposure: "internal" },
429
+ redisExporter: { enabled: true },
430
+ kafkaExporter: { enabled: true },
431
+ },
432
+ },
433
+ {
434
+ name: "aws-tracing-otlp",
435
+ provider: "aws",
436
+ tracing: {
437
+ enabled: true,
438
+ destination: "otlp",
439
+ otlp: {
440
+ endpoint: "https://otlp-gateway.example.com/otlp",
441
+ authMode: "bearer",
442
+ token: "otlp-bearer-token",
443
+ },
444
+ },
445
+ },
446
+ {
447
+ name: "azure-tracing-azure-monitor",
448
+ provider: "azure",
449
+ tracing: {
450
+ enabled: true,
451
+ destination: "azure-monitor",
452
+ azureMonitor: {
453
+ connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://eastus-1.in.applicationinsights.azure.com/",
454
+ },
455
+ },
456
+ },
457
+ {
458
+ name: "aws-tracing-and-app-logs",
459
+ provider: "aws",
460
+ tracing: {
461
+ enabled: true,
462
+ elastic: {
463
+ endpoint: "https://rb-deployment.apm.us-east-1.aws.elastic-cloud.com:443",
464
+ authMode: "api-key",
465
+ apiKey: "elastic-apm-api-key",
466
+ },
467
+ },
468
+ appLogs: {
469
+ enabled: true,
470
+ destination: "elasticsearch",
471
+ elasticsearch: {
472
+ endpoint: "https://rb-deployment.es.us-east-1.aws.elastic-cloud.com:9243",
473
+ authMode: "api-key",
474
+ apiKey: "elastic-es-api-key",
475
+ },
476
+ },
477
+ },
478
+ { name: "aws-version-latest", provider: "aws", version: "latest" },
479
+ {
480
+ name: "everything-external",
481
+ provider: "aws",
482
+ backupEnabled: true,
483
+ ai: true,
484
+ sso: true,
485
+ externalServices: {
486
+ redis: {
487
+ mode: "external",
488
+ external: {
489
+ host: "redis.example.com",
490
+ existingSecret: "redis-auth",
491
+ existingSecretKey: "redis-password",
492
+ tls: true,
493
+ },
494
+ },
495
+ kafka: {
496
+ mode: "external",
497
+ external: {
498
+ preset: "custom",
499
+ brokers: "kafka-1.example:9093",
500
+ topicPrefix: "com.rulebricks.",
501
+ ssl: true,
502
+ sasl: {
503
+ mechanism: "scram-sha-512",
504
+ username: "kafka-user",
505
+ password: "kafka-pass",
506
+ },
507
+ },
508
+ },
509
+ },
510
+ },
511
+ ];
512
+ return cases.map((options) => ({ name: options.name, config: build(options) }));
513
+ }
@@ -0,0 +1,32 @@
1
+ import { type PodStatus } from "./kubernetes.js";
2
+ import { DeploymentConfig, DeploymentState } from "../types/index.js";
3
+ export type DeploymentHealthKind = "online" | "installed-unreachable" | "installed-degraded" | "not-installed" | "destroyed" | "cluster-unreachable" | "config-error";
4
+ export interface DeploymentHealth {
5
+ name: string;
6
+ kind: DeploymentHealthKind;
7
+ config: DeploymentConfig | null;
8
+ state: DeploymentState | null;
9
+ namespace: string;
10
+ releaseName: string;
11
+ helmVersion: string | null;
12
+ pods: PodStatus[];
13
+ url: string | null;
14
+ httpReachable: boolean;
15
+ clusterError: string | null;
16
+ configError: string | null;
17
+ }
18
+ interface LoadDeploymentHealthOptions {
19
+ refreshKubeconfig?: boolean;
20
+ }
21
+ export declare function formatConfigError(error: unknown): string;
22
+ export declare function arePodsHealthy(pods: PodStatus[]): boolean;
23
+ export declare function classifyDeploymentHealth(input: {
24
+ state: DeploymentState | null;
25
+ helmVersion: string | null;
26
+ pods: PodStatus[];
27
+ httpReachable: boolean;
28
+ clusterError?: string | null;
29
+ }): DeploymentHealthKind;
30
+ export declare function checkDeploymentHttpHealth(deploymentUrl: string): Promise<boolean>;
31
+ export declare function loadDeploymentHealth(name: string, options?: LoadDeploymentHealthOptions): Promise<DeploymentHealth>;
32
+ export {};
@@ -0,0 +1,157 @@
1
+ import { ZodError } from "zod";
2
+ import { updateKubeconfig } from "./cloudCli.js";
3
+ import { loadDeploymentConfig, loadDeploymentState } from "./config.js";
4
+ import { getInstalledVersion } from "./helm.js";
5
+ import { checkClusterAccessible, getPodStatus, } from "./kubernetes.js";
6
+ import { getNamespace, getReleaseName, } from "../types/index.js";
7
+ export function formatConfigError(error) {
8
+ if (error instanceof ZodError) {
9
+ return error.issues
10
+ .map((issue) => {
11
+ const path = issue.path.length > 0 ? issue.path.join(".") : "config";
12
+ return `${path}: ${issue.message}`;
13
+ })
14
+ .join("\n");
15
+ }
16
+ return error instanceof Error ? error.message : "Invalid deployment config";
17
+ }
18
+ export function arePodsHealthy(pods) {
19
+ if (pods.length === 0)
20
+ return false;
21
+ return pods.every((pod) => pod.ready || pod.status === "Succeeded" || pod.status === "Completed");
22
+ }
23
+ export function classifyDeploymentHealth(input) {
24
+ if (input.clusterError) {
25
+ return input.state?.status === "destroyed"
26
+ ? "destroyed"
27
+ : "cluster-unreachable";
28
+ }
29
+ if (!input.helmVersion) {
30
+ return input.state?.status === "destroyed" ? "destroyed" : "not-installed";
31
+ }
32
+ if (!arePodsHealthy(input.pods)) {
33
+ return "installed-degraded";
34
+ }
35
+ if (!input.httpReachable) {
36
+ return "installed-unreachable";
37
+ }
38
+ return "online";
39
+ }
40
+ export async function checkDeploymentHttpHealth(deploymentUrl) {
41
+ try {
42
+ const baseUrl = deploymentUrl.startsWith("http")
43
+ ? deploymentUrl
44
+ : `https://${deploymentUrl}`;
45
+ const cleanUrl = baseUrl.replace(/\/$/, "");
46
+ const controller = new AbortController();
47
+ const timeout = setTimeout(() => controller.abort(), 10_000);
48
+ try {
49
+ const response = await fetch(`${cleanUrl}/api/health`, {
50
+ method: "GET",
51
+ signal: controller.signal,
52
+ headers: { Accept: "application/json" },
53
+ });
54
+ clearTimeout(timeout);
55
+ if (!response.ok)
56
+ return false;
57
+ const data = (await response.json());
58
+ return data.status === "OK";
59
+ }
60
+ catch {
61
+ clearTimeout(timeout);
62
+ return false;
63
+ }
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
69
+ async function ensureConfiguredCluster(config, refreshKubeconfig) {
70
+ let clusterError = await checkClusterAccessible();
71
+ if (clusterError &&
72
+ refreshKubeconfig &&
73
+ config.infrastructure.provider &&
74
+ config.infrastructure.region &&
75
+ config.infrastructure.clusterName) {
76
+ try {
77
+ await updateKubeconfig(config.infrastructure.provider, config.infrastructure.clusterName, config.infrastructure.region, {
78
+ gcpProjectId: config.infrastructure.gcpProjectId,
79
+ azureResourceGroup: config.infrastructure.azureResourceGroup,
80
+ });
81
+ clusterError = await checkClusterAccessible();
82
+ }
83
+ catch (error) {
84
+ const kubeconfigError = error instanceof Error ? error.message : "Unknown error";
85
+ return `${clusterError}\nKubeconfig refresh failed: ${kubeconfigError}`;
86
+ }
87
+ }
88
+ return clusterError;
89
+ }
90
+ export async function loadDeploymentHealth(name, options = {}) {
91
+ const state = await loadDeploymentState(name);
92
+ const namespace = state?.application?.namespace || getNamespace(name);
93
+ const releaseName = getReleaseName(name);
94
+ let config;
95
+ try {
96
+ config = await loadDeploymentConfig(name);
97
+ }
98
+ catch (error) {
99
+ return {
100
+ name,
101
+ kind: "config-error",
102
+ config: null,
103
+ state,
104
+ namespace,
105
+ releaseName,
106
+ helmVersion: null,
107
+ pods: [],
108
+ url: state?.application?.url || null,
109
+ httpReachable: false,
110
+ clusterError: null,
111
+ configError: formatConfigError(error),
112
+ };
113
+ }
114
+ const url = state?.application?.url || `https://${config.domain}`;
115
+ const clusterError = await ensureConfiguredCluster(config, options.refreshKubeconfig ?? false);
116
+ if (clusterError) {
117
+ return {
118
+ name,
119
+ kind: state?.status === "destroyed" ? "destroyed" : "cluster-unreachable",
120
+ config,
121
+ state,
122
+ namespace,
123
+ releaseName,
124
+ helmVersion: null,
125
+ pods: [],
126
+ url,
127
+ httpReachable: false,
128
+ clusterError,
129
+ configError: null,
130
+ };
131
+ }
132
+ const [helmVersion, pods, httpReachable] = await Promise.all([
133
+ getInstalledVersion(releaseName, namespace),
134
+ getPodStatus(namespace),
135
+ checkDeploymentHttpHealth(url),
136
+ ]);
137
+ const kind = classifyDeploymentHealth({
138
+ state,
139
+ helmVersion,
140
+ pods,
141
+ httpReachable,
142
+ });
143
+ return {
144
+ name,
145
+ kind,
146
+ config,
147
+ state,
148
+ namespace,
149
+ releaseName,
150
+ helmVersion,
151
+ pods,
152
+ url,
153
+ httpReachable,
154
+ clusterError: null,
155
+ configError: null,
156
+ };
157
+ }