pulumi-hcl

module
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2026 License: Apache-2.0

README

Pulumi HCL Language Plugin

A Pulumi language plugin that enables running Pulumi against a Terraform HCL IaC program.

[!WARNING] This language is in active development and not yet suitable for production use.

Overview

This plugin allows you to use familiar Terraform/HCL syntax while leveraging Pulumi's state management, secrets handling, and cloud platform. It parses HCL files and translates them to Pulumi resource registrations at runtime.

# main.tf
resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-unique-bucket-name"

  tags = {
    Environment = "dev"
    ManagedBy   = "Pulumi"
  }
}

output "bucket_arn" {
  value = aws_s3_bucket.my_bucket.arn
}

Installation

Pulumi HCL ships with the pulumi CLI. If you need the absolute latest version, you can install the plugin directly onto your path:

go install github.com/pulumi-labs/pulumi-hcl/cmd/pulumi-language-hcl@latest  # for the language
go install github.com/pulumi-labs/pulumi-hcl/cmd/pulumi-converter-hcl@latest # for the converter

Usage

  1. Create a Pulumi.yaml with runtime: hcl:
name: my-project
runtime: hcl
description: My HCL project
  1. Create HCL files (.tf extension):
# main.tf
resource "random_pet" "my_pet" {
  length = 2
}

output "pet_name" {
  value = random_pet.my_pet.id
}
  1. Run Pulumi commands as usual:
pulumi up

Supported Features

Resources
resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t3.micro"

  tags = {
    Name = "web-server"
  }
}
Data Sources
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-*-amd64-server-*"]
  }
}
Variables
variable "region" {
  type        = string
  default     = "us-west-2"
  description = "AWS region"
}

variable "instance_count" {
  type    = number
  default = 1
}
Locals
locals {
  common_tags = {
    Environment = "dev"
    Project     = "my-project"
  }

  name_prefix = "myapp-${var.environment}"
}
Outputs
output "instance_ip" {
  value       = aws_instance.web.public_ip
  description = "Public IP of the instance"
}
Meta-Arguments
resource "aws_instance" "web" {
  count = var.instance_count
  # or
  for_each = var.instances

  ami           = data.aws_ami.ubuntu.id
  instance_type = each.value.type

  depends_on = [aws_security_group.allow_http]

  lifecycle {
    create_before_destroy = true
    ignore_changes        = [tags["Timestamp"]]
  }

  timeouts {
    create = "60m"
    update = "30m"
    delete = "2h"
  }
}
Modules
# Local module
module "vpc" {
  source = "./modules/vpc"
  cidr_block = "10.0.0.0/16"
}

# Git source
module "eks" {
  source = "git::https://github.com/org/terraform-aws-eks.git?ref=v1.0.0"
}

# Terraform Registry
module "rds" {
  source  = "terraform-aws-modules/rds/aws"
  version = "6.0.0"
}

# GitHub shorthand
module "lambda" {
  source = "github.com/org/terraform-aws-lambda"
}

Modules map to Pulumi component resources. All source types are supported: local paths, Git URLs, GitHub/BitBucket shorthand, Terraform Registry, and HTTP archives.

Provisioners
resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t3.micro"

  provisioner "local-exec" {
    command = "echo ${self.public_ip} >> hosts.txt"
  }

  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx"
    ]
  }

  provisioner "file" {
    source      = "config.txt"
    destination = "/tmp/config.txt"
  }

  connection {
    type        = "ssh"
    user        = "ubuntu"
    private_key = file("~/.ssh/id_rsa")
    host        = self.public_ip
  }
}

Provisioners map to the Pulumi Command provider:

  • local-execcommand:local:Command
  • remote-execcommand:remote:Command
  • filecommand:remote:CopyToRemote

WinRM connections are not supported — SSH only.

Moved and Import Blocks
# Rename a resource without recreating it
moved {
  from = aws_instance.old_name
  to   = aws_instance.new_name
}

# Import an existing resource
import {
  to = aws_instance.web
  id = "i-1234567890abcdef0"
}
Provider Configuration

Providers are resolved the same way as OpenTofu. By default they are looked up in the OpenTofu registry, so unqualified sources such as aws resolve to registry.opentofu.org/hashicorp/aws and are bridged into Pulumi automatically — no required_providers entry is needed.

Use the terraform block to pin a source or version, and provider blocks to configure provider instances:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 6.0"
    }
  }
}

provider "aws" {
  region = var.region
}

Sources prefixed with pulumi/ (e.g. pulumi/aws) consume a native Pulumi provider instead of bridging a Terraform one. backend, required_version, and provider_meta inside the terraform block are accepted for compatibility but ignored with a warning. See docs/providers.md for details.

