<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.5">Jekyll</generator><link href="https://olafalo.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://olafalo.net/" rel="alternate" type="text/html" /><updated>2019-08-26T01:16:17+00:00</updated><id>https://olafalo.net/feed.xml</id><title type="html">olafalo</title><subtitle>Personal site of olafalo</subtitle><entry><title type="html">DIY Thermostat with a Raspberry Pi</title><link href="https://olafalo.net/thermostat/" rel="alternate" type="text/html" title="DIY Thermostat with a Raspberry Pi" /><published>2019-08-25T00:00:00+00:00</published><updated>2019-08-25T00:00:00+00:00</updated><id>https://olafalo.net/thermostat</id><content type="html" xml:base="https://olafalo.net/thermostat/">&lt;style&gt;
  img {
    max-height: 500px;
  }
&lt;/style&gt;

&lt;p&gt;I made a thermostat using a Raspberry Pi. It cost about $100, works great, and looks awful. I learned a lot and bothered lots of people by talking about it too much.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/thermostat-wall.jpg&quot; alt=&quot;Finished thermostat on wall&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;but-why&quot;&gt;But Why?&lt;/h2&gt;

&lt;p&gt;My apartment is nice, but there’s something I don’t like about it: the AC and heat is loud (exposed ductwork isn’t great for noise). On top of that, the thermostat turns on and off frequently, with cycles that are generally about 10 minutes. This means that watching a movie in the summer is an exercise in frustration, requiring volume adjustments about every 5 minutes.&lt;/p&gt;

&lt;p&gt;I looked for a way to change the temperature tolerance of the thermostat, because I don’t really need the temperature to always be within +/-0.5°F of the set temperature, but that wasn’t adjustable. I thought about buying a smart thermostat, but they’re expensive.&lt;/p&gt;

&lt;p&gt;Thus, I entered a train of thought that began with “what if I just did it myself?” and ended with, well, doing it myself.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;research-phase&quot;&gt;Research Phase&lt;/h2&gt;

&lt;p&gt;I started with the assumption that I could use a Raspberry Pi as the main controller for my thermostat. They’re cheap (less than $50, including accessories), the newer models have WiFi built-in, and they have super handy GPIO pins that are perfect for interacting with other electronics. So, the plan was to have a Raspberry Pi, with a web interface for control, doing… something that would allow thermostat-like behavior.&lt;/p&gt;

&lt;p&gt;The first question is: how will the Pi know what the temperature is? I had a few &lt;a href=&quot;https://www.adafruit.com/product/386&quot;&gt;DHT11 sensors&lt;/a&gt; lying around, but they have an accuracy of 2°C (3.6°F), which isn’t really accurate enough for use in a thermostat. The &lt;a href=&quot;https://www.adafruit.com/product/385&quot;&gt;DHT22&lt;/a&gt;, on the other hand, has an accuracy of 0.5°C (0.9°F), which is probably fine. I also considered the &lt;a href=&quot;https://www.sparkfun.com/products/15112&quot;&gt;SCD30&lt;/a&gt;, which measures temperature, humidity, and CO&lt;sub&gt;2&lt;/sub&gt; levels (cool, right?) but is much more costly. I ended up going with the DHT22.&lt;/p&gt;

&lt;p&gt;The next question is: how do we control a thermostat from a Pi? It turns out, it’s fairly simple. There’s &lt;a href=&quot;https://www.electrical-online.com/thermostat-wiring-explained/&quot;&gt;a good, in-depth explanation here&lt;/a&gt;, but here’s a summary: there are a bunch of control wires that go from the HVAC unit to your wall thermostat. The simplest unit—a heater—has two wires, R and W. If you connect the R wire to the W wire, the heat turns on. If the unit has AC as well as heat, it will also have a Y wire, and connecting the R wire to the Y wire turns the AC on. It’s basically just “connect this wire and that wire,” and which wires to connect depends on your HVAC unit. This means relays are the way to go for control (the reason you can hear your thermostat “click” on).&lt;/p&gt;

