Error trying to get command status with aws cli - bash

I seem to keep getting an error whenever I try to use bash to automate getting the status of a job.
My current bash script currently looks like this:
#!/bin/bash
aws ec2 start-instances --instance-ids=$1;
start=$(aws ec2 describe-instance-status --instance-id $1)
status=$(echo $start | jq '.InstanceStatuses[0].InstanceState.Name')
#wait for ec2 instance to start running before launching command
while [ "$status" != "\"running\"" ]
do
start=$(aws ec2 describe-instance-status --instance-id $1)
status=$(echo $star | jq '.InstanceStatueses[0].InstanceState.Name')
done
sh_command_id=$(aws ssm send-command --instance-ids=$1 --document-name "AWS-RunShellScript" --parameters 'commands=["echo Helloworld","sleep 60"]');
command_id=$(echo $sh_command_id | jq '.Command.CommandId')
full_status=$(aws ssm list-commands --command-id $command_id)
echo $command_id;
aws ec2 stop-instances --instance-ids=$1;
When the script gets to aws ssm list-commands --command-id $command_id I get this error.
An error occurred (ValidationException) when calling the ListCommands operation: 2
validation errors detected: Value '"67fb9aed-00bf-4741-ae1a-736ddbfba498"' at 'commandId'
failed to satisfy constraint: Member must satisfy regular expression pattern: ^[A-Fa-
f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$.; Value
'"67fb9aed-00bf-4741-ae1a-736ddbfba498"' at 'commandId' failed to satisfy constraint:
Member must have length less than or equal to 36.
When running everything individually in terminal I get the same error. However, I do not get an error when I mannually type in the commandId as so: full_status=$(aws ssm list-commands --command-id 67fb9aed-00bf-4741-ae1a-736ddbfba498).
Is there some aws formatting I am missing here?

You might be able to avoid the use of jq by using the aws cli built in --query 'your.json.query' to specify your JSON query and then the --output text to return plain text. It has been a while since I checked so your mileage may vary.
I was able to verify that the following works for checking an ec2 is running:
check_instance() {
local instance_id="${1}"
local status="_"
while [ "${status}" != "running" ] ; do
status=$(aws ec2 describe-instance-status \
--instance-ids ${instance_id} \
--query "InstanceStatuses[*].InstanceState.Name" \
--output text)
done
echo "${instance_id} is running"
}

Related

AWS CLI: How to use variable to filter EFS

I want to use the value of the DOMAIN_ID variable to filter the EFS to get a FileSystemId. I used the commands below. The first command works and it stores the domain ID. The second one returns an empty list, even though the DOMAIN_ID variable is present.
DOMAIN_ID=$(aws sagemaker list-domains --query 'Domains[0].DomainId')
aws efs describe-file-systems --query 'FileSystems[?CreationToken==`$DOMAIN_ID`].FileSystemId'
Output:
[]
Expected output:
<Some EFS identifier>
This works (escaping backticks) -
aws efs describe-file-systems --query "FileSystems[?CreationToken==\`$DOMAIN_ID\`].FileSystemId"
You can also use describe-domain command instead -
$ DOMAIN_ID=$(aws sagemaker list-domains --query 'Domains[0].DomainId' | tr -d '"')
$ aws sagemaker describe-domain --domain-id $DOMAIN_ID --query 'HomeEfsFileSystemId'

Create EC2 instance and get public IP when available

I have a monotonous work of creating an ec2 instance from an AMI and then ssh into it and run a crypto bot to get a sub domain SSL certificates for it and then run some frontend and backend onto it.
I would like to automate this...
I am able to create the EC2 instance
aws ec2 run-instances --image-id ami-0bef9cfcxxxxxx --count 1 --instance-type t2.medium --key-name MyKey --security-group-ids SG-1 --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=SubDomain}]'
but how do I wait till the instance is UP and get the public IP for the next steps?
Any suggestions would help,
Thanks!
Assuming you know the target instance id, you can query your instance and get the current state, instanceId, name (assuming you tagged them with a name) and public IP via something like:
aws ec2 describe-instances \
--instance-ids YOUR_TARGET_INSTANCE_ID \
--query "Reservations[*].Instances[*].[State.Name, InstanceId, Tags[?Key==\`Name\`]|[0].Value, PublicIpAddress]" \
--output text
You could run this query in a loop until the status comes back as running and then capture the public ip address:
while :
do
output=$(aws ec2 describe-instances \
--instance-ids YOUR_TARGET_INSTANCE_ID \
--query "Reservations[*].Instances[*].[State.Name, InstanceId, \
Tags[?Key==\`Name\`]|[0].Value, PublicIpAddress]" \
--output text)
if [[ "${output}" == running* ]] ; then
cut -d $'\t' -f4- <<<"${output}"
break
fi
done
Note:
The aws ec2 queries are all using the default AWS profile. You can specify a different profile via --profile your_target_profile_name if needed.
If you do not know your target instance id, you can remove the --instance-id portion of the aws ec2 query and list all of your instances (in your default region). You could choose the required instance id from that list.

