Learn how to Dynamically Add App Features with AWS AppConfig Feature Flags (+ Linux 101)
Tags: aws, linux, javascript, cloud
Published: 2022-06-13 03:58:00

How's it going everyone? 👋 Welcome back to the <b>Tech Stack Playbook</b>, your guide to apps, software, and tech (but in a fun way I promise).

This past week, I attended a really eye-opening AWS workshop put on for AWS Community Builders on a very interesting service I had not used before, called AppConfig. As described in the documentation, [AWS AppConfig](https://docs.aws.amazon.com/appconfig/latest/userguide/what-is-appconfig.html) is a service that lets developers create, manage, and quickly deploy application changes, modifications, features, or patches which are running on Amazon EC2 (Elastic Cloud Compute), AWS Lambda, containers, mobile apps, or IoT devices. 

In this blog post, I'll go through the workshop, the code I used to run everything, screenshots of the deployment process, and I will also go through how to use Linux and an Amazon EC2 instance to deploy all of the code in a VM (virtual machine).

What we will build is a simple Airbnb-for-Cribs home rental application and switch in an image viewer carousel for each listed home via AWS AppConfig. Here's what it will look like (unfortunately, IRL mansions are not included):

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zcyldsnymlt6i5nnavoq.png)
 
I have the starter repo that AWS created linked on my GitHub here under the branch `initial`. I also have the `final` branch code for reference as well.
{% github BrianHHough/AWSAppConfigWorkshop %}

If you're excited, read on!


## ☁️ Building for Scale (in the Cloud)

Let's take a step back, to talk about scale. "Building for scale" is something a lot of people talk about, but very seldom do we ever plan for scale to happen in the ways we want. Becoming an "overnight success" could take months, years, or even decades in some cases. We rarely ever know when "hitting scale" will take place, so we have to future-proof tech so that it will scale with our needs, when we need them. 

Imagine releasing a feature to a million plus member base. What if the feature includes a breaking change with a bug that decreases use by 60% per week per user. That could be disastrous. We want to get ahead of these issues, which is why AWS AppConfig can help us analyze a feature pushed to a select (but growing) number of users over time so that we can adapt to the change, see what it does to our user base, and adjust accordingly.


When you think about "building for scale," of course one of the top companies that comes to my mind is Facebook (...I mean, Meta...still not used to saying that). In Meta's 2022 Q1 2022 Earnings Report, the company reported a whopping 2.94 billion Monthly Active Users as of March 31, 2022 ([Meta Q1 2022 Earnings Report](https://investor.fb.com/investor-events/event-details/2022/Q1-2022-Earnings/default.aspx)).

One of the interviews that stands out in my mind in particular is Reid Hoffman's Masters of Scale interview with Meta CEO Mark Zuckerberg. Back in 2017, Mark Zuckerberg shared a bit more of the context behind his "move fast and break things" mantra that has allowed him to build the largest social media platform on the planet.

Mark shared that:

> 
"At any given point in time, there isn't just one version of Facebook running, there are probably 10,000. Any engineer at the company can basically decide that they want to test something. There are some rules on sensitive things, but in general, an engineer can test something, and they can launch a version of Facebook not to the whole community, but maybe to 10,000 people or 50,000 people—whatever is necessary to get a good test of an experience. ([Entrepreneur](https://www.entrepreneur.com/article/294242))"

### Check out the interview here:

{% embed https://art19.com/shows/masters-of-scale/episodes/83f96210-9287-4cca-a4db-6a5b518942f9 %}

You might think, why would you want something like this? All the users have different user experiences? Isn't that counter-intuitive to UI/UX testing, agile development, and scaling?

Not necessarily... because, let's say you are testing the incorporation of a feature or experience. Sure you can test it in controlled environments, but those are still, controlled. What is more optimal is to test real people, using your app in a real way, without the impression of being under a microscope, so you can really understand if the feature "hits" or resonates with the expected user base at the expected time.

To this day, I have always wondered how dynamic feature adds or dynamic user testing actually works in production. Thanks to the AWS Community Builders Program, I was able to see something like this in action, which is what I will be sharing with you today.


## ☁️ Intro to AWS AppConfig
With AWS AppConfig, we are going to add in a feature called "Photo Slider" which will allow users to switch between photos in the app for each crib. However, it will not be visible to everyone all at once as soon as the feature is pushed. 

AWS AppConfig allows us to set the launch deployment details, where we can release to 1% of users, then 5%, then 10%, and so on. It can be risky to release a new feature to users all at once, so we need to account for that. AWS AppConfig has a feature called "Feature Flags" that can allow us to pull the switch on a feature if something unexpected or disastrous happens in the app and allow us to revert back near-instantly.

## ☁️ Where does AWS AppConfig fit?

In this architecture diagram provided by the AWS team, it outlines where everything "lives." 
- Deployment; via AWS Amplify
- Database: via DynamoDB
- Front-end: via HTML, CS, and JS
- AppConfig: connected via a AWS Lambda function to the front-end

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8g7r0a9i1e6kgzcwzeq0.png)
Source: AWS 