&lt;p&gt;To find out how to interact with the control wires, I did some prodding with a multimeter on the thermostat that I already had. It turns out that my particular system is a bit odd; it doesn’t have a heating wire connection (W), but instead, the only wired connections were AUX (auxiliary heating), Y (compressor), G (fan), O (heat pump reversal valve), R (24VAC), and C (power). I didn’t need auxiliary heating, and C is for constant power (24VAC), which I didn’t need either because it was easier to just plug the Pi into the wall to power it. In the end, I found what I needed to know: turning the AC on means connecting the R, Y, G, and O wires together. Turning the heat on means connecting the R, Y, and G wires (but not O). Turning on only the fan is done by connecting R and G. In short, turn on the compressor and blower fan to get heat. Also turn on the heat pump reversal valve if you want AC instead.&lt;/p&gt;

&lt;p&gt;Armed with knowledge about what sort of switching I’d need to do, I looked around for relays, and ended up using a SainSmart 4-channel relay module ($10). This can be controlled directly from the Pi’s GPIO pins. It’s active low, so connecting an output pin from the Pi to the relay module would switch on the relay if the output pin was low, and switch it off if it was high. Slightly confusing, but that’s fine.&lt;/p&gt;

&lt;h2 id=&quot;assembly&quot;&gt;Assembly&lt;/h2&gt;

&lt;p&gt;I bought the parts I needed:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Raspberry Pi Model 3 B+ ($35)&lt;/li&gt;
  &lt;li&gt;5V 2.5A MicroUSB wall wart ($7.50)&lt;/li&gt;
  &lt;li&gt;Any decent SD card ($10)&lt;/li&gt;
  &lt;li&gt;DHT22/AM2302 ($10 for 2-pack)&lt;/li&gt;
  &lt;li&gt;4-channel relay module ($10)&lt;/li&gt;
  &lt;li&gt;Breadboard, jumper wires, multimeter, misc. junk from Sparkfun ($20-30)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.adafruit.com/product/2000&quot;&gt;Pro Trinket 5V 16MHz&lt;/a&gt; ($10)&lt;/li&gt;
  &lt;li&gt;Total: roughly $100&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, I attempted to read values from the temperature sensor directly from the Pi. Unfortunately, I had no luck with this. Reading from these sensors requires microsecond-precision timing, and since Linux is not a real-time OS, this was too error-prone to be reliable. Instead, I bought a $10 Pro Trinket, which is a tiny and cheap Arduino board. It’s overkill for what it actually does, but it works. The Trinket is connected to the DHT22, continually reads temperature/humidity values, and writes them over serial (UART) to the Pi.&lt;/p&gt;

&lt;p&gt;The Pi, therefore, only needed a couple of connections:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;5V and GND connections to the Trinket and the relay module&lt;/li&gt;
  &lt;li&gt;TX/RX UART connections to the Trinket&lt;/li&gt;
  &lt;li&gt;GPIO connections to the relay board inputs (GPIO pins 4, 17, and 27)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also used 18AWG solid copper wire to connect the relays to the wall wiring harness. I chose 18AWG because the pins on the existing thermostat were about 1mm in diameter, so 18AWG would be the best fit to the wall socket. I connected all the common terminals of the relay together to the R wire socket. Then, I connected the Y, G, and O sockets to the normally open (NO) relay terminals 1, 2, and 3, respectively.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/relay-connections.jpg&quot; alt=&quot;Relay connections&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After some breadboarding (and plenty of trial and error), the hardware side of things is ready (if not especially pretty)!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/thermostat-on-table.jpg&quot; alt=&quot;Finished thermostat on table&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;software&quot;&gt;Software&lt;/h2&gt;

&lt;p&gt;You can see &lt;a href=&quot;https://gitlab.com/olafalo/thermoctl&quot;&gt;the complete project here&lt;/a&gt; (please forgive the lack of polish).&lt;/p&gt;

