Skip to content

Improve PR testing container workflow#16184

Open
davidfowl wants to merge 5 commits intomainfrom
davidfowl/pr-testing
Open

Improve PR testing container workflow#16184
davidfowl wants to merge 5 commits intomainfrom
davidfowl/pr-testing

Conversation

@davidfowl
Copy link
Copy Markdown
Contributor

Description

Add a repo-local container workflow for Aspire PR testing and document the tested usage in the pr-testing skill.

  • Add eng/scripts/aspire-pr-container/ with bash and PowerShell runners plus the container image entrypoint.
  • Support optional host-side asciinema recording and keeping the mounted workspace around for later inspection.
  • Update the pr-testing skill to prefer the repo-local runner, document Windows PowerShell usage, and capture the setup, AppHost, recording, and inspection guidance learned while dogfooding PR builds.

This change packages the containerized PR-testing flow used during manual validation so it can be reused consistently from the repo on macOS/Linux/WSL and Windows PowerShell hosts.

No code dependencies were added. The container workflow relies on Docker and gh, and recording additionally requires asciinema on the host.

Validation:

Fixes # (issue)

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

Copilot AI review requested due to automatic review settings April 15, 2026 02:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Packages a repo-local, containerized workflow for testing Aspire PR builds and updates Azure deployment summaries to include Azure Portal links, with documentation updates for the pr-testing skill.

Changes:

  • Added eng/scripts/aspire-pr-container/ (Dockerfile + entrypoint + bash/PowerShell runners) to run PR testing consistently on macOS/Linux/WSL and Windows.
  • Centralized Azure Portal URL/link generation into src/Shared/AzurePortalUrls.cs and updated Azure deployment summaries to include Resource Group + Deployments + per-resource portal links.
  • Expanded Azure deployer tests to validate summary markdown content and portal links.
Show a summary per file
File Description
tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs Adds portal-link-focused summary assertions and resourceId parsing helper for tests.
src/Shared/AzurePortalUrls.cs New shared helper for building Azure Portal URLs and markdown links.
src/Aspire.Hosting.Azure/AzurePortalUrls.cs Removes duplicated AzurePortalUrls implementation in favor of shared version.
src/Aspire.Hosting.Azure/AzureEnvironmentResource.cs Uses shared portal link helpers; adds deployments link to pipeline summary.
src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj Links shared AzurePortalUrls into project; adjusts InternalsVisibleTo.
src/Aspire.Hosting.Azure.AppService/AzureAppServiceWebSiteResource.cs Adds portal link next to deployed endpoint in summary/log output; refactors site/slot naming helper.
src/Aspire.Hosting.Azure.AppService/Aspire.Hosting.Azure.AppService.csproj Links shared AzurePortalUrls into project.
src/Aspire.Hosting.Azure.AppService/AppSvcUrls.cs New helper to build App Service portal links from planId output.
src/Aspire.Hosting.Azure.AppContainers/ContainerAppUrls.cs New helper to build Container App portal links from environmentId output.
src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppResource.cs Adds portal link to summary/log output for container apps (including no-public-endpoints case).
src/Aspire.Hosting.Azure.AppContainers/Aspire.Hosting.Azure.AppContainers.csproj Links shared AzurePortalUrls into project.
eng/scripts/aspire-pr-container/run-aspire-pr-container.sh New bash host runner to build/run the PR-testing container, optionally recording via asciinema.
eng/scripts/aspire-pr-container/run-aspire-pr-container.ps1 New PowerShell host runner mirroring bash runner behavior (including optional recording).
eng/scripts/aspire-pr-container/entrypoint.sh Container entrypoint: runs get-aspire-cli-pr.sh with defaults and configures NuGet source to PR hive.
eng/scripts/aspire-pr-container/Dockerfile New container image definition for PR testing.
.github/skills/pr-testing/SKILL.md Updates guidance to prefer repo-local container runner and documents setup/recording/inspection workflows.

Copilot's findings

  • Files reviewed: 16/16 changed files
  • Comments generated: 4

fi