Design Overview

Architecture
┌─────────────────────────────────────────────────────────────────┐
│                        Pulumi Engine                             │
│  ┌─────────────┐  ┌──────────────┐  ┌─────────────────────────┐ │
│  │   CLI       │  │ State Mgmt   │  │   Provider Plugins      │ │
│  └─────────────┘  └──────────────┘  └─────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
                              │
                              │ gRPC (LanguageRuntime)
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                   pulumi-language-hcl                            │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │ Server (pkg/server)                                         ││
│  │  - LanguageRuntimeServer gRPC implementation                ││
│  │  - GetRequiredPlugins, Run, GetProgramDependencies          ││
│  └─────────────────────────────────────────────────────────────┘│
│  ┌─────────────────────────────────────────────────────────────┐│
│  │ Parser (pkg/hcl/parser)                                     ││
│  │  - Uses hashicorp/hcl/v2 (MPL licensed)                     ││
│  │  - Parses *.tf files into AST                               ││
│  └─────────────────────────────────────────────────────────────┘│
│  ┌─────────────────────────────────────────────────────────────┐│
│  │ AST (pkg/hcl/ast)                                           ││
│  │  - Config, Resource, Variable, Local, Output, Provider      ││
│  │  - Terraform-compatible block structures                    ││
│  └─────────────────────────────────────────────────────────────┘│
│  ┌─────────────────────────────────────────────────────────────┐│
│  │ Graph (pkg/hcl/graph)                                       ││
│  │  - Dependency extraction from HCL expressions               ││
│  │  - Topological sort for execution order                     ││
│  │  - Parallel execution scheduler                             ││
│  └─────────────────────────────────────────────────────────────┘│
│  ┌─────────────────────────────────────────────────────────────┐│
│  │ Evaluator (pkg/hcl/eval)                                    ││
│  │  - HCL expression evaluation                                ││
│  │  - Terraform-compatible function library                    ││
│  │  - Variable/resource reference resolution                   ││
│  └─────────────────────────────────────────────────────────────┘│
│  ┌─────────────────────────────────────────────────────────────┐│
│  │ Packages (pkg/hcl/packages)                                 ││
│  │  - Pulumi provider schema loading                           ││
│  │  - TF resource type → Pulumi token mapping                  ││
│  │  - Cached provider info for fast startup                    ││
│  └─────────────────────────────────────────────────────────────┘│
│  ┌─────────────────────────────────────────────────────────────┐│
│  │ Run Engine (pkg/hcl/run)                                    ││
│  │  - Orchestrates execution                                   ││
│  │  - Registers resources with Pulumi                          ││
│  │  - Handles count/for_each expansion                         ││
│  └─────────────────────────────────────────────────────────────┘│
│  ┌─────────────────────────────────────────────────────────────┐│
│  │ Transform (pkg/hcl/transform)                               ││
│  │  - cty.Value ↔ Pulumi PropertyValue conversion              ││
│  │  - camelCase ↔ snake_case name mapping                      ││
│  └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
Execution Flow
  1. Parse: HCL files are parsed using hashicorp/hcl/v2 into an AST
  2. Graph: Dependencies are extracted and a DAG is built
  3. Execute: Nodes are processed in parallel where dependencies allow:
    • Variables → set in evaluation context
    • Locals → evaluated and stored
    • Resources/Data Sources → registered with Pulumi (parallel)
    • Outputs → collected and registered on stack
Type Resolution

Pulumi HCL supports Terraform-style resource type names:

# Terraform-style
resource "aws_ec2_instance" "web" { }      # → aws:ec2/instance:Instance

Type resolution is conducted with the following algorithm:

  1. The provider package name is extracted from the first underscore-delimited segment of the HCL type (e.g. aws from aws_s3_bucket). If required_providers is specified, the longest matching provider prefix is used to resolve ambiguity.
  2. The provider's schema is loaded from the Pulumi registry.
  3. Each resource in the provider schema has its module path and resource name lowercased and joined, with all underscores and slashes stripped, forming a lookup key.
  4. The remaining portion of the HCL type (after the provider prefix) is compared against these keys (also with underscores stripped) to find the matching resource.

For example, aws_ec2_instance → provider aws, lookup key ec2instance → matches aws:ec2/instance:Instance in the AWS schema.

Multi-Language Components

HCL modules can be published as reusable Pulumi components consumable from any language. See docs/mlc.md for details on authoring MLCs with the terraform { component { ... } package { ... } } syntax.

