Skip to content

Commit 32302fc

Browse files
Copilotdavidfowl
andauthored
Upgrade to Aspire 13 (#15)
* Initial plan * Upgrade to Aspire 13 - Update package versions and API usage Co-authored-by: davidfowl <[email protected]> * Use AddViteApp with YARP PublishWithStaticFiles and remove deprecated NodeJS extensions Co-authored-by: davidfowl <[email protected]> * Use WithReference for chatapi, update proxy config, and add WithExplicitStart to YARP Co-authored-by: davidfowl <[email protected]> * Use Aspire.AppHost.Sdk directly, simplify vite.config.ts, and remove port resolution Co-authored-by: davidfowl <[email protected]> * Update GitHub Actions to use .NET 10 and aspire do build with dynamic Docker image discovery Co-authored-by: davidfowl <[email protected]> * Improve Docker image discovery with sort by creation date and specific pattern matching Co-authored-by: davidfowl <[email protected]> * Add --log-level debug to aspire do build command Co-authored-by: davidfowl <[email protected]> * Apply suggestions from code review * Apply suggestions from code review * Works * Upgrade project to target .NET 10.0 and update dependencies; remove obsolete Docker Compose file * Add support for GitHub Container Registry image push in the pipeline; update .editorconfig and add CliWrap dependency * Refactor GitHub Container Registry push step into a separate method and update pipeline configuration * Add comment to clarify purpose of GitHub Container Registry push step * Sanitize resource name for Docker repository in GHCR push step --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: davidfowl <[email protected]> Co-authored-by: David Fowler <[email protected]>
1 parent 1f27388 commit 32302fc

File tree

16 files changed

+159
-181
lines changed

16 files changed

+159
-181
lines changed

.editorconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ dotnet_diagnostic.ASPIREPROXYENDPOINTS001.severity = none
88

99
# ASPIREAZURE001: Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
1010
dotnet_diagnostic.ASPIREAZURE001.severity = none
11+
12+
# ASPIREPIPELINES001: Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
13+
dotnet_diagnostic.ASPIREPIPELINES001.severity = silent

.github/workflows/aspire-publish.yml

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,18 @@ jobs:
2121
- name: Set up .NET
2222
uses: actions/setup-dotnet@v3
2323
with:
24-
dotnet-version: '9.x'
24+
dotnet-version: '10.x'
2525

2626
- name: Install Aspire CLI
2727
run: dotnet tool install --global aspire.cli
2828

29-
- name: Run Aspire Publish
30-
run: aspire publish -p docker-compose -o artifacts
31-
working-directory: AIChat.AppHost
32-
3329
- name: Log in to GitHub Container Registry
3430
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
3531

36-
- name: Tag and Push Container Images
37-
run: |
38-
BUILD_NUMBER=${{ github.run_number }}
39-
BRANCH_NAME=${{ github.ref_name }}
40-
SANITIZED_BRANCH_NAME=$(echo "$BRANCH_NAME" | sed 's#[^a-zA-Z0-9._-]#-#g')
41-
for image in chatui chatapi; do
42-
docker tag $image:latest ghcr.io/${{ github.repository_owner }}/$image:${SANITIZED_BRANCH_NAME}-${BUILD_NUMBER}
43-
docker push ghcr.io/${{ github.repository_owner }}/$image:${SANITIZED_BRANCH_NAME}-${BUILD_NUMBER}
44-
done
32+
- name: Push to GitHub Container Registry
33+
run: aspire do push-gh
34+
env:
35+
GHCR_REPO: ghcr.io/${{ github.repository_owner }}
36+
BRANCH_NAME: ${{ github.ref_name }}
37+
BUILD_NUMBER: ${{ github.run_number }}
38+
GIT_SHA: ${{ github.sha }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,3 +482,6 @@ $RECYCLE.BIN/
482482

483483
# Vim temporary swap files
484484
*.swp
485+
486+
487+
aspire-output/

AIChat.AppHost/AIChat.AppHost.csproj

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
2-
3-
<Sdk Name="Aspire.AppHost.Sdk" Version="9.5.0" />
1+
<Project Sdk="Aspire.AppHost.Sdk/13.0.0">
42

53
<PropertyGroup>
64
<OutputType>Exe</OutputType>
7-
<TargetFramework>net9.0</TargetFramework>
5+
<TargetFramework>net10.0</TargetFramework>
86
<ImplicitUsings>enable</ImplicitUsings>
97
<Nullable>enable</Nullable>
108
<IsAspireHost>true</IsAspireHost>
119
<UserSecretsId>04350d74-4615-41b1-a3a6-37edf0e71727</UserSecretsId>
1210
</PropertyGroup>
1311

1412
<ItemGroup>
15-
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.5.0" />
16-
<PackageReference Include="Aspire.Hosting.Docker" Version="9.5.0-preview.1.25474.7" />
17-
<PackageReference Include="Aspire.Hosting.OpenAI" Version="9.5.0-preview.1.25474.7" />
18-
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="9.5.0" />
19-
<PackageReference Include="Aspire.Hosting.Redis" Version="9.5.0" />
20-
<PackageReference Include="Aspire.Hosting.NodeJs" Version="9.5.0" />
21-
<PackageReference Include="Aspire.Hosting.Yarp" Version="9.5.0-preview.1.25474.7" />
22-
<PackageReference Include="CommunityToolkit.Aspire.Hosting.NodeJS.Extensions" Version="9.4.0" />
23-
<PackageReference Include="CommunityToolkit.Aspire.Hosting.Ollama" Version="9.4.0" />
13+
<PackageReference Include="Aspire.Hosting.Docker" Version="13.0.0-preview.1.25560.3" />
14+
<PackageReference Include="Aspire.Hosting.OpenAI" Version="13.0.0" />
15+
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="13.0.0" />
16+
<PackageReference Include="Aspire.Hosting.Redis" Version="13.0.0" />
17+
<PackageReference Include="Aspire.Hosting.JavaScript" Version="13.0.0" />
18+
<PackageReference Include="Aspire.Hosting.Yarp" Version="13.0.0" />
19+
<PackageReference Include="CommunityToolkit.Aspire.Hosting.Ollama" Version="9.9.0" />
20+
<PackageReference Include="CliWrap" Version="3.6.7" />
2421
</ItemGroup>
2522

2623
<ItemGroup>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System.Text.RegularExpressions;
2+
using Aspire.Hosting.Pipelines;
3+
using CliWrap;
4+
using Microsoft.Extensions.Configuration;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace AIChat.AppHost;
9+
10+
public static partial class PipelineExtensions
11+
{
12+
public static void AddGhcrPushStep(this IDistributedApplicationPipeline pipeline, IComputeResource[] resourcesToPublish)
13+
{
14+
// This is is a single step that pushes multiple images to GitHub Container Registry
15+
pipeline.AddStep("push-gh", async context =>
16+
{
17+
var configuration = context.Services.GetRequiredService<IConfiguration>();
18+
19+
// Get raw configuration values from environment
20+
var ghcrRepo = configuration["GHCR_REPO"] ?? throw new InvalidOperationException("GHCR_REPO environment variable is required");
21+
var branchName = configuration["BRANCH_NAME"] ?? throw new InvalidOperationException("BRANCH_NAME environment variable is required");
22+
var buildNumber = configuration["BUILD_NUMBER"] ?? throw new InvalidOperationException("BUILD_NUMBER environment variable is required");
23+
var gitSha = configuration["GIT_SHA"] ?? throw new InvalidOperationException("GIT_SHA environment variable is required");
24+
25+
// Sanitize branch name for Docker tag (replace invalid characters with -)
26+
var sanitizedBranch = SanitizerRegex().Replace(branchName, "-").ToLowerInvariant();
27+
28+
// Use short SHA (first 7 characters)
29+
var shortSha = gitSha.Length > 7 ? gitSha[..7] : gitSha;
30+
31+
// Build tag: <sanitized-branch>-<build-number>-<short-sha>
32+
var tag = $"{sanitizedBranch}-{buildNumber}-{shortSha}";
33+
34+
foreach (var resource in resourcesToPublish)
35+
{
36+
// For project resources, use hardcoded "latest" tag
37+
var localImageName = resource is ProjectResource ? $"{resource.Name}:latest" : null;
38+
39+
if (localImageName is null && !resource.TryGetContainerImageName(out localImageName))
40+
{
41+
context.Logger.LogWarning("{ImageName} image name not found, skipping", resource.Name);
42+
continue;
43+
}
44+
45+
// Sanitize resource name for Docker repository (lowercase, replace invalid chars with -)
46+
var sanitizedResourceName = SanitizerRegex().Replace(resource.Name, "-").ToLowerInvariant();
47+
48+
var remoteTag = $"{ghcrRepo}/{sanitizedResourceName}:{tag}";
49+
50+
context.Logger.LogInformation("Tagging {LocalImage} as {RemoteTag}", localImageName, remoteTag);
51+
52+
// Tag the image
53+
await Cli.Wrap("docker")
54+
.WithArguments(["tag", localImageName, remoteTag])
55+
.WithStandardOutputPipe(PipeTarget.ToDelegate(line => context.Logger.LogDebug("{Output}", line)))
56+
.WithStandardErrorPipe(PipeTarget.ToDelegate(line => context.Logger.LogError("{Error}", line)))
57+
.ExecuteAsync();
58+
59+
context.Logger.LogInformation("Pushing {RemoteTag}", remoteTag);
60+
61+
// Push the image
62+
await Cli.Wrap("docker")
63+
.WithArguments(["push", remoteTag])
64+
.WithStandardOutputPipe(PipeTarget.ToDelegate(line => context.Logger.LogDebug("{Output}", line)))
65+
.WithStandardErrorPipe(PipeTarget.ToDelegate(line => context.Logger.LogError("{Error}", line)))
66+
.ExecuteAsync();
67+
}
68+
},
69+
dependsOn: WellKnownPipelineSteps.Build);
70+
}
71+
72+
[GeneratedRegex("[^a-zA-Z0-9._-]")]
73+
private static partial Regex SanitizerRegex();
74+
}

AIChat.AppHost/Program.cs

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
using Aspire.Hosting.Pipelines;
2+
using AIChat.AppHost;
3+
14
var builder = DistributedApplication.CreateBuilder(args);
25

36
// Publish this as a Docker Compose application
@@ -45,29 +48,38 @@
4548
.WithReference(db)
4649
.WaitFor(db)
4750
.WithReference(cache)
48-
.WaitFor(cache);
51+
.WaitFor(cache)
52+
.WithUrls(context =>
53+
{
54+
foreach (var u in context.Urls)
55+
{
56+
u.DisplayLocation = UrlDisplayLocation.DetailsOnly;
57+
}
4958

50-
if (builder.ExecutionContext.IsRunMode)
51-
{
52-
builder.AddNpmApp("chatui-fe", "../chatui")
53-
.WithNpmPackageInstallation()
54-
.WithHttpEndpoint(env: "PORT")
55-
.WithEnvironment("BACKEND_URL", chatapi.GetEndpoint("http"))
56-
.WithOtlpExporter()
57-
.WithEnvironment("BROWSER", "none");
58-
}
59+
context.Urls.Add(new()
60+
{
61+
Url = "/scalar",
62+
DisplayText = "API Reference",
63+
Endpoint = context.GetEndpoint("https")
64+
});
65+
});
5966

60-
// We use YARP as the static file server and reverse proxy. This is used to test
61-
// the application in a containerized environment.
62-
builder.AddYarp("chatui")
63-
.WithStaticFiles()
67+
var frontend = builder.AddViteApp("chatuife", "../chatui")
68+
.WithReference(chatapi)
69+
.WithEnvironment("BROWSER", "none")
70+
.WithUrl("", "Chat UI");
71+
72+
// We use YARP as the static file server and reverse proxy.
73+
var yarp =builder.AddYarp("chatui")
6474
.WithExternalHttpEndpoints()
65-
.WithDockerfile("../chatui")
75+
.PublishWithStaticFiles(frontend)
6676
.WithConfiguration(c =>
6777
{
68-
c.AddRoute("/api/{**catch-all}", chatapi.GetEndpoint("http"));
78+
c.AddRoute("/api/{**catch-all}", chatapi);
6979
})
7080
.WithExplicitStart();
7181

72-
builder.Build().Run();
82+
// Add a push to GitHub Container Registry step
83+
builder.Pipeline.AddGhcrPushStep([chatapi.Resource, yarp.Resource]);
7384

85+
builder.Build().Run();

AIChat.AppHost/docker-infra/docker-compose.yaml

Lines changed: 0 additions & 89 deletions
This file was deleted.

AIChat.ServiceDefaults/AIChat.ServiceDefaults.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net9.0</TargetFramework>
4+
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<IsAspireSharedProject>true</IsAspireSharedProject>
@@ -10,8 +10,8 @@
1010
<ItemGroup>
1111
<FrameworkReference Include="Microsoft.AspNetCore.App" />
1212

13-
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.5.0" />
14-
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="9.5.0" />
13+
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.0.0" />
14+
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="10.0.0" />
1515
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
1616
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
1717
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />

ChatApi/ChatApi.csproj

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
4-
<TargetFramework>net9.0</TargetFramework>
4+
<TargetFramework>net10.0</TargetFramework>
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>enable</ImplicitUsings>
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.5.0" />
11-
<PackageReference Include="CommunityToolkit.Aspire.OllamaSharp" Version="9.7.0" />
10+
<PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.5.2" />
11+
<PackageReference Include="CommunityToolkit.Aspire.OllamaSharp" Version="9.9.0" />
12+
<PackageReference Include="Scalar.AspNetCore" Version="2.10.3" />
13+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
1214
</ItemGroup>
1315

1416
<ItemGroup>
1517
<ProjectReference Include="..\AIChat.ServiceDefaults\AIChat.ServiceDefaults.csproj" />
16-
<PackageReference Include="Aspire.OpenAI" Version="9.5.0-preview.1.25474.7" />
17-
<PackageReference Include="Aspire.StackExchange.Redis" Version="9.5.0" />
18+
<PackageReference Include="Aspire.OpenAI" Version="13.0.0-preview.1.25560.3" />
19+
<PackageReference Include="Aspire.StackExchange.Redis" Version="13.0.0" />
1820
</ItemGroup>
1921

2022
</Project>

ChatApi/Program.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
using Scalar.AspNetCore;
2+
13
var builder = WebApplication.CreateBuilder(args);
24

5+
builder.Services.AddOpenApi();
6+
37
builder.AddServiceDefaults();
48

59
builder.AddChatClient("llm");
@@ -15,6 +19,13 @@
1519

1620
var app = builder.Build();
1721

22+
if (app.Environment.IsDevelopment())
23+
{
24+
// Map OpenAPI and Scalar
25+
app.MapOpenApi();
26+
app.MapScalarApiReference();
27+
}
28+
1829
app.MapDefaultEndpoints();
1930

2031
app.MapChatApi();

0 commit comments

Comments
 (0)