#!/usr/bin/env bash # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # # 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. set -euo pipefail # create-cluster.sh - Create an Aurora DSQL cluster # # Usage: ./create-cluster.sh --created-by MODEL_ID [--region REGION] [--tags KEY=VALUE,...] # # Examples: # ./create-cluster.sh --created-by claude-opus-4-6 # ./create-cluster.sh --created-by claude-opus-4-6 --region us-east-1 # ./create-cluster.sh --created-by claude-opus-4-6 --region us-west-2 --tags Environment=dev,Project=myapp REGION="${REGION:-${AWS_REGION:-us-east-1}}" TAGS="" CREATED_BY="" DELETION_PROTECTION=true # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --region) REGION="$2" shift 2 ;; --tags) TAGS="$2" shift 2 ;; --created-by) CREATED_BY="$2" shift 2 ;; --no-deletion-protection) DELETION_PROTECTION=false shift ;; -h|--help) echo "Usage: $0 --created-by MODEL_ID [--region REGION] [--tags KEY=VALUE,...] [--no-deletion-protection]" echo "" echo "Creates an Aurora DSQL cluster in the specified region." echo "Deletion protection is enabled by default." echo "" echo "Options:" echo " --region REGION AWS region (default: \$AWS_REGION or us-east-1)" echo " --tags TAGS Comma-separated tags (e.g., Env=dev,Project=app)" echo " --created-by ID Model/agent identifier added as a 'created_by' cluster tag" echo " --no-deletion-protection Disable deletion protection (default: enabled)" echo " -h, --help Show this help message" exit 0 ;; *) echo "Unknown option: $1" exit 1 ;; esac done echo "Creating Aurora DSQL cluster in $REGION..." # Prepend created_by tag if --created-by was provided if [[ -n "$CREATED_BY" ]]; then # Validate: allow only alphanumeric, hyphens, underscores, and dots (e.g. claude-opus-4-6) if [[ ! "$CREATED_BY" =~ ^[a-zA-Z0-9._-]+$ ]]; then echo "Error: --created-by must contain only alphanumeric characters, hyphens, underscores, and dots." >&2 exit 1 fi if [[ -n "$TAGS" ]]; then TAGS="created_by=${CREATED_BY},${TAGS}" else TAGS="created_by=${CREATED_BY}" fi fi # Build the AWS CLI command as an array to avoid eval and shell injection CMD=(aws dsql create-cluster --region "$REGION") # Enable deletion protection by default (use --no-deletion-protection to opt out) if [[ "$DELETION_PROTECTION" == "true" ]]; then CMD+=(--deletion-protection-enabled) else CMD+=(--no-deletion-protection-enabled) fi # Add tags if provided if [[ -n "$TAGS" ]]; then # Convert comma-separated tags to JSON format using jq for safe escaping TAG_JSON=$(printf '%s\n' "$TAGS" | tr ',' '\n' | jq -Rn ' [inputs | split("=") | {(.[0]): .[1:] | join("=")}] | add // {} ') || { echo "Error: Failed to convert tags to JSON." >&2 exit 1 } if [[ -z "$TAG_JSON" || "$TAG_JSON" == "{}" ]]; then echo "Error: Tags produced empty JSON. Check format: KEY=VALUE,..." >&2 exit 1 fi CMD+=(--tags "$TAG_JSON") fi # Execute the command directly (no eval) TEMP_FILE=$(mktemp) trap 'rm -f "$TEMP_FILE"' EXIT "${CMD[@]}" > "$TEMP_FILE" # Extract cluster identifier and endpoint CLUSTER_ID=$(jq -r '.identifier' "$TEMP_FILE") CLUSTER_ARN=$(jq -r '.arn' "$TEMP_FILE") if [[ -z "$CLUSTER_ID" || "$CLUSTER_ID" == "null" ]]; then echo "Error: Failed to extract cluster identifier from response." >&2 echo "Response:" >&2 cat "$TEMP_FILE" >&2 exit 1 fi CLUSTER_ENDPOINT="${CLUSTER_ID}.dsql.${REGION}.on.aws" echo "" echo "✓ Cluster created successfully!" echo "" echo "Cluster Identifier: $CLUSTER_ID" echo "Cluster Endpoint: $CLUSTER_ENDPOINT" echo "Cluster ARN: $CLUSTER_ARN" echo "Region: $REGION" echo "" echo "Export these environment variables for use with psql-connect.sh and other scripts:" echo "" echo "export CLUSTER=$CLUSTER_ID" echo "export REGION=$REGION" echo "" echo "To connect with psql:" echo "./scripts/psql-connect.sh" # Clean up handled by trap