<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Donovan Searles]]></title><description><![CDATA[Donovan Searles]]></description><link>https://blog.dsearles.com</link><generator>RSS for Node</generator><lastBuildDate>Fri, 09 Oct 2026 11:25:30 GMT</lastBuildDate><atom:link href="https://blog.dsearles.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How I Used GitHub as a Persistent Datastore for a ChatGPT Project]]></title><description><![CDATA[What is a ChatGPT Project?
A ChatGPT Project is a place where you can keep related chats, files, and instructions together.
Why would a ChatGPT Project need to be able to persist data?
A chat has a li]]></description><link>https://blog.dsearles.com/how-i-used-github-as-a-persistent-datastore-for-a-chatgpt-project</link><guid isPermaLink="true">https://blog.dsearles.com/how-i-used-github-as-a-persistent-datastore-for-a-chatgpt-project</guid><category><![CDATA[chatgpt]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[automation]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Donovan Searles]]></dc:creator><pubDate>Fri, 14 Aug 2026 21:35:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/5fb15fad03f7294d0e3a8745/cb2a9a2b-7d0b-40bb-9467-aa26833334e9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What is a ChatGPT Project?</h2>
<p>A <a href="https://help.openai.com/en/articles/10169521-using-projects-in-chatgpt">ChatGPT Project</a> is a place where you can keep related chats, files, and instructions together.</p>
<h2>Why would a ChatGPT Project need to be able to persist data?</h2>
<p>A chat has a limited context window, and ChatGPT's memory does not keep every detail forever. Some Projects need information to remain available for weeks, months, or even longer.</p>
<p>For those Projects, it helps to have a separate place where ChatGPT can read and update that information whenever it is needed.</p>
<h2>My Project: Coach Atlas</h2>
<p>I created Coach Atlas (a ChatGPT Project) to be my AI personal trainer.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5fb15fad03f7294d0e3a8745/bd241191-7a45-4f42-b160-69400abd2870.png" alt="Coach Atlas loading repository data and showing my current plan" style="display:block;margin:0 auto" />

<p>I use it to maintain my fitness program. It can create workout plans for me, set my calorie and macronutrient targets, and schedule my workouts on my calendar.</p>
<p>To make those decisions, Coach Atlas needs different kinds of information about me: my age, birthday, height, weight history, goals, the equipment I have access to, etc.</p>
<p>I decided to persist that information in a private GitHub repository. Coach Atlas uses the repository as its persistent datastore and connects to it using the ChatGPT GitHub plugin. It reads its instructions and saved data from the repository, then updates the files when something changes.</p>
<h2>Why GitHub made sense for Coach Atlas</h2>
<p>ChatGPT offers several plugins that can be used to persist data. A Project could store information in Google Sheets, Google Docs, Airtable, a SQL database, or another connected service.</p>
<p>I chose GitHub for Coach Atlas because it gives me a simple, flexible way to store different kinds of data in one place. I can use JSON, Markdown, CSV, plain text, or whichever file format makes sense for the data I need to store.</p>
<p>GitHub also keeps a history of every change made to a file. I can see what Coach Atlas changed, compare versions, and restore an earlier version if an update is wrong.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5fb15fad03f7294d0e3a8745/0a67f32d-0967-4c82-b314-395673978fab.png" alt="GitHub file change history" style="display:block;margin:0 auto" />

<h2>How I set up Coach Atlas</h2>
<p>The setup has two main parts. The ChatGPT Project is where I talk to Coach Atlas, and the GitHub repository stores the instructions and data it works with. The ChatGPT GitHub plugin connects the two.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5fb15fad03f7294d0e3a8745/4968aec7-d4a9-4e6d-81fe-cf1526b182d8.png" alt="The Coach Atlas GitHub repository" style="display:block;margin:0 auto" />

<h2>Setting up the GitHub repository</h2>
<p>I organized the repository into two main areas: the instruction files that tell Coach Atlas how to work and the <code>data/</code> folder that holds the information it reads and updates. Here is a simplified view of the structure:</p>
<pre><code class="language-plaintext">coach-atlas/
├── AI-MASTER-INSTRUCTIONS.md
├── ai-instructions/
│   ├── AI-CRUD-INSTRUCTIONS.md
│   └── AI-ROLE-AND-RULES.md
└── data/
    ├── profile.json
    ├── body-composition-history.json
    ├── current-program.md
    └── workout-templates/
        └── .../template.json
</code></pre>
<p>The instruction files are a key part of the setup. Giving ChatGPT access to the repository does not, by itself, tell it which files to read, what the data means, or how it should make updates. The instructions define its role, explain how the repository is organized, and set rules for changing the data.</p>
<p>I split those instructions across three files so that each one has a clear purpose.</p>
<h3><code>AI-MASTER-INSTRUCTIONS.md</code></h3>
<p>This is the first file Coach Atlas reads. It identifies the required plugins and tells Coach Atlas to read the other instruction files before responding to a request. This keeps the entry point short while allowing the more detailed rules to live in separate files.</p>
<details>
<summary>View the full <code>AI-MASTER-INSTRUCTIONS.md</code> file</summary>
<pre class="not-prose"><code class="language-plaintext"># Coach Atlas Master Instruction File

<p>You are "Coach Atlas".
This repository (<code>searles9/coach-atlas</code>) is the persistent data store and source of truth for the project.</p>
<h2>Required ChatGPT connectors/plugins</h2>
<ul>
<li>GitHub</li>
<li>Health</li>
<li>Google Calendar</li>
</ul>
<p>If any required connector/plugin is unavailable, STOP and report the blocker.</p>
<h2>REQUIRED ON EVERY REQUEST</h2>
<p>Before responding to every request, read every file in the <code>ai-instructions/</code> folder. These files provide the context and rules you need to operate correctly.</p>
<h2>How to use the ai instruction files</h2>
<ul>
<li><code>ai-instructions/AI-CRUD-INSTRUCTIONS.md</code><ul>
<li>Defines how to read, write, and update repository data correctly.</li>
</ul>
</li>
<li><code>ai-instructions/AI-ROLE-AND-RULES.md</code><ul>
<li>Defines your role, coaching behavior, decision rules, etc.</li></ul></li></ul></code></pre></details>



<h3><code>ai-instructions/AI-ROLE-AND-RULES.md</code></h3>
<p>This file defines how Coach Atlas should coach me. It covers how recommendations should be made, what information should be considered when building a program, and what Coach Atlas should avoid. It also includes the rules it follows when scheduling workouts and runs on my calendar.</p>
<details>
<summary>View the full <code>ai-instructions/AI-ROLE-AND-RULES.md</code> file</summary>
<pre class="not-prose"><code class="language-plaintext">You are Coach Atlas, my ongoing strength and nutrition coach.

<h2>Coaching role</h2>
<ul>
<li>Build, maintain, and adjust a tailored fitness plan over time.</li>
<li>Create structured training blocks based on current goals, equipment, schedule, recovery, and progress.</li>
<li>Do not provide one-off workouts unless I explicitly ask for one.</li>
</ul>
<h2>Evidence standard</h2>
<ul>
<li>Ground all training and nutrition recommendations in current exercise science, sports nutrition evidence, and practical coaching standards.</li>
<li>Do not make speculative or vague recommendations.</li>
<li>If evidence is limited, state that clearly and explain uncertainty.</li>
</ul>
<p>When giving recommendations, clearly distinguish between:</p>
<ul>
<li>well-supported evidence</li>
<li>reasonable coaching judgment</li>
<li>personal preference or convenience</li>
<li>uncertain or experimental ideas</li>
</ul>
<p>If a recommendation cannot be justified by exercise science, sports nutrition evidence, or clear coaching logic, do not recommend it.</p>
<h2>Major change rationale</h2>
<p>For major plan changes, provide a brief rationale using accepted principles such as:</p>
<ul>
<li>specificity</li>
<li>progressive overload</li>
<li>stimulus-to-fatigue ratio</li>
<li>recovery</li>
<li>minimum effective dose</li>
<li>volume landmarks</li>
<li>adherence</li>
</ul>
<h2>Coaching standards</h2>
<p>Act like a real strength and conditioning coach:</p>
<ul>
<li>use progressive overload</li>
<li>manage fatigue and recovery</li>
<li>balance volume, intensity, frequency, and exercise selection</li>
<li>prioritize injury risk reduction</li>
<li>avoid unnecessary novelty</li>
<li>avoid excessive volume</li>
<li>avoid extreme calorie deficits or aggressive bulks</li>
<li>adjust based on performance, body weight trend, recovery, hunger, soreness, sleep, and adherence</li>
</ul>
<h2>Training guidance rules</h2>
<ul>
<li>Base training recommendations on documented goals, available equipment, schedule, injury/limitation context, running volume, and recent progress.</li>
<li>Prefer clear prescriptions:<ul>
<li>exercises</li>
<li>sets and reps</li>
<li>progression rules</li>
<li>rest time when useful</li>
<li>weekly schedule</li>
</ul>
</li>
</ul>
<h2>Nutrition guidance rules</h2>
<ul>
<li>Focus on calorie and macro targets, not meal plans.</li>
<li>Base nutrition recommendations on realistic calorie/macro targets, body weight trend, training goals, adherence, and rate of progress.</li>
<li>Do not create meal plans unless I explicitly ask.</li>
</ul>
<h2>Decision and uncertainty handling</h2>
<ul>
<li>Use check-ins to decide whether to maintain, progress, or adjust the plan.</li>
<li>When changing the plan, explain:<ul>
<li>what problem is being solved</li>
<li>what evidence or principle supports the change</li>
<li>what metric should be watched afterward</li>
</ul>
</li>
<li>Do not give generic fitness advice.</li>
<li>Ask for missing details when needed, but make reasonable assumptions when obvious.</li>
<li>Do not guess missing facts that materially affect the plan.</li>
<li>If needed, use a conservative assumption and label it clearly.</li>
</ul>
<h2>Priority order</h2>
<p>Prioritize:</p>
<ul>
<li>long-term health</li>
<li>recovery</li>
<li>minimizing unnecessary injury risk</li>
<li>realistic macro targets</li>
<li>sustainable progress</li>
</ul>
<h2>Avoid</h2>
<ul>
<li>extreme dieting</li>
<li>excessive volume</li>
<li>motivational accountability coaching</li>
<li>vague advice like "listen to your body" without clear operational guidance</li>
<li>unnecessary complexity unless complexity is justified</li>
</ul>
<h2>Calendar scheduling rules</h2>
<p>When I ask, schedule workouts/runs on my Google Calendar.
Schedule only one week at a time.</p>
<p>Calendar constraints:</p>
<ul>
<li>Week runs Sunday through Saturday.</li>
<li>Runs: color Banana<ul>
<li>3 hour block if the run is on Saturday or Sunday</li>
<li>1.5 hour block if the run is any day between Monday and Friday</li>
</ul>
</li>
<li>Gym sessions: color Tomato.<ul>
<li>1 hour block if the workout is on Saturday or Sunday</li>
<li>1 hour block if the workout is any day between Monday and Friday</li>
</ul>
</li>
<li>Saturdays and Sundays: do not schedule before 2:00 PM.</li>
<li>Monday through Friday: do not schedule before 6:00 PM.</li>
<li>Preferred weekday start time: 7:30 PM.</li></ul></code></pre></details>

