Use your favorite programming language to provision Infrastructure as Code

Provision everything you need to get your infrastructure off the ground with Node.js or another programming language.
87 readers like this.
Puzzle pieces coming together to form a computer screen

Opensource.com

As you navigate the world of IT and technology, there are some terms you come across repeatedly. Some of them are hard to quantify and may take on different meanings as time goes on. "DevOps" is an example of a word that seems (to me) to change depending on the person using it; the original DevOps pioneers might not even recognize what we call DevOps today.

If you're a software developer, "Infrastructure as Code" (IaC) may be one of those terms. IaC is using the same software-development practices you'd use to write user-facing features to declare the infrastructure that applications run on. This often means using tools like Git or Mercurial for version control and Puppet, Chef, or Ansible for configuration management. At the infrastructure-provisioning layer, the most common technology is CloudFormation (for AWS specifically) or Terraform as an open source alternative for creating hybrid-cloud resources for your applications to run on.

There are great options in the configuration-management space to write IaC as either configuration files or preferred programming languages, but this choice is not well-known in the infrastructure-provisioning space.

Pulumi offers an option to use standard programming languages to define infrastructure. It supports an array of languages, including JavaScript, TypeScript, Go, Python and C#. Much like Terraform, Pulumi has first-class support for many familiar cloud providers, like AWS, Azure, Google Cloud, and other providers.

In this article, I'll show you how to use Pulumi to write infrastructure in Node.js.

Prerequisites

First, make sure you're ready to use Pulumi. Pulumi supports all major operating systems, so the methods you use to install its prerequisites depend on the operating system you're using.

First, install the interpreter for your preferred programming language. I'll be using TypeScript, so I need to install the node binary. Consult Node's installation instructions for information about your operating system. You can use Homebrew on Mac or Linux to install:

brew install node

On Linux, you can alternately use your usual package manager, such as apt or dnf:

$ sudo dnf install nodejs

In either case, the result should be that the node binary is available in your $PATH. To confirm it's accessible, run:

node --version

Next, install the Pulumi command-line interface (CLI). You can find operating system-specific installation instructions in Pulumi's docs. With brew on a Mac or Linux:

brew install pulumi

Alternately, you can use the install script. First download it and review it, and then execute it:

$ curl -fsSL --output pulumi_installer.sh \
https://get.pulumi.com/ 
$ more  pulumi_installer.sh 
$ sh ./pulumi_installer.sh

Again, the desired result is to have the pulumi binary available in your path. Check the version to make sure you're ready to proceed:

pulumi version
v2.5.0

Configure Pulumi

Before you start provisioning any infrastructure, give Pulumi somewhere to store its state.

Pulumi stores its state in a backend. The default backend is Pulumi's software-as-a-service (which has a free plan for individual users), but for this example, use the alternative file backend. The file backend will create a file on your local filesystem to store the state with:

pulumi login --local

If you're planning on sharing this project with someone else, the file backend might not be a good starting point. Pulumi can also store its state in a cloud object store like AWS S3. To use it, create an S3 bucket and log in:

pulumi login --cloud-url s3://my-pulumi-state-bucket

Now that you've logged into a state backend, you can create a project and a stack!

Before you start creating a Pulumi project, get to know the following Pulumi terminology, which you'll see in this tutorial.

Project

A project is a directory that contains a Pulumi.yaml file. This file contains metadata Pulumi needs to know to do its thing. Example fields you'll find in a Pulumi.yaml file are:

  • The runtime (e.g., Python, Node, Go, .Net)
  • A description of the project (e.g., "my first Pulumi project")
  • A name for the project

A project is a loosely defined concept that can fit your needs. Generally, a project contains a bunch of resources, which are things you want to provision and control. You might choose to have small Pulumi projects with very few resources or large projects that contain all the resources you need. As you become more familiar with Pulumi, it'll become more apparent how you want to lay out your projects.

Stack

A Pulumi stack allows you to differentiate your Pulumi projects depending on configurable values. A common use is to deploy a project to different environments like development or production or different regions like Europe, the Middle East and Africa, and the US.

When getting started, you're not likely to need a complex stack setup, so this walkthrough uses the default stack name, dev.

Use TypeScript for IaC

You can bootstrap a Pulumi project using the handy pulumi new command. The new command has a whole host of flags and options that should help you get started with Pulumi, so go ahead and create your first project:

$ pulumi new 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) my-first-project
project description: (A minimal TypeScript Pulumi program) My very first Pulumi program
Created project 'my-first-project'

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) dev
Created stack 'dev'