&lt;p&gt;The thermostat needs some sort of control interface, and I opted for a web interface. It’s easier and cheaper than wiring up buttons and a screen, plus, sometimes I don’t want to get up to change the temperature. For this, I reached for a simple Go webserver. This project has one dependency: Chart.js, which I use for showing a graph of temperature and humidity over the past 24 hours or so. Everything else is Go’s standard library and regular old HTML/CSS/JS. Besides the nice graph, there’s some basic control options: minimum and maximum temperature, an option to always have the fan on, an enable/disable option, and a setting for the minimum time between switching state (useful for testing).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/interface.png&quot; alt=&quot;Thermoctl web interface&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There are actually three servers that make up the thermostat software. One is the switch controller, which exposes a REST API for setting the HVAC state (e.g. POST/cool to turn the AC on). Another is the sensor server, which returns the latest sensor data as JSON. The last and biggest one is the management server, which serves up the web interface, controls settings, and decides when to turn the AC/heat/fan on and off.&lt;/p&gt;

&lt;p&gt;This microservice-ish approach might be overkill, but it was extremely useful in testing—I could work on the management interface from my desktop, while still being able to read actual sensor data and control actual switches. Additionally, it leaves the option for separate components. It would be pretty easy to set up multiple temperature sensors in different rooms, or run the management server on a device that isn’t stuck to the wall.&lt;/p&gt;

&lt;p&gt;Software also makes it possible to compensate for the low-ish resolution of the temperature sensor (0.1°C/0.18°F). If the actual temperature is between 22.1 and 22.2°C, for example, the sensor will alternate between reading as 22.1 and 22.2. Doing some basic averaging over time allows us to get a more accurate measure. You could also use humidity to adjust the target temperature, or get weather data from the internet and do… something… based on it. The possibilities are endless.&lt;/p&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;

&lt;p&gt;This thermostat is up and running, and working well! The graph is pretty, the controls are simple and easy. But there’s obviously room for improvement, especially in terms of appearance. If I wanted to really step it up, I would switch to a Raspberry Pi Zero instead, get a custom PCB printed, and package the whole thing up in a 3D-printed enclosure. It would probably look great! But it would also be a lot of work.&lt;/p&gt;

&lt;p&gt;This was a fun project, and I like my thermostat a lot more than I would like a normal smart thermostat. It was cheaper, too—comparable with a regular dumb thermostat, if you don’t count the time spent. The jury is still out on whether or not it will be a good conversation starter, however.&lt;/p&gt;</content><author><name></name></author><summary type="html">I made a thermostat using a Raspberry Pi. It cost about $100, works great, and looks awful. I learned a lot and bothered lots of people by talking about it too much.</summary></entry><entry><title type="html">An Image Host of Your Own, Part 1</title><link href="https://olafalo.net/image-host-1/" rel="alternate" type="text/html" title="An Image Host of Your Own, Part 1" /><published>2018-11-15T14:36:24+00:00</published><updated>2018-11-15T14:36:24+00:00</updated><id>https://olafalo.net/image-host-1</id><content type="html" xml:base="https://olafalo.net/image-host-1/">&lt;p&gt;The result of this post is live and running at &lt;a href=&quot;https://img.olafalo.net&quot;&gt;img.olafalo.net&lt;/a&gt;. Check it out! (Please excuse my front-end.)&lt;/p&gt;

&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;/h2&gt;

&lt;p&gt;A few years ago, starting an image host was a daunting task. The first few users might not have been too hard, but scaling was a problem. How do you reliably serve images to millions of users?&lt;/p&gt;

&lt;p&gt;The answer these days is, of course, to let someone else do it. AWS S3 is brilliant for this. You can generally stuff whatever you want into an S3 bucket, and anyone can reliably access it. Add on tools like CloudFront, and now anyone can reliably access it &lt;em&gt;quickly&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In this post, we’ll create a backend for the simplest MVP image host possible. All it will do is allow users to upload an image, then get back a sharable link to that image. The cool part, though, is that this setup could theoretically scale to millions of global users at any time without breaking a sweat, because all the technology we’ll be using (mostly S3, Lambda, and CloudFront) scales very well.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-0-setup&quot;&gt;Step 0: Setup&lt;/h2&gt;

&lt;p&gt;If you want to follow along, you’ll need an AWS account. You’ll probably also want to set up the AWS CLI tool. Some background knowledge would be helpful too, but I’ll explain the basics as we go.&lt;/p&gt;

&lt;h2 id=&quot;step-1-s3&quot;&gt;Step 1: S3&lt;/h2&gt;

&lt;p&gt;I’m going to create 3 S3 buckets:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Deployments: A private bucket which we use for lambda deployments, more on that later&lt;/li&gt;
  &lt;li&gt;StaticWebsite: A public bucket that’s set up as a website, to host the static part of our website (&lt;code class=&quot;highlighter-rouge&quot;&gt;index.html&lt;/code&gt; and such)&lt;/li&gt;
  &lt;li&gt;ImageBucket: The bucket that the actual images will go into&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I make an effort to use CloudFormation for everything, because “make an image host without writing any code!” sounds much better than it actually is, and I didn’t want to spend an entire day taking screenshots of the AWS console. Here’s &lt;code class=&quot;highlighter-rouge&quot;&gt;basestack.yaml&lt;/code&gt;, which describes a CFN stack with those three buckets:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;Resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;Deployments&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::S3::Bucket&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;AccessControl&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Private&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;StaticWebsite&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::S3::Bucket&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;AccessControl&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PublicRead&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ImageBucket&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::S3::Bucket&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;AccessControl&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PublicRead&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To deploy this stack, we can use the &lt;code class=&quot;highlighter-rouge&quot;&gt;aws&lt;/code&gt; CLI tool:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;aws cloudformation deploy template-file basestack.yaml --stack-name BaseStack --capabilities CAPABILITY_IAM
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;--capabilities&lt;/code&gt; flag may not be necessary in this case, but you generally need it when you’re changing permissions. The &lt;a href=&quot;https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deploy/index.html&quot;&gt;docs&lt;/a&gt; have this to say:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Some stack templates might include resources that can affect permissions in your AWS account, for example, by creating new AWS Identity and Access Management (IAM) users. For those stacks, you must explicitly acknowledge their capabilities by specifying this parameter. The only valid values are CAPABILITY_IAM and CAPABILITY_NAMED_IAM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the deployment went well, congrats! You have some S3 buckets. I recommend creating a shell script or two for building and deploying, once we have something to build.&lt;/p&gt;

&lt;h2 id=&quot;step-2-serverless-just-means-someone-elses-servers&quot;&gt;Step 2: “Serverless” Just Means Someone Else’s Servers&lt;/h2&gt;

&lt;p&gt;To allow image uploads, we’re going to create a lambda function that has upload access to our &lt;code class=&quot;highlighter-rouge&quot;&gt;ImageBucket&lt;/code&gt;. We’re also going to create an API Gateway endpoint that allows anyone to send image information to our lambda.&lt;/p&gt;

&lt;p&gt;Lambdas can be written in several languages, but I went with Go for this one. I’m a big fan of Go; it’s quick, predictable, and there’s no need to sprinkle &lt;code class=&quot;highlighter-rouge&quot;&gt;async&lt;/code&gt; keywords everywhere or to put your entire lambda in one long promise chain. Anyway, here’s &lt;code class=&quot;highlighter-rouge&quot;&gt;upload.go&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awsSession&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Session&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;// Get the name of the upload bucket as a config var&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bucketName&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Getenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UPLOAD_BUCKET&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;// getSession returns a new session if one did not already exist, and the&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;// existing session if it had already been created.&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Session&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awsSession&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
		&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awsSession&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;// Initialize a session that the SDK will use to load configuration,&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;// credentials, and region from the shared config file. (~/.aws/config).&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awsSession&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Must&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewSessionWithOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
		&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SharedConfigState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SharedConfigEnable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}))&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awsSession&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;// Lambda input structure&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imageEvent&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Image64&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;// image contents in base64&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Extension&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;// file extension, e.g. &quot;png&quot;&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;// Lambda output structure&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uploadResult&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;// The lambda function itself&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uploadImage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imageEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uploadResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uploader&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s3manager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewUploader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base64&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;StdEncoding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DecodeString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Image64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
		&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uploadResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;// Create a key for the image to store in S3, a UUID with an extension&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imageID&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewV4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imageKey&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;i/%s.%s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imageID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Extension&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imageReader&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewReader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uploadParams&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s3manager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UploadInput&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
		&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Bucket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bucketName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
		&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imageKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
		&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Body&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imageReader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
		&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;// I know, I know. We could definitely find a better way to set the&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
		&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;// content-type, but this works well enough as an MVP.&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
		&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ContentType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;aws&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;image/%s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Extension&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;// Do the upload and return its URL&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uploader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Upload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uploadParams&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
		&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uploadResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uploadResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imageKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)},&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;x&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uploadImage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I saved this file as &lt;code class=&quot;highlighter-rouge&quot;&gt;upload.go&lt;/code&gt; in a directory called &lt;code class=&quot;highlighter-rouge&quot;&gt;image-upload&lt;/code&gt;. To build this for use in a lambda, we have to do something like this:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; tmp/bin
