Infrastructure as C# - Define Terraform configurations using strongly-typed C# with compile-time safety and IntelliSense support.
The EmmittJ Terraform SDK is a .NET library that enables infrastructure-as-code using C# instead of HCL. It provides a strongly-typed API for building Terraform configurations programmatically with compile-time safety, IntelliSense support, and the full power of .NET.
Key Features:
Add the core SDK package to your project:
dotnet add package EmmittJ.Terraform.Sdk
Add provider packages as needed:
dotnet add package EmmittJ.Terraform.Sdk.Providers.Aws
dotnet add package EmmittJ.Terraform.Sdk.Providers.AzureRM
dotnet add package EmmittJ.Terraform.Sdk.Providers.Google
using EmmittJ.Terraform.Sdk;
using EmmittJ.Terraform.Sdk.Configuration;
using EmmittJ.Terraform.Sdk.Blocks;
var stack = new TerraformStack { Name = "my-infrastructure" };
// Configure Terraform settings
stack.Terraform = new TerraformSettings
{
RequiredVersion = ">= 1.9.0"
};
// Add AWS provider
var awsProvider = new TerraformProvider("aws")
{
["region"] = "us-west-2"
};
stack.Add(awsProvider);
// Create a VPC
var vpc = new TerraformResource("aws_vpc", "main")
{
["cidr_block"] = "10.0.0.0/16",
["enable_dns_hostnames"] = true,
["enable_dns_support"] = true
};
stack.Add(vpc);
// Create a subnet referencing the VPC
var subnet = new TerraformResource("aws_subnet", "public")
{
["vpc_id"] = vpc["id"],
["cidr_block"] = "10.0.1.0/24",
["availability_zone"] = "us-west-2a"
};
stack.Add(subnet);
// Generate HCL
string hcl = stack.ToHcl();
Console.WriteLine(hcl);
terraform {
required_version = ">= 1.9.0"
}
provider "aws" {
region = "us-west-2"
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-2a"
}
The SDK uses a three-layer architecture to transform C# into HCL:
The polymorphic value system provides compile-time type safety and eliminates null reference exceptions:
// Type-safe values that can be literals, references, or expressions
TerraformValue<string> region = "us-west-2"; // Literal
TerraformValue<string> vpcId = vpc["id"]; // Reference
TerraformValue<int> count = 3; // Literal
TerraformValue<string> name = Tf.Join("-", ["app"]); // Expression
Features:
TerraformList<T>, TerraformMap<T>, TerraformSet<T>Immutable expression nodes that compose into larger structures:
// Build complex expressions compositionally
var name = TerraformExpression.Conditional(
isProd,
TerraformExpression.Interpolate("prod-", baseName),
TerraformExpression.Interpolate("dev-", baseName)
);
// Use Terraform functions
var tags = TerraformExpression.ForList(users, user => user["email"]);
var combined = Tf.Join(",", ["a", "b", "c"]);
Features:
+, -, *, /, [])Direct representation of HCL output elements:
// Syntax nodes render to HCL
new TerraformArgumentNode("region", TerraformExpression.Literal("us-west-2"))
// β region = "us-west-2"
new TerraformBlockNode("tags", children)
// β tags { ... }
Features:
The SDK uses a single-pass resolution approach to generate HCL:
// References generate correct HCL identifiers
subnet["vpc_id"] = vpc["id"]; // Resolves to: aws_vpc.main.id
// Resolution generates valid HCL
stack.ToHcl(); // VPC and Subnet both rendered with correct references
For detailed architecture documentation, see:
docs/architecture-overview.md - Complete system architecturedocs/values-system.md - Polymorphic value systemdocs/expressions-system.md - Expression compositiondocs/syntax-system.md - HCL renderingThe Tf class provides access to Terraform built-in functions:
// String manipulation
var joined = Tf.Join(",", ["a", "b", "c"]);
var encoded = Tf.Base64Encode("hello");
// Type constraints
var stringType = Tf.Types.String;
var listOfStrings = Tf.Types.List(Tf.Types.String);
var mapOfNumbers = Tf.Types.Map(Tf.Types.Number);
// Define input variables
var region = new TerraformVariable("region")
{
Type = Tf.Types.String,
Default = "us-west-2",
Description = "AWS region for resources"
};
stack.Add(region);
// Define outputs
var vpcIdOutput = new TerraformOutput("vpc_id")
{
Value = vpc["id"],
Description = "The ID of the VPC"
};
stack.Add(vpcIdOutput);
Create dynamic nested blocks using the .AsDynamic() API:
var settings = new TerraformVariable("settings");
var settingsRef = TerraformValue.FromExpression<object>(settings.AsReference());
// Create dynamic block
var dynamicBlock = new TerraformDynamicBlock("setting", settingsRef);
dynamicBlock.Content.SetArgument("key", TerraformExpression.Identifier("setting.value.key"));
dynamicBlock.Content.SetArgument("value", TerraformExpression.Identifier("setting.value.value"));
resource.SetDynamicBlock("setting", dynamicBlock);
All standard Terraform meta-arguments are supported:
var servers = new TerraformResource("aws_instance", "server")
{
["ami"] = "ami-12345678",
["instance_type"] = "t2.micro",
Count = 3, // Create 3 instances
DependsOn = [vpc, subnet]
};
Deploy Terraform infrastructure as part of your Aspire applications:
var builder = DistributedApplication.CreateBuilder(args);
// Add Terraform environment
var terraform = builder.AddTerraformEnvironment("terraform")
.WithBackend("s3", backend =>
{
backend["bucket"] = "my-terraform-state";
backend["region"] = "us-west-2";
backend["key"] = "terraform.tfstate";
});
// Publish project with Terraform infrastructure
var api = builder.AddProject<Projects.ApiService>("api")
.PublishAsTerraform(terraform =>
{
// Customize infrastructure for API deployment
// terraform.Stack - the TerraformStack to add resources to
// terraform.TargetResource - the IResource being published
var container = new TerraformResource("azurerm_container_app", "api")
{
["name"] = terraform.TargetResource?.Name ?? "api",
["container_app_environment_id"] = environment.AsReference()
};
terraform.Add(container);
});
Run aspire publish to generate and deploy Terraform infrastructure.
# Clone the repository
git clone https://github.com/EmmittJ/terraform-sdk.git
cd terraform-sdk
# Restore packages
dotnet restore
# Build the solution
dotnet build
# Run tests
dotnet test
Provider bindings are auto-generated from Terraform provider schemas:
# Generate provider code for all configured providers
aspire publish
This generates:
AwsProvider, AzureRMProvider, etc.)Generated code locations:
src/providers/EmmittJ.Terraform.Sdk.Providers.Aws/ - AWS resourcessrc/providers/EmmittJ.Terraform.Sdk.Providers.AzureRM/ - Azure resourcessrc/providers/EmmittJ.Terraform.Sdk.Providers.Google/ - GCP resourcesImportant: Do not manually edit generated provider code - it will be overwritten on next generation.
The project uses xUnit and Verify for testing:
# Run all tests
dotnet test
# Run tests for a specific project
dotnet test tests/EmmittJ.Terraform.Sdk.Tests/
# Run a specific test
dotnet test --filter "FullyQualifiedName~TerraformResourceTests.CanCreateBasicResource"
# Accept snapshot changes (after reviewing)
dotnet verify accept -y
Contributions are welcome! Please read the development guidelines in .github/copilot-instructions.md for:
.editorconfig rules strictlyThis project is licensed under the MIT License - see the LICENSE file for details.
Inspired by Terraform CDK and AWS CDK.
Status: Pre-1.0.0 preview - APIs may change. Breaking changes are minimized but possible.
Version: 0.1.0-preview
Made with β€οΈ for .NET and Infrastructure-as-Code
C#
100.0%
Infrastructure as C# - Define Terraform configurations using strongly-typed C# with compile-time safety and IntelliSense support.
The EmmittJ Terraform SDK is a .NET library that enables infrastructure-as-code using C# instead of HCL. It provides a strongly-typed API for building Terraform configurations programmatically with compile-time safety, IntelliSense support, and the full power of .NET.
Key Features:
Add the core SDK package to your project:
dotnet add package EmmittJ.Terraform.Sdk
Add provider packages as needed:
dotnet add package EmmittJ.Terraform.Sdk.Providers.Aws
dotnet add package EmmittJ.Terraform.Sdk.Providers.AzureRM
dotnet add package EmmittJ.Terraform.Sdk.Providers.Google
using EmmittJ.Terraform.Sdk;
using EmmittJ.Terraform.Sdk.Configuration;
using EmmittJ.Terraform.Sdk.Blocks;
var stack = new TerraformStack { Name = "my-infrastructure" };
// Configure Terraform settings
stack.Terraform = new TerraformSettings
{
RequiredVersion = ">= 1.9.0"
};
// Add AWS provider
var awsProvider = new TerraformProvider("aws")
{
["region"] = "us-west-2"
};
stack.Add(awsProvider);
// Create a VPC
var vpc = new TerraformResource("aws_vpc", "main")
{
["cidr_block"] = "10.0.0.0/16",
["enable_dns_hostnames"] = true,
["enable_dns_support"] = true
};
stack.Add(vpc);
// Create a subnet referencing the VPC
var subnet = new TerraformResource("aws_subnet", "public")
{
["vpc_id"] = vpc["id"],
["cidr_block"] = "10.0.1.0/24",
["availability_zone"] = "us-west-2a"
};
stack.Add(subnet);
// Generate HCL
string hcl = stack.ToHcl();
Console.WriteLine(hcl);
terraform {
required_version = ">= 1.9.0"
}
provider "aws" {
region = "us-west-2"
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-2a"
}
The SDK uses a three-layer architecture to transform C# into HCL:
The polymorphic value system provides compile-time type safety and eliminates null reference exceptions:
// Type-safe values that can be literals, references, or expressions
TerraformValue<string> region = "us-west-2"; // Literal
TerraformValue<string> vpcId = vpc["id"]; // Reference
TerraformValue<int> count = 3; // Literal
TerraformValue<string> name = Tf.Join("-", ["app"]); // Expression
Features:
TerraformList<T>, TerraformMap<T>, TerraformSet<T>Immutable expression nodes that compose into larger structures:
// Build complex expressions compositionally
var name = TerraformExpression.Conditional(
isProd,
TerraformExpression.Interpolate("prod-", baseName),
TerraformExpression.Interpolate("dev-", baseName)
);
// Use Terraform functions
var tags = TerraformExpression.ForList(users, user => user["email"]);
var combined = Tf.Join(",", ["a", "b", "c"]);
Features:
+, -, *, /, [])Direct representation of HCL output elements:
// Syntax nodes render to HCL
new TerraformArgumentNode("region", TerraformExpression.Literal("us-west-2"))
// β region = "us-west-2"
new TerraformBlockNode("tags", children)
// β tags { ... }
Features:
The SDK uses a single-pass resolution approach to generate HCL:
// References generate correct HCL identifiers
subnet["vpc_id"] = vpc["id"]; // Resolves to: aws_vpc.main.id
// Resolution generates valid HCL
stack.ToHcl(); // VPC and Subnet both rendered with correct references
For detailed architecture documentation, see:
docs/architecture-overview.md - Complete system architecturedocs/values-system.md - Polymorphic value systemdocs/expressions-system.md - Expression compositiondocs/syntax-system.md - HCL renderingThe Tf class provides access to Terraform built-in functions:
// String manipulation
var joined = Tf.Join(",", ["a", "b", "c"]);
var encoded = Tf.Base64Encode("hello");
// Type constraints
var stringType = Tf.Types.String;
var listOfStrings = Tf.Types.List(Tf.Types.String);
var mapOfNumbers = Tf.Types.Map(Tf.Types.Number);
// Define input variables
var region = new TerraformVariable("region")
{
Type = Tf.Types.String,
Default = "us-west-2",
Description = "AWS region for resources"
};
stack.Add(region);
// Define outputs
var vpcIdOutput = new TerraformOutput("vpc_id")
{
Value = vpc["id"],
Description = "The ID of the VPC"
};
stack.Add(vpcIdOutput);
Create dynamic nested blocks using the .AsDynamic() API:
var settings = new TerraformVariable("settings");
var settingsRef = TerraformValue.FromExpression<object>(settings.AsReference());
// Create dynamic block
var dynamicBlock = new TerraformDynamicBlock("setting", settingsRef);
dynamicBlock.Content.SetArgument("key", TerraformExpression.Identifier("setting.value.key"));
dynamicBlock.Content.SetArgument("value", TerraformExpression.Identifier("setting.value.value"));
resource.SetDynamicBlock("setting", dynamicBlock);
All standard Terraform meta-arguments are supported:
var servers = new TerraformResource("aws_instance", "server")
{
["ami"] = "ami-12345678",
["instance_type"] = "t2.micro",
Count = 3, // Create 3 instances
DependsOn = [vpc, subnet]
};
Deploy Terraform infrastructure as part of your Aspire applications:
var builder = DistributedApplication.CreateBuilder(args);
// Add Terraform environment
var terraform = builder.AddTerraformEnvironment("terraform")
.WithBackend("s3", backend =>
{
backend["bucket"] = "my-terraform-state";
backend["region"] = "us-west-2";
backend["key"] = "terraform.tfstate";
});
// Publish project with Terraform infrastructure
var api = builder.AddProject<Projects.ApiService>("api")
.PublishAsTerraform(terraform =>
{
// Customize infrastructure for API deployment
// terraform.Stack - the TerraformStack to add resources to
// terraform.TargetResource - the IResource being published
var container = new TerraformResource("azurerm_container_app", "api")
{
["name"] = terraform.TargetResource?.Name ?? "api",
["container_app_environment_id"] = environment.AsReference()
};
terraform.Add(container);
});
Run aspire publish to generate and deploy Terraform infrastructure.
# Clone the repository
git clone https://github.com/EmmittJ/terraform-sdk.git
cd terraform-sdk
# Restore packages
dotnet restore
# Build the solution
dotnet build
# Run tests
dotnet test
Provider bindings are auto-generated from Terraform provider schemas:
# Generate provider code for all configured providers
aspire publish
This generates:
AwsProvider, AzureRMProvider, etc.)Generated code locations:
src/providers/EmmittJ.Terraform.Sdk.Providers.Aws/ - AWS resourcessrc/providers/EmmittJ.Terraform.Sdk.Providers.AzureRM/ - Azure resourcessrc/providers/EmmittJ.Terraform.Sdk.Providers.Google/ - GCP resourcesImportant: Do not manually edit generated provider code - it will be overwritten on next generation.
The project uses xUnit and Verify for testing:
# Run all tests
dotnet test
# Run tests for a specific project
dotnet test tests/EmmittJ.Terraform.Sdk.Tests/
# Run a specific test
dotnet test --filter "FullyQualifiedName~TerraformResourceTests.CanCreateBasicResource"
# Accept snapshot changes (after reviewing)
dotnet verify accept -y
Contributions are welcome! Please read the development guidelines in .github/copilot-instructions.md for:
.editorconfig rules strictlyThis project is licensed under the MIT License - see the LICENSE file for details.
Inspired by Terraform CDK and AWS CDK.
Status: Pre-1.0.0 preview - APIs may change. Breaking changes are minimized but possible.
Version: 0.1.0-preview
Made with β€οΈ for .NET and Infrastructure-as-Code
C#
100.0%