Installing dependencies...


> node scripts/postinstall

added 82 packages from 126 contributors and audited 82 packages in 2.84s

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

found 0 vulnerabilities

Finished installing dependencies

Your new project is ready to go! ✨

To perform an initial deployment, run 'pulumi up'

A lot happened here, so I'll break it down:

The first part identifies a template for your Pulumi project. I chose the generic typescript option, but there are many options available.

This new command grabbed the template from your templates repository and copied this file locally, including the runtime dependencies (in this case, package.json).

The new command took care of installing those dependencies by running npm install inside this directory. Then npm install downloaded and installed everything needed to run your Pulumi program, which, in this case, is very straightforward: the @pulumi/pulumi NPM package.

You're ready to create your very first resource!

Create your first cloud resource

A resource is a thing that is managed by your infrastructure-provisioning software lifecycle. Resources are generally a cloud provider object, like an S3 bucket. Pulumi providers handle Pulumi resources, and providers are specific to the cloud provider they manage. Pulumi has around 40 providers you can use, but for your first resource, use one of the simplest—the random provider.

The random provider does what the name suggests: it idempotently creates a random resource (which could be a string, for example) and stores it in the Pulumi state.

Add this as a dependency to your Pulumi project using npm:

npm install @pulumi/random

The npm package manager downloads and installs the random provider package and installs it for you. Now you're ready to write your Pulumi program.

When you generated your project earlier, Pulumi's bootstrap process created an index.ts TypeScript file. Open it in your favorite integrated development environment (IDE) and add your first resource:

import * as pulumi from "@pulumi/pulumi";
import * as random from "@pulumi/random";

const password = new random.RandomString(`password`, {
    length: 10
})

If you're at all familiar with TypeScript or JavaScript, this will look very familiar because it's written in a programming language you know. This also applies if you're using one of Pulumi's other supported languages. Here's that same random resource from before, but this time in Python:

import pulumi_random as random

password = random.RandomString("password", length=10)

Pulumi projects currently support only a single language, but each project can reference other projects written in other languages—a useful trick for members of the polyglot team.

You've written your first Pulumi resource. Now you need to deploy it.

Leave your editor and head back to the command line. From your project directory, run pulumi up and watch the magic happen:

pulumi up 

Previewing update (dev):
     Type                          Name                  Plan
 +   pulumi:pulumi:Stack           my-first-project-dev  create
 +   └─ random:index:RandomString  password              create

Resources:
    + 2 to create

Do you want to perform this update? yes
Updating (dev):
     Type                          Name                  Status
 +   pulumi:pulumi:Stack           my-first-project-dev  created
 +   └─ random:index:RandomString  password              created

Resources:
    + 2 created

Duration: 2s

Permalink: file:///Users/lbriggs/.pulumi/stacks/dev.json

Excellent, you have your first Pulumi resource! While you might be enjoying this sense of achievement, unfortunately, this random resource isn't that useful—it's just a random string, and you can't even see what it is. Address that part first: Modify your earlier program and add export to the constant you created:

import * as pulumi from "@pulumi/pulumi";
import * as random from "@pulumi/random";

export const password = new random.RandomString(`password`, {
    length: 10
})

Rerun pulumi up and look at the output:

pulumi up
Previewing update (dev):
     Type                 Name                  Plan
     pulumi:pulumi:Stack  my-first-project-dev

Outputs:
  + password: {
      + id             : "&+r?{}J$J7"
      + keepers        : output<string>
      + length         : 10
      + lower          : true
      + minLower       : 0
      + minNumeric     : 0
      + minSpecial     : 0
      + minUpper       : 0
      + number         : true
      + overrideSpecial: output<string>
      + result         : "&+r?{}J$J7"
      + special        : true
      + upper          : true
      + urn            : "urn:pulumi:dev::my-first-project::random:index/randomString:RandomString::password"
    }

Resources:
    2 unchanged

Do you want to perform this update? yes
Updating (dev):
     Type                 Name                  Status
     pulumi:pulumi:Stack  my-first-project-dev

Outputs:
  + password: {
      + id        : "&+r?{}J$J7"
      + length    : 10
      + lower     : true
      + minLower  : 0
      + minNumeric: 0
      + minSpecial: 0
      + minUpper  : 0
      + number    : true
      + result    : "&+r?{}J$J7"
      + special   : true
      + upper     : true
      + urn       : "urn:pulumi:dev::my-first-project::random:index/randomString:RandomString::password"
    }

Resources:
    2 unchanged

Duration: 1s
Permalink: file:///Users/lbriggs/.pulumi/stacks/dev.json

