DEV Community

MakendranG
MakendranG

Posted on • Originally published at makendran.hashnode.dev on

Overview of Pulumi and Its Challenge Tutorial 101: The Essential Guide

What is Pulumi ?.

image.png

Pulumi is a universal infrastructure as code platform that allows us to create, deploy and manage cloud infrastructure using well-known programming languages and tools.

Pulumi is free, open source and optionally integrated with Pulumi Service to make infrastructure management secure, reliable and seamless. Choose one of the following options to deploy a simple cloud application using Pulumi.

image.png

What is Pulumi service ?.

Pulumi is a fully managed service that makes it easy to host the open source Pulumi SDK. It provides built-in health and security management, integrates with source control and CI/CD, and offers a web console and API that simplify infrastructure visualization and management. It's free for personal use, along with features that can be used by teams.

How we can use Pulumi in AWS ?.

The Pulumi Infrastructure as Code SDK lets us create, deploy, and manage AWS containers, serverless functions, and infrastructure using programming languages like TypeScript, Python, Go, C#, and Java, and markup languages like YAML. With the Pulumi AWS Provider Bundle and CLI, we can accomplish all of this in minutes.

Benefits of Pulumi

  • Managing the complexity of the modern cloud

  • Bringing the cloud closer to application development

  • Use cases of technology with infrastructure

  • Make collaboration easier and innovate faster

Pulumi Challenge

Thousands of developers believe Pulumi is the perfect IaC tool to accelerate developers and manage complexity at modern cloud scale. Pulumi Challenges are a great way to experience the Pulumi platform.

This challenge is Startup in a Box, where we will build and deploy our very own website to run on Amazon S3 using Cloudfront and Checkly, all of this in Pulumi. The code is in TypeScript.

Prerequisites

To do this, we need to establish a few things in advance.

  1. A Pulumi account
  2. The Pulumi CLI
  3. AWS account
  4. Checkly account
  5. Install Language Runtime(Nodejs)
  6. AWS CLI
  7. Configure Pulumi to access your AWS account

Step 1 - First Pulumi Program

We will be creating a new Pulumi program using an AWS-specific Pulumi template using TypeScript. Create a new folder called Pulumi-challenge and run the following in it.

mkdir Pulumi-challenge
pulumi new aws-typescript


This command will walk you through creating a new Pulumi project.

Enter a value or leave blank to accept the (default), and press <ENTER>.
Press ^C at any time to quit.

project name: (pulumi-challenge)
project description: (A minimal AWS TypeScript Pulumi program)
Created project 'pulumi-challenge'

Please enter your desired stack name.
To create a stack in an organization, use the format <org-name>/<stack-name> (e.g. `acmecorp/dev`).
stack name: (dev) production
Created stack 'production'

aws:region: The AWS region to deploy into: (us-east-1)
Saved config

Installing dependencies...

added 140 packages, and audited 141 packages in 3m

48 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
npm notice
npm notice New minor version of npm available! 8.15.0 -> 8.19.1
npm notice Changelog: <https://github.com/npm/cli/releases/tag/v8.19.1>
npm notice Run `npm install -g npm@8.19.1` to update!
npm notice
Finished installing dependencies

Your new project is ready to go!

To perform an initial deployment, run `pulumi up`

Enter fullscreen mode Exit fullscreen mode

The pulumi new command creates a new Pulumi project with native support based on the specified cloud and language. After logging in, the CLI will guide us to create a new project.

First, we will be asked for a project name and description. Press ENTER to accept the default value or enter a new value.

We will then be prompted for the stack name. Accept Dev's default settings by pressing ENTER or enter a new value as Production.

Finally, some configuration values for the stack are requested. For AWS projects, we will be prompted to enter the AWS region. We can accept the default value or choose another value, such as us-east-1. After npm installs some dependencies, your project and stack will be ready.

image.png

Let's take a look at some of the generated project files.

image.png

  • Pulumi.yaml defines the project.
  • Pulumi.production.yaml contains the settings for the newly initialized stack.
  • index.ts is a Pulumi program that identifies stack resources.

Lets examine index.ts.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";

// Create an AWS resource (S3 Bucket)
const bucket = new aws.s3.Bucket("my-bucket");

// Export the name of the bucket
export const bucketName = bucket.id;

Enter fullscreen mode Exit fullscreen mode

This Pulumi program creates a new S3 bucket and exports the name of the bucket.

Step 2 - Create your first resource

Now that our basic AWS project is set up, we need to create our first resource. In this case, we create a new S3 bucket where we can store our static website. Also, make sure this bucket is private, as we only want to expose it to the CDN we'll configure later.

Delete the below existing code in pulumi-challenge/index.ts.

// Create an AWS resource (S3 Bucket)
const bucket = new aws.s3.Bucket("my-bucket");

// Export the name of the bucket
export const bucketName = bucket.id;

Enter fullscreen mode Exit fullscreen mode

Copy the below code and paste it into the pulumi-challenge/index.ts.

const bucket = new aws.s3.BucketV2(
  "bucketV2",
  {
    tags: {
      Name: "My bucket",
    },
  }
);

const bucketAcl = new aws.s3.BucketAclV2("bAcl", {
  bucket: bucket.id,
  acl: aws.s3.PublicReadAcl,
});

Enter fullscreen mode Exit fullscreen mode

Replace My bucket with a unique name. For example, pulumi-challenge-makendran-one

Step 3 - Working with Local Files

Pulumi allows us to define our infrastructure using our preferred programming language. Today we use TypeScript. This means we can access the Node API. This includes finding folders and files. We can use these APIs to sync local files to an S3 bucket along with the original Pulumi template.

We need to add the npm mime package because it is convenient to pass the mime type of the file to S3 without encoding it.

Run the below command to install mime package.

npm install mime @types/mime

Enter fullscreen mode Exit fullscreen mode

Copy the below code and paste it in the pulumi-challenge/index.ts.

import * as fs from "fs";
import * as mime from "mime";
const staticWebsiteDirectory = "website";

fs.readdirSync(staticWebsiteDirectory).forEach((file) => {
  const filePath = `${staticWebsiteDirectory}/${file}`;
  const fileContent = fs.readFileSync(filePath).toString();

  new aws.s3.BucketObject(file, {
    bucket: bucket.id,
    source: new pulumi.asset.FileAsset(filePath),
    contentType: mime.getType(filePath) || undefined,
    acl: aws.s3.PublicReadAcl,
  });
});

Enter fullscreen mode Exit fullscreen mode

But we need a real website. Create a directory called website in pulumi-challenge/website and add index.html, styles.css and normalize.css to it.

image.png

For index.html, you have a simple website layout where you can post links to your project's GitHub, Twitter and LinkedIn.

Copy index.html from GitHub and paste in the pulumi-challenge/website/index.html.

Make it beautiful with bright colors and CSS backgrounds in the style.css file.

Copy style.css from GitHub and paste in the pulumi-challenge/website/style.css.

You also need to normalize some styles so that they display consistently across browsers.

Copy normalize.css from GitHub and paste in the pulumi-challenge/website/normalize.css.

Before deploying the stack, Kindly run the command to preview the changes.

pulumi preview

Enter fullscreen mode Exit fullscreen mode

image.png

Lets go ahead and deploy stack by running the below command:

pulumi up --skip-preview

Enter fullscreen mode Exit fullscreen mode

image.png

Step 4 - Create a CDN

Next, we want to provision an S3 bucket with Cloudfront. These are quite large objects, but most of them can be copied and pasted without much thought.

Copy the below code and paste it in the pulumi-challenge/index.ts.

const s3OriginId = "myS3Origin";

const cloudfrontDistribution = new aws.cloudfront.Distribution(
  "s3Distribution",
  {
    origins: [
      {
        domainName: bucket.bucketRegionalDomainName,
        originId: s3OriginId,
      },
    ],
    enabled: true,
    isIpv6Enabled: true,
    comment: "Some comment",
    defaultRootObject: "index.html",
    defaultCacheBehavior: {
      allowedMethods: [
        "DELETE",
        "GET",
        "HEAD",
        "OPTIONS",
        "PATCH",
        "POST",
        "PUT",
      ],
      cachedMethods: ["GET", "HEAD"],
      targetOriginId: s3OriginId,
      forwardedValues: {
        queryString: false,
        cookies: {
          forward: "none",
        },
      },
      viewerProtocolPolicy: "allow-all",
      minTtl: 0,
      defaultTtl: 3600,
      maxTtl: 86400,
    },
    priceClass: "PriceClass_200",
    restrictions: {
      geoRestriction: {
        restrictionType: "whitelist",
        locations: ["US", "CA", "GB", "DE"],
      },
    },
    viewerCertificate: {
      cloudfrontDefaultCertificate: true,
    },
  }
);

Enter fullscreen mode Exit fullscreen mode

Lets go ahead and deploy stack by running the below command:

pulumi up --skip-preview

Enter fullscreen mode Exit fullscreen mode

image.png

To get the CloudFront URL, Copy the below code and paste it in the pulumi-challenge/index.ts.

export const url = cloudfrontDistribution.domainName;

Enter fullscreen mode Exit fullscreen mode

Lets go ahead and deploy the stack by running the below command:

pulumi up --skip-preview

Enter fullscreen mode Exit fullscreen mode

To see the output of the stack, run the below command

pulumi stack output


Current stack outputs (1):
    OUTPUT VALUE
    url dy97m8m1jyqks.cloudfront.net

Enter fullscreen mode Exit fullscreen mode

To view the output in JSON format, run the below command

pulumi stack output --json


{
  "url": "dy97m8m1jyqks.cloudfront.net"
}

Enter fullscreen mode Exit fullscreen mode

To see the HTML of the URL, run the below command

curl http://dy97m8m1jyqks.cloudfront.net

Enter fullscreen mode Exit fullscreen mode

Step 5 - Introduction to ComponentResources

We can keep adding resources, but Pulumi is more than that. We can create our own reusable components. Let's turn the above into a CdnWebsite component in pulumi-challenge/cdn-website/index.ts.

Create a directory under pulumi-challenge as cdn-website and a file called index.ts.

image.png

Copy index.ts from GitHub and paste in the pulumi-challenge/cdn-website/index.ts.

Keep the below code alone in the pulumi-challenge/index.ts and delete the remaining code.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";

Enter fullscreen mode Exit fullscreen mode

Copy the below code and paste it in the pulumi-challenge/index.ts

// Deploy Website to S3 with CloudFront
// Also shows the challenger how to build a ComponentResource
import { CdnWebsite } from "./cdn-website";

const website = new CdnWebsite("your-startup", {});

Enter fullscreen mode Exit fullscreen mode

Replace your-startup with a unique name. For example, pulumi-challenge-one

Lets go ahead and deploy the stack by running the below command:

pulumi up

Enter fullscreen mode Exit fullscreen mode

image.png

It will ask for us to proceed with the update or not, Select yes to deploy the change.

image.png

image.png

To get the CloudFront URL, Copy the below code and paste it in the pulumi-challenge/index.ts.

export const url = website.url;

Enter fullscreen mode Exit fullscreen mode

Lets go ahead and deploy the stack by running the below command:

pulumi up --skip-preview


Updating (production)

View Live: https://app.pulumi.com/MakendranG/pulumi-challenge/production/updates/6

     Type Name Status pulumi:pulumi:Stack pulumi-challenge-production                                                                   
Outputs:
  + url: "d2f4ewalsoswej.cloudfront.net"

Resources:
    8 unchanged

Duration: 57s

