Systems Manager 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 Systems Manager.
Scenarios are code examples that show you how to accomplish specific tasks by calling multiple functions within a service or combined with other AWS services.
Each example includes a link to the complete source code, where you can find instructions on how to set up and run the code in context.
Topics
Scenarios
The following code example shows how to:
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:
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 IAM permissions for Systems Manager
Create an IAM role for Systems Manager
Configure Systems Manager
Verify the setup
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 Systems Manager Setup Script # This script sets up AWS Systems Manager for a single account and region # # Version 17 fixes: # 1. Added cloudformation.amazonaws.com to the IAM role trust policy # 2. Systems Manager Quick Setup uses CloudFormation for deployments, so the role must trust CloudFormation service # Initialize log file LOG_FILE="ssm_setup_$(date +%Y%m%d_%H%M%S).log" echo "Starting AWS Systems Manager setup at $(date)" > "$LOG_FILE" # Function to log commands and their outputs with immediate terminal display log_cmd() { echo "$(date): Running command: $1" | tee -a "$LOG_FILE" local output output=$(eval "$1" 2>&1) local status=$? echo "$output" | tee -a "$LOG_FILE" return $status } # 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" echo "Command output: $cmd_output" | tee -a "$LOG_FILE" cleanup_on_error exit 1 fi } # Array to track created resources for cleanup declare -a CREATED_RESOURCES # Function to add a resource to the tracking array track_resource() { local resource_type="$1" local resource_id="$2" CREATED_RESOURCES+=("$resource_type:$resource_id") echo "Tracked resource: $resource_type:$resource_id" | tee -a "$LOG_FILE" } # Function to clean up resources on error cleanup_on_error() { echo "" | tee -a "$LOG_FILE" echo "==========================================" | tee -a "$LOG_FILE" echo "ERROR OCCURRED - CLEANING UP RESOURCES" | tee -a "$LOG_FILE" echo "==========================================" | tee -a "$LOG_FILE" echo "The following resources were created:" | tee -a "$LOG_FILE" # Display resources in reverse order for ((i=${#CREATED_RESOURCES[@]}-1; i>=0; i--)); do echo "${CREATED_RESOURCES[$i]}" | tee -a "$LOG_FILE" done echo "" | tee -a "$LOG_FILE" echo "Attempting to clean up resources..." | tee -a "$LOG_FILE" # Clean up resources in reverse order cleanup_resources } # Function to clean up all created resources cleanup_resources() { # Process resources in reverse order (last created, first deleted) for ((i=${#CREATED_RESOURCES[@]}-1; i>=0; i--)); do IFS=':' read -r resource_type resource_id <<< "${CREATED_RESOURCES[$i]}" echo "Deleting $resource_type: $resource_id" | tee -a "$LOG_FILE" case "$resource_type" in "IAM_POLICY") # Delete the policy (detachment should have been handled when the role was deleted) log_cmd "aws iam delete-policy --policy-arn $resource_id" || true ;; "IAM_ROLE") # Detach all policies from the role first if [[ -n "$POLICY_ARN" ]]; then log_cmd "aws iam detach-role-policy --role-name $resource_id --policy-arn $POLICY_ARN" || true fi # Delete the role log_cmd "aws iam delete-role --role-name $resource_id" || true ;; "SSM_CONFIG_MANAGER") log_cmd "aws ssm-quicksetup delete-configuration-manager --manager-arn $resource_id" || true ;; *) echo "Unknown resource type: $resource_type, cannot delete automatically" | tee -a "$LOG_FILE" ;; esac done echo "Cleanup completed" | tee -a "$LOG_FILE" # Clean up temporary files rm -f ssm-onboarding-policy.json trust-policy.json ssm-config.json 2>/dev/null || true } # Main script execution echo "AWS Systems Manager Setup Script" echo "================================" echo "This script will set up AWS Systems Manager for a single account and region." echo "It will create IAM policies and roles, then enable Systems Manager features." echo "" # Get the current AWS region CURRENT_REGION=$(aws configure get region) if [[ -z "$CURRENT_REGION" ]]; then echo "No AWS region configured. Please specify a region:" read -r CURRENT_REGION if [[ -z "$CURRENT_REGION" ]]; then echo "ERROR: A region must be specified" | tee -a "$LOG_FILE" exit 1 fi fi echo "Using AWS region: $CURRENT_REGION" | tee -a "$LOG_FILE" # Step 1: Create IAM policy for Systems Manager onboarding echo "Step 1: Creating IAM policy for Systems Manager onboarding..." # Create policy document cat > ssm-onboarding-policy.json << 'EOF' { "Version":"2012-10-17", "Statement": [ { "Sid": "QuickSetupActions", "Effect": "Allow", "Action": [ "ssm-quicksetup:*" ], "Resource": "*" }, { "Sid": "SsmReadOnly", "Effect": "Allow", "Action": [ "ssm:DescribeAutomationExecutions", "ssm:GetAutomationExecution", "ssm:ListAssociations", "ssm:DescribeAssociation", "ssm:ListDocuments", "ssm:ListResourceDataSync", "ssm:DescribePatchBaselines", "ssm:GetPatchBaseline", "ssm:DescribeMaintenanceWindows", "ssm:DescribeMaintenanceWindowTasks" ], "Resource": "*" }, { "Sid": "SsmDocument", "Effect": "Allow", "Action": [ "ssm:GetDocument", "ssm:DescribeDocument" ], "Resource": [ "arn:aws:ssm:*:*:document/AWSQuickSetupType-*", "arn:aws:ssm:*:*:document/AWS-EnableExplorer" ] }, { "Sid": "SsmEnableExplorer", "Effect": "Allow", "Action": "ssm:StartAutomationExecution", "Resource": "arn:aws:ssm:*:*:automation-definition/AWS-EnableExplorer:*" }, { "Sid": "SsmExplorerRds", "Effect": "Allow", "Action": [ "ssm:GetOpsSummary", "ssm:CreateResourceDataSync", "ssm:UpdateResourceDataSync" ], "Resource": "arn:aws:ssm:*:*:resource-data-sync/AWS-QuickSetup-*" }, { "Sid": "OrgsReadOnly", "Effect": "Allow", "Action": [ "organizations:DescribeAccount", "organizations:DescribeOrganization", "organizations:ListDelegatedAdministrators", "organizations:ListRoots", "organizations:ListParents", "organizations:ListOrganizationalUnitsForParent", "organizations:DescribeOrganizationalUnit", "organizations:ListAWSServiceAccessForOrganization" ], "Resource": "*" }, { "Sid": "OrgsAdministration", "Effect": "Allow", "Action": [ "organizations:EnableAWSServiceAccess", "organizations:RegisterDelegatedAdministrator", "organizations:DeregisterDelegatedAdministrator" ], "Resource": "*", "Condition": { "StringEquals": { "organizations:ServicePrincipal": [ "ssm.amazonaws.com", "ssm-quicksetup.amazonaws.com", "member.org.stacksets.cloudformation.amazonaws.com", "resource-explorer-2.amazonaws.com" ] } } }, { "Sid": "CfnReadOnly", "Effect": "Allow", "Action": [ "cloudformation:ListStacks", "cloudformation:DescribeStacks", "cloudformation:ListStackSets", "cloudformation:DescribeOrganizationsAccess" ], "Resource": "*" }, { "Sid": "OrgCfnAccess", "Effect": "Allow", "Action": [ "cloudformation:ActivateOrganizationsAccess" ], "Resource": "*" }, { "Sid": "CfnStackActions", "Effect": "Allow", "Action": [ "cloudformation:CreateStack", "cloudformation:DeleteStack", "cloudformation:DescribeStackResources", "cloudformation:DescribeStackEvents", "cloudformation:GetTemplate", "cloudformation:RollbackStack", "cloudformation:TagResource", "cloudformation:UntagResource", "cloudformation:UpdateStack" ], "Resource": [ "arn:aws:cloudformation:*:*:stack/StackSet-AWS-QuickSetup-*", "arn:aws:cloudformation:*:*:stack/AWS-QuickSetup-*", "arn:aws:cloudformation:*:*:type/resource/*" ] }, { "Sid": "CfnStackSetActions", "Effect": "Allow", "Action": [ "cloudformation:CreateStackInstances", "cloudformation:CreateStackSet", "cloudformation:DeleteStackInstances", "cloudformation:DeleteStackSet", "cloudformation:DescribeStackInstance", "cloudformation:DetectStackSetDrift", "cloudformation:ListStackInstanceResourceDrifts", "cloudformation:DescribeStackSet", "cloudformation:DescribeStackSetOperation", "cloudformation:ListStackInstances", "cloudformation:ListStackSetOperations", "cloudformation:ListStackSetOperationResults", "cloudformation:TagResource", "cloudformation:UntagResource", "cloudformation:UpdateStackSet" ], "Resource": [ "arn:aws:cloudformation:*:*:stackset/AWS-QuickSetup-*", "arn:aws:cloudformation:*:*:type/resource/*", "arn:aws:cloudformation:*:*:stackset-target/AWS-QuickSetup-*:*" ] }, { "Sid": "ValidationReadonlyActions", "Effect": "Allow", "Action": [ "iam:ListRoles", "iam:GetRole" ], "Resource": "*" }, { "Sid": "IamRolesMgmt", "Effect": "Allow", "Action": [ "iam:CreateRole", "iam:DeleteRole", "iam:GetRole", "iam:AttachRolePolicy", "iam:DetachRolePolicy", "iam:GetRolePolicy", "iam:ListRolePolicies" ], "Resource": [ "arn:aws:iam::*:role/AWS-QuickSetup-*", "arn:aws:iam::*:role/service-role/AWS-QuickSetup-*" ] }, { "Sid": "IamPassRole", "Effect": "Allow", "Action": [ "iam:PassRole" ], "Resource": [ "arn:aws:iam::*:role/AWS-QuickSetup-*", "arn:aws:iam::*:role/service-role/AWS-QuickSetup-*" ], "Condition": { "StringEquals": { "iam:PassedToService": [ "ssm.amazonaws.com", "ssm-quicksetup.amazonaws.com", "cloudformation.amazonaws.com" ] } } }, { "Sid": "IamRolesPoliciesMgmt", "Effect": "Allow", "Action": [ "iam:AttachRolePolicy", "iam:DetachRolePolicy" ], "Resource": [ "arn:aws:iam::*:role/AWS-QuickSetup-*", "arn:aws:iam::*:role/service-role/AWS-QuickSetup-*" ], "Condition": { "ArnEquals": { "iam:PolicyARN": [ "arn:aws:iam::aws:policy/AWSSystemsManagerEnableExplorerExecutionPolicy", "arn:aws:iam::aws:policy/AWSQuickSetupSSMDeploymentRolePolicy" ] } } }, { "Sid": "CfnStackSetsSLR", "Effect": "Allow", "Action": [ "iam:CreateServiceLinkedRole" ], "Resource": [ "arn:aws:iam::*:role/aws-service-role/stacksets.cloudformation.amazonaws.com/AWSServiceRoleForCloudFormationStackSetsOrgAdmin", "arn:aws:iam::*:role/aws-service-role/ssm.amazonaws.com/AWSServiceRoleForAmazonSSM", "arn:aws:iam::*:role/aws-service-role/accountdiscovery.ssm.amazonaws.com/AWSServiceRoleForAmazonSSM_AccountDiscovery", "arn:aws:iam::*:role/aws-service-role/ssm-quicksetup.amazonaws.com/AWSServiceRoleForSSMQuickSetup", "arn:aws:iam::*:role/aws-service-role/resource-explorer-2.amazonaws.com/AWSServiceRoleForResourceExplorer" ] } ] } EOF # Create the IAM policy POLICY_OUTPUT=$(log_cmd "aws iam create-policy --policy-name SSMOnboardingPolicy --policy-document file://ssm-onboarding-policy.json --output json") POLICY_STATUS=$? check_error "$POLICY_OUTPUT" $POLICY_STATUS "Failed to create IAM policy" # Extract the policy ARN POLICY_ARN=$(echo "$POLICY_OUTPUT" | grep -o 'arn:aws:iam::[0-9]*:policy/SSMOnboardingPolicy') if [[ -z "$POLICY_ARN" ]]; then echo "ERROR: Failed to extract policy ARN" | tee -a "$LOG_FILE" exit 1 fi # Track the created policy track_resource "IAM_POLICY" "$POLICY_ARN" echo "Created policy: $POLICY_ARN" | tee -a "$LOG_FILE" # Step 2: Create and configure IAM role for Systems Manager echo "" echo "Step 2: Creating IAM role for Systems Manager..." # Get current user name USER_OUTPUT=$(log_cmd "aws sts get-caller-identity --output json") USER_STATUS=$? check_error "$USER_OUTPUT" $USER_STATUS "Failed to get caller identity" # Extract account ID ACCOUNT_ID=$(echo "$USER_OUTPUT" | grep -o '"Account": "[0-9]*"' | cut -d'"' -f4) if [[ -z "$ACCOUNT_ID" ]]; then echo "ERROR: Failed to extract account ID" | tee -a "$LOG_FILE" exit 1 fi # Generate a unique role name ROLE_NAME="SSMTutorialRole-$(openssl rand -hex 4)" # Create trust policy for the role - FIXED: Added cloudformation.amazonaws.com cat > trust-policy.json << 'EOF' { "Version":"2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "ssm.amazonaws.com", "ssm-quicksetup.amazonaws.com", "cloudformation.amazonaws.com" ] }, "Action": "sts:AssumeRole" }, { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::ACCOUNT_ID:root" }, "Action": "sts:AssumeRole" } ] } EOF # Replace ACCOUNT_ID placeholder in trust policy sed -i "s/ACCOUNT_ID/$ACCOUNT_ID/g" trust-policy.json # Create the IAM role ROLE_OUTPUT=$(log_cmd "aws iam create-role --role-name $ROLE_NAME --assume-role-policy-document file://trust-policy.json --description 'Role for Systems Manager tutorial' --output json") ROLE_STATUS=$? check_error "$ROLE_OUTPUT" $ROLE_STATUS "Failed to create IAM role" # Extract the role ARN ROLE_ARN=$(echo "$ROLE_OUTPUT" | grep -o 'arn:aws:iam::[0-9]*:role/[^"]*') if [[ -z "$ROLE_ARN" ]]; then echo "ERROR: Failed to extract role ARN" | tee -a "$LOG_FILE" cleanup_on_error exit 1 fi # Track the created role track_resource "IAM_ROLE" "$ROLE_NAME" echo "Created IAM role: $ROLE_NAME" | tee -a "$LOG_FILE" echo "Role ARN: $ROLE_ARN" | tee -a "$LOG_FILE" # Set identity variables for cleanup IDENTITY_TYPE="role" IDENTITY_NAME="$ROLE_NAME" # Attach the policy to the role ATTACH_OUTPUT=$(log_cmd "aws iam attach-role-policy --role-name $ROLE_NAME --policy-arn $POLICY_ARN") ATTACH_STATUS=$? check_error "$ATTACH_OUTPUT" $ATTACH_STATUS "Failed to attach policy to role $ROLE_NAME" echo "Policy attached to role: $ROLE_NAME" | tee -a "$LOG_FILE" # Step 3: Create Systems Manager configuration using Host Management echo "" echo "Step 3: Creating Systems Manager configuration..." # Generate a random identifier for the configuration name CONFIG_NAME="SSMSetup-$(openssl rand -hex 4)" # Create configuration file for Systems Manager setup using Host Management # Added both required parameters for single account deployment based on CloudFormation documentation cat > ssm-config.json << EOF [ { "Type": "AWSQuickSetupType-SSMHostMgmt", "LocalDeploymentAdministrationRoleArn": "$ROLE_ARN", "LocalDeploymentExecutionRoleName": "$ROLE_NAME", "Parameters": { "TargetAccounts": "$ACCOUNT_ID", "TargetRegions": "$CURRENT_REGION" } } ] EOF echo "Configuration file created:" | tee -a "$LOG_FILE" cat ssm-config.json | tee -a "$LOG_FILE" # Create the configuration manager CONFIG_OUTPUT=$(log_cmd "aws ssm-quicksetup create-configuration-manager --name \"$CONFIG_NAME\" --configuration-definitions file://ssm-config.json --region $CURRENT_REGION") CONFIG_STATUS=$? check_error "$CONFIG_OUTPUT" $CONFIG_STATUS "Failed to create Systems Manager configuration" # Extract the manager ARN MANAGER_ARN=$(echo "$CONFIG_OUTPUT" | grep -o 'arn:aws:ssm-quicksetup:[^"]*') if [[ -z "$MANAGER_ARN" ]]; then echo "ERROR: Failed to extract manager ARN" | tee -a "$LOG_FILE" exit 1 fi # Track the created configuration manager track_resource "SSM_CONFIG_MANAGER" "$MANAGER_ARN" echo "Created Systems Manager configuration: $MANAGER_ARN" | tee -a "$LOG_FILE" # Step 4: Verify the setup echo "" echo "Step 4: Verifying the setup..." # Wait for the configuration to be fully deployed echo "Waiting for the configuration to be deployed (this may take a few minutes)..." sleep 30 # Check the configuration manager status VERIFY_OUTPUT=$(log_cmd "aws ssm-quicksetup get-configuration-manager --manager-arn $MANAGER_ARN --region $CURRENT_REGION") VERIFY_STATUS=$? check_error "$VERIFY_OUTPUT" $VERIFY_STATUS "Failed to verify configuration manager" echo "Systems Manager setup completed successfully!" | tee -a "$LOG_FILE" # List the created resources echo "" echo "===========================================" echo "CREATED RESOURCES" echo "===========================================" for resource in "${CREATED_RESOURCES[@]}"; do echo "$resource" done # 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 "Cleaning up resources..." | tee -a "$LOG_FILE" cleanup_resources echo "Cleanup completed." | tee -a "$LOG_FILE" else echo "Resources will not be cleaned up. You can manually clean them up later." | tee -a "$LOG_FILE" fi echo "" echo "Script execution completed. See $LOG_FILE for details." # Clean up temporary files rm -f ssm-onboarding-policy.json trust-policy.json ssm-config.json 2>/dev/null || true-
For API details, see the following topics in AWS CLI Command Reference.
-