<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Colin Duggan</title>
    <description>The latest articles on DEV Community by Colin Duggan (@cduggn).</description>
    <link>https://dev.to/cduggn</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F976512%2F347e9af5-0b84-4184-8e2b-e7f96e85aef1.png</url>
      <title>DEV Community: Colin Duggan</title>
      <link>https://dev.to/cduggn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cduggn"/>
    <language>en</language>
    <item>
      <title>A CLI tool to visualize AWS cost and usage data</title>
      <dc:creator>Colin Duggan</dc:creator>
      <pubDate>Mon, 27 Feb 2023 11:58:36 +0000</pubDate>
      <link>https://dev.to/aws-builders/a-cli-tool-to-visualize-aws-cost-and-usage-data-55j2</link>
      <guid>https://dev.to/aws-builders/a-cli-tool-to-visualize-aws-cost-and-usage-data-55j2</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/cduggn/ccExplorer" rel="noopener noreferrer"&gt;ccExplorer&lt;/a&gt; is a CLI tool I created to make it easier to surface AWS cost and usage data in human readable formats (like table, csv, chart, and HTML)and which can be used to easily sort results in descending order by cost or date. &lt;/p&gt;

&lt;p&gt;It doesn't replace the cost explorer capabilities provided by the &lt;a href="https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ce/get-cost-and-usage.html" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt; but instead focuses on providing more convenient output formats which enables the user to quickly reason about data without having to traverse a YAML or JSON tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  CostAndUsage Queries
&lt;/h2&gt;

&lt;p&gt;Where possible &lt;code&gt;ccExplorer&lt;/code&gt; uses the same nomenclature as the &lt;code&gt;AWS CLI&lt;/code&gt; when naming command line options. Queries use the &lt;code&gt;groupBy&lt;/code&gt; or &lt;code&gt;filterby&lt;/code&gt; flags to specify the dimensions and/or tags used when fetching cost and usage data and when outputting results. &lt;/p&gt;

&lt;p&gt;By default results are sorted by cost in descending order. The following command exports results to stdout and groups by SERVICE and OPERATION. The &lt;code&gt;-l&lt;/code&gt; flag is used to exclude discount, credit and refund information to get a true sense of costs. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;ccExplorer get aws -g DIMENSION=SERVICE,DIMENSION=OPERATION -p stdout -m MONTHLY -l&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F08e51jz1j5rhdd1ppofz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F08e51jz1j5rhdd1ppofz.jpg" alt=" " width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Allocation Tags
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ccExplorer&lt;/code&gt; makes it easy to surface costs associated with custom cost allocation tags. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Surface S3 Bucket Costs
&lt;code&gt;ccexplorer get aws -g DIMENSION=SERVICE,TAG=BucketName -f SERVICE="Amazon Simple Storage Service" -l&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Surface DynamoDB Table Costs
&lt;code&gt;ccexplorer get aws -g DIMENSION=SERVICE,TAG=DynamoTableName -f SERVICE="Amazon DynamoDB" -l&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Print Formats
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ccExplorer&lt;/code&gt; supports output formats including CSV, chart, and HTML. HTML reports are generated using &lt;a href="https://openai.com/api/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt; and the &lt;code&gt;GPT-3&lt;/code&gt; completions API. In order to use this feature the end user must supply an OPEN AI API KEY. The resulting HTML report includes the top 10 costs in descending order. Results also include cost optimization hints for each of the highest costing resources. Note: The HTML output type does not support queries which include the LINKED_ACCOUNT dimension type, which would require sending sensitive account information to GPT-3. The HTML output type is still very experimental and subject to changes. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jctzqu28rq93x0sgkxl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jctzqu28rq93x0sgkxl.jpg" alt=" " width="800" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;At the outset I was hoping to build something that could quickly surface high level data about the resources I was using. I quickly discovered that &lt;code&gt;ccExplorer&lt;/code&gt; was also deepening my knowledge across other facets of AWS including usage and operation types. I was able to pinpoint the cost implication of specific resources including S3 buckets and DynamoDB tables. This has helped me to better reason about the workloads I manage and ultimately eliminate inefficiencies. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;ccExplorer&lt;/code&gt; is still very much a work in progress but I hope you find it useful. Suggestions and feedback are welcome. &lt;/p&gt;

</description>
      <category>critique</category>
      <category>sideprojects</category>
      <category>mentorship</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Using AWS EventBridge to Handle Stripe Webhook Events</title>
      <dc:creator>Colin Duggan</dc:creator>
      <pubDate>Fri, 25 Mar 2022 18:09:12 +0000</pubDate>
      <link>https://dev.to/aws-builders/using-aws-eventbridge-to-handle-stripe-webhook-events-50p1</link>
      <guid>https://dev.to/aws-builders/using-aws-eventbridge-to-handle-stripe-webhook-events-50p1</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjopwvzpulruw7r15ine.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjopwvzpulruw7r15ine.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this article, I want to walk through the process of integrating a Stripe asynchronous subscription event with AWS EventBridge.&lt;/p&gt;

&lt;p&gt;In a nutshell, EventBridge is a serverless event bus which hugely simplifies the process of building event-driven applications. This simplification is made possible with AWS taking care of event ingestion, delivery, security, authorization, and error handling.&lt;/p&gt;

&lt;p&gt;What about Stripe? It is a fully integrated payments platform offering a rich set of APIs, a client library available in a range of languages, webhooks and a CLI which greatly simplifies life for a developer.&lt;/p&gt;