## ☁️ Set up a Feature Flag
In the AWS Console, you will navigate over to the **AWS AppConfig** service.
- Click **Get Started**
- In "Create Application" page, name your app: `AWSomeCribRentals`
- Add a description: `This is for feature flags related to the AWSomeCribRentals app`
- Click **Create application**

This will be our container for all flags related to our application. Think of it like a wrapper.

We then must create a **configuration profile** within the AppConfig application. This lets us define the Feature Flag type and set-up. You can think of it as an element within the wrapper (i.e. you could have multiple Configuration Profiles, perhaps even multiple feature flags within the same profile, which all sits within the AppConfig Application).
- Let's name the config profile of our feature addition of a card: `CardFeatureFlag`
- Add a description: `related to card`

We will then add a new flag by clicking **Add new flag** which will be a short-term flag, as we will deprecate it in the future granted everything is a success:
- Name: `showcarousel`
- Description: `this will let users swipe through images than only showing them one per Crib`
- Select: `short-term-flag`
- Click **Create flag** to create our first Feature Flag with AWS AppConfig
- Click **Save new version** to proceed.
 
## ☁️ Update the Photo Pagination for the Feature Flag
Navigate to the **CardFeatureFlag** that we just created and click **Add new flag**
- Name: `pagination`
- Description: `change how many homes returned on the page`
- Attributes: `number`
- Type: `number`
- Value: `8`
- Required Value: [✅]
- Constraint: `5 minumum` and `12 maximum`
- Click **Create flag**

Turn the pagination flag **on** with the switch and then press **save new version**

## ☁️ Deploy the Feature Flag
You should see something like this, which tells us we have two Feature Flags (**pagination** which is switched on, and **showcarousel** which is turned off) added to the Configuration Profile **CardFeatureFlag**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/re9n1geszp6moqzkss3r.png)
 
We want to deploy this, so here is what we will do:
- Click **Start deployment**
- Click **Create Environment** 
- Name the environment: `Beta`
 
Next we will want to create a Deployment Strategy. It's important to consider factors like the **Bake time:** the amount of time AppConfig monitors for CloudWatch alarms before advancing to the next deployment or step, such as rolling back a feature.

There are 3 pre-defined options available: `AppConfig.AllAtOnce` — instant deployment to all, `AppConfig.Linear50PercentEvery30Seconds` — deploys to half of target every 30 seconds and deploy time is 1 minute (i.e. useful for testing or demos), and `AppConfig.Canary10Percent20Minutes (AWS Recommended)` — deploys slowly over time (i.e. useful for production workload).

We will, however, **Create deployment strategy**:
- Name: `FFWorkshop_Deployment`
- Type: `Linear`
- Step percentage: `10`
- Deployment time: `1 minute`
- Bake time: `1 minute`g

Select **Create deployment strategy** and then **Start deployment** which will take about a couple of minutes to fully release. It will ultimately look like this when it's been deployed fully:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6ng18jrmv1fvuhe997c.png)