Enter fullscreen mode Exit fullscreen mode

To see the HTML of the URL, run the below command

curl http://d2f4ewalsoswej.cloudfront.net

Enter fullscreen mode Exit fullscreen mode

Step 6 - Add another provider

Now that we've served our website through S3 as fast as possible using CdnWebsite and Cloudfront, how do you know if our deployment is actually working? We can use a great service like Checkly to make sure your site goes through a series of health checks.

Checkly is the API & E2E monitoring platform for the modern stack: programmable, flexible and loving JavaScript.

image.pngSource Image

First, we need to add a new provider.

npm install @checkly/pulumi

Enter fullscreen mode Exit fullscreen mode

To get the API key from checkly

Login into checkly account and go to User Settings -> API keys.

Select Create API key

image.png

To get the Account ID from checkly

Go to Account Settings of checkly.

Run the below command to configure checkly provider.

# API KEY: https://app.checklyhq.com/settings/account/api-keys
pulumi config set checkly:apiKey --secret

# AccountID: https://app.checklyhq.com/settings/account/general
pulumi config set checkly:accountId

Enter fullscreen mode Exit fullscreen mode

Copy the below code and paste it in the pulumi-challenge/index.ts

import * as checkly from "@checkly/pulumi";
import * as fs from "fs";

new checkly.Check("index-page", {
  activated: true,
  frequency: 10,
  type: "BROWSER",
  locations: ["eu-west-2"],
  script: websiteUrl.apply((url) =>
    fs
      .readFileSync("checkly-embed.js")
      .toString("utf8")
      .replace("{{websiteUrl}}", url)
  ),
});

Enter fullscreen mode Exit fullscreen mode

Replace script: websiteUrl.apply((url) as script: url.apply((url) in the above code and also eu-west-2 as us-east-1.

We can see that we are using fs.readFileSync again. This is because it stores Checkly code and node-based code in its own file, so we get auto-completion and correct syntax highlighting without having to store them as string objects in our existing code.

Add the following to the pulumi-challenge/checkly-embed.js file:

const playwright = require("playwright");
const expect = require("expect");

const browser = await playwright.chromium.launch();
const page = await browser.newPage();

await page.goto("https://{{websiteUrl}}");
expect(await page.title()).toBe("Pulumi Challenge");

await browser.close();

Enter fullscreen mode Exit fullscreen mode

Lets go ahead and deploy the stack by running the below command:

pulumi up

Enter fullscreen mode Exit fullscreen mode

image.png

Step 7 - Dynamic implementation of the Swag provider

Everyone loves SWAG and Pulumi wants to give us something for completing this challenge. To do this, we need to align with dynamic providers through Pulumi. Create a new directory and file in pulumi-challenge/swag-provider/index.ts.

For this dynamic provider, only CommonJS modules are available. We can make HTTP requests using got version 11.8.0.

Run the below command to install got.

npm install got@11.8.0

Enter fullscreen mode Exit fullscreen mode

Copy index.ts from GitHub and paste in the pulumi-challenge/swag-provider/index.ts.

Now add this last block to pulumi-challenge/index.ts and run pulumi up.

import { Swag } from "./swag-provider";

const swag = new Swag("your-startup", {
  name: "YOUR NAME",
  email: "YOUR EMAIL",
  address: "YOUR ADDRESS",
  size: "SIZE",
});

Enter fullscreen mode Exit fullscreen mode

Replace the your-startup, YOUR NAME, YOUR EMAIL and SIZE as per your records.

Congratulations! We have Completed Pullumi's first challenge. To remove all these resources, perform a pulumi destroy. Otherwise, use the new site! Change it to make it your own.

To know more about the pulumi challenges, refer here

Gratitude for perusing my article till end. I hope you realized something unique today. If you enjoyed this article then please share to your buddies and if you have suggestions or thoughts to share with me then please write in the comment box.

Follow me and share your thoughts,GitHubLinkedInTwitter

Top comments (0)