There are more AWS SDK examples available in the AWS Doc SDK Examples
Amazon Redshift examples using AWS CLI with Bash script
The following code examples show you how to perform actions and implement common scenarios by using the AWS Command Line Interface with Bash script with Amazon Redshift.
Scenarios are code examples that show you how to accomplish specific tasks by calling multiple functions within a service or combined with other AWS services.
Each example includes a link to the complete source code, where you can find instructions on how to set up and run the code in context.
Topics
Scenarios
The following code example shows how to:
Use secrets-manager CreateSecret
Use secrets-manager DeleteSecret
Use secrets-manager GetSecretValue
Use redshift CreateNamespace
Use redshift CreateWorkgroup
Use redshift DeleteNamespace
Use iam CreateRole
- AWS CLI with Bash script
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the Sample developer tutorials
repository. #!/bin/bash # Amazon Redshift Serverless Tutorial Script with Secrets Manager (No jq dependency) # This script creates a Redshift Serverless environment, loads sample data, and runs queries # Uses AWS Secrets Manager for secure password management without requiring jq # Set up logging LOG_FILE="redshift-serverless-tutorial-v4.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting Amazon Redshift Serverless tutorial script at $(date)" echo "All commands and outputs will be logged to $LOG_FILE" # Function to check for errors in command output check_error() { local output=$1 local cmd=$2 if echo "$output" | grep -i "error\|exception\|fail" > /dev/null; then echo "ERROR: Command failed: $cmd" echo "Output: $output" cleanup_resources exit 1 fi } # Function to generate a secure password that meets Redshift requirements generate_secure_password() { # Redshift password requirements: # - 8-64 characters # - At least one uppercase letter # - At least one lowercase letter # - At least one decimal digit # - Can contain printable ASCII characters except /, ", ', \, @, space local password="" local valid=false local attempts=0 local max_attempts=10 while [[ "$valid" == false && $attempts -lt $max_attempts ]]; do # Generate base password with safe characters local base=$(openssl rand -base64 12 | tr -d '/+=' | head -c 12) # Ensure we have at least one of each required character type local upper=$(echo "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | fold -w1 | shuf -n1) local lower=$(echo "abcdefghijklmnopqrstuvwxyz" | fold -w1 | shuf -n1) local digit=$(echo "0123456789" | fold -w1 | shuf -n1) local special=$(echo "!#$%&*()_+-=[]{}|;:,.<>?" | fold -w1 | shuf -n1) # Combine and shuffle password="${base}${upper}${lower}${digit}${special}" password=$(echo "$password" | fold -w1 | shuf | tr -d '\n') # Validate password meets requirements if [[ ${#password} -ge 8 && ${#password} -le 64 ]] && \ [[ "$password" =~ [A-Z] ]] && \ [[ "$password" =~ [a-z] ]] && \ [[ "$password" =~ [0-9] ]] && \ [[ ! "$password" =~ [/\"\'\\@[:space:]] ]]; then valid=true fi ((attempts++)) done if [[ "$valid" == false ]]; then echo "ERROR: Failed to generate valid password after $max_attempts attempts" exit 1 fi echo "$password" } # Function to create secret in AWS Secrets Manager create_secret() { local secret_name=$1 local username=$2 local password=$3 local description=$4 echo "Creating secret in AWS Secrets Manager: $secret_name" # Create the secret using AWS CLI without jq local secret_output=$(aws secretsmanager create-secret \ --name "$secret_name" \ --description "$description" \ --secret-string "{\"username\":\"$username\",\"password\":\"$password\"}" 2>&1) if echo "$secret_output" | grep -i "error\|exception\|fail" > /dev/null; then echo "ERROR: Failed to create secret: $secret_output" return 1 fi echo "Secret created successfully: $secret_name" return 0 } # Function to retrieve password from AWS Secrets Manager get_password_from_secret() { local secret_name=$1 # Get the secret value and extract password using sed/grep instead of jq local secret_value=$(aws secretsmanager get-secret-value \ --secret-id "$secret_name" \ --query 'SecretString' \ --output text 2>/dev/null) if [[ $? -eq 0 ]]; then # Extract password from JSON using sed echo "$secret_value" | sed -n 's/.*"password":"\([^"]*\)".*/\1/p' else echo "" fi } # Function to wait for a resource to be available wait_for_resource() { local resource_type=$1 local resource_name=$2 local max_attempts=$3 local wait_seconds=$4 local check_cmd=$5 echo "Waiting for $resource_type $resource_name to be available..." for ((i=1; i<=$max_attempts; i++)); do local output=$($check_cmd 2>/dev/null) local status=$(echo "$output" | grep -o '"Status": "[^"]*' | cut -d'"' -f4 || echo "") if [[ "$status" == "AVAILABLE" ]]; then echo "$resource_type $resource_name is now available" return 0 fi echo "Attempt $i/$max_attempts: $resource_type $resource_name status: $status. Waiting $wait_seconds seconds..." sleep $wait_seconds done echo "ERROR: Timed out waiting for $resource_type $resource_name to be available" return 1 } # Function to wait for a resource to be deleted wait_for_resource_deletion() { local resource_type=$1 local resource_name=$2 local max_attempts=$3 local wait_seconds=$4 local check_cmd=$5 echo "Waiting for $resource_type $resource_name to be deleted..." for ((i=1; i<=$max_attempts; i++)); do local output=$($check_cmd 2>&1) if echo "$output" | grep -i "not found\|does not exist" > /dev/null; then echo "$resource_type $resource_name has been deleted" return 0 fi echo "Attempt $i/$max_attempts: $resource_type $resource_name is still being deleted. Waiting $wait_seconds seconds..." sleep $wait_seconds done echo "ERROR: Timed out waiting for $resource_type $resource_name to be deleted" return 1 } # Function to clean up resources cleanup_resources() { echo "" echo "===========================================" echo "CLEANUP CONFIRMATION" echo "===========================================" echo "The following resources were created:" echo "- Redshift Serverless Workgroup: $WORKGROUP_NAME" echo "- Redshift Serverless Namespace: $NAMESPACE_NAME" echo "- IAM Role: $ROLE_NAME" echo "- Secrets Manager Secret: $SECRET_NAME" echo "" echo "Do you want to clean up all created resources? (y/n): " read -r CLEANUP_CHOICE if [[ "${CLEANUP_CHOICE,,}" == "y" ]]; then echo "Cleaning up resources..." # Delete the workgroup echo "Deleting Redshift Serverless workgroup $WORKGROUP_NAME..." WORKGROUP_DELETE_OUTPUT=$(aws redshift-serverless delete-workgroup --workgroup-name "$WORKGROUP_NAME" 2>&1) echo "$WORKGROUP_DELETE_OUTPUT" # Wait for workgroup to be deleted before deleting namespace wait_for_resource_deletion "workgroup" "$WORKGROUP_NAME" 20 30 "aws redshift-serverless get-workgroup --workgroup-name $WORKGROUP_NAME" # Delete the namespace echo "Deleting Redshift Serverless namespace $NAMESPACE_NAME..." NAMESPACE_DELETE_OUTPUT=$(aws redshift-serverless delete-namespace --namespace-name "$NAMESPACE_NAME" 2>&1) echo "$NAMESPACE_DELETE_OUTPUT" # Wait for namespace to be deleted wait_for_resource_deletion "namespace" "$NAMESPACE_NAME" 20 30 "aws redshift-serverless get-namespace --namespace-name $NAMESPACE_NAME" # Delete the IAM role policy echo "Deleting IAM role policy..." POLICY_DELETE_OUTPUT=$(aws iam delete-role-policy --role-name "$ROLE_NAME" --policy-name S3Access 2>&1) echo "$POLICY_DELETE_OUTPUT" # Delete the IAM role echo "Deleting IAM role $ROLE_NAME..." ROLE_DELETE_OUTPUT=$(aws iam delete-role --role-name "$ROLE_NAME" 2>&1) echo "$ROLE_DELETE_OUTPUT" # Delete the secret echo "Deleting Secrets Manager secret $SECRET_NAME..." SECRET_DELETE_OUTPUT=$(aws secretsmanager delete-secret --secret-id "$SECRET_NAME" --force-delete-without-recovery 2>&1) echo "$SECRET_DELETE_OUTPUT" echo "Cleanup completed." else echo "Cleanup skipped. Resources will remain in your AWS account." fi } # Check if required tools are available if ! command -v openssl &> /dev/null; then echo "ERROR: openssl is required but not installed. Please install openssl to continue." exit 1 fi # Generate unique names for resources RANDOM_SUFFIX=$(cat /dev/urandom | tr -dc 'a-z0-9' | head -c 6) NAMESPACE_NAME="rs-namespace-${RANDOM_SUFFIX}" WORKGROUP_NAME="rs-workgroup-${RANDOM_SUFFIX}" ROLE_NAME="RedshiftServerlessS3Role-${RANDOM_SUFFIX}" SECRET_NAME="redshift-serverless-admin-${RANDOM_SUFFIX}" DB_NAME="dev" ADMIN_USERNAME="admin" # Generate secure password echo "Generating secure password..." ADMIN_PASSWORD=$(generate_secure_password) # Create secret in AWS Secrets Manager create_secret "$SECRET_NAME" "$ADMIN_USERNAME" "$ADMIN_PASSWORD" "Admin credentials for Redshift Serverless namespace $NAMESPACE_NAME" if [[ $? -ne 0 ]]; then echo "ERROR: Failed to create secret in AWS Secrets Manager" exit 1 fi # Track created resources CREATED_RESOURCES=() echo "Using the following resource names:" echo "- Namespace: $NAMESPACE_NAME" echo "- Workgroup: $WORKGROUP_NAME" echo "- IAM Role: $ROLE_NAME" echo "- Secret: $SECRET_NAME" echo "- Database: $DB_NAME" echo "- Admin Username: $ADMIN_USERNAME" echo "- Admin Password: [STORED IN SECRETS MANAGER]" # Step 1: Create IAM role for S3 access echo "Creating IAM role for Redshift Serverless S3 access..." # Create trust policy document cat > redshift-trust-policy.json << EOF { "Version":"2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "redshift-serverless.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF # Create S3 access policy document cat > redshift-s3-policy.json << EOF { "Version":"2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::redshift-downloads", "arn:aws:s3:::redshift-downloads/*" ] } ] } EOF # Create IAM role echo "Creating IAM role $ROLE_NAME..." ROLE_OUTPUT=$(aws iam create-role --role-name "$ROLE_NAME" --assume-role-policy-document file://redshift-trust-policy.json 2>&1) echo "$ROLE_OUTPUT" check_error "$ROLE_OUTPUT" "aws iam create-role" CREATED_RESOURCES+=("IAM Role: $ROLE_NAME") # Attach S3 policy to the role echo "Attaching S3 access policy to role $ROLE_NAME..." POLICY_OUTPUT=$(aws iam put-role-policy --role-name "$ROLE_NAME" --policy-name S3Access --policy-document file://redshift-s3-policy.json 2>&1) echo "$POLICY_OUTPUT" check_error "$POLICY_OUTPUT" "aws iam put-role-policy" # Get the role ARN ROLE_ARN=$(aws iam get-role --role-name "$ROLE_NAME" --query 'Role.Arn' --output text) echo "Role ARN: $ROLE_ARN" # Step 2: Create a namespace echo "Creating Redshift Serverless namespace $NAMESPACE_NAME..." NAMESPACE_OUTPUT=$(aws redshift-serverless create-namespace \ --namespace-name "$NAMESPACE_NAME" \ --admin-username "$ADMIN_USERNAME" \ --admin-user-password "$ADMIN_PASSWORD" \ --db-name "$DB_NAME" 2>&1) echo "$NAMESPACE_OUTPUT" check_error "$NAMESPACE_OUTPUT" "aws redshift-serverless create-namespace" CREATED_RESOURCES+=("Redshift Serverless Namespace: $NAMESPACE_NAME") # Wait for namespace to be available wait_for_resource "namespace" "$NAMESPACE_NAME" 10 30 "aws redshift-serverless get-namespace --namespace-name $NAMESPACE_NAME" # Associate IAM role with namespace echo "Associating IAM role with namespace..." UPDATE_NAMESPACE_OUTPUT=$(aws redshift-serverless update-namespace \ --namespace-name "$NAMESPACE_NAME" \ --iam-roles "$ROLE_ARN" 2>&1) echo "$UPDATE_NAMESPACE_OUTPUT" check_error "$UPDATE_NAMESPACE_OUTPUT" "aws redshift-serverless update-namespace" # Step 3: Create a workgroup echo "Creating Redshift Serverless workgroup $WORKGROUP_NAME..." WORKGROUP_OUTPUT=$(aws redshift-serverless create-workgroup \ --workgroup-name "$WORKGROUP_NAME" \ --namespace-name "$NAMESPACE_NAME" \ --base-capacity 8 2>&1) echo "$WORKGROUP_OUTPUT" check_error "$WORKGROUP_OUTPUT" "aws redshift-serverless create-workgroup" CREATED_RESOURCES+=("Redshift Serverless Workgroup: $WORKGROUP_NAME") # Wait for workgroup to be available wait_for_resource "workgroup" "$WORKGROUP_NAME" 20 30 "aws redshift-serverless get-workgroup --workgroup-name $WORKGROUP_NAME" # Get workgroup endpoint WORKGROUP_ENDPOINT=$(aws redshift-serverless get-workgroup \ --workgroup-name "$WORKGROUP_NAME" \ --query 'workgroup.endpoint.address' \ --output text) echo "Workgroup endpoint: $WORKGROUP_ENDPOINT" # Wait additional time for the endpoint to be fully operational echo "Waiting for endpoint to be fully operational..." sleep 60 # Step 4: Create tables for sample data echo "Creating tables for sample data..." # Create users table echo "Creating users table..." USERS_TABLE_OUTPUT=$(aws redshift-data execute-statement \ --database "$DB_NAME" \ --workgroup-name "$WORKGROUP_NAME" \ --sql "CREATE TABLE users( userid INTEGER NOT NULL DISTKEY SORTKEY, username CHAR(8), firstname VARCHAR(30), lastname VARCHAR(30), city VARCHAR(30), state CHAR(2), email VARCHAR(100), phone CHAR(14), likesports BOOLEAN, liketheatre BOOLEAN, likeconcerts BOOLEAN, likejazz BOOLEAN, likeclassical BOOLEAN, likeopera BOOLEAN, likerock BOOLEAN, likevegas BOOLEAN, likebroadway BOOLEAN, likemusicals BOOLEAN );" 2>&1) echo "$USERS_TABLE_OUTPUT" check_error "$USERS_TABLE_OUTPUT" "aws redshift-data execute-statement (users table)" USERS_QUERY_ID=$(echo "$USERS_TABLE_OUTPUT" | grep -o '"Id": "[^"]*' | cut -d'"' -f4) # Wait for query to complete echo "Waiting for users table creation to complete..." sleep 5 # Create event table echo "Creating event table..." EVENT_TABLE_OUTPUT=$(aws redshift-data execute-statement \ --database "$DB_NAME" \ --workgroup-name "$WORKGROUP_NAME" \ --sql "CREATE TABLE event( eventid INTEGER NOT NULL DISTKEY, venueid SMALLINT NOT NULL, catid SMALLINT NOT NULL, dateid SMALLINT NOT NULL SORTKEY, eventname VARCHAR(200), starttime TIMESTAMP );" 2>&1) echo "$EVENT_TABLE_OUTPUT" check_error "$EVENT_TABLE_OUTPUT" "aws redshift-data execute-statement (event table)" EVENT_QUERY_ID=$(echo "$EVENT_TABLE_OUTPUT" | grep -o '"Id": "[^"]*' | cut -d'"' -f4) # Wait for query to complete echo "Waiting for event table creation to complete..." sleep 5 # Create sales table echo "Creating sales table..." SALES_TABLE_OUTPUT=$(aws redshift-data execute-statement \ --database "$DB_NAME" \ --workgroup-name "$WORKGROUP_NAME" \ --sql "CREATE TABLE sales( salesid INTEGER NOT NULL, listid INTEGER NOT NULL DISTKEY, sellerid INTEGER NOT NULL, buyerid INTEGER NOT NULL, eventid INTEGER NOT NULL, dateid SMALLINT NOT NULL SORTKEY, qtysold SMALLINT NOT NULL, pricepaid DECIMAL(8,2), commission DECIMAL(8,2), saletime TIMESTAMP );" 2>&1) echo "$SALES_TABLE_OUTPUT" check_error "$SALES_TABLE_OUTPUT" "aws redshift-data execute-statement (sales table)" SALES_QUERY_ID=$(echo "$SALES_TABLE_OUTPUT" | grep -o '"Id": "[^"]*' | cut -d'"' -f4) # Wait for tables to be created echo "Waiting for tables to be created..." sleep 10 # Step 5: Load sample data from Amazon S3 echo "Loading sample data from Amazon S3..." # Load data into users table echo "Loading data into users table..." USERS_LOAD_OUTPUT=$(aws redshift-data execute-statement \ --database "$DB_NAME" \ --workgroup-name "$WORKGROUP_NAME" \ --sql "COPY users FROM 's3://redshift-downloads/tickit/allusers_pipe.txt' DELIMITER '|' TIMEFORMAT 'YYYY-MM-DD HH:MI:SS' IGNOREHEADER 1 IAM_ROLE '$ROLE_ARN';" 2>&1) echo "$USERS_LOAD_OUTPUT" check_error "$USERS_LOAD_OUTPUT" "aws redshift-data execute-statement (load users)" USERS_LOAD_QUERY_ID=$(echo "$USERS_LOAD_OUTPUT" | grep -o '"Id": "[^"]*' | cut -d'"' -f4) # Wait for data loading to complete echo "Waiting for users data loading to complete..." sleep 10 # Load data into event table echo "Loading data into event table..." EVENT_LOAD_OUTPUT=$(aws redshift-data execute-statement \ --database "$DB_NAME" \ --workgroup-name "$WORKGROUP_NAME" \ --sql "COPY event FROM 's3://redshift-downloads/tickit/allevents_pipe.txt' DELIMITER '|' TIMEFORMAT 'YYYY-MM-DD HH:MI:SS' IGNOREHEADER 1 IAM_ROLE '$ROLE_ARN';" 2>&1) echo "$EVENT_LOAD_OUTPUT" check_error "$EVENT_LOAD_OUTPUT" "aws redshift-data execute-statement (load event)" EVENT_LOAD_QUERY_ID=$(echo "$EVENT_LOAD_OUTPUT" | grep -o '"Id": "[^"]*' | cut -d'"' -f4) # Wait for data loading to complete echo "Waiting for event data loading to complete..." sleep 10 # Load data into sales table echo "Loading data into sales table..." SALES_LOAD_OUTPUT=$(aws redshift-data execute-statement \ --database "$DB_NAME" \ --workgroup-name "$WORKGROUP_NAME" \ --sql "COPY sales FROM 's3://redshift-downloads/tickit/sales_tab.txt' DELIMITER '\t' TIMEFORMAT 'MM/DD/YYYY HH:MI:SS' IGNOREHEADER 1 IAM_ROLE '$ROLE_ARN';" 2>&1) echo "$SALES_LOAD_OUTPUT" check_error "$SALES_LOAD_OUTPUT" "aws redshift-data execute-statement (load sales)" SALES_LOAD_QUERY_ID=$(echo "$SALES_LOAD_OUTPUT" | grep -o '"Id": "[^"]*' | cut -d'"' -f4) # Wait for data loading to complete echo "Waiting for sales data loading to complete..." sleep 30 # Step 6: Run sample queries echo "Running sample queries..." # Query 1: Find top 10 buyers by quantity echo "Running query: Find top 10 buyers by quantity..." QUERY1_OUTPUT=$(aws redshift-data execute-statement \ --database "$DB_NAME" \ --workgroup-name "$WORKGROUP_NAME" \ --sql "SELECT firstname, lastname, total_quantity FROM (SELECT buyerid, sum(qtysold) total_quantity FROM sales GROUP BY buyerid ORDER BY total_quantity desc limit 10) Q, users WHERE Q.buyerid = userid ORDER BY Q.total_quantity desc;" 2>&1) echo "$QUERY1_OUTPUT" check_error "$QUERY1_OUTPUT" "aws redshift-data execute-statement (query 1)" QUERY1_ID=$(echo "$QUERY1_OUTPUT" | grep -o '"Id": "[^"]*' | cut -d'"' -f4) # Wait for query to complete echo "Waiting for query 1 to complete..." sleep 10 # Get query 1 results echo "Getting results for query 1..." QUERY1_STATUS_OUTPUT=$(aws redshift-data describe-statement --id "$QUERY1_ID" 2>&1) echo "$QUERY1_STATUS_OUTPUT" check_error "$QUERY1_STATUS_OUTPUT" "aws redshift-data describe-statement (query 1)" QUERY1_STATUS=$(echo "$QUERY1_STATUS_OUTPUT" | grep -o '"Status": "[^"]*' | cut -d'"' -f4) if [ "$QUERY1_STATUS" == "FINISHED" ]; then QUERY1_RESULTS=$(aws redshift-data get-statement-result --id "$QUERY1_ID" 2>&1) echo "Query 1 Results:" echo "$QUERY1_RESULTS" else echo "Query 1 is not yet complete. Status: $QUERY1_STATUS" echo "Waiting additional time for query to complete..." sleep 20 # Check again QUERY1_STATUS_OUTPUT=$(aws redshift-data describe-statement --id "$QUERY1_ID" 2>&1) QUERY1_STATUS=$(echo "$QUERY1_STATUS_OUTPUT" | grep -o '"Status": "[^"]*' | cut -d'"' -f4) if [ "$QUERY1_STATUS" == "FINISHED" ]; then QUERY1_RESULTS=$(aws redshift-data get-statement-result --id "$QUERY1_ID" 2>&1) echo "Query 1 Results:" echo "$QUERY1_RESULTS" else echo "Query 1 is still not complete. Status: $QUERY1_STATUS" fi fi # Query 2: Find events in the 99.9 percentile in terms of all time total sales echo "Running query: Find events in the 99.9 percentile in terms of all time total sales..." QUERY2_OUTPUT=$(aws redshift-data execute-statement \ --database "$DB_NAME" \ --workgroup-name "$WORKGROUP_NAME" \ --sql "SELECT eventname, total_price FROM (SELECT eventid, total_price, ntile(1000) over(order by total_price desc) as percentile FROM (SELECT eventid, sum(pricepaid) total_price FROM sales GROUP BY eventid)) Q, event E WHERE Q.eventid = E.eventid AND percentile = 1 ORDER BY total_price desc;" 2>&1) echo "$QUERY2_OUTPUT" check_error "$QUERY2_OUTPUT" "aws redshift-data execute-statement (query 2)" QUERY2_ID=$(echo "$QUERY2_OUTPUT" | grep -o '"Id": "[^"]*' | cut -d'"' -f4) # Wait for query to complete echo "Waiting for query 2 to complete..." sleep 10 # Get query 2 results echo "Getting results for query 2..." QUERY2_STATUS_OUTPUT=$(aws redshift-data describe-statement --id "$QUERY2_ID" 2>&1) echo "$QUERY2_STATUS_OUTPUT" check_error "$QUERY2_STATUS_OUTPUT" "aws redshift-data describe-statement (query 2)" QUERY2_STATUS=$(echo "$QUERY2_STATUS_OUTPUT" | grep -o '"Status": "[^"]*' | cut -d'"' -f4) if [ "$QUERY2_STATUS" == "FINISHED" ]; then QUERY2_RESULTS=$(aws redshift-data get-statement-result --id "$QUERY2_ID" 2>&1) echo "Query 2 Results:" echo "$QUERY2_RESULTS" else echo "Query 2 is not yet complete. Status: $QUERY2_STATUS" echo "Waiting additional time for query to complete..." sleep 20 # Check again QUERY2_STATUS_OUTPUT=$(aws redshift-data describe-statement --id "$QUERY2_ID" 2>&1) QUERY2_STATUS=$(echo "$QUERY2_STATUS_OUTPUT" | grep -o '"Status": "[^"]*' | cut -d'"' -f4) if [ "$QUERY2_STATUS" == "FINISHED" ]; then QUERY2_RESULTS=$(aws redshift-data get-statement-result --id "$QUERY2_ID" 2>&1) echo "Query 2 Results:" echo "$QUERY2_RESULTS" else echo "Query 2 is still not complete. Status: $QUERY2_STATUS" fi fi # Summary echo "" echo "===========================================" echo "TUTORIAL SUMMARY" echo "===========================================" echo "You have successfully:" echo "1. Created a Redshift Serverless namespace and workgroup" echo "2. Created an IAM role with S3 access permissions" echo "3. Stored admin credentials securely in AWS Secrets Manager" echo "4. Created tables for sample data" echo "5. Loaded sample data from Amazon S3" echo "6. Run sample queries on the data" echo "" echo "Redshift Serverless Resources:" echo "- Namespace: $NAMESPACE_NAME" echo "- Workgroup: $WORKGROUP_NAME" echo "- Database: $DB_NAME" echo "- Endpoint: $WORKGROUP_ENDPOINT" echo "- Credentials Secret: $SECRET_NAME" echo "" echo "To connect to your Redshift Serverless database using SQL tools:" echo "- Host: $WORKGROUP_ENDPOINT" echo "- Database: $DB_NAME" echo "- Username: $ADMIN_USERNAME" echo "- Password: Retrieve from AWS Secrets Manager secret '$SECRET_NAME'" echo "" echo "To retrieve the password from Secrets Manager (without jq):" echo "aws secretsmanager get-secret-value --secret-id $SECRET_NAME --query 'SecretString' --output text | sed -n 's/.*\"password\":\"\([^\"]*\)\".*/\1/p'" echo "" # Clean up temporary files rm -f redshift-trust-policy.json redshift-s3-policy.json # Clean up resources cleanup_resources echo "Tutorial completed at $(date)"-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create a Redshift cluster
Create an IAM role for S3 access
Create tables and load data
Run example queries
Clean up resources
- AWS CLI with Bash script
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the Sample developer tutorials
repository. #!/bin/bash # Amazon Redshift Provisioned Cluster Tutorial Script # This script creates a Redshift cluster, loads sample data, runs queries, and cleans up resources # Version 3: Fixed IAM role usage in COPY commands # Set up logging LOG_FILE="redshift_tutorial.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting Amazon Redshift tutorial script at $(date)" echo "All commands and outputs will be logged to $LOG_FILE" # Function to handle errors handle_error() { echo "ERROR: $1" echo "Resources created so far:" if [ -n "$CLUSTER_ID" ]; then echo "- Redshift Cluster: $CLUSTER_ID"; fi if [ -n "$ROLE_NAME" ]; then echo "- IAM Role: $ROLE_NAME"; fi echo "Attempting to clean up resources..." cleanup_resources exit 1 } # Function to clean up resources cleanup_resources() { echo "Cleaning up resources..." # Delete the cluster if it exists if [ -n "$CLUSTER_ID" ]; then echo "Deleting Redshift cluster: $CLUSTER_ID" aws redshift delete-cluster --cluster-identifier "$CLUSTER_ID" --skip-final-cluster-snapshot echo "Waiting for cluster deletion to complete..." aws redshift wait cluster-deleted --cluster-identifier "$CLUSTER_ID" echo "Cluster deleted successfully." fi # Delete the IAM role if it exists if [ -n "$ROLE_NAME" ]; then echo "Removing IAM role policy..." aws iam delete-role-policy --role-name "$ROLE_NAME" --policy-name RedshiftS3Access || echo "Failed to delete role policy" echo "Deleting IAM role: $ROLE_NAME" aws iam delete-role --role-name "$ROLE_NAME" || echo "Failed to delete role" fi echo "Cleanup completed." } # Function to wait for SQL statement to complete wait_for_statement() { local statement_id=$1 local max_attempts=30 local attempt=1 local status="" echo "Waiting for statement $statement_id to complete..." while [ $attempt -le $max_attempts ]; do status=$(aws redshift-data describe-statement --id "$statement_id" --query 'Status' --output text) if [ "$status" == "FINISHED" ]; then echo "Statement completed successfully." return 0 elif [ "$status" == "FAILED" ]; then local error=$(aws redshift-data describe-statement --id "$statement_id" --query 'Error' --output text) echo "Statement failed with error: $error" return 1 elif [ "$status" == "ABORTED" ]; then echo "Statement was aborted." return 1 fi echo "Statement status: $status. Waiting... (Attempt $attempt/$max_attempts)" sleep 10 ((attempt++)) done echo "Timed out waiting for statement to complete." return 1 } # Function to check if IAM role is attached to cluster check_role_attached() { local role_arn=$1 local max_attempts=10 local attempt=1 echo "Checking if IAM role is attached to the cluster..." while [ $attempt -le $max_attempts ]; do local status=$(aws redshift describe-clusters \ --cluster-identifier "$CLUSTER_ID" \ --query "Clusters[0].IamRoles[?IamRoleArn=='$role_arn'].ApplyStatus" \ --output text) if [ "$status" == "in-sync" ]; then echo "IAM role is successfully attached to the cluster." return 0 fi echo "IAM role status: $status. Waiting... (Attempt $attempt/$max_attempts)" sleep 30 ((attempt++)) done echo "Timed out waiting for IAM role to be attached." return 1 } # Variables to track created resources CLUSTER_ID="examplecluster" ROLE_NAME="RedshiftS3Role-$(date +%s)" DB_NAME="dev" DB_USER="awsuser" DB_PASSWORD="Changeit1" # In production, use AWS Secrets Manager to generate and store passwords echo "=== Step 1: Creating Amazon Redshift Cluster ===" # Create the Redshift cluster echo "Creating Redshift cluster: $CLUSTER_ID" CLUSTER_RESULT=$(aws redshift create-cluster \ --cluster-identifier "$CLUSTER_ID" \ --node-type ra3.4xlarge \ --number-of-nodes 2 \ --master-username "$DB_USER" \ --master-user-password "$DB_PASSWORD" \ --db-name "$DB_NAME" \ --port 5439 2>&1) # Check for errors if echo "$CLUSTER_RESULT" | grep -i "error"; then handle_error "Failed to create Redshift cluster: $CLUSTER_RESULT" fi echo "$CLUSTER_RESULT" echo "Waiting for cluster to become available..." # Wait for the cluster to be available aws redshift wait cluster-available --cluster-identifier "$CLUSTER_ID" || handle_error "Timeout waiting for cluster to become available" # Get cluster status to confirm CLUSTER_STATUS=$(aws redshift describe-clusters \ --cluster-identifier "$CLUSTER_ID" \ --query 'Clusters[0].ClusterStatus' \ --output text) echo "Cluster status: $CLUSTER_STATUS" echo "=== Step 2: Creating IAM Role for S3 Access ===" # Create trust policy file echo "Creating trust policy for Redshift" cat > redshift-trust-policy.json << EOF { "Version":"2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "redshift.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF # Create IAM role echo "Creating IAM role: $ROLE_NAME" ROLE_RESULT=$(aws iam create-role \ --role-name "$ROLE_NAME" \ --assume-role-policy-document file://redshift-trust-policy.json 2>&1) # Check for errors if echo "$ROLE_RESULT" | grep -i "error"; then handle_error "Failed to create IAM role: $ROLE_RESULT" fi echo "$ROLE_RESULT" # Get the role ARN ROLE_ARN=$(aws iam get-role --role-name "$ROLE_NAME" --query 'Role.Arn' --output text) echo "Role ARN: $ROLE_ARN" # Create policy document for S3 access echo "Creating S3 access policy" cat > redshift-s3-policy.json << EOF { "Version":"2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::redshift-downloads", "arn:aws:s3:::redshift-downloads/*" ] } ] } EOF # Attach policy to role echo "Attaching S3 access policy to role" POLICY_RESULT=$(aws iam put-role-policy \ --role-name "$ROLE_NAME" \ --policy-name RedshiftS3Access \ --policy-document file://redshift-s3-policy.json 2>&1) # Check for errors if echo "$POLICY_RESULT" | grep -i "error"; then handle_error "Failed to attach policy to role: $POLICY_RESULT" fi echo "$POLICY_RESULT" # Attach role to cluster echo "Attaching IAM role to Redshift cluster" ATTACH_ROLE_RESULT=$(aws redshift modify-cluster-iam-roles \ --cluster-identifier "$CLUSTER_ID" \ --add-iam-roles "$ROLE_ARN" 2>&1) # Check for errors if echo "$ATTACH_ROLE_RESULT" | grep -i "error"; then handle_error "Failed to attach role to cluster: $ATTACH_ROLE_RESULT" fi echo "$ATTACH_ROLE_RESULT" # Wait for the role to be attached echo "Waiting for IAM role to be attached to the cluster..." if ! check_role_attached "$ROLE_ARN"; then handle_error "Failed to attach IAM role to cluster" fi echo "=== Step 3: Getting Cluster Connection Information ===" # Get cluster endpoint CLUSTER_INFO=$(aws redshift describe-clusters \ --cluster-identifier "$CLUSTER_ID" \ --query 'Clusters[0].Endpoint.{Address:Address,Port:Port}' \ --output json) echo "Cluster endpoint information:" echo "$CLUSTER_INFO" echo "=== Step 4: Creating Tables and Loading Data ===" echo "Creating sales table" SALES_TABLE_ID=$(aws redshift-data execute-statement \ --cluster-identifier "$CLUSTER_ID" \ --database "$DB_NAME" \ --db-user "$DB_USER" \ --sql "DROP TABLE IF EXISTS sales; CREATE TABLE sales(salesid integer not null, listid integer not null distkey, sellerid integer not null, buyerid integer not null, eventid integer not null, dateid smallint not null sortkey, qtysold smallint not null, pricepaid decimal(8,2), commission decimal(8,2), saletime timestamp);" \ --query 'Id' --output text) echo "Sales table creation statement ID: $SALES_TABLE_ID" # Wait for statement to complete if ! wait_for_statement "$SALES_TABLE_ID"; then handle_error "Failed to create sales table" fi echo "Creating date table" DATE_TABLE_ID=$(aws redshift-data execute-statement \ --cluster-identifier "$CLUSTER_ID" \ --database "$DB_NAME" \ --db-user "$DB_USER" \ --sql "DROP TABLE IF EXISTS date; CREATE TABLE date(dateid smallint not null distkey sortkey, caldate date not null, day character(3) not null, week smallint not null, month character(5) not null, qtr character(5) not null, year smallint not null, holiday boolean default('N'));" \ --query 'Id' --output text) echo "Date table creation statement ID: $DATE_TABLE_ID" # Wait for statement to complete if ! wait_for_statement "$DATE_TABLE_ID"; then handle_error "Failed to create date table" fi echo "Loading data into sales table" SALES_LOAD_ID=$(aws redshift-data execute-statement \ --cluster-identifier "$CLUSTER_ID" \ --database "$DB_NAME" \ --db-user "$DB_USER" \ --sql "COPY sales FROM 's3://redshift-downloads/tickit/sales_tab.txt' DELIMITER '\t' TIMEFORMAT 'MM/DD/YYYY HH:MI:SS' REGION 'us-east-1' IAM_ROLE '$ROLE_ARN';" \ --query 'Id' --output text) echo "Sales data load statement ID: $SALES_LOAD_ID" # Wait for statement to complete if ! wait_for_statement "$SALES_LOAD_ID"; then handle_error "Failed to load data into sales table" fi echo "Loading data into date table" DATE_LOAD_ID=$(aws redshift-data execute-statement \ --cluster-identifier "$CLUSTER_ID" \ --database "$DB_NAME" \ --db-user "$DB_USER" \ --sql "COPY date FROM 's3://redshift-downloads/tickit/date2008_pipe.txt' DELIMITER '|' REGION 'us-east-1' IAM_ROLE '$ROLE_ARN';" \ --query 'Id' --output text) echo "Date data load statement ID: $DATE_LOAD_ID" # Wait for statement to complete if ! wait_for_statement "$DATE_LOAD_ID"; then handle_error "Failed to load data into date table" fi echo "=== Step 5: Running Example Queries ===" echo "Running query: Get definition for the sales table" QUERY1_ID=$(aws redshift-data execute-statement \ --cluster-identifier "$CLUSTER_ID" \ --database "$DB_NAME" \ --db-user "$DB_USER" \ --sql "SELECT * FROM pg_table_def WHERE tablename = 'sales';" \ --query 'Id' --output text) echo "Query 1 statement ID: $QUERY1_ID" # Wait for statement to complete if ! wait_for_statement "$QUERY1_ID"; then handle_error "Query 1 failed" fi # Get and display results echo "Query 1 results (first 10 rows):" aws redshift-data get-statement-result --id "$QUERY1_ID" --max-items 10 echo "Running query: Find total sales on a given calendar date" QUERY2_ID=$(aws redshift-data execute-statement \ --cluster-identifier "$CLUSTER_ID" \ --database "$DB_NAME" \ --db-user "$DB_USER" \ --sql "SELECT sum(qtysold) FROM sales, date WHERE sales.dateid = date.dateid AND caldate = '2008-01-05';" \ --query 'Id' --output text) echo "Query 2 statement ID: $QUERY2_ID" # Wait for statement to complete if ! wait_for_statement "$QUERY2_ID"; then handle_error "Query 2 failed" fi # Get and display results echo "Query 2 results:" aws redshift-data get-statement-result --id "$QUERY2_ID" echo "=== Tutorial Complete ===" echo "The following resources were created:" echo "- Redshift Cluster: $CLUSTER_ID" echo "- IAM Role: $ROLE_NAME" echo "" echo "===========================================" echo "CLEANUP CONFIRMATION" echo "===========================================" echo "Do you want to clean up all created resources? (y/n): " read -r CLEANUP_CHOICE if [[ "$CLEANUP_CHOICE" =~ ^[Yy] ]]; then cleanup_resources echo "All resources have been cleaned up." else echo "Resources were not cleaned up. You can manually delete them later." echo "To avoid incurring charges, remember to delete the following resources:" echo "- Redshift Cluster: $CLUSTER_ID" echo "- IAM Role: $ROLE_NAME" fi echo "Script completed at $(date)"-
For API details, see the following topics in AWS CLI Command Reference.
-