Recently I was working on a project using AWS CLI and happened to come across some cool jq techniques of manipulating JSON. I will describe what my use-case was, but similar techniques can be applied whenever JSON is involved.
jq is a lightweight JSON processor written in C. You can find more information about the tool and how to install it in their official documentation. It’s quite powerful and is capable of doing quite a lot – i.e parsing, manipulating and processing json files. Now let’s see how it was kinda easy to get my work done using jq.
The Use Case
We use AWS Image Builder to build EC2 AMI’s and this requirement was to explicitly update the ami_name field of an existing Image Builder Distribution Configuration whenever a pipeline is triggered. There are some options in the distribution configuration to specify what we want to name the output AMI, but we wanted it to be very custom (with the base ami version) and dynamic. I will not be talking about how Image Builder works as its a separate topic in itself, however you can read about it here.
The Problem
So to achieve this (using AWS CLI), we just needed to run 2 AWS CLI commands:
- Get the existing distribution configuration:
aws imagebuilder get-distribution-configuration --output json --distribution-configuration-arn $distribution_config_arn
The above will return a json response of the existing distribution configuration like below:
{
"requestId": "42b6bcf5-9505-4c42-ad38-7efd8177f2ac",
"distributionConfiguration": {
"arn": "arn:aws:imagebuilder:us-east-1:123456789012:distribution-configuration/amazon-eks-node-latest-pipeline-distribution-config",
"name": "amazon-eks-node-latest-pipeline-distribution-config",
"description": "amazon-eks-node-latest image builder pipeline",
"distributions": [
{
"region": "us-east-1",
"amiDistributionConfiguration": {
"name": "amazon-eks-node-latest-golden-ami-{{ imagebuilder:buildDate }}",
"description": "amazon-eks-node-latest image builder pipeline",
"amiTags": {
"family": "amazon-eks-node-latest",
"Name": "amazon-eks-node-latest-golden-ami-{{ imagebuilder:buildDate }}"
},
"launchPermission": {}
}
}
],
"dateCreated": "2024-03-06T14:59:37.286Z",
"tags": {
"owner": "platform",
"project": "image-builder",
"env": "prod",
"family": "amazon-eks-node-latest",
"managedby": "terraform"
}
}
}
2. Update only the amiDistributionConfiguration.name field of this distribution configuration.
Now it is not possible to just update one field of the distribution configuration. If you see distributions is a list in the response and to update it we need to get the full response, change the fields we want and then pass this json as an input to the update-distribution-configuration CLI command.
But wait, if you see the input json syntax that update-distribution-configuration expects (shown below), it does not exactly match the response got from the get-distribution-configuration command.
{
"distributionConfigurationArn": "arn:aws:imagebuilder:us-east-1:123456789012:distribution-configuration/amazon-eks-node-latest-pipeline-distribution-config",
"description": "amazon-eks-node-latest image builder pipeline",
"distributions": [
{
"region": "us-east-1",
"amiDistributionConfiguration": {
"name": "Name {{imagebuilder:buildDate}}",
"description": "An example image name with parameter references"
}
},
{
"region": "eu-east-2",
"amiDistributionConfiguration": {
"name": "My {{imagebuilder:buildVersion}} image {{imagebuilder:buildDate}}"
}
}
]
}
This problem can be true for a variety of other scenarios (AWS or non AWS). Comes jq to the rescue! With jq, it’s just a one line command to process the json and transform it the way we want.
The Solution
The solution is, to transform the response from get-distribution-configuration to the required syntax using jq and then passing it to the update-distribution-configuration command, which can be effectively done with the below commands:
# 1. Get the existing distribution configuration
distribution_config=`aws imagebuilder get-distribution-configuration --output json --distribution-configuration-arn $arn`
# 2. Update the JSON syntax of the response to match the target commands requirements
updated_distribution_config=$(echo "$distribution_config" | jq '{ distributionConfigurationArn: .distributionConfiguration.arn, description: .distributionConfiguration.description, distributions: [.distributionConfiguration.distributions[] | .amiDistributionConfiguration.name = UPDATED_NAME]}')
aws imagebuilder update-distribution-configuration --cli-input-json $updated_distribution_config
The transformation happens in the 2nd command. I have split the command into new-lines to make it clearer. Let’s see how it works:
echo "$distribution_config" |
jq '{
distributionConfigurationArn: .distributionConfiguration.arn,
description: .distributionConfiguration.description,
distributions: [.distributionConfiguration.distributions[] |.amiDistributionConfiguration.name = UPDATED_NAME]
}'
The above filter tells jq to create a JSON object containing:
- A
distributionConfigurationArnattribute containing the value of.distributionConfiguration.arn - A
descriptionattribute containing the value of.distributionConfiguration.description - A
distributionsattribute containing a list of distributions from.distributionConfiguration.distributions[] - Also we update all
.amiDistributionConfiguration.namefields from this list of distributions to our desired value (UPDATED_NAME here) for all occurrences.
As you see, the new json structure is created and existing json is parsed to get the required fields, manipulated to replace the fields items as per requirement in this new structure simultaneously. This ends up creating a json like below, which is what the update-distribution-configuration command expects:
{
"distributionConfigurationArn": "arn:aws:imagebuilder:us-east-1:123456789012:distribution-configuration/amazon-eks-node-latest-pipeline-distribution-config",
"description": "amazon-eks-node-latest image builder pipeline",
"distributions": [
{
"region": "us-east-1",
"amiDistributionConfiguration": {
"name": "UPDATED_NAME" }}",
"description": "amazon-eks-node-latest image builder pipeline",
"amiTags": {
"family": "amazon-eks-node-latest",
"Name": "amazon-eks-node-latest-golden-ami-{{ imagebuilder:buildDate }}"
},
"launchPermission": {}
}
}
]
}
Though this is just a simple example, jq as you see is quite powerful. 🙂