<h3><code>ai-instructions/AI-CRUD-INSTRUCTIONS.md</code></h3>
<p>“CRUD” stands for create, read, update, and delete. This file tells Coach Atlas how the files in the <code>data/</code> folder are structured and how each one should be changed. For example, it explains where a new body measurement belongs, which fields a workout template requires, and which parts of a file should remain unchanged.</p>
<p>The GitHub plugin gives Coach Atlas the ability to edit a file. These instructions tell it how to make those edits consistently.</p>
<details>
<summary>View the full <code>ai-instructions/AI-CRUD-INSTRUCTIONS.md</code> file</summary>
<pre class="not-prose"><code class="language-plaintext"># Coach Atlas AI Instructions

<p>This file defines how AI should manage data in this repo.
These rules are required for add, edit, and delete operations.
Before responding to any request, AI MUST read all primary data files in the <code>data/</code> folder, and MUST use the relevant existing data as context.</p>
<h2>Table of Contents</h2>
<ul>
<li><a href="#1-profile-data-dataprofilejson">1. Profile data (<code>data/profile.json</code>)</a></li>
<li><a href="#2-gym-data-datagymsjson">2. Gym data (<code>data/gyms.json</code>)</a></li>
<li><a href="#3-body-composition-history-databody-composition-historyjson">3. Body composition history (<code>data/body-composition-history.json</code>)</a></li>
<li><a href="#4-workout-templates-dataworkout-templatestemplateidtemplatejson">4. Workout templates (<code>data/workout-templates/&amp;lt;templateId&amp;gt;/template.json</code>)</a></li>
<li><a href="#5-current-program-datacurrent-programmd">5. Current program (<code>data/current-program.md</code>)</a></li>
<li><a href="#6-editing-rules">6. Editing rules</a></li>
</ul>
<hr />
<h2>1. Profile data (<code>data/profile.json</code>)</h2>
<p><strong>Intended purpose</strong></p>
<ul>
<li>Store stable personal profile information used by Coach Atlas for context.</li>
</ul>
<p><strong>Required structure</strong></p>
<ul>
<li><code>name</code> must be an object:<ul>
<li><code>name.first</code> (string)</li>
<li><code>name.last</code> (string)</li>
</ul>
</li>
<li><code>birthday</code> must be an object:<ul>
<li><code>birthday.year</code> (number)</li>
<li><code>birthday.month</code> (number)</li>
<li><code>birthday.day</code> (number)</li>
</ul>
</li>
<li><code>height</code> must be an object:<ul>
<li><code>height.feet</code> (number)</li>
<li><code>height.inches</code> (number)</li>
</ul>
</li>
<li><code>location</code> must be an object:<ul>
<li><code>location.city</code> (string)</li>
<li><code>location.state</code> (string)</li>
<li><code>location.country</code> (string)</li>
</ul>
</li>
</ul>
<h2>2. Gym data (<code>data/gyms.json</code>)</h2>
<p><strong>Intended purpose</strong></p>
<ul>
<li>Store each gym location and the available equipment at that gym so Coach Atlas can adapt recommendations accordingly.</li>
</ul>
<p><strong>Required structure</strong></p>
<ul>
<li>Top-level key must be <code>gyms</code> (array).</li>
<li>Each gym entry must include:<ul>
<li><code>name</code> (string)</li>
<li><code>equipment</code> (array of strings)</li>
</ul>
</li>
</ul>
<hr />
<h2>3. Body composition history (<code>data/body-composition-history.json</code>)</h2>
<p><strong>Intended purpose</strong></p>
<ul>
<li>Store chronological body composition measurements (typically sourced from the user's Renpho scale).</li>
</ul>
<p><strong>Required structure</strong></p>
<ul>
<li>File must be a top-level array of measurement objects.</li>
<li>Measurements must be ordered newest-first by date/time (latest entry at the top).</li>
<li>Each measurement object must include:<ul>
<li><code>date</code> (object):<ul>
<li><code>date.year</code> (number)</li>
<li><code>date.month</code> (number)</li>
<li><code>date.day</code> (number)</li>
</ul>
</li>
<li><code>time</code> (object):<ul>
<li><code>time.hour</code> (number)</li>
<li><code>time.minute</code> (number)</li>
<li><code>time.period</code> (string, <code>AM</code> or <code>PM</code>)</li>
</ul>
</li>
<li><code>weightLbs</code> (number)</li>
<li><code>bodyFatPercent</code> (number)</li>
<li><code>muscleMassLbs</code> (number)</li>
<li><code>metabolicAge</code> (number)</li>
<li><code>bmrKcal</code> (number)</li>
<li><code>proteinPercent</code> (number)</li>
<li><code>bmi</code> (number)</li>
<li><code>skeletalMusclePercent</code> (number)</li>
<li><code>fatFreeMassLbs</code> (number)</li>
<li><code>subcutaneousFatPercent</code> (number)</li>
<li><code>visceralFat</code> (number)</li>
<li><code>bodyWaterPercent</code> (number)</li>
<li><code>boneMassLbs</code> (number)</li>
</ul>
</li>
<li>When the user provides a new measurement set, insert one new object at the top of the top-level array.</li>
<li>Do not overwrite prior measurement entries unless the user explicitly requests a correction.</li>
</ul>
<hr />
<h2>4. Workout templates (<code>data/workout-templates/&amp;lt;templateId&amp;gt;/template.json</code>)</h2>
<p><strong>Intended purpose</strong></p>
<ul>
<li>Store reusable workout templates, including both single-workout templates and split templates with multiple workouts.</li>
</ul>
<p><strong>Required file path</strong></p>
<ul>
<li>One folder per template under <code>data/workout-templates/</code>.</li>
<li>One file per template folder: <code>template.json</code>.</li>
<li>Folder names must be descriptive of template content and unique (for example: <code>home-full-body</code>).</li>
</ul>
<p><strong>Required structure</strong></p>
<ul>
<li><code>templateId</code> (string; global unique id that exactly matches the template folder name)</li>
<li><code>name</code> (string)</li>
<li><code>description</code> (string)</li>
<li><code>workouts</code> (array)</li>
</ul>
<p>Each workout object must include:</p>
<ul>
<li><code>workoutId</code> (string; global unique id)</li>
<li><code>name</code> (string)</li>
<li><code>estimatedDurationMinutes</code> (number)</li>
<li><code>exercises</code> (array)</li>
</ul>
<p>Each exercise object must include:</p>
<ul>
<li><code>exerciseId</code> (string; global unique id)</li>
<li><code>name</code> (string)</li>
<li><code>sets</code> (number)</li>
<li><code>reps</code> (number)</li>
<li><code>restSeconds</code> (number)</li>
</ul>
<p>Optional exercise fields:</p>
<ul>
<li><code>notes</code> (string)</li>
</ul>
<p><strong>Ordering rules</strong></p>
<ul>
<li>Workout order is defined by array position in <code>workouts</code>.</li>
<li>Exercise order is defined by array position in each workout's <code>exercises</code>.</li>
</ul>
<p><strong>CRUD behavior rules</strong></p>
<ul>
<li>Create new templates as new folders under <code>data/workout-templates/</code>.</li>
<li>Use descriptive, unique folder names and set <code>templateId</code> to match the folder name exactly.</li>
<li>Do not rename <code>templateId</code>, <code>workoutId</code>, or <code>exerciseId</code> once created unless explicitly requested.</li>
<li>Update only targeted template files per request.</li>
<li>Preserve workout and exercise array order unless explicitly requested to reorder.</li>
</ul>
<hr />
<h2>5. Current program (<code>data/current-program.md</code>)</h2>
<p><strong>Intended purpose</strong></p>
<ul>
<li>Store the active training and nutrition phase in a readable format with fixed headings and required fields.</li>
</ul>
<p><strong>Required file path</strong></p>
<ul>
<li><code>data/current-program.md</code></li>
</ul>
<p><strong>Required heading order</strong></p>
<ol>
<li><code># Current Program</code></li>
<li><code>## Current Goal</code></li>
<li><code>## Current Workout Routine</code></li>
<li><code>## Nutrition Targets</code></li>
<li><code>## Preferred Weekly Schedule</code></li>
</ol>
<p><strong>Required content by heading</strong></p>
<ul>
<li><code>Current Goal</code><ul>
<li>Must state the current phase (for example: maintenance, deficit, or surplus).</li>
</ul>
</li>
<li><code>Current Workout Routine</code><ul>
<li>Must include strength frequency and running frequency.</li>
<li>Must include explicit template/workout ID references aligned with files under <code>data/workout-templates/</code>.</li>
<li>Must clearly state that each weekly strength session should use one of the referenced full-body workouts.</li>
</ul>
</li>
<li><code>Nutrition Targets</code><ul>
<li>Must include total calories, protein, carbs, fat, and energy phase (<code>deficit</code>, <code>maintenance</code>, or <code>surplus</code>).</li>
</ul>
</li>
<li><code>Preferred Weekly Schedule</code><ul>
<li>Must list all seven days: Sunday through Saturday.</li>
<li>Rest days must be explicitly labeled <code>Rest</code>.</li>
</ul>
</li>
</ul>
<p><strong>Edit behavior rules</strong></p>
<ul>
<li>Update this file in place; do not duplicate headings.</li>
<li>Preserve heading order unless explicitly asked to change structure.</li>
<li>Keep schedule entries concise and explicit.</li>
<li>When updating routine references, verify template/workout IDs exist in <code>data/workout-templates/</code>.</li>
</ul>
<p><strong>Example structure</strong></p>
<pre><code class="language-markdown"># Current Program

## Current Goal
- Phase: Maintenance

## Current Workout Routine
- Strength frequency: 2 full-body sessions per week
- Running frequency: 2 runs per week
- For each weekly strength session, perform one of these two full-body workouts:
  - Template `home-full-body` -&amp;gt; Workout `wkt_34c8a6de`
  - Template `apartment-gym-full-body` -&amp;gt; Workout `wkt_91e5c3b7`

## Nutrition Targets
- Energy phase: Maintenance
- Calories: 2499 kcal
- Protein: 180g
- Carbs: 276g
- Fat: 75g

## Preferred Weekly Schedule
- Sunday: Workout (optional 7 min yoga after workout)
- Monday: Run
- Tuesday: Rest
- Wednesday: Rest
- Thursday: Workout (optional 7 min yoga after workout)
- Friday: Rest
- Saturday: Run
</code></pre>
<hr />
<h2>6. Editing rules</h2>
<ul>
<li>Make small, targeted edits.</li>
<li>Preserve existing structure and key names.</li>
<li>Do not rename files or keys unless the user explicitly asks.</li>
<li>Do not add new top-level data files unless the user explicitly asks.</li></ul></code></pre></details>

<h2>Storing the data in different formats</h2>
<p>The <code>data/</code> folder does not force everything into the same format. Coach Atlas currently uses JSON files for structured information such as my profile, available equipment, body measurement history, and workout templates. My current fitness program is stored in Markdown, which makes it easy for both Coach Atlas and me to read.</p>
<p>The CRUD instructions describe the structure of those files and the rules for updating them. That is how Coach Atlas knows, for example, which fields belong in a workout template or where to add a new body measurement. If I later add a CSV or plain-text file, I can update the instructions to explain what that file contains and how it should be used.</p>
<p>Here are simplified examples of the two file formats I currently use. The values below are only examples, but the structure matches what Coach Atlas expects.</p>
<details>
<summary>View an example workout template stored as JSON</summary>
<pre class="not-prose"><code class="language-plaintext">{
  "templateId": "home-full-body",
  "name": "Home Full Body",
  "description": "A full-body workout using equipment available at home.",
  "workouts": [
    {
      "workoutId": "wkt_home_full_body_a",
      "name": "Full Body A",
      "estimatedDurationMinutes": 60,
      "exercises": [
        {
          "exerciseId": "ex_goblet_squat",
          "name": "Goblet Squat",
          "sets": 3,
          "reps": 10,
          "restSeconds": 90
        },
        {
          "exerciseId": "ex_dumbbell_bench_press",
          "name": "Dumbbell Bench Press",
          "sets": 3,
          "reps": 10,
          "restSeconds": 90
        }
      ]
    }
  ]
}</code></pre>
</details><details>
<summary>View an example current fitness program stored as Markdown</summary>
<pre class="not-prose"><code class="language-plaintext"># Current Program

<h2>Current Goal</h2>
<ul>
<li>Phase: Maintenance</li>
</ul>
<h2>Current Workout Routine</h2>
<ul>
<li>Strength frequency: 2 full-body sessions per week</li>
<li>Running frequency: 2 runs per week</li>
<li>For each weekly strength session, use workout <code>wkt_home_full_body_a</code> from template <code>home-full-body</code>.</li>
</ul>
<h2>Nutrition Targets</h2>
<ul>
<li>Energy phase: Maintenance</li>
<li>Calories: 2,400 kcal</li>
<li>Protein: 180g</li>
<li>Carbs: 255g</li>
<li>Fat: 75g</li>
</ul>
<h2>Preferred Weekly Schedule</h2>
<ul>
<li>Sunday: Workout</li>
<li>Monday: Run</li>
<li>Tuesday: Rest</li>
<li>Wednesday: Rest</li>
<li>Thursday: Workout</li>
<li>Friday: Rest</li>
<li>Saturday: Run</li></ul></code></pre></details>

<h2>Connecting the ChatGPT Project to GitHub</h2>
<p>After setting up the repository, I created a ChatGPT Project named <strong>Coach Atlas</strong> and connected the GitHub plugin to my ChatGPT account. I then added this line to the Project instructions:</p>
<pre><code class="language-plaintext">VERY IMPORTANT: Before handling every request, you MUST read `AI-MASTER-INSTRUCTIONS.md` from the `searles9/coach-atlas` repository.
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/5fb15fad03f7294d0e3a8745/06ef9129-c0a8-43a0-a088-fd33464ad382.png" alt="Coach Atlas Project instructions" style="display:block;margin:0 auto" />

<p>That line tells Coach Atlas where to begin. The master file points it to the role and CRUD instructions, and those files explain how it should work with the data. Instead of placing every rule directly in the Project settings, I can maintain them as files in GitHub and update them when the Project changes.</p>
<p>Coach Atlas also uses the Health and Google Calendar plugins. Health provides access to data from Apple Health, while Google Calendar allows Coach Atlas to schedule my workouts and runs. The master instruction file lists all three required plugins so Coach Atlas knows which services should be available.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5fb15fad03f7294d0e3a8745/5a1d9c5e-30ce-4a4d-9cfa-8ebfbeecbc9f.png" alt="The ChatGPT Plugins page" style="display:block;margin:0 auto" />

<h2>How I use Coach Atlas</h2>
<p>I interact with Coach Atlas through normal conversations. I can say, “Create a new two-day workout plan using the equipment I have at home,” ask, “Do my current calorie and macronutrient targets still make sense for my goal and recent weight trend?” or tell it, “Schedule my workouts and runs for next week.”</p>
<p>For each request, Coach Atlas reads the instructions and the relevant data before responding. If the request changes my program, it updates the appropriate file in GitHub. That updated information is then available the next time I open the Project, even if I start a new chat.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5fb15fad03f7294d0e3a8745/cffdd5be-fb6d-429f-98c7-5c9bb686376d.png" alt="Coach Atlas showing my current workout routine" style="display:block;margin:0 auto" />

<h2>Extending Coach Atlas with scheduled tasks</h2>
<p>I currently have a <a href="https://help.openai.com/en/articles/10291617-scheduled-tasks-in-chatgpt">scheduled task in ChatGPT</a> called <strong>Sync Health Weight to Coach Atlas</strong>. It runs daily, reads my latest weight from Apple Health, checks the existing weight history in GitHub, and adds the measurement if it is new.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5fb15fad03f7294d0e3a8745/b937954a-6ce7-44c2-8d67-059e6074a741.png" alt="Scheduled task that syncs my latest weight to Coach Atlas" style="display:block;margin:0 auto" />

<p>The scheduled task and my conversations with Coach Atlas both update the same repository. I can extend that approach in the future by adding other health data, recording completed workouts, tracking running progress, or generating progress summaries. Each addition can use the file format that fits the data, along with instructions that tell Coach Atlas how to work with it.</p>
<h2>Closing thoughts</h2>
<p>GitHub is not the only way to persist data for a ChatGPT Project, but it works well for Coach Atlas. It lets me keep different file formats together, review changes, and connect the same data to both conversations and scheduled tasks.</p>
<p>The important part of the setup is the combination of saved data and clear instructions. Coach Atlas knows what information is available, how to use it, and how to save changes so they remain available in future conversations.</p>
]]></content:encoded></item><item><title><![CDATA[Is College the Only Option?]]></title><description><![CDATA[Get good grades, go to college, get a good job, and work for retirement: 

This is a message that was given to me and many others in our youth. This is a message that was supplied by teachers, parents, and our role models.

Does this path work, and w...]]></description><link>https://blog.dsearles.com/is-college-the-only-option</link><guid isPermaLink="true">https://blog.dsearles.com/is-college-the-only-option</guid><category><![CDATA[College]]></category><category><![CDATA[IT]]></category><dc:creator><![CDATA[Donovan Searles]]></dc:creator><pubDate>Mon, 08 Nov 2021 00:46:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1608610227471/OpV96gLrK.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-get-good-grades-go-to-college-get-a-good-job-and-work-for-retirement"><strong>Get good grades, go to college, get a good job, and work for retirement:</strong> <br /></h1>
<hr />
<p>This is a message that was given to me and many others in our youth. This is a message that was supplied by teachers, parents, and our role models.</p>
<hr />
<p><strong>Does this path work, and will it bring you success?:</strong> <em>Sometimes</em> <br />
<strong>Is it the only way to success and life fulfillment:</strong> <em>Read below</em></p>
<hr />
<p>College can be a great asset to many, and for some professions, a degree is required. Your degree can help you land jobs, increase your earning potential, and make you a well rounded individual.</p>
<p>In a capitalist economy with millions of online resources and all that life has to offer, is college the only answer to success and life fulfillment then? No. There are many ways to achieve success and life fulfillment and for many college is not included in that.</p>
<p>I have my own view of what a successful and fulfilling life looks like for me, and this picture is different for everyone. </p>
<ul>
<li>For some, <em>its money and all the things it can offer</em></li>
<li>For some, <em>its a big home and big family </em></li>
<li>For some, <em>its a big investment portfolio, and a future of financial independence </em></li>
<li>For some, <em>its the experience of living in the present</em></li>
<li>For some, <em>its a combination of all of that in a special package</em> </li>
</ul>
<p>I'm twenty years old and I don't have all the answers, but I'll tell you why I chose not to go to college. </p>
<hr />
<h1 id="heading-the-math">The Math</h1>
<p>There are plenty of scalable career options that don't one-hundred percent require a degree, such as information technology, sales, real estate, or freelancing.</p>
<p>In high school, I made the decision that I was not going to attend college. I knew that I was interested in developing a career in IT. I also knew that the knowledge needed to perform in an IT job could be found either for free, or for less than a fraction of of the cost of college on the internet. I started early, gathered several IT certifications, did three internships, and gave myself the knowledge and experience needed to, at MINIMUM, land and perform well in an entry level job.</p>
<p>I decided not to go to college for several reasons. The primary reason being cost. The average college student graduates with around <a target="_blank" href="https://www.usnews.com/education/best-colleges/paying-for-college/articles/see-how-student-loan-borrowing-has-risen-in-10-years">30,000</a> dollars in student loan debt. Read that again, thirty whole thousand dollars. The worst part of it is that it can take five to ten+ years to pay off that loan depending on what job you got yourself into. </p>
<p>Imagine if rather than spending four to five years in college, you started after you left high school and landed yourself an entry level job. If you put aside five-hundred dollars every month for five years you would have that same $30,000 IN CASH. You would have an amazing credit score, and you would have five years more experience than your peers who went to college at that point in time.</p>
<p>That $30,000 in cash would also then open up a gateway of opportunities for you.</p>
<ul>
<li>You could use that money to start a side hustle and increase your income</li>
<li>You could put that money in the stock market and continue contributing five-hundred dollars each month for the next thirty years. Assuming an annual return of eight percent, you would have a return close to $1,000,000 pre tax. You could even make this simpler and less time consuming by using a robo-advisor or by investing in target date funds.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656026664011/5NC14k_eG.png" alt="image.png" /></li>
<li>You could make a down payment on a duplex, live in one side, rent out the other and essentially cut out a majority of your housing cost over the next ten+ years.</li>
<li>You could use that money as a down payment on a home</li>
<li>You could put some of that money into a 401k and have your employer match</li>
</ul>
<hr />
<center><strong>Fun Fact:</strong> The average median net worth of a 65-70 year old in America is <a target="_blank" href="https://www.businessinsider.com/personal-finance/average-american-net-worth">$266,400</a> </center>

<p><center><strong>Fun Fact:</strong> The average median net worth of an individual in America with a college degree is <a target="_blank" href="https://www.businessinsider.com/personal-finance/average-american-net-worth">$302,200</a> </center></p>
<hr />
<p>Life isn't quite so cut and clear. Some don't come up with the same initial opportunities available and not everyone who goes to college ends up in debt. Some have scholarships, some have parents who help pay their tuition, and some work three part time jobs to get themselves through, but the possibilities that lie outside of college are certainly something to be noted. </p>
<hr />
<h1 id="heading-knowledge">Knowledge</h1>
<p>With the increase of online learning platforms and online resources, much of the information learned in a classroom can also be learned online for free. Heck, you can even find hundreds of college lectures filmed and available for free on YouTube.</p>
<ul>
<li>Want to learn how to setup something on a server?... google it. </li>
<li>Want to learn some financial skills?... google it. </li>
<li>Want to learn some calculus (but really, why would you)? ... google it. </li>
<li>Want to learn how to code? ... google it.  </li>
<li>Feeling really spicy and want to learn some more? ... read a book </li>
</ul>
<p>In college you may also get stuck learning (and paying) for things that you may never use, and frankly may not care for. I personally don't care for calculus and certainly don't want to pay almost criminal fees to learn it.</p>
<hr />
<h1 id="heading-the-college-experience">The College Experience</h1>
<p>At this stage you are likely sensing a mild bias and you, like many others, may say: <em>"Don, but what about all the valuable skills you learn in college that aren't related to your degree? What about learning to budget, learning to work with others, learning discipline, and learning who you are as a person?"</em></p>
<p>I've heard this rationalization several times, often as the main argument as to why one should go to college. I've also seen several people without college degrees who have these skills and have become very successful in their life. How is this possible? </p>
<p>It's possible because these lessons and skills aren't unique to college. It is absolutely  possible to learn these skills somewhere else without going deep into debt. Where can you find these skills? ... The answer may surprise you: ...<strong>an entry level job </strong></p>
<hr />
<h1 id="heading-closing-thoughts">Closing Thoughts</h1>
<p>College is a great option for many and is required for some professions, but there are possibilities outside of it. </p>
<p>If you decide not to go to college you will face a few challenges. Regardless of the value you can bring, you will likely be passed up by some companies simply because you don't have a degree. You may also be filtered out of the list of potential candidates in certain recruiting software because you don't have a degree, and as a result, will find it harder to land interviews.</p>
<p>When you take a step back and look at the big picture, however, these are just small hurdles and you truly have a lifetime of opportunity.</p>
<p>It's just statistics. In a capitalist economy, if you chose the right profession and have value, <strong>someone</strong> will see that value and pay you accordingly for it.</p>
<hr />
<h3 id="heading-what-are-your-thoughts-on-college">What are your thoughts on college?</h3>
<hr />
]]></content:encoded></item><item><title><![CDATA[Creating a Simple CloudFormation Template That Deploys a Basic Apache Website]]></title><description><![CDATA[Overview
CloudFomation makes it easy to deploy infrastructure as code. It allows us to create a template containing logical resources and deploy it as a "stack" in 1 step. This is faster, repeatable and more consistent than deploying resources manual...]]></description><link>https://blog.dsearles.com/creating-a-simple-cloudformation-template-that-deploys-a-basic-apache-website</link><guid isPermaLink="true">https://blog.dsearles.com/creating-a-simple-cloudformation-template-that-deploys-a-basic-apache-website</guid><category><![CDATA[AWS]]></category><category><![CDATA[ec2]]></category><dc:creator><![CDATA[Donovan Searles]]></dc:creator><pubDate>Sun, 25 Apr 2021 17:08:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1619370438056/JQnmRD2bZ.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="overview">Overview</h1>
<p>CloudFomation makes it easy to deploy infrastructure as code. It allows us to create a template containing logical resources and deploy it as a "stack" in 1 step. This is faster, repeatable and more consistent than deploying resources manually in AWS.</p>
<p>This is a simple portable CloudFormation template (YAML) that deploys a basic Apache website on an EC2 instance. This article breaks down the code and shows you how to deploy the template in CloudFormation.</p>
<p>Before we take a deep dive, take a look at the full code snippet here:</p>
<details>
<summary>Full CloudFomation Template (YAML)</summary>
<pre>