if [[ -e "$DOCKER_SOCKET_PATH" ]]; then
DOCKER_SOCKET_REALPATH="$(python3 -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$DOCKER_SOCKET_PATH")"
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The runner requires python3 on the host to resolve the Docker socket realpath, but python3 isn't guaranteed to be installed (common on minimal distros or some macOS setups). Replace this with a more portable approach (e.g., prefer realpath if available, else fall back to readlink -f, else use the original path) and emit a clear warning if resolution isn't possible.

Copilot uses AI. Check for mistakes.
Comment on lines +116 to +122
if [[ -z "${GH_TOKEN:-}" && -z "${GITHUB_TOKEN:-}" ]]; then
GH_TOKEN="$(gh auth token)"
export GH_TOKEN
elif [[ -z "${GH_TOKEN:-}" ]]; then
GH_TOKEN="$GITHUB_TOKEN"
export GH_TOKEN
fi
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When GH_TOKEN/GITHUB_TOKEN is unset, the script invokes gh auth token but doesn't check that gh is installed or that the auth call succeeds, so failures can be hard to diagnose under set -e. Add an explicit command -v gh check (and check for an empty token) with a targeted error message describing how to authenticate or provide a token.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +19
internal static async Task<MarkdownString> GetPortalLinkAsync(AzureAppServiceEnvironmentResource computerEnv, string siteName, string? deploymentSlot, CancellationToken cancellationToken)
{
var planIdValue = await computerEnv.PlanIdOutputReference.GetValueAsync(cancellationToken).ConfigureAwait(false)
?? throw new InvalidOperationException($"Missing app service plan id output for '{computerEnv.Name}'.");
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name computerEnv looks like a typo (likely meant to be computeEnv or appServiceEnv). Renaming it would improve readability and reduce confusion with unrelated 'computer' terminology.

Suggested change
internal static async Task<MarkdownString> GetPortalLinkAsync(AzureAppServiceEnvironmentResource computerEnv, string siteName, string? deploymentSlot, CancellationToken cancellationToken)
{
var planIdValue = await computerEnv.PlanIdOutputReference.GetValueAsync(cancellationToken).ConfigureAwait(false)
?? throw new InvalidOperationException($"Missing app service plan id output for '{computerEnv.Name}'.");
internal static async Task<MarkdownString> GetPortalLinkAsync(AzureAppServiceEnvironmentResource appServiceEnv, string siteName, string? deploymentSlot, CancellationToken cancellationToken)
{
var planIdValue = await appServiceEnv.PlanIdOutputReference.GetValueAsync(cancellationToken).ConfigureAwait(false)
?? throw new InvalidOperationException($"Missing app service plan id output for '{appServiceEnv.Name}'.");

Copilot uses AI. Check for mistakes.
Comment on lines +1388 to +1395
var subscriptionId = resourceIdentifier.SubscriptionId;
var resourceGroupName = resourceIdentifier.ResourceGroupName;

return (subscriptionId, resourceGroupName) switch
{
({ Length: > 0 }, { Length: > 0 }) => (subscriptionId, resourceGroupName),
_ => throw new InvalidOperationException($"Resource id '{resourceId}' does not contain both subscription and resource group segments.")
};
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResourceIdentifier.SubscriptionId / .ResourceGroupName are nullable in Azure.Core APIs; assigning them to non-nullable locals can produce nullable warnings (and can be inconsistent with the other helpers added in this PR that use ?? throw). Consider switching these locals to string? and using ?? throw with a dedicated message for each missing segment, or keep the tuple-pattern approach but ensure the local types match the API nullability to avoid warnings-as-errors in test builds.

Suggested change
var subscriptionId = resourceIdentifier.SubscriptionId;
var resourceGroupName = resourceIdentifier.ResourceGroupName;
return (subscriptionId, resourceGroupName) switch
{
({ Length: > 0 }, { Length: > 0 }) => (subscriptionId, resourceGroupName),
_ => throw new InvalidOperationException($"Resource id '{resourceId}' does not contain both subscription and resource group segments.")
};
var subscriptionId = resourceIdentifier.SubscriptionId
?? throw new InvalidOperationException($"Resource id '{resourceId}' does not contain a subscription segment.");
var resourceGroupName = resourceIdentifier.ResourceGroupName
?? throw new InvalidOperationException($"Resource id '{resourceId}' does not contain a resource group segment.");
if (subscriptionId.Length == 0)
{
throw new InvalidOperationException($"Resource id '{resourceId}' contains an empty subscription segment.");
}
if (resourceGroupName.Length == 0)
{
throw new InvalidOperationException($"Resource id '{resourceId}' contains an empty resource group segment.");
}
return (subscriptionId, resourceGroupName);

Copilot uses AI. Check for mistakes.
davidfowl and others added 3 commits April 14, 2026 19:53
Add repo-local PR testing container runners for bash and PowerShell, including optional host-side asciinema recording and workspace reuse guidance. Update the PR testing skill to prefer the container runner, document Windows usage, and capture the setup, apphost, and inspection workflows used during manual PR validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Treat the PR install script as the normal path again and leave setup --force as troubleshooting-only guidance for bundle extraction failures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Treat bundle extraction/layout failures as failures to report rather than steps the agent should auto-repair. Keep the PR-testing skill aligned with what a normal user would actually do after installing a PR build.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 15, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 16184

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 16184"

Strip the install/NuGet automation from the container entrypoint so the runner is just an isolated environment. The dogfood command from the PR comment is used directly inside the container instead.

- Entrypoint reduced to HOME fallback + exec
- Runner scripts no longer pass INSTALL_PREFIX
- Default command is bash when no args given
- Dockerfile unchanged (Node/Python deferred)
- Skill updated to use dogfood command directly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@davidfowl davidfowl force-pushed the davidfowl/pr-testing branch from 4630fa6 to 762c583 Compare April 15, 2026 03:39
The CLI is a self-contained native binary so we don't need the .NET SDK base image. Ubuntu 24.04 is smaller and has glibc compatibility for the native AOT binary.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 2 jobs were identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

@github-actions
Copy link
Copy Markdown
Contributor

🎬 CLI E2E Test Recordings — 71 recordings uploaded (commit 5f3d3e9)

View recordings
Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_DefaultSelection_InstallsSkillOnly ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AllPublishMethodsBuildDockerImages ▶️ View Recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
Banner_NotDisplayedWithNoLogoFlag ▶️ View Recording
CertificatesClean_RemovesCertificates ▶️ View Recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View Recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View Recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunEmptyAppHostProject ▶️ View Recording
CreateAndRunJavaEmptyAppHostProject ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View Recording
CreateAndRunTypeScriptStarterProject ▶️ View Recording
CreateJavaAppHostWithViteApp ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DashboardRunWithOtelTracesReturnsNoTraces ▶️ View Recording
DeployK8sBasicApiService ▶️ View Recording
DeployK8sWithGarnet ▶️ View Recording
DeployK8sWithMongoDB ▶️ View Recording
DeployK8sWithMySql ▶️ View Recording
DeployK8sWithPostgres ▶️ View Recording
DeployK8sWithRabbitMQ ▶️ View Recording
DeployK8sWithRedis ▶️ View Recording
DeployK8sWithSqlServer ▶️ View Recording
DeployK8sWithValkey ▶️ View Recording
DeployTypeScriptAppToKubernetes ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DoListStepsShowsPipelineSteps ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View Recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View Recording
GlobalMigration_PreservesAllValueTypes ▶️ View Recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View Recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View Recording
InitTypeScriptAppHost_AugmentsExistingViteRepoAtRoot ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
OtelLogsReturnsStructuredLogsFromStarterApp ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithConfigureEnvFileUpdatesEnvOutput ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RestoreSupportsConfigOnlyHelperPackageAndCrossPackageTypes ▶️ View Recording
RunFromParentDirectory_UsesExistingConfigNearAppHost ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StartAndWaitForTypeScriptSqlServerAppHostWithNativeAssets ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View Recording

📹 Recordings uploaded automatically from CI run #24435165743

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants