user-icon Matthias Müller
12. March 2021
timer-icon 5 min

How to write reusable infrastructure code with Pulumi

Extracting infrastructure code into modules is a simple way to share code among multiple teams or repos and to avoid code duplication. In this post I will show you how to write reusable infrastructure code with Pulumi.

Pulumi

The Pulumi SDK is an open-source tool to write infrastructure as code (IaC). IaC can be a powerful enabler for teams on their DevOps transformation journey. Using programming languages like Python, Go or Typescript, it’s possible to provision, track and manage cloud resources.

You can check the official homepage for more information.

In this blog post the examples are written with Typescript, but all the concepts I cover apply to every language supported by Pulumi.

An easy way to set up a new project is using the pulumi new command:


This will create all the basic files you need. There are also templates for other languages and clouds.

Component Resources 

Writing reusable infrastructure code for Pulumi can be done by creating a new component resource. That’s a construct by Pulumi, intended to bundle multiple resource into a new resource. 

Creating a new component resource is simple as this:


Component resources can be used in all languages supported by Pulumi, but the exact syntax will differ. See the Pulumi docs for more information.

A component resource is treated by Pulumi like a normal resource. By calling the super method you can define the internal identifier for your component resource, which gets added to the URN (Universal Resource Name) of all the resources inside the component resource. URNs are able to globally identify every resource and allow to perform various actions on specific resources, like updating or destroying. You should make your identifier unique to avoid name conflicts.

Inside the constructor you can add your resources. In this case I want to create a storage account on Azure: 


Adding the resource inside the constructor doesn’t automatically attach it to your component resource. Only by referencing the component resource as the parent does the resource get attached (see line 13).

In and Outputs

The component resource currently has only static content. As the next step you can add parameters to the constructor. The best way of doing this is by adding an interface. 


Using an interface allows you to have auto-completion in many IDEs and also the option to add documentation via TSDoc.

Configuring default values can be done by using the OR operator, see line 10 and 11.

In the interface you can see two different types of parameters: the basic Typescript string and Pulumi.Input<string>. 

Pulumi.Input and its complement Pulumi.Output are wrapper classes. They help in cases, where you deal with asynchronous values. Imagine you want to create a public IP and an A record pointing to the public IP. Before the public IP is created you won’t know the exact IP address to use for the A record. By wrapping the value with Pulumi.Input/Output, Pulumi will make sure that the public IP is created first. It will also pass the IP address to the A record resource once it is known.

To define outputs for a component resource you simply add a class property and assign a value in the constructor. Outputs for resources are usually wrapped by Pulumi.Output. It’s possible to do the same for component resource outputs.

Conditional Resources

Depending on the use case, it can happen that only a subset or a specific number of resources inside the component resource are required. Instead of developing an entire new module it’s possible to use language features like an if clause to make a resource optional: 


One of the advantages of Pulumi supporting general purpose languages is the possibility to use control structures like if clauses. It allows for more variants in your infrastructure while keeping the code clean. Another example would be iterating over a list with a for loop to create a set of network rules.

While those language features make Pulumi really powerful, this always carries the risk of developing a complicated module over time, which is hard to maintain.

Usage

That’s all you need to know to develop your first component resource. You can then publish your code as a npm package to make it available to your team(s).

Using your component resource works similar to any other package and resource:


After provisioning you can check your stack structure via pulumi stack -u and will see something similar to this:


The storage account (line 7+8) and network rule (line 9+10) are both bundled under the component resource (line 5+6).

And that’s how you can create reusable infrastructure code with Pulumi.

You can find the final version of the example on GitHub.

Comment article

Comments

  1. Daniel Givens

    Thank you so much for this. The docs on component resources completely leave out the the class property as how to expose resources defined within the component resources. They make it out such that you should use registerOutputs(), but that doesn’t work when you want to expose whole resources, such as when you create multiple aws.ec2.Instance’s within the component resource and want to return them as an array to be used by other resources outside of the component resource. I spent a fair amount of time trying to make that work they way they have it documented until I found this.

    • Müller Matthias

      I’m glad I could help you.