From 45373c8ac7ef759c3d6f259245295d6b12d6a295 Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Fri, 8 Mar 2024 14:57:12 -0800 Subject: [PATCH 1/3] Write container volume details to publishing manifest --- .../AppHost/Properties/launchSettings.json | 5 +-- .../Publishing/ManifestPublishingContext.cs | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/playground/TestShop/AppHost/Properties/launchSettings.json b/playground/TestShop/AppHost/Properties/launchSettings.json index f607729fb5..df947e1f10 100644 --- a/playground/TestShop/AppHost/Properties/launchSettings.json +++ b/playground/TestShop/AppHost/Properties/launchSettings.json @@ -14,14 +14,11 @@ }, "generate-manifest": { "commandName": "Project", - "launchBrowser": true, "dotnetRunMessages": true, "commandLineArgs": "--publisher manifest --output-path aspire-manifest.json", - "applicationUrl": "http://localhost:15888", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "DOTNET_ENVIRONMENT": "Development", - "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16031" + "DOTNET_ENVIRONMENT": "Development" } } }, diff --git a/src/Aspire.Hosting/Publishing/ManifestPublishingContext.cs b/src/Aspire.Hosting/Publishing/ManifestPublishingContext.cs index 9aba1f3a2f..e1679f837f 100644 --- a/src/Aspire.Hosting/Publishing/ManifestPublishingContext.cs +++ b/src/Aspire.Hosting/Publishing/ManifestPublishingContext.cs @@ -81,6 +81,7 @@ public async Task WriteContainerAsync(ContainerResource container) Writer.WriteString("entrypoint", container.Entrypoint); } + // Write args if they are present if (container.TryGetAnnotationsOfType(out var argsCallback)) { var args = new List(); @@ -104,6 +105,40 @@ public async Task WriteContainerAsync(ContainerResource container) } } + // Write volume details + if (container.TryGetAnnotationsOfType(out var mounts)) + { + var volumes = mounts.Where(mounts => mounts.Type == ContainerMountType.Named).ToList(); + + // Only write out details for volumes (no bind mounts) + if (volumes.Count > 0) + { + // Volumes are written as an array of objects as anonymous volumes do not have a name + Writer.WriteStartArray("volumes"); + + foreach (var volume in volumes) + { + Writer.WriteStartObject(); + + // This can be null for anonymous volumes + if (volume.Source is not null) + { + Writer.WritePropertyName("name"); + Writer.WriteStringValue(volume.Source); + } + + Writer.WritePropertyName("target"); + Writer.WriteStringValue(volume.Target); + + Writer.WriteBoolean("readOnly", volume.IsReadOnly); + + Writer.WriteEndObject(); + } + + Writer.WriteEndArray(); + } + } + await WriteEnvironmentVariablesAsync(container).ConfigureAwait(false); WriteBindings(container, emitContainerPort: true); WriteInputs(container); From 730fc82d9a977ef8852d5f9f441453c77632012a Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Fri, 8 Mar 2024 15:44:14 -0800 Subject: [PATCH 2/3] Add a test for writing volumes to the manifest --- .../ManifestGenerationTests.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs b/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs index 9f0845c0d8..f5c9e37244 100644 --- a/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs +++ b/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs @@ -183,6 +183,43 @@ public void EnsureContainerWithArgsEmitsContainerArgs() arg => Assert.Equal("more", arg.GetString())); } + [Fact] + public void EnsureContainerWithVolumesEmitsVolumes() + { + using var program = CreateTestProgramJsonDocumentManifestPublisher(); + + program.AppBuilder.AddContainer("containerwithvolumes", "image/name") + .WithVolume("myvolume", "/mount/here") + .WithBindMount("./some/source", "/bound") // This should be ignored and not written to the manifest + .WithVolume("myreadonlyvolume", "/mount/there", isReadOnly: true) + .WithVolume(null! /* anonymous volume */, "/mount/everywhere"); + + // Build AppHost so that publisher can be resolved. + program.Build(); + var publisher = program.GetManifestPublisher(); + + program.Run(); + + var resources = publisher.ManifestDocument.RootElement.GetProperty("resources"); + + var grafana = resources.GetProperty("containerwithvolumes"); + var volumes = grafana.GetProperty("volumes"); + Assert.Equal(3, volumes.GetArrayLength()); + Assert.Collection(volumes.EnumerateArray(), + volume => Assert.Collection(volume.EnumerateObject(), + property => { Assert.Equal("name", property.Name); Assert.Equal("myvolume", property.Value.GetString()); }, + property => { Assert.Equal("target", property.Name); Assert.Equal("/mount/here", property.Value.GetString()); }, + property => { Assert.Equal("readOnly", property.Name); Assert.False(property.Value.GetBoolean()); }), + volume => Assert.Collection(volume.EnumerateObject(), + property => { Assert.Equal("name", property.Name); Assert.Equal("myreadonlyvolume", property.Value.GetString()); }, + property => { Assert.Equal("target", property.Name); Assert.Equal("/mount/there", property.Value.GetString()); }, + property => { Assert.Equal("readOnly", property.Name); Assert.True(property.Value.GetBoolean()); }), + volume => Assert.Collection(volume.EnumerateObject(), + // Anonymous volumes don't have a name + property => { Assert.Equal("target", property.Name); Assert.Equal("/mount/everywhere", property.Value.GetString()); }, + property => { Assert.Equal("readOnly", property.Name); Assert.False(property.Value.GetBoolean()); })); + } + [Theory] [InlineData(new string[] { "args1", "args2" }, new string[] { "withArgs1", "withArgs2" })] [InlineData(new string[] { }, new string[] { "withArgs1", "withArgs2" })] From ad179e2eed5601f7660e451a25f602bf9c673eb1 Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Fri, 8 Mar 2024 16:36:09 -0800 Subject: [PATCH 3/3] Update assert to use ManifestUtils --- .../ManifestGenerationTests.cs | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs b/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs index f5c9e37244..801806d9a4 100644 --- a/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs +++ b/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs @@ -184,40 +184,44 @@ public void EnsureContainerWithArgsEmitsContainerArgs() } [Fact] - public void EnsureContainerWithVolumesEmitsVolumes() + public async Task EnsureContainerWithVolumesEmitsVolumes() { using var program = CreateTestProgramJsonDocumentManifestPublisher(); - program.AppBuilder.AddContainer("containerwithvolumes", "image/name") + var container = program.AppBuilder.AddContainer("containerwithvolumes", "image/name") .WithVolume("myvolume", "/mount/here") .WithBindMount("./some/source", "/bound") // This should be ignored and not written to the manifest .WithVolume("myreadonlyvolume", "/mount/there", isReadOnly: true) .WithVolume(null! /* anonymous volume */, "/mount/everywhere"); - // Build AppHost so that publisher can be resolved. program.Build(); - var publisher = program.GetManifestPublisher(); - program.Run(); + var manifest = await ManifestUtils.GetManifest(container.Resource); - var resources = publisher.ManifestDocument.RootElement.GetProperty("resources"); + var expectedManifest = """ + { + "type": "container.v0", + "image": "image/name:latest", + "volumes": [ + { + "name": "myvolume", + "target": "/mount/here", + "readOnly": false + }, + { + "name": "myreadonlyvolume", + "target": "/mount/there", + "readOnly": true + }, + { + "target": "/mount/everywhere", + "readOnly": false + } + ] + } + """; - var grafana = resources.GetProperty("containerwithvolumes"); - var volumes = grafana.GetProperty("volumes"); - Assert.Equal(3, volumes.GetArrayLength()); - Assert.Collection(volumes.EnumerateArray(), - volume => Assert.Collection(volume.EnumerateObject(), - property => { Assert.Equal("name", property.Name); Assert.Equal("myvolume", property.Value.GetString()); }, - property => { Assert.Equal("target", property.Name); Assert.Equal("/mount/here", property.Value.GetString()); }, - property => { Assert.Equal("readOnly", property.Name); Assert.False(property.Value.GetBoolean()); }), - volume => Assert.Collection(volume.EnumerateObject(), - property => { Assert.Equal("name", property.Name); Assert.Equal("myreadonlyvolume", property.Value.GetString()); }, - property => { Assert.Equal("target", property.Name); Assert.Equal("/mount/there", property.Value.GetString()); }, - property => { Assert.Equal("readOnly", property.Name); Assert.True(property.Value.GetBoolean()); }), - volume => Assert.Collection(volume.EnumerateObject(), - // Anonymous volumes don't have a name - property => { Assert.Equal("target", property.Name); Assert.Equal("/mount/everywhere", property.Value.GetString()); }, - property => { Assert.Equal("readOnly", property.Name); Assert.False(property.Value.GetBoolean()); })); + Assert.Equal(expectedManifest, manifest.ToString()); } [Theory]