&lt;p&gt;There are still several moving parts to consider with this serverless example With AWS CDK much of the complexity can be offloaded (GitHub link for sample project included later). The AWS stack includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An API Gateway endpoint acting as a target for the Stripe webhook event with integration request type of LAMBDA_PROXY.&lt;/li&gt;
&lt;li&gt;A Lambda function which verifies the Stripe webhook request signature before programmatically sending the EventBridge event. The handler function accepts events of type APIGatewayProxyRequest.&lt;/li&gt;
&lt;li&gt;The EventBridge event bus with supporting rule. When EventBridge receives an event matching the pattern defined by the rule it will send to the source supplied in the event pattern.&lt;/li&gt;
&lt;li&gt;A second Lambda function which acts as the target for your EventBridge rule. The handler function accepts events of type CloudWatchEvent.&lt;/li&gt;
&lt;li&gt;And finally, a DynamoDB table updated as a consequence of the target functions being invoked.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 1 Creating the Event Bus
&lt;/h3&gt;

&lt;p&gt;You have options when it comes to creating an event bus. Use the default event bus which handles all events emitted by AWS services, create a partner event bus described as being suitable for integrating with SaaS partners, or finally the option I have chosen which is to create a custom event bus which will receive events from the services and applications we create. AWS CDK reduces setup to a few lines of code(using Python CDK).&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzjtzeuavfxm3ng0sxydc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzjtzeuavfxm3ng0sxydc.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;L1 Create event bus. L3 Create rule and associate with event bus, the default event bus will be used if one is not specified. L9 Define rule event pattern. L14 Add the target EventBridge will send the event to when a rule is emitted matching the pattern you have defined.&lt;br&gt;
In this situation, we are not attempting to capture events emitted by the native AWS services in our account. If that was the case the default event bus must be used. When creating our rule we also have the option of choosing a target from one of the almost 20 AWS resources EventBridge can emit events to ranging from SQS to RedShift. We can also specify a Dead-Letter queue which EventBridge can use to send unsuccessfully delivered events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 Creating a Stripe Webhook
&lt;/h3&gt;

&lt;p&gt;I will be using the Stripe developer dashboard to create a test webhook, you can also do all this through the API. Through the web dashboard, use the API Gateway endpoint along with the customer.subscription.created event type to complete setup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkbikdizmg7c34c8c6py1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkbikdizmg7c34c8c6py1.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 Creating Lambda Handlers
&lt;/h3&gt;

&lt;p&gt;The first Lambda function we create will handle the webhook request via our API Gateway endpoint. Some code is excluded from the snippet below however the main thrust of this function is to verify the incoming Stripe request signature. The signature is included in the incoming requests Stripe-Signature header and allows you to verify the request without requiring a third party. Signature verification is done by first downloading the endpoint's secret which we subsequently place in AWS Secrets Manager. Once verification is successful, the function creates an event and emits it to EventBridge.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvqy83ew649ayii7g7i9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvqy83ew649ayii7g7i9a.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second Lambda function will act as the target for the event bus rule we have defined earlier and which will subsequently write to DynamoDB. It accepts events of type CloudWatchEvent. Details of the new customer subscription are extracted from the CloudWatchEvent object and written to the database table.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ohvvou8aymbba57jxym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ohvvou8aymbba57jxym.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 Invoke the Test Webhook
&lt;/h3&gt;

&lt;p&gt;Through the Stripe developer dashboard invoke the new webhook endpoint selecting the customer.subscription.created event type.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zkpxckpkle7mqf6grfx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zkpxckpkle7mqf6grfx.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 Verify Rule Metrics and Database Update
&lt;/h3&gt;

&lt;p&gt;CloudWatch metrics for your event bus rule allow you to quickly sanity check whether the rule was invoked successfully or not.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdkeg25seaaxawsyg56ob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdkeg25seaaxawsyg56ob.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, verify the database has been updated accordingly with the new customer ID.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxf6thql9qqtx4pq8hlh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxf6thql9qqtx4pq8hlh.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Conclusion&lt;br&gt;
It has been suggested that AWS EventBridge is the biggest thing to happen for serverless since Lambda. This is only my first foray with EventBridge so I won't wax lyrical just yet however I can certainly see where much of the excitement stems from. With an ever-growing number of integration options both AWS native services and SaaS providers, advanced monitoring and analytics, and a simple API for generating and emitting custom events it really does offer a management and coordination layer to tie all of your serverless modules together. I'm looking forward to watching this technology grow and hopefully have the opportunity to share more insights.&lt;br&gt;
A full code example can be found over on GitHub: &lt;a href="https://github.com/cdugga/eventbridge-stripe-go" rel="noopener noreferrer"&gt;https://github.com/cdugga/eventbridge-stripe-go&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>stripe</category>
    </item>
    <item>
      <title>Compiling a Go Program</title>
      <dc:creator>Colin Duggan</dc:creator>
      <pubDate>Mon, 07 Mar 2022 21:38:22 +0000</pubDate>
      <link>https://dev.to/cduggn/go-build-options-570e</link>
      <guid>https://dev.to/cduggn/go-build-options-570e</guid>
      <description>&lt;p&gt;The Go command provides a number of options allowing you to &lt;a href="https://pkg.go.dev/cmd/go#:~:text=go%20run%20%5Bbuild%20flags%5D%20%5B%2Dexec%20xprog%5D%20package%20%5Barguments...%5D"&gt;run&lt;/a&gt;, &lt;a href="https://pkg.go.dev/cmd/go#:~:text=go%20build%20%5B%2Do%20output%5D%20%5Bbuild%20flags%5D%20%5Bpackages%5D"&gt;build &lt;/a&gt; or &lt;a href="https://pkg.go.dev/cmd/go#:~:text=go%20install%20%5Bbuild%20flags%5D%20%5Bpackages%5D"&gt;install&lt;/a&gt; your Go program. The following diagram illustrates these options at a high-level. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0ZrmLPHR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fgi4vsahb6kcv3dt0ttt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0ZrmLPHR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fgi4vsahb6kcv3dt0ttt.png" alt="Image description" width="880" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Running a Go Program
