Skip to content

Commit ecf77f8

Browse files
committed
Add support for GitHub Container Registry image push in the pipeline; update .editorconfig and add CliWrap dependency
1 parent 70568a7 commit ecf77f8

File tree

4 files changed

+62
-33
lines changed

4 files changed

+62
-33
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: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -26,37 +26,11 @@ jobs:
2626
- name: Install Aspire CLI
2727
run: dotnet tool install --global aspire.cli
2828

29-
- name: Run Aspire Do Build
30-
run: aspire do build
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: Query, 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-
42-
# Query docker images and find the latest chatapi and chatui images (most recently built)
43-
CHATAPI_IMAGE=$(docker images --format "{{.CreatedAt}}\t{{.Repository}}:{{.Tag}}" | grep -E $'^\t[^/]*chatapi:' | sort -r | head -n 1 | awk '{print $NF}')
44-
CHATUI_IMAGE=$(docker images --format "{{.CreatedAt}}\t{{.Repository}}:{{.Tag}}" | grep -E $'^\t[^/]*chatui:' | sort -r | head -n 1 | awk '{print $NF}')
45-
46-
# Tag and push chatapi if found
47-
if [ -n "$CHATAPI_IMAGE" ]; then
48-
echo "Found chatapi image: $CHATAPI_IMAGE"
49-
docker tag $CHATAPI_IMAGE ghcr.io/${{ github.repository_owner }}/chatapi:${SANITIZED_BRANCH_NAME}-${BUILD_NUMBER}
50-
docker push ghcr.io/${{ github.repository_owner }}/chatapi:${SANITIZED_BRANCH_NAME}-${BUILD_NUMBER}
51-
else
52-
echo "Warning: No chatapi image found"
53-
fi
54-
55-
# Tag and push chatui if found
56-
if [ -n "$CHATUI_IMAGE" ]; then
57-
echo "Found chatui image: $CHATUI_IMAGE"
58-
docker tag $CHATUI_IMAGE ghcr.io/${{ github.repository_owner }}/chatui:${SANITIZED_BRANCH_NAME}-${BUILD_NUMBER}
59-
docker push ghcr.io/${{ github.repository_owner }}/chatui:${SANITIZED_BRANCH_NAME}-${BUILD_NUMBER}
60-
else
61-
echo "Warning: No chatui image found"
62-
fi
32+
- name: Push to GitHub Container Registry
33+
run: aspire do push-gh
34+
env:
35+
GHCR_REPO: ghcr.io/${{ github.repository_owner }}
36+
TAG_SUFFIX: ${{ github.run_number }}

AIChat.AppHost/AIChat.AppHost.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<PackageReference Include="Aspire.Hosting.JavaScript" Version="13.0.0" />
1818
<PackageReference Include="Aspire.Hosting.Yarp" Version="13.0.0" />
1919
<PackageReference Include="CommunityToolkit.Aspire.Hosting.Ollama" Version="9.9.0" />
20+
<PackageReference Include="CliWrap" Version="3.6.7" />
2021
</ItemGroup>
2122

2223
<ItemGroup>

AIChat.AppHost/Program.cs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
using Aspire.Hosting.Pipelines;
2+
using CliWrap;
3+
using Microsoft.Extensions.Logging;
4+
15
var builder = DistributedApplication.CreateBuilder(args);
26

37
// Publish this as a Docker Compose application
@@ -67,7 +71,7 @@
6771
.WithUrl("", "Chat UI");
6872

6973
// We use YARP as the static file server and reverse proxy.
70-
builder.AddYarp("chatui")
74+
var yarp =builder.AddYarp("chatui")
7175
.WithExternalHttpEndpoints()
7276
.PublishWithStaticFiles(frontend)
7377
.WithConfiguration(c =>
@@ -76,5 +80,52 @@
7680
})
7781
.WithExplicitStart();
7882

79-
builder.Build().Run();
83+
// Add a push to GitHub Container Registry step
84+
// that will be executed from the pipeline
85+
builder.Pipeline.AddStep("push-gh", async context =>
86+
{
87+
// Get configuration values
88+
var ghcrRepo = builder.Configuration["GHCR_REPO"] ?? throw new InvalidOperationException("GHCR_REPO environment variable is required");
89+
var tagSuffix = builder.Configuration["TAG_SUFFIX"] ?? "latest";
90+
91+
var resourcesToPublish = new (IResource resource, string imageName)[]
92+
{
93+
(chatapi.Resource, "chatapi"),
94+
(yarp.Resource, "chatui")
95+
};
8096

97+
foreach (var (resource, imageName) in resourcesToPublish)
98+
{
99+
// For project resources, use hardcoded "latest" tag
100+
var localImageName = resource is ProjectResource ? $"{imageName}:latest" : null;
101+
102+
if (localImageName is null && !resource.TryGetContainerImageName(out localImageName))
103+
{
104+
context.Logger.LogWarning("{ImageName} image name not found, skipping", imageName);
105+
continue;
106+
}
107+
108+
var remoteTag = $"{ghcrRepo}/{imageName}:{tagSuffix}";
109+
110+
context.Logger.LogInformation("Tagging {LocalImage} as {RemoteTag}", localImageName, remoteTag);
111+
112+
// Tag the image
113+
await Cli.Wrap("docker")
114+
.WithArguments(["tag", localImageName, remoteTag])
115+
.WithStandardOutputPipe(PipeTarget.ToDelegate(line => context.Logger.LogDebug("{Output}", line)))
116+
.WithStandardErrorPipe(PipeTarget.ToDelegate(line => context.Logger.LogError("{Error}", line)))
117+
.ExecuteAsync();
118+
119+
context.Logger.LogInformation("Pushing {RemoteTag}", remoteTag);
120+
121+
// Push the image
122+
await Cli.Wrap("docker")
123+
.WithArguments(["push", remoteTag])
124+
.WithStandardOutputPipe(PipeTarget.ToDelegate(line => context.Logger.LogDebug("{Output}", line)))
125+
.WithStandardErrorPipe(PipeTarget.ToDelegate(line => context.Logger.LogError("{Error}", line)))
126+
.ExecuteAsync();
127+
}
128+
},
129+
dependsOn: WellKnownPipelineSteps.Build);
130+
131+
builder.Build().Run();

0 commit comments

Comments
 (0)