## ☁️ Configure AWS Lambda
**[AWS Lambda](https://aws.amazon.com/lambda/) functions** are private functions that run in the cloud and allow you to run code without the need to run a server. You simply need to call a script to connect with them and then you can run compute without needing to provision a back-end for it.

> 
For some background context on AWS Lambda, check out Parts 1 and 2 of my "Serverless Workflows with Step Functions and Lambda" here:

> {% youtube https://www.youtube.com/watch?v=deKOfl6FHPg %}
> {% youtube https://www.youtube.com/watch?v=qEkkZzbmykQ %}

To set up Lambda for our project, we will:
- Navigate to **AWS Lambda**
- Click **Create Function**
- Name: `FF_Lambda`
- Runtime: `Node.js 14.x`
- Click **Create function**

In the code editor, add this code (generously provided to us by AWS) which allows us to toggle the Feature Flag on and off:
```js
const http = require('http');
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient();

exports.handler = async (event) => {

    const res = await new Promise((resolve, reject) => {
        http.get(
            "http://localhost:2772/applications/AWSomeCribRentals/environments/Beta/configurations/CardFeatureFlag",
            resolve
        );
    });

    let configData = await new Promise((resolve, reject) => {
        let data = '';
        res.on('data', chunk => data += chunk);
        res.on('error', err => reject(err));
        res.on('end', () => resolve(data));
    });

    const parsedConfigData = JSON.parse(configData);

    const DynamoParams = {
        TableName: 'AWSCribsRentalMansions'
    };
    
//Fetching the listings from DynamoDB

    async function listItems() {
        try {
            const data = await docClient.scan(DynamoParams).promise();
            return data;
        } catch (err) {
            return err;
        }
    }

//Checking for the Caraousel Feature Flag
    if (parsedConfigData.showcarousel.enabled == true) {
        let returnhtml = ``;

        try {

            const data = await listItems();

            for (let i = 0; i < parsedConfigData.pagination.number; i++) {
                returnhtml += `<div class="col-md-4 mt-4">
    		    <div class="card profile-card-5">
    		        <div class="card-img-block">

                        <div class="slideshow-container">`;

                for (let j = 0; j < data.Items[i].Image.length; j++) {
                    returnhtml += `<div class="mySlides` + (i + 1) + `">
                    <img class="card-img-top" style="height: 300px;" src="` + data.Items[i].Image[j].name + `" style="width:100%">
                  </div>`;
                }

                returnhtml += `</div>`;
                
                if(data.Items[i].Image.length > 1) {
                    returnhtml += `<a class="prev" onclick="plusSlides(-1, ` + i + `)">&#10094;</a>
                            <a class="next" onclick="plusSlides(1, ` + i + `)">&#10095;</a>`;
                }
                
    		        returnhtml += `</div>
                    <div class="card-body pt-0">
                    <h5 class="card-title">` + data.Items[i].Name + ` <span style="font-size: 0.7em;color:rgb(255, 64, 64)">(` + data.Items[i].Location + `)</span></h5>
                    <p class="card-text">` + data.Items[i].Description + `</p>
                    <a class="btn btn-primary"style="display: inline" href="#">Check Availability</a>
                    <span style="float: right;cursor: pointer;" onclick="favoriteStar(this)"><span class="fa fa-star"></span></span>
                  </div>
                </div>
    		</div>`;
            }

            return {
                statusCode: 200,
                body: returnhtml,
            };

        } catch (err) {
            return {
                error: err
            }
        }

    } else {

        let returnhtml = ``;

        try {

            const data = await listItems();
            console.log("dynamo db data: ", data)

//Checking for Pagination Numbers
            for (let i = 0; i < parsedConfigData.pagination.number; i++) {
                returnhtml += `<div class="col-md-4 mt-4">
    		    <div class="card profile-card-5">
    		        <div class="card-img-block">
                    <img class="card-img-top" style="height: 300px;" src="` + data.Items[i].Image[0].name + `" style="width:100%"
                        alt="Card image cap" style="height: 300px;">
    		        </div>
                    <div class="card-body pt-0">
                    <h5 class="card-title">` + data.Items[i].Name + ` <span style="font-size: 0.7em;color:rgb(255, 64, 64)">(` + data.Items[i].Location + `)</span></h5>
                    <p class="card-text">` + data.Items[i].Description + `</p>
                    <a class="btn btn-primary"style="display: inline" href="#">Check Availability</a>
                    <span style="float: right;cursor: pointer;" onclick="favoriteStar(this)"><span class="fa fa-star"></span></span>
                  </div>
                </div>
    		</div>`;
            }

            return {
                statusCode: 200,
                body: returnhtml,
            };

        } catch (err) {
            return {
                error: err
            };
        }
    }
};

