Skip to content

Commit b6c5b28

Browse files
committed
Update default container image name & tag generation
Fixes #7462
1 parent ad79417 commit b6c5b28

File tree

3 files changed

+100
-11
lines changed

3 files changed

+100
-11
lines changed

src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs

+9-6
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static IResourceBuilder<ContainerResource> AddContainer(this IDistributed
4040
/// <returns>The <see cref="IResourceBuilder{T}"/> for chaining.</returns>
4141
public static IResourceBuilder<ContainerResource> AddContainer(this IDistributedApplicationBuilder builder, [ResourceName] string name, string image, string tag)
4242
{
43-
return AddContainer(builder, name, image)
43+
return AddContainer(builder, name, image)
4444
.WithImageTag(tag);
4545
}
4646

@@ -180,7 +180,7 @@ public static IResourceBuilder<T> WithImage<T>(this IResourceBuilder<T> builder,
180180
}
181181

182182
// For continuity with 9.0 and earlier behaviour, keep the registry and image combined.
183-
var parsedRegistryAndImage = parsedReference.Registry is {}
183+
var parsedRegistryAndImage = parsedReference.Registry is { }
184184
? $"{parsedReference.Registry}/{parsedReference.Image}"
185185
: parsedReference.Image;
186186

@@ -202,10 +202,11 @@ public static IResourceBuilder<T> WithImage<T>(this IResourceBuilder<T> builder,
202202
throw new ArgumentOutOfRangeException(nameof(image), parsedReference.Digest, "invalid digest format");
203203
}
204204

205-
var digest = parsedReference.Digest.Substring(prefix.Length);
205+
var digest = parsedReference.Digest[prefix.Length..];
206206
imageAnnotation.SHA256 = digest;
207207
}
208-
else {
208+
else
209+
{
209210
imageAnnotation.Tag = parsedReference.Tag ?? tag ?? "latest";
210211
}
211212

@@ -381,12 +382,14 @@ public static IResourceBuilder<T> WithDockerfile<T>(this IResourceBuilder<T> bui
381382

382383
var fullyQualifiedDockerfilePath = Path.GetFullPath(dockerfilePath, fullyQualifiedContextPath);
383384

384-
var imageName = builder.GenerateImageName();
385+
var imageName = ImageNameGenerator.GenerateImageName(builder);
386+
var imageTag = ImageNameGenerator.GenerateImageTag(builder);
385387
var annotation = new DockerfileBuildAnnotation(fullyQualifiedContextPath, fullyQualifiedDockerfilePath, stage);
388+
386389
return builder.WithAnnotation(annotation, ResourceAnnotationMutationBehavior.Replace)
387390
.WithImageRegistry(registry: null)
388391
.WithImage(imageName)
389-
.WithImageTag("latest");
392+
.WithImageTag(imageTag);
390393
}
391394

392395
/// <summary>

src/Aspire.Hosting/Utils/ImageNameGenerator.cs

+11-4
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,18 @@ namespace Aspire.Hosting.Utils;
99

1010
internal static class ImageNameGenerator
1111
{
12-
public static string GenerateImageName<T>(this IResourceBuilder<T> builder) where T: IResource
12+
public static string GenerateImageName<T>(this IResourceBuilder<T> builder) where T : IResource
1313
{
14-
var bytes = Encoding.UTF8.GetBytes(builder.ApplicationBuilder.AppHostDirectory);
14+
// Resource names are already constrained to [a-zA-Z0-9-_] by the resource name validation
15+
// so we just have to lowercase it and return it.
16+
return builder.Resource.Name.ToLowerInvariant();
17+
}
18+
19+
public static string GenerateImageTag<T>(this IResourceBuilder<T> builder) where T : IResource
20+
{
21+
var bytes = Encoding.UTF8.GetBytes($"{builder.ApplicationBuilder.AppHostDirectory}{DateTime.UtcNow.Ticks}");
1522
var hash = SHA1.HashData(bytes);
16-
var hex = Convert.ToHexString(hash).ToLower();
17-
return $"{builder.Resource.Name}-image-{hex}";
23+
var hex = Convert.ToHexString(hash).ToLowerInvariant();
24+
return hex;
1825
}
1926
}

tests/Aspire.Hosting.Containers.Tests/WithDockerfileTests.cs

+80-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,85 @@ public async Task ContainerBuildLogsAreStreamedToAppHost()
8383
await app.StopAsync();
8484
}
8585

86+
[Theory]
87+
[InlineData("testcontainer")]
88+
[InlineData("TestContainer")]
89+
[InlineData("test-Container")]
90+
[InlineData("TEST-234-CONTAINER")]
91+
public async Task AddDockerfileUsesLowercaseResourceNameAsImageName(string resourceName)
92+
{
93+
using var builder = TestDistributedApplicationBuilder.Create();
94+
builder.Services.AddLogging(b => b.AddXunit(testOutputHelper));
95+
96+
var (tempContextPath, tempDockerfilePath) = await CreateTemporaryDockerfileAsync();
97+
98+
var dockerFile = builder.AddDockerfile(resourceName, tempContextPath, tempDockerfilePath);
99+
100+
Assert.True(dockerFile.Resource.TryGetLastAnnotation<ContainerImageAnnotation>(out var containerImageAnnotation));
101+
Assert.Equal(resourceName.ToLowerInvariant(), containerImageAnnotation.Image);
102+
}
103+
104+
[Theory]
105+
[InlineData("testcontainer")]
106+
[InlineData("TestContainer")]
107+
[InlineData("test-Container")]
108+
[InlineData("TEST-234-CONTAINER")]
109+
public async Task WithDockerfileUsesLowercaseResourceNameAsImageName(string resourceName)
110+
{
111+
using var builder = TestDistributedApplicationBuilder.Create();
112+
builder.Services.AddLogging(b => b.AddXunit(testOutputHelper));
113+
114+
var (tempContextPath, tempDockerfilePath) = await CreateTemporaryDockerfileAsync();
115+
116+
var dockerFile = builder.AddContainer(resourceName, "someimagename")
117+
.WithDockerfile(tempContextPath, tempDockerfilePath);
118+
119+
Assert.True(dockerFile.Resource.TryGetLastAnnotation<ContainerImageAnnotation>(out var containerImageAnnotation));
120+
Assert.Equal(resourceName.ToLowerInvariant(), containerImageAnnotation.Image);
121+
}
122+
123+
[Fact]
124+
public async Task WithDockerfileUsesGeneratesDifferentHashForImageTagOnEachCall()
125+
{
126+
using var builder = TestDistributedApplicationBuilder.Create();
127+
builder.Services.AddLogging(b => b.AddXunit(testOutputHelper));
128+
129+
var (tempContextPath, tempDockerfilePath) = await CreateTemporaryDockerfileAsync();
130+
131+
var dockerFile = builder.AddContainer("testcontainer", "someimagename")
132+
.WithDockerfile(tempContextPath, tempDockerfilePath);
133+
Assert.True(dockerFile.Resource.TryGetLastAnnotation<ContainerImageAnnotation>(out var containerImageAnnotation1));
134+
var tag1 = containerImageAnnotation1.Tag;
135+
136+
dockerFile.WithDockerfile(tempContextPath, tempDockerfilePath);
137+
Assert.True(dockerFile.Resource.TryGetLastAnnotation<ContainerImageAnnotation>(out var containerImageAnnotation2));
138+
var tag2 = containerImageAnnotation2.Tag;
139+
140+
Assert.NotEqual(tag1, tag2);
141+
}
142+
143+
[Fact]
144+
public async Task WithDockerfileGeneratedImageTagCanBeOverridden()
145+
{
146+
using var builder = TestDistributedApplicationBuilder.Create();
147+
builder.Services.AddLogging(b => b.AddXunit(testOutputHelper));
148+
149+
var (tempContextPath, tempDockerfilePath) = await CreateTemporaryDockerfileAsync();
150+
151+
var dockerFile = builder.AddContainer("testcontainer", "someimagename")
152+
.WithDockerfile(tempContextPath, tempDockerfilePath);
153+
154+
Assert.True(dockerFile.Resource.TryGetLastAnnotation<ContainerImageAnnotation>(out var containerImageAnnotation1));
155+
var generatedTag = containerImageAnnotation1.Tag;
156+
157+
dockerFile.WithImageTag("latest");
158+
Assert.True(dockerFile.Resource.TryGetLastAnnotation<ContainerImageAnnotation>(out var containerImageAnnotation2));
159+
var overriddenTag = containerImageAnnotation2.Tag;
160+
161+
Assert.NotEqual(generatedTag, overriddenTag);
162+
Assert.Equal("latest", overriddenTag);
163+
}
164+
86165
[Fact]
87166
[RequiresDocker]
88167
public async Task WithDockerfileLaunchesContainerSuccessfully()
@@ -110,7 +189,7 @@ public async Task WithDockerfileLaunchesContainerSuccessfully()
110189
var kubernetes = app.Services.GetRequiredService<IKubernetesService>();
111190
var containers = await kubernetes.ListAsync<Container>();
112191

113-
var container = Assert.Single<Container>(containers);
192+
var container = Assert.Single(containers);
114193
Assert.Equal(tempContextPath, container!.Spec!.Build!.Context);
115194
Assert.Equal(tempDockerfilePath, container!.Spec!.Build!.Dockerfile);
116195

0 commit comments

Comments
 (0)