&lt;span class=&quot;nv&quot;&gt;GOOS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;linux &lt;span class=&quot;nv&quot;&gt;GOARCH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;amd64 go build &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; tmp/bin/image-upload ./image-upload
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; tmp/dist
zip &lt;span class=&quot;nt&quot;&gt;-j&lt;/span&gt; tmp/dist/image-upload.zip tmp/bin/image-upload
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This gives us &lt;code class=&quot;highlighter-rouge&quot;&gt;tmp/dist/image-upload.zip&lt;/code&gt;, which we can use as the code for the lambda.&lt;/p&gt;

&lt;p&gt;Now we need another CloudFormation stack, which will contain the lambda and the API Gateway endpoint. The resources we need are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The lambda itself&lt;/li&gt;
  &lt;li&gt;A role for the lambda that allows uploading to S3&lt;/li&gt;
  &lt;li&gt;A permission that allows API Gateway to invoke the lambda&lt;/li&gt;
  &lt;li&gt;An API Gateway REST API&lt;/li&gt;
  &lt;li&gt;A POST method for that API (the upload endpoint)&lt;/li&gt;
  &lt;li&gt;An OPTIONS method for the API (so that we don’t run into CORS issues)&lt;/li&gt;
  &lt;li&gt;An API Gateway deployment for our API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yikes. We also need a few parameters, so that we know the names of our S3 buckets, and so that we can have a deployment prefix that updates each time we deploy. Putting it all together, this is what we get, &lt;code class=&quot;highlighter-rouge&quot;&gt;stack.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;Resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;S3UploadRole&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::IAM::Role&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;AssumeRolePolicyDocument&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;2012-10-17&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Statement&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Allow&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Principal&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;lambda.amazonaws.com&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sts:AssumeRole&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Policies&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PolicyName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;S3UploadPolicy&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PolicyDocument&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;2012-10-17&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Statement&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Allow&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;s3:*&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ImageUploadLambda&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::Lambda::Function&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;image-upload&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Runtime&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;go1.x&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Variables&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;UPLOAD_BUCKET&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ImageBucket&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Role&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!GetAtt&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;S3UploadRole.Arn&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Code&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;S3Bucket&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DeploymentsBucket&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;S3Key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;Fn::Join&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&quot;&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DeployPrefix&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;image-upload.zip&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ImageUploadLambdaPermission&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::Lambda::Permission&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;lambda:InvokeFunction&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;FunctionName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ImageUploadLambda&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Principal&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;apigateway.amazonaws.com&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;SourceArn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Sub&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${UploadAPI}/*/POST/*&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;DependsOn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ImageUploadLambda&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UploadAPI&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;UploadAPI&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::ApiGateway::RestApi&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UploadAPI&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;BinaryMediaTypes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;image/jpeg&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;image/png&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;image/gif&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;image/webp&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;EndpointConfiguration&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;EDGE&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;UploadAPIPost&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::ApiGateway::Method&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;AuthorizationType&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;NONE&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;HttpMethod&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;POST&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ResourceId&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;Fn::GetAtt&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UploadAPI&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;RootResourceId&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;RestApiId&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UploadAPI&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;MethodResponses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;StatusCode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;200&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;ResponseModels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;application/json&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Empty'&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;ResponseParameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Headers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Methods&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Origin&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;StatusCode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;500&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;ResponseModels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;application/json&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Empty'&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;ResponseParameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Headers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Methods&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Origin&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Integration&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;IntegrationHttpMethod&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;POST&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;IntegrationResponses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;StatusCode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;200&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;ResponseParameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Headers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'&quot;&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Methods&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'POST,OPTIONS'&quot;&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Origin&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'*'&quot;&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;StatusCode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;500&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;SelectionPattern&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;.+&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;ResponseParameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Headers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'&quot;&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Methods&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'POST,OPTIONS'&quot;&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Origin&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'*'&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Uri&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;Fn::Join&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;arn:aws:apigateway:&quot;&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::Region&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;:lambda:path/2015-03-31/functions/&quot;&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!GetAtt&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ImageUploadLambda.Arn&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/invocations&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;UploadAPIOptions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::ApiGateway::Method&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;AuthorizationType&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;NONE&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;RestApiId&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UploadAPI&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ResourceId&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!GetAtt&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UploadAPI.RootResourceId&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;HttpMethod&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;OPTIONS&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Integration&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;IntegrationResponses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;StatusCode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;200&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;ResponseParameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Headers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'&quot;&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Methods&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'POST,OPTIONS'&quot;&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Origin&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'*'&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;ResponseTemplates&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;application/json&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;PassthroughBehavior&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;WHEN_NO_MATCH&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;RequestTemplates&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;application/json&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{&quot;statusCode&quot;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;200}'&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;MOCK&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;MethodResponses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;StatusCode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;200&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ResponseModels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;application/json&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Empty'&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ResponseParameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Headers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Methods&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;method.response.header.Access-Control-Allow-Origin&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;UploadAPIDeployment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::ApiGateway::Deployment&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;RestApiId&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UploadAPI&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;StageName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v0&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;DependsOn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UploadAPIPost&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Parameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;DeployPrefix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;0&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;DeploymentsBucket&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;String&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ImageBucket&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;String&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I’ve never heard anyone call CloudFormation “terse”.&lt;/p&gt;

&lt;p&gt;Anyway, here’s my &lt;code class=&quot;highlighter-rouge&quot;&gt;deploy.sh&lt;/code&gt; that I used to deploy the whole backend in one go:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;TEMPLATE_FILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;basestack.yaml
&lt;span class=&quot;nv&quot;&gt;STACK_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;BaseStack

&lt;span class=&quot;k&quot;&gt;function &lt;/span&gt;getResourceId &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    aws cloudformation describe-stack-resource &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;--stack-name&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$STACK_NAME&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;--logical-resource-id&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        | jq &lt;span class=&quot;nt&quot;&gt;--raw-output&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'.StackResourceDetail.PhysicalResourceId'&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# create base stack&lt;/span&gt;
aws cloudformation deploy &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--template-file&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TEMPLATE_FILE&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--stack-name&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$STACK_NAME&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--capabilities&lt;/span&gt; CAPABILITY_IAM

&lt;span class=&quot;c&quot;&gt;# sync dist folder&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;DEPLOY_PREFIX&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;deploy-&lt;span class=&quot;k&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; +%s&lt;span class=&quot;k&quot;&gt;)&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;sync&lt;/span&gt; ./tmp/dist s3://&lt;span class=&quot;k&quot;&gt;$(&lt;/span&gt;getResourceId Deployments&lt;span class=&quot;k&quot;&gt;)&lt;/span&gt;/&lt;span class=&quot;nv&quot;&gt;$DEPLOY_PREFIX&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--delete&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# sync website (once we have one)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# aws s3 sync ./tmp/web s3://$(getResourceId StaticWebsite) --delete&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;DEPLOY_BUCKET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;$(&lt;/span&gt;getResourceId Deployments&lt;span class=&quot;k&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;IMAGE_BUCKET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;$(&lt;/span&gt;getResourceId ImageBucket&lt;span class=&quot;k&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;TEMPLATE_FILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;stack.yaml
&lt;span class=&quot;nv&quot;&gt;STACK_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;ImageHost

aws cloudformation deploy &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--template-file&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TEMPLATE_FILE&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--stack-name&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$STACK_NAME&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--capabilities&lt;/span&gt; CAPABILITY_IAM &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--parameter-overrides&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;DeployPrefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DEPLOY_PREFIX&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;DeploymentsBucket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DEPLOY_BUCKET&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;ImageBucket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$IMAGE_BUCKET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You should be able to deploy the whole backend with one command now, which is neat. We now have an endpoint that you can hit to upload an image and get back a link to it! We can’t actually view the uploaded image, though, because we haven’t set an access policy on the S3 bucket we use for hosting. Let’s set up a CloudFront distribution to get around that (and also get nice CDN junk, as well as SSL and a custom domain).&lt;/p&gt;

&lt;h2 id=&quot;step-3-distribute-content-with-a-network&quot;&gt;Step 3: Distribute Content (with a Network)&lt;/h2&gt;

&lt;p&gt;We could make our CloudFront distribution with CloudFormation, but we’ve had plenty of CFN already, and the console is pretty easy for this part. Go to CloudFront in the AWS web console, and click the friendly blue “Create Distribution” button.&lt;/p&gt;

&lt;p&gt;We want to create a web distribution. We want an Origin Domain Name of the static website bucket (we’ll make another distribution for the image hosting bucket later). We &lt;strong&gt;do&lt;/strong&gt; want to Restrict Bucket Access, Create a New Identity for the Origin Access Identity, and Yes, Update Bucket Policy. This will allow us to access the contents of the bucket through the CDN. The Default Root Object should probably be &lt;code class=&quot;highlighter-rouge&quot;&gt;index.html&lt;/code&gt;. Maybe you also want to force HTTPS, but that’s optional. Everything else should be good by default.&lt;/p&gt;

&lt;p&gt;Once that’s done, we can edit the distribution we just created. We should create another origin for the distribution, which points to the image hosting bucket (do the same restriction of bucket access, create another new identity, and let CloudFront update the bucket policy for you). We should also create a new Behavior, with a path pattern of &lt;code class=&quot;highlighter-rouge&quot;&gt;i/*&lt;/code&gt;, with an origin of the image hosting bucket.&lt;/p&gt;

&lt;p&gt;Once all that is done, we should have our subdomain ([keyboard-mashing].cloudfront.net). This will host all content from our static website bucket, unless the path starts with &lt;code class=&quot;highlighter-rouge&quot;&gt;i/*&lt;/code&gt;, in which case it will host content from our images bucket. Here’s an example of the flow of things:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Browser requests [whatever].cloudfront.net&lt;/li&gt;
  &lt;li&gt;CloudFront returns our hosted &lt;code class=&quot;highlighter-rouge&quot;&gt;index.html&lt;/code&gt; as well as any other hosted files&lt;/li&gt;
  &lt;li&gt;User uses our web frontend to POST image data to our lambda, which gets uploaded to the image host S3 bucket&lt;/li&gt;
  &lt;li&gt;Lambda returns a link to [whatever].cloudfront.net/i/something.png&lt;/li&gt;
  &lt;li&gt;CloudFront gets requests for [whatever].cloudfront.net/i/something.png and serves up the requested image, from the image host bucket (because the path starts with &lt;code class=&quot;highlighter-rouge&quot;&gt;i/*&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;step-4-congratulations&quot;&gt;Step 4: Congratulations&lt;/h2&gt;

&lt;p&gt;We’re done with the backend! We now have infrastructure to host a static website and any number of images, as well as an upload API. Next, we need the web front-end that will allow people to actually use our host.&lt;/p&gt;

&lt;p&gt;For extra credit, use CloudFront to allow your API to be accessed from the same .cloudfront.net domain. This will remove the requirement for an OPTIONS endpoint, because same-domain requests (exact domain matches, to be clear) don’t need to be pre-flighted.&lt;/p&gt;</content><author><name></name></author><summary type="html">The result of this post is live and running at img.olafalo.net. Check it out! (Please excuse my front-end.)</summary></entry></feed>