Now you can see a randomly generated string under the result section of the Outputs. The resource you created has a number of properties that you can see now.

This is all well and good, but if you want to enjoy IaC, you're going to have to provision something other than a random string. Give it a try!

Deploy a container

So far, you've bootstrapped your Pulumi experience by installing dependencies and registered a simple random resource. Now deploy some actual infrastructure, albeit to your local machine.

First, add the @pulumi/docker provider to your stack. Use your chosen package manager to add it to the project:

npm install @pulumi/docker

You've pulled down the Pulumi Docker provider package from npm, which means you can now create Docker images in your project.

If you don't have Docker installed on your machine, now is an excellent time to get it. Instructions will depend on your operating system, so take a look at Docker's installation page for information.

Open up your favorite IDE again and run a Docker container. Modify your index.ts file from earlier to make it look like this:

import * as pulumi from "@pulumi/pulumi";
import * as random from "@pulumi/random";
import * as docker from "@pulumi/docker";

const password = new random.RandomString(`password`, {
    length: 10
})

const container = new docker.Container(`my-password`, {
    image: 'hashicorp/http-echo',
    command: [ pulumi.interpolate`-text=Your super secret password is: ${password.result}` ],
    ports: [{
        internal: 5678,
        external: 5678,
    }]
})

export const id = container.id

This creates a container that creates a web server. The output of the web server is your randomly generated string, in this case, a password. Run this and see what happens:

pulumi up

Previewing update (dev):
     Type                       Name                  Plan
     pulumi:pulumi:Stack        my-first-project-dev
 +   └─ docker:index:Container  my-password           create

Outputs:
  + id      : output<string>
  ~ password: {
        id        : "&+r?{}J$J7"
        length    : 10
        lower     : true
        minLower  : 0
        minNumeric: 0
        minSpecial: 0
        minUpper  : 0
        number    : true
        result    : "&+r?{}J$J7"
        special   : true
        upper     : true
        urn       : "urn:pulumi:dev::my-first-project::random:index/randomString:RandomString::password"
    }

Resources:
    + 1 to create
    2 unchanged

Do you want to perform this update? yes
Updating (dev):
     Type                       Name                  Status
     pulumi:pulumi:Stack        my-first-project-dev
 +   └─ docker:index:Container  my-password           created

Outputs:
  + id      : "e73b34aeca34a64b72b61b0b9b8438637ce28853937bc359a1528ca99f49ddda"
    password: {
        id        : "&+r?{}J$J7"
        length    : 10
        lower     : true
        minLower  : 0
        minNumeric: 0
        minSpecial: 0
        minUpper  : 0
        number    : true
        result    : "&+r?{}J$J7"
        special   : true
        upper     : true
        urn       : "urn:pulumi:dev::my-first-project::random:index/randomString:RandomString::password"
    }

Resources:
    + 1 created
    2 unchanged

Duration: 2s
Permalink: file:///Users/lbriggs/.pulumi/stacks/dev.json

You'll notice in the Outputs section that the values you're outputting have changed; it's just a Docker container ID. Check whether your very simple password generator works:

curl http://localhost:5678
Your super secret password is: &+r?{}J$J7

It does! You just provisioned your first piece of infrastructure with TypeScript!

A quick note on Pulumi outputs

You'll notice in the code that creates the Docker container that it uses a special pulumi.interpolate call. If you're familiar with TypeScript, you might be curious why that's needed (as it's Pulumi-specific). There's an interesting reason.

When Pulumi creates a resource, there are values that Pulumi doesn't know until the program executes. In Pulumi, these are called Outputs. These Outputs can be seen in the code above; for example, in your first random resource, you used the export keyword to output the random resource's properties, and you also exported the container ID of the container you created.

Because Pulumi doesn't know the value of these Outputs until execution time, it needs special helpers to use them when manipulating strings. If you want to know more about this special programming model, watch this short video.

Wrapping up

IaC has evolved in many ways as complexity has arisen in hybrid-cloud infrastructures. In the infrastructure-provisioning space, Pulumi is a great choice for using your favorite programming language to provision everything you need to get your infrastructure off the ground, then you can tag in your favorite configuration management tooling to take the next steps.

What to read next
User profile image.
Lee Briggs is a Staff Software Engineer at Pulumi. With almost 10 years of experience designing, building and maintaining distributed and complex systems, he wears the scars of many deployment tools. When he’s not trying to fit monolithic applications into containers, he playing and watching Soccer and walks with his dog, Cindy.

Comments are closed.

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.