Terraform Compatibility

This plugin supports the majority of Terraform's HCL syntax. For detailed compatibility information and known limitations, see docs/terraform-compatibility.md.

Supported
  • resource blocks with all meta-arguments (count, for_each, depends_on, lifecycle, timeouts)
  • data source blocks
  • variable blocks with defaults and types
  • locals blocks
  • output blocks
  • provider blocks (including alias for multiple configurations)
  • terraform block with required_providers
  • module blocks (local, Git, Terraform Registry, HTTP sources)
  • provisioner blocks (local-exec, remote-exec, file)
  • dynamic blocks
  • moved blocks (map to Pulumi aliases)
  • import blocks (map to Pulumi import option)
  • check blocks (non-blocking assertions, with optional scoped data sources)
  • lifecycle meta-arguments, including replace_triggered_by
  • Most Terraform built-in functions
  • Resource and data source references
  • Splat expressions (resource.name[*].attr)
Not Supported
  • backend, required_version, and provider_meta in the terraform block — accepted but ignored with a warning; Pulumi manages state independently
  • WinRM connection blocks — connection supports type = "ssh" only
  • List<Object> empty vs null distinction: HCL block syntax cannot distinguish between an empty and null List<Object>, which is a known incompatibility with some Pulumi programs
Pulumi-Specific Extensions
# Stack references
resource "pulumi_stack_reference" "network" {
  name = "myorg/networking/prod"
}

output "vpc_id" {
  value = pulumi_stack_reference.network.outputs["vpc_id"]
}
# Method calls on resources
resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-unique-bucket-name"
}

call "my_bucket" "get_object" {
  key = "config.json"
}

The call block invokes a method on an existing resource. The first label is the resource's logical name (matching a declared resource) and the second is the method name. Results are referenced as call.<resource>.<method>.<output>.

Two built-in functions provide access to a resource's Pulumi identity at runtime:

  • pulumiResourceName(resource) — returns the logical name from the resource's URN
  • pulumiResourceType(resource) — returns the type token from the resource's URN

Development

Building
make build    # Outputs to bin/
Testing
make test
# or
go test ./...
Running Examples
cd examples/simple
pulumi up

License

Apache 2.0 - See LICENSE for details.

Note: This project uses github.com/hashicorp/hcl/v2 which is licensed under MPL 2.0.

Directories

