cli

Use CLI s3api to Retrieve Metadata of Amazon S3 Object

In Amazon S3 CLI, there are only a handful commands:

1
cp ls mb mv rb rm sync website

We can use cp command to retrieve S3 object:

1
$ aws s3 cp s3://mybucket/myfile.json myfile.json

But if only the metadata of the object, such as ETag or Content-Type is needed, the S3 CLI does not have any command to do that.

Now enter S3API CLI. Not only the CLI commands can retrieve S3 objects, but also associated metadata. For example, retrieve S3 object similar to aws s3 cp:

1
$ aws s3api get-object --bucket mybucket --key myfile.json

Just need the metadata of the object, use head-object, which retrieves metadata without the object itself, as HTTP HEAD method:

Revoke and Grant Public IP Addresses to Amazon EC2 Instances Via AWS Command Line Interface (CLI)

If you work from place to place, such as from one coffee shop to another, and you need access to your Amazon EC2 instances, but you don’t want to allow traffics from all IP addresses. You can use the EC2 Security Groups to allow the IP addresses from those locations. But once you move on to a different location, you want to delete the IP address from the previous location. The process to do these manually and over and over again quickly becomes cumbersome. Here is a command line method that quickly removes all other locations and allows only the traffic from your current location.

The steps are:

  1. Revoke all existing sources to a particular port
  2. Grant access to the port only from the current IP address

Assume the following:

  • Profile: default
  • Security group: mygroup
  • Protocol: tcp
  • Port: 22

First, revoke access to the port from all IP addresses:

1
2
3
4
5
6
7
8
9
10
11
$ aws ec2 describe-security-groups \
--profile default \
--group-names mygroup \
--query 'SecurityGroups[0].IpPermissions[?ToPort==`22`].IpRanges[].CidrIp' | \
jq .[] | \
xargs -n 1 aws ec2 revoke-security-group-ingress \
--profile default \
--group-name mygroup \
--protocol tcp \
--port 22 \
--cidr

The aws ec2 describe-security-groups command before the first pipe returns JSON formatted data, filtered via JMESPath query, which is supported by AWS CLI, for example:

1
2
3
4
[
"XXX.XXX.XXX.XXX/32",
"XXX.XXX.XXX.XXX/32"
]

jq command simply converts an array of JSON to line by line strings, which xarg takes in, loops through and deletes one IP address at a time.

After this step, all IP addresses originally allowed are all revoked. Next step is to grant access to the port from a single IP address:

Delete All Messages in an Amazon SQS Queue via AWS CLI

Amazon SQS or Simple Queue Service is a fast, reliable, scalable, fully managed message queuing service. There is also AWS CLI or Command Line Interface available to use with the service.

If you have a lot of messages in a queue, this command will show the approximate number:

1
2
3
$ aws sqs get-queue-attributes \
--queue-url $url \
--attribute-names ApproximateNumberOfMessages

Where $url is the URL to the Amazon SQS queue.

There is no command to delete all messages yet, but you can chain a few commands together to make it work:

Command Line JSON Processing

What is the best command line tool to process JSON?

Hmm… Okay, let’s try different command line JSON processing tools with the following use case to decide which one is the best to use.

Here is the use case: JSON | filter | shell. A program outputs JSON data, pipes into a JSON command line processing tool to filter data, and then send to a shell command to do more work.

Here is a snippet of sample JSON data:

1
2
3
4
5
6
7
8
9
10
11
12
[
{
"id": 1,
"username": "foo",
"name": "Foo Foo"
},
{
"id": 2,
"username": "bar",
"name": "Bar Bar"
}
]

Or in one-liner data.json:

1
[{"id":1,"username":"foo","name":"Foo Foo"},{"id":2,"username":"bar","name":"Bar Bar"}]

The command line JSON processor should filter each element of the array and convert it into its own line:

1
2
{"name":"Foo Foo"}
{"name":"Bar Bar"}

The result will be piped as the input line by line into a shell script echo.bash:

1
2
3
4
5
#!/usr/bin/env bash
while read line; do
echo "ECHO: '"$line"'"
done

The final output should be:

1
2
ECHO: '{"name":"Foo Foo"}'
ECHO: '{"name":"Bar Bar"}'

Custom Solution

Before start looking for existing tools, let’s see how difficult it is to write a custom solution.

1
2
3
4
5
6
7
8
9
10
11
12
13
// Filter and convert array element into its own line.
var rl = require('readline').createInterface({
input : process.stdin,
output: process.stdout,
});
rl.on('line', function (line) {
JSON.parse(line).forEach(function (item) {
console.log('{"name":"' + item.name + '"}');
});
}).on('close', function () {
// Shh...
});

Perform a test run:

1
2
3
$ cat data.json | node filter.js | bash echo.bash
ECHO: '{"name":"Foo Foo"}'
ECHO: '{"name":"Bar Bar"}'

Well, it works. In essence, we are writing a simple JSON parser. Unless you want to keep the footprint small, you don’t want to write another JSON parser. And why bother to reinvent the wheel? Let’s start look at the existing solutions.

Node Modules

Let’s start with the tools from NPM registry:

$ npm search json command

Here are a few candidates that appears to be matching from the description:

  • jku - Jku is a command-line tool to filter and/or modifiy a JSON stream. It is heavily inspired by jq. (2 stars and not active, last update 8 months ago).
  • json or json-command - JSON command line procesing toolkit. (122 stars and 14 forks, last update 9 months ago)
  • jutil - Command-line utilities for manipulating JSON. (88 stars and 2 forks, last update more than 2 years ago)

Not a lot of choice, and modules are not active. This might be that because there is already a really good solution, jq, which has 2493 stars and 145 forks, and the last update was 6 days ago.

jq

jq is like sed for JSON data - you can use it to slice and filter and map and transform structured data with the same ease that sed, awk, grep and friends let you play with text. - jq

Instead of NPM install, do:

$ sudo apt-get -y install jq

Since we don’t need color or prettified, just line by line. So, here is the command chain:

1
2
3
$ cat data.json | jq -c -M '.[] | {name}' | bash echo.bash
ECHO: '{"name":"Foo Foo"}'
ECHO: '{"name":"Bar Bar"}'

jq can do much more than just the example just shown. It has zero runtime dependencies, and flexible to deal with not just array but object as well.

Conclusion

jq is clearly the winner here, with the least dependency, the most functionality and more popularity, as well as a comprehensive documentation.

Amazon AWS Command Line Interface (CLI)

This is a brief guide for installing AWS Command Line Interface (CLI) on Ubuntu Linux.

The AWS Command Line Interface (CLI)] is a unified tool to manage your AWS services. With just one tool to download and configure, you can control multiple AWS services from the command line and automate them through scripts. [2]

The point here is unified, one tool to run all Amazon AWS services.

Install

The installation procedure applies to Ubuntu Linux with Zsh and Bash.

Install pip, a Python package manager:

$ sudo apt-get install python-pip

Install awscli:

$ sudo pip install awscli

Install autocompletion:

$ which aws_zsh_completer.sh
/usr/local/bin/aws_zsh_completer.sh
$ source aws_zsh_completer.sh

Add this line to ~/.zshrc as well.

Or for Bash ~/.bashrc:

$ echo '\n# Enable AWS CLI autocompletion' >> ~/.bashrc
$ echo 'complete -C aws_completer aws' >> ~/.bashrc
$ source ~/.bashrc

Test installation:

$ which aws
/usr/local/bin/aws
$ aws help

Test autocompletion:

$ aws <tab><tab>

You should see a list of all available AWS commands.

Usage

Before using aws-cli, you need to tell it about your AWS credentials. There are three ways to specify AWS credentials:

  1. Environment variables
  2. Config file
  3. IAM Role

Using config file is preferred, which is a simple ini file format to be stored in ~/.aws/config. A soft link can be used to link it or just tell awscli where to find it:

$ export AWS_CONFIG_FILE=/path/to/config_file

It is better to use IAM roles with any of the AWS services:

The final option for credentials is highly recommended if you are using aws-cli on an EC2 instance. IAM Roles are a great way to have credentials installed automatically on your instance. If you are using IAM Roles, aws-cli will find them and use them automatically. [4]

The default output is in JSON format. Other formats are tab-delimited text and ASCII-formatted table. For example, using --query filter and table output:

$ aws ec2 describe-instances --query 'Reservations[*].Instances[*].{ \
  ID:InstanceId, TYPE:InstanceType, ZONE:Placement.AvailabilityZone, \
  SECURITY:SecurityGroups[0].GroupId, KEY:KeyName, VPC:VpcId, \
  STATE:State.Name}' --output table

