From 560cfe0e76290fd03d5537f877febf74efce641d Mon Sep 17 00:00:00 2001 From: Gabriel Mechali Date: Tue, 18 Aug 2026 18:48:11 -0400 Subject: [PATCH 1/6] Deployed a Testbed into datcom-dcp, this script allows anyoneto quickly connect to test it out. Note it is IAM protected. --- tests/testbed/README.md | 162 +++++++++++++++++ tests/testbed/connect.sh | 374 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 536 insertions(+) create mode 100644 tests/testbed/README.md create mode 100755 tests/testbed/connect.sh diff --git a/tests/testbed/README.md b/tests/testbed/README.md new file mode 100644 index 00000000..f27834b4 --- /dev/null +++ b/tests/testbed/README.md @@ -0,0 +1,162 @@ +# Data Commons Platform (DCP) — Developer Testbeds + +## 🎯 Overview + +DCP Testbeds (e.g. `testbed-1`, `testbed-2`) are shared, pre-warmed Google Cloud environments running in the **`datcom-dcp`** project. + +They allow any engineer on the team to **deploy and test custom container builds or release candidates in under 2 minutes** without having to provision cloud infrastructure from scratch or copy API keys. + +--- + +## 🏗 Architecture + +``` + ┌──────────────────────────────────────────────┐ + │ GCP Project: datcom-dcp │ + │ - Secret Manager: dcp-testbed-1-tfvars │ + │ - GCS Remote State: tf-state-testbed-1-... │ + │ - Workflow Service Account (TokenCreator) │ + │ - Cloud Run, Spanner DB, Networking │ + └──────────────────────┬───────────────────────┘ + │ + ┌────────────────────────────┴────────────────────────────┐ + │ │ + 1. Connect & Sync 3. Push & Persist + `./tests/testbed/connect.sh connect` `./tests/testbed/connect.sh push-config` + - Pulls tfvars from Secret Manager - Saves updated tfvars back to + - Configures remote backend state Secret Manager so the whole team + - Configures SA Impersonation for CLI stays in sync. + - Sets up `workspaces/testbed-1` +``` + +--- + +## 📋 Prerequisites + +1. **Google Cloud SDK (`gcloud`)** authenticated with access to `datcom-dcp`: + ```bash + gcloud auth login + gcloud auth application-default login + ``` + +2. **Terraform (`>= 1.5.0`)** installed: + ```bash + terraform -version + ``` + +--- + +## 🚀 Step-by-Step Developer Workflow + +### Step 1: Connect to a Testbed + +Run the connect script from the repository root: + +```bash +# Connect directly to testbed-1: +./tests/testbed/connect.sh connect --instance testbed-1 + +# OR run interactively to choose from available testbeds: +./tests/testbed/connect.sh connect +``` + +**What the script does automatically:** +1. **Pulls Configuration:** Fetches `dcp-testbed-1-tfvars` from GCP Secret Manager. +2. **Wires Remote State:** Points Terraform backend to `gs://tf-state-testbed-1-datcom-dcp`. +3. **Initializes Workspace:** Scaffolds and runs `terraform init` inside `tests/testbed/workspaces/testbed-1/`. +4. **Configures IAM Impersonation:** Grants your user account `roles/iam.serviceAccountTokenCreator` on the testbed's Ingestion Workflow Service Account so you can run `datacommons` CLI commands seamlessly. + +--- + +### Step 2: Override Container Images or Versions + +You are now inside your workspace (`tests/testbed/workspaces/testbed-1/`). + +Open `terraform.tfvars` in your editor. At the bottom of the file, uncomment the override for the image or version you want to test: + +```hcl +# ============================================================================= +# DEVELOPER TESTBED OVERRIDES +# ============================================================================= + +# --- Option A: Test a platform version tag across all services --- +# dcp_version = "1.1.2-rc1" + +# --- Option B: Test granular custom container builds --- +# 1. Main Data Commons Web & Serving Service: +datacommons_services_image = "gcr.io/datcom-ci/datacommons-services:my-feature-branch" + +# 2. Ingestion Helper API Service: +# ingestion_helper_service_image = "gcr.io/datcom-ci/datacommons-ingestion-helper:my-fix" + +# 3. Ingestion Preprocessing Cloud Run Job: +# ingestion_preprocessing_job_image = "gcr.io/datcom-ci/datacommons-preprocessing:my-job" + +# 4. Ingestion Postprocessing Cloud Run Job: +# ingestion_postprocessing_job_image = "gcr.io/datcom-ci/datacommons-postprocessing:my-job" + +# 5. Dataflow Flex Template (same bucket, custom template filename): +# ingestion_dataflow_template_gcs_path = "gs://datcom-templates/templates/flex/ingestion-custom-name.json" +``` + +Apply your changes to GCP: + +```bash +terraform apply +``` +*Terraform will roll out a new Cloud Run revision with your custom image in ~60–90 seconds.* + +--- + +### Step 3: Running CLI Commands (Service Account Impersonation) + +To execute CLI commands against this testbed, run them using `uv` or your virtual environment: + +```bash +# Execute commands via uv (Recommended): +uv run datacommons ... + +# Or if installed in your activated virtual environment: +datacommons ... +``` + +The CLI automatically impersonates the testbed's ingestion workflow service account using the TokenCreator IAM role that `connect.sh` configured in Step 1. + +If you ever need to manually bind the impersonation permission for a teammate: +```bash +# 1. Get the service account email from your workspace: +terraform output ingestion_workflow_service_account_email + +# 2. Bind the TokenCreator role: +gcloud iam service-accounts add-iam-policy-binding "SERVICE_ACCOUNT_EMAIL" \ + --member="user:YOUR_USER_ACCOUNT" \ + --role="roles/iam.serviceAccountTokenCreator" \ + --project="datcom-dcp" +``` + +--- + +### Step 4: Persisting Configuration (`push-config`) + +If you want your updated configuration or image to remain the **shared baseline** for the testbed: + +```bash +../../connect.sh push-config --instance testbed-1 +``` + +**When to push:** +* After verifying a release candidate or stable container image that should stay deployed. +* After adding or rotating a shared testbed variable. + +**When NOT to push:** +* If you were only running a temporary, one-off test. (In that case, revert your local edit in `terraform.tfvars` and run `terraform apply` to restore the baseline). + +--- + +## 🔍 Discovery & Status + +### List All Registered Testbeds +```bash +./tests/testbed/connect.sh list +``` +Displays all registered testbed secrets in `datcom-dcp` and allows interactive selection to connect immediately. diff --git a/tests/testbed/connect.sh b/tests/testbed/connect.sh new file mode 100755 index 00000000..e08c913d --- /dev/null +++ b/tests/testbed/connect.sh @@ -0,0 +1,374 @@ +#!/usr/bin/env bash +# ============================================================================== +# Data Commons Platform (DCP) Developer Testbed CLI +# ============================================================================== +# Enables rapid connection, configuration synchronization, and IAM impersonation +# for shared and developer testbeds in Google Cloud Platform. +# ============================================================================== + +set -eo pipefail + +# Find repository root and testbed directories +TESTBED_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${TESTBED_DIR}/../.." && pwd)" +WORKSPACES_ROOT="${TESTBED_DIR}/workspaces" +INFRA_DCP_DIR="${REPO_ROOT}/infra/dcp" + +# Default project if not specified +DEFAULT_PROJECT="datcom-dcp" + +print_usage() { + cat < [options] + +Commands: + connect Connect to a testbed (pulls config, inits Terraform, checks IAM impersonation) + push-config Save and push local terraform.tfvars back to GCP Secret Manager + list List available testbeds in the project + +Options: + --instance Instance name (e.g. testbed-1, testbed-alpha, alice) + --project GCP Project ID (default: ${DEFAULT_PROJECT}) + +Developer Workflow: + 1. Connect to an instance (interactive or via flag): + $0 connect --instance testbed-1 + + 2. Navigate to your workspace, edit terraform.tfvars, and apply: + cd tests/testbed/workspaces/testbed-1 + terraform apply + + 3. Push your updated configuration back to the team secret: + $0 push-config --instance testbed-1 + + 4. List all active testbeds: + $0 list +HELP + exit 1 +} + +# Ensure dependencies exist +check_dependencies() { + local missing=0 + + if ! command -v gcloud &>/dev/null; then + echo "Error: 'gcloud' CLI is not installed or not in PATH." + echo " Install Google Cloud SDK: https://cloud.google.com/sdk/docs/install" + missing=1 + fi + + if ! command -v terraform &>/dev/null; then + echo "Error: 'terraform' CLI is not installed or not in PATH." + echo " Install Terraform: https://developer.hashicorp.com/terraform/install" + missing=1 + fi + + if [[ $missing -eq 1 ]]; then + exit 1 + fi + + # Check for datacommons CLI (warning if not in PATH or uv) + if ! command -v datacommons &>/dev/null && ! uv run datacommons --help &>/dev/null 2>&1; then + echo "Notice: 'datacommons' CLI is not installed in PATH." + echo " To run ingestion/workflow CLI commands, install it via: pip install -e packages/datacommons-cli" + echo " (or execute via: uv run datacommons )" + echo "" + fi +} + +ACTION="$1" +if [[ "$ACTION" == "--help" || "$ACTION" == "-h" ]]; then + print_usage +elif [[ "$ACTION" == "connect" || "$ACTION" == "push-config" || "$ACTION" == "list" ]]; then + shift +elif [[ "$ACTION" == --* || -z "$ACTION" ]]; then + ACTION="connect" +else + echo "Error: Unknown command '$ACTION'" + print_usage +fi + +INSTANCE="" +PROJECT="$DEFAULT_PROJECT" + +while [[ $# -gt 0 ]]; do + case "$1" in + --instance) + INSTANCE="$2" + shift 2 + ;; + --project) + PROJECT="$2" + shift 2 + ;; + --help|-h) + print_usage + ;; + *) + echo "Error: Unknown option: $1" + print_usage + ;; + esac +done + +# Auto-infer instance name if running inside a workspace folder (e.g. tests/testbed/workspaces/testbed-1) +if [[ -z "$INSTANCE" ]]; then + CURRENT_DIR="$(pwd)" + if [[ "$CURRENT_DIR" == *"/workspaces/"* ]]; then + INSTANCE="$(basename "$CURRENT_DIR")" + echo "==> Auto-detected instance '$INSTANCE' from current directory." + fi +fi + +check_dependencies + +# ============================================================================== +# ACTION: LIST +# ============================================================================== +if [[ "$ACTION" == "list" ]]; then + echo "================================================================================" + echo "DCP TESTBEDS in project: ${PROJECT}" + echo "================================================================================" + + echo "Fetching registered testbed secrets from Secret Manager..." + SECRETS=$(gcloud secrets list --project="${PROJECT}" --format="value(name)" 2>/dev/null || true) + + found=0 + options=() + for s in $SECRETS; do + secret_id=$(basename "$s") + if [[ "$secret_id" =~ ^dcp-(.+)-tfvars$ ]]; then + if [[ $found -eq 0 ]]; then + printf "%-5s %-25s %-35s\n" "#" "INSTANCE NAME" "SECRET NAME" + printf "%-5s %-25s %-35s\n" "--" "-------------" "-----------" + fi + inst_name="${BASH_REMATCH[1]}" + options+=("$inst_name") + found=$((found + 1)) + printf "%-5s %-25s %-35s\n" "$found" "$inst_name" "$secret_id" + fi + done + + if [[ $found -eq 0 ]]; then + echo "No 'dcp-*-tfvars' secrets found in project '${PROJECT}'." + exit 0 + fi + + # If running interactively, prompt to connect directly + if [[ -t 0 ]]; then + echo "" + read -p "Select a testbed to connect to [1-$found, or press Enter to exit]: " choice + if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= found )); then + INSTANCE="${options[$((choice - 1))]}" + ACTION="connect" + echo "" + else + exit 0 + fi + else + exit 0 + fi +fi + +# Interactive prompt to select or enter an instance if --instance was omitted +prompt_instance_if_missing() { + if [[ -n "$INSTANCE" ]]; then + return + fi + + # If not running interactively (e.g. CI/CD), error out + if [[ ! -t 0 ]]; then + echo "Error: --instance is required in non-interactive mode." + exit 1 + fi + + echo "==> No --instance provided. Querying available testbeds in '${PROJECT}'..." + local secrets + secrets=$(gcloud secrets list --project="${PROJECT}" --format="value(name)" 2>/dev/null || true) + + local options=() + for s in $secrets; do + local secret_id + secret_id=$(basename "$s") + if [[ "$secret_id" =~ ^dcp-(.+)-tfvars$ ]]; then + options+=("${BASH_REMATCH[1]}") + fi + done + + echo "" + if [[ ${#options[@]} -gt 0 ]]; then + echo "Available testbeds:" + local i=1 + for opt in "${options[@]}"; do + echo " $i) $opt" + ((i++)) + done + echo " $i) [Enter a custom instance name]" + echo "" + read -p "Select a testbed (1-$i): " choice + + if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice < i )); then + INSTANCE="${options[$((choice - 1))]}" + elif [[ "$choice" =~ ^[0-9]+$ ]] && (( choice == i )); then + read -p "Enter instance name: " custom_name + INSTANCE="$custom_name" + else + # If user typed the name directly + INSTANCE="$choice" + fi + else + read -p "No existing testbeds found. Enter instance name to create/connect: " INSTANCE + fi + + if [[ -z "$INSTANCE" ]]; then + echo "Error: Instance name cannot be empty." + exit 1 + fi + + echo "==> Selected instance: '$INSTANCE'" + echo "" +} + +prompt_instance_if_missing + +SECRET_NAME="dcp-${INSTANCE}-tfvars" +WORKSPACE_DIR="${WORKSPACES_ROOT}/${INSTANCE}" +STATE_BUCKET="tf-state-${INSTANCE}-${PROJECT}" + +# ============================================================================== +# ACTION: CONNECT +# ============================================================================== +if [[ "$ACTION" == "connect" ]]; then + echo "==> [1/5] Connecting to testbed '${INSTANCE}' in project '${PROJECT}'..." + mkdir -p "$WORKSPACE_DIR" + + echo "==> [2/5] Pulling configuration from Secret Manager ($SECRET_NAME)..." + if gcloud secrets describe "$SECRET_NAME" --project="$PROJECT" &>/dev/null; then + gcloud secrets versions access latest \ + --secret="$SECRET_NAME" \ + --project="$PROJECT" > "$WORKSPACE_DIR/terraform.tfvars" + echo " Successfully fetched terraform.tfvars from Secret Manager." + else + echo " Warning: Secret '$SECRET_NAME' does not exist in Secret Manager." + if [[ ! -f "$WORKSPACE_DIR/terraform.tfvars" ]]; then + echo " Creating new boilerplate terraform.tfvars for '${INSTANCE}'..." + cat < "$WORKSPACE_DIR/terraform.tfvars" +project_id = "${PROJECT}" +instance_name = "${INSTANCE}" +region = "us-central1" +TFVARS + fi + fi + + echo "==> [3/5] Setting up remote GCS backend state..." + cat < "$WORKSPACE_DIR/backend.tf" +terraform { + backend "gcs" { + bucket = "${STATE_BUCKET}" + prefix = "terraform/state/${INSTANCE}" + } +} +BACKEND + + # Copy root terraform definition files into workspace and symlink modules + echo "==> [4/5] Syncing Terraform scaffolding..." + cp "${INFRA_DCP_DIR}/main.tf" "$WORKSPACE_DIR/main.tf" + cp "${INFRA_DCP_DIR}/variables.tf" "$WORKSPACE_DIR/variables.tf" + cp "${INFRA_DCP_DIR}/outputs.tf" "$WORKSPACE_DIR/outputs.tf" + ln -sfn "${INFRA_DCP_DIR}/modules" "$WORKSPACE_DIR/modules" + + ( + cd "$WORKSPACE_DIR" + echo " Running terraform init..." + terraform init + ) + + # Check & configure service account impersonation for CLI commands + echo "==> [5/5] Checking Service Account impersonation permissions..." + CURRENT_USER=$(gcloud config get-value account 2>/dev/null || true) + WORKFLOW_SA=$(cd "$WORKSPACE_DIR" && terraform output -raw ingestion_workflow_service_account_email 2>/dev/null || true) + + if [[ -n "$CURRENT_USER" && -n "$WORKFLOW_SA" ]]; then + echo " Authenticated user: ${CURRENT_USER}" + echo " Workflow Service Account: ${WORKFLOW_SA}" + + # Check if user already has TokenCreator role + HAS_ROLE=$(gcloud iam service-accounts get-iam-policy "$WORKFLOW_SA" --project="$PROJECT" --format="json" 2>/dev/null | grep -i "${CURRENT_USER}" || true) + + if [[ -z "$HAS_ROLE" ]]; then + echo " Granting 'roles/iam.serviceAccountTokenCreator' to user:${CURRENT_USER} on ${WORKFLOW_SA}..." + if gcloud iam service-accounts add-iam-policy-binding "$WORKFLOW_SA" \ + --member="user:${CURRENT_USER}" \ + --role="roles/iam.serviceAccountTokenCreator" \ + --project="$PROJECT" --quiet &>/dev/null; then + echo " ✔ Successfully configured Service Account impersonation." + else + echo " Notice: Could not automatically grant TokenCreator permission (insufficient IAM admin rights)." + echo " If you plan to run ingestion CLI commands, ask a project admin to run:" + echo " gcloud iam service-accounts add-iam-policy-binding \"${WORKFLOW_SA}\" --member=\"user:${CURRENT_USER}\" --role=\"roles/iam.serviceAccountTokenCreator\" --project=\"${PROJECT}\"" + fi + else + echo " ✔ Service Account impersonation already configured for ${CURRENT_USER}." + fi + else + echo " Skipped SA impersonation check (instance might not be fully applied yet)." + fi + + echo "" + echo "================================================================================" + echo " SUCCESS: Connected to '${INSTANCE}'" + echo " Workspace directory: ${WORKSPACE_DIR}" + echo "" + echo " Ready to deploy:" + echo " 1. Edit terraform.tfvars (uncomment custom images or version overrides)" + echo " 2. Run 'terraform apply' to deploy" + echo " 3. Run '$0 push-config --instance ${INSTANCE}' when done" + echo "================================================================================" + echo "" + + # Automatically navigate into the workspace directory + if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then + # Sourced mode (source ./connect.sh): changes directory in parent shell + cd "$WORKSPACE_DIR" + elif [[ -t 0 ]]; then + # Interactive execution (./connect.sh): launches shell inside workspace + echo "==> Entered workspace: ${WORKSPACE_DIR}" + echo "" + cd "$WORKSPACE_DIR" + exec "${SHELL:-bash}" + else + cd "$WORKSPACE_DIR" + fi + +# ============================================================================== +# ACTION: PUSH-CONFIG +# ============================================================================== +elif [[ "$ACTION" == "push-config" ]]; then + TFVARS_FILE="$WORKSPACE_DIR/terraform.tfvars" + if [[ ! -f "$TFVARS_FILE" ]]; then + echo "Error: Local configuration '$TFVARS_FILE' not found." + echo "Have you run '$0 connect --instance $INSTANCE' first?" + exit 1 + fi + + echo "==> Pushing local terraform.tfvars to Secret Manager ($SECRET_NAME)..." + if gcloud secrets describe "$SECRET_NAME" --project="$PROJECT" &>/dev/null; then + gcloud secrets versions add "$SECRET_NAME" \ + --data-file="$TFVARS_FILE" \ + --project="$PROJECT" + else + gcloud secrets create "$SECRET_NAME" \ + --data-file="$TFVARS_FILE" \ + --project="$PROJECT" \ + --replication-policy="automatic" + fi + echo "==> Secret successfully updated in GCP Secret Manager!" + +else + echo "Error: Unknown command '$ACTION'" + echo "" + print_usage +fi From d81e8dfecca892e8eac421e589aa518f05dd899a Mon Sep 17 00:00:00 2001 From: Gabriel Mechali Date: Tue, 18 Aug 2026 18:58:13 -0400 Subject: [PATCH 2/6] Gemini comments --- tests/testbed/connect.sh | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/tests/testbed/connect.sh b/tests/testbed/connect.sh index e08c913d..3a1b806d 100755 --- a/tests/testbed/connect.sh +++ b/tests/testbed/connect.sh @@ -246,6 +246,10 @@ if [[ "$ACTION" == "connect" ]]; then mkdir -p "$WORKSPACE_DIR" echo "==> [2/5] Pulling configuration from Secret Manager ($SECRET_NAME)..." + if [[ -f "$WORKSPACE_DIR/terraform.tfvars" ]]; then + cp "$WORKSPACE_DIR/terraform.tfvars" "$WORKSPACE_DIR/terraform.tfvars.bak" + fi + if gcloud secrets describe "$SECRET_NAME" --project="$PROJECT" &>/dev/null; then gcloud secrets versions access latest \ --secret="$SECRET_NAME" \ @@ -263,7 +267,12 @@ TFVARS fi fi - echo "==> [3/5] Setting up remote GCS backend state..." + # Copy root terraform definition files into workspace and symlink modules + echo "==> [3/5] Syncing Terraform scaffolding..." + cp "${INFRA_DCP_DIR}"/*.tf "$WORKSPACE_DIR/" + ln -sfn "${INFRA_DCP_DIR}/modules" "$WORKSPACE_DIR/modules" + + echo "==> [4/5] Setting up remote GCS backend state..." cat < "$WORKSPACE_DIR/backend.tf" terraform { backend "gcs" { @@ -273,13 +282,6 @@ terraform { } BACKEND - # Copy root terraform definition files into workspace and symlink modules - echo "==> [4/5] Syncing Terraform scaffolding..." - cp "${INFRA_DCP_DIR}/main.tf" "$WORKSPACE_DIR/main.tf" - cp "${INFRA_DCP_DIR}/variables.tf" "$WORKSPACE_DIR/variables.tf" - cp "${INFRA_DCP_DIR}/outputs.tf" "$WORKSPACE_DIR/outputs.tf" - ln -sfn "${INFRA_DCP_DIR}/modules" "$WORKSPACE_DIR/modules" - ( cd "$WORKSPACE_DIR" echo " Running terraform init..." @@ -355,16 +357,15 @@ elif [[ "$ACTION" == "push-config" ]]; then fi echo "==> Pushing local terraform.tfvars to Secret Manager ($SECRET_NAME)..." - if gcloud secrets describe "$SECRET_NAME" --project="$PROJECT" &>/dev/null; then - gcloud secrets versions add "$SECRET_NAME" \ - --data-file="$TFVARS_FILE" \ - --project="$PROJECT" - else - gcloud secrets create "$SECRET_NAME" \ - --data-file="$TFVARS_FILE" \ - --project="$PROJECT" \ - --replication-policy="automatic" + if ! gcloud secrets describe "$SECRET_NAME" --project="$PROJECT" &>/dev/null; then + echo "Error: Secret '$SECRET_NAME' does not exist in project '$PROJECT'." + echo "Please ensure the testbed secret has been initialized by an administrator." + exit 1 fi + + gcloud secrets versions add "$SECRET_NAME" \ + --data-file="$TFVARS_FILE" \ + --project="$PROJECT" echo "==> Secret successfully updated in GCP Secret Manager!" else From 499f82f74cc5d759bbb43264da534a1cd40d9c10 Mon Sep 17 00:00:00 2001 From: Gabriel Mechali Date: Tue, 18 Aug 2026 18:59:40 -0400 Subject: [PATCH 3/6] copyright --- tests/testbed/connect.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/testbed/connect.sh b/tests/testbed/connect.sh index 3a1b806d..fc7299ed 100755 --- a/tests/testbed/connect.sh +++ b/tests/testbed/connect.sh @@ -1,3 +1,18 @@ +# Copyright 2026 Google LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + #!/usr/bin/env bash # ============================================================================== # Data Commons Platform (DCP) Developer Testbed CLI From a37dc79921ef3578bc64cfd843114f1fb3e9c893 Mon Sep 17 00:00:00 2001 From: Gabriel Mechali Date: Wed, 19 Aug 2026 08:24:37 -0400 Subject: [PATCH 4/6] Gemini comments round 2 --- tests/testbed/connect.sh | 456 +++++++++++++++++++++------------------ 1 file changed, 244 insertions(+), 212 deletions(-) diff --git a/tests/testbed/connect.sh b/tests/testbed/connect.sh index fc7299ed..a4953c3d 100755 --- a/tests/testbed/connect.sh +++ b/tests/testbed/connect.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash + # Copyright 2026 Google LLC. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,8 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. - -#!/usr/bin/env bash # ============================================================================== # Data Commons Platform (DCP) Developer Testbed CLI # ============================================================================== @@ -62,7 +62,6 @@ Developer Workflow: 4. List all active testbeds: $0 list HELP - exit 1 } # Ensure dependencies exist @@ -82,7 +81,7 @@ check_dependencies() { fi if [[ $missing -eq 1 ]]; then - exit 1 + return 1 fi # Check for datacommons CLI (warning if not in PATH or uv) @@ -92,112 +91,19 @@ check_dependencies() { echo " (or execute via: uv run datacommons )" echo "" fi + return 0 } -ACTION="$1" -if [[ "$ACTION" == "--help" || "$ACTION" == "-h" ]]; then - print_usage -elif [[ "$ACTION" == "connect" || "$ACTION" == "push-config" || "$ACTION" == "list" ]]; then - shift -elif [[ "$ACTION" == --* || -z "$ACTION" ]]; then - ACTION="connect" -else - echo "Error: Unknown command '$ACTION'" - print_usage -fi - -INSTANCE="" -PROJECT="$DEFAULT_PROJECT" - -while [[ $# -gt 0 ]]; do - case "$1" in - --instance) - INSTANCE="$2" - shift 2 - ;; - --project) - PROJECT="$2" - shift 2 - ;; - --help|-h) - print_usage - ;; - *) - echo "Error: Unknown option: $1" - print_usage - ;; - esac -done - -# Auto-infer instance name if running inside a workspace folder (e.g. tests/testbed/workspaces/testbed-1) -if [[ -z "$INSTANCE" ]]; then - CURRENT_DIR="$(pwd)" - if [[ "$CURRENT_DIR" == *"/workspaces/"* ]]; then - INSTANCE="$(basename "$CURRENT_DIR")" - echo "==> Auto-detected instance '$INSTANCE' from current directory." - fi -fi - -check_dependencies - -# ============================================================================== -# ACTION: LIST -# ============================================================================== -if [[ "$ACTION" == "list" ]]; then - echo "================================================================================" - echo "DCP TESTBEDS in project: ${PROJECT}" - echo "================================================================================" - - echo "Fetching registered testbed secrets from Secret Manager..." - SECRETS=$(gcloud secrets list --project="${PROJECT}" --format="value(name)" 2>/dev/null || true) - - found=0 - options=() - for s in $SECRETS; do - secret_id=$(basename "$s") - if [[ "$secret_id" =~ ^dcp-(.+)-tfvars$ ]]; then - if [[ $found -eq 0 ]]; then - printf "%-5s %-25s %-35s\n" "#" "INSTANCE NAME" "SECRET NAME" - printf "%-5s %-25s %-35s\n" "--" "-------------" "-----------" - fi - inst_name="${BASH_REMATCH[1]}" - options+=("$inst_name") - found=$((found + 1)) - printf "%-5s %-25s %-35s\n" "$found" "$inst_name" "$secret_id" - fi - done - - if [[ $found -eq 0 ]]; then - echo "No 'dcp-*-tfvars' secrets found in project '${PROJECT}'." - exit 0 - fi - - # If running interactively, prompt to connect directly - if [[ -t 0 ]]; then - echo "" - read -p "Select a testbed to connect to [1-$found, or press Enter to exit]: " choice - if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= found )); then - INSTANCE="${options[$((choice - 1))]}" - ACTION="connect" - echo "" - else - exit 0 - fi - else - exit 0 - fi -fi - # Interactive prompt to select or enter an instance if --instance was omitted prompt_instance_if_missing() { if [[ -n "$INSTANCE" ]]; then - return + return 0 fi # If not running interactively (e.g. CI/CD), error out if [[ ! -t 0 ]]; then echo "Error: --instance is required in non-interactive mode." - exit 1 + return 1 fi echo "==> No --instance provided. Querying available testbeds in '${PROJECT}'..." @@ -240,55 +146,162 @@ prompt_instance_if_missing() { if [[ -z "$INSTANCE" ]]; then echo "Error: Instance name cannot be empty." - exit 1 + return 1 fi echo "==> Selected instance: '$INSTANCE'" echo "" + return 0 } -prompt_instance_if_missing +main() { + local ACTION="$1" + if [[ "$ACTION" == "--help" || "$ACTION" == "-h" ]]; then + print_usage + return 0 + elif [[ "$ACTION" == "connect" || "$ACTION" == "push-config" || "$ACTION" == "list" ]]; then + shift + elif [[ "$ACTION" == --* || -z "$ACTION" ]]; then + ACTION="connect" + else + echo "Error: Unknown command '$ACTION'" + print_usage + return 1 + fi -SECRET_NAME="dcp-${INSTANCE}-tfvars" -WORKSPACE_DIR="${WORKSPACES_ROOT}/${INSTANCE}" -STATE_BUCKET="tf-state-${INSTANCE}-${PROJECT}" + INSTANCE="" + PROJECT="$DEFAULT_PROJECT" + + while [[ $# -gt 0 ]]; do + case "$1" in + --instance) + INSTANCE="$2" + shift 2 + ;; + --project) + PROJECT="$2" + shift 2 + ;; + --help|-h) + print_usage + return 0 + ;; + *) + echo "Error: Unknown option: $1" + print_usage + return 1 + ;; + esac + done -# ============================================================================== -# ACTION: CONNECT -# ============================================================================== -if [[ "$ACTION" == "connect" ]]; then - echo "==> [1/5] Connecting to testbed '${INSTANCE}' in project '${PROJECT}'..." - mkdir -p "$WORKSPACE_DIR" + # Auto-infer instance name if running inside a workspace folder (e.g. tests/testbed/workspaces/testbed-1) + if [[ -z "$INSTANCE" ]]; then + local CURRENT_DIR + CURRENT_DIR="$(pwd)" + if [[ "$CURRENT_DIR" == *"/workspaces/"* ]]; then + INSTANCE="$(basename "$CURRENT_DIR")" + echo "==> Auto-detected instance '$INSTANCE' from current directory." + fi + fi - echo "==> [2/5] Pulling configuration from Secret Manager ($SECRET_NAME)..." - if [[ -f "$WORKSPACE_DIR/terraform.tfvars" ]]; then - cp "$WORKSPACE_DIR/terraform.tfvars" "$WORKSPACE_DIR/terraform.tfvars.bak" + if ! check_dependencies; then + return 1 fi - if gcloud secrets describe "$SECRET_NAME" --project="$PROJECT" &>/dev/null; then - gcloud secrets versions access latest \ - --secret="$SECRET_NAME" \ - --project="$PROJECT" > "$WORKSPACE_DIR/terraform.tfvars" - echo " Successfully fetched terraform.tfvars from Secret Manager." - else - echo " Warning: Secret '$SECRET_NAME' does not exist in Secret Manager." - if [[ ! -f "$WORKSPACE_DIR/terraform.tfvars" ]]; then - echo " Creating new boilerplate terraform.tfvars for '${INSTANCE}'..." - cat < "$WORKSPACE_DIR/terraform.tfvars" + # ============================================================================== + # ACTION: LIST + # ============================================================================== + if [[ "$ACTION" == "list" ]]; then + echo "================================================================================" + echo "DCP TESTBEDS in project: ${PROJECT}" + echo "================================================================================" + + echo "Fetching registered testbed secrets from Secret Manager..." + local SECRETS + SECRETS=$(gcloud secrets list --project="${PROJECT}" --format="value(name)" 2>/dev/null || true) + + local found=0 + local options=() + for s in $SECRETS; do + local secret_id + secret_id=$(basename "$s") + if [[ "$secret_id" =~ ^dcp-(.+)-tfvars$ ]]; then + if [[ $found -eq 0 ]]; then + printf "%-5s %-25s %-35s\n" "#" "INSTANCE NAME" "SECRET NAME" + printf "%-5s %-25s %-35s\n" "--" "-------------" "-----------" + fi + local inst_name="${BASH_REMATCH[1]}" + options+=("$inst_name") + found=$((found + 1)) + printf "%-5s %-25s %-35s\n" "$found" "$inst_name" "$secret_id" + fi + done + + if [[ $found -eq 0 ]]; then + echo "No 'dcp-*-tfvars' secrets found in project '${PROJECT}'." + return 0 + fi + + # If running interactively, prompt to connect directly + if [[ -t 0 ]]; then + echo "" + read -p "Select a testbed to connect to [1-$found, or press Enter to exit]: " choice + if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= found )); then + INSTANCE="${options[$((choice - 1))]}" + ACTION="connect" + echo "" + else + return 0 + fi + else + return 0 + fi + fi + + if ! prompt_instance_if_missing; then + return 1 + fi + + local SECRET_NAME="dcp-${INSTANCE}-tfvars" + local WORKSPACE_DIR="${WORKSPACES_ROOT}/${INSTANCE}" + local STATE_BUCKET="tf-state-${INSTANCE}-${PROJECT}" + + # ============================================================================== + # ACTION: CONNECT + # ============================================================================== + if [[ "$ACTION" == "connect" ]]; then + echo "==> [1/5] Connecting to testbed '${INSTANCE}' in project '${PROJECT}'..." + mkdir -p "$WORKSPACE_DIR" + + echo "==> [2/5] Pulling configuration from Secret Manager ($SECRET_NAME)..." + if [[ -f "$WORKSPACE_DIR/terraform.tfvars" ]]; then + cp "$WORKSPACE_DIR/terraform.tfvars" "$WORKSPACE_DIR/terraform.tfvars.bak" + fi + + if gcloud secrets describe "$SECRET_NAME" --project="$PROJECT" &>/dev/null; then + gcloud secrets versions access latest \ + --secret="$SECRET_NAME" \ + --project="$PROJECT" > "$WORKSPACE_DIR/terraform.tfvars" + echo " Successfully fetched terraform.tfvars from Secret Manager." + else + echo " Warning: Secret '$SECRET_NAME' does not exist in Secret Manager." + if [[ ! -f "$WORKSPACE_DIR/terraform.tfvars" ]]; then + echo " Creating new boilerplate terraform.tfvars for '${INSTANCE}'..." + cat < "$WORKSPACE_DIR/terraform.tfvars" project_id = "${PROJECT}" instance_name = "${INSTANCE}" region = "us-central1" TFVARS + fi fi - fi - # Copy root terraform definition files into workspace and symlink modules - echo "==> [3/5] Syncing Terraform scaffolding..." - cp "${INFRA_DCP_DIR}"/*.tf "$WORKSPACE_DIR/" - ln -sfn "${INFRA_DCP_DIR}/modules" "$WORKSPACE_DIR/modules" + # Copy root terraform definition files into workspace and symlink modules + echo "==> [3/5] Syncing Terraform scaffolding..." + cp "${INFRA_DCP_DIR}"/*.tf "$WORKSPACE_DIR/" + ln -sfn "${INFRA_DCP_DIR}/modules" "$WORKSPACE_DIR/modules" - echo "==> [4/5] Setting up remote GCS backend state..." - cat < "$WORKSPACE_DIR/backend.tf" + echo "==> [4/5] Setting up remote GCS backend state..." + cat < "$WORKSPACE_DIR/backend.tf" terraform { backend "gcs" { bucket = "${STATE_BUCKET}" @@ -297,94 +310,113 @@ terraform { } BACKEND - ( - cd "$WORKSPACE_DIR" - echo " Running terraform init..." - terraform init - ) - - # Check & configure service account impersonation for CLI commands - echo "==> [5/5] Checking Service Account impersonation permissions..." - CURRENT_USER=$(gcloud config get-value account 2>/dev/null || true) - WORKFLOW_SA=$(cd "$WORKSPACE_DIR" && terraform output -raw ingestion_workflow_service_account_email 2>/dev/null || true) - - if [[ -n "$CURRENT_USER" && -n "$WORKFLOW_SA" ]]; then - echo " Authenticated user: ${CURRENT_USER}" - echo " Workflow Service Account: ${WORKFLOW_SA}" - - # Check if user already has TokenCreator role - HAS_ROLE=$(gcloud iam service-accounts get-iam-policy "$WORKFLOW_SA" --project="$PROJECT" --format="json" 2>/dev/null | grep -i "${CURRENT_USER}" || true) - - if [[ -z "$HAS_ROLE" ]]; then - echo " Granting 'roles/iam.serviceAccountTokenCreator' to user:${CURRENT_USER} on ${WORKFLOW_SA}..." - if gcloud iam service-accounts add-iam-policy-binding "$WORKFLOW_SA" \ - --member="user:${CURRENT_USER}" \ - --role="roles/iam.serviceAccountTokenCreator" \ - --project="$PROJECT" --quiet &>/dev/null; then - echo " ✔ Successfully configured Service Account impersonation." + ( + cd "$WORKSPACE_DIR" + echo " Running terraform init..." + terraform init + ) + + # Check & configure service account impersonation for CLI commands + echo "==> [5/5] Checking Service Account impersonation permissions..." + local CURRENT_USER + CURRENT_USER=$(gcloud config get-value account 2>/dev/null || true) + local WORKFLOW_SA + WORKFLOW_SA=$(cd "$WORKSPACE_DIR" && terraform output -raw ingestion_workflow_service_account_email 2>/dev/null || true) + + if [[ -n "$CURRENT_USER" && -n "$WORKFLOW_SA" ]]; then + echo " Authenticated user: ${CURRENT_USER}" + echo " Workflow Service Account: ${WORKFLOW_SA}" + + # Check if user already has TokenCreator role using precise gcloud filter + local HAS_ROLE + HAS_ROLE=$(gcloud iam service-accounts get-iam-policy "$WORKFLOW_SA" \ + --project="$PROJECT" \ + --filter="bindings.role=roles/iam.serviceAccountTokenCreator AND bindings.members=user:${CURRENT_USER}" \ + --format="value(bindings.role)" 2>/dev/null || true) + + if [[ -z "$HAS_ROLE" ]]; then + echo " Granting 'roles/iam.serviceAccountTokenCreator' to user:${CURRENT_USER} on ${WORKFLOW_SA}..." + if gcloud iam service-accounts add-iam-policy-binding "$WORKFLOW_SA" \ + --member="user:${CURRENT_USER}" \ + --role="roles/iam.serviceAccountTokenCreator" \ + --project="$PROJECT" --quiet &>/dev/null; then + echo " ✔ Successfully configured Service Account impersonation." + else + echo " Notice: Could not automatically grant TokenCreator permission (insufficient IAM admin rights)." + echo " If you plan to run ingestion CLI commands, ask a project admin to run:" + echo " gcloud iam service-accounts add-iam-policy-binding \"${WORKFLOW_SA}\" --member=\"user:${CURRENT_USER}\" --role=\"roles/iam.serviceAccountTokenCreator\" --project=\"${PROJECT}\"" + fi else - echo " Notice: Could not automatically grant TokenCreator permission (insufficient IAM admin rights)." - echo " If you plan to run ingestion CLI commands, ask a project admin to run:" - echo " gcloud iam service-accounts add-iam-policy-binding \"${WORKFLOW_SA}\" --member=\"user:${CURRENT_USER}\" --role=\"roles/iam.serviceAccountTokenCreator\" --project=\"${PROJECT}\"" + echo " ✔ Service Account impersonation already configured for ${CURRENT_USER}." fi else - echo " ✔ Service Account impersonation already configured for ${CURRENT_USER}." + echo " Skipped SA impersonation check (instance might not be fully applied yet)." fi - else - echo " Skipped SA impersonation check (instance might not be fully applied yet)." - fi - echo "" - echo "================================================================================" - echo " SUCCESS: Connected to '${INSTANCE}'" - echo " Workspace directory: ${WORKSPACE_DIR}" - echo "" - echo " Ready to deploy:" - echo " 1. Edit terraform.tfvars (uncomment custom images or version overrides)" - echo " 2. Run 'terraform apply' to deploy" - echo " 3. Run '$0 push-config --instance ${INSTANCE}' when done" - echo "================================================================================" - echo "" - - # Automatically navigate into the workspace directory - if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then - # Sourced mode (source ./connect.sh): changes directory in parent shell - cd "$WORKSPACE_DIR" - elif [[ -t 0 ]]; then - # Interactive execution (./connect.sh): launches shell inside workspace - echo "==> Entered workspace: ${WORKSPACE_DIR}" echo "" - cd "$WORKSPACE_DIR" - exec "${SHELL:-bash}" - else - cd "$WORKSPACE_DIR" - fi + echo "================================================================================" + echo " SUCCESS: Connected to '${INSTANCE}'" + echo " Workspace directory: ${WORKSPACE_DIR}" + echo "" + echo " Ready to deploy:" + echo " 1. Edit terraform.tfvars (uncomment custom images or version overrides)" + echo " 2. Run 'terraform apply' to deploy" + echo " 3. Run '$0 push-config --instance ${INSTANCE}' when done" + echo "================================================================================" + echo "" -# ============================================================================== -# ACTION: PUSH-CONFIG -# ============================================================================== -elif [[ "$ACTION" == "push-config" ]]; then - TFVARS_FILE="$WORKSPACE_DIR/terraform.tfvars" - if [[ ! -f "$TFVARS_FILE" ]]; then - echo "Error: Local configuration '$TFVARS_FILE' not found." - echo "Have you run '$0 connect --instance $INSTANCE' first?" - exit 1 - fi + # Automatically navigate into the workspace directory + if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then + # Sourced mode (source ./connect.sh): changes directory in parent shell + cd "$WORKSPACE_DIR" + elif [[ -t 0 ]]; then + # Interactive execution (./connect.sh): launches shell inside workspace + echo "==> Entered workspace: ${WORKSPACE_DIR}" + echo "" + cd "$WORKSPACE_DIR" + exec "${SHELL:-bash}" + else + cd "$WORKSPACE_DIR" + fi - echo "==> Pushing local terraform.tfvars to Secret Manager ($SECRET_NAME)..." - if ! gcloud secrets describe "$SECRET_NAME" --project="$PROJECT" &>/dev/null; then - echo "Error: Secret '$SECRET_NAME' does not exist in project '$PROJECT'." - echo "Please ensure the testbed secret has been initialized by an administrator." - exit 1 - fi + # ============================================================================== + # ACTION: PUSH-CONFIG + # ============================================================================== + elif [[ "$ACTION" == "push-config" ]]; then + local TFVARS_FILE="$WORKSPACE_DIR/terraform.tfvars" + if [[ ! -f "$TFVARS_FILE" ]]; then + echo "Error: Local configuration '$TFVARS_FILE' not found." + echo "Have you run '$0 connect --instance $INSTANCE' first?" + return 1 + fi - gcloud secrets versions add "$SECRET_NAME" \ - --data-file="$TFVARS_FILE" \ - --project="$PROJECT" - echo "==> Secret successfully updated in GCP Secret Manager!" + echo "==> Pushing local terraform.tfvars to Secret Manager ($SECRET_NAME)..." + if ! gcloud secrets describe "$SECRET_NAME" --project="$PROJECT" &>/dev/null; then + echo "Error: Secret '$SECRET_NAME' does not exist in project '$PROJECT'." + echo "Please ensure the testbed secret has been initialized by an administrator." + return 1 + fi -else - echo "Error: Unknown command '$ACTION'" - echo "" - print_usage -fi + if [[ -t 0 ]]; then + local confirm + read -p "Are you sure you want to push your local terraform.tfvars to the shared secret '$SECRET_NAME'? [y/N]: " confirm + if [[ ! "$confirm" =~ ^[yY](es)?$ ]]; then + echo "Push cancelled." + return 0 + fi + fi + + gcloud secrets versions add "$SECRET_NAME" \ + --data-file="$TFVARS_FILE" \ + --project="$PROJECT" + echo "==> Secret successfully updated in GCP Secret Manager!" + + else + echo "Error: Unknown command '$ACTION'" + echo "" + print_usage + return 1 + fi +} + +main "$@" From 70c45dd689735d3a5fabe0773de943a065c9f87f Mon Sep 17 00:00:00 2001 From: Gabriel Mechali Date: Wed, 19 Aug 2026 16:13:41 -0400 Subject: [PATCH 5/6] Rename to fetch tf state --- tests/testbed/README.md | 12 ++++++------ .../testbed/{connect.sh => fetch_terraform_state.sh} | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) rename tests/testbed/{connect.sh => fetch_terraform_state.sh} (98%) diff --git a/tests/testbed/README.md b/tests/testbed/README.md index f27834b4..1f9e0ef5 100644 --- a/tests/testbed/README.md +++ b/tests/testbed/README.md @@ -22,7 +22,7 @@ They allow any engineer on the team to **deploy and test custom container builds ┌────────────────────────────┴────────────────────────────┐ │ │ 1. Connect & Sync 3. Push & Persist - `./tests/testbed/connect.sh connect` `./tests/testbed/connect.sh push-config` + `./tests/testbed/fetch_terraform_state.sh connect` `./tests/testbed/fetch_terraform_state.sh push-config` - Pulls tfvars from Secret Manager - Saves updated tfvars back to - Configures remote backend state Secret Manager so the whole team - Configures SA Impersonation for CLI stays in sync. @@ -54,10 +54,10 @@ Run the connect script from the repository root: ```bash # Connect directly to testbed-1: -./tests/testbed/connect.sh connect --instance testbed-1 +./tests/testbed/fetch_terraform_state.sh connect --instance testbed-1 # OR run interactively to choose from available testbeds: -./tests/testbed/connect.sh connect +./tests/testbed/fetch_terraform_state.sh connect ``` **What the script does automatically:** @@ -120,7 +120,7 @@ uv run datacommons ... datacommons ... ``` -The CLI automatically impersonates the testbed's ingestion workflow service account using the TokenCreator IAM role that `connect.sh` configured in Step 1. +The CLI automatically impersonates the testbed's ingestion workflow service account using the TokenCreator IAM role that `fetch_terraform_state.sh` configured in Step 1. If you ever need to manually bind the impersonation permission for a teammate: ```bash @@ -141,7 +141,7 @@ gcloud iam service-accounts add-iam-policy-binding "SERVICE_ACCOUNT_EMAIL" \ If you want your updated configuration or image to remain the **shared baseline** for the testbed: ```bash -../../connect.sh push-config --instance testbed-1 +../../fetch_terraform_state.sh push-config --instance testbed-1 ``` **When to push:** @@ -157,6 +157,6 @@ If you want your updated configuration or image to remain the **shared baseline* ### List All Registered Testbeds ```bash -./tests/testbed/connect.sh list +./tests/testbed/fetch_terraform_state.sh list ``` Displays all registered testbed secrets in `datcom-dcp` and allows interactive selection to connect immediately. diff --git a/tests/testbed/connect.sh b/tests/testbed/fetch_terraform_state.sh similarity index 98% rename from tests/testbed/connect.sh rename to tests/testbed/fetch_terraform_state.sh index a4953c3d..94d6701b 100755 --- a/tests/testbed/connect.sh +++ b/tests/testbed/fetch_terraform_state.sh @@ -367,10 +367,10 @@ BACKEND # Automatically navigate into the workspace directory if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then - # Sourced mode (source ./connect.sh): changes directory in parent shell + # Sourced mode (source ./fetch_terraform_state.sh): changes directory in parent shell cd "$WORKSPACE_DIR" elif [[ -t 0 ]]; then - # Interactive execution (./connect.sh): launches shell inside workspace + # Interactive execution (./fetch_terraform_state.sh): launches shell inside workspace echo "==> Entered workspace: ${WORKSPACE_DIR}" echo "" cd "$WORKSPACE_DIR" From 7af706feee32a15629b2061f6bbf1c1a885488ad Mon Sep 17 00:00:00 2001 From: Gabriel Mechali Date: Thu, 20 Aug 2026 16:36:44 -0400 Subject: [PATCH 6/6] Stop the exec in the subshell --- tests/testbed/fetch_terraform_state.sh | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/tests/testbed/fetch_terraform_state.sh b/tests/testbed/fetch_terraform_state.sh index 94d6701b..c5dcca03 100755 --- a/tests/testbed/fetch_terraform_state.sh +++ b/tests/testbed/fetch_terraform_state.sh @@ -358,27 +358,13 @@ BACKEND echo " SUCCESS: Connected to '${INSTANCE}'" echo " Workspace directory: ${WORKSPACE_DIR}" echo "" - echo " Ready to deploy:" - echo " 1. Edit terraform.tfvars (uncomment custom images or version overrides)" - echo " 2. Run 'terraform apply' to deploy" - echo " 3. Run '$0 push-config --instance ${INSTANCE}' when done" + echo " Next Steps:" + echo " 1. cd ${WORKSPACE_DIR}" + echo " 2. Edit terraform.tfvars (if needed)" + echo " 3. terraform apply" echo "================================================================================" echo "" - # Automatically navigate into the workspace directory - if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then - # Sourced mode (source ./fetch_terraform_state.sh): changes directory in parent shell - cd "$WORKSPACE_DIR" - elif [[ -t 0 ]]; then - # Interactive execution (./fetch_terraform_state.sh): launches shell inside workspace - echo "==> Entered workspace: ${WORKSPACE_DIR}" - echo "" - cd "$WORKSPACE_DIR" - exec "${SHELL:-bash}" - else - cd "$WORKSPACE_DIR" - fi - # ============================================================================== # ACTION: PUSH-CONFIG # ==============================================================================