DevOps·

Using Any Terraform Provider in Pulumi: A Guide with Netlify provider

Manage Netlify Resources with Pulumi

Pulumi recently announced a new feature that lets developers reuse any Terraform or OpenTofu provider within a Pulumi program. In this article, we will explore this feature through a case study with Netlify.

What are “Providers” for IaC tools?

Let’s first talk about what a provider is in the context of an infrastructure as code solution.

A provider is a plugin enabling an IaC tool to interact with infrastructure services (cloud providers, SaaS providers, or any other API). Specifically, a provider defines the resources and operations available on an infrastructure service and how to communicate with its API.

There are already many providers in the Terraform/OpenTofu ecosystem. Since a provider is just an abstraction over an API, a Terraform provider can be leveraged by other IaC tools. Instead of creating new providers for each infrastructure service, some IaC tools have found ways to generate their own providers from Terraform providers, allowing them to use the same resource model:

That does not mean Pulumi or Crossplane use Terraform in their engines; they only use its providers to interact with some APIs. Apart from that, Pulumi and Crossplane have their ways of working, managing the state, and so on.

You can check this article for additional information about Pulumi “using” Terraform providers.

About Pulumi Providers

Not all Pulumi providers are generated from Terraform providers. Some are “native providers” (like Azure Native, AWS Native, or Kubernetes providers) and offer significant benefits. For example, the Azure Native provider is always up-to-date and has 100% API coverage. Therefore, using it instead of the Classic one bridged from the Terraform AzureRM provider is recommended.

You can find the full list of native providers in the Pulumi registry by filtering based on the provider type.

Nevertheless, many providers in the Pulumi registry are providers bridged from Terraform providers. Some are maintained by Pulumi, some by vendors, and others by the community. Pulumi has created a boilerplate repository to help people build a new Pulumi provider that wraps an existing Terraform provider. However, there is still some maintenance work required, so not all Terraform providers are bridged and published in the Pulumi registry.

That's why Pulumi introduced support for generating full Pulumi SDKs locally for any Terraform provider. This not only solves the problem of missing Pulumi providers in the registry but also allows you to generate Pulumi SDKs for Terraform providers that are internal to your company.

That's it for the explanations. Now, let's see this new capability in action!

Generate a Local Pulumi Package from the Netlify Terraform Provider

My blog is hosted on Netlify, and like many others, I manage it using the Netlify website. However, I realized it would be useful to have the infrastructure code to manage it as well. Even though my website is simple and doesn't require much, there are some site configurations and environment variables that would be better versioned in a code repository.

Unfortunately, there is currently no Netlify provider for Pulumi, but there is one for Terraform. Let's generate a local Pulumi SDK from it.

First, I can create a small Pulumi project in TypeScript (other languages would work too).

pulumi new typescript

Then, we can use the pulumi package add command to generate a TypeScript SDK from the Terraform Netlify provider.

pulumi package add terraform-provider netlify/netlify
By default, this command uses the latest version of the Terraform Provider specified in the OpenTofu registry. Alternatively, you can designate a different registry and select a different version to use.
If the Terraform provider you want to use to generate a Pulumi SDK is on your local file system, you can simply specify the path to the provider.

A terminal window displaying the process of adding the Netlify Terraform provider using the Pulumi package. The command pulumi package add terraform-provider netlify/netlify is used, followed by the generation of a Node.js SDK for the Netlify package. Instructions for adding the SDK to a Node.js project with the command pnpm add netlify@file:sdk\netlify and importing it in TypeScript code are also provided.

You can see that a ./sdks/netlify folder has been created that contains the Pulumi TypeScript SDK for the Netlify provider.

File directory structure displayed in a code editor. The root directory is named "AnyTerraformProvider," containing folders. Inside "sdks/netlify" there are various TypeScript files such as "deployKey.ts," "dnsRecord.ts," "dnsZone.ts," and others.

We can then reference the dependency to this local package using the following command:

pnpm add netlify@file:sdks\netlify

Now, we can start creating the infrastructure code using the Netlify provider.

Using the Netlify Provider

To use the Netlify provider, we need to provide the following configuration:

  • A personal access token to authenticate with the Netlify API.
  • The default team slug, which in our case is the team containing my blog website (note that this is not mandatory; we can also specify the team slug for each resource in the code that needs it).

We can set this configuration using the following commands:

pulumi config set netlify:token --secret xxxxxx
pulumi config set netlify:defaultTeamSlug mynetlifyteam

However, instead of doing this, I will use a Pulumi ESC environment that I created with all the settings and secrets I need for my blog. This way, I can simply import this blog/prod environment into my configuration:

 pulumi config env add blog/prod

My stack configuration file Pulumi.dev.yaml becomes short and simple:

Pulumi.dev.yaml
environment:
  - blog/prod
Storing your configuration in the Pulumi.dev.yaml file is perfectly fine, you don’t have to use Pulumi ESC like I did. I just wanted to mention it here because that’s what I used and because I have been a big fan of Pulumi ESC since I discovered it. However, please be advised that Pulumi ESC is not free of charge (except for individuals, with some limitations).

Since it’s a locally bridged provider, Pulumi does not have documentation for it on its registry yet (though it’s in Pulumi’s roadmap to host static documentation for most used providers). So we can rely on the documentation on the HashiCorp Terraform Registry (for some reason, the documentation is not available on the OpenTofu registry website). But, to be honest we don’t really need it because we have access to the SDK code with the documentation in it, and can rely on auto-completion to help us write the code. That’s the beauty of using programming languages for Infrastructure as Code.

The Netlify provider does not allow you to create a site; it only lets you retrieve existing sites and modify their configuration, domain, DNS, and environment variables. So, we can start by retrieving my blog site:

import * as netlify from "netlify";

const blog = netlify.getSiteOutput({
  name: "myawesomeblog"
})

export const customDomain = blog.customDomain

I added the customDomain output just to make sure the program was working correctly.

Screenshot showing the output of a Pulumi stack. The details include the stack type as "pulumi:pulumi:Stack," the name as "AnyTerraformProvider-dev," and the status is not stated. The outputs section lists "blogDomain: 'techwatching.dev'." The resources section indicates "1 unchanged." The duration of the process is 12 seconds.

My blog is a Nuxt application that uses the Nuxt UI Pro component library. For Netlify to be able to build the application, I have to set in Netlify the environment variable NUXT_UI_PRO_LICENSE with my license key. So, let’s do that.

My license key is already in my configuration (stored as a secret in my ESC blog/prod environment), so I can retrieve it like this:

const config = new Config()
const nuxtUIKey = config.requireSecret("nuxt.UIProLicenseKey")

Creating the environment variable is pretty straightforward:

new netlify.EnvironmentVariable("nuxtUIKeyEnvVar", {
  siteId: blog.id,
  key: "NUXT_UI_PRO_LICENSE", 
  values: [{
    value: nuxtUIKey,
    context: "all"
  }]
})

Because I used the requireSecret method on the config, Pulumi will treat the nuxtUIKey as a secret and encrypt it automatically in the state.

There is also an option to manually indicate a property of a resource is a secret: I could have added additionalSecretOutputs option set to “values” (when declaring the environment variable) to ensure Pulumi encrypts this property on the resource. I don’t need to do it here because Pulumi automatically detect that nuxtUIKey is a secret.

Pulumi stack update summary showing the creation of a Netlify environment variable for the "AnyTerraformProvider-dev" stack. The status indicates that one resource was created and one remained unchanged, with a total duration of 8 seconds.

Of course, I can configure many other things for my Netlify site, but I think this is enough to show Pulumi's support for using any Terraform provider.

Summary

By allowing developers to generate local Pulumi SDKs from any Terraform provider, Pulumi addresses the issue of missing providers in its registry and enables the use of internal company-specific providers as well.

In this article, we have seen a practical application of this feature with a real-world example using the Netlify provider. It was impressive to see how easy it has become to use a Terraform provider from a Pulumi program. While most commonly used providers were already available, this feature opens up exciting possibilities for niche services or internal tools.

You can get more information about this feature on the Pulumi Terraform Bridge repository. Don’t hesitate to experiment with it on your projects.


The opinions expressed herein are my own and do not represent those of my employer or any other third-party views in any way.

Copyright © 2024 Alexandre Nédélec. All rights reserved.