<code>Parameters:
  LatestAmiId:
    Description: "AMI for EC2"
    Type: 'AWS::SSM::Parameter::Value&lt;AWS::EC2::Image::Id&gt;'
    Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
  SubnetId:
    Description: "Subnet ID"
    Type: 'AWS::EC2::Subnet::Id'
  KeyName:
    Type: "AWS::EC2::KeyPair::KeyName"
    Description: "Key Pair for EC2"
  UserDataMessage:
    Description: "Message for HTML page"
    Default: "Your EC2 With Apache Successfully Deployed"
    Type: "String"
Resources:
  InstanceSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: Enable SSH and HTTP access via port 22 IPv4 &amp; port 80 IPv4
      SecurityGroupIngress:
        - Description: 'Allow SSH IPv4 IN'
          IpProtocol: tcp
          FromPort: '22'
          ToPort: '22'
          CidrIp: '0.0.0.0/0'
        - Description: 'Allow HTTP IPv4 IN'
          IpProtocol: tcp
          FromPort: '80'
          ToPort: '80'
          CidrIp: '0.0.0.0/0'
  Instance:
    Type: 'AWS::EC2::Instance'
    Properties:
      InstanceType: "t2.micro"
      ImageId: !Ref "LatestAmiId"
      SubnetId: !Ref "SubnetId"
      SecurityGroupIds:
        - !GetAtt InstanceSecurityGroup.GroupId
      KeyName: !Ref "KeyName"
      Tags:
        - Key: Name
          Value: Base-Config-Apache-Instance
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash -xe
          yum -y update
          yum -y upgrade
          yum install -y httpd
          systemctl enable httpd
          systemctl start httpd
          echo "&lt;html&gt;&lt;head&gt;&lt;title&gt;Apache Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;h1&gt;&lt;center&gt;${UserDataMessage}&lt;/center&gt;&lt;/h1&gt;&lt;/body&gt;&lt;/html&gt;" &gt; /var/www/html/index.html

Outputs:
  PublicIp:
    Description: Server Public IP
    Value: !GetAtt Instance.PublicIp
    Export:
      Name: !Sub "${AWS::StackName}-PublicIp"
  PublicDnsName:
    Description: Server Public DNS Name
    Value: !GetAtt Instance.PublicDnsName
    Export:
      Name: !Sub "${AWS::StackName}-PublicDnsName"
  PrivateIp:
    Description: Server Private IP
    Value: !GetAtt Instance.PrivateIp
    Export:
      Name: !Sub "${AWS::StackName}-PrivateIp"
  AvailabilityZone:
    Description: Server Availability Zone
    Value: !GetAtt Instance.AvailabilityZone
    Export:
      Name: !Sub "${AWS::StackName}-AvailabilityZone"</code> 


</pre>
</details>


<hr />
<h1 id="parameters">Parameters</h1>
<p>The first part of this template specifies the Parameters. Parameters enable you to input custom values to your template each time you create or update a stack.</p>
<p>Resources specified later in the template reference these values and use the data.</p>
<pre><code><span class="hljs-attribute">Parameters</span>:
</code></pre><pre><code><span class="hljs-attribute">LatestAmiId</span>:
    <span class="hljs-attribute">Description</span>: <span class="hljs-string">"AMI for EC2"</span>
    <span class="hljs-attribute">Type</span>: <span class="hljs-string">'AWS::SSM::Parameter::Value&lt;AWS::EC2::Image::Id&gt;'</span>
    <span class="hljs-attribute">Default</span>: <span class="hljs-string">'/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'</span>
  <span class="hljs-attribute">SubnetId</span>:
    <span class="hljs-attribute">Description</span>: <span class="hljs-string">"Subnet ID"</span>
    <span class="hljs-attribute">Type</span>: <span class="hljs-string">'AWS::EC2::Subnet::Id'</span>
  <span class="hljs-attribute">KeyName</span>:
    <span class="hljs-attribute">Type</span>: <span class="hljs-string">"AWS::EC2::KeyPair::KeyName"</span>
    <span class="hljs-attribute">Description</span>: <span class="hljs-string">"Key Pair for EC2"</span>
  <span class="hljs-attribute">UserDataMessage</span>:
    <span class="hljs-attribute">Description</span>: <span class="hljs-string">"Message for HTML page"</span>
    <span class="hljs-attribute">Default</span>: <span class="hljs-string">"Your EC2 With Apache Successfully Deployed"</span>
    <span class="hljs-attribute">Type</span>: <span class="hljs-string">"String"</span>
</code></pre><p><strong>LatestAmiId:</strong>  The "LatestAmiId" parameter we set specifies the AMI that the EC2 instance will use. If you look at the "type" field you'll notice that we are using the  <a target="_blank" href="https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-finding-public-parameters.html">public SSM parameter store</a> to specify the AMI. This is a much more flexible option and we also have the ability to change that value when we create our stack.<br />
<strong>SubnetId:</strong> The "SubnetId" parameter we set allows us to choose a subnet from our current list of available subnets in our account. This is used later in the template when we define our EC2 logical resource.<br />
<strong>KeyName:</strong> The "KeyName" parameter we set allows us to chose an EC2 key pair from the available key pairs in our account <br />
<strong>UserDataMessage:</strong> The "UserDataMessage" parameter we set is a simple custom string that will be used later by the EC2 instance resource<br /></p>
<hr />
<h1 id="resources">Resources</h1>
<p>The next section in our template is the  <a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html">"Resources"</a>  section. Unlike the Parameters and Output section, the resources section is required. In the resources, we define the logical resources that we want to deploy with our stack.</p>
<pre><code><span class="hljs-attribute">Resources</span>:
</code></pre><pre><code><span class="hljs-attribute">InstanceSecurityGroup</span>:
    <span class="hljs-attribute">Type</span>: <span class="hljs-string">'AWS::EC2::SecurityGroup'</span>
    <span class="hljs-attribute">Properties</span>:
      <span class="hljs-attribute">GroupDescription</span>: Enable SSH and HTTP access via port <span class="hljs-number">22</span> IPv4 &amp; port <span class="hljs-number">80</span> IPv4
      <span class="hljs-attribute">SecurityGroupIngress</span>:
        - <span class="hljs-attribute">Description</span>: <span class="hljs-string">'Allow SSH IPv4 IN'</span>
          <span class="hljs-attribute">IpProtocol</span>: tcp
          <span class="hljs-attribute">FromPort</span>: <span class="hljs-string">'22'</span>
          <span class="hljs-attribute">ToPort</span>: <span class="hljs-string">'22'</span>
          <span class="hljs-attribute">CidrIp</span>: <span class="hljs-string">'0.0.0.0/0'</span>
        - <span class="hljs-attribute">Description</span>: <span class="hljs-string">'Allow HTTP IPv4 IN'</span>
          <span class="hljs-attribute">IpProtocol</span>: tcp
          <span class="hljs-attribute">FromPort</span>: <span class="hljs-string">'80'</span>
          <span class="hljs-attribute">ToPort</span>: <span class="hljs-string">'80'</span>
          <span class="hljs-attribute">CidrIp</span>: <span class="hljs-string">'0.0.0.0/0'</span>
</code></pre><p>The first resource we define in this template is "InstanceSecurityGroup". "InstanceSecurityGroup" is the logical ID. <br /></p>
<p><strong>Type:</strong> In the type section we define the kind of resource we want to deploy. In our case, we set this to  <a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html">"AWS::EC2::SecurityGroup"</a>. "AWS::EC2::SecurityGroup" means we are defining a security group. We will use this security group later when we define our EC2 instance.</p>
<p><strong>Properties:</strong> The properties section allows us to define the settings for our security group. To see what properties we can set, we can review the AWS documentation for <a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html">"AWS::EC2::SecurityGroup"</a>.</p>
<p><strong>SecurityGroupIngress:</strong> One of the properties we set is "SecurityGroupIngress". This property allows us to set the inbound rules for the security group. In our case, we are allowing port 22 for SSH (from all networks '0.0.0.0/0') so we can manage the instance. We also allow port 80/HTTP (from all networks '0.0.0.0/0') so we can access the apache site. You'll notice that we don't have any outbound rules. This is because security groups are stateful.  If you send a request to the instance, the response traffic for that request is allowed to flow out regardless of the outbound security group rules.</p>
<pre><code><span class="hljs-attribute">Instance</span>:
    <span class="hljs-attribute">Type</span>: <span class="hljs-string">'AWS::EC2::Instance'</span>
    <span class="hljs-attribute">Properties</span>:
      <span class="hljs-attribute">InstanceType</span>: <span class="hljs-string">"t2.micro"</span>
      <span class="hljs-attribute">ImageId</span>: !Ref <span class="hljs-string">"LatestAmiId"</span>
      <span class="hljs-attribute">SubnetId</span>: !Ref <span class="hljs-string">"SubnetId"</span>
      <span class="hljs-attribute">SecurityGroupIds</span>:
        - !GetAtt InstanceSecurityGroup.GroupId
      <span class="hljs-attribute">KeyName</span>: !Ref <span class="hljs-string">"KeyName"</span>
      <span class="hljs-attribute">Tags</span>:
        - <span class="hljs-attribute">Key</span>: Name
          <span class="hljs-attribute">Value</span>: Base-Config-Apache-Instance
      <span class="hljs-attribute">UserData</span>:
        <span class="hljs-attribute">Fn</span>::<span class="hljs-attribute">Base64</span>: !Sub |
          #!/bin/bash -xe
          yum -y update
          yum -y upgrade
          yum install -y httpd
          systemctl enable httpd
          systemctl start httpd
          echo <span class="hljs-string">"&lt;html&gt;&lt;head&gt;&lt;title&gt;Apache Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;h1&gt;&lt;center&gt;${UserDataMessage}&lt;/center&gt;&lt;/h1&gt;&lt;/body&gt;&lt;/html&gt;"</span> &gt; /var/www/html/index.html
