@rulebricks/cli 2.0.0 → 2.0.2
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/README.md +3 -3
- package/benchmarks/README.md +98 -0
- package/benchmarks/Test Flow.rbf +4088 -0
- package/benchmarks/benchmark-flow.json +26 -0
- package/benchmarks/lib/payload.js +101 -0
- package/benchmarks/lib/report.js +929 -0
- package/benchmarks/qps-test.js +136 -0
- package/benchmarks/run-qps-test.sh +115 -0
- package/benchmarks/run-throughput-test.sh +123 -0
- package/benchmarks/throughput-report.html +632 -0
- package/benchmarks/throughput-results.json +298 -0
- package/benchmarks/throughput-test.js +159 -0
- package/dist/commands/benchmark.d.ts +11 -0
- package/dist/commands/benchmark.js +173 -0
- package/dist/commands/deploy.js +15 -4
- package/dist/commands/destroy.js +2 -2
- package/dist/commands/logs.js +1 -0
- package/dist/components/Wizard/steps/BenchmarkSteps.d.ts +31 -0
- package/dist/components/Wizard/steps/BenchmarkSteps.js +304 -0
- package/dist/components/Wizard/steps/DatabaseStep.js +49 -35
- package/dist/index.js +42 -6
- package/dist/lib/benchmark.d.ts +63 -0
- package/dist/lib/benchmark.js +466 -0
- package/dist/lib/dns.d.ts +3 -1
- package/dist/lib/dns.js +138 -56
- package/dist/lib/helm.d.ts +14 -1
- package/dist/lib/helm.js +36 -1
- package/dist/lib/kubernetes.js +2 -0
- package/dist/types/index.d.ts +90 -0
- package/dist/types/index.js +51 -0
- package/package.json +8 -6
- package/terraform/aws/main.tf +22 -0
- package/terraform/azure/main.tf +45 -0
- package/terraform/gcp/main.tf +34 -0
- /package/{email-templates → templates}/email_change.html +0 -0
- /package/{email-templates → templates}/invite.html +0 -0
- /package/{email-templates → templates}/password_change.html +0 -0
- /package/{email-templates → templates}/verify.html +0 -0
package/dist/lib/dns.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
import dns from "dns";
|
|
2
|
-
import { promisify } from "util";
|
|
1
|
+
import * as dns from "dns";
|
|
3
2
|
import { execa } from "execa";
|
|
4
3
|
import { DEFAULT_NAMESPACE } from "../types/index.js";
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
/**
|
|
5
|
+
* DNS resolvers to try in order:
|
|
6
|
+
* - null = system default
|
|
7
|
+
* - Google Public DNS
|
|
8
|
+
* - Cloudflare DNS
|
|
9
|
+
*/
|
|
10
|
+
const DNS_RESOLVERS = [
|
|
11
|
+
null, // System default
|
|
12
|
+
["8.8.8.8", "8.8.4.4"], // Google
|
|
13
|
+
["1.1.1.1", "1.0.0.1"], // Cloudflare
|
|
14
|
+
];
|
|
15
|
+
/** Timeout for each DNS lookup attempt (ms) */
|
|
16
|
+
const DNS_TIMEOUT_MS = 5000;
|
|
7
17
|
/**
|
|
8
18
|
* Helper to detect if a string is an IP address
|
|
9
19
|
*/
|
|
@@ -19,81 +29,153 @@ function cnameMatchesTarget(cnameRecords, expectedTarget) {
|
|
|
19
29
|
r.replace(/\.$/, "") === expectedTarget.replace(/\.$/, ""));
|
|
20
30
|
}
|
|
21
31
|
/**
|
|
22
|
-
*
|
|
32
|
+
* Wrap a promise with a timeout
|
|
23
33
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
function withTimeout(promise, ms) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const timer = setTimeout(() => reject(new Error("DNS lookup timeout")), ms);
|
|
37
|
+
promise
|
|
38
|
+
.then((result) => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
resolve(result);
|
|
41
|
+
})
|
|
42
|
+
.catch((err) => {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
reject(err);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create promisified resolver functions for a specific DNS server
|
|
50
|
+
*/
|
|
51
|
+
function createResolver(servers) {
|
|
52
|
+
const resolver = new dns.Resolver();
|
|
53
|
+
if (servers) {
|
|
54
|
+
resolver.setServers(servers);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
resolve4: (hostname) => new Promise((resolve, reject) => {
|
|
58
|
+
resolver.resolve4(hostname, (err, addresses) => {
|
|
59
|
+
if (err)
|
|
60
|
+
reject(err);
|
|
61
|
+
else
|
|
62
|
+
resolve(addresses);
|
|
63
|
+
});
|
|
64
|
+
}),
|
|
65
|
+
resolveCname: (hostname) => new Promise((resolve, reject) => {
|
|
66
|
+
resolver.resolveCname(hostname, (err, addresses) => {
|
|
67
|
+
if (err)
|
|
68
|
+
reject(err);
|
|
69
|
+
else
|
|
70
|
+
resolve(addresses);
|
|
71
|
+
});
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check DNS record with a specific resolver
|
|
77
|
+
*/
|
|
78
|
+
async function checkWithResolver(hostname, expectedTarget, servers) {
|
|
79
|
+
const { resolve4, resolveCname } = createResolver(servers);
|
|
80
|
+
// If expected target is a hostname (not an IP), check CNAME first
|
|
81
|
+
if (expectedTarget && !isIPAddress(expectedTarget)) {
|
|
82
|
+
try {
|
|
83
|
+
const cnameRecords = await withTimeout(resolveCname(hostname), DNS_TIMEOUT_MS);
|
|
84
|
+
const matchesTarget = cnameMatchesTarget(cnameRecords, expectedTarget);
|
|
85
|
+
return {
|
|
86
|
+
resolved: true,
|
|
87
|
+
records: cnameRecords,
|
|
88
|
+
type: "CNAME",
|
|
89
|
+
matchesTarget,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// No CNAME record exists - try A record check
|
|
94
|
+
// For hostname targets, we need to resolve both the hostname and target
|
|
95
|
+
// to IPs and compare them
|
|
28
96
|
try {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
// Don't fall through to A record check, as that would incorrectly
|
|
32
|
-
// compare IPs against a hostname target
|
|
33
|
-
const matchesTarget = cnameMatchesTarget(cnameRecords, expectedTarget);
|
|
97
|
+
const [hostnameIPs, targetIPs] = await withTimeout(Promise.all([resolve4(hostname), resolve4(expectedTarget)]), DNS_TIMEOUT_MS);
|
|
98
|
+
const matchesTarget = hostnameIPs.some((ip) => targetIPs.includes(ip));
|
|
34
99
|
return {
|
|
35
100
|
resolved: true,
|
|
36
|
-
records:
|
|
37
|
-
type: "
|
|
101
|
+
records: hostnameIPs,
|
|
102
|
+
type: "A",
|
|
38
103
|
matchesTarget,
|
|
39
104
|
};
|
|
40
105
|
}
|
|
41
106
|
catch {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
resolve4(expectedTarget),
|
|
49
|
-
]);
|
|
50
|
-
// Check if any of the hostname's IPs match any of the target's IPs
|
|
51
|
-
const matchesTarget = hostnameIPs.some((ip) => targetIPs.includes(ip));
|
|
52
|
-
return {
|
|
53
|
-
resolved: true,
|
|
54
|
-
records: hostnameIPs,
|
|
55
|
-
type: "A",
|
|
56
|
-
matchesTarget,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
// Could not resolve - DNS not configured
|
|
61
|
-
return {
|
|
62
|
-
resolved: false,
|
|
63
|
-
records: [],
|
|
64
|
-
type: null,
|
|
65
|
-
matchesTarget: false,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
107
|
+
return {
|
|
108
|
+
resolved: false,
|
|
109
|
+
records: [],
|
|
110
|
+
type: null,
|
|
111
|
+
matchesTarget: false,
|
|
112
|
+
};
|
|
68
113
|
}
|
|
69
114
|
}
|
|
70
|
-
|
|
115
|
+
}
|
|
116
|
+
// Expected target is an IP address - check A records directly
|
|
117
|
+
try {
|
|
118
|
+
const aRecords = await withTimeout(resolve4(hostname), DNS_TIMEOUT_MS);
|
|
119
|
+
const matchesTarget = expectedTarget
|
|
120
|
+
? aRecords.some((r) => r === expectedTarget)
|
|
121
|
+
: true;
|
|
122
|
+
return {
|
|
123
|
+
resolved: true,
|
|
124
|
+
records: aRecords,
|
|
125
|
+
type: "A",
|
|
126
|
+
matchesTarget,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// Try CNAME if A fails (fallback for when no expected target)
|
|
71
131
|
try {
|
|
72
|
-
const
|
|
132
|
+
const cnameRecords = await withTimeout(resolveCname(hostname), DNS_TIMEOUT_MS);
|
|
73
133
|
const matchesTarget = expectedTarget
|
|
74
|
-
?
|
|
134
|
+
? cnameMatchesTarget(cnameRecords, expectedTarget)
|
|
75
135
|
: true;
|
|
76
136
|
return {
|
|
77
137
|
resolved: true,
|
|
78
|
-
records:
|
|
79
|
-
type: "
|
|
138
|
+
records: cnameRecords,
|
|
139
|
+
type: "CNAME",
|
|
80
140
|
matchesTarget,
|
|
81
141
|
};
|
|
82
142
|
}
|
|
83
143
|
catch {
|
|
84
|
-
// Try CNAME if A fails (fallback for when no expected target)
|
|
85
|
-
const cnameRecords = await resolveCname(hostname);
|
|
86
|
-
const matchesTarget = expectedTarget
|
|
87
|
-
? cnameMatchesTarget(cnameRecords, expectedTarget)
|
|
88
|
-
: true;
|
|
89
144
|
return {
|
|
90
|
-
resolved:
|
|
91
|
-
records:
|
|
92
|
-
type:
|
|
93
|
-
matchesTarget,
|
|
145
|
+
resolved: false,
|
|
146
|
+
records: [],
|
|
147
|
+
type: null,
|
|
148
|
+
matchesTarget: false,
|
|
94
149
|
};
|
|
95
150
|
}
|
|
96
151
|
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Checks if a DNS record resolves using multiple DNS resolvers for reliability.
|
|
155
|
+
* Tries system DNS first, then Google (8.8.8.8), then Cloudflare (1.1.1.1).
|
|
156
|
+
* Returns success if ANY resolver confirms the record matches the target.
|
|
157
|
+
*/
|
|
158
|
+
export async function checkDNSRecord(hostname, expectedTarget) {
|
|
159
|
+
// Try each resolver in sequence until one succeeds with a matching target
|
|
160
|
+
for (const servers of DNS_RESOLVERS) {
|
|
161
|
+
try {
|
|
162
|
+
const result = await checkWithResolver(hostname, expectedTarget, servers);
|
|
163
|
+
// If we found a matching record, return immediately
|
|
164
|
+
if (result.resolved && result.matchesTarget) {
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
// If resolved but doesn't match, continue to next resolver
|
|
168
|
+
// (different resolvers might have different cache states)
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// This resolver failed entirely, try the next one
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// No resolver found a matching record - do one final check with system DNS
|
|
175
|
+
// to return whatever we can find (even if not matching)
|
|
176
|
+
try {
|
|
177
|
+
return await checkWithResolver(hostname, expectedTarget, null);
|
|
178
|
+
}
|
|
97
179
|
catch {
|
|
98
180
|
return {
|
|
99
181
|
resolved: false,
|
package/dist/lib/helm.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare function fetchChartVersions(): Promise<ChartVersion[]>;
|
|
|
16
16
|
*/
|
|
17
17
|
export declare function getInstalledVersion(releaseName: string, namespace: string): Promise<string | null>;
|
|
18
18
|
/**
|
|
19
|
-
* Installs the Rulebricks Helm chart
|
|
19
|
+
* Installs the Rulebricks Helm chart (use installOrUpgradeChart for idempotent operations)
|
|
20
20
|
*/
|
|
21
21
|
export declare function installChart(deploymentName: string, options: {
|
|
22
22
|
releaseName: string;
|
|
@@ -26,6 +26,19 @@ export declare function installChart(deploymentName: string, options: {
|
|
|
26
26
|
timeout?: string;
|
|
27
27
|
createNamespace?: boolean;
|
|
28
28
|
}): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Installs or upgrades the Rulebricks Helm chart (idempotent operation).
|
|
31
|
+
* Uses `helm upgrade --install` which will install if release doesn't exist,
|
|
32
|
+
* or upgrade if it does. This is safe to run multiple times.
|
|
33
|
+
*/
|
|
34
|
+
export declare function installOrUpgradeChart(deploymentName: string, options: {
|
|
35
|
+
releaseName: string;
|
|
36
|
+
namespace: string;
|
|
37
|
+
version?: string;
|
|
38
|
+
wait?: boolean;
|
|
39
|
+
timeout?: string;
|
|
40
|
+
createNamespace?: boolean;
|
|
41
|
+
}): Promise<void>;
|
|
29
42
|
/**
|
|
30
43
|
* Upgrades the Rulebricks Helm chart
|
|
31
44
|
*/
|
package/dist/lib/helm.js
CHANGED
|
@@ -102,7 +102,7 @@ export async function getInstalledVersion(releaseName, namespace) {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
105
|
-
* Installs the Rulebricks Helm chart
|
|
105
|
+
* Installs the Rulebricks Helm chart (use installOrUpgradeChart for idempotent operations)
|
|
106
106
|
*/
|
|
107
107
|
export async function installChart(deploymentName, options) {
|
|
108
108
|
const { releaseName, namespace, version, wait = true, timeout = "15m", createNamespace = true, } = options;
|
|
@@ -133,6 +133,41 @@ export async function installChart(deploymentName, options) {
|
|
|
133
133
|
throw new Error(`Helm install failed:\n${getErrorMessage(error)}`);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Installs or upgrades the Rulebricks Helm chart (idempotent operation).
|
|
138
|
+
* Uses `helm upgrade --install` which will install if release doesn't exist,
|
|
139
|
+
* or upgrade if it does. This is safe to run multiple times.
|
|
140
|
+
*/
|
|
141
|
+
export async function installOrUpgradeChart(deploymentName, options) {
|
|
142
|
+
const { releaseName, namespace, version, wait = true, timeout = "15m", createNamespace = true, } = options;
|
|
143
|
+
const valuesPath = getHelmValuesPath(deploymentName);
|
|
144
|
+
const args = [
|
|
145
|
+
"upgrade",
|
|
146
|
+
"--install", // This makes it idempotent - install if not exists, upgrade if exists
|
|
147
|
+
releaseName,
|
|
148
|
+
HELM_CHART_OCI,
|
|
149
|
+
"--namespace",
|
|
150
|
+
namespace,
|
|
151
|
+
"--values",
|
|
152
|
+
valuesPath,
|
|
153
|
+
];
|
|
154
|
+
if (version) {
|
|
155
|
+
args.push("--version", version);
|
|
156
|
+
}
|
|
157
|
+
if (createNamespace) {
|
|
158
|
+
args.push("--create-namespace");
|
|
159
|
+
}
|
|
160
|
+
if (wait) {
|
|
161
|
+
args.push("--wait");
|
|
162
|
+
args.push("--timeout", timeout);
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
await execa("helm", args);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
throw new Error(`Helm install/upgrade failed:\n${getErrorMessage(error)}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
136
171
|
/**
|
|
137
172
|
* Upgrades the Rulebricks Helm chart
|
|
138
173
|
*/
|
package/dist/lib/kubernetes.js
CHANGED
|
@@ -435,6 +435,7 @@ export const VALID_LOG_COMPONENTS = [
|
|
|
435
435
|
"kafka",
|
|
436
436
|
"supabase",
|
|
437
437
|
"traefik",
|
|
438
|
+
"redis",
|
|
438
439
|
];
|
|
439
440
|
/**
|
|
440
441
|
* Pod name patterns for each component.
|
|
@@ -447,6 +448,7 @@ const COMPONENT_POD_PATTERNS = {
|
|
|
447
448
|
kafka: ["kafka"],
|
|
448
449
|
supabase: ["supabase", "db", "postgres"],
|
|
449
450
|
traefik: ["traefik"],
|
|
451
|
+
redis: ["redis", "dragonfly", "keydb"],
|
|
450
452
|
};
|
|
451
453
|
/**
|
|
452
454
|
* Gets pods for a specific component in a deployment.
|
package/dist/types/index.d.ts
CHANGED
|
@@ -716,3 +716,93 @@ export declare function getNamespace(deploymentName: string): string;
|
|
|
716
716
|
* Example: rulebricks-prod, rulebricks-staging
|
|
717
717
|
*/
|
|
718
718
|
export declare function getReleaseName(deploymentName: string): string;
|
|
719
|
+
/** Benchmark test mode - QPS measures requests/sec, throughput measures solutions/sec */
|
|
720
|
+
export type BenchmarkTestMode = "qps" | "throughput";
|
|
721
|
+
/** Benchmark preset intensity level */
|
|
722
|
+
export type BenchmarkPreset = "light" | "medium" | "heavy" | "custom";
|
|
723
|
+
/** Configuration for a benchmark test run */
|
|
724
|
+
export interface BenchmarkConfig {
|
|
725
|
+
/** Name of the deployment being tested */
|
|
726
|
+
deploymentName: string;
|
|
727
|
+
/** Full API URL including flow slug (e.g., https://domain.com/api/v1/flows/benchmark-flow) */
|
|
728
|
+
apiUrl: string;
|
|
729
|
+
/** Rulebricks API key */
|
|
730
|
+
apiKey: string;
|
|
731
|
+
/** Test mode - qps or throughput */
|
|
732
|
+
testMode: BenchmarkTestMode;
|
|
733
|
+
/** Test duration (e.g., "2m", "4m") */
|
|
734
|
+
testDuration: string;
|
|
735
|
+
/** Target requests per second */
|
|
736
|
+
targetRps: number;
|
|
737
|
+
/** Bulk size - only for throughput mode (payloads per request) */
|
|
738
|
+
bulkSize?: number;
|
|
739
|
+
}
|
|
740
|
+
/** Result metrics from a benchmark test */
|
|
741
|
+
export interface BenchmarkMetrics {
|
|
742
|
+
/** Actual requests per second achieved */
|
|
743
|
+
actualRps: number;
|
|
744
|
+
/** Success rate as percentage (0-100) */
|
|
745
|
+
successRate: number;
|
|
746
|
+
/** P50 latency in milliseconds */
|
|
747
|
+
p50Latency: number;
|
|
748
|
+
/** P90 latency in milliseconds */
|
|
749
|
+
p90Latency: number;
|
|
750
|
+
/** P95 latency in milliseconds */
|
|
751
|
+
p95Latency: number;
|
|
752
|
+
/** P99 latency in milliseconds */
|
|
753
|
+
p99Latency: number;
|
|
754
|
+
/** Minimum latency in milliseconds */
|
|
755
|
+
minLatency: number;
|
|
756
|
+
/** Maximum latency in milliseconds */
|
|
757
|
+
maxLatency: number;
|
|
758
|
+
/** Average latency in milliseconds */
|
|
759
|
+
avgLatency: number;
|
|
760
|
+
/** Total requests made */
|
|
761
|
+
totalRequests: number;
|
|
762
|
+
/** Number of failed requests */
|
|
763
|
+
failedRequests: number;
|
|
764
|
+
/** Test duration in seconds */
|
|
765
|
+
testDuration: number;
|
|
766
|
+
/** Total data sent in bytes */
|
|
767
|
+
dataSent: number;
|
|
768
|
+
/** Total data received in bytes */
|
|
769
|
+
dataReceived: number;
|
|
770
|
+
/** Max virtual users used */
|
|
771
|
+
maxVUs: number;
|
|
772
|
+
/** For throughput tests: actual throughput (solutions/sec) */
|
|
773
|
+
actualThroughput?: number;
|
|
774
|
+
/** For throughput tests: total payloads processed */
|
|
775
|
+
totalPayloads?: number;
|
|
776
|
+
}
|
|
777
|
+
/** Result of a benchmark test run */
|
|
778
|
+
export interface BenchmarkResult {
|
|
779
|
+
/** Whether the test completed successfully */
|
|
780
|
+
success: boolean;
|
|
781
|
+
/** Path to the output directory */
|
|
782
|
+
outputDir: string;
|
|
783
|
+
/** Path to the HTML report */
|
|
784
|
+
reportPath: string;
|
|
785
|
+
/** Path to the JSON results */
|
|
786
|
+
resultsPath: string;
|
|
787
|
+
/** Parsed metrics from the test (if successful) */
|
|
788
|
+
metrics?: BenchmarkMetrics;
|
|
789
|
+
/** Error message if the test failed */
|
|
790
|
+
error?: string;
|
|
791
|
+
}
|
|
792
|
+
/** Preset configurations for QPS tests */
|
|
793
|
+
export declare const QPS_PRESETS: Record<Exclude<BenchmarkPreset, "custom">, {
|
|
794
|
+
targetRps: number;
|
|
795
|
+
testDuration: string;
|
|
796
|
+
label: string;
|
|
797
|
+
description: string;
|
|
798
|
+
}>;
|
|
799
|
+
/** Preset configurations for throughput tests */
|
|
800
|
+
export declare const THROUGHPUT_PRESETS: Record<Exclude<BenchmarkPreset, "custom">, {
|
|
801
|
+
targetRps: number;
|
|
802
|
+
bulkSize: number;
|
|
803
|
+
testDuration: string;
|
|
804
|
+
label: string;
|
|
805
|
+
description: string;
|
|
806
|
+
}>;
|
|
807
|
+
/** Cloud URLs that should be blocked from benchmarking */
|
|
808
|
+
export declare const BLOCKED_BENCHMARK_DOMAINS: string[];
|
package/dist/types/index.js
CHANGED
|
@@ -554,3 +554,54 @@ export function getNamespace(deploymentName) {
|
|
|
554
554
|
export function getReleaseName(deploymentName) {
|
|
555
555
|
return `rulebricks-${deploymentName}`;
|
|
556
556
|
}
|
|
557
|
+
/** Preset configurations for QPS tests */
|
|
558
|
+
export const QPS_PRESETS = {
|
|
559
|
+
light: {
|
|
560
|
+
targetRps: 100,
|
|
561
|
+
testDuration: "2m",
|
|
562
|
+
label: "Light",
|
|
563
|
+
description: "100 RPS for 2 minutes - quick validation",
|
|
564
|
+
},
|
|
565
|
+
medium: {
|
|
566
|
+
targetRps: 500,
|
|
567
|
+
testDuration: "4m",
|
|
568
|
+
label: "Medium",
|
|
569
|
+
description: "500 RPS for 4 minutes - standard load test",
|
|
570
|
+
},
|
|
571
|
+
heavy: {
|
|
572
|
+
targetRps: 1000,
|
|
573
|
+
testDuration: "4m",
|
|
574
|
+
label: "Heavy",
|
|
575
|
+
description: "1000 RPS for 4 minutes - stress test",
|
|
576
|
+
},
|
|
577
|
+
};
|
|
578
|
+
/** Preset configurations for throughput tests */
|
|
579
|
+
export const THROUGHPUT_PRESETS = {
|
|
580
|
+
light: {
|
|
581
|
+
targetRps: 50,
|
|
582
|
+
bulkSize: 25,
|
|
583
|
+
testDuration: "2m",
|
|
584
|
+
label: "Light",
|
|
585
|
+
description: "1,250 solutions/sec for 2 minutes",
|
|
586
|
+
},
|
|
587
|
+
medium: {
|
|
588
|
+
targetRps: 100,
|
|
589
|
+
bulkSize: 50,
|
|
590
|
+
testDuration: "4m",
|
|
591
|
+
label: "Medium",
|
|
592
|
+
description: "5,000 solutions/sec for 4 minutes",
|
|
593
|
+
},
|
|
594
|
+
heavy: {
|
|
595
|
+
targetRps: 200,
|
|
596
|
+
bulkSize: 100,
|
|
597
|
+
testDuration: "4m",
|
|
598
|
+
label: "Heavy",
|
|
599
|
+
description: "20,000 solutions/sec for 4 minutes",
|
|
600
|
+
},
|
|
601
|
+
};
|
|
602
|
+
/** Cloud URLs that should be blocked from benchmarking */
|
|
603
|
+
export const BLOCKED_BENCHMARK_DOMAINS = [
|
|
604
|
+
"api.rulebricks.com",
|
|
605
|
+
"rulebricks.io",
|
|
606
|
+
"app.rulebricks.com",
|
|
607
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulebricks/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "CLI for deploying and managing private Rulebricks instances",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -38,19 +38,19 @@
|
|
|
38
38
|
"commander": "^12.1.0",
|
|
39
39
|
"execa": "^8.0.1",
|
|
40
40
|
"figures": "^6.1.0",
|
|
41
|
-
"ink": "^
|
|
41
|
+
"ink": "^6.6.0",
|
|
42
42
|
"ink-select-input": "^6.0.0",
|
|
43
43
|
"ink-spinner": "^5.0.0",
|
|
44
44
|
"ink-text-input": "^6.0.0",
|
|
45
45
|
"node-fetch": "^3.3.2",
|
|
46
46
|
"ora": "^8.0.1",
|
|
47
|
-
"react": "^
|
|
47
|
+
"react": "^19.2.4",
|
|
48
48
|
"yaml": "^2.4.5",
|
|
49
49
|
"zod": "^3.23.8"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^20.14.10",
|
|
53
|
-
"@types/react": "^
|
|
53
|
+
"@types/react": "^19.2.4",
|
|
54
54
|
"typescript": "^5.5.3"
|
|
55
55
|
},
|
|
56
56
|
"engines": {
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"pkg": {
|
|
60
60
|
"scripts": "dist/**/*.js",
|
|
61
61
|
"assets": [
|
|
62
|
-
"terraform/**/*"
|
|
62
|
+
"terraform/**/*",
|
|
63
|
+
"benchmarks/**/*"
|
|
63
64
|
],
|
|
64
65
|
"targets": [
|
|
65
66
|
"node20-linux-x64",
|
|
@@ -73,6 +74,7 @@
|
|
|
73
74
|
"files": [
|
|
74
75
|
"dist",
|
|
75
76
|
"terraform",
|
|
76
|
-
"
|
|
77
|
+
"templates",
|
|
78
|
+
"benchmarks"
|
|
77
79
|
]
|
|
78
80
|
}
|
package/terraform/aws/main.tf
CHANGED
|
@@ -185,6 +185,28 @@ module "eks" {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
# Allow all node-to-node traffic for intra-cluster communication
|
|
189
|
+
# This ensures services on any port (including port 80 for serverless-redis-http) can communicate
|
|
190
|
+
node_security_group_additional_rules = {
|
|
191
|
+
ingress_self_all = {
|
|
192
|
+
description = "Node to node all ports/protocols"
|
|
193
|
+
protocol = "-1"
|
|
194
|
+
from_port = 0
|
|
195
|
+
to_port = 0
|
|
196
|
+
type = "ingress"
|
|
197
|
+
self = true
|
|
198
|
+
}
|
|
199
|
+
egress_all = {
|
|
200
|
+
description = "Node all egress"
|
|
201
|
+
protocol = "-1"
|
|
202
|
+
from_port = 0
|
|
203
|
+
to_port = 0
|
|
204
|
+
type = "egress"
|
|
205
|
+
cidr_blocks = ["0.0.0.0/0"]
|
|
206
|
+
ipv6_cidr_blocks = ["::/0"]
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
188
210
|
tags = {
|
|
189
211
|
Environment = "rulebricks"
|
|
190
212
|
Terraform = "true"
|
package/terraform/azure/main.tf
CHANGED
|
@@ -138,6 +138,51 @@ resource "azurerm_subnet" "aks" {
|
|
|
138
138
|
address_prefixes = ["10.240.0.0/16"]
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
# Network Security Group for AKS subnet
|
|
142
|
+
# Allows all intra-VNet traffic for Kubernetes node-to-node communication
|
|
143
|
+
resource "azurerm_network_security_group" "aks" {
|
|
144
|
+
name = "${var.cluster_name}-nsg"
|
|
145
|
+
location = azurerm_resource_group.rg.location
|
|
146
|
+
resource_group_name = azurerm_resource_group.rg.name
|
|
147
|
+
|
|
148
|
+
# Allow all intra-VNet inbound traffic for Kubernetes
|
|
149
|
+
security_rule {
|
|
150
|
+
name = "AllowVNetInbound"
|
|
151
|
+
priority = 100
|
|
152
|
+
direction = "Inbound"
|
|
153
|
+
access = "Allow"
|
|
154
|
+
protocol = "*"
|
|
155
|
+
source_port_range = "*"
|
|
156
|
+
destination_port_range = "*"
|
|
157
|
+
source_address_prefix = "VirtualNetwork"
|
|
158
|
+
destination_address_prefix = "VirtualNetwork"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# Allow all intra-VNet outbound traffic for Kubernetes
|
|
162
|
+
security_rule {
|
|
163
|
+
name = "AllowVNetOutbound"
|
|
164
|
+
priority = 100
|
|
165
|
+
direction = "Outbound"
|
|
166
|
+
access = "Allow"
|
|
167
|
+
protocol = "*"
|
|
168
|
+
source_port_range = "*"
|
|
169
|
+
destination_port_range = "*"
|
|
170
|
+
source_address_prefix = "VirtualNetwork"
|
|
171
|
+
destination_address_prefix = "VirtualNetwork"
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
tags = {
|
|
175
|
+
Environment = "rulebricks"
|
|
176
|
+
Terraform = "true"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# Associate NSG with AKS subnet
|
|
181
|
+
resource "azurerm_subnet_network_security_group_association" "aks" {
|
|
182
|
+
subnet_id = azurerm_subnet.aks.id
|
|
183
|
+
network_security_group_id = azurerm_network_security_group.aks.id
|
|
184
|
+
}
|
|
185
|
+
|
|
141
186
|
# User Assigned Identity for AKS
|
|
142
187
|
resource "azurerm_user_assigned_identity" "aks" {
|
|
143
188
|
name = "${var.cluster_name}-identity"
|
package/terraform/gcp/main.tf
CHANGED
|
@@ -165,6 +165,37 @@ resource "google_compute_router_nat" "nat" {
|
|
|
165
165
|
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
# Firewall rule to allow all internal traffic within the VPC
|
|
169
|
+
# This ensures services on any port can communicate between nodes
|
|
170
|
+
resource "google_compute_firewall" "allow_internal" {
|
|
171
|
+
name = "${var.cluster_name}-allow-internal"
|
|
172
|
+
network = google_compute_network.vpc.name
|
|
173
|
+
|
|
174
|
+
allow {
|
|
175
|
+
protocol = "tcp"
|
|
176
|
+
ports = ["0-65535"]
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
allow {
|
|
180
|
+
protocol = "udp"
|
|
181
|
+
ports = ["0-65535"]
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
allow {
|
|
185
|
+
protocol = "icmp"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# Allow traffic from nodes, pods, and services in the same VPC
|
|
189
|
+
source_ranges = [
|
|
190
|
+
google_compute_subnetwork.subnet.ip_cidr_range, # Node IPs (10.0.0.0/16)
|
|
191
|
+
google_compute_subnetwork.subnet.secondary_ip_range[0].ip_cidr_range, # Pod IPs (10.1.0.0/16)
|
|
192
|
+
google_compute_subnetwork.subnet.secondary_ip_range[1].ip_cidr_range # Service IPs (10.2.0.0/16)
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
# Target all instances in the VPC
|
|
196
|
+
target_tags = ["gke-${var.cluster_name}"]
|
|
197
|
+
}
|
|
198
|
+
|
|
168
199
|
# GKE Cluster
|
|
169
200
|
resource "google_container_cluster" "cluster" {
|
|
170
201
|
provider = google-beta
|
|
@@ -266,6 +297,9 @@ resource "google_container_node_pool" "primary" {
|
|
|
266
297
|
tier = var.tier
|
|
267
298
|
}
|
|
268
299
|
|
|
300
|
+
# Network tags for firewall rules
|
|
301
|
+
tags = ["gke-${var.cluster_name}"]
|
|
302
|
+
|
|
269
303
|
workload_metadata_config {
|
|
270
304
|
mode = "GKE_METADATA"
|
|
271
305
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|