&lt;/h1&gt;

&lt;p&gt;Go is a compiled language. Source code belonging to a Go project must be run through a compiler. The compiler generates a binary which can then be used to run the program on a target OS. &lt;/p&gt;

&lt;h2&gt;
  
  
  Phases of the compiler
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Scanner: Converts source code to tokens for use by the parser.&lt;/li&gt;
&lt;li&gt;Parser: Converts tokens into an abstract syntax tree to be used by code generation&lt;/li&gt;
&lt;li&gt;Code generation: Compiles and install the packages to &lt;code&gt;$GOPATH/bin&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Options for Compile and Run
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go run&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A command used for convenience allowing you to compile and run a program. While it does generate a temporary binary to run the program, that binary is deleted once execution completes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go build&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A command which compiles the packages named by the import paths, along with their dependencies, into a binary in the current working directory. It does not install the resulting binary.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go install&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A command which compiles the command in a temporary directory and moves the generated binary to the default target &lt;code&gt;$GOPATH/bin&lt;/code&gt; alongside third-party dependencies. If a value has been provided for the $GOBIN environment variable, then it will install to that location instead.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Related Environment Variables
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GOBIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;If the &lt;code&gt;GOBIN&lt;/code&gt; environment variable is set, commands are installed to the directory it names instead of the default &lt;code&gt;$GOPATH/bin&lt;/code&gt; folder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GOPATH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required to resolve import statements. Defaults to the user's home director, &lt;code&gt;$HOME/go&lt;/code&gt; on Unix or &lt;code&gt;%USERPROFILE%/go&lt;/code&gt; on Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;GOPATH&lt;/code&gt; directory structure
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;GOPATH&lt;/code&gt; is a list of directory trees containing source code.  It is consulted to resolve imports that cannot be found in the standard Go tree. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;src - Holds source code. The path below &lt;code&gt;src&lt;/code&gt; indicates the import path. &lt;/li&gt;
&lt;li&gt;pkg - Package directory holds installed package objects. Each target OS and architecture pair has its own subdirectory&lt;/li&gt;
&lt;li&gt;bin - Holds compiled commands. Each command is named from its source directory, but only the final element and not the entire path. The command prefix is stripped, so you can add &lt;code&gt;$GOPATH/bin&lt;/code&gt; to your path to get all installed commands. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Useful Commands
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go clean -cache&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removes entire go build cache.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go clean --modcache&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removes entire module download cache. Including unpacked source code of versioned dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go tool fix&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runs the &lt;code&gt;go fix&lt;/code&gt; command on packages named by the import paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go mod vendor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copies all third party dependencies to vendor directory in your project root. The go command loads packages from vendor directory instead of downloading modules from their sources into the module cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go env&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Location of cached files from &lt;code&gt;go build&lt;/code&gt;command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go mod tidy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Loads packages in main module and transitive dependencies recursively. Does not consider packages in the main module in directories named &lt;code&gt;testdata&lt;/code&gt;or with names that start with '.' or '_' unless those packages are explicitly imported by other packages.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  References
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://go.dev/doc/asm"&gt;A Quick Guide to Go's Assembler&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
    </item>
    <item>
      <title>Middleware in Go</title>
      <dc:creator>Colin Duggan</dc:creator>
      <pubDate>Wed, 16 Feb 2022 19:31:13 +0000</pubDate>
      <link>https://dev.to/cduggn/middleware-in-go-5019</link>
      <guid>https://dev.to/cduggn/middleware-in-go-5019</guid>
      <description>&lt;p&gt;Middleware is a powerful idea that allows us to decompose complex systems into layers of abstractions. When we discuss middleware in the context of Go we are referring to the ability of some self-contained code (third-party or other) to hook into a server's request/response processing before or after its handler is invoked. This is a powerful concept which allows us to separate cross-cutting concerns such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Observability&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Middleware code typically does one thing and then passes the request to the next layer or final processing before returning to the client. This results in code that is more modular, easier to maintain and reusable.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;HTTP request processing in Go is handled by two things, ServeMux and handlers. The ServeMux, a request multiplexer, simplifies the association between URLs and handlers by matching the URL of incoming requests against predefined patterns and then forwarding to the appropriate handler. Using middleware we can interrupt this flow and instruct our middleware function to act before or after the handler function has executed. We can also dictate whether to apply the middleware to all request or just those matching a particular pattern.&lt;/p&gt;

&lt;p&gt;The following snippet represents a typical example of how we would use a ServerMux to associate an incoming request URL with a specific handler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;mux&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewServeMux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usersList&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;usersList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="c"&gt;// fetch users&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our handler of type func(w http.ResponseWriter, req *http.Request) doesn't satisfy the http.Handler interface and therefore cannot be passed directly to mux.Handle.&lt;/p&gt;