```

Now we will want to add a layer to our Lambda Function (scroll to the bottom of the page and find **Add a layer**)
- Select `AWS-AppConfig-Extension`
- Click **Add**

If all goes well, it should look like this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s3asabluhej5b9ne91jr.png)
 

#### ⚠️ Warning
If you do not see `AWS-AppConfig-Extension` in the drop-down of AWS-provided layers...make sure you are actually in Node.js 14.x version of runtime (not 16.x like below). At the time of making this blog post, Node.js 16.x has not been configured to allow for the AppConfig extension yet.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0naf5k30r5m6s7od8of.png)
 
## ☁️ Set up the AWS Lambda Function URL
One of the amazing benefits of AWS Lambda is the ability to add HTTPS endpoints to any serverless function we launch in Lambda, as well as configure CORS headers if we wish.

Under the function overview:
- Click **Configuration**
- Click **Function URL** and then **Create Function URL**
- Auth Type: `NONE`
- Configure cross-origin resource sharing (CORS) [✅]
- We will include the wildcat `*` for this demo for `Allow origin`, `Expose headers`, `Allow headers`, and `Allow methods`...but you would want to configure more secure methods of connection for origin/headers in production.
- Click **Save** and save the Function URL for later.

Next, we need to update the Execution Role to ensure our Lambda function has the right IAM permissions to access AppConfig using the AWS best practice and principle of "least privilege." Basically, don't allow more permissions than are required for the need/resource.
- Go to **IAM (Identity and Access Management)**
- Select **Policies**
- Go to **Create Policy**, then **Choose a service** and select **AppConfig**. 
- For Actions, select **All AppConfig actions**
- For Resources, select **All Resources**
- Click **Next** to proceed through the steps and then at the end under name, write `AppConfigAllAccess` to name this IAM role.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lnv21b4fu3g1g4ahtu7h.png)
 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hpjdjsnpufcezc28luco.png)
 
Next, we will attach these policies to our Lambda Function: 
- AmplifyFullAccess
- AppConfigFullAccess
- AppConfigAllAccess - this is the one we just made
- AmazonDynamoDBFullAccess

The screen will look like this when you update the function and attached the policies successfully:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a2on8neuuzjtgo3y20qr.png)
 

## ☁️ Create and Populate a DynamoDB Table using an EC2 Instance (VM)

This was a very eye-opening part for me because the workshop had a lot of elements where a single user would be completing the upload/scripts from their computer. But what if you wanted to do it from a Virtual Machine because you have a different AWS Profile on your computer you don't want to overwrite/mess with? Or what if you wanted multiple people to have access to the same server files/items?

You can configure an EC2 server and run all the commands as you would on your normal computer, but instead on a rented server/computer, in the cloud!

<img width="100%" style="width:100%" src="https://media.giphy.com/media/SACoDGYTvVNhZYNb5a/giphy.gif">

This is where the real fun starts...

First, we'll set up an EC2 instance so that we can get a real-time server running and that we can SSH into.
- Navigate to the **EC2** service in the AWS list of tools
- **Launch an instance**
- Choose: `t2.medium` (this will give us a little bit more power than the `t2.micro` BUT be cognizant that the `t2.medium` server will incur costs. If you do not want to incur costs, choose a `t2.micro` server instead).
- Turn on all public access to the server (this is just for demo/testing purposes, so if you are going into production, you would want to lock down the server).
- Once you confirm all the settings, save your keypair to your Computer/drive (this is important that you do not lose this), and click **Launch**

The server will look like this once it is running:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0vuydjkyrp2bdz3x1ci.png)
 
## ☁️ Create the DynamoDB Table from within an EC2 Server Using Linux

EC2 forces you to `chmod 400` of the keypair so only your user can read/edit the file — anyone else would need to `sudo`. Make sure that the path does link to your terminal (Mac) or PowerShell (Windows). **TIP**: You can easily drag and drop a file from the Finder or Windows Explorer into your command line to automatically generate the file path.

```bash
chmod 400 /User/FF_Workshop_Keypair.cer
```

Now we will SSH into the server with the keypair and Public DNS.
- We will use `ec2-user` in the first part because this is an EC2 Instance
- We will then pull the Public IPv4 DNS of the EC2 instance which you will see in the above screenshot on the right for the Instance Summary: `ec2-3-82-148-71.compute-1.amazonaws.com` 

```bash
ssh -i /User/FF_Workshop_Keypair.cer ec2-user@ec2-3-82-148-71.compute-1.amazonaws.com
```

It will look like this if you are successfully signed into the server:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qkh43rfpe9bigd5rgegg.png)

Now we need to configure `aws` inside of the server.

The elements we will need to configure attributes for are: Default Region, Access Key ID, Secret Access Key, and AWS Session Token. These were generated for us via the AWS Event Bridge, but without it being set up for us, we would need to configure the aws profile (link to more about this in the [AWS docs](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html))

We will write `aws configure` and enter, and then copy and paste all of these values into the command line and press enter when prompted for each one. And then under output format, put `json`.
 
 
```bash
AWS_DEFAULT_REGION=us-east-1
AWS_ACCESS_KEY_ID=MSIA...MQ5U
AWS_SECRET_ACCESS_KEY=YI2d...JuZU
AWS_SESSION_TOKEN=IQoJ...wef4

```

It will look like this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8un5ph4nym4lkif2l8q5.png)

## ☁️  Next, you have to make sure the right AWS account is active for the session in the command line

Right now, you have configured AWS above but it’s not set up to connect to anything…we know this because if we then try to do: `aws sts get-caller-identity` we will get the following error: Or code snippet:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4o1zj2d58ndq07xuwbex.png)
```bash
An error occurred (InvalidClientTokenId) when calling the GetCallerIdentity operation: The security token included in the request is invalid.
```

To fix this, we need to add in the credentials into the command line as environmental variables and export (print) them into the profile for AWS in the Linux instance (same values as before):
```bash
export AWS_DEFAULT_REGION=us-east-1
export AWS_ACCESS_KEY_ID=MSIA...MQ5U
export AWS_SECRET_ACCESS_KEY=YI2d...JuZU
export AWS_SESSION_TOKEN=IQoJ...wef4
```

We can verify that the environmental variables are saved to our Linux session with `printenv AWS_DEFAULT_REGION` or `printenv AWS_ACCESS_KEY_ID`

If the output prints what we entered in previously, then it worked!

To validate that our AWS account is authenticated/synced, type `aws sts get-caller-identity`. If this worked correctly, you should see a print out of something like the below:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5n56eyi6848rl10ol7op.png)
```json
{
    "Account": "354789352487", 
    "UserId": "AROAVFGYX4QT3O2UKAPLI:MasterKey", 
    "Arn": "arn:aws:sts::354789352487:assumed-role/TeamRole/MasterKey"
}
```

In our AWS account, in the top right, you'll see the right Account # (the same as in "Account" in the terminal). 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mwyklmsi39xuxgvt3w9y.png)
 

Alright we did it!

To further validate this, if we use `aws ec2 describe-instances` then we should get a print out of the instances we are running or have terminated:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eckxhty17fjrrvc5s8i9.png)
 
## ☁️ Now we populate the DynamoDB Table to hold image links and home information

While we have our SSH link open to our server and running, let's open up another terminal window. This is because we cannot upload a local file from within the server (remember, we are in a virtual machine there...so we are in a completely different computer that doesn't recognize our local directories on our computer).

We need to upload a file to the server and then call it that way to communicate with DynamoDB. We will use the `scp` (Secure Copy Protocol) command in Linux, which is used to copy/send/retrieve files securely between servers.

For us, the script we will run is this below: we reference a IAM key-pair certificate file `FF_Workshop_Keypair.cer` file on our computer and then the `AWSCribsRentalMansions.json` file on our computer and then our EC2 AMI’s Public IPv4 DNS `ec2-3-82-148-71.compute-1.amazonaws.com` and declare that we will put it as a loose-hanging file in the server at position: `/` .

The linux command is:
- 1st part is the path to the secret key file
- 2nd part is the path to the file we want to upload
- 3rd part is `ec2-user` because it's an AMI EC2 "at" the Public IPv4 DNS URL `ec2-3-82-148-71.compute-1.amazonaws.com`
- 4th part is a `~` since we want to upload it as a loose-hanging file in the server.

```bash
scp -i /Users/FF_Workshop_Keypair.cer /Users/AWSCribsRentalMansions.json  ec2-user@ec2-3-82-148-71.compute-1.amazonaws.com:~
```

If all goes well, you should see a 100% printed below the script you entered.

Now, let's test if the file was uploaded in our server. Switch back to the terminal window where we SSH'ed into the server. If we do an `ls` from the root directory, we should now see the `json` file!

Now we will populate the DynamoDB table with our file uploaded to the EC2 instance:
```bash
aws dynamodb batch-write-item --request-items file://AWSCribsRentalMansions.json
```

It will look like this (don't worry that it says "Unprocessed Items" - this is normal ☺️):
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/55uydqm0loczvez1er5g.png)

Let's check if DynamoDB really did receive all of the files like our command line told us. If it worked successfully, it will look like this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/njpbfmgprdt8phno1zlk.png)
 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/95gw70lsasw73i3147q6.png)
 
Nice!! The files are all there!


## ☁️ Set up website on AWS with AWS Amplify and AWS CodeCommit
We will need 2 specific tools to hold our code and then to push our code to the internet with a link users can access.
- **AWS Amplify**: for delivering the front-end
- **AWS CodeCommit**: hold the code to deploy to Amplify

In our `index.html` file in the repo (check out my **[starter repo here](https://github.com/BrianHHough/AWSAppConfigWorkshop/tree/initial)** from the code provided by AWS), we will need add the Lambda function calling AppConfig into the `<script>` tag of the `index.html` file.

We will turn this:
```html
<!-- api call to populate cribs --> -->
<script>
     fetch('lamba-url-here')
     .then(response => response.text())
     .then((data) => {
            document.getElementById("populateCribsHere").innerHTML=data;
            setUI();
  });
</script>
```

Into this:
```html
<!-- api call to populate cribs --> -->
<script>
        fetch('https://2v35t2ppjx4vh7ygjzip7nauji0yzmxl.lambda-url.us-east-1.on.aws/')
     .then(response => response.text())
     .then((data) => {
            document.getElementById("populateCribsHere").innerHTML=data;
            setUI();
  });