Path Synopsis
cmd
pulumi-converter-hcl command
pulumi-converter-hcl converts between HCL and PCL (Pulumi Configuration Language).
pulumi-converter-hcl converts between HCL and PCL (Pulumi Configuration Language).
pulumi-language-hcl command
pulumi-language-hcl is the Pulumi language host for HCL (HashiCorp Configuration Language).
pulumi-language-hcl is the Pulumi language host for HCL (HashiCorp Configuration Language).
pulumi-resource-hcl command
pulumi-resource-hcl is the fully dynamic HCL resource provider.
pulumi-resource-hcl is the fully dynamic HCL resource provider.
pkg
converter
Package converter converts HCL programs to PCL (Pulumi Configuration Language).
Package converter converts HCL programs to PCL (Pulumi Configuration Language).
docs
Package docs provides language-specific helpers for rendering HCL in Pulumi schema-driven documentation.
Package docs provides language-specific helpers for rendering HCL in Pulumi schema-driven documentation.
grpcerr
Package grpcerr maps the errors pulumi-hcl produces onto gRPC status codes, so callers like `pulumi package get-schema` can classify a failure by its code instead of parsing message text.
Package grpcerr maps the errors pulumi-hcl produces onto gRPC status codes, so callers like `pulumi package get-schema` can classify a failure by its code instead of parsing message text.
hcl/ast
Package ast defines the Abstract Syntax Tree types for HCL configurations.
Package ast defines the Abstract Syntax Tree types for HCL configurations.
hcl/bridge
Package bridge resolves Terraform provider names to bridged tfbridge.ProviderInfo instances via the Pulumi convert.Mapper, so the engine can use original TF block/attribute layout when interpreting HCL.
Package bridge resolves Terraform provider names to bridged tfbridge.ProviderInfo instances via the Pulumi convert.Mapper, so the engine can use original TF block/attribute layout when interpreting HCL.
hcl/comments
Package comments associates source comments with the syntactic element that immediately follows them, so callers writing fresh hclwrite output can preserve comments from the original source.
Package comments associates source comments with the syntactic element that immediately follows them, so callers writing fresh hclwrite output can preserve comments from the original source.
hcl/eval
Package eval implements expression evaluation for HCL configurations.
Package eval implements expression evaluation for HCL configurations.
hcl/graph
Package graph implements dependency graph construction and topological sorting for HCL configuration execution ordering.
Package graph implements dependency graph construction and topological sorting for HCL configuration execution ordering.
hcl/modulepath
Package modulepath identifies a particular module instance in the nesting tree of an HCL configuration.
Package modulepath identifies a particular module instance in the nesting tree of an HCL configuration.
hcl/modules
Package modules loads and parses Terraform-compatible HCL module configurations.
Package modules loads and parses Terraform-compatible HCL module configurations.
hcl/packages
Package packages handles Pulumi package schema loading and type mapping.
Package packages handles Pulumi package schema loading and type mapping.
hcl/parser
Package parser implements HCL parsing for Terraform-compatible configurations.
Package parser implements HCL parsing for Terraform-compatible configurations.
hcl/resolve
Package resolve turns a module's provider requirements into concrete [workspace.PackageDescriptor]s using the engine's package-resolver service.
Package resolve turns a module's provider requirements into concrete [workspace.PackageDescriptor]s using the engine's package-resolver service.
hcl/run
Package run implements the HCL program execution engine.
Package run implements the HCL program execution engine.
hcl/schema
Package schema generates Pulumi package schemas from HCL module definitions.
Package schema generates Pulumi package schemas from HCL module definitions.
hcl/transform
Package transform handles conversion between cty values and Pulumi property values.
Package transform handles conversion between cty values and Pulumi property values.
provisioner/communicator/shared
Package shared replaces OpenTofu's internal/communicator/shared.
Package shared replaces OpenTofu's internal/communicator/shared.
provisioner/provisioners
Package provisioners shims the subset of OpenTofu's internal/provisioners the vendored communicator references.
Package provisioners shims the subset of OpenTofu's internal/provisioners the vendored communicator references.
provisioner/runtime
Package runtime executes TF-compatible provisioners in-process.
Package runtime executes TF-compatible provisioners in-process.
server
Package server implements the Pulumi language runtime gRPC server for HCL.
Package server implements the Pulumi language runtime gRPC server for HCL.
util
Package util provides utility types and functions.
Package util provides utility types and functions.
util/httpclient
Package httpclient is the in-tree replacement for opentofu's internal/httpclient package.
Package httpclient is the in-tree replacement for opentofu's internal/httpclient package.
util/tracing
Package tracing is the in-tree replacement for opentofu's internal/tracing package.
Package tracing is the in-tree replacement for opentofu's internal/tracing package.
util/tracing/traceattrs
Package traceattrs is the in-tree replacement for opentofu's internal/tracing/traceattrs package.
Package traceattrs is the in-tree replacement for opentofu's internal/tracing/traceattrs package.
version
Package version provides version information for the HCL language plugin.
Package version provides version information for the HCL language plugin.
sdk
go module
tests
testutil/pulexec
Package pulexec runs a Pulumi program through the real Pulumi engine and `pulumi-language-hcl` runtime, attaching bridged TF providers in-process.
Package pulexec runs a Pulumi program through the real Pulumi engine and `pulumi-language-hcl` runtime, attaching bridged TF providers in-process.
testutil/sshd
Package sshd starts a containerized OpenSSH server for tests that need a real SSH endpoint.
Package sshd starts a containerized OpenSSH server for tests that need a real SSH endpoint.
testutil/tfcompat
Package tfcompat is the Terraform-compatibility test harness.
Package tfcompat is the Terraform-compatibility test harness.
testutil/tfcompat/providers
Package providers holds reusable in-memory TF providers for tfcompat tests.
Package providers holds reusable in-memory TF providers for tfcompat tests.
testutil/tfexec
Package tfexec drives the Terraform/OpenTofu CLI against in-process TF providers (via reattach) so tests can exercise real Terraform behavior without installing remote provider binaries.
Package tfexec drives the Terraform/OpenTofu CLI against in-process TF providers (via reattach) so tests can exercise real Terraform behavior without installing remote provider binaries.
Package vendored holds third-party code copied verbatim from upstream projects and re-imported under our module path.
Package vendored holds third-party code copied verbatim from upstream projects and re-imported under our module path.
getmodules
Package getmodules contains the low-level functionality for fetching remote module packages.
Package getmodules contains the low-level functionality for fetching remote module packages.
ipaddr
Package ipaddr is a fork of a subset of the Go standard "net" package which retains parsing behaviors from Go 1.16 or earlier.
Package ipaddr is a fork of a subset of the Go standard "net" package which retains parsing behaviors from Go 1.16 or earlier.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL