diff --git a/.agents/skills/hosting-integration-authoring/SKILL.md b/.agents/skills/hosting-integration-authoring/SKILL.md new file mode 100644 index 000000000..e17fb9087 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/SKILL.md @@ -0,0 +1,69 @@ +--- +name: hosting-integration-authoring +description: Guides authoring and reviewing CommunityToolkit Aspire.Hosting integration APIs. Classifies integration archetypes, then applies self-contained best practices for naming, resource shape, run/publish/deploy behavior, eventing, connection properties, security, endpoint semantics, polyglot exports, READMEs, and tests. +--- + +# Aspire hosting integration authoring + +Use this skill when creating, modifying, or reviewing `CommunityToolkit.Aspire.Hosting.*` hosting integration packages and related `Aspire.Hosting.*` APIs. + +This skill is self-contained. The resource files in this skill are the authoritative guidance, and repository paths are examples of existing patterns only. + +## First step: classify the integration + +Read `resources/selector-matrix.md` first. Then read `resources/app-model-fundamentals.md` for the app model rules that apply to every hosting integration. Classify the work across all four axes: + +1. Resource shape. +2. Lifecycle mode. +3. Integration role. +4. Structure. + +Do not force the integration into a single bucket. Most integrations compose several patterns. For example, PostgreSQL is a container-backed service, has parent-child database resources, exposes connection properties, creates databases during resource readiness, and has admin UI companion helpers. + +## Then read the matching resources + +Always read the relevant archetype resource and the cross-cutting resources that apply to the change. + +| If the integration involves | Read | +| --- | --- | +| Any resource, annotation, lifecycle event, structured value, endpoint, manifest, or app-model behavior | `resources/app-model-fundamentals.md` | +| Local container service, database, broker, cache, vector DB | `resources/archetype-container-backed-service.md` | +| Admin UI, inspector, dashboard, load/test tool, or standalone utility container | `resources/archetype-admin-and-tool-container.md` | +| Migration, package/tool install, model pull, schema deployment, DACPAC, or one-shot setup helper | `resources/archetype-setup-and-migration-helper.md` | +| Serialized controller, reconciler, command-driven resource operations, drift detection, or state-machine orchestration | `resources/archetype-controller-reconciler.md` | +| Sidecar, component registry, telemetry collector, service mesh, middleware, or app-wide annotation-driven infrastructure | `resources/archetype-sidecar-and-middleware.md` | +| Local tunnel, webhook forwarder, callback bridge, or CLI that exposes/forwards local endpoints | `resources/archetype-tunnel-and-webhook-bridge.md` | +| Secret manager, credential broker, external secret provider, or provider-backed managed secret child resources | `resources/archetype-secret-provider.md` | +| External/SaaS service reference with API key/endpoint, no local container/provisioning | `resources/archetype-external-cloud-reference.md` | +| Azure resource provisioning, Bicep, role assignments, existing Azure resources, emulators, local containers for Azure resources | `resources/archetype-azure-provisioning.md` | +| Docker Compose, Kubernetes, Azure Container Apps, or another deployment target/publisher | `resources/archetype-deployment-target-publisher.md` and `resources/deployment-production-readiness.md` | +| Python, Go, JavaScript, Node, Vite, Next.js, or another language runtime/workload | `resources/archetype-language-executable-app.md` | +| Non-resource model/configuration overlay, e.g. Orleans-style APIs | `resources/archetype-overlay-configuration.md` | +| Public API names, overloads, return types, polyglot AppHost compatibility, annotations, experimental state | `resources/api-naming-and-shape.md` | +| Any run/publish/deploy branching | `resources/run-publish-deploy-modes.md` | +| Event subscriptions, initialization, generated files, health checks, pipeline steps | `resources/eventing-and-initialization.md` | +| Custom resource lifetime, synthetic/facade resources, manually allocated endpoints, or resource notification state machines | `resources/custom-lifecycle-and-facade-resources.md` | +| `IResourceWithConnectionString`, `WithReference`, environment variables, URI/JDBC properties | `resources/connection-properties.md` | +| Parent-child resources, companions, setup siblings, `WithReference`, waits, relationships | `resources/relationships-and-companions.md` | +| `[AspireExport]`, TypeScript/polyglot AppHosts, ATS metadata, analyzer diagnostics, DTOs, union parameters, value catalogs, callback contexts | `resources/polyglot-exports.md` | +| Endpoint names, service discovery, external URLs, reference endpoints, endpoint environment variables | `resources/endpoints-and-service-discovery.md` | +| Secrets, parameters, credentials, managed identity, RBAC, private networking, generated artifacts containing sensitive data | `resources/security-secrets-and-identity.md` | +| Resource names, physical names, annotations, constructors, mutability, model invariants | `resources/resource-model-invariants.md` | +| Package metadata, preview/stable posture, `aspire add` discovery, icons, gallery/README visibility | `resources/package-and-discoverability.md` | +| Dashboard icons, URLs, commands, notifications, resource logs, admin companion UX | `resources/dashboard-ux.md` | +| Generated config files, `WithContainerFiles`, file permissions, temp/store usage, build dependencies | `resources/generated-files-and-container-files.md` | +| Toolchain detection, path handling, Windows/macOS/Linux behavior, shell quoting, executable permissions | `resources/cross-platform-tooling.md` | +| Experimental/obsolete/deprecation lifecycle, compatibility shims, migration guidance | `resources/compatibility-and-deprecation.md` | +| README or tests | `resources/testing-and-readmes.md` | + +## Authoring workflow + +1. State the classification briefly before changing or reviewing code. +2. Apply the archetype-specific DO/DON'T list. +3. Apply every relevant cross-cutting checklist. +4. Validate both run-mode and publish/deploy behavior when the integration changes those surfaces. +5. Keep generated API baseline files under `*/api/*.cs` untouched unless the task explicitly asks for API baseline regeneration. + +## Review workflow + +When reviewing an integration PR, look for concrete violations that can cause wrong runtime behavior, wrong generated manifests, broken deployment output, bad public API, missing connection properties, or bad polyglot projection. Do not spend review budget on style-only comments. diff --git a/.agents/skills/hosting-integration-authoring/resources/api-naming-and-shape.md b/.agents/skills/hosting-integration-authoring/resources/api-naming-and-shape.md new file mode 100644 index 000000000..5304dcb2e --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/api-naming-and-shape.md @@ -0,0 +1,93 @@ +# API naming and shape + +Aspire hosting integrations should feel consistent across C# and polyglot AppHosts. Public APIs should be predictable, versionable, and hard to misuse. + +## Method naming + +| Pattern | Use for | Examples | +| --- | --- | --- | +| `Add{Technology}` | Create a top-level resource or integration object | `AddPostgres`, `AddAzureStorage`, `AddGoApp` | +| `Add{Child}` | Create parent-scoped subresources | `AddDatabase`, `AddQueue`, `AddBlobContainer`, `AddModel` | +| `With{Configuration}` | Configure an existing resource builder | `WithDataVolume`, `WithHostPort`, `WithEnvironment` | +| `RunAs{Mode}` | Change local run behavior only | `RunAsEmulator`, `RunAsContainer`, `RunAsExisting` | +| `PublishAs{Target}` | Change publish/deploy behavior only | `PublishAsDockerComposeService`, `PublishAsAzureContainerApp` | +| `AsExisting` | Apply existing-resource semantics in both run and publish modes | `AsExisting` | +| `Configure{Model}` | Mutate a generated deployment or infrastructure model | `ConfigureInfrastructure`, `ConfigureComposeFile` | + +## Polyglot AppHost compatibility + +Polyglot compatibility is a cross-cutting API shape constraint, not a separate integration archetype. Treat TypeScript and other generated AppHosts as first-class consumers when naming and shaping public APIs. + +DO: + +- Sketch the intended generated-SDK call shape before finalizing public C# names. +- Keep one user concept to one generated method name on a given target type. Put variation in an options DTO, enum, union parameter, or internal dispatcher. +- Use names that describe user behavior, not C# implementation mechanics. For example, prefer `PublishAsStaticWebsite` over names that expose callback, generic, or annotation details. +- Mark C# convenience overloads, callback overloads, and generic-metadata overloads with `[AspireExportIgnore(Reason = "...")]` when they are not the generated-SDK contract. +- Add a polyglot-friendly exported adapter when the ergonomic C# API uses callbacks, generics, framework types, or types that do not project cleanly. +- Use language-neutral XML docs and `ats-*` overrides when the C# docs mention types or behaviors generated SDK users cannot see. +- Inspect generated SDK names, signatures, docs, and capability IDs before shipping a new exported API. + +DON'T: + +- Don't rely on C# overload resolution, extension receiver types, generic constraints, or optional-parameter overloads to make generated APIs understandable. +- Don't expose C# implementation type names such as `Action`, `IServiceProvider`, `IConfiguration`, `IProjectMetadata`, annotations, or builder callbacks as the only way to configure a feature. +- Don't let internal adapter names leak into generated SDKs. Use an explicit export ID or `MethodName` so generated users see the conceptual API name. +- Don't add a C#-only API and defer polyglot shape decisions until after the API has shipped. + +## Type naming + +Use nouns or noun phrases for public types. + +- Primary resources: `{Technology}Resource` or `{Technology}ServerResource`. +- Child resources: `{Technology}{Child}Resource`, for example `PostgresDatabaseResource`. +- Admin/dev companion containers: `{Tool}ContainerResource`, for example `PgAdminContainerResource`. +- Annotations: `{Purpose}Annotation`, not verb-prefixed names. +- Options objects: `{Feature}Options`. + +Prefer consistent property names: + +- URI-producing values should be named `UriExpression`. +- Connection string expressions should be named `ConnectionStringExpression`. +- Endpoint references should be named after role: `PrimaryEndpoint`, `InternalEndpoint`, `HttpEndpoint`. +- `IServiceProvider` properties should be named `Services`. +- Port customizers should use existing names like `WithHostPort` or role-specific names like `WithGatewayPort`. + +## Return types + +Resource-producing APIs should return `IResourceBuilder`. + +Rare overlay/configuration APIs may return a non-resource object only when the integration intentionally does not create a resource. See `archetype-overlay-configuration.md`. + +Fluent configuration APIs should return the same builder type they receive, unless they create and return a child resource. + +## Parameters + +DO: + +- Use `[ResourceName] string name` for Aspire resource names. +- Validate `builder` with `ArgumentNullException.ThrowIfNull`. +- Validate required strings with `ArgumentException.ThrowIfNullOrEmpty`. +- Separate Aspire resource names from physical names. Use `databaseName`, `queueName`, or similar optional physical-name parameters that default to `name`. +- Use `IResourceBuilder` for secrets and user-supplied credentials. +- Prefer options objects when an API needs many optional parameters. + +DON'T: + +- Don't make logically required parameters optional just to avoid updating call sites. +- Don't add many optional parameters to public APIs; they are hard to version. +- Don't use `Tuple<>` in public APIs. +- Don't expose implementation-detail annotations, helpers, or generated deployment node types publicly unless users must customize them. +- Don't use boolean parameters when an enum is clearer and likely to grow. + +## Annotations + +Use `ResourceAnnotationMutationBehavior.Replace` for last-wins configuration, such as build flags, selected package manager, chosen publish mode, or existing-resource settings. + +Do not accumulate mutually exclusive annotations unless multiple annotations are intentionally meaningful. + +## Experimental APIs + +Mark unstable or emerging APIs with `[Experimental("ASPIRE...")]` and use a unique diagnostic ID. Deployment, publishing, compute, language-runtime, and generated-Dockerfile APIs often require experimental treatment. + +Do not add obsolete shims for APIs that have not shipped stable unless there is a specific compatibility reason. diff --git a/.agents/skills/hosting-integration-authoring/resources/app-model-fundamentals.md b/.agents/skills/hosting-integration-authoring/resources/app-model-fundamentals.md new file mode 100644 index 000000000..618373e07 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/app-model-fundamentals.md @@ -0,0 +1,167 @@ +# App model fundamentals + +Use these rules for every hosting integration. Resources are app-model data first; orchestration, publishing, dashboard display, and deployment are driven by annotations, standard interfaces, values, references, and lifecycle hooks. + +## Resource model basics + +DO: + +- Keep resource classes inert data objects. Constructors should capture model state only. +- Use fluent extension methods for behavior: construction, defaults, annotations, endpoints, environment, event subscriptions, and relationships. +- Treat the Aspire resource name as the unique graph identity. +- Use annotations as the primary extension mechanism for optional behavior and metadata. +- Prefer standard interfaces so runtime, tooling, publishers, and generated SDKs can discover capabilities polymorphically. + +DON'T: + +- Don't make resources start, stop, probe, allocate endpoints, resolve connection strings, write files, or call services from constructors. +- Don't infer dependencies, parent-child relationships, or value flows from names. +- Don't flatten structured values into strings before run/publish resolution. + +## Standard capability interfaces + +Implement interfaces for what the resource can do, not for what concrete type it happens to be: + +| Interface | Use when | +| --- | --- | +| `IResourceWithEnvironment` | The resource can receive environment variables. | +| `IResourceWithServiceDiscovery` | Other resources should call it through service discovery. | +| `IResourceWithEndpoints` | The resource exposes named endpoints. | +| `IResourceWithConnectionString` | Consumers need a connection string or connection properties. | +| `IResourceWithArgs` | The resource accepts launch arguments. | +| `IResourceWithWaitSupport` | The resource can wait on other resources. | +| `IResourceWithoutLifetime` | The resource is a value/configuration resource with no runtime lifecycle. | +| `IComputeResource` | The resource can be hosted by a compute environment or deployment target. | +| `IComputeEnvironmentResource` | The resource represents a deployment/hosting environment. | + +DO: + +- Let `WithReference`, publishers, dashboard, and deployment target code key off these interfaces where possible. +- Keep resource-specific helpers thin wrappers over standard interfaces. + +DON'T: + +- Don't require publishers or consumers to special-case your concrete type when an existing interface expresses the capability. + +## Lifecycle, readiness, and status + +Known resource states and dashboard status are managed by Aspire. `Unknown` is the normal initial state while the graph is still being constructed. + +Important lifecycle events: + +| Event | Use for | +| --- | --- | +| `InitializeResourceEvent` | First lifecycle setup for a resource. | +| `ResourceEndpointsAllocatedEvent` | Reading allocated endpoint values in run mode. | +| `BeforeResourceStartedEvent` | Last-chance runtime setup before process/container start. | +| `ConnectionStringAvailableEvent` | Creating clients or caching connection strings after references are resolvable. | +| `ResourceReadyEvent` | Creating service state after the resource is healthy and ready. | + +DO: + +- Remember event publishing is synchronous and blocking; keep handlers bounded and cancellation-aware. +- Use `ResourceNotificationService` snapshots for ongoing status updates. +- Use resource logs for human-readable diagnostics and setup/command progress. +- Let health checks drive readiness. Aspire publishes `ResourceReadyEvent` after the resource is running and health checks pass, or immediately after running when no health checks exist. + +DON'T: + +- Don't manually publish `ResourceReadyEvent`. +- Don't use health checks for side effects. +- Don't confuse one-time events with ongoing status snapshots. + +## Relationships and dependency graph + +References form a heterogeneous directed acyclic graph of value flows and dependency ordering. Endpoints are special: endpoint references are modeled outside the strict resource dependency graph and can support real-world cycles such as mutual frontend/OIDC callback URL wiring. + +DO: + +- Add explicit references through `WithReference`, environment variables, args, connection strings, or custom structured values. +- Use `IResourceWithParent` for true lifecycle containment. +- Use `.WithParentRelationship()` only for visual/dashboard grouping with no lifecycle impact. +- Use `.WithRelationship(parent, "Label")` for custom semantic relationships that are not ownership. + +DON'T: + +- Don't assume `WithParentRelationship()` controls lifecycle. +- Don't assume `IResourceWithParent` and a visual parent relationship are interchangeable. +- Don't rely on call order or resource names to infer dependency edges. + +## Structured values and deferred evaluation + +Structured values preserve graph meaning across run and publish. A value may resolve to a concrete local value in run mode and a manifest/deployment expression in publish mode. + +Core value shapes: + +| Type | Run mode | Publish mode | +| --- | --- | --- | +| `string` | Literal value | Literal value | +| `EndpointReference` | Concrete endpoint URL/host/port after allocation | Target-specific endpoint expression | +| `EndpointReferenceExpression` | One endpoint property | Target-specific property expression | +| `ConnectionStringReference` | Concrete connection string | Token or externalized secret/reference | +| `ParameterResource` | Local value or environment/user-secret lookup | Placeholder/parameter | +| `ReferenceExpression` | Composite resolved string | Composite expression with placeholders preserved | + +DO: + +- Build composite values inside `ReferenceExpression.Create(...)`. +- Implement `IValueProvider` and `IManifestExpressionProvider` on custom structured values. +- Implement `IValueWithReferences` when the value holds resource references. +- Pass value objects directly into environment variables, args, and annotations. + +DON'T: + +- Don't build a string first and wrap it later; structure is already lost. +- Don't mix resolved strings with placeholders in the same value. +- Don't concatenate secrets into plain strings. + +## Endpoint resolution + +Endpoints are allocated during run-mode startup. In publish mode, endpoint values are manifest expressions and concrete properties such as `Url`, `Host`, and `Port` are not available. + +DO: + +- Use `EndpointReference.Property(EndpointProperty.Host)`, `Port`, `HostAndPort`, `Scheme`, `TargetPort`, or `Url` when constructing deferred expressions. +- Use `ResourceEndpointsAllocatedEvent`, `BeforeResourceStartedEvent`, or `WithEnvironment` callbacks when you must access allocated run-mode values. +- Check `EndpointReference.IsAllocated` before reading concrete endpoint properties outside known allocation points. +- Let endpoint resolution account for source/target context: container-to-container, executable/project-to-container, and container-to-host communication can resolve differently. +- In unusual cross-context scenarios, branch on execution context and explain why the default endpoint resolver is not enough. + +DON'T: + +- Don't read `endpoint.Url`, `endpoint.Host`, or `endpoint.Port` during publish. +- Don't use host-process endpoint values for container-to-container traffic. +- Don't turn an endpoint property into a string when an `EndpointReferenceExpression` can stay structured. + +## Add/With API split + +DO: + +- Use `Add{Technology}` methods to validate inputs, instantiate a data-only resource, register it with `builder.AddResource(resource)`, and apply default annotations/wiring. +- Use `With{Configuration}` methods to attach or replace annotations on an existing resource builder. +- Keep resource-producing APIs returning `IResourceBuilder`. +- Keep fluent configuration APIs returning the builder they receive unless they intentionally create a child resource. + +DON'T: + +- Don't put behavior in resource constructors because it is convenient for `Add*`. +- Don't expose annotation plumbing when a fluent method can express the feature. + +## Annotation and manifest publishing + +Annotations are strongly typed model metadata. Public annotations can be used by runtime code, publishers, tests, and user callbacks. + +DO: + +- Use `ResourceAnnotationMutationBehavior.Replace` for last-wins configuration. +- Accumulate annotations only when multiple entries intentionally compose. +- Query annotations with `TryGetLastAnnotation()` when the last applied setting wins. +- For custom manifest resources, add `ManifestPublishingCallbackAnnotation` and write JSON through `ManifestPublishingContext.Writer`. +- Use `IManifestExpressionProvider.ValueExpression` for structured manifest fields and call `context.TryAddDependentResources(value)` when emitted values may reference resources. +- Use `.ExcludeFromManifest()` for run-only setup helpers, admin/dev companions, or resources that should not publish. + +DON'T: + +- Don't duplicate mutually exclusive annotations. +- Don't serialize resolved runtime values into manifest output. +- Don't include implementation-only resources in generated deployment artifacts. diff --git a/.agents/skills/hosting-integration-authoring/resources/archetype-admin-and-tool-container.md b/.agents/skills/hosting-integration-authoring/resources/archetype-admin-and-tool-container.md new file mode 100644 index 000000000..0c97da3fc --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/archetype-admin-and-tool-container.md @@ -0,0 +1,85 @@ +# Archetype: admin and tool container integration + +Use this archetype for local development tools backed by containers: admin UIs, inspectors, dashboards, test/load tools, and other utility containers. These resources may be standalone tools, singleton helpers shared by several resources, or companions attached to a parent service. + +Examples: + +- Database admin UIs such as Adminer, DbGate, PgAdmin, Mongo Express, RedisInsight. +- Inspectors and utility UIs such as MCP Inspector, Swagger-like viewers, dashboards, or protocol explorers. +- Test/load tools such as k6 when they are modeled as resources. + +## Distinguish tool containers from service dependencies + +DO: + +- Decide whether the tool is a parent-scoped companion or a standalone/singleton utility. +- Use `With{Tool}` on the parent when the tool is meaningful only for that parent and can be preconfigured from the parent endpoint/credentials. +- Use top-level `Add{Tool}` when the tool is a shared singleton, can connect to many resources, or is useful independently of one parent. +- Return the parent builder from parent-scoped `With{Tool}` helpers. +- Return the tool builder from top-level `Add{Tool}` helpers. +- Add dashboard URLs with clear display text for tools users are expected to open. +- Hide implementation-only or details-only URLs when the tool is not a primary entry point. + +DON'T: + +- Don't model a tool container as a service dependency unless workloads are supposed to consume it through `WithReference`. +- Don't force all admin UIs into parent-scoped APIs; singleton admin tools are valid when one resource instance can serve multiple backing services. +- Don't include development-only tools in publish/deploy output by default. + +## Manifest and lifecycle + +DO: + +- Call `.ExcludeFromManifest()` for run-only admin/dev tools. +- If a tool participates in publish/deploy, document why and test the generated output. +- Avoid duplicate singleton tools by checking for an existing resource and returning its builder when repeat calls are intentional. +- Make optional tools opt-in; adding a database or broker should not implicitly add heavy UI/tool containers unless that is an established convention for the integration. + +DON'T: + +- Don't make a dev tool a required dependency of the primary service. +- Don't let repeated `Add{Tool}` or `With{Tool}` calls create duplicate singleton resources unless multiple instances are intentionally supported. + +## Preconfiguration + +DO: + +- Preconfigure parent-scoped tools with deferred endpoint, credential, database, broker, or cluster values when the tool image supports it. +- Preserve secret handling with `ParameterResource` and `ReferenceExpression`; avoid resolved secret strings in environment variables until runtime evaluation. +- Sanitize generated environment variable names or connection identifiers according to the target container's documented constraints. +- Document what the tool can and cannot auto-discover. + +DON'T: + +- Don't leak parent credentials into logs, generated files, or manifests. +- Don't assume Aspire resource names are valid as container-specific IDs without normalization. + +## Multi-parent singleton aggregation + +Tools such as Adminer, DbGate, and Elasticvue can be singleton resources that aggregate configuration from multiple parent resources. + +DO: + +- In `Add{Tool}`, check `builder.Resources.OfType().SingleOrDefault()` and return `builder.CreateResourceBuilder(existing)` when repeat calls should share one tool instance. +- In parent-scoped `With{Tool}` helpers, create or get the singleton tool, add any parent relationship or display relationship that helps dashboard UX, then return the original parent builder. +- Use deferred `WithEnvironment(context => ...)` callbacks on the tool to read all finalized parent resources from `applicationBuilder.Resources.OfType()`. +- Merge incremental configuration instead of overwriting it. If several parent types contribute to the same tool, parse the existing environment value, add missing entries, and write it back; Adminer-style JSON server lists and DbGate-style connection lists are good models. +- Make repeated parent calls idempotent with stable sanitized connection IDs, labels, or keys. +- Test multiple parents and multiple parent types calling `With{Tool}` so one singleton contains the merged configuration. + +DON'T: + +- Don't capture a single parent at app-model construction time when the tool must discover every opted-in parent in the final model. +- Don't resolve secrets early just to build aggregate config. Prefer `ReferenceExpression` or runtime environment callback resolution, and ensure resolved values never enter manifests or logs. + +## Health checks + +DO: + +- Add a health check when readiness affects dependent resources or the dashboard should reflect tool health. +- Keep display-only tool health checks lightweight. + +DON'T: + +- Don't require deep protocol health checks for tools that are purely optional UI surfaces and have no dependents. +- Don't block a primary service on an optional admin tool unless the user explicitly requested that dependency. diff --git a/.agents/skills/hosting-integration-authoring/resources/archetype-azure-provisioning.md b/.agents/skills/hosting-integration-authoring/resources/archetype-azure-provisioning.md new file mode 100644 index 000000000..5a5b9c681 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/archetype-azure-provisioning.md @@ -0,0 +1,89 @@ +# Archetype: Azure provisioning and dual-mode integration + +Use this archetype for Azure resources that Aspire provisions, references, or can run locally through an emulator/container. + +Representative examples: + +- `src/CommunityToolkit.Aspire.Hosting.Azure.Dapr/AzureDaprHostingExtensions.cs` +- `src/CommunityToolkit.Aspire.Hosting.Azure.Dapr/AzureDaprComponentResource.cs` +- `src/CommunityToolkit.Aspire.Hosting.Azure.Dapr.Redis/AzureRedisCacheDaprHostingExtensions.cs` +- `src/CommunityToolkit.Aspire.Hosting.Azure.Extensions/AzureStorageExplorerBuilderExtensions.cs` + +## Provisioning resource shape + +Azure-managed resources usually derive from `AzureProvisioningResource` and implement `IResourceWithConnectionString` or Azure-specific target interfaces as needed. + +Top-level add methods should call `builder.AddAzureProvisioning()` and create a resource with an `Action` callback. + +## DO + +- Name top-level methods `AddAzure{Service}`. +- Use `AzureResourceInfrastructure` to create Bicep resources, outputs, role assignments, and dependencies. +- Use managed identity and RBAC by default when supported. +- Add provisioning outputs for values used by connection expressions or other resources. +- Add outputs from concrete provisionable-resource properties, not from the Aspire resource's own output references. +- Use secure defaults, for example modern TLS and disabled shared keys when supported. +- Add default role assignments for resources referenced by workloads, especially when the connection string or connection properties advertise managed-identity authentication. The authentication story is incomplete if the workload receives an Azure-auth connection string but provisioning emits no least-privilege data-plane RBAC. +- Support private endpoints and network restrictions when the service requires them. +- Keep child resource provisioning under the parent Azure provisioning resource. + +## DON'T + +- Don't put child provisioning callbacks on every child unless users truly need child-specific infrastructure customization. +- Don't duplicate emulator/access-key/managed-identity branching in child resources. +- Don't mutate resources marked as existing in ways that imply Aspire owns them. +- Don't hardcode generated Azure names when a naming convention/resource token is required. + +## Child resources + +Child Azure resources should: + +- Implement `IResourceWithParent`. +- Have parent-scoped builder methods like `AddDatabase`, `AddQueue`, `AddHub`, or `AddBlobContainer`. +- Use globally unique Aspire resource names. +- Use separate physical-name parameters such as `databaseName`. +- Inherit parent connection properties with `CombineProperties`. + +## Existing resources + +Existing-resource semantics apply broadly to Azure resources. + +| API | Scope | +| --- | --- | +| `RunAsExisting` | Running locally or with development provisioning/reference behavior | +| `PublishAsExisting` | Publishing/deploying infrastructure | +| `AsExisting` | Both run and publish | + +The existing-resource annotation records intent that Aspire should reference an existing resource instead of managing it as new. Treat that annotation as read-only deployment intent. Do not apply creation-only mutations such as new auth setup, new child infrastructure, new storage, or new dashboard components to existing resources unless the API explicitly supports that safe operation. + +Use `AzureProvisioningResource.CreateExistingOrNewProvisionableResource` when the Azure provisioning package exposes both normal and `FromExisting` constructors for the concrete resource. In `AddAsExistingResource`, check for an already-added provisionable resource with the same Bicep identifier, create `FromExisting(...)` when missing, and call `TryApplyExistingResourceAnnotation(...)`; if that returns `false`, set the provisionable name from a real name parameter/output fallback. + +Use polyglot-friendly overloads for string or `ParameterResource` names when exporting these APIs. + +## Dual-mode local emulator/container + +Azure resources may support local run behavior with `.RunAsEmulator()` or `.RunAsContainer()`. + +DO: + +- Return unchanged builder in publish mode for run-only emulator/container setup. +- Mark emulators with an emulator annotation. +- Use `InnerResource`, `IsContainer`, or `IsEmulator` to branch resource behavior. +- Delegate annotations to the inner resource when the local container is the runtime resource. +- Make connection properties mode-agnostic, including authentication semantics. Do not emit managed-identity or Azure-specific auth tokens in emulator/container connection strings unless the emulator actually supports that auth mode. +- Copy existing annotations to the inner resource before swapping when needed. +- Remove or hide Azure resources from the run model if the inner container replaces them. + +DON'T: + +- Don't let emulator endpoints/images leak into publish output. +- Don't leave both Azure and inner container resources active in run mode unless both intentionally run. +- Don't make child resources independently decide whether the parent is Azure or local; centralize mode state in the parent. + +## Provisioning callbacks + +Provisioning callbacks run during publish/deploy generation. They should produce deterministic infrastructure and use parameters/outputs/references rather than local runtime values. + +Do not read allocated ports, local process state, or container runtime state from provisioning callbacks. + +Do not create self-referential outputs, such as setting a provisionable resource name from the same Aspire resource's `NameOutputReference` before that output is produced. diff --git a/.agents/skills/hosting-integration-authoring/resources/archetype-container-backed-service.md b/.agents/skills/hosting-integration-authoring/resources/archetype-container-backed-service.md new file mode 100644 index 000000000..7b207ba70 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/archetype-container-backed-service.md @@ -0,0 +1,96 @@ +# Archetype: container-backed service integration + +Use this archetype for primary local service dependencies backed by containers: databases, caches, brokers, vector databases, search engines, object stores, and similar infrastructure that workloads reference with `WithReference`. + +Representative examples: + +- `src/CommunityToolkit.Aspire.Hosting.MongoDB.Extensions/MongoDBBuilderExtensions.cs` +- `src/CommunityToolkit.Aspire.Hosting.PostgreSQL.Extensions/PostgresBuilderExtensions.cs` +- `src/CommunityToolkit.Aspire.Hosting.ActiveMQ/ActiveMQServerResource.cs` +- `src/CommunityToolkit.Aspire.Hosting.Minio/MinioBuilderExtensions.cs` + +## Resource shape + +Primary resources usually derive from `ContainerResource` and implement `IResourceWithConnectionString` when they can be referenced by apps. + +Child resources derive from `Resource`, implement `IResourceWithParent`, and often implement `IResourceWithConnectionString`. + +This archetype is not the right primary guide for every container: + +- Admin UIs and development tools should also read `archetype-admin-and-tool-container.md`. +- Local tunnels and webhook forwarders should read `archetype-tunnel-and-webhook-bridge.md`. +- Sidecars, telemetry collectors, and middleware should read `archetype-sidecar-and-middleware.md`. +- One-shot migration/setup containers should read `archetype-setup-and-migration-helper.md`. + +## Builder pattern + +DO: + +- Add the primary resource with `Add{Technology}`. +- Validate builder and name. +- Create default credentials as `ParameterResource`s. +- Verify the container image actually consumes every generated credential you expose in connection strings or connection properties, for example through required command-line flags, environment variables, config files, or init scripts. +- When a container command relies on shell features such as environment-variable expansion, invoke the shell correctly, for example `/bin/sh -c 'exec service --password "$PASSWORD"'`; otherwise pass arguments directly without expecting shell expansion. +- Register health checks. +- Make health checks prove the readiness consumers need. A plain TCP-port check is not sufficient for services that open sockets before they can complete the service protocol, authenticate with configured credentials, or accept real client operations. +- Add the resource with `builder.AddResource(resource)`. +- Configure endpoint, image, registry, environment, icon, volumes, and health check in the returned chain. +- Use stable endpoint names, commonly `"tcp"` for protocol endpoints and `"http"` for HTTP UIs. +- Use separate host and internal endpoints when container-to-container connectivity differs from host-process connectivity. + +DON'T: + +- Don't hardcode host ports by default; accept `int? port = null` and let Aspire allocate. +- Don't inline image tags in fluent chains; use a container image tags class. +- Don't create random passwords directly; use `CreateDefaultPasswordParameter`. +- Don't add a password/API-key parameter to the connection string unless the underlying service is configured to require that credential. +- Don't resolve connection strings before `ConnectionStringAvailableEvent`. + +## Connection strings + +DO: + +- Build connection strings with `ReferenceExpressionBuilder`. +- Encode credentials and database names with URI formatting. +- Expose `Host`, `Port`, `Username`, `Password`, `Uri`, and database-specific properties as applicable. +- For child database resources, compose parent properties and override `Uri`/database values. + +DON'T: + +- Don't concatenate plain secret values. +- Don't make child resources guess parent endpoint/auth details. + +## Initialization + +Use `OnResourceReady` for side effects that require the service to be reachable, such as creating databases, queues, topics, or containers. + +Use `ConnectionStringAvailableEvent` to create clients or capture resolved connection strings. + +If a child resource is metadata-only and does not create provider state, implement `IResourceWithoutLifetime` and document that the API only supplies reference metadata. Otherwise, create or verify the child state from `OnResourceReady` after the parent service is healthy. + +Do not perform service-state creation in health checks. + +## Volumes and init files + +DO: + +- Provide `WithDataVolume` for named Docker volumes. +- Provide `WithDataBindMount` when host paths are a legitimate scenario. +- Use `VolumeNameGenerator.Generate(builder, "data")` for default names. +- Prefer `WithInitFiles` over obsolete bind-mount patterns for initialization scripts/files. +- Document standard container paths in XML docs and README when user-facing. + +DON'T: + +- Don't hand-roll default volume names. +- Don't assume all data mounts are writable; expose `isReadOnly` where useful. + +## Admin companions + +Admin UIs such as PgAdmin, Mongo Express, RedisInsight, or Kafka UI should be optional `With{Tool}` methods on the parent resource. + +They should add a companion container, configure endpoint/environment, add a parent/custom relationship, call `.ExcludeFromManifest()`, and return the original parent builder. + +Companions must be preconfigured to connect to the parent service when the image supports it. Include host, internal target port, credentials, and database/topic/cluster names required by the companion image, using deferred `ReferenceExpression`/parameters rather than resolved secret values where possible. + +If enabling a built-in admin UI requires changing the container image tag, preserve the user's version and variant whenever possible. Throw for digest-pinned images or tags that cannot be safely mapped; do not silently clear `SHA256` pins or downgrade to a default tag. diff --git a/.agents/skills/hosting-integration-authoring/resources/archetype-controller-reconciler.md b/.agents/skills/hosting-integration-authoring/resources/archetype-controller-reconciler.md new file mode 100644 index 000000000..4dc81d8a3 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/archetype-controller-reconciler.md @@ -0,0 +1,173 @@ +# Archetype: controller/reconciler integration + +Use this archetype when an integration must coordinate lifecycle events, dashboard commands, background probes, and per-resource operations against shared external state. The goal is resilient, reentrant orchestration rather than one-off event handlers that race each other. + +Representative examples: + +- A run-mode provisioning controller that serializes provision, reprovision, reset, delete, cancel, and drift-check operations. +- A deployment target controller that reconciles desired app-model resources with live platform state. +- A local infrastructure operator that reacts to commands and resource state changes while keeping dashboard command state accurate. +- A stateless lifecycle orchestrator that centralizes per-resource start/ready/stopped logic without serializing unrelated resources. + +## Shape + +Controller/reconciler logic should live in a singleton service. Resources stay app-model data and dashboard surfaces. + +DO: + +- Put orchestration state and operation dispatch in a singleton controller service. +- Keep resource classes inert; use resources for identity, relationships, references, initial state, and command annotations. +- Expose an environment/control resource when users need aggregate status and environment-level commands. +- Attach per-resource commands from a preparer/final-model hook when commands depend on the final resource set. +- Represent each operation as a typed intent with an explicit target scope, such as all resources, one resource, a resource set, or background/no user-visible scope. +- Mode-gate the controller. If the controller is run-mode only, exclude or hide its environment/control resources in publish output. + +DON'T: + +- Don't put long-running orchestration directly in resource constructors or random fluent methods. +- Don't let every command, event handler, and background probe mutate shared state independently. +- Don't use resource names or call order as implicit operation dependencies. + +## Pick the lightest controller shape + +Centralizing lifecycle logic in a controller service does not always mean adding a queue. + +DO: + +- Use a stateless lifecycle orchestrator when operations are per-resource, independent, and do not mutate shared controller state. Invoke its methods directly from resource lifecycle callbacks so Aspire's built-in per-resource concurrency is preserved. +- Keep shared external interactions concurrency-safe without a controller queue when a narrower primitive already exists, such as a coalesced login manager or per-client retry helper. +- Escalate to a serialized queue/reconciler only when operations contend on shared mutable state, dashboard command state, cancel/delete/reset workflows, drift probes, or an environment-level operation model. +- Scope serialization to the smallest conflict domain. Use one global queue only when a single intent owns the whole resource set and fans out internally; use keyed/per-resource serialization for independent per-resource lifecycle callbacks. + +DON'T: + +- Don't introduce a serialized control loop just to move code out of fluent extension methods. +- Don't funnel independent per-resource lifecycle callbacks through one global single-reader queue; it serializes unrelated resources, increases startup latency, and adds shutdown-loop failure modes. +- Don't make unrelated resources wait behind a long-running operation unless they really share the same external state or invariant. + +## Serialized control loop + +Use a serialized control loop only after the decision gate above says one is needed. A queued reconciler should have one synchronization boundary for the state it owns. + +DO: + +- Funnel public methods, dashboard commands, lifecycle callbacks, CLI/MCP commands, and background probes through the same serialized queue. +- Use a single-reader queue/channel when operations can be requested concurrently. +- Create each queued item with a typed intent, caller cancellation token, and `TaskCompletionSource` using `RunContinuationsAsynchronously`. +- Register accepted queued operations before writing to the queue so rapid callers cannot enqueue conflicting operations before command states refresh. +- Keep the locked state small: current intent, active operation snapshot, queued operation scopes, and coalescing flags. +- Never `await` while holding the controller state lock. +- Start background loops lazily and idempotently with `Interlocked` or an equivalent guard. +- Tie reader-loop lifetime to explicit channel completion/disposal, not only to host shutdown, when the controller must process shutdown-time intents such as stopped/finished transitions. +- Fence writers after the loop exits. Enqueue-and-await must fail fast rather than waiting on a `TaskCompletionSource` that no reader can complete. +- Complete every queued operation exactly once: success, failure, or cancellation. +- Re-enable command state in `finally`, using a non-cancelable token when needed so canceled requests do not leave commands disabled. + +DON'T: + +- Don't execute mutating command bodies inline when they can re-enter the same state from another path. +- Don't rely only on dashboard disabled-state to prevent concurrency; enforce conflicts in the controller. +- Don't let operation continuations run inline while holding queue or state machinery. +- Don't await a queued operation from code already running inside the single reader loop, including event handlers published by that operation; the loop cannot service the nested operation until the current one returns. +- Don't leave a latched "loop started" flag set after loop failure or completion unless writers are also closed/fenced. + +## Command state and dashboard integration + +Command state should reflect controller state, not independently recomputed ad hoc checks. + +DO: + +- Define command metadata centrally: name, display name, description, confirmation, icon, highlighted state, arguments, validation, execute callback. +- Use `UpdateState` callbacks that ask the controller whether a command is enabled, disabled, or hidden. +- Disable commands affected by the active or queued operation; keep unaffected resource commands enabled when safe. +- Keep read-only diagnostic commands enabled during operations when they help users recover. +- Make cancel a special command: enable it only for affected resources in cancelable states and disable it once cancellation has been requested. +- Publish no-op resource updates to refresh command state before and after operations; command state is evaluated during snapshot publication. +- Add active-operation properties such as operation name, phase, status, target, and start time to affected resources. +- Return structured command results with success, canceled, and failure shapes. Include machine-readable diagnostics when agents will consume the result. + +DON'T: + +- Don't expose destructive commands without confirmation text and clear result data. +- Don't leave commands disabled after failures or cancellations. +- Don't make resource commands guess global controller state from resource snapshots alone. + +## Reentrant and idempotent operations + +Controller intents should be safe to retry after cancellation, process restart, partial external success, or drift. + +DO: + +- Re-read persisted and live state at operation execution time. +- Separate "forget local state" from "delete live external resource" APIs. +- Preserve explicit user overrides when resetting generated state; discard inferred state that should not survive a context change. +- Reconcile live children to the desired app model when the controller owns them. +- Treat location/context changes as workflows: validate, delete incompatible live resources if required, persist the new intent, reset state, then reprovision. +- Keep prompts and long user interactions outside the serialized operation queue. Queue only the apply/reconcile intent after the user completes the interaction. +- Coalesce background probes so at most one is queued or running. +- Serialize background drift checks through the same queue, but avoid disabling user commands for read-only probes unless the probe mutates state. + +DON'T: + +- Don't assume an operation that failed before completion left no external state. +- Don't overwrite user-provided values while refreshing dynamic command inputs. +- Don't use health checks for reconciliation side effects. + +## Dependency-aware fan-out + +Serialization at the controller boundary does not mean all internal work must be sequential. + +DO: + +- Fan out independent resources within a single intent only after the controller has accepted the operation. +- Preserve dependency ordering with per-resource completion tasks or another explicit dependency graph. +- Complete each per-resource task through a small set of paths: success, failure, or cancellation. +- Preserve an existing incomplete per-resource completion task when other components may already be awaiting it. +- Publish state to both visible resources and any surrogate/resources that represent the same external operation. +- Propagate broad state changes to child resources without recursion when graphs can be deep. +- Publish `ConnectionStringAvailableEvent` or other readiness signals after outputs/references are available, including for children that derive values from a parent. + +DON'T: + +- Don't make unrelated resources wait for the entire batch when their prerequisites are already done. +- Don't replace a task that existing waiters might be awaiting. +- Don't hide a precise terminal state with a generic failure state after a lower-level operation already published the detailed status. + +## Cancellation and drift + +Long-running controllers need explicit cancellation and drift behavior. + +DO: + +- Link operation cancellation to the caller token and expose a cancel command when the external operation can be interrupted. +- Mark resources as canceling before waiting for external cancellation to finish. +- Treat external cancellation races as expected; use persisted external state as the source of truth for final status. +- Run drift detection only after the aggregate environment is running. +- Mark drifted/missing resources with actionable states and expose commands that can reprovision, delete, forget state, or inspect live state. + +DON'T: + +- Don't queue unlimited background drift checks. +- Don't mark resources drifted when there is not enough persisted state to identify live resources. +- Don't block the serialized queue while waiting for a user to respond to a notification. + +## Testing + +DO test: + +- Concurrent mutating commands serialize when they target different resources. +- Independent resources still run concurrently when the controller is only a stateless lifecycle orchestrator. +- Conflicting active or queued operations fail fast even if dashboard command state has not refreshed. +- Command states disable and re-enable on success, failure, and cancellation. +- Cancel command availability for active, queued, nonaffected, and noncancelable resource states. +- Background probes are coalesced and do not flicker command states when they are read-only. +- Per-resource completion tasks unblock dependents and are not replaced while incomplete. +- Dynamic command inputs preserve user-entered values and re-enable after loading failures when custom input is allowed. +- Diagnostic commands return structured data without live external service dependencies in unit tests. +- Queue lifetime behavior: after the reader loop stops or the controller is disposed, new operations fail fast and already queued operations complete or cancel. + +DON'T: + +- Don't test controller concurrency only through dashboard snapshots; directly exercise the controller queue and command execution paths. +- Don't skip a regression test for accidental global serialization. Use a gated fake external client where all independent resources must rendezvous before any operation is released. +- Don't rely on live cloud or external services for ordinary controller unit tests. diff --git a/.agents/skills/hosting-integration-authoring/resources/archetype-deployment-target-publisher.md b/.agents/skills/hosting-integration-authoring/resources/archetype-deployment-target-publisher.md new file mode 100644 index 000000000..a0e670a09 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/archetype-deployment-target-publisher.md @@ -0,0 +1,133 @@ +# Archetype: deployment target or publisher integration + +Use this archetype for integrations that generate or customize deployment artifacts: Docker Compose, Kubernetes, Azure Container Apps, or similar targets. + +When the integration applies artifacts to a real target or owns infrastructure lifecycle, also read `deployment-production-readiness.md`. + +Representative upstream Aspire examples to compare against when adding a similar Toolkit integration: + +- `src/Aspire.Hosting.Docker/DockerComposeEnvironmentExtensions.cs` +- `src/Aspire.Hosting.Docker/DockerComposeServiceExtensions.cs` +- `src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentExtensions.cs` +- `src/Aspire.Hosting.Kubernetes/KubernetesServiceExtensions.cs` +- `src/Aspire.Hosting.Azure.AppContainers/*Extensions.cs` + +## Resource shape + +Deployment targets usually have: + +- An environment resource, for example `DockerComposeEnvironmentResource`, `KubernetesEnvironmentResource`, or `AzureContainerAppEnvironmentResource`. +- Infrastructure registration helpers such as `Add{Target}InfrastructureCore`. +- Per-resource `PublishAs{Target}` APIs that attach customization annotations. +- Pipeline steps that validate target presence and create deployment target resources. +- `DeploymentTargetAnnotation` instances linking compute resources to generated target resources. +- Container registry selection through the shared `AddContainerRegistry` and `WithContainerRegistry` APIs. + +## Production readiness checklist + +A deployment target is production-ready only when it handles the full target contract, not just a happy-path manifest. + +DO: + +- Define the ownership boundary up front: does the integration provision target prerequisites, or does it deploy only to existing infrastructure? Validate and document anything the user must create out of band. +- Model target-specific concepts as first-class APIs when users need them regularly: identity/service account, public/private access, network attachment, secret references, scaling, health probes, resource limits, regions/locations, ingress, and cleanup behavior. +- Keep raw manifest/customization callbacks as escape hatches, not as the only way to configure common production settings. +- Produce actionable preflight errors for missing CLIs, auth, selected project/subscription/cluster, enabled services/APIs, registry auth, and required permissions. +- Persist useful deploy outputs in deployment state and summary: resource IDs, service URLs, regions, target project/subscription/cluster, and access mode. +- Make apply/deploy idempotent and update-safe. Re-running deploy should converge the target resource rather than fail because it already exists. +- Decide and document the teardown story. If the target supports destroy/delete, wire it into the deployment lifecycle or clearly state that cleanup is external. +- Treat provider defaults as part of the API contract. If a target is private by default, preserve that default and expose explicit opt-in APIs for public access. + +DON'T: + +- Don't call a target production-ready when it only handles a single HTTP container with no secrets, identity, networking, service references, or cleanup. +- Don't make users edit generated YAML/JSON for common production features. +- Don't silently rely on ambient cloud context when a project/subscription/cluster can be specified in the AppHost model. +- Don't expose insecure convenience APIs that grant broad public access, owner/contributor permissions, or plaintext secrets. + +## Environment resources + +DO: + +- Name environment methods `Add{Target}Environment`. +- Register infrastructure/pipeline services idempotently. +- In run mode, return `CreateResourceBuilder(environmentResource)` when the environment has no runtime representation. +- In publish mode, add the environment resource to the model. +- Create default dashboard/deployment support only when publishing if it is target infrastructure. +- Reuse framework-level `WithContainerRegistry` instead of adding target-specific exported overloads with the same generated name. + +DON'T: + +- Don't surface publish-only environment resources in local run/dashboard. +- Don't create target infrastructure when no target is being used. +- Don't duplicate generic deployment APIs such as `WithContainerRegistry`; duplicate exports can collide in polyglot SDKs even when C# overload resolution works. + +## PublishAs APIs + +DO: + +- Use `PublishAs{Target}` for per-resource deployment customization. +- Constrain overloads to the resource shapes that can publish to that target, such as `IComputeResource`, `ProjectResource`, `ContainerResource`, or `ExecutableResource`. +- Return unchanged builder outside publish mode. +- In publish mode, ensure target infrastructure is registered and attach a customization annotation. + +DON'T: + +- Don't mutate the run resource for publish-only customization. +- Don't attach publish customizations outside publish mode. +- Don't silently accept a `PublishAs{Target}` call when no matching environment can exist. + +## Pipeline steps + +DO: + +- Use a marker singleton so global pipeline steps are registered once. +- Split global validation from per-environment target generation. +- Run validation only in publish mode. +- Emit clear errors when a resource has target-specific customization but no target environment exists. +- Use `requiredBy: WellKnownPipelineSteps.BeforeStart` or another precise ordering point. + +DON'T: + +- Don't register duplicate pipeline steps when multiple environments are added. +- Don't perform deployment-target validation during run mode. + +## Generated artifacts + +DO: + +- Expose customization callbacks over generated models such as Compose files, Kubernetes resources, Bicep resources, or Container Apps. +- Use placeholders for parameters, images, ports, secrets, and environment variables. +- Keep generated output deterministic. +- Snapshot generated output in tests when the artifact shape matters. +- Derive target protocol fields from endpoint transport metadata. For example, HTTP/2/h2c decisions should use `EndpointAnnotation.Transport`, not the URI scheme. +- Keep publish artifacts and deploy-time artifacts separate when deploy requires resolved values. +- Validate metadata keys and values against the target platform. Labels and annotations are not portable across deployment targets; Kubernetes-style keys such as `app.kubernetes.io/name` may be invalid for other targets. +- Add at least one regression test for values the framework generates automatically, such as a project resource's self-referential `HTTP_PORTS` target-port expression. + +DON'T: + +- Don't hardcode local host values into generated artifacts. +- Don't write secrets directly into generated Compose/YAML/Bicep output. +- Don't resolve secret parameters into plaintext deploy manifests. Use the target's secret reference mechanism, or make the limitation explicit and fail safely. +- Don't assume one deployment target's concepts map exactly to another target. +- Don't copy Kubernetes labels, annotations, probes, or object metadata into another target without checking that target's schema and validation rules. + +## Deployment tooling + +DO: + +- Wrap target CLIs behind a runner abstraction so tests can verify exact arguments without requiring live credentials. +- Pass CLI arguments as an argument list or process-spec collection where possible. A single pre-quoted command string is harder to test and easier to break across shells/platforms. +- Include the full CLI command path in tests, not just the leaf verb. For example, assert `gcloud run services replace`, not merely `services replace`. +- Capture stderr and include actionable failure output without logging secrets. +- Separate preflight checks from deploy actions. Check tool availability, authenticated account/context, target project/subscription/cluster, enabled target APIs/services, registry push/pull access, and required permissions before mutating resources. +- Validate a new deployment publisher against a real target before treating snapshots as sufficient. A useful smoke test builds and pushes an image, applies the generated artifact with the real CLI/API, reads the deployed endpoint/status back from the target, performs one protocol call, and then cleans up the deployed resource and images. +- Query the target system after deploy and verify the expected resource state, URL, and access mode. Do not infer success only from the AppHost process exit code or local CLI output. + +DON'T: + +- Don't rely on ambient shell quoting behavior that differs across platforms. +- Don't let deploy steps silently skip missing generated artifacts. +- Don't make the first mutating CLI/API call be where users discover missing authentication, disabled APIs, or missing registry permissions. +- Don't rely only on generated YAML/JSON snapshots or fake CLI tests for target acceptance; real targets often reject otherwise plausible metadata, label keys, protocols, IAM defaults, or resource names. diff --git a/.agents/skills/hosting-integration-authoring/resources/archetype-external-cloud-reference.md b/.agents/skills/hosting-integration-authoring/resources/archetype-external-cloud-reference.md new file mode 100644 index 000000000..844ada0c1 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/archetype-external-cloud-reference.md @@ -0,0 +1,43 @@ +# Archetype: external/cloud-reference service + +Use this archetype for services that Aspire references but does not run locally or provision directly, such as external APIs, SaaS services, OpenAI-style endpoints, or GitHub Models-style services. + +## Resource shape + +These resources are usually plain `Resource` types that implement `IResourceWithConnectionString` or another reference interface. They are not `ContainerResource` and not `AzureProvisioningResource`. + +They often contain: + +- Endpoint URI or base address. +- API key or token `ParameterResource`. +- Optional child model/deployment resources. +- Optional health check that calls a live external endpoint. + +## DO + +- Use `Add{Service}` or `Add{Provider}` for the parent external reference. +- Store secrets in `ParameterResource`s. +- Mark secret parameters as secret. +- Validate user-supplied credential parameters are marked secret before accepting them. +- Validate endpoint/base-address inputs at construction time, including absolute URI and allowed schemes such as `https`. +- Build connection strings and URIs with `ReferenceExpression`. +- Expose connection properties for endpoint, API key, model/deployment name, and URI as applicable. +- Put child methods like `AddModel` or `AddDeployment` on the parent builder. +- Make live health checks explicit, side-effect-free, and easy to disable if the service charges or rate-limits. +- Document prerequisites clearly, including required API keys and external accounts. + +## DON'T + +- Don't invent a local container or emulator unless the ecosystem provides a real one. +- Don't use Azure provisioning just because the service is cloud-hosted. +- Don't call live APIs during resource construction. +- Don't defer basic endpoint validation until connection property evaluation. +- Don't hide network calls in connection property generation. +- Don't write API keys to generated files or Dockerfile layers. +- Don't assume all external services can be validated in CI. + +## Testing + +Unit tests should verify resource shape, parameter wiring, connection expressions, child resource behavior, and health-check registration without calling the real service. + +Use fake HTTP servers only when behavior needs protocol validation. diff --git a/.agents/skills/hosting-integration-authoring/resources/archetype-language-executable-app.md b/.agents/skills/hosting-integration-authoring/resources/archetype-language-executable-app.md new file mode 100644 index 000000000..b47abacfa --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/archetype-language-executable-app.md @@ -0,0 +1,131 @@ +# Archetype: language executable app integration + +Use this archetype for non-.NET app workloads such as Python, Go, JavaScript, Node, Vite, Next.js, and similar language runtimes. + +Representative examples: + +- `src/CommunityToolkit.Aspire.Hosting.Python.Extensions/UvicornAppHostingExtension.cs` +- `src/CommunityToolkit.Aspire.Hosting.Golang/GolangAppHostingExtension.cs` +- `src/CommunityToolkit.Aspire.Hosting.JavaScript.Extensions/JavaScriptHostingExtensions.cs` + +## Resource shape + +Language apps usually derive from `ExecutableResource` and represent workload code, not infrastructure. + +They commonly support: + +- Local run with the developer's toolchain. +- Toolchain validation with `WithRequiredCommand`. +- Debugger integration. +- OTLP telemetry configuration. +- Run-mode setup siblings. +- Publish-time Dockerfile generation when no user Dockerfile exists. +- Container-file support for publish output and generated Dockerfile build stages. + +## Add methods + +DO: + +- Name APIs by runtime and app style, for example `AddPythonApp`, `AddPythonModule`, `AddGoApp`, `AddNodeApp`, `AddViteApp`, `AddNextJsApp`. +- Normalize paths relative to `builder.AppHostDirectory`. +- Use `Path.GetFullPath(path, builder.AppHostDirectory)` or the repository path-normalization helper used by the existing integration. +- Validate app directory, script, module, package path, or run script parameters. +- Configure default executable, working directory, args, endpoints, and icons. +- Add `WithRequiredCommand` checks for the executable or package manager the resource actually invokes. +- Use specialized Add APIs for materially different entrypoint shapes. For example, first-party Python uses separate script, module, executable, and Uvicorn APIs instead of one ambiguous catch-all. +- Add language-specific docs that explain run and publish behavior. + +DON'T: + +- Don't assume the current process working directory is the AppHost directory. +- Don't use `Directory.SetCurrentDirectory`. +- Don't silently ignore missing required toolchains when the app cannot run without them. +- Don't validate `node`, `python`, `go`, or another transitive tool when the configured command is really `npm`, `uv`, `go`, `python`, or a package manager wrapper. + +## Run-mode behavior + +DO: + +- Use local toolchain commands such as `python`, `go run`, `node`, package manager scripts, or framework dev servers. +- Add debugging support when the language ecosystem has a standard debugger. +- Add run-mode setup siblings for dependency restore, virtual environments, `go mod`, static analysis, or install commands. +- Mark setup siblings `.ExcludeFromManifest()`. +- Wire setup siblings with `WaitForCompletion`. +- Create setup siblings idempotently with `TryCreateResourceBuilder` when multiple fluent calls can request the same installer or virtual environment creator. +- Add parent relationships from setup siblings back to the app resource so the dashboard remains understandable. +- Use `OnBeforeStart` or equivalent final-model hooks when setup dependencies depend on which annotations were ultimately applied. +- Add development flags such as reload/watch only in run mode. + +DON'T: + +- Don't run dev-only setup or reload/watch behavior in publish. +- Don't include setup siblings in generated manifests. + +## Publish behavior + +DO: + +- Use `PublishAsDockerFile` or target-specific publish APIs to produce containerizable workloads. +- Generate a Dockerfile only if the app directory does not already contain one. +- Respect user-authored Dockerfiles. +- Validate publish-only prerequisites in build/publish pipeline steps. +- Use deterministic base image defaults, and allow explicit base image overrides. +- Use BuildKit secrets for private package/module credentials. +- Ensure generated images bind to `0.0.0.0` and use deployment-provided ports. +- Add pipeline dependencies when generated container files come from other resources. +- Keep Dockerfile generation mode-aware. Run-mode package-manager scripts, dev servers, reload flags, and local virtual environment paths should not leak into publish output. + +DON'T: + +- Don't persist credentials in Dockerfile layers. +- Don't overwrite user Dockerfiles or entrypoints. +- Don't fail `aspire start` because a publish-only Dockerfile prerequisite is missing. +- Don't emit Dockerfiles that depend on host-specific absolute paths. + +## Generated Dockerfile details + +Language integrations that publish as containers should make the generated Dockerfile predictable, secure, and easy to override. + +DO: + +- Generate multi-stage Dockerfiles when the language benefits from a separate SDK/build image and smaller runtime image. +- Detect language/runtime versions from project files first, then installed toolchains, then a documented default. Examples include `go.mod`, `package.json`, `Cargo.toml`, `pyproject.toml`, and similar ecosystem files. +- Log version-detection failures at debug level and continue to the next fallback instead of failing unrelated AppHost construction. +- Allow users to override build and runtime base images through explicit APIs or annotations, and keep those overrides in the app model. +- Include runtime essentials such as CA certificates when the generated app may make HTTPS requests. +- Carry `WithContainerFiles` inputs into generated images with explicit destination paths and pipeline dependencies. +- Document publish behavior in the README: generated Dockerfile shape, version detection order, default versions, base-image override APIs, and container-file support. +- Add focused tests for version detection, generated Dockerfile shape, base-image override annotations, and publish-mode argument differences. + +DON'T: + +- Don't use host-installed toolchain versions as the only source of truth for generated container images. +- Don't leak run-mode setup commands, watch/reload flags, local virtual environment paths, or host-specific absolute paths into generated Dockerfiles. +- Don't hide an unsupported framework/runtime publish path behind a generic language API; fail clearly or require a user-authored Dockerfile. + +## Framework and publish variants + +First-party Aspire language integrations prefer explicit variants when the runtime behavior materially changes. + +DO: + +- Use subtype resources or thin wrapper methods for framework variants such as Vite and Uvicorn when they add endpoints, TLS, dev-server flags, or publish behavior. +- Keep framework-specific run and publish behavior close to the Add/Publish method that introduces it. +- Provide publish helpers for distinct output shapes, such as static website, Node server, or package-script runtime. +- For C# callback-based publish options, provide a polyglot-friendly exported adapter with primitive/DTO parameters. + +DON'T: + +- Don't use one generic language app API when the framework changes endpoint binding, TLS, debugger, or production container semantics. + +## Mode-specific arguments and environment + +Arguments and environment often differ by mode. + +Examples: + +- Uvicorn should use target host/reload in run mode and `0.0.0.0` without reload in publish mode. +- Next.js should use dev script in run mode and standalone output in publish mode. +- Windows Python run mode may need `PYTHONUTF8=1`. + +Always branch explicitly when behavior differs. diff --git a/.agents/skills/hosting-integration-authoring/resources/archetype-overlay-configuration.md b/.agents/skills/hosting-integration-authoring/resources/archetype-overlay-configuration.md new file mode 100644 index 000000000..23c5364aa --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/archetype-overlay-configuration.md @@ -0,0 +1,36 @@ +# Archetype: overlay or configuration-object integration + +Use this archetype for integrations that add app-model configuration without directly producing a resource. Orleans-style APIs are the main pattern. + +This archetype is rare. Most integrations should create resources or configure existing resource builders. + +## Shape + +An overlay/configuration object may: + +- Be returned from an `Add*` method even though it is not an `IResourceBuilder`. +- Hold integration-specific model state. +- Be applied to workloads with `WithReference`, `AsClient`, or another projection method. +- Configure multiple resources without owning a runtime resource itself. + +## DO + +- Use this shape only when there is a strong model reason. +- Make the API clearly communicate that it creates configuration, not a runtime resource. +- Provide explicit wiring methods for consumers. +- Keep the object small and purpose-specific. +- Document how it affects run and publish behavior. +- Test that applying the overlay to resources produces expected environment, references, annotations, or generated configuration. + +## DON'T + +- Don't create fake resources just to satisfy the usual `Add*` return pattern. +- Don't return a plain object from a resource-producing API. +- Don't hide global model mutations behind a name that sounds like a local resource. +- Don't make overlay application order-dependent unless order is the feature. + +## Naming guidance + +Resource-producing methods should still return `IResourceBuilder`. Only non-resource overlay methods should return a custom model/configuration object. + +When reviewing, do not flag a non-resource return type by itself. First determine whether the API is intentionally an overlay and whether the wiring APIs are clear. diff --git a/.agents/skills/hosting-integration-authoring/resources/archetype-secret-provider.md b/.agents/skills/hosting-integration-authoring/resources/archetype-secret-provider.md new file mode 100644 index 000000000..603f48f08 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/archetype-secret-provider.md @@ -0,0 +1,53 @@ +# Archetype: credentials and secrets provider integration + +Use this archetype for integrations that broker secrets or credentials from an external provider into the app model, deployment pipeline, or consuming resources. + +Examples: + +- Secret manager resources that create or adopt provider-side projects/secrets. +- Integrations that turn external provider secrets into `ParameterResource`-like values. +- Build or deployment helpers that need provider access tokens while keeping consuming workloads secret-safe. + +## Resource shape + +DO: + +- Decide whether the provider resource represents a live local service, an external/cloud reference, or a provisioning/deployment control plane. +- Prefer non-container `Resource` types when the integration only models an external provider and does not run a local service. +- Model managed secret entries as child resources when the provider owns individual secrets. +- Keep provider URLs, organization/project IDs, access tokens, and cache paths as resource state or annotations; constructors should not call the provider. +- Use `ParameterResource`, `ReferenceExpression`, and provider-specific value providers to keep secrets late-bound. + +DON'T: + +- Don't model an external secret provider as a container-backed service unless a container actually runs locally. +- Don't resolve or fetch secrets during app-model construction. +- Don't leak access tokens or managed secret values into resource snapshots, logs, generated files, or manifests. + +## Provisioning and state + +DO: + +- Use pipeline steps or lifecycle hooks for provider mutations such as creating projects, creating/updating secrets, or recording provider IDs. +- Make provider operations idempotent: adopt existing projects/secrets when configured to do so, and track provider identifiers when required. +- Keep local bookkeeping caches deterministic and documented, and let users override cache locations for CI/shared scenarios. +- Clearly separate management credentials from workload credentials. +- Surface provider errors with actionable messages that include resource names and operation names but not secret values. + +DON'T: + +- Don't silently recreate secrets when provider identifiers are missing; explain whether the integration is adopting, creating, or updating provider state. +- Don't assume provider-side names are unique unless the provider guarantees uniqueness. + +## Relationships and waits + +DO: + +- Add explicit relationships to parameter resources, external provider endpoints, and resources whose values are used by the secret provider. +- Use `WaitFor` only when a local resource or external-service resource must be ready before provider operations can run. +- Document whether managed secrets are available during run, publish, deploy, or all modes. + +DON'T: + +- Don't hide provider dependencies behind name conventions. +- Don't make workloads wait for the secret provider if they only consume already-resolved environment values. diff --git a/.agents/skills/hosting-integration-authoring/resources/archetype-setup-and-migration-helper.md b/.agents/skills/hosting-integration-authoring/resources/archetype-setup-and-migration-helper.md new file mode 100644 index 000000000..c8b7bf616 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/archetype-setup-and-migration-helper.md @@ -0,0 +1,58 @@ +# Archetype: setup and migration helper integration + +Use this archetype for resources that prepare other resources or apply artifacts rather than running a long-lived service: database migrations, schema deployment, package/tool installation, code generation, model pulls, seed data, or one-shot setup commands. + +Examples: + +- Database migration tools such as Flyway. +- SQL project or DACPAC deployment helpers. +- Language/package setup siblings such as `npm install`, `go mod tidy`, `cargo fetch`, Maven/Gradle builds, or Perl module installation. +- Model setup resources such as pulling an Ollama model before an app starts. + +## Lifecycle shape + +DO: + +- Decide whether the helper is run-only, publish/deploy-only, or dual-mode. +- Model run-only setup as a sibling resource that the target resource waits for with `WaitForCompletion`. +- Mark run-only helpers `.ExcludeFromManifest()`. +- Use predictable helper names such as `{resource}-install`, `{resource}-migrate`, or `{resource}-model-pull`. +- Make helper creation idempotent when multiple fluent calls can request the same setup resource. +- Surface setup status through normal resource state, resource logs, and clear exceptions. + +DON'T: + +- Don't perform setup side effects in resource constructors. +- Don't hide long-running setup inside a health check. +- Don't rely on user call order when multiple setup helpers can compose. +- Don't publish run-only setup helpers into deployment artifacts. + +## Database and service-state setup + +DO: + +- Run service-state setup after the target service is reachable and healthy. +- For one-shot migration/deployment resources, explicitly model the target database/service dependency. +- Use `WaitFor` or `WaitForCompletion` so workloads do not start before required setup has completed. +- Make setup idempotent or provide an explicit skip/already-applied mechanism when reruns are expected. +- Keep deployment credentials secret and late-bound. + +DON'T: + +- Don't create databases, queues, schemas, or indexes as part of plain connection-string construction. +- Don't assume a migration helper should always run in publish/deploy; deployment targets may require a different artifact or pipeline step. + +## Artifact inputs + +DO: + +- Treat paths to migration folders, DACPACs, publish profiles, scripts, or generated files as user input. +- Resolve relative paths against the AppHost or documented project root consistently. +- Normalize paths for the current platform. +- Validate required files early when the helper cannot run without them. +- Use polyglot-friendly overloads for path-based configuration when generic metadata types or callbacks are C#-only. + +DON'T: + +- Don't use C# project metadata-only overloads as the only API shape when the integration exports polyglot APIs. +- Don't serialize local absolute paths into publish/deploy artifacts unless the target explicitly consumes local files. diff --git a/.agents/skills/hosting-integration-authoring/resources/archetype-sidecar-and-middleware.md b/.agents/skills/hosting-integration-authoring/resources/archetype-sidecar-and-middleware.md new file mode 100644 index 000000000..eccdb3325 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/archetype-sidecar-and-middleware.md @@ -0,0 +1,85 @@ +# Archetype: sidecar and middleware infrastructure integration + +Use this archetype for infrastructure that attaches to, discovers, or rewrites behavior for multiple resources: sidecars, component registries, service meshes, telemetry collectors, policy engines, and middleware processes. + +Examples: + +- Dapr-style sidecars plus component resources. +- OpenTelemetry Collector-style resources that route telemetry for multiple workloads. +- Middleware that adds environment variables, command-line arguments, generated config, or extra processes to other resources. + +## Model shape + +DO: + +- Separate the target workload resource from sidecar, component, or middleware resources. +- Use annotations to attach sidecar/middleware configuration to target resources. +- Use explicit component/reference annotations when the middleware consumes other resources. +- Use global lifecycle hooks or event subscriptions to discover all relevant annotations and materialize derived resources. +- Register global hooks idempotently so repeated fluent calls do not duplicate derived processes or subscriptions. +- Keep component resources inert until lifecycle processing. + +DON'T: + +- Don't force sidecars or components into `IResourceWithParent` when they are discovered and coordinated across the whole app model rather than owned by a single parent lifecycle. +- Don't require every consumer to manually wire repeated low-level environment variables if an annotation-driven middleware integration can derive them consistently. +- Don't let global scans mutate unrelated resources without a clear opt-in annotation. + +## Annotation-discovery pattern + +Use this pattern when fluent calls attach local configuration, but the integration needs the final app model before it can create sidecar processes, component manifests, generated files, or endpoint routing. + +DO: + +- Add small, typed annotations from `With{Feature}` or `With{Sidecar}` APIs instead of doing final wiring immediately. +- Register one global lifecycle hook or `IDistributedApplicationEventingSubscriber` idempotently, for example from `Add{Integration}`. +- In `BeforeStartEvent`, scan only resources with the opt-in annotation, collect component/reference annotations, and then materialize derived sidecar resources, generated config, or command arguments. +- Propagate relevant `WaitAnnotation`s from the target resource to the derived sidecar/process so dependency ordering remains correct. +- For resource-level aggregators such as local tunnel tools, store endpoint annotations on the aggregator resource and build config in `OnBeforeResourceStarted` after endpoint references are available. + +Reference patterns: + +- Dapr: `WithDaprSidecar` stores `DaprSidecarAnnotation`; `DaprDistributedApplicationLifecycleHook` subscribes to `BeforeStartEvent`, scans annotated resources, propagates waits, and materializes sidecar execution/config from the final model. +- Ngrok: `WithTunnelEndpoint` adds endpoint annotations to the `NgrokResource`; `OnBeforeResourceStarted` reads endpoint references and writes the ngrok config just before the container starts. + +## Lifecycle and ordering + +DO: + +- Use `BeforeStartEvent` or equivalent lifecycle hooks when sidecars or middleware need the final app model before derived resources can be created. +- Propagate relevant `WaitAnnotation`s from the target resource to derived sidecar/process resources. +- Make target workloads wait for sidecars or setup resources only when the sidecar is required for correct startup. +- Use generated files or component manifests only from lifecycle callbacks where all referenced values are available. +- Branch carefully for publish/deploy; local sidecar processes often become deployment metadata instead of runnable resources. + +DON'T: + +- Don't create sidecar processes from constructors. +- Don't resolve endpoint host/port values before allocation. +- Don't cache global derived state without an invalidation path when inputs can change between runs. + +## Configuration and generated files + +DO: + +- Prefer structured annotations for component metadata, secrets, endpoint values, and local paths. +- Generate config deterministically: stable filenames, stable ordering, no timestamps or random IDs unless required. +- Mount generated config files read-only where possible. +- Explain non-obvious lifecycle ordering in comments near the hook that materializes sidecars or config. + +DON'T: + +- Don't write secrets into generated config files unless the target tool has no secret-reference mechanism and the file is protected and run-only. +- Don't put deployment-only configuration in local run sidecar files. + +## Observability and telemetry collectors + +DO: + +- Auto-wire telemetry only for resources that explicitly opt in through existing telemetry/exporter annotations. +- Keep collector health checks lightweight unless dependents require the collector to be protocol-ready. +- Avoid making telemetry forwarding a hidden hard dependency of application startup unless requested. + +DON'T: + +- Don't route all resources through a collector simply because they implement `IResourceWithEnvironment`; require a meaningful annotation or opt-in. diff --git a/.agents/skills/hosting-integration-authoring/resources/archetype-tunnel-and-webhook-bridge.md b/.agents/skills/hosting-integration-authoring/resources/archetype-tunnel-and-webhook-bridge.md new file mode 100644 index 000000000..d8a81b13c --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/archetype-tunnel-and-webhook-bridge.md @@ -0,0 +1,78 @@ +# Archetype: tunnel and webhook bridge integration + +Use this archetype for local tools that connect external systems to local Aspire resources: tunneling services, webhook forwarders, callback bridges, and local CLIs that expose or forward endpoints. + +Examples: + +- Dev Tunnels resources that host a local tunnel executable and expose public endpoint resources. +- Ngrok-style tunnels that expose local endpoints to public URLs. +- Stripe CLI-style webhook listeners that forward external events to local app endpoints. + +## Shape and lifecycle + +DO: + +- Treat tunnel/webhook bridges as run-only unless there is a real deployment target story. +- Call `.ExcludeFromManifest()` for local-only bridges. +- Model the bridge as a resource so it has logs, status, endpoints, and dashboard visibility. +- Use explicit APIs such as `WithTunnelEndpoint` or `WithListen` to select which resource endpoint is exposed or used as the forwarding target. +- Validate user-provided URLs, endpoint names, ports, and auth tokens at API boundaries. +- Make bridge resources wait for the target resource when forwarding requires the target to be ready. +- If the bridge exposes one public endpoint per target endpoint, model those endpoints as child or facade resources rather than overloading the owner resource with many unrelated URLs. +- Read `custom-lifecycle-and-facade-resources.md` when the bridge creates resources whose lifecycle, endpoints, or dashboard state are driven by the bridge process or an external service. +- When centralizing bridge start/ready/stopped behavior, prefer the stateless lifecycle-orchestrator variant in `archetype-controller-reconciler.md`. Add a serialized queue only if the bridge gains shared mutable controller state, command/cancel workflows, or drift/reconcile behavior. + +DON'T: + +- Don't classify tunnel/webhook bridges as external cloud references; they are local run resources that connect to external services. +- Don't expose every endpoint by default unless the bridge is explicitly designed to do that. +- Don't include local tunnels, webhook listeners, or public callback URLs in publish manifests by default. +- Don't globally serialize independent tunnel resources just because they use the same CLI or login flow; use a narrower concurrency primitive for the shared part. + +## Endpoint handling + +DO: + +- Use endpoint references to select the target endpoint and preserve app-model structure. +- Read allocated endpoint values only from run-mode lifecycle callbacks where allocation has happened. +- Account for host/container networking differences. For example, a tunnel container may need `host.docker.internal` to reach a host-bound local endpoint on Windows or macOS. +- Provide dashboard URLs for bridge control UIs and, when available, discovered public tunnel URLs. +- Return `EndpointReference` values for public bridge URLs so consumers can use normal environment and service discovery flows. + +DON'T: + +- Don't use host-process `localhost` blindly from inside a container. +- Don't read `EndpointReference.Host`, `Port`, or `Url` in publish-mode callbacks. +- Don't require consumers to copy public URLs from logs or the dashboard. + +## Runtime value extraction + +Some bridge tools only reveal runtime values such as webhook signing secrets or public tunnel URLs through stdout/stderr. + +DO: + +- Prefer documented files, APIs, or command options over log parsing when available. +- If log parsing is required, keep it run-only and bounded. +- Parse only documented or observed raw formats; include a comment with an example of the raw line being parsed. +- Add cancellation and timeout behavior so missing output does not hang startup indefinitely. +- Redact extracted secrets in logs and exceptions. +- Expose extracted secrets through deferred values or environment callbacks so consumers can wait for the bridge. + +DON'T: + +- Don't parse logs in constructors. +- Don't treat a missing extracted value as success when consumers require it. +- Don't write extracted secrets into publish/deploy artifacts. + +## Generated bridge configuration + +DO: + +- Generate tunnel/proxy configuration from `OnBeforeResourceStarted` or another pre-start lifecycle hook when the container/CLI needs the file before launch. +- Use deterministic paths under an AppHost-owned tool folder unless the user explicitly supplies a path. +- Mount generated configuration into the container and pass explicit command-line arguments to consume it. + +DON'T: + +- Don't generate config files during app-model construction. +- Don't leave generated config in ambiguous temp locations that are hard for users to inspect. diff --git a/.agents/skills/hosting-integration-authoring/resources/compatibility-and-deprecation.md b/.agents/skills/hosting-integration-authoring/resources/compatibility-and-deprecation.md new file mode 100644 index 000000000..67f40a659 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/compatibility-and-deprecation.md @@ -0,0 +1,73 @@ +# Compatibility and deprecation lifecycle + +Hosting integrations evolve across releases. Keep public API changes intentional, documented, and friendly to generated SDKs. + +## Experimental APIs + +DO: + +- Mark unstable APIs with `[Experimental("ASPIRE...")]`. +- Use unique diagnostic IDs. +- Apply experimental attributes consistently to dependent types and members. +- Include clear docs for known limitations. + +DON'T: + +- Don't expose new deployment, publishing, pipeline, or language-runtime APIs as stable by accident. +- Don't reuse an existing experimental diagnostic ID. + +## Obsolete APIs + +DO: + +- Use `[Obsolete]` for APIs that have shipped stable and need a migration path. +- Provide actionable obsolete messages. +- Keep obsolete APIs hidden from generated SDKs when they should not appear in new AppHost languages. +- Use `[EditorBrowsable(EditorBrowsableState.Never)]` when the API should be hidden from IntelliSense but retained for compatibility. +- Add tests or API review notes for important compatibility shims. + +DON'T: + +- Don't add obsolete shims for preview-only APIs that can still be renamed or removed directly. +- Don't leave obsolete APIs exported to generated SDKs without a reason. +- Don't remove stable public APIs without an explicit breaking-change decision. + +## Deprecating integrations + +When an entire integration is being retired, apply a consistent soft-deprecation path: + +- Mark public API `[Obsolete]`. +- Add a README warning. +- Hide the package from `aspire add` discovery. +- Remove or disable integration-specific automation only when it is no longer needed. +- Suppress resulting warnings in first-party consumers. +- Keep one final obsolete release when required. + +## Renames and replacements + +DO: + +- Prefer additive replacements for stable APIs. +- Keep old APIs delegating to new APIs when compatibility requires it. +- Preserve behavior unless the breaking change is intentional. +- Update C# docs, generated SDK docs, README examples, and tests together. + +DON'T: + +- Don't change parameter meaning while keeping the same name. +- Don't change endpoint names, connection property names, environment variable names, or generated method names casually. +- Don't use `MethodName` or export ID changes that break generated SDK callers unless that is the explicit migration. + +## API baselines + +Generated API baseline files under `*/api/*.cs` track shipped public surface. + +DO: + +- Leave API baseline files untouched during ordinary implementation work unless explicitly regenerating baselines. +- Review new public APIs for naming, versionability, nullability, experimental status, and polyglot projection. + +DON'T: + +- Don't manually edit API baseline files. +- Don't use baseline absence as proof that a public API is private or safe to change. diff --git a/.agents/skills/hosting-integration-authoring/resources/connection-properties.md b/.agents/skills/hosting-integration-authoring/resources/connection-properties.md new file mode 100644 index 000000000..8062d16b1 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/connection-properties.md @@ -0,0 +1,62 @@ +# Connection properties + +Resources that can be referenced by applications should expose structured connection metadata with `IResourceWithConnectionString.GetConnectionProperties`. + +## Standard properties + +Use these property names when available: + +| Property | Use | +| --- | --- | +| `Host` | Hostname or IP endpoint | +| `Port` | Port number | +| `Username` | User name for authentication | +| `Password` | Password or secret reference | +| `Uri` | Protocol URI, for example `postgres://user:password@host:port/database` | +| `DatabaseName` | Database or logical subresource name | +| `JdbcConnectionString` | JDBC connection string when the ecosystem has a documented JDBC format | +| `Azure` | Only for resources that can be Azure-hosted or non-Azure based on mode/context | + +Do not define `Azure` for resources that do not have an Azure/non-Azure split such as `IsContainer`, `IsEmulator`, or `InnerResource`. + +## Expressions + +DO: + +- Use `ReferenceExpression` or `ReferenceExpressionBuilder`. +- Use URI formatting for credentials and path segments, for example `{PasswordParameter:uri}`. +- Keep connection values late-bound. +- Expose both server-level and child-level properties when child resources represent different connection targets. +- Use `UriExpression` for URI-shaped values. + +DON'T: + +- Don't eagerly resolve endpoint host/port in constructors. +- Don't concatenate secrets into plain strings outside reference expressions. +- Don't omit optional properties when the resource actually has them. +- Don't invent non-standard property names when a common name exists. + +## Parent-child resources + +Child resources should implement `IResourceWithParent` and inherit parent connection properties. + +Use parent property combination and override child-specific values: + +```csharp +IEnumerable> IResourceWithConnectionString.GetConnectionProperties() => + Parent.CombineProperties( + [ + new("DatabaseName", ReferenceExpression.Create($"{DatabaseName}")), + new("Uri", UriExpression), + new("JdbcConnectionString", JdbcConnectionString), + ]); +``` + +## Dual-mode resources + +When a resource can run as a container/emulator or publish as Azure/cloud: + +- Connection properties must work in every mode. +- Mode-specific values should branch through `IsContainer`, `IsEmulator`, or `InnerResource`. +- Child resources must preserve the same property names across modes. +- The `Azure` property should be `"true"` or `"false"` only when that distinction is meaningful to consumers. diff --git a/.agents/skills/hosting-integration-authoring/resources/cross-platform-tooling.md b/.agents/skills/hosting-integration-authoring/resources/cross-platform-tooling.md new file mode 100644 index 000000000..197341a29 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/cross-platform-tooling.md @@ -0,0 +1,79 @@ +# Cross-platform tooling + +Hosting integrations should work on Windows, macOS, Linux, and containers unless a platform limitation is explicit. + +## Paths and working directories + +DO: + +- Resolve user-supplied app paths relative to `builder.AppHostDirectory`. +- Normalize paths for the current platform. +- Use `Path.Combine`, `Path.GetFullPath`, and existing path-normalization helpers. +- Keep working directories explicit on executable resources. + +DON'T: + +- Don't rely on the current process working directory. +- Don't call `Directory.SetCurrentDirectory`. +- Don't hardcode path separators. +- Don't assume filesystem case sensitivity. + +## Required tools + +DO: + +- Use required-command/toolchain checks for language runtimes and external CLIs needed in run mode. +- Include installation/help URLs in required-command guidance. +- Make publish-only tool requirements fail during publish/build validation, not during local run. +- Distinguish Docker-required features from toolchain-only features. +- For deployment targets, check external CLI authentication/context separately from CLI installation, and include the selected project/subscription/cluster/region in diagnostics. +- Check container registry authentication and push/pull access before building large images when the target requires a registry. + +DON'T: + +- Don't fail app model construction because an optional run-mode tool is missing before the user uses that feature. +- Don't assume `docker`, `go`, `python`, `node`, package managers, `helm`, or cloud CLIs are available. +- Don't rely on ambient cloud CLI state when the AppHost model provides an explicit target context. + +## Shell and arguments + +DO: + +- Prefer structured argument APIs over shell command strings. +- Preserve argument boundaries. +- Quote only at the shell boundary. +- Document command argument ordering when the underlying tool requires it. + +DON'T: + +- Don't concatenate user input into shell commands. +- Don't rely on Bash-specific syntax for Windows paths or PowerShell-specific syntax for Unix paths. +- Don't pass secrets as command-line args when environment variables or secret files work. + +## Platform-specific behavior + +DO: + +- Use `OperatingSystem.IsWindows()` or similar guards for platform-specific environment variables, encodings, or commands. +- Handle Windows path length, file locking, and executable permission differences. +- Use UTF-8 mode or environment settings when a language runtime needs it on Windows. +- Test path-heavy features on at least one non-Unix platform when practical. + +DON'T: + +- Don't assume file deletion succeeds immediately on Windows after a process exits. +- Don't assume executable permission bits are meaningful on Windows. +- Don't assume localhost binding behavior is identical across runtimes and platforms. + +## Containers and host networking + +DO: + +- Use Aspire endpoint abstractions instead of hardcoded hostnames. +- Use internal container endpoints for container-to-container traffic when the service requires different advertised addresses. +- Keep host-process and container-process connection strings distinct when needed. + +DON'T: + +- Don't assume `localhost` from inside a container points at the host. +- Don't publish host-only endpoint values into container environments. diff --git a/.agents/skills/hosting-integration-authoring/resources/custom-lifecycle-and-facade-resources.md b/.agents/skills/hosting-integration-authoring/resources/custom-lifecycle-and-facade-resources.md new file mode 100644 index 000000000..505f8583f --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/custom-lifecycle-and-facade-resources.md @@ -0,0 +1,140 @@ +# Custom lifecycle and facade resources + +Use this guidance when an integration resource is visible in the Aspire model but its lifecycle is not directly managed as a normal project, executable, or container instance. + +Representative examples: + +- A tunnel executable that hosts an external service session and creates synthetic child resources for each public forwarded endpoint. +- A local CLI bridge that creates remote state before the host process can start. +- A synthetic endpoint resource that exists only so other resources can reference a URL discovered at runtime. + +## When this pattern is appropriate + +Custom lifecycle resources are an escape hatch. Prefer normal `ProjectResource`, `ExecutableResource`, `ContainerResource`, setup sibling, or `IResourceWithoutLifetime` shapes first. + +DO: + +- Use a custom lifecycle/facade resource only when users need dashboard visibility, resource logs, endpoint references, waits, or service discovery for something that DCP does not manage directly. +- Keep the primary owner as a normal resource when possible, such as an `ExecutableResource` running the long-lived CLI. +- Model each externally addressable thing as a separate facade resource only when users can reason about it independently, such as a tunnel port, public callback URL, generated proxy endpoint, or inspection endpoint. +- Implement standard interfaces on facade resources, such as `IResourceWithEndpoints`, `IResourceWithServiceDiscovery`, or `IResourceWithWaitSupport`, instead of making consumers special-case concrete types. + +DON'T: + +- Don't create facade resources only to expose implementation details. +- Don't use manual lifecycle events when a normal resource plus annotations would work. +- Don't make a facade resource perform work from its constructor. + +## Lifecycle ownership + +The owner resource should drive lifecycle for its facades. + +DO: + +- Create external state in `OnBeforeResourceStarted` when the owner process needs that state before launch. +- Wait for target endpoint allocation before creating external endpoint/port state. +- Reconcile external child state to the app model if the integration owns it, for example deleting unmodeled forwarded ports. +- Use `OnResourceReady` on the owner to mark facade resources running after the owner is healthy and the external state is observable. +- Use `OnResourceStopped` on the owner to mark facade resources stopped and their URLs inactive. +- Keep remote parent state deletion explicit. If the external service expires or persists state by design, do not delete it on AppHost stop unless the public API promises session-scoped cleanup. +- Use the stateless lifecycle-orchestrator variant in `archetype-controller-reconciler.md` when several facade callbacks need the same helper logic but remain independent. +- Use the serialized controller/reconciler variant only when lifecycle callbacks, commands, background probes, and multiple resources must coordinate shared mutable state. + +DON'T: + +- Don't manually publish `ResourceReadyEvent`; let Aspire readiness drive it. +- Don't mutate external service state from health checks. Health checks should observe status and cache what lifecycle callbacks need. +- Don't assume `WithParentRelationship` controls lifecycle; it is only visual/semantic grouping. + +## Persistence and ownership contracts + +Executable, project, and container resources can use Aspire lifetime annotations. External services may also have their own persistence semantics, such as remote tunnels that expire after inactivity. + +DO: + +- Decide whether the process/container lifetime is session-scoped, persistent, matched to another resource, or tied to a parent process. +- Use the standard lifetime APIs (`WithSessionLifetime`, `WithPersistentLifetime`, `WithLifetimeOf`, or `WithParentProcessLifetime`) instead of custom annotations when they express the behavior. +- Document the difference between the local owner process lifetime and any remote state lifetime. +- Keep cleanup behavior aligned with the public API. If the integration leaves remote state behind for reuse or service-managed expiration, say so and do not imply AppHost stop deletes it. +- Detect circular resource lifetime references when creating custom lifetime-like behavior. + +DON'T: + +- Don't delete persistent external resources on stop unless the API explicitly requested session cleanup. +- Don't make a facade outlive its owner in the dashboard unless the facade is independently reachable and modeled as persistent. +- Don't invent new lifetime modes for normal executable/container/project resources when `PersistenceAnnotation` already covers the behavior. + +## Manual status and events + +When a facade has no DCP process/container, use resource notifications to keep the dashboard and waiters honest. + +DO: + +- Set an initial state with a clear resource type and useful properties. +- Publish `Starting`, `Running`, and terminal state snapshots through `ResourceNotificationService`. +- Publish `BeforeResourceStartedEvent` for a facade only when other integrations need a pre-start hook for that facade. +- Publish `ResourceStoppedEvent` for a facade only after publishing the terminal snapshot that represents the stopped state. +- Mark facade URLs inactive when the owner stops. +- Store only observed, non-authoritative status on the resource object; refresh it from the external service during health checks or lifecycle callbacks. + +DON'T: + +- Don't publish lifecycle events out of order. +- Don't leave facade resources in `Running` after the owner has stopped. +- Don't use a successful-looking state when the external service failed to create or expose the facade. +- Don't add a global controller queue for facade resources that are independently owned by different parent resources. + +## Runtime endpoint allocation + +Some facades expose endpoints that are unknown until an external service returns a host or URL. + +DO: + +- Add an `EndpointAnnotation` during model construction so consumers can hold an `EndpointReference`. +- Set `EndpointAnnotation.AllocatedEndpoint` only after the real runtime endpoint is known. +- Publish `ResourceEndpointsAllocatedEvent` once, the first time the facade endpoint is allocated. +- If the external endpoint changes on a later restart, update the resource snapshot URL directly instead of republishing the one-time allocation event. +- Set exceptions on endpoint allocation snapshots when external setup fails so dependent `EndpointReference` resolution fails clearly. +- Provide helper APIs such as `GetEndpoint(...)` that return the facade endpoint, and use custom `EndpointReference.ErrorMessage` values for missing associations. + +DON'T: + +- Don't read or allocate runtime endpoint values in publish mode. +- Don't publish local `localhost` URLs for externally hosted endpoints that cannot be reached through localhost. +- Don't require consumers to parse logs or dashboard URLs to discover the facade endpoint. + +## Process, CLI, auth, and interaction + +External CLIs often need toolchain validation and user authentication before resource startup. + +DO: + +- Use structured process arguments (`ProcessStartInfo.ArgumentList`) instead of shell command strings. +- Wire cancellation to kill child process trees for short-lived CLI operations. +- Drain stdout and stderr concurrently. +- Parse documented JSON output when available instead of human-readable logs. +- Validate required CLI versions before the first lifecycle step that depends on the CLI. If that step runs before the global required-command hook, call `IRequiredCommandValidator` explicitly with a `RequiredCommandAnnotation`. +- Coalesce login prompts and other interactive operations so concurrent resources do not prompt multiple times. +- Use `IInteractionService` only when available, and provide configuration overrides for non-interactive environments. +- Redact tokens, public-write secrets, and auth details in logs and dashboard properties. + +DON'T: + +- Don't assume an external CLI is installed or logged in. +- Don't block startup indefinitely waiting for interactive auth. +- Don't pass secrets on command lines when environment variables, files, or provider auth can be used. + +## Publish/deploy behavior + +Most custom lifecycle/facade resources are local run-only resources. + +DO: + +- Call `.ExcludeFromManifest()` for run-only owner and facade resources. +- Make publish-mode reference injection a no-op when the facade has no deployment story. +- If a deployment story exists, model it explicitly as a deployment target/provider feature rather than serializing local runtime URLs. + +DON'T: + +- Don't write public tunnel URLs, local callback URLs, allocated ports, or auth state into manifests. +- Don't let run-only facade resources appear as deployable infrastructure by accident. diff --git a/.agents/skills/hosting-integration-authoring/resources/dashboard-ux.md b/.agents/skills/hosting-integration-authoring/resources/dashboard-ux.md new file mode 100644 index 000000000..9971e860c --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/dashboard-ux.md @@ -0,0 +1,83 @@ +# Dashboard UX + +Hosting integrations shape what users see in the Aspire dashboard. Good dashboard UX makes resources understandable without exposing implementation details. + +## Icons and display + +DO: + +- Set an icon with `WithIconName` when a clear existing icon matches the resource. +- Use names and relationships that make parent-child and companion resources clear. +- Keep hidden setup/deployment-only resources out of the run model or manifest as appropriate. + +DON'T: + +- Don't show internal setup, deployment-only, or implementation resources as first-class dashboard resources unless users need to act on them. +- Don't use misleading icons or generic names when a clearer resource identity exists. + +## URLs + +DO: + +- Expose primary user-facing URLs. +- Use `WithUrlForEndpoint` to adjust display text or display location. +- Put diagnostic, health, metrics, or secondary URLs in details-only display. +- Expose admin companion URLs when users are expected to open them. + +DON'T: + +- Don't flood the resource summary with internal endpoints. +- Don't expose health check endpoints as primary app URLs. + +## Resource commands + +Resource commands are user actions. They should be safe, clear, cancellable, and observable. + +DO: + +- Use command names and display names that describe the action. +- Validate command preconditions and return clear disabled/unavailable states when possible. +- Honor cancellation tokens. +- Log useful action progress to resource logs. +- Avoid commands that require hidden global state. +- For controller/reconciler integrations, derive command enabled/disabled/hidden state from the controller's active and queued operation state. +- Keep read-only diagnostic commands available during mutating operations when they help recovery. +- Return structured command results for operations that agents or users need to inspect. + +DON'T: + +- Don't add destructive commands without clear naming and safeguards. +- Don't hide command failures behind success-shaped results. +- Don't log secrets from command arguments or results. +- Don't rely on dashboard command disabling as the only concurrency guard; enforce conflicts in the controller too. + +## Notifications and logs + +DO: + +- Use resource notifications for state transitions users need to see. +- Use resource logger services for generated setup or command logs. +- Keep logs actionable and redact secrets. +- For synthetic/facade resources, publish clear initial, starting, running, and stopped states because there is no DCP process to do it automatically. +- Mark URLs inactive when a manually managed owner resource stops. + +DON'T: + +- Don't emit noisy informational logs for every callback when they do not help users. +- Don't complete resource logs early if setup work is still running. +- Don't leave dashboard URLs active for endpoints that are no longer forwarded or reachable. + +## Admin companions + +Admin/dev companions should feel attached to their parent service. + +DO: + +- Add parent/custom relationships. +- Use clear companion names. +- Exclude companions from publish/deploy output unless intentionally supported. +- Prefer singleton-style companion behavior when the tool manages multiple parent instances. + +DON'T: + +- Don't make users discover an admin UI by inspecting a random standalone container. diff --git a/.agents/skills/hosting-integration-authoring/resources/deployment-production-readiness.md b/.agents/skills/hosting-integration-authoring/resources/deployment-production-readiness.md new file mode 100644 index 000000000..cec21af81 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/deployment-production-readiness.md @@ -0,0 +1,298 @@ +# Production-ready deployment target integrations + +Use this resource with `archetype-deployment-target-publisher.md` when a deployment integration is expected to create, update, or delete real infrastructure, not just emit artifacts. + +Production readiness is not one feature. It is a set of explicit contracts for ownership, state, prerequisites, security, service references, update behavior, teardown, tests, and docs. + +## Aspire concepts to map + +Start every deployment target design by mapping each Aspire concept to target-native concepts, an explicit unsupported error, or a documented external prerequisite. Do this before adding APIs. + +| Aspire concept | What it means in Aspire | Deployment target mapping questions | +| --- | --- | --- | +| Resource name | Stable graph identity used by references, dashboard, logs, and generated output. | What physical name is created? How is it normalized? Is it unique per environment? Where is the original Aspire name recorded for traceability? | +| Physical name | Provider-visible name that may differ from the Aspire name. | Does the target have length/character constraints? Is the name deterministic? How are collisions handled? | +| Compute resource | `ProjectResource`, `ContainerResource`, `ExecutableResource`, or another `IComputeResource` that becomes a workload. | What target workload primitive is created: service, deployment, job, function, task, container app, pod? Which compute resource kinds are supported or rejected? | +| Compute environment | `IComputeEnvironmentResource` receives compute resources during publish/deploy. | What target context is represented: project, subscription, cluster, namespace, region, environment, account? Is it run-visible or publish/deploy-only? | +| Container image | Built image or existing image selected through container annotations. | Where is the image pushed? How are tags chosen? Is digest pinning supported? Who owns pushed images and cleanup? | +| Container registry | Shared `IContainerRegistry` model and `WithContainerRegistry`. | How does the target authenticate/pull images? Is registry creation external or managed? How are registry credentials checked? | +| Endpoints | Named endpoints with scheme, transport, host port, target port, and reference endpoint semantics. | Which target port is exposed? Is TLS terminated by the platform? Does transport map to protocol fields such as HTTP/2/h2c? Which endpoints are public, private, health-only, or unsupported? | +| Service discovery | `WithReference` injects logical service endpoints into consumers. | Does the target provide stable DNS/service binding before deploy? If URLs are post-deploy outputs, do cross-service refs fail, use native bindings, or require a two-phase update? | +| Connection strings/properties | Structured resource values passed to consumers. | Can the target express them as env vars, service bindings, secrets, config maps, managed identity references, or provider-native dependency bindings? | +| Environment variables | Literal and structured values emitted through environment callbacks. | Which values can be resolved at publish, deploy, or runtime? Which values must stay as target-native references? | +| Parameters | User-provided or generated configuration values. | Are non-secret parameters emitted as literals/placeholders? Are secret parameters rejected, mapped to native secrets, or ingested through a secure managed secret path? | +| Secrets | Secret `ParameterResource` and secret-bearing connection strings. | What is the native secret store/reference shape? Who creates secret versions? Which runtime identity gets access? How is plaintext prevented from entering files, logs, state, and CLI args? | +| Identity | Deployer identity and runtime workload identity are different. | Which identity applies infrastructure? Which identity does the workload run as? How are least-privilege roles assigned or validated? | +| Access mode | Whether endpoints are private, internal, authenticated, or public. | What is the secure default? What API explicitly opts into public access? How are caller identities/IAM represented? | +| Networking | Internal/external endpoints, private egress, VPCs, DNS, ingress. | Does the target need VPC connectors, private endpoints, namespaces, subnets, or firewall rules? Are these existing prerequisites or managed resources? | +| Health checks/probes | Aspire health and readiness influence startup and dashboard. | What target readiness/liveness/startup probes are generated? Which endpoints are health-only and excluded from references? | +| Resource limits/scaling | CPU/memory, replicas, min/max scale, concurrency. | Which target knobs map to Aspire concepts? Which target constraints need validation? What defaults are preserved? | +| Arguments/command | `IResourceWithArgs` and container entrypoint/args. | How are args preserved without shell quoting issues? Does the target distinguish command from args? | +| Files/config | Generated config files, `WithContainerFiles`, manifests. | Are files baked into images, mounted, uploaded, or converted to provider config objects? Are secrets excluded? | +| Relationships/waits | `WithReference`, parent/child, wait annotations, dependencies. | Which relationships affect deployment order, IAM, service binding, or cleanup order? Which are dashboard-only and should not affect target output? | +| Parent/child resources | Databases, queues, topics, deployments, containers, models. | Are child resources target objects, app-level config, or unsupported? Who owns their lifecycle and deletion? | +| Dashboard URLs/commands | User-facing resource links and actions. | Which deployed URLs are summary links? Which are details-only? Are commands safe and target-aware? | +| Deployment state | Persisted outputs from prior deploys. | What IDs, URLs, ownership, image tags/digests, and schema version are stored? How is stale state handled? | +| Destroy/cleanup | Removing deployed resources. | Which target objects are managed and safe to delete? Which are retained? What order and retry behavior is required? | +| Polyglot exports | TypeScript/Python/etc. AppHosts use generated SDKs. | Are APIs callback-free or adapter-backed where needed? Are DTOs/exported models ATS-compatible? Are generated names stable and collision-free? | + +DO: + +- Include this mapping in design notes, review comments, or implementation plans for new deployment targets. +- For every unsupported mapping, fail with an actionable message and add a test. +- Prefer target-native mechanisms for concepts the target owns, such as service bindings, identity, secrets, probes, and traffic. + +DON'T: + +- Don't silently drop an Aspire concept because the target lacks a direct equivalent. +- Don't collapse structured Aspire values into strings before deciding which lifecycle phase can resolve them. +- Don't expose target-specific escape hatches as the only way to satisfy common Aspire concepts. + +## Maturity levels + +Classify the target before designing APIs: + +| Level | Contract | Examples | +| --- | --- | --- | +| Artifact publisher | Generates artifacts only. Another tool applies them. | Kubernetes YAML export, Docker Compose files | +| Existing-target deployer | Applies artifacts to an existing target and registry. Preconditions are validated, not provisioned. | Cloud Run deploy to an existing project/repository | +| Provisioning deployer | Creates or updates prerequisite infrastructure and then deploys workloads. | Creating registries, service accounts, APIs, namespaces, secret stores | +| Reconciler/operator | Continuously or repeatedly converges desired state, supports drift, commands, and deletion. | Controller-backed cloud or cluster integration | + +DO: + +- State the level in XML docs, README, tests, and review notes. +- Keep lower levels honest. If prerequisites are external, validate and document them instead of pretending they are managed. +- Move to a higher level only when the integration owns state, permissions, update, and cleanup semantics for that level. + +DON'T: + +- Don't auto-create chargeable, policy-governed, or shared infrastructure from an artifact publisher without an explicit provisioning contract. +- Don't call an existing-target deployer production-ready if users must edit generated artifacts for common production settings. + +## Ownership and deployment state + +Every real target object must have an ownership model. + +Use these ownership categories: + +- **Managed**: Aspire created it and may update/delete it. +- **Referenced existing**: User supplied it; Aspire may read/validate but must not mutate unless a specific API says so. +- **Generated deployment artifact**: Created for a deploy run, such as image tags or manifests. Cleanup policy must be explicit. +- **Shared prerequisite**: Used by multiple apps/environments. Default to retain. + +DO: + +- Record stable target identifiers in deployment state: project/subscription/cluster, region, resource ID/name, URL, identity, access mode, and ownership. +- Include a state schema/version so future code can migrate or ignore old entries safely. +- Use target labels/tags/annotations to mark Aspire-managed resources when the platform supports it, but never rely on labels alone for deletion. +- Prefer explicit `AsExisting`, `PublishAsExisting`, `WithExisting*`, or `WithManaged*` APIs when ownership is ambiguous. +- Treat app model names and physical target names separately. Store both when they differ. + +DON'T: + +- Don't delete or overwrite resources discovered only by matching names. +- Don't change a referenced existing resource's IAM, networking, lifecycle, or data-plane state unless the API name and docs make that mutation explicit. +- Don't store secrets in deployment state. + +## Pipeline shape + +A production deployer usually needs these phases: + +1. Model validation: app-model shape, required environment, unique physical names, unsupported references. +2. Preflight: tool/SDK availability, authenticated account, target context, enabled APIs, registry access, permissions, quota/policy checks where possible. +3. Build/push: image build and registry push using structured registry annotations. +4. Prepare artifacts: emit deploy-time artifacts with late-bound values resolved only when safe. +5. Apply: create/update target resources idempotently. +6. Verify: query provider state and confirm ready URL/status/access mode. +7. Record: persist target outputs and ownership in deployment state. +8. Destroy/cleanup: delete only managed resources according to the ownership model. + +DO: + +- Keep preflight non-mutating. +- Make apply idempotent and retry-safe. Re-running the same deploy should converge the target. +- Verify against the provider after apply; do not infer success from command exit alone. +- Save outputs only after verification succeeds. +- Use structured process arguments or SDK calls; quote only at display/logging boundaries. + +DON'T: + +- Don't perform cloud API calls during app-model construction. +- Don't let the first mutating call be where users discover missing permissions, disabled APIs, or invalid context. +- Don't leave partial failures success-shaped. Surface the failed operation, resource, target context, and sanitized provider error. + +## Publish assets vs deploy actions + +Design `aspire publish` and `aspire deploy` as separate contracts. + +`aspire publish` produces assets. Assets are the desired deployment shape and supporting files that a user can inspect, review, archive, or hand to another deployment system. + +Common publish assets: + +- Target manifests such as YAML, JSON, Bicep, Terraform-like files, Compose files, Helm values, or provider-specific descriptors. +- Generated Dockerfiles, config files, container-file inputs, and build metadata. +- Image references, tag placeholders, build contexts, or container registry requirements. +- Parameter placeholders and target-native secret references. +- A deployment plan or README fragment that lists prerequisites and unsupported mappings. +- Non-secret metadata that explains target context, resource names, and access mode. + +`aspire deploy` consumes assets and mutates the target. Deploy may generate a second deploy-time asset set when values are intentionally resolved later, but the difference from publish output must be explicit. + +Common deploy actions: + +- Validate credentials, selected account/project/subscription/cluster, enabled provider APIs, registry access, and permissions. +- Build and push images, or resolve image digests/tags. +- Resolve non-secret late-bound values that are safe to write. +- Apply target manifests or call provider APIs. +- Create or update IAM/access bindings that the user explicitly opted into. +- Query target state, URLs, revisions, routes, and readiness. +- Persist deployment state and outputs. + +DO: + +- Keep publish output deterministic and reviewable. +- Use placeholders or target-native references for values only deploy can know, such as image tags, generated resource IDs, service URLs, and secrets. +- If deploy needs resolved manifests, write them as separate deploy-time artifacts so users can compare publish assets with applied assets. +- Test `aspire publish` without live credentials for artifact-only scenarios, and test `aspire deploy` with live/faked provider state for mutation scenarios. +- Document which files are publish assets, which files are deploy-time assets, and which commands apply them. + +DON'T: + +- Don't make publish output look fully resolved when deploy will replace critical fields later. +- Don't write deploy-only values, provider state, or secret material into publish assets. +- Don't let deploy silently ignore stale publish assets or missing generated files. +- Don't require users to run deploy to learn what assets would be produced. + +## Prerequisite provisioning + +Provisioning prerequisites is a separate design contract from deploying workloads. + +Common prerequisites include: + +- Enabled cloud APIs/services. +- Container registries/repositories. +- Service accounts or managed identities. +- IAM role bindings. +- Secret stores and secret versions. +- Networks, private endpoints, VPC connectors, namespaces, or clusters. + +DO: + +- Decide whether each prerequisite is out-of-band, existing-reference, or managed by Aspire. +- Prefer first-class resource APIs for managed prerequisites instead of hidden side effects inside deploy. +- Make mutating prerequisite creation explicit. Use names like `Add{Target}Registry`, `WithManagedServiceAccount`, or `WithPrerequisiteProvisioning` only when the behavior is clear. +- Validate external prerequisites with actionable errors and exact commands/docs where possible. +- Respect org policies, quotas, billing, and permission failures as first-class errors. + +DON'T: + +- Don't silently enable cloud APIs, create repositories, or grant IAM from a generic `Add{Target}Environment` call. +- Don't require broad owner/contributor permissions when narrower deploy/runtime roles are enough. +- Don't make prerequisites untestable by hiding them behind ambient CLI state. + +## Destroy and cleanup + +Destroy is risky because current code may not match the resources created by previous deploys. + +DO: + +- Base destroy on deployment state plus target verification, not only the current app model. +- Delete in reverse dependency order: traffic/routes before services, services before identities/permissions when needed, images last. +- Default to retaining shared prerequisites and user-provided existing resources. +- Make destructive image cleanup opt-in or policy-based, especially for registries shared across deployments. +- Support dry-run/plan output when the target surface is broad or deletion is expensive. +- Treat missing resources as successful convergence, but report resources that cannot be proven safe to delete. + +DON'T: + +- Don't delete by prefix/name without an Aspire-managed state entry or verified ownership marker. +- Don't delete secrets, registries, networks, or identities that may be shared unless the user explicitly opted into managed ownership. +- Don't ignore partial cleanup failures; persist enough state for a retry. + +## Service-to-service references + +Deployment targets differ in when service addresses exist. + +Use this decision table: + +| Target capability | Publish behavior | Deploy behavior | +| --- | --- | --- | +| Stable logical name exists before deploy | Emit target-native reference, DNS name, or service binding expression. | Verify it resolves or target reports ready. | +| URL exists only after deploy | Fail publish for cross-resource endpoint refs, or use an explicit two-phase deploy/update design. | Record URL in deployment state and expose it as output, not as a publish-time env var. | +| Private service auth required | Emit identity-aware binding only when target supports it. | Configure least-privilege caller identity/IAM and test authenticated calls. | +| Unsupported target translation | Fail with guidance. | Do not silently drop service-discovery variables. | + +DO: + +- Model stable target-native references as structured values. +- Keep deployed URLs as deploy outputs unless the target offers a stable pre-deploy address. +- Distinguish user-facing URLs from workload-to-workload connection contracts. +- Test multi-service apps before claiming service references are supported. + +DON'T: + +- Don't serialize local run-mode URLs into publish/deploy output. +- Don't make users scrape dashboard summaries for values another resource needs. +- Don't invent service discovery semantics the target does not provide. + +## Secrets and identity + +Secret and identity support is required for production deployment. + +DO: + +- Support existing native secret references first, such as secret name/version/key. +- If managing secrets, create/update them through a secure API/SDK path, never by writing plaintext into generated manifests, logs, command-line args, or deployment state. +- Model runtime identity separately from deployer identity. The deployer applies infrastructure; the runtime service account/managed identity accesses dependencies. +- Wire least-privilege roles for runtime identity when the integration owns both sides of a dependency. +- Preserve private-by-default access unless there is an explicit public-access API with security-focused docs and tests. +- Test both secret reference output and denied plaintext secret materialization. + +DON'T: + +- Don't resolve `ParameterResource` secrets into deploy-time files unless the file is the target's secure ingestion mechanism and is cleaned up immediately. +- Don't grant broad roles such as owner/contributor/editor for convenience. +- Don't make anonymous ingress/public access the default to simplify smoke tests. + +## Target feature modeling + +For common production features, prefer first-class APIs over raw manifest editing. + +Good first-class candidates: + +- Public/private ingress and caller access. +- Runtime identity/service account. +- Secret references. +- Scaling/concurrency/resource limits. +- Health/startup probes. +- Registry/project/region/cluster context. +- Network attachment/private egress. +- Data service bindings such as database or queue connections. +- Custom domains/routes when the target owns routing. + +DO: + +- Keep escape hatches for rare or fast-moving target features. +- Validate combinations that the target rejects, such as private ingress with public unauthenticated access when incompatible. +- Keep APIs at the Aspire concept level when possible, and translate to target-specific schema internally. + +DON'T: + +- Don't force users to know the target YAML/JSON schema for common scenarios. +- Don't expose raw provider DTOs as the main API if they project poorly to polyglot AppHosts. + +## Stabilization gate + +Do not stabilize a deployment target package until all applicable items are true: + +- Run mode remains clean: publish/deploy-only resources are hidden or no-op locally. +- Publish produces deterministic artifacts for project and container resources. +- Deploy preflight covers tool, auth/context, target API/service enablement, registry access, and permissions. +- Real smoke deploy builds, pushes, applies, verifies provider state, calls the endpoint, and cleans up. +- Private/default access and explicit public access are both tested when public access is supported. +- Secrets use native secret references or managed secret APIs; plaintext secret materialization is rejected. +- Service-to-service references are either target-native and tested or fail clearly. +- Destroy/cleanup behavior is implemented or documented as external with safe manual commands. +- Polyglot analyzer diagnostics are clean and generated SDK signatures have been inspected. +- README documents prerequisites, deploy command, access mode, secrets, cleanup, limitations, and official target docs. diff --git a/.agents/skills/hosting-integration-authoring/resources/endpoints-and-service-discovery.md b/.agents/skills/hosting-integration-authoring/resources/endpoints-and-service-discovery.md new file mode 100644 index 000000000..ab3f0e514 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/endpoints-and-service-discovery.md @@ -0,0 +1,115 @@ +# Endpoints and service discovery + +Endpoints are part of the app model contract. They drive connection properties, service discovery, generated URLs, container networking, and deployment output. + +## Endpoint naming + +DO: + +- Use stable endpoint names. +- Use `"tcp"` for the primary non-HTTP protocol endpoint when there is only one protocol endpoint. +- Use `"http"` and `"https"` for HTTP endpoints. +- Use role-specific names when multiple endpoints exist, for example `"internal"`, `"management"`, `"emulator"`, or `"metrics"`. +- Expose common endpoint references as properties such as `PrimaryEndpoint`, `InternalEndpoint`, or `HttpEndpoint`. +- Use separate host-facing and container-internal endpoints when clients connect differently from host processes and containers. + +DON'T: + +- Don't rename endpoint names casually; consumers and connection properties may depend on them. +- Don't create multiple endpoints with ambiguous generic names. +- Don't hardcode host ports unless the integration explicitly models a fixed-port requirement. + +## Host, target, and internal ports + +The host port is the port exposed on the developer machine. The target port is the port inside the container or process. The target port is often fixed by the service. The host port should usually be nullable so Aspire can allocate it. + +DO: + +- Accept `int? port = null` for user-selected host ports. +- Set the service's known target port explicitly. +- Use `WithHostPort(int? port)` for companion/admin UI host-port customization. +- Use `EndpointProperty.Host`, `EndpointProperty.Port`, `EndpointProperty.HostAndPort`, or `EndpointProperty.Url` in reference expressions. + +DON'T: + +- Don't confuse host `Port` with container `TargetPort`. +- Don't use allocated host ports in publish-mode callbacks. +- Don't expose internal-only endpoints as reference endpoints unless consumers should use them. + +## Service discovery vs connection properties + +Use service discovery when a workload needs to call another HTTP/gRPC-style workload by logical service name and endpoint. + +Use connection properties when a consumer needs protocol-specific data such as database host, port, username, password, URI, JDBC string, queue name, model name, or API key. + +Some resources need both. For example, an app workload may expose HTTP endpoints for service discovery and also expose connection properties for protocol clients. + +## Reference endpoints + +DO: + +- Mark health-only, management-only, or internal-only endpoints with `ExcludeReferenceEndpoint` when they should not be used by `WithReference`. +- Prefer HTTPS reference endpoints when both HTTP and HTTPS are available and that matches existing resource conventions. +- Keep endpoint schemes accurate; URI expressions use the scheme. +- Use endpoint transport metadata for protocol-specific deployment output. For example, gRPC/HTTP/2 maps from `Transport == "http2"`, not from `UriScheme == "https"`. + +DON'T: + +- Don't make readiness/health probe endpoints the default reference endpoint. +- Don't expose admin UI endpoints as service dependencies unless that is the intended API. +- Don't infer container protocol from public URL scheme; deployment targets often terminate TLS before forwarding to the container. + +## URL display + +Dashboard URL display is part of developer experience. + +DO: + +- Use `WithUrlForEndpoint` to customize display names, locations, or URLs when defaults are confusing. +- Put secondary diagnostic URLs in details-only display when they are not primary user entry points. +- Show admin companion URLs only for resources users are expected to open. + +DON'T: + +- Don't flood the dashboard summary with internal, health, metrics, or implementation URLs. + +## Endpoint environment variables + +For language apps and frameworks that expect a port environment variable, use endpoint APIs that set env vars such as `PORT` instead of manually duplicating endpoint state. + +Branch mode-specific endpoint args carefully. Development servers may bind localhost or add reload flags in run mode, but published containers should bind `0.0.0.0` and use the deployment-provided port. + +## Mediated and externally allocated endpoints + +Some integrations expose an endpoint through a mediator such as a tunnel, proxy, or external CLI. The target endpoint and public endpoint are different resources. + +DO: + +- Preserve the original target endpoint as an `EndpointReference`. +- Create a separate facade endpoint for the mediated/public URL when consumers need to reference it. +- Allocate the facade endpoint only when the external endpoint is known at run time. +- Inject the facade endpoint into consumers through normal `WithReference`, service discovery, or environment flows. +- Account for host/container differences when deciding whether to forward a target port or an allocated host port. + +DON'T: + +- Don't overwrite the target resource endpoint with the mediated endpoint. +- Don't make users parse logs or dashboard URLs to get a mediated endpoint. +- Don't serialize mediated run-mode URLs into publish output. + +## Deployed endpoints + +Some deployment targets assign service URLs only after deploy. Model this as a deploy-time output, not as a publish-time endpoint value. + +DO: + +- Store deployed URLs and resource IDs in deployment state or target-specific output resources after the target reports them. +- Make cross-resource service references fail clearly when the target cannot know the destination URL until after deployment. +- Support target-native service-to-service discovery only when the target offers a stable logical name, DNS name, or service binding that works before deployment completes. +- Test both direct user-facing URLs and workload-to-workload references for deployment targets that claim to support them. + +DON'T: + +- Don't resolve post-deploy URLs during publish by reading local run-mode endpoint values. +- Don't silently drop generated service-discovery environment variables when the target cannot translate them; fail with guidance or map them to a supported target-native mechanism. +- Don't treat a dashboard/deploy summary URL as a connection contract for other resources unless it is modeled as a structured value. diff --git a/.agents/skills/hosting-integration-authoring/resources/eventing-and-initialization.md b/.agents/skills/hosting-integration-authoring/resources/eventing-and-initialization.md new file mode 100644 index 000000000..163b7a0bb --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/eventing-and-initialization.md @@ -0,0 +1,94 @@ +# Eventing and initialization + +Use lifecycle hooks based on what data is available and what side effects are safe. + +## Hook selection + +| Hook/location | Use for | Avoid | +| --- | --- | --- | +| Resource constructor | Store immutable app-model state only | Service provider access, connection resolution, file/network side effects | +| `WithEnvironment` callback | Populate environment variables from references | Resolving runtime-only values in publish mode | +| `OnConnectionStringAvailable` / `ConnectionStringAvailableEvent` | Create clients or cache resolved connection strings after references become resolvable | Creating databases/queues or other service state | +| `OnBeforeResourceStarted` / `BeforeResourceStartedEvent` | Prepare runtime clients or config before process/container start when connection string events are not available | Long-running service initialization that requires the service to be healthy | +| `OnResourceReady` / `ResourceReadyEvent` | Create databases, queues, containers, topics, models, or other child state after service health | Health checks, constructors, publish callbacks | +| Pipeline step | Publish/build/deploy validation, generated artifact checks, deployment target preparation | Local run-only setup unless guarded to run mode | + +## Initialization rules + +DO: + +- Keep health checks side-effect-free. +- Make health checks match the readiness contract. Prefer protocol-level or client-level checks over raw port checks when consumers need the service protocol to be ready, and include configured credentials when authentication is part of client readiness. +- Create dependent service state in `OnResourceReady` after the parent is healthy. +- If a child resource only models reference metadata and does not create service state, mark it `IResourceWithoutLifetime` and document that it is metadata-only. +- Fail clearly when required connection strings or clients are unavailable. +- Derive user-facing pipeline exceptions from `DistributedApplicationException` when the pipeline should surface them without extra wrapping. +- Use separate run-mode setup sibling resources for commands like dependency restore, tool install, `go mod`, or virtual environment creation. +- Mark setup siblings `.ExcludeFromManifest()` and wire the main resource with `WaitForCompletion`. +- Add comments explaining non-obvious lifecycle ordering. + +DON'T: + +- Don't create databases, queues, containers, or topics inside health checks. +- Don't cache annotation callback results without an invalidation path if inputs can change on restart/retry. +- Don't cache a faulted task when inputs may change. +- Don't treat a null process exit code as success; null means unknown. +- Don't throw raw, cryptic exceptions from publish/deploy pipeline user errors. + +## Final-model hooks and derived resources + +Some integrations need the final app model before they can wire setup resources, sidecars, package-manager commands, or telemetry forwarding. + +DO: + +- Register final-model hooks only in the modes where they are needed. +- Register global lifecycle hooks or event subscriptions idempotently. +- Discover only resources that opted in through a specific annotation or interface. +- Materialize derived resources before startup when the target process needs sidecars, config, setup resources, or command rewrites. +- Propagate relevant waits and relationships from source resources to derived resources. + +DON'T: + +- Don't scan and mutate unrelated resources just because they implement a broad interface. +- Don't register duplicate global subscriptions when multiple fluent calls enable the same feature. + +If multiple lifecycle hooks, commands, background probes, and resource operations must coordinate shared mutable state, use the controller/reconciler archetype instead of adding more independent event handlers. Read `archetype-controller-reconciler.md`. + +## Custom lifecycle resources + +Some integrations create facade resources whose status is driven by a parent resource or external service rather than by a DCP-started process/container. Read `custom-lifecycle-and-facade-resources.md` for the full pattern. + +DO: + +- Prefer normal resources first; manual lifecycle is an escape hatch. +- Drive facade status with `ResourceNotificationService` from the owning resource's lifecycle callbacks. +- Keep health checks observational, and let lifecycle callbacks perform create/update/delete side effects. + +DON'T: + +- Don't manually publish lifecycle events unless the facade resource has no normal DCP lifecycle and other components need those events. +- Don't leave manually managed resources in stale running states after their owner stops. + +## Runtime values in callbacks + +Any callback that reads allocated endpoints, host ports, local file paths generated at run time, container IDs, or process state must branch on publish mode. + +In publish mode, use a `ReferenceExpression`, manifest expression, environment placeholder, Bicep output, compose variable, or deployment model reference instead. + +## Runtime output parsing + +Some tools expose required run-mode values only through stdout/stderr, such as webhook signing secrets or public tunnel URLs. + +DO: + +- Prefer structured APIs or files over log parsing when available. +- Keep output parsing run-only and cancellation-aware. +- Include a nearby comment with an example of the raw output shape being parsed. +- Fail clearly when a required value is not observed before the timeout/cancellation path. +- Redact parsed secrets in diagnostics. + +DON'T: + +- Don't parse logs during app-model construction. +- Don't treat missing required runtime output as success. +- Don't write parsed secrets or machine-local runtime values to publish/deploy artifacts. diff --git a/.agents/skills/hosting-integration-authoring/resources/generated-files-and-container-files.md b/.agents/skills/hosting-integration-authoring/resources/generated-files-and-container-files.md new file mode 100644 index 000000000..370ed090e --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/generated-files-and-container-files.md @@ -0,0 +1,91 @@ +# Generated files and container files + +Many integrations generate config files, mount init files, or move files between resources and generated container images. Treat these files as part of the app model contract. + +## Generated config files + +DO: + +- Generate files from callbacks at the lifecycle point where all required model data is available. +- Generate pre-start config files from `OnBeforeResourceStarted` when a container or CLI must consume the file at launch. +- Keep generated content deterministic. +- Include comments in code that explain non-obvious file formats and examples of generated shape. +- Redact or parameterize secrets. +- Use stable filenames and paths. + +DON'T: + +- Don't generate files in constructors. +- Don't write generated files into source directories unless that is the explicit user-facing feature. +- Don't include host-specific absolute paths in generated deployment artifacts. + +## `WithContainerFiles` + +Use container file APIs when a resource needs files from another resource or generated content in a container image/runtime. + +DO: + +- Use `WithContainerFiles` for generated files copied into containers. +- Add build pipeline dependencies when one resource's container files come from another resource. +- Keep destination paths explicit and aligned with the image's expected paths. +- Set file permissions when the target runtime requires executable or restricted files. +- Ensure generated Dockerfile stages include container-file sources before the runtime image consumes them. + +DON'T: + +- Don't assume build order automatically follows file dependencies. +- Don't mount writable files as read-only unless the service supports it. +- Don't copy secrets into images when secret mounts or parameters are available. + +## Embedded files + +DO: + +- Use embedded resources for small static helper files that must travel with the integration package. +- Throw clear errors when an expected embedded resource is missing. +- Keep generated or embedded file contents deterministic so tests can assert the full output shape. + +DON'T: + +- Don't silently skip missing embedded files; that creates a success-shaped broken tool container. + +## Init files + +DO: + +- Prefer `WithInitFiles` for database or service initialization files. +- Document accepted file types, execution order, and target paths. +- Support read-only mounts for init content when possible. + +DON'T: + +- Don't keep adding obsolete init bind-mount APIs for new integrations. +- Don't assume file ordering unless the service defines it or the integration enforces it. + +## Temporary and persistent generated files + +DO: + +- Use Aspire store/temp abstractions when available for files created during AppHost execution. +- Otherwise use securely created temporary directories. +- Clean up temporary files when their lifetime ends. +- Keep generated persistent files under user-expected locations. + +DON'T: + +- Don't use ad hoc `Path.GetTempPath()` plus random names when a secure temp directory abstraction is available. +- Don't leak temporary files containing credentials. + +## Cross-platform file behavior + +DO: + +- Normalize paths for the current platform. +- Avoid hardcoded `/` or `\` in paths. +- Consider line endings and executable permissions for generated scripts. +- Use UTF-8 for generated text unless the target tool requires another encoding. + +DON'T: + +- Don't assume case-sensitive filesystems. +- Don't assume Linux file modes work on Windows. diff --git a/.agents/skills/hosting-integration-authoring/resources/package-and-discoverability.md b/.agents/skills/hosting-integration-authoring/resources/package-and-discoverability.md new file mode 100644 index 000000000..194b8b57f --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/package-and-discoverability.md @@ -0,0 +1,81 @@ +# Package and discoverability lifecycle + +Hosting integrations are discovered, installed, documented, and eventually deprecated as packages. API quality is not enough; users must be able to find and understand the integration. + +## Package shape + +DO: + +- Follow existing `CommunityToolkit.Aspire.Hosting.{Technology}` package naming. +- Keep hosting integration packages focused on AppHost resource modeling. +- Keep consuming-app client setup in client integration packages or consuming-app docs, not hosting package docs. +- Add package metadata, description, icons, README, and tags consistent with nearby integrations. +- Keep container image tag constants and package metadata easy for automation to update. +- Prefer stable, explicit container image tags such as concrete `major.minor` tags or immutable digests. Avoid floating tags such as `latest`, `edge`, or bare major tags unless the upstream image has no stable alternative and the README documents the tradeoff. +- For a new first-party packable integration that has not shipped a baseline package yet, set the existing repo baseline-validation opt-out used for new packages (for example `DisablePackageBaselineValidation`) until the first release establishes a baseline. + +DON'T: + +- Don't put unrelated client/runtime code into a hosting integration package. +- Don't add external package feeds or change shared package configuration unless explicitly requested. +- Don't fix missing baseline-package restore errors by editing NuGet feeds or package source mappings; use the repo's new-package baseline opt-out instead. +- Don't make new packages stable by default without checking release posture. + +## Preview, stable, and experimental posture + +New integrations and emerging deployment/language features commonly need preview or experimental treatment before stable release. + +DO: + +- Mark unstable APIs with `[Experimental]`. +- Keep new packages preview until the team intentionally stabilizes them. +- Document known limitations in XML docs and README. +- For new deployment target integrations, keep the package preview/experimental until at least one real deploy smoke path, cleanup story, identity/access model, secret handling path, and polyglot API shape have been validated. +- Avoid obsolete compatibility shims for APIs that have not shipped stable. + +DON'T: + +- Don't add `[Obsolete]` churn in a preview-only package when the API can still be corrected directly. +- Don't ship broad public APIs as stable without enough bake time. + +## `aspire add` discoverability + +DO: + +- Ensure the package can be added by exact integration ID, for example `aspire add CommunityToolkit.Aspire.Hosting.PostgreSQL.Extensions`. +- Keep package names and README examples aligned with the integration ID. +- Hide packages that should not be user-discoverable, such as deprecated, internal, or support-only packages. +- Include TypeScript usage examples when the package exports TypeScript-compatible APIs. + +DON'T: + +- Don't rely on fuzzy search names in docs. +- Don't make deprecated integrations prominent in discovery. + +## Gallery and docs presence + +DO: + +- Provide a concise hosting README. +- Link to the integration gallery and relevant official service docs. +- Include prerequisites such as Azure subscription, Docker, language toolchains, or external service accounts. +- Include trademark notices when required. + +DON'T: + +- Don't make the hosting README a full tutorial for the underlying technology. +- Don't document consuming-app dependency injection in the hosting README. + +## Automation and maintenance + +DO: + +- Keep container image registry/image/tag constants centralized. +- Make image tags easy for automated update tooling to find. +- Keep generated code or API baseline expectations clear. +- Include tests that fail when discovery-critical metadata drifts. + +DON'T: + +- Don't scatter image names or versions across extension methods, tests, and docs. +- Don't manually edit generated API baseline files unless explicitly regenerating API baselines. diff --git a/.agents/skills/hosting-integration-authoring/resources/polyglot-exports.md b/.agents/skills/hosting-integration-authoring/resources/polyglot-exports.md new file mode 100644 index 000000000..657fbd7dd --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/polyglot-exports.md @@ -0,0 +1,331 @@ +# Polyglot exports and multi-language integration authoring + +Aspire hosting integrations are C# libraries. Multi-language AppHosts call the C# implementation through generated SDKs. The Aspire CLI loads the integration assembly, scans ATS metadata such as `[AspireExport]`, generates a typed SDK, and dispatches calls back to the C# code over JSON-RPC. + +Design the ATS contract first: the methods, DTOs, callbacks, values, and docs that generated SDK users should see. Keep the C# API ergonomic, then adapt it to ATS with explicit exports, DTO/options types, unions, small context/editor types, ignored C#-only overloads, and internal export adapters. See `api-naming-and-shape.md` for the naming rules that keep the C# and generated AppHost APIs aligned. + +## Analyzer enablement + +Every integration that exports ATS APIs should have integration analyzer coverage. + +Use one supported path: + +- If the project already references `Aspire.Hosting`, set `true`. +- Otherwise, reference `Aspire.Hosting.Integration.Analyzers` with `PrivateAssets="all"` using the same Aspire package version. + +A clean build with zero analyzer warnings or errors is the baseline, but it is not enough. Inspect the generated SDK signatures and docs before shipping. + +Do not manually edit generated API or ATS baseline files as part of ordinary integration authoring. In this repo, checked-in `src/CommunityToolkit.Aspire.Hosting*/api/*.cs` and `*.ats.txt` files are release compatibility baselines; PR validation generates the current surface separately and the release/review workflow updates checked-in baselines after API changes are accepted. + +## Export attributes + +DO: + +- Mark generated-SDK-compatible APIs with `[AspireExport]`. +- Mark resource types with `[AspireExport]` so generated SDKs can reference typed handles. +- Use `[AspireExport(ExposeProperties = true)]` only on small resource/handle types where every public property should be projected. +- Prefer individual `[AspireExport]` attributes on callback context and editor properties. +- Add `[ResourceName]` to Aspire resource-name parameters. +- Use `[AspireExportIgnore(Reason = "...")]` for C#-only overloads, convenience overloads, deprecated APIs, unsupported types, or implementation details. +- Use `[AspireExport(RunSyncOnBackgroundThread = true)]` when an exported method invokes a synchronous callback inline, including async-returning methods that invoke the callback before the first `await`. +- Use `[AspireUnion(...)]` for method parameters that accept a bounded set of live AppHost value shapes. +- Use `[AspireDto]` for JSON-shaped options/configuration objects. +- Use `[AspireValue]` for immutable predefined value catalogs such as model names, SKUs, or regions. + +DON'T: + +- Don't export every C# overload and hope generated names make the API usable. +- Don't rely on C# overload resolution; ATS dispatch is not C# overload resolution. +- Don't expose interpolated string handlers, loggers, service providers, delegates, configuration objects, mutable framework types, or opaque implementation handles in DTOs. +- Don't bury live handles such as `EndpointReference`, `ReferenceExpression`, resources, or `IResourceBuilder` inside DTOs. Accept them as union-shaped method parameters or editor method parameters. +- Don't expose provisioning-only handles such as `BicepOutputReference` through broad `ExposeProperties`; mark them ignored, make them non-public, or export an ATS-compatible projection. +- Don't turn on broad `ExposeProperties` or `ExposeMethods` for large framework-style types. +- Don't omit the `Reason` from `[AspireExportIgnore]`. + +## XML docs and ATS doc overrides + +Generated SDK docs come from XML documentation. Treat docs as part of the ATS contract. + +Document every exported method, DTO, parameter, property, callback context, editor method, and value catalog with language-neutral XML comments. Avoid C#-specific implementation descriptions when generated SDK users cannot see those types. + +Use ATS override tags when standard C# XML docs do not translate well: + +- `` overrides ``. +- `` overrides a specific ``. +- `` overrides ``. +- `` overrides ``. + +An empty `ats-*` tag intentionally suppresses the matching standard doc in the generated SDK. + +Use `` and `` for generated SDK links. Supported `kind` values are `type`, `method`, and `field`. The `!:` prefix prevents the C# compiler from validating the custom `cref`. + +## Capability IDs and generated names + +Capability IDs are runtime dispatch identifiers. They do not include the C# receiver type, parameter list, generic constraints, or overload signature. + +Before adding or reviewing an export, answer two questions: + +1. What should the generated AppHost call look like? +2. Is that generated call still clear if the caller has never seen the C# overload set? + +DO: + +- Keep capability IDs unique and stable within an assembly. +- Avoid explicit export IDs when the convention-derived ID is already correct. +- Use `MethodName` only when the runtime capability ID must be unique but the generated methods live on different target types and can safely share a friendly method name. +- Inspect generated member names per target type, not just runtime capability IDs. +- Prefer existing framework exports for shared concepts such as container registries; target-specific convenience wrappers can create generated member collisions without adding capability. + +DON'T: + +- Don't reuse explicit export IDs across methods. +- Don't set an explicit export ID that duplicates the convention-derived name. +- Don't use `MethodName` to give multiple exports the same generated method name on the same generated target type. +- Don't assume different C# receiver types prevent capability collisions. +- Don't export a target-specific overload solely to change annotation mutation behavior when the generic exported helper already expresses the same user concept. + +For one user concept, export one ATS method and model variation with a DTO/options object, `[AspireUnion]`, or an internal dispatcher. + +For different concepts on the same generated target type, use distinct generated method names. + +When C# and ATS need different shapes, keep the C# API public and ergonomic, and add an internal exported adapter: + +```csharp +[AspireExport("publishAsStaticWebsite")] +internal static IResourceBuilder PublishAsStaticWebsitePolyglot( + this IResourceBuilder builder, + string? apiPath = null, + IResourceBuilder? apiTarget = null) + where TResource : JavaScriptAppResource +{ + return PublishAsStaticWebsiteCore(builder, apiPath, apiTarget); +} +``` + +Common cases that need a C# API plus a polyglot adapter: + +- Callback configuration such as `Action`, `Func, IResourceBuilder>`, or tool-specific option delegates. +- C# generic metadata markers such as `IProjectMetadata`, package metadata, or strongly typed project references. +- Mutable dictionaries or framework types that do not project cleanly. +- Endpoint-reference overloads when generated SDKs need a string/parameter/external-service alternative. +- Azure provisioning callbacks or Bicep/provisioning types such as `Action`, `BicepValue`, or provider SDK model types. + +DO: + +- Mark the C#-only overload `[AspireExportIgnore(Reason = "...")]` with a reason that names the incompatible type and the replacement API. +- Export a polyglot-friendly overload with primitive, DTO, resource-builder, parameter, or external-service inputs. +- Use `MethodName` when the generated name should match the C# concept but the internal adapter method needs a unique CLR name. + +DON'T: + +- Don't leave C# callback or generic-metadata overloads as the only way to configure an exported feature. + +Toolkit adapter pattern: + +1. Keep the public C# API ergonomic, even when it accepts callbacks or Azure/provisioning types. +2. Mark C#-only overloads with `[AspireExportIgnore(Reason = "...")]` and name the incompatible type plus the replacement export shape. +3. Add an internal static `*PolyglotExtensions` adapter when the exported shape should be different from the public C# shape. +4. Make exported adapters compose the public API internally and accept only resource builders, primitives, arrays, dictionaries, DTOs, parameters, endpoint references, or other ATS-compatible values. +5. Put polyglot options in `[AspireDto]` types with `init` properties and `ToXxxOptions()` conversion methods when the public C# API uses richer option objects. + +Example patterns in this repo: + +- Dapr exposes callback/options APIs for C# and DTO-based exports such as `AddDaprComponentExport` and `WithDaprSidecarExport`, backed by `DaprComponentExportOptions` and `DaprSidecarExportOptions`. +- Azure Dapr APIs that accept `Action` or Azure provisioning model types are ignored for ATS, while `AzureRedisCacheDaprHostingPolyglotExtensions` provides exported helper methods that compose the public C# APIs with primitive options. + +## DTOs, options, unions, and live values + +Use flat DTO/options objects for optional configuration: + +```csharp +[AspireDto] +public sealed class AddMyWorkerOptions +{ + public string? ImageTag { get; init; } + public string[] Args { get; init; } = []; +} +``` + +Generated SDKs should feel like plain object literals: + +```typescript +const worker = await builder.addMyWorker("worker", { + imageTag: "v1", + args: ["--debug"] +}); +``` + +DTO rules: + +- Mark DTOs with `[AspireDto]`. +- Keep DTOs JSON-serializable. +- Use `init` setters for input properties. +- Use arrays, records, primitives, enums, other DTOs, and `Dictionary` where `T` is ATS-compatible. +- Prefer flat options objects over nested `{ options: { ... } }` shapes for one optional options parameter. +- Avoid getter-only raw `List` or `Dictionary` properties on exported DTO/model types. Use `init`-settable arrays/DTO properties for JSON input/output shapes, or expose explicit editor methods/wrapper types for live mutation. + +Use `[AspireUnion]` for live AppHost values: + +```csharp +[AspireExport] +public static IResourceBuilder WithSetting( + this IResourceBuilder builder, + string name, + [AspireUnion( + typeof(string), + typeof(ReferenceExpression), + typeof(EndpointReference), + typeof(IResourceBuilder), + typeof(IResourceBuilder), + typeof(IExpressionValue))] + object value) + where T : IResourceWithEnvironment +{ + return WithSettingCore(builder, name, value); +} +``` + +## Callback contexts and editor types + +Generated SDK callbacks can call back into ATS/RPC. In TypeScript this usually means callbacks can `await` generated SDK calls. + +DO: + +- Keep callback context types small. +- Mark context types with `[AspireExport]`. +- Export only the members callback authors need. +- Expose editor objects for mutable state instead of raw mutable collections. +- Use editor methods such as `set`, `add`, and `remove`. +- Use exported editor/context types for callback mutations that must round-trip from generated SDKs. +- Set `RunSyncOnBackgroundThread = true` on exported methods that invoke synchronous callbacks inline. +- Make service access explicit through generated service-provider methods when callbacks need services. + +DON'T: + +- Don't expose raw `Dictionary`, `List`, `IServiceProvider`, or framework context objects directly unless they are known ATS-compatible and intentionally part of the generated contract. +- Don't export callbacks that mutate a DTO/options object and expect those mutations to round-trip from polyglot callers; DTOs model JSON-shaped input, not live editor state. +- Don't block an RPC thread while invoking a callback that may re-enter the generated SDK. + +Example context/editor shape: + +```csharp +[AspireExport] +internal sealed class EnvironmentEditor(Dictionary environmentVariables) +{ + [AspireExport] + public void Set( + string name, + [AspireUnion( + typeof(string), + typeof(ReferenceExpression), + typeof(EndpointReference), + typeof(IResourceBuilder), + typeof(IResourceBuilder))] + object value) + { + environmentVariables[name] = value; + } +} +``` + +## Exposed properties and methods + +`ExposeProperties = true` and `ExposeMethods = true` are broad expansion switches. They export every compatible public member on the type, including inherited members where applicable. + +Use them only on small, purpose-built handle/context types. Otherwise, export members individually. + +Generated TypeScript property shapes differ by C# property shape: + +| C# shape | Generated shape | +| --- | --- | +| Getter-only property | Async method, for example `resource(): Promise` | +| Getter-only `AspireList` or `AspireDict` | Async method returning the wrapper | +| Settable mutable collection wrapper | Readonly synchronous wrapper property | +| Scalar read-write property | Readonly `PropertyAccessor` with async `get()` and `set(value)` | + +Use `[AspireExportIgnore]` on properties that should not be exposed. If TypeScript callers need to mutate state, prefer an explicit method or editor type over exposing broad mutable properties. + +## Value catalogs + +Use `[AspireValue]` for immutable predefined values that generated SDK users should reference as typed constants. + +Rules: + +- Apply to public static fields or public static properties with public static getters. +- Use a valid generated SDK catalog name and identifier path. +- Values are snapped once at scan time and emitted as generated SDK constants. +- Supported copied shapes include primitives, enums, arrays, read-only dictionaries, and DTOs containing supported copied shapes. + +DON'T: + +- Don't use `List`, mutable `Dictionary`, runtime handles, resources, builders, delegates, or runtime state in value catalogs. +- Don't expect `[AspireValue]` values to refresh at runtime. + +## ATS-compatible type categories + +Exported method signatures can use: + +- Primitives: `string`, `bool`, numeric types. +- Value types: `DateTime`, `TimeSpan`, `Guid`, `Uri`. +- Enums. +- Handles: `IDistributedApplicationBuilder`, `IResourceBuilder`, and resource types marked with `[AspireExport]`. +- DTOs marked with `[AspireDto]`. +- Static values marked with `[AspireValue]`. +- Collections where the element type is ATS-compatible. +- Delegates such as `Action` and `Func`. +- Core exported services such as `ILogger`, `IServiceProvider`, and `IConfiguration`. +- Special value types such as `ParameterResource`, `ReferenceExpression`, `EndpointReference`, `IExpressionValue`, and `CancellationToken`. +- Nullable forms of compatible types. + +Types that are not ATS-compatible include interpolated string handlers and custom complex types without `[AspireExport]` or `[AspireDto]`. + +## Analyzer diagnostics to understand + +| ID | Meaning | +| --- | --- | +| ASPIREEXPORT001 | Standalone `[AspireExport]` method must be static | +| ASPIREEXPORT002 | Invalid export ID format | +| ASPIREEXPORT003 | Return type is not ATS-compatible | +| ASPIREEXPORT004 | Parameter type is not ATS-compatible | +| ASPIREEXPORT005 | `[AspireUnion]` requires at least two types | +| ASPIREEXPORT006 | Union type is not ATS-compatible | +| ASPIREEXPORT007 | Duplicate export ID for same target type | +| ASPIREEXPORT008 | Public extension method on exported type missing `[AspireExport]` or `[AspireExportIgnore]` | +| ASPIREEXPORT009 | Export name may collide with other integrations | +| ASPIREEXPORT010 | Synchronous callback invoked inline may deadlock | +| ASPIREEXPORT011 | Explicit export ID matches convention-derived name | +| ASPIREEXPORT012 | Callback context type missing `[AspireExport]` | +| ASPIREEXPORT013 | Duplicate polyglot capability ID across exports in same assembly | +| ASPIREEXPORT014 | Duplicate generated member name on same SDK target type | +| ASPIREEXPORT015 | `[AspireExport(Description = ...)]` is deprecated; use XML docs | +| ASPIREEXPORT016 | DTO property is a get-only mutable collection; add an init accessor | + +## Local generated SDK validation + +Test exports with a TypeScript AppHost that references the integration project directly in `aspire.config.json`. + +Example package mapping: + +```json +{ + "appHost": { + "path": "apphost.mts", + "language": "typescript/nodejs" + }, + "packages": { + "MyCompany.Hosting.MyDatabase": "../src/MyCompany.Hosting.MyDatabase/MyCompany.Hosting.MyDatabase.csproj" + } +} +``` + +Then run `aspire restore` or `aspire run`, inspect `.aspire/modules/`, and verify: + +- Generated imports. +- `.d.ts` method signatures. +- DTO shapes. +- Callback context accessors. +- Property accessor shapes. +- JSDoc generated from XML and `ats-*` docs. +- Capability/member name collisions. + +New TypeScript AppHosts use `apphost.mts` and `.aspire/modules/*.mjs` imports. Generated TypeScript APIs are promise-heavy: await `createBuilder`, fluent calls, getter-only property methods, and property accessor `get()`/`set(value)` calls. diff --git a/.agents/skills/hosting-integration-authoring/resources/relationships-and-companions.md b/.agents/skills/hosting-integration-authoring/resources/relationships-and-companions.md new file mode 100644 index 000000000..0eaf2fc39 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/relationships-and-companions.md @@ -0,0 +1,109 @@ +# Relationships, dependencies, and companions + +Aspire relationships are semantic. Pick the relationship that matches the behavior. + +## Parent-child resources + +Use parent-child structure for domain subresources owned by a parent service. + +DO: + +- Implement `IResourceWithParent` on child resource types. +- Expose child creation on the parent builder: `server.AddDatabase("db")`, `storage.AddBlobContainer("images")`. +- Use globally unique Aspire resource names and separate physical names. +- Keep child auth and endpoint logic delegated to the parent. + +DON'T: + +- Don't add top-level `builder.AddDatabase(...)` for a database that belongs to one server. +- Don't make the child re-implement parent connection, auth, emulator, or Azure branching. + +## Resource references + +Use `WithReference` when a consumer needs connection info, service discovery, or integration-provided environment variables. + +For overlay/configuration objects, define clear projection APIs such as `AsClient()` or `WithReference()` so users understand what gets wired. + +## Parent and custom relationships + +Use `WithParentRelationship` when a companion or child resource is visually/semantically attached to a parent in the app model. + +Use `WithRelationship(parent, "Label")` for display or custom semantic relationships that are not necessarily ownership. + +## Setup siblings + +Run-mode setup resources prepare an app before it starts. + +DO: + +- Create setup siblings only in run mode. +- Name setup siblings predictably, for example `{resource}-mod-tidy`. +- Mark setup siblings `.ExcludeFromManifest()`. +- Make the main resource wait with `WaitForCompletion`. +- Handle ordering if setup helpers can be called in any sequence. +- Make setup sibling creation idempotent when multiple fluent calls can request the same helper. + +DON'T: + +- Don't include setup siblings in publish manifests. +- Don't rely on user call order when setup resources have dependencies. + +Canonical setup sibling shape: + +```csharp +if (!resource.ApplicationBuilder.ExecutionContext.IsPublishMode) +{ + var setupName = $"{resource.Resource.Name}-setup"; + + if (resource.ApplicationBuilder.TryCreateResourceBuilder(setupName, out var existingSetup)) + { + configureSetup?.Invoke(existingSetup); + return resource; + } + + var setupBuilder = resource.ApplicationBuilder.AddResource(new SetupResource(setupName, resource.Resource.WorkingDirectory)) + .WithParentRelationship(resource.Resource) + .ExcludeFromManifest(); + + resource.WaitForCompletion(setupBuilder); + configureSetup?.Invoke(setupBuilder); +} +``` + +Use setup siblings only for work that must complete before the target starts. For post-ready background work such as model downloads or service-side child creation, prefer `ResourceReadyEvent`/`OnResourceReady`, publish status with `ResourceNotificationService`, and do not block the primary resource unless readiness truly depends on the helper. + +## Admin/dev companions + +Admin UIs and development tools are companion resources, not deployment resources. + +DO: + +- Add companions with `With{Tool}` on the parent resource. +- Return the original parent builder from `With{Tool}`. +- Use a container resource for the tool. +- Configure image, registry, endpoint, environment, health check if appropriate. +- Add parent/custom relationships. +- Call `.ExcludeFromManifest()`. +- Avoid duplicates when the companion is singleton-style. + +DON'T: + +- Don't expose admin UIs to publish/deploy output by default. +- Don't make a companion a top-level required resource. +- Don't hardcode host ports; provide `WithHostPort(int? port)` when fixed ports are useful. + +## Standalone utility containers + +Some tools are useful independently or can serve multiple resources, so they should be top-level utilities rather than parent-scoped companions. + +DO: + +- Use top-level `Add{Tool}` when one tool instance can connect to many resources, such as a database admin UI. +- Return an existing resource builder when repeat calls intentionally share a singleton tool. +- Still call `.ExcludeFromManifest()` when the utility is run-only. +- Provide `WithHostPort(int? port)` for tools where a fixed dashboard port is useful. + +DON'T: + +- Don't force singleton utility tools into `With{Tool}` APIs on every possible parent. +- Don't create duplicates from repeat `Add{Tool}` calls unless multiple tool instances are explicitly supported. diff --git a/.agents/skills/hosting-integration-authoring/resources/resource-model-invariants.md b/.agents/skills/hosting-integration-authoring/resources/resource-model-invariants.md new file mode 100644 index 000000000..a731799a5 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/resource-model-invariants.md @@ -0,0 +1,84 @@ +# Resource model invariants + +The Aspire app model must stay deterministic, composable, and safe to transform. + +## Resource names and physical names + +Aspire resource names are globally unique model identifiers. Physical names are service-specific names such as database names, queue names, hub names, or cloud resource names. + +DO: + +- Validate resource names with `ArgumentException.ThrowIfNullOrEmpty`. +- Use resource-name comparison semantics for dictionaries keyed by resource name. First-party integrations can use `StringComparers.ResourceName`; external packages should use an equivalent case-insensitive comparer when that helper is not available. +- Keep child resource names globally unique. +- Provide separate physical-name parameters like `databaseName`, `queueName`, or `hubName` that default to the Aspire resource name. + +DON'T: + +- Don't use case-sensitive default string dictionaries for resource-name lookups. +- Don't assume child resource names only need to be unique within a parent. +- Don't conflate an Aspire resource name with a provider's physical name constraints. + +## Constructor purity + +Resource constructors should capture app-model state only. + +DO: + +- Store immutable or model-time values. +- Validate constructor arguments. +- Keep endpoint references lazy when they depend on annotations. + +DON'T: + +- Don't resolve services, environment variables, connection strings, endpoint values, files, or network resources in constructors. +- Don't start processes, create containers, or make cloud calls from constructors. + +## Annotations + +Annotations are the primary extension mechanism for resource behavior. + +DO: + +- Use annotations for configuration that pipeline steps, publishers, code generators, or runtime callbacks consume. +- Use `ResourceAnnotationMutationBehavior.Replace` for override/last-wins settings. +- Accumulate annotations only when multiple entries are intentionally meaningful. +- Copy annotations carefully when swapping or wrapping inner resources. +- Document non-obvious annotation ordering or replacement behavior. + +DON'T: + +- Don't add duplicate mutually-exclusive annotations. +- Don't rely on `TryGetLastAnnotation` without `Replace` when repeated calls should override. +- Don't mutate another resource's annotations unexpectedly. + +## Model transformations + +Some integrations replace or wrap resources in specific modes, such as Azure resources that run locally as containers. + +DO: + +- Keep references and connection properties valid after transformation. +- Preserve annotations that should apply to the runtime resource. +- Remove or hide resources that no longer represent a real runtime entity. +- Test both pre-transform authoring shape and post-transform run/publish shape. + +DON'T: + +- Don't leave dangling active resources that duplicate the same runtime role. +- Don't make transformations depend on user call order unless documented and tested. + +## Mutability and thread safety + +The model may be inspected by callbacks and pipeline steps. + +DO: + +- Prefer explicit mutation APIs over exposing mutable collections publicly. +- Use read-only views for resource collections. +- Synchronize test fakes or use concurrent collections when callbacks mutate state concurrently. + +DON'T: + +- Don't expose broad mutable state as public API without a strong reason. +- Don't rely on resource object reference equality when resource names are the identity. diff --git a/.agents/skills/hosting-integration-authoring/resources/run-publish-deploy-modes.md b/.agents/skills/hosting-integration-authoring/resources/run-publish-deploy-modes.md new file mode 100644 index 000000000..59b8789cf --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/run-publish-deploy-modes.md @@ -0,0 +1,79 @@ +# Run, publish, and deploy modes + +Mode behavior is one of the most important Aspire hosting integration design points. + +## Definitions + +| Mode | Meaning | +| --- | --- | +| Run mode | Local orchestration by the AppHost and DCP. Runtime values such as allocated host ports and local container endpoints exist here. | +| Publish mode | Asset generation for `aspire publish`. Runtime values may not exist. Emit reviewable artifacts, references, placeholders, or deployment model customizations instead of reading local runtime state or mutating provider resources. | +| Deploy mode | Applying generated assets for `aspire deploy`. Deployment resolves safe late-bound values, performs preflight checks, builds/pushes required images when that is part of the pipeline, mutates provider resources, verifies target state, and records deployment outputs. | + +## Core rules + +DO: + +- Branch callbacks that read runtime-only values on `context.ExecutionContext.IsPublishMode`. +- Emit references, placeholders, or deployment target metadata in publish mode instead of resolving local endpoints. +- Make run-only helpers no-op or hidden in publish mode. +- Make publish-only APIs no-op outside publish mode. +- Keep deployment environment resources out of the run model unless they have a real local runtime role. +- For run-mode controllers/reconcilers, keep user-facing control resources visible only when they can act locally; in publish mode, either omit them or keep hidden/excluded marker resources only when later publish/deploy stages need to discover model metadata. +- Validate publish-only preconditions in publish/build pipeline steps, not during `aspire start`. +- Treat deploy as a separate lifecycle from publish: deploy resolves late-bound values, applies artifacts idempotently, records target outputs, and should have a clear update and cleanup/destroy story. +- Keep `aspire publish` assets useful without live target credentials when possible: generated manifests, parameter placeholders, image references/build metadata, prerequisite notes, and target-specific files should be inspectable and source-control/review friendly. +- Keep `aspire deploy` responsible for target-specific mutation: credential/context checks, registry push, provider apply, IAM changes, URL/status verification, and deployment state updates. + +DON'T: + +- Don't read allocated host ports, runtime endpoint URLs, container hostnames, or local process state during publish. +- Don't add deployment target environment resources to the local dashboard/run model. +- Don't let dev tools, setup siblings, or admin UIs appear in manifests. +- Don't mutate run-mode resources just to satisfy deployment output customization. +- Don't serialize controller runtime state, queued operations, command state, or drift probe results into publish artifacts. +- Don't assume publish success means deploy success; target auth, provider validation, registry access, and IAM can still fail after artifacts are generated. +- Don't make `aspire publish` require cloud credentials merely to inspect generated assets unless the target cannot generate meaningful assets without provider reads; if it must, document and test that requirement. +- Don't let `aspire deploy` regenerate a materially different desired model from `aspire publish` without writing the deploy-time assets separately and making the difference explicit. + +## API mode contracts + +| API shape | Expected behavior | +| --- | --- | +| `RunAs{Mode}` | Affects run mode only. Return unchanged builder in publish if the setup has no publish meaning. | +| `PublishAs{Target}` | Affects publish/deploy output only. Return unchanged builder outside publish. | +| `AsExisting` | Existing-resource semantics apply in both run and publish. | +| `RunAsExisting` | Existing-resource semantics apply when running. | +| `PublishAsExisting` | Existing-resource semantics apply when publishing/deploying. | +| `Add{DeploymentTarget}Environment` | Usually returns a non-added builder in run mode and adds the environment resource in publish. | + +## Common patterns + +Run-only companion: + +```csharp +if (builder.ApplicationBuilder.ExecutionContext.IsPublishMode) +{ + return builder; +} +``` + +Publish-only customization: + +```csharp +if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode) +{ + return builder; +} + +builder.ApplicationBuilder.AddTargetInfrastructureCore(); +return builder.WithAnnotation(new TargetCustomizationAnnotation(configure)); +``` + +Deployment environment hidden in run mode: + +```csharp +return builder.ExecutionContext.IsRunMode + ? builder.CreateResourceBuilder(environmentResource) + : builder.AddResource(environmentResource); +``` diff --git a/.agents/skills/hosting-integration-authoring/resources/security-secrets-and-identity.md b/.agents/skills/hosting-integration-authoring/resources/security-secrets-and-identity.md new file mode 100644 index 000000000..d609ea34c --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/security-secrets-and-identity.md @@ -0,0 +1,88 @@ +# Security, secrets, and identity + +Hosting integrations often handle credentials, generated files, deployment output, and cloud role assignments. Default to least privilege and never materialize secrets unless the target system requires it. + +## Parameters and secrets + +DO: + +- Use `ParameterResource` for passwords, API keys, tokens, connection-string secrets, and generated credentials. +- Use `ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter` for generated passwords. +- Mark user-provided secret parameters as secret. +- Pass secrets through `ReferenceExpression`, environment callbacks, parameter references, BuildKit secrets, or deployment-secret mechanisms. +- Keep secrets late-bound so they do not appear in app model logs or generated code. + +DON'T: + +- Don't generate random secrets inside publish callbacks. +- Don't write secrets to generated Dockerfiles, YAML, Bicep, README examples, or logs. +- Don't concatenate secret values into plain strings when a reference expression can preserve secrecy. +- Don't use deterministic or hardcoded default passwords. + +## Generated artifacts + +Generated deployment or config artifacts must not leak secrets. + +DO: + +- Use placeholders, parameter references, or secret mounts for generated artifacts. +- Use the deployment target's native secret reference shape for deploy-time manifests, such as Kubernetes `secretKeyRef`, cloud secret-manager references, or platform-managed secret resources. +- Use BuildKit secret mounts for private package/module credentials in generated Dockerfiles. +- Remove temporary credential files in the same Docker layer when a tool requires a credential file. +- Keep generated examples redacted. + +DON'T: + +- Don't persist credentials in Docker layers. +- Don't emit `.env`, Compose, Kubernetes, Bicep, or appsettings content with raw secret values. +- Don't treat deploy-time generated manifests as safe just because they are temporary; anything written under the publish output can be archived, logged, or inspected later. +- Don't put access tokens in command-line arguments when an environment variable, secret file, or parameter reference works. + +## Azure identity and RBAC + +DO: + +- Prefer managed identity and RBAC over access keys when the Azure service supports it. +- Assign least-privilege built-in roles required by consumers. +- Scope role assignments to the smallest practical resource. +- Treat existing Azure resources as read-only intent. Do not add creation-only auth or provisioning mutations to them. +- Use private endpoints or network restrictions when the integration supports private networking and the user opts in. + +DON'T: + +- Don't enable public network access by default when a private endpoint configuration requires denial. +- Don't grant broad owner/contributor roles when a data-plane role is sufficient. +- Don't re-enable shared key access unless there is a service-specific reason. + +## External services + +DO: + +- Make live external credentials explicit prerequisites. +- Expose common production identity settings as first-class APIs, such as service account/managed identity selection, role bindings, and public/private ingress controls. +- Use the target's native secret store/reference shape for deployment manifests instead of materializing secret values. +- Validate least-privilege permissions separately from resource mutation when the target supports non-mutating IAM/probe calls. +- Preserve least-privilege/private-by-default access for deployed resources unless the integration has an explicit public-access API. +- Document and test the identity required for smoke validation when the deployed endpoint is private by default. +- Keep health checks side-effect-free and avoid expensive/rate-limited calls. +- Avoid validating external credentials during `aspire start` unless the user explicitly opted into that behavior. + +DON'T: + +- Don't call chargeable or mutating APIs as part of ordinary app-model construction. +- Don't assume CI has live external-service credentials. +- Don't make deployed endpoints anonymously reachable just to simplify smoke tests. +- Don't provide a blanket "make public" helper that hides broad IAM changes; make the security impact explicit in API names, docs, and tests. + +## Logs and diagnostics + +DO: + +- Redact credentials and tokens in log messages and exception messages. +- Include resource names and operation names in errors without including secret values. +- Log enough context to diagnose missing credentials, missing role assignments, or denied access. + +DON'T: + +- Don't log connection strings that include credentials. +- Don't include secret values in `DistributedApplicationException` messages. diff --git a/.agents/skills/hosting-integration-authoring/resources/selector-matrix.md b/.agents/skills/hosting-integration-authoring/resources/selector-matrix.md new file mode 100644 index 000000000..308b4ba30 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/selector-matrix.md @@ -0,0 +1,76 @@ +# Selector matrix + +Classify an integration by composing values from each axis. The result determines which archetype and cross-cutting resources to apply. + +## Axis 1: resource shape + +| Shape | Typical base/type | Examples | Primary resource | +| --- | --- | --- | --- | +| Container-backed service | `ContainerResource` | MongoDB, PostgreSQL, Redis, Kafka, RabbitMQ | `archetype-container-backed-service.md` | +| Admin/tool container | `ContainerResource` | Adminer, DbGate, MCP Inspector, k6 | `archetype-admin-and-tool-container.md` | +| Setup or migration helper | `ExecutableResource`, `ContainerResource`, or metadata `Resource` | Flyway, SQL DACPAC deployment, package installers, model pulls | `archetype-setup-and-migration-helper.md` | +| Controller/reconciler | Singleton controller service plus environment/control resource | Azure run-mode provisioning controller, deployment reconciler, local infrastructure operator | `archetype-controller-reconciler.md` | +| Sidecar/middleware infrastructure | sidecar/component resources plus annotations | Dapr sidecars/components, OpenTelemetry Collector | `archetype-sidecar-and-middleware.md` | +| Tunnel/webhook bridge | `ContainerResource` or `ExecutableResource` | Ngrok, Stripe CLI webhook forwarding | `archetype-tunnel-and-webhook-bridge.md` | +| Secret provider/broker | `Resource` plus value providers or pipeline steps | Bitwarden Secrets Manager, external secret brokers | `archetype-secret-provider.md` | +| Azure provisioning service | `AzureProvisioningResource` | Azure Storage, Cosmos DB, Key Vault, Service Bus, Azure SQL | `archetype-azure-provisioning.md` | +| External/cloud reference | `Resource` plus connection properties | OpenAI, GitHub Models, external APIs | `archetype-external-cloud-reference.md` | +| Executable/language app | `ExecutableResource` | Python, Go, JavaScript, Node, Vite, Next.js | `archetype-language-executable-app.md` | +| Deployment target/publisher | environment resource plus deployment target annotations | Docker Compose, Kubernetes, Azure Container Apps | `archetype-deployment-target-publisher.md` | +| Overlay/configuration object | custom non-resource model object | Orleans-style app model overlays | `archetype-overlay-configuration.md` | + +## Axis 2: lifecycle mode + +| Mode | Meaning | Common API shape | +| --- | --- | --- | +| Run-capable publish/deploy participant | Runs locally and also participates in manifest or deployment output | `Add{Service}`, child `Add{Resource}` methods | +| Run-only | Exists only for local development/run orchestration and is excluded or no-op in publish/deploy output | `With{DevTool}`, setup sibling resources | +| Publish/deploy-only | Exists only while generating or applying deployment output | `Add{Target}Environment`, `PublishAs{Target}` | +| Dual-mode | Has one shape locally and another shape when publishing/deploying | Azure resource with `RunAsEmulator`, `RunAsContainer`, `InnerResource`, `IsContainer` | +| Mode-agnostic reference | Same app model representation in run and publish | External endpoint/API key resources | + +## Axis 3: role + +| Role | What it does | +| --- | --- | +| Service dependency | Resource referenced by workloads with `WithReference` | +| Workload/app | Runs application code | +| Deployment target | Receives compute resources during publish/deploy | +| Admin/dev companion | Optional local UI/tool container for a parent service | +| Standalone dev tool | Shared or independent local tool not owned by one parent | +| Child/subresource | Database, queue, topic, hub, model, deployment, container, or similar child | +| Setup sibling | Run-mode helper that prepares a workload before it starts | +| Sidecar/middleware | Process or configuration attached to one or more workloads through annotations | +| Bridge/proxy | Local resource that exposes or forwards endpoints to an external system | +| Secret provider | Resource that manages or resolves secrets/credentials for other resources | +| Controller/reconciler | Serializes commands, lifecycle events, background probes, and live-state reconciliation | + +## Axis 4: structure + +| Structure | Pattern | +| --- | --- | +| Standalone | Top-level `Add{Technology}` returns `IResourceBuilder` | +| Parent-child | Child `Add*` method is exposed on parent builder and child implements `IResourceWithParent` | +| Companion | Parent `With{Tool}` method adds a hidden/excluded companion and returns the original parent builder | +| Singleton utility | Top-level `Add{Tool}` returns an existing singleton builder on repeated calls | +| Synthetic facade | Resource has no independent DCP process/container; another resource or external service drives its status, endpoints, and lifecycle | +| Controller-driven | Resource commands and lifecycle hooks enqueue typed intents into a serialized controller/reconciler | +| Annotation-driven | Fluent APIs add annotations; lifecycle hooks discover annotations and materialize derived resources | +| Overlay | `Add*` returns a non-resource configuration object and resource wiring happens through `WithReference`, `AsClient`, or similar | + +## Classification examples + +| Integration | Classification | +| --- | --- | +| PostgreSQL | Container-backed service + run-capable publish/deploy participant + service dependency + parent-child + admin companion | +| Azure Storage with Azurite | Azure provisioning service + dual-mode + service dependency + child resources + emulator | +| Docker Compose | Deployment target/publisher + publish/deploy-only + environment resource + per-resource `PublishAs*` customizations | +| Python Uvicorn app | Executable/language app + run and publish + workload + generated Dockerfile | +| OpenAI | External/cloud reference + mode-agnostic + service dependency + optional child model/deployment resources | +| Orleans | Overlay/configuration object + resource references to compute resources | +| Adminer | Admin/tool container + run-only + standalone singleton utility | +| Dapr | Sidecar/middleware infrastructure + run/deploy bridging + annotation-driven sidecars/components | +| Ngrok | Tunnel/webhook bridge + run-only + bridge/proxy | +| Dev Tunnel port | Tunnel/webhook bridge + run-only + bridge/proxy + synthetic facade endpoint | +| Azure run-mode provisioning | Controller/reconciler + run-mode visible environment control resource + hidden/excluded publish marker + command-driven resource operations + drift detection | +| SQL DACPAC | Setup/migration helper + run-only or deploy-stage setup + service-state deployment | diff --git a/.agents/skills/hosting-integration-authoring/resources/testing-and-readmes.md b/.agents/skills/hosting-integration-authoring/resources/testing-and-readmes.md new file mode 100644 index 000000000..e076c7ee2 --- /dev/null +++ b/.agents/skills/hosting-integration-authoring/resources/testing-and-readmes.md @@ -0,0 +1,108 @@ +# Testing and READMEs + +Integration changes should prove the app model, run behavior, publish/deploy behavior, generated artifacts, and documentation stay consistent. + +## Tests + +DO test: + +- Resource type and name. +- Expected annotations. +- Endpoint names, schemes, target ports, and host-port behavior. +- Container image, tag, and registry annotations. +- Health check registration and keys. +- Real container startup, credential-enforced readiness, and a simple protocol operation for container-backed services when practical. +- Data volume and bind-mount persistence with the actual service when the integration exposes persistence helpers; preserve equivalent functional coverage when porting from another implementation. +- Connection string expressions and connection properties. +- Parent-child resource registration and physical-name defaults. +- Run-mode-only resources are absent from publish manifests. +- Publish-only environment/deployment resources are hidden from run mode. +- `RunAsEmulator`, `RunAsContainer`, `RunAsExisting`, `PublishAsExisting`, and `AsExisting` mode behavior. +- Generated manifests, Bicep, Docker Compose, Kubernetes YAML, or Dockerfiles with snapshots when shape matters. +- `aspire publish` asset behavior separately from `aspire deploy`: publish should produce deterministic, reviewable assets with placeholders/target-native references; deploy should consume or regenerate explicit deploy-time assets, mutate the target, verify provider state, and record outputs. +- Real deploy smoke coverage for new deployment publishers: build/push an image, apply the generated artifact to the target, query the deployed resource status/URL, call the service once, and clean up deployed resources and images. +- Target-specific schema constraints such as resource names, labels, annotations, protocol fields, and IAM defaults when generating artifacts for a new deployment platform. +- The expected deployed access mode. If the target is private by default, smoke tests should call it with the required identity/token; if the integration exposes public access, smoke tests should prove anonymous access only for that explicit opt-in path. +- Production deployment scenarios for new publishers: at least one project resource, one prebuilt container resource, one multi-service app if service references are supported, one secret/reference scenario, one private/default access scenario, and one cleanup or documented external-cleanup path. +- Preflight failure paths for missing CLI, missing auth/context, missing registry access, disabled target APIs/services, and insufficient permissions. +- Polyglot exports when exported API shape changes, including analyzer diagnostics and generated `.d.ts` signatures. +- Controller/reconciler command serialization, conflict detection, command state transitions, cancellation, drift coalescing, and per-resource completion behavior when the integration owns shared external state. +- Solution and CI wiring for new projects. Add new source, test, and example projects to `CommunityToolkit.Aspire.slnx`; when a new test project is added, run `./eng/testing/generate-test-list-for-workflow.sh` and include the `.github/workflows/tests.yml` update. + +DON'T: + +- Don't use live external services in ordinary unit tests. +- Don't drop functional coverage just because the hosting package avoids a client dependency; use raw HTTP/protocol calls when that keeps the hosting integration dependency-free. +- Don't rely on log text for readiness when structured readiness is available. +- Don't use fixed ports in tests unless the test specifically verifies fixed-port behavior. +- Don't mutate static environment or global current directory without cleanup. +- Don't only assert absence; verify the full relevant output shape. +- Don't use live cloud or external services for ordinary controller/reconciler unit tests; fake the controller's external client/provisioner and exercise the queue/command paths directly. +- Don't treat serializer snapshots or fake CLI argument tests as proof that a deployment target accepts the artifact. +- Don't stop validation at "the deploy command exited"; check the provider's resource state and perform the expected authenticated or anonymous request. +- Don't collapse publish and deploy tests into one path; a deployment integration can generate correct assets and still fail provider validation, and provider deploy can work while publish assets are incomplete or unreproducible. + +## Multi-language validation + +When APIs are exported with ATS metadata, validate the generated SDK shape, not just the C# compile. + +DO: + +- Enable the integration analyzer for exported integration projects. +- Keep `ASPIREEXPORT*` diagnostics clean. +- Test with a TypeScript AppHost that references the integration `.csproj` in `aspire.config.json`. +- Run `aspire restore` or `aspire run` to generate `.aspire/modules/`. +- Inspect generated `.d.ts` signatures, imports, DTO shapes, callback context accessors, property accessors, and JSDoc. +- Exercise the generated API in `apphost.mts` when the export shape is new or non-trivial. + +DON'T: + +- Don't assume a C# overload set projects cleanly. +- Don't ship exported APIs without checking generated member names and capability IDs. +- Don't document TypeScript usage until the generated signature has been inspected. + +## README content + +Hosting integration READMEs should focus on AppHost usage, not consuming-app dependency injection. + +Required structure: + +1. `# {Technology} hosting integration` +2. Short description starting with `Use this integration to model, configure, and orchestrate...` +3. `## Getting started` with `aspire add CommunityToolkit.Aspire.Hosting.{Technology}` +4. `## Usage example` showing resource creation and `WithReference` +5. `## Connection Properties` when the resource exposes connection properties +6. `## Additional documentation` +7. `## Feedback & contributing` +8. Trademark notice if required + +Deployment target READMEs should also include: + +- Required local tools and authentication commands. +- Required cloud/project/subscription/cluster prerequisites, including enabled APIs/services and registry setup. +- The default access mode and how to opt into public access if supported. +- Secret handling limitations or the target-native secret reference APIs. +- Publish asset output locations, deploy-time output locations, and cleanup/destroy expectations. +- The difference between `aspire publish` and `aspire deploy`: what assets are generated, what values remain placeholders, what deploy resolves, and what target mutations deploy performs. +- Known limitations that affect production use, such as unsupported service-to-service references, networking, custom domains, or provider-specific resources. + +Usage examples: + +- Show the minimal common AppHost path. +- Include C#. +- Include TypeScript when the APIs are exported for TypeScript. +- Use variable names that match the technology. +- Show child resources such as `.AddDatabase("db")` when they are the primary usage path. + +Connection property tables: + +- Put one table per resource shape when parent and child resources differ. +- Include property names exactly as emitted. +- Include URI/JDBC formats. +- Explain that properties become environment variables named `[RESOURCE]_[PROPERTY]`, for example `DB_URI`. + +DON'T: + +- Don't document consuming-app DI setup in hosting READMEs. +- Don't describe generic health checks, telemetry, or observability unless the integration has unusual AppHost behavior. +- Don't invent TypeScript examples for C#-only or non-exported APIs. diff --git a/.github/agents/hosting-integration-creator.agent.md b/.github/agents/hosting-integration-creator.agent.md index e8753c64d..61da53b86 100644 --- a/.github/agents/hosting-integration-creator.agent.md +++ b/.github/agents/hosting-integration-creator.agent.md @@ -18,609 +18,61 @@ tools: name: Hosting Integration Creator --- -You are an expert in Aspire and C# development, specializing in creating hosting integrations. The repo you are working in is a monorepo that contains multiple hosting integrations (as well as some client integrations, but they can be ignored for your task). +You are an expert in Aspire and C# development, specializing in creating CommunityToolkit hosting integrations. -## Repo structure +## Relevant skills -There are three core directories in the repo: +- aspire +- hosting-integration-authoring -- `src`: Contains all the integrations, each in their own subdirectory as they are separate .NET projects. -- `tests`: Contains all the test projects, each in their own subdirectory corresponding to the integration they are testing. -- `examples`: Contains example projects for each integration, each in their own subdirectory. +Use `hosting-integration-authoring` as the authoritative guidance for hosting integration API design, resource shape, run/publish/deploy behavior, eventing, connection properties, endpoints, security, dashboard UX, polyglot exports, README content, and tests. Use `aspire` when operating an AppHost, inspecting Aspire resources/logs, or validating local distributed-app behavior. -## Hosting Integration Structure +## Scope -Each hosting integration is a .NET project written in C#. The integration will use the naming format of `CommunityToolkit.Aspire.Hosting.[HostingName]`, where `[HostingName]` is the name of the hosting service, such as `RabbitMQ`, `Ollama`, etc. If the integration is for hosting a service in the cloud, the naming format should be `CommunityToolkit.Aspire.Hosting.[CloudProvider].[HostingName]`, such as `CommunityToolkit.Aspire.Hosting.Azure.Dapr`. +Create hosting integrations only. Client integrations belong in `CommunityToolkit.Aspire.[IntegrationName]` projects and are handled by the Client Integration Creator agent. -Each integration project will contain the following core files: +Hosting integration project names follow: -- `CommunityToolkit.Aspire.Hosting.[HostingName].csproj`: The project file for the integration. -- `[HostingName]Extensions.cs`: Contains extension methods for integrating with Aspire. -- `[HostingName]Resource.cs`: Contains resource definitions for the hosting integration. -- `README.md`: Documentation for the hosting integration. +- `CommunityToolkit.Aspire.Hosting.[IntegrationName]` +- `CommunityToolkit.Aspire.Hosting.[CloudProvider].[IntegrationName]` for cloud-provider-specific integrations -There may be other files as well, depending on the specific requirements of the hosting integration. +Core repo locations: -### csproj File +- `src/CommunityToolkit.Aspire.Hosting.[IntegrationName]/` +- `tests/CommunityToolkit.Aspire.Hosting.[IntegrationName].Tests/` +- `examples/[integration-name]/CommunityToolkit.Aspire.Hosting.[IntegrationName].AppHost/` -Here is an example of a basic `csproj` file for a hosting integration, where the integration is for hosting Bun apps: +## Workflow -```xml - +1. Clarify the target service/runtime and required behavior if the user did not provide enough detail. +2. Read `hosting-integration-authoring` first, classify the integration, and apply every relevant archetype/checklist before designing the API. +3. Inspect nearby integrations with the same archetype before creating files. Reuse repo conventions instead of inventing new patterns. +4. Scaffold the source project, test project, example AppHost, and README. +5. Add new projects to `CommunityToolkit.Aspire.slnx`. +6. If a new test project is added, run `./eng/testing/generate-test-list-for-workflow.sh` and include the `.github/workflows/tests.yml` update. +7. Validate with the narrowest relevant build/test command. Do not run broad test suites unless necessary. - - hosting bun javascript - An Aspire integration for hosting Bun apps. - +## Non-negotiable repo conventions - - - +- Extension methods live in the `Aspire.Hosting` namespace. +- Resource types live in `Aspire.Hosting.ApplicationModel` unless nearby integrations use a narrower established pattern. +- Public APIs require XML documentation. +- Validate public method inputs with `ArgumentNullException.ThrowIfNull` and `ArgumentException.ThrowIfNullOrEmpty`/`ThrowIfNullOrWhiteSpace` as appropriate. +- Use `[ResourceName]` on Aspire resource-name parameters. +- Reference `Aspire.Hosting` from hosting integration projects. +- Do not create or manually edit `api` folders or generated `*/api/*.cs` files. +- Do not use floating container image tags such as `latest` unless the upstream image has no stable alternative and the README documents the tradeoff. +- Mark Docker-dependent tests with `[RequiresDocker]`. +- Keep development-only tools and setup helpers out of publish manifests with `.ExcludeFromManifest()`. - -``` +## Expected output -When generating the `csproj` file, ensure that the `AdditionalPackageTags` and `Description` properties are relevant to the specific hosting integration being created. +The completed integration should include: -Once the `csproj` file is created, it needs to be added to the `CommunityToolkit.Aspire.slnx` solution file. You can find the solution file in the root of the repo. If you have access to the terminal, you can run `dotnet sln CommunityToolkit.Aspire.slnx add src/CommunityToolkit.Aspire.Hosting.[HostingName]/CommunityToolkit.Aspire.Hosting.[HostingName].csproj` to add the project to the solution. Otherwise, you can manually edit the solution file to include the new project. - -### Extension Methods File - -The `[HostingName]Extensions.cs` file contains extension methods for integrating the hosting service with Aspire. Here are some rules on how to create the extension methods: - -- The file should be named `[HostingName]Extensions.cs`, where `[HostingName]` is the name of the hosting service. -- The namespace should be `Aspire.Hosting`. -- The class should be named `[HostingName]Extensions`. -- The class should be `public static`. -- Methods to add the hosting integration should use `IDistribuedApplicationBuilder` as the type for the `this` parameter. -- Methods to add the hosting integration follow the naming convention of `Add[HostingName]`. -- Each integration will require a `name` to be passed as the first parameter (after the `this` parameter). It is of type `string` and needs a `ResourceName` attribute. -- Additional parameters can be added as needed, such as configuration options. -- Each method should return `IResourceBuilder<[HostingName]Resource>`. -- XML documentation comments should be included for each method, describing its purpose and parameters. -- Parameters should be validated, throwing `ArgumentNullException`/`ArgumentException` where appropriate. - -Here is an example of what the `[HostingName]Extensions.cs` file might look like for a hypothetical hosting service called "Bun": - -```csharp -namespace Aspire.Hosting; - -/// -/// Extension methods for adding a Bun app to a . -/// -public static class BunAppExtensions -{ - /// - /// Adds a Bun app to the builder. - /// - /// The to add the resource to. - /// The name of the resource. - /// The working directory. - /// The entry point, either a file or package.json script name. - /// Whether to watch for changes. - /// A reference to the . - public static IResourceBuilder AddBunApp( - this IDistributedApplicationBuilder builder, - [ResourceName] string name, - string? workingDirectory = null, - string entryPoint = "index.ts", - bool watch = false) - { - ArgumentNullException.ThrowIfNull(builder, nameof(builder)); - ArgumentException.ThrowIfNullOrEmpty(name, nameof(name)); - ArgumentException.ThrowIfNullOrEmpty(entryPoint, nameof(entryPoint)); - - workingDirectory ??= Path.Combine("..", name); - - var resource = new BunAppResource(name, PathNormalizer.NormalizePathForCurrentPlatform(Path.Combine(builder.AppHostDirectory, workingDirectory))); - - string[] args = watch ? ["--watch", "run", entryPoint] : ["run", entryPoint]; - - return builder.AddResource(resource) - .WithBunDefaults() - .WithArgs(args); - } -} -``` - -### Resource File - -The `[HostingName]Resource.cs` file contains the resource definition for the hosting integration. There are some common base types for a `Resource`: - -- `ContainerResource`: For hosting integrations that use containers (e.g., Docker). -- `ExecutableResource`: For hosting integrations that run executables directly. -- `Resource`: This is a really basic resource type that should be used sparingly. - -Additionally, there are some interfaces that can add additional functionality to a resource: - -- `IResourceWithConnectionString`: For resources that provide a connection string. -- `IResourceWithEndpoints`: For resources that expose endpoints. - -Here is an example of what the `[HostingName]Resource.cs` file looks like for the `BunAppResource`, which is an `ExecutableResource`: - -```csharp -namespace Aspire.Hosting.ApplicationModel; - -/// -/// Represents a Bun app resource. -/// -/// The name of the resource. -/// The working directory for the Bun app to launch from. -public class BunAppResource(string name, string workingDirectory) : - ExecutableResource(name, "bun", workingDirectory); -``` - -Here is a more complex example from the `OllamaResource`, which is a `ContainerResource` and implements `IResourceWithConnectionString`: - -```csharp -namespace Aspire.Hosting.ApplicationModel; - -/// -/// A resource that represents an Ollama container. -/// -/// -/// Constructs an . -/// -/// The name for the resource. -public class OllamaResource(string name) : ContainerResource(name), IResourceWithConnectionString -{ - internal const string OllamaEndpointName = "http"; - - private readonly List _models = []; - - private EndpointReference? _primaryEndpointReference; - - /// - /// Adds a model to the list of models to download on initial startup. - /// - public IReadOnlyList Models => _models; - - /// - /// Gets the endpoint for the Ollama server. - /// - public EndpointReference PrimaryEndpoint => _primaryEndpointReference ??= new(this, OllamaEndpointName); - - /// - /// Gets the connection string expression for the Ollama server. - /// - public ReferenceExpression ConnectionStringExpression => - ReferenceExpression.Create( - $"Endpoint={PrimaryEndpoint.Property(EndpointProperty.Scheme)}://{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}" - ); - - /// - /// Adds a model to the list of models to download on initial startup. - /// - /// The name of the model - public void AddModel(string modelName) - { - ArgumentException.ThrowIfNullOrEmpty(modelName, nameof(modelName)); - if (!_models.Contains(modelName)) - { - _models.Add(modelName); - } - } -} -``` - -### README.md - -The `README.md` file should contain documentation for the hosting integration. It should include the following sections: - -- Overview -- Installation -- Configuration -- Usage - -## Tests - -Each hosting integration should have a corresponding test project in the `tests` directory. The test project should follow the naming convention of `CommunityToolkit.Aspire.Hosting.[HostingName].Tests`. The test project should contain unit tests that cover the functionality of the hosting integration, as well as integration tests that runs the sample project from the `examples` directory. - -Here's an example of the `csproj` file for the test project: - -```xml - - - - - - -``` - -Once the test project is created, it needs to be added to the `CommunityToolkit.Aspire.slnx` solution file. You can find the solution file in the root of the repo. If you have access to the terminal, you can run `dotnet sln CommunityToolkit.Aspire.slnx add tests/CommunityToolkit.Aspire.Hosting.[HostingName].Tests/CommunityToolkit.Aspire.Hosting.[HostingName].Tests.csproj` to add the project to the solution. Otherwise, you can manually edit the solution file to include the new project. - -Lastly, ensure that the new test project is added to `.github/workflows/tests.yml` so that the tests are run in CI. - -## Sample Application - -Each hosting integration should have a corresponding sample application in the `examples` directory, within a subfolder for the hosting integration. At a minimum, the sample application **must** contain an AppHost project that demonstrates how to use the hosting integration. The sample application can also contain other projects, such as client applications that connect to the hosted service. - -Here is an example of the `csproj` file for the AppHost project for the Bun hosting integration: - -```xml - - - - Exe - true - 7e518d7d-87e8-4337-8806-1c99acce5dfb - - - - - - - -``` - -The AppHost project must contain a `AppHost.cs` file that sets up the distributed application and uses the hosting integration. Here is an example of what the `AppHost.cs` file might look like for the Bun hosting integration: - -```csharp -var builder = DistributedApplication.CreateBuilder(args); - -var api = builder.AddBunApp("api") - .WithBunPackageInstallation() - .WithHttpEndpoint(env: "PORT") - .WithHttpHealthCheck("/"); - -builder.Build().Run(); -``` - -Once the sample application is created, it needs to be added to the `CommunityToolkit.Aspire.slnx` solution file. You can find the solution file in the root of the repo. If you have access to the terminal, you can run `dotnet sln CommunityToolkit.Aspire.slnx add examples/[HostingName]/CommunityToolkit.Aspire.Hosting.[HostingName].AppHost/CommunityToolkit.Aspire.Hosting.[HostingName].AppHost.csproj` to add the project to the solution. Otherwise, you can manually edit the solution file to include the new project. - -### Create a plan - -When a user requests a new hosting integration, create a step-by-step plan for creating the integration. The plan should include: - -1. **Define the Hosting Integration**: Clearly define the scope and requirements of the hosting integration. What features and functionalities should it include? - -2. **Set Up the Project Structure**: Create the necessary project structure for the hosting integration. This includes creating the main project, test project, and example project. - -3. **Implement the Hosting Integration**: Develop the hosting integration according to the defined requirements. This may involve creating new classes, methods, and configuration files. - -4. **Create Tests**: Write unit tests and integration tests for the hosting integration. Ensure that all functionality is covered by tests. - -5. **Create Documentation**: Update the README.md file with documentation for the hosting integration. Include sections for Overview, Installation, Configuration, and Usage. - -6. **Create a Sample Application**: Develop a sample application that demonstrates how to use the hosting integration. This should be included in the examples directory. - -7. **Add Projects to Solution**: Add the new projects (test and example) to the CommunityToolkit.Aspire.slnx solution file. - -8. **Review and Refine**: Review the implementation, tests, and documentation. Make any necessary refinements before finalizing the integration. - -### Container Image Tag Guidance - -For any container-based hosting integration, you MUST use a stable, explicit image reference. Prefer a concrete `major.minor` tag (e.g. `myimage:1.4`) or an immutable `sha256` digest. Do NOT use floating tags such as `latest`, `edge`, or bare major tags (`1`). This ensures reproducible local and CI builds and avoids unexpected upstream changes. - -If the upstream project does not publish versioned tags, capture the current digest using `docker pull ` followed by `docker image inspect --format '{{.RepoDigests}}' ` and pin that digest. Document the chosen strategy in the integration `README.md` under a “Image Versioning” or “Upstream Image” section. - -### Add vs With Method Conventions - -Extension methods follow a two-phase pattern: - -- `Add[HostingName]()` methods CREATE and register the resource on the `IDistributedApplicationBuilder`. They should return `IResourceBuilder` and may perform basic validation and setup. -- `With[Capability]()` methods MODIFY or augment an already-added resource via the fluent `IResourceBuilder` chain (e.g. `.WithHttpEndpoint()`, `.WithArgs()`, `.WithEnvironment()`, `.WithModel("llama2")`). These should never add the resource again; they only decorate or configure it. - -When adding new configuration options, prefer a `WithXyz(...)` method over adding more parameters to the original `Add...()` unless the parameter is fundamental (e.g. mandatory port, mandatory working directory). Keep the `Add...()` signature concise. - -### Expanded Testing Guidance - -Testing consists of UNIT and INTEGRATION layers: - -1. Unit tests (in `tests/CommunityToolkit.Aspire.Hosting.[HostingName].Tests`) should validate: - - Resource construction (properties, defaults, tags, endpoints, connection string expression shape). - - Extension method behavior (e.g. `AddXyz()` returns a builder whose resource has expected defaults). - - Fluent `With...()` methods mutate the builder/resource as expected (args appended, endpoints created, environment variables present). - -2. Integration tests use the example AppHost via `ProjectReference` and `AspireIntegrationTestFixture` (from `CommunityToolkit.Aspire.Testing`). To create one: - - ```csharp - public class HostingNameIntegrationTests(AspireIntegrationTestFixture fixture) : IClassFixture> - { - [Fact] - public async Task ResourceStartsAndHealthCheckPasses() - { - await fixture.ResourceNotificationService.WaitForResourceHealthyAsync("resource-name").WaitAsync(TimeSpan.FromSeconds(30)); - - var client = fixture.CreateHttpClient("resource-name"); - var response = await client.GetAsync("/"); - Assert.True(response.IsSuccessStatusCode); - } - } - ``` - -Key points: - -- If the resource exposes a connection string via `IResourceWithConnectionString`, assert the generated expression includes expected host/port scheme parts. -- Mark tests requiring Docker with `[RequiresDocker]` so that CI filters them appropriately. -- After adding the test project, run `./eng/testing/generate-test-list-for-workflow.sh` and update `.github/workflows/tests.yml`. - -### Auto-Generated `api` Folder Warning - -Do NOT create or manually edit an `api` folder or any files within it for hosting integrations. Files under paths like `src/CommunityToolkit.Aspire.Hosting.[HostingName]/api/` are generated automatically (e.g. by source generators or build tooling). Manual changes will be overwritten and should instead be implemented in normal source files outside `api`. If you need new generated capabilities, extend the generator or add new partial types outside the `api` directory. - -## Language-Based Hosting Integrations - -When creating hosting integrations for programming languages (e.g., Golang, Rust, Python, Node.js, Java), there are additional capabilities and patterns to consider beyond basic executable hosting: - -### Package Manager Support - -Language-based hosting integrations should support the ecosystem's package managers and dependency management: - -- **Build Tags/Features**: Allow users to specify conditional compilation flags (e.g., Go build tags: `buildTags: ["dev", "production"]`) -- **Dependency Installation**: Optionally support automatic dependency installation before running (e.g., `go mod download`, `npm install`) -- **Executable Path Flexibility**: Support both default entry points (e.g., `"."` for Go) and custom paths (e.g., `"./cmd/server"`) - -Example from Golang integration: - -```csharp -public static IResourceBuilder AddGolangApp( - this IDistributedApplicationBuilder builder, - [ResourceName] string name, - string workingDirectory, - string executable, - string[]? args = null, - string[]? buildTags = null) -{ - var allArgs = new List { "run" }; - - if (buildTags is { Length: > 0 }) - { - allArgs.Add("-tags"); - allArgs.Add(string.Join(",", buildTags)); - } - - allArgs.Add(executable); - // ... rest of implementation -} -``` - -### Publish as Container (Multi-Stage Builds) - -Language-based integrations should automatically generate optimized Dockerfiles using multi-stage builds when publishing: - -- **Build Stage**: Uses the language's official SDK image to compile/build the application -- **Runtime Stage**: Uses a minimal base image (e.g., Alpine Linux) for smaller final images -- **Security**: Install necessary certificates (e.g., CA certificates for HTTPS support) -- **Optimization**: Disable unnecessary features in build (e.g., `CGO_ENABLED=0` for Go) - -Example from Golang integration: - -```csharp -private static IResourceBuilder PublishAsGolangDockerfile( - this IResourceBuilder builder, - string workingDirectory, - string executable, - string[]? buildTags) -{ - const string DefaultAlpineVersion = "3.21"; - - return builder.PublishAsDockerFile(publish => - { - publish.WithDockerfileBuilder(workingDirectory, context => - { - var buildArgs = new List { "build", "-o", "server" }; - - if (buildTags is { Length: > 0 }) - { - buildArgs.Add("-tags"); - buildArgs.Add(string.Join(",", buildTags)); - } - - buildArgs.Add(executable); - - // Get custom base image from annotation, if present - context.Resource.TryGetLastAnnotation(out var baseImageAnnotation); - var goVersion = baseImageAnnotation?.BuildImage ?? GetDefaultGoBaseImage(workingDirectory, context.Services); - - var buildStage = context.Builder - .From(goVersion, "builder") - .WorkDir("/build") - .Copy(".", "./") - .Run(string.Join(" ", ["CGO_ENABLED=0", "go", .. buildArgs])); - - var runtimeImage = baseImageAnnotation?.RuntimeImage ?? $"alpine:{DefaultAlpineVersion}"; - - context.Builder - .From(runtimeImage) - .Run("apk --no-cache add ca-certificates") - .WorkDir("/app") - .CopyFrom(buildStage.StageName!, "/build/server", "/app/server") - .Entrypoint(["/app/server"]); - }); - }); -} -``` - -### TLS/HTTPS Support - -For language integrations that may need secure connections: - -- **CA Certificates**: Install CA certificates in runtime image for HTTPS client requests -- **Runtime Configuration**: Ensure the generated container supports TLS connections (e.g., `apk --no-cache add ca-certificates`) - -Example from Golang Dockerfile generation: - -```csharp -context.Builder - .From(runtimeImage) - .Run("apk --no-cache add ca-certificates") // Enables HTTPS support - .WorkDir("/app") - // ... rest of configuration -``` - -### Version Detection - -Automatically detect and use the appropriate language version from project files: - -- **Project Files**: Parse version from language-specific files (e.g., `go.mod`, `package.json`, `Cargo.toml`) -- **Installed Toolchain**: Fall back to the installed language toolchain version -- **Default Version**: Use a sensible default if detection fails - -Example from Golang integration: - -```csharp -internal static string? DetectGoVersion(string workingDirectory, ILogger logger) -{ - // Check go.mod file - var goModPath = Path.Combine(workingDirectory, "go.mod"); - if (File.Exists(goModPath)) - { - try - { - var goModContent = File.ReadAllText(goModPath); - // Look for "go X.Y" or "go X.Y.Z" line in go.mod - var match = Regex.Match(goModContent, @"^\s*go\s+(\d+\.\d+(?:\.\d+)?)", RegexOptions.Multiline); - if (match.Success) - { - var version = match.Groups[1].Value; - // Extract major.minor (e.g., "1.22" from "1.22.3") - var versionParts = version.Split('.'); - if (versionParts.Length >= 2) - { - var majorMinor = $"{versionParts[0]}.{versionParts[1]}"; - logger.LogDebug("Detected Go version {Version} from go.mod file", majorMinor); - return majorMinor; - } - } - } - catch (IOException ex) - { - logger.LogDebug(ex, "Failed to parse go.mod file due to IO error"); - } - } - - // Try to detect from installed Go toolchain - try - { - var startInfo = new ProcessStartInfo - { - FileName = "go", - Arguments = "version", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = Process.Start(startInfo); - if (process != null) - { - var output = process.StandardOutput.ReadToEnd(); - process.WaitForExit(); - - if (process.ExitCode == 0) - { - var match = Regex.Match(output, @"go version go(\d+\.\d+)"); - if (match.Success) - { - var version = match.Groups[1].Value; - logger.LogDebug("Detected Go version {Version} from installed toolchain", version); - return version; - } - } - } - } - catch (Exception ex) - { - logger.LogDebug(ex, "Failed to detect Go version from installed toolchain"); - } - - logger.LogDebug("No Go version detected, will use default version"); - return null; -} -``` - -### Optimizing and Securing Container Images - -Allow users to customize base images for both build and runtime stages: - -- **Customizable Build Image**: Let users override the builder/SDK image (e.g., `golang:1.22-alpine` instead of `golang:1.22`) -- **Customizable Runtime Image**: Let users override the runtime image (e.g., `alpine:3.20` instead of `alpine:3.21`) -- **Annotation-Based Configuration**: Use annotations to store custom base image settings - -Example annotation and extension method: - -```csharp -// Annotation to store custom base images -internal sealed record DockerfileBaseImageAnnotation : IResourceAnnotation -{ - public string? BuildImage { get; init; } - public string? RuntimeImage { get; init; } -} - -// Extension method to configure base images -public static IResourceBuilder WithDockerfileBaseImage( - this IResourceBuilder builder, - string? buildImage = null, - string? runtimeImage = null) - where TResource : IResource -{ - return builder.WithAnnotation(new DockerfileBaseImageAnnotation - { - BuildImage = buildImage, - RuntimeImage = runtimeImage - }); -} -``` - -Usage example: - -```csharp -var golang = builder.AddGolangApp("golang", "../gin-api") - .WithHttpEndpoint(env: "PORT") - .WithDockerfileBaseImage( - buildImage: "golang:1.22-alpine", - runtimeImage: "alpine:3.20"); -``` - -### Documentation Requirements - -For language-based integrations, the README.md should include: - -- **Publishing Section**: Explain automatic Dockerfile generation -- **Version Detection**: Document how version detection works -- **Customization Options**: Show how to customize base images -- **TLS Support**: Note that CA certificates are included for HTTPS -- **Build Options**: Document package manager flags, build tags, etc. - -Example README structure: - -```markdown -## Publishing - -When publishing your Aspire application, the [Language] resource automatically generates a multi-stage Dockerfile for containerization. - -### Automatic Version Detection - -The integration automatically detects the [Language] version to use by: - -1. Checking the [project file] for the version directive -2. Falling back to the installed toolchain version -3. Using [version] as the default if no version is detected - -### Customizing Base Images - -You can customize the base images used in the Dockerfile: - -[code example] - -### Generated Dockerfile - -The automatically generated Dockerfile: - -- Uses the detected or default [Language] version as the build stage -- Uses a minimal runtime image for a smaller final image -- Installs CA certificates for HTTPS support -- Respects build options if specified -``` - -### Testing Considerations - -When testing language-based integrations: - -- **Unit Tests**: Verify build arguments, version detection logic, and annotation handling -- **Integration Tests**: Test the full publishing workflow if possible -- **Version Detection Tests**: Mock file system and process execution to test version detection -- **Dockerfile Generation**: Verify the generated Dockerfile structure matches expectations +1. A packable hosting integration project under `src/`. +2. A README focused on AppHost usage. +3. An example AppHost under `examples/`. +4. A corresponding xUnit test project under `tests/`. +5. Solution and CI test-list updates when applicable. +If the requested integration needs an uncommon pattern, prefer adding small, well-scoped code that matches existing Toolkit implementations over copying a large generic template.