How to find out which ECS cluster is associated to an ALB

We run an ECS cluster behind an ELB (ALB, to be specific).
I have a process that allows me to find out which ECS cluster is associated with the ALB by querying the ALB and tracing the results back through the target group and then instances:
Here is the bash script:
ELB_NAME=$(aws route53 list-resource-record-sets --hosted-zone-id <Zone-ID> | jq -r --arg URL "$URL"'.ResourceRecordSets[]|select(.Name==$URL)|.AliasTarget.DNSName')
ELB_NAME=$(echo $ELB_NAME | cut -f 2- -d "." | rev | cut -f 2- -d "." | rev)
ELB_ARN=$(aws elbv2 describe-load-balancers | jq -r --arg ELB_NAME "$ELB_NAME" '.LoadBalancers[]|select((.DNSName|ascii_downcase)==$ELB_NAME)|.LoadBalancerArn')
TG_ARNS=$(aws elbv2 describe-target-groups | jq -r --arg ELB_ARN "$ELB_ARN" '.TargetGroups[]|select(.LoadBalancerArns[]==$ELB_ARN)|.TG_ARN=$(echo $TG_ARNS | cut -f 1 -d " ")
INSTANCE_ID=$(aws elbv2 describe-target-health --target-group-arn $TG_ARN | jq -r '.TargetHealthDescriptions[].Target.Id' | head -n 1)
CLUSTER=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID | jq -r '.Reservations[].Instances[].Tags[]|select(.Key=="aws:cloudformation:stack-name")|.Value' | cut -f 2 -d "-")
The problem I have is that when there are no running instances associated with the ECS cluster, I can no longer query them for the the tag that returns the Cloudformation stack name, the request for the targets from the target group is empty.
How can I use the AWS API so that I can determine which ECS cluster the ALB would target if it had running instances?
It's not really clear what you're asking for, or indeed the purpose you are trying to achieve, but the following should set you on the right track.
An ECS "cluster" is really just an Amazon service, when you create a cluster nothing is really provisioned. You can think of an empty cluster as a record or a placeholder in the ECS service.
In order to do anything with a cluster, it needs instances. When you boot an EC2 machine from a supported AMI, appropriate IAM role and the cluster name written to a config file, the instance will join the cluster. (If you create a cluster via the AWS console, a CloudFormation template is created that handles the provisioning and orchestration of these steps.) The ECS cluster management can then schedule tasks and services onto that instance as you have defined in the ECS service.
Without any instances, there can be no listening containers, therefore there can be no target groups in your ALB that route to anything. So it is not possible to get from the ELB to the cluster... as you have asked when there are no running instances.
You might find the following commands are a better way of determining whether or not you have a running cluster.
First, use the list-clusters command to show which clusters are available:
aws ecs list-clusters
{
"clusterArns": [
"arn:aws:ecs:eu-west-1:XXXXXXXXX:cluster/your_cluster"
]
}
Then use the output from that to show if there are any EC2 instances registered to the cluster:
aws ecs describe-clusters --clusters your_cluster
{
"clusters": [
{
"status": "ACTIVE",
"statistics": [],
"clusterName": "your_cluster",
"registeredContainerInstancesCount": 1,
"pendingTasksCount": 0,
"runningTasksCount": 0,
"activeServicesCount": 0,
"clusterArn": "arn:aws:ecs:eu-west-1:XXXXXXXXX:cluster/your_cluster"
}
],
"failures": []
}
Note the registeredContainerInstancesCount property shows the number of running instances. I assume you have your ECS services set to register tasks (containers) with the ALB, so when the count is greater than 0, this will be possible.
So, querying that property should tell you if your cluster is "on" or not:
if [[ $(aws ecs describe-clusters --clusters your_cluster | jq -r '.clusters[].registeredContainerInstancesCount') -gt 0 ]] ; then
echo "cluster is on"
else
echo "cluster is off"
fi

How to run shell script on multiple EC2 instances using AWS CLI?

I'm trying to do a simple AWS CLI command that can run a shell command to multiple instances.
I know first I need to get the list of instances ids:
aws ec2 describe-instances --filter "Name=tag:Group,Values=Development" --query 'Reservations[].Instances[].[InstanceId]' --output text
I then will have to assign them to an array. Then loop through each instance id and send the command.
Do we have an option for aws to send a shell command to an instance with a specific id?
Something like this:
aws ssm send-command --instance-ids "i-xxxxxxxxxxxxxxxx" --document-name "shellscript"
I keep getting this error:
An error occurred (InvalidInstanceId) when calling the SendCommand operation:
I've made sure that the SSM agent is running on that specific instance and made sure everything is correct according to these docs pages.
You can use ssm send-command.
A sample command to see ip address of instance:
aws ssm send-command --instance-ids "your id's" --document-name "AWS-RunShellScript" --comment "IP config" --parameters "commands=ifconfig" --output text
Modify command as per your needs.
In case you've got the error, this can happen when you don't have SSM setup on the instance you're trying to access. For a list of instances where you can run SSM commands, run:
aws ssm describe-instance-information --output text
See: InvalidInstanceId: An error occurred (InvalidInstanceId) when calling the SendCommand operation.
I was able to create a script with Python using Boto3.
import boto3
import botocore
import paramiko
tagkey = 'Environment'
tagvalue = 'DEV'
# list_instances functions returns a list of ip addresses containing a set of tags
def list_instances(tagkey, tagvalue):
ec2client = boto3.client('ec2')
response = ec2client.describe_instances(
Filters=[
{
'Name': 'tag:'+tagkey,
'Values': [tagvalue]
}
]
)
instancelist = []
for reservation in (response["Reservations"]):
for instance in reservation["Instances"]:
instancelist.append(instance["PublicDnsName"])
return instancelist
# Results of the function get stored in a list.
list = list_instances(tagkey, tagvalue)
key = paramiko.RSAKey.from_private_key_file("/home/ec2-user/key.pem")
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# Looping through all the instannces in the list
for instance_ip in list[:]:
# Connect/ssh to an instance
try:
# Here 'ec2-user' is user name and 'instance_ip' is public IP of EC2
client.connect(hostname=instance_ip, username="ec2-user", pkey=key)
# Execute a command after connecting/ssh to an instance
stdin, stdout, stderr = client.exec_command("touch test")
# close the client connection once the job is done
print "Command sent:",instance_ip
except Exception, e:
print e

How to add tags to Buildbot EC2LatentBuildSlave

I am using EC2LatentBuildSlave to sawn EC2 instances running the build slaves. I would like to tag the slaves so a tag is visible in the EC2 dashboard Tags tab.
I am passing
tags={'Key':'BuildbotType', 'Value':'slaveName'}
but I can’t see the tags in the spawned EC2 instance.
Have I misunderstood this parameter?
Thank for your help.
This seems like a bug in buildbot version 0.8.12. Unfortunately we can't upgrade at this point. Instead I installed a init script (service) on the base AMI that reads user_data and tags its self. I pass the tag details using the user_data field in EC2LatentBuildSlave.
login_aws() {
# Some code to configer aws access keys.
}
get_userdata() {
AWS_USERDATA=$(curl -s http://169.254.169.254/latest/user-data)
echo $AWS_USERDATA
}
tag_self_for_identification() {
login_aws
INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
if [ -z "$INSTANCE_ID" ]; then
echo "Error unable to obtain instance id"
return 0
fi
USERDATA=get_userdata
set $USERDATA
NAME="$2 $3"
if [ ! -z "$NAME" ]; then
echo "aws ec2 create-tags --resources $INSTANCE_ID --tags Key=Name,Value=$NAME"
aws ec2 create-tags --resources $INSTANCE_ID --tags Key=Name,Value="$NAME" \
Key=Buildbot,Value=Latentslave
fi
}
tag_self_for_identification
This is an extremely over engineered solution. However I need tags to work for some lambda functions to be able to identify these latentlsaves

Resources