diff --git a/deployment/collect-data.sh b/deployment/collect-data.sh index 3cf46c8b..35e482f7 100755 --- a/deployment/collect-data.sh +++ b/deployment/collect-data.sh @@ -4,7 +4,7 @@ TIMESTAMP=$(date +%Y%m%d_%H%M%S) RUN_DIR="run_data_${TIMESTAMP}" mkdir -p "${RUN_DIR}" -cd "${RUN_DIR}" +cd "${RUN_DIR}" || exit 1 echo "Collecting all data post-run" # Get cluster info @@ -21,7 +21,7 @@ echo -e "\nPods in crs namespace:" kubectl get pods -n crs # Ask for confirmation -read -p "Is this the correct cluster to collect run data from? (y/N) " confirm +read -r -p "Is this the correct cluster to collect run data from? (y/N) " confirm if [[ $confirm != [yY] ]]; then echo "Aborting data collection" exit 1 diff --git a/deployment/collect-logs.sh b/deployment/collect-logs.sh index b9b2602c..9961f688 100755 --- a/deployment/collect-logs.sh +++ b/deployment/collect-logs.sh @@ -112,7 +112,7 @@ export -f process_pod running_jobs=0 for pod in $pods; do # Wait if we've reached the maximum number of parallel jobs - while [ $running_jobs -ge $MAX_PARALLEL ]; do + while [ "$running_jobs" -ge "$MAX_PARALLEL" ]; do # Wait for any background job to complete wait -n running_jobs=$((running_jobs - 1)) diff --git a/deployment/crs-architecture.sh b/deployment/crs-architecture.sh index 6f7a003e..d4d3be39 100755 --- a/deployment/crs-architecture.sh +++ b/deployment/crs-architecture.sh @@ -139,10 +139,15 @@ up() { FUZZER_BUILD_ARGS="" fi + # shellcheck disable=SC2086 # Intentionally unquoted for word splitting docker build $ORCHESTRATOR_BUILD_ARGS -f "$SCRIPT_DIR"/../orchestrator/Dockerfile -t localhost/orchestrator:latest "$SCRIPT_DIR"/.. + # shellcheck disable=SC2086 # Intentionally unquoted for word splitting docker build $FUZZER_BUILD_ARGS -f "$SCRIPT_DIR"/../fuzzer/Dockerfile -t localhost/fuzzer:latest "$SCRIPT_DIR"/.. + # shellcheck disable=SC2086 # Intentionally unquoted for word splitting docker build $SEED_GEN_BUILD_ARGS -f "$SCRIPT_DIR"/../seed-gen/Dockerfile -t localhost/seed-gen:latest "$SCRIPT_DIR"/.. + # shellcheck disable=SC2086 # Intentionally unquoted for word splitting docker build $PATCHER_BUILD_ARGS -f "$SCRIPT_DIR"/../patcher/Dockerfile -t localhost/patcher:latest "$SCRIPT_DIR"/.. + # shellcheck disable=SC2086 # Intentionally unquoted for word splitting docker build $PROGRAM_MODEL_BUILD_ARGS -f "$SCRIPT_DIR"/../program-model/Dockerfile -t localhost/program-model:latest "$SCRIPT_DIR"/.. ;; esac @@ -161,7 +166,7 @@ up() { --from-literal=scantron_github_pat="$SCANTRON_GITHUB_PAT" || echo -e "${GRN}ghcr secret already exists${NC}" echo -e "${BLU}Creating CRS_INSTANCE_ID${NC}" - CRS_INSTANCE_ID=$(echo $RANDOM | md5sum | head -c 20) + CRS_INSTANCE_ID=$(echo "$RANDOM" | md5sum | head -c 20) kubectl create configmap crs-instance-id \ --namespace "$BUTTERCUP_NAMESPACE" \ --from-literal=crs-instance-id="$CRS_INSTANCE_ID" || echo -e "${GRN}crs-instance-id configmap already exists${NC}" @@ -222,7 +227,7 @@ fi if [ "$TAILSCALE_ENABLED" = "true" ]; then kubectl apply -k k8s/base/tailscale-connections/ echo -e "${BLU}Waiting for ingress hostname DNS registration${NC}" - timeout 5m bash -c "until kubectl get ingress -n "$BUTTERCUP_NAMESPACE" buttercup-task-server -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' | grep -q '.'; do sleep 1; done" || echo -e "${BLU}Error: Ingress hostname failed to be to set within 5 minutes${NC}" + timeout 5m bash -c 'until kubectl get ingress -n '"$BUTTERCUP_NAMESPACE"' buttercup-task-server -o jsonpath='"'"'{.status.loadBalancer.ingress[0].hostname}'"'"' | grep -q "."; do sleep 1; done' || echo -e "${BLU}Error: Ingress hostname failed to be to set within 5 minutes${NC}" INGRESS_HOSTNAME=$(kubectl get ingress -n "$BUTTERCUP_NAMESPACE" buttercup-task-server -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') echo -e "${GRN}Your ingress DNS hostname is $INGRESS_HOSTNAME${NC}" fi diff --git a/fuzzer_runner/runner.sh b/fuzzer_runner/runner.sh index 16a7d143..813fb6c0 100755 --- a/fuzzer_runner/runner.sh +++ b/fuzzer_runner/runner.sh @@ -9,4 +9,4 @@ else exit 1 fi -buttercup-fuzzer-runner $@ # we use $@ to pass all the arguments to the python script +buttercup-fuzzer-runner "$@" # we use "$@" to pass all the arguments to the python script diff --git a/orchestrator/scripts/download_all.sh b/orchestrator/scripts/download_all.sh index 323add64..d6320113 100755 --- a/orchestrator/scripts/download_all.sh +++ b/orchestrator/scripts/download_all.sh @@ -41,7 +41,7 @@ find "$input_dir" -type d -name "*-delta-1" | while read -r dir; do # Look at the first dir inside the src_tar to guess the focus focus=$(tar -tf "$src_tar" | head -n 1) # remove any trailing slashes - focus=$(echo "$focus" | sed 's:/*$::') + focus="${focus%/}" if [ -z "$src_tar" ] || [ ! -f "$fuzz_tar" ] || [ -z "$diff_tar" ]; then echo "Error: Missing required files in $dir_name" @@ -61,5 +61,5 @@ find "$input_dir" -type d -name "*-delta-1" | while read -r dir; do --diff_url "$diff_url" \ --project_name "$project_name" \ --focus "$focus" \ - $dir_name + "$dir_name" done diff --git a/orchestrator/scripts/generate_competition_api.sh b/orchestrator/scripts/generate_competition_api.sh index 82f9663d..8579677e 100755 --- a/orchestrator/scripts/generate_competition_api.sh +++ b/orchestrator/scripts/generate_competition_api.sh @@ -37,13 +37,10 @@ TEMPDIR=$(mktemp -d) # Install required tools $PYTHON_CMD -m venv "$TEMPDIR/venv" -. $TEMPDIR/venv/bin/activate +. "$TEMPDIR/venv/bin/activate" # Install fastapi-codegen and other dependencies -pip install git+https://github.com/trail-of-forks/fastapi-code-generator -pip install uvicorn -pip install fastapi -pip install pyyaml +uv pip install --isolated git+https://github.com/trail-of-forks/fastapi-code-generator uvicorn fastapi pyyaml # Convert YAML to JSON first echo "Converting YAML to JSON..." diff --git a/orchestrator/scripts/update_apis.sh b/orchestrator/scripts/update_apis.sh index 534ca6e1..c02b8ab9 100755 --- a/orchestrator/scripts/update_apis.sh +++ b/orchestrator/scripts/update_apis.sh @@ -30,23 +30,23 @@ cp -rv example-crs-architecture/docs/ "$OUTPUT_DIR/src/buttercup/orchestrator/do TEMPDIR=$(mktemp -d) $PYTHON_CMD -m venv "$TEMPDIR/venv" -. $TEMPDIR/venv/bin/activate +. "$TEMPDIR/venv/bin/activate" # Update competition API client # Run docker with current user to ensure correct file permissions USER_ID=$(id -u) GROUP_ID=$(id -g) -docker run --rm --user $USER_ID:$GROUP_ID -v "$(realpath example-crs-architecture/docs/api):/local" -v "$OUTPUT_DIR/src/buttercup/orchestrator:/out" openapitools/openapi-generator-cli generate \ +docker run --rm --user "$USER_ID:$GROUP_ID" -v "$(realpath example-crs-architecture/docs/api):/local" -v "$OUTPUT_DIR/src/buttercup/orchestrator:/out" openapitools/openapi-generator-cli generate \ -i /local/competition-swagger-v1.4.0.json \ -g python \ -o /out \ --package-name competition_api_client -sed -i.bak 's/^from competition_api_client/from buttercup.orchestrator.competition_api_client/' $(find $OUTPUT_DIR/src/buttercup/orchestrator -name '*.py') -sed -i.bak 's/competition_api_client.models,/buttercup.orchestrator.competition_api_client.models,/' $(find $OUTPUT_DIR/src/buttercup/orchestrator -name '*.py') -sed -i.bak 's/^import competition_api_client/import buttercup.orchestrator.competition_api_client/' $(find $OUTPUT_DIR/src/buttercup/orchestrator -name '*.py') -sed -i.bak 's/^from competition_api_client/from buttercup.orchestrator.competition_api_client/' $(find $OUTPUT_DIR/src/buttercup/orchestrator -name '*.md') -sed -i.bak 's/^import competition_api_client./import buttercup.orchestrator.competition_api_client./' $(find $OUTPUT_DIR/src/buttercup/orchestrator -name '*.md') +find "$OUTPUT_DIR/src/buttercup/orchestrator" -name '*.py' -exec sed -i.bak 's/^from competition_api_client/from buttercup.orchestrator.competition_api_client/' {} + +find "$OUTPUT_DIR/src/buttercup/orchestrator" -name '*.py' -exec sed -i.bak 's/competition_api_client.models,/buttercup.orchestrator.competition_api_client.models,/' {} + +find "$OUTPUT_DIR/src/buttercup/orchestrator" -name '*.py' -exec sed -i.bak 's/^import competition_api_client/import buttercup.orchestrator.competition_api_client/' {} + +find "$OUTPUT_DIR/src/buttercup/orchestrator" -name '*.md' -exec sed -i.bak 's/^from competition_api_client/from buttercup.orchestrator.competition_api_client/' {} + +find "$OUTPUT_DIR/src/buttercup/orchestrator" -name '*.md' -exec sed -i.bak 's/^import competition_api_client./import buttercup.orchestrator.competition_api_client./' {} + mkdir -p "$OUTPUT_DIR/docs" mkdir -p "$OUTPUT_DIR/test" @@ -56,9 +56,7 @@ rm -rf "$OUTPUT_DIR/src/buttercup/orchestrator/docs" rm -rf "$OUTPUT_DIR/src/buttercup/orchestrator/test" # Update task_server APIs -pip install git+https://github.com/trail-of-forks/fastapi-code-generator -pip install uvicorn -pip install fastapi +uv pip install --isolated git+https://github.com/trail-of-forks/fastapi-code-generator uvicorn fastapi TEMPDIR=$(mktemp -d) curl -o "$TEMPDIR/openapi.json" -X POST https://converter.swagger.io/api/convert -H "Content-Type: application/json" --data-binary "@example-crs-architecture/docs/api/crs-swagger-v1.4.0.json" diff --git a/protoc.sh b/protoc.sh index e34b055b..2858e569 100755 --- a/protoc.sh +++ b/protoc.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash # this is a hack because i dont want to cut a new release of clusterfuzz that # supports later versions of grpc -localpath="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +localpath="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 || exit ; pwd -P )" echo "$localpath" echo "$localpath/common/protos" protoc \ --pyi_out="$localpath/common/src/buttercup/common/datastructures/" \ --python_out "$localpath/common/src/buttercup/common/datastructures/" \ -I"$localpath/common/protos" \ - $localpath/common/protos/*.proto + "$localpath"/common/protos/*.proto diff --git a/scripts/common.sh b/scripts/common.sh index 00e36776..3f7baf2f 100755 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -65,7 +65,7 @@ install_docker() { if ! command_exists docker; then curl -fsSL https://get.docker.com | sh print_status "Adding user to Docker group (sudo required)..." - sudo usermod -aG docker $USER + sudo usermod -aG docker "$USER" print_success "Docker installed successfully" print_warning "You need to log out and back in for Docker group changes to take effect" else @@ -83,7 +83,7 @@ install_uv() { if ! command_exists uv; then curl -fsSL https://astral.sh/uv/install.sh | sh print_status "Sourcing uv environment..." - source $HOME/.local/bin/env + source "$HOME/.local/bin/env" print_success "uv installed successfully" else print_success "uv is already installed" @@ -224,7 +224,8 @@ check_azure_cli() { print_status "Checking Azure CLI..." if command_exists az; then if az account show >/dev/null 2>&1; then - local subscription=$(az account show --query name -o tsv) + local subscription + subscription=$(az account show --query name -o tsv) print_success "Azure CLI is logged in (subscription: $subscription)" else print_warning "Azure CLI is installed but not logged in" @@ -257,7 +258,7 @@ setup_config_file() { else print_warning "Configuration file already exists" if [ "$overwrite_existing" = "true" ]; then - read -p "Do you want to overwrite it? (y/N): " -n 1 -r + read -r -p "Do you want to overwrite it? (y/N): " -n 1 echo if [[ $REPLY =~ ^[Yy]$ ]]; then cp deployment/env.template deployment/env @@ -298,7 +299,7 @@ prompt_for_update() { local is_secret="${5:-false}" if [ -n "$current_value" ] && [ "$current_value" != "$default_value" ]; then - echo -n "$display_name is already configured. Set a new value? (y/N): " + printf "%s is already configured. Set a new value? (y/N): " "$display_name" read -r response if [[ "$response" =~ ^[Yy]$ ]]; then return 0 # Proceed with update @@ -317,12 +318,12 @@ read_and_set_config() { local prompt_text="$3" local is_secret="${4:-false}" local value - + if [ "$is_secret" = true ]; then - read -s -p "$prompt_text" value + read -rs -p "$prompt_text" value echo else - read -p "$prompt_text" value + read -r -p "$prompt_text" value fi # Only update if value is not empty @@ -334,9 +335,9 @@ read_and_set_config() { # Helper function for GHCR configuration configure_ghcr_optional() { - read -p "Enter your GitHub username (press Enter to skip): " ghcr_username + read -r -p "Enter your GitHub username (press Enter to skip): " ghcr_username if [ -n "$ghcr_username" ]; then - read -s -p "Enter your GitHub Personal Access Token (PAT): " ghcr_pat + read -rs -p "Enter your GitHub Personal Access Token (PAT): " ghcr_pat echo # Compute GHCR_AUTH @@ -352,9 +353,9 @@ configure_ghcr_optional() { # Helper function for Docker Hub configuration configure_docker_hub() { - read -p "Enter your Docker Hub username (optional, press Enter to skip): " docker_username + read -r -p "Enter your Docker Hub username (optional, press Enter to skip): " docker_username if [ -n "$docker_username" ]; then - read -s -p "Enter your Docker Hub Personal Access Token: " docker_pat + read -rs -p "Enter your Docker Hub Personal Access Token: " docker_pat echo # Set Docker credentials (handles both commented and uncommented lines) @@ -411,12 +412,12 @@ configure_simple_api_key() { local prompt_text="$2" local is_secret="${3:-true}" local value - + if [ "$is_secret" = true ]; then - read -s -p "$prompt_text" value + read -rs -p "$prompt_text" value echo else - read -p "$prompt_text" value + read -r -p "$prompt_text" value fi if [ -n "$value" ]; then @@ -438,9 +439,9 @@ configure_simple_api_key() { # Wrapper functions for specific configurations configure_docker_hub_optional() { - read -p "Enter your Docker Hub username (press Enter to skip): " docker_username + read -r -p "Enter your Docker Hub username (press Enter to skip): " docker_username if [ -n "$docker_username" ]; then - read -s -p "Enter your Docker Hub Personal Access Token: " docker_pat + read -rs -p "Enter your Docker Hub Personal Access Token: " docker_pat echo # Set Docker credentials @@ -456,10 +457,10 @@ configure_docker_hub_optional() { } configure_otel_wrapper() { - read -p "Enter OTEL endpoint URL (press Enter to skip): " otel_endpoint + read -r -p "Enter OTEL endpoint URL (press Enter to skip): " otel_endpoint if [ -n "$otel_endpoint" ]; then - read -p "Enter OTEL protocol (http/grpc): " otel_protocol - read -s -p "Enter OTEL token (including Basic or Bearer, optional, press Enter to skip): " otel_token + read -r -p "Enter OTEL protocol (http/grpc): " otel_protocol + read -rs -p "Enter OTEL token (including Basic or Bearer, optional, press Enter to skip): " otel_token echo # Update the env file @@ -480,10 +481,10 @@ configure_otel_wrapper() { } configure_langfuse_wrapper() { - read -p "Enter LangFuse host URL (press Enter to skip): " langfuse_host + read -r -p "Enter LangFuse host URL (press Enter to skip): " langfuse_host if [ -n "$langfuse_host" ]; then - read -p "Enter LangFuse public key: " langfuse_public_key - read -s -p "Enter LangFuse secret key: " langfuse_secret_key + read -r -p "Enter LangFuse public key: " langfuse_public_key + read -rs -p "Enter LangFuse secret key: " langfuse_secret_key echo # Update the env file @@ -503,7 +504,7 @@ configure_langfuse_wrapper() { } configure_llm_budget_wrapper() { - read -p "Enter LLM budget (press Enter for \$100 default): " budget_value + read -r -p "Enter LLM budget (press Enter for \$100 default): " budget_value if [ -n "$budget_value" ]; then # Set the budget value portable_sed "s|.*export LITELLM_MAX_BUDGET=.*|export LITELLM_MAX_BUDGET=\"$budget_value\"|" deployment/env @@ -600,7 +601,8 @@ generate_litellm_master_key() { print_linebreak print_status "Configuring LiteLLM master key..." - local litellm_master_key=$(openssl rand -hex 16) + local litellm_master_key + litellm_master_key=$(openssl rand -hex 16) portable_sed "s|.*export LITELLM_MASTER_KEY=.*|export LITELLM_MASTER_KEY=\"$litellm_master_key\"|" deployment/env print_success "LiteLLM master key configured successfully" } @@ -617,10 +619,14 @@ generate_crs_key_id_token() { print_linebreak print_status "Configuring CRS key ID/token..." - local auth_tool_output=$(pushd orchestrator && uv run python3 ./src/buttercup/orchestrator/task_server/auth_tool.py --env) - local crs_key_id=$(echo "$auth_tool_output" | grep "BUTTERCUP_TASK_SERVER_API_KEY_ID" | cut -d'=' -f2-) - local crs_key_token=$(echo "$auth_tool_output" | grep "^API_TOKEN" | cut -d'=' -f2-) - local crs_key_token_hash=$(echo "$auth_tool_output" | grep "^BUTTERCUP_TASK_SERVER_API_TOKEN_HASH" | cut -d'=' -f2-) + local auth_tool_output + local crs_key_id + local crs_key_token + local crs_key_token_hash + auth_tool_output=$(pushd orchestrator && uv run python3 ./src/buttercup/orchestrator/task_server/auth_tool.py --env) + crs_key_id=$(echo "$auth_tool_output" | grep "BUTTERCUP_TASK_SERVER_API_KEY_ID" | cut -d'=' -f2-) + crs_key_token=$(echo "$auth_tool_output" | grep "^API_TOKEN" | cut -d'=' -f2-) + crs_key_token_hash=$(echo "$auth_tool_output" | grep "^BUTTERCUP_TASK_SERVER_API_TOKEN_HASH" | cut -d'=' -f2-) portable_sed "s|.*export CRS_KEY_ID=.*|export CRS_KEY_ID=\"$crs_key_id\"|" deployment/env portable_sed "s|.*export CRS_KEY_TOKEN=.*|export CRS_KEY_TOKEN=\"$crs_key_token\"|" deployment/env diff --git a/scripts/download_artifacts.sh b/scripts/download_artifacts.sh index cd6d3dff..c0a64d6c 100755 --- a/scripts/download_artifacts.sh +++ b/scripts/download_artifacts.sh @@ -1,7 +1,7 @@ #!/bin/bash # Download all artifacts from the buttercup-ui pod -BUTTERCUP_UI_POD=$(kubectl get pods -n ${BUTTERCUP_NAMESPACE:-crs} -l app=ui -o jsonpath='{.items[0].metadata.name}') -kubectl cp -n crs $BUTTERCUP_UI_POD:/tmp/buttercup-run-data /tmp/buttercup-run-data +BUTTERCUP_UI_POD=$(kubectl get pods -n "${BUTTERCUP_NAMESPACE:-crs}" -l app=ui -o jsonpath='{.items[0].metadata.name}') +kubectl cp -n crs "$BUTTERCUP_UI_POD":/tmp/buttercup-run-data /tmp/buttercup-run-data echo "Artifacts downloaded to /tmp/buttercup-run-data" diff --git a/scripts/results_unpack.sh b/scripts/results_unpack.sh index 863ea39d..c8d7e861 100755 --- a/scripts/results_unpack.sh +++ b/scripts/results_unpack.sh @@ -12,9 +12,9 @@ rm -rf tasks_storage tar xvf tasks_storage.tar # Decompress -for fn in `find tasks_storage -name *.tgz`; do +find tasks_storage -name '*.tgz' | while IFS= read -r fn; do dst="${fn%.tgz}" - mkdir $dst - tar xvzf $fn -C $dst - rm $fn -done \ No newline at end of file + mkdir "$dst" + tar xvzf "$fn" -C "$dst" + rm "$fn" +done diff --git a/scripts/setup-azure.sh b/scripts/setup-azure.sh index 40a396db..e9a07b5c 100755 --- a/scripts/setup-azure.sh +++ b/scripts/setup-azure.sh @@ -19,8 +19,10 @@ setup_service_principal() { print_status "Setting up Azure Service Principal..." # Get current subscription - local subscription_id=$(az account show --query id -o tsv) - local subscription_name=$(az account show --query name -o tsv) + local subscription_id + local subscription_name + subscription_id=$(az account show --query id -o tsv) + subscription_name=$(az account show --query name -o tsv) print_status "Current subscription: $subscription_name ($subscription_id)" @@ -32,7 +34,7 @@ setup_service_principal() { RESOURCE_GROUP_NAME="${TF_VAR_resource_group_name:-}" if [ -z "$RESOURCE_GROUP_NAME" ] || [ "$RESOURCE_GROUP_NAME" = "" ]; then print_status "Please specify the resource group where all Azure resources will be deployed:" - read -p "Enter resource group name (e.g., buttercup-crs-rg): " RESOURCE_GROUP_NAME + read -r -p "Enter resource group name (e.g., buttercup-crs-rg): " RESOURCE_GROUP_NAME if [ -z "$RESOURCE_GROUP_NAME" ]; then print_error "Resource group name is required" @@ -43,7 +45,7 @@ setup_service_principal() { fi RESOURCE_GROUP_LOCATION="${TF_VAR_resource_group_location:-}" if [ -z "$RESOURCE_GROUP_LOCATION" ] || [ "$RESOURCE_GROUP_LOCATION" = "" ]; then - read -p "Enter resource group location (default: eastus): " RESOURCE_GROUP_LOCATION + read -r -p "Enter resource group location (default: eastus): " RESOURCE_GROUP_LOCATION if [ -z "$RESOURCE_GROUP_LOCATION" ]; then RESOURCE_GROUP_LOCATION="eastus" print_status "No value provided. Using default: $RESOURCE_GROUP_LOCATION" @@ -85,17 +87,22 @@ setup_service_principal() { # Only prompt to create a new service principal if TF_VAR_ARM_CLIENT_ID and TF_VAR_ARM_CLIENT_SECRET are not set if [ -z "$TF_VAR_ARM_CLIENT_ID" ] || [ "$TF_VAR_ARM_CLIENT_ID" = "" ] || [ -z "$TF_VAR_ARM_CLIENT_SECRET" ] || [ "$TF_VAR_ARM_CLIENT_SECRET" = "" ]; then - read -p "Do you want to create a new service principal? (y/N): " -n 1 -r + read -r -p "Do you want to create a new service principal? (y/N): " -n 1 echo if [[ $REPLY =~ ^[Yy]$ ]]; then - local sp_name="ButtercupCRS-$(date +%Y%m%d-%H%M%S)" + local sp_name + sp_name="ButtercupCRS-$(date +%Y%m%d-%H%M%S)" print_status "Creating service principal: $sp_name" - local sp_output=$(az ad sp create-for-rbac --name "$sp_name" --role Contributor --scopes "/subscriptions/$subscription_id/resourceGroups/$RESOURCE_GROUP_NAME" --output json) + local sp_output + sp_output=$(az ad sp create-for-rbac --name "$sp_name" --role Contributor --scopes "/subscriptions/$subscription_id/resourceGroups/$RESOURCE_GROUP_NAME" --output json) - local app_id=$(echo "$sp_output" | jq -r '.appId') - local password=$(echo "$sp_output" | jq -r '.password') - local tenant_id=$(echo "$sp_output" | jq -r '.tenant') + local app_id + local password + local tenant_id + app_id=$(echo "$sp_output" | jq -r '.appId') + password=$(echo "$sp_output" | jq -r '.password') + tenant_id=$(echo "$sp_output" | jq -r '.tenant') print_success "Service principal created successfully" echo @@ -184,9 +191,10 @@ setup_remote_state() { # Use the same resource group as specified in setup_service_principal local state_rg="$RESOURCE_GROUP_NAME" # Generate a short random suffix (4 alphanumeric chars) - local rand_suffix=$(LC_CTYPE=C tr -dc 'a-z0-9' /dev/null + kill "$PORT_FORWARD_PID" 2>/dev/null echo "Port-forward stopped. Goodbye!" exit 0 } @@ -57,5 +57,5 @@ fi if [ -n "$PORT_FORWARD_PID" ]; then # Keep the script running - wait $PORT_FORWARD_PID + wait "$PORT_FORWARD_PID" fi