Amazon EC2 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 EC2.
Basics are code examples that show you how to perform the essential operations within a service.
Actions are code excerpts from larger programs and must be run in context. While actions show you how to call individual service functions, you can see actions in context in their related scenarios.
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.
Basics
The following code example shows how to:
Create a key pair and security group.
Select an Amazon Machine Image (AMI) and compatible instance type, then create an instance.
Stop and restart the instance.
Associate an Elastic IP address with your instance.
Connect to your instance with SSH, then 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 AWS Code Examples Repository
. Run an interactive scenario at a command prompt.
############################################################################### # function get_started_with_ec2_instances # # Runs an interactive scenario that shows how to get started using EC2 instances. # # "EC2 access" permissions are needed to run this code. # # Returns: # 0 - If successful. # 1 - If an error occurred. ############################################################################### function get_started_with_ec2_instances() { # Requires version 4 for mapfile. local required_version=4.0 # Get the current Bash version # Check if BASH_VERSION is set local current_version if [[ -n "$BASH_VERSION" ]]; then # Convert BASH_VERSION to a number for comparison current_version=$BASH_VERSION else # Get the current Bash version using the bash command current_version=$(bash --version | head -n 1 | awk '{ print $4 }') fi # Convert version strings to numbers for comparison local required_version_num current_version_num required_version_num=$(echo "$required_version" | awk -F. '{ print ($1 * 10000) + ($2 * 100) + $3 }') current_version_num=$(echo "$current_version" | awk -F. '{ print ($1 * 10000) + ($2 * 100) + $3 }') # Compare versions if ((current_version_num < required_version_num)); then echo "Error: This script requires Bash version $required_version or higher." echo "Your current Bash version is number is $current_version." exit 1 fi { if [ "$EC2_OPERATIONS_SOURCED" != "True" ]; then source ./ec2_operations.sh fi } echo_repeat "*" 88 echo "Welcome to the Amazon Elastic Compute Cloud (Amazon EC2) get started with instances demo." echo_repeat "*" 88 echo echo "Let's create an RSA key pair that you can be use to securely connect to " echo "your EC2 instance." echo -n "Enter a unique name for your key: " get_input local key_name key_name=$get_input_result local temp_dir temp_dir=$(mktemp -d) local key_file_name="$temp_dir/${key_name}.pem" if ec2_create_keypair -n "${key_name}" -f "${key_file_name}"; then echo "Created a key pair $key_name and saved the private key to $key_file_name" echo else errecho "The key pair failed to create. This demo will exit." return 1 fi chmod 400 "${key_file_name}" if yes_no_input "Do you want to list some of your key pairs? (y/n) "; then local keys_and_fingerprints keys_and_fingerprints="$(ec2_describe_key_pairs)" && { local image_name_and_id while IFS=$'\n' read -r image_name_and_id; do local entries IFS=$'\t' read -ra entries <<<"$image_name_and_id" echo "Found rsa key ${entries[0]} with fingerprint:" echo " ${entries[1]}" done <<<"$keys_and_fingerprints" } fi echo_repeat "*" 88 echo_repeat "*" 88 echo "Let's create a security group to manage access to your instance." echo -n "Enter a unique name for your security group: " get_input local security_group_name security_group_name=$get_input_result local security_group_id security_group_id=$(ec2_create_security_group -n "$security_group_name" \ -d "Security group for EC2 instance") || { errecho "The security failed to create. This demo will exit." clean_up "$key_name" "$key_file_name" return 1 } echo "Security group created with ID $security_group_id" echo local public_ip public_ip=$(curl -s http://checkip.amazonaws.com) echo "Let's add a rule to allow SSH only from your current IP address." echo "Your public IP address is $public_ip" echo -n "press return to add this rule to your security group." get_input if ! ec2_authorize_security_group_ingress -g "$security_group_id" -i "$public_ip" -p tcp -f 22 -t 22; then errecho "The security group rules failed to update. This demo will exit." clean_up "$key_name" "$key_file_name" "$security_group_id" return 1 fi echo "Security group rules updated" local security_group_description security_group_description="$(ec2_describe_security_groups -g "${security_group_id}")" || { errecho "Failed to describe security groups. This demo will exit." clean_up "$key_name" "$key_file_name" "$security_group_id" return 1 } mapfile -t parameters <<<"$security_group_description" IFS=$'\t' read -ra entries <<<"${parameters[0]}" echo "Security group: ${entries[0]}" echo " ID: ${entries[1]}" echo " VPC: ${entries[2]}" echo "Inbound permissions:" IFS=$'\t' read -ra entries <<<"${parameters[1]}" echo " IpProtocol: ${entries[0]}" echo " FromPort: ${entries[1]}" echo " ToPort: ${entries[2]}" echo " CidrIp: ${parameters[2]}" local parameters parameters="$(ssm_get_parameters_by_path -p "/aws/service/ami-amazon-linux-latest")" || { errecho "Failed to get parameters. This demo will exit." clean_up "$key_name" "$key_file_name" "$security_group_id" return 1 } local image_ids="" mapfile -t parameters <<<"$parameters" for image_name_and_id in "${parameters[@]}"; do IFS=$'\t' read -ra values <<<"$image_name_and_id" if [[ "${values[0]}" == *"amzn2"* ]]; then image_ids+="${values[1]} " fi done local images images="$(ec2_describe_images -i "$image_ids")" || { errecho "Failed to describe images. This demo will exit." clean_up "$key_name" "$key_file_name" "$security_group_id" return 1 } new_line_and_tab_to_list "$images" local images=("${list_result[@]}") # Get the size of the array local images_count=${#images[@]} if ((images_count == 0)); then errecho "No images found. This demo will exit." clean_up "$key_name" "$key_file_name" "$security_group_id" return 1 fi echo_repeat "*" 88 echo_repeat "*" 88 echo "Let's create an instance from an Amazon Linux 2 AMI. Here are some options:" for ((i = 0; i < images_count; i += 3)); do echo "$(((i / 3) + 1)) - ${images[$i]}" done integer_input "Please enter the number of the AMI you want to use: " 1 "$((images_count / 3))" local choice=$get_input_result choice=$(((choice - 1) * 3)) echo "Great choice." echo local architecture=${images[$((choice + 1))]} local image_id=${images[$((choice + 2))]} echo "Here are some instance types that support the ${architecture} architecture of the image:" response="$(ec2_describe_instance_types -a "${architecture}" -t "*.micro,*.small")" || { errecho "Failed to describe instance types. This demo will exit." clean_up "$key_name" "$key_file_name" "$security_group_id" return 1 } local instance_types mapfile -t instance_types <<<"$response" # Get the size of the array local instance_types_count=${#instance_types[@]} echo "Here are some options:" for ((i = 0; i < instance_types_count; i++)); do echo "$((i + 1)) - ${instance_types[$i]}" done integer_input "Which one do you want to use? " 1 "${#instance_types[@]} " choice=$get_input_result local instance_type=${instance_types[$((choice - 1))]} echo "Another great choice." echo echo "Creating your instance and waiting for it to start..." local instance_id instance_id=$(ec2_run_instances -i "$image_id" -t "$instance_type" -k "$key_name" -s "$security_group_id") || { errecho "Failed to run instance. This demo will exit." clean_up "$key_name" "$key_file_name" "$security_group_id" return 1 } ec2_wait_for_instance_running -i "$instance_id" echo "Your instance is ready:" echo local instance_details instance_details="$(ec2_describe_instances -i "${instance_id}")" echo print_instance_details "${instance_details}" local public_ip public_ip=$(echo "${instance_details}" | awk '{print $6}') echo echo "You can use SSH to connect to your instance" echo "If the connection attempt times out, you might have to manually update the SSH ingress rule" echo "for your IP address in the AWS Management Console." connect_to_instance "$key_file_name" "$public_ip" echo -n "Press Enter when you're ready to continue the demo: " get_input echo_repeat "*" 88 echo_repeat "*" 88 echo "Let's stop and start your instance to see what changes." echo "Stopping your instance and waiting until it's stopped..." ec2_stop_instances -i "$instance_id" ec2_wait_for_instance_stopped -i "$instance_id" echo "Your instance is stopped. Restarting..." ec2_start_instances -i "$instance_id" ec2_wait_for_instance_running -i "$instance_id" echo "Your instance is running again." local instance_details instance_details="$(ec2_describe_instances -i "${instance_id}")" print_instance_details "${instance_details}" public_ip=$(echo "${instance_details}" | awk '{print $6}') echo "Every time your instance is restarted, its public IP address changes" connect_to_instance "$key_file_name" "$public_ip" echo -n "Press Enter when you're ready to continue the demo: " get_input echo_repeat "*" 88 echo_repeat "*" 88 echo "You can allocate an Elastic IP address and associate it with your instance" echo "to keep a consistent IP address even when your instance restarts." local result result=$(ec2_allocate_address -d vpc) || { errecho "Failed to allocate an address. This demo will exit." clean_up "$key_name" "$key_file_name" "$security_group_id" "$instance_id" return 1 } local elastic_ip allocation_id elastic_ip=$(echo "$result" | awk '{print $1}') allocation_id=$(echo "$result" | awk '{print $2}') echo "Allocated static Elastic IP address: $elastic_ip" local association_id association_id=$(ec2_associate_address -i "$instance_id" -a "$allocation_id") || { errecho "Failed to associate an address. This demo will exit." clean_up "$key_name" "$key_file_name" "$security_group_id" "$instance_id" "$allocation_id" return 1 } echo "Associated your Elastic IP with your instance." echo "You can now use SSH to connect to your instance by using the Elastic IP." connect_to_instance "$key_file_name" "$elastic_ip" echo -n "Press Enter when you're ready to continue the demo: " get_input echo_repeat "*" 88 echo_repeat "*" 88 echo "Let's stop and start your instance to see what changes." echo "Stopping your instance and waiting until it's stopped..." ec2_stop_instances -i "$instance_id" ec2_wait_for_instance_stopped -i "$instance_id" echo "Your instance is stopped. Restarting..." ec2_start_instances -i "$instance_id" ec2_wait_for_instance_running -i "$instance_id" echo "Your instance is running again." local instance_details instance_details="$(ec2_describe_instances -i "${instance_id}")" print_instance_details "${instance_details}" echo "Because you have associated an Elastic IP with your instance, you can" echo "connect by using a consistent IP address after the instance restarts." connect_to_instance "$key_file_name" "$elastic_ip" echo -n "Press Enter when you're ready to continue the demo: " get_input echo_repeat "*" 88 echo_repeat "*" 88 if yes_no_input "Do you want to delete the resources created in this demo: (y/n) "; then clean_up "$key_name" "$key_file_name" "$security_group_id" "$instance_id" \ "$allocation_id" "$association_id" else echo "The following resources were not deleted." echo "Key pair: $key_name" echo "Key file: $key_file_name" echo "Security group: $security_group_id" echo "Instance: $instance_id" echo "Elastic IP address: $elastic_ip" fi } ############################################################################### # function clean_up # # This function cleans up the created resources. # $1 - The name of the ec2 key pair to delete. # $2 - The name of the key file to delete. # $3 - The ID of the security group to delete. # $4 - The ID of the instance to terminate. # $5 - The ID of the elastic IP address to release. # $6 - The ID of the elastic IP address to disassociate. # # Returns: # 0 - If successful. # 1 - If an error occurred. ############################################################################### function clean_up() { local result=0 local key_pair_name=$1 local key_file_name=$2 local security_group_id=$3 local instance_id=$4 local allocation_id=$5 local association_id=$6 if [ -n "$association_id" ]; then # bashsupport disable=BP2002 if (ec2_disassociate_address -a "$association_id"); then echo "Disassociated elastic IP address with ID $association_id" else errecho "The elastic IP address disassociation failed." result=1 fi fi if [ -n "$allocation_id" ]; then # bashsupport disable=BP2002 if (ec2_release_address -a "$allocation_id"); then echo "Released elastic IP address with ID $allocation_id" else errecho "The elastic IP address release failed." result=1 fi fi if [ -n "$instance_id" ]; then # bashsupport disable=BP2002 if (ec2_terminate_instances -i "$instance_id"); then echo "Started terminating instance with ID $instance_id" ec2_wait_for_instance_terminated -i "$instance_id" else errecho "The instance terminate failed." result=1 fi fi if [ -n "$security_group_id" ]; then # bashsupport disable=BP2002 if (ec2_delete_security_group -i "$security_group_id"); then echo "Deleted security group with ID $security_group_id" else errecho "The security group delete failed." result=1 fi fi if [ -n "$key_pair_name" ]; then # bashsupport disable=BP2002 if (ec2_delete_keypair -n "$key_pair_name"); then echo "Deleted key pair named $key_pair_name" else errecho "The key pair delete failed." result=1 fi fi if [ -n "$key_file_name" ]; then rm -f "$key_file_name" fi return $result } ############################################################################### # function ssm_get_parameters_by_path # # This function retrieves one or more parameters from the AWS Systems Manager Parameter Store # by specifying a parameter path. # # Parameters: # -p parameter_path - The path of the parameter(s) to retrieve. # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ssm_get_parameters_by_path() { local parameter_path response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ssm_get_parameters_by_path" echo "Retrieves one or more parameters from the AWS Systems Manager Parameter Store by specifying a parameter path." echo " -p parameter_path - The path of the parameter(s) to retrieve." echo "" } # Retrieve the calling parameters. while getopts "p:h" option; do case "${option}" in p) parameter_path="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$parameter_path" ]]; then errecho "ERROR: You must provide a parameter path with the -p parameter." usage return 1 fi response=$(aws ssm get-parameters-by-path \ --path "$parameter_path" \ --query "Parameters[*].[Name, Value]" \ --output text) || { aws_cli_error_log $? errecho "ERROR: AWS reports get-parameters-by-path operation failed.$response" return 1 } echo "$response" return 0 } ############################################################################### # function print_instance_details # # This function prints the details of an Amazon Elastic Compute Cloud (Amazon EC2) instance. # # Parameters: # instance_details - The instance details in the format "InstanceId ImageId InstanceType KeyName VpcId PublicIpAddress State.Name". # # Returns: # 0 - If successful. # 1 - If it fails. ############################################################################### function print_instance_details() { local instance_details="$1" if [[ -z "${instance_details}" ]]; then echo "Error: Missing required instance details argument." return 1 fi local instance_id image_id instance_type key_name vpc_id public_ip state instance_id=$(echo "${instance_details}" | awk '{print $1}') image_id=$(echo "${instance_details}" | awk '{print $2}') instance_type=$(echo "${instance_details}" | awk '{print $3}') key_name=$(echo "${instance_details}" | awk '{print $4}') vpc_id=$(echo "${instance_details}" | awk '{print $5}') public_ip=$(echo "${instance_details}" | awk '{print $6}') state=$(echo "${instance_details}" | awk '{print $7}') echo " ID: ${instance_id}" echo " Image ID: ${image_id}" echo " Instance type: ${instance_type}" echo " Key name: ${key_name}" echo " VPC ID: ${vpc_id}" echo " Public IP: ${public_ip}" echo " State: ${state}" return 0 } ############################################################################### # function connect_to_instance # # This function displays the public IP address of an Amazon Elastic Compute Cloud (Amazon EC2) instance and prompts the user to connect to the instance via SSH. # # Parameters: # $1 - The name of the key file used to connect to the instance. # $2 - The public IP address of the instance. # # Returns: # None ############################################################################### function connect_to_instance() { local key_file_name="$1" local public_ip="$2" # Validate the input parameters if [[ -z "$key_file_name" ]]; then echo "ERROR: You must provide a key file name as the first argument." >&2 return 1 fi if [[ -z "$public_ip" ]]; then echo "ERROR: You must provide a public IP address as the second argument." >&2 return 1 fi # Display the public IP address and connection command echo "To connect, run the following command:" echo " ssh -i ${key_file_name} ec2-user@${public_ip}" # Prompt the user to connect to the instance if yes_no_input "Do you want to connect now? (y/n) "; then echo "After you have connected, you can return to this example by typing 'exit'" ssh -i "${key_file_name}" ec2-user@"${public_ip}" fi } ############################################################################### # function get_input # # This function gets user input from the command line. # # Outputs: # User input to stdout. # # Returns: # 0 ############################################################################### function get_input() { if [ -z "${mock_input+x}" ]; then read -r get_input_result else if [ "$mock_input_array_index" -lt ${#mock_input_array[@]} ]; then get_input_result="${mock_input_array[$mock_input_array_index]}" # bashsupport disable=BP2001 # shellcheck disable=SC2206 ((mock_input_array_index++)) echo -n "$get_input_result" else echo "MOCK_INPUT_ARRAY has no more elements" 1>&2 return 1 fi fi return 0 } ############################################################################### # function yes_no_input # # This function requests a yes/no answer from the user, following to a prompt. # # Parameters: # $1 - The prompt. # # Returns: # 0 - If yes. # 1 - If no. ############################################################################### function yes_no_input() { if [ -z "$1" ]; then echo "Internal error yes_no_input" return 1 fi local index=0 local response="N" while [[ $index -lt 10 ]]; do index=$((index + 1)) echo -n "$1" if ! get_input; then return 1 fi response=$(echo "$get_input_result" | tr '[:upper:]' '[:lower:]') if [ "$response" = "y" ] || [ "$response" = "n" ]; then break else echo -e "\nPlease enter or 'y' or 'n'." fi done echo if [ "$response" = "y" ]; then return 0 else return 1 fi } ############################################################################### # function integer_input # # This function prompts the user to enter an integer within a specified range # and validates the input. # # Parameters: # $1 - The prompt message to display to the user. # $2 - The minimum value of the accepted range. # $3 - The maximum value of the accepted range. # # Returns: # The valid integer input from the user. # If the input is invalid or out of range, the function will continue # prompting the user until a valid input is provided. ############################################################################### function integer_input() { local prompt="$1" local min_value="$2" local max_value="$3" local input="" while true; do # Display the prompt message and wait for user input echo -n "$prompt" if ! get_input; then return 1 fi input="$get_input_result" # Check if the input is a valid integer if [[ "$input" =~ ^-?[0-9]+$ ]]; then # Check if the input is within the specified range if ((input >= min_value && input <= max_value)); then return 0 else echo "Error: Input, $input, must be between $min_value and $max_value." fi else echo "Error: Invalid input- $input. Please enter an integer." fi done } ############################################################################### # function new_line_and_tab_to_list # # This function takes a string input containing newlines and tabs, and # converts it into a list (array) of elements. # # Parameters: # $1 - The input string containing newlines and tabs. # # Returns: # The resulting list (array) is stored in the global variable # 'list_result'. ############################################################################### function new_line_and_tab_to_list() { local input=$1 export list_result list_result=() mapfile -t lines <<<"$input" local line for line in "${lines[@]}"; do IFS=$'\t' read -ra parameters <<<"$line" list_result+=("${parameters[@]}") done } ############################################################################### # function echo_repeat # # This function prints a string 'n' times to stdout. # # Parameters: # $1 - The string. # $2 - Number of times to print the string. # # Outputs: # String 'n' times to stdout. # # Returns: # 0 ############################################################################### function echo_repeat() { local end=$2 for ((i = 0; i < end; i++)); do echo -n "$1" done echo }The DynamoDB functions used in this scenario.
############################################################################### # function ec2_create_keypair # # This function creates an Amazon Elastic Compute Cloud (Amazon EC2) ED25519 or 2048-bit RSA key pair # and writes it to a file. # # Parameters: # -n key_pair_name - A key pair name. # -f file_path - File to store the key pair. # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_create_keypair() { local key_pair_name file_path response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_create_keypair" echo "Creates an Amazon Elastic Compute Cloud (Amazon EC2) ED25519 or 2048-bit RSA key pair" echo " and writes it to a file." echo " -n key_pair_name - A key pair name." echo " -f file_path - File to store the key pair." echo "" } # Retrieve the calling parameters. while getopts "n:f:h" option; do case "${option}" in n) key_pair_name="${OPTARG}" ;; f) file_path="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$key_pair_name" ]]; then errecho "ERROR: You must provide a key name with the -n parameter." usage return 1 fi if [[ -z "$file_path" ]]; then errecho "ERROR: You must provide a file path with the -f parameter." usage return 1 fi response=$(aws ec2 create-key-pair \ --key-name "$key_pair_name" \ --query 'KeyMaterial' \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports create-access-key operation failed.$response" return 1 } if [[ -n "$file_path" ]]; then echo "$response" >"$file_path" fi return 0 } ############################################################################### # function ec2_describe_key_pairs # # This function describes one or more Amazon Elastic Compute Cloud (Amazon EC2) key pairs. # # Parameters: # -h - Display help. # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_describe_key_pairs() { local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_describe_key_pairs" echo "Describes one or more Amazon Elastic Compute Cloud (Amazon EC2) key pairs." echo " -h - Display help." echo "" } # Retrieve the calling parameters. while getopts "h" option; do case "${option}" in h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 local response response=$(aws ec2 describe-key-pairs \ --query 'KeyPairs[*].[KeyName, KeyFingerprint]' \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports describe-key-pairs operation failed.$response" return 1 } echo "$response" return 0 } ############################################################################### # function ec2_create_security_group # # This function creates an Amazon Elastic Compute Cloud (Amazon EC2) security group. # # Parameters: # -n security_group_name - The name of the security group. # -d security_group_description - The description of the security group. # # Returns: # The ID of the created security group, or an error message if the operation fails. # And: # 0 - If successful. # 1 - If it fails. # ############################################################################### function ec2_create_security_group() { local security_group_name security_group_description response # Function to display usage information function usage() { echo "function ec2_create_security_group" echo "Creates an Amazon Elastic Compute Cloud (Amazon EC2) security group." echo " -n security_group_name - The name of the security group." echo " -d security_group_description - The description of the security group." echo "" } # Parse the command-line arguments while getopts "n:d:h" option; do case "${option}" in n) security_group_name="${OPTARG}" ;; d) security_group_description="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 # Validate the input parameters if [[ -z "$security_group_name" ]]; then errecho "ERROR: You must provide a security group name with the -n parameter." return 1 fi if [[ -z "$security_group_description" ]]; then errecho "ERROR: You must provide a security group description with the -d parameter." return 1 fi # Create the security group response=$(aws ec2 create-security-group \ --group-name "$security_group_name" \ --description "$security_group_description" \ --query "GroupId" \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports create-security-group operation failed." errecho "$response" return 1 } echo "$response" return 0 } ############################################################################### # function ec2_describe_security_groups # # This function describes one or more Amazon Elastic Compute Cloud (Amazon EC2) security groups. # # Parameters: # -g security_group_id - The ID of the security group to describe (optional). # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_describe_security_groups() { local security_group_id response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_describe_security_groups" echo "Describes one or more Amazon Elastic Compute Cloud (Amazon EC2) security groups." echo " -g security_group_id - The ID of the security group to describe (optional)." echo "" } # Retrieve the calling parameters. while getopts "g:h" option; do case "${option}" in g) security_group_id="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 local query="SecurityGroups[*].[GroupName, GroupId, VpcId, IpPermissions[*].[IpProtocol, FromPort, ToPort, IpRanges[*].CidrIp]]" if [[ -n "$security_group_id" ]]; then response=$(aws ec2 describe-security-groups --group-ids "$security_group_id" --query "${query}" --output text) else response=$(aws ec2 describe-security-groups --query "${query}" --output text) fi local error_code=${?} if [[ $error_code -ne 0 ]]; then aws_cli_error_log $error_code errecho "ERROR: AWS reports describe-security-groups operation failed.$response" return 1 fi echo "$response" return 0 } ############################################################################### # function ec2_authorize_security_group_ingress # # This function authorizes an ingress rule for an Amazon Elastic Compute Cloud (Amazon EC2) security group. # # Parameters: # -g security_group_id - The ID of the security group. # -i ip_address - The IP address or CIDR block to authorize. # -p protocol - The protocol to authorize (e.g., tcp, udp, icmp). # -f from_port - The start of the port range to authorize. # -t to_port - The end of the port range to authorize. # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_authorize_security_group_ingress() { local security_group_id ip_address protocol from_port to_port response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_authorize_security_group_ingress" echo "Authorizes an ingress rule for an Amazon Elastic Compute Cloud (Amazon EC2) security group." echo " -g security_group_id - The ID of the security group." echo " -i ip_address - The IP address or CIDR block to authorize." echo " -p protocol - The protocol to authorize (e.g., tcp, udp, icmp)." echo " -f from_port - The start of the port range to authorize." echo " -t to_port - The end of the port range to authorize." echo "" } # Retrieve the calling parameters. while getopts "g:i:p:f:t:h" option; do case "${option}" in g) security_group_id="${OPTARG}" ;; i) ip_address="${OPTARG}" ;; p) protocol="${OPTARG}" ;; f) from_port="${OPTARG}" ;; t) to_port="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$security_group_id" ]]; then errecho "ERROR: You must provide a security group ID with the -g parameter." usage return 1 fi if [[ -z "$ip_address" ]]; then errecho "ERROR: You must provide an IP address or CIDR block with the -i parameter." usage return 1 fi if [[ -z "$protocol" ]]; then errecho "ERROR: You must provide a protocol with the -p parameter." usage return 1 fi if [[ -z "$from_port" ]]; then errecho "ERROR: You must provide a start port with the -f parameter." usage return 1 fi if [[ -z "$to_port" ]]; then errecho "ERROR: You must provide an end port with the -t parameter." usage return 1 fi response=$(aws ec2 authorize-security-group-ingress \ --group-id "$security_group_id" \ --cidr "${ip_address}/32" \ --protocol "$protocol" \ --port "$from_port-$to_port" \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports authorize-security-group-ingress operation failed.$response" return 1 } return 0 } ############################################################################### # function ec2_describe_images # # This function describes one or more Amazon Elastic Compute Cloud (Amazon EC2) images. # # Parameters: # -i image_ids - A space-separated list of image IDs (optional). # -h - Display help. # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_describe_images() { local image_ids response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_describe_images" echo "Describes one or more Amazon Elastic Compute Cloud (Amazon EC2) images." echo " -i image_ids - A space-separated list of image IDs (optional)." echo " -h - Display help." echo "" } # Retrieve the calling parameters. while getopts "i:h" option; do case "${option}" in i) image_ids="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 local aws_cli_args=() if [[ -n "$image_ids" ]]; then # shellcheck disable=SC2206 aws_cli_args+=("--image-ids" $image_ids) fi response=$(aws ec2 describe-images \ "${aws_cli_args[@]}" \ --query 'Images[*].[Description,Architecture,ImageId]' \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports describe-images operation failed.$response" return 1 } echo "$response" return 0 } ############################################################################### # ec2_describe_instance_types # # This function describes EC2 instance types filtered by processor architecture # and optionally by instance type. It takes the following arguments: # # -a, --architecture ARCHITECTURE Specify the processor architecture (e.g., x86_64) # -t, --type INSTANCE_TYPE Comma-separated list of instance types (e.g., t2.micro) # -h, --help Show the usage help # # The function prints the instance type and supported architecture for each # matching instance type. ############################################################################### function ec2_describe_instance_types() { local architecture="" local instance_types="" # bashsupport disable=BP5008 function usage() { echo "Usage: ec2_describe_instance_types [-a|--architecture ARCHITECTURE] [-t|--type INSTANCE_TYPE] [-h|--help]" echo " -a, --architecture ARCHITECTURE Specify the processor architecture (e.g., x86_64)" echo " -t, --type INSTANCE_TYPE Comma-separated list of instance types (e.g., t2.micro)" echo " -h, --help Show this help message" } while [[ $# -gt 0 ]]; do case "$1" in -a | --architecture) architecture="$2" shift 2 ;; -t | --type) instance_types="$2" shift 2 ;; -h | --help) usage return 0 ;; *) echo "Unknown argument: $1" return 1 ;; esac done if [[ -z "$architecture" ]]; then errecho "Error: Architecture not specified." usage return 1 fi if [[ -z "$instance_types" ]]; then errecho "Error: Instance type not specified." usage return 1 fi local tmp_json_file="temp_ec2.json" echo -n '[ { "Name": "processor-info.supported-architecture", "Values": [' >"$tmp_json_file" local items IFS=',' read -ra items <<<"$architecture" local array_size array_size=${#items[@]} for i in $(seq 0 $((array_size - 1))); do echo -n '"'"${items[$i]}"'"' >>"$tmp_json_file" if [[ $i -lt $((array_size - 1)) ]]; then echo -n ',' >>"$tmp_json_file" fi done echo -n ']}, { "Name": "instance-type", "Values": [' >>"$tmp_json_file" IFS=',' read -ra items <<<"$instance_types" local array_size array_size=${#items[@]} for i in $(seq 0 $((array_size - 1))); do echo -n '"'"${items[$i]}"'"' >>"$tmp_json_file" if [[ $i -lt $((array_size - 1)) ]]; then echo -n ',' >>"$tmp_json_file" fi done echo -n ']}]' >>"$tmp_json_file" local response response=$(aws ec2 describe-instance-types --filters file://"$tmp_json_file" \ --query 'InstanceTypes[*].[InstanceType]' --output text) local error_code=$? rm "$tmp_json_file" if [[ $error_code -ne 0 ]]; then aws_cli_error_log $error_code echo "ERROR: AWS reports describe-instance-types operation failed." return 1 fi echo "$response" return 0 } ############################################################################### # function ec2_run_instances # # This function launches one or more Amazon Elastic Compute Cloud (Amazon EC2) instances. # # Parameters: # -i image_id - The ID of the Amazon Machine Image (AMI) to use. # -t instance_type - The instance type to use (e.g., t2.micro). # -k key_pair_name - The name of the key pair to use. # -s security_group_id - The ID of the security group to use. # -c count - The number of instances to launch (default: 1). # -h - Display help. # # Returns: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_run_instances() { local image_id instance_type key_pair_name security_group_id count response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_run_instances" echo "Launches one or more Amazon Elastic Compute Cloud (Amazon EC2) instances." echo " -i image_id - The ID of the Amazon Machine Image (AMI) to use." echo " -t instance_type - The instance type to use (e.g., t2.micro)." echo " -k key_pair_name - The name of the key pair to use." echo " -s security_group_id - The ID of the security group to use." echo " -c count - The number of instances to launch (default: 1)." echo " -h - Display help." echo "" } # Retrieve the calling parameters. while getopts "i:t:k:s:c:h" option; do case "${option}" in i) image_id="${OPTARG}" ;; t) instance_type="${OPTARG}" ;; k) key_pair_name="${OPTARG}" ;; s) security_group_id="${OPTARG}" ;; c) count="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$image_id" ]]; then errecho "ERROR: You must provide an Amazon Machine Image (AMI) ID with the -i parameter." usage return 1 fi if [[ -z "$instance_type" ]]; then errecho "ERROR: You must provide an instance type with the -t parameter." usage return 1 fi if [[ -z "$key_pair_name" ]]; then errecho "ERROR: You must provide a key pair name with the -k parameter." usage return 1 fi if [[ -z "$security_group_id" ]]; then errecho "ERROR: You must provide a security group ID with the -s parameter." usage return 1 fi if [[ -z "$count" ]]; then count=1 fi response=$(aws ec2 run-instances \ --image-id "$image_id" \ --instance-type "$instance_type" \ --key-name "$key_pair_name" \ --security-group-ids "$security_group_id" \ --count "$count" \ --query 'Instances[*].[InstanceId]' \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports run-instances operation failed.$response" return 1 } echo "$response" return 0 } ############################################################################### # function ec2_describe_instances # # This function describes one or more Amazon Elastic Compute Cloud (Amazon EC2) instances. # # Parameters: # -i instance_id - The ID of the instance to describe (optional). # -q query - The query to filter the response (optional). # -h - Display help. # # Returns: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_describe_instances() { local instance_id query response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_describe_instances" echo "Describes one or more Amazon Elastic Compute Cloud (Amazon EC2) instances." echo " -i instance_id - The ID of the instance to describe (optional)." echo " -q query - The query to filter the response (optional)." echo " -h - Display help." echo "" } # Retrieve the calling parameters. while getopts "i:q:h" option; do case "${option}" in i) instance_id="${OPTARG}" ;; q) query="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 local aws_cli_args=() if [[ -n "$instance_id" ]]; then # shellcheck disable=SC2206 aws_cli_args+=("--instance-ids" $instance_id) fi local query_arg="" if [[ -n "$query" ]]; then query_arg="--query '$query'" else query_arg="--query Reservations[*].Instances[*].[InstanceId,ImageId,InstanceType,KeyName,VpcId,PublicIpAddress,State.Name]" fi # shellcheck disable=SC2086 response=$(aws ec2 describe-instances \ "${aws_cli_args[@]}" \ $query_arg \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports describe-instances operation failed.$response" return 1 } echo "$response" return 0 } ############################################################################### # function ec2_stop_instances # # This function stops one or more Amazon Elastic Compute Cloud (Amazon EC2) instances. # # Parameters: # -i instance_id - The ID(s) of the instance(s) to stop (comma-separated). # -h - Display help. # # Returns: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_stop_instances() { local instance_ids local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_stop_instances" echo "Stops one or more Amazon Elastic Compute Cloud (Amazon EC2) instances." echo " -i instance_id - The ID(s) of the instance(s) to stop (comma-separated)." echo " -h - Display help." echo "" } # Retrieve the calling parameters. while getopts "i:h" option; do case "${option}" in i) instance_ids="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$instance_ids" ]]; then errecho "ERROR: You must provide one or more instance IDs with the -i parameter." usage return 1 fi response=$(aws ec2 stop-instances \ --instance-ids "${instance_ids}") || { aws_cli_error_log ${?} errecho "ERROR: AWS reports stop-instances operation failed with $response." return 1 } return 0 } ############################################################################### # function ec2_start_instances # # This function starts one or more Amazon Elastic Compute Cloud (Amazon EC2) instances. # # Parameters: # -i instance_id - The ID(s) of the instance(s) to start (comma-separated). # -h - Display help. # # Returns: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_start_instances() { local instance_ids local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_start_instances" echo "Starts one or more Amazon Elastic Compute Cloud (Amazon EC2) instances." echo " -i instance_id - The ID(s) of the instance(s) to start (comma-separated)." echo " -h - Display help." echo "" } # Retrieve the calling parameters. while getopts "i:h" option; do case "${option}" in i) instance_ids="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$instance_ids" ]]; then errecho "ERROR: You must provide one or more instance IDs with the -i parameter." usage return 1 fi response=$(aws ec2 start-instances \ --instance-ids "${instance_ids}") || { aws_cli_error_log ${?} errecho "ERROR: AWS reports start-instances operation failed with $response." return 1 } return 0 } ############################################################################### # function ec2_allocate_address # # This function allocates an Elastic IP address for use with Amazon Elastic Compute Cloud (Amazon EC2) instances in a specific AWS Region. # # Parameters: # -d domain - The domain for the Elastic IP address (either 'vpc' or 'standard'). # # Returns: # The allocated Elastic IP address, or an error message if the operation fails. # And: # 0 - If successful. # 1 - If it fails. # ############################################################################### function ec2_allocate_address() { local domain response # Function to display usage information function usage() { echo "function ec2_allocate_address" echo "Allocates an Elastic IP address for use with Amazon Elastic Compute Cloud (Amazon EC2) instances in a specific AWS Region." echo " -d domain - The domain for the Elastic IP address (either 'vpc' or 'standard')." echo "" } # Parse the command-line arguments while getopts "d:h" option; do case "${option}" in d) domain="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 # Validate the input parameters if [[ -z "$domain" ]]; then errecho "ERROR: You must provide a domain with the -d parameter (either 'vpc' or 'standard')." return 1 fi if [[ "$domain" != "vpc" && "$domain" != "standard" ]]; then errecho "ERROR: Invalid domain value. Must be either 'vpc' or 'standard'." return 1 fi # Allocate the Elastic IP address response=$(aws ec2 allocate-address \ --domain "$domain" \ --query "[PublicIp,AllocationId]" \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports allocate-address operation failed." errecho "$response" return 1 } echo "$response" return 0 } ############################################################################### # function ec2_associate_address # # This function associates an Elastic IP address with an Amazon Elastic Compute Cloud (Amazon EC2) instance. # # Parameters: # -a allocation_id - The allocation ID of the Elastic IP address to associate. # -i instance_id - The ID of the EC2 instance to associate the Elastic IP address with. # # Returns: # 0 - If successful. # 1 - If it fails. # ############################################################################### function ec2_associate_address() { local allocation_id instance_id response # Function to display usage information function usage() { echo "function ec2_associate_address" echo "Associates an Elastic IP address with an Amazon Elastic Compute Cloud (Amazon EC2) instance." echo " -a allocation_id - The allocation ID of the Elastic IP address to associate." echo " -i instance_id - The ID of the EC2 instance to associate the Elastic IP address with." echo "" } # Parse the command-line arguments while getopts "a:i:h" option; do case "${option}" in a) allocation_id="${OPTARG}" ;; i) instance_id="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 # Validate the input parameters if [[ -z "$allocation_id" ]]; then errecho "ERROR: You must provide an allocation ID with the -a parameter." return 1 fi if [[ -z "$instance_id" ]]; then errecho "ERROR: You must provide an instance ID with the -i parameter." return 1 fi # Associate the Elastic IP address response=$(aws ec2 associate-address \ --allocation-id "$allocation_id" \ --instance-id "$instance_id" \ --query "AssociationId" \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports associate-address operation failed." errecho "$response" return 1 } echo "$response" return 0 } ############################################################################### # function ec2_disassociate_address # # This function disassociates an Elastic IP address from an Amazon Elastic Compute Cloud (Amazon EC2) instance. # # Parameters: # -a association_id - The association ID that represents the association of the Elastic IP address with an instance. # # And: # 0 - If successful. # 1 - If it fails. # ############################################################################### function ec2_disassociate_address() { local association_id response # Function to display usage information function usage() { echo "function ec2_disassociate_address" echo "Disassociates an Elastic IP address from an Amazon Elastic Compute Cloud (Amazon EC2) instance." echo " -a association_id - The association ID that represents the association of the Elastic IP address with an instance." echo "" } # Parse the command-line arguments while getopts "a:h" option; do case "${option}" in a) association_id="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 # Validate the input parameters if [[ -z "$association_id" ]]; then errecho "ERROR: You must provide an association ID with the -a parameter." return 1 fi response=$(aws ec2 disassociate-address \ --association-id "$association_id") || { aws_cli_error_log ${?} errecho "ERROR: AWS reports disassociate-address operation failed." errecho "$response" return 1 } return 0 } ############################################################################### # function ec2_release_address # # This function releases an Elastic IP address from an Amazon Elastic Compute Cloud (Amazon EC2) instance. # # Parameters: # -a allocation_id - The allocation ID of the Elastic IP address to release. # # Returns: # 0 - If successful. # 1 - If it fails. # ############################################################################### function ec2_release_address() { local allocation_id response # Function to display usage information function usage() { echo "function ec2_release_address" echo "Releases an Elastic IP address from an Amazon Elastic Compute Cloud (Amazon EC2) instance." echo " -a allocation_id - The allocation ID of the Elastic IP address to release." echo "" } # Parse the command-line arguments while getopts "a:h" option; do case "${option}" in a) allocation_id="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 # Validate the input parameters if [[ -z "$allocation_id" ]]; then errecho "ERROR: You must provide an allocation ID with the -a parameter." return 1 fi response=$(aws ec2 release-address \ --allocation-id "$allocation_id") || { aws_cli_error_log ${?} errecho "ERROR: AWS reports release-address operation failed." errecho "$response" return 1 } return 0 } ############################################################################### # function ec2_terminate_instances # # This function terminates one or more Amazon Elastic Compute Cloud (Amazon EC2) # instances using the AWS CLI. # # Parameters: # -i instance_ids - A space-separated list of instance IDs. # -h - Display help. # # Returns: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_terminate_instances() { local instance_ids response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_terminate_instances" echo "Terminates one or more Amazon Elastic Compute Cloud (Amazon EC2) instances." echo " -i instance_ids - A space-separated list of instance IDs." echo " -h - Display help." echo "" } # Retrieve the calling parameters. while getopts "i:h" option; do case "${option}" in i) instance_ids="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 # Check if instance ID is provided if [[ -z "${instance_ids}" ]]; then echo "Error: Missing required instance IDs parameter." usage return 1 fi # shellcheck disable=SC2086 response=$(aws ec2 terminate-instances \ "--instance-ids" $instance_ids \ --query 'TerminatingInstances[*].[InstanceId,CurrentState.Name]' \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports terminate-instances operation failed.$response" return 1 } return 0 } ############################################################################### # function ec2_delete_security_group # # This function deletes an Amazon Elastic Compute Cloud (Amazon EC2) security group. # # Parameters: # -i security_group_id - The ID of the security group to delete. # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_delete_security_group() { local security_group_id response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_delete_security_group" echo "Deletes an Amazon Elastic Compute Cloud (Amazon EC2) security group." echo " -i security_group_id - The ID of the security group to delete." echo "" } # Retrieve the calling parameters. while getopts "i:h" option; do case "${option}" in i) security_group_id="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$security_group_id" ]]; then errecho "ERROR: You must provide a security group ID with the -i parameter." usage return 1 fi response=$(aws ec2 delete-security-group --group-id "$security_group_id" --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports delete-security-group operation failed.$response" return 1 } return 0 } ############################################################################### # function ec2_delete_keypair # # This function deletes an Amazon EC2 ED25519 or 2048-bit RSA key pair. # # Parameters: # -n key_pair_name - A key pair name. # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_delete_keypair() { local key_pair_name response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_delete_keypair" echo "Deletes an Amazon EC2 ED25519 or 2048-bit RSA key pair." echo " -n key_pair_name - A key pair name." echo "" } # Retrieve the calling parameters. while getopts "n:h" option; do case "${option}" in n) key_pair_name="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$key_pair_name" ]]; then errecho "ERROR: You must provide a key pair name with the -n parameter." usage return 1 fi response=$(aws ec2 delete-key-pair \ --key-name "$key_pair_name") || { aws_cli_error_log ${?} errecho "ERROR: AWS reports delete-key-pair operation failed.$response" return 1 } return 0 }The utility functions used in this scenario.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see the following topics in AWS CLI Command Reference.
-
Actions
The following code example shows how to use AllocateAddress.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_allocate_address # # This function allocates an Elastic IP address for use with Amazon Elastic Compute Cloud (Amazon EC2) instances in a specific AWS Region. # # Parameters: # -d domain - The domain for the Elastic IP address (either 'vpc' or 'standard'). # # Returns: # The allocated Elastic IP address, or an error message if the operation fails. # And: # 0 - If successful. # 1 - If it fails. # ############################################################################### function ec2_allocate_address() { local domain response # Function to display usage information function usage() { echo "function ec2_allocate_address" echo "Allocates an Elastic IP address for use with Amazon Elastic Compute Cloud (Amazon EC2) instances in a specific AWS Region." echo " -d domain - The domain for the Elastic IP address (either 'vpc' or 'standard')." echo "" } # Parse the command-line arguments while getopts "d:h" option; do case "${option}" in d) domain="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 # Validate the input parameters if [[ -z "$domain" ]]; then errecho "ERROR: You must provide a domain with the -d parameter (either 'vpc' or 'standard')." return 1 fi if [[ "$domain" != "vpc" && "$domain" != "standard" ]]; then errecho "ERROR: Invalid domain value. Must be either 'vpc' or 'standard'." return 1 fi # Allocate the Elastic IP address response=$(aws ec2 allocate-address \ --domain "$domain" \ --query "[PublicIp,AllocationId]" \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports allocate-address operation failed." errecho "$response" return 1 } echo "$response" return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see AllocateAddress in AWS CLI Command Reference.
-
The following code example shows how to use AssociateAddress.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_associate_address # # This function associates an Elastic IP address with an Amazon Elastic Compute Cloud (Amazon EC2) instance. # # Parameters: # -a allocation_id - The allocation ID of the Elastic IP address to associate. # -i instance_id - The ID of the EC2 instance to associate the Elastic IP address with. # # Returns: # 0 - If successful. # 1 - If it fails. # ############################################################################### function ec2_associate_address() { local allocation_id instance_id response # Function to display usage information function usage() { echo "function ec2_associate_address" echo "Associates an Elastic IP address with an Amazon Elastic Compute Cloud (Amazon EC2) instance." echo " -a allocation_id - The allocation ID of the Elastic IP address to associate." echo " -i instance_id - The ID of the EC2 instance to associate the Elastic IP address with." echo "" } # Parse the command-line arguments while getopts "a:i:h" option; do case "${option}" in a) allocation_id="${OPTARG}" ;; i) instance_id="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 # Validate the input parameters if [[ -z "$allocation_id" ]]; then errecho "ERROR: You must provide an allocation ID with the -a parameter." return 1 fi if [[ -z "$instance_id" ]]; then errecho "ERROR: You must provide an instance ID with the -i parameter." return 1 fi # Associate the Elastic IP address response=$(aws ec2 associate-address \ --allocation-id "$allocation_id" \ --instance-id "$instance_id" \ --query "AssociationId" \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports associate-address operation failed." errecho "$response" return 1 } echo "$response" return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see AssociateAddress in AWS CLI Command Reference.
-
The following code example shows how to use AuthorizeSecurityGroupIngress.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_authorize_security_group_ingress # # This function authorizes an ingress rule for an Amazon Elastic Compute Cloud (Amazon EC2) security group. # # Parameters: # -g security_group_id - The ID of the security group. # -i ip_address - The IP address or CIDR block to authorize. # -p protocol - The protocol to authorize (e.g., tcp, udp, icmp). # -f from_port - The start of the port range to authorize. # -t to_port - The end of the port range to authorize. # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_authorize_security_group_ingress() { local security_group_id ip_address protocol from_port to_port response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_authorize_security_group_ingress" echo "Authorizes an ingress rule for an Amazon Elastic Compute Cloud (Amazon EC2) security group." echo " -g security_group_id - The ID of the security group." echo " -i ip_address - The IP address or CIDR block to authorize." echo " -p protocol - The protocol to authorize (e.g., tcp, udp, icmp)." echo " -f from_port - The start of the port range to authorize." echo " -t to_port - The end of the port range to authorize." echo "" } # Retrieve the calling parameters. while getopts "g:i:p:f:t:h" option; do case "${option}" in g) security_group_id="${OPTARG}" ;; i) ip_address="${OPTARG}" ;; p) protocol="${OPTARG}" ;; f) from_port="${OPTARG}" ;; t) to_port="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$security_group_id" ]]; then errecho "ERROR: You must provide a security group ID with the -g parameter." usage return 1 fi if [[ -z "$ip_address" ]]; then errecho "ERROR: You must provide an IP address or CIDR block with the -i parameter." usage return 1 fi if [[ -z "$protocol" ]]; then errecho "ERROR: You must provide a protocol with the -p parameter." usage return 1 fi if [[ -z "$from_port" ]]; then errecho "ERROR: You must provide a start port with the -f parameter." usage return 1 fi if [[ -z "$to_port" ]]; then errecho "ERROR: You must provide an end port with the -t parameter." usage return 1 fi response=$(aws ec2 authorize-security-group-ingress \ --group-id "$security_group_id" \ --cidr "${ip_address}/32" \ --protocol "$protocol" \ --port "$from_port-$to_port" \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports authorize-security-group-ingress operation failed.$response" return 1 } return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see AuthorizeSecurityGroupIngress in AWS CLI Command Reference.
-
The following code example shows how to use CreateKeyPair.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_create_keypair # # This function creates an Amazon Elastic Compute Cloud (Amazon EC2) ED25519 or 2048-bit RSA key pair # and writes it to a file. # # Parameters: # -n key_pair_name - A key pair name. # -f file_path - File to store the key pair. # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_create_keypair() { local key_pair_name file_path response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_create_keypair" echo "Creates an Amazon Elastic Compute Cloud (Amazon EC2) ED25519 or 2048-bit RSA key pair" echo " and writes it to a file." echo " -n key_pair_name - A key pair name." echo " -f file_path - File to store the key pair." echo "" } # Retrieve the calling parameters. while getopts "n:f:h" option; do case "${option}" in n) key_pair_name="${OPTARG}" ;; f) file_path="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$key_pair_name" ]]; then errecho "ERROR: You must provide a key name with the -n parameter." usage return 1 fi if [[ -z "$file_path" ]]; then errecho "ERROR: You must provide a file path with the -f parameter." usage return 1 fi response=$(aws ec2 create-key-pair \ --key-name "$key_pair_name" \ --query 'KeyMaterial' \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports create-access-key operation failed.$response" return 1 } if [[ -n "$file_path" ]]; then echo "$response" >"$file_path" fi return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see CreateKeyPair in AWS CLI Command Reference.
-
The following code example shows how to use CreateSecurityGroup.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_create_security_group # # This function creates an Amazon Elastic Compute Cloud (Amazon EC2) security group. # # Parameters: # -n security_group_name - The name of the security group. # -d security_group_description - The description of the security group. # # Returns: # The ID of the created security group, or an error message if the operation fails. # And: # 0 - If successful. # 1 - If it fails. # ############################################################################### function ec2_create_security_group() { local security_group_name security_group_description response # Function to display usage information function usage() { echo "function ec2_create_security_group" echo "Creates an Amazon Elastic Compute Cloud (Amazon EC2) security group." echo " -n security_group_name - The name of the security group." echo " -d security_group_description - The description of the security group." echo "" } # Parse the command-line arguments while getopts "n:d:h" option; do case "${option}" in n) security_group_name="${OPTARG}" ;; d) security_group_description="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 # Validate the input parameters if [[ -z "$security_group_name" ]]; then errecho "ERROR: You must provide a security group name with the -n parameter." return 1 fi if [[ -z "$security_group_description" ]]; then errecho "ERROR: You must provide a security group description with the -d parameter." return 1 fi # Create the security group response=$(aws ec2 create-security-group \ --group-name "$security_group_name" \ --description "$security_group_description" \ --query "GroupId" \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports create-security-group operation failed." errecho "$response" return 1 } echo "$response" return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see CreateSecurityGroup in AWS CLI Command Reference.
-
The following code example shows how to use DeleteKeyPair.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_delete_keypair # # This function deletes an Amazon EC2 ED25519 or 2048-bit RSA key pair. # # Parameters: # -n key_pair_name - A key pair name. # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_delete_keypair() { local key_pair_name response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_delete_keypair" echo "Deletes an Amazon EC2 ED25519 or 2048-bit RSA key pair." echo " -n key_pair_name - A key pair name." echo "" } # Retrieve the calling parameters. while getopts "n:h" option; do case "${option}" in n) key_pair_name="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$key_pair_name" ]]; then errecho "ERROR: You must provide a key pair name with the -n parameter." usage return 1 fi response=$(aws ec2 delete-key-pair \ --key-name "$key_pair_name") || { aws_cli_error_log ${?} errecho "ERROR: AWS reports delete-key-pair operation failed.$response" return 1 } return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see DeleteKeyPair in AWS CLI Command Reference.
-
The following code example shows how to use DeleteSecurityGroup.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_delete_security_group # # This function deletes an Amazon Elastic Compute Cloud (Amazon EC2) security group. # # Parameters: # -i security_group_id - The ID of the security group to delete. # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_delete_security_group() { local security_group_id response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_delete_security_group" echo "Deletes an Amazon Elastic Compute Cloud (Amazon EC2) security group." echo " -i security_group_id - The ID of the security group to delete." echo "" } # Retrieve the calling parameters. while getopts "i:h" option; do case "${option}" in i) security_group_id="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$security_group_id" ]]; then errecho "ERROR: You must provide a security group ID with the -i parameter." usage return 1 fi response=$(aws ec2 delete-security-group --group-id "$security_group_id" --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports delete-security-group operation failed.$response" return 1 } return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see DeleteSecurityGroup in AWS CLI Command Reference.
-
The following code example shows how to use DescribeImages.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_describe_images # # This function describes one or more Amazon Elastic Compute Cloud (Amazon EC2) images. # # Parameters: # -i image_ids - A space-separated list of image IDs (optional). # -h - Display help. # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_describe_images() { local image_ids response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_describe_images" echo "Describes one or more Amazon Elastic Compute Cloud (Amazon EC2) images." echo " -i image_ids - A space-separated list of image IDs (optional)." echo " -h - Display help." echo "" } # Retrieve the calling parameters. while getopts "i:h" option; do case "${option}" in i) image_ids="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 local aws_cli_args=() if [[ -n "$image_ids" ]]; then # shellcheck disable=SC2206 aws_cli_args+=("--image-ids" $image_ids) fi response=$(aws ec2 describe-images \ "${aws_cli_args[@]}" \ --query 'Images[*].[Description,Architecture,ImageId]' \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports describe-images operation failed.$response" return 1 } echo "$response" return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see DescribeImages in AWS CLI Command Reference.
-
The following code example shows how to use DescribeInstanceTypes.
- 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 AWS Code Examples Repository
. ############################################################################### # ec2_describe_instance_types # # This function describes EC2 instance types filtered by processor architecture # and optionally by instance type. It takes the following arguments: # # -a, --architecture ARCHITECTURE Specify the processor architecture (e.g., x86_64) # -t, --type INSTANCE_TYPE Comma-separated list of instance types (e.g., t2.micro) # -h, --help Show the usage help # # The function prints the instance type and supported architecture for each # matching instance type. ############################################################################### function ec2_describe_instance_types() { local architecture="" local instance_types="" # bashsupport disable=BP5008 function usage() { echo "Usage: ec2_describe_instance_types [-a|--architecture ARCHITECTURE] [-t|--type INSTANCE_TYPE] [-h|--help]" echo " -a, --architecture ARCHITECTURE Specify the processor architecture (e.g., x86_64)" echo " -t, --type INSTANCE_TYPE Comma-separated list of instance types (e.g., t2.micro)" echo " -h, --help Show this help message" } while [[ $# -gt 0 ]]; do case "$1" in -a | --architecture) architecture="$2" shift 2 ;; -t | --type) instance_types="$2" shift 2 ;; -h | --help) usage return 0 ;; *) echo "Unknown argument: $1" return 1 ;; esac done if [[ -z "$architecture" ]]; then errecho "Error: Architecture not specified." usage return 1 fi if [[ -z "$instance_types" ]]; then errecho "Error: Instance type not specified." usage return 1 fi local tmp_json_file="temp_ec2.json" echo -n '[ { "Name": "processor-info.supported-architecture", "Values": [' >"$tmp_json_file" local items IFS=',' read -ra items <<<"$architecture" local array_size array_size=${#items[@]} for i in $(seq 0 $((array_size - 1))); do echo -n '"'"${items[$i]}"'"' >>"$tmp_json_file" if [[ $i -lt $((array_size - 1)) ]]; then echo -n ',' >>"$tmp_json_file" fi done echo -n ']}, { "Name": "instance-type", "Values": [' >>"$tmp_json_file" IFS=',' read -ra items <<<"$instance_types" local array_size array_size=${#items[@]} for i in $(seq 0 $((array_size - 1))); do echo -n '"'"${items[$i]}"'"' >>"$tmp_json_file" if [[ $i -lt $((array_size - 1)) ]]; then echo -n ',' >>"$tmp_json_file" fi done echo -n ']}]' >>"$tmp_json_file" local response response=$(aws ec2 describe-instance-types --filters file://"$tmp_json_file" \ --query 'InstanceTypes[*].[InstanceType]' --output text) local error_code=$? rm "$tmp_json_file" if [[ $error_code -ne 0 ]]; then aws_cli_error_log $error_code echo "ERROR: AWS reports describe-instance-types operation failed." return 1 fi echo "$response" return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see DescribeInstanceTypes in AWS CLI Command Reference.
-
The following code example shows how to use DescribeInstances.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_describe_instances # # This function describes one or more Amazon Elastic Compute Cloud (Amazon EC2) instances. # # Parameters: # -i instance_id - The ID of the instance to describe (optional). # -q query - The query to filter the response (optional). # -h - Display help. # # Returns: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_describe_instances() { local instance_id query response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_describe_instances" echo "Describes one or more Amazon Elastic Compute Cloud (Amazon EC2) instances." echo " -i instance_id - The ID of the instance to describe (optional)." echo " -q query - The query to filter the response (optional)." echo " -h - Display help." echo "" } # Retrieve the calling parameters. while getopts "i:q:h" option; do case "${option}" in i) instance_id="${OPTARG}" ;; q) query="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 local aws_cli_args=() if [[ -n "$instance_id" ]]; then # shellcheck disable=SC2206 aws_cli_args+=("--instance-ids" $instance_id) fi local query_arg="" if [[ -n "$query" ]]; then query_arg="--query '$query'" else query_arg="--query Reservations[*].Instances[*].[InstanceId,ImageId,InstanceType,KeyName,VpcId,PublicIpAddress,State.Name]" fi # shellcheck disable=SC2086 response=$(aws ec2 describe-instances \ "${aws_cli_args[@]}" \ $query_arg \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports describe-instances operation failed.$response" return 1 } echo "$response" return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see DescribeInstances in AWS CLI Command Reference.
-
The following code example shows how to use DescribeKeyPairs.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_describe_key_pairs # # This function describes one or more Amazon Elastic Compute Cloud (Amazon EC2) key pairs. # # Parameters: # -h - Display help. # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_describe_key_pairs() { local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_describe_key_pairs" echo "Describes one or more Amazon Elastic Compute Cloud (Amazon EC2) key pairs." echo " -h - Display help." echo "" } # Retrieve the calling parameters. while getopts "h" option; do case "${option}" in h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 local response response=$(aws ec2 describe-key-pairs \ --query 'KeyPairs[*].[KeyName, KeyFingerprint]' \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports describe-key-pairs operation failed.$response" return 1 } echo "$response" return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see DescribeKeyPairs in AWS CLI Command Reference.
-
The following code example shows how to use DescribeSecurityGroups.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_describe_security_groups # # This function describes one or more Amazon Elastic Compute Cloud (Amazon EC2) security groups. # # Parameters: # -g security_group_id - The ID of the security group to describe (optional). # # And: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_describe_security_groups() { local security_group_id response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_describe_security_groups" echo "Describes one or more Amazon Elastic Compute Cloud (Amazon EC2) security groups." echo " -g security_group_id - The ID of the security group to describe (optional)." echo "" } # Retrieve the calling parameters. while getopts "g:h" option; do case "${option}" in g) security_group_id="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 local query="SecurityGroups[*].[GroupName, GroupId, VpcId, IpPermissions[*].[IpProtocol, FromPort, ToPort, IpRanges[*].CidrIp]]" if [[ -n "$security_group_id" ]]; then response=$(aws ec2 describe-security-groups --group-ids "$security_group_id" --query "${query}" --output text) else response=$(aws ec2 describe-security-groups --query "${query}" --output text) fi local error_code=${?} if [[ $error_code -ne 0 ]]; then aws_cli_error_log $error_code errecho "ERROR: AWS reports describe-security-groups operation failed.$response" return 1 fi echo "$response" return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see DescribeSecurityGroups in AWS CLI Command Reference.
-
The following code example shows how to use DisassociateAddress.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_disassociate_address # # This function disassociates an Elastic IP address from an Amazon Elastic Compute Cloud (Amazon EC2) instance. # # Parameters: # -a association_id - The association ID that represents the association of the Elastic IP address with an instance. # # And: # 0 - If successful. # 1 - If it fails. # ############################################################################### function ec2_disassociate_address() { local association_id response # Function to display usage information function usage() { echo "function ec2_disassociate_address" echo "Disassociates an Elastic IP address from an Amazon Elastic Compute Cloud (Amazon EC2) instance." echo " -a association_id - The association ID that represents the association of the Elastic IP address with an instance." echo "" } # Parse the command-line arguments while getopts "a:h" option; do case "${option}" in a) association_id="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 # Validate the input parameters if [[ -z "$association_id" ]]; then errecho "ERROR: You must provide an association ID with the -a parameter." return 1 fi response=$(aws ec2 disassociate-address \ --association-id "$association_id") || { aws_cli_error_log ${?} errecho "ERROR: AWS reports disassociate-address operation failed." errecho "$response" return 1 } return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see DisassociateAddress in AWS CLI Command Reference.
-
The following code example shows how to use ReleaseAddress.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_release_address # # This function releases an Elastic IP address from an Amazon Elastic Compute Cloud (Amazon EC2) instance. # # Parameters: # -a allocation_id - The allocation ID of the Elastic IP address to release. # # Returns: # 0 - If successful. # 1 - If it fails. # ############################################################################### function ec2_release_address() { local allocation_id response # Function to display usage information function usage() { echo "function ec2_release_address" echo "Releases an Elastic IP address from an Amazon Elastic Compute Cloud (Amazon EC2) instance." echo " -a allocation_id - The allocation ID of the Elastic IP address to release." echo "" } # Parse the command-line arguments while getopts "a:h" option; do case "${option}" in a) allocation_id="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 # Validate the input parameters if [[ -z "$allocation_id" ]]; then errecho "ERROR: You must provide an allocation ID with the -a parameter." return 1 fi response=$(aws ec2 release-address \ --allocation-id "$allocation_id") || { aws_cli_error_log ${?} errecho "ERROR: AWS reports release-address operation failed." errecho "$response" return 1 } return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see ReleaseAddress in AWS CLI Command Reference.
-
The following code example shows how to use RunInstances.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_run_instances # # This function launches one or more Amazon Elastic Compute Cloud (Amazon EC2) instances. # # Parameters: # -i image_id - The ID of the Amazon Machine Image (AMI) to use. # -t instance_type - The instance type to use (e.g., t2.micro). # -k key_pair_name - The name of the key pair to use. # -s security_group_id - The ID of the security group to use. # -c count - The number of instances to launch (default: 1). # -h - Display help. # # Returns: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_run_instances() { local image_id instance_type key_pair_name security_group_id count response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_run_instances" echo "Launches one or more Amazon Elastic Compute Cloud (Amazon EC2) instances." echo " -i image_id - The ID of the Amazon Machine Image (AMI) to use." echo " -t instance_type - The instance type to use (e.g., t2.micro)." echo " -k key_pair_name - The name of the key pair to use." echo " -s security_group_id - The ID of the security group to use." echo " -c count - The number of instances to launch (default: 1)." echo " -h - Display help." echo "" } # Retrieve the calling parameters. while getopts "i:t:k:s:c:h" option; do case "${option}" in i) image_id="${OPTARG}" ;; t) instance_type="${OPTARG}" ;; k) key_pair_name="${OPTARG}" ;; s) security_group_id="${OPTARG}" ;; c) count="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$image_id" ]]; then errecho "ERROR: You must provide an Amazon Machine Image (AMI) ID with the -i parameter." usage return 1 fi if [[ -z "$instance_type" ]]; then errecho "ERROR: You must provide an instance type with the -t parameter." usage return 1 fi if [[ -z "$key_pair_name" ]]; then errecho "ERROR: You must provide a key pair name with the -k parameter." usage return 1 fi if [[ -z "$security_group_id" ]]; then errecho "ERROR: You must provide a security group ID with the -s parameter." usage return 1 fi if [[ -z "$count" ]]; then count=1 fi response=$(aws ec2 run-instances \ --image-id "$image_id" \ --instance-type "$instance_type" \ --key-name "$key_pair_name" \ --security-group-ids "$security_group_id" \ --count "$count" \ --query 'Instances[*].[InstanceId]' \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports run-instances operation failed.$response" return 1 } echo "$response" return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see RunInstances in AWS CLI Command Reference.
-
The following code example shows how to use StartInstances.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_start_instances # # This function starts one or more Amazon Elastic Compute Cloud (Amazon EC2) instances. # # Parameters: # -i instance_id - The ID(s) of the instance(s) to start (comma-separated). # -h - Display help. # # Returns: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_start_instances() { local instance_ids local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_start_instances" echo "Starts one or more Amazon Elastic Compute Cloud (Amazon EC2) instances." echo " -i instance_id - The ID(s) of the instance(s) to start (comma-separated)." echo " -h - Display help." echo "" } # Retrieve the calling parameters. while getopts "i:h" option; do case "${option}" in i) instance_ids="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$instance_ids" ]]; then errecho "ERROR: You must provide one or more instance IDs with the -i parameter." usage return 1 fi response=$(aws ec2 start-instances \ --instance-ids "${instance_ids}") || { aws_cli_error_log ${?} errecho "ERROR: AWS reports start-instances operation failed with $response." return 1 } return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see StartInstances in AWS CLI Command Reference.
-
The following code example shows how to use StopInstances.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_stop_instances # # This function stops one or more Amazon Elastic Compute Cloud (Amazon EC2) instances. # # Parameters: # -i instance_id - The ID(s) of the instance(s) to stop (comma-separated). # -h - Display help. # # Returns: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_stop_instances() { local instance_ids local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_stop_instances" echo "Stops one or more Amazon Elastic Compute Cloud (Amazon EC2) instances." echo " -i instance_id - The ID(s) of the instance(s) to stop (comma-separated)." echo " -h - Display help." echo "" } # Retrieve the calling parameters. while getopts "i:h" option; do case "${option}" in i) instance_ids="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 if [[ -z "$instance_ids" ]]; then errecho "ERROR: You must provide one or more instance IDs with the -i parameter." usage return 1 fi response=$(aws ec2 stop-instances \ --instance-ids "${instance_ids}") || { aws_cli_error_log ${?} errecho "ERROR: AWS reports stop-instances operation failed with $response." return 1 } return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see StopInstances in AWS CLI Command Reference.
-
The following code example shows how to use TerminateInstances.
- 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 AWS Code Examples Repository
. ############################################################################### # function ec2_terminate_instances # # This function terminates one or more Amazon Elastic Compute Cloud (Amazon EC2) # instances using the AWS CLI. # # Parameters: # -i instance_ids - A space-separated list of instance IDs. # -h - Display help. # # Returns: # 0 - If successful. # 1 - If it fails. ############################################################################### function ec2_terminate_instances() { local instance_ids response local option OPTARG # Required to use getopts command in a function. # bashsupport disable=BP5008 function usage() { echo "function ec2_terminate_instances" echo "Terminates one or more Amazon Elastic Compute Cloud (Amazon EC2) instances." echo " -i instance_ids - A space-separated list of instance IDs." echo " -h - Display help." echo "" } # Retrieve the calling parameters. while getopts "i:h" option; do case "${option}" in i) instance_ids="${OPTARG}" ;; h) usage return 0 ;; \?) echo "Invalid parameter" usage return 1 ;; esac done export OPTIND=1 # Check if instance ID is provided if [[ -z "${instance_ids}" ]]; then echo "Error: Missing required instance IDs parameter." usage return 1 fi # shellcheck disable=SC2086 response=$(aws ec2 terminate-instances \ "--instance-ids" $instance_ids \ --query 'TerminatingInstances[*].[InstanceId,CurrentState.Name]' \ --output text) || { aws_cli_error_log ${?} errecho "ERROR: AWS reports terminate-instances operation failed.$response" return 1 } return 0 }The utility functions used in this example.
############################################################################### # function errecho # # This function outputs everything sent to it to STDERR (standard error output). ############################################################################### function errecho() { printf "%s\n" "$*" 1>&2 } ############################################################################## # function aws_cli_error_log() # # This function is used to log the error messages from the AWS CLI. # # The function expects the following argument: # $1 - The error code returned by the AWS CLI. # # Returns: # 0: - Success. # ############################################################################## function aws_cli_error_log() { local err_code=$1 errecho "Error code : $err_code" if [ "$err_code" == 1 ]; then errecho " One or more S3 transfers failed." elif [ "$err_code" == 2 ]; then errecho " Command line failed to parse." elif [ "$err_code" == 130 ]; then errecho " Process received SIGINT." elif [ "$err_code" == 252 ]; then errecho " Command syntax invalid." elif [ "$err_code" == 253 ]; then errecho " The system environment or configuration was invalid." elif [ "$err_code" == 254 ]; then errecho " The service returned an error." elif [ "$err_code" == 255 ]; then errecho " 255 is a catch-all error." fi return 0 }-
For API details, see TerminateInstances in AWS CLI Command Reference.
-
Scenarios
The following code example shows how to:
Create the VPC infrastructure
Set up logging
Create the ECS cluster
Configure IAM roles
Create the service with Service Connect
Verify the deployment
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 # ECS Service Connect Tutorial Script v4 - Modified to use Default VPC # This script creates an ECS cluster with Service Connect and deploys an nginx service # Uses the default VPC to avoid VPC limits set -e # Exit on any error # Configuration SCRIPT_NAME="ECS Service Connect Tutorial" LOG_FILE="ecs-service-connect-tutorial-v4-default-vpc.log" REGION=${AWS_DEFAULT_REGION:-${AWS_REGION:-$(aws configure get region 2>/dev/null)}} if [ -z "$REGION" ]; then echo "ERROR: No AWS region configured." echo "Set one with: aws configure set region us-east-1" exit 1 fi ENV_PREFIX="tutorial" CLUSTER_NAME="${ENV_PREFIX}-cluster" NAMESPACE_NAME="service-connect" # Generate random suffix for unique resource names RANDOM_SUFFIX=$(openssl rand -hex 6) # Arrays to track created resources for cleanup declare -a CREATED_RESOURCES=() # Logging function log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } # Error handling function handle_error() { log "ERROR: Script failed at line $1" log "Attempting to clean up resources..." cleanup_resources exit 1 } # Set up error handling trap 'handle_error $LINENO' ERR # Function to add resource to tracking array track_resource() { CREATED_RESOURCES+=("$1") log "Tracking resource: $1" } # Function to check if command output contains actual errors check_for_errors() { local output="$1" local command_name="$2" # Check for specific AWS CLI error patterns, not just any occurrence of "error" if echo "$output" | grep -qi "An error occurred\|InvalidParameterException\|AccessDenied\|ValidationException\|ResourceNotFoundException"; then log "ERROR in $command_name: $output" return 1 fi return 0 } # Function to get AWS account ID get_account_id() { ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) log "Using AWS Account ID: $ACCOUNT_ID" } # Function to wait for resources to be ready wait_for_resource() { local resource_type="$1" local resource_id="$2" case "$resource_type" in "cluster") log "Waiting for cluster $resource_id to be active..." local attempt=1 local max_attempts=30 while [ $attempt -le $max_attempts ]; do local status=$(aws ecs describe-clusters --clusters "$resource_id" --query 'clusters[0].status' --output text) if [ "$status" = "ACTIVE" ]; then log "Cluster is now active" return 0 fi log "Cluster status: $status (attempt $attempt/$max_attempts)" sleep 10 ((attempt++)) done log "ERROR: Cluster did not become active within expected time" return 1 ;; "service") log "Waiting for service $resource_id to be stable..." aws ecs wait services-stable --cluster "$CLUSTER_NAME" --services "$resource_id" ;; "nat-gateway") log "Waiting for NAT Gateway $resource_id to be available..." aws ec2 wait nat-gateway-available --nat-gateway-ids "$resource_id" ;; esac } # Function to use default VPC infrastructure setup_default_vpc_infrastructure() { log "Using default VPC infrastructure..." # Get default VPC VPC_ID=$(aws ec2 describe-vpcs --filters "Name=isDefault,Values=true" --query 'Vpcs[0].VpcId' --output text) if [[ "$VPC_ID" == "None" || -z "$VPC_ID" ]]; then log "ERROR: No default VPC found. Please create a default VPC first." exit 1 fi log "Using default VPC: $VPC_ID" # Get default subnets SUBNETS=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" "Name=default-for-az,Values=true" --query 'Subnets[].SubnetId' --output text) SUBNET_ARRAY=($SUBNETS) if [ ${#SUBNET_ARRAY[@]} -lt 2 ]; then log "ERROR: Need at least 2 subnets for ECS Service Connect. Found: ${#SUBNET_ARRAY[@]}" exit 1 fi PUBLIC_SUBNET1=${SUBNET_ARRAY[0]} PUBLIC_SUBNET2=${SUBNET_ARRAY[1]} log "Using subnets: $PUBLIC_SUBNET1, $PUBLIC_SUBNET2" # Create security group for ECS tasks SG_OUTPUT=$(aws ec2 create-security-group \ --group-name "${ENV_PREFIX}-ecs-sg-${RANDOM_SUFFIX}" \ --description "Security group for ECS Service Connect tutorial" \ --vpc-id "$VPC_ID" 2>&1) check_for_errors "$SG_OUTPUT" "create-security-group" SECURITY_GROUP_ID=$(echo "$SG_OUTPUT" | grep -o '"GroupId": "[^"]*"' | cut -d'"' -f4) track_resource "SG:$SECURITY_GROUP_ID" log "Created security group: $SECURITY_GROUP_ID" # Add inbound rules to security group aws ec2 authorize-security-group-ingress \ --group-id "$SECURITY_GROUP_ID" \ --protocol tcp \ --port 80 \ --cidr 0.0.0.0/0 >/dev/null 2>&1 || true aws ec2 authorize-security-group-ingress \ --group-id "$SECURITY_GROUP_ID" \ --protocol tcp \ --port 443 \ --cidr 0.0.0.0/0 >/dev/null 2>&1 || true log "Default VPC infrastructure setup completed" } # Function to create CloudWatch log groups create_log_groups() { log "Creating CloudWatch log groups..." # Create log group for nginx container aws logs create-log-group --log-group-name "/ecs/service-connect-nginx" 2>&1 | grep -v "ResourceAlreadyExistsException" || { if [ ${PIPESTATUS[0]} -eq 0 ]; then log "Log group /ecs/service-connect-nginx created" track_resource "LOG_GROUP:/ecs/service-connect-nginx" else log "Log group /ecs/service-connect-nginx already exists" fi } # Create log group for service connect proxy aws logs create-log-group --log-group-name "/ecs/service-connect-proxy" 2>&1 | grep -v "ResourceAlreadyExistsException" || { if [ ${PIPESTATUS[0]} -eq 0 ]; then log "Log group /ecs/service-connect-proxy created" track_resource "LOG_GROUP:/ecs/service-connect-proxy" else log "Log group /ecs/service-connect-proxy already exists" fi } } # Function to create ECS cluster with Service Connect create_ecs_cluster() { log "Creating ECS cluster with Service Connect..." CLUSTER_OUTPUT=$(aws ecs create-cluster \ --cluster-name "$CLUSTER_NAME" \ --service-connect-defaults namespace="$NAMESPACE_NAME" \ --tags key=Environment,value=tutorial 2>&1) check_for_errors "$CLUSTER_OUTPUT" "create-cluster" track_resource "CLUSTER:$CLUSTER_NAME" log "Created ECS cluster: $CLUSTER_NAME" wait_for_resource "cluster" "$CLUSTER_NAME" # Track the Service Connect namespace that gets created # Wait a moment for the namespace to be created sleep 5 NAMESPACE_ID=$(aws servicediscovery list-namespaces \ --filters Name=TYPE,Values=HTTP \ --query "Namespaces[?Name=='$NAMESPACE_NAME'].Id" --output text 2>/dev/null || echo "") if [[ -n "$NAMESPACE_ID" && "$NAMESPACE_ID" != "None" ]]; then track_resource "NAMESPACE:$NAMESPACE_ID" log "Service Connect namespace created: $NAMESPACE_ID" fi } # Function to create IAM roles create_iam_roles() { log "Creating IAM roles..." # Check if ecsTaskExecutionRole exists if aws iam get-role --role-name ecsTaskExecutionRole >/dev/null 2>&1; then log "IAM role ecsTaskExecutionRole exists" else log "Creating ecsTaskExecutionRole..." aws iam create-role \ --role-name ecsTaskExecutionRole \ --assume-role-policy-document '{ "Version":"2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": {"Service": "ecs-tasks.amazonaws.com"}, "Action": "sts:AssumeRole" }] }' >/dev/null 2>&1 aws iam attach-role-policy \ --role-name ecsTaskExecutionRole \ --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy >/dev/null 2>&1 track_resource "ROLE:ecsTaskExecutionRole" log "Created ecsTaskExecutionRole" sleep 10 fi # Check if ecsTaskRole exists, create if not if aws iam get-role --role-name ecsTaskRole >/dev/null 2>&1; then log "IAM role ecsTaskRole exists" else log "IAM role ecsTaskRole does not exist, will create it" # Create trust policy for ECS tasks cat > /tmp/ecs-task-trust-policy.json << EOF { "Version":"2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ecs-tasks.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF aws iam create-role \ --role-name ecsTaskRole \ --assume-role-policy-document file:///tmp/ecs-task-trust-policy.json >/dev/null track_resource "IAM_ROLE:ecsTaskRole" log "Created ecsTaskRole" # Wait for role to be available sleep 10 fi } # Function to create task definition create_task_definition() { log "Creating task definition..." # Create task definition JSON cat > /tmp/task-definition.json << EOF { "family": "service-connect-nginx", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "256", "memory": "512", "executionRoleArn": "arn:aws:iam::${ACCOUNT_ID}:role/ecsTaskExecutionRole", "taskRoleArn": "arn:aws:iam::${ACCOUNT_ID}:role/ecsTaskRole", "containerDefinitions": [ { "name": "nginx", "image": "public.ecr.aws/docker/library/nginx:latest", "portMappings": [ { "containerPort": 80, "protocol": "tcp", "name": "nginx-port" } ], "essential": true, "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/service-connect-nginx", "awslogs-region": "${REGION}", "awslogs-stream-prefix": "ecs" } } } ] } EOF TASK_DEF_OUTPUT=$(aws ecs register-task-definition --cli-input-json file:///tmp/task-definition.json 2>&1) check_for_errors "$TASK_DEF_OUTPUT" "register-task-definition" TASK_DEF_ARN=$(echo "$TASK_DEF_OUTPUT" | grep -o '"taskDefinitionArn": "[^"]*"' | cut -d'"' -f4) track_resource "TASK_DEF:service-connect-nginx" log "Created task definition: $TASK_DEF_ARN" # Clean up temporary file rm -f /tmp/task-definition.json } # Function to create ECS service with Service Connect create_ecs_service() { log "Creating ECS service with Service Connect..." # Create service definition JSON cat > /tmp/service-definition.json << EOF { "serviceName": "service-connect-nginx-service", "cluster": "${CLUSTER_NAME}", "taskDefinition": "service-connect-nginx", "desiredCount": 1, "launchType": "FARGATE", "networkConfiguration": { "awsvpcConfiguration": { "subnets": ["${PUBLIC_SUBNET1}", "${PUBLIC_SUBNET2}"], "securityGroups": ["${SECURITY_GROUP_ID}"], "assignPublicIp": "ENABLED" } }, "serviceConnectConfiguration": { "enabled": true, "namespace": "${NAMESPACE_NAME}", "services": [ { "portName": "nginx-port", "discoveryName": "nginx", "clientAliases": [ { "port": 80, "dnsName": "nginx" } ] } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/service-connect-proxy", "awslogs-region": "${REGION}", "awslogs-stream-prefix": "ecs-service-connect" } } }, "tags": [ { "key": "Environment", "value": "tutorial" } ] } EOF SERVICE_OUTPUT=$(aws ecs create-service --cli-input-json file:///tmp/service-definition.json 2>&1) check_for_errors "$SERVICE_OUTPUT" "create-service" track_resource "SERVICE:service-connect-nginx-service" log "Created ECS service: service-connect-nginx-service" wait_for_resource "service" "service-connect-nginx-service" # Clean up temporary file rm -f /tmp/service-definition.json } # Function to verify deployment verify_deployment() { log "Verifying deployment..." # Check service status SERVICE_STATUS=$(aws ecs describe-services \ --cluster "$CLUSTER_NAME" \ --services "service-connect-nginx-service" \ --query 'services[0].status' --output text) log "Service status: $SERVICE_STATUS" # Check running tasks RUNNING_COUNT=$(aws ecs describe-services \ --cluster "$CLUSTER_NAME" \ --services "service-connect-nginx-service" \ --query 'services[0].runningCount' --output text) log "Running tasks: $RUNNING_COUNT" # Get task ARN TASK_ARN=$(aws ecs list-tasks \ --cluster "$CLUSTER_NAME" \ --service-name "service-connect-nginx-service" \ --query 'taskArns[0]' --output text) if [[ "$TASK_ARN" != "None" && -n "$TASK_ARN" ]]; then log "Task ARN: $TASK_ARN" # Try to get task IP address TASK_IP=$(aws ecs describe-tasks \ --cluster "$CLUSTER_NAME" \ --tasks "$TASK_ARN" \ --query 'tasks[0].attachments[0].details[?name==`privateIPv4Address`].value' \ --output text 2>/dev/null || echo "") if [[ -n "$TASK_IP" && "$TASK_IP" != "None" ]]; then log "Task IP address: $TASK_IP" else log "Could not retrieve task IP address" fi fi # Check Service Connect namespace NAMESPACE_STATUS=$(aws servicediscovery list-namespaces \ --filters Name=TYPE,Values=HTTP \ --query "Namespaces[?Name=='$NAMESPACE_NAME'].Id" --output text 2>/dev/null || echo "") if [[ -n "$NAMESPACE_STATUS" && "$NAMESPACE_STATUS" != "None" ]]; then log "Service Connect namespace '$NAMESPACE_NAME' is active" else log "Service Connect namespace '$NAMESPACE_NAME' not found or not active" fi # Display Service Connect configuration log "Service Connect configuration:" aws ecs describe-services \ --cluster "$CLUSTER_NAME" \ --services "service-connect-nginx-service" \ --query 'services[0].serviceConnectConfiguration' 2>/dev/null || true } # Function to display created resources display_resources() { echo "" echo "===========================================" echo "CREATED RESOURCES" echo "===========================================" for resource in "${CREATED_RESOURCES[@]}"; do echo "- $resource" done echo "===========================================" echo "" } # Function to cleanup resources cleanup_resources() { log "Starting cleanup process..." # Delete resources in reverse order of creation for ((i=${#CREATED_RESOURCES[@]}-1; i>=0; i--)); do resource="${CREATED_RESOURCES[i]}" resource_type=$(echo "$resource" | cut -d':' -f1) resource_id=$(echo "$resource" | cut -d':' -f2) log "Cleaning up $resource_type: $resource_id" case "$resource_type" in "SERVICE") aws ecs update-service --cluster "$CLUSTER_NAME" --service "$resource_id" --desired-count 0 2>&1 | grep -qi "error" && log "Warning: Failed to scale down service $resource_id" aws ecs wait services-stable --cluster "$CLUSTER_NAME" --services "$resource_id" 2>/dev/null || true aws ecs delete-service --cluster "$CLUSTER_NAME" --service "$resource_id" --force 2>&1 | grep -qi "error" && log "Warning: Failed to delete service $resource_id" ;; "TASK_DEF") TASK_DEF_ARNS=$(aws ecs list-task-definitions --family-prefix "$resource_id" --query 'taskDefinitionArns' --output text 2>/dev/null) for arn in $TASK_DEF_ARNS; do aws ecs deregister-task-definition --task-definition "$arn" >/dev/null 2>&1 || true done ;; "ROLE") aws iam detach-role-policy --role-name "$resource_id" --policy-arn "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" 2>/dev/null || true aws iam delete-role --role-name "$resource_id" 2>&1 | grep -qi "error" && log "Warning: Failed to delete role $resource_id" ;; "IAM_ROLE") aws iam detach-role-policy --role-name "$resource_id" --policy-arn "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" 2>/dev/null || true aws iam delete-role --role-name "$resource_id" 2>&1 | grep -qi "error" && log "Warning: Failed to delete role $resource_id" ;; "CLUSTER") aws ecs delete-cluster --cluster "$resource_id" 2>&1 | grep -qi "error" && log "Warning: Failed to delete cluster $resource_id" ;; "SG") for attempt in 1 2 3 4 5; do if aws ec2 delete-security-group --group-id "$resource_id" 2>/dev/null; then break fi log "Security group $resource_id still has dependencies, retrying in 30s ($attempt/5)..." sleep 30 done ;; "LOG_GROUP") aws logs delete-log-group --log-group-name "$resource_id" 2>&1 | grep -qi "error" && log "Warning: Failed to delete log group $resource_id" ;; "NAMESPACE") # First, delete any services in the namespace NAMESPACE_SERVICES=$(aws servicediscovery list-services \ --filters Name=NAMESPACE_ID,Values="$resource_id" \ --query 'Services[].Id' --output text 2>/dev/null || echo "") if [[ -n "$NAMESPACE_SERVICES" && "$NAMESPACE_SERVICES" != "None" ]]; then for service_id in $NAMESPACE_SERVICES; do aws servicediscovery delete-service --id "$service_id" >/dev/null 2>&1 || true sleep 2 done fi # Then delete the namespace aws servicediscovery delete-namespace --id "$resource_id" >/dev/null 2>&1 || true ;; esac sleep 2 # Brief pause between deletions done # Clean up temporary files rm -f /tmp/ecs-task-trust-policy.json rm -f /tmp/task-definition.json rm -f /tmp/service-definition.json log "Cleanup completed" } # Main execution main() { log "Starting $SCRIPT_NAME v4 (Default VPC)" log "Region: $REGION" log "Log file: $LOG_FILE" # Get AWS account ID get_account_id # Setup infrastructure using default VPC setup_default_vpc_infrastructure # Create CloudWatch log groups create_log_groups # Create ECS cluster create_ecs_cluster # Create IAM roles create_iam_roles # Create task definition create_task_definition # Create ECS service create_ecs_service # Verify deployment verify_deployment log "Tutorial completed successfully!" # Display created resources display_resources # Ask user if they want to clean up 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 log "All resources have been cleaned up" else log "Resources left intact. You can clean them up later by running the cleanup function." echo "" echo "To clean up resources later, you can use the AWS CLI commands or the AWS Management Console." echo "Remember to delete resources in the correct order to avoid dependency issues." fi } # Make script executable and run chmod +x "$0" main "$@"-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create a VPC
Create subnets
Configure internet connectivity
Create a NAT Gateway
Configure subnet settings
Create security groups
Verify your VPC configuration
- 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 # VPC Creation Script # This script creates a VPC with public and private subnets, internet gateway, NAT gateway, and security groups # Set up logging LOG_FILE="vpc_creation.log" exec > >(tee -a "$LOG_FILE") 2>&1 # Function to handle errors handle_error() { echo "ERROR: $1" echo "Resources created before error:" for resource in "${CREATED_RESOURCES[@]}" do echo "- $resource" done echo "Attempting to clean up resources..." cleanup_resources exit 1 } # Function to clean up resources cleanup_resources() { echo "Cleaning up resources in reverse order..." # Reverse the array to delete in reverse order of creation for ((i=${#CREATED_RESOURCES[@]}-1; i>=0; i--)) do resource="${CREATED_RESOURCES[$i]}" resource_type=$(echo "$resource" | cut -d':' -f1) resource_id=$(echo "$resource" | cut -d':' -f2) case "$resource_type" in "INSTANCE") echo "Terminating EC2 instance: $resource_id" aws ec2 terminate-instances --instance-ids "$resource_id" || echo "Failed to terminate instance: $resource_id" # Wait for instance to terminate echo "Waiting for instance to terminate..." aws ec2 wait instance-terminated --instance-ids "$resource_id" || echo "Failed to wait for instance termination: $resource_id" ;; "KEY_PAIR") echo "Deleting key pair: $resource_id" aws ec2 delete-key-pair --key-name "$resource_id" || echo "Failed to delete key pair: $resource_id" # Remove the .pem file if it exists if [ -f "${resource_id}.pem" ]; then rm -f "${resource_id}.pem" fi ;; "NAT_GATEWAY") echo "Deleting NAT Gateway: $resource_id" aws ec2 delete-nat-gateway --nat-gateway-id "$resource_id" || echo "Failed to delete NAT Gateway: $resource_id" # NAT Gateway deletion takes time, wait for it to complete echo "Waiting for NAT Gateway to be deleted..." aws ec2 wait nat-gateway-deleted --nat-gateway-ids "$resource_id" || echo "Failed to wait for NAT Gateway deletion: $resource_id" ;; "EIP") echo "Releasing Elastic IP: $resource_id" aws ec2 release-address --allocation-id "$resource_id" || echo "Failed to release Elastic IP: $resource_id" ;; "ROUTE_TABLE_ASSOCIATION") echo "Disassociating Route Table: $resource_id" aws ec2 disassociate-route-table --association-id "$resource_id" || echo "Failed to disassociate Route Table: $resource_id" ;; "ROUTE_TABLE") echo "Deleting Route Table: $resource_id" aws ec2 delete-route-table --route-table-id "$resource_id" || echo "Failed to delete Route Table: $resource_id" ;; "INTERNET_GATEWAY") echo "Detaching Internet Gateway: $resource_id from VPC: $VPC_ID" aws ec2 detach-internet-gateway --internet-gateway-id "$resource_id" --vpc-id "$VPC_ID" || echo "Failed to detach Internet Gateway: $resource_id" echo "Deleting Internet Gateway: $resource_id" aws ec2 delete-internet-gateway --internet-gateway-id "$resource_id" || echo "Failed to delete Internet Gateway: $resource_id" ;; "SECURITY_GROUP") echo "Deleting Security Group: $resource_id" aws ec2 delete-security-group --group-id "$resource_id" || echo "Failed to delete Security Group: $resource_id" ;; "SUBNET") echo "Deleting Subnet: $resource_id" aws ec2 delete-subnet --subnet-id "$resource_id" || echo "Failed to delete Subnet: $resource_id" ;; "VPC") echo "Deleting VPC: $resource_id" aws ec2 delete-vpc --vpc-id "$resource_id" || echo "Failed to delete VPC: $resource_id" ;; esac done } # Initialize array to track created resources CREATED_RESOURCES=() echo "Starting VPC creation script at $(date)" # Verify AWS CLI configuration echo "Verifying AWS CLI configuration..." aws configure list || handle_error "AWS CLI is not properly configured" # Verify identity and permissions echo "Verifying identity and permissions..." if ! aws sts get-caller-identity; then echo "ERROR: Unable to verify AWS identity. This could be due to:" echo " - Expired credentials" echo " - Missing or invalid AWS credentials" echo " - Insufficient permissions" echo "" echo "Please run 'aws configure' to update your credentials or check your IAM permissions." exit 1 fi # Create VPC echo "Creating VPC with CIDR block 10.0.0.0/16..." VPC_ID=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=MyVPC}]' --query 'Vpc.VpcId' --output text) if [ -z "$VPC_ID" ]; then handle_error "Failed to create VPC" fi CREATED_RESOURCES+=("VPC:$VPC_ID") echo "VPC created with ID: $VPC_ID" # Enable DNS support and hostnames echo "Enabling DNS support and hostnames for VPC..." aws ec2 modify-vpc-attribute --vpc-id "$VPC_ID" --enable-dns-support || handle_error "Failed to enable DNS support" aws ec2 modify-vpc-attribute --vpc-id "$VPC_ID" --enable-dns-hostnames || handle_error "Failed to enable DNS hostnames" # Get available Availability Zones echo "Getting available Availability Zones..." AZ1=$(aws ec2 describe-availability-zones --query 'AvailabilityZones[0].ZoneName' --output text) AZ2=$(aws ec2 describe-availability-zones --query 'AvailabilityZones[1].ZoneName' --output text) if [ -z "$AZ1" ] || [ -z "$AZ2" ]; then handle_error "Failed to get Availability Zones" fi echo "Using Availability Zones: $AZ1 and $AZ2" # Create public subnets echo "Creating public subnet in $AZ1..." PUBLIC_SUBNET_AZ1=$(aws ec2 create-subnet \ --vpc-id "$VPC_ID" \ --cidr-block 10.0.0.0/24 \ --availability-zone "$AZ1" \ --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Public-Subnet-AZ1}]' \ --query 'Subnet.SubnetId' \ --output text) if [ -z "$PUBLIC_SUBNET_AZ1" ]; then handle_error "Failed to create public subnet in AZ1" fi CREATED_RESOURCES+=("SUBNET:$PUBLIC_SUBNET_AZ1") echo "Public subnet created in $AZ1 with ID: $PUBLIC_SUBNET_AZ1" echo "Creating public subnet in $AZ2..." PUBLIC_SUBNET_AZ2=$(aws ec2 create-subnet \ --vpc-id "$VPC_ID" \ --cidr-block 10.0.1.0/24 \ --availability-zone "$AZ2" \ --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Public-Subnet-AZ2}]' \ --query 'Subnet.SubnetId' \ --output text) if [ -z "$PUBLIC_SUBNET_AZ2" ]; then handle_error "Failed to create public subnet in AZ2" fi CREATED_RESOURCES+=("SUBNET:$PUBLIC_SUBNET_AZ2") echo "Public subnet created in $AZ2 with ID: $PUBLIC_SUBNET_AZ2" # Create private subnets echo "Creating private subnet in $AZ1..." PRIVATE_SUBNET_AZ1=$(aws ec2 create-subnet \ --vpc-id "$VPC_ID" \ --cidr-block 10.0.2.0/24 \ --availability-zone "$AZ1" \ --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Private-Subnet-AZ1}]' \ --query 'Subnet.SubnetId' \ --output text) if [ -z "$PRIVATE_SUBNET_AZ1" ]; then handle_error "Failed to create private subnet in AZ1" fi CREATED_RESOURCES+=("SUBNET:$PRIVATE_SUBNET_AZ1") echo "Private subnet created in $AZ1 with ID: $PRIVATE_SUBNET_AZ1" echo "Creating private subnet in $AZ2..." PRIVATE_SUBNET_AZ2=$(aws ec2 create-subnet \ --vpc-id "$VPC_ID" \ --cidr-block 10.0.3.0/24 \ --availability-zone "$AZ2" \ --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Private-Subnet-AZ2}]' \ --query 'Subnet.SubnetId' \ --output text) if [ -z "$PRIVATE_SUBNET_AZ2" ]; then handle_error "Failed to create private subnet in AZ2" fi CREATED_RESOURCES+=("SUBNET:$PRIVATE_SUBNET_AZ2") echo "Private subnet created in $AZ2 with ID: $PRIVATE_SUBNET_AZ2" # Create Internet Gateway echo "Creating Internet Gateway..." IGW_ID=$(aws ec2 create-internet-gateway \ --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=MyIGW}]' \ --query 'InternetGateway.InternetGatewayId' \ --output text) if [ -z "$IGW_ID" ]; then handle_error "Failed to create Internet Gateway" fi CREATED_RESOURCES+=("INTERNET_GATEWAY:$IGW_ID") echo "Internet Gateway created with ID: $IGW_ID" # Attach Internet Gateway to VPC echo "Attaching Internet Gateway to VPC..." aws ec2 attach-internet-gateway --internet-gateway-id "$IGW_ID" --vpc-id "$VPC_ID" || handle_error "Failed to attach Internet Gateway to VPC" # Create public route table echo "Creating public route table..." PUBLIC_RT=$(aws ec2 create-route-table \ --vpc-id "$VPC_ID" \ --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=Public-RT}]' \ --query 'RouteTable.RouteTableId' \ --output text) if [ -z "$PUBLIC_RT" ]; then handle_error "Failed to create public route table" fi CREATED_RESOURCES+=("ROUTE_TABLE:$PUBLIC_RT") echo "Public route table created with ID: $PUBLIC_RT" # Add route to Internet Gateway echo "Adding route to Internet Gateway in public route table..." aws ec2 create-route --route-table-id "$PUBLIC_RT" --destination-cidr-block 0.0.0.0/0 --gateway-id "$IGW_ID" || handle_error "Failed to add route to Internet Gateway" # Associate public subnets with public route table echo "Associating public subnet in $AZ1 with public route table..." PUBLIC_RT_ASSOC_1=$(aws ec2 associate-route-table --route-table-id "$PUBLIC_RT" --subnet-id "$PUBLIC_SUBNET_AZ1" --query 'AssociationId' --output text) if [ -z "$PUBLIC_RT_ASSOC_1" ]; then handle_error "Failed to associate public subnet in AZ1 with public route table" fi CREATED_RESOURCES+=("ROUTE_TABLE_ASSOCIATION:$PUBLIC_RT_ASSOC_1") echo "Associating public subnet in $AZ2 with public route table..." PUBLIC_RT_ASSOC_2=$(aws ec2 associate-route-table --route-table-id "$PUBLIC_RT" --subnet-id "$PUBLIC_SUBNET_AZ2" --query 'AssociationId' --output text) if [ -z "$PUBLIC_RT_ASSOC_2" ]; then handle_error "Failed to associate public subnet in AZ2 with public route table" fi CREATED_RESOURCES+=("ROUTE_TABLE_ASSOCIATION:$PUBLIC_RT_ASSOC_2") # Create private route table echo "Creating private route table..." PRIVATE_RT=$(aws ec2 create-route-table \ --vpc-id "$VPC_ID" \ --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=Private-RT}]' \ --query 'RouteTable.RouteTableId' \ --output text) if [ -z "$PRIVATE_RT" ]; then handle_error "Failed to create private route table" fi CREATED_RESOURCES+=("ROUTE_TABLE:$PRIVATE_RT") echo "Private route table created with ID: $PRIVATE_RT" # Associate private subnets with private route table echo "Associating private subnet in $AZ1 with private route table..." PRIVATE_RT_ASSOC_1=$(aws ec2 associate-route-table --route-table-id "$PRIVATE_RT" --subnet-id "$PRIVATE_SUBNET_AZ1" --query 'AssociationId' --output text) if [ -z "$PRIVATE_RT_ASSOC_1" ]; then handle_error "Failed to associate private subnet in AZ1 with private route table" fi CREATED_RESOURCES+=("ROUTE_TABLE_ASSOCIATION:$PRIVATE_RT_ASSOC_1") echo "Associating private subnet in $AZ2 with private route table..." PRIVATE_RT_ASSOC_2=$(aws ec2 associate-route-table --route-table-id "$PRIVATE_RT" --subnet-id "$PRIVATE_SUBNET_AZ2" --query 'AssociationId' --output text) if [ -z "$PRIVATE_RT_ASSOC_2" ]; then handle_error "Failed to associate private subnet in AZ2 with private route table" fi CREATED_RESOURCES+=("ROUTE_TABLE_ASSOCIATION:$PRIVATE_RT_ASSOC_2") # Allocate Elastic IP for NAT Gateway echo "Allocating Elastic IP for NAT Gateway..." EIP_ALLOC=$(aws ec2 allocate-address --domain vpc --query 'AllocationId' --output text) if [ -z "$EIP_ALLOC" ]; then handle_error "Failed to allocate Elastic IP" fi CREATED_RESOURCES+=("EIP:$EIP_ALLOC") echo "Elastic IP allocated with ID: $EIP_ALLOC" # Create NAT Gateway echo "Creating NAT Gateway in public subnet in $AZ1..." NAT_GW=$(aws ec2 create-nat-gateway \ --subnet-id "$PUBLIC_SUBNET_AZ1" \ --allocation-id "$EIP_ALLOC" \ --tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=MyNATGateway}]' \ --query 'NatGateway.NatGatewayId' \ --output text) if [ -z "$NAT_GW" ]; then handle_error "Failed to create NAT Gateway" fi CREATED_RESOURCES+=("NAT_GATEWAY:$NAT_GW") echo "NAT Gateway created with ID: $NAT_GW" # Wait for NAT Gateway to be available echo "Waiting for NAT Gateway to be available..." aws ec2 wait nat-gateway-available --nat-gateway-ids "$NAT_GW" || handle_error "NAT Gateway did not become available" # Add route to NAT Gateway in private route table echo "Adding route to NAT Gateway in private route table..." aws ec2 create-route --route-table-id "$PRIVATE_RT" --destination-cidr-block 0.0.0.0/0 --nat-gateway-id "$NAT_GW" || handle_error "Failed to add route to NAT Gateway" # Enable auto-assign public IP for instances in public subnets echo "Enabling auto-assign public IP for instances in public subnet in $AZ1..." aws ec2 modify-subnet-attribute --subnet-id "$PUBLIC_SUBNET_AZ1" --map-public-ip-on-launch || handle_error "Failed to enable auto-assign public IP for public subnet in AZ1" echo "Enabling auto-assign public IP for instances in public subnet in $AZ2..." aws ec2 modify-subnet-attribute --subnet-id "$PUBLIC_SUBNET_AZ2" --map-public-ip-on-launch || handle_error "Failed to enable auto-assign public IP for public subnet in AZ2" # Create security group for web servers echo "Creating security group for web servers..." WEB_SG=$(aws ec2 create-security-group \ --group-name "WebServerSG-$(date +%s)" \ --description "Security group for web servers" \ --vpc-id "$VPC_ID" \ --query 'GroupId' \ --output text) if [ -z "$WEB_SG" ]; then handle_error "Failed to create security group for web servers" fi CREATED_RESOURCES+=("SECURITY_GROUP:$WEB_SG") echo "Security group for web servers created with ID: $WEB_SG" # Allow HTTP and HTTPS traffic echo "Allowing HTTP traffic to web servers security group..." aws ec2 authorize-security-group-ingress --group-id "$WEB_SG" --protocol tcp --port 80 --cidr 0.0.0.0/0 || handle_error "Failed to allow HTTP traffic" echo "Allowing HTTPS traffic to web servers security group..." aws ec2 authorize-security-group-ingress --group-id "$WEB_SG" --protocol tcp --port 443 --cidr 0.0.0.0/0 || handle_error "Failed to allow HTTPS traffic" # Note: In a production environment, you should restrict the source IP ranges for security echo "NOTE: In a production environment, you should restrict the source IP ranges for HTTP and HTTPS traffic" # Create security group for database servers echo "Creating security group for database servers..." DB_SG=$(aws ec2 create-security-group \ --group-name "DBServerSG-$(date +%s)" \ --description "Security group for database servers" \ --vpc-id "$VPC_ID" \ --query 'GroupId' \ --output text) if [ -z "$DB_SG" ]; then handle_error "Failed to create security group for database servers" fi CREATED_RESOURCES+=("SECURITY_GROUP:$DB_SG") echo "Security group for database servers created with ID: $DB_SG" # Allow MySQL/Aurora traffic from web servers only echo "Allowing MySQL/Aurora traffic from web servers to database servers..." aws ec2 authorize-security-group-ingress --group-id "$DB_SG" --protocol tcp --port 3306 --source-group "$WEB_SG" || handle_error "Failed to allow MySQL/Aurora traffic" # Verify VPC configuration echo "Verifying VPC configuration..." echo "VPC:" aws ec2 describe-vpcs --vpc-id "$VPC_ID" || handle_error "Failed to describe VPC" echo "Subnets:" aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" || handle_error "Failed to describe subnets" echo "Route tables:" aws ec2 describe-route-tables --filters "Name=vpc-id,Values=$VPC_ID" || handle_error "Failed to describe route tables" echo "Internet gateway:" aws ec2 describe-internet-gateways --filters "Name=attachment.vpc-id,Values=$VPC_ID" || handle_error "Failed to describe Internet Gateway" echo "NAT gateway:" aws ec2 describe-nat-gateways --filter "Name=vpc-id,Values=$VPC_ID" || handle_error "Failed to describe NAT Gateway" echo "Security groups:" aws ec2 describe-security-groups --filters "Name=vpc-id,Values=$VPC_ID" || handle_error "Failed to describe security groups" echo "" # Summary of created resources echo "VPC creation completed successfully!" echo "Summary of created resources:" echo "- VPC: $VPC_ID" echo "- Public Subnet in $AZ1: $PUBLIC_SUBNET_AZ1" echo "- Public Subnet in $AZ2: $PUBLIC_SUBNET_AZ2" echo "- Private Subnet in $AZ1: $PRIVATE_SUBNET_AZ1" echo "- Private Subnet in $AZ2: $PRIVATE_SUBNET_AZ2" echo "- Internet Gateway: $IGW_ID" echo "- Public Route Table: $PUBLIC_RT" echo "- Private Route Table: $PRIVATE_RT" echo "- Elastic IP: $EIP_ALLOC" echo "- NAT Gateway: $NAT_GW" echo "- Web Servers Security Group: $WEB_SG" echo "- Database Servers Security Group: $DB_SG" # Deploy EC2 instances echo "" echo "Deploying EC2 instances..." # Create key pair for SSH access KEY_NAME="vpc-tutorial-key-$(date +%s)" echo "Creating key pair $KEY_NAME..." aws ec2 create-key-pair --key-name "$KEY_NAME" --query 'KeyMaterial' --output text > "${KEY_NAME}.pem" || handle_error "Failed to create key pair" chmod 400 "${KEY_NAME}.pem" echo "Key pair saved to ${KEY_NAME}.pem" CREATED_RESOURCES+=("KEY_PAIR:$KEY_NAME") # Get latest Amazon Linux 2 AMI echo "Getting latest Amazon Linux 2 AMI..." AMI_ID=$(aws ec2 describe-images --owners amazon \ --filters "Name=name,Values=amzn2-ami-hvm-*-x86_64-gp2" "Name=state,Values=available" \ --query "sort_by(Images, &CreationDate)[-1].ImageId" --output text) || handle_error "Failed to get AMI" echo "Using AMI: $AMI_ID" # Launch web server in public subnet echo "Launching web server in public subnet..." WEB_INSTANCE=$(aws ec2 run-instances \ --image-id "$AMI_ID" \ --count 1 \ --instance-type t2.micro \ --key-name "$KEY_NAME" \ --security-group-ids "$WEB_SG" \ --subnet-id "$PUBLIC_SUBNET_AZ1" \ --associate-public-ip-address \ --user-data '#!/bin/bash yum update -y yum install -y httpd systemctl start httpd systemctl enable httpd echo "<h1>Hello from $(hostname -f) in the public subnet</h1>" > /var/www/html/index.html' \ --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=WebServer}]' \ --query 'Instances[0].InstanceId' \ --output text) || handle_error "Failed to launch web server" echo "Web server instance created with ID: $WEB_INSTANCE" CREATED_RESOURCES+=("INSTANCE:$WEB_INSTANCE") # Wait for web server to be running echo "Waiting for web server to be running..." aws ec2 wait instance-running --instance-ids "$WEB_INSTANCE" # Get web server public IP WEB_PUBLIC_IP=$(aws ec2 describe-instances --instance-ids "$WEB_INSTANCE" \ --query 'Reservations[0].Instances[0].PublicIpAddress' --output text) echo "Web server public IP: $WEB_PUBLIC_IP" echo "You can access the web server at: http://$WEB_PUBLIC_IP" # Launch database server in private subnet echo "Launching database server in private subnet..." DB_INSTANCE=$(aws ec2 run-instances \ --image-id "$AMI_ID" \ --count 1 \ --instance-type t2.micro \ --key-name "$KEY_NAME" \ --security-group-ids "$DB_SG" \ --subnet-id "$PRIVATE_SUBNET_AZ1" \ --user-data '#!/bin/bash yum update -y yum install -y mariadb-server systemctl start mariadb systemctl enable mariadb' \ --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=DBServer}]' \ --query 'Instances[0].InstanceId' \ --output text) || handle_error "Failed to launch database server" echo "Database server instance created with ID: $DB_INSTANCE" CREATED_RESOURCES+=("INSTANCE:$DB_INSTANCE") # Wait for database server to be running echo "Waiting for database server to be running..." aws ec2 wait instance-running --instance-ids "$DB_INSTANCE" # Get database server private IP DB_PRIVATE_IP=$(aws ec2 describe-instances --instance-ids "$DB_INSTANCE" \ --query 'Reservations[0].Instances[0].PrivateIpAddress' --output text) echo "Database server private IP: $DB_PRIVATE_IP" echo "EC2 instances deployed successfully!" echo "- Web Server (Public): $WEB_INSTANCE ($WEB_PUBLIC_IP)" echo "- Database Server (Private): $DB_INSTANCE ($DB_PRIVATE_IP)" echo "" echo "Note: To connect to the web server: ssh -i ${KEY_NAME}.pem ec2-user@$WEB_PUBLIC_IP" echo "To connect to the database server, you must first connect to the web server, then use it as a bastion host." 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 echo "Cleaning up resources..." cleanup_resources echo "All resources have been cleaned up." else echo "Resources will not be cleaned up. You can manually clean them up later." fi echo "Script completed at $(date)"-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create the cluster
Create a task definition
Create the service
Clean up
- 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 ECS Fargate Tutorial Script - Version 5 # This script creates an ECS cluster, task definition, and service using Fargate launch type # Fixed version with proper resource dependency handling during cleanup set -e # Exit on any error # Initialize logging LOG_FILE="ecs-fargate-tutorial-v5.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting Amazon ECS Fargate tutorial at $(date)" echo "Log file: $LOG_FILE" # Generate random identifier for unique resource names RANDOM_ID=$(openssl rand -hex 6) CLUSTER_NAME="fargate-cluster-${RANDOM_ID}" SERVICE_NAME="fargate-service-${RANDOM_ID}" TASK_FAMILY="sample-fargate-${RANDOM_ID}" SECURITY_GROUP_NAME="ecs-fargate-sg-${RANDOM_ID}" # Array to track created resources for cleanup CREATED_RESOURCES=() # Function to log and execute commands execute_command() { local cmd="$1" local description="$2" echo "" echo "==========================================" echo "EXECUTING: $description" echo "COMMAND: $cmd" echo "==========================================" local output local exit_code set +e # Temporarily disable exit on error output=$(eval "$cmd" 2>&1) exit_code=$? set -e # Re-enable exit on error if [[ $exit_code -eq 0 ]]; then echo "SUCCESS: $description" echo "OUTPUT: $output" return 0 else echo "FAILED: $description" echo "EXIT CODE: $exit_code" echo "OUTPUT: $output" return 1 fi } # Function to check for actual AWS API errors in command output check_for_aws_errors() { local output="$1" local description="$2" # Look for specific AWS error patterns, not just the word "error" if echo "$output" | grep -qi "An error occurred\|InvalidParameter\|AccessDenied\|ResourceNotFound\|ValidationException"; then echo "AWS API ERROR detected in output for: $description" echo "Output: $output" return 1 fi return 0 } # Function to wait for network interfaces to be cleaned up wait_for_network_interfaces_cleanup() { local security_group_id="$1" local max_attempts=30 local attempt=1 echo "Waiting for network interfaces to be cleaned up..." while [[ $attempt -le $max_attempts ]]; do echo "Attempt $attempt/$max_attempts: Checking for dependent network interfaces..." # Check if there are any network interfaces still using this security group local eni_count eni_count=$(aws ec2 describe-network-interfaces \ --filters "Name=group-id,Values=$security_group_id" \ --query "length(NetworkInterfaces)" \ --output text 2>/dev/null || echo "0") if [[ "$eni_count" == "0" ]]; then echo "No network interfaces found using security group $security_group_id" return 0 else echo "Found $eni_count network interface(s) still using security group $security_group_id" echo "Waiting 10 seconds before next check..." sleep 10 ((attempt++)) fi done echo "WARNING: Network interfaces may still be attached after $max_attempts attempts" echo "This is normal and the security group deletion will be retried" return 1 } # Function to retry security group deletion with exponential backoff retry_security_group_deletion() { local security_group_id="$1" local max_attempts=10 local attempt=1 local wait_time=5 while [[ $attempt -le $max_attempts ]]; do echo "Attempt $attempt/$max_attempts: Trying to delete security group $security_group_id" if execute_command "aws ec2 delete-security-group --group-id $security_group_id" "Delete security group (attempt $attempt)"; then echo "Successfully deleted security group $security_group_id" return 0 else if [[ $attempt -eq $max_attempts ]]; then echo "FAILED: Could not delete security group $security_group_id after $max_attempts attempts" echo "This may be due to network interfaces that are still being cleaned up by AWS" echo "You can manually delete it later using: aws ec2 delete-security-group --group-id $security_group_id" return 1 else echo "Waiting $wait_time seconds before retry..." sleep $wait_time wait_time=$((wait_time * 2)) # Exponential backoff ((attempt++)) fi fi done } # Function to cleanup resources with proper dependency handling cleanup_resources() { echo "" echo "===========================================" echo "CLEANUP PROCESS" echo "===========================================" echo "The following resources were created:" for resource in "${CREATED_RESOURCES[@]}"; do echo " - $resource" done echo "" echo "Do you want to clean up all created resources? (y/n): " read -r CLEANUP_CHOICE if [[ "$CLEANUP_CHOICE" =~ ^[Yy]$ ]]; then echo "Starting cleanup process..." # Step 1: Scale service to 0 tasks first, then delete service if [[ " ${CREATED_RESOURCES[*]} " =~ " ECS Service: $SERVICE_NAME " ]]; then echo "" echo "Step 1: Scaling service to 0 tasks..." if execute_command "aws ecs update-service --cluster $CLUSTER_NAME --service $SERVICE_NAME --desired-count 0" "Scale service to 0 tasks"; then echo "Waiting for service to stabilize after scaling to 0..." execute_command "aws ecs wait services-stable --cluster $CLUSTER_NAME --services $SERVICE_NAME" "Wait for service to stabilize" echo "Deleting service..." execute_command "aws ecs delete-service --cluster $CLUSTER_NAME --service $SERVICE_NAME" "Delete ECS service" else echo "WARNING: Failed to scale service. Attempting to delete anyway..." execute_command "aws ecs delete-service --cluster $CLUSTER_NAME --service $SERVICE_NAME --force" "Force delete ECS service" fi fi # Step 2: Wait a bit for tasks to fully terminate echo "" echo "Step 2: Waiting for tasks to fully terminate..." sleep 15 # Step 3: Delete cluster if [[ " ${CREATED_RESOURCES[*]} " =~ " ECS Cluster: $CLUSTER_NAME " ]]; then echo "" echo "Step 3: Deleting cluster..." execute_command "aws ecs delete-cluster --cluster $CLUSTER_NAME" "Delete ECS cluster" fi # Step 4: Wait for network interfaces to be cleaned up, then delete security group if [[ -n "$SECURITY_GROUP_ID" ]]; then echo "" echo "Step 4: Cleaning up security group..." # First, wait for network interfaces to be cleaned up wait_for_network_interfaces_cleanup "$SECURITY_GROUP_ID" # Then retry security group deletion with backoff retry_security_group_deletion "$SECURITY_GROUP_ID" fi # Step 5: Clean up task definition (deregister all revisions) if [[ " ${CREATED_RESOURCES[*]} " =~ " Task Definition: $TASK_FAMILY " ]]; then echo "" echo "Step 5: Deregistering task definition revisions..." # Get all revisions of the task definition local revisions revisions=$(aws ecs list-task-definitions --family-prefix "$TASK_FAMILY" --query "taskDefinitionArns" --output text 2>/dev/null || echo "") if [[ -n "$revisions" && "$revisions" != "None" ]]; then for revision_arn in $revisions; do echo "Deregistering task definition: $revision_arn" execute_command "aws ecs deregister-task-definition --task-definition $revision_arn" "Deregister task definition $revision_arn" || true done else echo "No task definition revisions found to deregister" fi fi echo "" echo "===========================================" echo "CLEANUP COMPLETED" echo "===========================================" echo "All resources have been cleaned up successfully!" else echo "Cleanup skipped. Resources remain active." echo "" echo "To clean up manually later, use the following commands in order:" echo "1. Scale service to 0: aws ecs update-service --cluster $CLUSTER_NAME --service $SERVICE_NAME --desired-count 0" echo "2. Wait for stability: aws ecs wait services-stable --cluster $CLUSTER_NAME --services $SERVICE_NAME" echo "3. Delete service: aws ecs delete-service --cluster $CLUSTER_NAME --service $SERVICE_NAME" echo "4. Delete cluster: aws ecs delete-cluster --cluster $CLUSTER_NAME" echo "5. Wait 2-3 minutes, then delete security group: aws ec2 delete-security-group --group-id $SECURITY_GROUP_ID" if [[ " ${CREATED_RESOURCES[*]} " =~ " Task Definition: $TASK_FAMILY " ]]; then echo "6. Deregister task definitions: aws ecs list-task-definitions --family-prefix $TASK_FAMILY" echo " Then for each ARN: aws ecs deregister-task-definition --task-definition <ARN>" fi fi } # Trap to handle script interruption trap cleanup_resources EXIT echo "Using random identifier: $RANDOM_ID" echo "Cluster name: $CLUSTER_NAME" echo "Service name: $SERVICE_NAME" echo "Task family: $TASK_FAMILY" # Step 1: Ensure ECS task execution role exists echo "" echo "===========================================" echo "STEP 1: VERIFY ECS TASK EXECUTION ROLE" echo "===========================================" ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) EXECUTION_ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/ecsTaskExecutionRole" # Check if role exists if aws iam get-role --role-name ecsTaskExecutionRole >/dev/null 2>&1; then echo "ECS task execution role already exists" else echo "Creating ECS task execution role..." # Create trust policy cat > trust-policy.json << 'EOF' { "Version":"2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ecs-tasks.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF execute_command "aws iam create-role --role-name ecsTaskExecutionRole --assume-role-policy-document file://trust-policy.json" "Create ECS task execution role" execute_command "aws iam attach-role-policy --role-name ecsTaskExecutionRole --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" "Attach ECS task execution policy" # Clean up temporary file rm -f trust-policy.json CREATED_RESOURCES+=("IAM Role: ecsTaskExecutionRole") fi # Step 2: Create ECS cluster echo "" echo "===========================================" echo "STEP 2: CREATE ECS CLUSTER" echo "===========================================" CLUSTER_OUTPUT=$(execute_command "aws ecs create-cluster --cluster-name $CLUSTER_NAME" "Create ECS cluster") check_for_aws_errors "$CLUSTER_OUTPUT" "Create ECS cluster" CREATED_RESOURCES+=("ECS Cluster: $CLUSTER_NAME") # Step 3: Create task definition echo "" echo "===========================================" echo "STEP 3: CREATE TASK DEFINITION" echo "===========================================" # Create task definition JSON cat > task-definition.json << EOF { "family": "$TASK_FAMILY", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "256", "memory": "512", "executionRoleArn": "$EXECUTION_ROLE_ARN", "containerDefinitions": [ { "name": "fargate-app", "image": "public.ecr.aws/docker/library/httpd:latest", "portMappings": [ { "containerPort": 80, "hostPort": 80, "protocol": "tcp" } ], "essential": true, "entryPoint": ["sh", "-c"], "command": [ "/bin/sh -c \"echo '<html> <head> <title>Amazon ECS Sample App</title> <style>body {margin-top: 40px; background-color: #333;} </style> </head><body> <div style=color:white;text-align:center> <h1>Amazon ECS Sample App</h1> <h2>Congratulations!</h2> <p>Your application is now running on a container in Amazon ECS.</p> </div></body></html>' > /usr/local/apache2/htdocs/index.html && httpd-foreground\"" ] } ] } EOF TASK_DEF_OUTPUT=$(execute_command "aws ecs register-task-definition --cli-input-json file://task-definition.json" "Register task definition") check_for_aws_errors "$TASK_DEF_OUTPUT" "Register task definition" # Clean up temporary file rm -f task-definition.json CREATED_RESOURCES+=("Task Definition: $TASK_FAMILY") # Step 4: Set up networking echo "" echo "===========================================" echo "STEP 4: SET UP NETWORKING" echo "===========================================" # Get default VPC ID VPC_ID=$(aws ec2 describe-vpcs --filters "Name=is-default,Values=true" --query "Vpcs[0].VpcId" --output text) if [[ "$VPC_ID" == "None" || -z "$VPC_ID" ]]; then echo "ERROR: No default VPC found. Please create a default VPC or specify a custom VPC." exit 1 fi echo "Using default VPC: $VPC_ID" # Create security group with restricted access # Note: This allows HTTP access from anywhere for demo purposes # In production, restrict source to specific IP ranges or security groups SECURITY_GROUP_OUTPUT=$(execute_command "aws ec2 create-security-group --group-name $SECURITY_GROUP_NAME --description 'Security group for ECS Fargate tutorial - HTTP access' --vpc-id $VPC_ID" "Create security group") check_for_aws_errors "$SECURITY_GROUP_OUTPUT" "Create security group" SECURITY_GROUP_ID=$(echo "$SECURITY_GROUP_OUTPUT" | grep -o '"GroupId": "[^"]*"' | cut -d'"' -f4) if [[ -z "$SECURITY_GROUP_ID" ]]; then SECURITY_GROUP_ID=$(aws ec2 describe-security-groups --group-names "$SECURITY_GROUP_NAME" --query "SecurityGroups[0].GroupId" --output text) fi echo "Created security group: $SECURITY_GROUP_ID" CREATED_RESOURCES+=("Security Group: $SECURITY_GROUP_ID") # Add HTTP inbound rule # WARNING: This allows HTTP access from anywhere (0.0.0.0/0) # In production environments, restrict this to specific IP ranges execute_command "aws ec2 authorize-security-group-ingress --group-id $SECURITY_GROUP_ID --protocol tcp --port 80 --cidr 0.0.0.0/0" "Add HTTP inbound rule to security group" # Get subnet IDs from default VPC echo "Getting subnet IDs from default VPC..." SUBNET_IDS_RAW=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" --query "Subnets[*].SubnetId" --output text) if [[ -z "$SUBNET_IDS_RAW" ]]; then echo "ERROR: No subnets found in default VPC" exit 1 fi # Convert to proper comma-separated format, handling both spaces and tabs SUBNET_IDS_COMMA=$(echo "$SUBNET_IDS_RAW" | tr -s '[:space:]' ',' | sed 's/,$//') echo "Raw subnet IDs: $SUBNET_IDS_RAW" echo "Formatted subnet IDs: $SUBNET_IDS_COMMA" # Validate subnet IDs format if [[ ! "$SUBNET_IDS_COMMA" =~ ^subnet-[a-z0-9]+(,subnet-[a-z0-9]+)*$ ]]; then echo "ERROR: Invalid subnet ID format: $SUBNET_IDS_COMMA" exit 1 fi # Step 5: Create ECS service echo "" echo "===========================================" echo "STEP 5: CREATE ECS SERVICE" echo "===========================================" # Create the service with proper JSON formatting for network configuration SERVICE_CMD="aws ecs create-service --cluster $CLUSTER_NAME --service-name $SERVICE_NAME --task-definition $TASK_FAMILY --desired-count 1 --launch-type FARGATE --network-configuration '{\"awsvpcConfiguration\":{\"subnets\":[\"$(echo $SUBNET_IDS_COMMA | sed 's/,/","/g')\"],\"securityGroups\":[\"$SECURITY_GROUP_ID\"],\"assignPublicIp\":\"ENABLED\"}}'" echo "Service creation command: $SERVICE_CMD" SERVICE_OUTPUT=$(execute_command "$SERVICE_CMD" "Create ECS service") check_for_aws_errors "$SERVICE_OUTPUT" "Create ECS service" CREATED_RESOURCES+=("ECS Service: $SERVICE_NAME") # Step 6: Wait for service to stabilize and get public IP echo "" echo "===========================================" echo "STEP 6: WAIT FOR SERVICE AND GET PUBLIC IP" echo "===========================================" echo "Waiting for service to stabilize (this may take a few minutes)..." execute_command "aws ecs wait services-stable --cluster $CLUSTER_NAME --services $SERVICE_NAME" "Wait for service to stabilize" # Get task ARN TASK_ARN=$(aws ecs list-tasks --cluster $CLUSTER_NAME --service-name $SERVICE_NAME --query "taskArns[0]" --output text) if [[ "$TASK_ARN" == "None" || -z "$TASK_ARN" ]]; then echo "ERROR: No running tasks found for service" exit 1 fi echo "Task ARN: $TASK_ARN" # Get network interface ID ENI_ID=$(aws ecs describe-tasks --cluster $CLUSTER_NAME --tasks $TASK_ARN --query "tasks[0].attachments[0].details[?name=='networkInterfaceId'].value" --output text) if [[ "$ENI_ID" == "None" || -z "$ENI_ID" ]]; then echo "ERROR: Could not retrieve network interface ID" exit 1 fi echo "Network Interface ID: $ENI_ID" # Get public IP PUBLIC_IP=$(aws ec2 describe-network-interfaces --network-interface-ids $ENI_ID --query "NetworkInterfaces[0].Association.PublicIp" --output text) if [[ "$PUBLIC_IP" == "None" || -z "$PUBLIC_IP" ]]; then echo "WARNING: No public IP assigned to the task" echo "The task may be in a private subnet or public IP assignment failed" else echo "" echo "===========================================" echo "SUCCESS! APPLICATION IS RUNNING" echo "===========================================" echo "Your application is available at: http://$PUBLIC_IP" echo "You can test it by opening this URL in your browser" echo "" fi # Display service information echo "" echo "===========================================" echo "SERVICE INFORMATION" echo "===========================================" execute_command "aws ecs describe-services --cluster $CLUSTER_NAME --services $SERVICE_NAME" "Get service details" echo "" echo "===========================================" echo "TUTORIAL COMPLETED SUCCESSFULLY" echo "===========================================" echo "Resources created:" for resource in "${CREATED_RESOURCES[@]}"; do echo " - $resource" done if [[ -n "$PUBLIC_IP" && "$PUBLIC_IP" != "None" ]]; then echo "" echo "Application URL: http://$PUBLIC_IP" fi echo "" echo "Script completed at $(date)"-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create an ECS cluster
Create and monitor a service
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 # ECS EC2 Launch Type Tutorial Script - UPDATED VERSION # This script demonstrates creating an ECS cluster, launching a container instance, # registering a task definition, and creating a service using the EC2 launch type. # Updated to match the tutorial draft with nginx web server and service creation. # # - UPDATED: Changed from sleep task to nginx web server with service set -e # Exit on any error # Configuration SCRIPT_NAME="ecs-ec2-tutorial" LOG_FILE="${SCRIPT_NAME}-$(date +%Y%m%d-%H%M%S).log" CLUSTER_NAME="tutorial-cluster-$(openssl rand -hex 4)" TASK_FAMILY="nginx-task-$(openssl rand -hex 4)" SERVICE_NAME="nginx-service-$(openssl rand -hex 4)" KEY_PAIR_NAME="ecs-tutorial-key-$(openssl rand -hex 4)" SECURITY_GROUP_NAME="ecs-tutorial-sg-$(openssl rand -hex 4)" # Get current AWS region dynamically AWS_REGION=$(aws configure get region || echo "us-east-1") # Resource tracking arrays CREATED_RESOURCES=() CLEANUP_ORDER=() # Logging function log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } # Error handling function handle_error() { local exit_code=$? log "ERROR: Script failed with exit code $exit_code" log "ERROR: Last command: $BASH_COMMAND" echo "" echo "===========================================" echo "ERROR OCCURRED - ATTEMPTING CLEANUP" echo "===========================================" echo "Resources created before error:" for resource in "${CREATED_RESOURCES[@]}"; do echo " - $resource" done cleanup_resources exit $exit_code } # Set error trap trap handle_error ERR # FIXED: Enhanced cleanup function with proper error handling and logging cleanup_resources() { log "Starting cleanup process..." local cleanup_errors=0 # Delete service first (this will stop tasks automatically) if [[ -n "${SERVICE_ARN:-}" ]]; then log "Updating service to desired count 0: $SERVICE_NAME" if ! aws ecs update-service --cluster "$CLUSTER_NAME" --service "$SERVICE_NAME" --desired-count 0 2>>"$LOG_FILE"; then log "WARNING: Failed to update service desired count to 0" ((cleanup_errors++)) else log "Waiting for service tasks to stop..." sleep 30 # Give time for tasks to stop fi log "Deleting service: $SERVICE_NAME" if ! aws ecs delete-service --cluster "$CLUSTER_NAME" --service "$SERVICE_NAME" 2>>"$LOG_FILE"; then log "WARNING: Failed to delete service $SERVICE_NAME" ((cleanup_errors++)) fi fi # Stop and delete any remaining tasks if [[ -n "${TASK_ARN:-}" ]]; then log "Stopping task: $TASK_ARN" if ! aws ecs stop-task --cluster "$CLUSTER_NAME" --task "$TASK_ARN" --reason "Tutorial cleanup" 2>>"$LOG_FILE"; then log "WARNING: Failed to stop task $TASK_ARN" ((cleanup_errors++)) else log "Waiting for task to stop..." if ! aws ecs wait tasks-stopped --cluster "$CLUSTER_NAME" --tasks "$TASK_ARN" 2>>"$LOG_FILE"; then log "WARNING: Task stop wait failed for $TASK_ARN" ((cleanup_errors++)) fi fi fi # Deregister task definition if [[ -n "${TASK_DEFINITION_ARN:-}" ]]; then log "Deregistering task definition: $TASK_DEFINITION_ARN" if ! aws ecs deregister-task-definition --task-definition "$TASK_DEFINITION_ARN" 2>>"$LOG_FILE"; then log "WARNING: Failed to deregister task definition $TASK_DEFINITION_ARN" ((cleanup_errors++)) fi fi # Terminate EC2 instance if [[ -n "${INSTANCE_ID:-}" ]]; then log "Terminating EC2 instance: $INSTANCE_ID" if ! aws ec2 terminate-instances --instance-ids "$INSTANCE_ID" 2>>"$LOG_FILE"; then log "WARNING: Failed to terminate instance $INSTANCE_ID" ((cleanup_errors++)) else log "Waiting for instance to terminate..." if ! aws ec2 wait instance-terminated --instance-ids "$INSTANCE_ID" 2>>"$LOG_FILE"; then log "WARNING: Instance termination wait failed for $INSTANCE_ID" ((cleanup_errors++)) fi fi fi # Delete security group with retry logic if [[ -n "${SECURITY_GROUP_ID:-}" ]]; then log "Deleting security group: $SECURITY_GROUP_ID" local retry_count=0 local max_retries=3 while [[ $retry_count -lt $max_retries ]]; do if aws ec2 delete-security-group --group-id "$SECURITY_GROUP_ID" 2>>"$LOG_FILE"; then log "Successfully deleted security group" break else ((retry_count++)) if [[ $retry_count -lt $max_retries ]]; then log "Retry $retry_count/$max_retries: Waiting 10 seconds before retrying security group deletion..." sleep 10 else log "ERROR: Failed to delete security group after $max_retries attempts" ((cleanup_errors++)) fi fi done fi # Delete key pair if [[ -n "${KEY_PAIR_NAME:-}" ]]; then log "Deleting key pair: $KEY_PAIR_NAME" if ! aws ec2 delete-key-pair --key-name "$KEY_PAIR_NAME" 2>>"$LOG_FILE"; then log "WARNING: Failed to delete key pair $KEY_PAIR_NAME" ((cleanup_errors++)) fi rm -f "${KEY_PAIR_NAME}.pem" 2>>"$LOG_FILE" || log "WARNING: Failed to remove local key file" fi # Delete ECS cluster if [[ -n "${CLUSTER_NAME:-}" ]]; then log "Deleting ECS cluster: $CLUSTER_NAME" if ! aws ecs delete-cluster --cluster "$CLUSTER_NAME" 2>>"$LOG_FILE"; then log "WARNING: Failed to delete cluster $CLUSTER_NAME" ((cleanup_errors++)) fi fi if [[ $cleanup_errors -eq 0 ]]; then log "Cleanup completed successfully" else log "Cleanup completed with $cleanup_errors warnings/errors. Check log file for details." fi } # Function to check prerequisites check_prerequisites() { log "Checking prerequisites..." # Check AWS CLI if ! command -v aws &> /dev/null; then log "ERROR: AWS CLI is not installed" exit 1 fi # Check AWS credentials if ! aws sts get-caller-identity &> /dev/null; then log "ERROR: AWS credentials not configured" exit 1 fi # Get caller identity CALLER_IDENTITY=$(aws sts get-caller-identity --output text --query 'Account') log "AWS Account: $CALLER_IDENTITY" log "AWS Region: $AWS_REGION" # Check for default VPC DEFAULT_VPC=$(aws ec2 describe-vpcs --filters "Name=is-default,Values=true" --query 'Vpcs[0].VpcId' --output text) if [[ "$DEFAULT_VPC" == "None" ]]; then log "ERROR: No default VPC found. Please create a VPC first." exit 1 fi log "Using default VPC: $DEFAULT_VPC" # Get default subnet DEFAULT_SUBNET=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$DEFAULT_VPC" "Name=default-for-az,Values=true" --query 'Subnets[0].SubnetId' --output text) if [[ "$DEFAULT_SUBNET" == "None" ]]; then log "ERROR: No default subnet found" exit 1 fi log "Using default subnet: $DEFAULT_SUBNET" log "Prerequisites check completed successfully" } # Function to create ECS cluster create_cluster() { log "Creating ECS cluster: $CLUSTER_NAME" CLUSTER_ARN=$(aws ecs create-cluster --cluster-name "$CLUSTER_NAME" --query 'cluster.clusterArn' --output text) if [[ -z "$CLUSTER_ARN" ]]; then log "ERROR: Failed to create cluster" exit 1 fi log "Created cluster: $CLUSTER_ARN" CREATED_RESOURCES+=("ECS Cluster: $CLUSTER_NAME") } # Function to create key pair create_key_pair() { log "Creating EC2 key pair: $KEY_PAIR_NAME" # FIXED: Set secure umask before key creation umask 077 aws ec2 create-key-pair --key-name "$KEY_PAIR_NAME" --query 'KeyMaterial' --output text > "${KEY_PAIR_NAME}.pem" chmod 400 "${KEY_PAIR_NAME}.pem" umask 022 # Reset umask log "Created key pair: $KEY_PAIR_NAME" CREATED_RESOURCES+=("EC2 Key Pair: $KEY_PAIR_NAME") } # Function to create security group create_security_group() { log "Creating security group: $SECURITY_GROUP_NAME" SECURITY_GROUP_ID=$(aws ec2 create-security-group \ --group-name "$SECURITY_GROUP_NAME" \ --description "ECS tutorial security group" \ --vpc-id "$DEFAULT_VPC" \ --query 'GroupId' --output text) if [[ -z "$SECURITY_GROUP_ID" ]]; then log "ERROR: Failed to create security group" exit 1 fi # Add HTTP access rule for nginx web server aws ec2 authorize-security-group-ingress \ --group-id "$SECURITY_GROUP_ID" \ --protocol tcp \ --port 80 \ --cidr "0.0.0.0/0" log "Created security group: $SECURITY_GROUP_ID" log "Added HTTP access on port 80" CREATED_RESOURCES+=("Security Group: $SECURITY_GROUP_ID") } # Function to get ECS optimized AMI get_ecs_ami() { log "Getting ECS-optimized AMI ID..." ECS_AMI_ID=$(aws ssm get-parameters \ --names /aws/service/ecs/optimized-ami/amazon-linux-2/recommended \ --query 'Parameters[0].Value' --output text | jq -r '.image_id') if [[ -z "$ECS_AMI_ID" ]]; then log "ERROR: Failed to get ECS-optimized AMI ID" exit 1 fi log "ECS-optimized AMI ID: $ECS_AMI_ID" } # Function to create IAM role for ECS instance (if it doesn't exist) ensure_ecs_instance_role() { log "Checking for ecsInstanceRole..." if ! aws iam get-role --role-name ecsInstanceRole &> /dev/null; then log "Creating ecsInstanceRole..." # Create trust policy cat > ecs-instance-trust-policy.json << 'EOF' { "Version":"2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF # Create role aws iam create-role \ --role-name ecsInstanceRole \ --assume-role-policy-document file://ecs-instance-trust-policy.json # Attach managed policy aws iam attach-role-policy \ --role-name ecsInstanceRole \ --policy-arn arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role # Create instance profile aws iam create-instance-profile --instance-profile-name ecsInstanceRole # Add role to instance profile aws iam add-role-to-instance-profile \ --instance-profile-name ecsInstanceRole \ --role-name ecsInstanceRole # FIXED: Enhanced wait for role to be ready log "Waiting for IAM role to be ready..." aws iam wait role-exists --role-name ecsInstanceRole sleep 30 # Additional buffer for eventual consistency rm -f ecs-instance-trust-policy.json log "Created ecsInstanceRole" CREATED_RESOURCES+=("IAM Role: ecsInstanceRole") else log "ecsInstanceRole already exists" fi } # Function to launch container instance launch_container_instance() { log "Launching ECS container instance..." # Create user data script cat > ecs-user-data.sh << EOF #!/bin/bash echo ECS_CLUSTER=$CLUSTER_NAME >> /etc/ecs/ecs.config EOF INSTANCE_ID=$(aws ec2 run-instances \ --image-id "$ECS_AMI_ID" \ --instance-type t3.micro \ --key-name "$KEY_PAIR_NAME" \ --security-group-ids "$SECURITY_GROUP_ID" \ --subnet-id "$DEFAULT_SUBNET" \ --iam-instance-profile Name=ecsInstanceRole \ --user-data file://ecs-user-data.sh \ --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=ecs-tutorial-instance}]" \ --query 'Instances[0].InstanceId' --output text) if [[ -z "$INSTANCE_ID" ]]; then log "ERROR: Failed to launch EC2 instance" exit 1 fi log "Launched EC2 instance: $INSTANCE_ID" CREATED_RESOURCES+=("EC2 Instance: $INSTANCE_ID") # Wait for instance to be running log "Waiting for instance to be running..." aws ec2 wait instance-running --instance-ids "$INSTANCE_ID" # Wait for ECS agent to register log "Waiting for ECS agent to register with cluster..." local max_attempts=30 local attempt=0 while [[ $attempt -lt $max_attempts ]]; do CONTAINER_INSTANCES=$(aws ecs list-container-instances --cluster "$CLUSTER_NAME" --query 'containerInstanceArns' --output text) if [[ -n "$CONTAINER_INSTANCES" && "$CONTAINER_INSTANCES" != "None" ]]; then log "Container instance registered successfully" break fi attempt=$((attempt + 1)) log "Waiting for container instance registration... (attempt $attempt/$max_attempts)" sleep 10 done if [[ $attempt -eq $max_attempts ]]; then log "ERROR: Container instance failed to register within expected time" exit 1 fi rm -f ecs-user-data.sh } # Function to register task definition register_task_definition() { log "Creating task definition..." # Create nginx task definition JSON matching the tutorial cat > task-definition.json << EOF { "family": "$TASK_FAMILY", "containerDefinitions": [ { "name": "nginx", "image": "public.ecr.aws/docker/library/nginx:latest", "cpu": 256, "memory": 512, "essential": true, "portMappings": [ { "containerPort": 80, "hostPort": 80, "protocol": "tcp" } ] } ], "requiresCompatibilities": ["EC2"], "networkMode": "bridge" } EOF # FIXED: Validate JSON before registration if ! jq empty task-definition.json 2>/dev/null; then log "ERROR: Invalid JSON in task definition" exit 1 fi TASK_DEFINITION_ARN=$(aws ecs register-task-definition \ --cli-input-json file://task-definition.json \ --query 'taskDefinition.taskDefinitionArn' --output text) if [[ -z "$TASK_DEFINITION_ARN" ]]; then log "ERROR: Failed to register task definition" exit 1 fi log "Registered task definition: $TASK_DEFINITION_ARN" CREATED_RESOURCES+=("Task Definition: $TASK_DEFINITION_ARN") rm -f task-definition.json } # Function to create service create_service() { log "Creating ECS service..." SERVICE_ARN=$(aws ecs create-service \ --cluster "$CLUSTER_NAME" \ --service-name "$SERVICE_NAME" \ --task-definition "$TASK_FAMILY" \ --desired-count 1 \ --query 'service.serviceArn' --output text) if [[ -z "$SERVICE_ARN" ]]; then log "ERROR: Failed to create service" exit 1 fi log "Created service: $SERVICE_ARN" CREATED_RESOURCES+=("ECS Service: $SERVICE_NAME") # Wait for service to be stable log "Waiting for service to be stable..." aws ecs wait services-stable --cluster "$CLUSTER_NAME" --services "$SERVICE_NAME" log "Service is now stable and running" # Get the task ARN for monitoring TASK_ARN=$(aws ecs list-tasks --cluster "$CLUSTER_NAME" --service-name "$SERVICE_NAME" --query 'taskArns[0]' --output text) if [[ -n "$TASK_ARN" && "$TASK_ARN" != "None" ]]; then log "Service task: $TASK_ARN" CREATED_RESOURCES+=("ECS Task: $TASK_ARN") fi } # Function to demonstrate monitoring and testing demonstrate_monitoring() { log "Demonstrating monitoring capabilities..." # List services log "Listing services in cluster:" aws ecs list-services --cluster "$CLUSTER_NAME" --output table # Describe service log "Service details:" aws ecs describe-services --cluster "$CLUSTER_NAME" --services "$SERVICE_NAME" --output table --query 'services[0].{ServiceName:serviceName,Status:status,RunningCount:runningCount,DesiredCount:desiredCount,TaskDefinition:taskDefinition}' # List tasks log "Listing tasks in service:" aws ecs list-tasks --cluster "$CLUSTER_NAME" --service-name "$SERVICE_NAME" --output table # Describe task if [[ -n "$TASK_ARN" && "$TASK_ARN" != "None" ]]; then log "Task details:" aws ecs describe-tasks --cluster "$CLUSTER_NAME" --tasks "$TASK_ARN" --output table --query 'tasks[0].{TaskArn:taskArn,LastStatus:lastStatus,DesiredStatus:desiredStatus,CreatedAt:createdAt}' fi # List container instances log "Container instances in cluster:" aws ecs list-container-instances --cluster "$CLUSTER_NAME" --output table # Describe container instance CONTAINER_INSTANCE_ARN=$(aws ecs list-container-instances --cluster "$CLUSTER_NAME" --query 'containerInstanceArns[0]' --output text) if [[ -n "$CONTAINER_INSTANCE_ARN" && "$CONTAINER_INSTANCE_ARN" != "None" ]]; then log "Container instance details:" aws ecs describe-container-instances --cluster "$CLUSTER_NAME" --container-instances "$CONTAINER_INSTANCE_ARN" --output table --query 'containerInstances[0].{Arn:containerInstanceArn,Status:status,RunningTasks:runningTasksCount,PendingTasks:pendingTasksCount}' fi # Test the nginx web server log "Testing nginx web server..." PUBLIC_IP=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" --query 'Reservations[0].Instances[0].PublicIpAddress' --output text) if [[ -n "$PUBLIC_IP" && "$PUBLIC_IP" != "None" ]]; then log "Container instance public IP: $PUBLIC_IP" log "Testing HTTP connection to nginx..." # Wait a moment for nginx to be fully ready sleep 10 if curl -s --connect-timeout 10 "http://$PUBLIC_IP" | grep -q "Welcome to nginx"; then log "SUCCESS: Nginx web server is responding correctly" echo "" echo "===========================================" echo "WEB SERVER TEST SUCCESSFUL" echo "===========================================" echo "You can access your nginx web server at: http://$PUBLIC_IP" echo "The nginx welcome page should be visible in your browser." else log "WARNING: Nginx web server may not be fully ready yet. Try accessing http://$PUBLIC_IP in a few minutes." fi else log "WARNING: Could not retrieve public IP address" fi } # Main execution main() { log "Starting ECS EC2 Launch Type Tutorial (UPDATED VERSION)" log "Log file: $LOG_FILE" check_prerequisites create_cluster create_key_pair create_security_group get_ecs_ami ensure_ecs_instance_role launch_container_instance register_task_definition create_service demonstrate_monitoring log "Tutorial completed successfully!" echo "" echo "===========================================" echo "TUTORIAL COMPLETED SUCCESSFULLY" echo "===========================================" echo "Resources created:" for resource in "${CREATED_RESOURCES[@]}"; do echo " - $resource" done echo "" echo "The nginx service will continue running and maintain the desired task count." echo "You can monitor the service status using:" echo " aws ecs describe-services --cluster $CLUSTER_NAME --services $SERVICE_NAME" echo "" if [[ -n "${PUBLIC_IP:-}" ]]; then echo "Access your web server at: http://$PUBLIC_IP" echo "" fi 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 log "All resources have been cleaned up" else log "Resources left running. Remember to clean them up manually to avoid charges." echo "" echo "To clean up manually later, run these commands:" echo " aws ecs update-service --cluster $CLUSTER_NAME --service $SERVICE_NAME --desired-count 0" echo " aws ecs delete-service --cluster $CLUSTER_NAME --service $SERVICE_NAME" echo " aws ecs delete-cluster --cluster $CLUSTER_NAME" echo " aws ec2 terminate-instances --instance-ids $INSTANCE_ID" echo " aws ec2 delete-security-group --group-id $SECURITY_GROUP_ID" echo " aws ec2 delete-key-pair --key-name $KEY_PAIR_NAME" fi log "Script execution completed" } # Run main function main "$@"-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Set up networking components
Create a DB subnet group
Create a DB instance
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 # Script to create an Amazon RDS DB instance # This script follows the tutorial at https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_CreateDBInstance.html # Set up logging LOG_FILE="rds_creation_$(date +%Y%m%d_%H%M%S).log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting RDS DB instance creation script - $(date)" echo "All actions will be logged to $LOG_FILE" echo "==============================================" # Function to check for errors in command output check_error() { local output=$1 local cmd=$2 if echo "$output" | grep -i "error" > /dev/null; then echo "ERROR: Command failed: $cmd" echo "$output" cleanup_on_error exit 1 fi } # Function to clean up resources on error cleanup_on_error() { echo "Error encountered. Attempting to clean up resources..." if [ -n "$DB_INSTANCE_ID" ]; then echo "Deleting DB instance $DB_INSTANCE_ID..." aws rds delete-db-instance --db-instance-identifier "$DB_INSTANCE_ID" --skip-final-snapshot echo "Waiting for DB instance to be deleted..." aws rds wait db-instance-deleted --db-instance-identifier "$DB_INSTANCE_ID" fi if [ -n "$DB_SUBNET_GROUP_NAME" ] && [ "$CREATED_SUBNET_GROUP" = "true" ]; then echo "Deleting DB subnet group $DB_SUBNET_GROUP_NAME..." aws rds delete-db-subnet-group --db-subnet-group-name "$DB_SUBNET_GROUP_NAME" fi if [ -n "$SECURITY_GROUP_ID" ] && [ "$CREATED_SECURITY_GROUP" = "true" ]; then echo "Deleting security group $SECURITY_GROUP_ID..." aws ec2 delete-security-group --group-id "$SECURITY_GROUP_ID" fi echo "Cleanup completed." } # Generate a random identifier for resources RANDOM_ID=$(openssl rand -hex 4) DB_INSTANCE_ID="mydb-${RANDOM_ID}" DB_SUBNET_GROUP_NAME="mydbsubnet-${RANDOM_ID}" SECURITY_GROUP_NAME="mydbsg-${RANDOM_ID}" # Track created resources CREATED_SECURITY_GROUP="false" CREATED_SUBNET_GROUP="false" # Array to store created resources for display declare -a CREATED_RESOURCES echo "Step 1: Checking for default VPC..." VPC_OUTPUT=$(aws ec2 describe-vpcs --filters "Name=isDefault,Values=true") check_error "$VPC_OUTPUT" "aws ec2 describe-vpcs" # Extract VPC ID VPC_ID=$(echo "$VPC_OUTPUT" | grep -o '"VpcId": "[^"]*' | cut -d'"' -f4) if [ -z "$VPC_ID" ]; then echo "No default VPC found. Please create a VPC before running this script." exit 1 fi echo "Using VPC: $VPC_ID" echo "Step 2: Getting subnets from the VPC..." SUBNET_OUTPUT=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID") check_error "$SUBNET_OUTPUT" "aws ec2 describe-subnets" # Extract subnet IDs (we need at least 2 in different AZs) SUBNET_IDS=($(echo "$SUBNET_OUTPUT" | grep -o '"SubnetId": "[^"]*' | cut -d'"' -f4)) if [ ${#SUBNET_IDS[@]} -lt 2 ]; then echo "Error: Need at least 2 subnets in different AZs. Found ${#SUBNET_IDS[@]} subnets." exit 1 fi echo "Found ${#SUBNET_IDS[@]} subnets: ${SUBNET_IDS[*]}" echo "Step 3: Creating security group for RDS..." SG_OUTPUT=$(aws ec2 create-security-group \ --group-name "$SECURITY_GROUP_NAME" \ --description "Security group for RDS database access" \ --vpc-id "$VPC_ID") check_error "$SG_OUTPUT" "aws ec2 create-security-group" SECURITY_GROUP_ID=$(echo "$SG_OUTPUT" | grep -o '"GroupId": "[^"]*' | cut -d'"' -f4) CREATED_SECURITY_GROUP="true" CREATED_RESOURCES+=("Security Group: $SECURITY_GROUP_ID ($SECURITY_GROUP_NAME)") echo "Created security group: $SECURITY_GROUP_ID" echo "Step 4: Adding inbound rule to security group..." # Note: In a production environment, you should restrict this to specific IP ranges # We're using the local machine's IP address for this example MY_IP=$(curl -s https://checkip.amazonaws.com) check_error "$MY_IP" "curl -s https://checkip.amazonaws.com" INGRESS_OUTPUT=$(aws ec2 authorize-security-group-ingress \ --group-id "$SECURITY_GROUP_ID" \ --protocol tcp \ --port 3306 \ --cidr "${MY_IP}/32") check_error "$INGRESS_OUTPUT" "aws ec2 authorize-security-group-ingress" echo "Added inbound rule to allow MySQL connections from ${MY_IP}/32" echo "Step 5: Creating DB subnet group..." # Select the first two subnets for the DB subnet group SUBNET1=${SUBNET_IDS[0]} SUBNET2=${SUBNET_IDS[1]} SUBNET_GROUP_OUTPUT=$(aws rds create-db-subnet-group \ --db-subnet-group-name "$DB_SUBNET_GROUP_NAME" \ --db-subnet-group-description "Subnet group for RDS tutorial" \ --subnet-ids "$SUBNET1" "$SUBNET2") check_error "$SUBNET_GROUP_OUTPUT" "aws rds create-db-subnet-group" CREATED_SUBNET_GROUP="true" CREATED_RESOURCES+=("DB Subnet Group: $DB_SUBNET_GROUP_NAME") echo "Created DB subnet group: $DB_SUBNET_GROUP_NAME" echo "Step 6: Creating a secure password in AWS Secrets Manager..." SECRET_NAME="rds-db-credentials-${RANDOM_ID}" SECRET_OUTPUT=$(aws secretsmanager create-secret \ --name "$SECRET_NAME" \ --description "RDS DB credentials for $DB_INSTANCE_ID" \ --secret-string '{"username":"adminuser","password":"'"$(openssl rand -base64 16)"'"}') check_error "$SECRET_OUTPUT" "aws secretsmanager create-secret" SECRET_ARN=$(echo "$SECRET_OUTPUT" | grep -o '"ARN": "[^"]*' | cut -d'"' -f4) CREATED_RESOURCES+=("Secret: $SECRET_ARN ($SECRET_NAME)") echo "Created secret: $SECRET_NAME" echo "Step 7: Retrieving the username and password from the secret..." SECRET_VALUE_OUTPUT=$(aws secretsmanager get-secret-value --secret-id "$SECRET_NAME" --query 'SecretString' --output text) check_error "$SECRET_VALUE_OUTPUT" "aws secretsmanager get-secret-value" DB_USERNAME=$(echo "$SECRET_VALUE_OUTPUT" | grep -o '"username":"[^"]*' | cut -d'"' -f4) DB_PASSWORD=$(echo "$SECRET_VALUE_OUTPUT" | grep -o '"password":"[^"]*' | cut -d'"' -f4) echo "Retrieved database credentials" echo "Step 8: Creating RDS DB instance..." echo "This may take several minutes..." DB_OUTPUT=$(aws rds create-db-instance \ --db-instance-identifier "$DB_INSTANCE_ID" \ --db-instance-class db.t3.micro \ --engine mysql \ --master-username "$DB_USERNAME" \ --master-user-password "$DB_PASSWORD" \ --allocated-storage 20 \ --vpc-security-group-ids "$SECURITY_GROUP_ID" \ --db-subnet-group-name "$DB_SUBNET_GROUP_NAME" \ --backup-retention-period 7 \ --no-publicly-accessible \ --no-multi-az) check_error "$DB_OUTPUT" "aws rds create-db-instance" CREATED_RESOURCES+=("DB Instance: $DB_INSTANCE_ID") echo "DB instance creation initiated: $DB_INSTANCE_ID" echo "Waiting for DB instance to become available..." echo "This may take 5-10 minutes..." aws rds wait db-instance-available --db-instance-identifier "$DB_INSTANCE_ID" DB_STATUS=$? if [ $DB_STATUS -ne 0 ]; then echo "Error waiting for DB instance to become available" cleanup_on_error exit 1 fi echo "DB instance is now available!" echo "Step 9: Getting connection information..." ENDPOINT_INFO=$(aws rds describe-db-instances \ --db-instance-identifier "$DB_INSTANCE_ID" \ --query 'DBInstances[0].[Endpoint.Address,Endpoint.Port,MasterUsername]' \ --output text) check_error "$ENDPOINT_INFO" "aws rds describe-db-instances" DB_ENDPOINT=$(echo "$ENDPOINT_INFO" | awk '{print $1}') DB_PORT=$(echo "$ENDPOINT_INFO" | awk '{print $2}') DB_USER=$(echo "$ENDPOINT_INFO" | awk '{print $3}') echo "==============================================" echo "DB Instance successfully created!" echo "==============================================" echo "Connection Information:" echo " Endpoint: $DB_ENDPOINT" echo " Port: $DB_PORT" echo " Username: $DB_USER" echo " Password: [Stored in AWS Secrets Manager - $SECRET_NAME]" echo "" echo "To connect using the mysql client:" echo "mysql -h $DB_ENDPOINT -P $DB_PORT -u $DB_USER -p" echo "==============================================" echo "" echo "Resources created:" for resource in "${CREATED_RESOURCES[@]}"; do echo " - $resource" done echo "" # Ask user if they want to clean up resources 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 echo "Starting cleanup process..." echo "Step 1: Deleting DB instance $DB_INSTANCE_ID..." aws rds delete-db-instance --db-instance-identifier "$DB_INSTANCE_ID" --skip-final-snapshot echo "Waiting for DB instance to be deleted..." aws rds wait db-instance-deleted --db-instance-identifier "$DB_INSTANCE_ID" echo "Step 2: Deleting secret $SECRET_NAME..." aws secretsmanager delete-secret --secret-id "$SECRET_NAME" --force-delete-without-recovery echo "Step 3: Deleting DB subnet group $DB_SUBNET_GROUP_NAME..." aws rds delete-db-subnet-group --db-subnet-group-name "$DB_SUBNET_GROUP_NAME" echo "Step 4: Deleting security group $SECURITY_GROUP_ID..." aws ec2 delete-security-group --group-id "$SECURITY_GROUP_ID" echo "Cleanup completed successfully!" else echo "Skipping cleanup. Resources will remain in your AWS account." echo "To clean up later, you'll need to delete these resources manually." fi echo "Script completed successfully!"-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create an EBS volume
Check volume status
Create an EC2 instance (optional)
Attach a volume to an EC2 instance
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 # Script to create and manage Amazon EBS volumes # This script demonstrates how to create an EBS volume and attach it to an EC2 instance # It can also create a test EC2 instance if needed # Set up logging LOG_FILE="ebs-volume-creation.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting EBS volume creation script at $(date)" echo "==============================================" # Function to handle errors handle_error() { echo "ERROR: $1" echo "Resources created:" if [ -n "$VOLUME_ID" ]; then echo "- EBS Volume: $VOLUME_ID" fi if [ -n "$INSTANCE_ID" ]; then echo "- EC2 Instance: $INSTANCE_ID" fi if [ -n "$SG_ID" ]; then echo "- Security Group: $SG_ID" fi echo "" echo "===========================================" echo "CLEANUP CONFIRMATION" echo "===========================================" echo "An error occurred. Do you want to clean up created resources? (y/n): " read -r CLEANUP_CHOICE if [[ "$CLEANUP_CHOICE" =~ ^[Yy]$ ]]; then cleanup_resources else echo "Resources were not cleaned up. You will need to delete them manually." fi exit 1 } # Function to clean up resources cleanup_resources() { echo "Cleaning up resources..." if [ -n "$VOLUME_ID" ] && [ "$ATTACHED" = true ]; then echo "Detaching volume $VOLUME_ID..." aws ec2 detach-volume --volume-id "$VOLUME_ID" # Wait for volume to be detached echo "Waiting for volume to be detached..." aws ec2 wait volume-available --volume-ids "$VOLUME_ID" fi if [ -n "$VOLUME_ID" ]; then echo "Deleting volume $VOLUME_ID..." aws ec2 delete-volume --volume-id "$VOLUME_ID" fi if [ -n "$INSTANCE_ID" ] && [ "$CREATED_INSTANCE" = true ]; then echo "Terminating instance $INSTANCE_ID..." aws ec2 terminate-instances --instance-ids "$INSTANCE_ID" echo "Waiting for instance to terminate..." aws ec2 wait instance-terminated --instance-ids "$INSTANCE_ID" fi # Clean up security group if created if [ -n "$SG_ID" ] && [ "$CREATED_INSTANCE" = true ]; then echo "Deleting security group $SG_ID..." # Wait a bit for instance termination to complete sleep 10 aws ec2 delete-security-group --group-id "$SG_ID" 2>/dev/null || echo "Security group may have dependencies, delete manually if needed" fi echo "Cleanup completed." } # Function to get available instance type get_available_instance_type() { local region=$1 # Try instance types in order of preference (cheapest first) local instance_types=("t3.nano" "t3.micro" "t2.micro" "t2.nano") for instance_type in "${instance_types[@]}"; do local available=$(aws ec2 describe-instance-type-offerings \ --region "$region" \ --filters "Name=instance-type,Values=$instance_type" \ --query "length(InstanceTypeOfferings)" \ --output text) if [ "$available" -gt 0 ]; then echo "$instance_type" return 0 fi done # If none of the preferred types are available, get any available type local fallback_type=$(aws ec2 describe-instance-type-offerings \ --region "$region" \ --query "InstanceTypeOfferings[0].InstanceType" \ --output text) if [ "$fallback_type" != "None" ] && [ -n "$fallback_type" ]; then echo "$fallback_type" return 0 fi return 1 } # Get current region REGION=$(aws configure get region) if [ -z "$REGION" ]; then REGION=$(aws ec2 describe-availability-zones --query "AvailabilityZones[0].RegionName" --output text) fi echo "Using region: $REGION" # Get available Availability Zones echo "Retrieving available Availability Zones..." AZ=$(aws ec2 describe-availability-zones --filters "Name=state,Values=available" --query "AvailabilityZones[0].ZoneName" --output text) if [ -z "$AZ" ]; then handle_error "Failed to retrieve Availability Zones" fi echo "Using Availability Zone: $AZ" # Create a gp3 volume echo "Creating a 10 GiB gp3 volume in $AZ..." VOLUME_ID=$(aws ec2 create-volume \ --volume-type gp3 \ --size 10 \ --availability-zone "$AZ" \ --tag-specifications 'ResourceType=volume,Tags=[{Key=Name,Value=EBSTutorialVolume},{Key=Purpose,Value=Tutorial}]' \ --query 'VolumeId' \ --output text) if [ -z "$VOLUME_ID" ]; then handle_error "Failed to create EBS volume" fi echo "Volume created with ID: $VOLUME_ID" # Wait for volume to become available echo "Waiting for volume to become available..." aws ec2 wait volume-available --volume-ids "$VOLUME_ID" if [ $? -ne 0 ]; then handle_error "Volume did not become available" fi # Check volume details echo "Retrieving volume details..." aws ec2 describe-volumes --volume-ids "$VOLUME_ID" # Ask if user wants to attach the volume to an instance echo "" echo "===========================================" echo "VOLUME ATTACHMENT" echo "===========================================" echo "Do you want to attach this volume to an EC2 instance? (y/n): " read -r ATTACH_CHOICE ATTACHED=false CREATED_INSTANCE=false INSTANCE_ID="" SG_ID="" if [[ "$ATTACH_CHOICE" =~ ^[Yy]$ ]]; then # List available instances in the same AZ echo "Retrieving EC2 instances in $AZ..." INSTANCES_COUNT=$(aws ec2 describe-instances \ --filters "Name=availability-zone,Values=$AZ" "Name=instance-state-name,Values=running" \ --query "length(Reservations[].Instances[])" \ --output text) # Check if there are any running instances in the AZ if [ "$INSTANCES_COUNT" -eq 0 ]; then echo "No running instances found in $AZ." echo "" echo "Would you like to create a test EC2 instance? (y/n): " read -r CREATE_INSTANCE_CHOICE if [[ "$CREATE_INSTANCE_CHOICE" =~ ^[Yy]$ ]]; then # Get available instance type echo "Finding available instance type for region $REGION..." INSTANCE_TYPE=$(get_available_instance_type "$REGION") if [ $? -ne 0 ] || [ -z "$INSTANCE_TYPE" ]; then handle_error "No suitable instance type found in region $REGION" fi echo "Using instance type: $INSTANCE_TYPE" # Get the latest Amazon Linux 2 AMI echo "Finding the latest Amazon Linux 2 AMI..." AMI_ID=$(aws ec2 describe-images \ --owners amazon \ --filters "Name=name,Values=amzn2-ami-hvm-*-x86_64-gp2" "Name=state,Values=available" \ --query "sort_by(Images, &CreationDate)[-1].ImageId" \ --output text) if [ -z "$AMI_ID" ]; then handle_error "Failed to find a suitable AMI" fi echo "Using AMI: $AMI_ID" # Check if a default VPC exists DEFAULT_VPC_ID=$(aws ec2 describe-vpcs \ --filters "Name=isDefault,Values=true" \ --query "Vpcs[0].VpcId" \ --output text) if [ "$DEFAULT_VPC_ID" = "None" ] || [ -z "$DEFAULT_VPC_ID" ]; then handle_error "No default VPC found. Please create a VPC and subnet before running this script." fi # Get a subnet in the selected AZ SUBNET_ID=$(aws ec2 describe-subnets \ --filters "Name=vpc-id,Values=$DEFAULT_VPC_ID" "Name=availability-zone,Values=$AZ" \ --query "Subnets[0].SubnetId" \ --output text) if [ -z "$SUBNET_ID" ] || [ "$SUBNET_ID" = "None" ]; then handle_error "No subnet found in $AZ. Please create a subnet before running this script." fi echo "Using subnet: $SUBNET_ID" # Create a security group that allows SSH SG_NAME="EBSTutorialSG-$(date +%s)" SG_ID=$(aws ec2 create-security-group \ --group-name "$SG_NAME" \ --description "Security group for EBS tutorial" \ --vpc-id "$DEFAULT_VPC_ID" \ --query "GroupId" \ --output text) if [ -z "$SG_ID" ]; then handle_error "Failed to create security group" fi echo "Created security group: $SG_ID" # Add a rule to allow SSH aws ec2 authorize-security-group-ingress \ --group-id "$SG_ID" \ --protocol tcp \ --port 22 \ --cidr 0.0.0.0/0 echo "Added SSH rule to security group" # Create the instance echo "Creating EC2 instance in $AZ with instance type $INSTANCE_TYPE..." INSTANCE_ID=$(aws ec2 run-instances \ --image-id "$AMI_ID" \ --instance-type "$INSTANCE_TYPE" \ --subnet-id "$SUBNET_ID" \ --security-group-ids "$SG_ID" \ --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=EBSTutorialInstance},{Key=Purpose,Value=Tutorial}]' \ --query "Instances[0].InstanceId" \ --output text) if [ -z "$INSTANCE_ID" ]; then handle_error "Failed to create EC2 instance" fi CREATED_INSTANCE=true echo "Instance created with ID: $INSTANCE_ID" # Wait for the instance to be running echo "Waiting for instance to be running..." aws ec2 wait instance-running --instance-ids "$INSTANCE_ID" # Wait a bit more for the instance to initialize echo "Waiting for instance initialization (30 seconds)..." sleep 30 else echo "Skipping instance creation and volume attachment." INSTANCE_ID="" fi else # Display available instances echo "Available instances in $AZ:" aws ec2 describe-instances \ --filters "Name=availability-zone,Values=$AZ" "Name=instance-state-name,Values=running" \ --query "Reservations[*].Instances[*].[InstanceId,Tags[?Key=='Name'].Value|[0],InstanceType]" \ --output table # Ask for instance ID echo "" echo "Enter the instance ID to attach the volume to (or press Enter to skip): " read -r INSTANCE_ID fi if [ -n "$INSTANCE_ID" ]; then # Attach volume to the instance echo "Attaching volume $VOLUME_ID to instance $INSTANCE_ID..." ATTACH_RESULT=$(aws ec2 attach-volume \ --volume-id "$VOLUME_ID" \ --instance-id "$INSTANCE_ID" \ --device "/dev/sdf" \ --query 'State' \ --output text) if [ $? -ne 0 ] || [ -z "$ATTACH_RESULT" ]; then handle_error "Failed to attach volume to instance" fi ATTACHED=true echo "Volume attached successfully. Device: /dev/sdf" # Verify attachment echo "Verifying attachment..." aws ec2 describe-volumes \ --volume-ids "$VOLUME_ID" \ --query "Volumes[0].Attachments" else echo "Skipping volume attachment." fi else echo "Skipping volume attachment." fi # Display summary of created resources echo "" echo "===========================================" echo "RESOURCE SUMMARY" echo "===========================================" echo "Created resources:" echo "- EBS Volume: $VOLUME_ID" if [ "$ATTACHED" = true ]; then echo " - Attached to: $INSTANCE_ID as /dev/sdf" fi if [ "$CREATED_INSTANCE" = true ]; then echo "- EC2 Instance: $INSTANCE_ID (type: $INSTANCE_TYPE)" fi if [ -n "$SG_ID" ]; then echo "- Security Group: $SG_ID" fi # Ask if user wants to clean up resources 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 else echo "" echo "Resources were not cleaned up. You can manually delete them later." if [ -n "$VOLUME_ID" ]; then if [ "$ATTACHED" = true ]; then echo "To detach the volume:" echo " aws ec2 detach-volume --volume-id $VOLUME_ID" fi echo "To delete the volume:" echo " aws ec2 delete-volume --volume-id $VOLUME_ID" fi if [ "$CREATED_INSTANCE" = true ]; then echo "To terminate the instance:" echo " aws ec2 terminate-instances --instance-ids $INSTANCE_ID" fi if [ -n "$SG_ID" ]; then echo "To delete the security group (after instance termination):" echo " aws ec2 delete-security-group --group-id $SG_ID" fi fi echo "" echo "Script completed at $(date)" echo "=============================================="-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create a service network
Create a service
List available VPCs
List security groups for the selected VPC
List service associations
List VPC associations
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 # VPC Lattice Service Network Tutorial Script # This script demonstrates how to create and manage a VPC Lattice service network # Set up logging LOG_FILE="vpc-lattice-tutorial.log" echo "Starting VPC Lattice tutorial script at $(date)" > $LOG_FILE # Function to log commands and their output log_command() { echo "$(date): Running command: $1" >> $LOG_FILE eval "$1" 2>&1 | tee -a $LOG_FILE return ${PIPESTATUS[0]} } # Function to check for errors check_error() { if [ $1 -ne 0 ]; then echo "ERROR: Command failed with exit code $1" | tee -a $LOG_FILE echo "See $LOG_FILE for details" exit $1 fi } # Function to wait for a resource to be in the desired state wait_for_resource() { local resource_type=$1 local resource_id=$2 local desired_status=$3 local command=$4 local max_attempts=30 local attempt=1 local status="" echo "Waiting for $resource_type $resource_id to be in state $desired_status..." | tee -a $LOG_FILE while [ $attempt -le $max_attempts ]; do echo "Attempt $attempt of $max_attempts..." >> $LOG_FILE # Run the command to get the status and capture the output status_output=$(eval "$command") echo "$status_output" >> $LOG_FILE # For service networks, they don't have a status field in the output # We'll consider them active if we can retrieve them if [[ "$resource_type" == "Service Network" ]]; then if [[ "$status_output" == *"$resource_id"* ]]; then echo "$resource_type $resource_id is now active" | tee -a $LOG_FILE return 0 fi else # For other resources, extract the status field status=$(echo "$status_output" | grep -i "status" | awk -F'"' '{print $4}') echo "Current status: $status" >> $LOG_FILE if [[ "$status" == "$desired_status" ]]; then echo "$resource_type $resource_id is now in state $desired_status" | tee -a $LOG_FILE return 0 elif [[ "$status" == *"FAIL"* ]]; then echo "ERROR: $resource_type $resource_id failed to reach desired state. Current status: $status" | tee -a $LOG_FILE return 1 fi fi echo "Waiting for status change... (attempt $attempt/$max_attempts)" >> $LOG_FILE sleep 10 ((attempt++)) done echo "ERROR: Timed out waiting for $resource_type $resource_id to reach state $desired_status" | tee -a $LOG_FILE return 1 } # Generate a random identifier for resource names RANDOM_ID=$(openssl rand -hex 4) SERVICE_NETWORK_NAME="lattice-network-${RANDOM_ID}" SERVICE_NAME="lattice-service-${RANDOM_ID}" # Store created resources for cleanup CREATED_RESOURCES=() echo "=== VPC Lattice Service Network Tutorial ===" | tee -a $LOG_FILE echo "Random ID for this session: ${RANDOM_ID}" | tee -a $LOG_FILE # Step 1: Create a VPC Lattice service network echo -e "\n=== Step 1: Creating a VPC Lattice service network ===" | tee -a $LOG_FILE echo "Creating service network: $SERVICE_NETWORK_NAME" | tee -a $LOG_FILE SERVICE_NETWORK_OUTPUT=$(log_command "aws vpc-lattice create-service-network --name $SERVICE_NETWORK_NAME") check_error $? # Extract the service network ID SERVICE_NETWORK_ID=$(echo "$SERVICE_NETWORK_OUTPUT" | grep -o '"id": "[^"]*' | cut -d'"' -f4) if [ -z "$SERVICE_NETWORK_ID" ]; then echo "ERROR: Failed to extract service network ID" | tee -a $LOG_FILE exit 1 fi echo "Service network created with ID: $SERVICE_NETWORK_ID" | tee -a $LOG_FILE CREATED_RESOURCES+=("Service Network: $SERVICE_NETWORK_ID") # Wait for the service network to be active wait_for_resource "Service Network" "$SERVICE_NETWORK_ID" "ACTIVE" "aws vpc-lattice get-service-network --service-network-identifier $SERVICE_NETWORK_ID" check_error $? # Step 2: Create a VPC Lattice service echo -e "\n=== Step 2: Creating a VPC Lattice service ===" | tee -a $LOG_FILE echo "Creating service: $SERVICE_NAME" | tee -a $LOG_FILE SERVICE_OUTPUT=$(log_command "aws vpc-lattice create-service --name $SERVICE_NAME") check_error $? # Extract the service ID SERVICE_ID=$(echo "$SERVICE_OUTPUT" | grep -o '"id": "[^"]*' | cut -d'"' -f4) if [ -z "$SERVICE_ID" ]; then echo "ERROR: Failed to extract service ID" | tee -a $LOG_FILE exit 1 fi echo "Service created with ID: $SERVICE_ID" | tee -a $LOG_FILE CREATED_RESOURCES+=("Service: $SERVICE_ID") # Wait for the service to be active wait_for_resource "Service" "$SERVICE_ID" "ACTIVE" "aws vpc-lattice get-service --service-identifier $SERVICE_ID" check_error $? # Step 3: Associate the service with the service network echo -e "\n=== Step 3: Associating service with service network ===" | tee -a $LOG_FILE SERVICE_ASSOC_OUTPUT=$(log_command "aws vpc-lattice create-service-network-service-association --service-identifier $SERVICE_ID --service-network-identifier $SERVICE_NETWORK_ID") check_error $? # Extract the service association ID SERVICE_ASSOC_ID=$(echo "$SERVICE_ASSOC_OUTPUT" | grep -o '"id": "[^"]*' | cut -d'"' -f4) if [ -z "$SERVICE_ASSOC_ID" ]; then echo "ERROR: Failed to extract service association ID" | tee -a $LOG_FILE exit 1 fi echo "Service association created with ID: $SERVICE_ASSOC_ID" | tee -a $LOG_FILE CREATED_RESOURCES+=("Service Association: $SERVICE_ASSOC_ID") # Wait for the service association to be active wait_for_resource "Service Association" "$SERVICE_ASSOC_ID" "ACTIVE" "aws vpc-lattice get-service-network-service-association --service-network-service-association-identifier $SERVICE_ASSOC_ID" check_error $? # Step 4: List available VPCs to associate with the service network echo -e "\n=== Step 4: Listing available VPCs ===" | tee -a $LOG_FILE VPC_LIST=$(log_command "aws ec2 describe-vpcs --query 'Vpcs[*].[VpcId,Tags[?Key==\`Name\`].Value|[0]]' --output text") check_error $? echo "Available VPCs:" | tee -a $LOG_FILE echo "$VPC_LIST" | tee -a $LOG_FILE # Step 5: Prompt user to select a VPC to associate echo -e "\n=== Step 5: Associate a VPC with the service network ===" | tee -a $LOG_FILE echo "" echo "===========================================" echo "VPC SELECTION" echo "===========================================" echo "Please enter the VPC ID you want to associate with the service network:" read -r VPC_ID if [ -z "$VPC_ID" ]; then echo "ERROR: No VPC ID provided" | tee -a $LOG_FILE echo "Skipping VPC association step" | tee -a $LOG_FILE else # Step 6: List security groups for the selected VPC echo -e "\n=== Step 6: Listing security groups for VPC $VPC_ID ===" | tee -a $LOG_FILE SG_LIST=$(log_command "aws ec2 describe-security-groups --filters Name=vpc-id,Values=$VPC_ID --query 'SecurityGroups[*].[GroupId,GroupName]' --output text") check_error $? echo "Available Security Groups for VPC $VPC_ID:" | tee -a $LOG_FILE echo "$SG_LIST" | tee -a $LOG_FILE # Step 7: Prompt user to select a security group echo -e "\n=== Step 7: Select a security group for the VPC association ===" | tee -a $LOG_FILE echo "" echo "===========================================" echo "SECURITY GROUP SELECTION" echo "===========================================" echo "Please enter the Security Group ID you want to use for the VPC association:" read -r SG_ID if [ -z "$SG_ID" ]; then echo "ERROR: No Security Group ID provided" | tee -a $LOG_FILE echo "Skipping VPC association step" | tee -a $LOG_FILE else # Step 8: Associate the VPC with the service network echo -e "\n=== Step 8: Associating VPC with service network ===" | tee -a $LOG_FILE VPC_ASSOC_OUTPUT=$(log_command "aws vpc-lattice create-service-network-vpc-association --vpc-identifier $VPC_ID --service-network-identifier $SERVICE_NETWORK_ID --security-group-ids $SG_ID") check_error $? # Extract the VPC association ID VPC_ASSOC_ID=$(echo "$VPC_ASSOC_OUTPUT" | grep -o '"id": "[^"]*' | cut -d'"' -f4) if [ -z "$VPC_ASSOC_ID" ]; then echo "ERROR: Failed to extract VPC association ID" | tee -a $LOG_FILE else echo "VPC association created with ID: $VPC_ASSOC_ID" | tee -a $LOG_FILE CREATED_RESOURCES+=("VPC Association: $VPC_ASSOC_ID") # Wait for the VPC association to be active wait_for_resource "VPC Association" "$VPC_ASSOC_ID" "ACTIVE" "aws vpc-lattice get-service-network-vpc-association --service-network-vpc-association-identifier $VPC_ASSOC_ID" check_error $? fi fi fi # Step 9: Display information about the created resources echo -e "\n=== Step 9: Displaying information about created resources ===" | tee -a $LOG_FILE echo "Service Network Details:" | tee -a $LOG_FILE log_command "aws vpc-lattice get-service-network --service-network-identifier $SERVICE_NETWORK_ID" echo "Service Details:" | tee -a $LOG_FILE log_command "aws vpc-lattice get-service --service-identifier $SERVICE_ID" echo "Service Network Service Associations:" | tee -a $LOG_FILE log_command "aws vpc-lattice list-service-network-service-associations --service-network-identifier $SERVICE_NETWORK_ID" echo "Service Network VPC Associations:" | tee -a $LOG_FILE log_command "aws vpc-lattice list-service-network-vpc-associations --service-network-identifier $SERVICE_NETWORK_ID" # Step 10: Cleanup confirmation echo -e "\n=== Step 10: Resource Cleanup ===" | tee -a $LOG_FILE echo "Resources created in this tutorial:" | tee -a $LOG_FILE for resource in "${CREATED_RESOURCES[@]}"; do echo "- $resource" | tee -a $LOG_FILE done 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 echo "Starting cleanup process..." | tee -a $LOG_FILE # Delete resources in reverse order # Delete VPC association if it was created if [[ -n "$VPC_ASSOC_ID" ]]; then echo "Deleting VPC association: $VPC_ASSOC_ID" | tee -a $LOG_FILE log_command "aws vpc-lattice delete-service-network-vpc-association --service-network-vpc-association-identifier $VPC_ASSOC_ID" # Wait for the VPC association to be deleted echo "Waiting for VPC association to be deleted..." | tee -a $LOG_FILE sleep 30 fi # Delete service association echo "Deleting service association: $SERVICE_ASSOC_ID" | tee -a $LOG_FILE log_command "aws vpc-lattice delete-service-network-service-association --service-network-service-association-identifier $SERVICE_ASSOC_ID" # Wait for the service association to be deleted echo "Waiting for service association to be deleted..." | tee -a $LOG_FILE sleep 30 # Delete service echo "Deleting service: $SERVICE_ID" | tee -a $LOG_FILE log_command "aws vpc-lattice delete-service --service-identifier $SERVICE_ID" # Wait for the service to be deleted echo "Waiting for service to be deleted..." | tee -a $LOG_FILE sleep 30 # Delete service network echo "Deleting service network: $SERVICE_NETWORK_ID" | tee -a $LOG_FILE log_command "aws vpc-lattice delete-service-network --service-network-identifier $SERVICE_NETWORK_ID" echo "Cleanup completed successfully!" | tee -a $LOG_FILE else echo "Skipping cleanup. Resources will remain in your account." | tee -a $LOG_FILE echo "To clean up resources later, use the AWS CLI or console." | tee -a $LOG_FILE fi echo -e "\n=== Tutorial completed! ===" | tee -a $LOG_FILE echo "Log file: $LOG_FILE" | tee -a $LOG_FILE-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Use ec2 CreateVpnGateway
Use ec2 DeleteVpnGateway
Use ec2 DescribeVpnGateways
Use directconnect CreateConnection
Use directconnect CreatePrivateVirtualInterface
Use directconnect DeleteConnection
- 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 # AWS Direct Connect Connection Management Script - Version 6 # This script demonstrates how to create and manage AWS Direct Connect connections using the AWS CLI # This version includes fixes for user input handling and better error reporting # Set up logging LOG_FILE="directconnect-script.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "$(date): Starting AWS Direct Connect script v6" # Function to check for errors in command output check_error() { local output=$1 local command=$2 if echo "$output" | grep -i "error" > /dev/null; then echo "ERROR: Command failed: $command" echo "Output: $output" cleanup_resources exit 1 fi } # Function to wait for VGW to be available wait_for_vgw() { local vgw_id=$1 local max_attempts=30 local attempt=1 echo "Waiting for virtual private gateway $vgw_id to become available..." while [ $attempt -le $max_attempts ]; do VGW_STATE=$(aws ec2 describe-vpn-gateways --vpn-gateway-ids "$vgw_id" --query 'VpnGateways[0].State' --output text) if [ "$VGW_STATE" == "available" ]; then echo "Virtual private gateway is now available" return 0 elif [ "$VGW_STATE" == "failed" ]; then echo "Virtual private gateway failed to become available" return 1 fi echo "Attempt $attempt/$max_attempts: VGW state is $VGW_STATE, waiting 10 seconds..." sleep 10 attempt=$((attempt + 1)) done echo "Timeout waiting for VGW to become available" return 1 } # Function to wait for connection to be available wait_for_connection() { local connection_id=$1 local max_attempts=60 local attempt=1 echo "Waiting for connection $connection_id to become available..." echo "Note: This can take 30+ minutes in production as AWS provisions the physical connection" while [ $attempt -le $max_attempts ]; do CONNECTION_STATE=$(aws directconnect describe-connections --connection-id "$connection_id" --query 'connections[0].connectionState' --output text) if [ "$CONNECTION_STATE" == "available" ]; then echo "Connection is now available" return 0 elif [ "$CONNECTION_STATE" == "rejected" ] || [ "$CONNECTION_STATE" == "deleted" ]; then echo "Connection failed with state: $CONNECTION_STATE" return 1 fi echo "Attempt $attempt/$max_attempts: Connection state is $CONNECTION_STATE, waiting 30 seconds..." sleep 30 attempt=$((attempt + 1)) done echo "Timeout waiting for connection to become available" return 1 } # Function to clean up resources cleanup_resources() { echo "Cleaning up resources..." # Delete virtual interfaces if they exist if [ -n "$PRIVATE_VIF_ID" ]; then echo "Deleting private virtual interface: $PRIVATE_VIF_ID" aws directconnect delete-virtual-interface --virtual-interface-id "$PRIVATE_VIF_ID" fi if [ -n "$PUBLIC_VIF_ID" ]; then echo "Deleting public virtual interface: $PUBLIC_VIF_ID" aws directconnect delete-virtual-interface --virtual-interface-id "$PUBLIC_VIF_ID" fi # Delete connection if it exists if [ -n "$CONNECTION_ID" ]; then echo "Deleting connection: $CONNECTION_ID" aws directconnect delete-connection --connection-id "$CONNECTION_ID" fi # Delete VGW if it exists if [ -n "$VGW_ID" ]; then echo "Deleting virtual private gateway: $VGW_ID" aws ec2 delete-vpn-gateway --vpn-gateway-id "$VGW_ID" fi } # Generate a random identifier for resource names RANDOM_ID=$(openssl rand -hex 6) CONNECTION_NAME="DxConn-${RANDOM_ID}" # Step 1: List available Direct Connect locations echo "Listing available Direct Connect locations..." LOCATIONS_OUTPUT=$(aws directconnect describe-locations) check_error "$LOCATIONS_OUTPUT" "describe-locations" echo "$LOCATIONS_OUTPUT" # Extract the first location code for demonstration purposes LOCATION_CODE=$(aws directconnect describe-locations --query 'locations[0].locationCode' --output text) if [ -z "$LOCATION_CODE" ] || [ "$LOCATION_CODE" == "None" ]; then echo "Error: Could not extract location code from the output." exit 1 fi echo "Using location: $LOCATION_CODE" # Step 2: Create a dedicated connection echo "Creating a dedicated connection at location $LOCATION_CODE with bandwidth 1Gbps..." CONNECTION_OUTPUT=$(aws directconnect create-connection \ --location "$LOCATION_CODE" \ --bandwidth "1Gbps" \ --connection-name "$CONNECTION_NAME") check_error "$CONNECTION_OUTPUT" "create-connection" echo "$CONNECTION_OUTPUT" # Extract connection ID directly from the output CONNECTION_ID=$(echo "$CONNECTION_OUTPUT" | grep -o '"connectionId": "[^"]*' | cut -d'"' -f4) if [ -z "$CONNECTION_ID" ]; then echo "Error: Could not extract connection ID from the output." exit 1 fi echo "Connection created with ID: $CONNECTION_ID" # Step 3: Describe the connection echo "Retrieving connection details..." DESCRIBE_OUTPUT=$(aws directconnect describe-connections --connection-id "$CONNECTION_ID") check_error "$DESCRIBE_OUTPUT" "describe-connections" echo "$DESCRIBE_OUTPUT" # Step 4: Update the connection name NEW_CONNECTION_NAME="${CONNECTION_NAME}-updated" echo "Updating connection name to $NEW_CONNECTION_NAME..." UPDATE_OUTPUT=$(aws directconnect update-connection \ --connection-id "$CONNECTION_ID" \ --connection-name "$NEW_CONNECTION_NAME") check_error "$UPDATE_OUTPUT" "update-connection" echo "$UPDATE_OUTPUT" # Step 5: Check if we can download the LOA-CFA # Note: In a real scenario, the LOA-CFA might not be immediately available echo "Attempting to download the LOA-CFA (this may not be available yet)..." LOA_OUTPUT=$(aws directconnect describe-loa --connection-id "$CONNECTION_ID" 2>&1) if echo "$LOA_OUTPUT" | grep -i "error" > /dev/null; then echo "LOA-CFA not available yet. This is expected for newly created connections." echo "The LOA-CFA will be available once AWS begins provisioning your connection." else LOA_CONTENT=$(echo "$LOA_OUTPUT" | grep -o '"loaContent": "[^"]*' | cut -d'"' -f4) echo "$LOA_CONTENT" | base64 --decode > "loa-cfa-${CONNECTION_ID}.pdf" echo "LOA-CFA downloaded to loa-cfa-${CONNECTION_ID}.pdf" fi # Step 6: Create a virtual private gateway (required for private virtual interface) echo "Creating a virtual private gateway..." VGW_OUTPUT=$(aws ec2 create-vpn-gateway --type ipsec.1) check_error "$VGW_OUTPUT" "create-vpn-gateway" echo "$VGW_OUTPUT" # Extract VGW ID directly from the output VGW_ID=$(echo "$VGW_OUTPUT" | grep -o '"VpnGatewayId": "[^"]*' | cut -d'"' -f4) if [ -z "$VGW_ID" ]; then echo "Error: Could not extract VPN gateway ID from the output." exit 1 fi echo "Virtual private gateway created with ID: $VGW_ID" # Wait for VGW to become available if ! wait_for_vgw "$VGW_ID"; then echo "Failed to wait for VGW to become available. Skipping virtual interface creation." VIF_CREATION_SKIPPED=true else VIF_CREATION_SKIPPED=false fi # Step 7: Create a private virtual interface (only if VGW is available) if [ "$VIF_CREATION_SKIPPED" = false ]; then echo "Creating a private virtual interface..." PRIVATE_VIF_OUTPUT=$(aws directconnect create-private-virtual-interface \ --connection-id "$CONNECTION_ID" \ --new-private-virtual-interface '{ "virtualInterfaceName": "PrivateVIF-'"$RANDOM_ID"'", "vlan": 100, "asn": 65000, "authKey": "'"$RANDOM_ID"'key", "amazonAddress": "192.168.1.1/30", "customerAddress": "192.168.1.2/30", "addressFamily": "ipv4", "virtualGatewayId": "'"$VGW_ID"'" }' 2>&1) if echo "$PRIVATE_VIF_OUTPUT" | grep -i "error" > /dev/null; then echo "Could not create private virtual interface. This is expected if the connection is not yet available." echo "Error: $PRIVATE_VIF_OUTPUT" PRIVATE_VIF_ID="" else echo "$PRIVATE_VIF_OUTPUT" PRIVATE_VIF_ID=$(echo "$PRIVATE_VIF_OUTPUT" | grep -o '"virtualInterfaceId": "[^"]*' | cut -d'"' -f4) echo "Private virtual interface created with ID: $PRIVATE_VIF_ID" fi else echo "Skipping private virtual interface creation due to VGW not being available" PRIVATE_VIF_ID="" fi # Step 8: Check connection state and provide guidance for public virtual interface CONNECTION_STATE=$(aws directconnect describe-connections --connection-id "$CONNECTION_ID" --query 'connections[0].connectionState' --output text) echo "Current connection state: $CONNECTION_STATE" if [ "$CONNECTION_STATE" != "available" ]; then echo "" echo "===========================================" echo "CONNECTION NOT YET AVAILABLE" echo "===========================================" echo "The connection is in '$CONNECTION_STATE' state." echo "In production, you would:" echo "1. Wait for AWS to provision the connection (can take 30+ minutes)" echo "2. Download the LOA-CFA when available" echo "3. Provide the LOA-CFA to your network provider for cross-connect" echo "4. Create virtual interfaces once connection is 'available'" echo "" # Ask if user wants to wait for connection to become available echo "" echo "===========================================" echo "CONNECTION WAIT CONFIRMATION" echo "===========================================" echo -n "Do you want to wait for the connection to become available? (y/n): " read -r WAIT_CHOICE if [[ "$WAIT_CHOICE" =~ ^[Yy]$ ]]; then if wait_for_connection "$CONNECTION_ID"; then echo "Connection is now available! You could now create virtual interfaces." else echo "Connection did not become available within the timeout period." fi else echo "Skipping wait for connection availability." fi else echo "Connection is available! Virtual interfaces can be created." fi # Step 9: List all virtual interfaces echo "Listing all virtual interfaces..." VIF_LIST_OUTPUT=$(aws directconnect describe-virtual-interfaces) check_error "$VIF_LIST_OUTPUT" "describe-virtual-interfaces" echo "$VIF_LIST_OUTPUT" # Step 10: Display important information about production usage echo "" echo "===========================================" echo "IMPORTANT PRODUCTION NOTES" echo "===========================================" echo "1. Direct Connect connections take time to be provisioned by AWS" echo "2. You cannot create virtual interfaces until the connection is 'available'" echo "3. For public virtual interfaces, you must own the public IP addresses" echo "4. LOA-CFA (Letter of Authorization) is needed for cross-connect at the facility" echo "5. This demo creates resources that incur costs (~\$300/month for 1Gbps)" echo "6. Always test connectivity before putting into production" echo "" # Step 11: Ask user if they want to clean up resources echo "" echo "===========================================" echo "CLEANUP CONFIRMATION" echo "===========================================" echo -n "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 "" echo "Created resources:" echo "- Connection ID: $CONNECTION_ID" if [ -n "$PRIVATE_VIF_ID" ]; then echo "- Private Virtual Interface ID: $PRIVATE_VIF_ID" fi if [ -n "$PUBLIC_VIF_ID" ]; then echo "- Public Virtual Interface ID: $PUBLIC_VIF_ID" fi echo "- Virtual Private Gateway ID: $VGW_ID" echo "" echo "Manual cleanup commands:" echo "aws directconnect delete-connection --connection-id $CONNECTION_ID" echo "aws ec2 delete-vpn-gateway --vpn-gateway-id $VGW_ID" echo "" echo "Remember: Direct Connect resources incur ongoing costs!" fi echo "$(date): Script completed"-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Use ec2 AuthorizeSecurityGroupIngress
Use ec2 CreateKeyPair
Use ec2 CreateSecurityGroup
- 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 # AWS Marketplace Buyer Getting Started Script # This script demonstrates how to search for products in AWS Marketplace, # launch an EC2 instance with a product AMI, and manage subscriptions. # Setup logging LOG_FILE="marketplace-tutorial.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "===================================================" echo "AWS Marketplace Buyer Getting Started Tutorial" echo "===================================================" echo "This script will:" echo "1. List available products in AWS Marketplace" echo "2. Create resources needed to launch an EC2 instance" echo "3. Launch an EC2 instance with an Amazon Linux 2 AMI" echo "4. Show how to manage and terminate the instance" echo "===================================================" echo "" # Function to check for errors in command output check_error() { local output=$1 local cmd=$2 if echo "$output" | grep -i "error" > /dev/null; then echo "ERROR: Command failed: $cmd" echo "Output: $output" cleanup_resources exit 1 fi } # Function to clean up resources cleanup_resources() { echo "" echo "===================================================" echo "CLEANING UP RESOURCES" echo "===================================================" if [ -n "$INSTANCE_ID" ]; then echo "Terminating EC2 instance: $INSTANCE_ID" aws ec2 terminate-instances --instance-ids "$INSTANCE_ID" echo "Waiting for instance to terminate..." aws ec2 wait instance-terminated --instance-ids "$INSTANCE_ID" echo "Instance terminated successfully." fi if [ -n "$SECURITY_GROUP_ID" ]; then echo "Deleting security group: $SECURITY_GROUP_ID" aws ec2 delete-security-group --group-id "$SECURITY_GROUP_ID" echo "Security group deleted." fi if [ -n "$KEY_NAME" ]; then echo "Deleting key pair: $KEY_NAME" aws ec2 delete-key-pair --key-name "$KEY_NAME" # Remove the local key file if it exists if [ -f "${KEY_NAME}.pem" ]; then rm "${KEY_NAME}.pem" echo "Local key file deleted." fi fi echo "Cleanup completed." } # Generate random identifier for resource names RANDOM_ID=$(openssl rand -hex 6) KEY_NAME="marketplace-key-${RANDOM_ID}" SECURITY_GROUP_NAME="marketplace-sg-${RANDOM_ID}" # Initialize variables to track created resources INSTANCE_ID="" SECURITY_GROUP_ID="" # Step 1: List available products in AWS Marketplace echo "Listing available products in AWS Marketplace..." echo "Note: In a real scenario, you would use marketplace-catalog commands to list and search for products." echo "However, this requires specific permissions and product knowledge." echo "" echo "For this tutorial, we'll use a public Amazon Linux 2 AMI instead of an actual marketplace product." echo "This is because subscribing to marketplace products requires accepting terms via the console." echo "" # Step 2: Create a key pair for SSH access echo "Creating key pair: $KEY_NAME" KEY_OUTPUT=$(aws ec2 create-key-pair \ --key-name "$KEY_NAME" \ --query 'KeyMaterial' \ --output text > "${KEY_NAME}.pem" 2>&1) check_error "$KEY_OUTPUT" "ec2 create-key-pair" # Set proper permissions for the key file chmod 400 "${KEY_NAME}.pem" echo "Key pair created and saved to ${KEY_NAME}.pem" # Step 3: Create a security group echo "Creating security group: $SECURITY_GROUP_NAME" SG_OUTPUT=$(aws ec2 create-security-group \ --group-name "$SECURITY_GROUP_NAME" \ --description "Security group for AWS Marketplace tutorial" 2>&1) check_error "$SG_OUTPUT" "ec2 create-security-group" # Extract security group ID SECURITY_GROUP_ID=$(echo "$SG_OUTPUT" | grep -o '"GroupId": "[^"]*' | cut -d'"' -f4) echo "Security group created with ID: $SECURITY_GROUP_ID" # Add inbound rule for SSH (port 22) echo "Adding inbound rule for SSH (port 22)..." SSH_RULE_OUTPUT=$(aws ec2 authorize-security-group-ingress \ --group-id "$SECURITY_GROUP_ID" \ --protocol tcp \ --port 22 \ --cidr 10.0.0.0/16 2>&1) check_error "$SSH_RULE_OUTPUT" "ec2 authorize-security-group-ingress (SSH)" # Add inbound rule for HTTP (port 80) echo "Adding inbound rule for HTTP (port 80)..." HTTP_RULE_OUTPUT=$(aws ec2 authorize-security-group-ingress \ --group-id "$SECURITY_GROUP_ID" \ --protocol tcp \ --port 80 \ --cidr 10.0.0.0/16 2>&1) check_error "$HTTP_RULE_OUTPUT" "ec2 authorize-security-group-ingress (HTTP)" echo "Security group configured with SSH and HTTP access from 10.0.0.0/16 network." echo "Note: In a production environment, you should restrict access to specific IP ranges." # Step 4: Get the latest Amazon Linux 2 AMI ID # Note: In a real scenario, you would use the AMI ID from a marketplace product echo "Getting the latest Amazon Linux 2 AMI ID..." AMI_OUTPUT=$(aws ec2 describe-images \ --owners amazon \ --filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" "Name=state,Values=available" \ --query "sort_by(Images, &CreationDate)[-1].ImageId" \ --output text 2>&1) check_error "$AMI_OUTPUT" "ec2 describe-images" AMI_ID=$AMI_OUTPUT echo "Using AMI ID: $AMI_ID" echo "Note: In a real marketplace scenario, you would use the AMI ID from your subscribed product." # Step 5: Launch an EC2 instance echo "Launching EC2 instance with the AMI..." INSTANCE_OUTPUT=$(aws ec2 run-instances \ --image-id "$AMI_ID" \ --instance-type t2.micro \ --key-name "$KEY_NAME" \ --security-group-ids "$SECURITY_GROUP_ID" \ --count 1 2>&1) check_error "$INSTANCE_OUTPUT" "ec2 run-instances" # Extract instance ID INSTANCE_ID=$(echo "$INSTANCE_OUTPUT" | grep -o '"InstanceId": "[^"]*' | head -1 | cut -d'"' -f4) echo "Instance launched with ID: $INSTANCE_ID" # Wait for the instance to be running echo "Waiting for instance to be in running state..." aws ec2 wait instance-running --instance-ids "$INSTANCE_ID" echo "Instance is now running." # Step 6: Get instance details echo "Getting instance details..." INSTANCE_DETAILS=$(aws ec2 describe-instances \ --instance-ids "$INSTANCE_ID" \ --query "Reservations[0].Instances[0].[InstanceId,State.Name,PublicDnsName]" \ --output text 2>&1) check_error "$INSTANCE_DETAILS" "ec2 describe-instances" echo "Instance details:" echo "$INSTANCE_DETAILS" # Display summary of created resources echo "" echo "===================================================" echo "RESOURCE SUMMARY" echo "===================================================" echo "Key Pair: $KEY_NAME" echo "Security Group: $SECURITY_GROUP_NAME (ID: $SECURITY_GROUP_ID)" echo "EC2 Instance: $INSTANCE_ID" echo "" echo "To connect to your instance (once it's fully initialized):" echo "ssh -i ${KEY_NAME}.pem ec2-user@<public-dns-name>" echo "Replace <public-dns-name> with the PublicDnsName from the instance details above." echo "" # Ask user if they want to clean up resources 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 else echo "" echo "Resources have not been cleaned up. You can manually clean them up later with:" echo "1. Terminate the EC2 instance: aws ec2 terminate-instances --instance-ids $INSTANCE_ID" echo "2. Delete the security group: aws ec2 delete-security-group --group-id $SECURITY_GROUP_ID" echo "3. Delete the key pair: aws ec2 delete-key-pair --key-name $KEY_NAME" echo "" fi echo "Script completed. See $LOG_FILE for the complete log."-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create a DB subnet group
Create a DocumentDB cluster
Create a DocumentDB instance
Configure security and connectivity
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 DocumentDB - Getting Started # This script creates a DocumentDB cluster with encrypted storage, stores the # master password in Secrets Manager, and displays connection information. set -eE ############################################################################### # Configuration ############################################################################### SUFFIX=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 8 | head -n 1) CLUSTER_ID="docdb-gs-${SUFFIX}" INSTANCE_ID="${CLUSTER_ID}-inst" SUBNET_GROUP_NAME="docdb-subnet-${SUFFIX}" SECRET_NAME="docdb-secret-${SUFFIX}" MASTER_USER="docdbadmin" ENGINE_VERSION="5.0.0" INSTANCE_CLASS="db.t3.medium" DOCDB_PORT=27017 WAIT_TIMEOUT=900 TEMP_DIR=$(mktemp -d) LOG_FILE="${TEMP_DIR}/documentdb-gs.log" CREATED_RESOURCES=() ############################################################################### # Logging ############################################################################### exec > >(tee -a "$LOG_FILE") 2>&1 echo "Log file: $LOG_FILE" echo "" ############################################################################### # Region pre-check ############################################################################### CONFIGURED_REGION=$(aws configure get region 2>/dev/null || true) if [ -z "$CONFIGURED_REGION" ] && [ -z "$AWS_DEFAULT_REGION" ] && [ -z "$AWS_REGION" ]; then echo "ERROR: No AWS region configured." echo "Run 'aws configure set region <region>' or export AWS_DEFAULT_REGION." exit 1 fi REGION="${AWS_REGION:-${AWS_DEFAULT_REGION:-$CONFIGURED_REGION}}" echo "Using region: $REGION" echo "" ############################################################################### # Error handler ############################################################################### handle_error() { echo "" echo "===========================================" echo "ERROR at $1" echo "===========================================" echo "" if [ ${#CREATED_RESOURCES[@]} -gt 0 ]; then echo "Resources created before error:" for r in "${CREATED_RESOURCES[@]}"; do echo " - $r" done echo "" echo "Attempting cleanup..." cleanup_resources fi rm -rf "$TEMP_DIR" exit 1 } trap 'handle_error "line $LINENO"' ERR ############################################################################### # Wait function ############################################################################### wait_for_status() { local resource_type="$1" local resource_id="$2" local target_status="$3" local timeout="${4:-$WAIT_TIMEOUT}" local elapsed=0 local interval=30 echo "Waiting for $resource_type '$resource_id' to reach '$target_status'..." while true; do local current_status="" if [ "$resource_type" = "cluster" ]; then current_status=$(aws docdb describe-db-clusters \ --db-cluster-identifier "$resource_id" \ --query "DBClusters[0].Status" --output text 2>&1) elif [ "$resource_type" = "instance" ]; then current_status=$(aws docdb describe-db-instances \ --db-instance-identifier "$resource_id" \ --query "DBInstances[0].DBInstanceStatus" --output text 2>&1) fi if echo "$current_status" | grep -iq "error"; then echo "ERROR checking status: $current_status" return 1 fi echo " Status: $current_status ($elapsed/${timeout}s)" if [ "$current_status" = "$target_status" ]; then echo " $resource_type '$resource_id' is now '$target_status'." return 0 fi if [ "$elapsed" -ge "$timeout" ]; then echo "ERROR: Timed out after ${timeout}s waiting for $resource_type '$resource_id'." return 1 fi sleep "$interval" elapsed=$((elapsed + interval)) done } ############################################################################### # Wait for deletion ############################################################################### wait_for_deletion() { local resource_type="$1" local resource_id="$2" local timeout="${3:-$WAIT_TIMEOUT}" local elapsed=0 local interval=30 echo "Waiting for $resource_type '$resource_id' to be deleted..." while true; do local result="" if [ "$resource_type" = "cluster" ]; then result=$(aws docdb describe-db-clusters \ --db-cluster-identifier "$resource_id" \ --query "DBClusters[0].Status" --output text 2>&1) || true elif [ "$resource_type" = "instance" ]; then result=$(aws docdb describe-db-instances \ --db-instance-identifier "$resource_id" \ --query "DBInstances[0].DBInstanceStatus" --output text 2>&1) || true fi if echo "$result" | grep -iq "DBClusterNotFoundFault\|DBInstanceNotFound\|not found"; then echo " $resource_type '$resource_id' deleted." return 0 fi echo " Still deleting... ($elapsed/${timeout}s)" if [ "$elapsed" -ge "$timeout" ]; then echo "WARNING: Timed out waiting for $resource_type '$resource_id' deletion." return 1 fi sleep "$interval" elapsed=$((elapsed + interval)) done } ############################################################################### # Cleanup ############################################################################### cleanup_resources() { echo "" echo "Cleaning up resources..." echo "" # Revoke security group ingress rule if [ -n "${SG_ID:-}" ] && [ -n "${MY_IP:-}" ]; then echo "Revoking security group ingress rule..." aws ec2 revoke-security-group-ingress \ --group-id "$SG_ID" \ --protocol tcp \ --port "$DOCDB_PORT" \ --cidr "${MY_IP}/32" 2>&1 || echo "WARNING: Failed to revoke SG ingress rule." fi # Delete instance (must be deleted before cluster) if printf '%s\n' "${CREATED_RESOURCES[@]}" | grep -q "instance:"; then echo "Deleting instance '${INSTANCE_ID}'..." aws docdb delete-db-instance \ --db-instance-identifier "$INSTANCE_ID" 2>&1 || echo "WARNING: Failed to delete instance." wait_for_deletion "instance" "$INSTANCE_ID" || true fi # Delete cluster (skip final snapshot) if printf '%s\n' "${CREATED_RESOURCES[@]}" | grep -q "cluster:"; then echo "Deleting cluster '${CLUSTER_ID}'..." aws docdb delete-db-cluster \ --db-cluster-identifier "$CLUSTER_ID" \ --skip-final-snapshot 2>&1 || echo "WARNING: Failed to delete cluster." wait_for_deletion "cluster" "$CLUSTER_ID" || true fi # Delete subnet group (must wait for cluster deletion) if printf '%s\n' "${CREATED_RESOURCES[@]}" | grep -q "subnet-group:"; then echo "Deleting subnet group '${SUBNET_GROUP_NAME}'..." aws docdb delete-db-subnet-group \ --db-subnet-group-name "$SUBNET_GROUP_NAME" 2>&1 || echo "WARNING: Failed to delete subnet group." fi # Delete secret if printf '%s\n' "${CREATED_RESOURCES[@]}" | grep -q "secret:"; then echo "Deleting secret '${SECRET_NAME}'..." aws secretsmanager delete-secret \ --secret-id "$SECRET_NAME" \ --force-delete-without-recovery 2>&1 || echo "WARNING: Failed to delete secret." fi echo "" echo "Cleanup complete." } ############################################################################### # Step 1: Generate password and store in Secrets Manager ############################################################################### echo "===========================================" echo "Step 1: Create master password in Secrets Manager" echo "===========================================" echo "" # Generate a safe password (no / @ " or spaces) MASTER_PASSWORD=$(cat /dev/urandom | tr -dc 'A-Za-z0-9!#$%^&*()_+=-' | fold -w 20 | head -n 1) SECRET_OUTPUT=$(aws secretsmanager create-secret \ --name "$SECRET_NAME" \ --description "DocumentDB master password for ${CLUSTER_ID}" \ --secret-string "$MASTER_PASSWORD" \ --output text --query "ARN" 2>&1) if echo "$SECRET_OUTPUT" | grep -iq "error"; then echo "ERROR creating secret: $SECRET_OUTPUT" exit 1 fi SECRET_ARN="$SECRET_OUTPUT" CREATED_RESOURCES+=("secret:${SECRET_NAME}") echo "Secret created: $SECRET_NAME" echo "Secret ARN: $SECRET_ARN" echo "" ############################################################################### # Step 2: Find default VPC and subnets ############################################################################### echo "===========================================" echo "Step 2: Find default VPC and subnets" echo "===========================================" echo "" VPC_ID=$(aws ec2 describe-vpcs \ --filters "Name=isDefault,Values=true" \ --query "Vpcs[0].VpcId" --output text 2>&1) if echo "$VPC_ID" | grep -iq "error"; then echo "ERROR finding default VPC: $VPC_ID" exit 1 fi if [ "$VPC_ID" = "None" ] || [ -z "$VPC_ID" ]; then echo "ERROR: No default VPC found. Create one with 'aws ec2 create-default-vpc'." exit 1 fi echo "Default VPC: $VPC_ID" # Get subnets in at least 2 different AZs (space-separated) SUBNET_INFO=$(aws ec2 describe-subnets \ --filters "Name=vpc-id,Values=${VPC_ID}" "Name=default-for-az,Values=true" \ --query "Subnets[*].[SubnetId,AvailabilityZone]" --output text 2>&1) if echo "$SUBNET_INFO" | grep -iq "error"; then echo "ERROR finding subnets: $SUBNET_INFO" exit 1 fi # Collect unique AZs and their subnet IDs declare -A AZ_SUBNETS while IFS=$'\t' read -r sid az; do if [ -z "${AZ_SUBNETS[$az]+x}" ]; then AZ_SUBNETS[$az]="$sid" fi done <<< "$SUBNET_INFO" AZ_COUNT=${#AZ_SUBNETS[@]} if [ "$AZ_COUNT" -lt 2 ]; then echo "ERROR: DocumentDB requires subnets in at least 2 AZs. Found $AZ_COUNT." exit 1 fi # Build space-separated subnet ID list SUBNET_IDS="" for az in "${!AZ_SUBNETS[@]}"; do if [ -n "$SUBNET_IDS" ]; then SUBNET_IDS="${SUBNET_IDS} ${AZ_SUBNETS[$az]}" else SUBNET_IDS="${AZ_SUBNETS[$az]}" fi done echo "Subnets (${AZ_COUNT} AZs): $SUBNET_IDS" echo "" ############################################################################### # Step 3: Create subnet group ############################################################################### echo "===========================================" echo "Step 3: Create DocumentDB subnet group" echo "===========================================" echo "" SUBNET_GROUP_OUTPUT=$(aws docdb create-db-subnet-group \ --db-subnet-group-name "$SUBNET_GROUP_NAME" \ --db-subnet-group-description "Subnet group for DocumentDB getting started" \ --subnet-ids $SUBNET_IDS \ --query "DBSubnetGroup.DBSubnetGroupName" --output text 2>&1) if echo "$SUBNET_GROUP_OUTPUT" | grep -iq "error"; then echo "ERROR creating subnet group: $SUBNET_GROUP_OUTPUT" exit 1 fi CREATED_RESOURCES+=("subnet-group:${SUBNET_GROUP_NAME}") echo "Subnet group created: $SUBNET_GROUP_NAME" echo "" ############################################################################### # Step 4: Create DocumentDB cluster ############################################################################### echo "===========================================" echo "Step 4: Create DocumentDB cluster" echo "===========================================" echo "" CLUSTER_OUTPUT=$(aws docdb create-db-cluster \ --db-cluster-identifier "$CLUSTER_ID" \ --engine docdb \ --engine-version "$ENGINE_VERSION" \ --master-username "$MASTER_USER" \ --master-user-password "$MASTER_PASSWORD" \ --db-subnet-group-name "$SUBNET_GROUP_NAME" \ --storage-encrypted \ --no-deletion-protection \ --query "DBCluster.DBClusterIdentifier" --output text 2>&1) if echo "$CLUSTER_OUTPUT" | grep -iq "error"; then echo "ERROR creating cluster: $CLUSTER_OUTPUT" exit 1 fi CREATED_RESOURCES+=("cluster:${CLUSTER_ID}") echo "Cluster created: $CLUSTER_ID" echo "" wait_for_status "cluster" "$CLUSTER_ID" "available" echo "" ############################################################################### # Step 5: Create DocumentDB instance ############################################################################### echo "===========================================" echo "Step 5: Create DocumentDB instance" echo "===========================================" echo "" INSTANCE_OUTPUT=$(aws docdb create-db-instance \ --db-instance-identifier "$INSTANCE_ID" \ --db-instance-class "$INSTANCE_CLASS" \ --db-cluster-identifier "$CLUSTER_ID" \ --engine docdb \ --query "DBInstance.DBInstanceIdentifier" --output text 2>&1) if echo "$INSTANCE_OUTPUT" | grep -iq "error"; then echo "ERROR creating instance: $INSTANCE_OUTPUT" exit 1 fi CREATED_RESOURCES+=("instance:${INSTANCE_ID}") echo "Instance created: $INSTANCE_ID" echo "" wait_for_status "instance" "$INSTANCE_ID" "available" echo "" ############################################################################### # Step 6: Get cluster endpoint and security group ############################################################################### echo "===========================================" echo "Step 6: Get cluster endpoint and security group" echo "===========================================" echo "" CLUSTER_DETAILS=$(aws docdb describe-db-clusters \ --db-cluster-identifier "$CLUSTER_ID" \ --query "DBClusters[0].[Endpoint,VpcSecurityGroups[0].VpcSecurityGroupId]" \ --output text 2>&1) if echo "$CLUSTER_DETAILS" | grep -iq "error"; then echo "ERROR getting cluster details: $CLUSTER_DETAILS" exit 1 fi CLUSTER_ENDPOINT=$(echo "$CLUSTER_DETAILS" | awk '{print $1}') SG_ID=$(echo "$CLUSTER_DETAILS" | awk '{print $2}') echo "Cluster endpoint: $CLUSTER_ENDPOINT" echo "Security group: $SG_ID" echo "" ############################################################################### # Step 7: Add security group ingress for port 27017 from user's IP ############################################################################### echo "===========================================" echo "Step 7: Add security group ingress rule" echo "===========================================" echo "" # Get the user's public IP MY_IP=$(curl -s https://checkip.amazonaws.com 2>&1) if echo "$MY_IP" | grep -iq "error\|could not\|failed"; then echo "ERROR: Could not determine public IP address." exit 1 fi # Trim whitespace MY_IP=$(echo "$MY_IP" | tr -d '[:space:]') echo "Your public IP: $MY_IP" SG_RULE_OUTPUT=$(aws ec2 authorize-security-group-ingress \ --group-id "$SG_ID" \ --protocol tcp \ --port "$DOCDB_PORT" \ --cidr "${MY_IP}/32" 2>&1) if echo "$SG_RULE_OUTPUT" | grep -iq "error"; then # Ignore if rule already exists if echo "$SG_RULE_OUTPUT" | grep -iq "Duplicate"; then echo "Ingress rule already exists." else echo "ERROR adding ingress rule: $SG_RULE_OUTPUT" exit 1 fi else echo "Ingress rule added: TCP ${DOCDB_PORT} from ${MY_IP}/32" fi CREATED_RESOURCES+=("sg-rule:${SG_ID}:${MY_IP}") echo "" ############################################################################### # Step 8: Download CA certificate ############################################################################### echo "===========================================" echo "Step 8: Download Amazon DocumentDB CA certificate" echo "===========================================" echo "" CA_CERT_PATH="${TEMP_DIR}/global-bundle.pem" curl -s -o "$CA_CERT_PATH" https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem 2>&1 if [ ! -s "$CA_CERT_PATH" ]; then echo "WARNING: Failed to download CA certificate." else echo "CA certificate downloaded to: $CA_CERT_PATH" fi echo "" ############################################################################### # Step 9: Display connection information ############################################################################### echo "===========================================" echo "CONNECTION INFORMATION" echo "===========================================" echo "" echo "Cluster endpoint : $CLUSTER_ENDPOINT" echo "Port : $DOCDB_PORT" echo "Master username : $MASTER_USER" echo "Secret name : $SECRET_NAME (contains password)" echo "Security group : $SG_ID" echo "CA certificate : $CA_CERT_PATH" echo "" echo "To connect with mongosh:" echo " mongosh --tls --host ${CLUSTER_ENDPOINT} --tlsCAFile ${CA_CERT_PATH} \\" echo " --retryWrites false --username ${MASTER_USER} --password \$(aws secretsmanager get-secret-value --secret-id ${SECRET_NAME} --query SecretString --output text)" echo "" ############################################################################### # Step 10: Cleanup ############################################################################### echo "" echo "===========================================" echo "CLEANUP CONFIRMATION" echo "===========================================" echo "" echo "Resources created:" for r in "${CREATED_RESOURCES[@]}"; do echo " - $r" done echo "" echo "Do you want to clean up all created resources? (y/n): " read -r CLEANUP_CHOICE if [ "$CLEANUP_CHOICE" = "y" ] || [ "$CLEANUP_CHOICE" = "Y" ]; then cleanup_resources else echo "" echo "Resources were NOT deleted. To clean up manually, run:" echo "" echo " # Revoke security group ingress rule" echo " aws ec2 revoke-security-group-ingress --group-id ${SG_ID} --protocol tcp --port ${DOCDB_PORT} --cidr ${MY_IP}/32" echo "" echo " # Delete instance (wait for it to finish before deleting cluster)" echo " aws docdb delete-db-instance --db-instance-identifier ${INSTANCE_ID}" echo " aws docdb wait db-instance-deleted --db-instance-identifier ${INSTANCE_ID}" echo "" echo " # Delete cluster" echo " aws docdb delete-db-cluster --db-cluster-identifier ${CLUSTER_ID} --skip-final-snapshot" echo "" echo " # Delete subnet group (after cluster is deleted)" echo " aws docdb delete-db-subnet-group --db-subnet-group-name ${SUBNET_GROUP_NAME}" echo "" echo " # Delete secret" echo " aws secretsmanager delete-secret --secret-id ${SECRET_NAME} --force-delete-without-recovery" echo "" fi rm -rf "$TEMP_DIR" echo "Done."-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create a key pair
Create a security group
Stop and start your instance
Test Elastic IP persistence
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 # EC2 Basics Tutorial Script - Revised # This script demonstrates the basics of working with EC2 instances using AWS CLI # Updated to use Amazon Linux 2023 and enhanced security settings # Set up logging LOG_FILE="ec2_tutorial_$(date +%Y%m%d_%H%M%S).log" exec > >(tee -a "$LOG_FILE") 2>&1 # Function to log messages log() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" } # Function to handle errors handle_error() { log "ERROR: $1" log "Cleaning up resources..." cleanup exit 1 } # Function to clean up resources cleanup() { log "Resources created:" if [ -n "$ASSOCIATION_ID" ]; then log "- Elastic IP Association: $ASSOCIATION_ID" fi if [ -n "$ALLOCATION_ID" ]; then log "- Elastic IP Allocation: $ALLOCATION_ID (IP: $ELASTIC_IP)" fi if [ -n "$INSTANCE_ID" ]; then log "- EC2 Instance: $INSTANCE_ID" fi if [ -n "$SECURITY_GROUP_ID" ]; then log "- Security Group: $SECURITY_GROUP_ID" fi if [ -n "$KEY_NAME" ]; then log "- Key Pair: $KEY_NAME (File: $KEY_FILE)" fi read -p "Do you want to delete these resources? (y/n): " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then log "Starting cleanup..." # Track cleanup failures CLEANUP_FAILURES=0 # Disassociate Elastic IP if it exists if [ -n "$ASSOCIATION_ID" ]; then log "Disassociating Elastic IP..." if ! aws ec2 disassociate-address --association-id "$ASSOCIATION_ID"; then log "Failed to disassociate Elastic IP" ((CLEANUP_FAILURES++)) fi fi # Release Elastic IP if it exists if [ -n "$ALLOCATION_ID" ]; then log "Releasing Elastic IP..." if ! aws ec2 release-address --allocation-id "$ALLOCATION_ID"; then log "Failed to release Elastic IP" ((CLEANUP_FAILURES++)) fi fi # Terminate instance if it exists if [ -n "$INSTANCE_ID" ]; then log "Terminating instance $INSTANCE_ID..." if ! aws ec2 terminate-instances --instance-ids "$INSTANCE_ID" > /dev/null; then log "Failed to terminate instance" ((CLEANUP_FAILURES++)) else log "Waiting for instance to terminate..." if ! aws ec2 wait instance-terminated --instance-ids "$INSTANCE_ID"; then log "Failed while waiting for instance to terminate" ((CLEANUP_FAILURES++)) fi fi fi # Delete security group if it exists if [ -n "$SECURITY_GROUP_ID" ]; then log "Deleting security group..." if ! aws ec2 delete-security-group --group-id "$SECURITY_GROUP_ID"; then log "Failed to delete security group" ((CLEANUP_FAILURES++)) fi fi # Delete key pair if it exists if [ -n "$KEY_NAME" ]; then log "Deleting key pair..." if ! aws ec2 delete-key-pair --key-name "$KEY_NAME"; then log "Failed to delete key pair" ((CLEANUP_FAILURES++)) fi # Remove key file if [ -f "$KEY_FILE" ]; then log "Removing key file..." if ! rm -f "$KEY_FILE"; then log "Failed to remove key file" ((CLEANUP_FAILURES++)) fi fi fi # Report cleanup status if [ $CLEANUP_FAILURES -eq 0 ]; then log "Cleanup completed successfully." else log "WARNING: Cleanup completed with $CLEANUP_FAILURES failures. Some resources may not have been deleted properly." fi else log "Resources were not deleted." fi } # Generate random identifier for resource names RANDOM_ID=$(openssl rand -hex 4) KEY_NAME="ec2-tutorial-key-$RANDOM_ID" SG_NAME="ec2-tutorial-sg-$RANDOM_ID" # Create a directory for the key file KEY_DIR=$(mktemp -d) KEY_FILE="$KEY_DIR/$KEY_NAME.pem" log "Starting EC2 basics tutorial script" log "Random identifier: $RANDOM_ID" log "Key name: $KEY_NAME" log "Security group name: $SG_NAME" # Step 1: Create a key pair log "Creating key pair..." KEY_RESULT=$(aws ec2 create-key-pair --key-name "$KEY_NAME" --query 'KeyMaterial' --output text) if [ $? -ne 0 ] || [ -z "$KEY_RESULT" ]; then handle_error "Failed to create key pair" fi echo "$KEY_RESULT" > "$KEY_FILE" chmod 400 "$KEY_FILE" log "Created key pair and saved to $KEY_FILE" # Step 2: Create a security group log "Creating security group..." SECURITY_GROUP_ID=$(aws ec2 create-security-group \ --group-name "$SG_NAME" \ --description "Security group for EC2 tutorial" \ --query "GroupId" \ --output text) if [ $? -ne 0 ] || [ -z "$SECURITY_GROUP_ID" ]; then handle_error "Failed to create security group" fi log "Created security group: $SECURITY_GROUP_ID" # Get current public IP address for SSH access MY_IP=$(curl -s http://checkip.amazonaws.com) if [ $? -ne 0 ] || [ -z "$MY_IP" ]; then handle_error "Failed to get current IP address" fi log "Adding SSH ingress rule for IP $MY_IP..." aws ec2 authorize-security-group-ingress \ --group-id "$SECURITY_GROUP_ID" \ --protocol tcp \ --port 22 \ --cidr "$MY_IP/32" > /dev/null if [ $? -ne 0 ]; then handle_error "Failed to add security group ingress rule" fi log "Added SSH ingress rule for IP $MY_IP" # Step 3: Find an Amazon Linux 2023 AMI (updated from AL2) log "Finding latest Amazon Linux 2023 AMI..." AMI_ID=$(aws ssm get-parameters-by-path \ --path "/aws/service/ami-amazon-linux-latest" \ --query "Parameters[?contains(Name, 'al2023-ami-kernel-default-x86_64')].Value" \ --output text | head -1) if [ $? -ne 0 ] || [ -z "$AMI_ID" ]; then handle_error "Failed to find Amazon Linux 2023 AMI" fi log "Selected AMI: $AMI_ID" # Get the architecture of the AMI log "Getting AMI architecture..." AMI_ARCH=$(aws ec2 describe-images \ --image-ids "$AMI_ID" \ --query "Images[0].Architecture" \ --output text) if [ $? -ne 0 ] || [ -z "$AMI_ARCH" ]; then handle_error "Failed to get AMI architecture" fi log "AMI architecture: $AMI_ARCH" # Find a compatible instance type log "Finding compatible instance type..." # Directly use t2.micro for simplicity INSTANCE_TYPE="t2.micro" log "Using instance type: $INSTANCE_TYPE" # Step 4: Launch an EC2 instance with enhanced security log "Launching EC2 instance with IMDSv2 and encryption enabled..." INSTANCE_ID=$(aws ec2 run-instances \ --image-id "$AMI_ID" \ --instance-type "$INSTANCE_TYPE" \ --key-name "$KEY_NAME" \ --security-group-ids "$SECURITY_GROUP_ID" \ --metadata-options "HttpTokens=required,HttpEndpoint=enabled" \ --block-device-mappings "DeviceName=/dev/xvda,Ebs={Encrypted=true}" \ --count 1 \ --query 'Instances[0].InstanceId' \ --output text) if [ $? -ne 0 ] || [ -z "$INSTANCE_ID" ]; then handle_error "Failed to launch EC2 instance" fi log "Launched instance $INSTANCE_ID. Waiting for it to start..." # Wait for the instance to be running aws ec2 wait instance-running --instance-ids "$INSTANCE_ID" if [ $? -ne 0 ]; then handle_error "Failed while waiting for instance to start" fi # Get instance details INSTANCE_DETAILS=$(aws ec2 describe-instances \ --instance-ids "$INSTANCE_ID" \ --query 'Reservations[0].Instances[0].{ID:InstanceId,Type:InstanceType,State:State.Name,PublicIP:PublicIpAddress}' \ --output json) if [ $? -ne 0 ]; then handle_error "Failed to get instance details" fi log "Instance details: $INSTANCE_DETAILS" # Get the public IP address PUBLIC_IP=$(echo "$INSTANCE_DETAILS" | grep -oP '"PublicIP": "\K[^"]+') if [ -z "$PUBLIC_IP" ]; then handle_error "Failed to get instance public IP" fi log "Instance public IP: $PUBLIC_IP" log "To connect to your instance, run: ssh -i $KEY_FILE ec2-user@$PUBLIC_IP" # Pause to allow user to connect if desired read -p "Press Enter to continue to the next step (stopping and starting the instance)..." # Step 6: Stop and Start the Instance log "Stopping instance $INSTANCE_ID..." aws ec2 stop-instances --instance-ids "$INSTANCE_ID" > /dev/null if [ $? -ne 0 ]; then handle_error "Failed to stop instance" fi log "Waiting for instance to stop..." aws ec2 wait instance-stopped --instance-ids "$INSTANCE_ID" if [ $? -ne 0 ]; then handle_error "Failed while waiting for instance to stop" fi log "Instance stopped. Starting instance again..." aws ec2 start-instances --instance-ids "$INSTANCE_ID" > /dev/null if [ $? -ne 0 ]; then handle_error "Failed to start instance" fi log "Waiting for instance to start..." aws ec2 wait instance-running --instance-ids "$INSTANCE_ID" if [ $? -ne 0 ]; then handle_error "Failed while waiting for instance to start" fi # Get the new public IP address NEW_PUBLIC_IP=$(aws ec2 describe-instances \ --instance-ids "$INSTANCE_ID" \ --query 'Reservations[0].Instances[0].PublicIpAddress' \ --output text) if [ $? -ne 0 ] || [ -z "$NEW_PUBLIC_IP" ]; then handle_error "Failed to get new public IP" fi log "Instance restarted with new public IP: $NEW_PUBLIC_IP" log "To connect to your instance, run: ssh -i $KEY_FILE ec2-user@$NEW_PUBLIC_IP" # Step 7: Allocate and Associate an Elastic IP Address log "Allocating Elastic IP address..." ALLOCATION_RESULT=$(aws ec2 allocate-address \ --domain vpc \ --query '[PublicIp,AllocationId]' \ --output text) if [ $? -ne 0 ] || [ -z "$ALLOCATION_RESULT" ]; then handle_error "Failed to allocate Elastic IP" fi ELASTIC_IP=$(echo "$ALLOCATION_RESULT" | awk '{print $1}') ALLOCATION_ID=$(echo "$ALLOCATION_RESULT" | awk '{print $2}') log "Allocated Elastic IP: $ELASTIC_IP with ID: $ALLOCATION_ID" log "Associating Elastic IP with instance..." ASSOCIATION_ID=$(aws ec2 associate-address \ --instance-id "$INSTANCE_ID" \ --allocation-id "$ALLOCATION_ID" \ --query "AssociationId" \ --output text) if [ $? -ne 0 ] || [ -z "$ASSOCIATION_ID" ]; then handle_error "Failed to associate Elastic IP" fi log "Associated Elastic IP with instance. Association ID: $ASSOCIATION_ID" log "To connect to your instance using the Elastic IP, run: ssh -i $KEY_FILE ec2-user@$ELASTIC_IP" # Pause to allow user to connect if desired read -p "Press Enter to continue to the next step (testing Elastic IP persistence)..." # Step 8: Test the Elastic IP by Stopping and Starting the Instance log "Stopping instance $INSTANCE_ID to test Elastic IP persistence..." aws ec2 stop-instances --instance-ids "$INSTANCE_ID" > /dev/null if [ $? -ne 0 ]; then handle_error "Failed to stop instance" fi log "Waiting for instance to stop..." aws ec2 wait instance-stopped --instance-ids "$INSTANCE_ID" if [ $? -ne 0 ]; then handle_error "Failed while waiting for instance to stop" fi log "Instance stopped. Starting instance again..." aws ec2 start-instances --instance-ids "$INSTANCE_ID" > /dev/null if [ $? -ne 0 ]; then handle_error "Failed to start instance" fi log "Waiting for instance to start..." aws ec2 wait instance-running --instance-ids "$INSTANCE_ID" if [ $? -ne 0 ]; then handle_error "Failed while waiting for instance to start" fi # Verify the Elastic IP is still associated CURRENT_IP=$(aws ec2 describe-instances \ --instance-ids "$INSTANCE_ID" \ --query 'Reservations[0].Instances[0].PublicIpAddress' \ --output text) if [ $? -ne 0 ] || [ -z "$CURRENT_IP" ]; then handle_error "Failed to get current public IP" fi log "Current public IP address: $CURRENT_IP" log "Elastic IP address: $ELASTIC_IP" if [ "$CURRENT_IP" = "$ELASTIC_IP" ]; then log "Success! The Elastic IP is still associated with your instance." else log "Something went wrong. The Elastic IP is not associated with your instance." fi log "To connect to your instance, run: ssh -i $KEY_FILE ec2-user@$ELASTIC_IP" # Step 9: Clean up resources log "Tutorial completed successfully!" cleanup exit 0-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create an EC2 key pair
Set up storage and prepare your application
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 # EMR Getting Started Tutorial Script # This script automates the steps in the Amazon EMR Getting Started tutorial # Set up logging LOG_FILE="emr-tutorial.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting Amazon EMR Getting Started Tutorial Script" echo "Logging to $LOG_FILE" # Function to handle errors handle_error() { echo "ERROR: $1" echo "Resources created so far:" if [ -n "$BUCKET_NAME" ]; then echo "- S3 Bucket: $BUCKET_NAME"; fi if [ -n "$CLUSTER_ID" ]; then echo "- EMR Cluster: $CLUSTER_ID"; fi echo "Attempting to clean up resources..." cleanup exit 1 } # Function to clean up resources cleanup() { 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,,}" == "y" ]]; then echo "Starting cleanup process..." # Terminate EMR cluster if it exists if [ -n "$CLUSTER_ID" ]; then echo "Terminating EMR cluster: $CLUSTER_ID" aws emr terminate-clusters --cluster-ids "$CLUSTER_ID" echo "Waiting for cluster to terminate..." aws emr wait cluster-terminated --cluster-id "$CLUSTER_ID" echo "Cluster terminated successfully." fi # Delete S3 bucket and contents if it exists if [ -n "$BUCKET_NAME" ]; then echo "Deleting S3 bucket contents: $BUCKET_NAME" aws s3 rm "s3://$BUCKET_NAME" --recursive echo "Deleting S3 bucket: $BUCKET_NAME" aws s3 rb "s3://$BUCKET_NAME" fi echo "Cleanup completed." else echo "Cleanup skipped. Resources will remain in your AWS account." echo "To avoid ongoing charges, remember to manually delete these resources." fi } # Generate a random identifier for S3 bucket RANDOM_ID=$(openssl rand -hex 6) BUCKET_NAME="emr${RANDOM_ID}" echo "Using bucket name: $BUCKET_NAME" # Create S3 bucket echo "Creating S3 bucket: $BUCKET_NAME" aws s3 mb "s3://$BUCKET_NAME" || handle_error "Failed to create S3 bucket" echo "S3 bucket created successfully." # Create PySpark script echo "Creating PySpark script: health_violations.py" cat > health_violations.py << 'EOL' import argparse from pyspark.sql import SparkSession def calculate_red_violations(data_source, output_uri): """ Processes sample food establishment inspection data and queries the data to find the top 10 establishments with the most Red violations from 2006 to 2020. :param data_source: The URI of your food establishment data CSV, such as 's3://emr-tutorial-bucket/food-establishment-data.csv'. :param output_uri: The URI where output is written, such as 's3://emr-tutorial-bucket/restaurant_violation_results'. """ with SparkSession.builder.appName("Calculate Red Health Violations").getOrCreate() as spark: # Load the restaurant violation CSV data if data_source is not None: restaurants_df = spark.read.option("header", "true").csv(data_source) # Create an in-memory DataFrame to query restaurants_df.createOrReplaceTempView("restaurant_violations") # Create a DataFrame of the top 10 restaurants with the most Red violations top_red_violation_restaurants = spark.sql("""SELECT name, count(*) AS total_red_violations FROM restaurant_violations WHERE violation_type = 'RED' GROUP BY name ORDER BY total_red_violations DESC LIMIT 10""") # Write the results to the specified output URI top_red_violation_restaurants.write.option("header", "true").mode("overwrite").csv(output_uri) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( '--data_source', help="The URI for you CSV restaurant data, like an S3 bucket location.") parser.add_argument( '--output_uri', help="The URI where output is saved, like an S3 bucket location.") args = parser.parse_args() calculate_red_violations(args.data_source, args.output_uri) EOL # Upload PySpark script to S3 echo "Uploading PySpark script to S3" aws s3 cp health_violations.py "s3://$BUCKET_NAME/" || handle_error "Failed to upload PySpark script" echo "PySpark script uploaded successfully." # Download and prepare sample data echo "Downloading sample data" curl -o food_establishment_data.zip https://docs.aws.amazon.com/emr/latest/ManagementGuide/samples/food_establishment_data.zip || handle_error "Failed to download sample data" unzip -o food_establishment_data.zip || handle_error "Failed to unzip sample data" echo "Sample data downloaded and extracted successfully." # Upload sample data to S3 echo "Uploading sample data to S3" aws s3 cp food_establishment_data.csv "s3://$BUCKET_NAME/" || handle_error "Failed to upload sample data" echo "Sample data uploaded successfully." # Create IAM default roles for EMR echo "Creating IAM default roles for EMR" aws emr create-default-roles || handle_error "Failed to create default roles" echo "IAM default roles created successfully." # Check if EC2 key pair exists echo "Checking for EC2 key pair" KEY_PAIRS=$(aws ec2 describe-key-pairs --query "KeyPairs[*].KeyName" --output text) if [ -z "$KEY_PAIRS" ]; then echo "No EC2 key pairs found. Creating a new key pair..." KEY_NAME="emr-tutorial-key-$RANDOM_ID" aws ec2 create-key-pair --key-name "$KEY_NAME" --query "KeyMaterial" --output text > "${KEY_NAME}.pem" chmod 400 "${KEY_NAME}.pem" echo "Created new key pair: $KEY_NAME" else # Use the first available key pair KEY_NAME=$(echo "$KEY_PAIRS" | awk '{print $1}') echo "Using existing key pair: $KEY_NAME" fi # Launch EMR cluster echo "Launching EMR cluster with Spark" CLUSTER_RESPONSE=$(aws emr create-cluster \ --name "EMR Tutorial Cluster" \ --release-label emr-6.10.0 \ --applications Name=Spark \ --ec2-attributes KeyName="$KEY_NAME" \ --instance-type m5.xlarge \ --instance-count 3 \ --use-default-roles \ --log-uri "s3://$BUCKET_NAME/logs/") # Check for errors in the response if echo "$CLUSTER_RESPONSE" | grep -i "error" > /dev/null; then handle_error "Failed to create EMR cluster: $CLUSTER_RESPONSE" fi # Extract cluster ID CLUSTER_ID=$(echo "$CLUSTER_RESPONSE" | grep -o '"ClusterId": "[^"]*' | cut -d'"' -f4) if [ -z "$CLUSTER_ID" ]; then handle_error "Failed to extract cluster ID from response" fi echo "EMR cluster created with ID: $CLUSTER_ID" # Wait for cluster to be ready echo "Waiting for cluster to be ready (this may take several minutes)..." aws emr wait cluster-running --cluster-id "$CLUSTER_ID" || handle_error "Cluster failed to reach running state" # Check if cluster is in WAITING state CLUSTER_STATE=$(aws emr describe-cluster --cluster-id "$CLUSTER_ID" --query "Cluster.Status.State" --output text) if [ "$CLUSTER_STATE" != "WAITING" ]; then echo "Waiting for cluster to reach WAITING state..." while [ "$CLUSTER_STATE" != "WAITING" ]; do sleep 30 CLUSTER_STATE=$(aws emr describe-cluster --cluster-id "$CLUSTER_ID" --query "Cluster.Status.State" --output text) echo "Current cluster state: $CLUSTER_STATE" # Check for error states if [[ "$CLUSTER_STATE" == "TERMINATED_WITH_ERRORS" || "$CLUSTER_STATE" == "TERMINATED" ]]; then handle_error "Cluster entered error state: $CLUSTER_STATE" fi done fi echo "Cluster is now in WAITING state and ready to accept work." # Submit Spark application as a step echo "Submitting Spark application as a step" STEP_RESPONSE=$(aws emr add-steps \ --cluster-id "$CLUSTER_ID" \ --steps Type=Spark,Name="Health Violations Analysis",ActionOnFailure=CONTINUE,Args=["s3://$BUCKET_NAME/health_violations.py","--data_source","s3://$BUCKET_NAME/food_establishment_data.csv","--output_uri","s3://$BUCKET_NAME/results/"]) # Check for errors in the response if echo "$STEP_RESPONSE" | grep -i "error" > /dev/null; then handle_error "Failed to submit step: $STEP_RESPONSE" fi # FIXED: Check if jq is available before using it # Extract step ID using the appropriate method based on available tools if command -v jq &> /dev/null; then # Use jq if available echo "Using jq to parse JSON response" STEP_ID=$(echo "$STEP_RESPONSE" | jq -r '.StepIds[0]') else # Fallback to grep/awk if jq is not available echo "jq not found, using grep for parsing" STEP_ID=$(echo "$STEP_RESPONSE" | grep -o '"StepIds":\s*\[\s*"[^"]*"' | grep -o 's-[A-Z0-9]*') if [ -z "$STEP_ID" ]; then # Another fallback method STEP_ID=$(echo "$STEP_RESPONSE" | grep -o '"StepIds":\s*\[\s*"[^"]*' | grep -o 's-[A-Z0-9]*') if [ -z "$STEP_ID" ]; then # One more attempt with a different pattern STEP_ID=$(echo "$STEP_RESPONSE" | grep -o 's-[A-Z0-9]*') if [ -z "$STEP_ID" ]; then echo "Full step response: $STEP_RESPONSE" handle_error "Failed to extract step ID from response" fi fi fi fi if [ -z "$STEP_ID" ] || [ "$STEP_ID" == "null" ]; then echo "Full step response: $STEP_RESPONSE" handle_error "Failed to extract valid step ID from response" fi echo "Step submitted with ID: $STEP_ID" # Wait for step to complete echo "Waiting for step to complete (this may take several minutes)..." aws emr wait step-complete --cluster-id "$CLUSTER_ID" --step-id "$STEP_ID" || handle_error "Step failed to complete" # Check step status STEP_STATE=$(aws emr describe-step --cluster-id "$CLUSTER_ID" --step-id "$STEP_ID" --query "Step.Status.State" --output text) if [ "$STEP_STATE" != "COMPLETED" ]; then handle_error "Step did not complete successfully. Final state: $STEP_STATE" fi echo "Step completed successfully." # View results echo "Listing output files in S3" aws s3 ls "s3://$BUCKET_NAME/results/" || handle_error "Failed to list output files" # Download results echo "Downloading results file" RESULT_FILE=$(aws s3 ls "s3://$BUCKET_NAME/results/" | grep -o "part-[0-9]*.csv" | head -1) if [ -z "$RESULT_FILE" ]; then echo "No result file found with pattern 'part-[0-9]*.csv'. Trying to find any CSV file..." RESULT_FILE=$(aws s3 ls "s3://$BUCKET_NAME/results/" | grep -o "part-.*\.csv" | head -1) if [ -z "$RESULT_FILE" ]; then echo "Listing all files in results directory:" aws s3 ls "s3://$BUCKET_NAME/results/" handle_error "No result file found in the output directory" fi fi aws s3 cp "s3://$BUCKET_NAME/results/$RESULT_FILE" ./results.csv || handle_error "Failed to download results file" echo "Results downloaded to results.csv" echo "Top 10 establishments with the most red violations:" cat results.csv # Display SSH connection information echo "" echo "To connect to the cluster via SSH, use the following command:" echo "aws emr ssh --cluster-id $CLUSTER_ID --key-pair-file ${KEY_NAME}.pem" # Display summary of created resources echo "" echo "===========================================" echo "RESOURCES CREATED" echo "===========================================" echo "- S3 Bucket: $BUCKET_NAME" echo "- EMR Cluster: $CLUSTER_ID" echo "- Results file: results.csv" if [ -f "${KEY_NAME}.pem" ]; then echo "- EC2 Key Pair: $KEY_NAME (saved to ${KEY_NAME}.pem)" fi # Offer to clean up resources cleanup echo "Script completed successfully."-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Set up security group for ElastiCache access
Create a Valkey serverless cache
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 ElastiCache Getting Started Script # This script creates a Valkey serverless cache, configures security groups, # and demonstrates how to connect to and use the cache. # Set up logging LOG_FILE="elasticache_tutorial_$(date +%Y%m%d_%H%M%S).log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting ElastiCache tutorial script. Logging to $LOG_FILE" echo "============================================================" # Function to handle errors handle_error() { echo "ERROR: $1" echo "Resources created:" if [ -n "$CACHE_NAME" ]; then echo "- ElastiCache serverless cache: $CACHE_NAME" fi if [ -n "$SG_RULE_6379" ] || [ -n "$SG_RULE_6380" ]; then echo "- Security group rules for ports 6379 and 6380" fi echo "Please clean up these resources manually." exit 1 } # Generate a random identifier for resource names RANDOM_ID=$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | fold -w 8 | head -n 1) CACHE_NAME="valkey-cache-${RANDOM_ID}" echo "Using cache name: $CACHE_NAME" # Step 1: Set up security group for ElastiCache access echo "Step 1: Setting up security group for ElastiCache access..." # Get default security group ID echo "Getting default security group ID..." SG_ID=$(aws ec2 describe-security-groups \ --filters Name=group-name,Values=default \ --query "SecurityGroups[0].GroupId" \ --output text) if [[ -z "$SG_ID" || "$SG_ID" == "None" ]]; then handle_error "Failed to get default security group ID" fi echo "Default security group ID: $SG_ID" # Add inbound rule for port 6379 echo "Adding inbound rule for port 6379..." SG_RULE_6379=$(aws ec2 authorize-security-group-ingress \ --group-id "$SG_ID" \ --protocol tcp \ --port 6379 \ --cidr 0.0.0.0/0 \ --query "SecurityGroupRules[0].SecurityGroupRuleId" \ --output text 2>&1) # Check for errors in the output if echo "$SG_RULE_6379" | grep -i "error" > /dev/null; then # If the rule already exists, this is not a fatal error if echo "$SG_RULE_6379" | grep -i "already exists" > /dev/null; then echo "Rule for port 6379 already exists, continuing..." SG_RULE_6379="existing" else handle_error "Failed to add security group rule for port 6379: $SG_RULE_6379" fi fi # Add inbound rule for port 6380 echo "Adding inbound rule for port 6380..." SG_RULE_6380=$(aws ec2 authorize-security-group-ingress \ --group-id "$SG_ID" \ --protocol tcp \ --port 6380 \ --cidr 0.0.0.0/0 \ --query "SecurityGroupRules[0].SecurityGroupRuleId" \ --output text 2>&1) # Check for errors in the output if echo "$SG_RULE_6380" | grep -i "error" > /dev/null; then # If the rule already exists, this is not a fatal error if echo "$SG_RULE_6380" | grep -i "already exists" > /dev/null; then echo "Rule for port 6380 already exists, continuing..." SG_RULE_6380="existing" else handle_error "Failed to add security group rule for port 6380: $SG_RULE_6380" fi fi echo "Security group rules added successfully." echo "" echo "SECURITY NOTE: The security group rules created allow access from any IP address (0.0.0.0/0)." echo "This is not recommended for production environments. For production," echo "you should restrict access to specific IP ranges or security groups." echo "" # Step 2: Create a Valkey serverless cache echo "Step 2: Creating Valkey serverless cache..." CREATE_RESULT=$(aws elasticache create-serverless-cache \ --serverless-cache-name "$CACHE_NAME" \ --engine valkey 2>&1) # Check for errors in the output if echo "$CREATE_RESULT" | grep -i "error" > /dev/null; then handle_error "Failed to create serverless cache: $CREATE_RESULT" fi echo "Cache creation initiated. Waiting for cache to become available..." # Step 3: Check the status of the cache creation echo "Step 3: Checking cache status..." # Wait for the cache to become active MAX_ATTEMPTS=30 ATTEMPT=1 CACHE_STATUS="" while [[ $ATTEMPT -le $MAX_ATTEMPTS ]]; do echo "Checking cache status (attempt $ATTEMPT of $MAX_ATTEMPTS)..." DESCRIBE_RESULT=$(aws elasticache describe-serverless-caches \ --serverless-cache-name "$CACHE_NAME" 2>&1) # Check for errors in the output if echo "$DESCRIBE_RESULT" | grep -i "error" > /dev/null; then handle_error "Failed to describe serverless cache: $DESCRIBE_RESULT" fi # Extract status using grep and awk for more reliable parsing CACHE_STATUS=$(echo "$DESCRIBE_RESULT" | grep -o '"Status": "[^"]*"' | awk -F'"' '{print $4}') echo "Current status: $CACHE_STATUS" if [[ "${CACHE_STATUS,,}" == "available" ]]; then echo "Cache is now available!" break elif [[ "${CACHE_STATUS,,}" == "create-failed" ]]; then handle_error "Cache creation failed. Please check the AWS console for details." fi if [[ $ATTEMPT -lt $MAX_ATTEMPTS ]]; then echo "Waiting 30 seconds..." sleep 30 fi ((ATTEMPT++)) done if [[ "${CACHE_STATUS,,}" != "available" ]]; then handle_error "Cache did not become available within the expected time. Last status: $CACHE_STATUS" fi # Step 4: Find your cache endpoint echo "Step 4: Getting cache endpoint..." ENDPOINT=$(aws elasticache describe-serverless-caches \ --serverless-cache-name "$CACHE_NAME" \ --query "ServerlessCaches[0].Endpoint.Address" \ --output text) if [[ -z "$ENDPOINT" || "$ENDPOINT" == "None" ]]; then handle_error "Failed to get cache endpoint" fi echo "Cache endpoint: $ENDPOINT" # Step 5: Instructions for connecting to the cache echo "" echo "============================================================" echo "Your Valkey serverless cache has been successfully created!" echo "Cache Name: $CACHE_NAME" echo "Endpoint: $ENDPOINT" echo "============================================================" echo "" echo "To connect to your cache from an EC2 instance, follow these steps:" echo "" echo "1. Install valkey-cli on your EC2 instance:" echo " sudo amazon-linux-extras install epel -y" echo " sudo yum install gcc jemalloc-devel openssl-devel tcl tcl-devel -y" echo " wget https://github.com/valkey-io/valkey/archive/refs/tags/8.0.0.tar.gz" echo " tar xvzf 8.0.0.tar.gz" echo " cd valkey-8.0.0" echo " make BUILD_TLS=yes" echo "" echo "2. Connect to your cache using valkey-cli:" echo " src/valkey-cli -h $ENDPOINT --tls -p 6379" echo "" echo "3. Once connected, you can run commands like:" echo " set mykey \"Hello ElastiCache\"" echo " get mykey" echo "" # Prompt for cleanup echo "" echo "===========================================" echo "CLEANUP CONFIRMATION" echo "===========================================" echo "Resources created:" echo "- ElastiCache serverless cache: $CACHE_NAME" if [ "$SG_RULE_6379" != "existing" ] || [ "$SG_RULE_6380" != "existing" ]; then echo "- Security group rules for ports 6379 and 6380" fi echo "" echo "Do you want to clean up all created resources? (y/n): " read -r CLEANUP_CHOICE if [[ "${CLEANUP_CHOICE,,}" == "y" ]]; then echo "Starting cleanup process..." # Step 7: Delete the cache echo "Deleting serverless cache $CACHE_NAME..." DELETE_RESULT=$(aws elasticache delete-serverless-cache \ --serverless-cache-name "$CACHE_NAME" 2>&1) # Check for errors in the output if echo "$DELETE_RESULT" | grep -i "error" > /dev/null; then echo "WARNING: Failed to delete serverless cache: $DELETE_RESULT" echo "Please delete the cache manually from the AWS console." else echo "Cache deletion initiated. This may take several minutes to complete." fi # Only attempt to remove security group rules if we created them if [ "$SG_RULE_6379" != "existing" ]; then echo "Removing security group rule for port 6379..." aws ec2 revoke-security-group-ingress \ --group-id "$SG_ID" \ --protocol tcp \ --port 6379 \ --cidr 0.0.0.0/0 fi if [ "$SG_RULE_6380" != "existing" ]; then echo "Removing security group rule for port 6380..." aws ec2 revoke-security-group-ingress \ --group-id "$SG_ID" \ --protocol tcp \ --port 6380 \ --cidr 0.0.0.0/0 fi echo "Cleanup completed." else echo "Cleanup skipped. Resources will remain in your AWS account." echo "To clean up later, run:" echo "aws elasticache delete-serverless-cache --serverless-cache-name $CACHE_NAME" if [ "$SG_RULE_6379" != "existing" ] || [ "$SG_RULE_6380" != "existing" ]; then echo "And remove the security group rules for ports 6379 and 6380 from security group $SG_ID" fi fi echo "" echo "Script completed. See $LOG_FILE for the full log." echo "============================================================"-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create an MSK cluster
Create IAM permissions for MSK access
Create a client machine
Get bootstrap brokers
Set up the client machine
Create a topic and produce/consume data
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 MSK Getting Started Tutorial Script - Version 8 # This script automates the steps in the Amazon MSK Getting Started tutorial # It creates an MSK cluster, sets up IAM permissions, creates a client machine, # and configures the client to interact with the cluster # Set up logging LOG_FILE="msk_tutorial_$(date +%Y%m%d_%H%M%S).log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting Amazon MSK Getting Started Tutorial Script - Version 8" echo "Logging to $LOG_FILE" echo "==============================================" # Function to handle errors handle_error() { echo "ERROR: $1" echo "Resources created so far:" if [ -n "$CLUSTER_ARN" ]; then echo "- MSK Cluster: $CLUSTER_ARN"; fi if [ -n "$POLICY_ARN" ]; then echo "- IAM Policy: $POLICY_ARN"; fi if [ -n "$ROLE_NAME" ]; then echo "- IAM Role: $ROLE_NAME"; fi if [ -n "$INSTANCE_PROFILE_NAME" ]; then echo "- IAM Instance Profile: $INSTANCE_PROFILE_NAME"; fi if [ -n "$CLIENT_SG_ID" ]; then echo "- Client Security Group: $CLIENT_SG_ID"; fi if [ -n "$INSTANCE_ID" ]; then echo "- EC2 Instance: $INSTANCE_ID"; fi if [ -n "$KEY_NAME" ]; then echo "- Key Pair: $KEY_NAME"; fi echo "Attempting to clean up resources..." cleanup_resources exit 1 } # Function to check if a resource exists resource_exists() { local resource_type="$1" local resource_id="$2" case "$resource_type" in "cluster") aws kafka describe-cluster --cluster-arn "$resource_id" &>/dev/null ;; "policy") aws iam get-policy --policy-arn "$resource_id" &>/dev/null ;; "role") aws iam get-role --role-name "$resource_id" &>/dev/null ;; "instance-profile") aws iam get-instance-profile --instance-profile-name "$resource_id" &>/dev/null ;; "security-group") aws ec2 describe-security-groups --group-ids "$resource_id" &>/dev/null ;; "instance") aws ec2 describe-instances --instance-ids "$resource_id" --query 'Reservations[0].Instances[0].State.Name' --output text | grep -v "terminated" &>/dev/null ;; "key-pair") aws ec2 describe-key-pairs --key-names "$resource_id" &>/dev/null ;; esac } # Function to remove security group references remove_security_group_references() { local sg_id="$1" if [ -z "$sg_id" ]; then echo "No security group ID provided for reference removal" return fi echo "Removing security group references for $sg_id" # Get all security groups in the VPC that might reference our client security group local vpc_security_groups=$(aws ec2 describe-security-groups \ --filters "Name=vpc-id,Values=$DEFAULT_VPC_ID" \ --query 'SecurityGroups[].GroupId' \ --output text 2>/dev/null) if [ -n "$vpc_security_groups" ]; then for other_sg in $vpc_security_groups; do if [ "$other_sg" != "$sg_id" ]; then echo "Checking security group $other_sg for references to $sg_id" # Get the security group details in JSON format local sg_details=$(aws ec2 describe-security-groups \ --group-ids "$other_sg" \ --output json 2>/dev/null) if [ -n "$sg_details" ]; then # Check if our security group is referenced in inbound rules local has_inbound_ref=$(echo "$sg_details" | grep -o "\"GroupId\": \"$sg_id\"" | head -1) if [ -n "$has_inbound_ref" ]; then echo "Found inbound rules in $other_sg referencing $sg_id, removing them..." # Try to remove common rule types echo "Attempting to remove all-traffic rule" aws ec2 revoke-security-group-ingress \ --group-id "$other_sg" \ --protocol all \ --source-group "$sg_id" 2>/dev/null || echo "No all-traffic rule to remove" # Try to remove TCP rules on common ports for port in 22 80 443 9092 9094 9096; do aws ec2 revoke-security-group-ingress \ --group-id "$other_sg" \ --protocol tcp \ --port "$port" \ --source-group "$sg_id" 2>/dev/null || true done # Try to remove UDP rules aws ec2 revoke-security-group-ingress \ --group-id "$other_sg" \ --protocol udp \ --source-group "$sg_id" 2>/dev/null || true fi # Check for outbound rules (less common but possible) local has_outbound_ref=$(echo "$sg_details" | grep -A 20 "IpPermissionsEgress" | grep -o "\"GroupId\": \"$sg_id\"" | head -1) if [ -n "$has_outbound_ref" ]; then echo "Found outbound rules in $other_sg referencing $sg_id, removing them..." aws ec2 revoke-security-group-egress \ --group-id "$other_sg" \ --protocol all \ --source-group "$sg_id" 2>/dev/null || echo "No outbound all-traffic rule to remove" fi fi fi done fi echo "Completed security group reference removal for $sg_id" } # Function to clean up resources cleanup_resources() { echo "Cleaning up resources..." # Delete EC2 instance if it exists if [ -n "$INSTANCE_ID" ] && resource_exists "instance" "$INSTANCE_ID"; then echo "Terminating EC2 instance: $INSTANCE_ID" aws ec2 terminate-instances --instance-ids "$INSTANCE_ID" || echo "Failed to terminate instance" echo "Waiting for instance to terminate..." aws ec2 wait instance-terminated --instance-ids "$INSTANCE_ID" || echo "Failed to wait for instance termination" fi # Delete MSK cluster first (to remove dependencies on security group) if [ -n "$CLUSTER_ARN" ] && resource_exists "cluster" "$CLUSTER_ARN"; then echo "Deleting MSK cluster: $CLUSTER_ARN" aws kafka delete-cluster --cluster-arn "$CLUSTER_ARN" || echo "Failed to delete cluster" # Wait a bit for the cluster deletion to start echo "Waiting 30 seconds for cluster deletion to begin..." sleep 30 fi # Remove security group references before attempting deletion if [ -n "$CLIENT_SG_ID" ] && resource_exists "security-group" "$CLIENT_SG_ID"; then remove_security_group_references "$CLIENT_SG_ID" echo "Deleting security group: $CLIENT_SG_ID" # Try multiple times with longer delays to ensure dependencies are removed for i in {1..10}; do if aws ec2 delete-security-group --group-id "$CLIENT_SG_ID"; then echo "Security group deleted successfully" break fi echo "Failed to delete security group (attempt $i/10), retrying in 30 seconds..." sleep 30 done fi # Delete key pair if it exists if [ -n "$KEY_NAME" ] && resource_exists "key-pair" "$KEY_NAME"; then echo "Deleting key pair: $KEY_NAME" aws ec2 delete-key-pair --key-name "$KEY_NAME" || echo "Failed to delete key pair" fi # Remove role from instance profile if [ -n "$ROLE_NAME" ] && [ -n "$INSTANCE_PROFILE_NAME" ] && resource_exists "instance-profile" "$INSTANCE_PROFILE_NAME"; then echo "Removing role from instance profile" aws iam remove-role-from-instance-profile \ --instance-profile-name "$INSTANCE_PROFILE_NAME" \ --role-name "$ROLE_NAME" || echo "Failed to remove role from instance profile" fi # Delete instance profile if [ -n "$INSTANCE_PROFILE_NAME" ] && resource_exists "instance-profile" "$INSTANCE_PROFILE_NAME"; then echo "Deleting instance profile: $INSTANCE_PROFILE_NAME" aws iam delete-instance-profile \ --instance-profile-name "$INSTANCE_PROFILE_NAME" || echo "Failed to delete instance profile" fi # Detach policy from role if [ -n "$ROLE_NAME" ] && [ -n "$POLICY_ARN" ] && resource_exists "role" "$ROLE_NAME"; then echo "Detaching policy from role" aws iam detach-role-policy \ --role-name "$ROLE_NAME" \ --policy-arn "$POLICY_ARN" || echo "Failed to detach policy" fi # Delete role if [ -n "$ROLE_NAME" ] && resource_exists "role" "$ROLE_NAME"; then echo "Deleting role: $ROLE_NAME" aws iam delete-role --role-name "$ROLE_NAME" || echo "Failed to delete role" fi # Delete policy if [ -n "$POLICY_ARN" ] && resource_exists "policy" "$POLICY_ARN"; then echo "Deleting policy: $POLICY_ARN" aws iam delete-policy --policy-arn "$POLICY_ARN" || echo "Failed to delete policy" fi echo "Cleanup completed" } # Function to find a suitable subnet and instance type combination find_suitable_subnet_and_instance_type() { local vpc_id="$1" local -a subnet_array=("${!2}") # List of instance types to try, in order of preference local instance_types=("t3.micro" "t2.micro" "t3.small" "t2.small") echo "Finding suitable subnet and instance type combination..." for instance_type in "${instance_types[@]}"; do echo "Trying instance type: $instance_type" for subnet_id in "${subnet_array[@]}"; do # Get the availability zone for this subnet local az=$(aws ec2 describe-subnets \ --subnet-ids "$subnet_id" \ --query 'Subnets[0].AvailabilityZone' \ --output text) echo " Checking subnet $subnet_id in AZ $az" # Check if this instance type is available in this AZ local available=$(aws ec2 describe-instance-type-offerings \ --location-type availability-zone \ --filters "Name=location,Values=$az" "Name=instance-type,Values=$instance_type" \ --query 'InstanceTypeOfferings[0].InstanceType' \ --output text 2>/dev/null) if [ "$available" = "$instance_type" ]; then echo " ✓ Found suitable combination: $instance_type in $az (subnet: $subnet_id)" SELECTED_SUBNET_ID="$subnet_id" SELECTED_INSTANCE_TYPE="$instance_type" return 0 else echo " ✗ $instance_type not available in $az" fi done done echo "ERROR: Could not find any suitable subnet and instance type combination" return 1 } # Generate unique identifiers RANDOM_SUFFIX=$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | fold -w 8 | head -n 1) CLUSTER_NAME="MSKTutorialCluster-${RANDOM_SUFFIX}" POLICY_NAME="msk-tutorial-policy-${RANDOM_SUFFIX}" ROLE_NAME="msk-tutorial-role-${RANDOM_SUFFIX}" INSTANCE_PROFILE_NAME="msk-tutorial-profile-${RANDOM_SUFFIX}" SG_NAME="MSKClientSecurityGroup-${RANDOM_SUFFIX}" echo "Using the following resource names:" echo "- Cluster Name: $CLUSTER_NAME" echo "- Policy Name: $POLICY_NAME" echo "- Role Name: $ROLE_NAME" echo "- Instance Profile Name: $INSTANCE_PROFILE_NAME" echo "- Security Group Name: $SG_NAME" echo "==============================================" # Step 1: Create an MSK Provisioned cluster echo "Step 1: Creating MSK Provisioned cluster" # Get the default VPC ID first echo "Getting default VPC..." DEFAULT_VPC_ID=$(aws ec2 describe-vpcs \ --filters "Name=is-default,Values=true" \ --query "Vpcs[0].VpcId" \ --output text) if [ -z "$DEFAULT_VPC_ID" ] || [ "$DEFAULT_VPC_ID" = "None" ]; then handle_error "Could not find default VPC. Please ensure you have a default VPC in your region." fi echo "Default VPC ID: $DEFAULT_VPC_ID" # Get available subnets in the default VPC echo "Getting available subnets in the default VPC..." SUBNETS=$(aws ec2 describe-subnets \ --filters "Name=vpc-id,Values=$DEFAULT_VPC_ID" "Name=default-for-az,Values=true" \ --query "Subnets[0:3].SubnetId" \ --output text) # Convert space-separated subnet IDs to an array read -r -a SUBNET_ARRAY <<< "$SUBNETS" if [ ${#SUBNET_ARRAY[@]} -lt 3 ]; then handle_error "Not enough subnets available in the default VPC. Need at least 3 subnets, found ${#SUBNET_ARRAY[@]}." fi # Get default security group for the default VPC echo "Getting default security group for the default VPC..." DEFAULT_SG=$(aws ec2 describe-security-groups \ --filters "Name=group-name,Values=default" "Name=vpc-id,Values=$DEFAULT_VPC_ID" \ --query "SecurityGroups[0].GroupId" \ --output text) if [ -z "$DEFAULT_SG" ] || [ "$DEFAULT_SG" = "None" ]; then handle_error "Could not find default security group for VPC $DEFAULT_VPC_ID" fi echo "Creating MSK cluster: $CLUSTER_NAME" echo "Using VPC: $DEFAULT_VPC_ID" echo "Using subnets: ${SUBNET_ARRAY[0]} ${SUBNET_ARRAY[1]} ${SUBNET_ARRAY[2]}" echo "Using security group: $DEFAULT_SG" # Create the MSK cluster with proper error handling CLUSTER_RESPONSE=$(aws kafka create-cluster \ --cluster-name "$CLUSTER_NAME" \ --broker-node-group-info "{\"InstanceType\": \"kafka.t3.small\", \"ClientSubnets\": [\"${SUBNET_ARRAY[0]}\", \"${SUBNET_ARRAY[1]}\", \"${SUBNET_ARRAY[2]}\"], \"SecurityGroups\": [\"$DEFAULT_SG\"]}" \ --kafka-version "3.6.0" \ --number-of-broker-nodes 3 \ --encryption-info "{\"EncryptionInTransit\": {\"InCluster\": true, \"ClientBroker\": \"TLS\"}}" 2>&1) # Check if the command was successful if [ $? -ne 0 ]; then handle_error "Failed to create MSK cluster: $CLUSTER_RESPONSE" fi # Extract the cluster ARN using grep CLUSTER_ARN=$(echo "$CLUSTER_RESPONSE" | grep -o '"ClusterArn": "[^"]*' | cut -d'"' -f4) if [ -z "$CLUSTER_ARN" ]; then handle_error "Failed to extract cluster ARN from response: $CLUSTER_RESPONSE" fi echo "MSK cluster creation initiated. ARN: $CLUSTER_ARN" echo "Waiting for cluster to become active (this may take 15-20 minutes)..." # Wait for the cluster to become active while true; do CLUSTER_STATUS=$(aws kafka describe-cluster --cluster-arn "$CLUSTER_ARN" --query "ClusterInfo.State" --output text 2>/dev/null) if [ $? -ne 0 ]; then echo "Failed to get cluster status. Retrying in 30 seconds..." sleep 30 continue fi echo "Current cluster status: $CLUSTER_STATUS" if [ "$CLUSTER_STATUS" = "ACTIVE" ]; then echo "Cluster is now active!" break elif [ "$CLUSTER_STATUS" = "FAILED" ]; then handle_error "Cluster creation failed" fi echo "Still waiting for cluster to become active... (checking again in 60 seconds)" sleep 60 done echo "==============================================" # Step 2: Create an IAM role granting access to create topics on the Amazon MSK cluster echo "Step 2: Creating IAM policy and role" # Get account ID and region ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) REGION=$(aws configure get region) if [ -z "$REGION" ]; then REGION=$(aws ec2 describe-availability-zones --query 'AvailabilityZones[0].RegionName' --output text) fi if [ -z "$ACCOUNT_ID" ] || [ -z "$REGION" ]; then handle_error "Could not determine AWS account ID or region" fi echo "Account ID: $ACCOUNT_ID" echo "Region: $REGION" # Create IAM policy echo "Creating IAM policy: $POLICY_NAME" POLICY_DOCUMENT="{ \"Version\": \"2012-10-17\", \"Statement\": [ { \"Effect\": \"Allow\", \"Action\": [ \"kafka-cluster:Connect\", \"kafka-cluster:AlterCluster\", \"kafka-cluster:DescribeCluster\" ], \"Resource\": [ \"$CLUSTER_ARN\" ] }, { \"Effect\": \"Allow\", \"Action\": [ \"kafka-cluster:*Topic*\", \"kafka-cluster:WriteData\", \"kafka-cluster:ReadData\" ], \"Resource\": [ \"arn:aws:kafka:$REGION:$ACCOUNT_ID:topic/$CLUSTER_NAME/*\" ] }, { \"Effect\": \"Allow\", \"Action\": [ \"kafka-cluster:AlterGroup\", \"kafka-cluster:DescribeGroup\" ], \"Resource\": [ \"arn:aws:kafka:$REGION:$ACCOUNT_ID:group/$CLUSTER_NAME/*\" ] } ] }" POLICY_RESPONSE=$(aws iam create-policy \ --policy-name "$POLICY_NAME" \ --policy-document "$POLICY_DOCUMENT" 2>&1) # Check if the command was successful if [ $? -ne 0 ]; then handle_error "Failed to create IAM policy: $POLICY_RESPONSE" fi # Extract the policy ARN using grep POLICY_ARN=$(echo "$POLICY_RESPONSE" | grep -o '"Arn": "[^"]*' | cut -d'"' -f4) if [ -z "$POLICY_ARN" ]; then handle_error "Failed to extract policy ARN from response: $POLICY_RESPONSE" fi echo "IAM policy created. ARN: $POLICY_ARN" # Create IAM role for EC2 echo "Creating IAM role: $ROLE_NAME" TRUST_POLICY="{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}" ROLE_RESPONSE=$(aws iam create-role \ --role-name "$ROLE_NAME" \ --assume-role-policy-document "$TRUST_POLICY" 2>&1) # Check if the command was successful if [ $? -ne 0 ]; then handle_error "Failed to create IAM role: $ROLE_RESPONSE" fi echo "IAM role created: $ROLE_NAME" # Attach policy to role echo "Attaching policy to role" ATTACH_RESPONSE=$(aws iam attach-role-policy \ --role-name "$ROLE_NAME" \ --policy-arn "$POLICY_ARN" 2>&1) # Check if the command was successful if [ $? -ne 0 ]; then handle_error "Failed to attach policy to role: $ATTACH_RESPONSE" fi echo "Policy attached to role" # Create instance profile and add role to it echo "Creating instance profile: $INSTANCE_PROFILE_NAME" PROFILE_RESPONSE=$(aws iam create-instance-profile \ --instance-profile-name "$INSTANCE_PROFILE_NAME" 2>&1) # Check if the command was successful if [ $? -ne 0 ]; then handle_error "Failed to create instance profile: $PROFILE_RESPONSE" fi echo "Instance profile created" echo "Adding role to instance profile" ADD_ROLE_RESPONSE=$(aws iam add-role-to-instance-profile \ --instance-profile-name "$INSTANCE_PROFILE_NAME" \ --role-name "$ROLE_NAME" 2>&1) # Check if the command was successful if [ $? -ne 0 ]; then handle_error "Failed to add role to instance profile: $ADD_ROLE_RESPONSE" fi echo "Role added to instance profile" # Wait a moment for IAM propagation echo "Waiting 10 seconds for IAM propagation..." sleep 10 echo "==============================================" # Step 3: Create a client machine echo "Step 3: Creating client machine" # Find a suitable subnet and instance type combination if ! find_suitable_subnet_and_instance_type "$DEFAULT_VPC_ID" SUBNET_ARRAY[@]; then handle_error "Could not find a suitable subnet and instance type combination" fi echo "Selected subnet: $SELECTED_SUBNET_ID" echo "Selected instance type: $SELECTED_INSTANCE_TYPE" # Verify the subnet is in the same VPC we're using SUBNET_VPC_ID=$(aws ec2 describe-subnets \ --subnet-ids "$SELECTED_SUBNET_ID" \ --query 'Subnets[0].VpcId' \ --output text) if [ "$SUBNET_VPC_ID" != "$DEFAULT_VPC_ID" ]; then handle_error "Subnet VPC ($SUBNET_VPC_ID) does not match default VPC ($DEFAULT_VPC_ID)" fi echo "VPC ID: $SUBNET_VPC_ID" # Get security group ID from the MSK cluster echo "Getting security group ID from the MSK cluster" MSK_SG_ID=$(aws kafka describe-cluster \ --cluster-arn "$CLUSTER_ARN" \ --query 'ClusterInfo.BrokerNodeGroupInfo.SecurityGroups[0]' \ --output text) if [ -z "$MSK_SG_ID" ] || [ "$MSK_SG_ID" = "None" ]; then handle_error "Failed to get security group ID from cluster" fi echo "MSK security group ID: $MSK_SG_ID" # Create security group for client echo "Creating security group for client: $SG_NAME" CLIENT_SG_RESPONSE=$(aws ec2 create-security-group \ --group-name "$SG_NAME" \ --description "Security group for MSK client" \ --vpc-id "$DEFAULT_VPC_ID" 2>&1) # Check if the command was successful if [ $? -ne 0 ]; then handle_error "Failed to create security group: $CLIENT_SG_RESPONSE" fi # Extract the security group ID using grep CLIENT_SG_ID=$(echo "$CLIENT_SG_RESPONSE" | grep -o '"GroupId": "[^"]*' | cut -d'"' -f4) if [ -z "$CLIENT_SG_ID" ]; then handle_error "Failed to extract security group ID from response: $CLIENT_SG_RESPONSE" fi echo "Client security group created. ID: $CLIENT_SG_ID" # Allow SSH access to client from your IP only echo "Getting your public IP address" MY_IP=$(curl -s https://checkip.amazonaws.com 2>/dev/null) if [ -z "$MY_IP" ]; then echo "Warning: Could not determine your IP address. Using 0.0.0.0/0 (not recommended for production)" MY_IP="0.0.0.0/0" else MY_IP="$MY_IP/32" echo "Your IP address: $MY_IP" fi echo "Adding SSH ingress rule to client security group" SSH_RULE_RESPONSE=$(aws ec2 authorize-security-group-ingress \ --group-id "$CLIENT_SG_ID" \ --protocol tcp \ --port 22 \ --cidr "$MY_IP" 2>&1) # Check if the command was successful if [ $? -ne 0 ]; then echo "Warning: Failed to add SSH ingress rule: $SSH_RULE_RESPONSE" echo "You may need to manually add SSH access to security group $CLIENT_SG_ID" fi echo "SSH ingress rule added" # Update MSK security group to allow traffic from client security group echo "Adding ingress rule to MSK security group to allow traffic from client" MSK_RULE_RESPONSE=$(aws ec2 authorize-security-group-ingress \ --group-id "$MSK_SG_ID" \ --protocol all \ --source-group "$CLIENT_SG_ID" 2>&1) # Check if the command was successful if [ $? -ne 0 ]; then echo "Warning: Failed to add ingress rule to MSK security group: $MSK_RULE_RESPONSE" echo "You may need to manually add ingress rule to security group $MSK_SG_ID" fi echo "Ingress rule added to MSK security group" # Create key pair KEY_NAME="MSKKeyPair-${RANDOM_SUFFIX}" echo "Creating key pair: $KEY_NAME" KEY_RESPONSE=$(aws ec2 create-key-pair --key-name "$KEY_NAME" --query 'KeyMaterial' --output text 2>&1) # Check if the command was successful if [ $? -ne 0 ]; then handle_error "Failed to create key pair: $KEY_RESPONSE" fi # Save the private key to a file KEY_FILE="${KEY_NAME}.pem" echo "$KEY_RESPONSE" > "$KEY_FILE" chmod 400 "$KEY_FILE" echo "Key pair created and saved to $KEY_FILE" # Get the latest Amazon Linux 2 AMI echo "Getting latest Amazon Linux 2 AMI ID" AMI_ID=$(aws ec2 describe-images \ --owners amazon \ --filters "Name=name,Values=amzn2-ami-hvm-*-x86_64-gp2" "Name=state,Values=available" \ --query "sort_by(Images, &CreationDate)[-1].ImageId" \ --output text 2>/dev/null) if [ -z "$AMI_ID" ] || [ "$AMI_ID" = "None" ]; then handle_error "Failed to get Amazon Linux 2 AMI ID" fi echo "Using AMI ID: $AMI_ID" # Launch EC2 instance with the selected subnet and instance type echo "Launching EC2 instance" echo "Instance type: $SELECTED_INSTANCE_TYPE" echo "Subnet: $SELECTED_SUBNET_ID" INSTANCE_RESPONSE=$(aws ec2 run-instances \ --image-id "$AMI_ID" \ --instance-type "$SELECTED_INSTANCE_TYPE" \ --key-name "$KEY_NAME" \ --security-group-ids "$CLIENT_SG_ID" \ --subnet-id "$SELECTED_SUBNET_ID" \ --iam-instance-profile "Name=$INSTANCE_PROFILE_NAME" \ --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=MSKTutorialClient-${RANDOM_SUFFIX}}]" 2>&1) # Check if the command was successful if [ $? -ne 0 ]; then handle_error "Failed to launch EC2 instance: $INSTANCE_RESPONSE" fi # Extract the instance ID using grep INSTANCE_ID=$(echo "$INSTANCE_RESPONSE" | grep -o '"InstanceId": "[^"]*' | head -1 | cut -d'"' -f4) if [ -z "$INSTANCE_ID" ]; then handle_error "Failed to extract instance ID from response: $INSTANCE_RESPONSE" fi echo "EC2 instance launched successfully. ID: $INSTANCE_ID" echo "Waiting for instance to be running..." # Wait for the instance to be running aws ec2 wait instance-running --instance-ids "$INSTANCE_ID" if [ $? -ne 0 ]; then handle_error "Instance failed to reach running state" fi # Wait a bit more for the instance to initialize echo "Instance is running. Waiting 30 seconds for initialization..." sleep 30 # Get public DNS name of instance CLIENT_DNS=$(aws ec2 describe-instances \ --instance-ids "$INSTANCE_ID" \ --query 'Reservations[0].Instances[0].PublicDnsName' \ --output text) if [ -z "$CLIENT_DNS" ] || [ "$CLIENT_DNS" = "None" ]; then echo "Warning: Could not get public DNS name for instance. Trying public IP..." CLIENT_DNS=$(aws ec2 describe-instances \ --instance-ids "$INSTANCE_ID" \ --query 'Reservations[0].Instances[0].PublicIpAddress' \ --output text) if [ -z "$CLIENT_DNS" ] || [ "$CLIENT_DNS" = "None" ]; then handle_error "Failed to get public DNS name or IP address for instance" fi fi echo "Client instance DNS/IP: $CLIENT_DNS" echo "==============================================" # Get bootstrap brokers with improved logic echo "Getting bootstrap brokers" MAX_RETRIES=10 RETRY_COUNT=0 BOOTSTRAP_BROKERS="" AUTH_METHOD="" while [ -z "$BOOTSTRAP_BROKERS" ] || [ "$BOOTSTRAP_BROKERS" = "None" ]; do # Get the full bootstrap brokers response BOOTSTRAP_RESPONSE=$(aws kafka get-bootstrap-brokers \ --cluster-arn "$CLUSTER_ARN" 2>/dev/null) if [ $? -eq 0 ] && [ -n "$BOOTSTRAP_RESPONSE" ]; then # Try to get IAM authentication brokers first using grep BOOTSTRAP_BROKERS=$(echo "$BOOTSTRAP_RESPONSE" | grep -o '"BootstrapBrokerStringSaslIam": "[^"]*' | cut -d'"' -f4) if [ -n "$BOOTSTRAP_BROKERS" ]; then AUTH_METHOD="IAM" else # Fall back to TLS authentication BOOTSTRAP_BROKERS=$(echo "$BOOTSTRAP_RESPONSE" | grep -o '"BootstrapBrokerStringTls": "[^"]*' | cut -d'"' -f4) if [ -n "$BOOTSTRAP_BROKERS" ]; then AUTH_METHOD="TLS" fi fi fi RETRY_COUNT=$((RETRY_COUNT + 1)) if [ "$RETRY_COUNT" -ge "$MAX_RETRIES" ]; then echo "Warning: Could not get bootstrap brokers after $MAX_RETRIES attempts." echo "You may need to manually retrieve them later using:" echo "aws kafka get-bootstrap-brokers --cluster-arn $CLUSTER_ARN" BOOTSTRAP_BROKERS="BOOTSTRAP_BROKERS_NOT_AVAILABLE" AUTH_METHOD="UNKNOWN" break fi if [ -z "$BOOTSTRAP_BROKERS" ] || [ "$BOOTSTRAP_BROKERS" = "None" ]; then echo "Bootstrap brokers not available yet. Retrying in 30 seconds... (Attempt $RETRY_COUNT/$MAX_RETRIES)" sleep 30 fi done echo "Bootstrap brokers: $BOOTSTRAP_BROKERS" echo "Authentication method: $AUTH_METHOD" echo "==============================================" # Create setup script for the client machine echo "Creating setup script for the client machine" cat > setup_client.sh << 'EOF' #!/bin/bash # Set up logging LOG_FILE="client_setup_$(date +%Y%m%d_%H%M%S).log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting client setup" echo "==============================================" # Install Java echo "Installing Java" sudo yum -y install java-11 # Set environment variables echo "Setting up environment variables" export KAFKA_VERSION="3.6.0" echo "KAFKA_VERSION=$KAFKA_VERSION" # Download and extract Apache Kafka echo "Downloading Apache Kafka" wget https://archive.apache.org/dist/kafka/$KAFKA_VERSION/kafka_2.13-$KAFKA_VERSION.tgz if [ $? -ne 0 ]; then echo "Failed to download Kafka. Trying alternative mirror..." wget https://www.apache.org/dyn/closer.cgi?path=/kafka/$KAFKA_VERSION/kafka_2.13-$KAFKA_VERSION.tgz fi echo "Extracting Kafka" tar -xzf kafka_2.13-$KAFKA_VERSION.tgz export KAFKA_ROOT=$(pwd)/kafka_2.13-$KAFKA_VERSION echo "KAFKA_ROOT=$KAFKA_ROOT" # Download the MSK IAM authentication package (needed for both IAM and TLS) echo "Downloading MSK IAM authentication package" cd $KAFKA_ROOT/libs wget https://github.com/aws/aws-msk-iam-auth/releases/latest/download/aws-msk-iam-auth-1.1.6-all.jar if [ $? -ne 0 ]; then echo "Failed to download specific version. Trying to get latest version..." LATEST_VERSION=$(curl -s https://api.github.com/repos/aws/aws-msk-iam-auth/releases/latest | grep -o '"tag_name": "[^"]*' | cut -d'"' -f4) wget https://github.com/aws/aws-msk-iam-auth/releases/download/$LATEST_VERSION/aws-msk-iam-auth-$LATEST_VERSION-all.jar if [ $? -ne 0 ]; then echo "Failed to download IAM auth package. Please check the URL and try again." exit 1 fi export CLASSPATH=$KAFKA_ROOT/libs/aws-msk-iam-auth-$LATEST_VERSION-all.jar else export CLASSPATH=$KAFKA_ROOT/libs/aws-msk-iam-auth-1.1.6-all.jar fi echo "CLASSPATH=$CLASSPATH" # Create client properties file based on authentication method echo "Creating client properties file" cd $KAFKA_ROOT/config # The AUTH_METHOD_PLACEHOLDER will be replaced by the script AUTH_METHOD="AUTH_METHOD_PLACEHOLDER" if [ "$AUTH_METHOD" = "IAM" ]; then echo "Configuring for IAM authentication" cat > client.properties << 'EOT' security.protocol=SASL_SSL sasl.mechanism=AWS_MSK_IAM sasl.jaas.config=software.amazon.msk.auth.iam.IAMLoginModule required; sasl.client.callback.handler.class=software.amazon.msk.auth.iam.IAMClientCallbackHandler EOT elif [ "$AUTH_METHOD" = "TLS" ]; then echo "Configuring for TLS authentication" cat > client.properties << 'EOT' security.protocol=SSL EOT else echo "Unknown authentication method. Creating basic TLS configuration." cat > client.properties << 'EOT' security.protocol=SSL EOT fi echo "Client setup completed" echo "==============================================" # Create a script to set up environment variables cat > ~/setup_env.sh << 'EOT' #!/bin/bash export KAFKA_VERSION="3.6.0" export KAFKA_ROOT=~/kafka_2.13-$KAFKA_VERSION export CLASSPATH=$KAFKA_ROOT/libs/aws-msk-iam-auth-1.1.6-all.jar export BOOTSTRAP_SERVER="BOOTSTRAP_SERVER_PLACEHOLDER" export AUTH_METHOD="AUTH_METHOD_PLACEHOLDER" echo "Environment variables set:" echo "KAFKA_VERSION=$KAFKA_VERSION" echo "KAFKA_ROOT=$KAFKA_ROOT" echo "CLASSPATH=$CLASSPATH" echo "BOOTSTRAP_SERVER=$BOOTSTRAP_SERVER" echo "AUTH_METHOD=$AUTH_METHOD" EOT chmod +x ~/setup_env.sh echo "Created environment setup script: ~/setup_env.sh" echo "Run 'source ~/setup_env.sh' to set up your environment" EOF # Replace placeholders in the setup script if [ -n "$BOOTSTRAP_BROKERS" ] && [ "$BOOTSTRAP_BROKERS" != "None" ] && [ "$BOOTSTRAP_BROKERS" != "BOOTSTRAP_BROKERS_NOT_AVAILABLE" ]; then sed -i "s|BOOTSTRAP_SERVER_PLACEHOLDER|$BOOTSTRAP_BROKERS|g" setup_client.sh else # If bootstrap brokers are not available, provide instructions to get them sed -i "s|BOOTSTRAP_SERVER_PLACEHOLDER|\$(aws kafka get-bootstrap-brokers --cluster-arn $CLUSTER_ARN --query 'BootstrapBrokerStringTls' --output text)|g" setup_client.sh fi # Replace auth method placeholder sed -i "s|AUTH_METHOD_PLACEHOLDER|$AUTH_METHOD|g" setup_client.sh echo "Setup script created" echo "==============================================" # Display summary of created resources echo "" echo "==============================================" echo "RESOURCE SUMMARY" echo "==============================================" echo "MSK Cluster ARN: $CLUSTER_ARN" echo "MSK Cluster Name: $CLUSTER_NAME" echo "Authentication Method: $AUTH_METHOD" echo "IAM Policy ARN: $POLICY_ARN" echo "IAM Role Name: $ROLE_NAME" echo "IAM Instance Profile: $INSTANCE_PROFILE_NAME" echo "Client Security Group: $CLIENT_SG_ID" echo "EC2 Instance ID: $INSTANCE_ID" echo "EC2 Instance Type: $SELECTED_INSTANCE_TYPE" echo "EC2 Instance DNS: $CLIENT_DNS" echo "Key Pair: $KEY_NAME (saved to $KEY_FILE)" echo "Bootstrap Brokers: $BOOTSTRAP_BROKERS" echo "==============================================" echo "" # Instructions for connecting to the instance and setting up the client echo "NEXT STEPS:" echo "1. Connect to your EC2 instance:" echo " ssh -i $KEY_FILE ec2-user@$CLIENT_DNS" echo "" echo "2. Upload the setup script to your instance:" echo " scp -i $KEY_FILE setup_client.sh ec2-user@$CLIENT_DNS:~/" echo "" echo "3. Run the setup script on your instance:" echo " ssh -i $KEY_FILE ec2-user@$CLIENT_DNS 'chmod +x ~/setup_client.sh && ~/setup_client.sh'" echo "" echo "4. Source the environment setup script:" echo " source ~/setup_env.sh" echo "" # Provide different instructions based on authentication method if [ "$AUTH_METHOD" = "IAM" ]; then echo "5. Create a Kafka topic (using IAM authentication):" echo " \$KAFKA_ROOT/bin/kafka-topics.sh --create \\" echo " --bootstrap-server \$BOOTSTRAP_SERVER \\" echo " --command-config \$KAFKA_ROOT/config/client.properties \\" echo " --replication-factor 3 \\" echo " --partitions 1 \\" echo " --topic MSKTutorialTopic" echo "" echo "6. Start a producer:" echo " \$KAFKA_ROOT/bin/kafka-console-producer.sh \\" echo " --broker-list \$BOOTSTRAP_SERVER \\" echo " --producer.config \$KAFKA_ROOT/config/client.properties \\" echo " --topic MSKTutorialTopic" echo "" echo "7. Start a consumer:" echo " \$KAFKA_ROOT/bin/kafka-console-consumer.sh \\" echo " --bootstrap-server \$BOOTSTRAP_SERVER \\" echo " --consumer.config \$KAFKA_ROOT/config/client.properties \\" echo " --topic MSKTutorialTopic \\" echo " --from-beginning" elif [ "$AUTH_METHOD" = "TLS" ]; then echo "5. Create a Kafka topic (using TLS authentication):" echo " \$KAFKA_ROOT/bin/kafka-topics.sh --create \\" echo " --bootstrap-server \$BOOTSTRAP_SERVER \\" echo " --command-config \$KAFKA_ROOT/config/client.properties \\" echo " --replication-factor 3 \\" echo " --partitions 1 \\" echo " --topic MSKTutorialTopic" echo "" echo "6. Start a producer:" echo " \$KAFKA_ROOT/bin/kafka-console-producer.sh \\" echo " --broker-list \$BOOTSTRAP_SERVER \\" echo " --producer.config \$KAFKA_ROOT/config/client.properties \\" echo " --topic MSKTutorialTopic" echo "" echo "7. Start a consumer:" echo " \$KAFKA_ROOT/bin/kafka-console-consumer.sh \\" echo " --bootstrap-server \$BOOTSTRAP_SERVER \\" echo " --consumer.config \$KAFKA_ROOT/config/client.properties \\" echo " --topic MSKTutorialTopic \\" echo " --from-beginning" else echo "5. Manually retrieve bootstrap brokers and configure authentication:" echo " aws kafka get-bootstrap-brokers --cluster-arn $CLUSTER_ARN" fi echo "" echo "8. Verify the topic was created:" echo " \$KAFKA_ROOT/bin/kafka-topics.sh --list \\" echo " --bootstrap-server \$BOOTSTRAP_SERVER \\" echo " --command-config \$KAFKA_ROOT/config/client.properties" echo "==============================================" echo "" # Ask if user wants to clean up resources 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 will not be cleaned up. You can manually clean them up later." echo "To clean up resources, run this script again and choose 'y' when prompted." fi echo "Script completed successfully!"-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create a VPC for your Neptune database
Create subnets in multiple availability zones
Configure security for your Neptune database
Create a Neptune DB subnet group
Create a Neptune DB cluster and instance
Add data to your graph database
Query your graph database
- 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 Neptune Getting Started Script # This script creates an Amazon Neptune database cluster and demonstrates basic operations # Set up logging LOG_FILE="neptune-setup.log" echo "Starting Neptune setup at $(date)" > "$LOG_FILE" # Function to log commands and their output log_cmd() { echo "Running: $1" | tee -a "$LOG_FILE" eval "$1" 2>&1 | tee -a "$LOG_FILE" return ${PIPESTATUS[0]} } # Function to check for errors in command output check_error() { local cmd_output="$1" local cmd_status="$2" local error_msg="$3" if [[ $cmd_status -ne 0 || "$cmd_output" =~ [Ee][Rr][Rr][Oo][Rr] ]]; then echo "ERROR: $error_msg" | tee -a "$LOG_FILE" cleanup_on_error exit 1 fi } # Function to clean up resources on error cleanup_on_error() { echo "Error encountered. Cleaning up resources..." | tee -a "$LOG_FILE" # Only attempt to delete resources that were successfully created if [[ -n "$DB_INSTANCE_ID" ]]; then echo "Deleting DB instance $DB_INSTANCE_ID..." | tee -a "$LOG_FILE" log_cmd "aws neptune delete-db-instance --db-instance-identifier $DB_INSTANCE_ID --skip-final-snapshot" log_cmd "aws neptune wait db-instance-deleted --db-instance-identifier $DB_INSTANCE_ID" fi if [[ -n "$DB_CLUSTER_ID" ]]; then echo "Deleting DB cluster $DB_CLUSTER_ID..." | tee -a "$LOG_FILE" log_cmd "aws neptune delete-db-cluster --db-cluster-identifier $DB_CLUSTER_ID --skip-final-snapshot" fi if [[ -n "$DB_SUBNET_GROUP" ]]; then echo "Deleting DB subnet group $DB_SUBNET_GROUP..." | tee -a "$LOG_FILE" log_cmd "aws neptune delete-db-subnet-group --db-subnet-group-name $DB_SUBNET_GROUP" fi if [[ -n "$SECURITY_GROUP_ID" ]]; then echo "Deleting security group $SECURITY_GROUP_ID..." | tee -a "$LOG_FILE" log_cmd "aws ec2 delete-security-group --group-id $SECURITY_GROUP_ID" fi if [[ -n "$SUBNET_IDS" ]]; then for SUBNET_ID in $SUBNET_IDS; do echo "Deleting subnet $SUBNET_ID..." | tee -a "$LOG_FILE" log_cmd "aws ec2 delete-subnet --subnet-id $SUBNET_ID" done fi if [[ -n "$VPC_ID" ]]; then echo "Deleting VPC $VPC_ID..." | tee -a "$LOG_FILE" log_cmd "aws ec2 delete-vpc --vpc-id $VPC_ID" fi } # Generate random identifier for resource names RANDOM_ID=$(openssl rand -hex 4) VPC_NAME="neptune-vpc-$RANDOM_ID" DB_SUBNET_GROUP="neptune-subnet-group-$RANDOM_ID" DB_CLUSTER_ID="neptune-cluster-$RANDOM_ID" DB_INSTANCE_ID="neptune-instance-$RANDOM_ID" SG_NAME="neptune-sg-$RANDOM_ID" echo "Using random identifier: $RANDOM_ID" | tee -a "$LOG_FILE" echo "VPC Name: $VPC_NAME" | tee -a "$LOG_FILE" echo "DB Subnet Group: $DB_SUBNET_GROUP" | tee -a "$LOG_FILE" echo "DB Cluster ID: $DB_CLUSTER_ID" | tee -a "$LOG_FILE" echo "DB Instance ID: $DB_INSTANCE_ID" | tee -a "$LOG_FILE" echo "Security Group Name: $SG_NAME" | tee -a "$LOG_FILE" # Step 1: Create VPC echo "Creating VPC..." | tee -a "$LOG_FILE" VPC_OUTPUT=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 --tag-specifications "ResourceType=vpc,Tags=[{Key=Name,Value=$VPC_NAME}]" --output json) check_error "$VPC_OUTPUT" $? "Failed to create VPC" VPC_ID=$(echo "$VPC_OUTPUT" | grep -o '"VpcId": "[^"]*' | cut -d'"' -f4) echo "VPC created with ID: $VPC_ID" | tee -a "$LOG_FILE" # Enable DNS support for the VPC log_cmd "aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-support" log_cmd "aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-hostnames" # Step 2: Create Internet Gateway and attach to VPC echo "Creating Internet Gateway..." | tee -a "$LOG_FILE" IGW_OUTPUT=$(aws ec2 create-internet-gateway --output json) check_error "$IGW_OUTPUT" $? "Failed to create Internet Gateway" IGW_ID=$(echo "$IGW_OUTPUT" | grep -o '"InternetGatewayId": "[^"]*' | cut -d'"' -f4) echo "Internet Gateway created with ID: $IGW_ID" | tee -a "$LOG_FILE" log_cmd "aws ec2 attach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID" # Step 3: Create subnets in different AZs echo "Creating subnets..." | tee -a "$LOG_FILE" # Get available AZs AZ_OUTPUT=$(aws ec2 describe-availability-zones --output json) check_error "$AZ_OUTPUT" $? "Failed to get availability zones" # Extract first 3 AZ names AZ1=$(echo "$AZ_OUTPUT" | grep -o '"ZoneName": "[^"]*' | cut -d'"' -f4 | head -1) AZ2=$(echo "$AZ_OUTPUT" | grep -o '"ZoneName": "[^"]*' | cut -d'"' -f4 | head -2 | tail -1) AZ3=$(echo "$AZ_OUTPUT" | grep -o '"ZoneName": "[^"]*' | cut -d'"' -f4 | head -3 | tail -1) # Create 3 subnets in different AZs SUBNET1_OUTPUT=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.1.0/24 --availability-zone $AZ1 --output json) check_error "$SUBNET1_OUTPUT" $? "Failed to create subnet 1" SUBNET1_ID=$(echo "$SUBNET1_OUTPUT" | grep -o '"SubnetId": "[^"]*' | cut -d'"' -f4) SUBNET2_OUTPUT=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.2.0/24 --availability-zone $AZ2 --output json) check_error "$SUBNET2_OUTPUT" $? "Failed to create subnet 2" SUBNET2_ID=$(echo "$SUBNET2_OUTPUT" | grep -o '"SubnetId": "[^"]*' | cut -d'"' -f4) SUBNET3_OUTPUT=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.3.0/24 --availability-zone $AZ3 --output json) check_error "$SUBNET3_OUTPUT" $? "Failed to create subnet 3" SUBNET3_ID=$(echo "$SUBNET3_OUTPUT" | grep -o '"SubnetId": "[^"]*' | cut -d'"' -f4) SUBNET_IDS="$SUBNET1_ID $SUBNET2_ID $SUBNET3_ID" echo "Created subnets: $SUBNET1_ID, $SUBNET2_ID, $SUBNET3_ID" | tee -a "$LOG_FILE" # Step 4: Create route table and add route to Internet Gateway echo "Creating route table..." | tee -a "$LOG_FILE" ROUTE_TABLE_OUTPUT=$(aws ec2 create-route-table --vpc-id $VPC_ID --output json) check_error "$ROUTE_TABLE_OUTPUT" $? "Failed to create route table" ROUTE_TABLE_ID=$(echo "$ROUTE_TABLE_OUTPUT" | grep -o '"RouteTableId": "[^"]*' | cut -d'"' -f4) echo "Route table created with ID: $ROUTE_TABLE_ID" | tee -a "$LOG_FILE" # Add route to Internet Gateway log_cmd "aws ec2 create-route --route-table-id $ROUTE_TABLE_ID --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID" # Associate route table with subnets log_cmd "aws ec2 associate-route-table --route-table-id $ROUTE_TABLE_ID --subnet-id $SUBNET1_ID" log_cmd "aws ec2 associate-route-table --route-table-id $ROUTE_TABLE_ID --subnet-id $SUBNET2_ID" log_cmd "aws ec2 associate-route-table --route-table-id $ROUTE_TABLE_ID --subnet-id $SUBNET3_ID" # Step 5: Create security group echo "Creating security group..." | tee -a "$LOG_FILE" SG_OUTPUT=$(aws ec2 create-security-group --group-name $SG_NAME --description "Security group for Neptune" --vpc-id $VPC_ID --output json) check_error "$SG_OUTPUT" $? "Failed to create security group" SECURITY_GROUP_ID=$(echo "$SG_OUTPUT" | grep -o '"GroupId": "[^"]*' | cut -d'"' -f4) echo "Security group created with ID: $SECURITY_GROUP_ID" | tee -a "$LOG_FILE" # Add inbound rule for Neptune port (8182) # Note: In production, you should restrict this to specific IP ranges echo "Adding security group rule for Neptune port 8182..." | tee -a "$LOG_FILE" log_cmd "aws ec2 authorize-security-group-ingress --group-id $SECURITY_GROUP_ID --protocol tcp --port 8182 --cidr 10.0.0.0/16" # Step 6: Create DB subnet group echo "Creating DB subnet group..." | tee -a "$LOG_FILE" DB_SUBNET_GROUP_OUTPUT=$(aws neptune create-db-subnet-group --db-subnet-group-name $DB_SUBNET_GROUP --db-subnet-group-description "Subnet group for Neptune" --subnet-ids $SUBNET1_ID $SUBNET2_ID $SUBNET3_ID --output json) check_error "$DB_SUBNET_GROUP_OUTPUT" $? "Failed to create DB subnet group" echo "DB subnet group created: $DB_SUBNET_GROUP" | tee -a "$LOG_FILE" # Step 7: Create Neptune DB cluster echo "Creating Neptune DB cluster..." | tee -a "$LOG_FILE" DB_CLUSTER_OUTPUT=$(aws neptune create-db-cluster --db-cluster-identifier $DB_CLUSTER_ID --engine neptune --vpc-security-group-ids $SECURITY_GROUP_ID --db-subnet-group-name $DB_SUBNET_GROUP --output json) check_error "$DB_CLUSTER_OUTPUT" $? "Failed to create Neptune DB cluster" echo "Neptune DB cluster created: $DB_CLUSTER_ID" | tee -a "$LOG_FILE" # Step 8: Create Neptune DB instance echo "Creating Neptune DB instance..." | tee -a "$LOG_FILE" DB_INSTANCE_OUTPUT=$(aws neptune create-db-instance --db-instance-identifier $DB_INSTANCE_ID --db-instance-class db.r5.large --engine neptune --db-cluster-identifier $DB_CLUSTER_ID --output json) check_error "$DB_INSTANCE_OUTPUT" $? "Failed to create Neptune DB instance" echo "Neptune DB instance created: $DB_INSTANCE_ID" | tee -a "$LOG_FILE" # Step 9: Wait for the DB instance to become available echo "Waiting for Neptune DB instance to become available..." | tee -a "$LOG_FILE" log_cmd "aws neptune wait db-instance-available --db-instance-identifier $DB_INSTANCE_ID" # Step 10: Get the Neptune endpoint echo "Getting Neptune endpoint..." | tee -a "$LOG_FILE" ENDPOINT_OUTPUT=$(aws neptune describe-db-clusters --db-cluster-identifier $DB_CLUSTER_ID --output json) check_error "$ENDPOINT_OUTPUT" $? "Failed to get Neptune endpoint" NEPTUNE_ENDPOINT=$(echo "$ENDPOINT_OUTPUT" | grep -o '"Endpoint": "[^"]*' | cut -d'"' -f4) echo "Neptune endpoint: $NEPTUNE_ENDPOINT" | tee -a "$LOG_FILE" # Step 11: Display information about how to connect to Neptune echo "" | tee -a "$LOG_FILE" echo "=============================================" | tee -a "$LOG_FILE" echo "NEPTUNE SETUP COMPLETE" | tee -a "$LOG_FILE" echo "=============================================" | tee -a "$LOG_FILE" echo "Neptune Endpoint: $NEPTUNE_ENDPOINT" | tee -a "$LOG_FILE" echo "Port: 8182" | tee -a "$LOG_FILE" echo "" | tee -a "$LOG_FILE" echo "To query your Neptune database using Gremlin, you can use curl:" | tee -a "$LOG_FILE" echo "curl -X POST -d '{\"gremlin\":\"g.V().limit(1)\"}' https://$NEPTUNE_ENDPOINT:8182/gremlin" | tee -a "$LOG_FILE" echo "" | tee -a "$LOG_FILE" echo "To add data to your graph:" | tee -a "$LOG_FILE" echo "curl -X POST -d '{\"gremlin\":\"g.addV(\\\"person\\\").property(\\\"name\\\", \\\"Howard\\\")\"}' https://$NEPTUNE_ENDPOINT:8182/gremlin" | tee -a "$LOG_FILE" echo "" | tee -a "$LOG_FILE" echo "Note: You may need to configure your client machine to access the Neptune instance within the VPC." | tee -a "$LOG_FILE" echo "For production use, consider using an EC2 instance in the same VPC or setting up a bastion host." | tee -a "$LOG_FILE" echo "=============================================" | tee -a "$LOG_FILE" # Step 12: List all created resources echo "" | tee -a "$LOG_FILE" echo "=============================================" | tee -a "$LOG_FILE" echo "RESOURCES CREATED" | tee -a "$LOG_FILE" echo "=============================================" | tee -a "$LOG_FILE" echo "VPC: $VPC_ID" | tee -a "$LOG_FILE" echo "Internet Gateway: $IGW_ID" | tee -a "$LOG_FILE" echo "Subnets: $SUBNET1_ID, $SUBNET2_ID, $SUBNET3_ID" | tee -a "$LOG_FILE" echo "Route Table: $ROUTE_TABLE_ID" | tee -a "$LOG_FILE" echo "Security Group: $SECURITY_GROUP_ID" | tee -a "$LOG_FILE" echo "DB Subnet Group: $DB_SUBNET_GROUP" | tee -a "$LOG_FILE" echo "Neptune DB Cluster: $DB_CLUSTER_ID" | tee -a "$LOG_FILE" echo "Neptune DB Instance: $DB_INSTANCE_ID" | tee -a "$LOG_FILE" echo "=============================================" | tee -a "$LOG_FILE" # Step 13: Ask if user wants to clean up resources echo "" | tee -a "$LOG_FILE" echo "=============================================" | tee -a "$LOG_FILE" echo "CLEANUP CONFIRMATION" | tee -a "$LOG_FILE" echo "=============================================" | tee -a "$LOG_FILE" echo "Do you want to clean up all created resources? (y/n): " | tee -a "$LOG_FILE" read -r CLEANUP_CHOICE if [[ "$CLEANUP_CHOICE" =~ ^[Yy]$ ]]; then echo "Starting cleanup process..." | tee -a "$LOG_FILE" # Delete DB instance echo "Deleting DB instance $DB_INSTANCE_ID..." | tee -a "$LOG_FILE" log_cmd "aws neptune delete-db-instance --db-instance-identifier $DB_INSTANCE_ID --skip-final-snapshot" # Wait for DB instance to be deleted echo "Waiting for DB instance to be deleted..." | tee -a "$LOG_FILE" log_cmd "aws neptune wait db-instance-deleted --db-instance-identifier $DB_INSTANCE_ID" # Delete DB cluster echo "Deleting DB cluster $DB_CLUSTER_ID..." | tee -a "$LOG_FILE" log_cmd "aws neptune delete-db-cluster --db-cluster-identifier $DB_CLUSTER_ID --skip-final-snapshot" # Wait for DB cluster to be deleted (no specific wait command for this, so we'll sleep) echo "Waiting for DB cluster to be deleted..." | tee -a "$LOG_FILE" sleep 60 # Delete DB subnet group echo "Deleting DB subnet group $DB_SUBNET_GROUP..." | tee -a "$LOG_FILE" log_cmd "aws neptune delete-db-subnet-group --db-subnet-group-name $DB_SUBNET_GROUP" # Delete security group echo "Deleting security group $SECURITY_GROUP_ID..." | tee -a "$LOG_FILE" log_cmd "aws ec2 delete-security-group --group-id $SECURITY_GROUP_ID" # Detach and delete internet gateway echo "Detaching and deleting internet gateway $IGW_ID..." | tee -a "$LOG_FILE" log_cmd "aws ec2 detach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID" log_cmd "aws ec2 delete-internet-gateway --internet-gateway-id $IGW_ID" # Delete subnets echo "Deleting subnets..." | tee -a "$LOG_FILE" log_cmd "aws ec2 delete-subnet --subnet-id $SUBNET1_ID" log_cmd "aws ec2 delete-subnet --subnet-id $SUBNET2_ID" log_cmd "aws ec2 delete-subnet --subnet-id $SUBNET3_ID" # Delete route table echo "Deleting route table $ROUTE_TABLE_ID..." | tee -a "$LOG_FILE" log_cmd "aws ec2 delete-route-table --route-table-id $ROUTE_TABLE_ID" # Delete VPC echo "Deleting VPC $VPC_ID..." | tee -a "$LOG_FILE" log_cmd "aws ec2 delete-vpc --vpc-id $VPC_ID" echo "Cleanup complete!" | tee -a "$LOG_FILE" else echo "Resources will not be cleaned up. You can delete them manually later." | tee -a "$LOG_FILE" echo "See the list of resources above for reference." | tee -a "$LOG_FILE" fi echo "Script completed. See $LOG_FILE for details." | tee -a "$LOG_FILE"-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create a transit gateway
Attach your VPCs to your transit gateway
Add routes between the transit gateway and your VPCs
Test the transit gateway
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 VPC Transit Gateway CLI Script # This script demonstrates how to create a transit gateway and connect two VPCs # Modified to work with older AWS CLI versions that don't support transit gateway wait commands # Error handling set -e LOG_FILE="transit-gateway-tutorial.log" exec > >(tee -a "$LOG_FILE") 2>&1 # Function to wait for transit gateway to be available wait_for_tgw() { local tgw_id=$1 echo "Waiting for Transit Gateway $tgw_id to become available..." while true; do status=$(aws ec2 describe-transit-gateways --transit-gateway-ids "$tgw_id" --query "TransitGateways[0].State" --output text) echo "Current status: $status" if [ "$status" = "available" ]; then echo "Transit Gateway is now available" break fi echo "Waiting for transit gateway to become available. Current state: $status" sleep 10 done } # Function to wait for transit gateway attachment to be available wait_for_tgw_attachment() { local attachment_id=$1 echo "Waiting for Transit Gateway Attachment $attachment_id to become available..." while true; do status=$(aws ec2 describe-transit-gateway-vpc-attachments --transit-gateway-attachment-ids "$attachment_id" --query "TransitGatewayVpcAttachments[0].State" --output text) echo "Current status: $status" if [ "$status" = "available" ]; then echo "Transit Gateway Attachment is now available" break fi echo "Waiting for transit gateway attachment to become available. Current state: $status" sleep 10 done } # Function to wait for transit gateway attachment to be deleted wait_for_tgw_attachment_deleted() { local attachment_id=$1 echo "Waiting for Transit Gateway Attachment $attachment_id to be deleted..." while true; do # Check if the attachment still exists count=$(aws ec2 describe-transit-gateway-vpc-attachments --filters "Name=transit-gateway-attachment-id,Values=$attachment_id" --query "length(TransitGatewayVpcAttachments)" --output text) if [ "$count" = "0" ]; then echo "Transit Gateway Attachment has been deleted" break fi status=$(aws ec2 describe-transit-gateway-vpc-attachments --transit-gateway-attachment-ids "$attachment_id" --query "TransitGatewayVpcAttachments[0].State" --output text 2>/dev/null || echo "deleted") if [ "$status" = "deleted" ]; then echo "Transit Gateway Attachment has been deleted" break fi echo "Waiting for transit gateway attachment to be deleted. Current state: $status" sleep 10 done } # Function to clean up resources cleanup() { echo "Error occurred. Cleaning up resources..." # Delete resources in reverse order if [ ! -z "$TGW_ATTACHMENT_1_ID" ]; then echo "Deleting Transit Gateway VPC Attachment 1: $TGW_ATTACHMENT_1_ID" aws ec2 delete-transit-gateway-vpc-attachment --transit-gateway-attachment-id "$TGW_ATTACHMENT_1_ID" || true wait_for_tgw_attachment_deleted "$TGW_ATTACHMENT_1_ID" || true fi if [ ! -z "$TGW_ATTACHMENT_2_ID" ]; then echo "Deleting Transit Gateway VPC Attachment 2: $TGW_ATTACHMENT_2_ID" aws ec2 delete-transit-gateway-vpc-attachment --transit-gateway-attachment-id "$TGW_ATTACHMENT_2_ID" || true wait_for_tgw_attachment_deleted "$TGW_ATTACHMENT_2_ID" || true fi if [ ! -z "$TGW_ID" ]; then echo "Deleting Transit Gateway: $TGW_ID" aws ec2 delete-transit-gateway --transit-gateway-id "$TGW_ID" || true fi exit 1 } # Set up trap for error handling trap cleanup ERR echo "=== Amazon VPC Transit Gateway Tutorial ===" echo "This script will create a transit gateway and connect two VPCs" echo "" # Get a valid availability zone dynamically echo "Getting available AZ in current region..." AZ=$(aws ec2 describe-availability-zones --query "AvailabilityZones[0].ZoneName" --output text) echo "Using availability zone: $AZ" # Check if VPCs exist echo "Checking for existing VPCs..." VPC1_ID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=VPC1" --query "Vpcs[0].VpcId" --output text) VPC2_ID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=VPC2" --query "Vpcs[0].VpcId" --output text) if [ "$VPC1_ID" == "None" ] || [ -z "$VPC1_ID" ]; then echo "Creating VPC1..." VPC1_ID=$(aws ec2 create-vpc --cidr-block 10.1.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=VPC1}]' --query Vpc.VpcId --output text) echo "Created VPC1: $VPC1_ID" # Create a subnet in VPC1 echo "Creating subnet in VPC1..." SUBNET1_ID=$(aws ec2 create-subnet --vpc-id "$VPC1_ID" --cidr-block 10.1.0.0/24 --availability-zone "$AZ" --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=VPC1-Subnet}]' --query Subnet.SubnetId --output text) echo "Created subnet in VPC1: $SUBNET1_ID" else echo "Using existing VPC1: $VPC1_ID" SUBNET1_ID=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC1_ID" --query "Subnets[0].SubnetId" --output text) if [ "$SUBNET1_ID" == "None" ] || [ -z "$SUBNET1_ID" ]; then echo "Creating subnet in VPC1..." SUBNET1_ID=$(aws ec2 create-subnet --vpc-id "$VPC1_ID" --cidr-block 10.1.0.0/24 --availability-zone "$AZ" --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=VPC1-Subnet}]' --query Subnet.SubnetId --output text) echo "Created subnet in VPC1: $SUBNET1_ID" else echo "Using existing subnet in VPC1: $SUBNET1_ID" fi fi if [ "$VPC2_ID" == "None" ] || [ -z "$VPC2_ID" ]; then echo "Creating VPC2..." VPC2_ID=$(aws ec2 create-vpc --cidr-block 10.2.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=VPC2}]' --query Vpc.VpcId --output text) echo "Created VPC2: $VPC2_ID" # Create a subnet in VPC2 echo "Creating subnet in VPC2..." SUBNET2_ID=$(aws ec2 create-subnet --vpc-id "$VPC2_ID" --cidr-block 10.2.0.0/24 --availability-zone "$AZ" --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=VPC2-Subnet}]' --query Subnet.SubnetId --output text) echo "Created subnet in VPC2: $SUBNET2_ID" else echo "Using existing VPC2: $VPC2_ID" SUBNET2_ID=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC2_ID" --query "Subnets[0].SubnetId" --output text) if [ "$SUBNET2_ID" == "None" ] || [ -z "$SUBNET2_ID" ]; then echo "Creating subnet in VPC2..." SUBNET2_ID=$(aws ec2 create-subnet --vpc-id "$VPC2_ID" --cidr-block 10.2.0.0/24 --availability-zone "$AZ" --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=VPC2-Subnet}]' --query Subnet.SubnetId --output text) echo "Created subnet in VPC2: $SUBNET2_ID" else echo "Using existing subnet in VPC2: $SUBNET2_ID" fi fi # Get route tables for each VPC RTB1_ID=$(aws ec2 describe-route-tables --filters "Name=vpc-id,Values=$VPC1_ID" --query "RouteTables[0].RouteTableId" --output text) RTB2_ID=$(aws ec2 describe-route-tables --filters "Name=vpc-id,Values=$VPC2_ID" --query "RouteTables[0].RouteTableId" --output text) echo "Route table for VPC1: $RTB1_ID" echo "Route table for VPC2: $RTB2_ID" # Step 1: Create the transit gateway echo "Creating Transit Gateway..." TGW_ID=$(aws ec2 create-transit-gateway \ --description "My Transit Gateway" \ --options AmazonSideAsn=64512,AutoAcceptSharedAttachments=disable,DefaultRouteTableAssociation=enable,DefaultRouteTablePropagation=enable,VpnEcmpSupport=enable,DnsSupport=enable,MulticastSupport=disable \ --tag-specifications 'ResourceType=transit-gateway,Tags=[{Key=Name,Value=MyTransitGateway}]' \ --query TransitGateway.TransitGatewayId \ --output text) echo "Created Transit Gateway: $TGW_ID" # Wait for the transit gateway to become available wait_for_tgw "$TGW_ID" # Step 2: Attach VPCs to the transit gateway echo "Attaching VPC1 to Transit Gateway..." TGW_ATTACHMENT_1_ID=$(aws ec2 create-transit-gateway-vpc-attachment \ --transit-gateway-id "$TGW_ID" \ --vpc-id "$VPC1_ID" \ --subnet-ids "$SUBNET1_ID" \ --tag-specifications 'ResourceType=transit-gateway-attachment,Tags=[{Key=Name,Value=VPC1-Attachment}]' \ --query TransitGatewayVpcAttachment.TransitGatewayAttachmentId \ --output text) echo "Created Transit Gateway VPC Attachment for VPC1: $TGW_ATTACHMENT_1_ID" echo "Attaching VPC2 to Transit Gateway..." TGW_ATTACHMENT_2_ID=$(aws ec2 create-transit-gateway-vpc-attachment \ --transit-gateway-id "$TGW_ID" \ --vpc-id "$VPC2_ID" \ --subnet-ids "$SUBNET2_ID" \ --tag-specifications 'ResourceType=transit-gateway-attachment,Tags=[{Key=Name,Value=VPC2-Attachment}]' \ --query TransitGatewayVpcAttachment.TransitGatewayAttachmentId \ --output text) echo "Created Transit Gateway VPC Attachment for VPC2: $TGW_ATTACHMENT_2_ID" # Wait for the attachments to become available wait_for_tgw_attachment "$TGW_ATTACHMENT_1_ID" wait_for_tgw_attachment "$TGW_ATTACHMENT_2_ID" # Step 3: Add routes between the transit gateway and VPCs echo "Adding route from VPC1 to VPC2 via Transit Gateway..." aws ec2 create-route \ --route-table-id "$RTB1_ID" \ --destination-cidr-block 10.2.0.0/16 \ --transit-gateway-id "$TGW_ID" echo "Adding route from VPC2 to VPC1 via Transit Gateway..." aws ec2 create-route \ --route-table-id "$RTB2_ID" \ --destination-cidr-block 10.1.0.0/16 \ --transit-gateway-id "$TGW_ID" echo "Routes added successfully" # Step 4: Display information for testing echo "" echo "=== Transit Gateway Setup Complete ===" echo "Transit Gateway ID: $TGW_ID" echo "VPC1 ID: $VPC1_ID" echo "VPC2 ID: $VPC2_ID" echo "" echo "To test connectivity:" echo "1. Launch an EC2 instance in each VPC" echo "2. Configure security groups to allow ICMP traffic" echo "3. Connect to one instance and ping the other instance's private IP" echo "" # Prompt user before cleanup read -p "Press Enter to view created resources, or Ctrl+C to exit without cleanup..." echo "" echo "=== Resources Created ===" echo "Transit Gateway: $TGW_ID" echo "VPC1: $VPC1_ID" echo "VPC2: $VPC2_ID" echo "Subnet in VPC1: $SUBNET1_ID" echo "Subnet in VPC2: $SUBNET2_ID" echo "Transit Gateway Attachment for VPC1: $TGW_ATTACHMENT_1_ID" echo "Transit Gateway Attachment for VPC2: $TGW_ATTACHMENT_2_ID" echo "" read -p "Do you want to clean up these resources? (y/n): " CLEANUP_CONFIRM if [[ $CLEANUP_CONFIRM == "y" || $CLEANUP_CONFIRM == "Y" ]]; then echo "Starting cleanup..." # Delete routes echo "Deleting routes..." aws ec2 delete-route --route-table-id "$RTB1_ID" --destination-cidr-block 10.2.0.0/16 aws ec2 delete-route --route-table-id "$RTB2_ID" --destination-cidr-block 10.1.0.0/16 # Delete transit gateway attachments echo "Deleting Transit Gateway VPC Attachment for VPC1: $TGW_ATTACHMENT_1_ID" aws ec2 delete-transit-gateway-vpc-attachment --transit-gateway-attachment-id "$TGW_ATTACHMENT_1_ID" echo "Deleting Transit Gateway VPC Attachment for VPC2: $TGW_ATTACHMENT_2_ID" aws ec2 delete-transit-gateway-vpc-attachment --transit-gateway-attachment-id "$TGW_ATTACHMENT_2_ID" # Wait for attachments to be deleted wait_for_tgw_attachment_deleted "$TGW_ATTACHMENT_1_ID" wait_for_tgw_attachment_deleted "$TGW_ATTACHMENT_2_ID" # Delete transit gateway echo "Deleting Transit Gateway: $TGW_ID" aws ec2 delete-transit-gateway --transit-gateway-id "$TGW_ID" echo "Cleanup completed successfully" else echo "Skipping cleanup. Resources will continue to incur charges until manually deleted." fi echo "Tutorial completed. See $LOG_FILE for detailed logs."-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create an Application Load Balancer
Create a target group
Create a listener
Verify your configuration
Add an HTTPS listener (optional)
Add path-based routing (optional)
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 # Elastic Load Balancing Getting Started Script - v2 # This script creates an Application Load Balancer with HTTP listener and target group # Set up logging LOG_FILE="elb-script-v2.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting Elastic Load Balancing setup script at $(date)" echo "All commands and outputs will be logged to $LOG_FILE" # Function to handle errors handle_error() { echo "ERROR: $1" echo "Attempting to clean up resources..." cleanup_resources exit 1 } # Function to check command success check_command() { if echo "$1" | grep -i "error" > /dev/null; then handle_error "$1" fi } # Function to clean up resources cleanup_resources() { echo "Cleaning up resources in reverse order..." if [ -n "$LISTENER_ARN" ]; then echo "Deleting listener: $LISTENER_ARN" aws elbv2 delete-listener --listener-arn "$LISTENER_ARN" fi if [ -n "$LOAD_BALANCER_ARN" ]; then echo "Deleting load balancer: $LOAD_BALANCER_ARN" aws elbv2 delete-load-balancer --load-balancer-arn "$LOAD_BALANCER_ARN" # Wait for load balancer to be deleted before deleting target group echo "Waiting for load balancer to be deleted..." aws elbv2 wait load-balancers-deleted --load-balancer-arns "$LOAD_BALANCER_ARN" fi if [ -n "$TARGET_GROUP_ARN" ]; then echo "Deleting target group: $TARGET_GROUP_ARN" aws elbv2 delete-target-group --target-group-arn "$TARGET_GROUP_ARN" fi # Add a delay before attempting to delete the security group # to ensure all ELB resources are fully deleted if [ -n "$SECURITY_GROUP_ID" ]; then echo "Waiting 30 seconds before deleting security group to ensure all dependencies are removed..." sleep 30 echo "Deleting security group: $SECURITY_GROUP_ID" SG_DELETE_OUTPUT=$(aws ec2 delete-security-group --group-id "$SECURITY_GROUP_ID" 2>&1) # If there's still a dependency issue, retry a few times RETRY_COUNT=0 MAX_RETRIES=5 while echo "$SG_DELETE_OUTPUT" | grep -i "DependencyViolation" > /dev/null && [ $RETRY_COUNT -lt $MAX_RETRIES ]; do RETRY_COUNT=$((RETRY_COUNT+1)) echo "Security group still has dependencies. Retrying in 30 seconds... (Attempt $RETRY_COUNT of $MAX_RETRIES)" sleep 30 SG_DELETE_OUTPUT=$(aws ec2 delete-security-group --group-id "$SECURITY_GROUP_ID" 2>&1) done if echo "$SG_DELETE_OUTPUT" | grep -i "error" > /dev/null; then echo "WARNING: Could not delete security group: $SECURITY_GROUP_ID" echo "You may need to delete it manually using: aws ec2 delete-security-group --group-id $SECURITY_GROUP_ID" else echo "Security group deleted successfully." fi fi } # Generate a random identifier for resource names RANDOM_ID=$(openssl rand -hex 4) RESOURCE_PREFIX="elb-demo-${RANDOM_ID}" # Step 1: Verify AWS CLI support for Elastic Load Balancing echo "Verifying AWS CLI support for Elastic Load Balancing..." aws elbv2 help > /dev/null 2>&1 if [ $? -ne 0 ]; then handle_error "AWS CLI does not support elbv2 commands. Please update your AWS CLI." fi # Step 2: Get VPC ID and subnet information echo "Retrieving VPC information..." VPC_INFO=$(aws ec2 describe-vpcs --filters "Name=isDefault,Values=true" --query "Vpcs[0].VpcId" --output text) check_command "$VPC_INFO" VPC_ID=$VPC_INFO echo "Using VPC: $VPC_ID" # Get two subnets from different Availability Zones echo "Retrieving subnet information..." SUBNET_INFO=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" --query "Subnets[0:2].SubnetId" --output text) check_command "$SUBNET_INFO" # Convert space-separated list to array read -r -a SUBNETS <<< "$SUBNET_INFO" if [ ${#SUBNETS[@]} -lt 2 ]; then handle_error "Need at least 2 subnets in different Availability Zones. Found: ${#SUBNETS[@]}" fi echo "Using subnets: ${SUBNETS[0]} and ${SUBNETS[1]}" # Step 3: Create a security group for the load balancer echo "Creating security group for the load balancer..." SG_INFO=$(aws ec2 create-security-group \ --group-name "${RESOURCE_PREFIX}-sg" \ --description "Security group for ELB demo" \ --vpc-id "$VPC_ID" \ --query "GroupId" --output text) check_command "$SG_INFO" SECURITY_GROUP_ID=$SG_INFO echo "Created security group: $SECURITY_GROUP_ID" # Add inbound rule to allow HTTP traffic echo "Adding inbound rule to allow HTTP traffic..." aws ec2 authorize-security-group-ingress \ --group-id "$SECURITY_GROUP_ID" \ --protocol tcp \ --port 80 \ --cidr "0.0.0.0/0" > /dev/null # Note: In production, you should restrict the CIDR range to specific IP addresses # Step 4: Create the load balancer echo "Creating Application Load Balancer..." LB_INFO=$(aws elbv2 create-load-balancer \ --name "${RESOURCE_PREFIX}-lb" \ --subnets "${SUBNETS[0]}" "${SUBNETS[1]}" \ --security-groups "$SECURITY_GROUP_ID" \ --query "LoadBalancers[0].LoadBalancerArn" --output text) check_command "$LB_INFO" LOAD_BALANCER_ARN=$LB_INFO echo "Created load balancer: $LOAD_BALANCER_ARN" # Wait for the load balancer to be active echo "Waiting for load balancer to become active..." aws elbv2 wait load-balancer-available --load-balancer-arns "$LOAD_BALANCER_ARN" # Step 5: Create a target group echo "Creating target group..." TG_INFO=$(aws elbv2 create-target-group \ --name "${RESOURCE_PREFIX}-targets" \ --protocol HTTP \ --port 80 \ --vpc-id "$VPC_ID" \ --target-type instance \ --query "TargetGroups[0].TargetGroupArn" --output text) check_command "$TG_INFO" TARGET_GROUP_ARN=$TG_INFO echo "Created target group: $TARGET_GROUP_ARN" # Step 6: Find EC2 instances to register as targets echo "Looking for available EC2 instances to register as targets..." INSTANCES=$(aws ec2 describe-instances \ --filters "Name=vpc-id,Values=$VPC_ID" "Name=instance-state-name,Values=running" \ --query "Reservations[*].Instances[*].InstanceId" --output text) check_command "$INSTANCES" # Convert space-separated list to array read -r -a INSTANCE_IDS <<< "$INSTANCES" if [ ${#INSTANCE_IDS[@]} -eq 0 ]; then echo "No running instances found in VPC $VPC_ID." echo "You will need to register targets manually after launching instances." else # Step 7: Register targets with the target group (up to 2 instances) echo "Registering targets with the target group..." TARGET_ARGS="" for i in "${!INSTANCE_IDS[@]}"; do if [ "$i" -lt 2 ]; then # Register up to 2 instances TARGET_ARGS="$TARGET_ARGS Id=${INSTANCE_IDS[$i]} " fi done if [ -n "$TARGET_ARGS" ]; then aws elbv2 register-targets \ --target-group-arn "$TARGET_GROUP_ARN" \ --targets $TARGET_ARGS echo "Registered instances: $TARGET_ARGS" fi fi # Step 8: Create a listener echo "Creating HTTP listener..." LISTENER_INFO=$(aws elbv2 create-listener \ --load-balancer-arn "$LOAD_BALANCER_ARN" \ --protocol HTTP \ --port 80 \ --default-actions Type=forward,TargetGroupArn="$TARGET_GROUP_ARN" \ --query "Listeners[0].ListenerArn" --output text) check_command "$LISTENER_INFO" LISTENER_ARN=$LISTENER_INFO echo "Created listener: $LISTENER_ARN" # Step 9: Verify target health echo "Verifying target health..." aws elbv2 describe-target-health --target-group-arn "$TARGET_GROUP_ARN" # Display load balancer DNS name LB_DNS=$(aws elbv2 describe-load-balancers \ --load-balancer-arns "$LOAD_BALANCER_ARN" \ --query "LoadBalancers[0].DNSName" --output text) check_command "$LB_DNS" echo "" echo "==============================================" echo "SETUP COMPLETE" echo "==============================================" echo "Load Balancer DNS Name: $LB_DNS" echo "" echo "Resources created:" echo "- Load Balancer: $LOAD_BALANCER_ARN" echo "- Target Group: $TARGET_GROUP_ARN" echo "- Listener: $LISTENER_ARN" echo "- Security Group: $SECURITY_GROUP_ID" echo "" # Ask user if they want to clean up resources 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 echo "Starting cleanup process..." cleanup_resources echo "Cleanup completed." else echo "Resources have been preserved." echo "To clean up later, run the following commands:" echo "aws elbv2 delete-listener --listener-arn $LISTENER_ARN" echo "aws elbv2 delete-load-balancer --load-balancer-arn $LOAD_BALANCER_ARN" echo "aws elbv2 wait load-balancers-deleted --load-balancer-arns $LOAD_BALANCER_ARN" echo "aws elbv2 delete-target-group --target-group-arn $TARGET_GROUP_ARN" echo "aws ec2 delete-security-group --group-id $SECURITY_GROUP_ID" fi echo "Script completed at $(date)"-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Verify access to Elemental MediaConnect
Create a flow
Add an output
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 # AWS Elemental MediaConnect Getting Started Tutorial Script # This script creates a MediaConnect flow, adds an output, grants an entitlement, # and then cleans up the resources. # Set up logging LOG_FILE="mediaconnect-tutorial.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting AWS Elemental MediaConnect 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 "Attempting to clean up resources..." cleanup_resources exit 1 } # Function to clean up resources cleanup_resources() { echo "Cleaning up resources..." if [ -n "$FLOW_ARN" ]; then # Check flow status before attempting to stop echo "Checking flow status..." FLOW_STATUS_OUTPUT=$(aws mediaconnect describe-flow --flow-arn "$FLOW_ARN" --query "Flow.Status" --output text 2>&1) echo "Current flow status: $FLOW_STATUS_OUTPUT" if [ "$FLOW_STATUS_OUTPUT" == "ACTIVE" ] || [ "$FLOW_STATUS_OUTPUT" == "UPDATING" ]; then echo "Stopping flow: $FLOW_ARN" STOP_FLOW_OUTPUT=$(aws mediaconnect stop-flow --flow-arn "$FLOW_ARN" 2>&1) if echo "$STOP_FLOW_OUTPUT" | grep -i "error" > /dev/null; then echo "WARNING: Failed to stop flow. Output: $STOP_FLOW_OUTPUT" echo "Attempting to delete anyway..." else echo "$STOP_FLOW_OUTPUT" # Wait for flow to stop before deleting echo "Waiting for flow to stop..." sleep 10 fi else echo "Flow is not in ACTIVE or UPDATING state, skipping stop operation." fi # Delete the flow echo "Deleting flow: $FLOW_ARN" DELETE_FLOW_OUTPUT=$(aws mediaconnect delete-flow --flow-arn "$FLOW_ARN" 2>&1) if echo "$DELETE_FLOW_OUTPUT" | grep -i "error" > /dev/null; then echo "WARNING: Failed to delete flow. Output: $DELETE_FLOW_OUTPUT" echo "You may need to manually delete the flow from the AWS console." else echo "$DELETE_FLOW_OUTPUT" fi fi } # Get the current AWS region AWS_REGION=$(aws configure get region) if [ -z "$AWS_REGION" ]; then handle_error "Failed to get AWS region. Please make sure AWS CLI is configured." fi echo "Using AWS Region: $AWS_REGION" # Get available availability zones in the current region echo "Getting available availability zones in region $AWS_REGION..." AZ_OUTPUT=$(aws ec2 describe-availability-zones --region "$AWS_REGION" --query "AvailabilityZones[0].ZoneName" --output text 2>&1) if echo "$AZ_OUTPUT" | grep -i "error" > /dev/null; then handle_error "Failed to get availability zones. Output: $AZ_OUTPUT" fi AVAILABILITY_ZONE="$AZ_OUTPUT" echo "Using availability zone: $AVAILABILITY_ZONE" # Generate a unique suffix for resource names SUFFIX=$(date +%s | cut -c 6-10) FLOW_NAME="AwardsNYCShow-${SUFFIX}" SOURCE_NAME="AwardsNYCSource-${SUFFIX}" OUTPUT_NAME="AwardsNYCOutput-${SUFFIX}" ENTITLEMENT_NAME="PhillyTeam-${SUFFIX}" echo "Using the following resource names:" echo "Flow name: $FLOW_NAME" echo "Source name: $SOURCE_NAME" echo "Output name: $OUTPUT_NAME" echo "Entitlement name: $ENTITLEMENT_NAME" # Step 1: Verify access to MediaConnect echo "Step 1: Verifying access to AWS Elemental MediaConnect..." LIST_FLOWS_OUTPUT=$(aws mediaconnect list-flows 2>&1) if echo "$LIST_FLOWS_OUTPUT" | grep -i "error" > /dev/null; then handle_error "Failed to list flows. Please check your AWS credentials and permissions. Output: $LIST_FLOWS_OUTPUT" fi echo "$LIST_FLOWS_OUTPUT" # Step 2: Create a flow echo "Step 2: Creating a flow..." CREATE_FLOW_OUTPUT=$(aws mediaconnect create-flow \ --availability-zone "$AVAILABILITY_ZONE" \ --name "$FLOW_NAME" \ --source "Name=$SOURCE_NAME,Protocol=zixi-push,WhitelistCidr=10.24.34.0/23,StreamId=ZixiAwardsNYCFeed" 2>&1) if echo "$CREATE_FLOW_OUTPUT" | grep -i "error" > /dev/null; then handle_error "Failed to create flow. Output: $CREATE_FLOW_OUTPUT" fi echo "$CREATE_FLOW_OUTPUT" # Extract the flow ARN from the output FLOW_ARN=$(echo "$CREATE_FLOW_OUTPUT" | grep -o '"FlowArn": "[^"]*' | cut -d'"' -f4) if [ -z "$FLOW_ARN" ]; then handle_error "Failed to extract flow ARN from output" fi echo "Flow ARN: $FLOW_ARN" # Step 3: Add an output echo "Step 3: Adding an output to the flow..." ADD_OUTPUT_OUTPUT=$(aws mediaconnect add-flow-outputs \ --flow-arn "$FLOW_ARN" \ --outputs "Name=$OUTPUT_NAME,Protocol=zixi-push,Destination=198.51.100.11,Port=1024,StreamId=ZixiAwardsOutput" 2>&1) if echo "$ADD_OUTPUT_OUTPUT" | grep -i "error" > /dev/null; then handle_error "Failed to add output to flow. Output: $ADD_OUTPUT_OUTPUT" fi echo "$ADD_OUTPUT_OUTPUT" # Extract the output ARN OUTPUT_ARN=$(echo "$ADD_OUTPUT_OUTPUT" | grep -o '"OutputArn": "[^"]*' | cut -d'"' -f4) echo "Output ARN: $OUTPUT_ARN" # Step 4: Grant an entitlement echo "Step 4: Granting an entitlement..." GRANT_ENTITLEMENT_OUTPUT=$(aws mediaconnect grant-flow-entitlements \ --flow-arn "$FLOW_ARN" \ --entitlements "Name=$ENTITLEMENT_NAME,Subscribers=222233334444" 2>&1) if echo "$GRANT_ENTITLEMENT_OUTPUT" | grep -i "error" > /dev/null; then handle_error "Failed to grant entitlement. Output: $GRANT_ENTITLEMENT_OUTPUT" fi echo "$GRANT_ENTITLEMENT_OUTPUT" # Extract the entitlement ARN ENTITLEMENT_ARN=$(echo "$GRANT_ENTITLEMENT_OUTPUT" | grep -o '"EntitlementArn": "[^"]*' | cut -d'"' -f4) echo "Entitlement ARN: $ENTITLEMENT_ARN" # Step 5: List entitlements to share with affiliates echo "Step 5: Listing entitlements for the flow..." DESCRIBE_FLOW_OUTPUT=$(aws mediaconnect describe-flow --flow-arn "$FLOW_ARN" --query "Flow.Entitlements" 2>&1) if echo "$DESCRIBE_FLOW_OUTPUT" | grep -i "error" > /dev/null; then handle_error "Failed to describe flow. Output: $DESCRIBE_FLOW_OUTPUT" fi echo "Entitlements for the flow:" echo "$DESCRIBE_FLOW_OUTPUT" # Display information to share with affiliates echo "" echo "Information to share with your Philadelphia affiliate:" echo "Entitlement ARN: $ENTITLEMENT_ARN" echo "AWS Region: $AWS_REGION" # Prompt user before cleanup echo "" echo "===========================================" echo "RESOURCE SUMMARY" echo "===========================================" echo "The following resources were created:" echo "1. Flow: $FLOW_NAME (ARN: $FLOW_ARN)" echo "2. Output: $OUTPUT_NAME (ARN: $OUTPUT_ARN)" echo "3. Entitlement: $ENTITLEMENT_NAME (ARN: $ENTITLEMENT_ARN)" 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 # Step 6: Clean up resources echo "Step 6: Cleaning up resources..." # Check flow status before attempting to stop echo "Checking flow status..." FLOW_STATUS_OUTPUT=$(aws mediaconnect describe-flow --flow-arn "$FLOW_ARN" --query "Flow.Status" --output text 2>&1) echo "Current flow status: $FLOW_STATUS_OUTPUT" if [ "$FLOW_STATUS_OUTPUT" == "ACTIVE" ] || [ "$FLOW_STATUS_OUTPUT" == "UPDATING" ]; then echo "Stopping flow: $FLOW_ARN" STOP_FLOW_OUTPUT=$(aws mediaconnect stop-flow --flow-arn "$FLOW_ARN" 2>&1) if echo "$STOP_FLOW_OUTPUT" | grep -i "error" > /dev/null; then echo "WARNING: Failed to stop flow. Output: $STOP_FLOW_OUTPUT" echo "Attempting to delete anyway..." else echo "$STOP_FLOW_OUTPUT" # Wait for flow to stop before deleting echo "Waiting for flow to stop..." sleep 10 fi else echo "Flow is not in ACTIVE or UPDATING state, skipping stop operation." fi # Delete the flow echo "Deleting flow: $FLOW_ARN" DELETE_FLOW_OUTPUT=$(aws mediaconnect delete-flow --flow-arn "$FLOW_ARN" 2>&1) if echo "$DELETE_FLOW_OUTPUT" | grep -i "error" > /dev/null; then echo "WARNING: Failed to delete flow. Output: $DELETE_FLOW_OUTPUT" echo "You may need to manually delete the flow from the AWS console." else echo "$DELETE_FLOW_OUTPUT" fi echo "Cleanup completed." else echo "Skipping cleanup. Resources will remain in your AWS account." echo "To clean up later, you'll need to manually stop and delete the flow using the AWS console or CLI." fi echo "Script completed at $(date)"-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create IAM roles
Create a CloudWatch alarm
Create an experiment template
Run the experiment
Verify the results
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 # AWS FIS CPU Stress Test Tutorial Script # This script automates the steps in the AWS FIS CPU stress test tutorial # approach using epoch time calculations that work across all Linux distributions # Set up logging LOG_FILE="fis-tutorial-$(date +%Y%m%d-%H%M%S).log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting AWS FIS CPU Stress Test Tutorial Script" echo "Logging to $LOG_FILE" echo "==============================================" # Function to check for errors in command output check_error() { local output=$1 local cmd=$2 if echo "$output" | grep -i "error" > /dev/null; then # Ignore specific expected errors if [[ "$cmd" == *"aws fis get-experiment"* ]] && [[ "$output" == *"ConfigurationFailure"* ]]; then echo "Note: Experiment failed due to configuration issue. This is expected in some cases." return 0 fi echo "ERROR: Command failed: $cmd" echo "Output: $output" cleanup_on_error exit 1 fi } # Function to clean up resources on error cleanup_on_error() { echo "Error encountered. Cleaning up resources..." if [ -n "$EXPERIMENT_ID" ]; then echo "Stopping experiment $EXPERIMENT_ID if running..." aws fis stop-experiment --id "$EXPERIMENT_ID" 2>/dev/null || true fi if [ -n "$TEMPLATE_ID" ]; then echo "Deleting experiment template $TEMPLATE_ID..." aws fis delete-experiment-template --id "$TEMPLATE_ID" || true fi if [ -n "$INSTANCE_ID" ]; then echo "Terminating EC2 instance $INSTANCE_ID..." aws ec2 terminate-instances --instance-ids "$INSTANCE_ID" || true fi if [ -n "$ALARM_NAME" ]; then echo "Deleting CloudWatch alarm $ALARM_NAME..." aws cloudwatch delete-alarms --alarm-names "$ALARM_NAME" || true fi if [ -n "$INSTANCE_PROFILE_NAME" ]; then echo "Removing role from instance profile..." aws iam remove-role-from-instance-profile --instance-profile-name "$INSTANCE_PROFILE_NAME" --role-name "$EC2_ROLE_NAME" || true echo "Deleting instance profile..." aws iam delete-instance-profile --instance-profile-name "$INSTANCE_PROFILE_NAME" || true fi if [ -n "$FIS_ROLE_NAME" ]; then echo "Deleting FIS role policy..." aws iam delete-role-policy --role-name "$FIS_ROLE_NAME" --policy-name "$FIS_POLICY_NAME" || true echo "Deleting FIS role..." aws iam delete-role --role-name "$FIS_ROLE_NAME" || true fi if [ -n "$EC2_ROLE_NAME" ]; then echo "Detaching policy from EC2 role..." aws iam detach-role-policy --role-name "$EC2_ROLE_NAME" --policy-arn "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" || true echo "Deleting EC2 role..." aws iam delete-role --role-name "$EC2_ROLE_NAME" || true fi echo "Cleanup completed." } # Generate unique identifiers for resources TIMESTAMP=$(date +%Y%m%d%H%M%S) FIS_ROLE_NAME="FISRole-${TIMESTAMP}" FIS_POLICY_NAME="FISPolicy-${TIMESTAMP}" EC2_ROLE_NAME="EC2SSMRole-${TIMESTAMP}" INSTANCE_PROFILE_NAME="EC2SSMProfile-${TIMESTAMP}" ALARM_NAME="FIS-CPU-Alarm-${TIMESTAMP}" # Track created resources CREATED_RESOURCES=() echo "Step 1: Creating IAM role for AWS FIS" # Create trust policy file for AWS FIS cat > fis-trust-policy.json << 'EOF' { "Version":"2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "fis.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF # Create IAM role for FIS echo "Creating IAM role $FIS_ROLE_NAME for AWS FIS..." FIS_ROLE_OUTPUT=$(aws iam create-role \ --role-name "$FIS_ROLE_NAME" \ --assume-role-policy-document file://fis-trust-policy.json) check_error "$FIS_ROLE_OUTPUT" "aws iam create-role" CREATED_RESOURCES+=("IAM Role: $FIS_ROLE_NAME") # Create policy document for SSM actions cat > fis-ssm-policy.json << 'EOF' { "Version":"2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ssm:SendCommand", "ssm:ListCommands", "ssm:ListCommandInvocations" ], "Resource": "*" } ] } EOF # Attach policy to the role echo "Attaching policy $FIS_POLICY_NAME to role $FIS_ROLE_NAME..." FIS_POLICY_OUTPUT=$(aws iam put-role-policy \ --role-name "$FIS_ROLE_NAME" \ --policy-name "$FIS_POLICY_NAME" \ --policy-document file://fis-ssm-policy.json) check_error "$FIS_POLICY_OUTPUT" "aws iam put-role-policy" CREATED_RESOURCES+=("IAM Policy: $FIS_POLICY_NAME attached to $FIS_ROLE_NAME") echo "Step 2: Creating IAM role for EC2 instance with SSM permissions" # Create trust policy file for EC2 cat > ec2-trust-policy.json << 'EOF' { "Version":"2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF # Create IAM role for EC2 echo "Creating IAM role $EC2_ROLE_NAME for EC2 instance..." EC2_ROLE_OUTPUT=$(aws iam create-role \ --role-name "$EC2_ROLE_NAME" \ --assume-role-policy-document file://ec2-trust-policy.json) check_error "$EC2_ROLE_OUTPUT" "aws iam create-role" CREATED_RESOURCES+=("IAM Role: $EC2_ROLE_NAME") # Attach SSM policy to the EC2 role echo "Attaching AmazonSSMManagedInstanceCore policy to role $EC2_ROLE_NAME..." EC2_POLICY_OUTPUT=$(aws iam attach-role-policy \ --role-name "$EC2_ROLE_NAME" \ --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore) check_error "$EC2_POLICY_OUTPUT" "aws iam attach-role-policy" CREATED_RESOURCES+=("IAM Policy: AmazonSSMManagedInstanceCore attached to $EC2_ROLE_NAME") # Create instance profile echo "Creating instance profile $INSTANCE_PROFILE_NAME..." PROFILE_OUTPUT=$(aws iam create-instance-profile \ --instance-profile-name "$INSTANCE_PROFILE_NAME") check_error "$PROFILE_OUTPUT" "aws iam create-instance-profile" CREATED_RESOURCES+=("IAM Instance Profile: $INSTANCE_PROFILE_NAME") # Add role to instance profile echo "Adding role $EC2_ROLE_NAME to instance profile $INSTANCE_PROFILE_NAME..." ADD_ROLE_OUTPUT=$(aws iam add-role-to-instance-profile \ --instance-profile-name "$INSTANCE_PROFILE_NAME" \ --role-name "$EC2_ROLE_NAME") check_error "$ADD_ROLE_OUTPUT" "aws iam add-role-to-instance-profile" # Wait for role to propagate echo "Waiting for IAM role to propagate..." sleep 10 echo "Step 3: Launching EC2 instance" # Get the latest Amazon Linux 2 AMI ID echo "Finding latest Amazon Linux 2 AMI..." AMI_ID=$(aws ec2 describe-images \ --owners amazon \ --filters "Name=name,Values=amzn2-ami-hvm-*-x86_64-gp2" "Name=state,Values=available" \ --query "sort_by(Images, &CreationDate)[-1].ImageId" \ --output text) check_error "$AMI_ID" "aws ec2 describe-images" echo "Using AMI: $AMI_ID" # Launch EC2 instance echo "Launching EC2 instance with AMI $AMI_ID..." INSTANCE_OUTPUT=$(aws ec2 run-instances \ --image-id "$AMI_ID" \ --instance-type t2.micro \ --iam-instance-profile Name="$INSTANCE_PROFILE_NAME" \ --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=FIS-Test-Instance}]') check_error "$INSTANCE_OUTPUT" "aws ec2 run-instances" # Get instance ID INSTANCE_ID=$(echo "$INSTANCE_OUTPUT" | grep -i "InstanceId" | head -1 | awk -F'"' '{print $4}') if [ -z "$INSTANCE_ID" ]; then echo "Failed to get instance ID" cleanup_on_error exit 1 fi echo "Launched instance: $INSTANCE_ID" CREATED_RESOURCES+=("EC2 Instance: $INSTANCE_ID") # Enable detailed monitoring echo "Enabling detailed monitoring for instance $INSTANCE_ID..." MONITOR_OUTPUT=$(aws ec2 monitor-instances --instance-ids "$INSTANCE_ID") check_error "$MONITOR_OUTPUT" "aws ec2 monitor-instances" # Wait for instance to be running and status checks to pass echo "Waiting for instance to be ready..." aws ec2 wait instance-running --instance-ids "$INSTANCE_ID" aws ec2 wait instance-status-ok --instance-ids "$INSTANCE_ID" echo "Instance is ready" echo "Step 4: Creating CloudWatch alarm for CPU utilization" # Create CloudWatch alarm echo "Creating CloudWatch alarm $ALARM_NAME..." ALARM_OUTPUT=$(aws cloudwatch put-metric-alarm \ --alarm-name "$ALARM_NAME" \ --alarm-description "Alarm when CPU exceeds 50%" \ --metric-name CPUUtilization \ --namespace AWS/EC2 \ --statistic Maximum \ --period 60 \ --threshold 50 \ --comparison-operator GreaterThanOrEqualToThreshold \ --dimensions "Name=InstanceId,Value=$INSTANCE_ID" \ --evaluation-periods 1) check_error "$ALARM_OUTPUT" "aws cloudwatch put-metric-alarm" CREATED_RESOURCES+=("CloudWatch Alarm: $ALARM_NAME") # Get the alarm ARN echo "Getting CloudWatch alarm ARN..." ALARM_ARN_OUTPUT=$(aws cloudwatch describe-alarms \ --alarm-names "$ALARM_NAME") check_error "$ALARM_ARN_OUTPUT" "aws cloudwatch describe-alarms" ALARM_ARN=$(echo "$ALARM_ARN_OUTPUT" | grep -i "AlarmArn" | head -1 | awk -F'"' '{print $4}') if [ -z "$ALARM_ARN" ]; then echo "Failed to get alarm ARN" cleanup_on_error exit 1 fi echo "Alarm ARN: $ALARM_ARN" # Wait for the alarm to initialize and reach OK state echo "Waiting for CloudWatch alarm to initialize (60 seconds)..." sleep 60 # Check alarm state echo "Checking alarm state..." ALARM_STATE_OUTPUT=$(aws cloudwatch describe-alarms \ --alarm-names "$ALARM_NAME") ALARM_STATE=$(echo "$ALARM_STATE_OUTPUT" | grep -i "StateValue" | head -1 | awk -F'"' '{print $4}') echo "Current alarm state: $ALARM_STATE" # If alarm is not in OK state, wait longer or generate some baseline metrics if [ "$ALARM_STATE" != "OK" ]; then echo "Alarm not in OK state. Waiting for alarm to stabilize (additional 60 seconds)..." sleep 60 # Check alarm state again ALARM_STATE_OUTPUT=$(aws cloudwatch describe-alarms \ --alarm-names "$ALARM_NAME") ALARM_STATE=$(echo "$ALARM_STATE_OUTPUT" | grep -i "StateValue" | head -1 | awk -F'"' '{print $4}') echo "Updated alarm state: $ALARM_STATE" if [ "$ALARM_STATE" != "OK" ]; then echo "Warning: Alarm still not in OK state. Experiment may fail to start." fi fi echo "Step 5: Creating AWS FIS experiment template" # Get the IAM role ARN echo "Getting IAM role ARN for $FIS_ROLE_NAME..." ROLE_ARN_OUTPUT=$(aws iam get-role \ --role-name "$FIS_ROLE_NAME") check_error "$ROLE_ARN_OUTPUT" "aws iam get-role" ROLE_ARN=$(echo "$ROLE_ARN_OUTPUT" | grep -i "Arn" | head -1 | awk -F'"' '{print $4}') if [ -z "$ROLE_ARN" ]; then echo "Failed to get role ARN" cleanup_on_error exit 1 fi echo "Role ARN: $ROLE_ARN" # Get account ID and region ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) REGION=$(aws configure get region) if [ -z "$REGION" ]; then REGION="us-east-1" # Default to us-east-1 if region not set fi INSTANCE_ARN="arn:aws:ec2:${REGION}:${ACCOUNT_ID}:instance/${INSTANCE_ID}" echo "Instance ARN: $INSTANCE_ARN" # Create experiment template - Fixed JSON escaping issue cat > experiment-template.json << EOF { "description": "Test CPU stress predefined SSM document", "targets": { "testInstance": { "resourceType": "aws:ec2:instance", "resourceArns": ["$INSTANCE_ARN"], "selectionMode": "ALL" } }, "actions": { "runCpuStress": { "actionId": "aws:ssm:send-command", "parameters": { "documentArn": "arn:aws:ssm:$REGION::document/AWSFIS-Run-CPU-Stress", "documentParameters": "{\"DurationSeconds\":\"120\"}", "duration": "PT5M" }, "targets": { "Instances": "testInstance" } } }, "stopConditions": [ { "source": "aws:cloudwatch:alarm", "value": "$ALARM_ARN" } ], "roleArn": "$ROLE_ARN", "tags": { "Name": "FIS-CPU-Stress-Experiment" } } EOF # Create experiment template echo "Creating AWS FIS experiment template..." TEMPLATE_OUTPUT=$(aws fis create-experiment-template --cli-input-json file://experiment-template.json) check_error "$TEMPLATE_OUTPUT" "aws fis create-experiment-template" TEMPLATE_ID=$(echo "$TEMPLATE_OUTPUT" | grep -i "id" | head -1 | awk -F'"' '{print $4}') if [ -z "$TEMPLATE_ID" ]; then echo "Failed to get template ID" cleanup_on_error exit 1 fi echo "Experiment template created with ID: $TEMPLATE_ID" CREATED_RESOURCES+=("FIS Experiment Template: $TEMPLATE_ID") echo "Step 6: Starting the experiment" # Start the experiment echo "Starting AWS FIS experiment using template $TEMPLATE_ID..." EXPERIMENT_OUTPUT=$(aws fis start-experiment \ --experiment-template-id "$TEMPLATE_ID" \ --tags '{"Name": "FIS-CPU-Stress-Run"}') check_error "$EXPERIMENT_OUTPUT" "aws fis start-experiment" EXPERIMENT_ID=$(echo "$EXPERIMENT_OUTPUT" | grep -i "id" | head -1 | awk -F'"' '{print $4}') if [ -z "$EXPERIMENT_ID" ]; then echo "Failed to get experiment ID" cleanup_on_error exit 1 fi echo "Experiment started with ID: $EXPERIMENT_ID" CREATED_RESOURCES+=("FIS Experiment: $EXPERIMENT_ID") echo "Step 7: Tracking experiment progress" # Track experiment progress echo "Tracking experiment progress..." MAX_CHECKS=30 CHECK_COUNT=0 EXPERIMENT_STATE="" while [ $CHECK_COUNT -lt $MAX_CHECKS ]; do EXPERIMENT_INFO=$(aws fis get-experiment --id "$EXPERIMENT_ID") # Don't check for errors here, as we expect some experiments to fail EXPERIMENT_STATE=$(echo "$EXPERIMENT_INFO" | grep -i "status" | head -1 | awk -F'"' '{print $4}') echo "Experiment state: $EXPERIMENT_STATE" if [ "$EXPERIMENT_STATE" == "completed" ] || [ "$EXPERIMENT_STATE" == "stopped" ] || [ "$EXPERIMENT_STATE" == "failed" ]; then # Show the reason for the state REASON=$(echo "$EXPERIMENT_INFO" | grep -i "reason" | head -1 | awk -F'"' '{print $4}') if [ -n "$REASON" ]; then echo "Reason: $REASON" fi break fi echo "Waiting 10 seconds before checking again..." sleep 10 CHECK_COUNT=$((CHECK_COUNT + 1)) done if [ $CHECK_COUNT -eq $MAX_CHECKS ]; then echo "Experiment is taking longer than expected. You can check its status later using:" echo "aws fis get-experiment --id $EXPERIMENT_ID" fi echo "Step 8: Verifying experiment results" # Check CloudWatch alarm state echo "Checking CloudWatch alarm state..." ALARM_STATE_OUTPUT=$(aws cloudwatch describe-alarms --alarm-names "$ALARM_NAME") check_error "$ALARM_STATE_OUTPUT" "aws cloudwatch describe-alarms" echo "$ALARM_STATE_OUTPUT" # Get CPU utilization metrics echo "Getting CPU utilization metrics..." END_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") # FIXED: Cross-platform compatible way to calculate time 10 minutes ago # This approach uses epoch seconds and basic arithmetic which works on all Linux distributions CURRENT_EPOCH=$(date +%s) TEN_MINUTES_AGO_EPOCH=$((CURRENT_EPOCH - 600)) START_TIME=$(date -u -d "@$TEN_MINUTES_AGO_EPOCH" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -r "$TEN_MINUTES_AGO_EPOCH" +"%Y-%m-%dT%H:%M:%SZ") # Create metric query file cat > metric-query.json << EOF [ { "Id": "cpu", "MetricStat": { "Metric": { "Namespace": "AWS/EC2", "MetricName": "CPUUtilization", "Dimensions": [ { "Name": "InstanceId", "Value": "$INSTANCE_ID" } ] }, "Period": 60, "Stat": "Maximum" } } ] EOF METRICS_OUTPUT=$(aws cloudwatch get-metric-data \ --start-time "$START_TIME" \ --end-time "$END_TIME" \ --metric-data-queries file://metric-query.json) check_error "$METRICS_OUTPUT" "aws cloudwatch get-metric-data" echo "CPU Utilization Metrics:" echo "$METRICS_OUTPUT" # Display summary of created resources echo "" echo "===========================================" echo "RESOURCES CREATED" echo "===========================================" for resource in "${CREATED_RESOURCES[@]}"; do echo "- $resource" done echo "===========================================" # Prompt for cleanup 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 echo "Starting cleanup process..." # Stop experiment if still running if [ "$EXPERIMENT_STATE" != "completed" ] && [ "$EXPERIMENT_STATE" != "stopped" ] && [ "$EXPERIMENT_STATE" != "failed" ]; then echo "Stopping experiment $EXPERIMENT_ID..." STOP_OUTPUT=$(aws fis stop-experiment --id "$EXPERIMENT_ID") check_error "$STOP_OUTPUT" "aws fis stop-experiment" echo "Waiting for experiment to stop..." sleep 10 fi # Delete experiment template echo "Deleting experiment template $TEMPLATE_ID..." DELETE_TEMPLATE_OUTPUT=$(aws fis delete-experiment-template --id "$TEMPLATE_ID") check_error "$DELETE_TEMPLATE_OUTPUT" "aws fis delete-experiment-template" # Delete CloudWatch alarm echo "Deleting CloudWatch alarm $ALARM_NAME..." DELETE_ALARM_OUTPUT=$(aws cloudwatch delete-alarms --alarm-names "$ALARM_NAME") check_error "$DELETE_ALARM_OUTPUT" "aws cloudwatch delete-alarms" # Terminate EC2 instance echo "Terminating EC2 instance $INSTANCE_ID..." TERMINATE_OUTPUT=$(aws ec2 terminate-instances --instance-ids "$INSTANCE_ID") check_error "$TERMINATE_OUTPUT" "aws ec2 terminate-instances" echo "Waiting for instance to terminate..." aws ec2 wait instance-terminated --instance-ids "$INSTANCE_ID" # Clean up IAM resources echo "Removing role from instance profile..." REMOVE_ROLE_OUTPUT=$(aws iam remove-role-from-instance-profile \ --instance-profile-name "$INSTANCE_PROFILE_NAME" \ --role-name "$EC2_ROLE_NAME") check_error "$REMOVE_ROLE_OUTPUT" "aws iam remove-role-from-instance-profile" echo "Deleting instance profile..." DELETE_PROFILE_OUTPUT=$(aws iam delete-instance-profile \ --instance-profile-name "$INSTANCE_PROFILE_NAME") check_error "$DELETE_PROFILE_OUTPUT" "aws iam delete-instance-profile" echo "Deleting FIS role policy..." DELETE_POLICY_OUTPUT=$(aws iam delete-role-policy \ --role-name "$FIS_ROLE_NAME" \ --policy-name "$FIS_POLICY_NAME") check_error "$DELETE_POLICY_OUTPUT" "aws iam delete-role-policy" echo "Detaching policy from EC2 role..." DETACH_POLICY_OUTPUT=$(aws iam detach-role-policy \ --role-name "$EC2_ROLE_NAME" \ --policy-arn "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore") check_error "$DETACH_POLICY_OUTPUT" "aws iam detach-role-policy" echo "Deleting FIS role..." DELETE_FIS_ROLE_OUTPUT=$(aws iam delete-role \ --role-name "$FIS_ROLE_NAME") check_error "$DELETE_FIS_ROLE_OUTPUT" "aws iam delete-role" echo "Deleting EC2 role..." DELETE_EC2_ROLE_OUTPUT=$(aws iam delete-role \ --role-name "$EC2_ROLE_NAME") check_error "$DELETE_EC2_ROLE_OUTPUT" "aws iam delete-role" # Clean up temporary files echo "Cleaning up temporary files..." rm -f fis-trust-policy.json ec2-trust-policy.json fis-ssm-policy.json experiment-template.json metric-query.json echo "Cleanup completed successfully." else echo "Cleanup skipped. Resources will remain in your AWS account." echo "You can manually clean up the resources listed above." fi echo "" echo "Script execution completed." echo "Log file: $LOG_FILE"-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Set up and configure Amazon VPC IP Address Manager (IPAM) using the CLI.
Create an IPAM with operating regions (e.g., us-east-1, us-west-2).
Retrieve the private scope ID for the IPAM.
Create a hierarchical structure of IPv4 pools (top-level, regional, and development pools).
Provision CIDR blocks to each pool (e.g., 10.0.0.0/8, 10.0.0.0/16, 10.0.0.0/24).
Create a VPC using a CIDR allocated from an IPAM pool.
Verify IPAM pool allocations and VPC creation.
Troubleshoot common issues like permission errors, CIDR allocation failures, and dependency violations.
Clean up IPAM resources (VPC, pools, CIDRs, and IPAM) to avoid unnecessary charges.
Explore next steps for advanced IPAM features.
- 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 # IPAM Getting Started CLI Script - Version 7 # This script creates an IPAM, creates a hierarchy of IP address pools, and allocates a CIDR to a VPC # Fixed to correctly identify the private scope ID, wait for resources to be available, add locale to development pool, # use the correct parameter names for VPC creation, and wait for CIDR provisioning to complete # Set up logging LOG_FILE="ipam_script.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting IPAM setup script at $(date)" echo "All commands and outputs will be logged to $LOG_FILE" # Function to handle errors handle_error() { echo "ERROR: $1" echo "Attempting to clean up resources..." cleanup_resources exit 1 } # Function to clean up resources cleanup_resources() { echo "" echo "===========================================" echo "RESOURCES CREATED:" echo "===========================================" if [ -n "$VPC_ID" ]; then echo "VPC: $VPC_ID" fi if [ -n "$DEV_POOL_ID" ]; then echo "Development Pool: $DEV_POOL_ID" fi if [ -n "$REGIONAL_POOL_ID" ]; then echo "Regional Pool: $REGIONAL_POOL_ID" fi if [ -n "$TOP_POOL_ID" ]; then echo "Top-level Pool: $TOP_POOL_ID" fi if [ -n "$IPAM_ID" ]; then echo "IPAM: $IPAM_ID" fi 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 echo "Starting cleanup..." # Delete resources in reverse order of creation to handle dependencies if [ -n "$VPC_ID" ]; then echo "Deleting VPC: $VPC_ID" aws ec2 delete-vpc --vpc-id "$VPC_ID" || echo "Failed to delete VPC" echo "Waiting for VPC to be deleted..." sleep 10 fi if [ -n "$DEV_POOL_ID" ]; then echo "Deleting Development Pool: $DEV_POOL_ID" # First deprovision any CIDRs from the pool CIDRS=$(aws ec2 get-ipam-pool-cidrs --ipam-pool-id "$DEV_POOL_ID" --query 'IpamPoolCidrs[].Cidr' --output text) for CIDR in $CIDRS; do echo "Deprovisioning CIDR $CIDR from Development Pool" aws ec2 deprovision-ipam-pool-cidr --ipam-pool-id "$DEV_POOL_ID" --cidr "$CIDR" || echo "Failed to deprovision CIDR $CIDR" sleep 5 done aws ec2 delete-ipam-pool --ipam-pool-id "$DEV_POOL_ID" || echo "Failed to delete Development Pool" echo "Waiting for Development Pool to be deleted..." sleep 10 fi if [ -n "$REGIONAL_POOL_ID" ]; then echo "Deleting Regional Pool: $REGIONAL_POOL_ID" # First deprovision any CIDRs from the pool CIDRS=$(aws ec2 get-ipam-pool-cidrs --ipam-pool-id "$REGIONAL_POOL_ID" --query 'IpamPoolCidrs[].Cidr' --output text) for CIDR in $CIDRS; do echo "Deprovisioning CIDR $CIDR from Regional Pool" aws ec2 deprovision-ipam-pool-cidr --ipam-pool-id "$REGIONAL_POOL_ID" --cidr "$CIDR" || echo "Failed to deprovision CIDR $CIDR" sleep 5 done aws ec2 delete-ipam-pool --ipam-pool-id "$REGIONAL_POOL_ID" || echo "Failed to delete Regional Pool" echo "Waiting for Regional Pool to be deleted..." sleep 10 fi if [ -n "$TOP_POOL_ID" ]; then echo "Deleting Top-level Pool: $TOP_POOL_ID" # First deprovision any CIDRs from the pool CIDRS=$(aws ec2 get-ipam-pool-cidrs --ipam-pool-id "$TOP_POOL_ID" --query 'IpamPoolCidrs[].Cidr' --output text) for CIDR in $CIDRS; do echo "Deprovisioning CIDR $CIDR from Top-level Pool" aws ec2 deprovision-ipam-pool-cidr --ipam-pool-id "$TOP_POOL_ID" --cidr "$CIDR" || echo "Failed to deprovision CIDR $CIDR" sleep 5 done aws ec2 delete-ipam-pool --ipam-pool-id "$TOP_POOL_ID" || echo "Failed to delete Top-level Pool" echo "Waiting for Top-level Pool to be deleted..." sleep 10 fi if [ -n "$IPAM_ID" ]; then echo "Deleting IPAM: $IPAM_ID" aws ec2 delete-ipam --ipam-id "$IPAM_ID" || echo "Failed to delete IPAM" fi echo "Cleanup completed." else echo "Cleanup skipped. Resources will remain in your account." fi } # Function to wait for a pool to be in the 'create-complete' state wait_for_pool() { local pool_id=$1 local max_attempts=30 local attempt=1 local state="" echo "Waiting for pool $pool_id to be available..." while [ $attempt -le $max_attempts ]; do state=$(aws ec2 describe-ipam-pools --ipam-pool-ids "$pool_id" --query 'IpamPools[0].State' --output text) if [ "$state" = "create-complete" ]; then echo "Pool $pool_id is now available (state: $state)" return 0 fi echo "Attempt $attempt/$max_attempts: Pool $pool_id is in state: $state. Waiting..." sleep 10 ((attempt++)) done echo "Timed out waiting for pool $pool_id to be available" return 1 } # Function to wait for a CIDR to be fully provisioned wait_for_cidr_provisioning() { local pool_id=$1 local cidr=$2 local max_attempts=30 local attempt=1 local state="" echo "Waiting for CIDR $cidr to be fully provisioned in pool $pool_id..." while [ $attempt -le $max_attempts ]; do state=$(aws ec2 get-ipam-pool-cidrs --ipam-pool-id "$pool_id" --query "IpamPoolCidrs[?Cidr=='$cidr'].State" --output text) if [ "$state" = "provisioned" ]; then echo "CIDR $cidr is now fully provisioned (state: $state)" return 0 fi echo "Attempt $attempt/$max_attempts: CIDR $cidr is in state: $state. Waiting..." sleep 10 ((attempt++)) done echo "Timed out waiting for CIDR $cidr to be provisioned" return 1 } # Step 1: Create an IPAM echo "Creating IPAM..." IPAM_RESULT=$(aws ec2 create-ipam \ --description "My IPAM" \ --operating-regions RegionName=us-east-1 RegionName=us-west-2) if [ $? -ne 0 ]; then handle_error "Failed to create IPAM" fi IPAM_ID=$(echo "$IPAM_RESULT" | grep -o '"IpamId": "[^"]*' | cut -d'"' -f4) echo "IPAM created with ID: $IPAM_ID" # Wait for IPAM to be created and available echo "Waiting for IPAM to be available..." sleep 20 # Step 2: Get the IPAM Scope ID - FIXED to correctly identify the private scope echo "Getting IPAM Scope ID..." SCOPE_RESULT=$(aws ec2 describe-ipams --ipam-id "$IPAM_ID") if [ $? -ne 0 ]; then handle_error "Failed to get IPAM details" fi # Extract the private scope ID directly from the IPAM details PRIVATE_SCOPE_ID=$(echo "$SCOPE_RESULT" | grep -o '"PrivateDefaultScopeId": "[^"]*' | cut -d'"' -f4) echo "Private Scope ID: $PRIVATE_SCOPE_ID" if [ -z "$PRIVATE_SCOPE_ID" ]; then handle_error "Failed to get Private Scope ID" fi # Step 3: Create a Top-Level IPv4 Pool echo "Creating Top-level IPv4 Pool..." TOP_POOL_RESULT=$(aws ec2 create-ipam-pool \ --ipam-scope-id "$PRIVATE_SCOPE_ID" \ --address-family ipv4 \ --description "Top-level pool") if [ $? -ne 0 ]; then handle_error "Failed to create Top-level Pool" fi TOP_POOL_ID=$(echo "$TOP_POOL_RESULT" | grep -o '"IpamPoolId": "[^"]*' | cut -d'"' -f4) echo "Top-level Pool created with ID: $TOP_POOL_ID" # Wait for the top-level pool to be available if ! wait_for_pool "$TOP_POOL_ID"; then handle_error "Top-level Pool did not become available in time" fi # Provision CIDR to the top-level pool echo "Provisioning CIDR to Top-level Pool..." TOP_POOL_CIDR="10.0.0.0/8" PROVISION_RESULT=$(aws ec2 provision-ipam-pool-cidr \ --ipam-pool-id "$TOP_POOL_ID" \ --cidr "$TOP_POOL_CIDR") if [ $? -ne 0 ]; then handle_error "Failed to provision CIDR to Top-level Pool" fi echo "$PROVISION_RESULT" # Wait for the CIDR to be fully provisioned if ! wait_for_cidr_provisioning "$TOP_POOL_ID" "$TOP_POOL_CIDR"; then handle_error "CIDR provisioning to Top-level Pool did not complete in time" fi # Step 4: Create a Regional IPv4 Pool echo "Creating Regional IPv4 Pool..." REGIONAL_POOL_RESULT=$(aws ec2 create-ipam-pool \ --ipam-scope-id "$PRIVATE_SCOPE_ID" \ --source-ipam-pool-id "$TOP_POOL_ID" \ --locale us-east-1 \ --address-family ipv4 \ --description "Regional pool in us-east-1") if [ $? -ne 0 ]; then handle_error "Failed to create Regional Pool" fi REGIONAL_POOL_ID=$(echo "$REGIONAL_POOL_RESULT" | grep -o '"IpamPoolId": "[^"]*' | cut -d'"' -f4) echo "Regional Pool created with ID: $REGIONAL_POOL_ID" # Wait for the regional pool to be available if ! wait_for_pool "$REGIONAL_POOL_ID"; then handle_error "Regional Pool did not become available in time" fi # Provision CIDR to the regional pool echo "Provisioning CIDR to Regional Pool..." REGIONAL_POOL_CIDR="10.0.0.0/16" PROVISION_RESULT=$(aws ec2 provision-ipam-pool-cidr \ --ipam-pool-id "$REGIONAL_POOL_ID" \ --cidr "$REGIONAL_POOL_CIDR") if [ $? -ne 0 ]; then handle_error "Failed to provision CIDR to Regional Pool" fi echo "$PROVISION_RESULT" # Wait for the CIDR to be fully provisioned if ! wait_for_cidr_provisioning "$REGIONAL_POOL_ID" "$REGIONAL_POOL_CIDR"; then handle_error "CIDR provisioning to Regional Pool did not complete in time" fi # Step 5: Create a Development IPv4 Pool - FIXED to include locale echo "Creating Development IPv4 Pool..." DEV_POOL_RESULT=$(aws ec2 create-ipam-pool \ --ipam-scope-id "$PRIVATE_SCOPE_ID" \ --source-ipam-pool-id "$REGIONAL_POOL_ID" \ --locale us-east-1 \ --address-family ipv4 \ --description "Development pool") if [ $? -ne 0 ]; then handle_error "Failed to create Development Pool" fi DEV_POOL_ID=$(echo "$DEV_POOL_RESULT" | grep -o '"IpamPoolId": "[^"]*' | cut -d'"' -f4) echo "Development Pool created with ID: $DEV_POOL_ID" # Wait for the development pool to be available if ! wait_for_pool "$DEV_POOL_ID"; then handle_error "Development Pool did not become available in time" fi # Provision CIDR to the development pool echo "Provisioning CIDR to Development Pool..." DEV_POOL_CIDR="10.0.0.0/24" PROVISION_RESULT=$(aws ec2 provision-ipam-pool-cidr \ --ipam-pool-id "$DEV_POOL_ID" \ --cidr "$DEV_POOL_CIDR") if [ $? -ne 0 ]; then handle_error "Failed to provision CIDR to Development Pool" fi echo "$PROVISION_RESULT" # Wait for the CIDR to be fully provisioned if ! wait_for_cidr_provisioning "$DEV_POOL_ID" "$DEV_POOL_CIDR"; then handle_error "CIDR provisioning to Development Pool did not complete in time" fi # Step 6: Create a VPC Using an IPAM Pool CIDR - FIXED to use the correct parameter names and a smaller netmask length echo "Creating VPC using IPAM Pool CIDR..." VPC_RESULT=$(aws ec2 create-vpc \ --ipv4-ipam-pool-id "$DEV_POOL_ID" \ --ipv4-netmask-length 26 \ --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=IPAM-VPC}]') if [ $? -ne 0 ]; then handle_error "Failed to create VPC" fi VPC_ID=$(echo "$VPC_RESULT" | grep -o '"VpcId": "[^"]*' | cut -d'"' -f4) echo "VPC created with ID: $VPC_ID" # Step 7: Verify the IPAM Pool Allocation echo "Verifying IPAM Pool Allocation..." ALLOCATION_RESULT=$(aws ec2 get-ipam-pool-allocations \ --ipam-pool-id "$DEV_POOL_ID") if [ $? -ne 0 ]; then handle_error "Failed to verify IPAM Pool Allocation" fi echo "IPAM Pool Allocation verified:" echo "$ALLOCATION_RESULT" | grep -A 5 "Allocations" echo "" echo "IPAM setup completed successfully!" echo "" # Prompt for cleanup cleanup_resources echo "Script completed at $(date)" exit 0-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create a VPC with private subnets and NAT gateways using the CLI.
Set up the necessary components including VPC, subnets, route tables, and NAT gateways.
Configure security groups and IAM roles for proper access and security.
Use CLI commands to automate the creation and configuration of these 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 # VPC with Private Subnets and NAT Gateways (IMDSv2 Compliant Version) # This script creates a VPC with public and private subnets in two Availability Zones, # NAT gateways, an internet gateway, route tables, a VPC endpoint for S3, # security groups, a launch template, an Auto Scaling group, and an Application Load Balancer. # Set up logging LOG_FILE="vpc-private-subnets-nat.log" exec > >(tee -a "$LOG_FILE") 2>&1 # Cleanup function to delete all created resources cleanup_resources() { echo "Cleaning up resources..." # Delete Auto Scaling group if it exists if [ -n "${ASG_NAME:-}" ]; then echo "Deleting Auto Scaling group: $ASG_NAME" aws autoscaling delete-auto-scaling-group --auto-scaling-group-name "$ASG_NAME" --force-delete echo "Waiting for Auto Scaling group to be deleted..." aws autoscaling wait auto-scaling-groups-deleted --auto-scaling-group-names "$ASG_NAME" fi # Delete load balancer if it exists if [ -n "${LB_ARN:-}" ]; then echo "Deleting load balancer: $LB_ARN" aws elbv2 delete-load-balancer --load-balancer-arn "$LB_ARN" # Wait for load balancer to be deleted sleep 30 fi # Delete target group if it exists if [ -n "${TARGET_GROUP_ARN:-}" ]; then echo "Deleting target group: $TARGET_GROUP_ARN" aws elbv2 delete-target-group --target-group-arn "$TARGET_GROUP_ARN" fi # Delete launch template if it exists if [ -n "${LAUNCH_TEMPLATE_NAME:-}" ]; then echo "Deleting launch template: $LAUNCH_TEMPLATE_NAME" aws ec2 delete-launch-template --launch-template-name "$LAUNCH_TEMPLATE_NAME" fi # Delete NAT Gateways if they exist if [ -n "${NAT_GW1_ID:-}" ]; then echo "Deleting NAT Gateway 1: $NAT_GW1_ID" aws ec2 delete-nat-gateway --nat-gateway-id "$NAT_GW1_ID" fi if [ -n "${NAT_GW2_ID:-}" ]; then echo "Deleting NAT Gateway 2: $NAT_GW2_ID" aws ec2 delete-nat-gateway --nat-gateway-id "$NAT_GW2_ID" fi # Wait for NAT Gateways to be deleted if [ -n "${NAT_GW1_ID:-}" ] || [ -n "${NAT_GW2_ID:-}" ]; then echo "Waiting for NAT Gateways to be deleted..." sleep 60 fi # Release Elastic IPs if they exist if [ -n "${EIP1_ALLOC_ID:-}" ]; then echo "Releasing Elastic IP 1: $EIP1_ALLOC_ID" aws ec2 release-address --allocation-id "$EIP1_ALLOC_ID" fi if [ -n "${EIP2_ALLOC_ID:-}" ]; then echo "Releasing Elastic IP 2: $EIP2_ALLOC_ID" aws ec2 release-address --allocation-id "$EIP2_ALLOC_ID" fi # Delete VPC endpoint if it exists if [ -n "${VPC_ENDPOINT_ID:-}" ]; then echo "Deleting VPC endpoint: $VPC_ENDPOINT_ID" aws ec2 delete-vpc-endpoints --vpc-endpoint-ids "$VPC_ENDPOINT_ID" fi # Delete security groups if they exist if [ -n "${APP_SG_ID:-}" ]; then echo "Deleting application security group: $APP_SG_ID" aws ec2 delete-security-group --group-id "$APP_SG_ID" fi if [ -n "${LB_SG_ID:-}" ]; then echo "Deleting load balancer security group: $LB_SG_ID" aws ec2 delete-security-group --group-id "$LB_SG_ID" fi # Detach and delete Internet Gateway if it exists if [ -n "${IGW_ID:-}" ] && [ -n "${VPC_ID:-}" ]; then echo "Detaching Internet Gateway: $IGW_ID from VPC: $VPC_ID" aws ec2 detach-internet-gateway --internet-gateway-id "$IGW_ID" --vpc-id "$VPC_ID" echo "Deleting Internet Gateway: $IGW_ID" aws ec2 delete-internet-gateway --internet-gateway-id "$IGW_ID" fi # Delete route table associations and route tables if they exist if [ -n "${PUBLIC_RT_ASSOC1_ID:-}" ]; then echo "Disassociating public route table from subnet 1: $PUBLIC_RT_ASSOC1_ID" aws ec2 disassociate-route-table --association-id "$PUBLIC_RT_ASSOC1_ID" fi if [ -n "${PUBLIC_RT_ASSOC2_ID:-}" ]; then echo "Disassociating public route table from subnet 2: $PUBLIC_RT_ASSOC2_ID" aws ec2 disassociate-route-table --association-id "$PUBLIC_RT_ASSOC2_ID" fi if [ -n "${PRIVATE_RT1_ASSOC_ID:-}" ]; then echo "Disassociating private route table 1: $PRIVATE_RT1_ASSOC_ID" aws ec2 disassociate-route-table --association-id "$PRIVATE_RT1_ASSOC_ID" fi if [ -n "${PRIVATE_RT2_ASSOC_ID:-}" ]; then echo "Disassociating private route table 2: $PRIVATE_RT2_ASSOC_ID" aws ec2 disassociate-route-table --association-id "$PRIVATE_RT2_ASSOC_ID" fi if [ -n "${PUBLIC_RT_ID:-}" ]; then echo "Deleting public route table: $PUBLIC_RT_ID" aws ec2 delete-route-table --route-table-id "$PUBLIC_RT_ID" fi if [ -n "${PRIVATE_RT1_ID:-}" ]; then echo "Deleting private route table 1: $PRIVATE_RT1_ID" aws ec2 delete-route-table --route-table-id "$PRIVATE_RT1_ID" fi if [ -n "${PRIVATE_RT2_ID:-}" ]; then echo "Deleting private route table 2: $PRIVATE_RT2_ID" aws ec2 delete-route-table --route-table-id "$PRIVATE_RT2_ID" fi # Delete subnets if they exist if [ -n "${PUBLIC_SUBNET1_ID:-}" ]; then echo "Deleting public subnet 1: $PUBLIC_SUBNET1_ID" aws ec2 delete-subnet --subnet-id "$PUBLIC_SUBNET1_ID" fi if [ -n "${PUBLIC_SUBNET2_ID:-}" ]; then echo "Deleting public subnet 2: $PUBLIC_SUBNET2_ID" aws ec2 delete-subnet --subnet-id "$PUBLIC_SUBNET2_ID" fi if [ -n "${PRIVATE_SUBNET1_ID:-}" ]; then echo "Deleting private subnet 1: $PRIVATE_SUBNET1_ID" aws ec2 delete-subnet --subnet-id "$PRIVATE_SUBNET1_ID" fi if [ -n "${PRIVATE_SUBNET2_ID:-}" ]; then echo "Deleting private subnet 2: $PRIVATE_SUBNET2_ID" aws ec2 delete-subnet --subnet-id "$PRIVATE_SUBNET2_ID" fi # Delete VPC if it exists if [ -n "${VPC_ID:-}" ]; then echo "Deleting VPC: $VPC_ID" aws ec2 delete-vpc --vpc-id "$VPC_ID" fi echo "Cleanup completed." } # Error handling function handle_error() { echo "ERROR: $1" echo "Attempting to clean up resources..." cleanup_resources exit 1 } # Function to check command success check_command() { if [ $? -ne 0 ]; then handle_error "$1" fi } # Generate a random identifier for resource names RANDOM_ID=$(openssl rand -hex 4) echo "Using random identifier: $RANDOM_ID" # Create VPC echo "Creating VPC..." VPC_RESULT=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 --tag-specifications "ResourceType=vpc,Tags=[{Key=Name,Value=ProductionVPC-$RANDOM_ID}]") check_command "Failed to create VPC" VPC_ID=$(echo "$VPC_RESULT" | jq -r '.Vpc.VpcId') echo "VPC created with ID: $VPC_ID" # Get Availability Zones echo "Getting Availability Zones..." AZ_RESULT=$(aws ec2 describe-availability-zones --query 'AvailabilityZones[0:2].ZoneName' --output text) check_command "Failed to get Availability Zones" # Convert space-separated output to array read -r -a AZS <<< "$AZ_RESULT" AZ1=${AZS[0]} AZ2=${AZS[1]} echo "Using Availability Zones: $AZ1 and $AZ2" # Create subnets echo "Creating subnets..." PUBLIC_SUBNET1_RESULT=$(aws ec2 create-subnet --vpc-id "$VPC_ID" --cidr-block 10.0.0.0/24 --availability-zone "$AZ1" --tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=PublicSubnet1-$RANDOM_ID}]") check_command "Failed to create public subnet 1" PUBLIC_SUBNET1_ID=$(echo "$PUBLIC_SUBNET1_RESULT" | jq -r '.Subnet.SubnetId') PRIVATE_SUBNET1_RESULT=$(aws ec2 create-subnet --vpc-id "$VPC_ID" --cidr-block 10.0.1.0/24 --availability-zone "$AZ1" --tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=PrivateSubnet1-$RANDOM_ID}]") check_command "Failed to create private subnet 1" PRIVATE_SUBNET1_ID=$(echo "$PRIVATE_SUBNET1_RESULT" | jq -r '.Subnet.SubnetId') PUBLIC_SUBNET2_RESULT=$(aws ec2 create-subnet --vpc-id "$VPC_ID" --cidr-block 10.0.2.0/24 --availability-zone "$AZ2" --tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=PublicSubnet2-$RANDOM_ID}]") check_command "Failed to create public subnet 2" PUBLIC_SUBNET2_ID=$(echo "$PUBLIC_SUBNET2_RESULT" | jq -r '.Subnet.SubnetId') PRIVATE_SUBNET2_RESULT=$(aws ec2 create-subnet --vpc-id "$VPC_ID" --cidr-block 10.0.3.0/24 --availability-zone "$AZ2" --tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=PrivateSubnet2-$RANDOM_ID}]") check_command "Failed to create private subnet 2" PRIVATE_SUBNET2_ID=$(echo "$PRIVATE_SUBNET2_RESULT" | jq -r '.Subnet.SubnetId') echo "Subnets created with IDs:" echo "Public Subnet 1: $PUBLIC_SUBNET1_ID" echo "Private Subnet 1: $PRIVATE_SUBNET1_ID" echo "Public Subnet 2: $PUBLIC_SUBNET2_ID" echo "Private Subnet 2: $PRIVATE_SUBNET2_ID" # Create Internet Gateway echo "Creating Internet Gateway..." IGW_RESULT=$(aws ec2 create-internet-gateway --tag-specifications "ResourceType=internet-gateway,Tags=[{Key=Name,Value=ProductionIGW-$RANDOM_ID}]") check_command "Failed to create Internet Gateway" IGW_ID=$(echo "$IGW_RESULT" | jq -r '.InternetGateway.InternetGatewayId') echo "Internet Gateway created with ID: $IGW_ID" # Attach Internet Gateway to VPC echo "Attaching Internet Gateway to VPC..." aws ec2 attach-internet-gateway --internet-gateway-id "$IGW_ID" --vpc-id "$VPC_ID" check_command "Failed to attach Internet Gateway to VPC" # Create route tables echo "Creating route tables..." PUBLIC_RT_RESULT=$(aws ec2 create-route-table --vpc-id "$VPC_ID" --tag-specifications "ResourceType=route-table,Tags=[{Key=Name,Value=PublicRouteTable-$RANDOM_ID}]") check_command "Failed to create public route table" PUBLIC_RT_ID=$(echo "$PUBLIC_RT_RESULT" | jq -r '.RouteTable.RouteTableId') PRIVATE_RT1_RESULT=$(aws ec2 create-route-table --vpc-id "$VPC_ID" --tag-specifications "ResourceType=route-table,Tags=[{Key=Name,Value=PrivateRouteTable1-$RANDOM_ID}]") check_command "Failed to create private route table 1" PRIVATE_RT1_ID=$(echo "$PRIVATE_RT1_RESULT" | jq -r '.RouteTable.RouteTableId') PRIVATE_RT2_RESULT=$(aws ec2 create-route-table --vpc-id "$VPC_ID" --tag-specifications "ResourceType=route-table,Tags=[{Key=Name,Value=PrivateRouteTable2-$RANDOM_ID}]") check_command "Failed to create private route table 2" PRIVATE_RT2_ID=$(echo "$PRIVATE_RT2_RESULT" | jq -r '.RouteTable.RouteTableId') echo "Route tables created with IDs:" echo "Public Route Table: $PUBLIC_RT_ID" echo "Private Route Table 1: $PRIVATE_RT1_ID" echo "Private Route Table 2: $PRIVATE_RT2_ID" # Add route to Internet Gateway in public route table echo "Adding route to Internet Gateway in public route table..." aws ec2 create-route --route-table-id "$PUBLIC_RT_ID" --destination-cidr-block 0.0.0.0/0 --gateway-id "$IGW_ID" check_command "Failed to add route to Internet Gateway" # Associate subnets with route tables echo "Associating subnets with route tables..." PUBLIC_RT_ASSOC1_RESULT=$(aws ec2 associate-route-table --route-table-id "$PUBLIC_RT_ID" --subnet-id "$PUBLIC_SUBNET1_ID") check_command "Failed to associate public subnet 1 with route table" PUBLIC_RT_ASSOC1_ID=$(echo "$PUBLIC_RT_ASSOC1_RESULT" | jq -r '.AssociationId') PUBLIC_RT_ASSOC2_RESULT=$(aws ec2 associate-route-table --route-table-id "$PUBLIC_RT_ID" --subnet-id "$PUBLIC_SUBNET2_ID") check_command "Failed to associate public subnet 2 with route table" PUBLIC_RT_ASSOC2_ID=$(echo "$PUBLIC_RT_ASSOC2_RESULT" | jq -r '.AssociationId') PRIVATE_RT1_ASSOC_RESULT=$(aws ec2 associate-route-table --route-table-id "$PRIVATE_RT1_ID" --subnet-id "$PRIVATE_SUBNET1_ID") check_command "Failed to associate private subnet 1 with route table" PRIVATE_RT1_ASSOC_ID=$(echo "$PRIVATE_RT1_ASSOC_RESULT" | jq -r '.AssociationId') PRIVATE_RT2_ASSOC_RESULT=$(aws ec2 associate-route-table --route-table-id "$PRIVATE_RT2_ID" --subnet-id "$PRIVATE_SUBNET2_ID") check_command "Failed to associate private subnet 2 with route table" PRIVATE_RT2_ASSOC_ID=$(echo "$PRIVATE_RT2_ASSOC_RESULT" | jq -r '.AssociationId') echo "Route table associations created with IDs:" echo "Public Subnet 1 Association: $PUBLIC_RT_ASSOC1_ID" echo "Public Subnet 2 Association: $PUBLIC_RT_ASSOC2_ID" echo "Private Subnet 1 Association: $PRIVATE_RT1_ASSOC_ID" echo "Private Subnet 2 Association: $PRIVATE_RT2_ASSOC_ID" # Create NAT Gateways echo "Creating NAT Gateways..." # Allocate Elastic IPs for NAT Gateways echo "Allocating Elastic IPs for NAT Gateways..." EIP1_RESULT=$(aws ec2 allocate-address --domain vpc --tag-specifications "ResourceType=elastic-ip,Tags=[{Key=Name,Value=NAT1-EIP-$RANDOM_ID}]") check_command "Failed to allocate Elastic IP 1" EIP1_ALLOC_ID=$(echo "$EIP1_RESULT" | jq -r '.AllocationId') EIP2_RESULT=$(aws ec2 allocate-address --domain vpc --tag-specifications "ResourceType=elastic-ip,Tags=[{Key=Name,Value=NAT2-EIP-$RANDOM_ID}]") check_command "Failed to allocate Elastic IP 2" EIP2_ALLOC_ID=$(echo "$EIP2_RESULT" | jq -r '.AllocationId') echo "Elastic IPs allocated with IDs:" echo "EIP 1 Allocation ID: $EIP1_ALLOC_ID" echo "EIP 2 Allocation ID: $EIP2_ALLOC_ID" # Create NAT Gateways echo "Creating NAT Gateway in public subnet 1..." NAT_GW1_RESULT=$(aws ec2 create-nat-gateway --subnet-id "$PUBLIC_SUBNET1_ID" --allocation-id "$EIP1_ALLOC_ID" --tag-specifications "ResourceType=natgateway,Tags=[{Key=Name,Value=NAT-Gateway1-$RANDOM_ID}]") check_command "Failed to create NAT Gateway 1" NAT_GW1_ID=$(echo "$NAT_GW1_RESULT" | jq -r '.NatGateway.NatGatewayId') echo "Creating NAT Gateway in public subnet 2..." NAT_GW2_RESULT=$(aws ec2 create-nat-gateway --subnet-id "$PUBLIC_SUBNET2_ID" --allocation-id "$EIP2_ALLOC_ID" --tag-specifications "ResourceType=natgateway,Tags=[{Key=Name,Value=NAT-Gateway2-$RANDOM_ID}]") check_command "Failed to create NAT Gateway 2" NAT_GW2_ID=$(echo "$NAT_GW2_RESULT" | jq -r '.NatGateway.NatGatewayId') echo "NAT Gateways created with IDs:" echo "NAT Gateway 1: $NAT_GW1_ID" echo "NAT Gateway 2: $NAT_GW2_ID" # Wait for NAT Gateways to be available echo "Waiting for NAT Gateways to be available..." aws ec2 wait nat-gateway-available --nat-gateway-ids "$NAT_GW1_ID" check_command "NAT Gateway 1 did not become available" aws ec2 wait nat-gateway-available --nat-gateway-ids "$NAT_GW2_ID" check_command "NAT Gateway 2 did not become available" echo "NAT Gateways are now available" # Add routes to NAT Gateways in private route tables echo "Adding routes to NAT Gateways in private route tables..." aws ec2 create-route --route-table-id "$PRIVATE_RT1_ID" --destination-cidr-block 0.0.0.0/0 --nat-gateway-id "$NAT_GW1_ID" check_command "Failed to add route to NAT Gateway 1" aws ec2 create-route --route-table-id "$PRIVATE_RT2_ID" --destination-cidr-block 0.0.0.0/0 --nat-gateway-id "$NAT_GW2_ID" check_command "Failed to add route to NAT Gateway 2" # Create VPC Endpoint for S3 echo "Creating VPC Endpoint for S3..." S3_PREFIX_LIST_ID=$(aws ec2 describe-prefix-lists --filters "Name=prefix-list-name,Values=com.amazonaws.$(aws configure get region).s3" --query 'PrefixLists[0].PrefixListId' --output text) check_command "Failed to get S3 prefix list ID" VPC_ENDPOINT_RESULT=$(aws ec2 create-vpc-endpoint --vpc-id "$VPC_ID" --service-name "com.amazonaws.$(aws configure get region).s3" --route-table-ids "$PRIVATE_RT1_ID" "$PRIVATE_RT2_ID" --tag-specifications "ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=S3-Endpoint-$RANDOM_ID}]") check_command "Failed to create VPC endpoint for S3" VPC_ENDPOINT_ID=$(echo "$VPC_ENDPOINT_RESULT" | jq -r '.VpcEndpoint.VpcEndpointId') echo "VPC Endpoint created with ID: $VPC_ENDPOINT_ID" # Create security groups echo "Creating security groups..." LB_SG_RESULT=$(aws ec2 create-security-group --group-name "LoadBalancerSG-$RANDOM_ID" --description "Security group for the load balancer" --vpc-id "$VPC_ID" --tag-specifications "ResourceType=security-group,Tags=[{Key=Name,Value=LoadBalancerSG-$RANDOM_ID}]") check_command "Failed to create load balancer security group" LB_SG_ID=$(echo "$LB_SG_RESULT" | jq -r '.GroupId') # Allow inbound HTTP traffic from anywhere to the load balancer aws ec2 authorize-security-group-ingress --group-id "$LB_SG_ID" --protocol tcp --port 80 --cidr 0.0.0.0/0 check_command "Failed to authorize ingress to load balancer security group" APP_SG_RESULT=$(aws ec2 create-security-group --group-name "AppServerSG-$RANDOM_ID" --description "Security group for the application servers" --vpc-id "$VPC_ID" --tag-specifications "ResourceType=security-group,Tags=[{Key=Name,Value=AppServerSG-$RANDOM_ID}]") check_command "Failed to create application server security group" APP_SG_ID=$(echo "$APP_SG_RESULT" | jq -r '.GroupId') # Allow inbound HTTP traffic from the load balancer security group to the application servers aws ec2 authorize-security-group-ingress --group-id "$APP_SG_ID" --protocol tcp --port 80 --source-group "$LB_SG_ID" check_command "Failed to authorize ingress to application server security group" echo "Security groups created with IDs:" echo "Load Balancer Security Group: $LB_SG_ID" echo "Application Server Security Group: $APP_SG_ID" # Create a launch template echo "Creating launch template..." # Create user data script with IMDSv2 support cat > user-data.sh << 'EOF' #!/bin/bash yum update -y yum install -y httpd systemctl start httpd systemctl enable httpd # Use IMDSv2 with session token TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") AZ=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/placement/availability-zone) HOSTNAME=$(hostname -f) echo "<h1>Hello from $HOSTNAME in $AZ</h1>" > /var/www/html/index.html EOF # Encode user data USER_DATA=$(base64 -w 0 user-data.sh) # Get latest Amazon Linux 2 AMI echo "Getting latest Amazon Linux 2 AMI..." AMI_ID=$(aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-*-x86_64-gp2" "Name=state,Values=available" --query 'sort_by(Images, &CreationDate)[-1].ImageId' --output text) check_command "Failed to get latest Amazon Linux 2 AMI" echo "Using AMI: $AMI_ID" # Create launch template with IMDSv2 required LAUNCH_TEMPLATE_NAME="AppServerTemplate-$RANDOM_ID" echo "Creating launch template: $LAUNCH_TEMPLATE_NAME" aws ec2 create-launch-template \ --launch-template-name "$LAUNCH_TEMPLATE_NAME" \ --version-description "Initial version" \ --tag-specifications "ResourceType=launch-template,Tags=[{Key=Name,Value=$LAUNCH_TEMPLATE_NAME}]" \ --launch-template-data "{ \"NetworkInterfaces\": [{ \"DeviceIndex\": 0, \"Groups\": [\"$APP_SG_ID\"], \"DeleteOnTermination\": true }], \"ImageId\": \"$AMI_ID\", \"InstanceType\": \"t3.micro\", \"UserData\": \"$USER_DATA\", \"MetadataOptions\": { \"HttpTokens\": \"required\", \"HttpEndpoint\": \"enabled\" }, \"TagSpecifications\": [{ \"ResourceType\": \"instance\", \"Tags\": [{ \"Key\": \"Name\", \"Value\": \"AppServer-$RANDOM_ID\" }] }] }" check_command "Failed to create launch template" # Create target group echo "Creating target group..." TARGET_GROUP_NAME="AppTargetGroup-$RANDOM_ID" TARGET_GROUP_RESULT=$(aws elbv2 create-target-group \ --name "$TARGET_GROUP_NAME" \ --protocol HTTP \ --port 80 \ --vpc-id "$VPC_ID" \ --target-type instance \ --health-check-protocol HTTP \ --health-check-path "/" \ --health-check-port traffic-port) check_command "Failed to create target group" TARGET_GROUP_ARN=$(echo "$TARGET_GROUP_RESULT" | jq -r '.TargetGroups[0].TargetGroupArn') echo "Target group created with ARN: $TARGET_GROUP_ARN" # Create load balancer echo "Creating load balancer..." LB_NAME="AppLoadBalancer-$RANDOM_ID" LB_RESULT=$(aws elbv2 create-load-balancer \ --name "$LB_NAME" \ --subnets "$PUBLIC_SUBNET1_ID" "$PUBLIC_SUBNET2_ID" \ --security-groups "$LB_SG_ID" \ --tags "Key=Name,Value=$LB_NAME") check_command "Failed to create load balancer" LB_ARN=$(echo "$LB_RESULT" | jq -r '.LoadBalancers[0].LoadBalancerArn') echo "Load balancer created with ARN: $LB_ARN" # Wait for load balancer to be active echo "Waiting for load balancer to be active..." aws elbv2 wait load-balancer-available --load-balancer-arns "$LB_ARN" check_command "Load balancer did not become available" # Create listener echo "Creating listener..." LISTENER_RESULT=$(aws elbv2 create-listener \ --load-balancer-arn "$LB_ARN" \ --protocol HTTP \ --port 80 \ --default-actions "Type=forward,TargetGroupArn=$TARGET_GROUP_ARN") check_command "Failed to create listener" LISTENER_ARN=$(echo "$LISTENER_RESULT" | jq -r '.Listeners[0].ListenerArn') echo "Listener created with ARN: $LISTENER_ARN" # Create Auto Scaling group echo "Creating Auto Scaling group..." ASG_NAME="AppAutoScalingGroup-$RANDOM_ID" aws autoscaling create-auto-scaling-group \ --auto-scaling-group-name "$ASG_NAME" \ --launch-template "LaunchTemplateName=$LAUNCH_TEMPLATE_NAME,Version=\$Latest" \ --min-size 2 \ --max-size 4 \ --desired-capacity 2 \ --vpc-zone-identifier "$PRIVATE_SUBNET1_ID,$PRIVATE_SUBNET2_ID" \ --target-group-arns "$TARGET_GROUP_ARN" \ --health-check-type ELB \ --health-check-grace-period 300 \ --tags "Key=Name,Value=AppServer-$RANDOM_ID,PropagateAtLaunch=true" check_command "Failed to create Auto Scaling group" echo "Auto Scaling group created with name: $ASG_NAME" # Get load balancer DNS name LB_DNS_NAME=$(aws elbv2 describe-load-balancers --load-balancer-arns "$LB_ARN" --query 'LoadBalancers[0].DNSName' --output text) check_command "Failed to get load balancer DNS name" echo "" echo "===========================================" echo "DEPLOYMENT COMPLETE" echo "===========================================" echo "VPC ID: $VPC_ID" echo "Public Subnet 1: $PUBLIC_SUBNET1_ID (AZ: $AZ1)" echo "Private Subnet 1: $PRIVATE_SUBNET1_ID (AZ: $AZ1)" echo "Public Subnet 2: $PUBLIC_SUBNET2_ID (AZ: $AZ2)" echo "Private Subnet 2: $PRIVATE_SUBNET2_ID (AZ: $AZ2)" echo "NAT Gateway 1: $NAT_GW1_ID" echo "NAT Gateway 2: $NAT_GW2_ID" echo "Load Balancer: $LB_NAME" echo "Auto Scaling Group: $ASG_NAME" echo "" echo "Your application will be available at: http://$LB_DNS_NAME" echo "It may take a few minutes for the instances to launch and pass health checks." echo "" # Add health check monitoring echo "===========================================" echo "MONITORING INSTANCE HEALTH AND LOAD BALANCER" echo "===========================================" echo "Waiting for instances to launch and pass health checks..." echo "This may take 3-5 minutes. Checking every 30 seconds..." # Monitor instance health and load balancer accessibility MAX_ATTEMPTS=10 ATTEMPT=1 HEALTHY_INSTANCES=0 while [ $ATTEMPT -le $MAX_ATTEMPTS ] && [ $HEALTHY_INSTANCES -lt 2 ]; do echo "Check attempt $ATTEMPT of $MAX_ATTEMPTS..." # Check Auto Scaling group instances echo "Checking Auto Scaling group instances..." ASG_INSTANCES=$(aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names "$ASG_NAME" --query 'AutoScalingGroups[0].Instances[*].[InstanceId,HealthStatus]' --output json) echo "ASG Instances status:" echo "$ASG_INSTANCES" | jq -r '.[] | "Instance: \(.[0]), Health: \(.[1])"' # Check target group health echo "Checking target group health..." TARGET_HEALTH=$(aws elbv2 describe-target-health --target-group-arn "$TARGET_GROUP_ARN" --output json) echo "Target health status:" echo "$TARGET_HEALTH" | jq -r '.TargetHealthDescriptions[] | "Instance: \(.Target.Id), State: \(.TargetHealth.State), Reason: \(.TargetHealth.Reason // "N/A"), Description: \(.TargetHealth.Description // "N/A")"' # Count healthy instances HEALTHY_INSTANCES=$(echo "$TARGET_HEALTH" | jq -r '[.TargetHealthDescriptions[] | select(.TargetHealth.State=="healthy")] | length') echo "Number of healthy instances: $HEALTHY_INSTANCES of 2 expected" # Check if we have healthy instances if [ $HEALTHY_INSTANCES -ge 2 ]; then echo "All instances are healthy!" # Test load balancer accessibility echo "Testing load balancer accessibility..." HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://$LB_DNS_NAME") if [ "$HTTP_STATUS" = "200" ]; then echo "Load balancer is accessible! HTTP Status: $HTTP_STATUS" echo "You can access your application at: http://$LB_DNS_NAME" # Try to get the content to verify IMDSv2 is working echo "Fetching content to verify IMDSv2 functionality..." CONTENT=$(curl -s "http://$LB_DNS_NAME") echo "Response from server:" echo "$CONTENT" # Check if the content contains the expected pattern if [[ "$CONTENT" == *"Hello from"* && "$CONTENT" == *"in"* ]]; then echo "IMDSv2 is working correctly! The instance was able to access metadata using the token-based approach." else echo "Warning: Content doesn't match expected pattern. IMDSv2 functionality could not be verified." fi break else echo "Load balancer returned HTTP status: $HTTP_STATUS" echo "Will try again in 30 seconds..." fi else echo "Waiting for instances to become healthy..." echo "Will check again in 30 seconds..." fi ATTEMPT=$((ATTEMPT+1)) if [ $ATTEMPT -le $MAX_ATTEMPTS ]; then sleep 30 fi done if [ $HEALTHY_INSTANCES -lt 2 ]; then echo "Warning: Not all instances are healthy after maximum attempts." echo "You may need to wait longer or check for configuration issues." fi echo "To test your application, run:" echo "curl http://$LB_DNS_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 deleted." else echo "Resources will not be deleted. You can manually delete them later." echo "To delete resources, run this script again and choose to clean up." fi-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Enable Amazon EBS encryption by default
Create an EBS snapshot
Create and initialize a volume from a snapshot
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 # Script for EBS operations: encryption, snapshots, and volume initialization # This script demonstrates: # 1. Enabling EBS encryption by default # 2. Creating an EBS snapshot # 3. Creating a volume from a snapshot # Setup logging LOG_FILE="ebs-operations-v2.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting EBS operations script at $(date)" echo "All operations will be logged to $LOG_FILE" # Function to check command status check_status() { if [ $? -ne 0 ]; then echo "ERROR: $1 failed. Exiting." cleanup_resources exit 1 fi } # Function to cleanup resources cleanup_resources() { echo "Attempting to clean up resources..." if [ -n "$NEW_VOLUME_ID" ]; then echo "Checking if new volume is attached..." ATTACHMENT_STATE=$(aws ec2 describe-volumes --volume-ids "$NEW_VOLUME_ID" --query 'Volumes[0].Attachments[0].State' --output text 2>/dev/null) if [ "$ATTACHMENT_STATE" == "attached" ]; then echo "Detaching new volume $NEW_VOLUME_ID..." aws ec2 detach-volume --volume-id "$NEW_VOLUME_ID" echo "Waiting for volume to detach..." aws ec2 wait volume-available --volume-ids "$NEW_VOLUME_ID" fi echo "Deleting new volume $NEW_VOLUME_ID..." aws ec2 delete-volume --volume-id "$NEW_VOLUME_ID" fi if [ -n "$VOLUME_ID" ]; then echo "Checking if original volume is attached..." ATTACHMENT_STATE=$(aws ec2 describe-volumes --volume-ids "$VOLUME_ID" --query 'Volumes[0].Attachments[0].State' --output text 2>/dev/null) if [ "$ATTACHMENT_STATE" == "attached" ]; then echo "Detaching original volume $VOLUME_ID..." aws ec2 detach-volume --volume-id "$VOLUME_ID" echo "Waiting for volume to detach..." aws ec2 wait volume-available --volume-ids "$VOLUME_ID" fi echo "Deleting original volume $VOLUME_ID..." aws ec2 delete-volume --volume-id "$VOLUME_ID" fi if [ -n "$SNAPSHOT_ID" ]; then echo "Deleting snapshot $SNAPSHOT_ID..." aws ec2 delete-snapshot --snapshot-id "$SNAPSHOT_ID" fi if [ "$ENCRYPTION_MODIFIED" = true ]; then echo "Restoring original encryption setting..." if [ "$ORIGINAL_ENCRYPTION" = "False" ]; then aws ec2 disable-ebs-encryption-by-default else aws ec2 enable-ebs-encryption-by-default fi fi echo "Cleanup completed." } # Track created resources VOLUME_ID="" NEW_VOLUME_ID="" SNAPSHOT_ID="" ENCRYPTION_MODIFIED=false ORIGINAL_ENCRYPTION="" # Get the current AWS region AWS_REGION=$(aws configure get region) if [ -z "$AWS_REGION" ]; then AWS_REGION="us-east-1" echo "No region found in AWS config. Using default: $AWS_REGION" fi # Get availability zones in the region AVAILABILITY_ZONE=$(aws ec2 describe-availability-zones --query 'AvailabilityZones[0].ZoneName' --output text) check_status "Getting availability zone" echo "Using availability zone: $AVAILABILITY_ZONE" # Step 1: Check and enable EBS encryption by default echo "Step 1: Checking current EBS encryption by default setting..." ORIGINAL_ENCRYPTION=$(aws ec2 get-ebs-encryption-by-default --query 'EbsEncryptionByDefault' --output text) check_status "Checking encryption status" echo "Current encryption by default setting: $ORIGINAL_ENCRYPTION" if [ "$ORIGINAL_ENCRYPTION" = "False" ]; then echo "Enabling EBS encryption by default..." aws ec2 enable-ebs-encryption-by-default check_status "Enabling encryption by default" ENCRYPTION_MODIFIED=true # Verify encryption is enabled ENCRYPTION_STATUS=$(aws ec2 get-ebs-encryption-by-default --query 'EbsEncryptionByDefault' --output text) check_status "Verifying encryption status" echo "Updated encryption by default setting: $ENCRYPTION_STATUS" else echo "EBS encryption by default is already enabled." fi # Check the default KMS key echo "Checking default KMS key for EBS encryption..." KMS_KEY=$(aws ec2 get-ebs-default-kms-key-id --query 'KmsKeyId' --output text) check_status "Getting default KMS key" echo "Default KMS key: $KMS_KEY" # Step 2: Create a test volume for snapshot echo "Step 2: Creating a test volume..." VOLUME_ID=$(aws ec2 create-volume --availability-zone "$AVAILABILITY_ZONE" --size 1 --volume-type gp3 --query 'VolumeId' --output text) check_status "Creating test volume" echo "Created test volume: $VOLUME_ID" # Wait for volume to become available echo "Waiting for volume to become available..." aws ec2 wait volume-available --volume-ids "$VOLUME_ID" check_status "Waiting for volume" # Step 3: Create a snapshot of the volume echo "Step 3: Creating snapshot of the volume..." SNAPSHOT_ID=$(aws ec2 create-snapshot --volume-id "$VOLUME_ID" --description "Snapshot for EBS tutorial" --query 'SnapshotId' --output text) check_status "Creating snapshot" echo "Created snapshot: $SNAPSHOT_ID" # Wait for snapshot to complete echo "Waiting for snapshot to complete (this may take several minutes)..." aws ec2 wait snapshot-completed --snapshot-ids "$SNAPSHOT_ID" check_status "Waiting for snapshot" echo "Snapshot completed." # Step 4: Create a new volume from the snapshot echo "Step 4: Creating a new volume from the snapshot..." NEW_VOLUME_ID=$(aws ec2 create-volume --snapshot-id "$SNAPSHOT_ID" --availability-zone "$AVAILABILITY_ZONE" --volume-type gp3 --query 'VolumeId' --output text) check_status "Creating new volume from snapshot" echo "Created new volume from snapshot: $NEW_VOLUME_ID" # Wait for new volume to become available echo "Waiting for new volume to become available..." aws ec2 wait volume-available --volume-ids "$NEW_VOLUME_ID" check_status "Waiting for new volume" # Display created resources echo "" echo "===========================================" echo "RESOURCES CREATED" echo "===========================================" echo "Original Volume: $VOLUME_ID" echo "Snapshot: $SNAPSHOT_ID" echo "New Volume: $NEW_VOLUME_ID" echo "===========================================" # Prompt for cleanup 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 echo "Starting cleanup process..." # Delete the new volume echo "Deleting new volume $NEW_VOLUME_ID..." aws ec2 delete-volume --volume-id "$NEW_VOLUME_ID" check_status "Deleting new volume" # Delete the original volume echo "Deleting original volume $VOLUME_ID..." aws ec2 delete-volume --volume-id "$VOLUME_ID" check_status "Deleting original volume" # Delete the snapshot echo "Deleting snapshot $SNAPSHOT_ID..." aws ec2 delete-snapshot --snapshot-id "$SNAPSHOT_ID" check_status "Deleting snapshot" # Restore original encryption setting if modified if [ "$ENCRYPTION_MODIFIED" = true ]; then echo "Restoring original encryption setting..." if [ "$ORIGINAL_ENCRYPTION" = "False" ]; then aws ec2 disable-ebs-encryption-by-default check_status "Disabling encryption by default" fi fi echo "Cleanup completed successfully." else echo "Skipping cleanup. Resources will remain in your account." echo "To clean up manually, delete the following resources:" echo "1. Volume: $NEW_VOLUME_ID" echo "2. Volume: $VOLUME_ID" echo "3. Snapshot: $SNAPSHOT_ID" echo "4. Restore encryption setting with: aws ec2 disable-ebs-encryption-by-default (if needed)" fi echo "Script completed at $(date)"-
For API details, see the following topics in AWS CLI Command Reference.
-
The following code example shows how to:
Create VPCs for peering
Create a VPC peering connection
Update route tables
Verify the VPC peering connection
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 # VPC Peering Connection Script - Version 4 (Fixed) # This script establishes a VPC peering connection between two VPCs, # creates subnets if needed, and configures the necessary route tables. # It will use existing VPCs if available, or create new ones if needed. # Initialize log file LOG_FILE="vpc-peering-script-v4.log" echo "Starting VPC Peering script at $(date)" > $LOG_FILE # Function to log commands and their output log_cmd() { echo "$(date): COMMAND: $1" >> $LOG_FILE eval "$1" 2>&1 | tee -a $LOG_FILE return ${PIPESTATUS[0]} } # Function to check for errors check_error() { if [ $1 -ne 0 ]; then echo "ERROR: Command failed with exit code $1" | tee -a $LOG_FILE echo "See $LOG_FILE for details" cleanup_on_error exit $1 fi } # Function to clean up resources on error cleanup_on_error() { echo "Error encountered. Attempting to clean up resources..." | tee -a $LOG_FILE # List created resources echo "Resources created:" | tee -a $LOG_FILE for resource in "${CREATED_RESOURCES[@]}"; do echo "- $resource" | tee -a $LOG_FILE done # Clean up in reverse order for ((i=${#CLEANUP_COMMANDS[@]}-1; i>=0; i--)); do echo "Executing cleanup: ${CLEANUP_COMMANDS[$i]}" >> $LOG_FILE eval "${CLEANUP_COMMANDS[$i]}" 2>&1 >> $LOG_FILE done } # Array to store created resources and cleanup commands declare -a CREATED_RESOURCES declare -a CLEANUP_COMMANDS echo "Setting up VPC peering connection..." # Check for existing VPCs echo "Checking for existing VPCs..." EXISTING_VPCS=$(aws ec2 describe-vpcs --query 'Vpcs[?State==`available`].[VpcId,CidrBlock]' --output text 2>/dev/null) if [ -z "$EXISTING_VPCS" ]; then echo "No existing VPCs found. Creating new VPCs..." CREATE_VPCS=true else echo "Found existing VPCs:" echo "$EXISTING_VPCS" echo "" echo "Do you want to use existing VPCs (e) or create new ones (n)? [e/n]: " read -r VPC_CHOICE if [[ "${VPC_CHOICE,,}" == "e" ]]; then CREATE_VPCS=false # Get the first two available VPCs VPC1_INFO=$(echo "$EXISTING_VPCS" | head -n 1) VPC2_INFO=$(echo "$EXISTING_VPCS" | head -n 2 | tail -n 1) if [ -z "$VPC2_INFO" ]; then echo "Only one VPC found. Creating a second VPC..." VPC1_ID=$(echo $VPC1_INFO | awk '{print $1}') VPC1_CIDR=$(echo $VPC1_INFO | awk '{print $2}') CREATE_VPC2_ONLY=true else VPC1_ID=$(echo $VPC1_INFO | awk '{print $1}') VPC1_CIDR=$(echo $VPC1_INFO | awk '{print $2}') VPC2_ID=$(echo $VPC2_INFO | awk '{print $1}') VPC2_CIDR=$(echo $VPC2_INFO | awk '{print $2}') CREATE_VPC2_ONLY=false fi else CREATE_VPCS=true fi fi # Create VPCs if needed if [ "$CREATE_VPCS" = true ]; then echo "Creating VPC1..." VPC1_ID=$(log_cmd "aws ec2 create-vpc --cidr-block 10.1.0.0/16 --tag-specifications \"ResourceType=vpc,Tags=[{Key=Name,Value=VPC1-Peering-Demo}]\" --query 'Vpc.VpcId' --output text") check_error $? VPC1_CIDR="10.1.0.0/16" CREATED_RESOURCES+=("VPC1: $VPC1_ID") CLEANUP_COMMANDS+=("aws ec2 delete-vpc --vpc-id $VPC1_ID") echo "VPC1 created with ID: $VPC1_ID" echo "Creating VPC2..." VPC2_ID=$(log_cmd "aws ec2 create-vpc --cidr-block 10.2.0.0/16 --tag-specifications \"ResourceType=vpc,Tags=[{Key=Name,Value=VPC2-Peering-Demo}]\" --query 'Vpc.VpcId' --output text") check_error $? VPC2_CIDR="10.2.0.0/16" CREATED_RESOURCES+=("VPC2: $VPC2_ID") CLEANUP_COMMANDS+=("aws ec2 delete-vpc --vpc-id $VPC2_ID") echo "VPC2 created with ID: $VPC2_ID" # Wait for VPCs to be available echo "Waiting for VPCs to be available..." log_cmd "aws ec2 wait vpc-available --vpc-ids $VPC1_ID $VPC2_ID" check_error $? elif [ "$CREATE_VPC2_ONLY" = true ]; then echo "Creating VPC2..." VPC2_ID=$(log_cmd "aws ec2 create-vpc --cidr-block 10.2.0.0/16 --tag-specifications \"ResourceType=vpc,Tags=[{Key=Name,Value=VPC2-Peering-Demo}]\" --query 'Vpc.VpcId' --output text") check_error $? VPC2_CIDR="10.2.0.0/16" CREATED_RESOURCES+=("VPC2: $VPC2_ID") CLEANUP_COMMANDS+=("aws ec2 delete-vpc --vpc-id $VPC2_ID") echo "VPC2 created with ID: $VPC2_ID" # Wait for VPC2 to be available echo "Waiting for VPC2 to be available..." log_cmd "aws ec2 wait vpc-available --vpc-ids $VPC2_ID" check_error $? fi echo "Using the following VPCs:" echo "VPC1: $VPC1_ID ($VPC1_CIDR)" echo "VPC2: $VPC2_ID ($VPC2_CIDR)" # Verify the VPCs exist and are available echo "Verifying VPCs..." log_cmd "aws ec2 describe-vpcs --vpc-ids $VPC1_ID $VPC2_ID --query 'Vpcs[*].[VpcId,State,CidrBlock]' --output table" check_error $? # Determine subnet CIDR blocks based on VPC CIDR blocks VPC1_SUBNET_CIDR=$(echo $VPC1_CIDR | sed 's/0\.0\/16/1.0\/24/') VPC2_SUBNET_CIDR=$(echo $VPC2_CIDR | sed 's/0\.0\/16/1.0\/24/') # Create subnets in both VPCs echo "Creating subnet in VPC1..." SUBNET1_ID=$(log_cmd "aws ec2 create-subnet --vpc-id $VPC1_ID --cidr-block $VPC1_SUBNET_CIDR --tag-specifications \"ResourceType=subnet,Tags=[{Key=Name,Value=VPC1-Peering-Subnet}]\" --query 'Subnet.SubnetId' --output text") check_error $? CREATED_RESOURCES+=("Subnet in VPC1: $SUBNET1_ID") CLEANUP_COMMANDS+=("aws ec2 delete-subnet --subnet-id $SUBNET1_ID") echo "Subnet created in VPC1 with ID: $SUBNET1_ID (CIDR: $VPC1_SUBNET_CIDR)" echo "Creating subnet in VPC2..." SUBNET2_ID=$(log_cmd "aws ec2 create-subnet --vpc-id $VPC2_ID --cidr-block $VPC2_SUBNET_CIDR --tag-specifications \"ResourceType=subnet,Tags=[{Key=Name,Value=VPC2-Peering-Subnet}]\" --query 'Subnet.SubnetId' --output text") check_error $? CREATED_RESOURCES+=("Subnet in VPC2: $SUBNET2_ID") CLEANUP_COMMANDS+=("aws ec2 delete-subnet --subnet-id $SUBNET2_ID") echo "Subnet created in VPC2 with ID: $SUBNET2_ID (CIDR: $VPC2_SUBNET_CIDR)" # Create a VPC peering connection echo "Creating VPC peering connection..." PEERING_ID=$(log_cmd "aws ec2 create-vpc-peering-connection --vpc-id $VPC1_ID --peer-vpc-id $VPC2_ID --tag-specifications \"ResourceType=vpc-peering-connection,Tags=[{Key=Name,Value=VPC1-VPC2-Peering}]\" --query 'VpcPeeringConnection.VpcPeeringConnectionId' --output text") check_error $? CREATED_RESOURCES+=("VPC Peering Connection: $PEERING_ID") CLEANUP_COMMANDS+=("aws ec2 delete-vpc-peering-connection --vpc-peering-connection-id $PEERING_ID") echo "VPC Peering Connection created with ID: $PEERING_ID" # Accept the VPC peering connection echo "Accepting VPC peering connection..." log_cmd "aws ec2 accept-vpc-peering-connection --vpc-peering-connection-id $PEERING_ID" check_error $? echo "VPC Peering Connection accepted" # Wait for the peering connection to become active echo "Waiting for peering connection to become active..." log_cmd "aws ec2 wait vpc-peering-connection-exists --vpc-peering-connection-ids $PEERING_ID" check_error $? # Create a route table for VPC1 echo "Creating route table for VPC1..." RTB1_ID=$(log_cmd "aws ec2 create-route-table --vpc-id $VPC1_ID --tag-specifications \"ResourceType=route-table,Tags=[{Key=Name,Value=VPC1-RouteTable}]\" --query 'RouteTable.RouteTableId' --output text") check_error $? CREATED_RESOURCES+=("Route Table for VPC1: $RTB1_ID") CLEANUP_COMMANDS+=("aws ec2 delete-route-table --route-table-id $RTB1_ID") echo "Route table created for VPC1 with ID: $RTB1_ID" # Create a route from VPC1 to VPC2 echo "Creating route from VPC1 to VPC2..." log_cmd "aws ec2 create-route --route-table-id $RTB1_ID --destination-cidr-block $VPC2_CIDR --vpc-peering-connection-id $PEERING_ID" check_error $? echo "Route created from VPC1 to VPC2" # Associate the route table with the subnet in VPC1 echo "Associating route table with subnet in VPC1..." RTB1_ASSOC_ID=$(log_cmd "aws ec2 associate-route-table --route-table-id $RTB1_ID --subnet-id $SUBNET1_ID --query 'AssociationId' --output text") check_error $? CREATED_RESOURCES+=("Route Table Association for VPC1: $RTB1_ASSOC_ID") CLEANUP_COMMANDS+=("aws ec2 disassociate-route-table --association-id $RTB1_ASSOC_ID") echo "Route table associated with subnet in VPC1" # Create a route table for VPC2 echo "Creating route table for VPC2..." RTB2_ID=$(log_cmd "aws ec2 create-route-table --vpc-id $VPC2_ID --tag-specifications \"ResourceType=route-table,Tags=[{Key=Name,Value=VPC2-RouteTable}]\" --query 'RouteTable.RouteTableId' --output text") check_error $? CREATED_RESOURCES+=("Route Table for VPC2: $RTB2_ID") CLEANUP_COMMANDS+=("aws ec2 delete-route-table --route-table-id $RTB2_ID") echo "Route table created for VPC2 with ID: $RTB2_ID" # Create a route from VPC2 to VPC1 echo "Creating route from VPC2 to VPC1..." log_cmd "aws ec2 create-route --route-table-id $RTB2_ID --destination-cidr-block $VPC1_CIDR --vpc-peering-connection-id $PEERING_ID" check_error $? echo "Route created from VPC2 to VPC1" # Associate the route table with the subnet in VPC2 echo "Associating route table with subnet in VPC2..." RTB2_ASSOC_ID=$(log_cmd "aws ec2 associate-route-table --route-table-id $RTB2_ID --subnet-id $SUBNET2_ID --query 'AssociationId' --output text") check_error $? CREATED_RESOURCES+=("Route Table Association for VPC2: $RTB2_ASSOC_ID") CLEANUP_COMMANDS+=("aws ec2 disassociate-route-table --association-id $RTB2_ASSOC_ID") echo "Route table associated with subnet in VPC2" # Verify the VPC peering connection echo "Verifying VPC peering connection..." log_cmd "aws ec2 describe-vpc-peering-connections --vpc-peering-connection-ids $PEERING_ID --query 'VpcPeeringConnections[0].[VpcPeeringConnectionId,Status.Code,AccepterVpcInfo.VpcId,RequesterVpcInfo.VpcId]' --output table" check_error $? echo "VPC peering connection verified" # Display summary of created resources echo "" echo "==============================================" echo "SUMMARY OF CREATED RESOURCES" echo "==============================================" echo "VPC1 ID: $VPC1_ID" echo "VPC1 CIDR: $VPC1_CIDR" echo "Subnet1 ID: $SUBNET1_ID (CIDR: $VPC1_SUBNET_CIDR)" echo "VPC2 ID: $VPC2_ID" echo "VPC2 CIDR: $VPC2_CIDR" echo "Subnet2 ID: $SUBNET2_ID (CIDR: $VPC2_SUBNET_CIDR)" echo "Peering Connection ID: $PEERING_ID" echo "Route Table 1 ID: $RTB1_ID" echo "Route Table 1 Association ID: $RTB1_ASSOC_ID" echo "Route Table 2 ID: $RTB2_ID" echo "Route Table 2 Association ID: $RTB2_ASSOC_ID" echo "" echo "Created resources:" for resource in "${CREATED_RESOURCES[@]}"; do echo "- $resource" done echo "==============================================" echo "" # Test connectivity (optional) echo "==============================================" echo "CONNECTIVITY TEST" echo "==============================================" echo "To test connectivity between VPCs, you would need to:" echo "1. Launch EC2 instances in each subnet" echo "2. Configure security groups to allow traffic" echo "3. Test ping or other network connectivity" echo "" # Prompt for cleanup 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,,}" == "y" ]]; then echo "Starting cleanup process..." # Clean up in reverse order echo "Disassociating route table from subnet in VPC2..." log_cmd "aws ec2 disassociate-route-table --association-id $RTB2_ASSOC_ID" echo "Disassociating route table from subnet in VPC1..." log_cmd "aws ec2 disassociate-route-table --association-id $RTB1_ASSOC_ID" echo "Deleting route table for VPC2..." log_cmd "aws ec2 delete-route-table --route-table-id $RTB2_ID" echo "Deleting route table for VPC1..." log_cmd "aws ec2 delete-route-table --route-table-id $RTB1_ID" echo "Deleting VPC peering connection..." log_cmd "aws ec2 delete-vpc-peering-connection --vpc-peering-connection-id $PEERING_ID" echo "Deleting subnet in VPC2..." log_cmd "aws ec2 delete-subnet --subnet-id $SUBNET2_ID" echo "Deleting subnet in VPC1..." log_cmd "aws ec2 delete-subnet --subnet-id $SUBNET1_ID" # Delete VPCs if they were created by this script if [ "$CREATE_VPCS" = true ]; then echo "Deleting VPC2..." log_cmd "aws ec2 delete-vpc --vpc-id $VPC2_ID" echo "Deleting VPC1..." log_cmd "aws ec2 delete-vpc --vpc-id $VPC1_ID" elif [ "$CREATE_VPC2_ONLY" = true ]; then echo "Deleting VPC2..." log_cmd "aws ec2 delete-vpc --vpc-id $VPC2_ID" fi echo "Cleanup completed successfully." else echo "Cleanup skipped. Resources will remain in your AWS account." echo "" echo "To manually clean up later, you can delete resources in this order:" echo "1. Route table associations" echo "2. Route tables" echo "3. VPC peering connection" echo "4. Subnets" if [ "$CREATE_VPCS" = true ] || [ "$CREATE_VPC2_ONLY" = true ]; then echo "5. VPCs (if created by this script)" fi fi echo "Script execution completed. See $LOG_FILE for detailed logs."-
For API details, see the following topics in AWS CLI Command Reference.
-