Add aspire resources and aspire logs CLI commands for polyglot testing#14122
Add aspire resources and aspire logs CLI commands for polyglot testing#14122
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14122Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 14122" |
There was a problem hiding this comment.
Pull request overview
Adds new aspire resources and aspire logs CLI commands to support polyglot AppHost integration testing via structured JSON/NDJSON output over the existing auxiliary backchannel.
Changes:
- Introduces
aspire resources(snapshot + watch/NDJSON) andaspire logs(snapshot + follow/NDJSON) CLI commands. - Extends the auxiliary backchannel RPC surface and DTOs to expose richer resource snapshots and stream resource logs.
- Shares the resource JSON schema between Dashboard and CLI, and adds an initial spec document plus end-to-end coverage.
Reviewed changes
Copilot reviewed 61 out of 64 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs | Registers the new CLI commands for test DI. |
| tests/Aspire.Cli.EndToEnd.Tests/ResourcesCommandTests.cs | Adds E2E coverage for aspire resources output (table + --json). |
| tests/Aspire.Cli.EndToEnd.Tests/LogsCommandTests.cs | Adds E2E coverage for aspire logs <resource> output (text + --json). |
| src/Shared/Model/Serialization/ResourceJson.cs | Moves resource JSON schema to a shared namespace and extends it with additional fields. |
| src/Aspire.Hosting/Backchannel/BackchannelDataTypes.cs | Expands backchannel DTOs for richer resource snapshots and introduces log line DTO. |
| src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs | Implements richer resource snapshot mapping and adds log streaming/snapshot RPC methods. |
| src/Aspire.Dashboard/Model/TelemetryExportService.cs | Updates Dashboard to reference shared resource JSON schema. |
| src/Aspire.Dashboard/Model/Serialization/ResourceJsonSerializerContext.cs | Updates serializer context to use shared resource JSON schema types. |
| src/Aspire.Dashboard/Aspire.Dashboard.csproj | Links shared ResourceJson.cs into Dashboard build. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.zh-Hant.xlf | Adds localization entry for new Run --json help text. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.zh-Hans.xlf | Adds localization entry for new Run --json help text. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.tr.xlf | Adds localization entry for new Run --json help text. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.ru.xlf | Adds localization entry for new Run --json help text. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.pt-BR.xlf | Adds localization entry for new Run --json help text. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.pl.xlf | Adds localization entry for new Run --json help text. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.ko.xlf | Adds localization entry for new Run --json help text. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.ja.xlf | Adds localization entry for new Run --json help text. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.it.xlf | Adds localization entry for new Run --json help text. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.fr.xlf | Adds localization entry for new Run --json help text. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.es.xlf | Adds localization entry for new Run --json help text. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.de.xlf | Adds localization entry for new Run --json help text. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.cs.xlf | Adds localization entry for new Run --json help text. |
| src/Aspire.Cli/Resources/xlf/ResourcesCommandStrings.zh-Hant.xlf | Adds localized strings for aspire resources. |
| src/Aspire.Cli/Resources/xlf/ResourcesCommandStrings.zh-Hans.xlf | Adds localized strings for aspire resources. |
| src/Aspire.Cli/Resources/xlf/ResourcesCommandStrings.tr.xlf | Adds localized strings for aspire resources. |
| src/Aspire.Cli/Resources/xlf/ResourcesCommandStrings.ru.xlf | Adds localized strings for aspire resources. |
| src/Aspire.Cli/Resources/xlf/ResourcesCommandStrings.pt-BR.xlf | Adds localized strings for aspire resources. |
| src/Aspire.Cli/Resources/xlf/ResourcesCommandStrings.pl.xlf | Adds localized strings for aspire resources. |
| src/Aspire.Cli/Resources/xlf/ResourcesCommandStrings.ko.xlf | Adds localized strings for aspire resources. |
| src/Aspire.Cli/Resources/xlf/ResourcesCommandStrings.ja.xlf | Adds localized strings for aspire resources. |
| src/Aspire.Cli/Resources/xlf/ResourcesCommandStrings.it.xlf | Adds localized strings for aspire resources. |
| src/Aspire.Cli/Resources/xlf/ResourcesCommandStrings.fr.xlf | Adds localized strings for aspire resources. |
| src/Aspire.Cli/Resources/xlf/ResourcesCommandStrings.es.xlf | Adds localized strings for aspire resources. |
| src/Aspire.Cli/Resources/xlf/ResourcesCommandStrings.de.xlf | Adds localized strings for aspire resources. |
| src/Aspire.Cli/Resources/xlf/ResourcesCommandStrings.cs.xlf | Adds localized strings for aspire resources. |
| src/Aspire.Cli/Resources/xlf/LogsCommandStrings.zh-Hant.xlf | Adds localized strings for aspire logs. |
| src/Aspire.Cli/Resources/xlf/LogsCommandStrings.zh-Hans.xlf | Adds localized strings for aspire logs. |
| src/Aspire.Cli/Resources/xlf/LogsCommandStrings.tr.xlf | Adds localized strings for aspire logs. |
| src/Aspire.Cli/Resources/xlf/LogsCommandStrings.ru.xlf | Adds localized strings for aspire logs. |
| src/Aspire.Cli/Resources/xlf/LogsCommandStrings.pt-BR.xlf | Adds localized strings for aspire logs. |
| src/Aspire.Cli/Resources/xlf/LogsCommandStrings.pl.xlf | Adds localized strings for aspire logs. |
| src/Aspire.Cli/Resources/xlf/LogsCommandStrings.ko.xlf | Adds localized strings for aspire logs. |
| src/Aspire.Cli/Resources/xlf/LogsCommandStrings.ja.xlf | Adds localized strings for aspire logs. |
| src/Aspire.Cli/Resources/xlf/LogsCommandStrings.it.xlf | Adds localized strings for aspire logs. |
| src/Aspire.Cli/Resources/xlf/LogsCommandStrings.fr.xlf | Adds localized strings for aspire logs. |
| src/Aspire.Cli/Resources/xlf/LogsCommandStrings.es.xlf | Adds localized strings for aspire logs. |
| src/Aspire.Cli/Resources/xlf/LogsCommandStrings.de.xlf | Adds localized strings for aspire logs. |
| src/Aspire.Cli/Resources/xlf/LogsCommandStrings.cs.xlf | Adds localized strings for aspire logs. |
| src/Aspire.Cli/Resources/RunCommandStrings.resx | Adds --json help text for aspire run --detach. |
| src/Aspire.Cli/Resources/RunCommandStrings.Designer.cs | Updates strongly-typed resources for Run strings. |
| src/Aspire.Cli/Resources/ResourcesCommandStrings.resx | Adds resource strings for aspire resources. |
| src/Aspire.Cli/Resources/ResourcesCommandStrings.Designer.cs | Adds strongly-typed resources for Resources strings. |
| src/Aspire.Cli/Resources/LogsCommandStrings.resx | Adds resource strings for aspire logs. |
| src/Aspire.Cli/Resources/LogsCommandStrings.Designer.cs | Adds strongly-typed resources for Logs strings. |
| src/Aspire.Cli/Program.cs | Registers the new CLI commands in the host container. |
| src/Aspire.Cli/Commands/StopCommand.cs | Refactors AppHost selection logic to use the shared resolver. |
| src/Aspire.Cli/Commands/RunCommand.cs | Adds --json output support for detached runs via source-generated JSON serialization. |
| src/Aspire.Cli/Commands/RootCommand.cs | Adds resources and logs to the CLI root command. |
| src/Aspire.Cli/Commands/ResourcesCommand.cs | Implements aspire resources command (table, JSON, watch/NDJSON). |
| src/Aspire.Cli/Commands/LogsCommand.cs | Implements aspire logs command (JSON/NDJSON, follow, file output). |
| src/Aspire.Cli/Backchannel/AppHostConnectionResolver.cs | Adds reusable socket-first AppHost discovery/selection helper. |
| src/Aspire.Cli/Backchannel/AppHostAuxiliaryBackchannel.cs | Adds RPC client wrappers for resource log streaming/snapshot retrieval. |
| src/Aspire.Cli/Aspire.Cli.csproj | Links shared ResourceJson.cs into CLI build. |
| docs/specs/polyglot-apphost-testing.md | Adds draft spec documenting CLI primitives, schemas, and wrapper concepts. |
Files not reviewed (3)
- src/Aspire.Cli/Resources/LogsCommandStrings.Designer.cs: Language not supported
- src/Aspire.Cli/Resources/ResourcesCommandStrings.Designer.cs: Language not supported
- src/Aspire.Cli/Resources/RunCommandStrings.Designer.cs: Language not supported
Comments suppressed due to low confidence (3)
docs/specs/polyglot-apphost-testing.md:206
- The documented
aspire resourcesJSON schema uses fields liketype,endpoints, andpropertiesas an object, but the CLI currently serializesResourceJson(e.g.,resourceType,urls, andpropertiesas an array of name/value pairs). Please align the spec’s examples/schema with the actual output (or adjust the CLI output to match the spec).
**Output (JSON):**
```json
{
"resources": [
{
"name": "redis",
"type": "Container",
"state": "Running",
"healthStatus": "Healthy",
"endpoints": [
{
"name": "tcp",
"url": "tcp://localhost:6379",
"isInternal": false
}
],
"connectionString": "localhost:6379",
"properties": {
"container.image": "redis:7.4",
"container.id": "abc123def456"
}
},
{
"name": "api",
"type": "Project",
"state": "Running",
"healthStatus": "Healthy",
"endpoints": [
{
"name": "http",
"url": "http://localhost:5001",
"isInternal": false
},
{
"name": "https",
"url": "https://localhost:5002",
"isInternal": false
}
],
"connectionString": null,
"properties": {
"project.path": "/path/to/Api/Api.csproj"
}
}
]
}
aspire resources --watch
Streams resource snapshots as NDJSON (newline-delimited JSON).
aspire resources --watch [--project <path>]Output (NDJSON):
{"name":"redis","type":"Container","state":"Starting","healthStatus":null,"endpoints":[],"connectionString":null}
{"name":"redis","type":"Container","state":"Running","healthStatus":"Healthy","endpoints":[{"name":"tcp","url":"tcp://localhost:6379"}],"connectionString":"localhost:6379"}
{"name":"api","type":"Project","state":"Starting","healthStatus":null,"endpoints":[]}
{"name":"api","type":"Project","state":"Running","healthStatus":"Healthy","endpoints":[{"name":"http","url":"http://localhost:5001"}]}**docs/specs/polyglot-apphost-testing.md:314**
* The `aspire logs` section documents options/behaviors that aren’t implemented (`--tail`, `--timestamps`, and non-follow `aspire logs` returning all resources, plus a different JSON shape). Please either scope the spec to what’s implemented in this PR or mark the unimplemented flags/output shapes explicitly as future work to avoid misleading consumers.
aspire logs
Retrieves resource console logs from the ResourceLoggerService. These are the same logs displayed in the Aspire Dashboard's console logs view - stdout/stderr from containers, projects, and executables.
See GitHub Issue #8069 for the original feature request.
aspire logs [resource] [--project <path>] [--follow] [--output <dir>] [--json]Arguments:
resource- Optional resource name. If omitted, shows logs for all resources.
Options:
--follow,-f- Stream logs in real-time (likedocker logs -f)--output <dir>- Write logs to files (one file per resource) instead of console--tail <lines>- Number of lines to show from the end (default: all)--timestamps- Include timestamps in output--json- Output logs as JSON/NDJSON for programmatic consumption--project <path>- Path to the AppHost project
Implementation Notes:
- Uses auxiliary backchannel RPC to call
WatchResourceLogsAsyncon the running AppHost - Logs come from
ResourceLoggerService.WatchAsync(resourceName) - Same log source as Dashboard's
WatchResourceConsoleLogsgRPC method
JSON Output
Use --json for structured log output that's easy to parse programmatically.
Single snapshot (aspire logs --json):
{
"logs": [
{
"resource": "redis",
"lineNumber": 1,
"content": "1:C 15 Jan 2024 10:30:14.123 * oO0OoO0OoO0Oo Redis is starting",
"isError": false
},
{
"resource": "api",
"lineNumber": 1,
"content": "info: Application started",
"isError": false
}
]
}Streaming (aspire logs --json --follow):
Outputs NDJSON (one JSON object per line):
{"resource":"redis","timestamp":"2024-01-15T10:30:14.123Z","line":"Ready to accept connections","isError":false}
{"resource":"api","timestamp":"2024-01-15T10:30:15.456Z","line":"info: Application started","isError":false}
{"resource":"api","timestamp":"2024-01-15T10:30:15.789Z","line":"warn: Cache miss","isError":false}Get All Logs
Retrieve logs from all resources at once. Useful for collecting diagnostic information after a test run.
# Get all logs from all resources
aspire logs
# Save all logs to files (one per resource)
aspire logs --output ./test-artifactsOutput (interleaved with resource prefix):
[redis] 1:C 15 Jan 2024 10:30:14.123 * oO0OoO0OoO0Oo Redis is starting
[redis] 1:M 15 Jan 2024 10:30:14.456 * Ready to accept connections
[api] info: Application started
[api] info: Listening on http://localhost:5001
[postgres] LOG: database system is ready to accept connections
Output (files with --output ./logs):
./logs/
redis.log
api.log
postgres.log
Tail/Follow Logs
Stream logs in real-time, similar to docker logs -f or kubectl logs -f.
# Follow all logs
aspire logs --follow
# Follow logs for a specific resource
aspire logs api --follow
# Show last 50 lines then followsrc/Aspire.Cli/Commands/ResourcesCommand.cs:126
ExecuteSnapshotAsyncbreaks on the first repeated resource snapshot (!seenResources.Add(...)), which can return an incomplete resource list if some resources haven’t produced an initial snapshot yet (or if updates arrive before all initial resources are observed). Consider implementing a dedicated non-streaming RPC to fetch the current snapshot set, or keep consumingWatchResourceSnapshotsAsyncuntil an idle timeout with no new resource names (and then emit the latest snapshot per resource).
if (!seenResources.Add(snapshot.Name))
{
// We've already seen this resource, so we have a complete picture
// Update to latest and break
var existingIndex = resources.FindIndex(r => r.Name == snapshot.Name);
if (existingIndex >= 0)
{
resources[existingIndex] = resourceJson;
}
break;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Suggest using |
🎬 CLI E2E Test RecordingsThe following terminal recordings are available for commit
📹 Recordings uploaded automatically from CI run #21392075478 |
7a5d300 to
29c3cb4
Compare
CLI Comparison: Aspire vs DockerCommand Mapping
✅ Aligned with Docker Conventions
📋 Potential Future Enhancements
These can be added in follow-up PRs based on user feedback. |
|
Need unit tests for the new and changed commands. |
35d8afb to
d062119
Compare
This PR adds CLI commands for polyglot AppHost testing scenarios: - `aspire ps` - List running Aspire AppHosts with process info and dashboard URLs - `aspire resources [name]` - Show resources in a running AppHost (table or JSON) - `aspire logs [resource]` - Stream or fetch logs from resources - `--format json|table` output option for all commands - `--follow` mode for streaming logs in real-time - `--tail N` option to show last N log lines (like Docker) - `--watch` mode for streaming resource updates - Automatic AppHost discovery via backchannel sockets - DCP replica name resolution (shows unique names for replicas) - Non-ASCII character support in JSON output (Chinese, Japanese, etc.) - Added `GetResourceSnapshotsAsync` RPC for fetching resource state - Added `GetResourceLogsAsync` RPC with follow/snapshot modes - Fixed `ResourceLogSource` to handle non-cancellable tokens - Added `CommonErrorData` to JSON serialization context for StreamJsonRpc - Fixed `ShowStatusAsync` to handle empty status text for JSON output - Added unit tests for LogsCommand, ResourcesCommand, PsCommand - Tests cover JSON serialization, option parsing, validation, error handling
d062119 to
ab7e7b0
Compare
|
Done |
- Add EnsureConnected() helper to reduce duplication in AppHostAuxiliaryBackchannel - Use JavaScriptEncoder.UnsafeRelaxedJsonEscaping for all CLI JSON output - Use DisplayRawText instead of DisplayPlainText for JSON output - Remove OSC 8 hyperlink formatting in PsCommand (terminals auto-detect URLs) - Use DateTimeOffset consistently instead of DateTime for timestamps - Add RelaxedEscaping property to all JSON contexts
|
|
||
| // Lazy-initialized stderr console for update notifications | ||
| private static IAnsiConsole? s_stderrConsole; | ||
| private static IAnsiConsole StderrConsole => s_stderrConsole ??= AnsiConsole.Create(new AnsiConsoleSettings |
There was a problem hiding this comment.
I needed to do this same thing to display first run experience: https://github.com/dotnet/aspire/pull/13963/changes#diff-f211b05473c1f49afd029a211c33b506235ee9e499189695eba9dd281f77fc69R314
I think we need to improve how we do this, but I'm ok with merging this as-is. Can improve after this PR and telemetry PR are merged.
There was a problem hiding this comment.
Agreed, we should consolidate the stderr console pattern. I'll follow up after both PRs merge to refactor this into a shared helper.
JamesNK
left a comment
There was a problem hiding this comment.
I think unit tests around streamed JSON formatting would be good. Assure that it correctly follows line deliminted format.
Summary
This PR adds CLI commands for polyglot AppHost testing, enabling non-.NET languages (Python, TypeScript, etc.) to programmatically interact with running Aspire applications.
New Commands
aspire psList running AppHost processes with their dashboard URLs.
aspire resourcesList resources in a running AppHost with their state, health, and endpoints.
Use
--watchfor streaming updates (NDJSON format):aspire logsView logs from resources in a running AppHost.
Use
--followfor streaming logs (NDJSON format):JSON Output Format
ps --format json[...]jq '.[].appHostPath'resources --format json{"resources": [...]}jq '.resources[].name'resources --watch --format jsonjq '.name'logs --format json{"logs": [...]}jq '.logs[].content'logs --follow --format jsonjq '.content'Pattern:
--watch/--follow) = NDJSON (line-by-line processing)Key Features
--watchfor resources,--followfor logs--tail Nto get last N log linesRelated
docs/specs/polyglot-apphost-testing.mdTesting
Fixes #8069