@rulebricks/cli 2.1.6 → 2.1.7

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.
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
3
+ "contentVersion": "1.0.0.0",
4
+ "parameters": {
5
+ "clusterName": { "value": "rulebricks-cluster" },
6
+ "location": { "value": "eastus" },
7
+ "kubernetesVersion": { "value": "1.34" },
8
+ "nodeCount": { "value": 4 },
9
+ "nodeVmSize": { "value": "Standard_D2ps_v5" },
10
+ "osDiskSizeGB": { "value": 20 },
11
+ "osDiskType": { "value": "Managed" },
12
+
13
+ "rulebricksNamespace": { "value": "rulebricks" },
14
+ "vectorServiceAccountName": { "value": "vector" },
15
+ "prometheusServiceAccountName": { "value": "prometheus" },
16
+ "enableExternalDns": { "value": false },
17
+ "dnsZoneResourceGroup": { "value": "" },
18
+ "enableBlobLogging": { "value": false },
19
+ "loggingStorageAccountName": { "value": "" }
20
+ }
21
+ }
@@ -0,0 +1,172 @@
1
+ # GCP Cluster Setup
2
+
3
+ Use these commands to create a minimum GKE cluster that can run Rulebricks without using the Rulebricks CLI Terraform flow. GCP does not have an `eksctl`-style cluster YAML or a concise Bicep equivalent; the most familiar native interface is `gcloud`.
4
+
5
+ ## Files
6
+
7
+ - `check-gke-prereqs.sh` verifies `gcloud` auth, Application Default Credentials, required APIs, selected-region quota, GKE access, `kubectl`, and Helm.
8
+
9
+ ## Core Cluster Parameters
10
+
11
+ - Cluster name: `rulebricks-cluster` (`Core cluster parameters` block -> `CLUSTER_NAME`)
12
+ - Region / zone: `us-central1` / `us-central1-a` (`Core cluster parameters` block -> `REGION` / `ZONE`)
13
+ - Kubernetes version: `1.34` (`Core cluster parameters` block -> `KUBERNETES_VERSION`)
14
+ - Node count: `4` (`Core cluster parameters` block -> `NODE_COUNT`)
15
+ - Machine type: `c4a-standard-2` (`Core cluster parameters` block -> `MACHINE_TYPE`)
16
+ - Disk size (GB): `20` (`Core cluster parameters` block -> `DISK_SIZE`)
17
+ - Disk type: `hyperdisk-balanced` (`Core cluster parameters` block -> `DISK_TYPE`)
18
+
19
+ ## Check Access
20
+
21
+ ```bash
22
+ gcloud auth login
23
+ gcloud config set project <project-id>
24
+ gcloud auth application-default login
25
+ GCP_REGION=us-central1 bash check-gke-prereqs.sh
26
+ ```
27
+
28
+ If API warnings appear, run the suggested `gcloud services enable` commands and wait for enablement to complete.
29
+
30
+ ## Create The Cluster
31
+
32
+ Set the core cluster parameters. The default example uses `us-central1-a` because it supports C4A ARM64 nodes.
33
+
34
+ ```bash
35
+ PROJECT_ID="$(gcloud config get-value project)"
36
+ CLUSTER_NAME=rulebricks-cluster
37
+ REGION=us-central1
38
+ ZONE=us-central1-a
39
+ KUBERNETES_VERSION="1.34"
40
+ NODE_COUNT=4
41
+ MACHINE_TYPE=c4a-standard-2
42
+ DISK_SIZE=20
43
+ DISK_TYPE=hyperdisk-balanced
44
+ ```
45
+
46
+ Enable required APIs:
47
+
48
+ ```bash
49
+ gcloud services enable \
50
+ compute.googleapis.com \
51
+ container.googleapis.com \
52
+ iam.googleapis.com \
53
+ cloudresourcemanager.googleapis.com \
54
+ --project "$PROJECT_ID"
55
+ ```
56
+
57
+ Create the VPC, subnet, NAT, and internal firewall rule:
58
+
59
+ ```bash
60
+ gcloud compute networks create "${CLUSTER_NAME}-vpc" \
61
+ --project "$PROJECT_ID" \
62
+ --subnet-mode custom
63
+
64
+ gcloud compute networks subnets create "${CLUSTER_NAME}-subnet" \
65
+ --project "$PROJECT_ID" \
66
+ --region "$REGION" \
67
+ --network "${CLUSTER_NAME}-vpc" \
68
+ --range 10.0.0.0/16 \
69
+ --secondary-range pods=10.1.0.0/16,services=10.2.0.0/16 \
70
+ --enable-private-ip-google-access
71
+
72
+ gcloud compute routers create "${CLUSTER_NAME}-router" \
73
+ --project "$PROJECT_ID" \
74
+ --region "$REGION" \
75
+ --network "${CLUSTER_NAME}-vpc"
76
+
77
+ gcloud compute routers nats create "${CLUSTER_NAME}-nat" \
78
+ --project "$PROJECT_ID" \
79
+ --region "$REGION" \
80
+ --router "${CLUSTER_NAME}-router" \
81
+ --auto-allocate-nat-external-ips \
82
+ --nat-all-subnet-ip-ranges
83
+
84
+ gcloud compute firewall-rules create "${CLUSTER_NAME}-allow-internal" \
85
+ --project "$PROJECT_ID" \
86
+ --network "${CLUSTER_NAME}-vpc" \
87
+ --allow tcp:0-65535,udp:0-65535,icmp \
88
+ --source-ranges 10.0.0.0/16,10.1.0.0/16,10.2.0.0/16 \
89
+ --target-tags "gke-${CLUSTER_NAME}"
90
+ ```
91
+
92
+ Create the GKE cluster:
93
+
94
+ ```bash
95
+ gcloud container clusters create "$CLUSTER_NAME" \
96
+ --project "$PROJECT_ID" \
97
+ --region "$REGION" \
98
+ --node-locations "$ZONE" \
99
+ --cluster-version "$KUBERNETES_VERSION" \
100
+ --release-channel regular \
101
+ --network "${CLUSTER_NAME}-vpc" \
102
+ --subnetwork "${CLUSTER_NAME}-subnet" \
103
+ --enable-ip-alias \
104
+ --cluster-secondary-range-name pods \
105
+ --services-secondary-range-name services \
106
+ --enable-private-nodes \
107
+ --master-ipv4-cidr 172.16.0.0/28 \
108
+ --enable-master-authorized-networks \
109
+ --master-authorized-networks 0.0.0.0/0 \
110
+ --workload-pool "${PROJECT_ID}.svc.id.goog" \
111
+ --enable-network-policy \
112
+ --addons HttpLoadBalancing,HorizontalPodAutoscaling,GcePersistentDiskCsiDriver \
113
+ --node-pool rulebricks-nodes \
114
+ --machine-type "$MACHINE_TYPE" \
115
+ --num-nodes "$NODE_COUNT" \
116
+ --disk-type "$DISK_TYPE" \
117
+ --disk-size "$DISK_SIZE" \
118
+ --scopes cloud-platform \
119
+ --workload-metadata GKE_METADATA \
120
+ --enable-autorepair \
121
+ --enable-autoupgrade \
122
+ --node-labels environment=rulebricks \
123
+ --tags "gke-${CLUSTER_NAME}"
124
+ ```
125
+
126
+ Configure kubeconfig:
127
+
128
+ ```bash
129
+ gcloud container clusters get-credentials "$CLUSTER_NAME" \
130
+ --region "$REGION" \
131
+ --project "$PROJECT_ID"
132
+ ```
133
+
134
+ Use `rulebricks init` with **Use existing Kubernetes cluster** after kubeconfig works.
135
+
136
+ ## Optional Identity Setup
137
+
138
+ If you use GCS decision-log export, bind the `vector` Kubernetes service account to a Google service account that can write to the bucket:
139
+
140
+ ```bash
141
+ NAMESPACE=rulebricks-demo
142
+ PROJECT_ID="$(gcloud config get-value project)"
143
+ GSA=rulebricks-vector@"$PROJECT_ID".iam.gserviceaccount.com
144
+
145
+ gcloud iam service-accounts create rulebricks-vector \
146
+ --project "$PROJECT_ID"
147
+
148
+ gcloud storage buckets add-iam-policy-binding gs://<bucket-name> \
149
+ --member "serviceAccount:$GSA" \
150
+ --role roles/storage.objectCreator
151
+
152
+ gcloud iam service-accounts add-iam-policy-binding "$GSA" \
153
+ --project "$PROJECT_ID" \
154
+ --role roles/iam.workloadIdentityUser \
155
+ --member "serviceAccount:$PROJECT_ID.svc.id.goog[$NAMESPACE/vector]"
156
+ ```
157
+
158
+ Annotate the service account after the Rulebricks namespace exists:
159
+
160
+ ```bash
161
+ kubectl annotate serviceaccount vector \
162
+ --namespace "$NAMESPACE" \
163
+ iam.gke.io/gcp-service-account="$GSA"
164
+ ```
165
+
166
+ Enter the Google service account email when prompted by the CLI.
167
+
168
+ ## Notes
169
+
170
+ - The example creates four `c4a-standard-2` ARM64 nodes with `hyperdisk-balanced`, matching the minimum CLI Terraform defaults.
171
+ - C4A availability varies by region and zone. If you change `REGION`, choose a `ZONE` where C4A is available.
172
+ - Regional GKE clusters can multiply node counts across node locations. This example pins one node location to keep the minimum cluster shape predictable.
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ PROJECT_ID="${GOOGLE_CLOUD_PROJECT:-$(gcloud config get-value project 2>/dev/null || true)}"
5
+ REGION="${GCP_REGION:-us-central1}"
6
+ REQUIRED_VCPU=8
7
+ REQUIRED_APIS=(
8
+ compute.googleapis.com
9
+ container.googleapis.com
10
+ iam.googleapis.com
11
+ cloudresourcemanager.googleapis.com
12
+ )
13
+
14
+ require_cmd() {
15
+ command -v "$1" >/dev/null 2>&1 || {
16
+ echo "Missing required command: $1" >&2
17
+ exit 1
18
+ }
19
+ }
20
+
21
+ check_api() {
22
+ local api="$1"
23
+ if gcloud services list \
24
+ --project "$PROJECT_ID" \
25
+ --enabled \
26
+ --filter="name:${api}" \
27
+ --format="value(name)" | awk -v api="$api" '$0 ~ api { found=1 } END { exit found ? 0 : 1 }'; then
28
+ echo "OK: $api is enabled"
29
+ else
30
+ echo "WARN: $api is not enabled. Run: gcloud services enable $api --project $PROJECT_ID"
31
+ fi
32
+ }
33
+
34
+ check_quota() {
35
+ echo "Checking regional CPU quota in $REGION..."
36
+ local quota_line
37
+ quota_line="$(gcloud compute regions describe "$REGION" \
38
+ --project "$PROJECT_ID" \
39
+ --format="csv[no-heading](quotas.metric,quotas.limit,quotas.usage)" 2>/dev/null \
40
+ | awk -F, '$1=="CPUS"{print $2 "," $3; exit}' || true)"
41
+
42
+ if [[ -z "$quota_line" ]]; then
43
+ echo "WARN: Could not read regional CPU quota."
44
+ return
45
+ fi
46
+
47
+ local limit="${quota_line%,*}"
48
+ local usage="${quota_line#*,}"
49
+ local available
50
+ available="$(awk -v limit="$limit" -v usage="$usage" 'BEGIN { printf "%d", limit - usage }')"
51
+
52
+ if (( available < REQUIRED_VCPU )); then
53
+ echo "WARN: ${available}/${limit} CPUs available; ${REQUIRED_VCPU}+ recommended for the included cluster commands."
54
+ else
55
+ echo "OK: ${available}/${limit} CPUs available."
56
+ fi
57
+ }
58
+
59
+ require_cmd gcloud
60
+ require_cmd kubectl
61
+ require_cmd helm
62
+ require_cmd awk
63
+
64
+ if [[ -z "$PROJECT_ID" ]]; then
65
+ echo "No GCP project configured. Run: gcloud config set project PROJECT_ID" >&2
66
+ exit 1
67
+ fi
68
+
69
+ echo "Rulebricks GKE prerequisite checks"
70
+ echo "Project: $PROJECT_ID"
71
+ echo "Region: $REGION"
72
+ echo
73
+
74
+ echo "Checking gcloud account..."
75
+ gcloud auth list --filter=status:ACTIVE --format="value(account)"
76
+ gcloud auth application-default print-access-token >/dev/null
77
+ echo "OK: gcloud auth and Application Default Credentials are available."
78
+ echo
79
+
80
+ for api in "${REQUIRED_APIS[@]}"; do
81
+ check_api "$api"
82
+ done
83
+
84
+ echo
85
+ gcloud compute regions describe "$REGION" --project "$PROJECT_ID" >/dev/null
86
+ echo "OK: region $REGION is accessible."
87
+ gcloud container clusters list --region "$REGION" --project "$PROJECT_ID" >/dev/null
88
+ echo "OK: GKE cluster list access works."
89
+ check_quota
90
+
91
+ echo
92
+ echo "Checking local Kubernetes tools..."
93
+ kubectl version --client=true >/dev/null
94
+ helm version >/dev/null
95
+ echo "OK: kubectl and Helm are installed."
96
+
97
+ echo
98
+ echo "GKE prerequisite checks completed. Warnings may require GCP project-admin review before cluster creation."
@@ -6,6 +6,7 @@ import { DeploymentModeStep, CloudProviderStep, DomainStep, SMTPStep, DatabaseSt
6
6
  import { AppShell, ProgressHeader, ThemeProvider, useTheme, Logo, LOGO_LINES, } from "../components/common/index.js";
7
7
  import { saveDeploymentConfig, deploymentExists, loadProfile, updateProfile, extractProfileFromConfig, } from "../lib/config.js";
8
8
  import { generateHelmValues } from "../lib/helmValues.js";
9
+ import { inferClusterTier } from "../lib/kubernetes.js";
9
10
  const STEP_INFO = {
10
11
  mode: { title: "Deployment Mode", description: "Choose how to deploy" },
11
12
  cloud: { title: "Cloud Provider", description: "Select your cloud provider" },
@@ -65,7 +66,10 @@ function WizardStepController({ onSaveComplete }) {
65
66
  if (state.databaseType === "self-hosted") {
66
67
  steps.push("database-creds");
67
68
  }
68
- steps.push("tier", "features");
69
+ if (state.infrastructureMode === "provision") {
70
+ steps.push("tier");
71
+ }
72
+ steps.push("features");
69
73
  // Feature config only if AI, SSO, monitoring, external logging, or custom emails enabled
70
74
  if (state.aiEnabled ||
71
75
  state.ssoEnabled ||
@@ -107,7 +111,10 @@ function WizardStepController({ onSaveComplete }) {
107
111
  setPendingNav("back");
108
112
  }, []);
109
113
  const handleSave = useCallback(async () => {
110
- const config = toConfig();
114
+ const inferredTier = state.infrastructureMode === "existing"
115
+ ? (await inferClusterTier()) || state.tier || "small"
116
+ : state.tier || undefined;
117
+ const config = toConfig({ tier: inferredTier });
111
118
  if (!config) {
112
119
  setError("Invalid configuration - please check all required fields");
113
120
  return;
@@ -1,5 +1,5 @@
1
1
  import React, { ReactNode } from "react";
2
- import { DeploymentConfig, CloudProvider, DatabaseType, PerformanceTier, SSOProvider, DnsProvider, LoggingSink, EmailSubjects, EmailTemplates, ProfileConfig } from "../../types/index.js";
2
+ import { DeploymentConfig, CloudProvider, DatabaseType, PerformanceTier, SSOProvider, DnsProvider, LoggingSink, CloudLoggingAuthMode, MonitoringDestination, RemoteWriteAuthType, RemoteWriteDestination, EmailSubjects, EmailTemplates, ProfileConfig } from "../../types/index.js";
3
3
  export interface WizardState {
4
4
  step: number;
5
5
  name: string;
@@ -40,10 +40,29 @@ export interface WizardState {
40
40
  ssoClientId: string;
41
41
  ssoClientSecret: string;
42
42
  monitoringEnabled: boolean;
43
+ prometheusMonitoringDestination: MonitoringDestination | null;
43
44
  prometheusRemoteWriteUrl: string;
45
+ prometheusRemoteWriteDestination: RemoteWriteDestination | null;
46
+ prometheusRemoteWriteAuthType: RemoteWriteAuthType | null;
47
+ prometheusRemoteWriteAwsRegion: string;
48
+ prometheusRemoteWriteAwsRoleArn: string;
49
+ prometheusRemoteWriteAzureCloud: "AzurePublic" | "AzureChina" | "AzureGovernment";
50
+ prometheusRemoteWriteClientId: string;
51
+ prometheusRemoteWriteTenantId: string;
52
+ prometheusRemoteWriteSecretRef: string;
53
+ prometheusRemoteWriteUsernameSecretRef: string;
54
+ prometheusRemoteWritePasswordSecretRef: string;
55
+ prometheusRemoteWriteBearerTokenSecretRef: string;
44
56
  loggingSink: LoggingSink;
45
57
  loggingBucket: string;
46
58
  loggingRegion: string;
59
+ loggingCloudAuthMode: CloudLoggingAuthMode;
60
+ loggingAwsIamRoleArn: string;
61
+ loggingAzureBlobContainer: string;
62
+ loggingAzureBlobClientId: string;
63
+ loggingAzureBlobTenantId: string;
64
+ loggingAzureBlobConnectionStringSecretRef: string;
65
+ loggingGcpServiceAccountEmail: string;
47
66
  customEmailsEnabled: boolean;
48
67
  emailSubjects: EmailSubjects;
49
68
  emailTemplates: EmailTemplates;
@@ -127,12 +146,15 @@ type WizardAction = {
127
146
  } | {
128
147
  type: "SET_PROMETHEUS_REMOTE_WRITE";
129
148
  url: string;
149
+ } | {
150
+ type: "SET_PROMETHEUS_REMOTE_WRITE_CONFIG";
151
+ config: Partial<Pick<WizardState, "prometheusMonitoringDestination" | "prometheusRemoteWriteDestination" | "prometheusRemoteWriteAuthType" | "prometheusRemoteWriteAwsRegion" | "prometheusRemoteWriteAwsRoleArn" | "prometheusRemoteWriteAzureCloud" | "prometheusRemoteWriteClientId" | "prometheusRemoteWriteTenantId" | "prometheusRemoteWriteSecretRef" | "prometheusRemoteWriteUsernameSecretRef" | "prometheusRemoteWritePasswordSecretRef" | "prometheusRemoteWriteBearerTokenSecretRef">>;
130
152
  } | {
131
153
  type: "SET_LOGGING_SINK";
132
154
  sink: LoggingSink;
133
155
  } | {
134
156
  type: "SET_LOGGING_CONFIG";
135
- config: Partial<Pick<WizardState, "loggingBucket" | "loggingRegion">>;
157
+ config: Partial<Pick<WizardState, "loggingBucket" | "loggingRegion" | "loggingCloudAuthMode" | "loggingAwsIamRoleArn" | "loggingAzureBlobContainer" | "loggingAzureBlobClientId" | "loggingAzureBlobTenantId" | "loggingAzureBlobConnectionStringSecretRef" | "loggingGcpServiceAccountEmail">>;
136
158
  } | {
137
159
  type: "SET_CUSTOM_EMAILS_ENABLED";
138
160
  enabled: boolean;
@@ -160,7 +182,9 @@ type WizardAction = {
160
182
  interface WizardContextValue {
161
183
  state: WizardState;
162
184
  dispatch: React.Dispatch<WizardAction>;
163
- toConfig: () => DeploymentConfig | null;
185
+ toConfig: (options?: {
186
+ tier?: PerformanceTier;
187
+ }) => DeploymentConfig | null;
164
188
  skipToStep: (stepId: string) => void;
165
189
  profile: ProfileConfig | null;
166
190
  /** Suggests a domain based on the profile's domain suffix and deployment name */
@@ -55,11 +55,30 @@ function getInitialState(profile) {
55
55
  ssoClientSecret: profile?.ssoClientSecret ?? "",
56
56
  // Features - Monitoring
57
57
  monitoringEnabled: false,
58
+ prometheusMonitoringDestination: null,
58
59
  prometheusRemoteWriteUrl: "",
60
+ prometheusRemoteWriteDestination: null,
61
+ prometheusRemoteWriteAuthType: null,
62
+ prometheusRemoteWriteAwsRegion: "",
63
+ prometheusRemoteWriteAwsRoleArn: "",
64
+ prometheusRemoteWriteAzureCloud: "AzurePublic",
65
+ prometheusRemoteWriteClientId: "",
66
+ prometheusRemoteWriteTenantId: "",
67
+ prometheusRemoteWriteSecretRef: "",
68
+ prometheusRemoteWriteUsernameSecretRef: "",
69
+ prometheusRemoteWritePasswordSecretRef: "",
70
+ prometheusRemoteWriteBearerTokenSecretRef: "",
59
71
  // Features - Logging
60
72
  loggingSink: "console", // Default to console only
61
73
  loggingBucket: "",
62
74
  loggingRegion: "",
75
+ loggingCloudAuthMode: "workload-identity",
76
+ loggingAwsIamRoleArn: "",
77
+ loggingAzureBlobContainer: "rulebricks-logs",
78
+ loggingAzureBlobClientId: "",
79
+ loggingAzureBlobTenantId: "",
80
+ loggingAzureBlobConnectionStringSecretRef: "",
81
+ loggingGcpServiceAccountEmail: "",
63
82
  // Features - Custom Email Templates
64
83
  customEmailsEnabled: false,
65
84
  emailSubjects: { ...DEFAULT_EMAIL_SUBJECTS },
@@ -79,6 +98,12 @@ function getInitialState(profile) {
79
98
  }
80
99
  // Default initial state (for backwards compatibility)
81
100
  const initialState = getInitialState();
101
+ function parseSecretKeyRef(value) {
102
+ const [name, key] = value.split(":").map((part) => part.trim());
103
+ if (!name || !key)
104
+ return undefined;
105
+ return { name, key };
106
+ }
82
107
  function wizardReducer(state, action) {
83
108
  switch (action.type) {
84
109
  case "SET_STEP":
@@ -136,6 +161,8 @@ function wizardReducer(state, action) {
136
161
  return { ...state, monitoringEnabled: action.enabled };
137
162
  case "SET_PROMETHEUS_REMOTE_WRITE":
138
163
  return { ...state, prometheusRemoteWriteUrl: action.url };
164
+ case "SET_PROMETHEUS_REMOTE_WRITE_CONFIG":
165
+ return { ...state, ...action.config };
139
166
  case "SET_LOGGING_SINK":
140
167
  // Reset bucket/region if switching to console
141
168
  return {
@@ -183,7 +210,7 @@ export function WizardProvider({ children, initialName, profile, }) {
183
210
  ...getInitialState(profile),
184
211
  name: initialName || "",
185
212
  });
186
- const toConfig = () => {
213
+ const toConfig = (options = {}) => {
187
214
  // Validate required fields
188
215
  if (!state.name ||
189
216
  !state.domain ||
@@ -216,6 +243,45 @@ export function WizardProvider({ children, initialName, profile, }) {
216
243
  if (state.loggingSink !== "console" && !state.loggingBucket) {
217
244
  return null;
218
245
  }
246
+ if (state.loggingSink === "azure-blob" &&
247
+ (!state.loggingAzureBlobContainer ||
248
+ (state.loggingCloudAuthMode === "workload-identity" &&
249
+ (!state.loggingAzureBlobClientId || !state.loggingAzureBlobTenantId)) ||
250
+ (state.loggingCloudAuthMode === "secret" &&
251
+ !parseSecretKeyRef(state.loggingAzureBlobConnectionStringSecretRef)))) {
252
+ return null;
253
+ }
254
+ if (state.loggingSink === "s3" && !state.loggingAwsIamRoleArn) {
255
+ return null;
256
+ }
257
+ if (state.loggingSink === "gcs" && !state.loggingGcpServiceAccountEmail) {
258
+ return null;
259
+ }
260
+ const remoteWrite = state.monitoringEnabled &&
261
+ state.prometheusMonitoringDestination !== "local-grafana" &&
262
+ state.prometheusRemoteWriteDestination &&
263
+ state.prometheusRemoteWriteUrl
264
+ ? {
265
+ destination: state.prometheusRemoteWriteDestination,
266
+ url: state.prometheusRemoteWriteUrl,
267
+ authType: state.prometheusRemoteWriteAuthType || undefined,
268
+ awsRegion: state.prometheusRemoteWriteDestination === "aws-amp"
269
+ ? state.prometheusRemoteWriteAwsRegion ||
270
+ state.region ||
271
+ undefined
272
+ : undefined,
273
+ awsRoleArn: state.prometheusRemoteWriteDestination === "aws-amp"
274
+ ? state.prometheusRemoteWriteAwsRoleArn || undefined
275
+ : undefined,
276
+ azureCloud: state.prometheusRemoteWriteAzureCloud,
277
+ clientId: state.prometheusRemoteWriteClientId || undefined,
278
+ tenantId: state.prometheusRemoteWriteTenantId || undefined,
279
+ clientSecretRef: parseSecretKeyRef(state.prometheusRemoteWriteSecretRef),
280
+ usernameSecretRef: parseSecretKeyRef(state.prometheusRemoteWriteUsernameSecretRef),
281
+ passwordSecretRef: parseSecretKeyRef(state.prometheusRemoteWritePasswordSecretRef),
282
+ bearerTokenSecretRef: parseSecretKeyRef(state.prometheusRemoteWriteBearerTokenSecretRef),
283
+ }
284
+ : undefined;
219
285
  return {
220
286
  name: state.name,
221
287
  infrastructure: {
@@ -254,7 +320,7 @@ export function WizardProvider({ children, initialName, profile, }) {
254
320
  supabaseDashboardUser: state.supabaseDashboardUser || undefined,
255
321
  supabaseDashboardPass: state.supabaseDashboardPass || undefined,
256
322
  },
257
- tier: state.tier || "small",
323
+ tier: options.tier || state.tier || "small",
258
324
  features: {
259
325
  ai: {
260
326
  enabled: state.aiEnabled,
@@ -269,12 +335,39 @@ export function WizardProvider({ children, initialName, profile, }) {
269
335
  },
270
336
  monitoring: {
271
337
  enabled: state.monitoringEnabled,
338
+ destination: state.prometheusMonitoringDestination ||
339
+ remoteWrite?.destination ||
340
+ undefined,
272
341
  remoteWriteUrl: state.prometheusRemoteWriteUrl || undefined,
342
+ remoteWrite,
273
343
  },
274
344
  logging: {
275
345
  sink: state.loggingSink,
276
346
  bucket: state.loggingBucket || undefined,
277
347
  region: state.loggingRegion || undefined,
348
+ cloudAuthMode: state.loggingSink === "s3" ||
349
+ state.loggingSink === "azure-blob" ||
350
+ state.loggingSink === "gcs"
351
+ ? state.loggingCloudAuthMode
352
+ : undefined,
353
+ awsIamRoleArn: state.loggingSink === "s3"
354
+ ? state.loggingAwsIamRoleArn || undefined
355
+ : undefined,
356
+ azureBlobContainer: state.loggingSink === "azure-blob"
357
+ ? state.loggingAzureBlobContainer || undefined
358
+ : undefined,
359
+ azureBlobClientId: state.loggingSink === "azure-blob"
360
+ ? state.loggingAzureBlobClientId || undefined
361
+ : undefined,
362
+ azureBlobTenantId: state.loggingSink === "azure-blob"
363
+ ? state.loggingAzureBlobTenantId || undefined
364
+ : undefined,
365
+ azureBlobConnectionStringSecretRef: state.loggingSink === "azure-blob"
366
+ ? parseSecretKeyRef(state.loggingAzureBlobConnectionStringSecretRef)
367
+ : undefined,
368
+ gcpServiceAccountEmail: state.loggingSink === "gcs"
369
+ ? state.loggingGcpServiceAccountEmail || undefined
370
+ : undefined,
278
371
  },
279
372
  customEmails: state.customEmailsEnabled
280
373
  ? {
@@ -78,7 +78,12 @@ export function CloudProviderStep({ onComplete, onBack, }) {
78
78
  ];
79
79
  return providers.map((p) => ({
80
80
  ...p,
81
- disabled: !p.status.installed || !p.status.authenticated,
81
+ // Existing clusters only need a selectable provider so we can record
82
+ // kubeconfig refresh details. Strict auth/quota checks are only required
83
+ // when the CLI will provision infrastructure.
84
+ disabled: needsTerraform
85
+ ? !p.status.installed || !p.status.authenticated
86
+ : !p.status.installed,
82
87
  }));
83
88
  };
84
89
  const providerItems = getProviderItems();
@@ -215,7 +220,7 @@ export function CloudProviderStep({ onComplete, onBack, }) {
215
220
  ? colors.accent
216
221
  : undefined;
217
222
  return (_jsxs(Box, { children: [_jsxs(Text, { color: textColor, dimColor: item.disabled, children: [isSelected && !item.disabled ? "❯ " : " ", label] }), renderStatusIndicator(item.status), item.status.identity && item.status.authenticated && (_jsxs(Text, { color: "gray", dimColor: true, children: [" ", "(", item.status.identity, ")"] }))] }));
218
- } })] })), subStep === "gcp-project" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your GCP Project ID:" }), gcpProject && (_jsxs(Text, { color: "gray", dimColor: true, children: ["Detected project: ", gcpProject] })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: gcpProject, onChange: setGcpProject, onSubmit: handleGcpProjectSubmit, placeholder: "my-gcp-project" })] })] })), subStep === "azure-rg" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Azure Resource Group name:" }), _jsx(Text, { color: "gray", dimColor: true, children: "This resource group will contain all Rulebricks resources" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: azureRg, onChange: setAzureRg, onSubmit: handleAzureRgSubmit, placeholder: "rulebricks-rg" })] })] })), subStep === "region-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: `Fetching ${state.provider?.toUpperCase()} regions...` }) })), subStep === "region" && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { children: ["Select a region for ", state.provider?.toUpperCase(), ":"] }), _jsxs(Text, { color: "gray", dimColor: true, children: [regions.length, " regions available"] })] }), _jsx(Box, { height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: regionItems, onSelect: handleRegionSelect, limit: 8, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) })] })), subStep === "cluster-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: `Fetching ${state.provider?.toUpperCase()} clusters in ${state.region}...` }) })), subStep === "cluster-select" && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select your Kubernetes cluster:" }), _jsxs(Text, { color: "gray", dimColor: true, children: [clusters.length, " cluster", clusters.length !== 1 ? "s" : "", " found in ", state.region] })] }), _jsx(Box, { height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: clusters.map((c) => ({ label: c, value: c })), onSelect: handleClusterSelect, limit: 8, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) }), state.provider && state.region && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" ", state.provider?.toUpperCase(), " \u2022 ", state.region] })] }))] })), subStep === "cluster" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter the Kubernetes cluster name:" }), _jsx(Text, { color: "gray", dimColor: true, children: state.infrastructureMode === "provision"
223
+ } })] })), subStep === "gcp-project" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your GCP Project ID:" }), gcpProject && (_jsxs(Text, { color: "gray", dimColor: true, children: ["Detected project: ", gcpProject] })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: gcpProject, onChange: setGcpProject, onSubmit: handleGcpProjectSubmit, placeholder: "my-gcp-project" })] })] })), subStep === "azure-rg" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Azure Resource Group name:" }), _jsx(Text, { color: "gray", dimColor: true, children: "This should be the resource group containing your AKS cluster" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: azureRg, onChange: setAzureRg, onSubmit: handleAzureRgSubmit, placeholder: "rulebricks-rg" })] })] })), subStep === "region-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: `Fetching ${state.provider?.toUpperCase()} regions...` }) })), subStep === "region" && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { children: ["Select a region for ", state.provider?.toUpperCase(), ":"] }), _jsxs(Text, { color: "gray", dimColor: true, children: [regions.length, " regions available"] })] }), _jsx(Box, { height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: regionItems, onSelect: handleRegionSelect, limit: 8, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) })] })), subStep === "cluster-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: `Fetching ${state.provider?.toUpperCase()} clusters in ${state.region}...` }) })), subStep === "cluster-select" && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select your Kubernetes cluster:" }), _jsxs(Text, { color: "gray", dimColor: true, children: [clusters.length, " cluster", clusters.length !== 1 ? "s" : "", " found in ", state.region] })] }), _jsx(Box, { height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: clusters.map((c) => ({ label: c, value: c })), onSelect: handleClusterSelect, limit: 8, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) }), state.provider && state.region && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" ", state.provider?.toUpperCase(), " \u2022 ", state.region] })] }))] })), subStep === "cluster" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter the Kubernetes cluster name:" }), _jsx(Text, { color: "gray", dimColor: true, children: state.infrastructureMode === "provision"
219
224
  ? "This cluster will be created"
220
225
  : clusters.length === 0 && state.infrastructureMode === "existing"
221
226
  ? "No clusters found in this region - enter the name manually"