<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-03-02T12:00:44+00:00</updated><id>/feed.xml</id><title type="html">Deplication</title><subtitle>Simpler is better.</subtitle><author><name>Craig Watcham</name><email>craig@deplication.net</email></author><entry><title type="html">Installing Amazon Workspaces on Ubuntu 24.04 (noble)</title><link href="/2025/03/02/installing-amazon-workspaces-ubuntu-24.html" rel="alternate" type="text/html" title="Installing Amazon Workspaces on Ubuntu 24.04 (noble)" /><published>2025-03-02T11:37:00+00:00</published><updated>2025-03-02T11:37:00+00:00</updated><id>/2025/03/02/installing-amazon-workspaces-ubuntu-24</id><content type="html" xml:base="/2025/03/02/installing-amazon-workspaces-ubuntu-24.html"><![CDATA[<p>There is currently no Ubuntu 24.04 client for Amazon Workspaces, fortunately the 22.04 client can be installed via apt with only a minor tweak.</p>

<p>Following the Linux install instructions <a href="https://clients.amazonworkspaces.com/linux-install">here</a> will lead to a signature error when performing ‘apt update’:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Failed to fetch https://d3nt0h4h6pmmc4.cloudfront.net/ubuntu/dists/jammy/InRelease  The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 04B0588859EF5026
</code></pre></div></div>
<p><br />
The reason for this is the key from Amazon is not in the binary format apt is expecting. Changing the key retrieval command from</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget -q -O - https://workspaces-client-linux-public-key.s3-us-west-2.amazonaws.com/ADB332E7.asc | sudo tee /etc/apt/trusted.gpg.d/amazon-workspaces-clients.gpg &gt; /dev/null
</code></pre></div></div>
<p>To:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget -q -O - https://workspaces-client-linux-public-key.s3-us-west-2.amazonaws.com/ADB332E7.asc | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/amazon-workspaces-clients.gpg
</code></pre></div></div>
<p>Writes the key in the expected format and allows apt to verify the source signatures and install the package correctly. Note that the 22.04 client only supports DCV, you will need to modify your Workspace to switch to DCV if it is provisioned as PCoIP.</p>]]></content><author><name>Craig Watcham</name></author><category term="Amazon Workspaces" /><category term="gpg" /><summary type="html"><![CDATA[There is currently no Ubuntu 24.04 client for Amazon Workspaces, fortunately the 22.04 client can be installed via apt with only a minor tweak.]]></summary></entry><entry><title type="html">Retrieving EC2 instance profile credentials with IMDSv2</title><link href="/2025/03/01/retrieving-ec2-profile-credentials-imdsv2.html" rel="alternate" type="text/html" title="Retrieving EC2 instance profile credentials with IMDSv2" /><published>2025-03-01T09:13:00+00:00</published><updated>2025-03-01T09:13:00+00:00</updated><id>/2025/03/01/retrieving-ec2-profile-credentials-imdsv2</id><content type="html" xml:base="/2025/03/01/retrieving-ec2-profile-credentials-imdsv2.html"><![CDATA[<p>Quick example of fetching EC2 instance profile credentials using IMDSv2.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TOKEN=`curl --no-progress-meter -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
curl --no-progress-meter -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance
</code></pre></div></div>
<p><br />
The response should look something like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
  "Code" : "Success",
  "LastUpdated" : "2025-02-21T12:13:35Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASIAZKEYKEYKEY",
  "SecretAccessKey" : "SECRETSECRETSECRET",
  "Token" : "AVERYLONGTOKENSTRING",
  "Expiration" : "2025-02-21T18:37:11Z"
}
</code></pre></div></div>
<p><br />
To extract the credentials into environment variables for use with the AWS CLI:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREDS=`curl --no-progress-meter -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance`
export AWS_ACCESS_KEY_ID=`echo $CREDS | jq -r .AccessKeyId`
export AWS_SECRET_ACCESS_KEY=`echo $CREDS | jq -r .SecretAccessKey`
export AWS_SESSION_TOKEN=`echo $CREDS | jq -r .Token`
</code></pre></div></div>
<p><br />
After which you can use the CLI as usual until the credentials expire and are rotated.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws sts get-caller-identity
{
    "UserId": "112233445566:aws:ec2-instance:i-1122334455667788",
    "Account": "112233445566",
    "Arn": "arn:aws:sts::112233445566:assumed-role/aws:ec2-instance/i-1122334455667788"
}
</code></pre></div></div>]]></content><author><name>Craig Watcham</name></author><category term="IMDSv2" /><category term="AWS CLI" /><summary type="html"><![CDATA[Quick example of fetching EC2 instance profile credentials using IMDSv2.]]></summary></entry><entry><title type="html">Reducing VPC interface endpoint costs in dev/test environments</title><link href="/2021/08/08/reducing-vpc-interface-endpoint-costs.html" rel="alternate" type="text/html" title="Reducing VPC interface endpoint costs in dev/test environments" /><published>2021-08-08T09:44:00+00:00</published><updated>2021-08-08T09:44:00+00:00</updated><id>/2021/08/08/reducing-vpc-interface-endpoint-costs</id><content type="html" xml:base="/2021/08/08/reducing-vpc-interface-endpoint-costs.html"><![CDATA[<p>Two quick tips for potentially reducing VPC interface endpoint costs.</p>

<p>Firstly an Athena query for finding unused VPC interface endpoints in cost and usage report data. This requires you to have <a href="https://docs.aws.amazon.com/cur/latest/userguide/what-is-cur.html">cost and usage reporting</a> enabled (you should already) and configured for querying in Athena, see <a href="https://docs.aws.amazon.com/cur/latest/userguide/cur-query-athena.html">this guide</a>. The query searches for all VPC interface endpoints that have not accrued any data transfer charges during the query period. VPC endpoints incur both hourly and data transfer <a href="https://aws.amazon.com/privatelink/pricing/">charges</a>, if you are not transferring any data it is very likely the endpoint is not being used and can be deleted. This will save around $7 a month ($0.01 x 24hours x 30 days) in each availability zone configured for the unused endpoint (by default all AZs). Annual cost saving potential of around $260 for each (3 AZ) endpoint removed in each account.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select 
  line_item_usage_account_id, line_item_resource_id
from 
  cur
where 
  line_item_usage_type like '%VpcEndpoint-Hour%'
  and year='2021'
  and month in ('7','07')
  and line_item_resource_id not in (
    select distinct(line_item_resource_id)
    from cur
    where line_item_usage_type like '%VpcEndpoint-Byte%'
    and year='2021'
    and month in ('7','07')
  )
group by line_item_usage_account_id, line_item_resource_id
</code></pre></div></div>

<p>The second approach is only recommended for dev/test environments where availability zone fault tolerance is not a requirement. When you create a VPC interface endpoint an ENI is created in each subnet (availability zone) unless you explicitly deselect the subnets. This is a best practice for interface endpoints as it provides continued availability in the case of an AZ failure - as long as you are using the (default) regional interface endpoint DNS name with <a href="https://docs.aws.amazon.com/vpc/latest/privatelink/vpce-interface.html#vpce-private-dns">private DNS</a>. When using private DNS the regional endpoint DNS name, ec2.eu-west-1.amazonaws.com for example, is resolved to the private IP of the VPC interface endpoint ENI rather than the public IP of the service. In the case where one AZ is not reachable the request will be routed to an interface endpoint ENI in a different AZ, allowing your client applications to continue functioning. The down side of this is that you will incur intra-region data transfer charges (of $0.01/GB) for sending requests to the ENI in a different AZ. The potential cost saving opportunity is to remove this availability mechanism to reduce the hourly interface endpoint charges.</p>

<p>As long as the data transferred to and from the interface endpoint is around 1GB/hour it is cheaper to configure the VPC interface endpoint to only be available in one (or two) availability zones. This makes sense for any API specific endpoints (EC2 for example) but may not make sense for high throughput data intensive endpoints (such as S3/Kinesis/Cloudwatch Logs). The cost and usage report data can once again be used to help identify interface endpoints where the data transfer is low enough to justify exposing the endpoint in only one availability zone. Note that the endpoint data charges are aggregated across all ENIs so the query will not correctly identify cases where traffic is not more-or-less equally balanced across AZs. The query assumes that the cost and usage report is configured for hourly granularity, the line_item_usage_amount would need to be modified for daily/monthly reports and would be less accurate. Annual cost saving potential would depend on data transfer and number of AZs provisioned but should be around $175 for API endpoints reduced from 3 AZs to 1 AZ.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select 
  line_item_usage_account_id, line_item_resource_id
from 
  cur
where 
  line_item_usage_type like '%VpcEndpoint-Byte%'
  and year='2021'
  and month in ('7','07')
  and line_item_usage_amount &lt; 1.0
  and line_item_resource_id not in (
    select distinct(line_item_resource_id)
    from cur
    where line_item_usage_type like '%VpcEndpoint-Byte%'
    and year='2021'
    and month in ('7','07')
    and line_item_usage_amount &gt; 1.0
  )
group by line_item_usage_account_id, line_item_resource_id
</code></pre></div></div>]]></content><author><name>Craig Watcham</name></author><category term="VPC Interface Endpoint" /><category term="Athena" /><category term="Cost saving" /><category term="AWS" /><summary type="html"><![CDATA[Two quick tips for potentially reducing VPC interface endpoint costs.]]></summary></entry><entry><title type="html">Checking regional service availability with the AWS CLI</title><link href="/2021/07/01/checking-regional-service-availability.html" rel="alternate" type="text/html" title="Checking regional service availability with the AWS CLI" /><published>2021-07-01T08:52:00+00:00</published><updated>2021-07-01T08:52:00+00:00</updated><id>/2021/07/01/checking-regional-service-availability</id><content type="html" xml:base="/2021/07/01/checking-regional-service-availability.html"><![CDATA[<p>Using <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-public-parameters-global-infrastructure.html">System Manager public parameters</a> a quick CLI one liner for checking if an AWS service is available in a region:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws ssm get-parameters-by-path \
--path /aws/service/global-infrastructure/services/[SERVICE]/regions/[REGION] \
--output text
</code></pre></div></div>

<p>As an example, checking if Athena is available in ap-northeast-3:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws ssm get-parameters-by-path --path /aws/service/global-infrastructure/services/athena/regions/ap-northeast-3 --output text
</code></pre></div></div>

<p>An empty result indicates the service is not available. The list of service names can be retrieved as below (from the documentation linked above):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws ssm get-parameters-by-path --path /aws/service/global-infrastructure/services --query 'Parameters[].Name | sort(@)'
</code></pre></div></div>

<p>Similarly the list of regions can be found using:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws ssm get-parameters-by-path --path /aws/service/global-infrastructure/regions --query 'Parameters[].Name'
</code></pre></div></div>]]></content><author><name>Craig Watcham</name></author><category term="SSM" /><category term="CLI" /><category term="AWS" /><summary type="html"><![CDATA[Using System Manager public parameters a quick CLI one liner for checking if an AWS service is available in a region:]]></summary></entry><entry><title type="html">AWS in the weeds - S3 CloudWatch metrics and lifecycle actions that are in progress</title><link href="/2019/09/07/aws-in-weeds-s3-cloudwatch-metrics-and.html" rel="alternate" type="text/html" title="AWS in the weeds - S3 CloudWatch metrics and lifecycle actions that are in progress" /><published>2019-09-07T11:56:00+00:00</published><updated>2019-09-07T11:56:00+00:00</updated><id>/2019/09/07/aws-in-weeds-s3-cloudwatch-metrics-and</id><content type="html" xml:base="/2019/09/07/aws-in-weeds-s3-cloudwatch-metrics-and.html"><![CDATA[<p>I was recently working with a customer on an issue that highlighted a poorly explained side effect of the
  reversibility of lifecycle actions. The purpose of this post is to explain this behaviour with the hope that it will
  save S3 customers unexpected costs. TLDR: S3 CloudWatch metrics don't accurately display metrics about lifecycle
  actions that are still in progress.</p>

The customer use case was fairly simple, they had a bucket with a large amount of data that needed to be deleted. They
added a <a
  href="https://docs.aws.amazon.com/AmazonS3/latest/dev/intro-lifecycle-rules.html#intro-lifecycle-rules-actions">lifecycle
  expiration action</a> to the bucket and the following day noticed that the CloudWatch bucket size metric showed the
bucket size to have reduced by the expected amount. An example of how this looks in CloudWatch is below, this graph is a
reproduction in my own account, the customer had a significantly larger amount of data to delete (petabytes rather than
terabytes):<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
  <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhk1PVB1gYCpJbcVqjEVr83jdZ_ILHPb5BeSvgjmoG6b6bs2EeqgLdwrkoqRYIjiLe-lVs9ffabtTWfpq26GKB_U0GTsprZBPI3Pd4XR2pykEr8KZl5_b9l-4dPXDr0H5zaUoYMxdjojSA/s1600/mistaken.png"
    imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="495"
      data-original-width="493" height="320"
      src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhk1PVB1gYCpJbcVqjEVr83jdZ_ILHPb5BeSvgjmoG6b6bs2EeqgLdwrkoqRYIjiLe-lVs9ffabtTWfpq26GKB_U0GTsprZBPI3Pd4XR2pykEr8KZl5_b9l-4dPXDr0H5zaUoYMxdjojSA/s320/mistaken.png"
      width="318" /></a>
</div>
<br />
The customer assumed (logically) that the drop in the graph meant that the lifecycle action had completed and they
removed the expiration lifecycle action. A couple of weeks later the customer's finance team noticed that their S3
storage costs had not reduced despite the engineering team telling them to expect a significant decrease. Looking at the
relevant bucket the CloudWatch graph showed that the bucket size reduction had been reverted and most of the supposedly
deleted data was still present. Example graph:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
  <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbicCXItjfs768KBURlA5GYrfZIpJPZBBrlXazNezP6e9et0aj0P-fh1UhgYSFsIILna1z_JGC1KTdWy4vimQZn9YK6yVoCmIFzETu41Ae1ABrWhJ-ooVckgN7FHFlmRsX2d8SGX_6Y7I/s1600/bounce.png"
    imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="487"
      data-original-width="462" height="320"
      src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbicCXItjfs768KBURlA5GYrfZIpJPZBBrlXazNezP6e9et0aj0P-fh1UhgYSFsIILna1z_JGC1KTdWy4vimQZn9YK6yVoCmIFzETu41Ae1ABrWhJ-ooVckgN7FHFlmRsX2d8SGX_6Y7I/s320/bounce.png"
      width="303" /></a>
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
After investigating with the customer it became clear that this was an unintended consequence of the way S3 lifecycle
actions are <a
  href="https://docs.aws.amazon.com/AmazonS3/latest/dev/how-to-set-lifecycle-configuration-intro.html">implemented</a>,
specifically that:<br />
<br />
"When you disable or delete a lifecycle rule, after a small delay Amazon S3 stops scheduling new objects for deletion or
transition. Any objects that were already scheduled will be unscheduled and they won't be deleted or
transitioned."<br />
<br />
The documentation unfortunately neglects to mention that the CloudWatch bucket metrics will update immediately even when
the lifecycle action has not actually completed processing. There is currently no way to view progress on the lifecycle
action and in this case it took the better part of 2 weeks for the action to complete. This was a fairly expensive
lesson for the customer as S3 was 'operating as designed' and the customer was charged for the data storage costs from
the time they removed the lifecycle action until they added it back when they noticed the issue.<br />
<br />
Takeaways to help avoid this problem:<br />
1. Lifecycle actions can take a long time to complete, potentially weeks for large (petabyte) volumes of data.<br />
2. Don't used CloudWatch bucket metrics as an indicator of lifecycle action progress or completion.<br />
3. After removing or disabling a lifecycle action make sure you check the CloudWatch metrics the next day or two to
confirm there is no unexpected reversion of the action.<br />
4. You will be charged for data costs if the removal of the lifecycle action results in the action being reverted for
some objects.]]></content><author><name>Craig Watcham</name></author><category term="lifecycle" /><category term="S3" /><category term="aws" /><summary type="html"><![CDATA[I was recently working with a customer on an issue that highlighted a poorly explained side effect of the reversibility of lifecycle actions. The purpose of this post is to explain this behaviour with the hope that it will save S3 customers unexpected costs. TLDR: S3 CloudWatch metrics don't accurately display metrics about lifecycle actions that are still in progress.]]></summary></entry><entry><title type="html">AWS tip: Wildcard characters in S3 lifecycle policy prefixes</title><link href="/2019/02/16/aws-tip-wildcard-characters-in-s3.html" rel="alternate" type="text/html" title="AWS tip: Wildcard characters in S3 lifecycle policy prefixes" /><published>2019-02-16T15:21:00+00:00</published><updated>2019-02-16T15:21:00+00:00</updated><id>/2019/02/16/aws-tip-wildcard-characters-in-s3</id><content type="html" xml:base="/2019/02/16/aws-tip-wildcard-characters-in-s3.html"><![CDATA[<p>A quick word of warning regarding S3's treatment of asterisks (*) in object&nbsp;<a
    href="https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lifecycle-mgmt.html">lifecycle policies</a>. In S3
asterisks are valid <a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#object-keys">'special'
    characters</a>&nbsp;and can be used in object key names, this can lead to a lifecycle action not being applied as
expected when the prefix contains an asterisk.</p>


<br />
Historically an asterisk is treated as a wildcard to pattern match 'any', so you would be able to conveniently match all
files for a certain pattern: 'rm *' as an example, would delete all files. This is NOT how an asterisk behaves in S3
lifecycle prefixes. If you specify a prefix of '*' or '/*' it will only be applied to objects that start with an
asterisk and not all objects. The '*' prefix rule would be applied to these objects:<br />
example-bucket/*/key<br />
example-bucket/*yek<br />
<br />
But would not be applied to:<br />
example-bucket/object<br />
example-bucket/directory/key<br />
example-bucket/tcejbo*<br />
<br />
It is not an error to specify an asterisk and it will merely result in the policy not being applied so you may not even
know this as an issue. Fortunately it is fairly easy to check for this configuration with the CLI, the following bash
one liner will iterate through all buckets owned by the caller and check if the bucket has any policies with an asterisk
in their name. It will print out the bucket name and policy if affected or a '.' (to show progress) if not.<br />
<br />
<script src="https://gist.github.com/watchamcb/bc99754981bc68b5c398829ca51b72be.js"></script>

<div>
    You can also check the S3 console, it should display 'Whole bucket' under the 'Applied To' column for any lifecycle
    rules you intended to have applied to the entire bucket.</div>]]></content><author><name>Craig Watcham</name></author><category term="lifecycle" /><category term="S3" /><category term="CLI" /><category term="aws" /><summary type="html"><![CDATA[A quick word of warning regarding S3's treatment of asterisks (*) in object&nbsp;lifecycle policies. In S3 asterisks are valid 'special' characters&nbsp;and can be used in object key names, this can lead to a lifecycle action not being applied as expected when the prefix contains an asterisk.]]></summary></entry><entry><title type="html">Finding S3 API requests from previous versions of the AWS CLI and SDKs</title><link href="/2018/10/14/finding-s3-api-requests-from-previous.html" rel="alternate" type="text/html" title="Finding S3 API requests from previous versions of the AWS CLI and SDKs" /><published>2018-10-14T11:14:00+00:00</published><updated>2018-10-14T11:14:00+00:00</updated><id>/2018/10/14/finding-s3-api-requests-from-previous</id><content type="html" xml:base="/2018/10/14/finding-s3-api-requests-from-previous.html"><![CDATA[<div class="separator" style="clear: both; text-align: center;">
</div>
<p>
Earlier this year the S3 team <span class="MsoHyperlink"><a
    href="https://forums.aws.amazon.com/ann.jspa?annID=5816">announced</a></span>
that S3 will stop accepting API requests <span class="MsoHyperlink"><a
    href="https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html">signed</a></span>
using AWS Signature Version 2 after June 24th, 2019. Customers will need to
update their SDKs, CLIs, and custom implementations to make use of <span class="MsoHyperlink"><a
    href="https://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html">AWS
    Signature Version 4</a></span> to avoid impact after this date. It might be
difficult to find older applications or instances using outdated versions of
the AWS CLI or SDKs that need to be updated, the purpose of this post is to
explain how AWS CloudTrail <span class="MsoHyperlink"><a
    href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-management-and-data-events-with-cloudtrail.html#logging-data-events">data
    events</a></span> and Amazon Athena can be used to help identify applications
that may need to be updated. We will cover the setup of the CloudTrail data
events, the Athena table creation, and some Athena queries to filter and refine
the results to help with this process.</p>

<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<h3>
  Update (January/February 2019)</h3>
<div>
  S3 <a href="https://forums.aws.amazon.com/ann.jspa?annID=6551">recently added</a> a&nbsp;SignatureVersion item to the
  AdditionalEventData field in the S3 data events, this significantly simplifies the process of finding clients using
  SigV2. The SQL queries below have been updated to exclude events with a SigV4 signature (additionaleventdata NOT LIKE
  '%SigV4%'). You can equally search for only '%SigV2%' and skip the CLI version string munging entirely.</div>
<div>
  <br />
</div>
<div class="MsoNormal">
  <o:p></o:p>
</div>
<h3>
  Setting up CloudTrail data events in the AWS console</h3>
<h1>
  <o:p></o:p>
</h1>
<div class="MsoNormal">
  The first step is to create a trail to capture S3 data
  events. This should be done in the region you plan on running your Athena
  queries in order to avoid unnecessary data transfer charges. In the CloudTrail console
  for the region, create a new trail specifying the trail name. The ‘Apply trail
  to all regions’ option should be left as ‘Yes’ unless you plan on running
  separate analyses for each region. Given that we are creating a data events
  trail, select ‘None’ under the Management Events section and check the “Select
  all S3 buckets in your account” checkbox. Finally select the S3 location where
  the CloudTrail data will be written, we will create new bucket for simplicity:<o:p></o:p><br />
  <br />
</div>
<div class="separator" style="clear: both; text-align: center;">
  <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicqyJR3bVMAK9WZJje_DhhmUibchHe63cewImTcKtavLOQW9aEkJN460Xch5-hsycJJ6aHf00MwxKFwa0muLfbn5F5ElogevzGe_D_V6T0hxaSIDyhlxnoveNfaybNKLi3bD9MbNiToOU/s1600/create_trail.png"
    imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="873"
      data-original-width="1128" height="494"
      src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicqyJR3bVMAK9WZJje_DhhmUibchHe63cewImTcKtavLOQW9aEkJN460Xch5-hsycJJ6aHf00MwxKFwa0muLfbn5F5ElogevzGe_D_V6T0hxaSIDyhlxnoveNfaybNKLi3bD9MbNiToOU/s640/create_trail.png"
      width="640" /></a>
</div>
<br />
<div class="MsoNormal">
  <br />
</div>
<h3>
  Setting up CloudTrail data events using the AWS CLI</h3>
<h1>
  <o:p></o:p>
</h1>
<div class="MsoNormal">
  If you prefer to create the trail using the AWS CLI then you
  can use the <span class="MsoHyperlink"><a
      href="https://docs.aws.amazon.com/cli/latest/reference/cloudtrail/create-subscription.html">create-subscription</a></span>
  command to create the S3 bucket and trail with the correct permissions,
  updating it to be a global trail and then adding the S3 data event
  configuration:<o:p></o:p>
</div>
<script src="https://gist.github.com/watchamcb/6afc2efe1e6b287d4007c17e696cfc1c.js"></script>

<br />
<div class="MsoNormal">
  <br />
  <br />
</div>
<h3>
  A word on cost</h3>
<h1>
  <o:p></o:p>
</h1>
<div class="MsoNormal">
  Once the trail has been created, CloudTrail will start
  recording S3 data events and delivering them to the configured S3 bucket. Data
  events are currently priced at $0.10 per 100,000 events with the storage costs
  being the standard S3 data storage charges for the (compressed) events, see the&nbsp;<span class="MsoHyperlink"><a
      href="https://aws.amazon.com/cloudtrail/pricing/">CloudTrail pricing</a></span> page for additional details. It is
  recommend that you disable the data event trail once you are satisfied that you have gathered sufficient
  request data, it can be re-enabled if further analysis is required at a later stage.<o:p></o:p>
</div>
<div class="MsoNormal">
  <br />
</div>
<h3>
  Creating the Athena table</h3>
<h1>
  <o:p></o:p>
</h1>
<div class="MsoNormal">
  The CloudTrail team simplified the process for using Athena
  to analyse CloudTrail logs by <span class="MsoHyperlink"><a
      href="https://aws.amazon.com/about-aws/whats-new/2018/03/aws-cloudtrail-log-search-using-amazon-athena/">adding
      a feature</a></span> to allow customers to create an Athena table directly from
  the CloudTrail console event history page by simply clicking on the ‘Run
  advanced queries in Amazon Athena’ link and selecting the corresponding S3
  CloudTrail bucket:<o:p></o:p>
</div>
<div class="MsoNormal">
  <br />
</div>
<div class="separator" style="clear: both; text-align: center;">
  <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9yd9A9WTbwlw_y5Cb30_efrqjg3x3KZDZDkq32rQkIGn7hUVc_8bjvjJTpYnZny6xdFlHTM3B4RYVfBuroR6hj1fKuKiHUut6ZLc-sNPnRB5LbMV3jd17MYRFjGMP6D16_5km1-hGHS8/s1600/create_table.png"
    imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="277"
      data-original-width="1329" height="132"
      src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9yd9A9WTbwlw_y5Cb30_efrqjg3x3KZDZDkq32rQkIGn7hUVc_8bjvjJTpYnZny6xdFlHTM3B4RYVfBuroR6hj1fKuKiHUut6ZLc-sNPnRB5LbMV3jd17MYRFjGMP6D16_5km1-hGHS8/s640/create_table.png"
      width="640" /></a>
</div>
<br />
<br />
<div class="MsoNormal">
  <br />
</div>
<div class="separator" style="clear: both; text-align: center;">
  <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2hrhGinV_7AQJNtF3h7AY56L_XemUFifhgjoxGNgGaHeu2KyMMrwlg9t6erxHQa0qRSiCphbqmnrGHnjDPmT5cGgTnlLcDh6UqzYFfUu03Wa53idDKh6hLDUfSttL6XBYx-UWOlXE1QQ/s1600/enable_athena.png"
    imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="790"
      data-original-width="806" height="626"
      src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2hrhGinV_7AQJNtF3h7AY56L_XemUFifhgjoxGNgGaHeu2KyMMrwlg9t6erxHQa0qRSiCphbqmnrGHnjDPmT5cGgTnlLcDh6UqzYFfUu03Wa53idDKh6hLDUfSttL6XBYx-UWOlXE1QQ/s640/enable_athena.png"
      width="640" /></a>
</div>
<div class="MsoNormal">
  <br />
</div>
<div class="MsoNormal">
  An explanation of how to create the Athena table manually
  can be found in the <span class="MsoHyperlink"><a
      href="https://docs.aws.amazon.com/athena/latest/ug/cloudtrail-logs.html#create-cloudtrail-table-ct">Athena
      CloudTrail documentation</a></span>.<o:p></o:p><br />
  <br />
</div>
<h3>
  Analysing the data events with Athena</h3>
<h1>
  <o:p></o:p>
</h1>
<div class="MsoNormal">
  We now have all the components needed to begin searching for
  clients that may need to be updated. Starting with a basic query that filters
  out most of the AWS requests (for example the AWS Console, CloudTrail, Athena, Storage
  Gateway, CloudFront):<o:p></o:p>
</div>
<br />
<script src="https://gist.github.com/watchamcb/f544054890f86dca6a43748e83405ec4.js"></script>

<br />
<div class="MsoNormal">
  <o:p><br /></o:p>
</div>
<div class="separator" style="clear: both; text-align: center;">
  <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8SjfozLYB5VoM-t9j6sYnRBTy-EOiwUzv-V5O60NyY38osOUQj18ujaVlIsC97yGtaJcsYaV5PjWYCXuxXEzftunjWEdyhxYWkXhAwnXuQ47wUK0pJ3lOOUdrGip7DwV2Yd6rQdOYH68/s1600/query2.png"
    imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="502"
      data-original-width="867" height="370"
      src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8SjfozLYB5VoM-t9j6sYnRBTy-EOiwUzv-V5O60NyY38osOUQj18ujaVlIsC97yGtaJcsYaV5PjWYCXuxXEzftunjWEdyhxYWkXhAwnXuQ47wUK0pJ3lOOUdrGip7DwV2Yd6rQdOYH68/s640/query2.png"
      width="640" /></a>
</div>
<div class="MsoNormal">
  <br />
</div>
<div class="MsoNormal">
  <br />
</div>
<div class="MsoNormal">
  These results should mostly be client API/CLI requests but the
  large number of requests can still be refined by only including regions that
  actually support AWS Signature Version 2. From the <span class="MsoHyperlink"><a
      href="https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region">region
      and endpoint documentation for S3</a></span> we can see that we only need to
  check eight of the regions. We can safely exclude the AWS Signature Version 4
  (SigV4) regions as clients would not work correctly against these regions if
  they did not already have SigV4 support. Let’s also look at distinct user
  agents and extract the version from the user agent string:<o:p></o:p>
</div>
<br />
<script src="https://gist.github.com/watchamcb/448e7a0f9419dca53392bfd20198527e.js"></script>

<br />
<div class="separator" style="clear: both; text-align: center;">
  <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9N32SwpunF63o4vlfC4FdzpulJXiyLczFFPDIksluBfMF8D1a8OMdjVlqvphZQRrwbpnvOknfJC99a6TNR9Le78GvlxiqanMnFDychXHIeEuDDbezH9E6FusPwd3RMOWqX-OX58z4TxI/s1600/query3.png"
    imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="497"
      data-original-width="919" height="346"
      src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9N32SwpunF63o4vlfC4FdzpulJXiyLczFFPDIksluBfMF8D1a8OMdjVlqvphZQRrwbpnvOknfJC99a6TNR9Le78GvlxiqanMnFDychXHIeEuDDbezH9E6FusPwd3RMOWqX-OX58z4TxI/s640/query3.png"
      width="640" /></a>
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="MsoNormal">
  <br />
</div>
<div class="MsoNormal">
  We are unfortunately not able to filter on the calculated
  ‘version’ column and as it is a string it is also difficult to perform direct
  numerical version comparison. We can use some arithmetic to create a version
  number that can be compared. Using the AWS CLI requests as an example for the moment and
  adding back the source IP address and user identity<o:p></o:p>
</div>
<br />
<script src="https://gist.github.com/watchamcb/4759d6bc9f1dc8b1304590c0be793972.js"></script>

<br />
<div class="MsoNormal">
  The version comparison number (10110108) translates to the
  version string 1.11.108 which is the first version of AWS CLI supporting SigV4
  by default. This results in a list of clients accessing S3 objects in this
  account using a version of the AWS CLI that needs to be updated:<o:p></o:p>
</div>
<div class="separator" style="clear: both; text-align: center;">
  <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdmsDfVaHUK-6pJ4-HjEopfiaW3uZagAN4gu3_nZUSCzF3J5C3rKPsKfpWjkZNnQdXMMQZVN9sQUAyhaAykkhfGYSmlI95PC-nx54Nhk7BMVle2abpRXnTSXpSvltt05MbAIYnhkJZbGQ/s1600/query4.png"
    imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="429"
      data-original-width="920" height="298"
      src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdmsDfVaHUK-6pJ4-HjEopfiaW3uZagAN4gu3_nZUSCzF3J5C3rKPsKfpWjkZNnQdXMMQZVN9sQUAyhaAykkhfGYSmlI95PC-nx54Nhk7BMVle2abpRXnTSXpSvltt05MbAIYnhkJZbGQ/s640/query4.png"
      width="640" /></a>
</div>
<br />
<br />
<div class="MsoNormal">
  The same query can be applied to all the AWS CLI and SDK
  user agent strings by substituting the corresponding agent string and version
  number for SDK versions using SigV4 by default:<o:p></o:p><br />
  <br />
</div>
<table border="1" cellpadding="0" cellspacing="0" class="MsoTableGrid"
  style="border-collapse: collapse; border: none; mso-border-alt: solid windowtext .5pt; mso-padding-alt: 0cm 5.4pt 0cm 5.4pt; mso-yfti-tbllook: 1184;">
  <tbody>
    <tr style="height: 15.0pt; mso-yfti-firstrow: yes; mso-yfti-irow: 0;">
      <td nowrap=""
        style="border: solid windowtext 1.0pt; height: 15.0pt; mso-border-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 99.0pt;"
        valign="top" width="132">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          <b>AWS Client<o:p></o:p></b>
        </div>
      </td>
      <td nowrap=""
        style="border-left: none; border: solid windowtext 1.0pt; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 89.0pt;"
        valign="top" width="119">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          <b>SigV4 default version<o:p></o:p></b>
        </div>
      </td>
      <td nowrap=""
        style="border-left: none; border: solid windowtext 1.0pt; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 86.0pt;"
        valign="top" width="115">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          <b>User Agent String<o:p></o:p></b>
        </div>
      </td>
      <td nowrap=""
        style="border-left: none; border: solid windowtext 1.0pt; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 65.95pt;"
        valign="top" width="88">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          <b>Version comparator<o:p></o:p></b>
        </div>
      </td>
    </tr>
    <tr style="height: 15.0pt; mso-yfti-irow: 1;">
      <td nowrap=""
        style="border-top: none; border: solid windowtext 1.0pt; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 99.0pt;"
        valign="top" width="132">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          Java<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 89.0pt;"
        valign="top" width="119">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          1.11.x<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 86.0pt;"
        valign="top" width="115">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          aws-sdk-java<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 65.95pt;"
        valign="top" width="88">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          10110000<o:p></o:p>
        </div>
      </td>
    </tr>
    <tr style="height: 15.0pt; mso-yfti-irow: 2;">
      <td nowrap=""
        style="border-top: none; border: solid windowtext 1.0pt; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 99.0pt;"
        valign="top" width="132">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          .NET<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 89.0pt;"
        valign="top" width="119">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          3.1.10.0<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 86.0pt;"
        valign="top" width="115">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          aws-sdk-dotnet<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 65.95pt;"
        valign="top" width="88">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          30010010<o:p></o:p>
        </div>
      </td>
    </tr>
    <tr style="height: 15.0pt; mso-yfti-irow: 3;">
      <td nowrap=""
        style="border-top: none; border: solid windowtext 1.0pt; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 99.0pt;"
        valign="top" width="132">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          Node.js<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 89.0pt;"
        valign="top" width="119">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          2.68.0<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 86.0pt;"
        valign="top" width="115">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          aws-sdk-nodejs<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 65.95pt;"
        valign="top" width="88">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          20680000<o:p></o:p>
        </div>
      </td>
    </tr>
    <tr style="height: 15.0pt; mso-yfti-irow: 4;">
      <td nowrap=""
        style="border-top: none; border: solid windowtext 1.0pt; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 99.0pt;"
        valign="top" width="132">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          PHP<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 89.0pt;"
        valign="top" width="119">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          3<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 86.0pt;"
        valign="top" width="115">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          aws-sdk-php<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 65.95pt;"
        valign="top" width="88">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          30000000<o:p></o:p>
        </div>
      </td>
    </tr>
    <tr style="height: 15.0pt; mso-yfti-irow: 5;">
      <td nowrap=""
        style="border-top: none; border: solid windowtext 1.0pt; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 99.0pt;"
        valign="top" width="132">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          Python Botocore<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 89.0pt;"
        valign="top" width="119">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          1.5.71<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 86.0pt;"
        valign="top" width="115">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          Botocore<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 65.95pt;"
        valign="top" width="88">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          10050071<o:p></o:p>
        </div>
      </td>
    </tr>
    <tr style="height: 15.0pt; mso-yfti-irow: 6;">
      <td nowrap=""
        style="border-top: none; border: solid windowtext 1.0pt; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 99.0pt;"
        valign="top" width="132">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          Python Boto3<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 89.0pt;"
        valign="top" width="119">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          1.4.6<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 86.0pt;"
        valign="top" width="115">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          Boto3<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 65.95pt;"
        valign="top" width="88">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          10040006<o:p></o:p>
        </div>
      </td>
    </tr>
    <tr style="height: 15.0pt; mso-yfti-irow: 7;">
      <td nowrap=""
        style="border-top: none; border: solid windowtext 1.0pt; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 99.0pt;"
        valign="top" width="132">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          Ruby<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 89.0pt;"
        valign="top" width="119">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          2.2.0<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 86.0pt;"
        valign="top" width="115">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          aws-sdk-ruby<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 65.95pt;"
        valign="top" width="88">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          20020000<o:p></o:p>
        </div>
      </td>
    </tr>
    <tr style="height: 15.0pt; mso-yfti-irow: 8;">
      <td nowrap=""
        style="border-top: none; border: solid windowtext 1.0pt; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 99.0pt;"
        valign="top" width="132">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          AWS CLI<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 89.0pt;"
        valign="top" width="119">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          1.11.108<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 86.0pt;"
        valign="top" width="115">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          aws-cli<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 65.95pt;"
        valign="top" width="88">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          10110108<o:p></o:p>
        </div>
      </td>
    </tr>
    <tr style="height: 15.0pt; mso-yfti-irow: 9; mso-yfti-lastrow: yes;">
      <td nowrap=""
        style="border-top: none; border: solid windowtext 1.0pt; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 99.0pt;"
        valign="top" width="132">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          Powershell<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 89.0pt;"
        valign="top" width="119">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          3.1.10.0<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 86.0pt;"
        valign="top" width="115">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          AWSPowerShell<o:p></o:p>
        </div>
      </td>
      <td nowrap=""
        style="border-bottom: solid windowtext 1.0pt; border-left: none; border-right: solid windowtext 1.0pt; border-top: none; height: 15.0pt; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; padding: 0cm 5.4pt 0cm 5.4pt; width: 65.95pt;"
        valign="top" width="88">
        <div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm;">
          30010010<o:p></o:p>
        </div>
      </td>
    </tr>
  </tbody>
</table>
<div class="MsoNormal">
  <br />
</div>
<div class="MsoNormal">
  Note:<o:p></o:p>
</div>
<div class="MsoNormal">
  .NET35,.NET45, and
  CoreCLR only, PCL, Xamarin, UWP platforms do not support SigV4 at all<o:p></o:p>
</div>
<div class="MsoNormal">
  All versions of Go and C++ SDKs support SigV4 by default<o:p></o:p>
</div>
<div class="MsoNormal">
  <br />
  Additional Note:<br />
  There is no need to look at the client version number for new events which will automatically include the
  SignatureVersion.<br />
  <br />
</div>
<h3>
  Tracing the source of the requests</h3>
<h1>
  <o:p></o:p>
</h1>
<div class="MsoNormal">
  The source IP address will reflect the private IP of the EC2
  instance accessing S3 through a VPC endpoint or the public IP if accessing S3
  directly. You can search for either of these IPs in EC2 AWS Console for the
  corresponding region. For non-EC2 or NAT access you should be able to use the
  ARN to track down the source of the requests.<o:p></o:p>
</div>
<br />]]></content><author><name>Craig Watcham</name></author><summary type="html"><![CDATA[Earlier this year the S3 team announced that S3 will stop accepting API requests signed using AWS Signature Version 2 after June 24th, 2019. Customers will need to update their SDKs, CLIs, and custom implementations to make use of AWS Signature Version 4 to avoid impact after this date. It might be difficult to find older applications or instances using outdated versions of the AWS CLI or SDKs that need to be updated, the purpose of this post is to explain how AWS CloudTrail data events and Amazon Athena can be used to help identify applications that may need to be updated. We will cover the setup of the CloudTrail data events, the Athena table creation, and some Athena queries to filter and refine the results to help with this process.]]></summary></entry><entry><title type="html">AWS S3 event aggregation with Lambda and DynamoDB</title><link href="/2018/08/25/aws-s3-event-aggregation-with-lambda.html" rel="alternate" type="text/html" title="AWS S3 event aggregation with Lambda and DynamoDB" /><published>2018-08-25T16:23:00+00:00</published><updated>2018-08-25T16:23:00+00:00</updated><id>/2018/08/25/aws-s3-event-aggregation-with-lambda</id><content type="html" xml:base="/2018/08/25/aws-s3-event-aggregation-with-lambda.html"><![CDATA[<p>S3 has had <a href="https://aws.amazon.com/blogs/aws/s3-event-notification/">event notifications</a> since 2014 and
    for <a href="https://docs.aws.amazon.com/lambda/latest/dg/with-s3.html">individual object
        notifications</a>&nbsp;these events work well with Lambda, allowing you to perform an action on every object
    event in a bucket. It is harder to use this approach when you want to perform an action a limited number of times or
    at an aggregated bucket level. An example use case would be refreshing a dependency (like <a
        href="https://docs.aws.amazon.com/storagegateway/latest/userguide/managing-gateway-file.html#refresh-cache">Storage
        Gateway RefreshCache</a>) when you are expecting a large number of objects events in a bucket. Performing a
    relatively expensive action for every event is not practical or efficient in this case. This post provides a
    solution for aggregating these events using Lambda, DynamoDB, and SQS.
<div><br />
    <h4 style="text-align: left;">Update (June/July 2020)</h4>
    <div>Cache refresh can now be <a
            href="https://aws.amazon.com/blogs/storage/automating-cache-refresh-process-for-file-gateway-on-aws-storage-gateway/">automated</a>.
        The event aggregation approach below may still be useful depending on your requirements.</div>
    </p>


    <h4>
        The problem</h4>
    <div>
        We want to call RefreshCache on our Storage Gateway (SGW) whenever the contents of the S3 bucket it exposes are
        updated by an external process. If the external process is updating a large number of (small) S3 objects then a
        large number of S3 events will be triggered. We don't want to overload our SGW with refresh requests so we need
        a way to aggregate these events to only send occasional refresh requests.</div>
    <div>
        <br />
    </div>
    <h4>
        The solution</h4>
    The solution is fairly simple and uses DynamoDB's <a
        href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.ConditionalUpdate">Conditional
        Writes</a>&nbsp;for synchronisation and SQS <a
        href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-message-timers.html">Message
        Timers</a>&nbsp;to enable aggregation. When the Lambda function processes a new object event it first checks to
    see if the event falls within the window of the currently active refresh request. If the event is within the window
    it will automatically be included when the refresh executes and the event can be ignored. If the event occurred
    after the last refresh then a new refresh request is sent to an SQS queue with a message timer equal to the refresh
    window period. This allows for all messages received within a refresh window to be included in a single refresh
    operation.<br />
    <br />
    <h4>
        Implementation</h4>
    <div>
        At a high level we need to create resources (SQS queue, DynamoDB table, Lambda functions), set up permissions
        (create and assign IAM roles), and apply some configuration (linking Lambda to S3 event notification and SQS
        queues). This implementation really belongs in a CloudFormation template (and I may actually create one) but I
        was interested to try and do this entirely via the AWS CLI, masochistic as that may be. If you are not
        interested in the gory implementation details then skip ahead to 'Creation and deletion script' section<br />
        <br />
        <script
            src="https://github.com/watchamcb/s3-event-aggregator/blob/f324ca33e18d0dcc7dd45e1294d0b2aeadb03b7b/iam/dynamo-writer.json#L1"> </script>

        Let's start with the S3 event aggregation piece. We need:<br />
        <ol>
            <li>A DynamoDB table to track state</li>
            <li>An SQS queue as a destination for aggregated actions</li>
            <li>A Lambda function for processing and aggregating the S3 events</li>
            <li>IAM permissions for all of the above</li>
        </ol>
        <div>
            As the DynamoDB table and SQS queue are independent we can create these first:<br />
            <span
                style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace; font-size: x-small;">aws
                dynamodb create-table --table-name S3EventAggregator --attribute-definitions
                AttributeName=BucketName,AttributeType=S&nbsp;</span><span
                style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace; font-size: x-small;">--key-schema
                AttributeName=BucketName,KeyType=HASH --provisioned-throughput
                ReadCapacityUnits=1,WriteCapacityUnits=5</span><br />
            <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"><span
                    style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace; font-size: x-small;"><br /></span>
                <span
                    style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace; font-size: x-small;">aws
                    sqs create-queue --queue-name S3EventAggregatorActionQueue</span></span><br />
            <span
                style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace; font-size: x-small;"><br /></span><span
                style="font-family: inherit;">
                Naturally this needs to be done with a user that has sufficient permissions and assumes your default
                region is set.</span><br />
            <span style="font-family: inherit;"><br /></span>
            <span style="font-family: inherit;">The Lambda function is a bit trickier as it requires a role to be
                created before the function can be created. So let's start with the IAM permissions. First let's create
                a policy allowing DynamoDB GetItem and UpdateItem to be performed on the DynamoDB table we created
                earlier. To do this we need a JSON file containing the necessary permissions.&nbsp;</span>The&nbsp;<a
                href="https://github.com/watchamcb/s3-event-aggregator/blob/master/iam/dynamo-writer.json">dynamo-writer.json</a>&nbsp;file
            looks like this:<br />
            <div>
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">{</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">&nbsp; &nbsp; "Version": "2012-10-17",</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">&nbsp; &nbsp; "Statement": [</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; {</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "Effect":
                    "Allow",</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "Action": [</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;"><span style="white-space: pre;"> </span> &nbsp;
                    &nbsp;"dynamodb:UpdateItem",</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;"><span style="white-space: pre;"> </span> &nbsp;
                    &nbsp;"dynamodb:GetItem"</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;"><span style="white-space: pre;"> </span> &nbsp; &nbsp;],</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "Resource":
                    "arn:aws:dynamodb:REGION:ACCOUNT_ID:table/S3EventAggregator"</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; }</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">&nbsp; &nbsp; ]</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">}</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;"><br /></span>
            </div>
            <div>
                We need to replace REGION and ACCOUNT_ID with the relevant values. As we are aiming at using the command
                line for this exercise, let's use STS to retrieve our account ID, set our region, and then use sed to
                substitute both variables:<br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;"><br /></span>
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output
                    text)&nbsp;</span>
            </div>
            <div>
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">AWS_DEFAULT_REGION="eu-west-1"&nbsp;</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">wget -O dynamo-writer.json&nbsp;</span><span
                    face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">https://raw.githubusercontent.com/watchamcb/s3-event-aggregator/master/iam/dynamo-writer.json&nbsp;</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">sed -i "s/ACCOUNT_ID/$ACCOUNT_ID/g" dynamo-writer.json</span><br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">sed -i "s/REGION/$AWS_DEFAULT_REGION/g" dynamo-writer.json</span><br />
                <div>
                </div>
            </div>
            <br />
            <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                style="font-size: x-small;">aws iam create-policy --policy-name S3EventAggregatorDynamo
                --policy-document file://dynamo-writer.json</span><br />
            <div>
                <div>
                    <br />
                </div>
                <div>
                    We now have a policy that allows the caller (our soon to be created Lambda function in this case) to
                    update items in the S3EventAggregator DynamoDB table. Next we need to create a policy to allow the
                    function to write messages to SQS. The <a
                        href="https://github.com/watchamcb/s3-event-aggregator/blob/master/iam/sqs-writer.json">sqs-writer.json</a>&nbsp;policy
                    file contents are similar to the DynamoDB policy:<br />
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small; white-space: pre-wrap;">{</span><br />
                        <pre style="overflow-wrap: break-word; white-space: pre-wrap; word-wrap: break-word;"><span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif" style="font-size: x-small;">    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sqs:SendMessage",
            "Resource": "arn:aws:sqs:REGION:ACCOUNT_ID:S3EventAggregatorActionQueue"
        }
    ]
}</span></pre>
                    </div>
                    <div>
                        <br />
                    </div>
                    <div>
                        Retrieving the file and substituting the ACCOUNT_ID and REGION using the environment variables
                        we created for the DynamoDB policy:<br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">wget -O sqs-writer.json&nbsp;</span><span
                            face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">https://raw.githubusercontent.com/watchamcb/s3-event-aggregator/master/iam/sqs-writer.json&nbsp;</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">sed -i "s/ACCOUNT_ID/$ACCOUNT_ID/g" sqs-writer.json</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">sed -i "s/REGION/$AWS_DEFAULT_REGION/g" sqs-writer.json</span>
                    </div>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws iam create-policy --policy-name S3EventAggregatorSqsWriter
                        --policy-document file://sqs-writer.json</span><br />
                    <br />
                    Having defined the two resource access policies let's create an IAM role for the Lambda
                    function.&nbsp;
                </div>
                <div>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">wget -O lambda-trust.json&nbsp;</span><span
                        face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">https://raw.githubusercontent.com/watchamcb/s3-event-aggregator/master/iam/lambda-trust.json</span><br />

                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws iam create-role --role-name S3EventAggregatorLambdaRole
                        --assume-role-policy-document file://lambda-trust.json</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;"><br /></span>
                    The <a
                        href="https://github.com/watchamcb/s3-event-aggregator/blob/master/iam/lambda-trust.json">lambda-trust.json</a>&nbsp;policy
                    allows Lambda access to assume roles via STS and looks like this (no substitutions required for this
                    one):<br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">{</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; "Version": "2012-10-17",</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; "Statement": [</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; {</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; "Effect": "Allow",</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; "Principal": {</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; "Service":
                        "lambda.amazonaws.com"</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; },</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; "Action": "sts:AssumeRole"</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; }</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; ]</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">}</span>
                </div>
                <div>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;"><br /></span>
                    <span style="font-family: inherit;">We can now attach the SQS and DynamoDB policies to the new
                        created Lambda role. We also need the AWS</span>LambdaBasicExecutionRole which is an AWS managed
                    policy providing access to CloudWatch logs and Lambda function execution:<br />
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">aws iam attach-role-policy --policy-arn
                            arn:aws:iam::$ACCOUNT_ID:policy/S3EventAggregatorDynamo --role-name
                            S3EventAggregatorLambdaRole</span>
                    </div>
                </div>
                <div>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws iam attach-role-policy --policy-arn
                        arn:aws:iam::$ACCOUNT_ID:policy/S3EventAggregatorSqsWriter --role-name
                        S3EventAggregatorLambdaRole</span><br />

                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws iam attach-role-policy --policy-arn
                        arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole --role-name
                        S3EventAggregatorLambdaRole</span>
                </div>
            </div>
            <span
                style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace; font-size: x-small;"></span><br />
            <div style="font-family: &quot;Times New Roman&quot;; font-size: medium;">
            </div>
            <span style="font-family: inherit;">We are now finally ready to create the </span><a
                href="https://github.com/watchamcb/s3-event-aggregator/blob/master/src/s3_aggregator.py"
                style="font-family: inherit;">S3EventAggregator Lambda function</a><span style="font-family: inherit;">.
                Starting by creating the </span><a
                href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html"
                style="font-family: inherit;">Python deployment package</a><span
                style="font-family: inherit;">:</span><br />
            <div
                style="orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; widows: 2;">
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">wget -O s3_aggregator.py
                    https://raw.githubusercontent.com/watchamcb/s3-event-aggregator/master/src/s3_aggregator.py</span><br />

                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">zip function.zip s3_aggregator.py</span><br />
                <br />
                And then creating the function (using the AWS_DEFAULT_REGION and ACCOUNT_ID environment variables
                again):<br />
                <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">aws lambda create-function --function-name S3EventAggregator --runtime
                    python3.6&nbsp;</span><span
                    face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">--role arn:aws:iam::$ACCOUNT_ID:role/S3EventAggregatorLambdaRole
                    --zip-file fileb://function.zip&nbsp;</span><span
                    face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">--handler s3_aggregator.lambda_handler --timeout 10&nbsp;</span><span
                    face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                    style="font-size: x-small;">--environment
                    "Variables={QUEUE_URL=https://sqs.$AWS_DEFAULT_REGION.amazonaws.com/$ACCOUNT_ID/S3EventAggregatorActionQueue,REFRESH_DELAY_SECONDS=30,LOG_LEVEL=INFO}"</span><br />
                <div>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws lambda put-function-concurrency --function-name
                        S3EventAggregator --reserved-concurrent-executions 1</span>
                </div>
                <div>
                    <br />
                </div>
                <div>
                    The function concurrency is set to 1 as there is no benefit to having the function processing S3
                    events concurrently and 'single threading' the function will limit the maximum concurrent DynamoDB
                    request rate to reduce DynamoDB capacity usage and costs.</div>
                <div>
                    <br />
                </div>
                <div>
                    All that is left now is to give S3 permission to execute the Lambda function and link the bucket
                    notification events to the S3EventAggregator function. Giving S3 permission on the specific bucket:
                </div>
                <div>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">BUCKET=my-bucket</span>
                </div>
                <div>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws lambda add-permission --function-name S3EventAggregator
                        --statement-id SID_$BUCKET --action lambda:InvokeFunction --principal s3.amazonaws.com
                        --source-account $ACCOUNT_ID --source-arn arn:aws:s3:::$BUCKET</span>
                </div>
                <div>
                    <br />
                </div>
                <div>
                    Interestingly, the --source-arn can be omitted to avoid needing to add permissions for each bucket
                    you want the function to operate on but it is required (and must match a specific bucket) for the
                    Lambda Console to display the function and trigger correctly. The S3 <a
                        href="https://github.com/watchamcb/s3-event-aggregator/blob/master/s3/event.json">event.json</a>
                    configuration creates an event on any object creation or removal events:</div>
                <div>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">{</span>
                </div>
                <div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; "LambdaFunctionConfigurations": [</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; {</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "Id":
                            "s3-event-aggregator",</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "LambdaFunctionArn":
                            "arn:aws:lambda:REGION:ACCOUNT_ID:function:S3EventAggregator",</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "Events": [</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                            "s3:ObjectCreated:*",</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                            "s3:ObjectRemoved:*"</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ]</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; }</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; ]</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">}</span>
                    </div>
                </div>
                <div>
                    <br />
                </div>
                <div>
                    Once again substituting the relevant region and account IDs:</div>
                <div>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">wget -O event.json
                        https://raw.githubusercontent.com/watchamcb/s3-event-aggregator/master/s3/event.json</span>
                </div>
                <div>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">sed -i "s/ACCOUNT_ID/$ACCOUNT_ID/g" event.json</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">sed -i "s/REGION/$AWS_DEFAULT_REGION/g" event.json</span>
                </div>
                <div>
                    <br />
                </div>
                <div>
                    And linking the event configuration to a bucket:</div>
                <div>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws s3api put-bucket-notification-configuration --bucket $BUCKET
                        --notification-configuration file://event.json</span>
                </div>
                <div>
                    <br />
                </div>
                <div>
                    Thus concludes the event aggregation part of the solution. A quick test confirms the event
                    aggregation is working as expected:</div>
                <div>
                    <br />
                </div>
                <div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">time for i in $(seq 1 5); do aws s3 cp test.txt
                            s3://$BUCKET/test$i.txt; done</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">upload: ./test.txt to s3://s3-test-net/test1.txt&nbsp; &nbsp;
                            &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">upload: ./test.txt to s3://s3-test-net/test2.txt&nbsp; &nbsp;
                            &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">upload: ./test.txt to s3://s3-test-net/test3.txt&nbsp; &nbsp;
                            &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">upload: ./test.txt to s3://s3-test-net/test4.txt&nbsp; &nbsp;
                            &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">upload: ./test.txt to s3://s3-test-net/test5.txt&nbsp; &nbsp;
                            &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;"><br /></span>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">real<span style="white-space: pre;">
                            </span>0m2.106s</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">user<span style="white-space: pre;">
                            </span>0m1.227s</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">sys<span style="white-space: pre;"> </span>0m0.140s</span>
                    </div>
                </div>
                <div>
                    <br />
                </div>
                <div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">STREAM=$(aws logs describe-log-streams --log-group-name
                            /aws/lambda/S3EventAggregator --order-by LastEventTime --descending --query
                            'logStreams[0].logStreamName' --output text); aws logs get-log-events --log-group-name
                            /aws/lambda/S3EventAggregator --log-stream-name $STREAM --query 'events[*].{msg:message}'
                            --output text | grep "^\[" | sed 's/\t/ /g'</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">[INFO] 2018-08-12T18:14:03.647Z
                            7da07415-9e5b-11e8-ab6d-8f962149ce24 Sending refresh request for bucket: s3-test-net,
                            timestamp: 1534097642149</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">[INFO] 2018-08-12T18:14:04.207Z
                            7e1d6ca2-9e5b-11e8-ac9d-e1f0f9729f66 Refresh for bucket: s3-test-net within refresh window,
                            skipping. S3 Event timestamp: 1534097642938</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">[INFO] 2018-08-12T18:14:04.426Z
                            7eefb013-9e5b-11e8-ab6d-8f962149ce24 Refresh for bucket: s3-test-net within refresh window,
                            skipping. S3 Event timestamp: 1534097643812</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">[INFO] 2018-08-12T18:14:04.635Z
                            7e5b5feb-9e5b-11e8-8aa9-7908c99c450a Refresh for bucket: s3-test-net within refresh window,
                            skipping. S3 Event timestamp: 1534097643371</span><br />
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">[INFO] 2018-08-12T18:14:05.915Z
                            7ddb5a72-9e5b-11e8-80de-0dd6a15c3f62 Refresh for bucket: s3-test-net within refresh window,
                            skipping. S3 Event timestamp: 1534097642517</span>
                    </div>
                </div>
                <div>
                    <br />
                </div>
                <div>
                    From the 'within refresh window' log messages we can see 4 of the 5 events were skipped as they fell
                    within the refresh aggregation window. Checking the SQS queue we can see the refresh request event:
                </div>
                <div>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;"><br /></span>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws sqs receive-message --queue-url
                        https://$AWS_DEFAULT_REGION.queue.amazonaws.com/$ACCOUNT_ID/S3EventAggregatorActionQueue
                        --attribute-names All --message-attribute-names All</span>
                </div>
                <div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">{</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; "Messages": [</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; {</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "MessageId":
                            "c0027dd2-30bc-48bc-b622-b5c85d862c92",</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "ReceiptHandle":
                            "AQEB9DQXkIWsWn...5XU2a13Q8=",</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "MD5OfBody":
                            "99914b932bd37a50b983c5e7c90ae93b",</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "Body": "{}",</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "Attributes": {</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                            "SenderId": "AROAI55PXBF63XVSEBNYM:S3EventAggregator",</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                            "ApproximateFirstReceiveTimestamp": "1534097653846",</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                            "ApproximateReceiveCount": "1",</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                            "SentTimestamp": "1534097642728"</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                            "MD5OfMessageAttributes": "6f6eaf397811cbece985f3e8d87546c3",</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "MessageAttributes":
                            {</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                            "bucket-name": {</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                            &nbsp; "StringValue": "s3-test-net",</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                            &nbsp; "DataType": "String"</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                            },</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                            "timestamp": {</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                            &nbsp; "StringValue": "1534097642149",</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                            &nbsp; "DataType": "Number"</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; }</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">&nbsp; &nbsp; ]</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">}</span>
                    </div>
                </div>
                <div>
                    <br />
                </div>
                <div>
                    Moving onto the final part of the solution, we need a Lambda function that processes the events that
                    the S3EventAggregator function sends to SQS. For the function's permissions we can reuse
                    the&nbsp;S3EventAggregatorDynamo policy for DynamoDB access but will need to create a new policy for
                    reading and deleting SQS messages and refreshing the Storage Gateway cache.<br />
                    <br />
                    The <a
                        href="https://github.com/watchamcb/s3-event-aggregator/blob/master/iam/sgw-refresh.json">sgw-refresh.json</a>
                    is as follows, note that SMB file shares are included but the current <a
                        href="https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html">Lambda
                        execution environment</a>&nbsp;only supports boto3 1.7.30 which does not actually expose the SMB
                    APIs (more on working around this later):<br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">{</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; "Version": "2012-10-17",</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; "Statement": [</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; {</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "Effect":
                        "Allow",</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "Action": [</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                        "storagegateway:RefreshCache",</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                        "storagegateway:ListFileShares",</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                        "storagegateway:DescribeNFSFileShares",</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                        "storagegateway:DescribeSMBFileShares"</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ],</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "Resource":
                        "*"</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; }</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; ]</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">}</span><br />
                    <br />
                    Creating the policy:<br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">wget -O sgw-refresh.json
                        https://raw.githubusercontent.com/watchamcb/s3-event-aggregator/master/iam/sgw-refresh.json</span><br />

                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws iam create-policy --policy-name StorageGatewayRefreshPolicy
                        --policy-document file://sgw-refresh.json</span><br />
                    <br />
                    The <a
                        href="https://github.com/watchamcb/s3-event-aggregator/blob/master/iam/sqs-reader.json">sqs-reader.json</a>
                    gives the necessary SQS read permissions &nbsp;on the S3EventAggregatorActionQueue:<br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">{</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; "Version": "2012-10-17",</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; "Statement": [</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; {</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "Effect":
                        "Allow",</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "Action": [</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                        "sqs:DeleteMessage",</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                        "sqs:GetQueueAttributes",</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                        "sqs:ReceiveMessage"</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ],</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "Resource":
                        "arn:aws:sqs:REGION:ACCOUNT_ID:S3EventAggregatorActionQueue"</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; }</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">&nbsp; &nbsp; ]</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">}</span><br />
                    <br />
                    Substituting &nbsp;and creating the policy:<br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">wget -O sqs-reader.json
                        https://raw.githubusercontent.com/watchamcb/s3-event-aggregator/master/iam/sqs-reader.json</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">sed -i "s/ACCOUNT_ID/$ACCOUNT_ID/g"
                        sqs-reader.json&nbsp;</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">sed -i "s/REGION/$AWS_DEFAULT_REGION/g" sqs-reader.json</span><br />

                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws iam create-policy --policy-name S3EventAggregatorSqsReader
                        --policy-document file://sqs-reader.json</span><br />
                    <div>
                        <br />
                    </div>
                    And then creating the role and adding the relevant policies:<br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">wget -O lambda-trust.json
                        https://raw.githubusercontent.com/watchamcb/s3-event-aggregator/master/iam/lambda-trust.json</span><br />

                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws iam create-role --role-name S3AggregatorActionLambdaRole
                        --assume-role-policy-document file://lambda-trust.json</span><br />

                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws iam attach-role-policy --policy-arn
                        arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole --role-name
                        S3AggregatorActionLambdaRole</span><br />

                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws iam attach-role-policy --policy-arn
                        arn:aws:iam::$ACCOUNT_ID:policy/S3EventAggregatorSqsReader --role-name
                        S3AggregatorActionLambdaRole</span><br />

                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws iam attach-role-policy --policy-arn
                        arn:aws:iam::$ACCOUNT_ID:policy/S3EventAggregatorDynamo --role-name
                        S3AggregatorActionLambdaRole</span><br />

                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws iam attach-role-policy --policy-arn
                        arn:aws:iam::$ACCOUNT_ID:policy/StorageGatewayRefreshPolicy --role-name
                        S3AggregatorActionLambdaRole</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;"><br /></span>
                    Next we will create the Lambda function, this will however depend on whether or not you require SMB
                    file share support. As mentioned earlier the current Lambda execution environment does not expose
                    the new SMB file share APIs so if you have SMB shares mapped on your Storage Gateway you will have
                    to <a
                        href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html">include
                        the latest botocore and boto3</a> libraries with your deployment. The disadvantage of this is
                    that you are not able to view the code in the Lambda console (due to the deployment file size
                    limitation). If you are only using NFS shares then you only need the code without the latest
                    libraries but it will break if you add an SMB share before the Lambda execution environment supports
                    it. Including the dependency in the deployment is the preferred option so that is what we are going
                    to do:<br />
                    <br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">mkdir deploy</span><br />
                    <div>
                        <div>
                            <span
                                face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                                style="font-size: x-small;">pip install boto3 botocore -t deploy</span>
                        </div>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">wget -O
                            deploy/s3_sgw_refresh.py&nbsp;https://raw.githubusercontent.com/watchamcb/s3-event-aggregator/master/src/s3_sgw_refresh.py</span>
                    </div>
                    <div>
                        <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                            style="font-size: x-small;">cd deploy</span>
                    </div>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">zip -r function.zip *</span><br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;"><br /></span>
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws lambda create-function --function-name S3StorageGatewayRefresh
                        --runtime python3.6 --role arn:aws:iam::$ACCOUNT_ID:role/S3AggregatorActionLambdaRole --zip-file
                        fileb://function.zip --handler s3_sgw_refresh.lambda_handler --timeout 5&nbsp;</span><span
                        face="&quot;helvetica neue&quot;, arial, helvetica, sans-serif"
                        style="font-size: x-small;">--environment "Variables={LOG_LEVEL=INFO}"</span><br />

                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws lambda put-function-concurrency --function-name
                        S3StorageGatewayRefresh --reserved-concurrent-executions 1</span><br />
                    <br />
                    And finally create the event mapping to execute the <a
                        href="https://github.com/watchamcb/s3-event-aggregator/blob/master/src/s3_sgw_refresh.py">S3StorageGatewayRefresh</a>
                    function when messages are received on the queue:<br />
                    <br />
                    <span face="&quot;helvetica neue&quot; , &quot;arial&quot; , &quot;helvetica&quot; , sans-serif"
                        style="font-size: x-small;">aws lambda create-event-source-mapping --function-name
                        S3StorageGatewayRefresh --event-source
                        arn:aws:sqs:$AWS_DEFAULT_REGION:$ACCOUNT_ID:S3EventAggregatorActionQueue --batch-size
                        1</span><br />
                    <br />
                    And that is the final solution. Verifying it works as expected, let's mount the NFS share and upload
                    some files via the CLI and confirm the share is refreshed. Mounting the share:<br />
                    <br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">$ sudo mount
                        -t nfs -o nolock 172.31.2.13:/s3-test-net share</span><br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">$ cd
                        share/sgw</span><br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">$ ls
                        -l</span><br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">total
                        0</span><br />
                    <div>
                        <br />
                    </div>
                    <div>
                        Uploading the files through the CLI (from a different machine):</div>
                    <br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">$ for i in
                        $(seq 1 20); do aws s3 cp test.txt s3://$BUCKET/sgw/test$i.txt; done</span><br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">upload:
                        ./test.txt to s3://s3-test-net/test1.txt &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                    </span><br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">upload:
                        ./test.txt to s3://s3-test-net/test2.txt &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                    </span><br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif"
                        style="font-size: x-small;">...</span><br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">upload:
                        ./test.txt to s3://s3-test-net/test19.txt &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                    </span><br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">upload:
                        ./test.txt to s3://s3-test-net/test20.txt &nbsp; &nbsp; </span><br />
                    <br />
                    And confirming the refresh of the share:<br />
                    <br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">$ ls
                        -l</span><br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">total
                        10</span><br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">-rw-rw-rw- 1
                        nobody nogroup 19 Aug 25 07:46 test10.txt</span><br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">-rw-rw-rw- 1
                        nobody nogroup 19 Aug 25 07:46 test11.txt</span><br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif"
                        style="font-size: x-small;">...</span><br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">-rw-rw-rw- 1
                        nobody nogroup 19 Aug 25 07:46 test8.txt</span><br />
                    <span face="Helvetica Neue, Arial, Helvetica, sans-serif" style="font-size: x-small;">-rw-rw-rw- 1
                        nobody nogroup 19 Aug 25 07:46 test9.txt</span>
                </div>
                <div>
                    <br />
                </div>
                <div>
                    <h4>
                        Creation and deletion scripts</h4>
                    <div>
                        For convenience a script to create (and remove) this stack is provided on GitHub. Clone
                        the&nbsp;<a
                            href="https://github.com/watchamcb/s3-event-aggregator">s3-event-aggregator</a>&nbsp;repository
                        and run the create-stack.sh and delete-stack.sh scripts respectively. You need to have the AWS
                        CLI installed and configured and sed and zip must be available. Be sure to edit the BUCKET
                        variable in the script to match your bucket name and change the REGION if appropriate.</div>
                    <div>
                        <br />
                    </div>
                    <div>
                        Note that the delete stack script will not remove the S3 event notification configuration by
                        default. There is no safe and convenient way to remove only the S3EventAggregator configuration
                        (other than removing all configuration which may result in unintended loss of other event
                        configuration). If you have other events configured on a bucket it is best to use the AWS
                        Console to remove the s3-event-aggregator event configuration. If there are no other events
                        configured on your bucket you can safely uncomment the relevant line in the deletion script.
                    </div>
                </div>
                <div>
                    <br />
                </div>
                <h4>
                    Configuration</h4>
                <div>
                    The two Lambda functions both have a LOG_LEVEL environment variable to control the details logged to
                    CloudWatch Logs, the functions were created with the level set to INFO but DEBUG may be useful for
                    troubleshooting and WARN is probably appropriate for use in production.</div>
                <div>
                    <br />
                </div>
                <div>
                    The S3EventAggregator function also has an environment variable called REFRESH_DELAY_SECONDS for
                    controlling the event aggregation window. It was initialised to 30 seconds when the function was
                    created but it may be appropriate to change it depending on your S3 upload pattern. If the uploads
                    are mostly small and complete quickly, or if you need the Storage Gateway to reflect changes quickly
                    then this may be a reasonable value. If you are performing larger uploads or the total upload
                    process takes significantly longer then the refresh window would need to be increased to be longer
                    than the total expected upload time.</div>
                <div>
                    <br />
                </div>
                <div>
                    The DynamoDB table was created with 5 write capacity units and as the entries are less than 1KB this
                    should be sufficient as long as you are not writing more than 5 objects a second to the Storage
                    Gateway S3 bucket. Writing more than this will required additional write capacity to be provisioned
                    (or auto scaling enabled).</div>
                <div>
                    <br />
                </div>
                <div>
                    The same code can be used for multiple buckets by simply adding additional bucket event
                    configurations via the CLI&nbsp;put-bucket-notification-configuration as above or using the AWS
                    Console.</div>
                <div>
                    <br />
                </div>
                <h4>
                    Cost</h4>
                <div>
                    There are three component costs involved in this solution, the two Lambda functions, DynamoDB, and
                    SQS. The Lambda and DynamoDB costs will scale fairly linearly with usage with both the
                    S3EventAggregator and DynamoDB being charged for each S3 event that is triggered. To get an idea of
                    the number of events to expect you can enable <a
                        href="https://docs.aws.amazon.com/AmazonS3/latest/dev/metrics-configurations.html">S3
                        metrics</a> on the bucket and check the PUT and DELETE counts. The S3StorageGatewayRefresh
                    function and SQS messages will be a fraction of the total S3 event counts and dependent on the
                    REFRESH_DELAY_SECONDS configuration. A longer refresh delay will result in fewer SQS messages and
                    S3StorageGatewayRefresh function executions.</div>
                <div>
                    <br />
                </div>
                <div>
                    As an example lets use an example of 1000 objects uploaded a day with these being aggregated into 50
                    refresh events. For simplicity we will also assume that the free tier has been exhausted and that
                    there are 30 days in the month. The total Lambda request count will then be:</div>
                <div>
                    (30 days x 1000 S3 events) + (30 days x 50 refresh events)&nbsp; = 31 500 request</div>
                <div>
                    As Lambda requests are charged in 1 million increments this will result in a charge of $0.2 for the
                    requests</div>
                <div>
                    <br />
                </div>
                <div>
                    The compute charges are based on duration, with the S3EventAggregator executing in less than 100ms
                    for all aggregate events and around 300 - 600ms for the refresh events. The S3StorageGatewayRefesh
                    function takes between 400ms and 800ms. Giving us:</div>
                <div>
                    (30 days x 950 S3 requests x 0.1s) + (30 days x 50 S3 requests x 0.5s) + (30 days x 50 refresh
                    events x 0.7s) = 4650 seconds</div>
                <div>
                    Lambda compute is charged in GB-s, so:</div>
                <div>
                    4650 seconds x 128MB/1024 = 581.25 GB-s at $0.00001667 = $0.0096894</div>
                <div>
                    Bringing the total Lambda charges for the month to $0.21</div>
                <div>
                    <br />
                </div>
                <div>
                    For DynamoDB we have provisioned 5 WCU at $0.000735/WCU/hour and 1 RCU at&nbsp;$0.000147/RCU/hour
                    working out as:</div>
                <div>
                    (5 WCU x 0.000735 per hour x 24 hours a day x 30 days) + (1 RCU x 0.000147 per hour x 24 hours x 30
                    days) = $2.75 a month</div>
                <div>
                    <br />
                </div>
                <div>
                    SQS charges per million requests with the 1500 send message requests and a further 1500 receive and
                    delete requests all falling under this limit (and thus only costing $0.40 for the month). It is
                    worth noting that Lambda does poll the SQS queue roughly 4 times a minute and this will contribute
                    to your total SQS request costs, using around 172,800 SQS requests a month.</div>
                <div>
                    <br />
                </div>
                <div>
                    There are some other costs associated with CloudWatch Logs and DynamoDB storage but these should be
                    fairly small compared to the request costs and I would not expect the total cost of the stack to be
                    more than $10 - $15 a month.</div>
                <div>
                    <br />
                </div>
                <h4>
                    Conclusion</h4>
                <div>
                    And so ends this post, well done for reading to the end. I quite enjoyed building this solution and
                    will look at converting it to a CloudFormation template at a later stage. Feel free to log issues or
                    pull requests against the GitHub repo.</div>
            </div>
        </div>
    </div>
</div>]]></content><author><name>Craig Watcham</name></author><category term="Lambda" /><category term="DynamoDB" /><category term="S3" /><category term="CLI" /><category term="Storage Gateway" /><category term="aws" /><category term="SQS" /><summary type="html"><![CDATA[S3 has had event notifications since 2014 and for individual object notifications&nbsp;these events work well with Lambda, allowing you to perform an action on every object event in a bucket. It is harder to use this approach when you want to perform an action a limited number of times or at an aggregated bucket level. An example use case would be refreshing a dependency (like Storage Gateway RefreshCache) when you are expecting a large number of objects events in a bucket. Performing a relatively expensive action for every event is not practical or efficient in this case. This post provides a solution for aggregating these events using Lambda, DynamoDB, and SQS. Update (June/July 2020) Cache refresh can now be automated. The event aggregation approach below may still be useful depending on your requirements.]]></summary></entry><entry><title type="html">Working with 20+ node ElastiCache Memcached clusters</title><link href="/2018/05/18/working-with-20-node-elasticache.html" rel="alternate" type="text/html" title="Working with 20+ node ElastiCache Memcached clusters" /><published>2018-05-18T08:28:00+00:00</published><updated>2018-05-18T08:28:00+00:00</updated><id>/2018/05/18/working-with-20-node-elasticache</id><content type="html" xml:base="/2018/05/18/working-with-20-node-elasticache.html"><![CDATA[<p>ElastiCache Memcached <a
        href="https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_elasticache">limits</a> the
    number of nodes in a cluster to 20 by default. This limit can be <a
        href="https://aws.amazon.com/contact-us/elasticache-node-limit-request/">increased</a>&nbsp;with ElastiCache
    recommending against clusters larger than 50 nodes.</p>


<br />
The increased cluster node limits don't reflect in the console so even after the limit has been increased the maximum
number of nodes you can select when creating a new cluster is 20. Fortunately the CLI allows you to work around this and
you can tweak the examples in the ElastiCache cluster creation documentation to achieve your desired outcome. Creating a
cluster with more than 20 nodes using the <a
    href="https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/Clusters.Create.CLI.html">CLI</a>:<br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">$ aws elasticache create-cache-cluster
    --cache-cluster-id my-super-cluster --cache-node-type cache.t2.micro --engine memcached --engine-version 1.4.34
    --cache-parameter-group default.memcached1.4 --num-cache-nodes 30</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">{</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; "CacheCluster":
    {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheClusterId": "my-super-cluster",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheNodeType": "cache.t2.micro",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; "Engine":
    "memcached",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "EngineVersion": "1.4.34",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheClusterStatus": "creating",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "NumCacheNodes": 30,</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "PreferredMaintenanceWindow": "mon:02:30-mon:03:30",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "PendingModifiedValues": {},</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheSecurityGroups": [],</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheParameterGroup": {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "CacheParameterGroupName": "default.memcached1.4",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "ParameterApplyStatus": "in-sync",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "CacheNodeIdsToReboot": []</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    },</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheSubnetGroupName": "default",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "AutoMinorVersionUpgrade": true,</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "TransitEncryptionEnabled": false,</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "AtRestEncryptionEnabled": false</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span><br />
<div>
    <br />
</div>
<div>
    Adding nodes can be done via the console as it allows you to specify the number of nodes rather than selecting from
    a drop down. For completeness this can also be done via the <a
        href="https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/Clusters.AddNode.html#Clusters.AddNode.CLI">CLI</a>:
</div>
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">$ aws elasticache modify-cache-cluster
    --cache-cluster-id test-limits --num-cache-nodes 26 --apply-immediately</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">{</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; "CacheCluster":
    {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheClusterId": "test-limits",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "ConfigurationEndpoint": {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "Address": "test-limits.rftr8g.cfg.euw1.cache.amazonaws.com",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "Port": 11211</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    },</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheNodeType": "cache.t2.micro",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; "Engine":
    "memcached",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "EngineVersion": "1.4.34",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheClusterStatus": "modifying",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "NumCacheNodes": 6,</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "PreferredAvailabilityZone": "Multiple",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheClusterCreateTime": "2018-05-18T07:10:22.530Z",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "PreferredMaintenanceWindow": "sat:23:30-sun:00:30",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "PendingModifiedValues": {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "NumCacheNodes": 26</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    },</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheSecurityGroups": [],</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheParameterGroup": {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "CacheParameterGroupName": "default.memcached1.4",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "ParameterApplyStatus": "in-sync",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "CacheNodeIdsToReboot": []</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    },</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheSubnetGroupName": "default",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "AutoMinorVersionUpgrade": true,</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "SecurityGroups": [</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "SecurityGroupId": "sg-5814fd37",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "Status": "active"</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    ],</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "TransitEncryptionEnabled": false,</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "AtRestEncryptionEnabled": false</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span><br />
<div>
    <br />
</div>
<br />
And finally <a
    href="https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/Clusters.DeleteNode.html#Clusters.DeleteNode.CLI">removing
    nodes</a>&nbsp;via the CLI (note the spaces rather than commas between the node IDs):<br />
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">$ aws elasticache modify-cache-cluster
    --cache-cluster-id test-limits --num-cache-nodes 6 --cache-node-ids-to-remove 0007 0008 0009 0010 0011 0012 0013
    0014 0015 0016 0017 0018 0019 0020 0021 0022 0023 0024 0025 0026 --apply-immediately &nbsp;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">{</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; "CacheCluster":
    {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheClusterId": "test-limits",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "ConfigurationEndpoint": {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "Address": "test-limits.rftr8g.cfg.euw1.cache.amazonaws.com",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "Port": 11211</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    },</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheNodeType": "cache.t2.micro",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; "Engine":
    "memcached",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "EngineVersion": "1.4.34",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheClusterStatus": "modifying",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "NumCacheNodes": 26,</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "PreferredAvailabilityZone": "Multiple",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheClusterCreateTime": "2018-05-18T07:10:22.530Z",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "PreferredMaintenanceWindow": "sat:23:30-sun:00:30",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "PendingModifiedValues": {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "NumCacheNodes": 6,</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "CacheNodeIdsToRemove": [</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0007",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0008",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0009",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0010",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0011",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0012",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0013",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0014",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0015",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0016",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0017",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0018",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0019",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0020",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0021",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0022",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0023",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0024",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0025",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "0026"</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; ]</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    },</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheSecurityGroups": [],</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheParameterGroup": {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "CacheParameterGroupName": "default.memcached1.4",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "ParameterApplyStatus": "in-sync",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; "CacheNodeIdsToReboot": []</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    },</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "CacheSubnetGroupName": "default",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "AutoMinorVersionUpgrade": true,</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "SecurityGroups": [</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "SecurityGroupId": "sg-5814fd37",</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; "Status": "active"</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    ],</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "TransitEncryptionEnabled": false,</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; &nbsp; &nbsp;
    "AtRestEncryptionEnabled": false</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">&nbsp; &nbsp; }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span><br />
<div>
    <br />
</div>]]></content><author><name>Craig Watcham</name></author><summary type="html"><![CDATA[ElastiCache Memcached limits the number of nodes in a cluster to 20 by default. This limit can be increased&nbsp;with ElastiCache recommending against clusters larger than 50 nodes.]]></summary></entry><entry><title type="html">Extracting S3 bucket sizes using the AWS CLI</title><link href="/2017/11/16/extracting-s3-bucket-sizes-using-aws-cli.html" rel="alternate" type="text/html" title="Extracting S3 bucket sizes using the AWS CLI" /><published>2017-11-16T15:20:00+00:00</published><updated>2017-11-16T15:20:00+00:00</updated><id>/2017/11/16/extracting-s3-bucket-sizes-using-aws-cli</id><content type="html" xml:base="/2017/11/16/extracting-s3-bucket-sizes-using-aws-cli.html"><![CDATA[<p>A quick one liner for printing out the size (in bytes) of S3 StandardStorage buckets in your account (using bash)</p>

<br />
<span style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace; font-size: xx-small;"><br /></span>
<span style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace; font-size: x-small;">for name in
    $(aws s3api list-buckets --query 'Buckets[*].Name' --output text); do size=$(aws cloudwatch get-metric-statistics
    --namespace AWS/S3 --metric-name BucketSizeBytes --start-time $(date --date="yesterday" +%Y-%m-%d) --end-time $(date
    +%Y-%m-%d) --period 86400 --statistics Maximum --dimensions Name=BucketName,Value=$name
    Name=StorageType,Value=StandardStorage --query 'Datapoints[0].Maximum' | sed 's/null/0.0/' | cut -d. -f1); echo
    "$name,$size"; done</span><br />
<div>
    <br />
</div>
<div>
    <br />
</div>
<div>
    Some of the individual components may be independently useful, starting off with listing buckets:</div>
<div>
    <br />
</div>
<div>
    <span style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace; font-size: x-small;">aws s3api
        list-buckets --query 'Buckets[*].Name' --output text</span>
</div>
<div>
    <span
        style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace; font-size: x-small;"><br /></span>
</div>
<span style="font-family: inherit;">Pretty straight forward, uses the <a
        href="http://docs.aws.amazon.com/cli/latest/reference/s3api/index.html">s3api</a>&nbsp;in the CLI to list
    buckets returning only their names in text format.</span><br />
<span style="font-family: inherit;"><br /></span>
<br />
To get the bucket sizes we are actually querying CloudWatch (using <a
    href="http://docs.aws.amazon.com/cli/latest/reference/cloudwatch/get-metric-statistics.html">get-metric-statistics</a>)
which provides bucket size and object count <a
    href="http://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/s3-metricscollected.html">metrics for
    S3</a>:<br />
<br />
<span style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace; font-size: x-small;">aws cloudwatch
    get-metric-statistics --namespace AWS/S3 --metric-name BucketSizeBytes --start-time $(date --date="yesterday"
    +%Y-%m-%d) --end-time $(date +%Y-%m-%d) --period 86400 --statistics Maximum --dimensions Name=BucketName,Value=$name
    Name=StorageType,Value=StandardStorage --query 'Datapoints[0].Maximum' | sed 's/null/0.0/' | cut -d.
    -f1</span><br />
<br />
Most of this is fairly straight forward some of the parameters worth explaining further:<br />
--start-time: we are setting the start date to yesterday and formatting it in yyyy-mm-dd format, this ensures we get at
least one data point containing the bucket size<br />
--period: 86400 = 1 day as the bucket size metric is only published once a day (at 00:00:00)<br />
--query: we are only interested in the the actual value for one of the metric datapoints<br />
<br />
The sed and cut commands are just to clean up formatting, if the bucket is empty the CloudWatch metric request will
return null so we replace null with 0.0. The bucket size metric value will always end in .0 so we truncate it using cut
(yes there are other ways of doing this).<br />
<br />
If you are only wanting to check the size of a single bucket or the bucket size on a specific date you can use a simpler
version:<br />
<br class="Apple-interchange-newline" />
<span style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace; font-size: x-small;">aws cloudwatch
    get-metric-statistics --namespace AWS/S3 --metric-name BucketSizeBytes --start-time 2017-11-15 --end-time 2017-11-16
    --period 86400 --statistics Maximum --dimensions Name=BucketName,Value=MY_BUCKET_NAME
    Name=StorageType,Value=StandardStorage --query 'Datapoints[0].Maximum'</span>]]></content><author><name>Craig Watcham</name></author><category term="CloudWatch" /><category term="S3" /><category term="CLI" /><category term="aws" /><summary type="html"><![CDATA[A quick one liner for printing out the size (in bytes) of S3 StandardStorage buckets in your account (using bash)]]></summary></entry></feed>