&lt;p&gt;To resolve this issue and satisfy the http.Handler contract we use the http.HandlerFunc(usersList) expression. Note this is not a function call but instead a conversion. The HandlerFunc type has methods and satisfies the http.Handler interface. Its ServeHTTP method calls our underlying function, therefore acting as an adapter. We can now use our usersList function as a http handler now that we are satisfying the http.Handler interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// The HandlerFunc type is an adapter to allow the use of&lt;/span&gt;
&lt;span class="c"&gt;// ordinary functions as HTTP handlers. If f is a function&lt;/span&gt;
&lt;span class="c"&gt;// with the appropriate signature, HandlerFunc(f) is a&lt;/span&gt;
&lt;span class="c"&gt;// Handler that calls f.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c"&gt;// ServeHTTP calls f(w, r).&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding Middleware
&lt;/h2&gt;

&lt;p&gt;Armed with the knowledge of how a typical request handler is constructed we can now quite easily extend our example to include a middleware capability. Once again we are relying on the http.HandlerFunc adapter type to allow us wrap our middleware function so it implements the http.Handler interface. This time we also want the ability to chain handlers together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;middlewareFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;// Some logic …&lt;/span&gt;
  &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The anonymous inner function closes over the next variable allowing us to effectively chain or transfer control from this handler to the next by calling ServeHTTP. Because the inner function has the signature func(rw http.ResponseWriter, r *http.Request) we can convert it to a HandlerFunc type using the http.HandlerFunc adapter.&lt;/p&gt;

&lt;p&gt;The signature of our middleware function can be replicated by other middlewares to create arbitrarily long chains. Control can also be stopped at any time by simply issuing a return from the middleware handler instead of calling the next handler.&lt;/p&gt;

</description>
      <category>go</category>
      <category>programming</category>
    </item>
    <item>
      <title>Package Management in Go</title>
      <dc:creator>Colin Duggan</dc:creator>
      <pubDate>Wed, 16 Feb 2022 09:18:15 +0000</pubDate>
      <link>https://dev.to/cduggn/package-management-in-go-2hjl</link>
      <guid>https://dev.to/cduggn/package-management-in-go-2hjl</guid>
      <description>&lt;h2&gt;
  
  
  Go Package Management
&lt;/h2&gt;

&lt;p&gt;Go takes a semi-decentralized approach to package management, allowing any git repo to be used as a module package system. Modules can be downloaded directly from their source control servers or alternatively through a module proxy which performs a simple forwarding of requests to the appropriate source. The following diagram is a high-level take on what happens when go get attempts to resolve a package or module.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc20xseigz99dca3uxr7n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc20xseigz99dca3uxr7n.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Module Proxy
&lt;/h2&gt;

&lt;p&gt;The module proxy is an HTTP server which implements the &lt;a href="https://go.dev/ref/mod#goproxy-protocol" rel="noopener noreferrer"&gt;module proxy protocol&lt;/a&gt;. It responds to GET requests matching a particular pattern, See examples below. The module proxy ensures changes or downtime to a module origin server will not bubble up and impact your build process. The module proxy can also act as a gatekeeper, restricting access to modules which have unsuitable licences or contain security vulnerabilities. Module proxies can also provide faster dependency resolution by caching previously fetched modules, which can then be used to serve future requests. A module's dependencies are defined in the following files:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go.mod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defines the module name, the version of Go with which to build the project and the list of dependencies.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go.sum&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contains cryptographic hashes of the module's direct and indirect dependencies.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Module Proxy Protocol
&lt;/h3&gt;

&lt;p&gt;Since version 1.13, &lt;a href="//proxy.golang.org"&gt;proxy.golang.org&lt;/a&gt; is the default module proxy for fetching modules.&lt;/p&gt;

&lt;p&gt;It's possible to build your own module proxy, which consists of an HTTP server and REST API that satisfies the &lt;a href="https://go.dev/ref/mod#goproxy-protocol" rel="noopener noreferrer"&gt;GOPROXY protocol&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Go Get Command
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;go get&lt;/code&gt; command follows this protocol when attempting to resolve a package path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search modules in the current project build list (go.mod) including all build lists of transitive dependencies, looking for modules with paths which are prefixes of the requested package path.&lt;/li&gt;
&lt;li&gt;If one module in the build list contains the required package, then that module will be used.&lt;/li&gt;
&lt;li&gt;If none of the modules in the searched build lists provide the package or if two or more modules provide the requested package, then &lt;code&gt;go get&lt;/code&gt; will report an error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When &lt;code&gt;go get&lt;/code&gt; is resolving a new module for a package path , it uses the &lt;code&gt;GOPROXY&lt;/code&gt; environment variable to determine whether modules will be downloaded directly, through a module proxy or some other private mirror. &lt;code&gt;GOPROXY&lt;/code&gt; accepts a list of proxy URLs separated by either commas or pipes. The separator used in this list determines how the &lt;code&gt;go get&lt;/code&gt; command will fallback in different scenarios:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;
&lt;code&gt;GOPROXY&lt;/code&gt; Separator Type&lt;/th&gt;
&lt;th&gt;Behaviour&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;List separated by commas&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;go get&lt;/code&gt; will fallback to the next URL in the event of a 404 or 410 HTTP response. All other response codes are treated as terminal.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List separated by pipes&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;go get&lt;/code&gt; will fallback to the next URL in the proxy list in the event of any HTTP or non-HTTP error.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;go get&lt;/code&gt; command visits each proxy in the list sequentially until it receives a successful response or error.&lt;/p&gt;