This will print a nice looking table of all EC2 instances.

The command line options also accept JSON format. But when passing in large blocks of data, referring a JSON file is much easier. Both local file and remote URL can be used.

Upgrade

Check the installed and the latest versions:

$ pip search awscli

Upgrade AWS CLI to the latest version:

$ sudo pip install --upgrade awscli

References

  1. AWS Command Line Interface
  2. User Guide
  3. Reference
  4. GitHub repository

Amazon Route 53 via Command Line

Retrieve a list of hosted zones:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ aws route53 list-hosted-zones
{
"HostedZones": [
{
"ResourceRecordSetCount": 4,
"CallerReference": "12345678-ABCD-EFGH-IJKL-ABCDEFGHIJKL",
"Config": {},
"Id": "/hostedzone/1234567890ABC",
"Name": "realguess.net."
}
],
"IsTruncated": false,
"MaxItems": "100"
}

Get a single hosted zone with delegation set (four Route 53 name servers that were assigned to the hosted zone):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ aws route53 get-hosted-zone --id 1234567890ABC
{
"HostedZone": {
"ResourceRecordSetCount": 4,
"CallerReference": "12345678-ABCD-EFGH-IJKL-ABCDEFGHIJKL",
"Config": {},
"Id": "/hostedzone/1234567890ABC",
"Name": "realguess.net."
},
"DelegationSet": {
"NameServers": [
"ns-1727.awsdns-23.co.uk",
"ns-1312.awsdns-36.org",
"ns-402.awsdns-50.com",
"ns-587.awsdns-09.net"
]
}
}

List all resource record sets in a hosted zone:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
$ aws route53 list-resource-record-sets --hosted-zone-id 1234567890ABC
{
"IsTruncated": false,
"ResourceRecordSets": [
{
"ResourceRecords": [
{
"Value": "192.168.153.123"
}
],
"Type": "A",
"Name": "realguess.net.",
"TTL": 172800
},
{
"ResourceRecords": [
{
"Value": "ns-1727.awsdns-23.co.uk."
},
{
"Value": "ns-1312.awsdns-36.org."
},
{
"Value": "ns-402.awsdns-50.com."
},
{
"Value": "ns-587.awsdns-09.net."
}
],
"Type": "NS",
"Name": "realguess.net.",
"TTL": 172800
},
{
"ResourceRecords": [
{
"Value": "ns-1727.awsdns-23.co.uk. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400"
}
],
"Type": "SOA",
"Name": "realguess.net.",
"TTL": 900
},
{
"ResourceRecords": [
{
"Value": "192.168.153.123"
}
],
"Type": "A",
"Name": "www.realguess.net.",
"TTL": 86400
}
],
"MaxItems": "100"
}

Retrieve a single record set:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ aws route53 list-resource-record-sets --hosted-zone-id 1234567890ABC \
--start-record-name www.realguess.net --start-record-type A --max-items 1
{
"IsTruncated": false,
"ResourceRecordSets": [
{
"ResourceRecords": [
{
"Value": "192.168.153.123"
}
],
"Type": "A",
"Name": "www.realguess.net.",
"TTL": 86400
}
],
"MaxItems": "1"
}

In order to create a new record set, first create a JSON file to describe the new record:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"Comment": "A new record set for the zone.",
"Changes": [
{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "api.realguess.net.",
"Type": "CNAME",
"TTL": 60,
"ResourceRecords": [
{
"Value": "www.realguess.net"
}
]
}
}
]
}

Add a new record set (note that the JSON file must use file:// starting path):

1
2
3
4
5
6
7
8
9
10
$ aws route53 change-resource-record-sets --hosted-zone-id 1234567890ABC \
--change-batch file:///path/to/record.json
{
"ChangeInfo": {
"Status": "PENDING",
"Comment": "A new record set for the zone.",
"SubmittedAt": "2013-12-06T00:00:00.000Z",
"Id": "/change/CHANGEID123"
}
}

The status of adding the new record is currently pending. Poll the server to get the updated status:

1
2
3
4
5
6
7
8
9
$ aws route53 get-change --id CHANGEID123
{
"ChangeInfo": {
"Status": "INSYNC",
"Comment": "A new record set for the zone.",
"SubmittedAt": "2013-12-06T00:00:00.000Z",
"Id": "/change/CHANGEID123"
}
}

A new record has been created and has been propagated to all hosts.