AspireOpenNimbus.Hosting 0.1.0

dotnet add package AspireOpenNimbus.Hosting --version 0.1.0
                    
NuGet\Install-Package AspireOpenNimbus.Hosting -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AspireOpenNimbus.Hosting" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AspireOpenNimbus.Hosting" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="AspireOpenNimbus.Hosting" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AspireOpenNimbus.Hosting --version 0.1.0
                    
#r "nuget: AspireOpenNimbus.Hosting, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package AspireOpenNimbus.Hosting@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=AspireOpenNimbus.Hosting&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=AspireOpenNimbus.Hosting&version=0.1.0
                    
Install as a Cake Tool

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 root main.tf composes them as Terraform modules with explicit cross-resource wiring.
  • Smart output directory — auto-resolves to infra/{SolutionName}.OpenTofu/ by finding the nearest .slnx or .sln file. Falls back to infra/ if no solution file exists. Overridable via -d (CLI) or OutputDirectory (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 — RegistryConfig lets 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 IChatClient with function-calling.
  • Automatic OpenTofu management — tofu is auto-detected and installed if missing. tofu init runs 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

Requires the AspireOpenNimbus.Hosting project 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 · requires Microsoft.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:

  1. Search module registries — starting with your company-private registry (if configured), then library.tf, search.opentofu.org, and registry.terraform.io.
  2. Fetch the best-fit module's documentation and input/output variable reference.
  3. Generate a module block with sensible defaults mapped from the Aspire resource's properties.
  4. 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):

  1. library.tf — community-curated modules
  2. search.opentofu.org — official OpenTofu search
  3. 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 tofu automatically.
  • Initialise the OpenTofu workspace (.terraform/) if not already done.
  • Resolve manifest {..} placeholders to OpenTofu references.
  • Generate shared-resources.tf with VPC data sources and common infrastructure.
  • Write per-resource .tf files to infra/{SolutionName}.OpenTofu/.
  • Re-run tofu init to pull provider plugins for any newly generated versions.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 -d to override.

init

Initialises the OpenTofu workspace and creates a default main.tf scaffold.

Note: You rarely need to call this directly — generate handles 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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