&lt;h3&gt;
  
  
  GoProxy Environment Variable Options
&lt;/h3&gt;

&lt;p&gt;The default configuration for  the &lt;code&gt;GOPROXY&lt;/code&gt; environment variable is &lt;code&gt;GOPROXY=proxy.golang.org,direct&lt;/code&gt; which effectively tells the &lt;code&gt;go get&lt;/code&gt; command to first attempt module retrieval using the module mirror and if that approach is unsuccessful, fallback to the approach of connecting directly to the module origin repository. A number of other environment variables can also impact how &lt;code&gt;go get&lt;/code&gt; will attempt to resolve dependencies. These variables are listed here.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;
&lt;code&gt;GPROXY&lt;/code&gt; keywords&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;direct&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;go get&lt;/code&gt; will download directly from version control repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;Disallows downloading from any source&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Go Module Proxy Request Patterns
&lt;/h3&gt;

&lt;p&gt;A GET request for a particular module will follow the pattern: &lt;code&gt;https://$base/$module/@v/$version.zip&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Where:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request portion&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Sample value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;$base&lt;/td&gt;
&lt;td&gt;The path portion of the proxy URL&lt;/td&gt;
&lt;td&gt;proxy.golang.org&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$module&lt;/td&gt;
&lt;td&gt;Module path&lt;/td&gt;
&lt;td&gt;go.uber.org/zap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$version&lt;/td&gt;
&lt;td&gt;Version&lt;/td&gt;
&lt;td&gt;v1.21.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The resulting GET request will download the module and dependencies from: &lt;code&gt;https://proxy.golang.org/go.uber.org/zap/@v/v1.21.0.zip&lt;/code&gt;. The 'go get' and 'go mod tidy' commands abstract the details of this from the developer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Go Module Proxy Response Codes
&lt;/h3&gt;

&lt;p&gt;The type of separator used in the &lt;code&gt;GOPROXY&lt;/code&gt; environment variable will determine if the &lt;code&gt;go get&lt;/code&gt; command will fall back to the next option in the list or fail completely. See section on 'go get'.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Proxy Response Code&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;Redirects are followed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;td&gt;Not found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;403&lt;/td&gt;
&lt;td&gt;Forbidden - Private proxies can choose to prevent access to modules based on unsuitable licenses or security vulnerabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;410&lt;/td&gt;
&lt;td&gt;Module not available on the proxy server but might be available elsewhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4xx &amp;amp; 5xx&lt;/td&gt;
&lt;td&gt;Treated as errors&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Go Environment Variables Which Impact Module Retrieval
&lt;/h3&gt;

&lt;p&gt;If set with specific values, &lt;code&gt;GOPRIVATE&lt;/code&gt; and &lt;code&gt;GONOPROXY&lt;/code&gt; can impact the behaviour of &lt;code&gt;GOPROXY&lt;/code&gt;, by preventing specific modules from being downloaded through the proxy.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment Variable&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GOPRIVATE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Comma separated list of module prefixes considered private. If set, this acts as a default value for &lt;code&gt;GONOSUMDB&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GONOPROXY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Comma separated list of module prefixes, which should always be downloaded directly from source. If set, this acts as a default value for &lt;code&gt;GOPRIVATE&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GONOSUMDB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Comma separated list of module prefixes which the &lt;code&gt;go&lt;/code&gt; command should not verify checksums using the checksum database.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Verifying Modules
&lt;/h3&gt;

&lt;p&gt;After downloading a .mod or .zip file, &lt;code&gt;go get&lt;/code&gt; computes a cryptographic hash and checks that it matches a hash in the main modules &lt;code&gt;go.sum&lt;/code&gt;. If the hash is not present in &lt;code&gt;go.sum&lt;/code&gt; then the go command retrieves it from the &lt;a href="https://go.dev/ref/mod#checksum-database" rel="noopener noreferrer"&gt;checksum database&lt;/a&gt;. The checksum database, located at &lt;code&gt;sum.golang.org&lt;/code&gt;, allows for global consistency and reliability for all publicly available modules. It also ensures bits associated with specific module versions do not change from one day to the next. &lt;/p&gt;

&lt;h3&gt;
  
  
  Module Caching
&lt;/h3&gt;

&lt;p&gt;The 'go get' command will cache modules downloaded from both module proxies and directly from a module's source repository in &lt;code&gt;$GOPATH/pkg/mod/cache/download&lt;/code&gt;. This local cache layout is identical to the proxy URL space. &lt;/p&gt;

&lt;h3&gt;
  
  
  Module Graph
&lt;/h3&gt;

