AspireOpenNimbus.Hosting
0.1.0
dotnet add package AspireOpenNimbus.Hosting --version 0.1.0
NuGet\Install-Package AspireOpenNimbus.Hosting -Version 0.1.0
<PackageReference Include="AspireOpenNimbus.Hosting" Version="0.1.0" />
<PackageVersion Include="AspireOpenNimbus.Hosting" Version="0.1.0" />
<PackageReference Include="AspireOpenNimbus.Hosting" />
paket add AspireOpenNimbus.Hosting --version 0.1.0
#r "nuget: AspireOpenNimbus.Hosting, 0.1.0"
#:package AspireOpenNimbus.Hosting@0.1.0
#addin nuget:?package=AspireOpenNimbus.Hosting&version=0.1.0
#tool nuget:?package=AspireOpenNimbus.Hosting&version=0.1.0
AspireOpenNimbus
Deploy .NET Aspire apps to any cloud with OpenTofu — no cloud-specific code in your AppHost.
Most Aspire deployment tools require you to couple your application model to a specific cloud provider:
// Other tools — cloud provider baked into your C# code
builder.AddProject<Projects.Api>("api")
.PublishAsECSFargateServiceWithALB();
This ties your application model to a single cloud provider at the C# level. AspireOpenNimbus takes a different approach: use standard Aspire primitives (AddProject, AddRedis, AddPostgres, etc.) and drive the deployment target from the AspireOpenNimbus.Hosting package or a CLI flag — not from application code. Switch clouds without rewriting your model.
AppHost-First Deployment
Package:
AspireOpenNimbus.Hosting· requires .NET 10 and Aspire.Hosting 9.x or later
The hosting extension maps Aspire resources to OpenTofu HCL by convention. Each resource gets its own subdirectory and main.tf; a root main.tf composes them as Terraform modules. Cross-resource wiring (e.g. cache endpoint appearing as an ECS task environment variable) is resolved automatically from WithReference() chains.
Output directory convention
By default, generated infrastructure goes to infra/{SolutionName}.OpenTofu/ — for example infra/MyApp.OpenTofu/. The path is auto-resolved by walking up from the current directory to find the nearest .slnx or .sln file. If no solution file is found, the fallback is infra/.
Both the hosting package and the CLI share this convention. Override it explicitly when needed:
// Hosting — explicit override
var env = builder.AddOpenTofuEnvironment("aws", e =>
{
e.Region = "us-east-1";
e.OutputDirectory = "my-custom-path"; // Override auto-resolution
});
# CLI — explicit override
aspire-open-nimbus generate -d my-custom-path
Install
dotnet add package AspireOpenNimbus.Hosting --prerelease
Networking assumption
AspireOpenNimbus generates infrastructure for your application workloads — containers, databases, queues, and caches. It assumes a separate networking stack already exists that defines the VPC, public subnets, private subnets, and data subnets. The generated HCL references those resources via Terraform data sources — no VPC resources are created in the application stack.
Your networking stack should export, at minimum:
| Output name | Description |
|---|---|
vpc_id |
ID of the target VPC |
public_subnet_ids |
List — load balancers, NAT gateways |
private_subnet_ids |
List — ECS tasks, Lambda |
data_subnet_ids |
List — RDS, ElastiCache |
Comprehensive AWS example
The following AppHost/Program.cs deploys a full application stack to AWS:
- Web + API service on ECS Fargate
- Valkey (Redis-compatible) cache shared by both services
- SNS topic + SQS queue that both services can publish and subscribe to
- DynamoDB table for the API service
- Aurora Serverless v2 PostgreSQL at the smallest, cheapest configuration
using AspireOpenNimbus.Hosting;
using AspireOpenNimbus.HclBuilder.Blocks;
using AspireOpenNimbus.HclBuilder.Values;
var builder = DistributedApplication.CreateBuilder(args);
// ── Deployment environment ─────────────────────────────────────
// Generates infra/{SolutionName}.OpenTofu/{resource}/main.tf for
// each resource and a root main.tf that composes them as Terraform
// modules.
//
// OutputDirectory is auto-resolved from the solution file name.
// Assumes a separate networking stack already defines the VPC,
// public, private, and data subnets.
var env = builder.AddOpenTofuEnvironment("aws", e =>
{
e.Region = "us-east-1";
});
// ── Shared Valkey (Redis-compatible) cache ─────────────────────
// Maps to aws_elasticache_serverless — usable by both services.
// The cache endpoint is automatically threaded into any ECS task
// that has .WithReference(cache).
var cache = builder.AddValkey("cache")
.PublishAsOpenTofu(env);
// ── Messaging ──────────────────────────────────────────────────
// AddSnsTopic and AddSqsQueue are AWS-resource extensions provided
// by AspireOpenNimbus.Hosting. Both Web and Api can publish and
// subscribe. Connection details are injected as environment
// variables via WithReference().
var eventsTopic = builder.AddSnsTopic("events")
.PublishAsOpenTofu(env);
var eventsQueue = builder.AddSqsQueue("events-queue")
.PublishAsOpenTofu(env);
// ── DynamoDB table ─────────────────────────────────────────────
// On-demand (PAY_PER_REQUEST) — no capacity planning needed.
var ordersTable = builder.AddDynamoDbTable("orders")
.PublishAsOpenTofu(env);
// ── Aurora Serverless v2 PostgreSQL ────────────────────────────
// The smallest, cheapest Aurora config: 0.5–1 ACU.
var db = builder.AddPostgres("db")
.PublishAsOpenTofu(env, r =>
{
r["engine_mode"] = TofuLiteral.Of("provisioned");
r["engine_version"] = TofuLiteral.Of("16.6");
r["serverlessv2_scaling_configuration"] = new TofuNestedBlock("serverlessv2_scaling_configuration")
{
["min_capacity"] = TofuLiteral.Of(0.5),
["max_capacity"] = TofuLiteral.Of(1.0),
};
});
// ── Application services ───────────────────────────────────────
var apiService = builder.AddProject<Projects.ApiService>("apiservice")
.WithReference(cache)
.WithReference(eventsTopic)
.WithReference(eventsQueue)
.WithReference(ordersTable)
.WithReference(db)
.PublishAsOpenTofu(env);
builder.AddProject<Projects.Web>("webfrontend")
.WithReference(cache)
.WithReference(eventsTopic)
.WithReference(eventsQueue)
.WithReference(apiService)
.PublishAsOpenTofu(env);
builder.Build().Run();
Running dotnet run --project MyApp.AppHost -- --publisher manifest produces a tree like:
infra/MyApp.OpenTofu/
├── main.tf # Root module — cross-resource references resolved here
├── cache/
│ └── main.tf # aws_elasticache_serverless "cache"
├── events/
│ └── main.tf # aws_sns_topic "events"
├── events-queue/
│ └── main.tf # aws_sqs_queue "events-queue"
├── orders/
│ └── main.tf # aws_dynamodb_table "orders" (PAY_PER_REQUEST)
├── db/
│ └── main.tf # aws_rds_cluster "db" (Aurora Serverless v2)
├── apiservice/
│ └── main.tf # aws_ecs_task_definition + aws_ecs_service
└── webfrontend/
└── main.tf # aws_ecs_task_definition + aws_ecs_service
Cross-resource references — for example the Valkey endpoint needed by both ECS tasks — are emitted as module outputs in the producing module and wired as module input variables from the root. Each module is independently versionable, and the dependency graph is explicit in Terraform's module call graph.
Per-resource customisation
Every PublishAsOpenTofu call accepts an optional Action<TofuResource> callback that receives the primary Terraform resource for that service. Use it to override any convention default without leaving the AppHost:
builder.AddProject<Projects.ApiService>("apiservice")
.PublishAsOpenTofu(env, r =>
{
// Give the API more memory than the web frontend
r["memory"] = TofuLiteral.Of(1024);
r["cpu"] = TofuLiteral.Of(512);
});
How It Works
AspireOpenNimbus has two complementary infrastructure paths:
╔══ AppHost-first (Hosting package) ══════════════════════════════╗
║ aspire publish ║
║ │ ║
║ ▼ ║
║ OpenTofuInfrastructure (BeforeStartEvent) ║
║ │ ← reads PublishAsOpenTofu annotations on each resource ║
║ │ ← calls IResourceMapper.MapAsync per resource ║
║ ▼ ║
║ Per-resource main.tf + root main.tf with module composition ║
║ → infra/{SolutionName}.OpenTofu/ ║
╚══════════════════════════════════════════════════════════════════╝
╔══ CLI (manifest-driven) ════════════════════════════════════════╗
║ dotnet publish --publisher manifest ║
║ │ ║
║ ▼ ║
║ aspire-open-nimbus generate --manifest manifest.json ║
║ │ ← reads Aspire manifest JSON ║
║ │ ← resolves {..} placeholders → OpenTofu references ║
║ │ ← generates shared-resources.tf (VPC data sources, etc.) ║
║ │ ← applies embedded .tf templates per resource type ║
║ ▼ ║
║ infra/{SolutionName}.OpenTofu/ directory ║
╚══════════════════════════════════════════════════════════════════╝
tofu init is managed automatically in both paths: the CLI detects whether the workspace is initialised, runs tofu init if not, and re-runs it after generation to pull any new provider requirements.
Features
- Convention-over-configuration — resources mapped to cloud infrastructure automatically by Aspire type (
project.v0→ ECS Fargate,redis.v0→ ElastiCache Serverless, etc.); override only what you need via an optional callback. - Module-per-resource output — each Aspire resource gets its own
{resource}/main.tf; a rootmain.tfcomposes them as Terraform modules with explicit cross-resource wiring. - Smart output directory — auto-resolves to
infra/{SolutionName}.OpenTofu/by finding the nearest.slnxor.slnfile. Falls back toinfra/if no solution file exists. Overridable via-d(CLI) orOutputDirectory(hosting). - Manifest placeholder resolution — Aspire manifest
{resource.bindings.http.port}placeholders are automatically converted to OpenTofu expressions (module.resource.output_name), so cross-resource wiring works at apply time. - Module-first AI strategy — the AI fallback mapper searches OpenTofu module registries (company-private first, then public) before falling back to individual provider resources. Modules are battle-tested and opinionated — they produce better infrastructure.
- Configurable registry priority —
RegistryConfiglets you place your company's private module registry at the top of the search chain so internal modules always take precedence. - Cloud-agnostic HCL builder — a fully typed C# API for generating HCL:
TofuResource,TofuModule,TofuVariable,TofuOutput,TofuLiteral<T>,TofuReference,TofuFunctionCall,TofuList,TofuMap, and more. No string templates — all HCL is built from objects. - AI-powered fallback — unknown Aspire resource types are dynamically mapped by searching the OpenTofu registry, reading argument documentation, and generating a minimal HCL block via an
IChatClientwith function-calling. - Automatic OpenTofu management —
tofuis auto-detected and installed if missing.tofu initruns automatically before and after generation. - AWS-first, cloud-extensible — full AWS ECS Fargate, Lambda, Aurora, ElastiCache, DynamoDB, SQS, SNS, S3, and EventBridge support today. Azure and GCP are planned.
Supported Cloud Providers
| Target environment | Cloud | Status |
|---|---|---|
aws-dev / aws |
AWS ECS Fargate | ✅ Implemented |
azure |
Azure Container Apps | 🔜 Planned |
gcp / google |
Google Cloud Run | 🔜 Planned |
onprem / on-prem |
On-premises (Kubernetes) | 🔜 Planned |
Prerequisites
| Requirement | Why | Required? |
|---|---|---|
| .NET 10 SDK | Build and run | ✅ Required |
| .NET Aspire 9.x | AppHost model | ✅ Required |
| AWS CLI | Authenticate with AWS for apply/destroy |
✅ Required for deployment |
| OpenTofu | IaC engine | Optional — auto-installed if missing |
Installation
From source
git clone https://github.com/barryadwyer/AspireOpenNimbus.git
cd AspireOpenNimbus
dotnet build
dotnet pack
# Install as a global tool
dotnet tool install --global --add-source ./nupkg AspireOpenNimbus
From GitHub Packages
dotnet nuget add source "https://nuget.pkg.github.com/barryadwyer/index.json" \
--name github --username YOUR_GITHUB_USERNAME --password YOUR_GITHUB_PAT
dotnet tool install --global AspireOpenNimbus --source github
From NuGet (when published)
dotnet tool install --global AspireOpenNimbus
Getting Started
AppHost-first (recommended for new projects)
Requires the
AspireOpenNimbus.Hostingproject reference in your AppHost.
1. Add the hosting package
dotnet add package AspireOpenNimbus.Hosting --prerelease
Or add a direct project reference:
<ProjectReference Include="path/to/AspireOpenNimbus.Hosting.csproj" />
2. Wire up OpenTofu in your AppHost
using AspireOpenNimbus.Hosting;
var builder = DistributedApplication.CreateBuilder(args);
var env = builder.AddOpenTofuEnvironment("aws", e =>
{
e.Region = "us-east-1";
// OutputDirectory auto-resolves to infra/{SolutionName}.OpenTofu/
});
var apiService = builder.AddProject<Projects.ApiService>("apiservice")
.PublishAsOpenTofu(env);
builder.AddProject<Projects.Web>("webfrontend")
.WithReference(apiService)
.PublishAsOpenTofu(env);
builder.Build().Run();
3. Publish to generate .tf files
dotnet run --project MyApp.AppHost -- --publisher manifest
This triggers the PublishAsOpenTofu callbacks and writes per-resource .tf modules to infra/{SolutionName}.OpenTofu/.
4. Deploy with OpenTofu
cd infra/MyApp.OpenTofu # path auto-resolved from solution name
tofu init
tofu plan # review the execution plan
tofu apply # deploy
5. Tear down
cd infra/MyApp.OpenTofu
tofu destroy
AI Fallback Mapper (dynamic resource discovery)
Package:
AspireOpenNimbus.Hosting.AI· requiresMicrosoft.Extensions.AI
When the static AwsResourceMapper doesn't recognise an Aspire resource type, the AI fallback mapper takes over. It uses a module-first strategy with an IChatClient and function-calling to:
- Search module registries — starting with your company-private registry (if configured), then library.tf, search.opentofu.org, and registry.terraform.io.
- Fetch the best-fit module's documentation and input/output variable reference.
- Generate a
moduleblock with sensible defaults mapped from the Aspire resource's properties. - Fall back to individual provider resources only when no suitable module exists.
This means you can use any Aspire resource type — even custom ones — and the mapper will figure out the right infrastructure on the fly.
Setup
Add the AI project reference:
<ProjectReference Include="path/to/AspireOpenNimbus.Hosting.AI.csproj" />
Wire it up in your AppHost (register after the static mappers):
using AspireOpenNimbus.Hosting;
using AspireOpenNimbus.Hosting.AI;
using Microsoft.Extensions.AI;
using OpenAI;
var builder = DistributedApplication.CreateBuilder(args);
// Configure your IChatClient (OpenAI, Azure OpenAI, Ollama, etc.)
IChatClient chatClient = new ChatClientBuilder(
new OpenAIClient(Environment.GetEnvironmentVariable("OPENAI_API_KEY")!)
.GetChatClient("gpt-4o").AsIChatClient())
.UseFunctionInvocation()
.Build();
var env = builder.AddOpenTofuEnvironment("aws", e =>
{
e.Region = "us-east-1";
})
.WithAiFallbackMapper(chatClient); // <-- catch-all for unknown resources
// These use the static AwsResourceMapper (fast, deterministic):
var cache = builder.AddRedis("cache").PublishAsOpenTofu(env);
// This has no static mapper — the AI fallback will search registries,
// find the best module, read the docs, and generate the HCL:
var kafka = builder.AddKafka("events").PublishAsOpenTofu(env);
builder.Build().Run();
Configurable registry priority
Control which module registries are searched and in what order via RegistryConfig:
// Company-private registry searched first
var env = builder.AddOpenTofuEnvironment("aws", e => e.Region = "us-east-1")
.WithAiFallbackMapper(chatClient, registries =>
{
registries.AddFirst(new ModuleRegistryEntry(
"acme-corp",
"https://tofu-registry.acme.com",
RegistryKind.TerraformRegistry,
ApiToken: Environment.GetEnvironmentVariable("ACME_TOFU_TOKEN")));
});
Default search order (when no company registry is configured):
- library.tf — community-curated modules
- search.opentofu.org — official OpenTofu search
- registry.terraform.io — Terraform registry fallback
The AI mapper runs only at publish time and only for resource types not already handled by a static mapper. Normal dotnet run (non-publish) is unaffected.
CLI-driven (for existing apps or CI pipelines)
The standalone CLI reads an Aspire manifest JSON and generates infrastructure to infra/{SolutionName}.OpenTofu/.
1. Create an Aspire starter application
dotnet new aspire-starter --name MyApp
cd MyApp
2. Publish the Aspire manifest
dotnet publish MyApp.AppHost \
--publisher manifest \
--output-path ./publish \
-p:PublishProfile=DefaultContainer
This produces publish/manifest.json describing every project and resource in your application.
3. Generate OpenTofu infrastructure
aspire-open-nimbus generate --manifest ./publish/manifest.json
AspireOpenNimbus will:
- Detect or install
tofuautomatically. - Initialise the OpenTofu workspace (
.terraform/) if not already done. - Resolve manifest
{..}placeholders to OpenTofu references. - Generate
shared-resources.tfwith VPC data sources and common infrastructure. - Write per-resource
.tffiles toinfra/{SolutionName}.OpenTofu/. - Re-run
tofu initto pull provider plugins for any newly generatedversions.tf.
Generated files:
| File | Contents |
|---|---|
versions.tf |
Provider version constraints (AWS provider) |
variables.tf |
Input variables for each Aspire project |
shared-resources.tf |
VPC data sources, common security groups |
networking.tf |
Subnets, internet gateway, route tables |
ecs.tf |
ECS cluster, task definitions, and Fargate services |
outputs.tf |
Load balancer DNS names, service ARNs |
main.tf |
Root module wiring |
All files are generated inside
infra/{SolutionName}.OpenTofu/.
4. Validate and plan
aspire-open-nimbus build
Runs tofu validate then tofu plan against the generated configuration and prints the execution plan.
5. Apply
aspire-open-nimbus apply --target-environment aws-dev
Materialises embedded per-service templates (ECS Fargate, Lambda, Redis, Postgres, etc.) into the infrastructure directory, then runs tofu apply with a confirmation prompt.
Add --auto-approve to skip the prompt in CI pipelines:
aspire-open-nimbus apply --target-environment aws-dev --auto-approve
6. Tear down
aspire-open-nimbus destroy
Runs tofu destroy with a confirmation prompt. Add --auto-approve to skip.
Commands Reference
All commands auto-discover the infrastructure directory from the solution file name. Use
-dto override.
init
Initialises the OpenTofu workspace and creates a default main.tf scaffold.
Note: You rarely need to call this directly —
generatehandles initialisation automatically.
aspire-open-nimbus init [options]
Options:
-d, --infra-dir <path> Infrastructure directory (default: infra/{SolutionName}.OpenTofu)
generate
Reads the Aspire manifest and generates all .tf files. Resolves manifest placeholders to OpenTofu references. Runs tofu init before and after generation.
aspire-open-nimbus generate [options]
Options:
-m, --manifest <path> Path to the Aspire manifest.json (default: manifest.json)
-d, --infra-dir <path> Infrastructure directory (default: infra/{SolutionName}.OpenTofu)
apply
Materialises embedded resource templates and runs tofu apply.
aspire-open-nimbus apply [options]
Options:
-d, --infra-dir <path> Infrastructure directory (default: infra/{SolutionName}.OpenTofu)
-e, --target-environment <env> Deployment target (default: aws-dev)
-y, --auto-approve Skip the confirmation prompt
build
Validates and plans the current infrastructure without making changes.
aspire-open-nimbus build [options]
Options:
-d, --infra-dir <path> Infrastructure directory (default: infra/{SolutionName}.OpenTofu)
destroy
Tears down all provisioned infrastructure.
aspire-open-nimbus destroy [options]
Options:
-d, --infra-dir <path> Infrastructure directory (default: infra/{SolutionName}.OpenTofu)
-y, --auto-approve Skip the confirmation prompt
CI / CD
A GitHub Actions workflow (.github/workflows/publish.yml) publishes the CLI tool as a NuGet package to GitHub Packages on every tag push matching v*:
on:
push:
tags: ['v*']
The workflow runs dotnet pack on the CLI project and pushes the .nupkg to https://nuget.pkg.github.com/barryadwyer/index.json.
Project Structure
AspireOpenNimbus/ ← CLI tool (dotnet global tool)
├── Program.cs # Host builder + command registration
├── Commands/
│ ├── InitCommand.cs # init — workspace init + main.tf scaffold
│ ├── GenerateCommand.cs # generate — manifest → .tf files (placeholder resolution)
│ ├── ApplyCommand.cs # apply — template materialisation + tofu apply
│ ├── BuildCommand.cs # build — tofu validate + plan
│ └── DestroyCommand.cs # destroy — tofu destroy
├── Infrastructure/
│ ├── SolutionPathResolver.cs # Discovers .slnx/.sln → infra/{Name}.OpenTofu
│ ├── Tofu/
│ │ └── TofuProcess.cs # Shared tofu lifecycle: detect, install, init, run
│ ├── Environments/
│ │ ├── TargetEnvironmentConfiguration.cs
│ │ └── TargetEnvironmentConfigurationProvider.cs
│ ├── Manifest/
│ │ ├── ManifestResource.cs # Aspire manifest data model
│ │ ├── ManifestParser.cs # JSON → ManifestResource[]
│ │ ├── ManifestPlaceholderResolver.cs # {..} placeholders → tofu refs
│ │ ├── InfraResourceMapping.cs # Resource-to-infra mapping record
│ │ └── SharedResourceGenerator.cs # VPC data sources, shared infra
│ └── Templates/
│ ├── TemplateKind.cs # Enum of all 12 AWS template types
│ ├── EmbeddedTemplateRepository.cs
│ └── TofuTemplateMaterializer.cs
└── Templates/AWS/ # 12 embedded .tf templates
├── ecs-fargate-app.tf
├── lambda-container-app.tf
├── aurora-postgres-serverless.tf
├── rds-sqlserver.tf
├── redis-cluster.tf
├── valkey-cluster.tf
├── elasticache-cluster.tf
├── dynamodb-table.tf
├── sqs-queue.tf
├── sns-topic.tf
├── s3-bucket.tf
└── eventbridge-bus.tf
src/
├── AspireOpenNimbus.HclBuilder/ ← Cloud-agnostic typed HCL builder
│ ├── TofuStack.cs # Ordered block container → ToHcl()
│ ├── Blocks/
│ │ ├── ITofuBlock.cs # Block contract
│ │ ├── TofuResource.cs # resource "type" "name" { }
│ │ ├── TofuModule.cs # module "name" { source = "..." }
│ │ ├── TofuVariable.cs # variable "name" { }
│ │ ├── TofuOutput.cs # output "name" { value = ... }
│ │ ├── TofuProvider.cs # provider "name" { }
│ │ ├── TofuDataSource.cs # data "type" "name" { }
│ │ ├── TofuLocals.cs # locals { }
│ │ └── TofuNestedBlock.cs # Inline nested blocks
│ ├── Values/
│ │ ├── ITofuValue.cs # Value contract
│ │ ├── TofuLiteral.cs # TofuLiteral<T> — typed literal values
│ │ ├── TofuReference.cs # References (var.x, module.y.output)
│ │ ├── TofuRawExpression.cs # Raw HCL expressions
│ │ ├── TofuFunctionCall.cs # Function calls: fn(arg1, arg2)
│ │ ├── TofuList.cs # [ val1, val2, ... ]
│ │ └── TofuMap.cs # { key1 = val1, key2 = val2 }
│ └── Rendering/
│ ├── IHclWriter.cs # Writer contract
│ └── HclWriter.cs # Indentation, block formatting
│
├── AspireOpenNimbus.Hosting/ ← Aspire AppHost extension package
│ ├── OpenTofuExtensions.cs # AddOpenTofuEnvironment, PublishAsOpenTofu,
│ │ # AddSnsTopic, AddSqsQueue, AddDynamoDbTable
│ ├── OpenTofuEnvironmentResource.cs
│ ├── SolutionPathResolver.cs # Discovers .slnx/.sln → infra/{Name}.OpenTofu
│ ├── TofuOutputDescriptor.cs # Output descriptor record
│ ├── Resources/
│ │ ├── SnsTopicResource.cs
│ │ ├── SqsQueueResource.cs
│ │ └── DynamoDbTableResource.cs
│ └── Mapping/
│ ├── IResourceMapper.cs # Contract: CanMap, MapAsync
│ └── Aws/
│ └── AwsResourceMapper.cs # project.v0 → ECS, redis.v0 → ElastiCache,
│ # postgres.v0 → Aurora, sqlserver.v0 → RDS,
│ # rabbitmq.v0 → MQ, container.v0 → ECS
│ # + SNS, SQS, DynamoDB extensions
│
└── AspireOpenNimbus.Hosting.AI/ ← AI-powered fallback mapper
├── AiFallbackResourceMapper.cs # IChatClient + function-calling, module-first
├── AiFallbackExtensions.cs # .WithAiFallbackMapper(chatClient, ...)
├── HclBlockParser.cs # Parses LLM-generated HCL → ITofuBlock
├── Configuration/
│ ├── RegistryConfig.cs # Priority-ordered registry chain
│ └── ModuleSearchResult.cs # Search result record
└── Tools/
├── ModuleRegistryTools.cs # Multi-registry module search + doc fetch
└── TofuRegistryTools.cs # Provider resource search + doc fetch
tests/
├── Unit/
│ ├── HclBuilder/
│ │ ├── TofuModuleTests.cs
│ │ └── TofuStackTests.cs
│ └── Hosting/
│ ├── AwsResourceMapperTests.cs
│ └── TofuOutputDescriptorTests.cs
├── Integration/
│ └── GenerateCommandTests.cs
└── Golden/ # Golden file / snapshot tests
Troubleshooting
tofu command not found
AspireOpenNimbus will attempt to install OpenTofu automatically (via winget on Windows or a shell installer on macOS/Linux). If automatic installation fails, install it manually from opentofu.org/docs/intro/install and ensure tofu is on your PATH.
AWS authentication errors during apply
Ensure the AWS CLI is configured: aws configure or set AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION environment variables. The IAM principal needs permissions to create ECS, VPC, IAM roles, and any other resources defined in your configuration.
manifest.json not found
Run dotnet publish on your AppHost project with --publisher manifest before running generate. The default output path is the current directory; use -m to specify a different path.
tofu init fails with provider errors
Ensure you have network access to the OpenTofu registry (registry.opentofu.org). If you are behind a proxy, set HTTPS_PROXY before running the CLI.
Output directory resolves to infra/ instead of infra/{SolutionName}.OpenTofu/
The SolutionPathResolver walks up from the current directory looking for a .slnx or .sln file. Make sure you're running from within the repository tree. If you're running from outside, use -d to specify the path explicitly.
Contributing
Contributions are welcome. Please open an issue before submitting a pull request for significant changes.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Aspire.Hosting (>= 9.2.1)
- AspireOpenNimbus.HclBuilder (>= 0.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 195 | 4/23/2026 |