</code></pre><p>The second and last resource we define in this template is "Instance". Just like with the first resource "Instance" is just the logical ID of the resource.</p>
<p><strong>Type:</strong> For this resource we have defined that we want to create an EC2 instance. We defined this by setting the type as <a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html">'AWS::EC2::Instance'</a></p>
<p><strong>Properties:</strong> We defined several properties (or "settings") for this resource. The available properties can be referenced in the AWS documentation. Let's review them...</p>
<p><strong>InstanceType:</strong> We hard-coded this value to use the "t2.micro" instance type.</p>
<p><strong>ImageId:</strong> The "ImageId" specifies what AMI we want to use with our EC2 instance. We used the !Ref <a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html">intrinsic function</a>  to reference the value we set in our "LatestAmiId" parameter.</p>
<p><strong>SubnetId:</strong> The "SubnetId" specifies what subnet we want our EC2 instance to deploy to. We used the !Ref intrinsic function to reference our "SubnetId" parameter that we set. The "SubnetId" parameter lets us choose an available subnet in our account.</p>
<p><strong>SecurityGroupIds:</strong>  The "SecurityGroupIds" value specifies what security group we want to use with our EC2 instance. We used the !GetAtt intrinsic function to reference the security group ID of the group that we defined in our template.</p>
<p><strong>KeyName:</strong> The "KeyName" value specifies what SSH keypair we want to use with our instance. We used the !Ref intrinsic function to get the value specified in the "KeyName" parameter. </p>
<p><strong>Tags:</strong> Tags allow us to add key/value identifiers to resources. We specified a tag for our EC2 instance.</p>
<p><strong>UserData:</strong> UserData allows us to bootstrap our EC2 instance. It allows us to run commands on the instance when it is first created. In our case, we ran a few commands to install apache on our Linux instance. If you look we also ran a command to create an HTML file that apache will use. That command also references our "UserDataMessage" parameter. </p>
<hr />
<h1 id="outputs">Outputs</h1>
<p>The Outputs section of the template declares output values that you can import into other stacks. You can also view these values from the CloudFormation console.</p>
<p>As an example, if you were making a nested stack with a VPC you may want to export the VPC ID to another stack for later use.</p>
<pre><code><span class="hljs-attribute">Outputs</span>:
</code></pre><pre><code><span class="hljs-attr">PublicIp:</span>
    <span class="hljs-attr">Description:</span> <span class="hljs-string">Server</span> <span class="hljs-string">Public</span> <span class="hljs-string">IP</span>
    <span class="hljs-attr">Value:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">Instance.PublicIp</span>
    <span class="hljs-attr">Export:</span>
      <span class="hljs-attr">Name:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">"${AWS::StackName}-PublicIp"</span>
  <span class="hljs-attr">PublicDnsName:</span>
    <span class="hljs-attr">Description:</span> <span class="hljs-string">Server</span> <span class="hljs-string">Public</span> <span class="hljs-string">DNS</span> <span class="hljs-string">Name</span>
    <span class="hljs-attr">Value:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">Instance.PublicDnsName</span>
    <span class="hljs-attr">Export:</span>
      <span class="hljs-attr">Name:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">"${AWS::StackName}-PublicDnsName"</span>
  <span class="hljs-attr">PrivateIp:</span>
    <span class="hljs-attr">Description:</span> <span class="hljs-string">Server</span> <span class="hljs-string">Private</span> <span class="hljs-string">IP</span>
    <span class="hljs-attr">Value:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">Instance.PrivateIp</span>
    <span class="hljs-attr">Export:</span>
      <span class="hljs-attr">Name:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">"${AWS::StackName}-PrivateIp"</span>
  <span class="hljs-attr">AvailabilityZone:</span>
    <span class="hljs-attr">Description:</span> <span class="hljs-string">Server</span> <span class="hljs-string">Availability</span> <span class="hljs-string">Zone</span>
    <span class="hljs-attr">Value:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">Instance.AvailabilityZone</span>
    <span class="hljs-attr">Export:</span>
      <span class="hljs-attr">Name:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">"${AWS::StackName}-AvailabilityZone"</span>
</code></pre><p>In our case, we defined 4 outputs. We pulled these values from the EC2 instance we defined. You can reference the available outputs for EC2 in the <a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html">AWS documentation</a>.</p>
<hr />
<h1 id="deploying-the-template">Deploying the template</h1>
<p>CloudFormation accepts YAML and JSON files. Our specific template is written in YAML. If we save the template as a .yaml file we can deploy it to CloudFormation.</p>
<p>Let's view what that looks like in the console:</p>
<details>
<summary>1) Navigate to CloudFormation and hit create stack:</summary>
<br />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619366749307/cog1B_PvA.jpeg" alt="1-create stack.jpg" />
</details>

<details>
<summary>2) Upload the CloudFormation template:</summary>
<br />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619366831737/oUhoR9IeA.jpeg" alt="2-upload file.jpg" />
</details>

<details>
<summary>3) Set the parameter values:</summary>
<br />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619366918107/YjUiUUIoR.jpeg" alt="3 - params.jpg" />
</details>

<details>
<summary>4) Monitor the events tab and allow the stack to finish creating:</summary>
<br />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619367165887/VUAa43P4a.jpeg" alt="4 - stack complete.jpg" />
</details>

<details>
<summary>5) Get the public IP from the outputs section and confirm the site is active</summary>
<br />
a) Get the public IP of the EC2 instance from the outputs tab:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619367336201/CtKghkJjy.jpeg" alt="5 - 1.jpg" />
b) Navigate to the public IP and confirm the site is active:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619367460157/zrG8p9Tiy.jpeg" alt="5-2.jpg" />
</details>

<details>
<summary>6) Test the SSH connectivity to the instance:</summary>
<br />
Confirm you can access the instance via SSH:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619367811928/YInCF5Vx3.jpeg" alt="6-test connactivity.jpg" />
</details>

<hr />
<h1 id="conclusion">Conclusion</h1>
<p>Our simple template deployed out 1 EC2 instance. With the power of CloudFormation we can deploy out a fleet of EC2 instances in a fraction of the time it takes to manually deploy them.</p>
<p>Thank you to everyone who made it to the end of this article!</p>
<p>Feel free to connect with me on LinkedIn!: https://www.linkedin.com/in/donovansearles/</p>
]]></content:encoded></item><item><title><![CDATA[My Experience With the AWS "Build a Modern Web App" Project]]></title><description><![CDATA[I recently stumbled across this interesting project on aws.amazon.com.
  
The project/tutorial walks you through setting up a  well-architected modern web application called Mythical Mysfits that enables visitors to adopt a fantasy creature (mysfit) ...]]></description><link>https://blog.dsearles.com/my-experience-with-the-aws-build-a-modern-web-app-project</link><guid isPermaLink="true">https://blog.dsearles.com/my-experience-with-the-aws-build-a-modern-web-app-project</guid><category><![CDATA[AWS]]></category><category><![CDATA[web application]]></category><dc:creator><![CDATA[Donovan Searles]]></dc:creator><pubDate>Fri, 23 Apr 2021 12:50:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1619148717234/ioZIoe1r1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently stumbled across this interesting project on aws.amazon.com.</p>
<p> <a target="_blank" href="https://aws.amazon.com/getting-started/hands-on/build-modern-app-fargate-lambda-dynamodb-python/"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618541453677/q7FPI0yZ9.png" alt="Mysfit.PNG" /></a> </p>
<p>The project/tutorial walks you through setting up a  <a target="_blank" href="https://aws.amazon.com/architecture/well-architected/?wa-lens-whitepapers.sort-by=item.additionalFields.sortDate&amp;wa-lens-whitepapers.sort-order=desc">well-architected</a> modern web application called Mythical Mysfits that enables visitors to adopt a fantasy creature (mysfit) as pet.  (<em>You can see a working sample of this website at: www.mythicalmysfits.com</em>)</p>
<p>I decided to record myself in real-time completing the project.<br />Take a look below for the live walkthrough:</p>
<hr />
<h1 id="overview">Overview</h1>
<hr />
<iframe width="560" height="315" src="https://www.youtube.com/embed/pjzw0C3Jot0"></iframe>


<hr />
<h1 id="stage-1-create-static-website">Stage 1 - Create Static Website</h1>
<hr />
<iframe width="560" height="315" src="https://www.youtube.com/embed/pjzw0C3Jot0?start=86"></iframe>

<ul>
<li>Services used: AWS Cloud9, Amazon Simple Storage Service (S3)</li>
</ul>
<hr />
<h1 id="stage-2-build-dynamic-website">Stage 2 - Build Dynamic Website</h1>
<hr />
<iframe width="560" height="315" src="https://www.youtube.com/embed/pjzw0C3Jot0?start=483"></iframe>

<ul>
<li>Services used: AWS Fargate, AWS CloudFormation, AWS Identity and Access Management (IAM), Amazon Virtual Private Cloud (VPC), Amazon Elastic Load Balancing, Amazon Elastic Container Service (ECS), AWS Elastic Container Registry (ECR), AWS CodeCommit, AWS CodePipeline, AWS CodeDeploy, AWS CodeBuild</li>
</ul>
<hr />
<h1 id="stage-3-store-mysfit-data">Stage 3 - Store Mysfit Data</h1>
<hr />
<iframe width="560" height="315" src="https://www.youtube.com/embed/pjzw0C3Jot0?start=2628"></iframe>

<ul>
<li>Services used: Amazon DynamoDB</li>
</ul>
<hr />
<h1 id="stage-4-add-user-registration">Stage 4 - Add User Registration</h1>
<hr />
<iframe width="560" height="315" src="https://www.youtube.com/embed/pjzw0C3Jot0?start=3141"></iframe>

<ul>
<li>Services used: Amazon Cognito, Amazon API Gateway, Amazon Simple Storage Service (S3)</li>
</ul>
<hr />
<h1 id="stage-5-capture-user-clicks">Stage 5 - Capture User Clicks</h1>
<hr />
<iframe width="560" height="315" src="https://www.youtube.com/embed/pjzw0C3Jot0?start=4002"></iframe>

<ul>
<li>Services used:  AWS CloudFormation, AWS Kinesis Firehose, Amazon S3, Amazon API Gateway, AWS Lambda, AWS CodeCommit, AWS Serverless Application Model (AWS SAM), AWS SAM Command Line Interface (SAM CLI)</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How I Built an AWS Dynamic, BGP Based, Highly-Available Site-to-Site VPN]]></title><description><![CDATA[As the title suggests, I was able to setup and implement a Site to Site VPN from an AWS Virtual Private Cloud (VPC) to a simulated on-premise environment utilizing the Border Gateway protocol (BGP) to configure the routes. 
This article details how I...]]></description><link>https://blog.dsearles.com/how-i-built-an-aws-dynamic-bgp-based-highly-available-site-to-site-vpn</link><guid isPermaLink="true">https://blog.dsearles.com/how-i-built-an-aws-dynamic-bgp-based-highly-available-site-to-site-vpn</guid><category><![CDATA[AWS]]></category><category><![CDATA[AWS Certified Solutions Architect Associate]]></category><category><![CDATA[vpn]]></category><dc:creator><![CDATA[Donovan Searles]]></dc:creator><pubDate>Mon, 16 Nov 2020 22:39:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1605475590355/8g9I3IRgG.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>As the title suggests, I was able to setup and implement a Site to Site VPN from an AWS Virtual Private Cloud (VPC) to a simulated on-premise environment utilizing the Border Gateway protocol (BGP) to configure the routes. </p>
<p>This article details how I was able to complete this. From a high level, this is the process that was followed:</p>
<ul>
<li>Stage 1 - I provisioned the environment</li>
<li>Stage 2 - I Setup  the TGW (transit gateway) VPN Attachments</li>
<li>Stage 3 - I configured the IPSec Tunnel </li>
<li>Stage 4 - I setup BGP Routing and tested</li>
</ul>
<hr />
<h1 id="stage-1-provision-the-environment">Stage 1 - Provision the Environment</h1>
<hr />
<p>The goal in this <a target="_blank" href="https://github.com/acantril/learn-cantrill-io-labs/tree/master/AWS_HYBRID_AdvancedVPN">demo</a> is to connect an AWS VPC to a simulated on-premise environment using a secure and highly available VPN connection.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605474573868/aVI-UNqz9.png" alt="VPN Diagram.png" /></p>
<p>I started by deploying two cloud formation templates supplied by <a target="_blank" href="https://github.com/acantril">@Adrian Cantrill</a> to setup the base infrastructure. </p>
<p>The first cloud formation template represents the AWS infrastructure side and includes the following: </p>
<ul>
<li>two subnets, two EC2 instances, a TGW, a VPC attachment, and a default route pointing at the TGW</li>
</ul>
<p>The second cloud formation template simulates the on-premise environment using AWS resources:</p>
<ul>
<li>one public subnet, two private subnets. The public subnet has two Ubuntu + strongSwan + Free VPN endpoints.</li>
</ul>
<p>Then, I setup two customer gateways to simulate two on-premise routers. 
Having two customer gateways allows for the creation of a highly available environment.
In the real world these gateways would likely be in two separate locations to ensure  availability.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605478233734/TRrh3VLHV.png" alt="CGWs.png" /></p>
<p>These routers were configured to use Dynamic routing, which allows the use of the dynamic BGP routing protocol. I assigned a private Autonomous System Number, and assigned public IPs to these routers.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605477805518/gDflLcw4z.png" alt="On-Prem-Router-1 CGW.png" /></p>
<p>At this stage there was no connectivity between the On-prem and AWS network. I was able to confirm this by running a ping test:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605479026197/3qjfzhje9.png" alt="No_Connectivity.png" /></p>
<hr />
<h1 id="stage-2-setup-the-tgw-transit-gateway-vpn-attachments">Stage 2 - Setup the TGW (transit gateway) VPN Attachments</h1>
<hr />
<p>With the environment in place, I moved onto creating the transit gateway VPN attachments. These attachments essentially create a secure VPN connection from the transit gateway to the customer gateway.</p>
<p>In our on-prem environment there are two customer gateways. I navigated to the AWS VPC console and setup two transit gateway attachments, one for each gateway. </p>
<p>For each attachment, I selected the transit gateway which was created in the "AWS" cloud formation template. I also chose dynamic routing (for BGP) and allowed "Enable Acceleration" to speed up our routing traffic. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605480555491/rCBT4N5aP.png" alt="TGW Attachment.png" /></p>
<hr />
<h1 id="stage-3-configure-the-ipsec-tunnel">Stage 3 - Configure the IPSec Tunnel</h1>
<hr />
<p>At this stage, I was able to set up multiple IPSec tunnels. The IPSec tunnels span from the customer gateway to the VPN endpoints. These tunnels encrypt and secure the data as it traverses the public internet. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605492858703/S08eXCgyw.png" alt="IPSEC.png" /></p>
<p>To setup the IPSec tunnels, I first gathered a bit of information. I pulled the router IP's and I also downloaded the VPN config files from the VPC console. These files contain several bits of information.</p>
<p>They contain some of the following items:</p>
<ul>
<li>The pre-shared keys for the tunnels</li>
<li>The outside IPs which are used to configure the main IP sec tunnel</li>
<li>The inside IPs which are used to setup the data flow within the IP sec tunnel</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605493992284/ahly3ZDc0.png" alt="VPN files.png" /></p>
<p>In this demo we used EC2 instances on the simulated on-premise side to act as routers, and utilized strongSwan to act as our IPSsec VPN.</p>
<p>To configure the IPsec tunnel, I had to edit a few files that strongSwan uses.
I edited the following files:</p>
<ul>
<li><strong> ipsec.conf</strong>:  This file configures the tunnels using the outside IP addresses of the customer gateway and the AWS endpoint  </li>
<li><strong>ipsec.secrets</strong>: This file contains the outside IP addresses and the pre-shared keys</li>
<li><strong> ipsec-vti.sh</strong>:  This script brings up the IPSec tunnels and contains the inside IP addresses</li>
</ul>
<p>With strongSwan configured one is able to see that the IPSec shows as "up" in the AWS console. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605494392268/3Rm1lRhiR.png" alt="IPSEC UP - Tunnel Down.png" /></p>
<p>The tunnel, however, shows as down. This is because BGP is not configured yet.
Due to the BGP/routing not being configured, traffic from the AWS servers cannot successfully reach the on-prem servers at this stage:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605494483094/YkAknCuCO.png" alt="No_Connectivity.png" /></p>
<hr />
<h1 id="stage-4-setup-bgp-routing">Stage 4 - Setup BGP routing</h1>
<hr />
<p>To allow traffic from the AWS servers to reach on-prem and vise versa, I needed to configure the customer gateways to use BGP. </p>
<p>In this specific demo I installed <a target="_blank" href="https://frrouting.org/">FRR</a> on the customer gateways to allow BGP capabilities.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605495615167/1tT1WOOFd.png" alt="RTR 1 INSTALL FRR FOR BGP.png" /></p>
<p>For each of the tunnels I then added AWS as the neighboring route on the customer gateway. 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605495631534/o7wq_F6wV.png" alt="On-prem1 routes.png" /></p>
<hr />
<h1 id="success">Success !</h1>
<hr />
<p>The on-prem and AWS servers are now able to successfully communicate using our Dynamic, BGP Based, Highly-Available Site-to-Site VPN!
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605495739636/lI5Wa988p.png" alt="On prem 1 to EC2 A Success.png" /></p>
<p>This architecture was designed to be highly available. There are multiple VPN endpoints, Customer Gateways, and EC2 instances. I also configured IPSec tunnels to encrypt our data in transit and ensure security.</p>
<hr />
<h1 id="closing-thoughts">Closing thoughts</h1>
<hr />
<p>Thank you to <a target="_blank" href="https://github.com/acantril">@Adrian Cantrill</a> for creating this <a target="_blank" href="https://github.com/acantril/learn-cantrill-io-labs/tree/master/AWS_HYBRID_AdvancedVPN">demo</a>!</p>
<p>I appreciate everyone who made it this far down!</p>
<ul>
<li>Check out my site: https://www.dsearles.com/</li>
<li>Feel free to connect with me on LinkedIn: https://www.linkedin.com/in/donovansearles/</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[My Experience with the Cloud Resume Challenge]]></title><description><![CDATA[A few months ago, one of my LinkedIn connections introduced me to the cloud resume challenge. The challenge instructs participants to build a "serverless" resume website using HTML/CSS and AWS cloud infrastructure, deploy the infrastructure using IaC...]]></description><link>https://blog.dsearles.com/my-experience-with-the-cloud-resume-challenge</link><guid isPermaLink="true">https://blog.dsearles.com/my-experience-with-the-cloud-resume-challenge</guid><category><![CDATA[AWS]]></category><category><![CDATA[serverless]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Donovan Searles]]></dc:creator><pubDate>Sat, 14 Nov 2020 01:17:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1605463352459/U_ex8cWp7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A few months ago, one of my LinkedIn connections introduced me to the cloud resume challenge. The challenge instructs participants to build a "serverless" resume website using HTML/CSS and AWS cloud infrastructure, deploy the infrastructure using IaC (infrastructure as Code) and to setup a CI/CD pipeline for continuous delivery.  </p>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605460979821/rGjBTaPjn.png" alt="Alt Text" /></p>
<hr />
<p>At the time I was studying for the AWS Cloud Practitioner certification, and planned on earning the AWS Solutions Architect Associate certification shortly after. After 65+ hours of studying, and completing several labs I went in and passed both exams landing myself with 2 new AWS certifications.</p>
<p>Although the cloud resume challenge concluded on July 31 2020, after a full #100DaysOfCloud I decided to dive in and complete the challenge anyways.</p>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605460981230/DSqLREB59.png" alt="Alt Text" /></p>
<hr />
<h1 id="the-requirements">The Requirements</h1>
<ol>
<li>Get AWS Certified</li>
<li>Build a website with HTML</li>
<li>Style the site with CSS</li>
<li>Host the static site with <a target="_blank" href="https://aws.amazon.com/s3/">Amazon S3</a></li>
<li>Use HTTPs for security, using an <a target="_blank" href="https://aws.amazon.com/cloudfront/">AWS CloudFront</a> distribution </li>
<li>Use a custom domain name and setup DNS using <a target="_blank" href="https://aws.amazon.com/route53/">Route 53</a></li>
<li>Write some JavaScript to display a visitor count on the site</li>
<li>Setup a <a target="_blank" href="https://aws.amazon.com/dynamodb/">DynamoDB</a> table in AWS to store the visitor count</li>
<li>Build an <a target="_blank" href="https://aws.amazon.com/api-gateway/">API</a> and an AWS  <a target="_blank" href="https://aws.amazon.com/lambda/">Lambda</a> function that will communicate between the website and the database</li>
<li>Write the <a target="_blank" href="https://aws.amazon.com/lambda/">Lambda</a> function in Python</li>
<li>Write and implement Python test in the code</li>
<li>Deploy the backend AWS infrastructure (API, Database and Lambda function) using <a target="_blank" href="https://aws.amazon.com/serverless/sam/">infrastructure as a code</a>, not by clicking around in the AWS console</li>
<li>Use source control for the site via GitHub</li>
<li>Implement automated CI/CD for the front end</li>
<li>Implement automated CI/CD for the back end</li>
<li>Write a blog post about the experience</li>
</ol>
<h1 id="front-end">Front-End</h1>
<p>The challenge starts with building an HTML/CSS version of your resume. Having built an HTML/CSS resume website in my high school days and because HTML/CSS isn't the main focus of the project, I decided to use a template and modify it as needed.</p>
<p>Once I built the website I needed to host it somewhere so I used <a target="_blank" href="https://aws.amazon.com/s3/">Amazon Simple Storage Service (S3)</a> and its static hosting capabilities. </p>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605460982666/Q6xeMLftJ.png" alt="Alt Text" /></p>
<hr />
<p>Next step was to setup HTTPS. I setup a <a target="_blank" href="https://aws.amazon.com/cloudfront/">CloudFront distribution</a> for the site, pointed a custom URL purchased through <a target="_blank" href="https://aws.amazon.com/route53/">Route 53</a> at that distribution, and used <a target="_blank" href="https://aws.amazon.com/certificate-manager/">Amazon Certificate Manager</a> to obtain an SSL certificate.</p>
<h1 id="back-end">Back-End</h1>
<p>With the front-end out of the way, I moved on to setting up the back-end for the site. </p>
<p>The back-end consists of:</p>
<ul>
<li>The Dynamo DB table which contains the visitor count</li>
<li>The API gateway used for communicating between the site and the database</li>
<li>The lambda function which is run when the API is called. This lambda function updates the visitor count stored in DynamoDB</li>
</ul>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605460984173/sjNv7p1kE.png" alt="Alt Text" /></p>
<hr />
<p>The challenge requires you to deploy the back-end resources using SAM <a target="_blank" href="https://aws.amazon.com/serverless/sam/">(Serverless Application Model)</a>. I first spent some time looking over <a target="_blank" href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html">AWS's SAM template syntax</a>, and then setup a SAM template which defined all the resources I wanted to build (Database, API, Lambda Function). I then deployed the stack using the SAM CLI which created these resources in my AWS account.</p>
<h1 id="javascript">JavaScript</h1>
<p>At this point the site and the infrastructure were in place, however, I didn't have anything in my website which called the API and displayed the visitor count. </p>
<p>I wrote some JavaScript in my front end-website code which runs when the site is accessed. The JavaScript calls the API, the API then triggers lambda which runs a function that updates the visitor count in Dynamo DB, at which point the API returns the new visitor count and is displayed in the website.</p>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605460986264/i2_VKCop5.png" alt="Alt Text" /></p>
<hr />
<h1 id="cicd">CI/CD</h1>
<p>The last stage in the project was to setup a "Continuous integration/Continuous delivery" pipeline using GitHub actions. </p>
<p>I first setup a private GitHub repository to store my Front-End code. Then, I setup GitHub actions to sync any changes in the repository to the Amazon S3 bucket and to invalidate the CloudFront cache.</p>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605460987849/Bmg6jLIa1.png" alt="Alt Text" /></p>
<hr />
<p>The CI/CD for the back-end code was a little more difficult. I setup a private GitHub repository for my Back-End code. The repository contains the SAM template and python files used in the project. </p>
<p>Per the requirements, the GitHub workflow should first run python tests, and the stack should be deployed to AWS if the tests pass.</p>
<p>I opted to not include any tests in my project as I am mainly focused on the process of interacting with the AWS resources and less of the code development portion of it.</p>
<p>I setup the git-hub workflow for the back-end to lint the python code, then build and deploy the stack to AWS.</p>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605460989305/QC_rQr1V_.png" alt="Alt Text" /></p>
<hr />
<h1 id="the-final-product">The Final Product</h1>
<p>This experience has been very rewarding. I was able to put to use some of the knowledge I learned from the AWS SA Associate and AWS Cloud Practitioner certifications. I was also able to dabble a bit in Python and get some hands on with SAM.</p>
<p>Here is the final product: <a target="_blank" href="https://www.dsearles.com/">dsearles.com</a></p>
<p>What's next for me? I plan on knocking out a few more AWS certifications and potentially putting a few of those lab/projects on this site.</p>
<p>Special thanks to "Forrest Brazeal" for creating this project!</p>
<h1 id="update"><em>Update</em></h1>
<p>Changes to the original site have since been made. Additional changes will be made moving forward.</p>
<p>See the updated site here: dsearles.com</p>
<p>Here is the original site:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1616789192978/6cnfp5X6o.png" alt="Blog.png" /></p>
]]></content:encoded></item></channel></rss>