<?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" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Infrastructure as code - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Infrastructure as code - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 22 May 2026 22:36:17 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/infrastructure-as-code/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Migrate to S3 Native State Locking in Terraform ]]>
                </title>
                <description>
                    <![CDATA[ If you've been running Terraform on AWS for any length of time, you know the setup: an S3 bucket for state storage, a DynamoDB table for state locking, and a handful of IAM policies tying them togethe ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-migrate-to-s3-native-state-locking-in-terraform/</link>
                <guid isPermaLink="false">69fd19239f93a850a430069b</guid>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ S3 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tolani Akintayo ]]>
                </dc:creator>
                <pubDate>Thu, 07 May 2026 22:58:43 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/9619ad45-15c5-4be7-9221-ed4b76bc2b24.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you've been running Terraform on AWS for any length of time, you know the setup: an S3 bucket for state storage, a DynamoDB table for state locking, and a handful of IAM policies tying them together. It works. It has worked for years.</p>
<p>But it has always carried a cost that rarely gets discussed openly. That cost isn't just money, though a DynamoDB table with on-demand billing adds up across multiple teams and environments.</p>
<p>The real cost is complexity. Every new AWS environment needs both resources provisioned before Terraform can manage anything else. Every engineer who sets up their first Terraform backend has to understand why two completely different AWS services are responsible for what is logically one thing: storing and protecting state. And every incident involving a stuck lock has required someone to manually delete a record from DynamoDB to unblock the team.</p>
<p>In November 2024, AWS announced that S3 now supports native object locking for Terraform state files, meaning <strong>DynamoDB is no longer required for state locking</strong>. Terraform 1.10 added support for this feature, and it's now generally available.</p>
<p>In this tutorial, you'll learn:</p>
<ul>
<li><p>What S3 native locking is and how it works</p>
</li>
<li><p>How to set it up from scratch if you're starting a new project</p>
</li>
<li><p>How to migrate an existing S3 + DynamoDB setup to S3 native locking safely</p>
</li>
<li><p>How to verify locking is working and handle edge cases</p>
</li>
</ul>
<p>By the end, you'll have a simpler, cleaner Terraform backend with one fewer AWS resource to manage.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-is-terraform-state-locking">What Is Terraform State Locking?</a></p>
</li>
<li><p><a href="#heading-what-is-s3-native-state-locking">What Is S3 Native State Locking?</a></p>
</li>
<li><p><a href="#heading-how-s3-native-locking-compares-to-the-s3-dynamodb-approach">How S3 Native Locking Compares to the S3 + DynamoDB Approach</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-part-1-fresh-setup-how-to-configure-s3-native-locking-from-scratch">Part 1: Fresh Setup – How to Configure S3 Native Locking from Scratch</a></p>
<ul>
<li><p><a href="#heading-step-1-create-the-s3-bucket-with-versioning-and-encryption">Step 1: Create the S3 Bucket with Versioning and Encryption</a></p>
</li>
<li><p><a href="#heading-step-2-configure-the-terraform-backend-with-native-locking">Step 2: Configure the Terraform Backend with Native Locking</a></p>
</li>
<li><p><a href="#heading-step-3-initialize-and-verify">Step 3: Initialize and Verify</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-part-2-migration-how-to-move-from-s3-dynamodb-to-s3-native-locking">Part 2: Migration – How to Move from S3 + DynamoDB to S3 Native Locking</a></p>
<ul>
<li><p><a href="#heading-step-1-verify-your-current-setup">Step 1: Verify Your Current Setup</a></p>
</li>
<li><p><a href="#heading-step-2-enable-object-lock-on-the-existing-s3-bucket">Step 2: Enable Object Lock on the Existing S3 Bucket</a></p>
</li>
<li><p><a href="#heading-step-3-update-the-terraform-backend-configuration">Step 3: Update the Terraform Backend Configuration</a></p>
</li>
<li><p><a href="#heading-step-4-reinitialize-terraform">Step 4: Reinitialize Terraform</a></p>
</li>
<li><p><a href="#heading-step-5-verify-the-migration">Step 5: Verify the Migration</a></p>
</li>
<li><p><a href="#heading-step-6-clean-up-the-dynamodb-table">Step 6: Clean Up the DynamoDB Table</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-verify-that-locking-is-working">How to Verify That Locking Is Working</a></p>
</li>
<li><p><a href="#heading-how-to-handle-a-stuck-lock">How to Handle a Stuck Lock</a></p>
</li>
<li><p><a href="#heading-rollback-plan-if-something-goes-wrong">Rollback Plan: If Something Goes Wrong</a></p>
</li>
<li><p><a href="#heading-security-best-practices-for-your-state-bucket">Security Best Practices for Your State Bucket</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-references">References</a></p>
</li>
</ul>
<h2 id="heading-what-is-terraform-state-locking">What is Terraform State Locking?</h2>
<p>Before looking at the new approach, it helps to understand what state locking is solving.</p>
<p>Terraform stores everything it knows about your infrastructure in a <strong>state file</strong> – a JSON document that maps your configuration to real AWS resources. When you run <code>terraform apply</code>, Terraform reads this file, calculates the difference between the current state and your configuration, and makes the necessary changes.</p>
<p>The problem arises when two engineers or two CI/CD pipelines run and try to apply changes at the same time. If both read the state file simultaneously, calculate changes independently, and both try to write back, you get a <strong>race condition</strong>. The second write overwrites changes from the first, and your state is now out of sync with reality. This is a serious problem that can cause resources to be untracked, doubled, or destroyed unexpectedly.</p>
<p><strong>State locking</strong> solves this by creating a lock when any operation starts that could modify state. If a lock already exists, Terraform refuses to proceed and reports who holds the lock and when it was acquired. Only one operation can hold the lock at a time. When the operation completes, the lock is released.</p>
<pre><code class="language-plaintext">Terraform Run A                 State File / Lock                Terraform Run B
(User 1)                         (S3/DynamoDB)                   (User 2)

   |                                   |                            |
   |------- 1. Acquire Lock ----------&gt;|                            |
   |                                   |                            |
   |&lt;------ 2. Lock Granted -----------|                            |
   |                                   |                            |
   |                                   |------- 3. Acquire Lock ---&gt;|
   |            [PROCESSING]           |                            |
   |      (Modifying Infrastructure)   |&lt;------ 4. Lock Denied -----|
   |                                   |        (Wait / Retry)      |
   |                                   |                            |
   |------- 5. Release Lock ----------&gt;|                            |
   |                                   |                            |
   |           [COMPLETED]             |&lt;------ 6. Lock Granted ----|
   |                                   |                            |
   |                                   |       [PROCESSING]         |
   |                                   | (Modifying Infrastructure) |              
   |                                   |                            |
</code></pre>
<h2 id="heading-what-is-s3-native-state-locking">What Is S3 Native State Locking?</h2>
<p>Previously, Terraform's S3 backend used a DynamoDB table as the locking mechanism. When a lock was needed, Terraform wrote a record to DynamoDB with a <code>LockID</code> primary key. DynamoDB's conditional writes guaranteed that only one process could create that record, which is what made the locking atomic.</p>
<p>S3 native locking uses <strong>S3 Object Lock</strong> instead. S3 Object Lock is an S3 feature originally designed to enforce WORM (Write Once, Read Many) compliance for regulatory requirements. AWS extended this capability to support Terraform's state locking workflow.</p>
<p>When S3 native locking is enabled in your Terraform backend:</p>
<ol>
<li><p>Terraform writes your state to an <code>.tfstate</code> object in S3 (as before)</p>
</li>
<li><p>To acquire a lock, Terraform uses <strong>S3's conditional write operations</strong> – specifically the <code>if-none-match</code> conditional header to create a lock file atomically</p>
</li>
<li><p>If the lock file already exists, S3 rejects the write, and Terraform reports that a lock is held</p>
</li>
<li><p>When the operation completes, Terraform deletes the lock file to release the lock.</p>
</li>
</ol>
<p>The key difference from DynamoDB: the entire locking mechanism lives inside S3. No second service. No second set of IAM permissions. No second resource to provision.</p>
<p><strong>Note:</strong> This feature requires Terraform version <strong>1.10.0 or later</strong> and an S3 bucket with <strong>Object Lock enabled</strong>. Object Lock must be enabled at bucket creation time. You can't enable it on an existing bucket through the console or CLI. But there is a supported workaround for existing buckets, which we'll cover in Part 2.</p>
<h2 id="heading-how-s3-native-locking-compares-to-the-s3-dynamodb-approach">How S3 Native Locking Compares to the S3 + DynamoDB Approach</h2>
<table>
<thead>
<tr>
<th><strong>Aspect</strong></th>
<th><strong>S3 + DynamoDB (Old)</strong></th>
<th><strong>S3 Native Locking (New)</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>AWS services required</strong></td>
<td>S3 + DynamoDB</td>
<td>S3 only</td>
</tr>
<tr>
<td><strong>IAM permissions needed</strong></td>
<td>S3 + DynamoDB permissions</td>
<td>S3 permissions only</td>
</tr>
<tr>
<td><strong>Terraform version</strong></td>
<td>Any</td>
<td>1.10.0 or later</td>
</tr>
<tr>
<td><strong>Setup complexity</strong></td>
<td>Two resources, two IAM scopes</td>
<td>One resource</td>
</tr>
<tr>
<td><strong>Stuck lock resolution</strong></td>
<td>Delete DynamoDB record</td>
<td>Delete S3 lock file</td>
</tr>
<tr>
<td><strong>Cost</strong></td>
<td>S3 storage + DynamoDB on-demand</td>
<td>S3 storage only</td>
</tr>
<tr>
<td><strong>Object Lock requirement</strong></td>
<td>Not required</td>
<td>Required on S3 bucket</td>
</tr>
<tr>
<td><strong>Locking mechanism</strong></td>
<td>DynamoDB conditional writes</td>
<td>S3 conditional writes (<code>if-none-match</code>)</td>
</tr>
<tr>
<td><strong>State versioning</strong></td>
<td>S3 Versioning (recommended)</td>
<td>S3 Versioning (required for full safety)</td>
</tr>
</tbody></table>
<p>The functional behavior from Terraform's perspective is identical. Locking works the same way. The lock information displayed when a lock is held has the same structure. The only difference is what happens under the hood.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you start, make sure you have the following in place:</p>
<ul>
<li><strong>Terraform 1.10.0 or later</strong> installed. Check your version:</li>
</ul>
<pre><code class="language-shell">terraform version
</code></pre>
<p>If you need to upgrade, follow the <a href="https://developer.hashicorp.com/terraform/install">official upgrade guide</a>.</p>
<ul>
<li><strong>AWS CLI</strong> installed and configured with credentials that have permission to create and manage S3 buckets.</li>
</ul>
<pre><code class="language-shell">aws --version
aws sts get-caller-identity   # confirm you're authenticated
</code></pre>
<ul>
<li><p><strong>IAM permissions</strong> to perform the following S3 actions:</p>
<ul>
<li><p><code>s3:CreateBucket</code></p>
</li>
<li><p><code>s3:PutBucketVersioning</code></p>
</li>
<li><p><code>s3:PutBucketEncryption</code></p>
</li>
<li><p><code>s3:PutObjectLegalHold</code></p>
</li>
<li><p><code>s3:PutObjectRetention</code></p>
</li>
<li><p><code>s3:GetObject</code></p>
</li>
<li><p><code>s3:PutObject</code></p>
</li>
<li><p><code>s3:DeleteObject</code></p>
</li>
<li><p><code>s3:ListBucket</code></p>
</li>
</ul>
</li>
<li><p>For the <strong>migration path</strong>: access to your existing Terraform project and the S3 bucket and DynamoDB table currently in use.</p>
</li>
</ul>
<h2 id="heading-part-1-fresh-setup-how-to-configure-s3-native-locking-from-scratch">Part 1: Fresh Setup – How to Configure S3 Native Locking from Scratch</h2>
<p>Follow this section if you're starting a new Terraform project and want to use S3 native locking from the beginning.</p>
<h3 id="heading-step-1-create-the-s3-bucket-with-versioning-and-encryption">Step 1: Create the S3 Bucket with Versioning and Encryption</h3>
<p>Object Lock <strong>must be enabled at bucket creation time</strong>. You can't add it afterward through the standard console flow. Create the bucket using the AWS CLI with Object Lock enabled:</p>
<pre><code class="language-shell">aws s3api create-bucket \
  --bucket your-project-terraform-state \
  --region us-east-1 \
  --object-lock-enabled-for-bucket
</code></pre>
<p><strong>Note:</strong> For regions other than <code>us-east-1</code>, add the <code>--create-bucket-configuration</code> flag.</p>
<pre><code class="language-shell">aws s3api create-bucket \
  --bucket your-project-terraform-state \
  --region eu-west-1 \
  --create-bucket-configuration LocationConstraint=eu-west-1 \
  --object-lock-enabled-for-bucket
</code></pre>
<p>Now enable versioning on the bucket. Versioning is required alongside Object Lock and allows Terraform to recover previous state versions if something goes wrong:</p>
<pre><code class="language-shell">aws s3api put-bucket-versioning \
  --bucket your-project-terraform-state \
  --versioning-configuration Status=Enabled
</code></pre>
<p>Enable server-side encryption so your state files are encrypted at rest:</p>
<pre><code class="language-shell">aws s3api put-bucket-encryption \
  --bucket your-project-terraform-state \
  --server-side-encryption-configuration '{
    "Rules": [
      {
        "ApplyServerSideEncryptionByDefault": {
          "SSEAlgorithm": "AES256"
        },
        "BucketKeyEnabled": true
      }
    ]
  }'
</code></pre>
<p>Block all public access to the bucket. A Terraform state file contains resource IDs, IP addresses, and potentially sensitive values. It should never be publicly accessible:</p>
<pre><code class="language-shell">aws s3api put-public-access-block \
  --bucket your-project-terraform-state \
  --public-access-block-configuration \
    "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
</code></pre>
<p>Verify the bucket configuration:</p>
<pre><code class="language-shell"># Confirm Object Lock is enabled
aws s3api get-object-lock-configuration \
  --bucket your-project-terraform-state
 
# Confirm versioning is enabled
aws s3api get-bucket-versioning \
  --bucket your-project-terraform-state
 
# Confirm encryption is configured
aws s3api get-bucket-encryption \
  --bucket your-project-terraform-state
</code></pre>
<p>Expected output for the Object Lock check:</p>
<pre><code class="language-json">{
    "ObjectLockConfiguration": {
        "ObjectLockEnabled": "Enabled"
    }
}
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/65a5bfab4c73b29396c0b895/2b2e56cf-687f-4932-a61e-ed7cc33ea6f1.png" alt="Terminal showing AWS CLI verification commands confirming S3 bucket is configured correctly with Object Lock, versioning, and encryption enabled" style="display:block;margin:0 auto" width="1120" height="616" loading="lazy">

<h3 id="heading-step-2-configure-the-terraform-backend-with-native-locking">Step 2: Configure the Terraform Backend with Native Locking</h3>
<p>In your Terraform project, create or update your <code>backend.tf</code> file:</p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket = "your-project-terraform-state"
    key    = "production/terraform.tfstate"
    region = "us-east-1"
 
    # Enable S3 native state locking
    # Requires Terraform 1.10.0+ and a bucket with Object Lock enabled
    use_lockfile = true
 
    # Encryption at rest
    encrypt = true
  }
}
</code></pre>
<p>The critical difference from the old configuration is the <code>use_lockfile = true</code> parameter. Notice what is <strong>absent</strong>: there's no <code>dynamodb_table</code> argument. No DynamoDB table. No second service.</p>
<p>Here's a direct comparison of the old and new configurations:</p>
<p><strong>Old configuration (S3 + DynamoDB):</strong></p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket         = "your-project-terraform-state"
    key            = "production/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"   # this goes away
  }
}
</code></pre>
<p><strong>New configuration (S3 native locking):</strong></p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket       = "your-project-terraform-state"
    key          = "production/terraform.tfstate"
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true   # this replaces dynamodb_table
  }
}
</code></pre>
<h3 id="heading-step-3-initialize-and-verify">Step 3: Initialize and Verify</h3>
<p>Run <code>terraform init</code> to initialize the backend:</p>
<pre><code class="language-shell">terraform init
</code></pre>
<p>Expected output:</p>
<pre><code class="language-plaintext">Initializing the backend...
 
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
 
Initializing provider plugins...
 
Terraform has been successfully initialized!
</code></pre>
<p>Run a plan to confirm everything is working end-to-end:</p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>If locking is working, you'll see a brief pause while Terraform acquires the lock before the plan output appears. You'll also see the lock information if you look at the S3 bucket&nbsp;– a <code>.tflock</code> file will appear temporarily alongside your state file during the operation and disappear when it completes.</p>
<h2 id="heading-part-2-migration-how-to-move-from-s3-dynamodb-to-s3-native-locking">Part 2: Migration&nbsp;– How to Move from S3 + DynamoDB to S3 Native Locking</h2>
<p>Follow this section if you have an <strong>existing Terraform setup</strong> using an S3 bucket and DynamoDB table for state locking, and you want to migrate to S3 native locking.</p>
<p><strong>Important:</strong> Migration requires a maintenance window or at minimum a period where no Terraform operations are running. You're changing the backend configuration, which means <strong>all team members and CI/CD pipelines must stop running</strong> <code>terraform plan</code> <strong>or</strong> <code>terraform apply</code> <strong>during the migration</strong>. The migration itself takes under 10 minutes.</p>
<h3 id="heading-step-1-verify-your-current-setup">Step 1: Verify Your Current Setup</h3>
<p>Before making any changes, document your existing backend configuration and confirm the state file is accessible:</p>
<pre><code class="language-shell"># Confirm your state file is in S3
aws s3 ls s3://your-existing-bucket/path/to/terraform.tfstate
 
# Confirm the DynamoDB table exists
aws dynamodb describe-table \
  --table-name your-dynamodb-lock-table \
  --query 'Table.TableStatus'
</code></pre>
<p>Check your current <code>backend.tf</code> and note the exact values:</p>
<pre><code class="language-shell"># Your current backend.tf - note these values before changing anything
terraform {
  backend "s3" {
    bucket         = "your-existing-bucket"       # note this
    key            = "path/to/terraform.tfstate"   # note this
    region         = "us-east-1"                   # note this
    encrypt        = true
    dynamodb_table = "your-dynamodb-lock-table"    # this will be removed
  }
}
</code></pre>
<p>Run one final plan to confirm the current state is clean and there are no unexpected changes pending:</p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>If the plan shows no changes, you're in a safe state to proceed.</p>
<h3 id="heading-step-2-enable-object-lock-on-the-existing-s3-bucket">Step 2: Enable Object Lock on the Existing S3 Bucket</h3>
<p>This is the most important step in the migration. Object Lock can't normally be enabled on an existing bucket. It's a setting that must be configured at creation time.</p>
<p>But AWS provides a way to enable Object Lock on an existing bucket through a support request or through a direct API call that's not exposed in the standard console UI. AWS has officially documented this path for the Terraform migration use case.</p>
<p>Run the following AWS CLI command to enable Object Lock on your <strong>existing</strong> bucket:</p>
<pre><code class="language-bash">aws s3api put-object-lock-configuration \
  --bucket your-existing-bucket \
  --object-lock-configuration '{"ObjectLockEnabled": "Enabled"}'
</code></pre>
<p><strong>Note:</strong> This command enables Object Lock in <strong>governance mode with no default retention</strong>, meaning it enables the locking capability without setting a default retention period on all objects. This is exactly what Terraform's native locking needs: the ability to create and delete lock files, not permanent object retention.</p>
<p>Verify Object Lock is now enabled:</p>
<pre><code class="language-shell">aws s3api get-object-lock-configuration \
  --bucket your-existing-bucket
</code></pre>
<p>Expected output:</p>
<pre><code class="language-json">{
    "ObjectLockConfiguration": {
        "ObjectLockEnabled": "Enabled"
    }
}
</code></pre>
<p>Also verify that versioning is already enabled (it should be if you are running a production Terraform setup):</p>
<pre><code class="language-shell">aws s3api get-bucket-versioning \
  --bucket your-existing-bucket
</code></pre>
<p>Expected output:</p>
<pre><code class="language-json">{
    "Status": "Enabled"
}
</code></pre>
<p>If versioning isn't enabled, enable it before proceeding:</p>
<pre><code class="language-shell">aws s3api put-bucket-versioning \
  --bucket your-existing-bucket \
  --versioning-configuration Status=Enabled
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/65a5bfab4c73b29396c0b895/cd17df01-3d0a-4f93-9250-3f51627e91c8.png" alt="Terminal output showing successful Object Lock enablement on an existing S3 bucket using the AWS CLI" style="display:block;margin:0 auto" width="1204" height="320" loading="lazy">

<h3 id="heading-step-3-update-the-terraform-backend-configuration">Step 3: Update the Terraform Backend Configuration</h3>
<p>Update your <code>backend.tf</code> to remove the <code>dynamodb_table</code> argument and add <code>use_lockfile = true</code>:</p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket = "your-existing-bucket"
    key    = "path/to/terraform.tfstate"
    region = "us-east-1"
    encrypt = true
 
    # Add this:
    use_lockfile = true
 
    # Remove this line entirely:
    # dynamodb_table = "your-dynamodb-lock-table"
  }
}
</code></pre>
<p>Your updated <code>backend.tf</code> should look like this:</p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket       = "your-existing-bucket"
    key          = "path/to/terraform.tfstate"
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true
  }
}
</code></pre>
<h3 id="heading-step-4-reinitialize-terraform">Step 4: Reinitialize Terraform</h3>
<p>Run <code>terraform init</code> with the <code>-reconfigure</code> flag. This flag tells Terraform that the backend configuration has changed intentionally and to reinitialize without prompting you to copy state (the state is already in the same bucket):</p>
<pre><code class="language-shell">terraform init -reconfigure
</code></pre>
<p>Expected output:</p>
<pre><code class="language-plaintext">Initializing the backend...
 
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
 
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
 
Terraform has been successfully initialized!
</code></pre>
<p><strong>If you see an error here:</strong> The most common cause is that Object Lock wasn't successfully enabled on the bucket. Re-run the verification from Step 2 before proceeding.</p>
<h3 id="heading-step-5-verify-the-migration">Step 5: Verify the Migration</h3>
<p>Run a plan to confirm Terraform is working correctly with the new backend configuration:</p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>The plan should:</p>
<ul>
<li><p>Complete successfully</p>
</li>
<li><p>Show the same result as the plan you ran in Step 1 (no changes, or the same changes as before)</p>
</li>
<li><p>NOT mention DynamoDB anywhere in its output</p>
</li>
</ul>
<p>To confirm that locking is actually using S3 instead of DynamoDB, open a second terminal and run a plan while the first one is running. You should see the second terminal output a lock error that mentions S3, not DynamoDB:</p>
<pre><code class="language-plaintext">╷
│ Error: Error acquiring the state lock
│
│Error message: operation error S3: PutObject, https response       error StatusCode: 409,
│ RequestID: ..., api error Conflict: Object lock already exists for this key.
│
│ Lock Info:
│   ID:        a1b2c3d4-e5f6-7890-abcd-ef1234567890
│   Path:      your-existing-bucket/path/to/terraform.tfstate.tflock
│   Operation: OperationTypePlan
│   Who:       user@hostname
│   Version:   1.10.0
│   Created:   2026-05-06 14:22:01 UTC
│   Info:
╵
</code></pre>
<p>The <code>Path</code> field shows <code>.tfstate.tflock</code>, a file in your S3 bucket, not a DynamoDB record. This confirms that locking is now handled entirely by S3.</p>
<img src="https://cdn.hashnode.com/uploads/covers/65a5bfab4c73b29396c0b895/e9abb703-af6e-429c-83bb-2ea2dac43a3a.png" alt="Two terminals showing concurrent terraform plan commands, the second one displays a lock error confirming S3 native locking is working" style="display:block;margin:0 auto" width="1264" height="539" loading="lazy">

<h3 id="heading-step-6-clean-up-the-dynamodb-table">Step 6: Clean Up the DynamoDB Table</h3>
<p>Once you've confirmed the migration is working correctly and your team has run at least one successful <code>plan</code> and <code>apply</code> cycle using the new backend, you can remove the DynamoDB table.</p>
<p><strong>Wait at least 24-48 hours before deleting the DynamoDB table</strong> if you have CI/CD pipelines or multiple team members. This gives time to catch any pipeline that wasn't updated with the new backend configuration.</p>
<p>When you're ready, delete the DynamoDB table:</p>
<pre><code class="language-shell">aws dynamodb delete-table \
  --table-name your-dynamodb-lock-table
</code></pre>
<p>Confirm the deletion:</p>
<pre><code class="language-shell">aws dynamodb describe-table \
  --table-name your-dynamodb-lock-table
</code></pre>
<p>Expected output:</p>
<pre><code class="language-plaintext">An error occurred (ResourceNotFoundException) when calling the DescribeTable operation:
Requested resource not found
</code></pre>
<p>This error confirms that the table is gone. The migration is complete.</p>
<p>If you provisioned the DynamoDB table using Terraform (which is the recommended pattern), remove the resource from your Terraform configuration and run <code>terraform apply</code> to destroy it via Terraform rather than the CLI directly. This keeps your state clean:</p>
<pre><code class="language-hcl"># Remove this entire block from your Terraform configuration:
resource "aws_dynamodb_table" "terraform_state_lock" {
  name         = "terraform-state-lock"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"
 
  attribute {
    name = "LockID"
    type = "S"
  }
}
</code></pre>
<p>After removing the block, run:</p>
<pre><code class="language-bash">terraform apply
</code></pre>
<p>Terraform will detect that the DynamoDB table resource has been removed from configuration and will destroy the table.</p>
<h2 id="heading-how-to-verify-that-locking-is-working">How to Verify That Locking Is Working</h2>
<p>After completing either the fresh setup or the migration, use this procedure to independently verify that locking is functioning correctly.</p>
<h3 id="heading-method-1-observe-the-lock-file-during-an-operation">Method 1: Observe the lock file during an operation</h3>
<p>In one terminal, start a long-running plan against a configuration with many resources:</p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>While it's running, in a second terminal, check for the lock file in S3:</p>
<pre><code class="language-shell">aws s3 ls s3://your-bucket/path/to/ | grep tflock
</code></pre>
<p>You should see a file like:</p>
<pre><code class="language-plaintext">2026-05-06 14:22:01        512 terraform.tfstate.tflock
</code></pre>
<p>After the plan completes, run the same command again. The <code>.tflock</code> file should be gone.</p>
<h3 id="heading-method-2-read-the-lock-file-contents">Method 2: Read the lock file contents</h3>
<p>While a plan is running, download and read the lock file to see its contents:</p>
<pre><code class="language-shell">aws s3 cp \
  s3://your-bucket/path/to/terraform.tfstate.tflock \
  /tmp/current.lock &amp;&amp; cat /tmp/current.lock
</code></pre>
<p>Expected output (formatted for readability):</p>
<pre><code class="language-json">{
  "ID": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "Operation": "OperationTypePlan",
  "Info": "",
  "Who": "tolani@dev-machine",
  "Version": "1.10.0",
  "Created": "2026-05-06T14:22:01.123456789Z",
  "Path": "your-bucket/path/to/terraform.tfstate"
}
</code></pre>
<p>This is the same lock information that Terraform displays when a lock is held. It's now a JSON file in S3 rather than a record in DynamoDB.</p>
<h2 id="heading-how-to-handle-a-stuck-lock">How to Handle a Stuck Lock</h2>
<p>With the DynamoDB backend, resolving a stuck lock meant deleting a record from the DynamoDB table. With S3 native locking, it means deleting the <code>.tflock</code> file from S3.</p>
<p>A lock can get stuck if:</p>
<ul>
<li><p>A <code>terraform apply</code> or <code>plan</code> process was killed mid-execution</p>
</li>
<li><p>A CI/CD pipeline runner crashed during a Terraform operation</p>
</li>
<li><p>A network interruption prevented the lock release from completing</p>
</li>
</ul>
<p>Here's how you can check for a stuck lock:</p>
<pre><code class="language-shell">aws s3 ls s3://your-bucket/path/to/ | grep tflock
</code></pre>
<p>If a <code>.tflock</code> file exists and no Terraform operation is currently running, it is a stuck lock.</p>
<p>You can also read the lock to understand who held it:</p>
<pre><code class="language-shell">aws s3 cp \
  s3://your-bucket/path/to/terraform.tfstate.tflock \
  /tmp/stuck.lock &amp;&amp; cat /tmp/stuck.lock
</code></pre>
<p>This tells you who (<code>Who</code> field) was running the operation, what operation it was (<code>Operation</code> field), and when it was acquired (<code>Created</code> field).</p>
<p>And you can force-unlock using Terraform like this:</p>
<pre><code class="language-shell">terraform force-unlock LOCK-ID
</code></pre>
<p>Replace <code>LOCK-ID</code> with the <code>ID</code> value from the lock file contents. For example:</p>
<pre><code class="language-shell">terraform force-unlock a1b2c3d4-e5f6-7890-abcd-ef1234567890
</code></pre>
<p>Terraform will confirm:</p>
<pre><code class="language-plaintext">Do you really want to force-unlock?
  Terraform will remove the lock on the remote state.
  This will allow local Terraform commands to modify this state, even though it
  may be still be in use. Only 'yes' will be accepted to confirm.
 
  Enter a value: yes
 
Terraform state has been successfully unlocked!
</code></pre>
<p>An alternative is to delete the lock file directly via CLI. If <code>terraform force-unlock</code> doesn't work (for example, because you are running in a CI environment without Terraform available), delete the lock file directly:</p>
<pre><code class="language-shell">aws s3 rm s3://your-bucket/path/to/terraform.tfstate.tflock
</code></pre>
<p><strong>Only delete the lock file if you are certain no Terraform operation is currently running.</strong> Deleting a lock that is actively held by a running operation will allow a second concurrent operation to start, which is exactly the race condition locking is designed to prevent.</p>
<h2 id="heading-rollback-plan-if-something-goes-wrong">Rollback Plan: If Something Goes Wrong</h2>
<p>If you encounter problems after migrating, you can roll back to the S3 + DynamoDB setup with these steps.</p>
<p><strong>Step 1: Stop all Terraform operations</strong> in your team and CI/CD pipelines.</p>
<p><strong>Step 2: Recreate the DynamoDB table</strong> if you already deleted it:</p>
<pre><code class="language-shell">aws dynamodb create-table \
  --table-name terraform-state-lock \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST
</code></pre>
<p><strong>Step 3: Revert</strong> <code>backend.tf</code> to the previous configuration:</p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket         = "your-existing-bucket"
    key            = "path/to/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"   # restored
    # Remove: use_lockfile = true
  }
}
</code></pre>
<p><strong>Step 4: Reinitialize:</strong></p>
<pre><code class="language-shell">terraform init -reconfigure
</code></pre>
<p><strong>Step 5: Verify:</strong></p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>The state file hasn't moved, so there's no data loss during a rollback. The only change is which locking mechanism Terraform uses.</p>
<p><strong>Note:</strong> Object Lock being enabled on the S3 bucket doesn't prevent the rollback. Object Lock and DynamoDB locking can coexist, Object Lock simply adds a capability to the bucket. Using <code>dynamodb_table</code> in your backend config tells Terraform to use DynamoDB regardless of whether Object Lock is enabled on the bucket.</p>
<h2 id="heading-security-best-practices-for-your-state-bucket">Security Best Practices for Your State Bucket</h2>
<p>Migrating to S3 native locking is a good opportunity to review the overall security configuration of your state bucket. Here are the practices every production Terraform state bucket should implement:</p>
<h3 id="heading-enable-versioning-required">Enable Versioning (Required)</h3>
<p>Versioning is a hard requirement for S3 native locking to work safely. It ensures that if a state file is accidentally overwritten or corrupted, you can restore a previous version.</p>
<pre><code class="language-shell">aws s3api put-bucket-versioning \
  --bucket your-state-bucket \
  --versioning-configuration Status=Enabled
</code></pre>
<h3 id="heading-block-all-public-access-non-negotiable">Block All Public Access (Non-Negotiable)</h3>
<p>Your state file contains resource ARNs, IP addresses, and may contain sensitive values passed through Terraform variables. It must never be publicly accessible.</p>
<pre><code class="language-shell">aws s3api put-public-access-block \
  --bucket your-state-bucket \
  --public-access-block-configuration \
    "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
</code></pre>
<h3 id="heading-enable-server-side-encryption">Enable Server-Side Encryption</h3>
<p>Always encrypt state files at rest. AES256 is the minimum. If your organization requires KMS key management:</p>
<pre><code class="language-shell">aws s3api put-bucket-encryption \
  --bucket your-state-bucket \
  --server-side-encryption-configuration '{
    "Rules": [
      {
        "ApplyServerSideEncryptionByDefault": {
          "SSEAlgorithm": "aws:kms",
          "KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789012:key/your-kms-key-id"
        },
        "BucketKeyEnabled": true
      }
    ]
  }'
</code></pre>
<h3 id="heading-apply-least-privilege-iam-permissions">Apply Least-Privilege IAM Permissions</h3>
<p>The role or user that Terraform uses to access the state bucket should have only the permissions it needs. Here's a minimal IAM policy for S3 native locking:</p>
<pre><code class="language-json">{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "TerraformStateAccess",
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": [
        "arn:aws:s3:::your-state-bucket",
        "arn:aws:s3:::your-state-bucket/*"
      ]
    },
    {
      "Sid": "TerraformStateLocking",
      "Effect": "Allow",
      "Action": [
        "s3:GetObjectLegalHold",
        "s3:PutObjectLegalHold",
        "s3:GetObjectRetention",
        "s3:PutObjectRetention"
      ],
      "Resource": "arn:aws:s3:::your-state-bucket/*.tflock"
    }
  ]
}
</code></pre>
<p>Notice what is absent: there are no DynamoDB permissions. This is a cleaner, smaller permission set than the old approach required.</p>
<h3 id="heading-enable-access-logging">Enable Access Logging</h3>
<p>Log all access to your state bucket in CloudTrail or S3 server access logs. This gives you an audit trail of every time state was read, written, or locked:</p>
<pre><code class="language-shell">aws s3api put-bucket-logging \
  --bucket your-state-bucket \
  --bucket-logging-status '{
    "LoggingEnabled": {
      "TargetBucket": "your-logging-bucket",
      "TargetPrefix": "terraform-state-access/"
    }
  }'
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>AWS S3 native state locking removes the need for a DynamoDB table from your Terraform backend setup. The result is simpler infrastructure, a smaller IAM permission surface, and one fewer service to provision, monitor, and pay for across every environment your team manages.</p>
<p>Here's a summary of what you accomplished:</p>
<ul>
<li><p>Understood what state locking is and why it's required for safe Terraform operations</p>
</li>
<li><p>Compared S3 native locking to the existing S3 + DynamoDB approach</p>
</li>
<li><p>Set up a fresh Terraform backend using S3 native locking with correct bucket configuration</p>
</li>
<li><p>Migrated an existing backend from S3 + DynamoDB to S3 native locking safely</p>
</li>
<li><p>Learned how to verify locking, handle stuck locks, and roll back if needed</p>
</li>
<li><p>Applied security best practices to the state bucket</p>
</li>
</ul>
<p>This pattern – using S3 native locking – is the recommended approach for all new Terraform projects on AWS going forward. If you're managing a large estate with multiple Terraform backends, consider automating the migration using a script or Terraform module that applies the pattern across all your state buckets.</p>
<p><em>If you are building or optimizing cloud infrastructure for a startup and want a complete reference for production-ready Terraform modules, CI/CD pipeline patterns, and infrastructure runbooks, check out</em> <a href="https://coachli.co/tolani-akintayo/PR-H4oQS">The Startup DevOps Field Guide</a><em>. It covers the full lifecycle of AWS infrastructure from initial setup to production reliability.</em></p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a href="https://developer.hashicorp.com/terraform/language/backend/s3#use_lockfile">HashiCorp - S3 Backend Configuration: use_lockfile</a></p>
</li>
<li><p><a href="https://github.com/hashicorp/terraform/releases/tag/v1.10.0">HashiCorp: Terraform 1.10 Release Notes</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html">AWS Docs: S3 Object Lock Overview</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLockConfiguration.html">AWS Docs: PutObjectLockConfiguration API</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/conditional-requests.html">AWS Docs: S3 Conditional Writes</a></p>
</li>
<li><p><a href="https://developer.hashicorp.com/terraform/language/state/locking">HashiCorp: Backend State Locking</a></p>
</li>
<li><p><a href="https://developer.hashicorp.com/terraform/cli/commands/force-unlock">HashiCorp: terraform force-unlock Command</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/manage-versioning-examples.html">AWS Docs: Enabling S3 Versioning</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/serv-side-encryption.html">AWS Docs: S3 Server-Side Encryption</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ A Beginner's Guide to Terraform – Infrastructure-as-Code in Practice ]]>
                </title>
                <description>
                    <![CDATA[ Over the years, cloud development has seen a major paradigm shift. Newer and more complex applications are deployed rapidly to the cloud to minimize downtime. And through all of this, the concept of Infrastructure-as-Code and various tools have emerg... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/a-beginners-guide-to-terraform-infrastructure-as-code-in-practice/</link>
                <guid isPermaLink="false">67782a8fd3a89d514105e418</guid>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Beginner Developers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraweekchallenge ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwatobi ]]>
                </dc:creator>
                <pubDate>Fri, 03 Jan 2025 18:21:03 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1735900327439/09832fb8-8cc0-4182-b70a-5f54ee6fce7d.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Over the years, cloud development has seen a major paradigm shift. Newer and more complex applications are deployed rapidly to the cloud to minimize downtime. And through all of this, the concept of Infrastructure-as-Code and various tools have emerged to simplify the process of application development.</p>
<p>You might be wondering: what is Infrastructure-as-Code? How does it improve the development process and experience, and where does Terraform come into the picture? Well, we’ll explore all this and more in this guide. But before we start, here are some pre-requisites:</p>
<ul>
<li><p>Basic knowledge of the cloud and cloud terminologies</p>
</li>
<li><p>Access to a PC to implement code examples</p>
</li>
<li><p>A GCP account</p>
</li>
</ul>
<p>With this, let's get started.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-overview-of-infrastructure-as-code-iac">Overview of Infrastructure as Code</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-terraform">What is Terraform?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-benefits-of-terraform">Benefits of Terraform</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-common-terms-used-in-terraform">Common Terms Used in Terraform</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-demo-project-how-to-write-a-terraform-configuration">Demo Project: How to Write a Terraform Configuration</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-overview-of-infrastructure-as-code-iac">Overview of Infrastructure as Code (IaC)</h2>
<p>Infrastructure as code refers to generating cloud infrastructure tools and applications with a code-based configuration document. This process, when running, automates the sequence and process of creating databases, virtual machines, and servers. This improves the user experience by reducing the frequency of manual cloud service deployments, especially for multiple identical services.</p>
<p>There are two distinct approaches to infrastructure as code: the <code>Imperative</code> approach and the <code>Declarative</code> approach.</p>
<p>When you’re using the Declarative approach to infrastructure generation, you simply detail your expected/desired outputs for the Infrastructure to be generated, and then the IaC tool you’re using figures out how to produce that output.</p>
<p>On the other hand, the Imperative approach involves specifying the exact steps needed to achieve the desired infrastructure state. While the Imperative approach seems more suited for complex infrastructure setups, the Declarative approach can work just as well. </p>
<p>Some tools are capable of both approaches while others are only suited to one or the other. Examples of some of the popular IaC tools used globally include <a target="_blank" href="https://www.terraform.io/">Terraform IaC</a>, <a target="_blank" href="https://aws.amazon.com/cloudformation/">AWS Cloud Formation</a>, <a target="_blank" href="https://www.redhat.com/en/ansible-collaborative">Ansible</a>, and <a target="_blank" href="https://www.pulumi.com/">Pulumi</a>, <a target="_blank" href="https://www.chef.io/glossary/what-is-infrastructure-as-code">Chef</a>, among others.</p>
<p>Like the name implies – infrastructure as <strong>code</strong> – the code creating the infrastructure is written in various template languages within the IaC space. Popular template languages include JSON, YAML, ARM template, HCL, Heat Scripts, and so on.</p>
<p>You can also use scripting tools to execute cloud infrastructure. Some popular ones include Bash and PowerShell. These sometimes come preinstalled on most personal computers.</p>
<p>Out of all these tools, though, Terraform is distinct for various reasons – and it’s the one we’ll be examining in this article.</p>
<h2 id="heading-what-is-terraform">What is Terraform?</h2>
<p>Terraform is an open source tool developed by HashiCorp in 2014. It has evolved over the years and now serves as a cloud agnostic infrastructure tool that allows you to create infrastructure across multiple cloud service providers.</p>
<p>Terraform also offers <a target="_blank" href="https://app.terraform.io/session">Terraform Cloud</a>, a cloud-based software as a service tool. It allows for cloud-based deployment of cloud tools, instead of using the old local-based methods we had in the defunct Terraform CLI tool.</p>
<p>Also, like other IaC tools which utilize template languages, the template framework used to create infrastructure in Terraform is the HashiCorp template language (HCL).</p>
<h2 id="heading-benefits-of-terraform">Benefits of Terraform</h2>
<p>Now I’ll highlight some of the benefits of using Terraform as a cloud engineer, along with the tool’s key role in the cloud ecosystem.</p>
<h3 id="heading-1-declarative-approach"><strong>1. Declarative Approach</strong></h3>
<p>This approach to cloud infrastructure automation ensures that all required infrastructure to be deployed (databases, servers, and so on) is stated explicitly and executed accordingly. This helps avoid conflicts.</p>
<h3 id="heading-2-conflict-handling"><strong>2. Conflict Handling</strong></h3>
<p>In addition to its efficient cloud tool automation capabilities, Terraform has some robust conflict detection and handling properties. One of the ways it handles conflicts is via the <code>Terraform plan</code> function. This function highlights any perceived or potential conflicts of infrastructure orchestration which allows for easy correction before deployment. I’ll discuss this further in subsequent sections.</p>
<h3 id="heading-3-cloud-agnostic"><strong>3. Cloud Agnostic</strong></h3>
<p>Terraform is a multipurpose, multi-cloud automation service provider with efficient infrastructure automation capabilities across the major cloud service providers (AWS, GCP and Azure). It also allows for hybrid and inter-provider automation.</p>
<h3 id="heading-4-user-friendly"><strong>4. User-friendly</strong></h3>
<p>Terraform is one of the largest cloud automation tools with the largest user communities out there. It has extensive beginner-friendly tutorials that help you get a quick hang of the tool. Here is a link to its <a target="_blank" href="https://developer.hashicorp.com/terraform/docs">documentation</a> so you can dive in deeper.</p>
<h3 id="heading-5-file-management-capabilities"><strong>5. File Management Capabilities</strong></h3>
<p>Terraform automatically creates a local backup of the automation states on your local computer to ensure immediate recall and file handling in case anything goes wrong. It also offers remote backup options to remote cloud service providers where necessary.</p>
<h3 id="heading-6-version-control"><strong>6. Version Control</strong></h3>
<p>Just like the Git version control system, Terraform has a built-in version control system which lets you track changes to a Terraform file. It also lets you go back to previous versions of your code if there are errors in the present version, for example.</p>
<h3 id="heading-7-code-reusability"><strong>7. Code Reusability</strong></h3>
<p>Terraform offers a wide variety of code templates for easy reuse on its developer documentation page.</p>
<p>Now that we’ve highlighted the benefits of Terraform, let’s learn some common terminologies used in Terraform and what they mean.</p>
<h2 id="heading-common-terms-used-in-terraform">Common Terms Used in Terraform</h2>
<p>Before you start using Terraform, you should be familiar with some key terms that come up a lot. Here’s what you need to know:</p>
<ol>
<li><p><strong>Providers</strong>: in Terraform, a Provider is a programming interface that lets Terraform interact with various APIs and cloud services. For example, you’d use a provider to interface with a cloud service provider like GCP or Azure.</p>
</li>
<li><p><strong>Modules:</strong> Modules are specifically created within the Terraform framework and serve as reusable components that let you easily orchestrate cloud services. You can also store key information regarding cloud services in a module, and then modify it to ensure uniqueness using module variables.</p>
</li>
<li><p><strong>Resources:</strong> Resources in Terraform refer to the cloud infrastructure components to be created. Examples include cloud networks, virtual machines, availability zones, and other infrastructures.</p>
</li>
<li><p><strong>State:</strong> The concept of state in Terraform forms the basis for its efficiency. State keeps track of the current configuration of your infrastructure resources, and contains details about every resource Terraform has created, modified, or deleted. Terraforms version control system uses it to track any changes you make to a code file and uses that information to destroy and provision infrastructure as necessary.</p>
</li>
<li><p><strong>Workspace:</strong> a Workspace functions sort of similarly to a version control system, as it creates a sort of constraint around a work file. Workspaces let you manage multiple instances of a single infrastructure configuration in a clean and isolated way within the same backend. You can use workspaces to separate environments like development, staging, and production while using the same Terraform configuration.</p>
</li>
</ol>
<h2 id="heading-demo-project-how-to-write-a-terraform-configuration">Demo Project: How to Write a Terraform Configuration</h2>
<p>In this section, we’ll be diving deeper into writing our first Terraform file to orchestrate a Google Cloud program virtual machine with just a few lines of code. But before we begin, we’ll discuss the various commands that you should understand before we implement the demo project.</p>
<h3 id="heading-common-terraform-commands">Common Terraform Commands</h3>
<ul>
<li><p><code>Terraform init</code><strong>:</strong> This command initializes the Terraform tool and downloads essential cloud provider-specific files. It also establishes a connection between Terraform and the cloud provider in question. In our case, it’s between GCP and the Terraform provider.</p>
</li>
<li><p><code>Terraform fmt</code><strong>:</strong> This command automatically ensures optimal code formatting and indentation. It ensures orderly execution of the code and minimizes any errors.</p>
</li>
<li><p><code>Terraform plan</code><strong>:</strong> This command outlines the steps of execution of the Terraform code, and detects any errors that may occur during the process of execution. It also highlights any errors in the Terraform code that may hinder execution. Lastly, it works alongside Terraform state management to detect any change of state and de-provision or generate any additional cloud services if necessary.</p>
</li>
<li><p><code>Terraform apply</code><strong>:</strong> This command executes the planned Terraform state implemented by the <code>Terraform plan</code> command.</p>
</li>
<li><p><code>Terraform destroy</code><strong>:</strong> This command is the final command in the Terraform scheme which is used to deactivate or destroy all the cloud services created using the Terraform apply command. It's important to note that you should execute the commands listed above sequentially to ensure that your infrastructure gets created properly.</p>
</li>
</ul>
<h3 id="heading-creating-an-iac-powered-gcp-virtual-machine">Creating an IaC-Powered GCP Virtual Machine</h3>
<p>Now that you’ve learned these important commands, let’s test them all out by creating our first-ever IaC-powered GCP virtual machine.</p>
<p>In your code editor, type the following code:</p>
<pre><code class="lang-plaintext">provider "google" {
  project = "your-gcp-project-id"  # Replace with your GCP Project ID
  region  = "us-central1"          
  zone    = "us-central1-a"        
}
</code></pre>
<p>This code highlights the cloud provider we’re using to generate the cloud resources we need. In our case, it’s the Google cloud program. The name assigned to it is just “google”. Other cloud providers like AWS and Azure are “aws” and “azure” respectively.</p>
<p>The second line identifies the GCP subscription identifier, which is unique to each GCP account (and helps facilitate accurate integration). <strong>You should use yours in the space provided.</strong></p>
<p>You’ll also need to include a suitable resource region and resource availability zone. This serves as the physical base for the virtual machine we’ll create so we can run it. In this scenario, I chose the USA central region and 1-a availability zone, respectively. You can read more <a target="_blank" href="https://cloud.google.com/compute/docs/regions-zones">here</a> about cloud regions and availability zones.</p>
<pre><code class="lang-plaintext">resource "google_compute_instance" "vm_instance" {
  name         = "example-vm"      
  machine_type = "e2-medium"          

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-11" 
    }
  }
</code></pre>
<p>The code snippet above specifies the exact compute resource that’ll be orchestrated, which in our case is a virtual machine instance coded as “vm_instance”. <code>'example-vm’</code> is the name we want to assign to the virtual machine we will be creating for this project. It is important to note that the virtual machine name must be unique too. The type of the virtual machine we opted for was the E2 (General purpose)-medium type VM. You can get more information on Virtual machine types <a target="_blank" href="https://cloud.google.com/compute/docs/general-purpose-machines">here</a>.</p>
<p>Going further, we also specify the expected booted Operating system (“boot_disk”) which is an image of the Debian Linux Operating system version 11 in my case.</p>
<pre><code class="lang-plaintext">  network_interface {
    network = "default"  # Attach to the default VPC network
    access_config {

    }
  }

output "instance_ip" {
  value = google_compute_instance.vm_instance.network_interface[0].access_config[0].nat_ip
}
</code></pre>
<p>To complete the creation of our virtual machine, we need to set up a Virtual Network to allow remote access to the VM. The network interface block connects the virtual machine to the default VPC (Virtual Private Cloud) network provided by GCP. We won’t be able to interface with our virtual machine without the VPC network. The output block also displays the default access IP address in the terminal, which we can use to connect to the virtual machine.</p>
<p>Here is the final expected code:</p>
<pre><code class="lang-plaintext">
provider "google" {
  project = "your-gcp-project-id"  # Replace with your GCP Project ID
  region  = "us-central1"          
  zone    = "us-central1-a"       
}

resource "google_compute_instance" "vm_instance" {
  name         = "example-vm"         
  machine_type = "e2-medium"          

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-11"  
    }
  }

  network_interface {
    network = "default"  # Attach to the default VPC network
    access_config {

    }
  }

output "instance_ip" {
  value = google_compute_instance.vm_instance.network_interface[0].access_config[0].nat_ip
}
</code></pre>
<p>Going on from there, we’ll now be executing this code using the commands highlighted in the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734796321588/29561c5c-3908-43d1-8579-53a3de33358a.png" alt="29561c5c-3908-43d1-8579-53a3de33358a" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The command <code>terraform -v</code> confirms that Terraform has been successfully installed on the terminal. The expected output will be the version of the Terraform tool installed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734796340539/149f5f24-90eb-4777-8ae3-18acdd3c758a.png" alt="149f5f24-90eb-4777-8ae3-18acdd3c758a" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The next command executed is the <code>terraform init</code> function which initializes a communication with the cloud service provider, which in our case is GCP. All needed dependencies are also installed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734796301342/bd886728-dfdb-49f7-bcbf-1e53ff203b35.png" alt="bd886728-dfdb-49f7-bcbf-1e53ff203b35" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The <code>terraform fmt</code> command is also run to ensure adequate code formatting and indentation. Then the <code>terraform plan</code> command is sequentially executed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734889731467/bb454ec4-47e4-40a4-84fc-91c580fb77bb.png" alt="bb454ec4-47e4-40a4-84fc-91c580fb77bb" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>From the image above, you can see the steps Terraform intends to use to generate the expected Virtual machine.</p>
<p>On successful execution of Terraform plan, we will then execute the <code>terraform apply</code> function to execute the steps outlined by Terraform plan.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734796355772/d1d8f9f9-98a9-4ab0-be00-60a9b2b993a9.png" alt="d1d8f9f9-98a9-4ab0-be00-60a9b2b993a9" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>This will generate a prompt asking for a confirmation of the Terraform execution as shown above. Typing “Yes” will allow the operation to proceed smoothly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734796361770/a08254b0-878a-4681-b6ce-f6b0a9a83bc6.png" alt="a08254b0-878a-4681-b6ce-f6b0a9a83bc6" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>On successful execution, a success message will be displayed as shown above. With that, we have created our Cloud infrastructure with just code. The <code>terraform destroy</code> command can then be called to remove the created Virtual machines.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you’ve learned the basics about infrastructure as code. We discussed Terraform, its benefits, and some of its key features and commands. We also illustrated its use in a demo project.</p>
<p>To further enhance your knowledge, you can <a target="_blank" href="https://developer.hashicorp.com/terraform?product_intent=terraform">consult Terraform‘s documentation</a> for more code examples. I would also recommend utilizing your newly gained knowledge to automate a project with real-life uses.</p>
<p>Feel free to message me with any comments or questions. You can also check out my other articles <a target="_blank" href="http://portfolio-oluwatobi.netlify.app">here</a>. Till next time, keep on coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Choose the Right IaC Tool – AWS CDK, CloudFormation, and Terraform Compared ]]>
                </title>
                <description>
                    <![CDATA[ Infrastructure as Code (IaC) has become a cornerstone of modern cloud resource management. It enables developers and engineers to manage their cloud resources with the same level of control and precision as application code.  When you're working with... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/comparing-iac-tools-aws-cdk-cloudformation-terraform/</link>
                <guid isPermaLink="false">66c37738b737bb2ce7073282</guid>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ifeanyi Otuonye ]]>
                </dc:creator>
                <pubDate>Mon, 03 Jun 2024 21:27:31 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/06/Level-Up-Tech-Design-Portfolio.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Infrastructure as Code (IaC) has become a cornerstone of modern cloud resource management. It enables developers and engineers to manage their cloud resources with the same level of control and precision as application code. </p>
<p>When you're working with AWS, among the tools at the forefront of utilizing IaC are AWS CloudFormation, AWS Cloud Development Kit (CDK), and HashiCorp’s Terraform. </p>
<p>Each of these IaC tools offers unique features and approaches to infrastructure management. This makes them suitable for different scenarios and preferences, and they can help you automate and standardize your or your team's cloud resource deployments.</p>
<p>This article will provide a high-level comparison of these three tools, focusing on their capabilities, abstraction levels, and practical use cases. You'll explore how these tools enable you to programmatically create and manage complex cloud infrastructures. </p>
<p>Specifically, the focus will be on deploying a three-tier architecture networking infrastructure. It'll include deploying a Virtual Private Cloud (VPC) configured with multiple subnets, route tables, an internet gateway, and NAT gateways to showcase the unique capabilities and syntax of each IaC tool.</p>
<p>By the end of this article, you'll gain a thorough understanding of the functionalities of these tools so you can make an informed decision when selecting one to build resilient, scalable and efficiently managed cloud infrastructures.</p>
<p>Without further ado, let’s get this party started!</p>
<h2 id="heading-what-well-cover">What We'll Cover:</h2>
<ol>
<li><a class="post-section-overview" href="#heading-what-is-infrastructure-as-code-iac">What is Infrastructure as Code (IaC)?</a></li>
<li><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></li>
<li><a class="post-section-overview" href="#heading-use-case-scenario">Use Case Scenario</a></li>
<li><a class="post-section-overview" href="#heading-iac-tool-code-examples">IaC Tool Code Examples</a></li>
<li><a class="post-section-overview" href="#heading-analysis-and-comparison">Analysis and Comparison</a></li>
<li><a class="post-section-overview" href="#heading-why-choose-one-over-the-other">Why Choose One Over the Other?</a></li>
</ol>
<h2 id="heading-what-is-infrastructure-as-code-iac">What is Infrastructure as Code (IaC)?</h2>
<p>Infrastructure as Code is a key DevOps principle that involves managing and provisioning infrastructure resources by defining it as code in configuration files, instead of using manual processes and settings.</p>
<p>If you want to learn more about IaC basics, <a target="_blank" href="https://www.freecodecamp.org/news/infrastructure-as-code-basics/">here's a helpful guide to get you started</a>.</p>
<p>Now let's learn a bit more about the three tools we'll be comparing in this overview.</p>
<h3 id="heading-what-does-aws-cloudformation-do">What Does AWS CloudFormation Do?</h3>
<p>AWS CloudFormation uses YAML or JSON to describe as well as automatically and securely provision infrastructure resources needed for your applications – across all regions and accounts in your AWS cloud environment.</p>
<h3 id="heading-what-does-aws-cloud-development-kit-cdk-do">What Does AWS Cloud Development Kit (CDK) Do?</h3>
<p>AWS Cloud Development Kit is a software development framework specifically used for defining cloud infrastructure in code. It ultimately provisions resources through AWS CloudFormation. </p>
<p>AWS CDK uses familiar programming languages like TypeScript, JavaScript, Python, Java, and others to define reusable cloud components known as constructs. These are then shared and used to create complex and scalable cloud architectures.</p>
<h4 id="heading-whats-a-construct">What's a construct?</h4>
<p>In the context of AWS CDK, a construct represents a cloud component that encapsulates certain functionality and configuration in a reusable form.</p>
<h3 id="heading-what-does-terraform-do">What Does Terraform Do?</h3>
<p>Terraform is a multi-tenant tool created by HashiCorp that allows you to define both low-level and high-level components of your cloud infrastructure using a declarative configuration language. </p>
<p>It is cloud-agnostic and is capable of managing multi-provider setups within a single configuration.</p>
<h4 id="heading-what-does-cloud-agnostic-mean">What does cloud-agnostic mean?</h4>
<p>Cloud-agnostic refers to the ability of a tool or service to operate across different cloud providers without significant changes to its operational procedures or architecture.</p>
<p>Alright, now that you understand the tools we'll be discussing, let's dive in.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li>AWS Account with an IAM User with admin permissions</li>
<li>Basic knowledge and use of AWS CloudFormation, AWS CDK, and Terraform</li>
<li>Basic understanding of YAML, Python, and the HashiCorp Configuration Language</li>
<li>Experience with an Interactive Development Environment (IDE)</li>
</ul>
<h2 id="heading-use-case-scenario">Use Case Scenario</h2>
<p>You’re a Cloud Network Engineer at REXTECH Corp, a startup on the verge of launching a new online service that offers digital content streaming. As the service is expected to attract a substantial user base right from the start, you need to deploy a highly scalable, reliable, and secure cloud infrastructure that can handle peak traffic and provide continuous availability.</p>
<p>Your manager has mandated a cloud network solution that not only meets these performance requirements but also allows for rapid scaling and efficient management. </p>
<p>In response to this, you are tasked with automating the deployment of a three-tier architecture networking infrastructure. It needs to have a Virtual Private Cloud (VPC) that includes multiple subnets across multiple Availability Zones (AZs), NAT gateways, and route tables to ensure resiliency and optimal configuration.</p>
<p>With the need for agility and maintainability in your infrastructure, you decide to evaluate and choose between AWS CloudFormation, AWS CDK, and Terraform for this project. </p>
<p>Before you evaluate each tool's application to the scenario, let’s break down the deployment resource components.</p>
<p>This deployment involves configuring a VPC with two public subnets for frontend facing web servers, two private subnets for servers in the application tier, and another two private subnets to host a multi-AZ database. All subnets will be deployed across multiple AZs and will include the connectivity configurations between components through route tables and an internet gateway.</p>
<p>Also, two NAT gateways in the public subnets will ensure that the resources in the private subnets of the application tier can securely access the internet for updates and inter-service communication without direct exposure to the outside world.</p>
<p>Now, let’s learn how you can automate the creation of this solution using all three IaC tools: <strong>AWS</strong> <strong>CloudFormation</strong>, <strong>AWS CDK</strong> and <strong>Terraform</strong>.</p>
<h2 id="heading-iac-tool-code-examples">IaC Tool Code Examples</h2>
<h3 id="heading-aws-cloudformation-example">AWS CloudFormation Example</h3>
<p>AWS CloudFormation allows you to define your desired infrastructure using a declarative JSON or YAML configuration file. But you must define the interdependencies and connections between resources using intrinsic functions like <strong>!Ref</strong>, referencing other resources or <strong>!GetAtt</strong>, to help select availability zones dynamically.</p>
<p>Below is how you define the three-tier networking solution using AWS CloudFormation:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">AWSTemplateFormatVersion:</span> <span class="hljs-string">'2010-09-09'</span>
<span class="hljs-attr">Resources:</span>
  <span class="hljs-attr">MyVPC:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::VPC'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">CidrBlock:</span> <span class="hljs-string">'10.0.0.0/16'</span>
      <span class="hljs-attr">EnableDnsSupport:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">EnableDnsHostnames:</span> <span class="hljs-literal">true</span>

  <span class="hljs-attr">InternetGateway:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::InternetGateway'</span>

  <span class="hljs-attr">AttachGateway:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::VPCGatewayAttachment'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">VpcId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">MyVPC</span>
      <span class="hljs-attr">InternetGatewayId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">InternetGateway</span>

  <span class="hljs-attr">PublicSubnetOne:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::Subnet'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">VpcId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">MyVPC</span>
      <span class="hljs-attr">CidrBlock:</span> <span class="hljs-string">'10.0.1.0/24'</span>
      <span class="hljs-attr">AvailabilityZone:</span> <span class="hljs-type">!Select</span> [<span class="hljs-number">0</span>, <span class="hljs-type">!GetAZs</span> <span class="hljs-string">''</span>]
      <span class="hljs-attr">MapPublicIpOnLaunch:</span> <span class="hljs-literal">true</span>

  <span class="hljs-attr">PublicSubnetTwo:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::Subnet'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">VpcId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">MyVPC</span>
      <span class="hljs-attr">CidrBlock:</span> <span class="hljs-string">'10.0.2.0/24'</span>
      <span class="hljs-attr">AvailabilityZone:</span> <span class="hljs-type">!Select</span> [<span class="hljs-number">1</span>, <span class="hljs-type">!GetAZs</span> <span class="hljs-string">''</span>]
      <span class="hljs-attr">MapPublicIpOnLaunch:</span> <span class="hljs-literal">true</span>

  <span class="hljs-attr">PrivateSubnetAppOne:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::Subnet'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">VpcId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">MyVPC</span>
      <span class="hljs-attr">CidrBlock:</span> <span class="hljs-string">'10.0.3.0/24'</span>
      <span class="hljs-attr">AvailabilityZone:</span> <span class="hljs-type">!Select</span> [<span class="hljs-number">0</span>, <span class="hljs-type">!GetAZs</span> <span class="hljs-string">''</span>]

  <span class="hljs-attr">PrivateSubnetAppTwo:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::Subnet'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">VpcId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">MyVPC</span>
      <span class="hljs-attr">CidrBlock:</span> <span class="hljs-string">'10.0.4.0/24'</span>
      <span class="hljs-attr">AvailabilityZone:</span> <span class="hljs-type">!Select</span> [<span class="hljs-number">1</span>, <span class="hljs-type">!GetAZs</span> <span class="hljs-string">''</span>]

  <span class="hljs-attr">PrivateSubnetDBOne:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::Subnet'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">VpcId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">MyVPC</span>
      <span class="hljs-attr">CidrBlock:</span> <span class="hljs-string">'10.0.5.0/24'</span>
      <span class="hljs-attr">AvailabilityZone:</span> <span class="hljs-type">!Select</span> [<span class="hljs-number">0</span>, <span class="hljs-type">!GetAZs</span> <span class="hljs-string">''</span>]

  <span class="hljs-attr">PrivateSubnetDBTwo:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::Subnet'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">VpcId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">MyVPC</span>
      <span class="hljs-attr">CidrBlock:</span> <span class="hljs-string">'10.0.6.0/24'</span>
      <span class="hljs-attr">AvailabilityZone:</span> <span class="hljs-type">!Select</span> [<span class="hljs-number">1</span>, <span class="hljs-type">!GetAZs</span> <span class="hljs-string">''</span>]

  <span class="hljs-attr">EIPOne:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::EIP'</span>

  <span class="hljs-attr">EIPTwo:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::EIP'</span>

  <span class="hljs-attr">NATGatewayOne:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::NatGateway'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">AllocationId:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">'EIPOne.AllocationId'</span>
      <span class="hljs-attr">SubnetId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PublicSubnetOne</span>

  <span class="hljs-attr">NATGatewayTwo:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::NatGateway'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">AllocationId:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">'EIPTwo.AllocationId'</span>
      <span class="hljs-attr">SubnetId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PublicSubnetTwo</span>

  <span class="hljs-attr">PublicRouteTable:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::RouteTable'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">VpcId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">MyVPC</span>

  <span class="hljs-attr">PublicRoute:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::Route'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">RouteTableId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PublicRouteTable</span>
      <span class="hljs-attr">DestinationCidrBlock:</span> <span class="hljs-string">'0.0.0.0/0'</span>
      <span class="hljs-attr">GatewayId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">InternetGateway</span>

  <span class="hljs-attr">PublicSubnetOneRouteTableAssociation:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::SubnetRouteTableAssociation'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">SubnetId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PublicSubnetOne</span>
      <span class="hljs-attr">RouteTableId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PublicRouteTable</span>

  <span class="hljs-attr">PublicSubnetTwoRouteTableAssociation:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::SubnetRouteTableAssociation'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">SubnetId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PublicSubnetTwo</span>
      <span class="hljs-attr">RouteTableId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PublicRouteTable</span>

  <span class="hljs-attr">PrivateAppRouteTableOne:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::RouteTable'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">VpcId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">MyVPC</span>

  <span class="hljs-attr">PrivateAppRouteTableTwo:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::RouteTable'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">VpcId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">MyVPC</span>

  <span class="hljs-attr">PrivateAppRouteOne:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::Route'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">RouteTableId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PrivateAppRouteTableOne</span>
      <span class="hljs-attr">DestinationCidrBlock:</span> <span class="hljs-string">'0.0.0.0/0'</span>
      <span class="hljs-attr">NatGatewayId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">NATGatewayOne</span>

  <span class="hljs-attr">PrivateAppRouteTwo:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::Route'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">RouteTableId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PrivateAppRouteTableTwo</span>
      <span class="hljs-attr">DestinationCidrBlock:</span> <span class="hljs-string">'0.0.0.0/0'</span>
      <span class="hljs-attr">NatGatewayId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">NATGatewayTwo</span>

  <span class="hljs-attr">PrivateSubnetAppOneRouteTableAssociation:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::SubnetRouteTableAssociation'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">SubnetId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PrivateSubnetAppOne</span>
      <span class="hljs-attr">RouteTableId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PrivateAppRouteTableOne</span>

  <span class="hljs-attr">PrivateSubnetAppTwoRouteTableAssociation:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::SubnetRouteTableAssociation'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">SubnetId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PrivateSubnetAppTwo</span>
      <span class="hljs-attr">RouteTableId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PrivateAppRouteTableTwo</span>

  <span class="hljs-attr">PrivateDBRouteTable:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::RouteTable'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">VpcId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">MyVPC</span>

  <span class="hljs-attr">PrivateSubnetDBOneRouteTableAssociation:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::SubnetRouteTableAssociation'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">SubnetId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PrivateSubnetDBOne</span>
      <span class="hljs-attr">RouteTableId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PrivateDBRouteTable</span>

  <span class="hljs-attr">PrivateSubnetDBTwoRouteTableAssociation:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">'AWS::EC2::SubnetRouteTableAssociation'</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">SubnetId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PrivateSubnetDBTwo</span>
      <span class="hljs-attr">RouteTableId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">PrivateDBRouteTable</span>
</code></pre>
<p>This YAML script creates the intended VPC, two public subnets, an internet gateway, two elastic IP addresses, and two NAT gateways. Here, you also leverage AWS CloudFormation’s capabilities to link resources and manage dependencies explicitly.</p>
<h3 id="heading-aws-cdk-example">AWS CDK Example</h3>
<p>When using the AWS CDK, you define cloud resources in an imperative programming style. It's offers an abstraction over AWS CloudFormation but offers more flexibility by using constructs, which can encapsulate multiple resources into a single logical unit. It also allows you to use of loops, conditionals, and other programming logic to dynamically generate your resources.</p>
<p>When configuration resources like subnets, it is simplified by grouping them under <strong>subnet_configuration</strong> in a VPC construct. This automatically handles subnet associations for you.</p>
<p>Below, you'll use the Python programming language to define the three-tier solution with AWS CDK:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> constructs <span class="hljs-keyword">import</span> Construct
<span class="hljs-keyword">from</span> aws_cdk <span class="hljs-keyword">import</span> (
    Stack,
    aws_ec2 <span class="hljs-keyword">as</span> ec2
)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyVpcStack</span>(<span class="hljs-params">Stack</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, scope: Construct, id: str, **kwargs</span>):</span>
        super().__init__(scope, id, **kwargs)

        <span class="hljs-comment"># Create a VPC with specific configurations</span>
        vpc = ec2.Vpc(self, <span class="hljs-string">"MyVpc"</span>,
                      ip_addresses=ec2.IpAddresses.cidr(<span class="hljs-string">"10.0.0.0/16"</span>),
                      max_azs=<span class="hljs-number">2</span>,
                      subnet_configuration=[
                          ec2.SubnetConfiguration(
                              name=<span class="hljs-string">"PublicSubnet"</span>,
                              subnet_type=ec2.SubnetType.PUBLIC,
                              cidr_mask=<span class="hljs-number">24</span>
                          ),
                          ec2.SubnetConfiguration(
                              subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS,
                              name=<span class="hljs-string">"PrivateSubnet1"</span>,
                              cidr_mask=<span class="hljs-number">24</span>
                          ),
                          ec2.SubnetConfiguration(
                              subnet_type=ec2.SubnetType.PRIVATE_ISOLATED,
                              name=<span class="hljs-string">"PrivateSubnet2"</span>,
                              cidr_mask=<span class="hljs-number">24</span>
                          )
                      ],
                      nat_gateways=<span class="hljs-number">2</span>,  <span class="hljs-comment"># Number of NAT Gateways</span>
                      )
</code></pre>
<p>As you can see, this AWS CDK Python script is more concise and allows you to work with a very familiar high-level programming language, which provides powerful abstractions and leverages use of constructs.</p>
<h3 id="heading-terraform-example">Terraform Example</h3>
<p>Terraform’s approach involves defining infrastructure using a declarative configuration language. But it differs from AWS CloudFormation in its approach to managing state and dependencies. It also allows more controlled resource creation, updating, and destruction with constructs like <strong>resource</strong>, <strong>provider</strong> and <strong>variable</strong>.</p>
<p>Here’s how you define the same solution with Terraform:</p>
<pre><code class="lang-hcl">provider "aws" {
  region = "us-east-1"
}

resource "aws_vpc" "my_vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
}

# Public Subnets
resource "aws_subnet" "public_subnet_one" {
  vpc_id                  = aws_vpc.my_vpc.id
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "us-east-1a"
}

resource "aws_subnet" "public_subnet_two" {
  vpc_id                  = aws_vpc.my_vpc.id
  cidr_block              = "10.0.2.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "us-east-1b"
}

# Private Subnets for Application Tier
resource "aws_subnet" "private_app_subnet_one" {
  vpc_id            = aws_vpc.my_vpc.id
  cidr_block        = "10.0.3.0/24"
  availability_zone = "us-east-1a"
}

resource "aws_subnet" "private_app_subnet_two" {
  vpc_id            = aws_vpc.my_vpc.id
  cidr_block        = "10.0.4.0/24"
  availability_zone = "us-east-1b"
}

# Private Subnets for Database Tier
resource "aws_subnet" "private_db_subnet_one" {
  vpc_id            = aws_vpc.my_vpc.id
  cidr_block        = "10.0.5.0/24"
  availability_zone = "us-east-1a"
}

resource "aws_subnet" "private_db_subnet_two" {
  vpc_id            = aws_vpc.my_vpc.id
  cidr_block        = "10.0.6.0/24"
  availability_zone = "us-east-1b"
}

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.my_vpc.id
}

resource "aws_nat_gateway" "nat_gateway_one" {
  allocation_id = aws_eip.nat_one.id
  subnet_id     = aws_subnet.public_subnet_one.id
}

resource "aws_nat_gateway" "nat_gateway_two" {
  allocation_id = aws_eip.nat_two.id
  subnet_id     = aws_subnet.public_subnet_two.id
}

resource "aws_eip" "nat_one" {
  domain = "vpc"
}

resource "aws_eip" "nat_two" {
  domain = "vpc"
}

# Public Route Table
resource "aws_route_table" "public_rt" {
  vpc_id = aws_vpc.my_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }
}

# Private Route Tables for Application Tier
resource "aws_route_table" "private_app_rt_one" {
  vpc_id = aws_vpc.my_vpc.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.nat_gateway_one.id
  }
}

resource "aws_route_table" "private_app_rt_two" {
  vpc_id = aws_vpc.my_vpc.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.nat_gateway_two.id
  }
}

# Private Route Tables for Database Tier
resource "aws_route_table" "private_db_rt" {
  vpc_id = aws_vpc.my_vpc.id
}

# Route Table Associations
resource "aws_route_table_association" "public_subnet_one_association" {
  subnet_id      = aws_subnet.public_subnet_one.id
  route_table_id = aws_route_table.public_rt.id
}

resource "aws_route_table_association" "public_subnet_two_association" {
  subnet_id      = aws_subnet.public_subnet_two.id
  route_table_id = aws_route_table.public_rt.id
}

resource "aws_route_table_association" "private_app_subnet_one_association" {
  subnet_id      = aws_subnet.private_app_subnet_one.id
  route_table_id = aws_route_table.private_app_rt_one.id
}

resource "aws_route_table_association" "private_app_subnet_two_association" {
  subnet_id      = aws_subnet.private_app_subnet_two.id
  route_table_id = aws_route_table.private_app_rt_two.id
}

resource "aws_route_table_association" "private_db_subnet_one_association" {
  subnet_id      = aws_subnet.private_db_subnet_one.id
  route_table_id = aws_route_table.private_db_rt.id
}

resource "aws_route_table_association" "private_db_subnet_two_association" {
  subnet_id      = aws_subnet.private_db_subnet_two.id
  route_table_id = aws_route_table.private_db_rt.id
}
</code></pre>
<p>This script shows how Terraform allows for a modular approach to infrastructure as code, with explicit definitions and dependency management with syntax that is relatively easy to read and write.</p>
<h2 id="heading-analysis-and-comparison">Analysis and Comparison</h2>
<p>When choosing between AWS CloudFormation, AWS CDK, and Terraform for managing cloud infrastructure, you have consider a number of factors. But in this article, will specifically focus on the <strong>ease of use</strong>, <strong>flexibility</strong>, <strong>scalability</strong>, <strong>language support</strong>, and the <strong>ability to handle complex environments</strong>. </p>
<h3 id="heading-ease-of-use-and-learning-curve">Ease of Use and Learning Curve</h3>
<p>AWS CloudFormation offers a JSON or YAML-based template format. This is straightforward for defining the infrastructure, but can become complex as infrastructure grows. It requires an understanding of specific syntax and AWS resource definitions, which might have a steeper learning curve for those not familiar with JSON or YAML.</p>
<p>AWS CDK uses familiar programming languages like Python, JavaScript, TypeScript and Java. This can make it more accessible for developers already familiar with these languages. </p>
<p>Also, since AWS CDK allows for defining infrastructure through code, it provides more intuitive logic, conditions, and loops, and it abstracts much of the boilerplate needed in AWS CloudFormation. This simplifies the development process.</p>
<p>Terraform uses its own domain-specific language, HashiCorp Configuration Language (HCL), which is designed to be easily readable and writable by humans. While it can be easy to learn, you'll need to be familiar with another new language. However, its declarative nature allows clear definitions of <strong>what</strong> the infrastructure should look like without the need of specifying <strong>how</strong> to achieve it.</p>
<h3 id="heading-flexibility-and-cloud-provider-support">Flexibility and Cloud Provider Support</h3>
<p>AWS CloudFormation is tightly integrated with AWS and is updated in tandem with AWS services. But it’s inherently limited to AWS, making it less suitable for the possibilities for hybrid or multi-cloud environments.</p>
<p>AWS CDK also primarily targets AWS services but supports the use of AWS CloudFormation custom resources to manage resources outside of AWS. Still, it doesn’t naturally lend itself to managing multi-cloud resources as directly as Terraform.</p>
<p>Terraform is designed to be cloud-agnostic, supporting multiple providers including AWS, Microsoft Azure, Google Cloud Platform and others. This makes it an ideal choice for complex deployments spanning more than one cloud provider.</p>
<h3 id="heading-scalability-and-maintainability">Scalability and Maintainability</h3>
<p>AWS CloudFormation templates can become unwieldy and difficult to manage as projects scale. But AWS provides nested stacks as a solution to manage large infrastructures but even with this capability, managing many stacks can become cumbersome track.</p>
<p>AWS CDK provides high-level abstractions and modular constructs, making it easier to manage and scale large infrastructures by breaking them down into smaller, reusable components.</p>
<p>Terraform excels in managing large-scale infrastructures due to its modular approach. By using Terraform modules, you can reuse configurations and ensure consistency across deployments.</p>
<h3 id="heading-community-support-and-ecosystem">Community Support and Ecosystem</h3>
<p>AWS CloudFormation has great adoption and support from AWS with a large user base, but its community contributions are limited to sharing templates.</p>
<p>AWS CDK is open-source and has a growing community, especially among developers preferring to use general-purpose programming languages for infrastructure management. The ecosystem includes a rich set of high-level constructs developed by both AWS and the community.</p>
<p>Terraform benefits from strong community engagement and a vast ecosystem of providers and modules shared publicly in the Terraform Registry. Its wide adoption across different platforms also helps foster a large and active community.</p>
<h3 id="heading-code-length-and-complexity">Code Length and Complexity</h3>
<p>AWS CloudFormation scripts tend to be verbose, requiring detailed specifications of every property. This can lead to lengthy and complex templates for larger infrastructures.</p>
<p>AWS CDK scripts are typically shorter and less complex due to the use of programming constructs that abstract away much of the detailed specifications required in AWS CloudFormation.</p>
<p>Terraform configurations strike a balance, being more concise than AWS CloudFormation but typically more verbose than AWS CDK due to its declarative nature, which requires explicit resource and configuration definitions.</p>
<h2 id="heading-why-choose-one-over-the-other">Why Choose One Over the Other?</h2>
<p>When choosing between AWS CloudFormation, AWS CDK, and Terraform, consider each tools' unique features, operational principles, and your own personal preferences.</p>
<p>Now I'll share recommendations based on this article's information to help you figure out when it’s best to use each of these IaC tools.</p>
<ul>
<li>AWS CloudFormation is suitable for when you are looking for stable, native AWS tooling and don’t necessarily need to manage resources outside of AWS. It’s particularly great when compliance with specific AWS configurations is required.</li>
<li>Choose AWS CDK when you prefer using standard programming languages and enjoy the benefits of object-oriented techniques to create reusable and modular cloud components. It is usually more appealing to developers who want to apply software development best practices to infrastructure provisioning.</li>
<li>Terraform is the ultimate leader for multi-cloud environments, or if you need a tool that is both powerful and flexible enough to manage complex architectures. It is also the right choice if you anticipate integrating a variety of cloud services and need a unified approach to manage them.</li>
</ul>
<p>Though these recommendations are based on the special makeup of each of these IaC tools, I advise you to gain some experience with each tool, so you can decide on the one that best aligns with the specific skills and needs of your team and projects.</p>
<p>If you’ve got this far, <strong>thanks so much for reading!</strong> I hope it was worthwhile to you.</p>
<p>If you want to learn more about me and my story of transitioning from a Pro Athlete to a Cloud Engineer, connect with me <strong><a target="_blank" href="https://www.linkedin.com/in/ifeanyi-otuonye/">here on LinkedIn</a></strong>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Terraform Security Best Practices ]]>
                </title>
                <description>
                    <![CDATA[ By Aaron Katz Terraform is a popular Infrastructure as Code (IaC) tool that allows users to define and manage cloud infrastructure in a declarative way.  However, like any tool, Terraform can introduce security risks if not used properly. In this art... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/terraform-security-best-practices/</link>
                <guid isPermaLink="false">66d45d6351f567b42d9f8427</guid>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 28 Sep 2023 05:12:03 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/09/1-_rE5zXXVTZ_R4u-2FZh0iw.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Aaron Katz</p>
<p>Terraform is a popular Infrastructure as Code (IaC) tool that allows users to define and manage cloud infrastructure in a declarative way.  However, like any tool, Terraform can introduce security risks if not used properly.</p>
<p>In this article, we will explore the most common security risks when using Terraform, the threat landscape and attack surface, how it can be exploited, and how users can stay secure by following Terraform security best practices.</p>
<h2 id="heading-what-is-terraform-and-why-should-i-use-it">What is Terraform and why Should I Use it?</h2>
<p>Terraform is an open-source tool developed by HashiCorp that enables users to define and provision infrastructure resources across multiple cloud providers and on-premises environments. It allows organizations to treat infrastructure as code, bringing the benefits of version control, collaboration, and automation to infrastructure management.</p>
<p>By adopting Terraform and embracing the principles of Infrastructure as Code, organizations can achieve several benefits:</p>
<ul>
<li><strong>Consistency and repeatability</strong>: Infrastructure deployments become consistent and repeatable, eliminating the risk of manual errors and ensuring that the same configuration can be applied across different environments.</li>
<li><strong>Version control and collaboration</strong>: Infrastructure code can be stored in version control systems, enabling teams to collaborate, track changes, and roll back to previous versions if needed.</li>
<li><strong>Automation and scalability</strong>: Infrastructure deployments can be automated, allowing organizations to scale their infrastructure quickly and efficiently based on demand.</li>
<li><strong>Auditing and compliance</strong>: Infrastructure code can be audited and reviewed for compliance with security and regulatory standards, ensuring that best practices are followed.</li>
</ul>
<p>While Terraform offers a powerful and flexible solution for managing infrastructure, it is essential to address the security considerations associated with using the tool effectively.</p>
<h2 id="heading-security-considerations">Security considerations</h2>
<p>As with any tool or technology, Terraform is not immune to security threats and challenges. It is crucial to understand the threat landscape and the potential risks associated with using Terraform. </p>
<p>By identifying these risks, organizations can implement appropriate security measures to mitigate them effectively.</p>
<h3 id="heading-configuration-errors">Configuration errors</h3>
<p>One of the primary security risks associated with Terraform is misconfigurations in the infrastructure code. </p>
<p>Misconfigurations can lead to vulnerabilities and expose critical resources to unauthorized access or compromise. Common misconfigurations include weak access controls, open network ports, and incorrect permission settings on cloud resources.</p>
<h3 id="heading-secrets-management">Secrets management</h3>
<p>Terraform relies on access keys and secret keys to authenticate with cloud providers and provision resources. </p>
<p>Storing these credentials insecurely can lead to security vulnerabilities such as unauthorized access and data breaches. </p>
<p>Handling access credentials securely is crucial to the overall security posture of Terraform deployments.</p>
<h3 id="heading-state-security">State security</h3>
<p>Terraform uses state files to keep track of the resources it has created or modified.</p>
<p>These state files contain sensitive information, such as resource IDs, metadata, and secrets as referenced above. Inadequate management of state files can lead to security risks, including unauthorized access and data exposure. </p>
<p>State files can be stored either locally on a machine (suitable only for solo testing) or on a remote backend, such as a cloud storage resource, which should be encrypted and locked down.</p>
<h3 id="heading-supply-chain-security">Supply chain security</h3>
<p>As with any software development process, the supply chain of Terraform modules and associated dependencies can introduce security risks.</p>
<p>Organizations must assess the trustworthiness and security of the modules they use and ensure they are regularly updated to address any vulnerabilities.</p>
<h3 id="heading-permissions-management">Permissions management</h3>
<p>Appropriate permissions and access controls are essential to prevent unauthorized changes to infrastructure resources. </p>
<p>Managing permissions effectively can help reduce the risk of accidental or malicious actions that could compromise the security of the infrastructure.</p>
<h2 id="heading-recommendations">Recommendations</h2>
<p>To stay secure while using Terraform, users should follow these best practices:</p>
<ol>
<li>Execute Terraform programmatically to minimize human error and enforce security policies.</li>
<li>Use safe Terraform modules and avoid using untrusted or vulnerable third-party components.</li>
<li>Secure the data store when remotely storing state data to prevent unauthorized access (for example, through encryption and restrictive access permissions).</li>
<li>Avoid storing secrets in state files; instead, use secret management solutions like AWS Secrets Manager, Azure Key Vault, or Google Cloud Secret Manager.</li>
<li>Use Terraform security scanners to identify and remediate potential vulnerabilities.</li>
<li>Implement centralized security policy and governance within Terraform code to improve visibility and enforce least privilege.</li>
<li>Require multi-factor authentication for collaborators to improve security posture.</li>
<li>Keep Terraform and all modules up to date.</li>
<li>Regularly audit terraform configurations for security vulnerabilities and misconfigurations, and build in appropriate automated tooling to detect violations prior to deployment.</li>
<li>Conduct regular drift detection to determine if resources deployed in your cloud provider match what should have been deployed in the terraform state file.</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Terraform is a powerful tool for managing cloud infrastructure, but it can introduce security risks if not used properly. </p>
<p>By following Terraform security best practices, users can minimize the risk of security incidents and maintain a secure Terraform environment. </p>
<p>It's essential to keep Terraform configurations and infrastructure up-to-date, monitor for security threats, and adapt to the evolving threat landscape. By doing so, users can ensure that their cloud infrastructure is secure and compliant with industry standards.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Infrastructure as Code? Explained for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ Infrastructure as Code (IaC) is a way of managing your infrastructure like it was code. This gives you all the benefits of using code to create your infrastructure, like version control, faster and safer infrastructure deployments across different en... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/infrastructure-as-code-basics/</link>
                <guid isPermaLink="false">66d45e193dce891ac3a967e4</guid>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Daniel Adetunji ]]>
                </dc:creator>
                <pubDate>Thu, 15 Jun 2023 14:32:46 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/06/cover-5.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Infrastructure as Code (IaC) is a way of managing your infrastructure like it was code. This gives you all the benefits of using code to create your infrastructure, like version control, faster and safer infrastructure deployments across different environments, and having up to date documentation of your infrastructure.</p>
<p>The article will cover how infrastructure as code works using an analogy. We'll cover the different infrastructure as code tools available as well as declarative vs imperative code</p>
<p>I'll also introduce you to Terraform, which is an open source infrastructure as code tool you can use to create infrastructure across multiple cloud providers like AWS, GCP, Azure and others.</p>
<h2 id="heading-infrastructure-as-code-in-practice">Infrastructure as Code in Practice</h2>
<p>Imagine you are trying to create a three-tiered web application on AWS as you can see in the image below:</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f739c67-e79c-4995-b58d-71d695cccd47_1018x1682.png" alt="Image" width="1018" height="1682" loading="lazy"></p>
<p><em>Three tiered web application example</em></p>
<p>The presentation tier is responsible for presenting the user interface to the user. It includes the user interface components such as HTML, CSS, and JavaScript running on EC2 instances.</p>
<p>The logic tier is responsible for processing user requests and generating responses, by communicating with the database layer to retrieve or store data. This is also deployed on EC2 instances</p>
<p>The database tier is responsible for storing and managing the application's data and allows access to its data through the logic tier. The database runs on AWS RDS.</p>
<p>Each of the instances are in an <a target="_blank" href="https://lightcloud.substack.com/i/102200211/auto-scaling-explained">autoscaling</a> group with a <a target="_blank" href="https://lightcloud.substack.com/i/102200211/load-balancing-explained">load balancer</a> in front of it (except for the database tier).</p>
<p>If you want to create this infrastructure through the AWS console, you would have to manually click through various screens to spin up the infrastructure. This is fine if it is a one time activity.</p>
<p>But if you need to repeat this across different environments like development and test, or need to add additional infrastructure like caches, queues, firewall rules, <a target="_blank" href="https://lightcloud.substack.com/p/aws-iam-identity-and-access-management">IAM</a> or SSL certificates, then it becomes increasingly more complex to manage through the AWS console.</p>
<p>Managing complex infrastructure through the console also introduces the possibility of human error.</p>
<p>Infrastructure as code expresses your desired infrastructure in the language of code. This brings all the benefits of code to managing your infrastructure like:</p>
<ol>
<li><p>Version Control – allows you to store the history of your infrastructure and revert to a previous version if needed.</p>
</li>
<li><p>Faster &amp; safer deployments – can recreate infrastructure in new environments quickly and with less errors since every part of the infrastructure is clearly defined in the code.</p>
</li>
<li><p>Documentation – your current infrastructure state is documented and kept up to date automatically whenever you make a change. This keeps your infrastructure documentation detailed and accurate, compared to having the infrastructure written in a document or on a confluence page that may not be updated whenever there is a change.</p>
</li>
</ol>
<h2 id="heading-how-infrastructure-as-code-works-explained-with-an-analogy">How Infrastructure as Code Works – Explained with an Analogy</h2>
<p>Infrastructure as code allows you to create a detailed blueprint of your infrastructure. This blueprint gives instructions to your cloud provider about the infrastructure you want created.</p>
<p>This is similar to how an architecture blueprint works. It outlines the layout, dimensions, materials, and various components of the structure. The blueprint serves as a reference for architects and engineers to understand the desired construction.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d1aacbf-34c7-43ae-8384-2f912072cc00_2728x1514.png" alt="Image" width="1456" height="808" loading="lazy"></p>
<p><em>how an architectural blueprint is analogous to infrastructure as code</em></p>
<p>The blueprint leaves little room for error. It will be interpreted in the same way by any architect or engineer. If you wanted to build exact copies of this house, all you need is the architecture blueprint.</p>
<p>Infrastructure as code, at a basic level, works in the same way as an architecture blueprint. It details the infrastructure you want to create as code in a number of different possible languages (JSON, YAML, HCL, Python, Ruby, JavaScript, and so on), instructing the cloud provider to create your infrastructure exactly as specified.</p>
<h2 id="heading-declarative-amp-imperative-infrastructure-as-code-tools">Declarative &amp; Imperative Infrastructure as Code Tools</h2>
<p>There are many IaC options to choose from, and all the major cloud providers have their own dedicated tools:</p>
<ul>
<li><p>AWS has CloudFormation</p>
</li>
<li><p>GCP has Deployment Manager</p>
</li>
<li><p>Azure has Resource manager</p>
</li>
</ul>
<p>One limitation of these cloud provider-specific tools is that they can only create infrastructure in their respective clouds. So CloudFormation only works in AWS and Deployment Manager only works in GCP. IaC using these providers is usually written in JSON or YAML format.</p>
<p>Terraform, on the other hand, is open source and you can use it to create infrastructure across all the major cloud providers. It uses HCL (HashiCorp Configuration Language).</p>
<p>Infrastructure as code can also be written using popular languages like Python and JavaScript.</p>
<p>These scripting/programming languages lie on a spectrum of declarative and imperative code as shown below.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaba03dc-13eb-4f45-873f-6f00dd648ffb_2508x870.png" alt="Image" width="1456" height="505" loading="lazy"></p>
<p><em>A spectrum of declarative &amp; imperative languages and where Terraform HCL fits</em></p>
<p>The main difference between an imperative and declarative language is that imperative languages explicitly define the <em>control flow</em>. This is simply the order in which instructions are executed in a program. Control flow determines the path the program takes and how it responds to different conditions or events.</p>
<p>In imperative languages, control flow is explicitly defined using control structures such as loops, conditionals, and function calls. Imperative languages give you more flexibility in configuring your infrastructure. This is not necessarily a positive, as more flexibility means more opportunity to introduce errors into your infrastructure.</p>
<p>A declarative language focuses on describing the desired result without giving specific instructions on how to achieve it.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fe4d2ce-6449-4597-beca-5a46f2aa6ee8_2620x1468.png" alt="Image" width="1456" height="816" loading="lazy"></p>
<p><em>An illustration demonstrating the difference between declarative and imperative languages</em></p>
<p>An example JSON is shown below, used in AWS CloudFormation to create an EC2 instance:</p>
<pre><code class="lang-python"><span class="hljs-string">"Type"</span>: <span class="hljs-string">"AWS::EC2::Instance"</span>,
      <span class="hljs-string">"Properties"</span>: {
        <span class="hljs-string">"ImageId"</span>: <span class="hljs-string">"ami-0123456789"</span>,
        <span class="hljs-string">"InstanceType"</span>: <span class="hljs-string">"t2.micro"</span>,
        <span class="hljs-string">"KeyName"</span>: <span class="hljs-string">"my-key-pair"</span>,
        <span class="hljs-string">"SecurityGroupIds"</span>: [<span class="hljs-string">"sg-0123456789"</span>],
        <span class="hljs-string">"SubnetId"</span>: <span class="hljs-string">"subnet-0123456789"</span>,
        <span class="hljs-string">"Tags"</span>: [
          {
            <span class="hljs-string">"Key"</span>: <span class="hljs-string">"Name"</span>,
            <span class="hljs-string">"Value"</span>: <span class="hljs-string">"MyEC2Instance"</span>
          }
        ]
      }
</code></pre>
<p>A declarative language like JSON <a target="_blank" href="https://lightcloud.substack.com/p/cloud-computing-abstractions-explained">abstracts</a> away the underlying complexity that details how the EC2 instance will be created. All it cares about is the end state.</p>
<p>Terraform HCL is closer to the declarative end of the spectrum. Terraform allows you to describe the desired infrastructure's final state without specifying the exact steps to get there. Terraform internally manages the execution order, resource dependencies, and handles the infrastructure changes based on the desired configuration.</p>
<p>But Terraform does have support for some imperative features like variables and expressions, allowing dynamic behaviour based on inputs. So, it is not a completely declarative language like JSON.</p>
<h2 id="heading-how-terraform-works">How Terraform Works</h2>
<p>There are two fundamental concepts that serve as a foundation for understanding Terraform:</p>
<ol>
<li><p>The configuration file – this describes the desired infrastructure</p>
</li>
<li><p>The state file – this describes the current infrastructure as it exists in the real world</p>
</li>
</ol>
<p>Terraform’s job is to create, modify or delete infrastructure as needed so that the desired infrastructure configuration is met. It does this by executing the necessary API calls to your cloud provider(s) to create, modify, or destroy the resources as specified.</p>
<p>Once the infrastructure has been created/modified/destroyed to match the configuration file, the state file is updated to reflect the current infrastructure.</p>
<p>The <code>terraform plan</code> command creates an <a target="_blank" href="https://developer.hashicorp.com/terraform/cli/commands/plan">execution plan</a>, which lets you preview the changes that Terraform plans to make to your infrastructure.</p>
<p>By default, when Terraform creates a plan, it compares the desired configuration as described in the configuration file, with the current configuration as described in the state file. Terraform then proposes a list of changes needed what will ensure that the current configuration matches the desired configuration.</p>
<p>If you then run the <code>terraform apply</code> command, terraform will modify the real world infrastructure so that it matches the desired configuration, and updates the state file to show the new infrastructure configuration.</p>
<p>At a high level, this is what terraform does:</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba9f88a0-e8bb-4507-9fd9-a9576bb8fefe_2650x1380.png" alt="Image" width="1456" height="758" loading="lazy"></p>
<p><em>What happens when you run the</em> <code>terraform apply</code> command</p>
<p>Let’s bring back the architectural blueprint analogy.</p>
<p>The configuration file is like the architectural blueprint. It details the infrastructure that needs to be built, that is the desired construction. The real world infrastructure is the existing construction in the physical world and the state file is a representation of what currently exists – the current blueprint. The engineers work to ensure that the existing construction matches the architecture blueprint.</p>
<p>In this analogy, engineers do the work of Terraform in ensuring that the existing construction matches the architecture blueprint. You don’t need to specify the details of how to build the house, you just need to specify what you want built and the engineers handle the rest.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F973320df-191b-46b6-be0a-18cd43305288_2620x1468.png" alt="Image" width="1456" height="816" loading="lazy"></p>
<p><em>An architectural analogy to running</em> <code>terraform apply</code></p>
<p>If you want to learn more about how Terraform works and how you can use it in your projects, you can <a target="_blank" href="https://www.freecodecamp.org/news/learn-terraform-and-aws-by-building-a-dev-environment/">check out this free course</a> on freeCodeCamp's YouTube channel.</p>
<h2 id="heading-bringing-it-together">Bringing it Together</h2>
<p>Infrastructure as code (IaC) is a great way of managing complex infrastructure configuration in the form of code. This naturally brings all the advantages of code to your infrastructure like version control, faster and safer infrastructure deployments across different environments and up to date documentation of your infrastructure.</p>
<p>Terraform is an open source IaC tool that allows you to work with multiple cloud providers to spin up infrastructure as defined in your configuration files.</p>
<p>Terraform HCL is a declarative language that allows you to describe your desired infrastructure configuration. All you have to do is specify what you want created and terraform handles the creation on your behalf by making API calls to your chosen cloud provider(s).</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Deploy a NodeJS App with AWS CloudFormation ]]>
                </title>
                <description>
                    <![CDATA[ Imagine you have built a great product and its user base is growing rapidly. You want to scale your product to be available to people around the world. To do this, you'll need good cloud infrastructure. But managing your cloud infrastructure manually... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/deploy-nodejs-app-with-cloudformation/</link>
                <guid isPermaLink="false">66ba10a3052fa53219e0a372</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Arunachalam B ]]>
                </dc:creator>
                <pubDate>Tue, 18 Apr 2023 18:03:26 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/04/CloudFormation.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Imagine you have built a great product and its user base is growing rapidly. You want to scale your product to be available to people around the world. To do this, you'll need good cloud infrastructure.</p>
<p>But managing your cloud infrastructure manually can be exhausting. You might start wondering, "how are the enterprise companies doing it?"</p>
<p>Well, they automate their cloud infrastructure management via code. And that's what AWS CloudFormation offers – a way to manage your cloud infrastructure as code.</p>
<p>In this tutorial, we'll explore the basics of AWS CloudFormation and how it can help you automate your cloud infrastructure management. Let's dive into the world of infrastructure as code.</p>
<h2 id="heading-what-is-cloudformation">What is CloudFormation?</h2>
<p>AWS CloudFormation is a service that helps you automate creating and managing your cloud resources. </p>
<p>Imagine you're building a house, and you want to ensure everything is in the right place – the walls, the roof, the doors, and the windows. Before you build, you would create a blueprint for your house, and specify exactly what you want and where you want it.</p>
<p>Similarly, CloudFormation allows you to create a blueprint for your cloud infrastructure. You can specify what resources you want to create (for example EC2 servers, databases, storage, and so on) and how they should be configured. CloudFormation takes care of creating and managing those resources for you automatically.</p>
<p>CloudFormation can be helpful in many cases. I'll list a few of them here:</p>
<ol>
<li>Managing the infrastructure changes in multiple environments (Development, Staging, Production)</li>
<li>Re-creating the same infrastructure in a different region / account</li>
<li>Re-creating a resource that's been accidentally deleted with the exact configuration in seconds (not manually, as you have all configurations in your code)</li>
</ol>
<p>The best part is that CloudFormation makes updating your infrastructure super simple and automatic. If you want to add a new resource, change a configuration, or delete a resource, you can update your blueprint, and CloudFormation will handle the changes for you.</p>
<h2 id="heading-how-cloudformation-works">How CloudFormation Works</h2>
<p>You may wonder how CloudFormation works. It's simple: we'll upload our CloudFormation templates in Amazon S3 (behind the scenes) which will be pulled by CloudFormation. </p>
<p>One important point to note is we cannot edit the template once uploaded. We'd need to re-upload the updated template to AWS which CloudFormation compares with the existing infrastructure and makes the necessary changes. </p>
<p>One awesome feature is that you can delete all resources created by CloudFormation in one click by deleting the stack. The CloudFormation stack is nothing but a collection of AWS resources that you can manage as a single unit. </p>
<p>You define a stack by creating a CloudFormation template that describes the resources you want to create, and run the template to create the stack. </p>
<h2 id="heading-how-to-deploy-cloudformation-templates">How to Deploy CloudFormation Templates</h2>
<p>We can deploy the CloudFormation template in two ways. One is using CloudFormation Designer and the other is writing the code in a YAML/JSON file. </p>
<p>If you're not familiar with YAML/JSON then you can go for CloudFormation Designer. This is what I'd recommended for those who don't know how to code. It allows you to create and edit templates graphically, making it easier to visualize your infrastructure and simplify the template creation process. But in this tutorial we'll be writing YAML code to deploy our app.</p>
<h2 id="heading-how-to-create-a-cloudformation-template-to-deploy-a-nodejs-app">How to Create a CloudFormation Template to Deploy a NodeJS App</h2>
<p>You can create a CloudFormation Template using either a YAML or JSON file – but we're going to use a YAML file in the tutorial. </p>
<p>In this template, we'll be creating an EC2 instance, we'll configure a Security Group for EC2, and add a script to deploy a simple NodeJS app. </p>
<h3 id="heading-cloudformation-template-to-create-an-ec2-instance">CloudFormation Template to Create an EC2 Instance</h3>
<p>There are over 224 types of resources, but we need to create an EC2 resource. <strong>Resources</strong> represent the different AWS Components that will be created and configured. We'll define the <strong>Resource</strong> type identifiers in the below format:</p>
<pre><code>AWS::aws-product-name::data-type-name
</code></pre><p>The resource format for the EC2 instance is <code>Aws::EC2::Instance</code>. To learn more about AWS resources and syntax, checkout the AWS official <a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html">documentation</a> and play with it. Look at the <a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html">EC2 documentation</a> to understand the declaration of EC2 instance. Both JSON and YAML syntax is available but we'll stick with YAML for this tutorial. </p>
<p>There are a lot of properties available to customize the creation of our EC2 instances. To make things simple, we'll be configuring AvailabilityZone, ImageId, and InstanceType which are basic properties needed to create an EC2 instance. </p>
<pre><code>Resources:
  SampleNodejsDeploy:
    Type: AWS::EC2::Instance
    <span class="hljs-attr">Properties</span>:
      AvailabilityZone: us-east<span class="hljs-number">-1</span>a
      <span class="hljs-attr">ImageId</span>: ami-a4c7edb2
      <span class="hljs-attr">InstanceType</span>: t2.micro
</code></pre><p>Here <code>SampleNodejsDeploy</code> refers to the name of the resource we'll be creating. You can name your resource as your wish. </p>
<p>Let's see the process to deploy the NodeJS app. </p>
<h3 id="heading-how-to-deploy-a-nodejs-app-using-a-cloudformation-template">How to Deploy a NodeJS App using a CloudFormation Template</h3>
<p>We're going to deploy the NodeJS app using the <code>UserData</code> property in the EC2 resource. </p>
<p>If you don't know about EC2 user data, it is a feature of AWS EC2 which allows us to pass information during the launch of the EC2 instance. You can use it to perform custom actions, such as installing software and executing the script. </p>
<p>Let's write the bash script to deploy the NodeJS app and attach it to the user data.</p>
<p>Here is the simple script to deploy the NodeJS app:</p>
<pre><code>#!<span class="hljs-regexp">/bin/</span>bash 
set -e
curl -sL https:<span class="hljs-comment">//deb.nodesource.com/setup_16.x | bash -</span>
sudo apt install nodejs
node -v
npm -v
curl -sS https:<span class="hljs-comment">//dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -</span>
echo <span class="hljs-string">"deb https://dl.yarnpkg.com/debian/ stable main"</span> | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update &amp;&amp; sudo apt install yarn
yarn --version
sudo -i -u ubuntu bash &lt;&lt; EOF
set -e
cd /home/ubuntu
sudo npm install -g pm2
git clone https:<span class="hljs-comment">//github.com/5minslearn/node_with_docker.git</span>
cd node_with_docker
yarn install 
pm2 start yarn --time --interpreter bash --name sample_node -- start -p <span class="hljs-number">8000</span>
EOF
</code></pre><p>The above script installs NodeJS, Yarn, and PM2. It clones a NodeJS project from <a target="_blank" href="https://github.com/5minslearn/node_with_docker.git">Git</a>, installs the dependencies, and starts the app with PM2. </p>
<p>Our next step is to attach this script to the CloudFormation template. </p>
<h3 id="heading-how-to-attach-userdata-to-the-cloudformation-template">How to Attach UserData to the CloudFormation Template</h3>
<pre><code>Resources:
  SampleNodejsDeploy:
    Type: AWS::EC2::Instance
    <span class="hljs-attr">Properties</span>:
      InstanceType: t2.micro
      <span class="hljs-attr">ImageId</span>: ami<span class="hljs-number">-014</span>d05e6b24240371
      <span class="hljs-attr">UserData</span>: 
        Fn::Base64:
          |
          #!<span class="hljs-regexp">/bin/</span>bash 
          set -e
          curl -sL https:<span class="hljs-comment">//deb.nodesource.com/setup_16.x | bash -</span>
          sudo apt install nodejs
          node -v
          npm -v
          curl -sS https:<span class="hljs-comment">//dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -</span>
          echo <span class="hljs-string">"deb https://dl.yarnpkg.com/debian/ stable main"</span> | sudo tee /etc/apt/sources.list.d/yarn.list
          sudo apt update &amp;&amp; sudo apt install yarn
          yarn --version
          sudo -i -u ubuntu bash &lt;&lt; EOF
          set -e
          cd /home/ubuntu
          sudo npm install -g pm2
          git clone https:<span class="hljs-comment">//github.com/5minslearn/node_with_docker.git</span>
          cd node_with_docker
          yarn install 
          pm2 start yarn --time --interpreter bash --name sample_node -- start -p <span class="hljs-number">8000</span>
          EOF
</code></pre><p>You'll notice that the <code>UserData</code> property is added to the EC2 block. <code>Fn::Base64</code> is a function in AWS CloudFormation that allows users to encode a string to base64 format. This function can be used to pass sensitive information, such as credentials, to AWS resources in a secure manner. Since EC2 user data is not encrypted it's always best practice to encode it. </p>
<p>Right below that line, you can see a small vertical bar (<code>|</code>). It is used for multi-line string support as our script is more than 1 line.</p>
<p>Alright. Now we have a script to deploy the NodeJS app. But, we have to remember one super important item. By default NodeJS applications run on port 8000. We should expose port 8000 from EC2. Now we need to create a security group configuration for our EC2 instance. </p>
<h3 id="heading-how-to-create-a-security-group-using-a-cloudformation-template">How to Create a Security Group using a CloudFormation Template</h3>
<p>This process is similar to creating an EC2 instance, except we'll replace the type from <code>Instance</code> to <code>SecurityGroup</code>.</p>
<pre><code>SampleNodejsDeploySG:
    Type: AWS::EC2::SecurityGroup
    <span class="hljs-attr">Properties</span>:
      GroupDescription: <span class="hljs-keyword">for</span> the app nodes that allow ssh, http, <span class="hljs-number">8000</span>
      <span class="hljs-attr">SecurityGroupIngress</span>:
      - IpProtocol: tcp
        <span class="hljs-attr">FromPort</span>: <span class="hljs-string">'80'</span>
        <span class="hljs-attr">ToPort</span>: <span class="hljs-string">'80'</span>
        <span class="hljs-attr">CidrIp</span>: <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>/<span class="hljs-number">0</span>
      - IpProtocol: tcp
        <span class="hljs-attr">FromPort</span>: <span class="hljs-string">'22'</span>
        <span class="hljs-attr">ToPort</span>: <span class="hljs-string">'22'</span>
        <span class="hljs-attr">CidrIp</span>: <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>/<span class="hljs-number">0</span>
      - IpProtocol: tcp
        <span class="hljs-attr">FromPort</span>: <span class="hljs-string">'8000'</span>
        <span class="hljs-attr">ToPort</span>: <span class="hljs-string">'8000'</span>
        <span class="hljs-attr">CidrIp</span>: <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>/<span class="hljs-number">0</span>
</code></pre><p>The above code should be pretty self explanatory – we defined a Security group, allowing ports 22 (SSH port), 80 (HTTP port), and 8000 (NodeJS). We named the Resource as <code>SampleNodejsDeploySG</code>. </p>
<h3 id="heading-how-to-attach-the-security-group-to-ec2">How to Attach the Security Group to EC2</h3>
<p>You may be wondering – "We've created a template for creating a Security group but how will this be linked to the EC2 instance?"</p>
<p>The solution is simple. CloudFormation provides an intrinsic function called <code>!Ref</code> that allows us to reference a resource or parameter within a CloudFormation template.</p>
<pre><code>Resources:
  SampleNodejsDeploy:
    Type: AWS::EC2::Instance
    <span class="hljs-attr">Properties</span>:
      InstanceType: t2.micro
      <span class="hljs-attr">ImageId</span>: ami<span class="hljs-number">-014</span>d05e6b24240371
      <span class="hljs-attr">SecurityGroups</span>:
        - !Ref SampleNodejsDeploySG
      <span class="hljs-attr">UserData</span>: 
        Fn::Base64:
          |
          #!<span class="hljs-regexp">/bin/</span>bash 
          set -e
          curl -sL https:<span class="hljs-comment">//deb.nodesource.com/setup_16.x | bash -</span>
          sudo apt install nodejs
          node -v
          npm -v
          curl -sS https:<span class="hljs-comment">//dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -</span>
          echo <span class="hljs-string">"deb https://dl.yarnpkg.com/debian/ stable main"</span> | sudo tee /etc/apt/sources.list.d/yarn.list
          sudo apt update &amp;&amp; sudo apt install yarn
          yarn --version
          sudo -i -u ubuntu bash &lt;&lt; EOF
          set -e
          cd /home/ubuntu
          sudo npm install -g pm2
          git clone https:<span class="hljs-comment">//github.com/5minslearn/node_with_docker.git</span>
          cd node_with_docker
          yarn install 
          pm2 start yarn --time --interpreter bash --name sample_node -- start -p <span class="hljs-number">8000</span>
          EOF

  <span class="hljs-attr">SampleNodejsDeploySG</span>:
    Type: AWS::EC2::SecurityGroup
    <span class="hljs-attr">Properties</span>:
      GroupDescription: <span class="hljs-keyword">for</span> the app nodes that allow ssh, http 
      <span class="hljs-attr">SecurityGroupIngress</span>:
      - IpProtocol: tcp
        <span class="hljs-attr">FromPort</span>: <span class="hljs-string">'80'</span>
        <span class="hljs-attr">ToPort</span>: <span class="hljs-string">'80'</span>
        <span class="hljs-attr">CidrIp</span>: <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>/<span class="hljs-number">0</span>
      - IpProtocol: tcp
        <span class="hljs-attr">FromPort</span>: <span class="hljs-string">'22'</span>
        <span class="hljs-attr">ToPort</span>: <span class="hljs-string">'22'</span>
        <span class="hljs-attr">CidrIp</span>: <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>/<span class="hljs-number">0</span>
      - IpProtocol: tcp
        <span class="hljs-attr">FromPort</span>: <span class="hljs-string">'8000'</span>
        <span class="hljs-attr">ToPort</span>: <span class="hljs-string">'8000'</span>
        <span class="hljs-attr">CidrIp</span>: <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>/<span class="hljs-number">0</span>
</code></pre><p>You can see that the <code>SecurityGroups</code> property is added to the EC2 instance and the created Security Group configuration is linked to the EC2 instance by using the <code>!Ref</code> parameter. </p>
<p>Now we have the CloudFormation template. But we're not yet finished. We're still missing one more thing. Can you figure it out? We created an EC2 instance, and we allowed an SSH port...but to log in using SSH we need to attach a key-value pair, right? Let's do that. </p>
<p>We can attach the key-value pair name directly to the template. For example, let's assume your key-value pair name is <code>5minslearn</code> you can attach the property <code>KeyName</code> directly to the EC2 resource block like what's shown below or we can pass it in via parameters. </p>
<pre><code>Resources:
  SampleNodejsDeploy:
    Type: AWS::EC2::Instance
    <span class="hljs-attr">Properties</span>:
      InstanceType: t2.micro
      <span class="hljs-attr">ImageId</span>: ami<span class="hljs-number">-014</span>d05e6b24240371
      <span class="hljs-attr">KeyName</span>: <span class="hljs-number">5</span>minslearn
      <span class="hljs-attr">SecurityGroups</span>:
        - !Ref SampleNodejsDeploySG
</code></pre><h3 id="heading-how-to-use-parameters-in-the-cloudformation-template">How to use parameters in the CloudFormation template</h3>
<p>We can use parameters to get the name of the key-value pair from the user while creating the stack. Basically, parameters allow us to pass input values into CloudFormation templates at runtime. Let's see how to do that. </p>
<pre><code>Parameters:
  SSHKey:
    Type: AWS::EC2::KeyPair::KeyName
    <span class="hljs-attr">Description</span>: name <span class="hljs-keyword">of</span> the key pair to ssh into the instance
<span class="hljs-attr">Resources</span>:
  SampleNodejsDeploy:
    Type: AWS::EC2::Instance
    <span class="hljs-attr">Properties</span>:
      InstanceType: t2.micro
      <span class="hljs-attr">ImageId</span>: ami<span class="hljs-number">-014</span>d05e6b24240371
      <span class="hljs-attr">KeyName</span>: !Ref SSHKey
      <span class="hljs-attr">SecurityGroups</span>:
        - !Ref SampleNodejsDeploySG
      <span class="hljs-attr">UserData</span>: 
        Fn::Base64:
          |
          #!<span class="hljs-regexp">/bin/</span>bash 
          set -e
          curl -sL https:<span class="hljs-comment">//deb.nodesource.com/setup_16.x | bash -</span>
          sudo apt install nodejs
          node -v
          npm -v
          curl -sS https:<span class="hljs-comment">//dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -</span>
          echo <span class="hljs-string">"deb https://dl.yarnpkg.com/debian/ stable main"</span> | sudo tee /etc/apt/sources.list.d/yarn.list
          sudo apt update &amp;&amp; sudo apt install yarn
          yarn --version
          sudo -i -u ubuntu bash &lt;&lt; EOF
          set -e
          cd /home/ubuntu
          sudo npm install -g pm2
          git clone https:<span class="hljs-comment">//github.com/5minslearn/node_with_docker.git</span>
          cd node_with_docker
          yarn install 
          pm2 start yarn --time --interpreter bash --name sample_node -- start -p <span class="hljs-number">8000</span>
          EOF

  <span class="hljs-attr">SampleNodejsDeploySG</span>:
    Type: AWS::EC2::SecurityGroup
    <span class="hljs-attr">Properties</span>:
      GroupDescription: <span class="hljs-keyword">for</span> the app nodes that allow ssh, http 
      <span class="hljs-attr">SecurityGroupIngress</span>:
      - IpProtocol: tcp
        <span class="hljs-attr">FromPort</span>: <span class="hljs-string">'80'</span>
        <span class="hljs-attr">ToPort</span>: <span class="hljs-string">'80'</span>
        <span class="hljs-attr">CidrIp</span>: <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>/<span class="hljs-number">0</span>
      - IpProtocol: tcp
        <span class="hljs-attr">FromPort</span>: <span class="hljs-string">'22'</span>
        <span class="hljs-attr">ToPort</span>: <span class="hljs-string">'22'</span>
        <span class="hljs-attr">CidrIp</span>: <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>/<span class="hljs-number">0</span>
      - IpProtocol: tcp
        <span class="hljs-attr">FromPort</span>: <span class="hljs-string">'8000'</span>
        <span class="hljs-attr">ToPort</span>: <span class="hljs-string">'8000'</span>
        <span class="hljs-attr">CidrIp</span>: <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>/<span class="hljs-number">0</span>
</code></pre><p>In the above template, we added a parameter to get the key pair name and referenced it to <code>KeyName</code> property. </p>
<p>Great! We successfully created a CloudFormation template to create an EC2 instance and security group. In addition to that, we also added a script to deploy the NodeJS app. Now it's time to create a CloudFormation stack.</p>
<h2 id="heading-how-to-create-a-cloudformation-stack">How to Create a CloudFormation Stack</h2>
<p>The first step is to log in to the AWS console and search for CloudFormation in the search bar (see the below screenshot). Click on stacks in the left sidebar to get started with CloudFormation. </p>
<p><img src="https://lh6.googleusercontent.com/tIzQQwxjsDLgPxI65f8l9jcAtw90UVVCRakld4M9h8ZUJrMvMhPVxPuSHm_Pdr-UZO1YPeAFTSP_6CU9fNx7a99Hjp04LnhkmmzG9ZdhEvi-o9mqil-vr6yKFYkdDv21AK1s13rKAVQue6l09MOFEpU" alt="Image" width="600" height="400" loading="lazy">
<em>CloudFormation Getting Started</em></p>
<p>Click on the create stack button to create the CloudFormation stack.</p>
<p><img src="https://lh4.googleusercontent.com/64WKWRSq7334K9DEXzOmuE_u-sDWUcSO3ZqpAhhJqFOnLC0Alp7NbP38PWjB0fj_qZw5sookagPnANLkJfjVASZrCwF4OODljGNdLdMKeaSrQfGg7BiyHmopUWBEQcIh1JuRWHZlvYgnFqxzyfTACpo" alt="Image" width="600" height="400" loading="lazy">
<em>Create CloudFormation Stack</em></p>
<p>As we have our template ready, select "Template is ready" and choose "Upload a template file" in the Template source section, and upload the template file.</p>
<p><img src="https://lh5.googleusercontent.com/KsaSLPVYV3UbphozR2kSzWR9ICXWnW1O6w2H5ooT_AmfxWknCCkfZ3km3fT2nscCsekhxgz6zrcphBY5l8olGTzPX5ZcmxoAwGFXPg92B4W9N1BVbUwUGdLq43gfD1FGIdj9O60vpO25wI9-3DJvBok" alt="Image" width="600" height="400" loading="lazy">
<em>Deploying CloudFormation Template</em></p>
<p>Once you upload the file, the "View in Designer" button will be enabled. Click on it to view your template design.</p>
<h2 id="heading-how-to-validate-the-cloudformation-template">How to Validate the CloudFormation Template</h2>
<p><img src="https://lh3.googleusercontent.com/Ddg61T-pDsR33QjMFGyVk7sIhDDs7qQ9nVQd6P1vOn5RY7DtlquWnEWFrzmDHH_4Ny78jzuAlmOg49ONtKw0XcxcDDjJtAqAEALC3RNsSuhwuFgkgQz_UzldzSHwPfwoGhEAnwALs8jlcq9_FyKUglU" alt="Image" width="600" height="400" loading="lazy">
<em>Validate CloudFormation Template</em></p>
<p>To validate our template click on the "Tick" icon on top left in the designer. It will validate and show us errors if any. Once the validation is done, click on the "Cloud" icon to the left of "Tick" icon. It will take you to the create stack page.</p>
<p>In the stack details page, enter the stack name and select your key-value pair. If you don't have key-value pair, create one and select it.</p>
<p><img src="https://lh4.googleusercontent.com/o9myUy3ijGaRzsqsISNq0eoMB6_JyvmJyF2JiwkKxbCoDPFKPTOHSiC5I5ioZP1CXQAyraUYLT72u17sKtZfIaRyHWnJLojFqumkaeEXeFXhMwBL1-rnmvZOSpemDDZ-axzGtxmfYAm0RQi1Pf7VUu8" alt="Image" width="600" height="400" loading="lazy">
<em>Specify CloudFormation stack details</em></p>
<p>Leave the Configure stack options section as it is, and click continue since we don't need any IAM permissions or advanced options.</p>
<p>Finally, review the page and submit the template. The template will start creating the resources. </p>
<p><img src="https://lh4.googleusercontent.com/wsD_CWoQk3P90rku7vPiC0uozKlj6fwYdmoZfP3SggCDOwLs5g1hshhK3crKMoDkvvTILf8206AktFBDQ1IeVDAPQ5ksPmYDAhu7v2h7R9Rg7mb9qSETOYQHhEoXNvOFyTD8sgf_AbHGO7MaGTNIjIE" alt="Image" width="600" height="400" loading="lazy">
<em>CloudFormation stack creating resources</em></p>
<p>Once that's done, click on the resources tab. You'll be able to see the resources we created (EC2 and Security group resources).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Resources created by CloudFormation Template</em></p>
<p>Click on the EC2 instance, and you can see that our instance will be up and running. Copy the public IPv4 address. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/2.png" alt="Image" width="600" height="400" loading="lazy">
<em>EC2 instance up and running</em></p>
<p>Open your browser and hit <code>http://&lt;ip_address&gt;:8000</code> (In my case it is <code>http://54.176.19.18:8000/</code>). You should be able to see a page similar to the one below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-from-2023-04-14-02-35-22.png" alt="Image" width="600" height="400" loading="lazy">
<em>NodeJS app running</em></p>
<p>This represents that our NodeJS app is successfully deployed! </p>
<p><strong>Note:</strong> EC2 user data will take some time to install dependencies. So for the first time, the page will take long time to load. Just be patient until the site is loaded. </p>
<h2 id="heading-how-to-delete-the-cloudformation-stack">How to Delete the CloudFormation Stack</h2>
<p>If you no longer need the stack, you can delete it from the CloudFormation console. </p>
<p>Select the stack you want to delete, click "Delete Stack," and confirm the action. This action will delete all resources created using this stack. In our case, it'll delete both EC2 and Security Group. You don't need to delete the EC2 instance and Security Group individually.</p>
<p><img src="https://lh5.googleusercontent.com/qIonqqdC9U22tCeGsJXtcLG6U5LaiCnbNATAzNBKA8bKXttf9GLhHRMa28F9oMYE_W1OjeoPCdrDnlUk569rwnPlMFgcVKjLOc-oKYQxHYKA_SkrzI4UBjgTlf5gCrGukTTDuXR61pa8I5OWDMSd5Gg" alt="Image" width="600" height="400" loading="lazy">
<em>Deleting CloudFormation stack</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, we learned about CloudFormation, how it works, and how to create and delete a template stack. </p>
<p>Hope you enjoyed reading this article! If you are stuck at any point feel free to drop your queries to me at my <a target="_blank" href="mailto:arun@gogosoon.com">email</a>. I’ll be happy to help you.</p>
<p>If you wish to learn more about AWS, subscribe to my <a target="_blank" href="https://5minslearn.gogosoon.com/?ref=fcc_cloud_formation">newsletter</a> (<a target="_blank" href="https://5minslearn.gogosoon.com/?ref=fcc_cloud_formation">https://5minslearn.gogosoon.com/</a>) and follow me on social media. </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Implement Infrastructure as Code with AWS ]]>
                </title>
                <description>
                    <![CDATA[ Infrastructure as code is the process of provisioning and managing your cloud resources by writing a template file that is both human-readable and machine consumable.  For AWS cloud development, the built-in choice for infrastructure as code is AWS C... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-implement-infrastructure-as-code-with-aws/</link>
                <guid isPermaLink="false">66b9ef60d5fabb18363d1946</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kayode Adeniyi ]]>
                </dc:creator>
                <pubDate>Mon, 31 Oct 2022 17:07:04 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/10/network-g381392bcb_1280.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Infrastructure as code is the process of provisioning and managing your cloud resources by writing a template file that is both human-readable and machine consumable. </p>
<p>For AWS cloud development, the built-in choice for infrastructure as code is AWS CloudFormation.</p>
<p>Using IaC, developers can manage a project’s infrastructure efficiently, allowing them to easily configure and maintain changes within a project’s architecture and resources.</p>
<p>There are numerous IaC tools available such as Ansible, Puppet, Chef, and Terraform. </p>
<p>But for this guide, we will use CloudFormation, which was made specifically for AWS resources.</p>
<h2 id="heading-what-you-will-learn-in-this-tutorial">What You Will Learn in This Tutorial</h2>
<p>After going through this tutorial, you will understand how to maintain your resources within one software file. </p>
<p>In addition to this, you will learn the benefits related to speed that Infrastructure as Code brings to the table. Without IaC, the time and cost of manual deployment of various infrastructures can be much greater compared to maintaining infrastructure as software. </p>
<p>In this article, we will consider an example. It will demonstrate manually provisioning resources vs deploying a CloudFormation script to create a serverless Lambda function and REST API on AWS.</p>
<h3 id="heading-services-well-use-in-this-tutorial">Services We'll Use in This Tutorial</h3>
<p>We will use the following services to implement Infrastructure as Code in AWS: </p>
<table><colgroup><col><col></colgroup><tbody><tr><td><p><span>AWS Service Name</span></p></td><td><p><span>Description</span></p></td></tr><tr><td><p><span>AWS API Gateway (API GW)</span></p></td><td><p><span>We will use this service to create our REST API. It also allows for creating, publishing, and monitoring secure Socket and Restful APIs.</span></p></td></tr><tr><td><p><span>AWS Lambda</span></p></td><td><p><span>We will use this service to set up an example serverless function on the backend which will be integrated with our REST API.</span></p></td></tr><tr><td><p><span>Identity Access and Management (IAM)</span></p></td><td><p><span>Service that allows you to manage access to various AWS services through roles and permissions. We will create a role for our Lambda function so that we can access the API gateway.</span></p></td></tr><tr><td><p><span>AWS CLI</span></p></td><td><p><span>To work with AWS services and resources, you can use the command line interface rather than the console for easy access.</span></p></td></tr><tr><td><p><span>AWS SAM</span></p></td><td><p><span>An abstraction of CoudFormation allows developing the serverless applications.</span></p></td></tr></tbody></table>

<p>For those who are new to AWS, it's good to have some knowledge of it to understand the article. So, you can follow along with me by creating an account on AWS <a target="_blank" href="https://aws.amazon.com/console/">here</a> and making sure you have <a target="_blank" href="https://aws.amazon.com/cli/">AWS CLI</a> installed to work with the example.</p>
<h3 id="heading-overview-of-the-example">Overview of the Example</h3>
<p>For the article, we will be implementing a REST API with an API gateway. It will be integrated with a serverless backend Lambda function that handles POST and gets requests made by our API.</p>
<p>The first step will show you how to manually build and deploy these resources using the AWS console. The second step will show you how to automate the process using CloudFormation.</p>
<h2 id="heading-how-to-deploy-manually">How to Deploy Manually</h2>
<p>In manual deployment, we will work inside the AWS console. It is a bit hard to track changes while working outside the local IDE, especially for large-scale projects. </p>
<p>In the first step, we will create a Lambda function.</p>
<p><img src="https://lh5.googleusercontent.com/dA4vn3WDgKdVRCf9dJyZjGG5CxtHGVrB-EuGs3EW9P0KkIGxMf64fWg-NXNhFGaVPios3ryNb4OpUfNCMEYvWE1rOtk1QfE_FjSF01E4DVUUlouuUY4KdzCt8J68_OnTz72x6PmousW5auYLYMZF_lYP69T-VljKBwD39ssmU2R-463xL7UQQCM9kg" alt="Image" width="1600" height="724" loading="lazy"></p>
<p>If you want your Lambda function to work with some other service like Comprehend, you must give permissions for that service. So, make sure to create a role with these permissions.</p>
<p>Following is our Lambda function that will return “Hello World” when integrated with the API gateway. </p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> json

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    <span class="hljs-comment"># TODO implement</span>
    <span class="hljs-keyword">return</span> {
        <span class="hljs-string">'statusCode'</span>: <span class="hljs-number">200</span>,
        <span class="hljs-string">'body'</span>: json.dumps(<span class="hljs-string">'Hello World!'</span>)
    }
</code></pre>
<p>Now that we have configured our Lambda function, the next step is to create a REST API to interact with the Lambda function. </p>
<p>For this, go to Amazon API Gateway, Click create API, and select REST API from the provided options.</p>
<p><img src="https://lh4.googleusercontent.com/DZDV8mRaosjWYOyeQgPpI2jI8U73bAYAJ-Y_FIEVZMYD_KjjSatXTNcUiv9rgpoBUg-tOL7Kc4-nXthNR8MsUcW8NtrbD7Sg6fx8VQHHG98DqBZDGvAC2alCjHunqu1W9OynLD83NcVu-kKE1jq4nu5byTcq6cLxjmeaAdI05l0MTfi37sxcIRcfSQ" alt="Image" width="1600" height="677" loading="lazy"></p>
<p>Now we will integrate Lambda with the API. For this, create a GET method from the actions menu and point our REST API to the Lambda function.</p>
<p><img src="https://lh4.googleusercontent.com/H8Aw6L3o8xpkEPJQI2X0Hw7O2hAmyYuUKeEY2Q8SH0DOR7R_uCAJc94Z4lnJCsT-ZIYkrky3GWqroyrhSgUr2Hr8kiN8Ye3jOyTJdO_3WZSqTdC9shRDOju9oNIHx_ijkj2B3ig7xf83l2emLGVZei7Obj9twWhQifWMnR256HAFdUrc26fq52-70Q" alt="Image" width="1600" height="692" loading="lazy"></p>
<p>We are in a position to deploy and test our API for its proper integration with Lambda. Select any stage name you want – for this example, I'm using “prod”.</p>
<p><img src="https://lh6.googleusercontent.com/YYKoUxe7L76Mcy6dJSK8rGo5b2KL3tru7D4pwGDqdREoHuPxNICkjGYvVT6KVDe_TrRDsfFj3wfu9TysVLDaJ9kb-fTyQoyc2DAYQ9y42Wtf1S4TGVAAmvdkQvgTVtxfwkU97_XRIAt4ge5HaDtnoroKS_uMPanS2GhC9Tk2mNhgyr1DAp5r9WG8tA" alt="Image" width="1600" height="1005" loading="lazy"></p>
<p>After deploying the API, you can see a URL on the “prod” stage. Hitting this URL will trigger the Lambda function. As we have returned “Hello World” from our Lambda function, so you should be able to see the desired result.</p>
<p><img src="https://lh6.googleusercontent.com/TY4GfBhk2V2RARNKYvD894FxeFKOZd4csJvj-aori2Ct524F1jOpx43CQpWEP-2irtjJTGIXgf6fhI4JZej_DgjEFM0UL0mzPoe7L2BQBHrMY5mv8mMbW6MbPKE-Qv7CC95VUPjqKeJ43L7iUea2qj4HMixEhm3p79Dma3cNxt4PanqJ49Hi6YJgrA" alt="Image" width="1600" height="415" loading="lazy"></p>
<h2 id="heading-how-to-deploy-with-cloudformation">How to Deploy with CloudFormation</h2>
<p>Up to this point, we have seen how manual deployment works, which usually takes a few minutes. </p>
<p>But let’s imagine that we have more than one API, method, and more than one developer working on them. In this scenario, tracking all the resources and changes would be challenging. </p>
<p>So, in this section, we will use AWS CloudFormation instead. It will give flexibility to the developers, allowing them to adjust their Infrastructure with a simple script.</p>
<h3 id="heading-how-does-cloudformation-work">How Does CloudFormation Work?</h3>
<p>We will use the YAML file to provision and declare these resources and deploy them to AWS to create a CloudFormation stack. CloudFormation is a stack that contains all the resources required for the project. </p>
<p>We will be using the SAM template as described above in the services section. It is an abstraction of CloudFormation to build serverless applications with less YAML code. </p>
<p>For those who don’t know about YAML, you can think about it like JSON. But CloudFormation uses both of these file formats.</p>
<p><strong>In the first step,</strong> we head to our local IDE and write the same Lambda function as we did in the AWS console.</p>
<p><strong>helloworld.py</strong>:</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> json

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    <span class="hljs-comment"># TODO implement</span>
    <span class="hljs-keyword">return</span> {
        <span class="hljs-string">'statusCode'</span>: <span class="hljs-number">200</span>,
        <span class="hljs-string">'body'</span>: json.dumps(<span class="hljs-string">'Hello World!'</span>)
    }
</code></pre>
<p>Next, we will create a <strong>template</strong>.yaml file containing our infrastructure. We will define our Lambda function and API Gateway in this file. </p>
<p>To build this file, we need to add some information that is common to all SAM templates.</p>
<p><strong>template.yaml</strong>:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">AWSTemplateFormatVersion:</span> <span class="hljs-string">'2010-09-09'</span>
<span class="hljs-attr">Transform:</span> <span class="hljs-string">AWS::Serverless-2016-10-31</span>
<span class="hljs-attr">Description:</span> <span class="hljs-string">First</span> <span class="hljs-string">CloudFormation</span> <span class="hljs-string">template</span>
</code></pre>
<p>Now, we have to add “Globals” to this CloudFormation template.yaml file. <strong>Globals</strong> are the common configs for the resources that you are going to deploy. Globals allow you to declare information globally for a specific resource type rather than specifying it again and again for different resources. </p>
<p><strong>template.yaml</strong>:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">Globals:</span>
    <span class="hljs-comment">#Common to all Lambda functions you create</span>
    <span class="hljs-attr">Function:</span>
      <span class="hljs-attr">MemorySize:</span> <span class="hljs-number">128</span>
      <span class="hljs-attr">Runtime:</span> <span class="hljs-string">python3.6</span>
      <span class="hljs-attr">Timeout:</span> <span class="hljs-number">5</span>
</code></pre>
<p>We define have to define the Resources tag in our template.yaml file. The Lambda function and REST API will come under this tag. </p>
<p><strong>template.yaml</strong>:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">Resources:</span>

    <span class="hljs-comment">##Lambda and API GW Integrated</span>
    <span class="hljs-attr">helloworld:</span>
        <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::Serverless::Function</span>
        <span class="hljs-attr">Properties:</span>
          <span class="hljs-comment">#filename.functionname</span>
          <span class="hljs-attr">Handler:</span> <span class="hljs-string">helloworld.lambda_handler</span>

          <span class="hljs-comment">#REST API created</span>
          <span class="hljs-attr">Events:</span>
            <span class="hljs-attr">PostAdd:</span>
              <span class="hljs-attr">Type:</span> <span class="hljs-string">Api</span>
              <span class="hljs-attr">Properties:</span>
                <span class="hljs-attr">Path:</span> <span class="hljs-string">/helloworld</span>
                <span class="hljs-attr">Method:</span> <span class="hljs-string">get</span>
</code></pre>
<p>In the above code, we define parameters for creating the Lambda function. For the event, we create a REST API that triggers the Lambda function. </p>
<p><strong>Note:</strong> There is an array of parameters like CodeURI and description that you can specify for your serverless function. The best way to create a template file is to go through the CloudFormation docs and see the parameters available for your specified resource/service.</p>
<h2 id="heading-how-to-deploy-the-template-file">How to Deploy the Template File</h2>
<p>We can deploy our <strong>template</strong>.yaml file using the following two AWS CLI commands:</p>
<pre><code class="lang-yaml"><span class="hljs-comment">##s3 bucket stores our sam template which we need to deploy</span>
<span class="hljs-string">aws</span> <span class="hljs-string">cloudformation</span> <span class="hljs-string">package</span> <span class="hljs-string">--template-file</span> <span class="hljs-string">template.yaml</span> <span class="hljs-string">--output-template-file</span> <span class="hljs-string">sam-template.yaml</span> <span class="hljs-string">--s3-bucket</span> <span class="hljs-string">helloworld-sam</span>
</code></pre>
<p>After running the above command, you will be able to see a SAM template file. We will use this file in the second command below. </p>
<p>In this command, give your appropriate path to the sam-template.yaml file:</p>
<pre><code class="lang-yaml"><span class="hljs-comment">#Deploy stack</span>
<span class="hljs-comment">#point to template file created by a previous command and a stack name as well as your region you're deploying</span>

<span class="hljs-string">aws</span> <span class="hljs-string">cloudformation</span> <span class="hljs-string">deploy</span> <span class="hljs-string">--template-file</span> <span class="hljs-string">/path</span> <span class="hljs-string">to</span> <span class="hljs-string">sam-template.yaml</span> <span class="hljs-string">file</span> <span class="hljs-string">--stack-name</span> <span class="hljs-string">test-stack</span> <span class="hljs-string">--capabilities</span> <span class="hljs-string">CAPABILITY_IAM</span> <span class="hljs-string">--region</span> <span class="hljs-string">us-east-1</span>
</code></pre>
<p>After executing both of these commands, you will see the stack created in the CLI. You can verify it using CloudFormation in the console. </p>
<p>Here you will see all the resources provisioned through the code created and deployed using the template.yaml file. </p>
<p><img src="https://lh5.googleusercontent.com/h9wcmfT3Kx-jSIMEeCqZv-0qFTs05puZ6ox5CuRBywubpqdiPRpnYiCZZTaYFBaSDEuQmi5HtXCPNPpZcuKCs_jtBTc6WZP5pUceHxR-jRWmRLycxFwESMYkdYpN5Qi5c3_TACNRjpqfwpRdDf5qV6Wee5-uAMhtvVoAWUtJvIA4h4no_fk-NPT7Sw" alt="Image" width="1600" height="1168" loading="lazy"></p>
<p>You can click on API and access the URL to check the output as we did for the manual deployment. </p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>That’s it – you have successfully implemented infrastructure as code in AWS using CloudFormation.</p>
<p>I hope this article has been helpful for anyone wanting to understand implementing infrastructure as code in AWS.</p>
<p>Connect with me on <a target="_blank" href="https://www.linkedin.com/in/kadeniyi/">LinkedIn</a> and <a target="_blank" href="https://twitter.com/mkbadeniyi">Twitter</a></p>
<p>Hasta la vista!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Infrastructure as Code by Building a Custom Machine Image in AWS ]]>
                </title>
                <description>
                    <![CDATA[ Hey everyone! If you're wondering what infrastructure-as-code means, then you've come to the right place.  In this article I will explain the concepts behind Infrastructure-As-Code (IaC) at a high level and dive into a category of IaC known as server... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-instructure-as-a-code-by-building-custom-machine-image-in-aws/</link>
                <guid isPermaLink="false">66b906b491df28fd39092e49</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Destiny Erhabor ]]>
                </dc:creator>
                <pubDate>Fri, 15 Jul 2022 17:15:48 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/07/Understand-Infrastructure-as-a-code--4-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Hey everyone! If you're wondering what infrastructure-as-code means, then you've come to the right place. </p>
<p>In this article I will explain the concepts behind Infrastructure-As-Code (<strong>IaC</strong>) at a high level and dive into a category of IaC known as <strong>server templating</strong>. </p>
<p>For the purpose of this article, we will be using a server templating tool called <strong>Packer</strong> to create a custom Amazon Machine Image (AMI) and use it to launch an AWS EC2 instance.</p>
<h1 id="heading-what-is-infrastructure-as-code">What is Infrastructure-As-Code?</h1>
<p>‌‌Infrastructure-As-Code (<strong>IaC</strong>) refers to the process of managing and supplying infrastructure using code rather than manual methods. </p>
<p>The core concept is that you define, deploy, update, and destroy your infrastructure (servers, databases, networks, configuration, and so on) by writing and executing code.</p>
<h1 id="heading-types-of-infrastructure">Types of Infrastructure</h1>
<h2 id="heading-what-is-mutable-infrastructure">What is Mutable Infrastructure?</h2>
<p>You can change and configure infrastructure that is mutable depending on your needs. You can log into servers, apply fixes, update configurations, and more.</p>
<p>‌‌Let's say we have a web application that we want to deploy to the cloud or to an onsite server. </p>
<p>We configure the program to install all necessary instructions and configurations after deployment in order for it to be production ready. We manually or automatically change the server whenever a new upgrade/configuration to the application is required. </p>
<p>This is fantastic for a few servers, but for many servers, it can result in application mismatches unless you use configuration tools like Ansible. This is referred to as mutable infrastructure. It follows the <strong>BUILD, DEPLOY, CONFIGURE</strong> pattern.</p>
<h3 id="heading-benefits-of-mutable-infrastructure">Benefits of Mutable Infrastructure</h3>
<ul>
<li>Resolve issues more rapidly. Rather of having to build a new server from the ground up, IT staff gets to know each server on a "personal" level, allowing them to diagnose issues more quickly.</li>
<li>Updates are usually quicker and can be tailored to each server individually.</li>
<li>The infrastructure can be tailored to the exact requirements of the server-side applications.</li>
</ul>
<h3 id="heading-challenges-of-mutable-infrastructure">Challenges of Mutable Infrastructure</h3>
<ul>
<li>Because each server has a unique configuration, which is known as configuration drift, technical difficulties are difficult to diagnose or reproduce.</li>
<li>Because you have to manually configure the servers, provisioning servers is frequently a lengthy procedure.</li>
<li>Server changes aren't always recorded, making version tracking more complex.</li>
<li>Update failures happen more often because of a range of unanticipated factors such as network connectivity, unresponsive repositories, DNS downtime, and so on.</li>
<li>If updates aren't applied correctly, there's a higher danger and complexity in production workloads. Due to these unexpected situations, debugging is tough.</li>
</ul>
<p>Fortunately, you can employ <strong>immutable infrastructure</strong> to avoid the issues that mutable infrastructure can cause.</p>
<h2 id="heading-what-is-immutable-infrastructure">What is Immutable Infrastructure?</h2>
<p>In immutable infrastructure, components are recreated and replaced rather than updated after they're formed. This makes your product more consistent and reliable.</p>
<p>‌‌Making your infrastructure immutable is the best way to ensure scalability in the realm of IaC.‌‌</p>
<p>Now, from the use-case in mutable infrastructure, instead of <strong>configuring the server after deployment</strong>, how about <strong>configuring it before deployment</strong>, like in the mutable infrastructure above? </p>
<p>Immutable infrastructure is based on this concept, which you can implement with the help of technologies like Packer.‌‌ </p>
<h3 id="heading-benefits-of-immutable-infrastructure">Benefits of Immutable Infrastructure</h3>
<ul>
<li>There is no configuration drift because there are no adjustments to make.</li>
<li>Tracking and rollbacks are significantly easier with discrete versioning. Each new server or virtual machine may be tracked by the IT department as it is deployed.</li>
<li>Testing is made easier by the uniformity of setups across several servers.</li>
<li>Predictable state due to the fact that the infrastructure is never changed, which reduces complexity.</li>
<li>In a multi-threaded context, safe thread code means mutation is almost non-existent.</li>
</ul>
<h3 id="heading-challenges-of-immutable-infrastructure">Challenges of Immutable Infrastructure</h3>
<ul>
<li>Infrastructure can't be changed while it's in place. For example, if a zero-day vulnerability is discovered, all servers with the same configuration must be updated.</li>
<li>Immutable infrastructure's increased agility and dynamism can occasionally conflict with standard IT security measures.</li>
<li>Copying array data from one location to another incurs overhead. Instead of writing data to the local disk, data is externalized.</li>
</ul>
<h1 id="heading-types-of-infrastructure-as-code-tools">Types of Infrastructure-As-code Tools</h1>
<p>There five five broad categories of IAC tools:</p>
<ul>
<li><strong>Ad hoc scripts</strong></li>
<li><strong>Configuration Management tools</strong></li>
<li><strong>Orchestration tools</strong></li>
<li><strong>Provisioning tools</strong></li>
<li><strong>Server Templating tools</strong></li>
</ul>
<h2 id="heading-what-are-ad-hoc-scripts">What are Ad Hoc Scripts?</h2>
<p>‌‌An Ad hoc technique is when you build a script in your preferred programming language (like Bash or Python) to automate a task, configure a tool, and run it on the server.</p>
<h2 id="heading-what-are-configuration-management-tools">What are Configuration Management Tools?</h2>
<p>‌‌Large amounts of software is installed and managed using provisioning tools on existing servers. </p>
<p>When clustering computers, keeping the hardware and software as homogeneous as feasible is ideal. This helps make sure that performance is consistent and that the separate nodes get along with one another. </p>
<p>Configuration management tools make it simpler to manage the software side of clusters. Some of these tools include:</p>
<ul>
<li><strong>Chef</strong></li>
<li><strong>Puppet</strong></li>
<li><strong>Ansible</strong></li>
<li><strong>SaltStack</strong></li>
</ul>
<h2 id="heading-what-are-orchestration-tools">What are Orchestration Tools?</h2>
<p>‌‌How do you manage VMs and containers once they've been created? You need to roll out updates, monitor the health of your VMs, and distribute traffic between VMs and containers in the real world. Even communication with one another over the internet is possible (server discovery). </p>
<p>Orchestration tools are designed to deal with these issues. ‌‌Orchestration is the simultaneous automation of many operations in order to reduce production difficulties and time to market. </p>
<p>Some of these tools include:</p>
<ul>
<li><strong>Kubernetes</strong></li>
<li><strong>Marathon/Mesos</strong></li>
<li><strong>Amazon Elastic Container Service</strong></li>
<li><strong>Docker Swarm</strong></li>
<li><strong>Normad</strong></li>
</ul>
<h2 id="heading-what-are-provisioning-tools">What are Provisioning Tools?</h2>
<p>The code that runs on each server is defined by configuration management tools, server templating tools, and orchestration tools. </p>
<p>Provisioning tools, on the other hand, are in charge of creating servers, databases, load balancers, monitoring, secure socket layer (SSL) certificates, and many other aspects of your infrastructure. </p>
<p>Example of provisioning tools are‌‌:</p>
<ul>
<li><strong>Terraform</strong></li>
<li><strong>CloudFormation</strong></li>
<li><strong>OpenStack Heat</strong></li>
</ul>
<h2 id="heading-what-are-server-templating-tools">What are Server Templating Tools?</h2>
<p>‌‌Server templating is a popular alternative to configuration management that has lately gained traction. </p>
<p>The goal behind server templating tools is to build an image of a server that takes a fully self-contained "snapshot" of the operating system (OS), software, files, and all other essential characteristics, rather than launching a number of servers and configuring them by running the same code on each one. </p>
<p>You can then install that image on all of your servers using another IaC program.</p>
<p>Examples of server templating tools are:</p>
<ul>
<li><strong>Packer</strong></li>
<li><strong>Docker</strong></li>
<li><strong>Vagrant.</strong></li>
</ul>
<p>In this article, I'll go through how to generate a custom image using Packer server templating tools, and you'll see how to deploy the custom image on AWS instance .</p>
<h1 id="heading-packer-and-the-custom-image">Packer and the Custom Image</h1>
<h2 id="heading-what-is-packer">What is Packer?</h2>
<p>‌‌Packer helps you create and customize images that already have specific applications installed, so that your programs are copied over, and so on. </p>
<p>These images are known as machine images, and each platform has its own image format name with preloaded applications. </p>
<p>For example, here's how some platforms identify these images:</p>
<ul>
<li><strong>AMAZON</strong> (AMI)</li>
<li><strong>VMWare</strong> (VMDK/VMX)</li>
<li><strong>VirtualBox</strong> (OVF)</li>
</ul>
<p>Packer lets you generate your own custom machine image and bake your code and configuration into it before deploying it to the server. It's finished. There's no need to alter the config because it's already baked into the image. </p>
<p>And for any subsequent updates, you destroy the server and spin up a new server with a load balancer to help maintain the destroying of one server and the starting of another.</p>
<h2 id="heading-core-packer-building-blocks">Core Packer Building Blocks</h2>
<p>There are three major building blocks in the Packer configuration file:</p>
<ul>
<li>builders</li>
<li>provisioners</li>
<li>post-processors</li>
</ul>
<p><strong>Builders</strong> create the machine and generate the image across platforms. This is a very important component. The builder block can take an array of different builders from different platform.‌‌</p>
<p><strong>Provisioners</strong> help you install, customize and configure the machine image, either through third party software or built-in ones.‌‌</p>
<p>And <strong>post-processors</strong> run after the image has been build and customized.</p>
<h3 id="heading-enough-of-the-theory">Enough of the theory...</h3>
<p>Now, it is time for us to build some immutable system with Packer, shall we?</p>
<h2 id="heading-prerequisites-and-installation">Prerequisites and Installation</h2>
<ul>
<li><a target="_blank" href="https://aws.amazon.com/console/">Create an AWS account</a></li>
<li><a target="_blank" href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html#id_users_create_console">Create an IAM user</a></li>
<li><a target="_blank" href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey">Create and Download your user secret and access key</a></li>
<li><a target="_blank" href="https://www.packer.io">Download and Install Packer</a></li>
</ul>
<h2 id="heading-how-to-build-your-first-image-with-packer">How to Build Your First Image with Packer</h2>
<p>First, create a folder called <strong>packer_custom_image</strong>.</p>
<p>Open the folder and create the following files: <strong>packer.json, variable.json, setup.sh,</strong> and <strong>.gitignore</strong>.</p>
<ul>
<li><strong>packer.json</strong> contains all the necessary code for creating the custom i=machine image</li>
<li><strong>variable.json</strong> contains sensitive information we want to keep alway from the public</li>
<li><strong>setup.sh</strong> contains shell scripts we will need to run instructions to our image</li>
<li><strong>.gitignore</strong> contains files/folders hidden from the Git remote server</li>
</ul>
<p>Note that Packer files used the .json extension and have a format like the JavaScript JSON script.</p>
<h3 id="heading-folderfile-structure"><strong>Folder/File structure</strong></h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Capture-1.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p><strong>Let's get started!</strong></p>
<p>First let's define our variables and Packer builders inside variable.json like this:</p>
<pre><code class="lang-json"> { 
     <span class="hljs-attr">"description"</span> : <span class="hljs-string">"myWebServer"</span>, 
    <span class="hljs-attr">"access_key"</span> : <span class="hljs-string">"enter-aws-your-key"</span>, 
    <span class="hljs-attr">"secret_key"</span> : <span class="hljs-string">"enter-aws-your-secret"</span> 
    <span class="hljs-string">"source_ami"</span> : <span class="hljs-string">"enter-yours"</span> 
   }
</code></pre>
<p>Alright so what's going on here?</p>
<ul>
<li><strong>descriptions</strong>: This variable detects the name of the machine image we are creating</li>
<li><strong>access_key</strong> and <strong>secret_key</strong>: Your IAM user credentials you created and downloaded. These are required by Packer for authentications and authorization.</li>
<li><strong>source_ami</strong>: The source AMI is the AMI you want to used as a base for your custom AMI. AWS has numerous AMIs such as Amazon Linux, Ubuntu, Windows, Redhat and so on. You can choose from these as a base image. In our case we will use the <strong>Amazon Linux AMI.</strong></li>
</ul>
<p>Now, log into your AWS console. Click services and search for EC2.</p>
<p>Click the <strong>launch instance</strong> button (we are not launching any instance, we just want to copy our base image).</p>
<p><img src="https://paper-attachments.dropbox.com/s_27CF84DFB65AC89AFB11F73B9BBE903F909EE2F6D867768E80956C8C0E105718_1655555078518_ec2.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Next, scroll to <strong>Application and OS Images (Amazon Machine Image)</strong>. Click and choose Amazon image, then copy your most recent AMI.</p>
<p><img src="https://paper-attachments.dropbox.com/s_27CF84DFB65AC89AFB11F73B9BBE903F909EE2F6D867768E80956C8C0E105718_1655555557189_base-ami.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Then close the instance <strong>(DO NOT CREATE/LAUNCH)</strong>.</p>
<p>Next, let's update the <strong>packer.json</strong> file like this:</p>
<pre><code class="lang-json">{ 
    <span class="hljs-attr">"builders"</span>: [ 
        { 
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"amazon-ebs"</span>, 
            <span class="hljs-attr">"access_key"</span>: <span class="hljs-string">"{{user `access_key` }}"</span>, 
            <span class="hljs-attr">"secret_key"</span>: <span class="hljs-string">"{{user `secret_key` }}"</span>, 
            <span class="hljs-attr">"region"</span> : <span class="hljs-string">"us-east-1"</span>, 
            <span class="hljs-attr">"ami_name"</span> : <span class="hljs-string">"myfirstami"</span>, 
            <span class="hljs-attr">"source_ami"</span> : <span class="hljs-string">"{{user `source_ami` }}"</span>, 
            <span class="hljs-attr">"instance_type"</span> : <span class="hljs-string">"t2.micro"</span>, 
            <span class="hljs-attr">"ssh_username"</span> : <span class="hljs-string">"ec2-user"</span> 
         } 
    ] 
 }
</code></pre>
<p>Let's see what's going on here:</p>
<ul>
<li><strong>builder</strong>: As previously stated, the builder creates the machine image and can produce identical images across multiple platforms, such as AWS and Azure, thus the array. It requires the builder.</li>
<li><strong>type</strong>: Builder type name</li>
<li><strong>access and secret keys</strong>: For authentication, it fetches data from the variable.json file we created for security, hence the <strong>"{{user <code>variable-name</code> }}"</strong> format.</li>
<li><strong>variable-name</strong>: the name we gave in our variable.json file (you should use this similar format for variables you don't intend to make public)</li>
<li><strong>region</strong>: identifies the AMI region.</li>
<li><strong>ami_name</strong>: Custom name for our custom machine image name.</li>
<li><strong>source ami</strong>: For the machine image we want to customize from the AWS AMI.</li>
<li><strong>Instance type</strong>: For the temporary instance required by Packer to create our image.</li>
<li><strong>ssh username</strong>: For credentials to create the temporary instance as related to the builder platform. <strong>AWS</strong> is the word that comes to mind in our case.</li>
</ul>
<p>Next, we'll customize the image with provisioners. We will be customizing the source AMI with a Jenkins instance.</p>
<p>Update the packer.json, and add the following after the builder array block:</p>
<pre><code class="lang-json"> {  ... 
         <span class="hljs-attr">"provisioners"</span>: 
            [ 
                { 
                    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"shell"</span>, 
                    <span class="hljs-attr">"script"</span>: <span class="hljs-string">"setup.sh"</span> 
                 } 
             ] 
}
</code></pre>
<p>‌‌The provisioners come after the builders and take an array as well. We can use many provisioners for a single or multiple builders, giving us more flexibility.</p>
<ul>
<li><strong>type</strong>: The shell type is the most basic and widely used provisioner. It can accept a script property (external file to run our code), inline properties to run as on the command line, or file properties to upload files, code, and other stuff our images.</li>
<li><strong>scripts</strong>: Takes an external script property to run our configurations.</li>
</ul>
<p>Let’s setup the scripts file we created earlier.</p>
<p><strong>setup.sh</strong></p>
<pre><code class="lang-bash">sleep 30
sudo yum update –y
sudo wget -O /etc/yum.repos.d/jenkins.repo \
    https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
sudo yum upgrade
sudo amazon-linux-extras install java-openjdk11 -y
sudo yum install jenkins -y
sudo systemctl <span class="hljs-built_in">enable</span> jenkins
sudo systemctl start jenkins
sudo systemctl status jenkins
</code></pre>
<p>The script installs Jenkins and its dependencies into our image and starts up the Jenkins instance.</p>
<p>Optionally, we can pass post-processors and sensitive-variables. There are numerous post processors you can use after an image has been built and altered.</p>
<p>‌‌<strong>packer.json</strong></p>
<pre><code class="lang-json">{  ... 
        <span class="hljs-attr">"post-processors"</span>: 
            [ 
                { 
                    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"manifest"</span>, 
                    <span class="hljs-attr">"output"</span>: <span class="hljs-string">"out.json"</span> 
                  } 
             ] 
 }
</code></pre>
<ul>
<li><strong>type</strong>: Takes the type of post processor instance, the <strong>manifest</strong> type defines the way we want our image architecture to be printed after it's created.</li>
<li><strong>output</strong>: the name of the file to print the after the baked image artifacts</li>
<li><strong>Sensitive-variables</strong>‌: Packer allows us specify sensitive variable to omit from the post processor manifest. Let's add our secret and access key:</li>
</ul>
<pre><code class="lang-json">{...
    <span class="hljs-attr">"sensitive-variables"</span>: 
        [     
            <span class="hljs-string">"secret_key"</span>,    <span class="hljs-string">"access_key"</span>  
         ]
 }
</code></pre>
<h2 id="heading-how-to-run-and-test-the-code">How to Run and Test the Code</h2>
<p>‌‌In your terminal, in the root directory, run the code. You should see <strong>out.json</strong> and <strong>out.json.lock</strong> created at the root of your application. (These files contain your build and artifacts.)</p>
<p>‌‌<code>packer build -var-file="variable.json" packer.json</code>‌‌</p>
<p><img src="https://paper-attachments.dropbox.com/s_27CF84DFB65AC89AFB11F73B9BBE903F909EE2F6D867768E80956C8C0E105718_1655557789760_firstami.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-ami-in-the-aws-console">AMI in the AWS Console</h3>
<p>Now, let's log in to our AWS console to view our just created custom image. We will also need to spin up an EC2 instance to verify if our image has the proper configurations of the Jenkins instance baked into it.</p>
<ul>
<li>Go to your AWS console</li>
<li>Click service and search for EC2 and scroll to image</li>
<li>There click AMI and find your newly created custom image</li>
</ul>
<p><img src="https://paper-attachments.dropbox.com/s_27CF84DFB65AC89AFB11F73B9BBE903F909EE2F6D867768E80956C8C0E105718_1655557986410_ami.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Yay! Our image was created successfully. Now it's time to launch an instance from it. </p>
<ul>
<li>From the AMI page, select the recent created AMI and click the <strong>Launch instance from AMI</strong> at the top right corner. This takes you to the instance page.</li>
<li><strong>Name and tags</strong> – Give the instance a name. say ""My Jenkins Server"</li>
<li><strong>Application and OS Images (Amazon Machine Image)</strong> should remain same (that is, our image)</li>
<li><strong>Instance type</strong> – Leave as default "t2-micro". This allows us to use AWS's free tier</li>
<li><strong>Key pair</strong> – We will need to create a new key-pair to allow us login (SSH) to the instance from our computer terminal. Click the <strong>Create new key</strong> button and the following window should show:</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/key-par.PNG" alt="Image" width="600" height="400" loading="lazy">
<em>create new keypair</em></p>
<ul>
<li>Give the key-pair a name and type. If you are using Windows, you can choose the .pem private key file format and use your Windows powershell to SSH into the server. Or you can use .ppk and you will need to download PuTTy, an application that helps give you SSH to remote servers. </li>
<li>Click the Create key pair once and the key pair is automatically downloaded on your local system (<strong>take note of the file download location</strong>)</li>
<li><strong>Network settings</strong> – We will need to setup some networking to allow access to our Jenkins instance, and to allow SSH, HTTP, and HTTPS. So click edit and scroll to <strong>inbound security groups rules</strong> to add the following rule (custom TCP at port 8080) and allow access to anywhere. For more security you can add access from only your IP or customize to your desire.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/network-sett.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/customTCP.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Custom TCP at Port range will allow us view our Jenkins instance from our browser after SSHing into it via public IP or DNS.</p>
<p>Leave <strong>Configure storage and Advance Details</strong> as Default.</p>
<p>Then click launch and wait for a bit for the instance to fully launch. Once it's launched click on the instance to find all your configurations (DNS, IPs, and so on)</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-163.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, for the final step, Remember the key-pair that was downloaded into our local system? We will need that now to SSH into this instance that has our Jenkins server.</p>
<p>Open your terminal (PowerShell for Windows users) and enter the following with your correct public-dns or (replace with public ip) and key-pair path:</p>
<pre><code>ssh -i /path/key-pair-name.pem ec2-user@instance-public-dns-name
</code></pre><p>Follow and accept the prompt to successfully login.</p>
<p>Enter <strong><code>sudo systemctl status jenkins</code></strong> to check the status of the Jenkins instance. If it's not running, start it by entering <strong><code>sudo systemctl start jenkins</code></strong> on the terminal.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/termnal-1.PNG" alt="Image" width="600" height="400" loading="lazy">
<em>Start jenkins instance</em></p>
<p>Now, let's launch our instance from the browser. Open your favourite browser and Enter '<strong>http://44.201.88.238:8080</strong>' to see the Jenkins instance:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Jenkins-instance-1.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Enter the following inside the EC2 instance terminal to see the password and login.</p>
<pre><code>sudo cat /<span class="hljs-keyword">var</span>/lib/jenkins/secrets/initialAdminPassword
</code></pre><p>Then unlock and enjoy all the features of your newly created Jenkins instance.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/get-started-1.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-congratulations-you-made-it">Congratulations, You Made It!</h2>
<p>You can see the full code for this tutorial here: </p>
<ul>
<li><a target="_blank" href="https://gist.github.com/Caesarsage/acd419178acec18010a2f5bc51996cec">packer.json</a></li>
<li><a target="_blank" href="https://gist.github.com/Caesarsage/5da51fff02bb945c932029c4ba48d064">setup.sh</a></li>
</ul>
<h2 id="heading-clean-up">Clean Up</h2>
<p>If you want to avoid unnecessary charges from AWS (based on resource use) and since we are not using this in production, we should clean up our resources.</p>
<p>To finish up, you can log into your AWS console‌‌ and terminate the EC2 instance launch with the custom image and delete the image if you are not using it.</p>
<h2 id="heading-summary">Summary</h2>
<p>In this tutorial, you have learned what infrastructure as a code is at a high level and how it apply in mutable/immutable practice. </p>
<p>You have  also learned how to create a custom image with a powerful too called Hashicorp Packer. For our case we customized a Jenkins server into the image but you can extend it with any custom configurations. </p>
<p>And finally we tested our image by launching an EC2 instance.</p>
<p>I hope this article helps you understand what infrastructure as a code is and how you can use it tools in your applications. Happy learning!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ OpenStack Tutorial – Operate Your Own Private Cloud (Full Course) ]]>
                </title>
                <description>
                    <![CDATA[ OpenStack is an open source software that provides cloud infrastructure for virtual machines, bare metal, and containers. In this article, you will learn how to use OpenStack to operate your own private cloud. By the end of the tutorial, you will hav... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/openstack-tutorial-operate-your-own-private-cloud/</link>
                <guid isPermaLink="false">66b205f3297cd6de0bd5468a</guid>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Mon, 11 Jul 2022 14:40:04 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/07/openstack.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>OpenStack is an open source software that provides cloud infrastructure for virtual machines, bare metal, and containers.</p>
<p>In this article, you will learn how to use OpenStack to operate your own private cloud.</p>
<p>By the end of the tutorial, you will have a core understanding of what OpenStack is and you will know the basics of setting up and administering OpenStack using the OpenMetal platform. You will also understand some commonly used OpenStack services.</p>
<p>In addition to creating this article version of the tutorial, I also created a video version. You can watch the video on the freeCodeCamp.org YouTube channel.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/_gWfFEuert8" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>To follow along with this tutorial, it can be helpful to have a basic understanding of the Linux command line, networking, and virtualization. But none of it is required.</p>
<p>Thanks to OpenMetal for sponsoring this tutorial.</p>
<h2 id="heading-what-is-openstack">What is OpenStack?</h2>
<p>OpenStack is an open source cloud computing platform that is used by organizations to manage and control large scale deployments of virtual machines, such as in a cloud computing or virtual private server environment. OpenStack is a popular choice for organizations because it is scalable, reliable, and provides a high degree of control over the underlying infrastructure.</p>
<p>Besides being used to manage deployments of virtual machines, OpenStack can also be used to manage storage and networking resources in a cloud environment.</p>
<p>In some ways OpenStack can be compared to AWS but here are some key differences between the two:</p>
<ul>
<li>OpenStack is an open source platform, while AWS is a proprietary platform.</li>
<li>OpenStack offers more flexibility and customization options than AWS.</li>
<li>OpenStack typically requires more technical expertise to set up and manage than AWS since you basically have to set up everything yourself.</li>
</ul>
<p>Let's go into more details about what OpenStack offers.</p>
<p>Beyond standard infrastructure-as-a-service functionality, additional components provide orchestration, fault &amp; service management, and other services to ensure high availability of user applications.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/openstack.svg" alt="Image" width="600" height="400" loading="lazy">
<em>OpenStack diagram.</em></p>
<p>OpenStack is broken up into services to allow you to plug and play components depending on your needs. The OpenStack map below shows common services and how they fit together.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/openstack-map.svg" alt="Image" width="600" height="400" loading="lazy">
<em>OpenStack map.</em></p>
<p>I won't cover every service but here is what some of the more common OpenStack services do.</p>
<p><strong>Object Storage</strong>: OpenStack Object Storage (Swift) is a highly scalable, distributed object storage system.</p>
<p><strong>Compute</strong>: OpenStack Compute (Nova) is a cloud computing fabric controller, which manages the allocation of compute resources.</p>
<p><strong>Networking</strong>: OpenStack Networking (Neutron) is a system for managing networks and IP addresses.</p>
<p><strong>Dashboard</strong>: The OpenStack Dashboard (Horizon) is a web-based interface for managing OpenStack resources.</p>
<p><strong>Identity</strong>: OpenStack Identity (Keystone) is a system for managing user accounts and access control.</p>
<p><strong>Image</strong>: OpenStack Image (Glance) is a service for storing and retrieving virtual machine images.</p>
<p><strong>Block Storage</strong>: OpenStack Block Storage (Cinder) is a service for managing block storage devices.</p>
<p><strong>Telemetry</strong>: OpenStack Telemetry (Ceilometer) is a service for collecting and storing metering data.</p>
<p><strong>Orchestration</strong>: OpenStack Orchestration (Heat) is a service for orchestration and cloud formation.</p>
<p><strong>Bare Metal</strong>: OpenStack Bare Metal (Ironic) is a service for provisioning and managing bare metal servers.</p>
<p><strong>Data Processing</strong>: OpenStack Data Processing (Sahara) is a service for provisioning and managing Hadoop and Spark clusters.</p>
<p>We will be demonstrating a few of the more common OpenStack services later int his course.</p>
<p>There are a bunch of different ways to deploy and configure OpenStack based on the needs of your application or organization.</p>
<p>In this course, we will learn how to get started with OpenStack and use many of the most common features. </p>
<p>One of the easiest ways to get started with OpenStack is by using the OpenMetal on-demand private cloud. This allows us to quickly deploy OpenStack to the cloud and simplifies the setup process. OpenMetal provided a grant that made this tutorial possible.</p>
<p>While we will be using OpenMetal to learn about OpenStack, the material covered in this tutorial applies to any OpenStack deployment, not just ones that use OpenMetal. So no matter how you want to use OpenStack, this tutorial is for you.</p>
<h2 id="heading-setting-up-openstack-on-openmetal">Setting Up OpenStack on OpenMetal</h2>
<p>To get OpenStack setup, you need to provision and set up your cloud on OpenMetal. Just follow the prompts on <a target="_blank" href="https://central.openmetal.io/">this OpenMetal Central page</a> to get everything setup.</p>
<p>When setting up, you will have to </p>
<p>OpenMetal Private Clouds are deployed with OpenStack to three bare metal servers. These three servers comprise the Private Cloud Core. To OpenStack, these three servers are considered the control plane. Private Clouds are deployed with Ceph, providing your cloud with shared storage. Ceph is an open source software-defined storage solution. </p>
<p>Let's view the hardware assets that were created on OpenMetal. If you just created a cloud, you may already be on on the cloud management page. If not, click "manage". Now click "Assets" on the left side menu.</p>
<p>This page contains a list of assets included with your Private Cloud Deployment. These include your Hardware Control Plane Nodes and IP blocks for Inventory and Provider IP addresses.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-51.png" alt="Image" width="600" height="400" loading="lazy">
<em>Assets Page of Cloud Management Dashboard for OpenMetal</em></p>
<p>The screenshot above is a list of assets in a Demo Private Cloud. Your Private Cloud can have different hardware based on the options you have selected in your deployment:</p>
<p>In this example, you will notice three main sections:</p>
<ul>
<li>3 Cloud Core <strong>mb_small_v1</strong> Control Plane Nodes</li>
<li>Inventory IP Address Blocks</li>
<li>Provider IP Address Blocks</li>
</ul>
<p>With these Private Clouds, OpenStack is deployed with three hyper-converged control plane nodes.</p>
<p>You can access these Control Plane Nodes directly through SSH as the root user. This access is done through the SSH keys you provided during your Private Cloud Deployment. Use this command to connect (you will have to change the key name and IP to match your information):</p>
<p><code>ssh -i ~/.ssh/your_key_name root@173.231.217.21</code></p>
<h2 id="heading-getting-started-with-openstack-horizon">Getting Started with OpenStack Horizon</h2>
<p>Horizon is the name of the default OpenStack dashboard, which provides a web based user interface to OpenStack services. It allows a user to manage the cloud. </p>
<p>To access your new cloud's OpenStack dashboard (called Horizon) you will need to obtain Horizon's administrator password. The username is "admin".</p>
<p>To begin, SSH into one of the cloud's servers (you can use any IP address from the "Assets" page). For example:</p>
<p><code>ssh -i ~/.ssh/your_key_name root@173.231.217.21</code></p>
<p>Once you are logged in to the server, run this command:</p>
<p><code>grep keystone_admin_password /etc/kolla/passwords.yml</code></p>
<p>The password will be shown in the output as in this example:</p>
<p><code>keystone_admin_password: aB0cD1eF2gH3iJ4kL5mN6oP7qR8sT9uV</code></p>
<p>Next, launch Horizon. On OpenMetal, you can click the "Horizon" tab on the left menu. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-52.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Login using "admin" and the password you just accessed.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-53.png" alt="Image" width="600" height="400" loading="lazy">
<em>Horizon dashboard.</em></p>
<h2 id="heading-create-a-project-in-openstack-horizon">Create a Project in OpenStack Horizon</h2>
<p>In OpenStack, the cloud is divided through the use of projects. Projects have associated with them users, who have differing levels of access, defined by roles. An administrator defines resource limits per project by modifying quotas. </p>
<p>Now we'll learn how to create a project and associate a user with it. And we will learn how project quotas can be adjusted. The Horizon interface will be similar no matter where you deploy OpenStack. This is not specific to OpenMetal.</p>
<p>There are three root-level tabs on the left menu in Horizon: Project, Admin, and Identity. Only users with administrative privileges can see the admin tab.</p>
<p>To create your first project, navigate to Identity -&gt; Projects.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-56.png" alt="Image" width="600" height="400" loading="lazy">
<em>Projects.</em></p>
<p>Several projects already exist, including the admin project. These projects are deployed by default and generally should not be modified.</p>
<p>Click the <strong>Create Project</strong> button near the top right to create a new project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-57.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Under the <strong>Name</strong> field, specify a name for the project. This example project is called <strong>Development</strong>. You can also add Project Members and Project Groups but we are not going to cover those yet. Click <strong>Create Project</strong> to finish creating the first project.</p>
<p>Once created, the project appears in the Project Listing page.</p>
<p>While in the project listing page, you can view and adjust quotas for this project as the <strong>admin</strong> user. Quotas are limits on resources, like the number of instances.</p>
<p>To view the quotas for this project while in <strong>Identity -&gt; Projects</strong> tab, find the drop down to the right with the first option being <strong>Manage Members</strong>. From this menu, click <strong>Modify Quotas</strong> to view the default quota values.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-58.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You will see a form with several tabs and you are presented with the quotas for the Compute service. Quotas exist for the Volume and Network services as well.</p>
<p>You may want to adjust the parameters in this form depending on your workload. Setting a value to <code>-1</code> means that quota is unlimited.</p>
<h2 id="heading-how-to-create-a-user-and-associate-with-project">How to Create a User and Associate with Project</h2>
<p>Now that you have a project, you can associate a user with it. There is already the default <strong>admin</strong> user but now let's see how to create a new user and login with the new user.</p>
<p>First navigate as <strong>admin</strong> to <strong>Identity -&gt; Users</strong>. By default, there are several users already listed, and this is expected. These are created during cloud deployment and should generally not be modified.</p>
<p>Click the <strong>Create User</strong> button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-74.png" alt="Image" width="600" height="400" loading="lazy">
<em>Users tab.</em></p>
<p>On the Create User form set values for <strong>User Name</strong>, <strong>Password</strong>, <strong>Primary Project</strong>, and <strong>Role</strong>. The <strong>Email</strong> field is optional but is helpful for password resets. For the <strong>Project</strong> choose the project we created earlier.</p>
<p>For <strong>Role</strong> there are several options depending on the level of access required. The default OpenStack roles are <strong>reader</strong>, <strong>member</strong>, and <strong>admin</strong>. Additional roles also exist in the drop down. <strong>Reader</strong> is the least authoritative role in the hierarchy. For this example, choose <strong>member</strong> for the role. </p>
<p>Press <strong>Create User</strong> to create the user.</p>
<p>Next, log out of Horizon as <strong>admin</strong>, and log back in with your new user. Upon logging back in you are by default in the newly created project. You can see the project you are currently in at the top left and your user can be seen at the top right of Horizon.</p>
<h2 id="heading-managing-and-creating-images">Managing and Creating Images</h2>
<p>Now let's learn how to upload an image (not a graphical image but a copy of a Linux installation) into OpenStack as well as create images out of an existing instance.</p>
<p>Images contain a bootable operating system that is used to create instances. Within your OpenMetal Cloud, there are several different images that are readily available including CentOS, Debian, Fedora, and Ubuntu. In addition to this, you have the option to upload images from other sources or create your images.</p>
<p>We will learn how to upload images to Glance through Horizon and how to create an image from an instance snapshot. Glance is tool for managing images that allows users to discover, retrieve, and register VM (virtual machine) images and container images. Glance uses Ceph to store images instead of the local file system. </p>
<p>To access images from within your Horizon Dashboard, navigate to the <strong>Projects</strong> tab. Within the projects tab, select <strong>Compute</strong> and then select <strong>Images</strong>. This tab contains a list of all your images within OpenStack.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-75.png" alt="Image" width="600" height="400" loading="lazy">
<em>Images</em></p>
<p>Images can be uploaded through your Horizon dashboard by clicking the <strong>Create Image</strong> button. When creating an image you must choose the Format of the image. With our configuration, the recommended format for images is <strong>QCOW2 – QEMU emulator</strong>. QCOW2 is the most common format for Linux KVM, expands dynamically, and supports copy on write. </p>
<p>In order to upload an image on Horizon, you must first have the image locally on your machine. In this example, we will upload a CirrOS image. You can download a <a target="_blank" href="https://download.cirros-cloud.net/0.5.2/cirros-0.5.2-x86_64-disk.img">CirrOS image here</a>.</p>
<p>Now click the <strong>Create Image</strong> button near the top right.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-85.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>For this example we'll use the following values for the fields:</p>
<ul>
<li><strong>Image Name</strong>: Name of the image</li>
<li><strong>Image Description</strong>: Optional description of the image</li>
<li><strong>File</strong>: The source file on your machine</li>
<li><strong>Format</strong>: QCOW2 – QEMU Emulator</li>
</ul>
<p>Fill out the details as needed and submit the form. It may take some time to complete uploading the image.</p>
<h2 id="heading-create-an-instance-in-openstack-horizon">Create an Instance in OpenStack Horizon</h2>
<p>With OpenStack, instances, or virtual machines, play a large role in a cloud’s workload. OpenStack provides a way to create and manage instances with its compute service, called <a target="_blank" href="https://docs.openstack.org/nova/latest/">Nova</a>.</p>
<p>Nova is the OpenStack project that provides a way to provision compute instances (aka virtual servers). Nova supports creating virtual machines, baremetal servers, and has limited support for system containers. Nova runs as a set of daemons on top of existing Linux servers to provide that service.</p>
<p>Now let's learn how to create an instance, including setting up a private network and router, creating a security group, and how to add an SSH key pair.</p>
<h3 id="heading-create-a-private-network">Create a Private Network</h3>
<p>First, let's learn how to create a private network and router. Later we will create an instance on this private network. The router is created so the private network can be connected to your cloud’s public network, allowing you to assign a floating IP address to it, making the instance accessible over the Internet.</p>
<p>To create a private network, begin by navigating to <strong>Project -&gt; Network -&gt; Networks</strong>. Then click <strong>Create Network</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-86.png" alt="Image" width="600" height="400" loading="lazy">
<em>Networks tab.</em></p>
<p>For this example, we'll create a network with the following details:</p>
<ul>
<li><strong>Network Name</strong>: Set a name for the network. This example is called <strong>Private</strong>.</li>
<li><strong>Enable Admin State</strong>: Leave this checked to enable the network.</li>
<li><strong>Create Subnet</strong>: Leave this checked to create a subnet.</li>
<li><strong>Availability Zone Hints</strong>: Leave this option as default.</li>
</ul>
<p>Next, move on to the <strong>Subnet</strong> tab of this form and use these details:</p>
<ul>
<li><strong>Subnet Name</strong>: Set a name for the subnet. This example subnet is called <strong>private-subnet</strong>.</li>
<li><strong>Network Address</strong>: Select a private network range. For example: <code>192.168.0.1/24</code></li>
<li><strong>IP Version</strong>: Leave this as IPv4.</li>
<li><strong>Gateway IP</strong>: This is optional. If unset, a gateway IP is selected automatically.</li>
<li><strong>Disable Gateway</strong>: Leave this unchecked.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-87.png" alt="Image" width="600" height="400" loading="lazy">
<em>Create a network.</em></p>
<p>For now we will keep the default details in the <strong>Subnet Details</strong> tab.</p>
<p>Click <strong>Create</strong> to create the network. Once created, it appears in the list of networks.</p>
<h3 id="heading-create-a-router">Create a Router</h3>
<p>You next need to create a router to bridge the connection between the private network and the public network. The public network is called <strong>External</strong>.</p>
<p>To create a router, begin by navigating to <strong>Project -&gt; Network -&gt; Routers</strong>. Click <strong>Create Router</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-88.png" alt="Image" width="600" height="400" loading="lazy">
<em>Routers.</em></p>
<p>Input this data for this example:</p>
<ul>
<li><strong>Router Name</strong>: Set a name for the router here. This example router is called <strong>Router</strong>.</li>
<li><strong>Enable Admin State</strong>: Leave this checked to enable the router.</li>
<li><strong>External Network</strong>: Choose the network <strong>External</strong>.</li>
<li><strong>Availability Zone Hints</strong>: Leave this as the default.</li>
</ul>
<p>Once complete, create the router by pressing <strong>Create Router</strong>.</p>
<h3 id="heading-connect-router-to-private-network">Connect Router to Private Network</h3>
<p>Next, connect the router to the private network by attaching an interface. Performing this step allows network communication between the Private and External networks.</p>
<p>To attach an interface to the router, first navigate to the list of routers and locate the one previously created.</p>
<p>Click the name of the router to access its details page. This is where the interface is attached. There are three tabs: <strong>Overview</strong>, <strong>Interfaces</strong>, and <strong>Static Routes</strong>. To attach an interface, navigate to the <strong>Interfaces</strong> tab then load the form to attach an interface by clicking <strong>Add Interface</strong> near the top right.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-89.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>On the new interface, choose the private-subnet for <strong>Subnet</strong>. If you don't set an IP address one is selected automatically. Press <strong>Submit</strong> to attach the <strong>Private</strong> network to this router. The interface is then attached and now listed.</p>
<p>You can visually see the network topology for your cloud by navigating to <strong>Project -&gt; Network -&gt; Network Topology</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-90.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The example above indicates the <strong>External</strong> network is connected to the <strong>Private</strong> network through the router called <strong>Router</strong>.</p>
<h3 id="heading-security-groups">Security Groups</h3>
<p>Security groups allow control of network traffic to and from instances. For example, port 22 can be opened for SSH for a single IP or a range of IPs. </p>
<p>Let's see how to create a security group for SSH access. Later we'll apply the security group we create to an instance.</p>
<p>To view and manage security groups, navigate to <strong>Project -&gt; Network -&gt; Security Groups</strong>.</p>
<p>You should notice a single security group called <strong>default</strong>. This security group restricts all incoming (ingress) network traffic and allows all outgoing (egress) network traffic. When an instance is created, this security group is applied by default. To allow the network traffic your instance requires, only open ports as required to just the needed IP ranges.</p>
<p>To create a security group for SSH, click <strong>Create Security Group</strong> near the top right.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-92.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Name the group <strong>SSH</strong> and then click <strong>Create Security Group</strong></p>
<p>After creating the SSH security group, we need to add a rule allowing SSH traffic. We will allow SSH traffic from the first hardware node in this cloud to this instance.</p>
<p>To add a rule, load the form by navigating to <strong>Add Rule</strong> near the top right.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-93.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We'll need to obtain the IP address of the first hardware node of your cloud. You can find this using <a target="_blank" href="https://central.openmetal.io/">OpenMetal Central</a> under your cloud’s <a target="_blank" href="https://central.openmetal.io/documentation/operators-manual/introduction-to-openmetal-central-and-your-private-cloud-core#how-to-view-your-hardware-assets">Assets Page</a>. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-94.png" alt="Image" width="600" height="400" loading="lazy">
<em>The IP address for the first hardware node.</em></p>
<p>In the <strong>Add Rule</strong> menu, add the following information:</p>
<ul>
<li><strong>Rule</strong>: Select <strong>SSH</strong>. When adding rules you can choose from predefined options. In this case, we choose the <strong>SSH</strong> rule from the first drop down.</li>
<li><strong>Description</strong>: Optional. Provide a description of the rule.</li>
<li><strong>Remote</strong>: Select <strong>CIDR</strong>.</li>
<li><strong>CIDR</strong>: Specify the IP address of your first hardware node.</li>
</ul>
<p>Press <strong>Add</strong> to add this rule to the security group.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-95.png" alt="Image" width="600" height="400" loading="lazy">
<em>Adding a rule.</em></p>
<h2 id="heading-create-an-instance">Create an Instance</h2>
<p>We now have almost everything in place to create an instance. </p>
<p>We will need an SSH public key. An SSH public key is required to access an instance over SSH. This key is injected into the instance when created. An SSH key cannot be added to an already running instance.</p>
<p>We will create an instance that can be accessed over SSH from one of the cloud’s hardware nodes. So we will have to create an SSH key pair in one of the hardware nodes. The public portion of that key pair is associated with the instance we'll create soon. </p>
<p><strong>** To learn how to create this key pair, see the supplementary guide: <a target="_blank" href="https://central.openmetal.io/documentation/operators-manual/create-ssh-key-pair-for-an-openstack-control-plane-node/">Create SSH Key Pair for an OpenStack Control Plane Node</a>.</strong></p>
<p>To create an instance, begin by navigating to <strong>Project -&gt; Compute -&gt; Instances</strong>. Then click the <strong>Launch Instance</strong> button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-96.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>On the details tab, fill in the following details:</p>
<ul>
<li><strong>Instance Name</strong>: Set a name for the instance. This example instance is called <strong>Jumpstation</strong>.</li>
<li><strong>Description</strong>: Optional. Set a description if this applies.</li>
<li><strong>Availability Zone</strong>: Leave as the default, which is <strong>nova</strong>.</li>
<li><strong>Count</strong>: Controls the number of instances spawned. Just create 1.</li>
</ul>
<p>Next, move to the <strong>Source</strong> tab allowing you to specify an operating system image. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-97.png" alt="Image" width="600" height="400" loading="lazy">
<em>Source tab.</em></p>
<p>Fill in the following details:</p>
<ul>
<li><strong>Select Boot Source</strong>: In this example, we use <strong>Image</strong> as the boot source.</li>
<li><strong>Create New Volume</strong>: Leave this checked as <strong>Yes</strong>. This creates a new Cinder volume where the specified operating system image is copied into it. The volume ultimately exists with the Ceph cluster, in the <code>vms</code> pool.</li>
<li><strong>Volume Size</strong>: Allow the system to determine this for you.</li>
<li><strong>Delete Volume on Instance Delete</strong>: Leave this option set as <strong>No</strong>. If checked, when the instance is deleted, the volume is as well.</li>
<li>Under the <strong>Available</strong> section, select the appropriate operating system. This example uses <code>CentOS 8 Stream (el8-x86_64)</code>. Clicking the up arrow will move it to the <strong>Allocated</strong> section.</li>
</ul>
<p>This concludes configuring the instance’s source. Next, move to the <strong>Flavor</strong> tab.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-100.png" alt="Image" width="600" height="400" loading="lazy">
<em>Flavor tab.</em></p>
<p>Flavors are a way to define the VCPUs, RAM, and Disk space used by an instance. Pre-built flavors are available for you. For this step, select an appropriate flavor from the options under the Available heading. This example uses the m1.small flavor. Click the up arrow to move it to the <strong>Allocated</strong> section.</p>
<p>Next, move to the Networks tab.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-101.png" alt="Image" width="600" height="400" loading="lazy">
<em>Network tab.</em></p>
<p>In this section, you specify the network with which the instance is associated. For this example, select the <strong>Private</strong> network created previously. You can choose the <strong>External</strong> network as well, but this is generally recommended against in favor of using a floating IP should your instance require Internet connectivity.</p>
<p>You should only expose portions of your network as necessary. This reduces the attack surface and improves application security. If a private network is not created and an instance is created in a default cloud, it is associated with the <strong>External</strong> network. This means the instance consumes a public IP and it could be reached over the Internet.</p>
<p>Next, skip over the <strong>Network Ports</strong> tab and move to the <strong>Security Groups</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-102.png" alt="Image" width="600" height="400" loading="lazy">
<em>Security Groups tab.</em></p>
<p>This is where you select security groups for the instance. This example uses the <strong>SSH</strong> security group in the <strong>Available</strong> section. Click the up arrow to move the SSH security group to the <strong>Allocated</strong> section.</p>
<p>As the final step, move to the <strong>Key Pair</strong> tab.</p>
<p>In this section, you specify an SSH public key to inject into the instance. You can upload your key at this stage using this form using the <strong>Import Key Pair</strong> button. You can also create a key pair on this tab. </p>
<p>We will create a key pair from the first hardware node in our cloud so this instance will be accessible over SSH from that node.</p>
<p>To create the SSH key pair from the first hardware node, the first step is to login to the first hardware node. You can get the IP of the hardware node from the Assets tab on OpenMetal. We already connected to this node toward the beginning of the tutorial and the command is the same. It will look something like this:</p>
<p><code>ssh -i ~/.ssh/your_key_name root@173.231.217.21</code></p>
<p>After logging in to the node, use <code>ssh-keygen</code> to generate an SSH key pair. </p>
<p>For example:</p>
<pre><code>[root@modest-galliform ~]# ssh-keygen
Generating public/private rsa key pair.
Enter file <span class="hljs-keyword">in</span> which to save the key (<span class="hljs-regexp">/root/</span>.ssh/id_rsa): 
Enter passphrase (empty <span class="hljs-keyword">for</span> no passphrase): 
Enter same passphrase again: 
Your identification has been saved <span class="hljs-keyword">in</span> /root/.ssh/id_rsa.
Your public key has been saved <span class="hljs-keyword">in</span> /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:BNIzHPcqCyjjZqWm88s0zqHrj8J8+gUnkF1cNOEDKZs root@modest-galliform.local
The key<span class="hljs-string">'s randomart image is:
+---[RSA 3072]----+
|    o=**o        |
|  o..+Bo..       |
| o .+  =. .      |
|  .E   ...       |
|o .+... S        |
|.oo +. o         |
|o*+  ..          |
|BO.+.            |
|*B@+             |
+----[SHA256]-----+</span>
</code></pre><p>The private key is saved in the default location of <code>/root/.ssh/id_rsa</code> and a passphrase is set for additional security.</p>
<p>To view the contents of the public key, use <code>cat /root/.ssh/id_rsa.pub</code>.</p>
<p>For example:</p>
<pre><code>[root@modest-galliform ~]# cat /root/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCv6YOgYbRmXCEFxZP+t+pzh/RRKzsgWpvcnmKwF+uwiKDuihHadScCkgd8dE6ymCjP/+UVdVLGEzXfHXG5EfbcPQYOGjqqOGqOVCHIrhFMG3GjSPao99KaDIAvXsWyTDI9FmrXTiC+<span class="hljs-number">0</span>WkmOLNb0UeDic+lQ6KJumw12O1niZjC19jMpWR5amRWEJo6oKFylC8JLHsdfhqr7EBcBzvUJkqh/<span class="hljs-number">1</span>zY3qcsABHBrBCWOKC5oNiDAzctQ5MeHq6tv6w6YxdZLLdupczteERN6roroySMtR2JZnOIcnq1aUgD/YDJDeg9zpvUN7stsndONYVOH42+bBu7xEWsm8zobgdfLlmhv+<span class="hljs-number">8</span>ab7dKVlYvJUkITqCoKpp8m0f3dbLtQSevCJ9qaeQvmxkjU9OHVPkkTolw4aUHvUsutpVynNfmErf3RGMjQRiQ3ZE7xGKVV7iSFDK9l0mMWBHpYu2OnVKQlP823IC0YKD2dP3qDd/nnvGXVlxfRI+C08n9ehoHwZAIz4SM3dU= root@modest-galliform.local
</code></pre><p>Copy the entire key. It starts with "ssh-rsa" and continues all the way until the end.</p>
<p>Now back to the <strong>Key Pair</strong> tab. Click <strong>Import Key Pair.</strong></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-103.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Input the following values:</p>
<ul>
<li><strong>Key Pair Name</strong>: Set a name for the SSH public key. This example public key is called <code>jumpstation-key</code> but it can really be anything you like.</li>
<li><strong>Key Type</strong>: This example uses an <strong>SSH Key</strong> key type.</li>
<li><strong>Public Key</strong>: Paste in the public key you just copied.</li>
</ul>
<p>Click <strong>Import Key Pair</strong>.</p>
<p>Once the public key is imported, create the instance by pressing <strong>Launch Instance</strong>. (The other tabs our outside the scope of this demonstration.) </p>
<p>The instance goes through a build process. Allow a few minutes for this to occur. When complete, the instance appears in the <strong>Instances Listing</strong> page.</p>
<h3 id="heading-assign-and-attach-floating-ip">Assign and Attach Floating IP</h3>
<p>The instance created previously is associated with a private network. Presently, the only way to access this instance is to connect to it from with the cloud’s hardware nodes. Another option for connecting is to use a floating IP. In this section, we demonstrate how to allocate a floating IP and attach it to this instance.</p>
<p>To allocate a floating IP, first navigate to <strong>Project -&gt; Network -&gt; Floating IPs</strong>. Then click <strong>Allocate IP to Project</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-104.png" alt="Image" width="600" height="400" loading="lazy">
<em>Floating IPs tab.</em></p>
<p>In the popup, make sure <strong>Pool</strong> is set to <strong>External</strong> (and optionally add a description) and then click <strong>Allocate IP</strong> to add this floating IP address for use.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-105.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In the same section, allocate the IP to the Jumpstation instance by clicking the <strong>Associate</strong> button at the far right.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-106.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Fill in the details:</p>
<ul>
<li><strong>IP Address</strong>: This field comes pre-selected with the floating IP so there’s no need to change anything here.</li>
<li><strong>Port to be associated</strong>: Select the instance created previously. In this case, we use the Jumpstation instance.</li>
</ul>
<p>Click <strong>Associate</strong>. This instance is now accessible over SSH from the first hardware node of your cloud. </p>
<p>To login to this instance, after you login to your hardware node, run the following command [you will have to change the IP address to the one you just associated] :</p>
<p><code>ssh -i /root/.ssh/id_rsa centos@173.231.255.40</code></p>
<p>It should look something like this in your terminal:</p>
<pre><code>[root@modest-galliform ~]# ssh -i /root/.ssh/id_rsa centos@<span class="hljs-number">173.231</span><span class="hljs-number">.255</span><span class="hljs-number">.40</span>
The authenticity <span class="hljs-keyword">of</span> host <span class="hljs-string">'173.231.255.40 (173.231.255.40)'</span> can<span class="hljs-string">'t be established.
ECDSA key fingerprint is SHA256:z45zzE8fPuKagtyrSGP9AWR4vIVpBppoaqkqj1Kx4SA.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '</span><span class="hljs-number">173.231</span><span class="hljs-number">.255</span><span class="hljs-number">.40</span><span class="hljs-string">' (ECDSA) to the list of known hosts.
Enter passphrase for key '</span>/root/.ssh/id_rsa<span class="hljs-string">': 
Activate the web console with: systemctl enable --now cockpit.socket

[centos@jumpstation ~]$</span>
</code></pre><p>In the next section, you will have to do something while logged in to this machine.</p>
<h3 id="heading-how-to-install-and-use-openstacks-cli">How to Install and Use OpenStack’s CLI</h3>
<p>So far we've been learning how to manage OpenStack through a web browser. But it is also possible to manage through the command line using OpenStack’s CLI called OpenStackClient.</p>
<p>Using the command line to manage your cloud introduces more flexibility in automation tasks and can generally make an administrator’s life simpler. Let's learn how.</p>
<p>We'll now install the OpenStackClient on the instance we just created that we named Jumpstation.</p>
<p>Before installing OpenStackClient, you must obtain two files from Horizon, which are required to prepare your shell environment. Those two files are <code>clouds.yaml</code> and the OpenStack RC file.</p>
<ul>
<li><code>clouds.yaml</code>: Used as a source of configuration for how to connect to a cloud</li>
<li>OpenStack RC file: Used as a source of authentication for your user and project</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-141.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>To collect these files, log in to Horizon as your user. Navigate to <strong>Project -&gt; API Access.</strong> Then click <strong>Download OpenStack RC File</strong> and download the OpenStack <code>clouds.yaml</code> and the <code>OpenStack RC</code> files to your machine. The files are associated with the current user and project that user is in.</p>
<h4 id="heading-prepare-and-install-openstackclient">Prepare and Install OpenStackClient</h4>
<p>Next, use SSH to log in to the instance created previously. If you've been following along, then this instance can only be accessed from one of your control plane nodes. Use the instructions recently given to login to the instance.</p>
<p>Here are the steps to prepare and install the OpenStackClient, after you are logged in.</p>
<p><strong>Step 1</strong>: Prepare <code>clouds.yaml</code> and OpenStack RC files</p>
<p>The <code>clouds.yaml</code> file obtained previously must be prepared in this instance. For this demonstration, we'll save it to <code>~/.config/openstack/clouds.yaml</code>. We will have to copy the contents of <code>clouds.yaml</code> that we downloaded to our machine from Horizon and store it as <code>~/.config/openstack/clouds.yaml</code>.</p>
<p>Here is how you create the directory and then edit the file. Run the commands after the <code>$</code> on your command line.</p>
<pre><code># Create the following directory
$ mkdir -p ~<span class="hljs-regexp">/.config/</span>openstack

# Create and open the clouds.yaml to edit
$ vi ~<span class="hljs-regexp">/.config/</span>openstack/clouds.yaml
</code></pre><p>To get the contents from the <code>clouds.yaml</code> on your local computer into the instance, you will first have to open up the local version in a text editor. Then you will have to copy all the text and then paste it into the version you just created on the instance.</p>
<p>After you paste the text of the file into the vi editor in you terminal, use the command <code>:wq</code> to save and quit the editor.</p>
<p>The <code>clouds.yaml</code> file can actually be placed in several locations. For more see the <a target="_blank" href="https://docs.openstack.org/python-openstackclient/victoria/configuration/index.html#configuration-files">Configuration Files</a> heading of the OpenStack Victoria’s documentation.</p>
<p>Next, copy the contents of the OpenStack RC file from your local machine into the instance. The file can be placed anywhere so for this example we will store it in the user's home directory. The full path will be <code>~/Development-openrc.sh</code>.</p>
<p>Create and start editing the file with the following command (don't use the <code>$</code> when you copy and paste the command).</p>
<pre><code>$ vi ~/Development-openrc.sh
</code></pre><p>Just like before, you will have to open up the local version of the file, copy all the text, then paste it into the instance version of the file that is open in your terminal. Then, use the command <code>:wq</code> to save and quit the editor.</p>
<p>Now you have to run the file. First change the permissions:</p>
<p><code>$ chmod +x Development-openrc.sh</code></p>
<p>Then, run the file:</p>
<p><code>$ ./Development-openrc.sh</code></p>
<p>You will have to enter your OpenStack password. </p>
<p>Then run the command:</p>
<p><code>$ source Development-openrc.sh</code></p>
<p><strong>Step 2</strong>: Create a Python virtual environment</p>
<p>We'll create a virtual environment so we don't interfere with the system’s Python version.</p>
<p>In a default CentOS 8 Stream installation, the system’s Python executable is <code>/usr/libexec/platform-python</code> and is what will be used to create the virtual environment.</p>
<p>Use <code>/usr/libexec/platform-python -m venv ~/venv</code> to create a virtual environment in path <code>~/venv</code>.</p>
<p>For example:</p>
<pre><code>$ /usr/libexec/platform-python -m venv ~/venv
</code></pre><p><strong>Step 3</strong>: Activate the Python virtual environment</p>
<p>Use <code>source ~/venv/bin/activate</code> to activate the virtual environment.</p>
<p>For example:</p>
<pre><code>$ source ~<span class="hljs-regexp">/venv/</span>bin/activate
</code></pre><p>After you have activated the virtual environment, the name of it will appear at the beginning of the command line. So it will look something like this:</p>
<pre><code>[centos@jumpstation ~]$ /usr/libexec/platform-python -m venv ~/venv
[centos@jumpstation ~]$ source ~<span class="hljs-regexp">/venv/</span>bin/activate
(venv) [centos@jumpstation ~]$
</code></pre><p><strong>Step 4</strong>: Upgrade <code>pip</code></p>
<p>Before installing OpenStackClient and to aid in a smooth installation, upgrade <code>pip</code>. Upgrade <code>pip</code> by using <code>pip install --upgrade pip</code>.</p>
<p>For example:</p>
<pre><code>$ pip install --upgrade pip
</code></pre><p><strong>Step 5</strong>: Install OpenStackClient</p>
<p>With everything prepared, OpenStackClient can be installed.</p>
<p><strong>Note!</strong> – There exist two OpenStackClient packages: <code>python-openstackclient</code> and <code>openstackclient</code>. I recommend using <code>python-openstackclient</code> because it is maintained much more frequently than the prior package.</p>
<p>Install OpenStackClient using:</p>
<pre><code>$ pip install python-openstackclient
</code></pre><p><strong>Step 6</strong>: List servers associated with your project</p>
<p>For an initial command, list the servers associated with your project by running <code>openstack server list</code>.</p>
<p>For example:</p>
<pre><code>(venv) [centos@jumpstation ~]$ openstack server list
+--------------------------------------+-------------+--------+---------------------------------------+--------------------------+----------+
| ID                                   | Name        | Status | Networks                              | Image                    | Flavor   |
+--------------------------------------+-------------+--------+---------------------------------------+--------------------------+----------+
| <span class="hljs-number">412</span>fc87f-a4d9<span class="hljs-number">-40</span>f1-ba07-fe3eee216c38 | Jumpstation | ACTIVE | Private=<span class="hljs-number">173.231</span><span class="hljs-number">.255</span><span class="hljs-number">.40</span>, <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.140</span> | N/A (booted <span class="hljs-keyword">from</span> volume) | m1.small |
+--------------------------------------+-------------+--------+---------------------------------------+--------------------------+----------+
</code></pre><p>Here, we can see the server created previously.</p>
<h2 id="heading-command-structure">Command Structure</h2>
<p>When using OpenStackClient, there is typically a common command pattern for what you want to accomplish. All <code>openstack</code> commands begin with <code>openstack</code>. You can execute <code>openstack</code> by itself to enter into a shell, where commands no longer need to be prefixed by <code>openstack</code>:</p>
<pre><code>(venv) [centos@jumpstation ~]# openstack
(openstack)
</code></pre><h3 id="heading-list-all-available-subcommands">List all Available Subcommands</h3>
<p>Use <code>openstack --help</code> to list all available subcommands. You initially see all the flags you can pass, but after scrolling a bit, the subcommand list starts:</p>
<pre><code>Commands:
  access rule <span class="hljs-keyword">delete</span>  Delete access rule(s)
  access rule list  List access rules
  access rule show  Display access rule details
  access token create  Create an access token
  acl <span class="hljs-keyword">delete</span>  Delete ACLs <span class="hljs-keyword">for</span> a secret or container <span class="hljs-keyword">as</span> identified by its href. (py
thon-barbicanclient)
[...output truncated...]
</code></pre><h3 id="heading-learn-more-about-a-subcommand">Learn more about a Subcommand</h3>
<p>After seeing available commands, learn more about a command by using <code>openstack help &lt;command&gt;</code>.</p>
<p>For example, to learn more about the <code>openstack server</code> command, use <code>openstack help server</code>:</p>
<pre><code>$ openstack help server
Command <span class="hljs-string">"server"</span> matches:
  server add fixed ip
  server add floating ip
  server add network
  server add port
  server add security group
[...output truncated...]
</code></pre><h3 id="heading-list-items-and-show-details">List Items and Show Details</h3>
<p>It is very common when using OpenStackClient to list items and the command form is typically <code>openstack &lt;subcommand&gt; list</code>. For example, <code>openstack server list</code>, lists all servers for the currently configured project.</p>
<p>Furthermore, more information about an item can be found by typically running <code>openstack &lt;subcommand&gt; show &lt;item&gt;</code>. For example, <code>openstack server show Jumpstation</code> shows the details about the instance named <strong>Jumpstation</strong>.</p>
<h2 id="heading-how-private-clouds-are-deployed">How Private Clouds are Deployed</h2>
<p>Now we'll learn more about how your Private Cloud was deployed and learn more about the environment. OpenStack can be deployed in several different ways and this section highlights the characteristics of your Private Cloud. We also explain some of the advantages for this type of deployment and areas that are unique to OpenMetal.</p>
<p>In OpenMetal, OpenStack is containerized through Docker using Kolla Ansible. This is done through an initial deployment container called FM-Deploy. FM-Deploy provides the initial setup changes during the provisioning process of your Private Cloud. The FM-Deploy Container is a necessary part of the current architecture of your Private Cloud. The FM-Deploy Container should remain running in your Private Cloud as it is used by our systems in the event you want to add or remove nodes from your cloud.</p>
<h3 id="heading-containerization-of-openstack">Containerization of OpenStack</h3>
<p>OpenMetal uses Kolla Ansible to set up Docker containers for all running services. Should you need to make any configuration changes to your nodes, Kolla Ansible should be used to push these changes. If Kolla Ansible is not used then there is a risk of these changes being reverted during any system updates.</p>
<p>Some advantages of containerization through docker are:</p>
<ul>
<li>Containers create an isolated environment reducing software dependencies</li>
<li>Containers can be scaled and allow for services to balance across your cluster</li>
<li>Containers provide increased flexibility for test releases, patches, and automation</li>
<li>Containers have a consistent and repeatable deployment and a shorter initialization time</li>
</ul>
<h3 id="heading-disk-storage-and-ceph">Disk Storage and Ceph</h3>
<p>In OpenMetal, disk storage is provided through Ceph. Ceph is an object storage interface that can provide interfaces for multiple different storage types on a single cluster. In OpenMetal Ceph is comprised of two elements: object storage and block storage.</p>
<p>Ceph <strong>object storage</strong> utilizes Ceph Object Storage Gateway daemon (RADOSGW). With OpenMetal clouds, Ceph’s RGW replaces Swift so there is no Docker container for Swift. Instead, Swift endpoints are connected directly to the RGW. Authentication for RGW is handled through Keystone in <code>/etc/ceph/ceph.conf</code>.</p>
<p>Ceph <strong>block storage</strong> connects to the Cinder service utilizing Ceph’s RADOS Block Device. Within your cloud, those objects are stored in Ceph pools. Ceph provides a layer of abstraction that allows objects to be recognized as blocks.</p>
<p>Some advantages of using ceph are:</p>
<ul>
<li>Data is self-healing and will redistribute data across your cluster in the event of power, hardware, or connectivity issues</li>
<li>Data is replicated and highly available</li>
<li>Ceph has the ability to run on commodity hardware and to mix hardware from different vendors</li>
</ul>
<h2 id="heading-introduction-to-ceph">Introduction to Ceph</h2>
<p>Ceph is an open-source, distributed storage system that provides object, block and file storage interfaces from a single cluster. </p>
<p>Ceph was selected as the storage solution for Private Cloud Core OpenStack clouds due to its ability store data in a replicated fashion. The data stored in the Ceph cluster is accessible from any of your cloud’s control plane nodes. The storage is considered shared across all nodes, which can make recovering an instance and its data trivial. </p>
<p>Let's learn how to check the status of your Ceph cluster and see available disk usage using the command line.</p>
<h3 id="heading-check-ceph-status">Check Ceph Status</h3>
<p>First, make sure you are logged into one of your cloud’s control plane nodes (not an instance). To check the status of your Ceph cluster, use <code>ceph status</code>.</p>
<p>For example:</p>
<pre><code>[root@modest-galliform ~]# ceph status
  <span class="hljs-attr">cluster</span>:
    id:     ac5c03ba-fcb8<span class="hljs-number">-4963</span>-b235-e9020b5bfcc2
    <span class="hljs-attr">health</span>: HEALTH_OK

  <span class="hljs-attr">services</span>:
    mon: <span class="hljs-number">3</span> daemons, quorum modest-galliform,gifted-badger,hopeful-guineafowl (age <span class="hljs-number">7</span>d)
    <span class="hljs-attr">mgr</span>: hopeful-guineafowl(active, since <span class="hljs-number">7</span>d), <span class="hljs-attr">standbys</span>: modest-galliform, gifted-badger
    <span class="hljs-attr">osd</span>: <span class="hljs-number">3</span> osds: <span class="hljs-number">3</span> up (since <span class="hljs-number">7</span>d), <span class="hljs-number">3</span> <span class="hljs-keyword">in</span> (since <span class="hljs-number">7</span>d)
    <span class="hljs-attr">rgw</span>: <span class="hljs-number">3</span> daemons active (gifted-badger.rgw0, hopeful-guineafowl.rgw0, modest-galliform.rgw0)

  task status:

  data:
    pools:   <span class="hljs-number">12</span> pools, <span class="hljs-number">329</span> pgs
    <span class="hljs-attr">objects</span>: <span class="hljs-number">2.06</span>k objects, <span class="hljs-number">9.4</span> GiB
    <span class="hljs-attr">usage</span>:   <span class="hljs-number">31</span> GiB used, <span class="hljs-number">2.6</span> TiB / <span class="hljs-number">2.6</span> TiB avail
    <span class="hljs-attr">pgs</span>:     <span class="hljs-number">329</span> active+clean

  <span class="hljs-attr">io</span>:
    client:   <span class="hljs-number">2.2</span> KiB/s rd, <span class="hljs-number">2</span> op/s rd, <span class="hljs-number">0</span> op/s wr
</code></pre><h3 id="heading-check-ceph-disk-usage">Check Ceph Disk Usage</h3>
<p>To check the available disk space in your Ceph cluster, use <code>ceph df</code>.</p>
<p>For example:</p>
<pre><code>[root@modest-galliform ~]# ceph df
--- RAW STORAGE ---
CLASS  SIZE     AVAIL    USED    RAW USED  %RAW USED
ssd    <span class="hljs-number">2.6</span> TiB  <span class="hljs-number">2.6</span> TiB  <span class="hljs-number">28</span> GiB    <span class="hljs-number">31</span> GiB       <span class="hljs-number">1.15</span>
TOTAL  <span class="hljs-number">2.6</span> TiB  <span class="hljs-number">2.6</span> TiB  <span class="hljs-number">28</span> GiB    <span class="hljs-number">31</span> GiB       <span class="hljs-number">1.15</span>

--- POOLS ---
POOL                   ID  PGS  STORED   OBJECTS  USED     %USED  MAX AVAIL
device_health_metrics   <span class="hljs-number">1</span>    <span class="hljs-number">1</span>  <span class="hljs-number">418</span> KiB        <span class="hljs-number">3</span>  <span class="hljs-number">1.2</span> MiB      <span class="hljs-number">0</span>    <span class="hljs-number">839</span> GiB
images                  <span class="hljs-number">2</span>   <span class="hljs-number">32</span>  <span class="hljs-number">6.8</span> GiB      <span class="hljs-number">921</span>   <span class="hljs-number">20</span> GiB   <span class="hljs-number">0.81</span>    <span class="hljs-number">839</span> GiB
volumes                 <span class="hljs-number">3</span>   <span class="hljs-number">32</span>  <span class="hljs-number">2.4</span> GiB      <span class="hljs-number">662</span>  <span class="hljs-number">7.3</span> GiB   <span class="hljs-number">0.29</span>    <span class="hljs-number">839</span> GiB
vms                     <span class="hljs-number">4</span>   <span class="hljs-number">32</span>  <span class="hljs-number">4.2</span> KiB        <span class="hljs-number">6</span>   <span class="hljs-number">48</span> KiB      <span class="hljs-number">0</span>    <span class="hljs-number">839</span> GiB
backups                 <span class="hljs-number">5</span>   <span class="hljs-number">32</span>      <span class="hljs-number">0</span> B        <span class="hljs-number">0</span>      <span class="hljs-number">0</span> B      <span class="hljs-number">0</span>    <span class="hljs-number">839</span> GiB
metrics                 <span class="hljs-number">6</span>   <span class="hljs-number">32</span>  <span class="hljs-number">2.1</span> MiB      <span class="hljs-number">242</span>  <span class="hljs-number">8.4</span> MiB      <span class="hljs-number">0</span>    <span class="hljs-number">839</span> GiB
manila_data             <span class="hljs-number">7</span>   <span class="hljs-number">32</span>      <span class="hljs-number">0</span> B        <span class="hljs-number">0</span>      <span class="hljs-number">0</span> B      <span class="hljs-number">0</span>    <span class="hljs-number">839</span> GiB
manila_metadata         <span class="hljs-number">8</span>   <span class="hljs-number">32</span>      <span class="hljs-number">0</span> B        <span class="hljs-number">0</span>      <span class="hljs-number">0</span> B      <span class="hljs-number">0</span>    <span class="hljs-number">839</span> GiB
.rgw.root               <span class="hljs-number">9</span>   <span class="hljs-number">32</span>  <span class="hljs-number">2.4</span> KiB        <span class="hljs-number">6</span>   <span class="hljs-number">72</span> KiB      <span class="hljs-number">0</span>    <span class="hljs-number">839</span> GiB
<span class="hljs-keyword">default</span>.rgw.log        <span class="hljs-number">10</span>   <span class="hljs-number">32</span>  <span class="hljs-number">3.4</span> KiB      <span class="hljs-number">207</span>  <span class="hljs-number">384</span> KiB      <span class="hljs-number">0</span>    <span class="hljs-number">839</span> GiB
<span class="hljs-keyword">default</span>.rgw.control    <span class="hljs-number">11</span>   <span class="hljs-number">32</span>      <span class="hljs-number">0</span> B        <span class="hljs-number">8</span>      <span class="hljs-number">0</span> B      <span class="hljs-number">0</span>    <span class="hljs-number">839</span> GiB
<span class="hljs-keyword">default</span>.rgw.meta       <span class="hljs-number">12</span>    <span class="hljs-number">8</span>      <span class="hljs-number">0</span> B        <span class="hljs-number">0</span>      <span class="hljs-number">0</span> B      <span class="hljs-number">0</span>    <span class="hljs-number">839</span> GiB
</code></pre><h2 id="heading-maintaining-openstack-software-updates">Maintaining OpenStack Software Updates</h2>
<p>Software in the OpenStack ecosystem evolves over time, either through new feature additions, bug fixes, or when vulnerabilities are patched. Part of operating an OpenStack cloud involves maintaining its software through updates. In this section, we point out the sections in an OpenMetal cloud where software updates occur and explain best practices when performing updates.</p>
<p>The software of an OpenMetal cloud that can be updated include each hardware node’s package manager and the Kolla Ansible Docker images. Ceph updates are handled through the node’s package manager.</p>
<p>Now we will specifically cover the step to perform package manager updates.</p>
<ol>
<li><strong>Migrate Workload</strong></li>
</ol>
<p>Package manager updates requiring a server reboot to an OpenMetal control plane node can be disruptive to any workload running on it. Prior to performing disruptive actions, it may be possible to migrate instances another node running the Compute service. For information on how to migrate instances, see OpenStack Nova’s <a target="_blank" href="https://docs.openstack.org/nova/latest/admin/live-migration-usage.html">documentation</a>.</p>
<p><strong>2. Update One Node at a Time</strong></p>
<p>While performing package manager updates, ensure updates occur successfully for one hardware node before updating another node.</p>
<p><strong>3. Disable Docker</strong></p>
<p>Before updating the package manager, ensure the Docker socket and service within SystemD are stopped and disabled. For example:</p>
<pre><code>systemctl disable docker.socket
systemctl stop docker.socket
systemctl disable docker.service
systemctl stop docker.service
</code></pre><p><strong>4. Upgrade Host OS Packages</strong></p>
<p>After verifying the Docker socket and service are stopped, perform the package manager updates.:</p>
<pre><code>dnf upgrade
</code></pre><p><strong>5. Determine Reboot Need</strong></p>
<p>Once package manager completes, check if a reboot is required with dnf-utils <code>needs-restarting</code> and the reboot hint flag (-r):</p>
<pre><code>$ needs-restarting -r
Core libraries or services have been updated since boot-up:
  * kernel
  * systemd
Reboot is required to fully utilize these updates.
More information: https:<span class="hljs-comment">//access.redhat.com/solutions/27943</span>
$
</code></pre><p><strong>6. Ceph Maintenance</strong></p>
<p><em>This step is optional and only required if the node needs to be rebooted.</em></p>
<p>Prior to reboot, if the node is part of the Ceph cluster automatic OSD removal and data rebalance should be temporarily suspended. To do so, perform:</p>
<pre><code>ceph osd set noout
ceph osd set norebalance
</code></pre><p>This will reduce rebuild time and help ensure the node rejoins the cluster<br>automatically.</p>
<p>Once the node reboots and a healthy Ceph cluster is confirmed, these parameters must be unset. To unset this configuration, perform:</p>
<pre><code>ceph osd unset noout
ceph osd unset norebalance
</code></pre><h3 id="heading-reboot-if-required">Reboot if Required</h3>
<p>Reboot the node if required:</p>
<pre><code>shutdown -r now
</code></pre><p>You may have to wait a minute or two before you can log back in to the control plan node.</p>
<h3 id="heading-verify-successful-reboot">Verify Successful Reboot</h3>
<p>When the node comes back online, SSH into it to verify the OpenStack Docker containers have started. Additionally, if this node was part of the Ceph cluster, check Ceph’s cluster status.</p>
<p>To verify the Docker containers have started, use <code>docker ps</code>. You should see a number of Docker containers running. Under the <strong>STATUS</strong> column, each container should reflect the status <code>Up</code>.</p>
<p>For example:</p>
<pre><code>[root@modest-galliform ~]# docker ps
CONTAINER ID   IMAGE                                                                        COMMAND                  CREATED        STATUS                          PORTS     NAMES
<span class="hljs-number">6</span>f7590bc2191   harbor.imhadmin.net/kolla/centos-binary-telegraf:victoria                    <span class="hljs-string">"dumb-init --single-â€¦"</span>   <span class="hljs-number">20</span> hours ago   Restarting (<span class="hljs-number">1</span>) <span class="hljs-number">14</span> seconds ago             telegraf
<span class="hljs-number">67</span>a4d47e8c78   harbor.imhadmin.net/kolla/centos-binary-watcher-api:victoria                 <span class="hljs-string">"dumb-init --single-â€¦"</span>   <span class="hljs-number">3</span> days ago     Up <span class="hljs-number">6</span> minutes                              watcher_api
af815b1dcb5d   harbor.imhadmin.net/kolla/centos-binary-watcher-engine:victoria              <span class="hljs-string">"dumb-init --single-â€¦"</span>   <span class="hljs-number">3</span> days ago     Up <span class="hljs-number">6</span> minutes                              watcher_engine
a52ab61933ac   harbor.imhadmin.net/kolla/centos-binary-watcher-applier:victoria             <span class="hljs-string">"dumb-init --single-â€¦"</span>   <span class="hljs-number">3</span> days ago     Up <span class="hljs-number">6</span> minutes                              watcher_applier
[...output truncated...]
</code></pre><p>Next, if this node is part of a Ceph cluster, check Ceph’s status using <code>ceph status</code>.</p>
<p>For example:</p>
<pre><code>[root@modest-galliform ~]# ceph status
  <span class="hljs-attr">cluster</span>:
    id:     <span class="hljs-number">06</span>bf4555<span class="hljs-number">-7</span>c0c<span class="hljs-number">-4</span>b96-a3b7<span class="hljs-number">-502</span>bf8f6f213
    <span class="hljs-attr">health</span>: HEALTH_OK
[...output truncated...]
</code></pre><p>The above output shows the status as <code>HEALTH_OK</code>, indicating the Ceph cluster is healthy. Ceph is naturally resilient and should recover from a node being rebooted.</p>
<h1 id="heading-view-your-private-clouds-resource-usage">View your Private Cloud’s Resource Usage</h1>
<p>Now let's look at how to find the resource usage of your private cloud. We will explore how to utilize the Horizon Dashboard to determine the total memory and compute usage for a project as well as how to view instances stored on each node. Next, we'll look at disk usage by explaining how to briefly interact with your cloud’s Ceph cluster using the command line. Finally, we'll go over adding and removing nodes from your Ceph cluster.</p>
<p>There are currently three variations to private cloud deployments: Small, Standard, and Large. All private cloud deployments have a cluster of three hyper-converged servers but will have different allocations of memory, storage, and CPU processing power depending on the configuration and hardware. In addition, you have the option of adding additional hardware nodes to your cluster.</p>
<h3 id="heading-view-memory-and-compute-usage-in-horizon">View Memory and Compute Usage in Horizon</h3>
<p>To view the resources used by your cloud, you have to be the admin user and assigned to the admin project. Once you are in the admin project, navigate to <strong>Admin -&gt; Compute -&gt; Hypervisors</strong>. This section, lists the following items:</p>
<ul>
<li>VCPU Usage</li>
<li>Memory Usage</li>
<li>Local disk usage</li>
</ul>
<h3 id="heading-view-instance-state-across-cluster">View Instance State Across Cluster</h3>
<p>There is also an option to see the location of your instances within your cluster. To view this information, navigate to <strong>Admin -&gt; Compute -&gt; Instances</strong>. You have the option to see the project, the host, as well as the IP address, and state.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-156.png" alt="Image" width="600" height="400" loading="lazy">
<em>Summary of Instances.</em></p>
<h3 id="heading-how-to-access-resource-information-from-ceph">How to Access Resource Information from Ceph</h3>
<p>To access information regarding your Ceph cluster’s resource pools, you will need to use Ceph’s CLI. These are a summary of some useful resource monitoring and health commands. </p>
<ul>
<li><code>ceph -s</code> to check the status of Ceph</li>
<li><code>ceph df</code> to list the disk usage overview</li>
<li><code>ceph health detail</code> provides details about existing health issues</li>
<li><code>ceph osd pool ls</code> to list pools add <code>detail</code> for additional details regarding replication and health metrics</li>
</ul>
<p>### </p>
<h2 id="heading-create-and-restore-volume-backups">Create and Restore Volume Backups</h2>
<p>With Private Clouds, you have the ability to create backups and snapshots of your volume data. If a volume’s data goes corrupt or is removed by mistake, having a copy of that data could be invaluable. Now we'll look at how to create and recover volume backups using Horizon.</p>
<p>First of all, let's review what a volume is. Volumes are block storage devices that you attach to instances to enable persistent storage. You can attach a volume to a running instance or detach a volume and attach it to another instance at any time. You can also create a snapshot from or delete a volume.</p>
<h3 id="heading-create-a-volume-backup">Create a Volume Backup</h3>
<p>Navigate in Horizon to Project -&gt; Volume -&gt; Volumes.</p>
<p><img src="https://docs.flexmetal.net/wp-content/uploads/2022/01/jumpstation-volume-list.png" alt="Image" width="600" height="400" loading="lazy">
<em>Volumes List</em></p>
<p>Create a backup of your volume by selecting from the drop down <strong>Create Backup</strong>.</p>
<p><img src="https://docs.flexmetal.net/wp-content/uploads/2021/09/create_volume_backup.png" alt="Image" width="600" height="400" loading="lazy">
<em>Access Create Volume Backup Form</em></p>
<p>You will need to fill in the following fields.</p>
<ul>
<li><strong>Backup Name</strong>: Specify a name for the volume backup</li>
<li><strong>Description</strong>: Provide a description if necessary</li>
<li><strong>Container Name</strong>: Leave this blank otherwise the volume backup cannot be created. Horizon tells you if this field is blank, the backup is stored in a container called <code>volumebackups</code>, but this is not the case with our configuration. With Private Clouds, all volume backups created this way are stored in the Ceph pool called <code>backups</code>.</li>
<li><strong>Backup Snapshot</strong>: If applicable specify a snapshot to create a backup from</li>
</ul>
<p>After submitting the form, you are navigated to <strong>Project -&gt; Volume -&gt; Volume Backups</strong> where you can see the volume you just created a backup of.</p>
<p><img src="https://docs.flexmetal.net/wp-content/uploads/2021/09/volume_backup_list.png" alt="Image" width="600" height="400" loading="lazy">
<em>Volume Backup List</em></p>
<h3 id="heading-test-volume-backups">Test Volume Backups</h3>
<p>Creating backup copies of your important data is only one part of having a solid backup and recovery plan. Additionally, consider testing backed-up data to ensure if something unexpected does happen that restoring your backups will actually be useful. To test volume backups, you can restore a volume backup within OpenStack alongside the original volume and compare the contents.</p>
<h3 id="heading-restore-a-volume-backup">Restore a Volume Backup</h3>
<p>To restore a volume backup, being by navigating in Horizon to <strong>Project -&gt; Volume -&gt; Volume Backups</strong>.</p>
<p>Next, find the volume backup you wish to restore and from its drop down on the right, select <strong>Restore Backup</strong>.</p>
<p><img src="https://docs.flexmetal.net/wp-content/uploads/2021/09/restore_volume_backup.png" alt="Image" width="600" height="400" loading="lazy">
<em>Restore Volume Backup</em></p>
<p>Choose the volume to restore to, or have the system restore the backup to a new volume.</p>
<h3 id="heading-ceph-volumes-and-data-durability">Ceph, Volumes, and Data Durability</h3>
<p>When volume backups are created, they are stored in your cloud’s Ceph cluster in a pool called backups. By default, the Ceph cluster is configured with host level replication across each of your cloud’s three control plane nodes. With this configuration, your cloud could suffer losing all but one Ceph node and still retain all of the cluster’s data. </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You should now have a basic understanding of OpenStack and some of the things you can do with it. Good luck setting up your own OpenStack system.</p>
<p>## </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Build a Heroku Clone – Provision Infrastructure Programmatically ]]>
                </title>
                <description>
                    <![CDATA[ Heroku is a platform as a service that enables developers to build, run, and operate applications entirely in the cloud. Heroku makes it simple to do things like create virtual machines to host applications and to deploy websites.   Some of the featu... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-heroku-clone-provision-infrastructure-programmatically/</link>
                <guid isPermaLink="false">66b200e182069b4c678c98a0</guid>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Thu, 02 Jun 2022 14:08:55 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/06/heroku-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Heroku is a platform as a service that enables developers to build, run, and operate applications entirely in the cloud.</p>
<p>Heroku makes it simple to do things like create virtual machines to host applications and to deploy websites.  </p>
<p>Some of the features that Heroku offers can actually be created easily with other tools. </p>
<p>In this article, you will learn how to create a very simple web app that allows users to provision virtual machines and deploy static websites at the click of a button, all hosted on Amazon Web Services.</p>
<p>You will learn how to provision infrastructure with code and then you will be able to apply what you learn to your own applications.</p>
<p>This article is a companion to the full course we just posted on the freeCodeCamp.org YouTube channel that will teach you how to provision infrastructure programmatically using Python.</p>
<p>You can watch the course below or on the freeCodeCamp.org YouTube channel (1.5-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/zhJLVFR3pE8" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>Provisioning infrastructure is related to platform engineering. A platform engineering team serves an organization by planning, designing, and managing its cloud platforms. And often this can be done programatically.</p>
<p>The tools I teach in this course can be used for much more than just provisioning VMs and deploying websites. They can be used for platform engineering to make it simpler to manage cloud platforms.</p>
<h3 id="heading-automation-api">Automation API</h3>
<p>This course focusses on the Automation API from Pulumi. Pulumi provided freeCodeCamp a grant that made this course possible. </p>
<p>Pulumi's open source infrastructure as code SDK enables you to create, deploy, and manage infrastructure on any cloud, using many different programming languages.</p>
<p>Their Automation API makes it possible to provision infrastructure programmatically using the Pulumi engine. Basically, it makes it simple to write a program that automatically creates VMs, databases, VPCs, static websites and more on a variety of different cloud platforms.</p>
<p>I'm going to show you how to create our Heroku-clone web app using Flask and Python on the backend. However, you don't already have to know how to use Flask and Python to follow along. Also, everything I show you could also be done with many other different frameworks and programming languages and many of the steps are the same no matter what web framework you use.</p>
<p>Our app will provision resources on AWS, but Pulumi makes it simple to provision resources on most of the major cloud providers and it wouldn't take that much updating to the code to use a different provider.</p>
<p>Thanks to Komal Ali who created the code that my code in this course is based off of.</p>
<h2 id="heading-creating-the-heroku-clone">Creating the Heroku Clone</h2>
<h3 id="heading-pulumi-cli">Pulumi CLI</h3>
<p>First, make sure you have the Pulumi CLI installed. The way to install is different depending on your operating system.</p>
<p>If you have MacsOS and <a target="_blank" href="https://brew.sh/">Homebrew</a>, you can use the command <code>brew install pulumi</code>.</p>
<p>If you have Windows and <a target="_blank" href="https://chocolatey.org/">Chocolatey</a>, you can use the command <code>choco install pulumi</code>.</p>
<p><a target="_blank" href="https://www.pulumi.com/docs/get-started/install/">This page will give you additional ways of installing Pulumi</a>.</p>
<h3 id="heading-aws-cli">AWS CLI</h3>
<p>This project also uses AWS so you'll have to make sure you have an AWS account and have the CLI set up and authenticated.</p>
<p>You can sign up for a free AWS account here: <a target="_blank" href="https://aws.amazon.com/free/">https://aws.amazon.com/free/</a></p>
<p>Learn how to install the AWS CLI for your operating system here: <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html">https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html</a></p>
<p>For MacOS, you can use these commands:</p>
<pre><code>curl <span class="hljs-string">"https://awscli.amazonaws.com/AWSCLIV2.pkg"</span> -o <span class="hljs-string">"AWSCLIV2.pkg"</span>
sudo installer -pkg AWSCLIV2.pkg -target /
</code></pre><p>For Windows there are a few extra steps and you should just <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-windows.html">follow the instructions here</a>.</p>
<p>Next, you need to get an Access key ID and secret access key from AWS. <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-creds">Follow the instructions from Amazon to get these</a>.</p>
<p>Now run the following in the command line:</p>
<p><code>aws configure</code></p>
<p>Enter your Access Key ID and Secret Access Key when prompted. You can keep the "Default region name" and "Default output format" as None.</p>
<h3 id="heading-ia"> </h3>
<p>Setup the Project</p>
<p>A Pulumi project is just a directory with some files in it. It's possible for you to create a new one by hand. The <code>pulumi new</code> command, however, automates the process:</p>
<p><code>pulumi new</code></p>
<p>If this is the first time you have used Pulumi, you will be directed to enter an access code or login. To get an access code, go to <a target="_blank" href="https://app.pulumi.com/account/tokens">https://app.pulumi.com/account/tokens</a></p>
<p>The <code>pulumi new</code> command allows you to choose between many templates. Choose the "aws-python" template. You can select the defaults for the other options.</p>
<p>This command has created all the files we need, initialized a new stack named <code>dev</code> (an instance of our project). </p>
<p>Every Pulumi program is deployed to a <em>stack</em>. A stack is an isolated, independently configurable instance of a Pulumi program. Stacks are commonly used to denote different phases of development. In this case, we called it 'dev'.</p>
<p>Now we need to install two more dependencies for our project with</p>
<p><code>venv/bin/pip install flask requests</code></p>
<h3 id="heading-create-flask-app">Create Flask App</h3>
<p>We can now start creating the files for our Flask web app.</p>
<p>Pulumi created a file called "<strong>main</strong>.py". Change the name of that file to "app.py" because this is a Flask app and Flask will look for a file with that name.</p>
<p>There is some code in that file and we will eventually use code similar to that but for now let's just test that flask is working by replacing the code in "app.py" to the following:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask

app = Flask(__name__)

<span class="hljs-meta">@app.route('/')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">hello_world</span>():</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">'Hello World'</span>

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:

    app.run()
</code></pre>
<p>This is the most basic Flask web app and we can test it by running the following command in the terminal:</p>
<p><code>env/bin/flask run</code></p>
<p>Then we can access the running app in the web browser at the URL "<a target="_blank" href="http://127.0.0.1:5000/">http://127.0.0.1:5000/</a>".</p>
<p>If you go to that URL it should say "Hello World". If so, Flask is working correctly so we can update the "app.py" file to the following:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask, render_template

<span class="hljs-keyword">import</span> pulumi.automation <span class="hljs-keyword">as</span> auto


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">ensure_plugins</span>():</span>
    ws = auto.LocalWorkspace()
    ws.install_plugin(<span class="hljs-string">"aws"</span>, <span class="hljs-string">"v4.0.0"</span>)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_app</span>():</span>
    ensure_plugins()
    app = Flask(__name__, instance_relative_config=<span class="hljs-literal">True</span>)
    app.config.from_mapping(
        SECRET_KEY=<span class="hljs-string">"secret"</span>,
        PROJECT_NAME=<span class="hljs-string">"herocool"</span>,
        PULUMI_ORG=os.environ.get(<span class="hljs-string">"PULUMI_ORG"</span>),
    )

<span class="hljs-meta">    @app.route("/", methods=["GET"])</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span>():</span>
        <span class="hljs-keyword">return</span> render_template(<span class="hljs-string">"index.html"</span>)

    <span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> sites

    app.register_blueprint(sites.bp)

    <span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> virtual_machines

    app.register_blueprint(virtual_machines.bp)

    <span class="hljs-keyword">return</span> app
</code></pre>
<p>We import Pulumi's automation framework as a variable called <code>auto</code>. Then in the <code>ensure_plugins</code> function we get access to the <code>LocalWorkspace</code>. A <code>Workspace</code> is the execution context containing a single Pulumi project. Workspaces are used to manage the execution environment, providing various utilities such as plugin installation, environment configuration, and creation, deletion, and listing of stacks.</p>
<p>Because we are deploying AWS resources in this tutorial, we must install the AWS provider plugin within the <code>Workspace</code> so that the Pulumi program will have it available during execution.</p>
<p>The <code>create_app</code> function is what Flask will run when we star the flask app. We first run the <code>ensure_plugins</code> function and then create the Flask app. The <code>app.config.from_mapping()</code> function is used by Flask to set some default configurations that the app will use. This will not be a production ready app but if you ever deploy a Flask app make sure to change secret key. The other variables are used by pulumi.</p>
<p>The rest of the file is common to Flask apps. We set the default route  (<code>@app.route("/", methods=["GET"])</code>) to render the template "index.html" (which we still have to create.</p>
<p>Finally, the file imports the sites and virtual_machines files and registers them as a blueprint.</p>
<p>A Blueprint in Flask is a way to organize a group of related views and other code. Rather than registering views and other code directly with an application, they are registered with a blueprint. Then the blueprint is registered with the application when it is available in the factory function.</p>
<p>Basically, we are using Blueprints so we can define additional routes for our web app in other files that we still have to create.</p>
<h3 id="heading-create-templates">Create Templates</h3>
<p>And speaking of creating other files, let's create some. Create a director called "templates" and then create a file inside that directory named "index.html".</p>
<p>Add the following code:</p>
<pre><code class="lang-html">{% extends "base.html" %}

{% block content %}
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"row row-cols-1 row-cols-md-2 g-4"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"col"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-body"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h5</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-title"</span>&gt;</span>Static Websites<span class="hljs-tag">&lt;/<span class="hljs-name">h5</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-text"</span>&gt;</span>Deploy your own static website in seconds!<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">sites.list_sites</span>") }}" <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;</span>Get started<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"col"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-body"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h5</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-title"</span>&gt;</span>Virtual Machines<span class="hljs-tag">&lt;/<span class="hljs-name">h5</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-text"</span>&gt;</span>Set up a virtual machine for development and testing.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">virtual_machines.list_vms</span>") }}" <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;</span>Get started<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
{% endblock %}
</code></pre>
<p>I won't go into too much detail about this code. It is basic HTML but anything inside curly braces is an expression that will be output to the final document. Flask uses the Jinja template library to render templates. You will notice that it dynamically renders a list of sites and virtual machines. Soon we will create the Python code that will get those lists using Pulumi. </p>
<p>But next create a file called "base.html". You will notice that "index.html" extends "base.html". This will basically be the header that all our pages share.</p>
<p>Add this code:</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Herocool - {% block title %}{% endblock %}<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">static</span>", <span class="hljs-attr">filename</span>=<span class="hljs-string">"bootstrap.min.css"</span>) }}"&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">static</span>", <span class="hljs-attr">filename</span>=<span class="hljs-string">"bootstrap.min.js"</span>) }}"&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container p-2"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">index</span>") }}" <span class="hljs-attr">class</span>=<span class="hljs-string">"d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fs-3"</span>&gt;</span>Herocool<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      {% block nav %}{% endblock %}
    <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container-md px-4"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"row gy-4"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fs-4"</span>&gt;</span>{% block header %}{% endblock %}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        {% for category, message in get_flashed_messages(with_categories=true) %}
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"alert alert-{{ category }}"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"alert"</span>&gt;</span>{{ message }}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        {% endfor %}
      <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    {% block content %}{% endblock %}
  <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre>
<p>Now, we'll quickly create the rest of our HTML templates, then create the Python files that do the real heavy lifting in our app.</p>
<p>Inside our "templates" directory, create two more directories called "sites" and "virtual_machines". Inside each of those directories create the same three files: "index.html", "create.html", and "update.html".</p>
<p>Here is the code to add to each:</p>
<p><strong>sites/index.html</strong></p>
<pre><code class="lang-html">{% extends "base.html" %}

{% block nav %}
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav nav-pills"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-item fs-6"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">sites.create_site</span>") }}" <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-link active"</span>&gt;</span>Create static site<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
{% endblock %}

{% block header %}
  {% block title %}Site Directory{% endblock %}
{% endblock %}

{% block content %}
  <span class="hljs-tag">&lt;<span class="hljs-name">table</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"table"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tbody</span>&gt;</span>
      {% if not sites %}
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container gy-5"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"row py-4"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"alert alert-secondary"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"alert"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>No websites are currently deployed. Create one to get started!<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">sites.create_site</span>") }}" <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;</span>Create static site<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      {%  endif %}
      {% for site in sites %}
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"align-bottom"</span> <span class="hljs-attr">colspan</span>=<span class="hljs-string">"4"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-1"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ site["</span><span class="hljs-attr">url</span>"] }}" <span class="hljs-attr">class</span>=<span class="hljs-string">"fs-5 align-bottom"</span> <span class="hljs-attr">target</span>=<span class="hljs-string">"_blank"</span>&gt;</span>{{ site["name"] }}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"float-end p-1"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">sites.delete_site</span>", <span class="hljs-attr">id</span>=<span class="hljs-string">site[</span>"<span class="hljs-attr">name</span>"]) }}" <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-sm btn-danger"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Delete"</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"float-end p-1"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">sites.update_site</span>", <span class="hljs-attr">id</span>=<span class="hljs-string">site[</span>"<span class="hljs-attr">name</span>"]) }}" <span class="hljs-attr">method</span>=<span class="hljs-string">"get"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-sm btn-primary"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Edit"</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"float-end p-1"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ site["</span><span class="hljs-attr">console_url</span>"] }}" <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-sm btn-outline-primary"</span> <span class="hljs-attr">target</span>=<span class="hljs-string">"_blank"</span>&gt;</span>View in console<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
      {% endfor %}
    <span class="hljs-tag">&lt;/<span class="hljs-name">tbody</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">table</span>&gt;</span>
{% endblock %}
</code></pre>
<p><strong>sites/create.html</strong></p>
<pre><code class="lang-html">{% extends "base.html" %}

{% block nav %}
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav nav-pills"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-item fs-6"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">sites.list_sites</span>") }}" <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-link"</span>&gt;</span>Back to site directory<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
{% endblock %}

{% block header %}
  {% block title %}Create new static site{% endblock %}
{% endblock %}

{% block content %}
<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-2"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-3"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"site-id"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-label"</span>&gt;</span>Name<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"site-id"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"site-id"</span> <span class="hljs-attr">aria-describedby</span>=<span class="hljs-string">"nameHelp"</span> <span class="hljs-attr">required</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"nameHelp"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-text"</span>&gt;</span>Choose a unique name as a label for your website<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-3"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"file-url"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-label"</span>&gt;</span>File URL<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"file-url"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"file-url"</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-3"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>OR<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-3"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"site-content"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-label"</span>&gt;</span>Content<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">textarea</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"site-content"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"site-content"</span> <span class="hljs-attr">rows</span>=<span class="hljs-string">"5"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">textarea</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;</span>Create<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
{% endblock %}
</code></pre>
<p><strong>sites/update.html</strong></p>
<pre><code class="lang-html">{% extends "base.html" %}

{% block nav %}
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav nav-pills"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-item fs-6"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">sites.list_sites</span>") }}" <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-link"</span>&gt;</span>Back to site directory<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
{% endblock %}

{% block header %}
  {% block title %}Update site '{{ name }}'{% endblock %}
{% endblock %}

{% block content %}
  <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-2"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-3"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"file-url"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-label"</span>&gt;</span>File URL<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"file-url"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"file-url"</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-3"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>OR<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-3"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"site-content"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-label"</span>&gt;</span>Content<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">textarea</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"site-content"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"site-content"</span> <span class="hljs-attr">rows</span>=<span class="hljs-string">"5"</span>&gt;</span>{{ content }}<span class="hljs-tag">&lt;/<span class="hljs-name">textarea</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;</span>Update<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
{% endblock %}
</code></pre>
<p><strong>virtual_machines/index.html</strong></p>
<pre><code class="lang-html">{% extends "base.html" %}

{% block nav %}
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav nav-pills"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-item fs-6"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">virtual_machines.create_vm</span>") }}" <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-link active"</span>&gt;</span>Create VM<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
{% endblock %}

{% block header %}
  {% block title %}Deployed Virtual Machines{% endblock %}
{% endblock %}

{% block content %}
  <span class="hljs-tag">&lt;<span class="hljs-name">table</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"table"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tbody</span>&gt;</span>
      {% if not vms %}
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container gy-5"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"row py-4"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"alert alert-secondary"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"alert"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>No virtual machines are currently deployed. Create one to get started!<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">virtual_machines.create_vm</span>") }}" <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;</span>Create VM<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      {%  endif %}
      {% for vm in vms %}
      <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"align-bottom"</span> <span class="hljs-attr">colspan</span>=<span class="hljs-string">"4"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-1"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">pre</span>&gt;</span> ssh -i ~/.ssh/id_rsa.pem ec2-user@{{ vm["dns_name"] }} <span class="hljs-tag">&lt;/<span class="hljs-name">pre</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"float-end p-1"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">virtual_machines.delete_vm</span>", <span class="hljs-attr">id</span>=<span class="hljs-string">vm[</span>"<span class="hljs-attr">name</span>"]) }}" <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-sm btn-danger"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Delete"</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"float-end p-1"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">virtual_machines.update_vm</span>", <span class="hljs-attr">id</span>=<span class="hljs-string">vm[</span>"<span class="hljs-attr">name</span>"]) }}" <span class="hljs-attr">method</span>=<span class="hljs-string">"get"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-sm btn-primary"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Edit"</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"float-end p-1"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ vm["</span><span class="hljs-attr">console_url</span>"] }}" <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-sm btn-outline-primary"</span> <span class="hljs-attr">target</span>=<span class="hljs-string">"_blank"</span>&gt;</span>View in console<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
      {% endfor %}
    <span class="hljs-tag">&lt;/<span class="hljs-name">tbody</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">table</span>&gt;</span>
{% endblock %}
</code></pre>
<p><strong>virtual_machines/create.html</strong></p>
<pre><code class="lang-html">{% extends "base.html" %}

{% block nav %}
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav nav-pills"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-item fs-6"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">virtual_machines.list_vms</span>") }}" <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-link"</span>&gt;</span>Back to Virtual Machines directory<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
{% endblock %}

{% block header %}
  {% block title %}Create new virtual machine{% endblock %}
{% endblock %}

{% block content %}
<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-2"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-3"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"vm-id"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-label"</span>&gt;</span>Name<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"vm-id"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"vm-id"</span> <span class="hljs-attr">aria-describedby</span>=<span class="hljs-string">"nameHelp"</span> <span class="hljs-attr">required</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"nameHelp"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-text"</span>&gt;</span>Choose a unique name as a label for your virtual machine<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-3"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"vm-id"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-label"</span>&gt;</span>Instance Type<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"instance_type"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"instance_type"</span>&gt;</span>
      {% for instance_type in instance_types %}
        <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"{{ instance_type }}"</span> {% <span class="hljs-attr">if</span> <span class="hljs-attr">instance_type</span> == <span class="hljs-string">curr_instance_type</span> %} <span class="hljs-attr">selected</span> {% <span class="hljs-attr">endif</span> %}&gt;</span>
          {{ instance_type }}
        <span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
      {% endfor %}
      <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-3"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"vm-keypair"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-label"</span>&gt;</span>Public Key<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">textarea</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"vm-keypair"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"vm-keypair-content"</span> <span class="hljs-attr">rows</span>=<span class="hljs-string">"5"</span> <span class="hljs-attr">aria-describedby</span>=<span class="hljs-string">"keypairHelp"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">textarea</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"keypairHelp"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-text"</span>&gt;</span>The public key to use to connect to the VM <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;</span>Create<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
{% endblock %}
</code></pre>
<p><strong>virtual_machines/update.html</strong></p>
<pre><code class="lang-html">{% extends "base.html" %}

{% block nav %}
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav nav-pills"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-item fs-6"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ url_for("</span><span class="hljs-attr">virtual_machines.list_vms</span>") }}" <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-link"</span>&gt;</span>Back to Virtual Machines directory<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
{% endblock %}

{% block header %}
  {% block title %}Update Virtual Machine '{{ name }}'{% endblock %}
{% endblock %}

{% block content %}
  <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-2"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-3"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"vm-id"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-label"</span>&gt;</span>Instance Type<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"instance_type"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"instance_type"</span>&gt;</span>
          {% for instance_type in instance_types %}
            <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"{{ instance_type }}"</span> {% <span class="hljs-attr">if</span> <span class="hljs-attr">instance_type</span> == <span class="hljs-string">curr_instance_type</span> %} <span class="hljs-attr">selected</span> {% <span class="hljs-attr">endif</span> %}&gt;</span>
              {{ instance_type }}
            <span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
          {% endfor %}
          <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-3"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"vm-keypair"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-label"</span>&gt;</span>Public Key<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">textarea</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"vm-keypair"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"vm-keypair-content"</span> <span class="hljs-attr">rows</span>=<span class="hljs-string">"5"</span>&gt;</span>{{ public_key }}<span class="hljs-tag">&lt;/<span class="hljs-name">textarea</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;</span>Update<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
{% endblock %}
</code></pre>
<h3 id="heading-create-functionality-with-python">Create Functionality with Python</h3>
<p>Now it's time to create the functionality of our Heroku clone using Python. The "app.py" file imports from files we still have to create. In the same directory as "app.py", create "sites.py".</p>
<p>Add the following to "sites.py":</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> (
    current_app,
    Blueprint,
    request,
    flash,
    redirect,
    url_for,
    render_template,
)

<span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi.automation <span class="hljs-keyword">as</span> auto
<span class="hljs-keyword">from</span> pulumi_aws <span class="hljs-keyword">import</span> s3

bp = Blueprint(<span class="hljs-string">"sites"</span>, __name__, url_prefix=<span class="hljs-string">"/sites"</span>)
</code></pre>
<p>Those are just the basic imports we need for Flask and Pulumi, including importing the automation framework.</p>
<p>Next, add the the following code to that file. This function defines our Pulumi s3 static website in terms of the content that the caller passes in. This allows us to dynamically deploy websites based on user defined values from the POST body.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_pulumi_program</span>(<span class="hljs-params">content: str</span>):</span>
    <span class="hljs-comment"># Create a bucket and expose a website index document</span>
    site_bucket = s3.Bucket(
        <span class="hljs-string">"s3-website-bucket"</span>, website=s3.BucketWebsiteArgs(index_document=<span class="hljs-string">"index.html"</span>)
    )
    index_content = content

    <span class="hljs-comment"># Write our index.html into the site bucket</span>
    s3.BucketObject(
        <span class="hljs-string">"index"</span>,
        bucket=site_bucket.id,
        content=index_content,
        key=<span class="hljs-string">"index.html"</span>,
        content_type=<span class="hljs-string">"text/html; charset=utf-8"</span>,
    )

    <span class="hljs-comment"># Set the access policy for the bucket so all objects are readable</span>
    s3.BucketPolicy(
        <span class="hljs-string">"bucket-policy"</span>,
        bucket=site_bucket.id,
        policy=site_bucket.id.apply(
            <span class="hljs-keyword">lambda</span> id: json.dumps(
                {
                    <span class="hljs-string">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
                    <span class="hljs-string">"Statement"</span>: {
                        <span class="hljs-string">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
                        <span class="hljs-string">"Principal"</span>: <span class="hljs-string">"*"</span>,
                        <span class="hljs-string">"Action"</span>: [<span class="hljs-string">"s3:GetObject"</span>],
                        <span class="hljs-comment"># Policy refers to bucket explicitly</span>
                        <span class="hljs-string">"Resource"</span>: [<span class="hljs-string">f"arn:aws:s3:::<span class="hljs-subst">{id}</span>/*"</span>],
                    },
                }
            )
        ),
    )

    <span class="hljs-comment"># Export the website URL</span>
    pulumi.export(<span class="hljs-string">"website_url"</span>, site_bucket.website_endpoint)
    pulumi.export(<span class="hljs-string">"website_content"</span>, index_content)
</code></pre>
<p>The purpose of <code>pulumi.export</code> that you see at the end of the code is to export a named stack output. Exported values are attached to the program’s Stack resource. Later you will see how we can access this data that is being exported.</p>
<p>So far, nothing is calling the function we just created. At this pointe, we will create URL endpoints that can be used to call that function and create websites. </p>
<p>First, we'll add the code for the /new URL that will create a new site:</p>
<pre><code class="lang-python"><span class="hljs-meta">@bp.route("/new", methods=["GET", "POST"])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_site</span>():</span>
    <span class="hljs-string">"""creates new sites"""</span>
    <span class="hljs-keyword">if</span> request.method == <span class="hljs-string">"POST"</span>:
        stack_name = request.form.get(<span class="hljs-string">"site-id"</span>)
        file_url = request.form.get(<span class="hljs-string">"file-url"</span>)
        <span class="hljs-keyword">if</span> file_url:
            site_content = requests.get(file_url).text
        <span class="hljs-keyword">else</span>:
            site_content = request.form.get(<span class="hljs-string">"site-content"</span>)

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">pulumi_program</span>():</span>
            <span class="hljs-keyword">return</span> create_pulumi_program(str(site_content))

        <span class="hljs-keyword">try</span>:
            <span class="hljs-comment"># create a new stack, generating our pulumi program on the fly from the POST body</span>
            stack = auto.create_stack(
                stack_name=str(stack_name),
                project_name=current_app.config[<span class="hljs-string">"PROJECT_NAME"</span>],
                program=pulumi_program,
            )
            stack.set_config(<span class="hljs-string">"aws:region"</span>, auto.ConfigValue(<span class="hljs-string">"us-east-1"</span>))
            <span class="hljs-comment"># deploy the stack, tailing the logs to stdout</span>
            stack.up(on_output=<span class="hljs-keyword">print</span>)
            flash(
                <span class="hljs-string">f"Successfully created site '<span class="hljs-subst">{stack_name}</span>'"</span>, category=<span class="hljs-string">"success"</span>)
        <span class="hljs-keyword">except</span> auto.StackAlreadyExistsError:
            flash(
                <span class="hljs-string">f"Error: Site with name '<span class="hljs-subst">{stack_name}</span>' already exists, pick a unique name"</span>,
                category=<span class="hljs-string">"danger"</span>,
            )

        <span class="hljs-keyword">return</span> redirect(url_for(<span class="hljs-string">"sites.list_sites"</span>))

    <span class="hljs-keyword">return</span> render_template(<span class="hljs-string">"sites/create.html"</span>)
</code></pre>
<p>For a GET request, this route renders the "create.html" template. For a POST request, it creates a new site and the redirects to the list of sites at the <code>/</code> route.</p>
<p>You will notice that there is a <code>stack</code> created. As a reminder, every Pulumi program is deployed to a stack. A stack is an isolated, independently configurable instance of a Pulumi program. In this code, the stack name is based on the id that the user types into the form. There is also a project name, and the program, which is the code block we previously discussed.</p>
<p>Once the stack is created, we can execute commands against the <code>Stack</code>, including update, preview, refresh, destroy, import, and export. In this code we use <code>stack.up()</code> to <code>up</code>date the stack. And we pass in a.callback function to <code>stack.up()</code> for standard output.</p>
<p>Next add the code for the root URL that will list the sites:</p>
<pre><code class="lang-python"><span class="hljs-meta">@bp.route("/", methods=["GET"])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">list_sites</span>():</span>
    <span class="hljs-string">"""lists all sites"""</span>
    sites = []
    org_name = current_app.config[<span class="hljs-string">"PULUMI_ORG"</span>]
    project_name = current_app.config[<span class="hljs-string">"PROJECT_NAME"</span>]
    <span class="hljs-keyword">try</span>:
        ws = auto.LocalWorkspace(
            project_settings=auto.ProjectSettings(
                name=project_name, runtime=<span class="hljs-string">"python"</span>)
        )
        all_stacks = ws.list_stacks()
        <span class="hljs-keyword">for</span> stack <span class="hljs-keyword">in</span> all_stacks:
            stack = auto.select_stack(
                stack_name=stack.name,
                project_name=project_name,
                <span class="hljs-comment"># no-op program, just to get outputs</span>
                program=<span class="hljs-keyword">lambda</span>: <span class="hljs-literal">None</span>,
            )
            outs = stack.outputs()
            <span class="hljs-keyword">if</span> <span class="hljs-string">'website_url'</span> <span class="hljs-keyword">in</span> outs:
                sites.append(
                    {
                        <span class="hljs-string">"name"</span>: stack.name,
                        <span class="hljs-string">"url"</span>: <span class="hljs-string">f"http://<span class="hljs-subst">{outs[<span class="hljs-string">'website_url'</span>].value}</span>"</span>,
                        <span class="hljs-string">"console_url"</span>: <span class="hljs-string">f"https://app.pulumi.com/<span class="hljs-subst">{org_name}</span>/<span class="hljs-subst">{project_name}</span>/<span class="hljs-subst">{stack.name}</span>"</span>,
                    }
                )
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> exn:
        flash(str(exn), category=<span class="hljs-string">"danger"</span>)

    <span class="hljs-keyword">return</span> render_template(<span class="hljs-string">"sites/index.html"</span>, sites=sites)
</code></pre>
<p>A <code>Workspace</code> is the execution context containing a single Pulumi project, a program, and multiple stacks. So we first get access <code>ws = auto.LocalWorkspace()</code>.</p>
<p>Then we <code>list_stacks()</code>. This just gives us access to the stack name so we must use <code>auto.select_stack()</code> to get access to the stack. We specifically want the <code>stack.outputs()</code>. This will be what we exported to the stack, including the <code>website_url</code>. </p>
<p>Then we append information for each site to the <code>sites</code> list which is used on the frontend to show the list of sites. </p>
<p>Now, add the code for the /update route. </p>
<pre><code class="lang-python"><span class="hljs-meta">@bp.route("/&lt;string:id&gt;/update", methods=["GET", "POST"])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">update_site</span>(<span class="hljs-params">id: str</span>):</span>
    stack_name = id

    <span class="hljs-keyword">if</span> request.method == <span class="hljs-string">"POST"</span>:
        file_url = request.form.get(<span class="hljs-string">"file-url"</span>)
        <span class="hljs-keyword">if</span> file_url:
            site_content = requests.get(file_url).text
        <span class="hljs-keyword">else</span>:
            site_content = str(request.form.get(<span class="hljs-string">"site-content"</span>))

        <span class="hljs-keyword">try</span>:

            <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">pulumi_program</span>():</span>
                create_pulumi_program(str(site_content))

            stack = auto.select_stack(
                stack_name=stack_name,
                project_name=current_app.config[<span class="hljs-string">"PROJECT_NAME"</span>],
                program=pulumi_program,
            )
            stack.set_config(<span class="hljs-string">"aws:region"</span>, auto.ConfigValue(<span class="hljs-string">"us-east-1"</span>))
            <span class="hljs-comment"># deploy the stack, tailing the logs to stdout</span>
            stack.up(on_output=<span class="hljs-keyword">print</span>)
            flash(<span class="hljs-string">f"Site '<span class="hljs-subst">{stack_name}</span>' successfully updated!"</span>,
                  category=<span class="hljs-string">"success"</span>)
        <span class="hljs-keyword">except</span> auto.ConcurrentUpdateError:
            flash(
                <span class="hljs-string">f"Error: site '<span class="hljs-subst">{stack_name}</span>' already has an update in progress"</span>,
                category=<span class="hljs-string">"danger"</span>,
            )
        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> exn:
            flash(str(exn), category=<span class="hljs-string">"danger"</span>)
        <span class="hljs-keyword">return</span> redirect(url_for(<span class="hljs-string">"sites.list_sites"</span>))

    stack = auto.select_stack(
        stack_name=stack_name,
        project_name=current_app.config[<span class="hljs-string">"PROJECT_NAME"</span>],
        <span class="hljs-comment"># noop just to get the outputs</span>
        program=<span class="hljs-keyword">lambda</span>: <span class="hljs-literal">None</span>,
    )
    outs = stack.outputs()
    content_output = outs.get(<span class="hljs-string">"website_content"</span>)
    content = content_output.value <span class="hljs-keyword">if</span> content_output <span class="hljs-keyword">else</span> <span class="hljs-literal">None</span>
    <span class="hljs-keyword">return</span> render_template(<span class="hljs-string">"sites/update.html"</span>, name=stack_name, content=content)
</code></pre>
<p>You will notice that the /update route is very similar to the /new route. Since it uses the same <code>stack_name</code>, a new site is not created.</p>
<p>A new thing in this function is that the code gets the content of the website to return to the frontend template.</p>
<p>Finally, add the following code for a /delete route.</p>
<pre><code>@bp.route(<span class="hljs-string">"/&lt;string:id&gt;/delete"</span>, methods=[<span class="hljs-string">"POST"</span>])
def delete_site(id: str):
    stack_name = id
    <span class="hljs-attr">try</span>:
        stack = auto.select_stack(
            stack_name=stack_name,
            project_name=current_app.config[<span class="hljs-string">"PROJECT_NAME"</span>],
            # noop program <span class="hljs-keyword">for</span> destroy
            program=lambda: None,
        )
        stack.destroy(on_output=print)
        stack.workspace.remove_stack(stack_name)
        flash(f<span class="hljs-string">"Site '{stack_name}' successfully deleted!"</span>, category=<span class="hljs-string">"success"</span>)
    except auto.ConcurrentUpdateError:
        flash(
            f<span class="hljs-string">"Error: Site '{stack_name}' already has update in progress"</span>,
            category=<span class="hljs-string">"danger"</span>,
        )
    except Exception <span class="hljs-keyword">as</span> exn:
        flash(str(exn), category=<span class="hljs-string">"danger"</span>)

    <span class="hljs-keyword">return</span> redirect(url_for(<span class="hljs-string">"sites.list_sites"</span>))
</code></pre><p>In this delete function we first we get access to the stack then destroy and remove the stack. Just by calling the <code>stack.destroy()</code> function will delete the resource on AWS.</p>
<p>Now, in the same directory as "app.py", create "virtual_machines.py".</p>
<p>Add the following to "virtual_machines.py". This time, we'll add it all at once:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> (Blueprint, current_app, request, flash,
                   redirect, url_for, render_template)

<span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws
<span class="hljs-keyword">import</span> pulumi.automation <span class="hljs-keyword">as</span> auto
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> pathlib <span class="hljs-keyword">import</span> Path

bp = Blueprint(<span class="hljs-string">"virtual_machines"</span>, __name__, url_prefix=<span class="hljs-string">"/vms"</span>)
instance_types = [<span class="hljs-string">'c5.xlarge'</span>, <span class="hljs-string">'p2.xlarge'</span>, <span class="hljs-string">'p3.2xlarge'</span>]


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_pulumi_program</span>(<span class="hljs-params">keydata: str, instance_type=str</span>):</span>
    <span class="hljs-comment"># Choose the latest minimal amzn2 Linux AMI.</span>
    <span class="hljs-comment"># <span class="hljs-doctag">TODO:</span> Make this something the user can choose.</span>
    ami = aws.ec2.get_ami(most_recent=<span class="hljs-literal">True</span>,
                          owners=[<span class="hljs-string">"amazon"</span>],
                          filters=[aws.GetAmiFilterArgs(name=<span class="hljs-string">"name"</span>, values=[<span class="hljs-string">"*amzn2-ami-minimal-hvm*"</span>])])

    group = aws.ec2.SecurityGroup(<span class="hljs-string">'web-secgrp'</span>,
                                  description=<span class="hljs-string">'Enable SSH access'</span>,
                                  ingress=[aws.ec2.SecurityGroupIngressArgs(
                                      protocol=<span class="hljs-string">'tcp'</span>,
                                      from_port=<span class="hljs-number">22</span>,
                                      to_port=<span class="hljs-number">22</span>,
                                      cidr_blocks=[<span class="hljs-string">'0.0.0.0/0'</span>],
                                  )])

    public_key = keydata
    <span class="hljs-keyword">if</span> public_key <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span> <span class="hljs-keyword">or</span> public_key == <span class="hljs-string">""</span>:
        home = str(Path.home())
        f = open(os.path.join(home, <span class="hljs-string">'.ssh/id_rsa.pub'</span>), <span class="hljs-string">'r'</span>)
        public_key = f.read()
        f.close()

    public_key = public_key.strip()

    print(<span class="hljs-string">f"Public Key: '<span class="hljs-subst">{public_key}</span>'\n"</span>)

    keypair = aws.ec2.KeyPair(<span class="hljs-string">"dlami-keypair"</span>, public_key=public_key)

    server = aws.ec2.Instance(<span class="hljs-string">'dlami-server'</span>,
                              instance_type=instance_type,
                              vpc_security_group_ids=[group.id],
                              key_name=keypair.id,
                              ami=ami.id)

    pulumi.export(<span class="hljs-string">'instance_type'</span>, server.instance_type)
    pulumi.export(<span class="hljs-string">'public_key'</span>, keypair.public_key)
    pulumi.export(<span class="hljs-string">'public_ip'</span>, server.public_ip)
    pulumi.export(<span class="hljs-string">'public_dns'</span>, server.public_dns)


<span class="hljs-meta">@bp.route("/new", methods=["GET", "POST"])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_vm</span>():</span>
    <span class="hljs-string">"""creates new VM"""</span>
    <span class="hljs-keyword">if</span> request.method == <span class="hljs-string">"POST"</span>:
        stack_name = request.form.get(<span class="hljs-string">"vm-id"</span>)
        keydata = request.form.get(<span class="hljs-string">"vm-keypair"</span>)
        instance_type = request.form.get(<span class="hljs-string">"instance_type"</span>)

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">pulumi_program</span>():</span>
            <span class="hljs-keyword">return</span> create_pulumi_program(keydata, instance_type)
        <span class="hljs-keyword">try</span>:
            <span class="hljs-comment"># create a new stack, generating our pulumi program on the fly from the POST body</span>
            stack = auto.create_stack(
                stack_name=str(stack_name),
                project_name=current_app.config[<span class="hljs-string">"PROJECT_NAME"</span>],
                program=pulumi_program,
            )
            stack.set_config(<span class="hljs-string">"aws:region"</span>, auto.ConfigValue(<span class="hljs-string">"us-east-1"</span>))
            <span class="hljs-comment"># deploy the stack, tailing the logs to stdout</span>
            stack.up(on_output=<span class="hljs-keyword">print</span>)
            flash(
                <span class="hljs-string">f"Successfully created VM '<span class="hljs-subst">{stack_name}</span>'"</span>, category=<span class="hljs-string">"success"</span>)
        <span class="hljs-keyword">except</span> auto.StackAlreadyExistsError:
            flash(
                <span class="hljs-string">f"Error: VM with name '<span class="hljs-subst">{stack_name}</span>' already exists, pick a unique name"</span>,
                category=<span class="hljs-string">"danger"</span>,
            )
        <span class="hljs-keyword">return</span> redirect(url_for(<span class="hljs-string">"virtual_machines.list_vms"</span>))

    current_app.logger.info(<span class="hljs-string">f"Instance types: <span class="hljs-subst">{instance_types}</span>"</span>)
    <span class="hljs-keyword">return</span> render_template(<span class="hljs-string">"virtual_machines/create.html"</span>, instance_types=instance_types, curr_instance_type=<span class="hljs-literal">None</span>)


<span class="hljs-meta">@bp.route("/", methods=["GET"])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">list_vms</span>():</span>
    <span class="hljs-string">"""lists all vms"""</span>
    vms = []
    org_name = current_app.config[<span class="hljs-string">"PULUMI_ORG"</span>]
    project_name = current_app.config[<span class="hljs-string">"PROJECT_NAME"</span>]
    <span class="hljs-keyword">try</span>:
        ws = auto.LocalWorkspace(
            project_settings=auto.ProjectSettings(
                name=project_name, runtime=<span class="hljs-string">"python"</span>)
        )
        all_stacks = ws.list_stacks()
        <span class="hljs-keyword">for</span> stack <span class="hljs-keyword">in</span> all_stacks:
            stack = auto.select_stack(
                stack_name=stack.name,
                project_name=project_name,
                <span class="hljs-comment"># no-op program, just to get outputs</span>
                program=<span class="hljs-keyword">lambda</span>: <span class="hljs-literal">None</span>,
            )
            outs = stack.outputs()
            <span class="hljs-keyword">if</span> <span class="hljs-string">'public_dns'</span> <span class="hljs-keyword">in</span> outs:
                vms.append(
                    {
                        <span class="hljs-string">"name"</span>: stack.name,
                        <span class="hljs-string">"dns_name"</span>: <span class="hljs-string">f"<span class="hljs-subst">{outs[<span class="hljs-string">'public_dns'</span>].value}</span>"</span>,
                        <span class="hljs-string">"console_url"</span>: <span class="hljs-string">f"https://app.pulumi.com/<span class="hljs-subst">{org_name}</span>/<span class="hljs-subst">{project_name}</span>/<span class="hljs-subst">{stack.name}</span>"</span>,
                    }
                )
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> exn:
        flash(str(exn), category=<span class="hljs-string">"danger"</span>)

    current_app.logger.info(<span class="hljs-string">f"VMS: <span class="hljs-subst">{vms}</span>"</span>)
    <span class="hljs-keyword">return</span> render_template(<span class="hljs-string">"virtual_machines/index.html"</span>, vms=vms)


<span class="hljs-meta">@bp.route("/&lt;string:id&gt;/update", methods=["GET", "POST"])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">update_vm</span>(<span class="hljs-params">id: str</span>):</span>
    stack_name = id
    <span class="hljs-keyword">if</span> request.method == <span class="hljs-string">"POST"</span>:
        current_app.logger.info(
            <span class="hljs-string">f"Updating VM: <span class="hljs-subst">{stack_name}</span>, form data: <span class="hljs-subst">{request.form}</span>"</span>)
        keydata = request.form.get(<span class="hljs-string">"vm-keypair"</span>)
        current_app.logger.info(<span class="hljs-string">f"updating keydata: <span class="hljs-subst">{keydata}</span>"</span>)
        instance_type = request.form.get(<span class="hljs-string">"instance_type"</span>)

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">pulumi_program</span>():</span>
            <span class="hljs-keyword">return</span> create_pulumi_program(keydata, instance_type)
        <span class="hljs-keyword">try</span>:
            stack = auto.select_stack(
                stack_name=stack_name,
                project_name=current_app.config[<span class="hljs-string">"PROJECT_NAME"</span>],
                program=pulumi_program,
            )
            stack.set_config(<span class="hljs-string">"aws:region"</span>, auto.ConfigValue(<span class="hljs-string">"us-east-1"</span>))
            <span class="hljs-comment"># deploy the stack, tailing the logs to stdout</span>
            stack.up(on_output=<span class="hljs-keyword">print</span>)
            flash(<span class="hljs-string">f"VM '<span class="hljs-subst">{stack_name}</span>' successfully updated!"</span>,
                  category=<span class="hljs-string">"success"</span>)
        <span class="hljs-keyword">except</span> auto.ConcurrentUpdateError:
            flash(
                <span class="hljs-string">f"Error: VM '<span class="hljs-subst">{stack_name}</span>' already has an update in progress"</span>,
                category=<span class="hljs-string">"danger"</span>,
            )
        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> exn:
            flash(str(exn), category=<span class="hljs-string">"danger"</span>)
        <span class="hljs-keyword">return</span> redirect(url_for(<span class="hljs-string">"virtual_machines.list_vms"</span>))

    stack = auto.select_stack(
        stack_name=stack_name,
        project_name=current_app.config[<span class="hljs-string">"PROJECT_NAME"</span>],
        <span class="hljs-comment"># noop just to get the outputs</span>
        program=<span class="hljs-keyword">lambda</span>: <span class="hljs-literal">None</span>,
    )
    outs = stack.outputs()
    public_key = outs.get(<span class="hljs-string">"public_key"</span>)
    pk = public_key.value <span class="hljs-keyword">if</span> public_key <span class="hljs-keyword">else</span> <span class="hljs-literal">None</span>
    instance_type = outs.get(<span class="hljs-string">"instance_type"</span>)
    <span class="hljs-keyword">return</span> render_template(<span class="hljs-string">"virtual_machines/update.html"</span>, name=stack_name, public_key=pk, instance_types=instance_types, curr_instance_type=instance_type.value)


<span class="hljs-meta">@bp.route("/&lt;string:id&gt;/delete", methods=["POST"])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">delete_vm</span>(<span class="hljs-params">id: str</span>):</span>
    stack_name = id
    <span class="hljs-keyword">try</span>:
        stack = auto.select_stack(
            stack_name=stack_name,
            project_name=current_app.config[<span class="hljs-string">"PROJECT_NAME"</span>],
            <span class="hljs-comment"># noop program for destroy</span>
            program=<span class="hljs-keyword">lambda</span>: <span class="hljs-literal">None</span>,
        )
        stack.destroy(on_output=<span class="hljs-keyword">print</span>)
        stack.workspace.remove_stack(stack_name)
        flash(<span class="hljs-string">f"VM '<span class="hljs-subst">{stack_name}</span>' successfully deleted!"</span>, category=<span class="hljs-string">"success"</span>)
    <span class="hljs-keyword">except</span> auto.ConcurrentUpdateError:
        flash(
            <span class="hljs-string">f"Error: VM '<span class="hljs-subst">{stack_name}</span>' already has update in progress"</span>,
            category=<span class="hljs-string">"danger"</span>,
        )
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> exn:
        flash(str(exn), category=<span class="hljs-string">"danger"</span>)

    <span class="hljs-keyword">return</span> redirect(url_for(<span class="hljs-string">"virtual_machines.list_vms"</span>))
</code></pre>
<p>There is a lot of similarities between this file and the 'sites.py' file. A virtual machine has quite a few more settings that must be set and Pulumi is able to create the exact type of VM we want. </p>
<p>We could make it so a user could customize everything from the web interface, but we just give the user the ability to choose the instance type. </p>
<p>One thing that is required for a VM is a public/private key pair. When creating a VM on AWS, you must give a public key. Then, in order to access the VM you have to have the private key.</p>
<p>You can create keys in your terminal. Run the following command:</p>
<p><code>ssh-keygen -m PEM</code></p>
<p>Later when testing out the app, you will have to open the public key file so you can copy the key and paste it into the website.</p>
<p>Now switch the directory with the file you just created. The directory should be the same whether you are on MacOS or Windows.</p>
<p>Type in on your terminal:</p>
<p><code>cd /Users/[username]/.ssh</code></p>
<p>AWS needs the file to be in .pem format and that is why we created it with "PEM" above. Now lets rename the file to have the correct extension. You need to change the name of the file called <code>id_rsa</code> to be <code>id_rsa.pem</code>.</p>
<p>On macOS, you can rename with this command:</p>
<p><code>mv id_rsa id_rsa.pem</code></p>
<p>On Windows, use:</p>
<p><code>rename id_rsa id_rsa.pem</code></p>
<p>When running the Flask app, you may need to enter the public key you just created. You can open the <code>id_rsa.pub</code> in any text editor in order to copy the text. If you have vim, you can use this command to open the file:</p>
<p><code>vim /Users/beau/.ssh/id_rsa.pub</code></p>
<h3 id="heading-testing-the-app">Testing the App</h3>
<p>Now it's time to try out the app. On the terminal, run this command:</p>
<p><code>FLASK_RUN_PORT=1337 FLASK_ENV=development FLASK_APP=__init__ PULUMI_ORG=[your-org-name] venv/bin/flask run</code></p>
<p>Now you can try out the app and create websites and VMs. Make sure to delete them after creation so AWS does not keep charging you for the resources.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You should now know enough to start provisioning infrastructure in your own applications using Pulumi's automation API. And if you want to see step-by-step how to do the things in this article, check out the video tutorial:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/zhJLVFR3pE8" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is IT Infrastructure? A Beginner's Guide ]]>
                </title>
                <description>
                    <![CDATA[ By Vitaly Kuprenko It is difficult to imagine a business that operates without at least some basic IT infrastructure.  Digital solutions and hardware that are linked together help boost a company's productivity. And good infrastructure improves inter... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-is-it-infrastructure/</link>
                <guid isPermaLink="false">66d461757df3a1f32ee7f8ba</guid>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 03 Jan 2022 22:24:55 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/12/mohammad-rahmani-CDBkMNZmd7o-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Vitaly Kuprenko</p>
<p>It is difficult to imagine a business that operates without at least some basic IT infrastructure. </p>
<p>Digital solutions and hardware that are linked together help boost a company's productivity. And good infrastructure improves internal processes and communication between different departments.</p>
<p>IT infrastructure is based on various components that aim to manage the internal business ecosystem or provide services outside the business. The better and more thoughtfully the infrastructure is organized, the more a business can profit and expand. </p>
<p>In this article, we will talk in more detail about what IT infrastructure consists of, what types of infrastructure are most popular, and how you can manage IT infrastructure to make it more efficient.</p>
<p>So, without further ado, let’s get started.</p>
<h2 id="heading-components-of-it-infrastructure">Components of IT Infrastructure</h2>
<p>You can’t build IT infrastructure from nothing. The whole ecosystem is made up of software solutions, various hardware, and network connections that work together simultaneously and complement each other. </p>
<p>The whole idea is to improve communication between different devices, whether it's desktop computers, tablets, scanners, various servers, and cloud storage. </p>
<p>So, let's take a closer look at the main components of IT infrastructure:</p>
<h3 id="heading-hardware">Hardware</h3>
<p>Hardware refers to the physical components and devices that help you organize the infrastructure. They are its foundation. Hardware refers to:</p>
<ul>
<li>Desktop computers</li>
<li>Laptops</li>
<li>Tablets, smartphones, and other mobile devices</li>
<li>Servers and data centers</li>
<li>Internet hubs and routers</li>
</ul>
<h3 id="heading-software">Software</h3>
<p>Software can include various programs and applications that a business uses to function, provide services, operate internal pipelines, and more. Also, various operating systems can be assigned to Software on top of which all programs and applications are installed. </p>
<p>So, the software parts include:</p>
<ul>
<li>Content Management Systems (CMS)</li>
<li>Customer Relationship Management (CRM)</li>
<li>Enterprise resource planning (ERP)</li>
<li>Operating systems</li>
<li>Web servers</li>
<li>Custom software for internal work</li>
</ul>
<h3 id="heading-networks">Networks</h3>
<p>Networks allow you to combine devices into a single network and connect them to the Internet. The connection is secured by security firewalls that protect it from malware and breaches. A network includes the following components:</p>
<ul>
<li>Servers</li>
<li>Data centers</li>
<li>Hubs</li>
<li>Routers</li>
<li>Switches</li>
</ul>
<h2 id="heading-types-of-it-infrastructure">Types of IT Infrastructure</h2>
<p>Everything that we have discussed above is part of the standard IT infrastructure that we are all used to. Such a structure can be found in enterprises of various sizes and is managed entirely from within the enterprise. </p>
<p>In simple terms, the company has its own servers or data centers, to which all other devices with their own software are connected. </p>
<p>This approach allows a company to fully control the state of its infrastructure, manage it, improve it, and much more. And it is not surprising that, to many people, this type of infrastructure seems to be the only correct one.</p>
<p>However, there is also a second type of infrastructure – based in the cloud – which is gaining popularity because of the many advantages if offers over a classic infrastructure setup. Let's talk in more detail about both types so you can have a complete picture.</p>
<h3 id="heading-traditional-infrastructure">Traditional Infrastructure</h3>
<p>The main components of a traditional infrastructure are hardware and software. For example, this can be various servers and desktop PCs. All equipment is located under one roof, making it easy to access the necessary information and computing power.</p>
<p>This infrastructure requires a lot of free space at the deployment site, as well as the power of the equipment itself. As a result, it's more expensive to develop and install this type of infrastructure. </p>
<p>In addition, such a system requires constant monitoring, maintenance, and updating in order to meet the ever-changing market realities. </p>
<p>This means that in addition to spending on equipment, you will also spend money on an infrastructure management team that will monitor its condition, troubleshoot problems, and make timely diagnostics and upgrades.  </p>
<p><img src="https://lh3.googleusercontent.com/OPsFPWSuJVT6Y153ISVJrgd49xxtmZaXFhWRiYDwRyoftIpeNEaQHWeuZs2B_drvDBm2fP7d5tBY37JLZwmLfq6V0qTDJVp91DGkVKvounthKSl1Xn70bPKYvps5Ysn9NCSJaXp8" alt="Image" width="1600" height="1370" loading="lazy"></p>
<p>This option is the safest solution for data storage, as it allows you to completely manage all data and software within the enterprise.</p>
<h4 id="heading-advantages-of-traditional-infrastructure">Advantages of traditional infrastructure</h4>
<ul>
<li>Together with the system, you get a dedicated team that is fully involved in infrastructure management.</li>
<li>Along with updating the infrastructure, you also need to update the software, which allows you to always be on the cutting edge of new technological solutions.</li>
<li>You have the ability to work with various types of software.</li>
</ul>
<h3 id="heading-cloud-based-infrastructure">Cloud-Based Infrastructure</h3>
<p>Cloud infrastructures are used by more and more businesses these days due to their flexibility. So what is the main difference from standard infrastructure?</p>
<p>The main difference is that you do not have the equipment to support the infrastructure right in your office. All servers, software solutions, and data storage are located in the cloud. </p>
<p>As a result, you don’t have to spend tons of money on hardware and data centers that can handle the internal IT infrastructure for your facility. Instead, you can find a cloud service provider and rent the cloud storage space you need to transfer all your internal pipeline into it (including valuable data, applications, and much more).  </p>
<p><img src="https://lh5.googleusercontent.com/hVmmxGbgmjhUJKieCyFyStymJHRnO7STCUukUUFxjq9pIUDvb7SwZyYwq4KKC8xEmUB8OLjToW9mxSYcHD2zc1kkuLDHeA7jU759tVk_1f8_hriWAi9rJ-oIfhhzHECvTtthy9gb" alt="Image" width="1600" height="1370" loading="lazy"></p>
<p>Besides that, you can find your IT infrastructure on one cloud-based software. As a result, your business will receive these advantages from <a target="_blank" href="https://www.cleveroad.com/blog/cloud-application-development">cloud app development</a>:</p>
<ul>
<li>All data will be stored in the cloud. But if there's a need, users can store it on their devices to have offline access.</li>
<li>Staff can use this kind of application on any device with access anywhere.</li>
</ul>
<p>Now, let’s talk about the main strengths of cloud-based IT infrastructures.</p>
<h3 id="heading-advantages-of-cloud-based-it-infrastructure">Advantages of Cloud-based IT Infrastructure</h3>
<h4 id="heading-great-flexibility-and-scalability">Great flexibility and scalability</h4>
<p>Flexibility and scalability are important if your company aims for rapid growth. And сloud structures have no limits on storage space and much more horsepower for computing needs. </p>
<p>Besides that, it is much easier to scale the whole structure up and down and raise or lower the number of servers if you need to.</p>
<h4 id="heading-automation-capabilities">Automation capabilities</h4>
<p>By introducing cloud technologies to your business, you are relieved of the headaches associated with equipment management and security issues. </p>
<p>All of those operations are outsourced to a vendor who provides you with a cloud-based structure while you can focus on other important aspects of the business.</p>
<h4 id="heading-its-cost-effective">It's cost-effective</h4>
<p>You might be surprised how cost-effective cloud IT infrastructure is. This is mainly because you pay only for the specific service you use. You do not need to spend money on equipment, management, upgrades, and other aspects that the vendor is now dealing with.</p>
<h2 id="heading-what-is-it-infrastructure-management">What is IT Infrastructure Management?</h2>
<p>IT infrastructure is a complex mechanism that requires a special approach to management. And in order to make it easier, there are three areas of management that you should know about.</p>
<h3 id="heading-it-operational-management">IT Operational Management</h3>
<p>IT Operational Management is all about tools and processes that help keep infrastructure live. The main idea is to maximize the reliability and efficiency of the infrastructure through a wide range of functions such as network asset discovery, operational analysis, data collection, and much more.</p>
<p>Quite popular in this area is the solution developed by ServiceNow. It includes a developed complex set of modules, namely:</p>
<ul>
<li>Module for collecting information about the Discovery structure</li>
<li>Event management module</li>
<li>Operational Intelligence module</li>
<li>Automation module for routine IT processes</li>
<li>Service topology building module</li>
<li>Cloud resource management module</li>
</ul>
<p>ITOM tools track the health of your IT infrastructure in real-time. For example, Time Warner took advantage of this opportunity to simplify incident management.</p>
<h3 id="heading-it-service-management">IT Service Management</h3>
<p>This approach is somewhat different from the previous one since its main goal is to combine all the operations responsible for the design, creation, delivery, support and lifecycle management of various IT services. At their core, these operations help to optimally deploy a specific IT solution for a specific user in a large business.</p>
<p>FreshService is an excellent purchase for ITSM as it is an ITIL-ready cloud service support platform. The tool can offer a wide range of ITSM support at any scale for businesses of all types and sizes. </p>
<p>Its main features are the presence of mobile applications, the features of the analysis of the root causes of incidents, as well as the management of problems, projects, and configurations.</p>
<h3 id="heading-it-asset-management">IT Asset Management</h3>
<p>The final type is responsible for the operations that allow you to manage the life cycle of the infrastructure, from hardware to software solutions. The main objectives of the approach are to optimize support costs, control the infrastructure itself, as well as license agreements for software used in the infrastructure.</p>
<p>Two ServiceNow applications, Discovery and Mapping Service, can help with these tasks. </p>
<p>The former automatically finds and identifies new assets (for example, servers connected to the corporate network) and enters information about them into a special database. In the meantime, the second solution defines the relationships between services and the infrastructure elements on which these services are built. As a result, all IT departments and the company processes become more transparent.</p>
<h2 id="heading-what-is-infrastructure-as-a-service-and-what-are-its-benefits-for-the-business">What Is Infrastructure as a Service and What Are Its Benefits for the Business?</h2>
<p>Infrastructure as a Service is the ultimate way to create infrastructure for an enterprise today. The main idea is that the vendor that provides cloud capabilities is also involved in the management of equipment and networks, while the business itself gets access through a separate API. </p>
<p>The main advantages of organizing your infrastructure this way are:</p>
<ul>
<li>You pay for what you use. You have the right to choose the operations and services necessary for your enterprise, and the final check will contain only the cost of these services.</li>
<li>Global spending is minimal as payments are made in small installments once a month.</li>
<li>High-tier security measures. Vendors invest a lot of money and technical means to ensure maximum security of their customers' infrastructures.</li>
<li>Access to the infrastructure is extremely simple and possible from any device with an Internet connection.  </li>
</ul>
<p><img src="https://lh4.googleusercontent.com/5NdLhwRvCxFRyXSfGd13-pwByCT7cHyG-PqxwY9TWee36ZWLT8UZHJs7ZeL6Mds6XbKxgdHErIBL-urRl-GTBVRhZoRGMsecYcc_-y2e4GyTaln094jyffK3VPXfMVEAN_YsfamX" alt="Image" width="1600" height="1370" loading="lazy"></p>
<h2 id="heading-wrapping-things-up">Wrapping Things Up</h2>
<p>IT infrastructures today are the mainstay of any modern business, regardless of its size or industry. Technologies combined into one system allow businesses to automate their processes, improve internal and external pipelines, and achieve new performance records. </p>
<p>But without a well-tuned IT infrastructure, it's impossible to gain a competitive advantage, and as a result, you can fall into the shadow of your rivals. </p>
<p>That is why you should think about organizing your own IT infrastructure or work on upgrading your existing one to more advanced cloud solutions that will boost your business and improve all operational aspects.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Infrastructure as Code? (Tutorial) ]]>
                </title>
                <description>
                    <![CDATA[ What is Infrastructure as Code? In this article you will learn all about Infrastructure as Code. I will start with an overview of the general concepts, and then I will show you how to implement Infrastructure as Code with three different labs. The la... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-is-infrastructure-as-code/</link>
                <guid isPermaLink="false">66b20724903dc07a135166ad</guid>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Tue, 16 Nov 2021 15:02:13 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/11/iac.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>What is Infrastructure as Code?</p>
<p>In this article you will learn all about Infrastructure as Code. I will start with an overview of the general concepts, and then I will show you how to implement Infrastructure as Code with three different labs. The labs use Python and AWS but the concepts will apply to other programming languages and cloud providers.</p>
<p>There is also a video version of this article. You can watch the <a target="_blank" href="https://youtu.be/EtEb40LE5zQ">video on the freeCodeCamp.org YouTube channel</a> (1-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/EtEb40LE5zQ" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>Let’s start by talking about what infrastructure as code is. To put it simply, it is setting up your infrastructure as code.</p>
<p>By infrastructure, I mean all the different things needed to deploy your software into a cloud environment. That can mean things like virtual machines, containers, or serverless functions. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/image-92.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Infrastructure also means all the other pieces of infrastructure you need to set up to make that successful. That can be security like IAM and KMS, or networking, or some of the monitoring and logging capabilities.</p>
<p>You can also use code to configure and set up data stores. These are the things that your application needs to store and manage data.</p>
<p>The last piece of the infrastructure landscape is the applications themselves and getting the applications that we're building into the infrastructure.</p>
<p>All of these different pieces of infrastructure can be set up using code. </p>
<p>It is becoming more and more important to automate infrastructure because applications can be deployed to production up to a hundred times per day. You don't want to have to do that manually.</p>
<p>Also, it is helpful to automate infrastructure to be provisioned or deprovisioned in response to load.</p>
<p>Infrastructures as code is is all about finding a way to describe using code what pieces of our infrastructure need to do.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/image-93.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Over the years there has been a transition with how people are using cloud infrastructure within their organizations.</p>
<p>In the first wave, it was relatively simple. The infrastructures were fairly static. It was often a single virtual machine that you just accessed through SSH.</p>
<p>It got a little more complex in the second wave. There were more containers and people started using provisioning tools to specify the application behaviors. People used Docker and DataDog.</p>
<p>Modern cloud infrastructure has added way more complexity. It uses containers, serverless, and more managed services as part of the applications. There are now way more different pieces involved in how people build the infrastructure.</p>
<p>Infrastructure as code is becoming a more important part of how people build and deliver applications because infrastructure as code is what what describes the glue between all the different edges on these diagrams.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/image-94.png" alt="Image" width="600" height="400" loading="lazy">
<em>Modern Infrastructure</em></p>
<p>The diagram for the modern infrastructure may look more complicated than the previous ones but it can actually be easier to maintain. A key benefit is that the dark grey squares in the diagram is the only part which is the code that you own. And that part is smaller that the previous ways of doing things. So a lot of the operational burden has been decreased compared to how it used to be done.</p>
<p>With infrastructure as code, you hand off some of the operational burden to AWS, Azure, Kubernetes, or some other system. Now the focus is on the glue between the different services that are being managed by a cloud provider.</p>
<p>Infrastructure code takes on an increasingly more important role in how you manage everything in the modern cloud.</p>
<p>So here are the three main ways that can be used to manage all the resources:</p>
<ul>
<li><strong>manual</strong>: point and click to create/modify resources in the console. </li>
<li><strong>ad-hoc automation</strong>: CLI commands or scripts to create/modify resources. </li>
<li><strong>infrastructure as code</strong>:<br>○ <strong>provisioning</strong>: declaratively create/modify resources.<br>○ <strong>configuration</strong>: change state of an existing resource post-provisioning.</li>
</ul>
<p>Infrastructure as code gives us the ability to write down what we want the desired state of our infrastructure to be. I'll be showing you exactly how to do these things later.</p>
<h2 id="heading-writing-infrastructure-as-code">Writing Infrastructure as Code</h2>
<p>One approach to infrastructure as code is to use JSON. Here is an example:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"AWSTemplateFormatVersion"</span>: <span class="hljs-string">"2010-09-09"</span>,
  <span class="hljs-attr">"Resources"</span>: {
    <span class="hljs-attr">"EC2Instance"</span>: {
      <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"AWS::EC2::Instance"</span>,
      <span class="hljs-attr">"Properties"</span>: {
        <span class="hljs-attr">"InstanceType"</span>: <span class="hljs-string">"t2.micro"</span>,
        <span class="hljs-attr">"SecurityGroups"</span>: [
          {
            <span class="hljs-attr">"Ref"</span>: <span class="hljs-string">"InstanceSecurityGroup"</span>
          }
        ],
        <span class="hljs-attr">"ImageId"</span>: <span class="hljs-string">"ami-0080e4c5bc078760e"</span>
      }
    },
    <span class="hljs-attr">"InstanceSecurityGroup"</span>: {
      <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"AWS::EC2::SecurityGroup"</span>,
      <span class="hljs-attr">"Properties"</span>: {
        <span class="hljs-attr">"GroupDescription"</span>: <span class="hljs-string">"Enable HTTP over port 80"</span>,
        <span class="hljs-attr">"SecurityGroupIngress"</span>: [
          {
            <span class="hljs-attr">"IpProtocol"</span>: <span class="hljs-string">"tcp"</span>,
            <span class="hljs-attr">"FromPort"</span>: <span class="hljs-string">"80"</span>,
            <span class="hljs-attr">"ToPort"</span>: <span class="hljs-string">"80"</span>,
            <span class="hljs-attr">"CidrIp"</span>: <span class="hljs-string">"0.0.0.0/0"</span>
          }
        ]
      }
    }
  }
}
</code></pre>
<p>The above is a way to tell AWS what resources you want to have.</p>
<p>Another method is to use a domain specific language (DSL). This is a custom method specific to the tool or cloud provider you are using. Here is an example:</p>
<pre><code>provider aws {
    region = <span class="hljs-string">"eu-central-1"</span>
}
resource <span class="hljs-string">"aws_security_group"</span>
<span class="hljs-string">"web_sg"</span> {
    description = <span class="hljs-string">"Enable HTTP over port 80"</span>
    ingress {
        protocol = <span class="hljs-string">"tcp"</span>
        from_port = <span class="hljs-number">80</span>
        to_port = <span class="hljs-number">80</span>
        cidr_blocks = [<span class="hljs-string">"0.0.0.0/0"</span>]
    }
}
resource <span class="hljs-string">"aws_instance"</span>
<span class="hljs-string">"web"</span> {
    ami = <span class="hljs-string">"ami-0080e4c5bc078760e"</span>
    instance_type = <span class="hljs-string">"t2.micro"</span>
    security_groups = [<span class="hljs-string">"$(aws_security_group.web_sg.id)"</span>]
}
</code></pre><p>Yet another way of defining infrastructure using code is to use a well-known programming language. For instance, Pulumi can be used to write infrastructure as code using TypeScript, JavaScript, Python, Go, and .NET.</p>
<p>Here is an example using TypeScript (and later we'll be using Python).</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> aws <span class="hljs-keyword">from</span> <span class="hljs-string">"@pulumi/aws"</span>;
<span class="hljs-keyword">let</span> group = <span class="hljs-keyword">new</span> aws.ec2.SecurityGroup(<span class="hljs-string">"web-sg"</span>, {
    description: <span class="hljs-string">"Enable HTTP over port 80"</span>,
    ingress: [{
        protocol: <span class="hljs-string">"tcp"</span>,
        fromPort: <span class="hljs-number">80</span>,
        toPort: <span class="hljs-number">80</span>,
        cidrBlocks: [<span class="hljs-string">"0.0.0.0/0"</span>]
    }, ],
});
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> az <span class="hljs-keyword">in</span> aws.getAvailabilityZones().names) {
    <span class="hljs-keyword">let</span> server = <span class="hljs-keyword">new</span> aws.ec2.Instance(<span class="hljs-string">`web-<span class="hljs-subst">${az}</span>`</span>, {
        instanceType: <span class="hljs-string">"t2.micro"</span>,
        securityGroups: [group.id],
        ami: <span class="hljs-string">"ami-0080e4c5bc078760e"</span>,
        availabilityZone: az,
    });
}
</code></pre>
<p>Using code gives the ability to do things that are not possible in some of the other methods. In the example above, there is a <code>for</code> loop that creates in Instance for each Availability Zone. Code gives the ability to use loops, conditionals, classes, packages, and more. Using popular programming languages also allows the use common IDEs, linters, and test frameworks.</p>
<h2 id="heading-labs">Labs</h2>
<p>There are a few different services that allow you to use popular languages to create infrastructure as code. In this article, we'll be using Pulumi.</p>
<p>This article (and the video course) was made possible through a grant from Pulumi.</p>
<p>Pulumi is an open source infrastructure as code tool for creating, deploying, and managing cloud infrastructure. Pulumi works with traditional infrastructure like VMs, networks, and databases, in addition to modern architectures, including containers, Kubernetes clusters, and serverless functions. Pulumi supports dozens of public, private, and hybrid cloud service providers.</p>
<p>We'll be using Python and deploying on AWS, though it can be done with other programming languages and cloud providers.</p>
<h1 id="heading-lab-1-provisioning-infrastructure-with-an-s3-bucket">Lab 1: Provisioning Infrastructure with an S3 Bucket</h1>
<p>We'll start out with a simple example. This will show an end-to-end experience of working with Pulumi using very simple resources.</p>
<p>In this first example we will do the following:</p>
<ul>
<li>Create a New Project</li>
<li>Configure AWS</li>
<li>Provision Infrastructure</li>
<li>Update Infrastructure</li>
<li>Make Your Stack Configurable</li>
<li>Create a Second Stack</li>
<li>Destroy Your Infrastructure</li>
</ul>
<p>We will use an S3 bucket and then work through the life cycle with a simple set of resources. Then in future examples you will learn how to implement more complex things.</p>
<p>With Pulumi, infrastructure is organized into projects. Each project is a single program that, when run, declares the desired infrastructure for Pulumi to manage.</p>
<p>Before we start the first lab, make sure Pulumi is installed. The way to install is different depending on your operating system. </p>
<p>If you have MacsOS and <a target="_blank" href="https://brew.sh/">Homebrew</a>, you can use the command <code>brew install pulumi</code>.</p>
<p>If you have Windows and <a target="_blank" href="https://chocolatey.org/">Chocolatey</a>, you can use the command <code>choco install pulumi</code>.</p>
<p><a target="_blank" href="https://www.pulumi.com/docs/get-started/install/">This page will give you additional ways of installing Pulumi</a>.</p>
<h3 id="heading-aws">AWS</h3>
<p>For this example, we are using AWS. You'll have to make sure you have an AWS account and have the CLI set up and authenticated. (<a target="_blank" href="https://www.pulumi.com/docs/intro/cloud-providers/aws/setup/">There is also a method</a> that does not use the CLI.)</p>
<p>You can sign up for a free AWS account here: <a target="_blank" href="https://aws.amazon.com/free/">https://aws.amazon.com/free/</a></p>
<p>Learn how to install the AWS CLI for your operating system here: <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html">https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html</a></p>
<p>For MacOS, you can use these commands:</p>
<pre><code>curl <span class="hljs-string">"https://awscli.amazonaws.com/AWSCLIV2.pkg"</span> -o <span class="hljs-string">"AWSCLIV2.pkg"</span>
sudo installer -pkg AWSCLIV2.pkg -target /
</code></pre><p>For Windows there are a few extra steps and you should just <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-windows.html">follow the instructions here</a>.</p>
<p>Next, you need to get an Access key ID and secret access key from AWS. <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-creds">Follow the instructions from Amazon to get these</a>.</p>
<p>Now run the following in the command line:</p>
<p><code>aws configure</code></p>
<p>Enter your Access Key ID and Secret Access Key when prompted. You can keep the "Default region name" and "Default output format" as None.</p>
<h3 id="heading-step-1-create-a-directory">Step 1: Create a Directory</h3>
<p>Each Pulumi project lives in its own directory. Create one now and change into it:</p>
<p><code>mkdir iac-lab1 cd iac-lab1</code></p>
<h3 id="heading-step-2-initialize-your-project">Step 2: Initialize Your Project</h3>
<p>A Pulumi project is just a directory with some files in it. It's possible for you to create a new one by hand. The <code>pulumi new</code> command, however, automates the process:</p>
<p><code>pulumi new python -y</code></p>
<p>If this is the first time you have used Pulumi, you will be directed to enter an access code or login. To get an access code, go to <a target="_blank" href="https://app.pulumi.com/account/tokens">https://app.pulumi.com/account/tokens</a></p>
<p>This command has created all the files we need, initialized a new stack named <code>dev</code> (an instance of our project). We now need to install our dependencies as part of our <code>virtualenv</code>.</p>
<h3 id="heading-step-3-setup-virtual-environment">Step 3: Setup Virtual Environment</h3>
<p>We now need to create a virtual environment and install the required Python packages. The Python module used to create and manage virtual environments is called <a target="_blank" href="https://docs.python.org/3/library/venv.html#module-venv"><code>venv</code></a>. Run the following commands:</p>
<pre><code>python3 -m venv venv
source venv/bin/activate
pip3 install -r requirements.txt
</code></pre><h3 id="heading-step-4-inspect-your-new-project">Step 4: Inspect Your New Project</h3>
<p>Our project is comprised of multiple files:</p>
<ul>
<li><strong><code>__main__.py</code></strong>: your program's main entry point file</li>
<li><strong><code>requirements.txt</code></strong>: your project's pip dependency information</li>
<li><strong><code>Pulumi.yaml</code></strong>: your project's metadata, containing its name and language</li>
</ul>
<p>If you look in the <code>__main__.py</code> file you will notice a single line of code:</p>
<p><code>import pulumi</code></p>
<h3 id="heading-configuring-aws">Configuring AWS</h3>
<p>Now that you have a basic project, let's configure AWS support for it.</p>
<h3 id="heading-step-5-install-the-aws-package">Step 5: Install the AWS Package</h3>
<p>Run the following command to install the AWS package:</p>
<p><code>pip3 install pulumi-aws</code></p>
<h3 id="heading-step-6-import-the-aws-package">Step 6: Import the AWS Package</h3>
<p>Now that the AWS package is installed, add the following line to <code>__main__.py</code> to import it:</p>
<p><code>import pulumi_aws as aws</code></p>
<h3 id="heading-step-7-configure-an-aws-region">Step 7: Configure an AWS Region</h3>
<p>Configure the AWS region you would like to deploy to by running the following on the command line.</p>
<p><code>pulumi config set aws:region us-east-1</code></p>
<p>You can choose a different AWS region if you like. (<a target="_blank" href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions">See this table</a> for a list of available regions.)</p>
<h3 id="heading-optional-step-configure-an-aws-profile">Optional Step: Configure an AWS Profile</h3>
<p>If you're using an alternative AWS profile, you can tell Pulumi which to use in one of two ways:</p>
<ul>
<li>Using an environment variable: <code>export AWS_PROFILE=&lt;profile name&gt;</code></li>
<li>Using configuration: <code>pulumi config set aws:profile &lt;profile name&gt;</code></li>
</ul>
<h3 id="heading-provisioning-infrastructure">Provisioning Infrastructure</h3>
<p>Now that you have a project configured to use AWS, you'll create some basic infrastructure in it. We will start with a simple S3 bucket.</p>
<h3 id="heading-step-8-declare-a-new-bucket">Step 8: Declare a New Bucket</h3>
<p>Add the following to your <code>__main__.py</code> file:</p>
<p><code>bucket = aws.s3.Bucket("my-bucket")</code></p>
<p>So the full file should look like this:</p>
<pre><code><span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws

bucket = aws.s3.Bucket(<span class="hljs-string">"my-bucket"</span>)
</code></pre><h3 id="heading-step-9-preview-your-changes">Step 9: Preview Your Changes</h3>
<p>Now preview your changes:</p>
<pre><code>pulumi up
</code></pre><p>This command evaluates your program, determines the resource updates to make, and shows you an outline of these changes:</p>
<pre><code>Previewing update (dev)

View Live: https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab1/dev/previews/827f9488-cc23-441f-946a-9852090ab0e3</span>

     Type                 Name          Plan
 +   pulumi:pulumi:Stack  iac-lab1-dev  create
 +   └─ aws:s3:Bucket     my-bucket     create

<span class="hljs-attr">Resources</span>:
    + <span class="hljs-number">2</span> to create

Do you want to perform <span class="hljs-built_in">this</span> update?  [Use arrows to move, enter to select, type to filter]
  yes
&gt; no
  details
</code></pre><p>This is a summary view. Select <code>details</code> to view the full set of properties:</p>
<pre><code>+ pulumi:pulumi:Stack: (create)
    [urn=urn:pulumi:dev::iac-lab1::pulumi:pulumi:Stack::iac-lab1-dev]
    + aws:s3/bucket:Bucket: (create)
        [urn=urn:pulumi:dev::iac-lab1::aws:s3/bucket:Bucket::my-bucket]
        [provider=urn:pulumi:dev::iac-lab1::pulumi:providers:aws::default_4_22_1::<span class="hljs-number">04</span>da6b54<span class="hljs-number">-80e4</span><span class="hljs-number">-46</span>f7<span class="hljs-number">-96</span>ec-b56ff0331ba9]
        <span class="hljs-attr">acl</span>         : <span class="hljs-string">"private"</span>
        <span class="hljs-attr">bucket</span>      : <span class="hljs-string">"my-bucket-185b4e1"</span>
        <span class="hljs-attr">forceDestroy</span>: <span class="hljs-literal">false</span>

Do you want to perform <span class="hljs-built_in">this</span> update?  [Use arrows to move, enter to select, type to filter]
  yes
&gt; no
  details
</code></pre><p>The stack resource is a synthetic resource that all resources your program creates are parented to.</p>
<h3 id="heading-step-10-deploy-your-changes">Step 10: Deploy Your Changes</h3>
<p>Now that we've seen the full set of changes, let's deploy them. Select <code>yes</code>:</p>
<pre><code>Updating (dev)

View Live: https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab1/dev/updates/1</span>

     Type                 Name          Status
 +   pulumi:pulumi:Stack  iac-lab1-dev  created
 +   └─ aws:s3:Bucket     my-bucket     created

<span class="hljs-attr">Resources</span>:
    + <span class="hljs-number">2</span> created

<span class="hljs-attr">Duration</span>: <span class="hljs-number">5</span>s
</code></pre><p>Now our S3 bucket has been created in our AWS account. If you view your buckets on the AWS website, you will see the new bucket. If you go to the URL shown in the output you will be taken to the <a target="_blank" href="https://www.pulumi.com/docs/intro/console/">Pulumi Console</a>, which records your deployment history.</p>
<h3 id="heading-step-11-export-your-new-bucket-name">Step 11: Export Your New Bucket Name</h3>
<p>To inspect your new bucket, you will need its physical AWS name. Pulumi records a logical name, <code>my-bucket</code>, however the resulting AWS name will be different.</p>
<p>Programs can export variables which will be shown in the CLI and recorded for each deployment. Export your bucket's name by adding this line to the end of <code>__main__.py</code>:</p>
<p><code>pulumi.export('bucket_name', bucket.bucket)</code></p>
<p>Now deploy the changes with:</p>
<p><code>pulumi up</code></p>
<p>You will now notice a new <code>Outputs</code> section in the output displaying the bucket's name:</p>
<pre><code>Outputs:
  + bucket_name: <span class="hljs-string">"my-bucket-25cb812"</span>
</code></pre><p>You can select "Yes" to perform the update.</p>
<h3 id="heading-step-12-inspect-your-new-bucket">Step 12: Inspect Your New Bucket</h3>
<p>You can see all of the output by running <code>pulumi stack output</code>.</p>
<p>The result should look something like this:</p>
<pre><code>Current stack outputs (<span class="hljs-number">1</span>):
    OUTPUT       VALUE
    bucket_name  my-bucket<span class="hljs-number">-25</span>cb812
</code></pre><p>Then you can run the <code>aws</code> CLI to list the objects in this new bucket (getting the bucket name using the command from above):</p>
<pre><code>aws s3 ls $(pulumi stack output bucket_name)
</code></pre><p>There currently will not be any result from that command since the bucket is still empty.</p>
<h3 id="heading-updating-your-infrastructure">Updating Your Infrastructure</h3>
<p>We just saw how to create new infrastructure from scratch. Next, let's make a few updates:</p>
<ol>
<li>Add an object to your bucket.</li>
<li>Serve content from your bucket as a website.</li>
<li>Programmatically create infrastructure.</li>
</ol>
<p>This demonstrates how declarative infrastructure as code tools can be used not just for initial provisioning, but also subsequent changes to existing resources.</p>
<h3 id="heading-step-13-add-an-object-to-your-bucket">Step 13: Add an Object to Your Bucket</h3>
<p>Create a directory <code>site/</code> and add a new <code>index.html</code> file with the following contents:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Hello everybody!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Now update your <code>__main__.py</code> file so that it looks like this:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws
<span class="hljs-keyword">import</span> os

bucket = aws.s3.Bucket(<span class="hljs-string">"my-bucket"</span>)

filepath = os.path.join(<span class="hljs-string">"site"</span>, <span class="hljs-string">"index.html"</span>)
obj = aws.s3.BucketObject(<span class="hljs-string">"index.html"</span>,
    bucket=bucket.bucket,
    source=pulumi.FileAsset(filepath)
)

pulumi.export(<span class="hljs-string">'bucket_name'</span>, bucket.bucket)
</code></pre>
<p>Deploy the changes:</p>
<p><code>pulumi up</code></p>
<p>This will give you a preview and selecting <code>yes</code> will apply the changes:</p>
<pre><code>Previewing update (dev)

View Live: https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab1/dev/previews/35a07205-2889-403c-b01d-0342eb01ed85</span>

     Type                    Name          Plan
     <span class="hljs-attr">pulumi</span>:pulumi:Stack     iac-lab1-dev
 +   └─ aws:s3:BucketObject  index.html    create

<span class="hljs-attr">Resources</span>:
    + <span class="hljs-number">1</span> to create
    <span class="hljs-number">2</span> unchanged

Do you want to perform <span class="hljs-built_in">this</span> update?  [Use arrows to move, enter to select, type to filter]
  yes
&gt; no
  details
</code></pre><p>A single resource is added and the 2 existing resources are left unchanged. This is a key attribute of infrastructure as code — such tools determine the minimal set of changes necessary to update your infrastructure from one change to the next.</p>
<p>Now, list again the contents of your bucket:</p>
<p><code>aws s3 ls $(pulumi stack output bucket_name)</code></p>
<p>You will notice that the <code>index.html</code> file has been added:</p>
<pre><code><span class="hljs-number">2019</span><span class="hljs-number">-10</span><span class="hljs-number">-22</span> <span class="hljs-number">16</span>:<span class="hljs-number">50</span>:<span class="hljs-number">54</span>        <span class="hljs-number">68</span> index.html
</code></pre><h3 id="heading-step-14-serve-content-from-your-bucket-as-a-website">Step 14: Serve Content From Your Bucket as a Website</h3>
<p>To serve content from your bucket as a website, you'll need to update a few properties.</p>
<p>First, your bucket needs a website property that sets the default index document to <code>index.html</code>. That can be achieved by updating <code>__main__.py</code> file with the following:</p>
<pre><code>bucket = aws.s3.Bucket(<span class="hljs-string">"my-bucket"</span>,
    website={
        <span class="hljs-string">"index_document"</span>: <span class="hljs-string">"index.html"</span>
})
</code></pre><p>Next, your <code>index.html</code> object will need two changes: an ACL (Access Control List) of <code>public-read</code> so that it can be accessed anonymously over the Internet, and a content type so that it is served as HTML. You will also need to export the resulting bucket's endpoint URL so we can easily access it.</p>
<p>Combining everything, the file should now look like this:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> mimetypes

bucket = aws.s3.Bucket(<span class="hljs-string">"my-bucket"</span>,
    website={
        <span class="hljs-string">"index_document"</span>: <span class="hljs-string">"index.html"</span>
})

filepath = os.path.join(<span class="hljs-string">"site"</span>, <span class="hljs-string">"index.html"</span>)
mime_type, _ = mimetypes.guess_type(filepath)
obj = aws.s3.BucketObject(<span class="hljs-string">"index.html"</span>,
        bucket=bucket.bucket,
        source=pulumi.FileAsset(filepath),
        acl=<span class="hljs-string">"public-read"</span>,
        content_type=mime_type
)

pulumi.export(<span class="hljs-string">'bucket_name'</span>, bucket.bucket)
pulumi.export(<span class="hljs-string">'bucket_endpoint'</span>, pulumi.Output.concat(<span class="hljs-string">"http://"</span>, bucket.website_endpoint))
</code></pre>
<p>Now deploy the changes:</p>
<p><code>pulumi up</code></p>
<p>The preview will look something like this:</p>
<pre><code>Previewing update (dev)

View Live: https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab1/dev/previews/50730def-0493-4667-83b8-fcedc4408f3a</span>

     Type                    Name          Plan       Info
     <span class="hljs-attr">pulumi</span>:pulumi:Stack     iac-lab1-dev
 ~   ├─ aws:s3:Bucket        my-bucket     update     [diff: +website]
 ~   └─ aws:s3:BucketObject  index.html    update     [diff: ~acl,contentType]

<span class="hljs-attr">Outputs</span>:
  + bucket_endpoint: output&lt;string&gt;

Resources:
    ~ <span class="hljs-number">2</span> to update
    <span class="hljs-number">1</span> unchanged

Do you want to perform <span class="hljs-built_in">this</span> update?  [Use arrows to move, enter to select, type to filter]
  yes
&gt; no
  details
</code></pre><p>Selecting <code>details</code> during the preview has a lot more information this time:</p>
<pre><code>  pulumi:pulumi:Stack: (same)
    [urn=urn:pulumi:dev::iac-lab1::pulumi:pulumi:Stack::iac-lab1-dev]
    ~ aws:s3/bucket:Bucket: (update)
        [id=my-bucket<span class="hljs-number">-25</span>cb812]
        [urn=urn:pulumi:dev::iac-lab1::aws:s3/bucket:Bucket::my-bucket]
        [provider=urn:pulumi:dev::iac-lab1::pulumi:providers:aws::default_4_22_1::<span class="hljs-number">032</span>fffef<span class="hljs-number">-7448</span><span class="hljs-number">-4</span>be3<span class="hljs-number">-97</span>d2<span class="hljs-number">-3198</span>f149eb39]
      + website: {
          + indexDocument: <span class="hljs-string">"index.html"</span>
        }
    --outputs:--
  + bucket_endpoint: output&lt;string&gt;
    ~ aws:s3/bucketObject:BucketObject: (update)
        [id=index.html]
        [urn=urn:pulumi:dev::iac-lab1::aws:s3/bucketObject:BucketObject::index.html]
        [provider=urn:pulumi:dev::iac-lab1::pulumi:providers:aws::default_4_22_1::<span class="hljs-number">032</span>fffef<span class="hljs-number">-7448</span><span class="hljs-number">-4</span>be3<span class="hljs-number">-97</span>d2<span class="hljs-number">-3198</span>f149eb39]
      ~ acl        : <span class="hljs-string">"private"</span> =&gt; <span class="hljs-string">"public-read"</span>
      ~ contentType: <span class="hljs-string">"binary/octet-stream"</span> =&gt; <span class="hljs-string">"text/html"</span>

Do you want to perform <span class="hljs-built_in">this</span> update?  [Use arrows to move, enter to select, type to filter]
  yes
&gt; no
  details
</code></pre><p>Now just select <code>yes</code> to deploy all of the updates. The result will show the URL that can be used to access your <code>index.html</code> file.</p>
<pre><code>Updating (dev)

View Live: https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab1/dev/updates/4</span>

     Type                    Name          Status      Info
     <span class="hljs-attr">pulumi</span>:pulumi:Stack     iac-lab1-dev
 ~   ├─ aws:s3:Bucket        my-bucket     updated     [diff: +website]
 ~   └─ aws:s3:BucketObject  index.html    updated     [diff: ~acl,contentType]

<span class="hljs-attr">Outputs</span>:
  + bucket_endpoint: <span class="hljs-string">"http://my-bucket-25cb812.s3-website-us-east-1.amazonaws.com"</span>
    <span class="hljs-attr">bucket_name</span>    : <span class="hljs-string">"my-bucket-25cb812"</span>

<span class="hljs-attr">Resources</span>:
    ~ <span class="hljs-number">2</span> updated
    <span class="hljs-number">1</span> unchanged

<span class="hljs-attr">Duration</span>: <span class="hljs-number">6</span>s
</code></pre><h3 id="heading-step-15-access-your-website">Step 15: Access Your Website</h3>
<p>Besides going to the URL in a web browser (from the results shown above), you can also access the website with this command in the terminal:</p>
<p><code>curl $(pulumi stack output bucket_endpoint)</code></p>
<p>This will fetch and print our <code>index.html</code> file:</p>
<pre><code>&lt;html&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Hello Pulumi<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span></span>
&lt;/html&gt;
</code></pre><h3 id="heading-making-your-stack-configurable">Making Your Stack Configurable</h3>
<p>Right now, the bucket's contents are hard-coded. Next, you'll make the location of the contents configurable, and add support for populating the bucket with an entire directory's worth of contents.</p>
<h3 id="heading-step-16-adding-a-config-variable">Step 16: Adding a Config Variable</h3>
<p>Instead of hard-coding the <code>"site"</code> directory, we will use configuration to make it easy to change the location without editing the program.</p>
<p>Add this to your <code>__main__.py</code> file right below the imports:</p>
<pre><code>config = pulumi.Config()
site_dir = config.require(<span class="hljs-string">"siteDir"</span>)
</code></pre><h3 id="heading-step-17-populating-the-bucket-based-on-config">Step 17: Populating the Bucket Based on Config</h3>
<p>And replace the hard-coded <code>"site"</code> parameter with this imported <code>siteDir</code> variable:</p>
<pre><code>filepath = os.path.join(site_dir, <span class="hljs-string">"index.html"</span>)
mime_type, _ = mimetypes.guess_type(filepath)

obj = aws.s3.BucketObject(<span class="hljs-string">"index.html"</span>,
    bucket=bucket.bucket,
    source=pulumi.FileAsset(filepath),
    acl=<span class="hljs-string">"public-read"</span>,
    content_type=mime_type
)
</code></pre><p>Now let's see how this can be helpful. Using the terminal rename the <code>site</code> directory to <code>www</code>:</p>
<p><code>mv site www</code></p>
<h3 id="heading-step-18-deploying-the-changes">Step 18: Deploying the Changes</h3>
<p>Now, deploy your changes. To do so, first configure your stack. If you don't, you'll get an error:</p>
<p><code>pulumi up</code></p>
<p>This results in an error like the following:</p>
<pre><code>    error: Missing required configuration variable <span class="hljs-string">'iac-lab1:siteDir'</span>
        please set a value using the command <span class="hljs-string">`pulumi config set iac-lab1:siteDir &lt;value&gt;`</span>
    <span class="hljs-attr">error</span>: an unhandled error occurred: Program exited <span class="hljs-keyword">with</span> non-zero exit code: <span class="hljs-number">1</span>
</code></pre><p>Configure the <code>iac-workshop:siteDir</code> variable similar to how the <code>aws:region</code> variable was configured:</p>
<p><code>pulumi config set iac-lab1:siteDir www</code></p>
<p>Then run:</p>
<p><code>pulumi up</code></p>
<p>This detects that the path has changed and will perform a simple update.</p>
<h3 id="heading-step-19-add-more-files">Step 19: Add More Files</h3>
<p>Instead of hard-coding the set of files, you will now change the program to read the entire contents of the <code>www</code> directory.</p>
<p>Add a new file named  <code>about.html</code> to the <code>www</code> directory and add some HTML. Here is one option:</p>
<pre><code>&lt;html&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>I am not a cat.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
&lt;/html&gt;
</code></pre><p>Now replace the object allocation code with the code below. You will notice that there is now a for loop to loop through every file in the site directory:</p>
<pre><code><span class="hljs-keyword">for</span> file <span class="hljs-keyword">in</span> os.listdir(site_dir):
    filepath = os.path.join(site_dir, file)
    mime_type, _ = mimetypes.guess_type(filepath)
    obj = aws.s3.BucketObject(file,
          bucket=bucket.bucket,
          source=pulumi.FileAsset(filepath),
          acl=<span class="hljs-string">"public-read"</span>,
          content_type=mime_type
    )
</code></pre><p>Deploy:</p>
<p><code>pulumi up</code></p>
<p>You will see a single new object created for the <code>about.html</code> file:</p>
<pre><code>Updating (dev)

View Live: https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab1/dev/updates/5</span>

     Type                    Name          Status
     <span class="hljs-attr">pulumi</span>:pulumi:Stack     iac-lab1-dev
 +   └─ aws:s3:BucketObject  about.html    created
</code></pre><p>You can now go to the URL from before and add "/about.html" to the end.</p>
<p>You can also get the file in the terminal with the command:</p>
<p><code>curl $(pulumi stack output bucket_endpoint)/about.html</code></p>
<h3 id="heading-creating-a-second-stack">Creating a Second Stack</h3>
<p>It is easy to create multiple instances of the same project. In Pulumi, each instance is called a stack. This is useful if you have multiple development or test environments, staging versus production, and scaling a given infrastructure across many regions.</p>
<h3 id="heading-step-20-create-and-configure-a-new-stack">Step 20: Create and Configure a New Stack</h3>
<p>Create a new stack named 'prod':</p>
<p><code>pulumi stack init prod</code></p>
<p>Next, configure its two required variables:</p>
<pre><code>pulumi config set aws:region eu-west<span class="hljs-number">-1</span>
pulumi config set iac-lab1:siteDir wwwprod
</code></pre><p>You can see the list of stacks for your current project with the command:</p>
<p><code>pulumi stack ls</code></p>
<p>It will print all stacks for this project that are available to you:</p>
<pre><code>NAME   LAST UPDATE     RESOURCE COUNT  URL
dev    <span class="hljs-number">12</span> minutes ago  <span class="hljs-number">5</span>               https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab1/dev</span>
prod*  n/a             n/a             https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab1/prod</span>
</code></pre><h3 id="heading-step-21-populate-the-new-site-directory">Step 21: Populate the New Site Directory</h3>
<p>We could have used the existing <code>www</code> directory for the <code>siteDir</code>. But for this example you will use a different <code>wwwprod</code> directory to demonstrate how it can be configured.</p>
<p>So create this new directory:</p>
<p><code>mkdir wwwprod</code></p>
<p>Add a new <code>index.html</code> file to it. It can contain any HTML but here is a suggestion:</p>
<pre><code>&lt;html&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Hello universe.<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>(in production)<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span></span>
&lt;/html&gt;
</code></pre><h3 id="heading-step-22-deploy-the-new-stack">Step 22: Deploy the New Stack</h3>
<p>Now deploy all of the changes:</p>
<p><code>pulumi up</code></p>
<p>This will create an entirely new set of resources from scratch, unrelated to the existing <code>dev</code> stack's resources.</p>
<pre><code>Updating (prod)

View Live: https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab1/prod/updates/1</span>

     Type                    Name           Status
 +   pulumi:pulumi:Stack     iac-lab1-prod  created
 +   ├─ aws:s3:Bucket        my-bucket      created
 +   └─ aws:s3:BucketObject  index.html     created

<span class="hljs-attr">Outputs</span>:
    bucket_endpoint: <span class="hljs-string">"http://my-bucket-0d2f29a.s3-website-eu-west-1.amazonaws.com"</span>
    <span class="hljs-attr">bucket_name</span>    : <span class="hljs-string">"my-bucket-0d2f29a"</span>

<span class="hljs-attr">Resources</span>:
    + <span class="hljs-number">3</span> created

<span class="hljs-attr">Duration</span>: <span class="hljs-number">14</span>s
</code></pre><p>You will notice a new URL in the output. You can access that URL in a browser to see the new website.</p>
<p>Also you can get it using this command in the terminal:</p>
<p><code>curl $(pulumi stack output bucket_endpoint)</code></p>
<h3 id="heading-destroying-your-infrastructure">Destroying Your Infrastructure</h3>
<p>The final thing we'll cover in this first lab is to destroy all of the resources from the two stacks created.</p>
<h3 id="heading-step-23-destroy-resources">Step 23: Destroy Resources</h3>
<p>First, destroy the resources in your current stack:</p>
<p><code>pulumi destroy</code></p>
<p>This will show you a preview, much like the <code>pulumi up</code> command does:</p>
<pre><code>     Type                    Name           Plan
 -   pulumi:pulumi:Stack     iac-lab1-prod  <span class="hljs-keyword">delete</span>
 -   ├─ aws:s3:BucketObject  index.html     <span class="hljs-keyword">delete</span>
 -   └─ aws:s3:Bucket        my-bucket      <span class="hljs-keyword">delete</span>

<span class="hljs-attr">Outputs</span>:
  - bucket_endpoint: <span class="hljs-string">"http://my-bucket-0d2f29a.s3-website-eu-west-1.amazonaws.com"</span>
  - bucket_name    : <span class="hljs-string">"my-bucket-0d2f29a"</span>

<span class="hljs-attr">Resources</span>:
    - <span class="hljs-number">3</span> to <span class="hljs-keyword">delete</span>

Do you want to perform <span class="hljs-built_in">this</span> destroy?  [Use arrows to move, enter to select, type to filter]
  yes
&gt; no
  details
</code></pre><p>To proceed, select <code>yes</code>.</p>
<pre><code>Destroying (prod)

View Live: https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab1/prod/updates/2</span>

     Type                    Name           Status
 -   pulumi:pulumi:Stack     iac-lab1-prod  deleted
 -   ├─ aws:s3:BucketObject  index.html     deleted
 -   └─ aws:s3:Bucket        my-bucket      deleted

<span class="hljs-attr">Outputs</span>:
  - bucket_endpoint: <span class="hljs-string">"http://my-bucket-0d2f29a.s3-website-eu-west-1.amazonaws.com"</span>
  - bucket_name    : <span class="hljs-string">"my-bucket-0d2f29a"</span>

<span class="hljs-attr">Resources</span>:
    - <span class="hljs-number">3</span> deleted

<span class="hljs-attr">Duration</span>: <span class="hljs-number">3</span>s

The resources <span class="hljs-keyword">in</span> the stack have been deleted, but the history and configuration associated <span class="hljs-keyword">with</span> the stack are still maintained.
If you want to remove the stack completely, run <span class="hljs-string">'pulumi stack rm prod'</span>.
</code></pre><h3 id="heading-step-24-remove-the-stack">Step 24: Remove the Stack</h3>
<p>The AWS resources for this stack have been destroyed. You may have noticed the message printed at the end that the history and configuration associated with the stack are still maintained. This means all past history is still available and you can perform subsequent updates on this stack.</p>
<p>Now, fully remove the stack and all history:</p>
<p><code>pulumi stack rm</code></p>
<p>This is irreversible and so asks to confirm that this is your intent:</p>
<pre><code>This will permanently remove the <span class="hljs-string">'prod'</span> stack!
Please confirm that <span class="hljs-built_in">this</span> is what you<span class="hljs-string">'d like to do by typing ("prod"):</span>
</code></pre><p>Type the name of the stack and hit enter. The stack is now gone.</p>
<h3 id="heading-step-24-select-another-stack-rinse-and-repeat">Step 24: Select Another Stack, Rinse and Repeat</h3>
<p>After destroying <code>prod</code>, you still have the <code>dev</code> stack. To destroy it too, first select it:</p>
<pre><code>pulumi stack select dev
</code></pre><p>Now, go back and repeat the previous two steps to destroy it.</p>
<h3 id="heading-step-25-verify-that-stacks-are-gone">Step 25: Verify That Stacks are Gone</h3>
<p>Verify that all of this project's stacks are now gone:</p>
<p><code>pulumi stack ls</code></p>
<h2 id="heading-conclusion-of-lab-1">Conclusion of Lab 1</h2>
<p>Congratulations! You have completed the first lab. The next few labs are a little shorter and demonstrate some more advanced tasks.</p>
<h1 id="heading-lab-2-provisioning-ec2-virtual-machines">Lab 2: Provisioning EC2 Virtual Machines</h1>
<p>Amazon Elastic Compute Cloud (Amazon EC2) is a web service that provides secure, resizable compute capacity in the cloud. It is designed to make web-scale cloud computing easier for developers. Amazon EC2’s simple web service interface allows you to obtain and configure capacity with minimal friction.</p>
<p>In this second lab, you'll first create a single EC2 virtual machine (VM). Afterwards, you'll scale that out to a VM per availability zone in your region, and then add a load balancer to spread load across the entire fleet.</p>
<h3 id="heading-step-1-create-a-directory-1">Step 1: Create a Directory</h3>
<p>We could use the same project and directory as before. But we'll create a new one to get more practice.</p>
<p>Create a new directory that is not inside the directory of used in the last lab.</p>
<pre><code>cd ..
mkdir iac-lab2
cd iac-lab2
</code></pre><h3 id="heading-step-2-initialize-your-project-1">Step 2: Initialize Your Project</h3>
<p>Remember, a Pulumi project is just a directory with some files in it. It’s possible for you to create a new one by hand. The <code>pulumi new</code> command, however, automates the process:</p>
<pre><code class="lang-bash">pulumi new aws-python -y
</code></pre>
<p>This command has created all the files we need, initialized a new stack named <code>dev</code> (an instance of our project), and installed the needed package dependencies from PyPi. The "aws" part of the command made sure our <code>__main__.py</code> file started with samle code to create an AWS bucket.</p>
<h3 id="heading-step-3-configure-an-aws-region">Step 3: Configure an AWS Region</h3>
<p>Configure the AWS region you would like to deploy to:</p>
<pre><code class="lang-bash">pulumi config <span class="hljs-built_in">set</span> aws:region us-west-2
</code></pre>
<h3 id="heading-step-4-declare-the-ec2-instance">Step 4: Declare the EC2 Instance</h3>
<p>Remove any existing code here from the bootstrapping of your project. Then, import the AWS package in an empty <code>__main__.py</code> file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> pulumi <span class="hljs-keyword">import</span> export
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws
</code></pre>
<p>Now dynamically query the Amazon Linux machine image. Doing this in code avoids needing to hard-code the machine image (a.k.a., its AMI - An Amazon Machine Image provides the information required to launch an instance):</p>
<pre><code class="lang-python">ami = aws.ec2.get_ami(
    most_recent=<span class="hljs-string">"true"</span>,
    owners=[<span class="hljs-string">"137112412989"</span>],
    filters=[{<span class="hljs-string">"name"</span>:<span class="hljs-string">"name"</span>,<span class="hljs-string">"values"</span>:[<span class="hljs-string">"amzn-ami-hvm-*-x86_64-ebs"</span>]}])
</code></pre>
<p>We also need to grab the default Virtual Private Cloud is a service that lets you launch AWS resources in a logically isolated virtual network that you define) that is available in our AWS account:</p>
<pre><code class="lang-python">vpc = aws.ec2.get_vpc(default=<span class="hljs-literal">True</span>)
</code></pre>
<p>Next, create an AWS security group. This enables <code>ping</code> over ICMP and HTTP traffic on port 80:</p>
<pre><code class="lang-python">group = aws.ec2.SecurityGroup(
    <span class="hljs-string">"web-secgrp"</span>,
    description=<span class="hljs-string">'Enable HTTP access'</span>,
    vpc_id=vpc.id,
    ingress=[
        { <span class="hljs-string">'protocol'</span>: <span class="hljs-string">'icmp'</span>, <span class="hljs-string">'from_port'</span>: <span class="hljs-number">8</span>, <span class="hljs-string">'to_port'</span>: <span class="hljs-number">0</span>, <span class="hljs-string">'cidr_blocks'</span>: [<span class="hljs-string">'0.0.0.0/0'</span>] },
        { <span class="hljs-string">'protocol'</span>: <span class="hljs-string">'tcp'</span>, <span class="hljs-string">'from_port'</span>: <span class="hljs-number">80</span>, <span class="hljs-string">'to_port'</span>: <span class="hljs-number">80</span>, <span class="hljs-string">'cidr_blocks'</span>: [<span class="hljs-string">'0.0.0.0/0'</span>] }
])
</code></pre>
<p>Create the server. Notice it has a startup script that spins up a simple Python webserver:</p>
<pre><code class="lang-python">server = aws.ec2.Instance(
    <span class="hljs-string">'web-server'</span>,
    instance_type=<span class="hljs-string">"t2.micro"</span>,
    vpc_security_group_ids=[group.id],
    ami=ami.id,
    user_data=<span class="hljs-string">"""
#!/bin/bash
echo "Hello, World!" &gt; index.html
nohup python -m SimpleHTTPServer 80 &amp;
    """</span>,
    tags={
        <span class="hljs-string">"Name"</span>: <span class="hljs-string">"web-server"</span>,
    },
)
</code></pre>
<p>For most real-world applications, you would want to create a dedicated image for your application, rather than embedding the script in your code like this.</p>
<p>Finally export the EC2 instances’s resulting IP address and hostname:</p>
<pre><code class="lang-python">pulumi.export(<span class="hljs-string">'ip'</span>, server.public_ip)
pulumi.export(<span class="hljs-string">'hostname'</span>, server.public_dns)
</code></pre>
<p>After this change, your <code>__main__.py</code> should look like this:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws


ami = aws.ec2.get_ami(
    most_recent=<span class="hljs-string">"true"</span>,
    owners=[<span class="hljs-string">"137112412989"</span>],
    filters=[{<span class="hljs-string">"name"</span>: <span class="hljs-string">"name"</span>, <span class="hljs-string">"values"</span>: [<span class="hljs-string">"amzn-ami-hvm-*-x86_64-ebs"</span>]}],
)

vpc = aws.ec2.get_vpc(default=<span class="hljs-literal">True</span>)

group = aws.ec2.SecurityGroup(
    <span class="hljs-string">"web-secgrp"</span>,
    description=<span class="hljs-string">"Enable HTTP Access"</span>,
    ingress=[
        {
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"icmp"</span>,
            <span class="hljs-string">"from_port"</span>: <span class="hljs-number">8</span>,
            <span class="hljs-string">"to_port"</span>: <span class="hljs-number">0</span>,
            <span class="hljs-string">"cidr_blocks"</span>: [<span class="hljs-string">"0.0.0.0/0"</span>],
        },
        {
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"tcp"</span>,
            <span class="hljs-string">"from_port"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"to_port"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"cidr_blocks"</span>: [<span class="hljs-string">"0.0.0.0/0"</span>],
        },
    ],
)

server = aws.ec2.Instance(
    <span class="hljs-string">"web=server"</span>,
    instance_type=<span class="hljs-string">"t2.micro"</span>,
    vpc_security_group_ids=[group.name],
    ami=ami.id,
    user_data=<span class="hljs-string">"""
#!/bin/bash
echo "Hello, World!" &gt; index.html
nohup python -m SimpleHTTPServer 80 &amp;
    """</span>,
    tags={
        <span class="hljs-string">"Name"</span>: <span class="hljs-string">"web-server"</span>,
    },
)

pulumi.export(<span class="hljs-string">"ip"</span>, server.public_ip)
pulumi.export(<span class="hljs-string">"hostname"</span>, server.public_dns)
</code></pre>
<h3 id="heading-step-5-provision-the-ec2-instance-and-access-it">Step 5: Provision the EC2 Instance and Access It</h3>
<p>To provision the VM, run:</p>
<pre><code class="lang-bash">pulumi up
</code></pre>
<p>After confirming, you will see output like the following:</p>
<pre><code>Updating (dev)

View Live: https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab2/dev/updates/3</span>

     Type                 Name          Status
     <span class="hljs-attr">pulumi</span>:pulumi:Stack  iac-lab2-dev
 ~   └─ aws:ec2:Instance  web=server    updated

<span class="hljs-attr">Outputs</span>:
    hostname: <span class="hljs-string">"ec2-52-13-25-152.us-west-2.compute.amazonaws.com"</span>
    <span class="hljs-attr">ip</span>      : <span class="hljs-string">"52.13.25.152"</span>

<span class="hljs-attr">Resources</span>:
    ~ <span class="hljs-number">1</span> updated
    <span class="hljs-number">2</span> unchanged

<span class="hljs-attr">Duration</span>: <span class="hljs-number">8</span>s
</code></pre><p>If you get an error, try running <code>pulumi up</code> again and confirming. The first time I ran it there was a race condition which required re-running the command.</p>
<p>You can now try accessing the URL from the output in a web browser. </p>
<p>Alternatively, you can use this command:</p>
<pre><code class="lang-bash">curl $(pulumi stack output hostname)
</code></pre>
<p>Either way you should see a response from the Python webserver:</p>
<pre><code>Hello, World!
</code></pre><h3 id="heading-step-6-add-more-ec2-instances">Step 6: Add more EC2 instances</h3>
<p>Now you will create multiple EC2 instances, each running the same Python webserver, across all AWS availability zones in your region. Replace the part of your code that creates the webserver and exports the resulting IP address and hostname with the following:</p>
<pre><code class="lang-python">...
ips = []
hostnames = []
<span class="hljs-keyword">for</span> az <span class="hljs-keyword">in</span> aws.get_availability_zones().names:
    server = aws.ec2.Instance(<span class="hljs-string">f'web-server-<span class="hljs-subst">{az}</span>'</span>,
      instance_type=<span class="hljs-string">"t2.micro"</span>,
      vpc_security_group_ids=[group.id],
      ami=ami.id,
      availability_zone=az,
      user_data=<span class="hljs-string">"""#!/bin/bash
echo \"Hello, World -- from {}!\" &gt; index.html
nohup python -m SimpleHTTPServer 80 &amp;
"""</span>.format(az),
      tags={
          <span class="hljs-string">"Name"</span>: <span class="hljs-string">"web-server"</span>,
      },
    )
    ips.append(server.public_ip)
    hostnames.append(server.public_dns)

pulumi.export(<span class="hljs-string">"ips"</span>, ips)
pulumi.export(<span class="hljs-string">"hostnames"</span>, hostnames)
</code></pre>
<p>After this change, your <code>__main__.py</code> should look like this:</p>
<pre><code class="lang-python"><span class="hljs-string">"""An AWS Python Pulumi program"""</span>

<span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws


ami = aws.ec2.get_ami(
    most_recent=<span class="hljs-string">"true"</span>,
    owners=[<span class="hljs-string">"137112412989"</span>],
    filters=[{<span class="hljs-string">"name"</span>: <span class="hljs-string">"name"</span>, <span class="hljs-string">"values"</span>: [<span class="hljs-string">"amzn-ami-hvm-*-x86_64-ebs"</span>]}],
)

vpc = aws.ec2.get_vpc(default=<span class="hljs-literal">True</span>)

group = aws.ec2.SecurityGroup(
    <span class="hljs-string">"web-secgrp"</span>,
    description=<span class="hljs-string">"Enable HTTP Access"</span>,
    vpc_id=vpc.id,
    ingress=[
        {
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"icmp"</span>,
            <span class="hljs-string">"from_port"</span>: <span class="hljs-number">8</span>,
            <span class="hljs-string">"to_port"</span>: <span class="hljs-number">0</span>,
            <span class="hljs-string">"cidr_blocks"</span>: [<span class="hljs-string">"0.0.0.0/0"</span>],
        },
        {
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"tcp"</span>,
            <span class="hljs-string">"from_port"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"to_port"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"cidr_blocks"</span>: [<span class="hljs-string">"0.0.0.0/0"</span>],
        },
    ],
)


ips = []
hostnames = []

<span class="hljs-keyword">for</span> az <span class="hljs-keyword">in</span> aws.get_availability_zones().names:
    server = aws.ec2.Instance(
        <span class="hljs-string">f"web-server-<span class="hljs-subst">{az}</span>"</span>,
        instance_type=<span class="hljs-string">"t2.micro"</span>,
        vpc_security_group_ids=[group.id],
        ami=ami.id,
        user_data=<span class="hljs-string">"""#!/bin/bash
echo \"Hello, World -- from {}!\" &gt; index.html
nohup python -m SimpleHTTPServer 80 &amp;
"""</span>.format(
            az
        ),
        tags={
            <span class="hljs-string">"Name"</span>: <span class="hljs-string">"web-server"</span>,
        },
    )
    ips.append(server.public_ip)
    hostnames.append(server.public_dns)

pulumi.export(<span class="hljs-string">"ips"</span>, ips)
pulumi.export(<span class="hljs-string">"hostnames"</span>, hostnames)
</code></pre>
<p>Now run a command to update your stack with the new resource definitions:</p>
<pre><code class="lang-bash">pulumi up
</code></pre>
<p>You will see output like the following:</p>
<pre><code>Updating (dev)

View Live: https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab2/dev/updates/4</span>

     Type                 Name                   Status
     <span class="hljs-attr">pulumi</span>:pulumi:Stack  iac-lab2-dev
 +   ├─ aws:ec2:Instance  web-server-us-west<span class="hljs-number">-2</span>a  created
 +   ├─ aws:ec2:Instance  web-server-us-west<span class="hljs-number">-2</span>b  created
 +   ├─ aws:ec2:Instance  web-server-us-west<span class="hljs-number">-2</span>c  created
 +   ├─ aws:ec2:Instance  web-server-us-west<span class="hljs-number">-2</span>d  created
 -   └─ aws:ec2:Instance  web=server             deleted

<span class="hljs-attr">Outputs</span>:
  - hostname : <span class="hljs-string">"ec2-52-13-25-152.us-west-2.compute.amazonaws.com"</span>
  + hostnames: [
  +     [<span class="hljs-number">0</span>]: <span class="hljs-string">"ec2-34-222-148-4.us-west-2.compute.amazonaws.com"</span>
  +     [<span class="hljs-number">1</span>]: <span class="hljs-string">"ec2-54-69-196-240.us-west-2.compute.amazonaws.com"</span>
  +     [<span class="hljs-number">2</span>]: <span class="hljs-string">"ec2-54-186-64-129.us-west-2.compute.amazonaws.com"</span>
  +     [<span class="hljs-number">3</span>]: <span class="hljs-string">"ec2-52-25-211-116.us-west-2.compute.amazonaws.com"</span>
    ]
  - ip       : <span class="hljs-string">"52.13.25.152"</span>
  + ips      : [
  +     [<span class="hljs-number">0</span>]: <span class="hljs-string">"34.222.148.4"</span>
  +     [<span class="hljs-number">1</span>]: <span class="hljs-string">"54.69.196.240"</span>
  +     [<span class="hljs-number">2</span>]: <span class="hljs-string">"54.186.64.129"</span>
  +     [<span class="hljs-number">3</span>]: <span class="hljs-string">"52.25.211.116"</span>
    ]

<span class="hljs-attr">Resources</span>:
    + <span class="hljs-number">4</span> created
    - <span class="hljs-number">1</span> deleted
    <span class="hljs-number">5</span> changes. <span class="hljs-number">2</span> unchanged

<span class="hljs-attr">Duration</span>: <span class="hljs-number">1</span>m55s
</code></pre><p>Notice that your original server was deleted and new ones created in its place, because its name changed.</p>
<p>To test the changes, curl any of the resulting IP addresses or hostnames (or access the URLs in a web browser):</p>
<pre><code class="lang-bash"><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> {0..2}; <span class="hljs-keyword">do</span> curl $(pulumi stack output hostnames | jq -r <span class="hljs-string">".[<span class="hljs-variable">${i}</span>]"</span>); <span class="hljs-keyword">done</span>
</code></pre>
<p>The count of servers depends on the number of AZs in your region. You may need to adjust the <code>{0..2}</code> .</p>
<p>The <code>pulumi stack output</code> command emits JSON serialized data — hence the use of the <code>jq</code> tool to extract a specific index. If you don’t have <code>jq</code>, don’t worry; simply copy-and-paste the hostname or IP address from the console output and <code>curl</code> that.</p>
<p>Note that the webserver number is included in its response:</p>
<pre><code>Hello, World -- <span class="hljs-keyword">from</span> us-west<span class="hljs-number">-2</span>a!
Hello, World -- <span class="hljs-keyword">from</span> us-west<span class="hljs-number">-2</span>b!
Hello, World -- <span class="hljs-keyword">from</span> us-west<span class="hljs-number">-2</span>c!
</code></pre><h3 id="heading-add-a-loadbalancer">Add a loadbalancer</h3>
<p>Needing to loop over the webservers isn’t very realistic. You will now create a load balancer over them to distribute load evenly.</p>
<h3 id="heading-step-7-update-our-security-group">Step 7: Update our Security Group</h3>
<p>We need to add an egress rule to our security group. Whenever you add a listener to your load balancer or update the health check port for a target group used by the load balancer to route requests, you must verify that the security groups associated with the load balancer allow traffic on the new port in both directions.</p>
<pre><code class="lang-python">...
group = aws.ec2.SecurityGroup(
    <span class="hljs-string">"web-secgrp"</span>,
    ingress=[
        { <span class="hljs-string">'protocol'</span>: <span class="hljs-string">'icmp'</span>, <span class="hljs-string">'from_port'</span>: <span class="hljs-number">8</span>, <span class="hljs-string">'to_port'</span>: <span class="hljs-number">0</span>, <span class="hljs-string">'cidr_blocks'</span>: [<span class="hljs-string">'0.0.0.0/0'</span>] },
        { <span class="hljs-string">'protocol'</span>: <span class="hljs-string">'tcp'</span>, <span class="hljs-string">'from_port'</span>: <span class="hljs-number">80</span>, <span class="hljs-string">'to_port'</span>: <span class="hljs-number">80</span>, <span class="hljs-string">'cidr_blocks'</span>: [<span class="hljs-string">'0.0.0.0/0'</span>] },
    ],
    egress=[
        { <span class="hljs-string">'protocol'</span>: <span class="hljs-string">'tcp'</span>, <span class="hljs-string">'from_port'</span>: <span class="hljs-number">80</span>, <span class="hljs-string">'to_port'</span>: <span class="hljs-number">80</span>, <span class="hljs-string">'cidr_blocks'</span>: [<span class="hljs-string">'0.0.0.0/0'</span>] },
    ]
)
...
</code></pre>
<p>This is required to ensure the security group ingress rules don’t conflict with the load balancer’s.</p>
<h3 id="heading-step-8-define-the-alb">Step 8: Define the ALB</h3>
<p>Now right after the security group creation, and before the EC2 creation block, add the load balancer creation steps:</p>
<pre><code class="lang-python">...
vpc = aws.ec2.get_vpc(default=<span class="hljs-literal">True</span>)
vpc_subnets = aws.ec2.get_subnet_ids(vpc_id=vpc.id)

lb = aws.lb.LoadBalancer(
    <span class="hljs-string">"loadbalancer"</span>,
    internal=<span class="hljs-literal">False</span>,
    security_groups=[group.id],
    subnets=vpc_subnets.ids,
    load_balancer_type=<span class="hljs-string">"application"</span>,
)

target_group = aws.lb.TargetGroup(
    <span class="hljs-string">"target-group"</span>, port=<span class="hljs-number">80</span>, protocol=<span class="hljs-string">"HTTP"</span>, target_type=<span class="hljs-string">"ip"</span>, vpc_id=vpc.id
)

listener = aws.lb.Listener(
    <span class="hljs-string">"listener"</span>,
    load_balancer_arn=lb.arn,
    port=<span class="hljs-number">80</span>,
    default_actions=[{<span class="hljs-string">"type"</span>: <span class="hljs-string">"forward"</span>, <span class="hljs-string">"target_group_arn"</span>: target_group.arn}],
)
...
</code></pre>
<p>Here, we’ve defined the ALB, its TargetGroup and some Listeners, but we haven’t actually added the EC2 instances to the ALB.</p>
<h3 id="heading-step-9-add-the-instances-to-the-alb">Step 9: Add the Instances to the ALB</h3>
<p>Replace the EC2 creation block with the following:</p>
<pre><code class="lang-python">...
ips = []
hostnames = []
<span class="hljs-keyword">for</span> az <span class="hljs-keyword">in</span> aws.get_availability_zones().names:
    server = aws.ec2.Instance(<span class="hljs-string">f'web-server-<span class="hljs-subst">{az}</span>'</span>,
      instance_type=<span class="hljs-string">"t2.micro"</span>,
      security_groups=[group.name],
      ami=ami.id,
      user_data=<span class="hljs-string">"""#!/bin/bash
echo \"Hello, World -- from {}!\" &gt; index.html
nohup python -m SimpleHTTPServer 80 &amp;
"""</span>.format(az),
      availability_zone=az,
      tags={
          <span class="hljs-string">"Name"</span>: <span class="hljs-string">"web-server"</span>,
      },
    )
    ips.append(server.public_ip)
    hostnames.append(server.public_dns)

    attachment = aws.lb.TargetGroupAttachment(<span class="hljs-string">f'web-server-<span class="hljs-subst">{az}</span>'</span>,
        target_group_arn=target_group.arn,
        target_id=server.private_ip,
        port=<span class="hljs-number">80</span>,
    )

export(<span class="hljs-string">'ips'</span>, ips)
export(<span class="hljs-string">'hostnames'</span>, hostnames)
export(<span class="hljs-string">"url"</span>, lb.dns_name)
</code></pre>
<p>After this change, your <strong>main</strong>.py should look like this:</p>
<pre><code class="lang-python"><span class="hljs-string">"""An AWS Python Pulumi program"""</span>

<span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws


ami = aws.ec2.get_ami(
    most_recent=<span class="hljs-string">"true"</span>,
    owners=[<span class="hljs-string">"137112412989"</span>],
    filters=[{<span class="hljs-string">"name"</span>: <span class="hljs-string">"name"</span>, <span class="hljs-string">"values"</span>: [<span class="hljs-string">"amzn-ami-hvm-*-x86_64-ebs"</span>]}],
)

vpc = aws.ec2.get_vpc(default=<span class="hljs-literal">True</span>)
vpc_subnets = aws.ec2.get_subnet_ids(vpc_id=vpc.id)

group = aws.ec2.SecurityGroup(
    <span class="hljs-string">"web-secgrp"</span>,
    description=<span class="hljs-string">"Enable HTTP Access"</span>,
    vpc_id=vpc.id,
    ingress=[
        {
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"icmp"</span>,
            <span class="hljs-string">"from_port"</span>: <span class="hljs-number">8</span>,
            <span class="hljs-string">"to_port"</span>: <span class="hljs-number">0</span>,
            <span class="hljs-string">"cidr_blocks"</span>: [<span class="hljs-string">"0.0.0.0/0"</span>],
        },
        {
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"tcp"</span>,
            <span class="hljs-string">"from_port"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"to_port"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"cidr_blocks"</span>: [<span class="hljs-string">"0.0.0.0/0"</span>],
        },
    ],
    egress=[
        {
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"tcp"</span>,
            <span class="hljs-string">"from_port"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"to_port"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"cidr_blocks"</span>: [<span class="hljs-string">"0.0.0.0/0"</span>],
        }
    ],
)

lb = aws.lb.LoadBalancer(
    <span class="hljs-string">"loadbalancer"</span>,
    internal=<span class="hljs-literal">False</span>,
    security_groups=[group.id],
    subnets=vpc_subnets.ids,
    load_balancer_type=<span class="hljs-string">"application"</span>,
)

target_group = aws.lb.TargetGroup(
    <span class="hljs-string">"target-group"</span>, port=<span class="hljs-number">80</span>, protocol=<span class="hljs-string">"HTTP"</span>, target_type=<span class="hljs-string">"ip"</span>, vpc_id=vpc.id
)

listener = aws.lb.Listener(
    <span class="hljs-string">"listener"</span>,
    load_balancer_arn=lb.arn,
    port=<span class="hljs-number">80</span>,
    default_actions=[{<span class="hljs-string">"type"</span>: <span class="hljs-string">"forward"</span>, <span class="hljs-string">"target_group_arn"</span>: target_group.arn}],
)


ips = []
hostnames = []

<span class="hljs-keyword">for</span> az <span class="hljs-keyword">in</span> aws.get_availability_zones().names:
    server = aws.ec2.Instance(
        <span class="hljs-string">f"web-server-<span class="hljs-subst">{az}</span>"</span>,
        instance_type=<span class="hljs-string">"t2.micro"</span>,
        vpc_security_group_ids=[group.id],
        ami=ami.id,
        user_data=<span class="hljs-string">"""#!/bin/bash
echo \"Hello, World -- from {}!\" &gt; index.html
nohup python -m SimpleHTTPServer 80 &amp;
"""</span>.format(
            az
        ),
        tags={
            <span class="hljs-string">"Name"</span>: <span class="hljs-string">"web-server"</span>,
        },
    )
    ips.append(server.public_ip)
    hostnames.append(server.public_dns)

    attachment = aws.lb.TargetGroupAttachment(
        <span class="hljs-string">f"web-server-<span class="hljs-subst">{az}</span>"</span>,
        target_group_arn=target_group.arn,
        target_id=server.private_ip,
        port=<span class="hljs-number">80</span>,
    )


pulumi.export(<span class="hljs-string">"ips"</span>, ips)
pulumi.export(<span class="hljs-string">"hostnames"</span>, hostnames)
pulumi.export(<span class="hljs-string">"url"</span>, lb.dns_name)
</code></pre>
<p>This is all the infrastructure we need for our load balanced webserver. Let’s apply it.</p>
<h3 id="heading-step-10-deploy-your-changes-1">Step 10: Deploy your Changes</h3>
<p>Deploy these updates:</p>
<pre><code class="lang-bash">pulumi up
</code></pre>
<p>This should result in a fairly large update and, if all goes well, the load balancer’s resulting endpoint URL:</p>
<pre><code>Updating (dev)

View Live: https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab2/dev/updates/5</span>

     Type                             Name                   Status      Info
     <span class="hljs-attr">pulumi</span>:pulumi:Stack              iac-lab2-dev
 +   ├─ aws:lb:TargetGroup            target-group           created
 ~   ├─ aws:ec2:SecurityGroup         web-secgrp             updated     [diff: ~egress]
 +   ├─ aws:lb:LoadBalancer           loadbalancer           created
 +   ├─ aws:lb:TargetGroupAttachment  web-server-us-west<span class="hljs-number">-2</span>a  created
 +   ├─ aws:lb:TargetGroupAttachment  web-server-us-west<span class="hljs-number">-2</span>b  created
 +   ├─ aws:lb:TargetGroupAttachment  web-server-us-west<span class="hljs-number">-2</span>c  created
 +   ├─ aws:lb:TargetGroupAttachment  web-server-us-west<span class="hljs-number">-2</span>d  created
 +   └─ aws:lb:Listener               listener               created

<span class="hljs-attr">Outputs</span>:
    hostnames: [
        [<span class="hljs-number">0</span>]: <span class="hljs-string">"ec2-34-222-148-4.us-west-2.compute.amazonaws.com"</span>
        [<span class="hljs-number">1</span>]: <span class="hljs-string">"ec2-54-69-196-240.us-west-2.compute.amazonaws.com"</span>
        [<span class="hljs-number">2</span>]: <span class="hljs-string">"ec2-54-186-64-129.us-west-2.compute.amazonaws.com"</span>
        [<span class="hljs-number">3</span>]: <span class="hljs-string">"ec2-52-25-211-116.us-west-2.compute.amazonaws.com"</span>
    ]
    <span class="hljs-attr">ips</span>      : [
        [<span class="hljs-number">0</span>]: <span class="hljs-string">"34.222.148.4"</span>
        [<span class="hljs-number">1</span>]: <span class="hljs-string">"54.69.196.240"</span>
        [<span class="hljs-number">2</span>]: <span class="hljs-string">"54.186.64.129"</span>
        [<span class="hljs-number">3</span>]: <span class="hljs-string">"52.25.211.116"</span>
    ]
  + url      : <span class="hljs-string">"loadbalancer-c9a1e24-965935136.us-west-2.elb.amazonaws.com"</span>

<span class="hljs-attr">Resources</span>:
    + <span class="hljs-number">7</span> created
    ~ <span class="hljs-number">1</span> updated
    <span class="hljs-number">8</span> changes. <span class="hljs-number">5</span> unchanged

<span class="hljs-attr">Duration</span>: <span class="hljs-number">3</span>m28s
</code></pre><h3 id="heading-step-11-verify">Step 11: Verify</h3>
<p>Now we can curl the load balancer:</p>
<pre><code class="lang-bash"><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> {0..10}; <span class="hljs-keyword">do</span> curl $(pulumi stack output url); <span class="hljs-keyword">done</span>
</code></pre>
<p>Observe that the resulting text changes based on where the request is routed:</p>
<pre><code>Hello, World -- <span class="hljs-keyword">from</span> us-west<span class="hljs-number">-2</span>a!
Hello, World -- <span class="hljs-keyword">from</span> us-west<span class="hljs-number">-2</span>a!
Hello, World -- <span class="hljs-keyword">from</span> us-west<span class="hljs-number">-2</span>d!
Hello, World -- <span class="hljs-keyword">from</span> us-west<span class="hljs-number">-2</span>b!
Hello, World -- <span class="hljs-keyword">from</span> us-west<span class="hljs-number">-2</span>b!
Hello, World -- <span class="hljs-keyword">from</span> us-west<span class="hljs-number">-2</span>c!
Hello, World -- <span class="hljs-keyword">from</span> us-west<span class="hljs-number">-2</span>b!
Hello, World -- <span class="hljs-keyword">from</span> us-west<span class="hljs-number">-2</span>a!
Hello, World -- <span class="hljs-keyword">from</span> us-west<span class="hljs-number">-2</span>c!
Hello, World -- <span class="hljs-keyword">from</span> us-west<span class="hljs-number">-2</span>d!
Hello, World -- <span class="hljs-keyword">from</span> us-west<span class="hljs-number">-2</span>a!
</code></pre><h3 id="heading-step-12-destroy-everything">Step 12: Destroy Everything</h3>
<p>Finally, destroy the resources and the stack itself:</p>
<pre><code>pulumi destroy
pulumi stack rm
</code></pre><h2 id="heading-lab-3-deploying-docker-images-to-ecs-amp-fargate">Lab 3: Deploying Docker images to ECS &amp; Fargate</h2>
<p>In this lab, you will use Pulumi to deploy a Docker Image to ECS with Fargate.</p>
<p>Let's start from the beginning again to get more practice.</p>
<h3 id="heading-step-1-create-a-directory-2">Step 1: Create a Directory</h3>
<p>Create a new directory and change into it:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ..
mkdir iac-lab3
<span class="hljs-built_in">cd</span> iac-lab3
</code></pre>
<p>Pulumi will use the directory name as your project name by default. To create an independent project, simply name the directory differently.</p>
<h3 id="heading-step-2-initialize-your-project-2">Step 2: Initialize Your Project</h3>
<p>A Pulumi project is just a directory with some files in it. It’s possible for you to create a new one by hand. The <code>pulumi new</code> command, however, automates the process:</p>
<pre><code class="lang-bash">pulumi new aws-python -y
</code></pre>
<p>This command has created all the files we need, initialized a new stack named <code>dev</code> (an instance of our project), and installed the needed package dependencies from PyPi.</p>
<h3 id="heading-step-3-inspect-your-new-project">Step 3: Inspect Your New Project</h3>
<p>Our project is comprised of multiple files (these are the same as before so this is a review):</p>
<ul>
<li><strong><code>__main__.py</code></strong>: your program’s main entrypoint file</li>
<li><strong><code>requirements.txt</code></strong>: your project’s Python dependency information</li>
<li><strong><code>Pulumi.yaml</code></strong>: your project’s metadata, containing its name and language</li>
<li><strong><code>venv</code></strong>: a <a target="_blank" href="https://pypi.org/project/virtualenv/">virtualenv</a> for your project</li>
</ul>
<p>Run <code>cat __main__.py</code> to see the contents of your project’s empty program:</p>
<pre><code class="lang-python"><span class="hljs-string">"""An AWS Python Pulumi program"""</span>

<span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">from</span> pulumi_aws <span class="hljs-keyword">import</span> s3

<span class="hljs-comment"># Create an AWS resource (S3 Bucket)</span>
bucket = s3.Bucket(<span class="hljs-string">'my-bucket'</span>)

<span class="hljs-comment"># Export the name of the bucket</span>
pulumi.export(<span class="hljs-string">'bucket_name'</span>, bucket.id)
</code></pre>
<p>Feel free to explore the other files, although we won’t be editing any of them by hand.</p>
<h3 id="heading-step-4-configure-an-aws-region">Step 4: Configure an AWS Region</h3>
<p>Configure the AWS region you would like to deploy to:</p>
<pre><code class="lang-bash">pulumi config <span class="hljs-built_in">set</span> aws:region us-west-2
</code></pre>
<h3 id="heading-step-5-create-an-ecs-cluster">Step 5: Create an ECS Cluster</h3>
<p>Remove all the boilerplate from the project bootstrap.</p>
<p>Import the AWS and Pulumi packages in an empty <code>__main__.py</code> file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws
</code></pre>
<p>And now create a new ECS cluster. You will use the default values, so doing so is very concise:</p>
<p>After this change, your <code>__main__.py</code> should look like this:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws

cluster = aws.ecs.Cluster(<span class="hljs-string">"cluster"</span>)
</code></pre>
<h3 id="heading-step-6-create-a-load-balanced-container-service">Step 6: Create a Load-Balanced Container Service</h3>
<p>Next, allocate the application load balancer (ALB) and listen for HTTP traffic port 80. In order to do this, we need to find the default VPC and the subnet groups for it:</p>
<pre><code class="lang-python">...
vpc = aws.ec2.get_vpc(default=<span class="hljs-literal">True</span>)
vpc_subnets = aws.ec2.get_subnet_ids(vpc_id=vpc.id)

group = aws.ec2.SecurityGroup(
    <span class="hljs-string">"web-secgrp"</span>,
    vpc_id=vpc.id,
    description=<span class="hljs-string">'Enable HTTP access'</span>,
    ingress=[
        { <span class="hljs-string">'protocol'</span>: <span class="hljs-string">'icmp'</span>, <span class="hljs-string">'from_port'</span>: <span class="hljs-number">8</span>, <span class="hljs-string">'to_port'</span>: <span class="hljs-number">0</span>, <span class="hljs-string">'cidr_blocks'</span>: [<span class="hljs-string">'0.0.0.0/0'</span>] },
        { <span class="hljs-string">'protocol'</span>: <span class="hljs-string">'tcp'</span>, <span class="hljs-string">'from_port'</span>: <span class="hljs-number">80</span>, <span class="hljs-string">'to_port'</span>: <span class="hljs-number">80</span>, <span class="hljs-string">'cidr_blocks'</span>: [<span class="hljs-string">'0.0.0.0/0'</span>] }
    ],
    egress=[
        { <span class="hljs-string">'protocol'</span>: <span class="hljs-string">"-1"</span>, <span class="hljs-string">'from_port'</span>: <span class="hljs-number">0</span>, <span class="hljs-string">'to_port'</span>: <span class="hljs-number">0</span>, <span class="hljs-string">'cidr_blocks'</span>: [<span class="hljs-string">'0.0.0.0/0'</span>] }
    ])

alb = aws.lb.LoadBalancer(
    <span class="hljs-string">"app-lb"</span>,
    internal=<span class="hljs-string">"false"</span>,
    security_groups=[group.id],
    subnets=vpc_subnets.ids,
    load_balancer_type=<span class="hljs-string">"application"</span>,
)

atg = aws.lb.TargetGroup(
    <span class="hljs-string">"app-tg"</span>,
    port=<span class="hljs-number">80</span>,
    deregistration_delay=<span class="hljs-number">0</span>,
    protocol=<span class="hljs-string">"HTTP"</span>,
    target_type=<span class="hljs-string">"ip"</span>,
    vpc_id=vpc.id,
)

wl = aws.lb.Listener(
    <span class="hljs-string">"web"</span>,
    load_balancer_arn=alb.arn,
    port=<span class="hljs-number">80</span>,
    default_actions=[{<span class="hljs-string">"type"</span>: <span class="hljs-string">"forward"</span>, <span class="hljs-string">"target_group_arn"</span>: atg.arn}],
)
</code></pre>
<p>After this change, your <code>__main__.py</code> should look like this:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws

cluster = aws.ecs.Cluster(<span class="hljs-string">"cluster"</span>)

vpc = aws.ec2.get_vpc(default=<span class="hljs-literal">True</span>)
vpc_subnets = aws.ec2.get_subnet_ids(vpc_id=vpc.id)

group = aws.ec2.SecurityGroup(
    <span class="hljs-string">"web-secgrp"</span>,
    vpc_id=vpc.id,
    description=<span class="hljs-string">"Enable HTTP access"</span>,
    ingress=[
        {
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"icmp"</span>,
            <span class="hljs-string">"from_port"</span>: <span class="hljs-number">8</span>,
            <span class="hljs-string">"to_port"</span>: <span class="hljs-number">0</span>,
            <span class="hljs-string">"cidr_blocks"</span>: [<span class="hljs-string">"0.0.0.0/0"</span>],
        },
        {
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"tcp"</span>,
            <span class="hljs-string">"from_port"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"to_port"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"cidr_blocks"</span>: [<span class="hljs-string">"0.0.0.0/0"</span>],
        },
    ],
    egress=[
        {
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"-1"</span>,
            <span class="hljs-string">"from_port"</span>: <span class="hljs-number">0</span>,
            <span class="hljs-string">"to_port"</span>: <span class="hljs-number">0</span>,
            <span class="hljs-string">"cidr_blocks"</span>: [<span class="hljs-string">"0.0.0.0/0"</span>],
        }
    ],
)

alb = aws.lb.LoadBalancer(
    <span class="hljs-string">"app-lb"</span>,
    internal=<span class="hljs-string">"false"</span>,
    security_groups=[group.id],
    subnets=vpc_subnets.ids,
    load_balancer_type=<span class="hljs-string">"application"</span>,
)

atg = aws.lb.TargetGroup(
    <span class="hljs-string">"app-tg"</span>,
    port=<span class="hljs-number">80</span>,
    deregistration_delay=<span class="hljs-number">0</span>,
    protocol=<span class="hljs-string">"HTTP"</span>,
    target_type=<span class="hljs-string">"ip"</span>,
    vpc_id=vpc.id,
)

wl = aws.lb.Listener(
    <span class="hljs-string">"web"</span>,
    load_balancer_arn=alb.arn,
    port=<span class="hljs-number">80</span>,
    default_actions=[{<span class="hljs-string">"type"</span>: <span class="hljs-string">"forward"</span>, <span class="hljs-string">"target_group_arn"</span>: atg.arn}],
)
</code></pre>
<p>Run your program with <code>pulumi up</code>:</p>
<pre><code>Updating (dev)

View Live: https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab3/dev/updates/1</span>

     Type                      Name          Status
 +   pulumi:pulumi:Stack       iac-lab3-dev  created
 +   ├─ aws:ecs:Cluster        cluster       created
 +   ├─ aws:ec2:SecurityGroup  web-secgrp    created
 +   ├─ aws:lb:TargetGroup     app-tg        created
 +   ├─ aws:lb:LoadBalancer    app-lb        created
 +   └─ aws:lb:Listener        web           created

<span class="hljs-attr">Resources</span>:
    + <span class="hljs-number">6</span> created

<span class="hljs-attr">Duration</span>: <span class="hljs-number">3</span>m36s
</code></pre><p>We’ve fleshed out our infrastructure and added a LoadBalancer we can add infrastructure to, in the next steps we’ll run a container.</p>
<h3 id="heading-step-7-create-ecs-fargateservice">Step 7: Create ECS FargateService</h3>
<p>In order to create a Fargate service, we need to add an IAM Role and a Task Definition and Service. The ECS Cluster will run the <code>"nginx"</code> image from the Docker Hub.</p>
<p>Firstly, we need to add a new import at the top of our file</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
</code></pre>
<p>Now let’s define our IAM Role and attach a policy. You should define this at the end of your <code>__main__.py</code>:</p>
<pre><code class="lang-python">...
role = aws.iam.Role(<span class="hljs-string">"task-exec-role"</span>,
    assume_role_policy=json.dumps({
        <span class="hljs-string">"Version"</span>: <span class="hljs-string">"2008-10-17"</span>,
        <span class="hljs-string">"Statement"</span>: [{
            <span class="hljs-string">"Sid"</span>: <span class="hljs-string">""</span>,
            <span class="hljs-string">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-string">"Principal"</span>: {
                <span class="hljs-string">"Service"</span>: <span class="hljs-string">"ecs-tasks.amazonaws.com"</span>
            },
            <span class="hljs-string">"Action"</span>: <span class="hljs-string">"sts:AssumeRole"</span>
        }]
    }))

rpa = aws.iam.RolePolicyAttachment(<span class="hljs-string">"task-exec-policy"</span>,
    role=role.name,
    policy_arn=<span class="hljs-string">"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"</span>
)
...
</code></pre>
<p>Then we can define a task definition for our ECS service:</p>
<pre><code class="lang-python">task_definition = aws.ecs.TaskDefinition(<span class="hljs-string">"app-task"</span>,
    family=<span class="hljs-string">"fargate-task-definition"</span>,
    cpu=<span class="hljs-string">"256"</span>,
    memory=<span class="hljs-string">"512"</span>,
    network_mode=<span class="hljs-string">"awsvpc"</span>,
    requires_compatibilities=[<span class="hljs-string">"FARGATE"</span>],
    execution_role_arn=role.arn,
    container_definitions=json.dumps([{
        <span class="hljs-string">"name"</span>: <span class="hljs-string">"my-app"</span>,
        <span class="hljs-string">"image"</span>: <span class="hljs-string">"nginx"</span>,
        <span class="hljs-string">"portMappings"</span>: [{
            <span class="hljs-string">"containerPort"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"hostPort"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"tcp"</span>
        }]
    }])
)

service = aws.ecs.Service(<span class="hljs-string">"app-svc"</span>,
    cluster=cluster.arn,
    desired_count=<span class="hljs-number">1</span>,
    launch_type=<span class="hljs-string">"FARGATE"</span>,
    task_definition=task_definition.arn,
    network_configuration={
        <span class="hljs-string">"assign_public_ip"</span>: <span class="hljs-string">"true"</span>,
        <span class="hljs-string">"subnets"</span>: vpc_subnets.ids,
        <span class="hljs-string">"security_groups"</span>: [group.id]
    },
    load_balancers=[{
        <span class="hljs-string">"target_group_arn"</span>: atg.arn,
        <span class="hljs-string">"container_name"</span>: <span class="hljs-string">"my-app"</span>,
        <span class="hljs-string">"container_port"</span>: <span class="hljs-number">80</span>
    }],
    opts=pulumi.ResourceOptions(depends_on=[wl])
)

pulumi.export(<span class="hljs-string">"url"</span>, alb.dns_name)
</code></pre>
<p>After these changes, your <code>__main__.py</code> should look like this</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws
<span class="hljs-keyword">import</span> json

cluster = aws.ecs.Cluster(<span class="hljs-string">"cluster"</span>)

vpc = aws.ec2.get_vpc(default=<span class="hljs-literal">True</span>)
vpc_subnets = aws.ec2.get_subnet_ids(vpc_id=vpc.id)

group = aws.ec2.SecurityGroup(
    <span class="hljs-string">"web-secgrp"</span>,
    vpc_id=vpc.id,
    description=<span class="hljs-string">"Enable HTTP access"</span>,
    ingress=[
        {
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"icmp"</span>,
            <span class="hljs-string">"from_port"</span>: <span class="hljs-number">8</span>,
            <span class="hljs-string">"to_port"</span>: <span class="hljs-number">0</span>,
            <span class="hljs-string">"cidr_blocks"</span>: [<span class="hljs-string">"0.0.0.0/0"</span>],
        },
        {
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"tcp"</span>,
            <span class="hljs-string">"from_port"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"to_port"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"cidr_blocks"</span>: [<span class="hljs-string">"0.0.0.0/0"</span>],
        },
    ],
    egress=[
        {
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"-1"</span>,
            <span class="hljs-string">"from_port"</span>: <span class="hljs-number">0</span>,
            <span class="hljs-string">"to_port"</span>: <span class="hljs-number">0</span>,
            <span class="hljs-string">"cidr_blocks"</span>: [<span class="hljs-string">"0.0.0.0/0"</span>],
        }
    ],
)

alb = aws.lb.LoadBalancer(
    <span class="hljs-string">"app-lb"</span>,
    internal=<span class="hljs-string">"false"</span>,
    security_groups=[group.id],
    subnets=vpc_subnets.ids,
    load_balancer_type=<span class="hljs-string">"application"</span>,
)

atg = aws.lb.TargetGroup(
    <span class="hljs-string">"app-tg"</span>,
    port=<span class="hljs-number">80</span>,
    deregistration_delay=<span class="hljs-number">0</span>,
    protocol=<span class="hljs-string">"HTTP"</span>,
    target_type=<span class="hljs-string">"ip"</span>,
    vpc_id=vpc.id,
)

wl = aws.lb.Listener(
    <span class="hljs-string">"web"</span>,
    load_balancer_arn=alb.arn,
    port=<span class="hljs-number">80</span>,
    default_actions=[{<span class="hljs-string">"type"</span>: <span class="hljs-string">"forward"</span>, <span class="hljs-string">"target_group_arn"</span>: atg.arn}],
)

role = aws.iam.Role(<span class="hljs-string">"task-exec-role"</span>,
    assume_role_policy=json.dumps({
        <span class="hljs-string">"Version"</span>: <span class="hljs-string">"2008-10-17"</span>,
        <span class="hljs-string">"Statement"</span>: [{
            <span class="hljs-string">"Sid"</span>: <span class="hljs-string">""</span>,
            <span class="hljs-string">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-string">"Principal"</span>: {
                <span class="hljs-string">"Service"</span>: <span class="hljs-string">"ecs-tasks.amazonaws.com"</span>
            },
            <span class="hljs-string">"Action"</span>: <span class="hljs-string">"sts:AssumeRole"</span>
        }]
    }))

rpa = aws.iam.RolePolicyAttachment(<span class="hljs-string">"task-exec-policy"</span>,
    role=role.name,
    policy_arn=<span class="hljs-string">"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"</span>
)

task_definition = aws.ecs.TaskDefinition(<span class="hljs-string">"app-task"</span>,
    family=<span class="hljs-string">"fargate-task-definition"</span>,
    cpu=<span class="hljs-string">"256"</span>,
    memory=<span class="hljs-string">"512"</span>,
    network_mode=<span class="hljs-string">"awsvpc"</span>,
    requires_compatibilities=[<span class="hljs-string">"FARGATE"</span>],
    execution_role_arn=role.arn,
    container_definitions=json.dumps([{
        <span class="hljs-string">"name"</span>: <span class="hljs-string">"my-app"</span>,
        <span class="hljs-string">"image"</span>: <span class="hljs-string">"nginx"</span>,
        <span class="hljs-string">"portMappings"</span>: [{
            <span class="hljs-string">"containerPort"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"hostPort"</span>: <span class="hljs-number">80</span>,
            <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"tcp"</span>
        }]
    }])
)

service = aws.ecs.Service(<span class="hljs-string">"app-svc"</span>,
    cluster=cluster.arn,
    desired_count=<span class="hljs-number">1</span>,
    launch_type=<span class="hljs-string">"FARGATE"</span>,
    task_definition=task_definition.arn,
    network_configuration={
        <span class="hljs-string">"assign_public_ip"</span>: <span class="hljs-string">"true"</span>,
        <span class="hljs-string">"subnets"</span>: vpc_subnets.ids,
        <span class="hljs-string">"security_groups"</span>: [group.id]
    },
    load_balancers=[{
        <span class="hljs-string">"target_group_arn"</span>: atg.arn,
        <span class="hljs-string">"container_name"</span>: <span class="hljs-string">"my-app"</span>,
        <span class="hljs-string">"container_port"</span>: <span class="hljs-number">80</span>
    }],
    opts=pulumi.ResourceOptions(depends_on=[wl])
)

pulumi.export(<span class="hljs-string">"url"</span>, alb.dns_name)
</code></pre>
<h3 id="heading-step-8-provision-the-cluster-and-service">Step 8: Provision the Cluster and Service</h3>
<p>Deploy the program to stand up your initial cluster and service:</p>
<pre><code class="lang-bash">pulumi up
</code></pre>
<p>This will output the status and resulting load balancer URL:</p>
<pre><code>Updating (dev)

View Live: https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab3/dev/updates/2</span>

     Type                             Name              Status      Info
     <span class="hljs-attr">pulumi</span>:pulumi:Stack              iac-lab3-dev
 +   ├─ aws:iam:Role                  task-exec-role    created
 ~   ├─ aws:lb:TargetGroup            app-tg            updated     [diff: ~deregistrationDelay]
 +   ├─ aws:iam:RolePolicyAttachment  task-exec-policy  created
 +   ├─ aws:ecs:TaskDefinition        app-task          created
 +   └─ aws:ecs:Service               app-svc           created

<span class="hljs-attr">Outputs</span>:
  + url: <span class="hljs-string">"app-lb-e92ae89-2123599743.us-west-2.elb.amazonaws.com"</span>

<span class="hljs-attr">Resources</span>:
    + <span class="hljs-number">4</span> created
    ~ <span class="hljs-number">1</span> updated
    <span class="hljs-number">5</span> changes. <span class="hljs-number">5</span> unchanged

<span class="hljs-attr">Duration</span>: <span class="hljs-number">10</span>s
</code></pre><p>You can access the URL from the output above in a web browser. Also, you can now curl the resulting endpoint:</p>
<pre><code class="lang-bash">curl $(pulumi stack output url)
</code></pre>
<p>And you’ll see the Nginx default homepage:</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Welcome to nginx!&lt;/title&gt;
...
</code></pre><h3 id="heading-step-9-update-the-service">Step 9: Update the Service</h3>
<p>Now, also update the desired container count from <code>1</code> to <code>3</code>:</p>
<pre><code>...
    desiredCount: <span class="hljs-number">3</span>,
...
</code></pre><p>Next update the stack:</p>
<pre><code class="lang-bash">pulumi up
</code></pre>
<p>The output should look something like this:</p>
<pre><code>Updating (dev)

View Live: https:<span class="hljs-comment">//app.pulumi.com/beaucarnes/iac-lab3/dev/updates/3</span>

     Type                 Name          Status      Info
     <span class="hljs-attr">pulumi</span>:pulumi:Stack  iac-lab3-dev
 ~   └─ aws:ecs:Service   app-svc       updated     [diff: ~desiredCount]

<span class="hljs-attr">Outputs</span>:
    url: <span class="hljs-string">"app-lb-e92ae89-2123599743.us-west-2.elb.amazonaws.com"</span>

<span class="hljs-attr">Resources</span>:
    ~ <span class="hljs-number">1</span> updated
    <span class="hljs-number">9</span> unchanged

<span class="hljs-attr">Duration</span>: <span class="hljs-number">6</span>s
</code></pre><h3 id="heading-step-10-destroy-everything">Step 10: Destroy Everything</h3>
<p>Finally, destroy the resources and the stack itself:</p>
<pre><code>pulumi destroy
pulumi stack rm
</code></pre> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Basic Terraform Syntax in 20 minutes ]]>
                </title>
                <description>
                    <![CDATA[ By Sumeet Ninawe In this article, I'll give you a brief overview of the configuration syntax of Terraform.  Terraform's docs provide the most comprehensive look at its syntax. But this article should serve as a condensed quick start introduction that... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/terraform-syntax-for-beginners/</link>
                <guid isPermaLink="false">66d46016246e57ac83a2c78b</guid>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 26 May 2021 18:00:52 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/05/terraform-article-cover-image.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Sumeet Ninawe</p>
<p>In this article, I'll give you a brief overview of the configuration syntax of Terraform. </p>
<p><a target="_blank" href="https://www.terraform.io/docs/index.html">Terraform's docs</a> provide the most comprehensive look at its syntax. But this article should serve as a condensed quick start introduction that'll give new users a simplified overview. We'll focus on the basic structures without getting too deep in the weeds.</p>
<p>To help you learn the syntax, we'll go through an example and I'll teach you the most important parts of the Terraform configuration language (which is called HCL - the HashiCorp Configuration Language). Then we'll build out some infrastructure as code (IaC) to see it in action.</p>
<p>Before we start, you should have Terraform installed on your local system. You should also have access to the AWS management console, and have set up and configured an IAM user for Terraform.</p>
<h2 id="heading-arguments-and-blocks-in-terraform">Arguments and Blocks in Terraform</h2>
<p>The first example below creates an EC2 instance:</p>
<pre><code>provider “aws” {
    region = “us-west<span class="hljs-number">-1</span>”
}

resource “aws_instance” “myec2” {
    ami = “ami<span class="hljs-number">-12345</span>qwert”
    instance_type = “t2.micro”
}
</code></pre><p>The code consists of two blocks wrapped in curly braces ( {} ), and each of these blocks has certain <strong>arguments</strong> defined.</p>
<p>Just like in most programming languages, you use arguments to assign values to variables. In Terraform, these variables are attributes associated with a particular type of block.</p>
<p>The provider “aws” block has one argument – <code>region = “us-west-1”</code>, where the region is an attribute associated with the block, and it is assigned a value “us-west-1”. The value is a string type, so it is enclosed in a pair of double quotes ( “” ).</p>
<p>Similarly, the resource block has two arguments that set the values of associated attributes.</p>
<p>Terraform uses various types of <strong>blocks</strong>. Based on the type, blocks represent and enclose a set of attributes and functions. In the given example, we have a block of type provider and another of type resource, and each block has an identifier and a set of input labels.</p>
<p>The provider block takes one input label – the name of the provider. In this case “aws”. It also tells Terraform to install the AWS provider plugin, during the init phase.</p>
<p>The resource block takes 2 inputs labels – the type of resource and the name of the resource. In this case, the type is “aws_instance” and the name is “myec2”. What follows is the block body enclosed in curly braces.</p>
<h2 id="heading-how-to-create-an-ec2-instance-on-aws">How to Create an EC2 Instance on AWS</h2>
<p>So, how do we start expressing our infrastructure as code and make use of it? Let’s take an example of creating a simple EC2 instance on AWS.</p>
<p>Start by creating a directory of your choice where you would place all the configuration code required to create an EC2 instance.</p>
<p>By default, Terraform assumes that all the files with <code>.tf*</code> extensions in a directory are part of the configuration, irrespective of the file names. Create a file named <code>main.tf</code> in this directory.</p>
<p>The first thing we need to decide is which providers we are going to use. Since we are going to spin up an EC2 instance on AWS, we need to declare the required providers as you can see in the snippet below:</p>
<pre><code>terraform {
 required_providers {
   aws = {
     source  = <span class="hljs-string">"hashicorp/aws"</span>
     version = <span class="hljs-string">"~&gt; 3.0"</span>
   }
 }
}

provider <span class="hljs-string">"aws"</span> {
 region = “us-west<span class="hljs-number">-1</span>”
}
</code></pre><p>We have declared two blocks – <code>terraform</code> and <code>provider</code>. <code>terraform</code> is a top-most block, but it is optional as well. It is a good practice to specify this, especially when you're working with remote state management. </p>
<p>The <code>terraform</code> block has a nested block that specifies <code>required_providers</code>. We require the <code>aws</code> provider. <code>aws</code> within required_providers is a map, which specifies the <code>source</code> and <code>version</code> of the provider. </p>
<p>Next, we have a provider block for <code>aws</code>, which specifies the desired <code>region</code>.</p>
<p>Generally, this is how every Terraform code starts. Of course, there could be some variations, and the best way to be sure about it is to refer to the <a target="_blank" href="https://registry.terraform.io/">Terraform registry</a> for specific versions of Terraform as well as the provider plugin itself. </p>
<p>For the sake of the current example, we are referring to <a target="_blank" href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs">AWS plugin documentation</a>. </p>
<p>The Terraform registry documents usage of all the resources of various cloud providers with examples. It is a great reference when you're working with Terraform.</p>
<h3 id="heading-terraform-providers">Terraform Providers</h3>
<p>Installing Terraform on the system is not enough. Terraform works on a plugin-based architecture, where the Terraform binary forms the core and every cloud provider is a plugin.</p>
<p>To make configurations work, these plugins are installed in the initialization phase. </p>
<p>Provider plugins come with their own set of configurations, resource types, and data sources. The Terraform registry documents all the details for a given provider.</p>
<h3 id="heading-terraform-resources">Terraform Resources</h3>
<p>Every provider comes with a set of resources. <code>resource</code>, as the name suggests, represents the actual cloud resource to be created in the configuration language.</p>
<p>Providers enable resources. In the given example, <code>aws</code> is a provider and <code>aws_instance</code> is a resource provided by the AWS provider. </p>
<p>The resource has its attributes. These attributes are documented in the Terraform registry. Out of all the attributes, some of them are required. Resources are the exact constructs that are executed by Terraform.</p>
<p>Continuing with the example, let's define an AWS EC2 instance resource by appending the below code into our <a target="_blank" href="http://main.tf/"><code>main.tf</code></a> file.</p>
<pre><code>resource <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"demo"</span> {
 ami = “ami<span class="hljs-number">-00831</span>fc7c1e3ddc60”
 instance_type = “t2.micro”

 tags = {
   name = <span class="hljs-string">"Demo System"</span>
 }
}
</code></pre><p>We start with a resource block named <code>“aws_instance”</code> and we pass a label and name it <code>“demo”</code>. The label is the name of your choice. </p>
<p>Next, open the block using curly braces and specify the required attributes used by the resource <code>aws_instance</code>. The first attribute is <code>ami</code> which specifies the Amazon machine image ID for the EC2 instance. </p>
<p>The second attribute is the <code>instance_type</code> which specifies the size of the machine to be created. </p>
<p>We are also passing tags which is an optional argument. As a tag, we pass <code>“name”</code> as the key and <code>“Demo System”</code> in the value. That's it – we have defined our resource.</p>
<p>We are now technically ready with the configuration. We can go ahead and initialize Terraform into this directory so that it installs the provider plugin for AWS. We can then <code>plan</code> and <code>apply</code> this configuration. </p>
<p>Save the file, and then go ahead and run <code>terraform init</code> and see if it installs the AWS provider plugin. Once that's done successfully, run <code>terraform plan</code> and observe the output.</p>
<p>Let's put everything into perspective. <code>providers</code> let Terraform know which plugins need to be installed to execute the configuration. <code>resources</code> represent the actual cloud resources to be created. </p>
<p>Generally, every resource has a name ( <code>“aws_instance”</code> ). The initial part of the name of the resource is the provider identifier ( <code>“aws”</code> ) which is separated by an underscore.</p>
<h3 id="heading-terraform-variables">Terraform Variables</h3>
<p>Terraform is a declarative language. In the example, we have declared the final state of the virtual machine on the desired cloud. </p>
<p>Now it is up to Terraform to take this configuration and execute it to create the virtual resource. Having said that, Terraform gives us the ability to specify <strong>input variables</strong> to its configuration.</p>
<p>Input variables are like parameters for a given function just like in any programming language.</p>
<p>They are particularly useful when you have to specify the same value at multiple places in your code. As the project grows in size, it becomes easier to change certain values that might be used in multiple places using variables.</p>
<p>Terraform supports primitive types of variables such as string, number, boolean, and several complex types such as list, set, map, object, and tuple.</p>
<p>Let's define some variables into our code as below:</p>
<pre><code>variable <span class="hljs-string">"region"</span> {
 <span class="hljs-keyword">default</span> = <span class="hljs-string">"us-west-1"</span>
 description = <span class="hljs-string">"AWS Region"</span>
}

variable <span class="hljs-string">"ami"</span> {
 <span class="hljs-keyword">default</span> = <span class="hljs-string">"ami-00831fc7c1e3ddc60"</span>
 description = <span class="hljs-string">"Amazon Machine Image ID for Ubuntu Server 20.04"</span>
}

variable <span class="hljs-string">"type"</span> {
 <span class="hljs-keyword">default</span> = <span class="hljs-string">"t2.micro"</span>
 description = <span class="hljs-string">"Size of VM"</span>
}
</code></pre><p>As you can see, we have introduced three new variables for the <code>region</code>, the <code>ami</code>, and the <code>type</code>. We'll use this in our configuration so far. The values of the variables can be referred to using <code>var.&lt;variable name&gt;</code>.</p>
<p>Terraform configuration also gives us the ability to return values. These values are known as <strong>output values</strong>. </p>
<p>When Terraform completes the execution of the configuration, it makes the output values available. They can then be used as input to other interfaces. </p>
<p>We have defined one output variable <code>“instance_id”</code> in our code. The value of this output variable is set using the attribute reference of <code>“aws_instance.demo”</code>. Similarly, we can refer to other output variables available from any resource in the configuration.</p>
<p>Below is the updated code of our <code>main.tf</code>. We have used three variables here:</p>
<pre><code>terraform {
 required_providers {
   aws = {
     source  = <span class="hljs-string">"hashicorp/aws"</span>
     version = <span class="hljs-string">"~&gt; 3.0"</span>
   }
 }
}

provider <span class="hljs-string">"aws"</span> {
 region = <span class="hljs-keyword">var</span>.region
}

variable <span class="hljs-string">"region"</span> {
 <span class="hljs-keyword">default</span> = <span class="hljs-string">"us-west-1"</span>
 description = <span class="hljs-string">"AWS Region"</span>
}

variable <span class="hljs-string">"ami"</span> {
 <span class="hljs-keyword">default</span> = <span class="hljs-string">"ami-00831fc7c1e3ddc60"</span>
 description = <span class="hljs-string">"Amazon Machine Image ID for Ubuntu Server 20.04"</span>
}

variable <span class="hljs-string">"type"</span> {
 <span class="hljs-keyword">default</span> = <span class="hljs-string">"t2.micro"</span>
 description = <span class="hljs-string">"Size of VM"</span>
}

resource <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"demo"</span> {
 ami = <span class="hljs-keyword">var</span>.ami
 instance_type = <span class="hljs-keyword">var</span>.type

 tags = {
   name = <span class="hljs-string">"Demo System"</span>
 }
}

output <span class="hljs-string">"instance_id"</span> {
 instance = aws_instance.demo.id
}
</code></pre><p>Save the file and run <code>terraform plan</code>. Notice that Terraform takes note of the output variable this time. It states that the output is known <code>after apply</code>:</p>
<pre><code>Plan: <span class="hljs-number">1</span> to add, <span class="hljs-number">0</span> to change, <span class="hljs-number">0</span> to destroy.

Changes to Outputs:
  + instance_id = (known after apply)
</code></pre><p>Go ahead and do <code>terraform apply</code>, and check out the output. Don't forget to run <code>terraform destroy</code> after every successful <code>apply</code>.</p>
<p>Lastly, Terraform also supports <strong>local variables</strong>, which are temporary values used locally by functions and blocks.</p>
<h3 id="heading-terraform-provisioners">Terraform Provisioners</h3>
<p>Provisioning means to install, update, and maintain the required software once the hardware or virtual machine is ready to go. </p>
<p>Terraform can trigger software provisioning processes once a virtual machine is ready, but that doesn't mean it is a full-time provisioning tool. </p>
<p>You can use this tool to make the infrastructure ready for management by installing initial and essential software components.</p>
<p>There are tools like Salt Stack, Ansible, Chef, and others, and most of them are agent-based. Terraform can run initial scripts to install some patch updates, agent software, or even set some user access policies to make sure machines are ready to go.</p>
<p>Terraform comes bundled with generic provisioners and also supports vendor-specific provisioners.</p>
<h2 id="heading-how-to-manage-code-in-terraform">How to Manage Code in Terraform</h2>
<p>Before we proceed, let's first organize our code into multiple files. As a general practice, the Terraform codebase is divided into multiple files based on the providers, resources, and variables. Let's create three files as below:</p>
<ol>
<li><code>variables.tf</code> – This file contains all the declared input variables. In our example, we have input variables defined for <code>region</code>, <code>ami</code>, and <code>type</code> and an output variable <code>instance_id</code>.</li>
<li><code>provider.tf</code> – This file contains declarations for the providers you're using. In our case, we have <code>terraform</code>, and the provider <code>aws</code> blocks.</li>
<li><code>main.tf</code> – This file contains the declarations for actual resources to be created.</li>
</ol>
<p>By default, Terraform assumes that all the code placed in a particular directory is part of the same configuration. </p>
<p>So technically it doesn't make much of a difference if you put the code in a single file or divide it into multiple files and sub-directories. From a maintainability point of view, it makes a lot of sense to do so.</p>
<h3 id="heading-meta-arguments-in-terraform">Meta-Arguments in Terraform</h3>
<p>Before we get into this, make sure that when you're working through the examples, always run <code>terraform destroy</code> after every <code>terraform apply</code> run.</p>
<p>Meta-arguments are special constructs provided for resources. We have seen that resource blocks are the actual cloud resources that Terraform creates.</p>
<p>Often, it's tricky to declare resources in a way that satisfies certain requirements. </p>
<p>Meta-arguments come in handy in situations like creating resources in the same cloud provider but in different regions. They're also useful when we are creating multiple identical resources with different names, or when we have to declare implicit dependencies at places where Terraform is not able to identify the dependency itself.</p>
<p>The sections below describe some meta-arguments in action.</p>
<h4 id="heading-the-provider-meta-argument">The Provider meta-argument</h4>
<p>You'll use the <code>provider</code> meta-argument when you have multiple provider configurations in a given Terraform config. Terraform automatically maps the given resource to the default provider identified by the resource’s identifier. </p>
<p>For example, the default provider for <code>aws_instance</code> is <code>aws</code>. This <code>aws</code> provider is currently configured to deploy a resource in a particular region. </p>
<p>However, if we want to have another <code>aws</code> provider for another region, or with a different configuration setting, we can write another provider block.</p>
<p>Even though it's possible to write multiple provider configs, Terraform by default picks the same provider for <code>aws</code> for creating resources. </p>
<p>This is where <strong>aliases</strong> come into the picture. Every provider configuration can be tagged with an alias, and the value of this alias is used in our <strong>provider</strong> meta-argument in the resource block to specify different provider configurations for identical resources.</p>
<p>In our example, let's duplicate the <code>aws</code> provider and give them appropriate aliases. Modified providers with an alias should look like the below in the <code>provider.tf</code> file:</p>
<pre><code>provider <span class="hljs-string">"aws"</span> {
  alias  = <span class="hljs-string">"aws_west"</span>
  region = <span class="hljs-keyword">var</span>.region_west
}

provider <span class="hljs-string">"aws"</span> {
  alias  = <span class="hljs-string">"aws_east"</span>
  region = <span class="hljs-keyword">var</span>.region_east
}
</code></pre><p>Notice that we have also modified variables for the region to represent two different regions, west and east. Make the corresponding changes to the <code>variables.tf</code> file as below:</p>
<pre><code>variable <span class="hljs-string">"region_west"</span> {
  <span class="hljs-keyword">default</span>     = <span class="hljs-string">"us-west-1"</span>
  description = <span class="hljs-string">"AWS West Region"</span>
}

variable <span class="hljs-string">"region_east"</span> {
  <span class="hljs-keyword">default</span>     = <span class="hljs-string">"us-east-1"</span>
  description = <span class="hljs-string">"AWS East Region"</span>
}
</code></pre><p>One final change that we need to make is in the <code>main.tf</code> file. There, we can now use provider meta-argument to specify a specific provider's alias. </p>
<p>We can mention the provider config we want by specifying <code>&lt;provider&gt;.&lt;alias&gt;</code> in the meta-argument. Refer to the modified <code>main.tf</code> file below:</p>
<pre><code>resource <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"demo"</span> {
  provider      = aws.aws_west
  ami           = <span class="hljs-keyword">var</span>.ami
  instance_type = <span class="hljs-keyword">var</span>.type

  tags = {
    name = <span class="hljs-string">"Demo System"</span>
  }
}
</code></pre><p>Validate the final configuration by running <code>terraform validate</code>, and it should say “<code>Success!</code>”</p>
<h4 id="heading-the-lifecycle-meta-argument">The Lifecycle meta-argument</h4>
<p>The <code>lifecycle</code> meta-argument specifies the settings related to the <code>lifecycle</code> of resources managed by Terraform. By default, whenever a configuration is changed and applied, Terraform operates in this sequence:</p>
<ol>
<li>Create new resources.</li>
<li>Destroy those resources which do not exist in config anymore.</li>
<li>Update those resources which can be updated without destruction.</li>
<li>Destroy and re-create changed resources that cannot be changed on the fly.</li>
</ol>
<p>You can use a <code>lifecycle</code> meta-argument if you want to alter this default behavior. These meta-arguments are used in resource blocks similar to provider meta-arguments. </p>
<p>There are three lifecycle meta-argument settings:</p>
<ol>
<li><code>create_before_destroy</code>: Used when you want to avoid accidental loss of infrastructure when a changed config is applied. When this is set to true, Terraform will first create the new resource before destroying the older resource.</li>
<li><code>prevent_destroy</code>: When set to true, any attempt to destroy this in the config will result in an error. This is often useful in the case of those resources where reproduction can prove to be expensive.</li>
<li><code>ignore_changes</code>: This is a list type meta-argument which specifies the attributes of a certain resource in the form of a list. During update operations, often there is a situation where you want to prevent changes caused by external factors. In those cases, you need to declare the list of attributes that should not be changed without being reviewed.</li>
</ol>
<p><code>lifecycle</code> meta-arguments come in handy when we are in the process of setting up complex infrastructure. </p>
<p>By altering the default behavior of Terraform, we can put some protection in the form of <code>lifecycle</code> meta-arguments for confirmed and finalized resource blocks. </p>
<p>In our example, we would not use any lifecycle meta-arguments.</p>
<h4 id="heading-the-dependson-meta-argument">The depends_on meta-argument</h4>
<p>Generally, Terraform is aware of dependencies while creating or modifying resources and takes care of the sequence by itself. </p>
<p>However, in certain cases Terraform can't detect the implicit dependencies and just moves on with creating resources in parallel if it doesn’t see any dependency.</p>
<p>Take, for example, a Terraform configuration for two EC2 instances enclosed in a VPC. </p>
<p>When you have this configuration, Terraform automatically knows that it shoudl create the VPC before spinning up the EC2 instances. </p>
<p>This is general knowledge and Terraform knows it very well. In situations where dependencies are not so obvious, the <code>depends_on</code> meta-argument comes to the rescue. </p>
<p>It is a list type of argument that takes in the list of resource identifiers declared in the configuration.</p>
<h4 id="heading-the-count-meta-argument">The count meta-argument</h4>
<p>Imagine a situation where you would like to create multiple similar resources. By default, Terraform creates one real resource for a single resource block. </p>
<p>But in the case of multiple resources, Terraform provides a meta-argument named <code>count</code>. As the name suggests, the <code>count</code> can be assigned with a whole number to represent multiple resources.</p>
<p>In our example, let's create three similar EC2 instances. In your <code>main.tf</code> file, add an attribute count to the resource <code>aws_instance.demo</code>, and assign it a value of <code>3</code>. It should look something like the below:</p>
<pre><code>resource <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"demo"</span> {
  count         = <span class="hljs-number">3</span>
  provider      = aws.aws_west
  ami           = <span class="hljs-keyword">var</span>.ami
  instance_type = <span class="hljs-keyword">var</span>.type

  tags = {
    name = <span class="hljs-string">"Demo System"</span>
  }
}
</code></pre><p>By doing this, we let Terraform know that we need to create three EC2 instances with the same configuration. </p>
<p>Save the file and execute <code>terraform validate</code>. It throws an error saying “<code>Missing resource instance key</code>”. </p>
<p>Remember in our <code>variables.tf</code> file we mentioned an output variable to output the <code>id</code> of the created resource. Since we have asked Terraform to create three instances, it is not very clear  which ID it should print. </p>
<p>To get around this problem, we use a special expression called a <code>splat</code> expression. </p>
<p>The ideal case here is to run a for loop over the instance and print out the ID property. The splat expression is a better way to do the same task with fewer lines of code. </p>
<p>All you need to do is in the <code>variables.tf</code> file, replace the output value code with the below:</p>
<pre><code>output <span class="hljs-string">"instance_id"</span> {
  value = aws_instance.demo[*].id
}
</code></pre><p>Save this file and run <code>terraform validate</code> to see if everything is okay. </p>
<p>Once successful, go ahead and run <code>terraform plan</code> and <code>apply</code> and check your AWS management console in us-west-1 region, that is <code>aws_west</code>. Let me know the IDs too.</p>
<p>Splat is one of a kind and we will take a better look at expressions in upcoming sections.</p>
<h4 id="heading-the-foreach-meta-argument">The for_each meta-argument</h4>
<p><code>for_each</code>, as the name suggests, is essentially a “for each” loop. <code>for_each</code> meta-argument is used to create/loop through multiple similar cloud resources. Yes, it does sound similar to the <code>count</code> meta-argument but there is a difference.</p>
<p>Firstly, <code>for_each</code> and <code>count</code> cannot be used together.</p>
<p>Secondly, you can say this is an enhanced version of the <code>count</code>. Count meta-argument is a number type. Terraform simply creates this many resources. </p>
<p>However, if you would like to create these resources with some customizations in the output, or if you already have an object of type map or list based on which you want to create resources, then the <code>for_each</code> meta-argument is the way to go.</p>
<p>As mentioned earlier, you can assign <code>for_each</code> map and list type of values. A map is a collection of key-value pairs, whereas a list is a collection of values (in this case string values).</p>
<p><code>for_each</code> comes with a special object <code>each</code>. This is the iterator in the loop which you can use to refer to the <code>key</code> or <code>value</code>, or only the key in case of list. </p>
<p>Let's take a look at our example. We would like to create EC2 instances for the given map. The map is assigned to the <code>for_each</code> meta-argument and Terraform creates an EC2 instance for each key-value pair in the map. </p>
<p>Lastly, we use the <code>key</code> and <code>value</code> information using <code>each</code> object to set the name attribute in the <code>tag</code>.</p>
<p>The resource block in <code>main.tf</code> now looks something like this:</p>
<pre><code>resource <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"demo"</span> {
  for_each = {
    fruit = <span class="hljs-string">"apple"</span>
    vehicle = <span class="hljs-string">"car"</span>
    continent = <span class="hljs-string">"Europe"</span>
  }
  provider      = aws.aws_west
  ami           = <span class="hljs-keyword">var</span>.ami
  instance_type = <span class="hljs-keyword">var</span>.type

  tags = {
    name = <span class="hljs-string">"${each.key}: ${each.value}"</span>
  }
}
</code></pre><p>Execute <code>terraform validate</code> and observe the output. It throws an error for the output variable – “<code>This object does not have an attribute named id</code>”. </p>
<p>A quick note here: <code>splat</code> expressions work for the list type of variables. Since we used map while setting our <code>for_each</code> meta-argument, we need to change the return value expression to for each, as below:</p>
<pre><code>output <span class="hljs-string">"instance_id"</span> {
  <span class="hljs-comment">//value = aws_instance.demo[*].id</span>
  value = [<span class="hljs-keyword">for</span> b <span class="hljs-keyword">in</span> aws_instance.demo : b.id]
}
</code></pre><p>Execute <code>terraform validate</code> again. If it's successful, go ahead and <code>apply</code> the configuration. Check the AWS management console for the machines created and the names assigned to them.</p>
<h3 id="heading-terraform-expressions">Terraform Expressions</h3>
<p>Expressions are ways to make your Terraform code dynamic. </p>
<p>Expressions come in two forms – simple and complex. Up until now in our examples, we have mostly dealt with simple expressions. </p>
<p>A simple expression is any argument used as part of some block. Writing down an argument with a primitive value assigned to it is a form of expression.</p>
<p>We have used a complex expression called splat ( <code>*</code> ) in our example while working with meta-arguments. </p>
<p>However, there are even more complex expressions that you can use to make your Terraform code more dynamic, readable, and flexible. There are various types of expressions that you can take a look at in the <a target="_blank" href="https://www.terraform.io/docs/configuration/expressions/index.html">Terraform documentation</a>.</p>
<h3 id="heading-terraform-functions">Terraform Functions</h3>
<p>Terraform has built-in functions that you can use with expressions. These are utility functions that are useful in number and string manipulations. </p>
<p>There are functions to work with file systems, date and time, network, type conversion, and more.</p>
<p>Functions along with expressions make it super easy to write a really dynamic IaC. You can refer to the list of functions <a target="_blank" href="https://www.terraform.io/docs/configuration/functions.html">here</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this post, we were able to play around with AWS EC2 instances using Terraform. I hope this helps you get more comfortable working with Terraform.</p>
<p>If you like this content, do consider subscribing, following, and sharing this blog post! <a target="_blank" href="https://letsdotech.dev/">Let'sDoTech</a>, <a target="_blank" href="https://www.instagram.com/letsdotech/">Instagram</a>, <a target="_blank" href="https://twitter.com/letsdotech_dev">Twitter</a>, <a target="_blank" href="https://www.linkedin.com/company/letsdotech">LinkedIn</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Terraform? Learn Terraform and Infrastructure as Code ]]>
                </title>
                <description>
                    <![CDATA[ By Sumeet Ninawe Terraform is a tool that helps you manage various cloud infrastructure services in the form of code. You codify your infrastructure, and so it's also known as Infrastructure as Code (IaC). The cloud has become important to more and m... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-is-terraform-learn-infrastructure-as-code/</link>
                <guid isPermaLink="false">66d460183bc3ab877dae2216</guid>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 15 Apr 2021 23:38:22 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/04/terraform-article.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Sumeet Ninawe</p>
<p>Terraform is a tool that helps you manage various cloud infrastructure services in the form of code. You codify your infrastructure, and so it's also known as Infrastructure as Code (IaC).</p>
<p>The cloud has become important to more and more companies. It not only helps reduce time and costs but also lets customers focus on their core business.</p>
<h2 id="heading-why-infrastructure-as-code">Why Infrastructure as Code?</h2>
<p>As the number of cloud providers increases and their services become more flexible, it's becoming more important to be able to manage your cloud infrastructure resources. </p>
<p>Terraform works on the concept of Infrastructure as Code (IaC). In simple terms, IaC is the ability to represent your infrastructure in the form code.</p>
<p>Let's take as an example any computing resource on a given cloud, like EC2 on AWS. Requesting an EC2 instance from AWS is a matter of signing up with AWS, providing a bunch of values, and clicking on the “Launch” button. The "resource" will be ready in a few minutes.</p>
<p>As long as we can provide those values to AWS, they'll live on that cloud provider. Of course, this is the traditional way to do this.</p>
<p>Terraform provides a way to take these credentials and inputs in the form of <em>configurations</em> and process them to create a resource in the target cloud.</p>
<p>These configurations describe the resource in a language that Terraform understands. Configurations are how you can declare the desired state of your infrastructure – basically, "Declarative" syntax.</p>
<p>Terraform uses cloud provider APIs to create the resource.</p>
<h2 id="heading-advantages-of-terraform">Advantages of Terraform</h2>
<p>Terraform is a product from <a target="_blank" href="https://www.hashicorp.com/">Hashicorp</a>, and uses <a target="_blank" href="https://github.com/hashicorp/hcl">Hashicorp Configuration Language</a> (HCL) syntax to represent the configurations. </p>
<p>In the example below, you can see the representation of the EC2 instance in its simplest form:</p>
<pre><code>provider “aws” {
    region = “us-west<span class="hljs-number">-1</span>”
}

resource “aws_instance” “myec2” {
    ami = “ami<span class="hljs-number">-12345</span>qwert”
    instance_type = “t2.micro”
}
</code></pre><p>This simple example is enough for us to understand the capabilities of Terraform. </p>
<p>The code contains two blocks – the <code>provider</code> and <code>resource</code>.  The <code>provider</code> block lets Terraform know that we want to use <code>aws</code> provider in the region “us-west-1”. </p>
<p>The <code>resource</code> block lets Terraform know that out of all the infrastructure resources offered by AWS, we want to create a resource of type “instance” (EC2).</p>
<p>The first parameter represents this to the resource block as “aws_instance”. The second parameter is what we've named the resource – in this case, “myec2”. </p>
<p>The resource block has a couple of arguments that state the AWS machine image and the type of instance used to create this resource.</p>
<p>Here, we have managed to express our infrastructure in the form of code. Let's go through some of the advantages of IaC.</p>
<ol>
<li>Since infrastructure creation is now condensed in config/code files, it is <strong>easier to maintain</strong> since we can now leverage version control systems like Git to collaborate and maintain it.</li>
<li>The time required for the planning phase of the infrastructure is reduced as we can write the configurations in a <strong>short amount of time</strong>. These configs are readily consumed by Terraform to create cloud resources in the matter for few minutes.</li>
<li><strong>Changes</strong> to the infrastructure <strong>are easier</strong> and are comparable to application code changes.</li>
<li>The advantages for application management lifecycle in the case of software development are also applicable for infrastructure. This makes it <strong>more efficient</strong>.</li>
</ol>
<h2 id="heading-features-of-terraform">Features of Terraform</h2>
<h3 id="heading-orchestration">Orchestration</h3>
<p>When deploying various end-to-end services, Terraform acts as the core of the orchestration process when it comes to creating cloud resources.</p>
<h3 id="heading-cloud-agnostic">Cloud agnostic</h3>
<p>Since Terraform supports most of the clouds including AWS, MS Azure, and GCP, you don't have to worry as much about vendor lock-in issues. The Terraform registry provides the documentation for all the supported cloud providers.</p>
<p>Syntax patterns used to code infrastructure on various clouds are the same, so the learning curve related to provider-specific APIs is on a back burner, but not forgotten.</p>
<h3 id="heading-declarative-syntax">Declarative syntax</h3>
<p>Infrastructure expressed in Terraform files is declarative – so as developers, we don’t need to worry about making Terraform understand the steps required to create a resource. Rather, all we need to do is let Terraform know about the desired state and Terraform takes care of the steps internally.</p>
<h3 id="heading-modules">Modules</h3>
<p>Terraform provides modules which help us reuse our Terraform code. A complex infrastructure is broken into multiple modules and each module is reusable in different projects.</p>
<p>It is very easy to convert a given Terraform configuration into modules and Terraform has its eco-system for pre-built modules.</p>
<h3 id="heading-state-management">State management</h3>
<p>While Terraform is creating and planning the infrastructure, state is maintained. This can be shared with other team members for collaboration purposes.</p>
<p>Terraform lets you manage state remotely, which helps prevent confusion amongst team members in case they attempt to recreate the infrastructure.</p>
<h3 id="heading-provisioning">Provisioning</h3>
<p>Terraform is not a full-blown provisioning tool, but it helps with day one provisioning activities. Terraform’s <em>local-exec</em> and <em>remote-exec</em> blocks let you run inline scripts. Inline scripts help install software components upon the successful creation of the resource.</p>
<p>This is especially useful when helping configuration management tools like Chef, Ansible, and Salt Stack install their respective agents. They can just send an “UP” signal once they are installed successfully.</p>
<h3 id="heading-open-source">Open Source</h3>
<p>Terraform is available for use as open-source software. It also has an Enterprise version.</p>
<h2 id="heading-terraform-workflow-init-plan-apply-destroy">Terraform Workflow [init - plan - apply - destroy]</h2>
<p>There are some simple steps you need to take to execute your Terraform code. These steps are closely related to the lifecycle of resources on cloud platforms.</p>
<p>Again, these steps are cloud-agnostic, meaning the same steps/commands are valid to <strong>create, update, and destroy</strong> resources on any given cloud provider.</p>
<p><strong>Note:</strong> This blog post doesn’t cover installation steps for Terraform, and I assume you already have the Terraform CLI installed on your system.</p>
<h3 id="heading-run-the-init-command">Run the <code>init</code> command</h3>
<p>Once we have the configuration files ready, the very first command we need to run is <code>terraform init</code>. The Terraform binary installation does not include support for all the cloud providers at once.</p>
<p>Instead, based on the provider, appropriate <strong>plugins are downloaded</strong> before Terraform executes the code. In our example, running <code>terraform init</code> would download the <code>aws</code> provider plugin. This command helps <em>initialize</em> the backend for a given Terraform directory.</p>
<h3 id="heading-generate-an-execution-plan">Generate an execution plan</h3>
<p>The <code>terraform plan</code> command helps <strong>generate an execution plan</strong>. Based on the configuration you provide, Terraform generates an execution plan. In this phase, Terraform performs <strong>feasibility checks</strong> in terms of syntax errors, API authentication, state verification, and more.</p>
<p><code>plan</code> highlights any fixes in the Terraform script before actual execution. If it's successful, it outputs a <strong>summary of potential changes</strong> in the infrastructure. You should run this before <em>apply</em>, as it makes you aware of any risks before modifying the infrastructure.</p>
<h3 id="heading-apply-the-changes"><code>Apply</code> the changes</h3>
<p><code>terraform apply</code> helps to <strong>execute any changes to the infrastructure</strong>. You should run the <code>plan</code> command before running <code>terraform apply</code>, as planning creates a plan file which is referred to during apply phase. </p>
<p>However, if in case <code>terraform apply</code> is executed directly, a new plan file will be created automatically.</p>
<h3 id="heading-destroy-resources"><code>Destroy</code> resources</h3>
<p>Lastly, <code>terraform destroy</code> helps destroy any resources which are part of the current configuration/state.</p>
<h2 id="heading-terraform-in-action">Terraform in Action</h2>
<p>Okay, enough theory for this post. Let's try to implement what we have learned so far by actually creating an instance of EC2 on AWS.</p>
<p>First, install the Terraform CLI if you haven't already. Installation is pretty easy and you can find the steps <a target="_blank" href="https://learn.hashicorp.com/tutorials/terraform/install-cli">here</a> for the OS of your choice.</p>
<p>Create a directory/folder on your system and create the first Terraform file. Name it <code>main.tf</code>. Ideally, we can name it anything as long as it has the extension <code>.tf</code>.</p>
<p>Terraform CLI recognizes all the files with a <code>.tf</code> extension saved in a particular directory for execution.</p>
<p>Paste the above code in this file and save it. <strong>Please note</strong> that you need to use the correct AMI based on your preferred region.</p>
<p>Since this will be the first time we are executing the Terraform code, we need to <em>initialize</em> Terraform in this directory. Running <code>terraform init</code> will install the required <code>aws</code> plugin.</p>
<p>Fire up a terminal, navigate to the directory where our Terraform file resides, and run the below command.</p>
<p><code>terraform init</code></p>
<p>This should generate the output as below. The output is very clear and we can see that the <code>aws</code> plugin was installed with version v3.22.0.</p>
<pre><code>Initializing the backend...
Initializing provider plugins...
- Reusing previous version <span class="hljs-keyword">of</span> hashicorp/aws <span class="hljs-keyword">from</span> the dependency lock file
- Installing hashicorp/aws v3<span class="hljs-number">.22</span><span class="hljs-number">.0</span>...
- Installed hashicorp/aws v3<span class="hljs-number">.22</span><span class="hljs-number">.0</span> (signed by HashiCorp)
Terraform has been successfully initialized!
You may now begin working <span class="hljs-keyword">with</span> Terraform. Try running <span class="hljs-string">"terraform plan"</span> to see
any changes that are required <span class="hljs-keyword">for</span> your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration <span class="hljs-keyword">for</span> Terraform,
rerun <span class="hljs-built_in">this</span> command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to <span class="hljs-keyword">do</span> so <span class="hljs-keyword">if</span> necessary.
</code></pre><p>Next, we will run the <code>terraform plan</code> command to see a tentative execution plan. This also validates against any syntactical or reference errors. The <code>plan</code> command checks the feasibility of the declared resources in the <code>main.tf</code> file. Run this in the same terminal.</p>
<p><code>terraform plan</code></p>
<p>If everything worked well, the following output is generated.</p>
<pre><code>An execution plan has been generated and is shown below.
Resource actions are indicated <span class="hljs-keyword">with</span> the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.example will be created
  + resource <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"myec2"</span> {
      + ami                          = <span class="hljs-string">"ami-12345qwert"</span>
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
. . .
Plan: <span class="hljs-number">1</span> to add, <span class="hljs-number">0</span> to change, <span class="hljs-number">0</span> to destroy.

------------------------------------------------------------------------

Note: You didn<span class="hljs-string">'t specify an "-out" parameter to save this plan, so Terraform
can'</span>t guarantee that exactly these actions will be performed <span class="hljs-keyword">if</span>
<span class="hljs-string">"terraform apply"</span> is subsequently run.
</code></pre><p>The <code>plan</code> command indicates which resources will be created. In our case, it plans to create a <code>myec2</code> instance with the given configuration. It shows the AMI ID being used to create the instance.</p>
<p>Additionally, it also indicates that other attributes like <code>arn</code> and <code>associate_public_ip_address</code> are known after <code>apply</code>, that is when the instance will be created.</p>
<p>The bottom line here indicates the final set of changes – to add 1 resource and nothing to change or destroy.</p>
<p>So, everything looks good as of now. Let's go ahead and apply the configuration. Run the below command in the terminal and observe the output.</p>
<p><code>terraform apply</code></p>
<p>Once confirmed, it takes a few seconds to complete the creation of the EC2 instance on AWS, and this is indicated by the output being generated by <code>terraform apply</code>.</p>
<p>As indicated by the output below, in my case it took 51 seconds to create an EC2 instance and the ID of the instance is made available as well.</p>
<p>Verify the same by logging into your AWS console and searching for an EC2 instance with the below ID. If everything has worked well, you should be able to find it.</p>
<pre><code>Plan: <span class="hljs-number">1</span> to add, <span class="hljs-number">0</span> to change, <span class="hljs-number">0</span> to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only <span class="hljs-string">'yes'</span> will be accepted to approve.

  Enter a value: yes

aws_instance.myec2: Creating...
aws_instance.myec2: Still creating... [<span class="hljs-number">10</span>s elapsed]
aws_instance.myec2: Still creating... [<span class="hljs-number">20</span>s elapsed]
aws_instance.myec2: Still creating... [<span class="hljs-number">30</span>s elapsed]
aws_instance.myec2: Still creating... [<span class="hljs-number">40</span>s elapsed]
aws_instance.myec2: Still creating... [<span class="hljs-number">50</span>s elapsed]
aws_instance.myec2: Creation complete after <span class="hljs-number">51</span>s [id=i<span class="hljs-number">-04</span>ef3120a0006a153]

Apply complete! Resources: <span class="hljs-number">1</span> added, <span class="hljs-number">0</span> changed, <span class="hljs-number">0</span> destroyed.
</code></pre><p>Thus, we have successfully used IaC to define/declare and create our virtual machine configuration on AWS.</p>
<p>If we don't need this virtual machine anymore, we can use the same configuration to destroy it.</p>
<p>Please note that if we make any changes to the configuration without the intention of applying those changes – and then we try to destroy the same – we may run into errors.</p>
<p>This is because Terraform maintains the relationship between configuration and real-world resources in state files. Changing the configuration with its application will affect the alignment and Terraform will treat this as new resources to be created.</p>
<p>Terraform states is a topic that deserves its own post, so we shall cover that later. For now, to destroy the EC2 instance, run the below command in the terminal.</p>
<p><code>terraform destroy</code></p>
<p>Before destroying the resource, Terraform asks for our confirmation by providing a plan output. It indicates that running the destroy command will delete 1 resource, which is exactly what we expect.</p>
<pre><code>Terraform destroy
<span class="hljs-attr">Plan</span>: <span class="hljs-number">0</span> to add, <span class="hljs-number">0</span> to change, <span class="hljs-number">1</span> to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, <span class="hljs-keyword">as</span> shown above.
  There is no undo. Only <span class="hljs-string">'yes'</span> will be accepted to confirm.

  Enter a value: yes

aws_instance.myec2: Destroying... [id=i<span class="hljs-number">-04</span>ef3120a0006a153]
aws_instance.myec2: Still destroying... [id=i<span class="hljs-number">-04</span>ef3120a0006a153, <span class="hljs-number">10</span>s elapsed]
aws_instance.myec2: Still destroying... [id=i<span class="hljs-number">-04</span>ef3120a0006a153, <span class="hljs-number">20</span>s elapsed]
aws_instance.myec2: Still destroying... [id=i<span class="hljs-number">-04</span>ef3120a0006a153, <span class="hljs-number">30</span>s elapsed]
aws_instance.myec2: Still destroying... [id=i<span class="hljs-number">-04</span>ef3120a0006a153, <span class="hljs-number">40</span>s elapsed]
aws_instance.myec2: Still destroying... [id=i<span class="hljs-number">-04</span>ef3120a0006a153, <span class="hljs-number">50</span>s elapsed]
aws_instance.myec2: Still destroying... [id=i<span class="hljs-number">-04</span>ef3120a0006a153, <span class="hljs-number">1</span>m0s elapsed]
aws_instance.myec2: Destruction complete after <span class="hljs-number">1</span>m5s

Destroy complete! Resources: <span class="hljs-number">1</span> destroyed.
</code></pre><p>Again, it takes a few seconds to destroy the resource. Terraform doesn't keep you hanging as it updates the status in a 10-second interval.</p>
<p>Once the resource is destroyed, it confirms that it's done. Feel free to log in to the AWS console and verify if the resource is terminated.</p>
<h3 id="heading-thanks-for-reading">Thanks for reading!</h3>
<p>I hope you understand how Terraform works from this basic introduction. I will write more posts covering deeper concepts like states, syntax, the CLI, the back end, and so on in future posts. </p>
<p>If you like this content, do consider subscribing, following, and sharing this blog post! <a target="_blank" href="https://letsdotech.dev/">Let'sDoTech</a>, <a target="_blank" href="https://www.instagram.com/letsdotech/">Instagram</a>, <a target="_blank" href="https://twitter.com/letsdotech_dev">Twitter</a>, <a target="_blank" href="https://www.linkedin.com/company/letsdotech">LinkedIn</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Manage Wavefront Resources using Terraform ]]>
                </title>
                <description>
                    <![CDATA[ By Siben Nayak In my previous article, I wrote about metrics and how they help you gain visibility into the operational health of your hardware and software systems. Wavefront is a high-performance streaming analytics platform that supports 3D observ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-manage-wavefront-resources-using-terraform/</link>
                <guid isPermaLink="false">66d46153bd438296f45cd3c4</guid>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ metrics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Microservices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 04 Jan 2021 16:46:06 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/12/wavefront-terraform.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Siben Nayak</p>
<p>In my previous <a target="_blank" href="https://www.freecodecamp.org/news/microservice-observability-metrics/">article</a>, I wrote about metrics and how they help you gain visibility into the operational health of your hardware and software systems.</p>
<p><strong>Wavefront</strong> is a high-performance streaming analytics platform that supports 3D observability (metrics, histograms, traces/spans). </p>
<p>It can scale to very high data ingestion rates and query loads. You can collect data from many services and sources across your entire application stack, and can look at details for earlier data collected by Wavefront.</p>
<p><strong>Terraform</strong> is an open-source “Infrastructure as Code” tool, created by HashiCorp. </p>
<p>It is a <em>declarative</em> coding tool and enables developers to use a high-level configuration language called HCL (HashiCorp Configuration Language) to describe the desired “end-state” for the infrastructure. </p>
<p>This infrastructure can be on the cloud or on-premises. It then generates a plan for reaching that end-state and executes the plan to create the infrastructure.</p>
<p>In this article, we will take a look at how we can use Terraform to write code that will automatically build dashboards and alarms in Wavefront. This is really helpful in maintaining a DevOps culture in your team, where all the monitoring infrastructure is maintained as code and checked into your version control system such as GitHub.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-21.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-install-terraform">How to Install Terraform</h2>
<p>Depending on your OS, the installation <a target="_blank" href="https://learn.hashicorp.com/tutorials/terraform/install-cli">instructions</a> for Terraform will vary. This article covers the instructions for installing it on macOS.</p>
<p>The recommended approach for installing it on macOS is to use the Homebrew package manager.</p>
<h3 id="heading-install-terraform">Install Terraform</h3>
<p>Verify that you have Homebrew installed, like this:</p>
<pre><code>$ brew --version

Homebrew/homebrew-core (git revision fe68a; last commit <span class="hljs-number">2020</span><span class="hljs-number">-10</span><span class="hljs-number">-15</span>)
Homebrew/homebrew-cask (git revision <span class="hljs-number">4</span>a2c25; last commit <span class="hljs-number">2020</span><span class="hljs-number">-10</span><span class="hljs-number">-15</span>)
</code></pre><p>If not, you can install Homebrew using the following command:</p>
<pre><code>$ /bin/bash -c <span class="hljs-string">"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"</span>
</code></pre><p>Next, install Terraform using the following commands:</p>
<pre><code>$ brew tap hashicorp/tap
$ brew install hashicorp/tap/terraform
</code></pre><h3 id="heading-verify-terraform-installation">Verify Terraform Installation</h3>
<p>To verify that Terraform is properly installed, open another terminal session, and try a Terraform command.</p>
<pre><code>$ terraform --help

<span class="hljs-attr">Usage</span>: terraform [<span class="hljs-built_in">global</span> options] &lt;subcommand&gt; [args]

The available commands <span class="hljs-keyword">for</span> execution are listed below.The primary workflow commands are given first, followed byless common or more advanced commands.
</code></pre><h2 id="heading-how-to-get-an-api-token">How to Get an API Token</h2>
<p>To allow Terraform to access your Wavefront installation, you will need to provide it an access token. This token can be found in the API tokens section of your account.</p>
<p>Go to <em>Gear Icon &gt; Account Name &gt; API Access</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/Screenshot-2020-12-26-at-12.49.23-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Getting your Wavefront API Token</em></p>
<h2 id="heading-how-to-setup-a-terraform-project">How to Setup a Terraform Project</h2>
<p>First, create a new folder for your Terraform project:</p>
<pre><code>$ mkdir wavefront-terraform
</code></pre><p>A usual Terraform project contains 3 major files:</p>
<ol>
<li><strong>versions.tf</strong> — this contains the Terraform provider declaration that specifies the plugin version to be used</li>
<li><strong>variables.tf</strong> — this contains the variables that you can refer to in your main Terraform code</li>
<li><strong>main.tf</strong> — as the name suggests, this contains the actual code required to build the resources</li>
</ol>
<p>Create a <strong>versions.tf</strong> file in the project folder and add the following code:</p>


<p>Next, run the <code>terraform init</code> command to initialize the Wavefront provider:</p>
<pre><code>$ terraform init
</code></pre><p>This downloads the <code>terraform-wavefront-provider-&lt;version&gt;</code> file and puts it inside a<code>.terraform</code> folder in the current project folder.</p>
<p>Next, create a <strong>main.tf</strong> file in the project folder and add the following code:</p>


<p>With the setup completed, we are now ready to create some dashboards and alerts.</p>
<h2 id="heading-how-to-create-wavefront-dashboards">How to Create Wavefront Dashboards</h2>
<p>Before we jump into creating dashboards, let us first understand the anatomy of a Wavefront dashboard.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/Wavefront_Dashboard.png" alt="Image" width="600" height="400" loading="lazy">
<em>Anatomy of a Wavefront Dashboard</em></p>
<p>A dashboard in Wavefront consists of 5 types of entities:</p>
<ul>
<li><strong>Dashboard</strong> — This is the main dashboard and contains all other entities.</li>
<li><strong>Section</strong> — A dashboard can contain one or more sections. A section is a logical group of charts. For example you can have one section for displaying charts related to hardware utilization, and another section for displaying charts related to API calls.</li>
<li><strong>Row</strong> — A row is a collection of charts. You can define the number of charts you want to be present in a row. My personal recommendation is to have 3 charts in a row. Anything more than that clutters the dashboard.</li>
<li><strong>Chart</strong> — This is the final chart that displays the metrics on the dashboard. There are various options for creating charts like line charts, bar charts, pie charts, and so on.</li>
<li><strong>Source</strong> — A chart can contain one or more sources. Each source has a query that works on an underlying metric to create a visual representation on the chart.</li>
</ul>
<p>Now we are ready to write some code to create a dashboard. Add the following code to the <strong>main.tf</strong> file:</p>


<p>This creates a dashboard with one section for EC2 Metrics. There is one row in this section with two charts. One chart displays CPU Utilization and the other displays Memory Utilization. Both of them are line charts and show the percentage of usage.</p>
<h2 id="heading-how-to-create-alerts">How to Create Alerts</h2>
<p>The dashboard we created is great to look at the CPU and Memory utilization of our EC2 instances. But if we want to be notified when the CPU or memory utilization increases beyond a certain threshold, we need to set up some alerts.</p>
<p>To create an alert on CPU Utilization, add the following code to the <strong>main.tf</strong> file:</p>


<p>This creates two resources:</p>
<ol>
<li>An alert target that sends an email to the specified address whenever an alert is opened or resolved.</li>
<li>An alert on CPU Utilization that fires when the CPU utilization crosses the given threshold (a WARN alert when it goes over 60% and a SEVERE alert when it goes over 80%).</li>
</ol>
<p>Wavefront continuously monitors the CPU utilization and sends a notification to the email address when the threshold is breached. Similarly, when the utilization becomes normal, it sends another notification indicating that things have recovered.</p>
<h2 id="heading-how-to-generate-resources-in-wavefront">How to Generate Resources in Wavefront</h2>
<p>The code for creating our resources is ready. Now we need to apply them so that the actual resources are created on Wavefront.</p>
<p>To view what changes will be made to Wavefront by our code, run the following command:</p>
<pre><code>$ terraform plan
</code></pre><p>This will verify our code and show the difference between the current setup in Wavefront, and the changes that will happen due to your code.</p>
<p>Finally, to create the resources on Wavefront, run the following command:</p>
<pre><code>$ terraform apply -auto-approve
</code></pre><p>This will upload the configuration to Wavefront and create the actual dashboard and alert. You can now go to Wavefront and verify these resources.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Congratulations! You just created a new Wavefront dashboard and alert through code. </p>
<p>You can now go ahead and make any modifications to your code, and run <code>terraform apply -auto-approve</code> to apply your changes to Wavefront.</p>
<p>Terraform is a great way to maintain your resources in the form of code that can be checked into your version control system. This allows multiple developers to work on your resources while also keeping track of the changes.</p>
<p>The full source code for this tutorial can be found <a target="_blank" href="https://github.com/theawesomenayak/wavefront-terraform">here</a>.</p>
<p>Thank you for staying with me so far. Hope you liked the article. You can connect with me on <a target="_blank" href="https://www.linkedin.com/in/theawesomenayak/">LinkedIn</a> where I regularly discuss technology and life. Also take a look at some of <a target="_blank" href="https://www.freecodecamp.org/news/author/theawesomenayak/">my other articles</a>. Happy reading. 🙂</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
