From c57aa6f30eb2f8f6a2f847404fe5c26a726841c9 Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Thu, 20 Nov 2025 12:59:46 -0700 Subject: [PATCH 1/3] Add Aspire hosting support, #16 --- .../AppHost.cs | 11 ++++ ...reSearchEmulator.Aspire.DemoAppHost.csproj | 16 +++++ .../Properties/launchSettings.json | 31 +++++++++ .../appsettings.Development.json | 8 +++ .../appsettings.json | 9 +++ .../AzureSearchEmulator.Aspire.csproj | 39 ++++++++++++ .../AzureSearchEmulatorResource.cs | 9 +++ .../AzureSearchEmulatorResourceExtensions.cs | 59 ++++++++++++++++++ ...zureSearchEmulator.IntegrationTests.csproj | 1 + AzureSearchEmulator.sln | 12 ++++ .../AzureSearchEmulator.csproj | 1 + .../Properties/launchSettings.json | 27 +------- DebugClient/DebugClient.csproj | 1 + DebugClient/Program.cs | 22 ++++++- logo.png | Bin 0 -> 3256 bytes 15 files changed, 219 insertions(+), 27 deletions(-) create mode 100644 AzureSearchEmulator.Aspire.DemoAppHost/AppHost.cs create mode 100644 AzureSearchEmulator.Aspire.DemoAppHost/AzureSearchEmulator.Aspire.DemoAppHost.csproj create mode 100644 AzureSearchEmulator.Aspire.DemoAppHost/Properties/launchSettings.json create mode 100644 AzureSearchEmulator.Aspire.DemoAppHost/appsettings.Development.json create mode 100644 AzureSearchEmulator.Aspire.DemoAppHost/appsettings.json create mode 100644 AzureSearchEmulator.Aspire/AzureSearchEmulator.Aspire.csproj create mode 100644 AzureSearchEmulator.Aspire/AzureSearchEmulatorResource.cs create mode 100644 AzureSearchEmulator.Aspire/AzureSearchEmulatorResourceExtensions.cs create mode 100644 logo.png diff --git a/AzureSearchEmulator.Aspire.DemoAppHost/AppHost.cs b/AzureSearchEmulator.Aspire.DemoAppHost/AppHost.cs new file mode 100644 index 0000000..6616435 --- /dev/null +++ b/AzureSearchEmulator.Aspire.DemoAppHost/AppHost.cs @@ -0,0 +1,11 @@ +var builder = DistributedApplication.CreateBuilder(args); + +// For local development and testing, we add an Azure Search Emulator instance based on the project directly +builder.AddProject("emulator-project") + .WithExternalHttpEndpoints(); + +// Example container usage via F23.Aspire.Hosting.AzureSearchEmulator +builder.AddAzureSearchEmulator("emulator-container") + .WithIndexesVolume(); + +builder.Build().Run(); diff --git a/AzureSearchEmulator.Aspire.DemoAppHost/AzureSearchEmulator.Aspire.DemoAppHost.csproj b/AzureSearchEmulator.Aspire.DemoAppHost/AzureSearchEmulator.Aspire.DemoAppHost.csproj new file mode 100644 index 0000000..68e7b6d --- /dev/null +++ b/AzureSearchEmulator.Aspire.DemoAppHost/AzureSearchEmulator.Aspire.DemoAppHost.csproj @@ -0,0 +1,16 @@ + + + + Exe + net10.0 + enable + enable + e92d4b6e-a95b-491f-9fe2-8c8ecb4be28f + + + + + + + + diff --git a/AzureSearchEmulator.Aspire.DemoAppHost/Properties/launchSettings.json b/AzureSearchEmulator.Aspire.DemoAppHost/Properties/launchSettings.json new file mode 100644 index 0000000..cb92b02 --- /dev/null +++ b/AzureSearchEmulator.Aspire.DemoAppHost/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17078;http://localhost:15216", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21046", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "https://localhost:23199", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22069" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15216", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19291", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:18104", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20178" + } + } + } +} diff --git a/AzureSearchEmulator.Aspire.DemoAppHost/appsettings.Development.json b/AzureSearchEmulator.Aspire.DemoAppHost/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/AzureSearchEmulator.Aspire.DemoAppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/AzureSearchEmulator.Aspire.DemoAppHost/appsettings.json b/AzureSearchEmulator.Aspire.DemoAppHost/appsettings.json new file mode 100644 index 0000000..31c092a --- /dev/null +++ b/AzureSearchEmulator.Aspire.DemoAppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/AzureSearchEmulator.Aspire/AzureSearchEmulator.Aspire.csproj b/AzureSearchEmulator.Aspire/AzureSearchEmulator.Aspire.csproj new file mode 100644 index 0000000..91ff030 --- /dev/null +++ b/AzureSearchEmulator.Aspire/AzureSearchEmulator.Aspire.csproj @@ -0,0 +1,39 @@ + + + + net10.0 + enable + enable + F23.Aspire.Hosting.AzureSearchEmulator + true + 14 + F23.Aspire.Hosting.AzureSearchEmulator + AGPL-3.0-or-later + https://github.com/feature23/azuresearchemulator + azure search emulator ai hosting docker + Azure Search Emulator hosting support for Aspire. + true + 1.0.0-beta + feature[23] + Copyright (c) feature[23] 2025 + Paul Irwin + logo.png + README.md + true + snupkg + + + + + + + + + + + + + + + + diff --git a/AzureSearchEmulator.Aspire/AzureSearchEmulatorResource.cs b/AzureSearchEmulator.Aspire/AzureSearchEmulatorResource.cs new file mode 100644 index 0000000..438fe7c --- /dev/null +++ b/AzureSearchEmulator.Aspire/AzureSearchEmulatorResource.cs @@ -0,0 +1,9 @@ +using Aspire.Hosting.ApplicationModel; + +namespace F23.Aspire.Hosting.AzureSearchEmulator; + +public class AzureSearchEmulatorResource(string name) : ContainerResource(name) +{ + public const int DefaultHttpPort = 5100; + public const int DefaultHttpsPort = 5143; +} diff --git a/AzureSearchEmulator.Aspire/AzureSearchEmulatorResourceExtensions.cs b/AzureSearchEmulator.Aspire/AzureSearchEmulatorResourceExtensions.cs new file mode 100644 index 0000000..c84692e --- /dev/null +++ b/AzureSearchEmulator.Aspire/AzureSearchEmulatorResourceExtensions.cs @@ -0,0 +1,59 @@ +using Aspire.Hosting.ApplicationModel; +using F23.Aspire.Hosting.AzureSearchEmulator; + +// ReSharper disable once CheckNamespace +namespace Aspire.Hosting; + +/// +/// Extension methods for adding and configuring Azure Search Emulator resources in Aspire. +/// +public static class AzureSearchEmulatorResourceExtensions +{ + extension(IDistributedApplicationBuilder builder) + { + /// + /// Adds an Azure Search Emulator container resource to the distributed application. + /// + /// The name of the resource. + /// An optional HTTP port. If null, will use a generated port number. + /// An optional HTTPS port. If null, will use a generated port number. + /// A resource builder for further configuration. + /// + /// It is recommended to configure a volume for persisting index data using + /// . + /// You can also override the default image tag ("latest") by using the returned resource builder's + /// method. + /// + public IResourceBuilder AddAzureSearchEmulator(string name, + int? httpPort = null, + int? httpsPort = null) + { + var resource = new AzureSearchEmulatorResource(name); + + var resourceBuilder = builder.AddResource(resource) + .WithImage("feature23/azuresearchemulator") + .WithImageTag("latest") + .WithImageRegistry("ghcr.io") + .WithHttpEndpoint(port: httpPort, targetPort: AzureSearchEmulatorResource.DefaultHttpPort, env: "HTTP_PORTS") + .WithHttpsEndpoint(port: httpsPort, targetPort: AzureSearchEmulatorResource.DefaultHttpsPort, env: "HTTPS_PORTS") + .WithEnvironment("ASPNETCORE_URLS", $"https://+:{resource.GetEndpoint("https").Property(EndpointProperty.Port)};http://+:{resource.GetEndpoint("http").Property(EndpointProperty.Port)}") + .WithEnvironment("ASPNETCORE_Kestrel__Certificates__Default__Password", "password") + .WithEnvironment("ASPNETCORE_Kestrel__Certificates__Default__Path", "/app/aspnetapp.pfx"); + + return resourceBuilder; + } + } + + extension(IResourceBuilder builder) + { + /// + /// Configures a volume for persisting Azure Search index data. + /// + /// Optional name for the volume. If null, a name will be generated. + /// The resource builder for further configuration. + public IResourceBuilder WithIndexesVolume(string? volumeName = null) + { + return builder.WithVolume(volumeName ?? VolumeNameGenerator.Generate(builder, "indexes"), "/app/indexes"); + } + } +} diff --git a/AzureSearchEmulator.IntegrationTests/AzureSearchEmulator.IntegrationTests.csproj b/AzureSearchEmulator.IntegrationTests/AzureSearchEmulator.IntegrationTests.csproj index bd3d87d..a8715b6 100644 --- a/AzureSearchEmulator.IntegrationTests/AzureSearchEmulator.IntegrationTests.csproj +++ b/AzureSearchEmulator.IntegrationTests/AzureSearchEmulator.IntegrationTests.csproj @@ -6,6 +6,7 @@ enable true nullable + false diff --git a/AzureSearchEmulator.sln b/AzureSearchEmulator.sln index 6bde183..ad844ad 100644 --- a/AzureSearchEmulator.sln +++ b/AzureSearchEmulator.sln @@ -9,6 +9,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureSearchEmulator.Integra EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DebugClient", "DebugClient\DebugClient.csproj", "{787C4AEB-D46D-472F-9BC5-10857FEF2A05}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureSearchEmulator.Aspire", "AzureSearchEmulator.Aspire\AzureSearchEmulator.Aspire.csproj", "{D0B85C5F-2B49-4D93-BCDA-7D33E6CC1E05}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureSearchEmulator.Aspire.DemoAppHost", "AzureSearchEmulator.Aspire.DemoAppHost\AzureSearchEmulator.Aspire.DemoAppHost.csproj", "{D771EC01-A785-4B7F-AC50-A86D2E281210}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +31,14 @@ Global {787C4AEB-D46D-472F-9BC5-10857FEF2A05}.Debug|Any CPU.Build.0 = Debug|Any CPU {787C4AEB-D46D-472F-9BC5-10857FEF2A05}.Release|Any CPU.ActiveCfg = Release|Any CPU {787C4AEB-D46D-472F-9BC5-10857FEF2A05}.Release|Any CPU.Build.0 = Release|Any CPU + {D0B85C5F-2B49-4D93-BCDA-7D33E6CC1E05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0B85C5F-2B49-4D93-BCDA-7D33E6CC1E05}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0B85C5F-2B49-4D93-BCDA-7D33E6CC1E05}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0B85C5F-2B49-4D93-BCDA-7D33E6CC1E05}.Release|Any CPU.Build.0 = Release|Any CPU + {D771EC01-A785-4B7F-AC50-A86D2E281210}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D771EC01-A785-4B7F-AC50-A86D2E281210}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D771EC01-A785-4B7F-AC50-A86D2E281210}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D771EC01-A785-4B7F-AC50-A86D2E281210}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AzureSearchEmulator/AzureSearchEmulator.csproj b/AzureSearchEmulator/AzureSearchEmulator.csproj index 4adbb36..e57a39c 100644 --- a/AzureSearchEmulator/AzureSearchEmulator.csproj +++ b/AzureSearchEmulator/AzureSearchEmulator.csproj @@ -8,6 +8,7 @@ 1.0.0-beta Nullable 14 + false diff --git a/AzureSearchEmulator/Properties/launchSettings.json b/AzureSearchEmulator/Properties/launchSettings.json index 53ceed9..33168b4 100644 --- a/AzureSearchEmulator/Properties/launchSettings.json +++ b/AzureSearchEmulator/Properties/launchSettings.json @@ -1,35 +1,12 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:54359", - "sslPort": 44381 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "AzureSearchEmulator": { "commandName": "Project", - "launchBrowser": true, + "launchBrowser": false, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "dotnetRunMessages": "true", "applicationUrl": "https://localhost:5123" - }, - "Docker": { - "commandName": "Docker", - "launchBrowser": true, - "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", - "publishAllPorts": true, - "useSSL": true } } -} \ No newline at end of file +} diff --git a/DebugClient/DebugClient.csproj b/DebugClient/DebugClient.csproj index ccd72fe..a05e2f1 100644 --- a/DebugClient/DebugClient.csproj +++ b/DebugClient/DebugClient.csproj @@ -5,6 +5,7 @@ net10.0 enable enable + false diff --git a/DebugClient/Program.cs b/DebugClient/Program.cs index 9276658..6c6aa6b 100644 --- a/DebugClient/Program.cs +++ b/DebugClient/Program.cs @@ -1,11 +1,29 @@ -using Azure; +using System.Globalization; +using Azure; using Azure.Core.Pipeline; using Azure.Search.Documents; using Azure.Search.Documents.Indexes; using Azure.Search.Documents.Indexes.Models; using Azure.Search.Documents.Models; -const string endpoint = "https://localhost:5123"; +int port = 5123; + +if (args.Length > 0 && int.TryParse(args[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var argPort)) +{ + port = argPort; +} +else +{ + Console.WriteLine("Enter HTTPS port number for Azure Search Emulator (default 5123): "); + var portInput = Console.ReadLine(); + if (!string.IsNullOrWhiteSpace(portInput) && int.TryParse(portInput, NumberStyles.Integer, + CultureInfo.InvariantCulture, out var parsedPort)) + { + port = parsedPort; + } +} + +string endpoint = $"https://localhost:{port}"; const string indexName = "test-index"; var handler = new HttpClientHandler(); diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5ac3f0ded4956ee7203a57a6b1c30467427905bc GIT binary patch literal 3256 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 Date: Thu, 20 Nov 2025 13:10:55 -0700 Subject: [PATCH 2/3] Add unit tests and read-only volume support --- .../AzureSearchEmulator.Aspire.Tests.csproj | 25 ++++++++ ...reSearchEmulatorResourceExtensionsTests.cs | 62 +++++++++++++++++++ .../AzureSearchEmulatorResourceTests.cs | 19 ++++++ .../AzureSearchEmulatorResourceExtensions.cs | 8 ++- AzureSearchEmulator.sln | 6 ++ 5 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 AzureSearchEmulator.Aspire.Tests/AzureSearchEmulator.Aspire.Tests.csproj create mode 100644 AzureSearchEmulator.Aspire.Tests/AzureSearchEmulatorResourceExtensionsTests.cs create mode 100644 AzureSearchEmulator.Aspire.Tests/AzureSearchEmulatorResourceTests.cs diff --git a/AzureSearchEmulator.Aspire.Tests/AzureSearchEmulator.Aspire.Tests.csproj b/AzureSearchEmulator.Aspire.Tests/AzureSearchEmulator.Aspire.Tests.csproj new file mode 100644 index 0000000..4376526 --- /dev/null +++ b/AzureSearchEmulator.Aspire.Tests/AzureSearchEmulator.Aspire.Tests.csproj @@ -0,0 +1,25 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AzureSearchEmulator.Aspire.Tests/AzureSearchEmulatorResourceExtensionsTests.cs b/AzureSearchEmulator.Aspire.Tests/AzureSearchEmulatorResourceExtensionsTests.cs new file mode 100644 index 0000000..d96317d --- /dev/null +++ b/AzureSearchEmulator.Aspire.Tests/AzureSearchEmulatorResourceExtensionsTests.cs @@ -0,0 +1,62 @@ +using Aspire.Hosting; +using Aspire.Hosting.ApplicationModel; +using F23.Aspire.Hosting.AzureSearchEmulator; + +namespace AzureSearchEmulator.Aspire.Tests; + +public class AzureSearchEmulatorResourceExtensionsTests +{ + [Fact] + public async Task AddAzureSearchEmulator_ShouldAddResourceToBuilder() + { + // Arrange + var builder = DistributedApplication.CreateBuilder(); + const string resourceName = "my-emulator"; + + // Act + var resource = builder.AddAzureSearchEmulator(resourceName); + + // Assert + Assert.NotNull(resource); + Assert.Equal(resourceName, resource.Resource.Name); + Assert.Contains(resource.Resource, builder.Resources.ToList()); + + var http = resource.Resource.GetEndpoint("http"); + Assert.NotNull(http); + Assert.Equal(AzureSearchEmulatorResource.DefaultHttpPort, http.TargetPort); + Assert.Equal("http", http.Scheme); + + var https = resource.Resource.GetEndpoint("https"); + Assert.NotNull(https); + Assert.Equal(AzureSearchEmulatorResource.DefaultHttpsPort, https.TargetPort); + Assert.Equal("https", https.Scheme); + + var envVars = await resource.Resource.GetEnvironmentVariableValuesAsync(); + Assert.True(envVars.ContainsKey("ASPNETCORE_URLS")); + } + + [InlineData(false)] + [InlineData(true)] + [Theory] + public void WithIndexesVolume_ShouldAddVolumeToResource(bool isReadOnly) + { + // Arrange + var builder = DistributedApplication.CreateBuilder(); + var resourceBuilder = builder.AddAzureSearchEmulator("my-emulator"); + + // Act + var updatedBuilder = resourceBuilder.WithIndexesVolume(isReadOnly: isReadOnly); + + // Assert + Assert.NotNull(updatedBuilder); + + if (!updatedBuilder.Resource.TryGetAnnotationsOfType(out var mountAnnotations)) + { + Assert.Fail("No mount annotations found on the resource."); + } + + var mount = mountAnnotations.FirstOrDefault(ma => ma.Target == "/app/indexes"); + Assert.NotNull(mount); + Assert.Equal(isReadOnly, mount.IsReadOnly); + } +} diff --git a/AzureSearchEmulator.Aspire.Tests/AzureSearchEmulatorResourceTests.cs b/AzureSearchEmulator.Aspire.Tests/AzureSearchEmulatorResourceTests.cs new file mode 100644 index 0000000..d9bf234 --- /dev/null +++ b/AzureSearchEmulator.Aspire.Tests/AzureSearchEmulatorResourceTests.cs @@ -0,0 +1,19 @@ +using F23.Aspire.Hosting.AzureSearchEmulator; + +namespace AzureSearchEmulator.Aspire.Tests; + +public class AzureSearchEmulatorResourceTests +{ + [Fact] + public void Constructor_InitializesResourceProperly() + { + // Arrange + const string name = "my-emulator"; + + // Act + var resource = new AzureSearchEmulatorResource(name); + + // Assert + Assert.Equal(name, resource.Name); + } +} diff --git a/AzureSearchEmulator.Aspire/AzureSearchEmulatorResourceExtensions.cs b/AzureSearchEmulator.Aspire/AzureSearchEmulatorResourceExtensions.cs index c84692e..e3ba8c8 100644 --- a/AzureSearchEmulator.Aspire/AzureSearchEmulatorResourceExtensions.cs +++ b/AzureSearchEmulator.Aspire/AzureSearchEmulatorResourceExtensions.cs @@ -50,10 +50,14 @@ public IResourceBuilder AddAzureSearchEmulator(stri /// Configures a volume for persisting Azure Search index data. /// /// Optional name for the volume. If null, a name will be generated. + /// Indicates whether the volume should be mounted as read-only. /// The resource builder for further configuration. - public IResourceBuilder WithIndexesVolume(string? volumeName = null) + public IResourceBuilder WithIndexesVolume(string? volumeName = null, bool isReadOnly = false) { - return builder.WithVolume(volumeName ?? VolumeNameGenerator.Generate(builder, "indexes"), "/app/indexes"); + return builder.WithVolume( + name: volumeName ?? VolumeNameGenerator.Generate(builder, "indexes"), + target: "/app/indexes", + isReadOnly: isReadOnly); } } } diff --git a/AzureSearchEmulator.sln b/AzureSearchEmulator.sln index ad844ad..6a384ee 100644 --- a/AzureSearchEmulator.sln +++ b/AzureSearchEmulator.sln @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureSearchEmulator.Aspire" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureSearchEmulator.Aspire.DemoAppHost", "AzureSearchEmulator.Aspire.DemoAppHost\AzureSearchEmulator.Aspire.DemoAppHost.csproj", "{D771EC01-A785-4B7F-AC50-A86D2E281210}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureSearchEmulator.Aspire.Tests", "AzureSearchEmulator.Aspire.Tests\AzureSearchEmulator.Aspire.Tests.csproj", "{EDF10963-768B-426B-B4E2-F2C1DBC2C47C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,6 +41,10 @@ Global {D771EC01-A785-4B7F-AC50-A86D2E281210}.Debug|Any CPU.Build.0 = Debug|Any CPU {D771EC01-A785-4B7F-AC50-A86D2E281210}.Release|Any CPU.ActiveCfg = Release|Any CPU {D771EC01-A785-4B7F-AC50-A86D2E281210}.Release|Any CPU.Build.0 = Release|Any CPU + {EDF10963-768B-426B-B4E2-F2C1DBC2C47C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDF10963-768B-426B-B4E2-F2C1DBC2C47C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDF10963-768B-426B-B4E2-F2C1DBC2C47C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDF10963-768B-426B-B4E2-F2C1DBC2C47C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 987097f481d8fb279cda360c8f88e533d7d4af2a Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Thu, 20 Nov 2025 13:15:11 -0700 Subject: [PATCH 3/3] Prevent duplicate restore in Docker build --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index c35197d..11d60a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,10 +9,10 @@ COPY ["AzureSearchEmulator/AzureSearchEmulator.csproj", "AzureSearchEmulator/"] RUN dotnet restore "AzureSearchEmulator/AzureSearchEmulator.csproj" COPY . . WORKDIR "/src/AzureSearchEmulator" -RUN dotnet build "AzureSearchEmulator.csproj" -c Release -o /app/build +RUN dotnet build --no-restore "AzureSearchEmulator.csproj" -c Release -o /app/build FROM build AS publish -RUN dotnet publish "AzureSearchEmulator.csproj" -c Release -o /app/publish +RUN dotnet publish --no-restore "AzureSearchEmulator.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app