@rulebricks/cli 2.1.6 → 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.
- package/README.md +75 -14
- package/cluster-setup/aws/README.md +123 -0
- package/cluster-setup/aws/check-aws-access.sh +242 -0
- package/cluster-setup/aws/parameters.json +13 -0
- package/cluster-setup/aws/rulebricks-cluster.cfn.yaml +355 -0
- package/cluster-setup/azure/README.md +141 -0
- package/cluster-setup/azure/check-aks-prereqs.sh +276 -0
- package/cluster-setup/azure/parameters.json +30 -0
- package/cluster-setup/azure/rulebricks-cluster.bicep +546 -0
- package/cluster-setup/gcp/README.md +189 -0
- package/cluster-setup/gcp/check-gke-prereqs.sh +260 -0
- package/dist/commands/backup.d.ts +5 -0
- package/dist/commands/backup.js +104 -0
- package/dist/commands/deploy.d.ts +3 -1
- package/dist/commands/deploy.js +226 -326
- package/dist/commands/destroy.d.ts +1 -1
- package/dist/commands/destroy.js +73 -123
- package/dist/commands/init.d.ts +5 -1
- package/dist/commands/init.js +78 -47
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +74 -0
- package/dist/commands/open.d.ts +1 -1
- package/dist/commands/open.js +4 -12
- package/dist/commands/redeploy.d.ts +6 -0
- package/dist/commands/redeploy.js +310 -0
- package/dist/commands/restore.d.ts +5 -0
- package/dist/commands/restore.js +338 -0
- package/dist/commands/status.js +62 -49
- package/dist/commands/upgrade.js +74 -51
- package/dist/components/DNSWaitScreen.d.ts +5 -1
- package/dist/components/DNSWaitScreen.js +47 -41
- package/dist/components/Wizard/WizardContext.d.ts +174 -29
- package/dist/components/Wizard/WizardContext.js +896 -91
- package/dist/components/Wizard/steps/CloudProviderStep.js +192 -102
- package/dist/components/Wizard/steps/DomainStep.js +5 -24
- package/dist/components/Wizard/steps/ExternalServicesStep.d.ts +6 -0
- package/dist/components/Wizard/steps/ExternalServicesStep.js +645 -0
- package/dist/components/Wizard/steps/FeatureConfigStep.d.ts +2 -1
- package/dist/components/Wizard/steps/FeatureConfigStep.js +959 -248
- package/dist/components/Wizard/steps/FeaturesStep.js +31 -35
- package/dist/components/Wizard/steps/ObservabilityStep.d.ts +6 -0
- package/dist/components/Wizard/steps/ObservabilityStep.js +137 -0
- package/dist/components/Wizard/steps/ReviewStep.d.ts +2 -1
- package/dist/components/Wizard/steps/ReviewStep.js +56 -7
- package/dist/components/Wizard/steps/StorageStep.d.ts +9 -0
- package/dist/components/Wizard/steps/StorageStep.js +592 -0
- package/dist/components/Wizard/steps/SupabaseCredentialsStep.js +20 -21
- package/dist/components/Wizard/steps/VersionStep.js +45 -23
- package/dist/components/Wizard/steps/index.d.ts +3 -3
- package/dist/components/Wizard/steps/index.js +3 -3
- package/dist/components/common/CommandApproval.d.ts +12 -0
- package/dist/components/common/CommandApproval.js +91 -0
- package/dist/components/common/DeploymentPicker.d.ts +14 -0
- package/dist/components/common/DeploymentPicker.js +16 -0
- package/dist/components/common/index.d.ts +2 -0
- package/dist/components/common/index.js +2 -0
- package/dist/index.js +94 -62
- package/dist/lib/cloudCli.d.ts +134 -63
- package/dist/lib/cloudCli.js +512 -220
- package/dist/lib/clusterSetupDefaults.d.ts +30 -0
- package/dist/lib/clusterSetupDefaults.js +64 -0
- package/dist/lib/commandApproval.d.ts +26 -0
- package/dist/lib/commandApproval.js +114 -0
- package/dist/lib/config.d.ts +12 -10
- package/dist/lib/config.js +91 -33
- package/dist/lib/configFixtures.d.ts +5 -0
- package/dist/lib/configFixtures.js +513 -0
- package/dist/lib/deploymentHealth.d.ts +32 -0
- package/dist/lib/deploymentHealth.js +157 -0
- package/dist/lib/dns.d.ts +1 -1
- package/dist/lib/dns.js +19 -1
- package/dist/lib/dns.test.d.ts +1 -0
- package/dist/lib/dns.test.js +27 -0
- package/dist/lib/dockerHub.d.ts +12 -1
- package/dist/lib/dockerHub.js +18 -8
- package/dist/lib/helm.d.ts +4 -0
- package/dist/lib/helm.js +16 -0
- package/dist/lib/helmValues.d.ts +25 -0
- package/dist/lib/helmValues.js +1937 -259
- package/dist/lib/helmValues.test.d.ts +1 -0
- package/dist/lib/helmValues.test.js +966 -0
- package/dist/lib/htpasswd.d.ts +1 -0
- package/dist/lib/htpasswd.js +15 -0
- package/dist/lib/kubernetes.d.ts +126 -13
- package/dist/lib/kubernetes.js +624 -134
- package/dist/lib/secrets.d.ts +23 -0
- package/dist/lib/secrets.js +158 -0
- package/dist/lib/validateValues.d.ts +31 -0
- package/dist/lib/validateValues.js +253 -0
- package/dist/lib/versions.d.ts +82 -11
- package/dist/lib/versions.js +131 -31
- package/dist/lib/versions.test.d.ts +1 -0
- package/dist/lib/versions.test.js +81 -0
- package/dist/lib/wizardSteps.d.ts +14 -0
- package/dist/lib/wizardSteps.js +23 -0
- package/dist/lib/workloadIdentity.d.ts +26 -0
- package/dist/lib/workloadIdentity.js +323 -0
- package/dist/lib/workloadIdentity.test.d.ts +1 -0
- package/dist/lib/workloadIdentity.test.js +57 -0
- package/dist/types/index.d.ts +2152 -95
- package/dist/types/index.js +554 -286
- package/package.json +10 -4
- package/schema/values.schema.json +1934 -0
- package/dist/components/Wizard/steps/CredentialsStep.d.ts +0 -6
- package/dist/components/Wizard/steps/CredentialsStep.js +0 -22
- package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +0 -5
- package/dist/components/Wizard/steps/DeploymentModeStep.js +0 -26
- package/dist/components/Wizard/steps/TierStep.d.ts +0 -6
- package/dist/components/Wizard/steps/TierStep.js +0 -29
- package/dist/lib/terraform.d.ts +0 -66
- package/dist/lib/terraform.js +0 -754
- package/terraform/aws/main.tf +0 -355
- package/terraform/azure/main.tf +0 -371
- package/terraform/gcp/main.tf +0 -407
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# GCP Cluster Setup
|
|
2
|
+
|
|
3
|
+
Use these commands to create a compact GKE cluster that can run Rulebricks before installing with the Rulebricks CLI. 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
|
+
- Initial node count: `2` (`Core cluster parameters` block -> `NODE_COUNT`)
|
|
15
|
+
- Autoscaling range: `2-4` nodes (`Core cluster parameters` block -> `NODE_COUNT` / `MAX_NODE_COUNT`)
|
|
16
|
+
- Machine type: `n2-standard-4` (`Core cluster parameters` block -> `MACHINE_TYPE`)
|
|
17
|
+
- Disk size (GB): `20` (`Core cluster parameters` block -> `DISK_SIZE`)
|
|
18
|
+
- Disk type: `pd-balanced` (`Core cluster parameters` block -> `DISK_TYPE`)
|
|
19
|
+
|
|
20
|
+
## Check Access
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
gcloud auth login
|
|
24
|
+
gcloud config set project <project-id>
|
|
25
|
+
gcloud auth application-default login
|
|
26
|
+
GCP_REGION=us-central1 bash check-gke-prereqs.sh
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
If API warnings appear, run the suggested `gcloud services enable` commands and wait for enablement to complete.
|
|
30
|
+
|
|
31
|
+
## Create The Cluster
|
|
32
|
+
|
|
33
|
+
Set the core cluster parameters.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
PROJECT_ID="$(gcloud config get-value project)"
|
|
37
|
+
CLUSTER_NAME=rulebricks-cluster
|
|
38
|
+
REGION=us-central1
|
|
39
|
+
ZONE=us-central1-a
|
|
40
|
+
KUBERNETES_VERSION="1.34"
|
|
41
|
+
NODE_COUNT=2
|
|
42
|
+
MAX_NODE_COUNT=4
|
|
43
|
+
MACHINE_TYPE=n2-standard-4
|
|
44
|
+
DISK_SIZE=20
|
|
45
|
+
DISK_TYPE=pd-balanced
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Enable required APIs:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
gcloud services enable \
|
|
52
|
+
compute.googleapis.com \
|
|
53
|
+
container.googleapis.com \
|
|
54
|
+
iam.googleapis.com \
|
|
55
|
+
cloudresourcemanager.googleapis.com \
|
|
56
|
+
--project "$PROJECT_ID"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Create the VPC, subnet, NAT, and firewall rules:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
gcloud compute networks create "${CLUSTER_NAME}-vpc" \
|
|
63
|
+
--project "$PROJECT_ID" \
|
|
64
|
+
--subnet-mode custom
|
|
65
|
+
|
|
66
|
+
gcloud compute networks subnets create "${CLUSTER_NAME}-subnet" \
|
|
67
|
+
--project "$PROJECT_ID" \
|
|
68
|
+
--region "$REGION" \
|
|
69
|
+
--network "${CLUSTER_NAME}-vpc" \
|
|
70
|
+
--range 10.0.0.0/16 \
|
|
71
|
+
--secondary-range pods=10.1.0.0/16,services=10.2.0.0/16 \
|
|
72
|
+
--enable-private-ip-google-access
|
|
73
|
+
|
|
74
|
+
gcloud compute routers create "${CLUSTER_NAME}-router" \
|
|
75
|
+
--project "$PROJECT_ID" \
|
|
76
|
+
--region "$REGION" \
|
|
77
|
+
--network "${CLUSTER_NAME}-vpc"
|
|
78
|
+
|
|
79
|
+
gcloud compute routers nats create "${CLUSTER_NAME}-nat" \
|
|
80
|
+
--project "$PROJECT_ID" \
|
|
81
|
+
--region "$REGION" \
|
|
82
|
+
--router "${CLUSTER_NAME}-router" \
|
|
83
|
+
--auto-allocate-nat-external-ips \
|
|
84
|
+
--nat-all-subnet-ip-ranges
|
|
85
|
+
|
|
86
|
+
gcloud compute firewall-rules create "${CLUSTER_NAME}-allow-internal" \
|
|
87
|
+
--project "$PROJECT_ID" \
|
|
88
|
+
--network "${CLUSTER_NAME}-vpc" \
|
|
89
|
+
--allow tcp:0-65535,udp:0-65535,icmp \
|
|
90
|
+
--source-ranges 10.0.0.0/16,10.1.0.0/16,10.2.0.0/16 \
|
|
91
|
+
--target-tags "gke-${CLUSTER_NAME}"
|
|
92
|
+
|
|
93
|
+
gcloud compute firewall-rules create "${CLUSTER_NAME}-allow-web" \
|
|
94
|
+
--project "$PROJECT_ID" \
|
|
95
|
+
--network "${CLUSTER_NAME}-vpc" \
|
|
96
|
+
--allow tcp:80,tcp:443 \
|
|
97
|
+
--source-ranges 0.0.0.0/0 \
|
|
98
|
+
--target-tags "gke-${CLUSTER_NAME}"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Create the GKE cluster:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
gcloud container clusters create "$CLUSTER_NAME" \
|
|
105
|
+
--project "$PROJECT_ID" \
|
|
106
|
+
--region "$REGION" \
|
|
107
|
+
--node-locations "$ZONE" \
|
|
108
|
+
--cluster-version "$KUBERNETES_VERSION" \
|
|
109
|
+
--release-channel regular \
|
|
110
|
+
--network "${CLUSTER_NAME}-vpc" \
|
|
111
|
+
--subnetwork "${CLUSTER_NAME}-subnet" \
|
|
112
|
+
--enable-ip-alias \
|
|
113
|
+
--cluster-secondary-range-name pods \
|
|
114
|
+
--services-secondary-range-name services \
|
|
115
|
+
--enable-private-nodes \
|
|
116
|
+
--master-ipv4-cidr 172.16.0.0/28 \
|
|
117
|
+
--enable-master-authorized-networks \
|
|
118
|
+
--master-authorized-networks 0.0.0.0/0 \
|
|
119
|
+
--workload-pool "${PROJECT_ID}.svc.id.goog" \
|
|
120
|
+
--enable-network-policy \
|
|
121
|
+
--addons HttpLoadBalancing,HorizontalPodAutoscaling,GcePersistentDiskCsiDriver \
|
|
122
|
+
--node-pool rulebricks-nodes \
|
|
123
|
+
--machine-type "$MACHINE_TYPE" \
|
|
124
|
+
--num-nodes "$NODE_COUNT" \
|
|
125
|
+
--enable-autoscaling \
|
|
126
|
+
--min-nodes "$NODE_COUNT" \
|
|
127
|
+
--max-nodes "$MAX_NODE_COUNT" \
|
|
128
|
+
--disk-type "$DISK_TYPE" \
|
|
129
|
+
--disk-size "$DISK_SIZE" \
|
|
130
|
+
--scopes cloud-platform \
|
|
131
|
+
--workload-metadata GKE_METADATA \
|
|
132
|
+
--enable-autorepair \
|
|
133
|
+
--enable-autoupgrade \
|
|
134
|
+
--tags "gke-${CLUSTER_NAME}"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Configure kubeconfig:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
gcloud container clusters get-credentials "$CLUSTER_NAME" \
|
|
141
|
+
--region "$REGION" \
|
|
142
|
+
--project "$PROJECT_ID"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Use `rulebricks init` after kubeconfig works, then select this cluster from the GCP cluster list.
|
|
146
|
+
|
|
147
|
+
## Multi-Node Scheduling
|
|
148
|
+
|
|
149
|
+
The default configuration starts with two 4-vCPU nodes for a simple standalone deployment and can scale out to four nodes. Splitting the baseline across two nodes provides more Kubernetes pod slots than a single large node while keeping the initial 8-vCPU footprint. Rulebricks worker pods use soft scheduling preferences so Kubernetes can place them away from the rest of the deployment when extra nodes are available. No node labels or taints are required.
|
|
150
|
+
|
|
151
|
+
## Identity Setup (one service account, one bucket)
|
|
152
|
+
|
|
153
|
+
All Rulebricks data lives in a single GCS bucket; decision logs and database
|
|
154
|
+
backups are key prefixes (`decision-logs/`, `db-backups/`) within it. Create one
|
|
155
|
+
Google service account and the bucket — this is deployment-independent:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
PROJECT_ID="$(gcloud config get-value project)"
|
|
159
|
+
CLUSTER_NAME=rulebricks-cluster
|
|
160
|
+
GSA=rulebricks@"$PROJECT_ID".iam.gserviceaccount.com
|
|
161
|
+
BUCKET="$CLUSTER_NAME-data"
|
|
162
|
+
|
|
163
|
+
gcloud iam service-accounts create rulebricks --project "$PROJECT_ID"
|
|
164
|
+
|
|
165
|
+
# Create the single data bucket and grant read/write/delete (delete is needed so
|
|
166
|
+
# the backup job can prune backups older than the retention window).
|
|
167
|
+
gcloud storage buckets create "gs://$BUCKET" --project "$PROJECT_ID" --location "$REGION"
|
|
168
|
+
gcloud storage buckets add-iam-policy-binding "gs://$BUCKET" \
|
|
169
|
+
--member "serviceAccount:$GSA" \
|
|
170
|
+
--role roles/storage.objectAdmin
|
|
171
|
+
|
|
172
|
+
# Prometheus remote write to Google Managed Prometheus (skip if unused).
|
|
173
|
+
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
|
|
174
|
+
--member "serviceAccount:$GSA" \
|
|
175
|
+
--role roles/monitoring.metricWriter
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The per-namespace `roles/iam.workloadIdentityUser` bindings (for `vector`,
|
|
179
|
+
`<release>-backup`, and `prometheus`) are **created by the Rulebricks CLI at
|
|
180
|
+
`rulebricks deploy` time**, since they're namespace-scoped — so this setup stays
|
|
181
|
+
generic and one cluster can host many deployments. Enter the Google service
|
|
182
|
+
account email (`$GSA`) and the `$BUCKET` name when prompted by the CLI.
|
|
183
|
+
|
|
184
|
+
## Notes
|
|
185
|
+
|
|
186
|
+
- The example creates two `n2-standard-4` nodes initially and enables autoscaling up to four nodes. The initial nodes provide 8 vCPU total for the compact Rulebricks cluster shape while avoiding single-node pod density limits.
|
|
187
|
+
- If you change `REGION`, choose a `ZONE` where the selected machine type is available.
|
|
188
|
+
- Regional GKE clusters can multiply node counts across node locations. This example pins one node location to keep the minimum cluster shape predictable.
|
|
189
|
+
- The public web firewall rule allows HTTP and HTTPS to the node pool so Kubernetes LoadBalancer services and cert-manager HTTP-01 validation can receive internet traffic.
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Rulebricks GKE prerequisite check.
|
|
3
|
+
#
|
|
4
|
+
# Prints a short pass/fail report and a final READY / NOT READY verdict
|
|
5
|
+
# with the exact actions you need to take before running the GKE deploy.
|
|
6
|
+
#
|
|
7
|
+
# Env vars:
|
|
8
|
+
# GOOGLE_CLOUD_PROJECT GCP project id (defaults to gcloud config)
|
|
9
|
+
# GCP_REGION Region to check (default: us-central1)
|
|
10
|
+
# VERBOSE=1 Print raw gcloud error messages inline
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
if [[ -z "${BASH_VERSION:-}" ]]; then
|
|
15
|
+
exec bash "$0" "$@"
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Quiet gcloud's interactive nags / survey output.
|
|
19
|
+
export CLOUDSDK_CORE_DISABLE_PROMPTS=1
|
|
20
|
+
|
|
21
|
+
PROJECT_ID="${GOOGLE_CLOUD_PROJECT:-$(gcloud config get-value project 2>/dev/null || true)}"
|
|
22
|
+
REGION="${GCP_REGION:-us-central1}"
|
|
23
|
+
REQUIRED_VCPU=8
|
|
24
|
+
VERBOSE="${VERBOSE:-0}"
|
|
25
|
+
|
|
26
|
+
REQUIRED_APIS=(
|
|
27
|
+
compute.googleapis.com
|
|
28
|
+
container.googleapis.com
|
|
29
|
+
iam.googleapis.com
|
|
30
|
+
cloudresourcemanager.googleapis.com
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
ACTIONS=()
|
|
34
|
+
BLOCKERS=0
|
|
35
|
+
|
|
36
|
+
# ---------- helpers ----------
|
|
37
|
+
|
|
38
|
+
require_cmd() {
|
|
39
|
+
command -v "$1" >/dev/null 2>&1 || {
|
|
40
|
+
printf "ERROR: required command not found: %s\n" "$1" >&2
|
|
41
|
+
exit 1
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Run a gcloud command. Sets GC_STDOUT / GC_STDERR / GC_RC. Never aborts.
|
|
46
|
+
gc_run() {
|
|
47
|
+
GC_STDOUT=""; GC_STDERR=""; GC_RC=0
|
|
48
|
+
local _err
|
|
49
|
+
_err="$(mktemp)"
|
|
50
|
+
GC_STDOUT="$(gcloud "$@" 2>"$_err")" || GC_RC=$?
|
|
51
|
+
GC_STDERR="$(cat "$_err")"
|
|
52
|
+
rm -f "$_err"
|
|
53
|
+
if [[ "$VERBOSE" == "1" && -n "$GC_STDERR" ]]; then
|
|
54
|
+
printf " debug: %s\n" "${GC_STDERR%%$'\n'*}" >&2
|
|
55
|
+
fi
|
|
56
|
+
return "$GC_RC"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
is_auth_error() {
|
|
60
|
+
[[ "$GC_STDERR" == *"reauthentication"* ]] && return 0
|
|
61
|
+
[[ "$GC_STDERR" == *"credentials"*"expired"* ]] && return 0
|
|
62
|
+
[[ "$GC_STDERR" == *"invalid_grant"* ]] && return 0
|
|
63
|
+
[[ "$GC_STDERR" == *"gcloud auth login"* ]] && return 0
|
|
64
|
+
[[ "$GC_STDERR" == *"gcloud auth application-default login"* ]] && return 0
|
|
65
|
+
[[ "$GC_STDERR" == *"There was a problem refreshing"* ]] && return 0
|
|
66
|
+
[[ "$GC_STDERR" == *"do not have active credentials"* ]] && return 0
|
|
67
|
+
return 1
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
row() {
|
|
71
|
+
printf " %-50s %s\n" "$1" "$2"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
mark_blocker() { BLOCKERS=$((BLOCKERS + 1)); }
|
|
75
|
+
add_action() { ACTIONS+=("$1"); }
|
|
76
|
+
|
|
77
|
+
# ---------- pre-flight ----------
|
|
78
|
+
|
|
79
|
+
require_cmd gcloud
|
|
80
|
+
require_cmd kubectl
|
|
81
|
+
require_cmd helm
|
|
82
|
+
require_cmd awk
|
|
83
|
+
|
|
84
|
+
printf "Rulebricks GKE prerequisite check\n"
|
|
85
|
+
printf " Region: %s\n" "$REGION"
|
|
86
|
+
printf " Project: %s\n" "${PROJECT_ID:-<unset>}"
|
|
87
|
+
printf "\n"
|
|
88
|
+
|
|
89
|
+
if [[ -z "$PROJECT_ID" ]]; then
|
|
90
|
+
row "GCP project configured" "FAIL - no project set"
|
|
91
|
+
add_action "Set a project: gcloud config set project <PROJECT_ID>"
|
|
92
|
+
printf "\n========================================\n"
|
|
93
|
+
printf "RESULT: NOT READY\n"
|
|
94
|
+
printf "========================================\n"
|
|
95
|
+
printf "Required actions:\n 1. %s\n" "${ACTIONS[0]}"
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# ---------- 1. Authentication ----------
|
|
100
|
+
AUTH_OK=0
|
|
101
|
+
ACTIVE_ACCOUNT=""
|
|
102
|
+
|
|
103
|
+
if gc_run auth list --filter=status:ACTIVE --format="value(account)"; then
|
|
104
|
+
ACTIVE_ACCOUNT="$(printf '%s' "$GC_STDOUT" | head -n 1)"
|
|
105
|
+
if [[ -z "$ACTIVE_ACCOUNT" ]]; then
|
|
106
|
+
row "gcloud user signed in" "FAIL - no active account"
|
|
107
|
+
add_action "Run: gcloud auth login"
|
|
108
|
+
mark_blocker
|
|
109
|
+
else
|
|
110
|
+
row "gcloud user signed in" "OK ($ACTIVE_ACCOUNT)"
|
|
111
|
+
|
|
112
|
+
# Now verify ADC actually works against Google APIs - this is what
|
|
113
|
+
# the deploy tooling uses, and the most common breakage.
|
|
114
|
+
if gc_run auth application-default print-access-token; then
|
|
115
|
+
row "Application Default Credentials" "OK"
|
|
116
|
+
AUTH_OK=1
|
|
117
|
+
else
|
|
118
|
+
if is_auth_error; then
|
|
119
|
+
row "Application Default Credentials" "FAIL - ADC missing or expired"
|
|
120
|
+
else
|
|
121
|
+
row "Application Default Credentials" "FAIL - ${GC_STDERR%%$'\n'*}"
|
|
122
|
+
fi
|
|
123
|
+
add_action "Run: gcloud auth application-default login"
|
|
124
|
+
mark_blocker
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
else
|
|
128
|
+
row "gcloud user signed in" "FAIL - ${GC_STDERR%%$'\n'*}"
|
|
129
|
+
add_action "Run: gcloud auth login && gcloud auth application-default login"
|
|
130
|
+
mark_blocker
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
if [[ $AUTH_OK -eq 0 ]]; then
|
|
134
|
+
printf "\nRemaining checks skipped - fix authentication first.\n"
|
|
135
|
+
printf "\n========================================\n"
|
|
136
|
+
printf "RESULT: NOT READY\n"
|
|
137
|
+
printf "========================================\n"
|
|
138
|
+
printf "Required actions:\n"
|
|
139
|
+
i=1
|
|
140
|
+
for a in "${ACTIONS[@]}"; do
|
|
141
|
+
printf " %d. %s\n" "$i" "$a"
|
|
142
|
+
i=$((i + 1))
|
|
143
|
+
done
|
|
144
|
+
exit 1
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# ---------- 2. Project access ----------
|
|
148
|
+
if gc_run projects describe "$PROJECT_ID" --format="value(projectId)"; then
|
|
149
|
+
row "Project '$PROJECT_ID' accessible" "OK"
|
|
150
|
+
else
|
|
151
|
+
row "Project '$PROJECT_ID' accessible" "FAIL - ${GC_STDERR%%$'\n'*}"
|
|
152
|
+
add_action "Ensure '$ACTIVE_ACCOUNT' has at least roles/viewer on project '$PROJECT_ID', or fix the project id."
|
|
153
|
+
mark_blocker
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
# ---------- 3. Required APIs ----------
|
|
157
|
+
missing_apis=()
|
|
158
|
+
unknown_apis=()
|
|
159
|
+
if gc_run services list --project "$PROJECT_ID" --enabled --format="value(config.name)"; then
|
|
160
|
+
enabled="$GC_STDOUT"
|
|
161
|
+
for api in "${REQUIRED_APIS[@]}"; do
|
|
162
|
+
if ! printf '%s\n' "$enabled" | grep -qx "$api"; then
|
|
163
|
+
missing_apis+=("$api")
|
|
164
|
+
fi
|
|
165
|
+
done
|
|
166
|
+
else
|
|
167
|
+
unknown_apis=("${REQUIRED_APIS[@]}")
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
total=${#REQUIRED_APIS[@]}
|
|
171
|
+
|
|
172
|
+
if [[ ${#unknown_apis[@]} -gt 0 ]]; then
|
|
173
|
+
row "Required APIs enabled" "WARN - could not list enabled services"
|
|
174
|
+
add_action "Verify 'serviceusage.services.list' permission, then re-run."
|
|
175
|
+
elif [[ ${#missing_apis[@]} -eq 0 ]]; then
|
|
176
|
+
row "Required APIs enabled" "OK ($total/$total)"
|
|
177
|
+
else
|
|
178
|
+
enabled_count=$((total - ${#missing_apis[@]}))
|
|
179
|
+
row "Required APIs enabled" "WARN ($enabled_count/$total)"
|
|
180
|
+
enable_cmd="gcloud services enable ${missing_apis[*]} --project $PROJECT_ID"
|
|
181
|
+
add_action "Enable missing APIs (takes ~1 min):"
|
|
182
|
+
add_action " $enable_cmd"
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
# ---------- 4. Region + GKE access ----------
|
|
186
|
+
REGION_OK=1
|
|
187
|
+
if ! gc_run compute regions describe "$REGION" --project "$PROJECT_ID" --format="value(name)"; then
|
|
188
|
+
REGION_OK=0
|
|
189
|
+
row "Region '$REGION' accessible" "FAIL - ${GC_STDERR%%$'\n'*}"
|
|
190
|
+
add_action "Verify region name '$REGION' and that the Compute Engine API is enabled."
|
|
191
|
+
mark_blocker
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
if [[ $REGION_OK -eq 1 ]]; then
|
|
195
|
+
if gc_run container clusters list --region "$REGION" --project "$PROJECT_ID" --format="value(name)"; then
|
|
196
|
+
row "Region + GKE list access" "OK"
|
|
197
|
+
else
|
|
198
|
+
row "Region + GKE list access" "WARN - ${GC_STDERR%%$'\n'*}"
|
|
199
|
+
add_action "Ensure '$ACTIVE_ACCOUNT' has roles/container.viewer (or higher) on '$PROJECT_ID'."
|
|
200
|
+
fi
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# ---------- 5. Regional CPU quota ----------
|
|
204
|
+
quota_label="Regional CPU quota in $REGION (need ${REQUIRED_VCPU}+)"
|
|
205
|
+
quota_line=""
|
|
206
|
+
if gc_run compute regions describe "$REGION" --project "$PROJECT_ID" \
|
|
207
|
+
--format="csv[no-heading](quotas.metric,quotas.limit,quotas.usage)"; then
|
|
208
|
+
quota_line="$(printf '%s\n' "$GC_STDOUT" | awk -F, '$1=="CPUS"{print $2 "," $3; exit}')"
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
if [[ -z "$quota_line" ]]; then
|
|
212
|
+
row "$quota_label" "WARN - could not read quota"
|
|
213
|
+
add_action "Manually check CPU quota in Console → IAM & Admin → Quotas (region: $REGION)."
|
|
214
|
+
else
|
|
215
|
+
limit="${quota_line%,*}"
|
|
216
|
+
usage="${quota_line#*,}"
|
|
217
|
+
available="$(awk -v l="$limit" -v u="$usage" 'BEGIN { printf "%d", l - u }')"
|
|
218
|
+
if (( available < REQUIRED_VCPU )); then
|
|
219
|
+
row "$quota_label" "WARN ($available/$limit free)"
|
|
220
|
+
add_action "Request CPU quota increase: Console → IAM & Admin → Quotas → 'Compute Engine API CPUs' in $REGION."
|
|
221
|
+
else
|
|
222
|
+
row "$quota_label" "OK ($available/$limit free)"
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
# ---------- 6. Local tools ----------
|
|
227
|
+
if kubectl version --client=true >/dev/null 2>&1 && helm version >/dev/null 2>&1; then
|
|
228
|
+
row "Local tools (kubectl, helm)" "OK"
|
|
229
|
+
else
|
|
230
|
+
row "Local tools (kubectl, helm)" "FAIL"
|
|
231
|
+
add_action "Install/repair kubectl and helm."
|
|
232
|
+
mark_blocker
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
# ---------- summary ----------
|
|
236
|
+
printf "\n========================================\n"
|
|
237
|
+
if [[ $BLOCKERS -eq 0 && ${#ACTIONS[@]} -eq 0 ]]; then
|
|
238
|
+
printf "RESULT: READY - you can run the GKE deploy.\n"
|
|
239
|
+
printf "========================================\n"
|
|
240
|
+
exit 0
|
|
241
|
+
elif [[ $BLOCKERS -eq 0 ]]; then
|
|
242
|
+
printf "RESULT: READY WITH WARNINGS\n"
|
|
243
|
+
printf "========================================\n"
|
|
244
|
+
printf "The deploy should work, but address these first if possible:\n"
|
|
245
|
+
else
|
|
246
|
+
printf "RESULT: NOT READY\n"
|
|
247
|
+
printf "========================================\n"
|
|
248
|
+
printf "Required actions:\n"
|
|
249
|
+
fi
|
|
250
|
+
|
|
251
|
+
i=1
|
|
252
|
+
for a in "${ACTIONS[@]}"; do
|
|
253
|
+
printf " %d. %s\n" "$i" "$a"
|
|
254
|
+
i=$((i + 1))
|
|
255
|
+
done
|
|
256
|
+
|
|
257
|
+
printf "\nRe-run this script after completing the actions above.\n"
|
|
258
|
+
printf "(Set VERBOSE=1 to see raw gcloud error messages.)\n"
|
|
259
|
+
|
|
260
|
+
[[ $BLOCKERS -gt 0 ]] && exit 1 || exit 0
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Box, Text, useApp } from "ink";
|
|
4
|
+
import { BorderBox, Logo, Spinner, StatusLine, ThemeProvider, useTheme, CommandApprovalProvider, } from "../components/common/index.js";
|
|
5
|
+
import { loadDeploymentConfig } from "../lib/config.js";
|
|
6
|
+
import { updateKubeconfig } from "../lib/cloudCli.js";
|
|
7
|
+
import { CommandDeniedError } from "../lib/commandApproval.js";
|
|
8
|
+
import { checkClusterAccessible, createJobFromCronJob, isKubectlInstalled, waitForJobComplete, } from "../lib/kubernetes.js";
|
|
9
|
+
import { getNamespace, getReleaseName } from "../types/index.js";
|
|
10
|
+
function k8sName(value) {
|
|
11
|
+
return value.toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(0, 63).replace(/-+$/, "");
|
|
12
|
+
}
|
|
13
|
+
function BackupCommandInner({ name }) {
|
|
14
|
+
const { exit } = useApp();
|
|
15
|
+
const { colors } = useTheme();
|
|
16
|
+
const [step, setStep] = useState("loading");
|
|
17
|
+
const [error, setError] = useState(null);
|
|
18
|
+
const [logs, setLogs] = useState("");
|
|
19
|
+
const [status, setStatus] = useState({
|
|
20
|
+
preflight: "pending",
|
|
21
|
+
job: "pending",
|
|
22
|
+
});
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
runBackup();
|
|
25
|
+
}, []);
|
|
26
|
+
async function runBackup() {
|
|
27
|
+
try {
|
|
28
|
+
const config = await loadDeploymentConfig(name);
|
|
29
|
+
validateConfig(config);
|
|
30
|
+
setStep("preflight");
|
|
31
|
+
setStatus((current) => ({ ...current, preflight: "running" }));
|
|
32
|
+
await runPreflight(config);
|
|
33
|
+
setStatus((current) => ({ ...current, preflight: "success" }));
|
|
34
|
+
const namespace = getNamespace(config.name);
|
|
35
|
+
const releaseName = getReleaseName(config.name);
|
|
36
|
+
const cronJobName = `${releaseName}-db-backup`;
|
|
37
|
+
const jobName = k8sName(`${cronJobName}-manual-${Date.now()}`);
|
|
38
|
+
setStep("running");
|
|
39
|
+
setStatus((current) => ({ ...current, job: "running" }));
|
|
40
|
+
await createJobFromCronJob(namespace, cronJobName, jobName);
|
|
41
|
+
const jobLogs = await waitForJobComplete(namespace, jobName);
|
|
42
|
+
setLogs(jobLogs);
|
|
43
|
+
setStatus((current) => ({ ...current, job: "success" }));
|
|
44
|
+
setStep("complete");
|
|
45
|
+
setTimeout(() => exit(), 5000);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
setError(err instanceof Error ? err.message : "Backup failed");
|
|
49
|
+
setStatus((current) => ({
|
|
50
|
+
...current,
|
|
51
|
+
preflight: step === "preflight" ? "error" : current.preflight,
|
|
52
|
+
job: step === "running" ? "error" : current.job,
|
|
53
|
+
}));
|
|
54
|
+
setStep("error");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function validateConfig(config) {
|
|
58
|
+
if (config.database.type !== "self-hosted") {
|
|
59
|
+
throw new Error("Backups are only available for self-hosted Supabase.");
|
|
60
|
+
}
|
|
61
|
+
if (!config.storage) {
|
|
62
|
+
throw new Error("Shared object storage is required for database backups.");
|
|
63
|
+
}
|
|
64
|
+
if (!config.backup?.enabled) {
|
|
65
|
+
throw new Error("Database backups are disabled for this deployment. Re-run `rulebricks init` to enable them.");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function runPreflight(config) {
|
|
69
|
+
if (!(await isKubectlInstalled())) {
|
|
70
|
+
throw new Error("kubectl is not installed. Please install kubectl first.");
|
|
71
|
+
}
|
|
72
|
+
let clusterError = await checkClusterAccessible();
|
|
73
|
+
if (clusterError &&
|
|
74
|
+
config.infrastructure.provider &&
|
|
75
|
+
config.infrastructure.region &&
|
|
76
|
+
config.infrastructure.clusterName) {
|
|
77
|
+
try {
|
|
78
|
+
await updateKubeconfig(config.infrastructure.provider, config.infrastructure.clusterName, config.infrastructure.region, {
|
|
79
|
+
gcpProjectId: config.infrastructure.gcpProjectId,
|
|
80
|
+
azureResourceGroup: config.infrastructure.azureResourceGroup,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
if (!(err instanceof CommandDeniedError)) {
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
clusterError = await checkClusterAccessible();
|
|
89
|
+
}
|
|
90
|
+
if (clusterError) {
|
|
91
|
+
throw new Error(`Cannot access Kubernetes cluster:\n${clusterError}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (step === "error") {
|
|
95
|
+
return (_jsx(BorderBox, { title: "Backup Failed", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.error, bold: true, children: "\u2717 Error" }), _jsx(Text, { color: colors.error, children: error })] }) }));
|
|
96
|
+
}
|
|
97
|
+
if (step === "complete") {
|
|
98
|
+
return (_jsx(BorderBox, { title: "Backup Complete", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.success, bold: true, children: "\u2713 Database backup completed" }), logs && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.muted, children: "Job output:" }), logs.split("\n").slice(-8).map((line, index) => (_jsx(Text, { color: colors.muted, children: line }, index)))] }))] }) }));
|
|
99
|
+
}
|
|
100
|
+
return (_jsx(BorderBox, { title: `Backing Up ${name}`, children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(StatusLine, { status: status.preflight, label: "Preflight checks" }), _jsx(StatusLine, { status: status.job, label: "Database backup job" }), _jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: step === "running" ? "Running backup job..." : "Preparing backup..." }) })] }) }));
|
|
101
|
+
}
|
|
102
|
+
export function BackupCommand(props) {
|
|
103
|
+
return (_jsxs(ThemeProvider, { theme: "status", children: [_jsx(Logo, {}), _jsx(CommandApprovalProvider, { children: _jsx(BackupCommandInner, { ...props }) })] }));
|
|
104
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
interface DeployCommandProps {
|
|
2
2
|
name: string;
|
|
3
|
-
skipInfra?: boolean;
|
|
4
3
|
skipDns?: boolean;
|
|
5
4
|
version?: string;
|
|
5
|
+
regenerateValues?: boolean;
|
|
6
|
+
assumeDnsConfigured?: boolean;
|
|
7
|
+
inlineSecrets?: boolean;
|
|
6
8
|
}
|
|
7
9
|
export declare function DeployCommand(props: DeployCommandProps): import("react/jsx-runtime").JSX.Element;
|
|
8
10
|
export {};
|