&lt;p&gt;Module graph pruning was introduced in version 1.17. See &lt;a href="https://go.dev/ref/mod#graph-pruning" rel="noopener noreferrer"&gt;go.dev&lt;/a&gt; for more information. Module pruning uses &lt;a href="https://go.dev/ref/mod#minimal-version-selection" rel="noopener noreferrer"&gt;Minimal version selection&lt;/a&gt; to select a set of module versions to use when building packages. It operates on a directed graph of modules, specified in go.mod. Each vertex in the graph represents a module version. Each edge represents a minimum required version of a dependency, specified using a require directive. The graph may be modified by exclude and replace directives in go.mod. MVS produces the build list as an output. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go list -m all&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prints a modules build list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go mod graph&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prints the edges of the graph, one per line. Each line contains a module version and one of its dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go mod graph -go &amp;lt;some-version&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Identical to previous command however it prints based on how the selected go version would interrupt the graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go mod verify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checks dependencies of the main module stored in module cache have not been modified since they were downloaded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go mod why&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows the shortest path in the import graph from the main module to each of the listed packages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Full details can be found at &lt;a href="https://github.com/cduggn/gopkg" rel="noopener noreferrer"&gt;understanding-go-get&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>programming</category>
    </item>
    <item>
      <title>Working with EKS: Using IAM and native K8s service accounts to access AWS S3</title>
      <dc:creator>Colin Duggan</dc:creator>
      <pubDate>Sat, 05 Feb 2022 13:40:32 +0000</pubDate>
      <link>https://dev.to/aws-builders/working-with-eks-using-iam-and-native-k8s-service-accounts-to-access-aws-s3-3e20</link>
      <guid>https://dev.to/aws-builders/working-with-eks-using-iam-and-native-k8s-service-accounts-to-access-aws-s3-3e20</guid>
      <description>&lt;p&gt;This post looks at how a native K8s service account can be used along with an IAM role as a strategy for managing AWS credentials for applications running in EKS. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9d6pkmu0s0g88zzd6oww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9d6pkmu0s0g88zzd6oww.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This use case arises when an application running in an EKS pod wants to use the AWS SDK to call some AWS service. Requests must be signed with an AWS credential. The SDK will use the default credential provider chain to identify a suitable credential to use. In this scenario, we want to leverage the &lt;a href="https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#:~:text=Web%20Identity%20Token%20credentials%20from%20the%20environment%20or%20container." rel="noopener noreferrer"&gt;AWS_WEB_IDENTITY_TOKEN_FILE credential&lt;/a&gt;. The following paragraphs look at the steps required to surface that credential, so we can connect to and read from an S3 bucket. &lt;/p&gt;

&lt;p&gt;We will take advantage of EKS's built-in support for using AWS IAM user and roles as entities for authenticating against a cluster. The first step is to create an IAM OIDC Identity provider using the OpenID Connect provider URL, which is automatically provided during cluster creation. &lt;/p&gt;

&lt;h2&gt;
  
  
  Create IAM OIDC provider for the EKS cluster
&lt;/h2&gt;

&lt;p&gt;The OpenID Connect provider URL is available under the EKS dashboard under tabs: Configuration - Details&lt;br&gt;
The provider URL can be copied and used to create a new Identity Provider in IAM. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provider Type - choose OpenID Connect&lt;/li&gt;
&lt;li&gt;Provider URL - paste the OIDC URL from your cluster&lt;/li&gt;
&lt;li&gt;Audience - sts.amazonaws.com
If a provider already exists matching the name of your clusters' provider URL, then there is no need to continue with this step. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create IAM Role for the EKS Service Account
&lt;/h2&gt;

&lt;p&gt;In this step, we create an IAM policy which specifies the permissions our container will need in order to connect to and read from an S3 bucket. Once the policy is created, we require a new role against which the policy will be attached. The following snippet provides a sample policy file which grants permissions to read from a specific S3 bucket. &lt;/p&gt;

&lt;p&gt;IAM Policy&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="pi"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Version"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2012-10-17"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Statement"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;
    &lt;span class="pi"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Effect"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Allow"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Action"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3:GetObject"&lt;/span&gt;
      &lt;span class="pi"&gt;],&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Resource"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:s3:::my-s3-bucket/*"&lt;/span&gt;
      &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="pi"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Create a new IAM role. Attach the previously described IAM policy. Edit the trust policy and add the following statement;&lt;/p&gt;

&lt;p&gt;IAM Role for Service Account&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="pi"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Version"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2012-10-17"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Statement"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;
        &lt;span class="pi"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Effect"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Allow"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Principal"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Federated"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:iam::&amp;lt;aws&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;account&amp;gt;:oidc-provider/oidc.eks.&amp;lt;region&amp;gt;.amazonaws.com/id/&amp;lt;oidc&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;provider&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID&amp;gt;"&lt;/span&gt;
            &lt;span class="pi"&gt;},&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Action"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sts:AssumeRoleWithWebIdentity"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Condition"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;StringEquals"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oidc.eks.&amp;lt;region&amp;gt;.amazonaws.com/id/&amp;lt;oidc&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;provider&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID&amp;gt;:sub"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system:serviceaccount:&amp;lt;cluster&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;namespace&amp;gt;:&amp;lt;service&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;account&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;name&amp;gt;"&lt;/span&gt;
                &lt;span class="pi"&gt;}&lt;/span&gt;
            &lt;span class="pi"&gt;}&lt;/span&gt;
        &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="pi"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;OIDC provider ID = OIDC provider URL for your cluster&lt;/li&gt;
&lt;li&gt;Region =  AWS region where your cluster is deployed&lt;/li&gt;
&lt;li&gt;Cluster namespace = Namespace where your EKS service account lives&lt;/li&gt;
&lt;li&gt;Service account name = Associate the Role with a service account in our EKS cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Detailed instructions for creation of both IAM Policy and Role are available at &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html" rel="noopener noreferrer"&gt;EKS Documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Associate IAM Role with K8s Service Account
&lt;/h2&gt;

&lt;p&gt;Add the following annotation to your k8s service account definition to associate the newly created IAM Role&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::&amp;lt;aws account&amp;gt;:role/eks-s3-role
    eks.amazonaws.com/sts-regional-endpoints: "true"
  name: my-serviceaccount
  namespace: my-cluster-namespace



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The service account is also annotated to use the AWS Security Token Service &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/specify-service-account-role.html#:~:text=(Optional)%20Use%20the,regional%2Dendpoints%3Dtrue" rel="noopener noreferrer"&gt;Regional endpoint&lt;/a&gt; rather than the global endpoint to reduce latency, build in redundancy and increase session token validity. &lt;/p&gt;

&lt;p&gt;The service account is associated with pods running in your namespace through the _serviceAccountName _directive in your deployment definition. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

spec:
      serviceAccountName: my-serviceaccount
      containers:
      ....


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The next time EKS attempts to schedule a pod with this definition, it will trigger a mutating webhook, which is responsible for writing the following environment variables to our pod&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS_WEB_IDENTITY_TOKEN_FILE &lt;/li&gt;
&lt;li&gt;AWS_ROLE_ARN &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can verify the environment variables exist by running the command:&lt;br&gt;
&lt;code&gt;kubectl exec -n &amp;lt;namespace&amp;gt; &amp;lt;pod-name&amp;gt;-- env  | grep AWS&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing S3 Access
&lt;/h2&gt;

&lt;p&gt;Once we have verified the presence of the AWS_WEB_IDENTITY_TOKEN_FILE and AWS_ROLE_ARN environment variables, our pod is ready to connect to and read from S3. The following Typescript snippet illustrates a simple example which fetches the names of all the S3 buckets in our account. It uses the &lt;a href="https://github.com/aws/aws-sdk-js-v3" rel="noopener noreferrer"&gt;AWS SDK for JavaScript V3&lt;/a&gt;&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
  &lt;span class="nx"&gt;ListBucketsCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
  &lt;span class="nx"&gt;ListBucketsCommandOutput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
  &lt;span class="nx"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-sdk/client-s3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;listBuckets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;bucketNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;&lt;br&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3Client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;br&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eu-west-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;br&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ListBucketsCommandOutput&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;br&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
        &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ListBucketsCommand&lt;/span&gt;&lt;span class="p"&gt;({}));&lt;/span&gt;&lt;br&gt;
        &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Buckets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
          &lt;span class="nx"&gt;bucketNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;&lt;br&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;br&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;bucketNames&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Summary&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;By using IAM Roles with k8s native service accounts, we obviate the need to provide extended permissions to the EKS node IAM Role. This allows us to follow the principle of least privilege. We can scope IAM permissions for each service account, ensuring containers only have access to those privileges needed to complete its task.  &lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Creating an IAM OIDC provider &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;EKS Workshop IAM Roles for Service Accounts &lt;a href="https://www.eksworkshop.com/beginner/110_irsa/oidc-provider/" rel="noopener noreferrer"&gt;https://www.eksworkshop.com/beginner/110_irsa/oidc-provider/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create IAM Role for Service Account &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html&lt;/a&gt; &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Associate IAM role to Service Account &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/specify-service-account-role.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/eks/latest/userguide/specify-service-account-role.html&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS SDK for JavaScript v3 &lt;a href="https://github.com/aws/aws-sdk-js-v3" rel="noopener noreferrer"&gt;https://github.com/aws/aws-sdk-js-v3&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Hosting a static WordPress Site on AWS S3</title>
      <dc:creator>Colin Duggan</dc:creator>
      <pubDate>Thu, 27 Jan 2022 10:45:26 +0000</pubDate>
      <link>https://dev.to/aws-builders/hosting-your-static-wordpress-site-on-aws-s3-4cib</link>
      <guid>https://dev.to/aws-builders/hosting-your-static-wordpress-site-on-aws-s3-4cib</guid>
      <description>&lt;p&gt;AWS S3 is a good choice for hosting static WordPress sites. It's secure and inexpensive, and frees up time which would otherwise be spent ensuring plugins, themes, and security procedures were appropriately configured.&lt;/p&gt;

&lt;p&gt;The article describes one approach for generating the static site. The local development environment requires Docker Compose, and the only requisite plugin is the &lt;a href="https://wordpress.org/plugins/simply-static/" rel="noopener noreferrer"&gt;Simply Static WP Plugin&lt;/a&gt;, which exports the static site to a zip file before being uploading to S3.&lt;/p&gt;

&lt;p&gt;Prerequisites&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker Engine&lt;/li&gt;
&lt;li&gt;Docker Compose&lt;/li&gt;
&lt;li&gt;S3 bucket configured to host a static website &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Preparing the WordPress Site
&lt;/h2&gt;

&lt;p&gt;A quick start guide for running WordPress with Docker Compose can be found at &lt;a href="https://docs.docker.com/samples/wordpress/" rel="noopener noreferrer"&gt;docs.docker.com&lt;/a&gt;. I've included the reference &lt;em&gt;docker-compose.yml&lt;/em&gt; file included in that startup guide in the snippet below. It can be used to build a local WordPress instance by running the command &lt;strong&gt;docker-compose up -d&lt;/strong&gt;. The resulting containerized version of WordPress will be available at  &lt;strong&gt;&lt;a href="http://localhost:8000" rel="noopener noreferrer"&gt;http://localhost:8000&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;version: "3.9"&lt;/p&gt;