</script>
```

Remember that Lambda Function URL we got from before? We will put that into the `lamba-url-here`. We can get that here:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztzmju7x1fmu0dc3ixrc.png)
  
## ☁️ Use AWS CodeCommit to Host Private Git Repo
[AWS CodeCommit](https://aws.amazon.com/codecommit/) is a secure, highly scalable, managed source control service that hosts private Git repositories.
- Go to **CodeCommit**
- Click **Create a Repository**
- Name: `AWSomeCribRepo`
- Description: `This holds the code of my AWSomeCribRepo project`
- Click **Create**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xekcxwp4rq4wl4ba3v9l.png)
 
After the repo is created, we need to add our code to this CodeCommit repo. We will see a couple different methods for how to Connect. We will choose the `HTTPS (GRC)` method.

> HTTPS (GRC) is the protocol to use with git-remote-codecommit (GRC). This utility provides a simple method for pushing and pulling code from CodeCommit repositories by extending Git. It is the recommended method for supporting connections made with federated access, identity providers, and temporary credentials.

What we really want to do is `pip install git-remote-codecommit` to install the git-remote-code-commit framework, BUT, we don't have `pip` installed yet. We need Python to run pip so let's check if it's installed:
- Run `python --version` — for me, the output is `Python 2.7.18` and it should be added since this is an EC2 instance.
- To install pip, run `sudo yum install python-pip` 

It should look like this if it's successfully installed:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kg5vjiz0c359l7p6a74a.png)
 
- Now that pip is installed, let’s run the command we wanted to initially: `pip install git-remote-codecommit`.

It should look like this if all goes well:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o5irrhuqb3uycz9hf2ro.png)
 
Now we want to clone the repository to our EC2 instance with this command: `git clone codecommit::us-east-1://AWSomeCribRepo`. However, we don’t have git yet in our server, so we’ll install git this way:
- Run this command: `sudo yum install git`

If all goes well, it will look like this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n1s69x0oku3vd5b2ltws.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9dijpf6y4lzyu4ftl87i.png)
  
- We will verify the installation went through with: `git --version` — if it worked, you will see a print out like `git version 2.32.0`
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t1wvkquiwbju1q4i8vk7.png)
 
- Now we will finally run: `codecommit::us-east-1://AWSomeCribRepo` — this will clone the empty repo into our server. When we do ls we see the blue folder in there:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e31yg6g0oqsfgtr77rjv.png)
 
✨ Amazing job! We now have a link to our AWS CodeCommit Repo (in our AWS Environment) and our EC2 server. Especially cool because this is now OUR computer, but a virtual machine.

## ☁️ How do we upload a folder’s contents locally (our code repo) into a code repo in our EC2 instance using Linux?

Linux can be a bit confusing here but what we essentially will be doing is using a 2nd terminal window to connect to our SSH'ed terminal window, and then use that to connect to AWS. Don't worry... we will get there 😊 

We will use `scp` to upload the file contents WITHIN our local folder and serve that content INTO the folder that is our AWS CodeCommit-connected folder.
- We will use `scp` (secure copy protocol)
- We use the -r flag (to recursively go through all the files)
- We will input the path of our Keypair
- We will then input the path of our folder and because it's the contents WITHIN it, we add the wildcard `/*` after the folder name
- We will then input the EC2 instance with `ec2-user` and `@` and then our EC2's Public IPv4 DNS value.

```
scp -r -i  /Users/FF_Workshop_Keypair.cer /Users/AWSAppConfigWorkshop/*  ec2-user@ec2-3-82-148-71.compute-1.amazonaws.com:~/AWSomeCribRepo
```

You can see how the commands work between the 1st and 2nd terminal windows. On the right (connected to our local computer), we serve the files to the server. Then on the left (connected to the EC2 instance), we check to see if the files were in fact uploaded.

#### ⚠️ NOTE: in the screenshot, it says `...amazonaws.com:~/test` but this was taken while I was testing Linux 😊. You will want that part to read `amazonaws.com:~/AWSomeCribRepo` like what is referenced in the above code snippet:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qtgqq9kwi76coq1nacaz.png)


## ☁️ Now we will push the code to AWS Code Commit
Back in our SSH'ed terminal window, we will want to make sure we are in our `AWSomeCribRepo` and run the commands to send the repo into AWS CodeCommit:
- Run: `cd AWSomeCribRepo`
- Add all the files in the directory to git with `git add -A`
- Create a commit message: `git commit -m "commit from EC2"`
- Push the code to AWS CodeCommit: `git push`


When you do the `git push`, you will see output like this:
```bash
Enumerating objects: 1847, done.
Counting objects: 100% (1847/1847), done.
Delta compression using up to 2 threads
Compressing objects: 100% (1845/1845), done.
Writing objects: 100% (1847/1847), 3.99 MiB | 4.68 MiB/s, done.
Total 1847 (delta 213), reused 0 (delta 0), pack-reused 0
To codecommit::us-east-1://AWSomeCribRepo
 * [new branch]      master -> master
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8zxjxcmt7vho8ny34wsv.png)

#### ⚠️ IMPORTANT: If you run into this type of error after `git push`:
> ```bash
fatal: unable to access 'https://git-codecommit.us-east-1.amazonaws.com/v1/repos/AWSomeCribRepo/': The requested URL returned error: 403
```
> Or looks like this in the command line: 
> ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sxvqqwl25uso2gum58k4.png)

> It’s probably because your configuration environmental variables aren’t set correctly in the EC2 server. 

> These are provided to us thanks the AWS EventBridge for the workshop, but you can retrieve these as part of your AWS Account. You need to make sure you are "logged in" or authenticated in the EC2 instance for the push command to know which AWS Account's CodeCommit to push to. Imagine you push your code to a stranger's CodeCommit... (that would be so bad...). This is to make sure we are truly authenticated in the right place to then send our code to the right destination.

> ```bash
AWS_DEFAULT_REGION=us-east-1
AWS_ACCESS_KEY_ID=MSIA...MQ5U
AWS_SECRET_ACCESS_KEY=YI2d...JuZU
AWS_SESSION_TOKEN=IQoJ...wef4
```

> Confirm that `aws sts get-caller-identity` returns a value like this:
> ```bash
{
    "Account": "354789352487", 
    "UserId": "AROAVFGYX4QT3O2UKAPLI:MasterKey", 
    "Arn": "arn:aws:sts::354789352487:assumed-role/TeamRole/MasterKey"
}
```

> Now when you do the `git push`, you will see output like this:
> ```bash
Enumerating objects: 1847, done.
Counting objects: 100% (1847/1847), done.
Delta compression using up to 2 threads
Compressing objects: 100% (1845/1845), done.
Writing objects: 100% (1847/1847), 3.99 MiB | 4.68 MiB/s, done.
Total 1847 (delta 213), reused 0 (delta 0), pack-reused 0
To codecommit::us-east-1://AWSomeCribRepo
 * [new branch]      master -> master
```
> ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/llm1qqq508l5vz47qnim.png)
 

Back in the AWS Console, CodeCommit will now go from this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1x3w7d2a0mey6smwlls8.png)
 
To this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5abeekku95g4edfvro6p.png)
 
✨ You have now created a repository for your website with files uploaded from your computer, to your EC2 instance, and now to AWS. Next, you will connect this repository to AWS Amplify to host your website with a real URL.

## 🚀 Host your Website on AWS Amplify
One of the best benefits of [AWS Amplify](https://aws.amazon.com/amplify/) when running deployments is the ability to see the various stages of the build process. This becomes very helpful when there are errors or you need to check the validation of a build, the logs include a great amount of detail, and of course you can roll in a vast amount of tools and AWS features thanks to Amplify and it's framework to abstract complexity from deployments.

To accomplish this we will:
- Go to the **AWS Amplify** service in the AWS Console
- Go to **All apps**
- Select **New App** and in the drop-down, select **Host web app**
> ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hsmd7lpti5qdi28ffbob.png)
- Select **AWS CodeCommit** and then **Continue** to advance
> ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mdrd9gewivwpyherw9ku.png)
- Select `AWSomeCribRepo`in the CodeCommit dropdown and then **Next**
> ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hfk60pac3j49ljpdjjnt.png)
- In the **Configure build settings** make sure to click **✅ Allow AWS Amplify to automatically deploy all files hosted in your project root directory** and then press **Next**.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aml77p5rrtm9sk4ktae8.png)
- Now click **Save and deploy**
> ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztc1ckh5wanli20xxufb.png)
 
AWS Amplify will then go through it’s 4 stages of deployment automatically for us

- Provision
- Build
- Deploy
- Verify

It will go from this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wi344g6ulu8vzxj585rr.png)

✨ To this: 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/84y9ni4b1vi1045cua8h.png)
  
When our site is building, it will go from this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qnsitonfl3rhqfzidjyo.png)

✨ To this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3nhmx0yl657zpuyamsjh.png)
 
## ☁️ Using Feature Flags
If you notice in the deployed version, it doesn't show that carousel feature we created AppConfig for in the first place... where did it go? Wasn't it supposed to appear there?

We haven't turned it on yet 😊 

So what we are going to now do is turn on and deploy the carousel Feature Flag to launch this feature. We will then put on an operational toggle to ensure that during spikes in usage, it will limit the number of homes displayed on the homepage. This will help to optimize our performance with fewer queries on our DynamoDB table at peak times. 
- Go to **AppConfig**
- Select our app `AWSomeCribRentals`
- Click on the Feature Flag we created: `CardFeatureFlag` (notice that the CardFeature Flag is OFF for the `showcarousel` Flag as shown below)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9d81u6lkx4lvqzl410ub.png)

- Toggle the `showcarousel` flag ON and then **Save new version**
- Click **Start Deployment** and configure the elements
- Environment: `Beta` - we set this up earlier
- Hosted configuration version: 2
- Deployment strategy: `FFWorkshop_Deployment` 
- Description: `Switching showcarousel feature on gradually for users`
- Click **Start deployment**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h9lb2yb8pb48bzcgcv3s.png)
 
The deployment (this is technically Deployment #2 because we already deployed earlier on this Feature Flag) will look like this as it builds and gives you data about the deployment like so until its state reaches 100%:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0hwozcx4lgskjlcipnxk.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aeytc0k4jr1jcgw09bke.png)
 
## ✨ Viola! Check out the updated changes to our app:
Notice the carousel? This is now a feature that users will begin to see in our application:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kbbk5mipwbtqlkyo7b2q.png)
 
Here is a video of the deployed AppConfig Feature Flag in action live on the URL:
{% embed https://player.vimeo.com/video/719701137?h=a0b60d98f5 %}

## ☁️ Operational Feature Flags
This above is an example of a feature add that we will definitely add to our site and keep. However, let's say we wanted to throttle the number of requests coming back to users when they search for something or want a certain type of data. 

Remember our `pagination` Feature Flag in `CardFeatureFlag` that we configured earlier? Well, if we want to adjust how many results are returned in the pagination results (i.e. to improve performance when there is a lot of traffic), we can do this by:
- Navigate to **AppConfig**
- Click on the **CardFeatureFlag** that we set up
- Select the **pagination** flag
- Click **Edit**
- Change the number in the **value** to: 6
- Click **Confirm**
- Ensure this flag's toggle is turned **ON**
- Click **Save new version**
- Click **Start deployment**
- Keep the same settings as you did in Deployment #2 above, and especially make sure the Deployment Strategy says: `FFWorkshop_Deployment`
- Click **Start deployment**

✨ When you navigate to your URL, you should now see 6 crib results! This means we can adjust this feature manually from AWS AppConfig without needing to delete or comment out the code and push a new version.

## 🧹 Clean up Release Flags
If you need to clean up a release flag — for example, let's say the feature add was a big success and there's no reason for you to revert or roll-back the changes — it's important to know what to do.

If you do not need to have a Feature Flag for one of your released features, you can simply delete that short-term flag since it's no longer needed. We already marked the `showcarousel` as a short-term flag

To delete a flag, we will:
- Go to **AppConfig**
- Select our app: **AWSomeCribRentals**
- Click on the Feature Flag: `CardFeatureFlag`
- On the `showcarousel` Feature Flag, click **Delete** and re-confirm **Delete** on the pop-up.
- Click **Save new version** to reflect this change

In a real life situation, we would want to clean up the code to reflect this feature add, but since this was a workshop setting, we did not do that here. For example, in **line 39** of the Lambda Function, you would need to make sure that the Crib carousel feature is not conditionally turned on via AppConfig.

## 🧹 Clean Up Our AWS Resources:
Congrats on making it through! Since you are probably running these services on your own AWS account, it is important to make sure you delete instances of the services so that you do not incur costs:

- **EC2:** terminate/delete the running instance
- **AWS Lambda:** delete the function
- **DynamoDB:** delete the `AWSCribsRentalMansions` table
- **Amplify:** delete the app we created
- **CodeCommit:** delete the repository we created
- **AppConfig:** delete each Feature Flag, the configuration profile, and in environments, delete Beta, as well as the AWSomeCribRentals application

WOW! That was a lot! Hopefully you enjoyed going through this AWS workshop with me and the steps I took. Let me know what you think below and if you have any questions on any of the AWS tools I used, feel free to let me know what you thought 😊

## 🧑‍💻 Subscribe to the Tech Stack Playbook for more:
Let me know if you found this post helpful! And if you haven't yet, make sure to check out these free resources below:
 
- <b>Follow my Instagram for more: <a href="https://instagram.com/brianhhough" target="_blank">@BrianHHough</a></b>
- <b>I'm also on Tik Tok too: <a href="https://www.tiktok.com/@brianhhough" target="_blank">@BrianHHough</a></b>
- <b>Watch my latest <a href="https://youtube.com/brianhhough" target="_blank">YouTube video for more</a></b>
- <b>Listen to my Podcast on <a href="https://podcasts.apple.com/us/podcast/tech-stack-playbook/id1553712082" target="_blank">Apple Podcasts</a> and <a href="https://open.spotify.com/show/01bHbabSyBmPfbc3ORMZa9?si=av_16aPbQEqL_bVu3RnO0g" target="_blank">Spotify</a></b>
- <b>Join my FREE <a href="https://facebook.com/groups/techstackplaybook" target="_blank">Tech Stack Playbook Facebook Group</a></b>
- <b>Join entrepreneurs and developers in the <a href="https://bit.ly/HelpMeDevDiscordLink" target="_blank">HelpMeDev Discord Server</a></b>

Let's digitize the world together! 🚀

-- Brian