&lt;p&gt;services:&lt;br&gt;
  db:&lt;br&gt;
    image: mysql:5.7&lt;br&gt;
    volumes:&lt;br&gt;
      - db_data:/var/lib/mysql&lt;br&gt;
    restart: always&lt;br&gt;
    environment:&lt;br&gt;
      MYSQL_ROOT_PASSWORD: somewordpress&lt;br&gt;
      MYSQL_DATABASE: wordpress&lt;br&gt;
      MYSQL_USER: wordpress&lt;br&gt;
      MYSQL_PASSWORD: wordpress&lt;/p&gt;

&lt;p&gt;wordpress:&lt;br&gt;
    depends_on:&lt;br&gt;
      - db&lt;br&gt;
    image: wordpress:latest&lt;br&gt;
    extra_hosts:&lt;br&gt;
      - localhost:172.23.0.3&lt;br&gt;
      - localhost:172.23.0.1&lt;br&gt;
    volumes:&lt;br&gt;
      - wordpress_data:/var/www/html&lt;br&gt;
    ports:&lt;br&gt;
      - "8000:80"&lt;br&gt;
    restart: always&lt;br&gt;
    environment:&lt;br&gt;
      WORDPRESS_DB_HOST: db&lt;br&gt;
      WORDPRESS_DB_USER: wordpress&lt;br&gt;
      WORDPRESS_DB_PASSWORD: wordpress&lt;br&gt;
      WORDPRESS_DB_NAME: wordpress&lt;br&gt;
volumes:&lt;br&gt;
  db_data: {}&lt;br&gt;
  wordpress_data: {}&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Export Static Site&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;The static site generation part of this work can begin once the WordPress site has been fully configured. Configuration includes selection of the site's theme. Installation of plugins which support the site's functionality. Addition of site specific content, as well as any other customizations required to produce the final result.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 Navigate to _Plugins\Add New_ and search for _Simply Stati_c.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2tj2ej2jvzgpp8bafro.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2tj2ej2jvzgpp8bafro.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 Once installed, navigate to &lt;em&gt;Simply Static\Settings&lt;/em&gt; and ensure the following options are configured for destination URLs and delivery method&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7aeq5gspkpynhjhj5ps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7aeq5gspkpynhjhj5ps.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 Update the location for Temporary Files by navigating to &lt;em&gt;Simply Static\Settings\Advanced&lt;/em&gt;. (This path will write to our wordpress_data volume)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9sr894bozr5srlplgad5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9sr894bozr5srlplgad5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4 Generate static files by navigating to &lt;em&gt;Simply Static\Generate&lt;/em&gt; and clicking the Generate Static Files button&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx93l3bnm2yfpj9vdaxwx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx93l3bnm2yfpj9vdaxwx.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5 Once step 4 is complete, click the link to download the static content. Inspect the contents of the .zip file. It should include the following items&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;- index.html&lt;/li&gt;
&lt;li&gt;- wp-content/&lt;/li&gt;
&lt;li&gt;- wp-includes/&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Uploading to AWS S3
&lt;/h2&gt;

&lt;p&gt;In this step, output from running the Simply Static Plugin, is uploaded to S3. &lt;/p&gt;

&lt;p&gt;At the point, the S3 bucket should be configured for &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/EnableWebsiteHosting.html" rel="noopener noreferrer"&gt;static website hosting&lt;/a&gt;. This can be enabled through all the usual AWS paths (REST API, SDK, CLI, CloudFormation). We will be sticking with the console.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on the S3 bucket. Navigate to the Properties tab. Scroll down to the section titled Static website hosting. Enable and select Host a static website as the hosting type. Update the Index document field with the index.html landing page. Save changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhvxeqx318to3uoz22r2h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhvxeqx318to3uoz22r2h.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
Now upload the contents of the Zip file (index.html, wp-content, wp-includes) using the Upload option on the S3 bucket landing page.&lt;/p&gt;

&lt;p&gt;The shiny new static WordPress site will now be accessible using the public URL for the site (available under the Properties tab for the S3 bucket under the section &lt;em&gt;Static website hosting&lt;/em&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Secure S3 Bucket with Origin Access Identity (OAI)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdsseepxy099ekkd176zl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdsseepxy099ekkd176zl.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The S3 bucket can be secured behind a CloudFront distribution to give greater control over access to the S3 bucket. The S3 bucket should be marked private. An &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html" rel="noopener noreferrer"&gt;Origin Access Identity (OAI)&lt;/a&gt; will be created and associated with a CloudFront distribution. The S3 bucket permissions are then updated to allow the CloudFront distribution to use the OAI to access the static site. The following snippet illustrates the bucket policy which will allow the CloudFront distribution access. Full details can be found on &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html" rel="noopener noreferrer"&gt;docs.aws.amazon.com&lt;/a&gt;.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PolicyForCloudFrontPrivateContent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"AWS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity &amp;lt;distribution ID&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::&amp;lt;s3-bucket&amp;gt;/*"&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Future Enhancements&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;The previous guide acts as an introduction to getting a static WordPress site up and running in AWS. In a follow-up article, I refine this current setup and include automation for building and deploying to S3. If you have any comments or suggestions on how this process could be refined, please leave them in the comments section below.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>docker</category>
      <category>wordpress</category>
    </item>
  </channel>
</rss>
