From 18e4a370777bd3f2fde4757e6500d93866b2310c Mon Sep 17 00:00:00 2001 From: Tom Kerkhove Date: Tue, 17 Nov 2020 22:09:36 +0100 Subject: [PATCH 1/3] Provide `X-Version` response header for API endpoints Signed-off-by: Tom Kerkhove --- changelog/content/experimental/unreleased.md | 1 + .../IApplicationBuilderExtensions.cs | 11 +++++ src/Promitor.Agents.Core/HttpHeaders.cs | 7 ++++ .../Middleware/AgentVersionMiddleware.cs | 42 +++++++++++++++++++ .../Startup.cs | 1 + src/Promitor.Agents.Scraper/Startup.cs | 2 + .../Services/ResourceDiscovery/HealthTests.cs | 2 + .../ResourceCollectionTests.cs | 15 +++++++ .../ResourceDiscoveryTests.cs | 17 ++++++++ .../Services/ResourceDiscovery/SystemTests.cs | 6 ++- .../Services/Scraper/HealthTests.cs | 2 + .../Services/Scraper/SystemTests.cs | 6 ++- 12 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 src/Promitor.Agents.Core/HttpHeaders.cs create mode 100644 src/Promitor.Agents.Core/Middleware/AgentVersionMiddleware.cs diff --git a/changelog/content/experimental/unreleased.md b/changelog/content/experimental/unreleased.md index 1d49ec920..df99ac05f 100644 --- a/changelog/content/experimental/unreleased.md +++ b/changelog/content/experimental/unreleased.md @@ -26,6 +26,7 @@ version: - {{% tag added %}} New validation rule to ensure declarative or dynamic discovery for metrics to scrape are configured - {{% tag added %}} New System API endpoint giving runtime information ([docs](https://promitor.io/operations/#system) | [#1208](https://github.com/tomkerkhove/promitor/issues/1208)) +- {{% tag added %}} Provide `X-Version` response header for API endpoints ([#1209](https://github.com/tomkerkhove/promitor/issues/1209)) - {{% tag changed %}} Show Promitor version during startup - {{% tag changed %}} Provide capability to scrape all queues in Azure Service Bus, instead of having to declare the queue name. ([#529](https://github.com/tomkerkhove/promitor/issues/529)). diff --git a/src/Promitor.Agents.Core/Extensions/IApplicationBuilderExtensions.cs b/src/Promitor.Agents.Core/Extensions/IApplicationBuilderExtensions.cs index 707ef378f..e2f6b3e1a 100644 --- a/src/Promitor.Agents.Core/Extensions/IApplicationBuilderExtensions.cs +++ b/src/Promitor.Agents.Core/Extensions/IApplicationBuilderExtensions.cs @@ -1,4 +1,5 @@ using System; +using Promitor.Agents.Core.Middleware; using Swashbuckle.AspNetCore.Swagger; using Swashbuckle.AspNetCore.SwaggerUI; @@ -8,6 +9,16 @@ namespace Microsoft.AspNetCore.Builder // ReSharper disable once InconsistentNaming public static class IApplicationBuilderExtensions { + /// + /// Adds middleware to automatically add the version in our responses + /// + public static IApplicationBuilder UseVersionMiddleware(this IApplicationBuilder app) + { + app.UseMiddleware(); + + return app; + } + /// /// Add support for Open API with API explorer /// diff --git a/src/Promitor.Agents.Core/HttpHeaders.cs b/src/Promitor.Agents.Core/HttpHeaders.cs new file mode 100644 index 000000000..da3a2c9bb --- /dev/null +++ b/src/Promitor.Agents.Core/HttpHeaders.cs @@ -0,0 +1,7 @@ +namespace Promitor.Agents.Core +{ + public class HttpHeaders + { + public const string AgentVersion = "X-Version"; + } +} diff --git a/src/Promitor.Agents.Core/Middleware/AgentVersionMiddleware.cs b/src/Promitor.Agents.Core/Middleware/AgentVersionMiddleware.cs new file mode 100644 index 000000000..ababcacf9 --- /dev/null +++ b/src/Promitor.Agents.Core/Middleware/AgentVersionMiddleware.cs @@ -0,0 +1,42 @@ + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Promitor.Core; + +namespace Promitor.Agents.Core.Middleware +{ + public class AgentVersionMiddleware + { + private readonly RequestDelegate _next; + + /// + /// Initializes a new instance of the class which automatically adds the version of the agent to all HTTP responses + /// + /// The next in the ASP.NET Core request pipeline. + /// When the is null. + public AgentVersionMiddleware(RequestDelegate next) + { + _next = next; + } + + /// + /// Invoke the middleware to automatically add version to HTTP responses. + /// + /// The context for the current HTTP request. + public async Task Invoke(HttpContext httpContext) + { + var version = Version.Get(); + + httpContext.Response.OnStarting(state => + { + var context = (HttpContext)state; + context.Response.Headers.TryAdd(HttpHeaders.AgentVersion, version); + + return Task.CompletedTask; + }, httpContext); + + await _next(httpContext); + } + } +} diff --git a/src/Promitor.Agents.ResourceDiscovery/Startup.cs b/src/Promitor.Agents.ResourceDiscovery/Startup.cs index 883fee5b0..e33a9ca89 100644 --- a/src/Promitor.Agents.ResourceDiscovery/Startup.cs +++ b/src/Promitor.Agents.ResourceDiscovery/Startup.cs @@ -49,6 +49,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseExceptionHandling(); app.UseRequestTracking(); app.UseHttpCorrelation(); + app.UseVersionMiddleware(); app.UseRouting(); app.ExposeOpenApiUi(ApiName); diff --git a/src/Promitor.Agents.Scraper/Startup.cs b/src/Promitor.Agents.Scraper/Startup.cs index 84ad9707a..848abd2bd 100644 --- a/src/Promitor.Agents.Scraper/Startup.cs +++ b/src/Promitor.Agents.Scraper/Startup.cs @@ -63,10 +63,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseExceptionHandling() .UseRequestTracking() .UseHttpCorrelation() + .UseVersionMiddleware() .UseRouting() .UseMetricSinks(Configuration) .ExposeOpenApiUi() .UseEndpoints(endpoints => endpoints.MapControllers()); + UseSerilog(ComponentName, app.ApplicationServices); } diff --git a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/HealthTests.cs b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/HealthTests.cs index e8454a5cd..62de7a785 100644 --- a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/HealthTests.cs +++ b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/HealthTests.cs @@ -1,5 +1,6 @@ using System.Net; using System.Threading.Tasks; +using Promitor.Agents.Core; using Promitor.Tests.Integration.Clients; using Xunit; using Xunit.Abstractions; @@ -24,6 +25,7 @@ public async Task Health_Get_ReturnsOk() // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.True(response.Headers.Contains(HttpHeaders.AgentVersion)); } } } diff --git a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceCollectionTests.cs b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceCollectionTests.cs index e95dd1695..a612d42c6 100644 --- a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceCollectionTests.cs +++ b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceCollectionTests.cs @@ -2,6 +2,7 @@ using System.Net; using System.Threading.Tasks; using Newtonsoft.Json; +using Promitor.Agents.Core; using Promitor.Agents.ResourceDiscovery.Configuration; using Promitor.Tests.Integration.Clients; using Xunit; @@ -33,5 +34,19 @@ public async Task ResourceDiscoveryGroup_GetAll_ReturnsValidList() Assert.NotNull(resourceDiscoveryGroups); Assert.NotEmpty(resourceDiscoveryGroups); } + + [Fact] + public async Task ResourceDiscoveryGroup_SuccessfulCall_ReturnsVersionHeader() + { + // Arrange + var resourceDiscoveryClient = new ResourceDiscoveryClient(Configuration, Logger); + + // Act + var response = await resourceDiscoveryClient.GetResourceDiscoveryGroupsAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.True(response.Headers.Contains(HttpHeaders.AgentVersion)); + } } } diff --git a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceDiscoveryTests.cs b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceDiscoveryTests.cs index 7e111a0d8..390cb9dce 100644 --- a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceDiscoveryTests.cs +++ b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceDiscoveryTests.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Bogus; using Newtonsoft.Json; +using Promitor.Agents.Core; using Promitor.Agents.ResourceDiscovery.Graph.Model; using Promitor.Tests.Integration.Clients; using Xunit; @@ -31,6 +32,22 @@ public async Task ResourceDiscovery_GetForUnexistingResourceDiscoveryGroup_Retur // Assert Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + Assert.True(response.Headers.Contains(HttpHeaders.AgentVersion)); + } + + [Fact] + public async Task ResourceDiscovery_SuccessfulCall_ReturnsVersionHeader() + { + // Arrange + const string resourceDiscoveryGroupName = "logic-apps-unfiltered"; + var resourceDiscoveryClient = new ResourceDiscoveryClient(Configuration, Logger); + + // Act + var response = await resourceDiscoveryClient.GetDiscoveredResourcesAsync(resourceDiscoveryGroupName); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.True(response.Headers.Contains(HttpHeaders.AgentVersion)); } [Fact] diff --git a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/SystemTests.cs b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/SystemTests.cs index dbc654b0e..8468bc8bc 100644 --- a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/SystemTests.cs +++ b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/SystemTests.cs @@ -1,6 +1,8 @@ -using System.Net; +using System.Linq; +using System.Net; using System.Threading.Tasks; using Newtonsoft.Json; +using Promitor.Agents.Core; using Promitor.Agents.Core.Contracts; using Promitor.Tests.Integration.Clients; using Xunit; @@ -32,6 +34,8 @@ public async Task System_GetInfo_ReturnsOk() var systemInfo = JsonConvert.DeserializeObject(rawPayload); Assert.NotNull(systemInfo); Assert.Equal(expectedVersion, systemInfo.Version); + Assert.True(response.Headers.Contains(HttpHeaders.AgentVersion)); + Assert.Equal(expectedVersion, response.Headers.GetValues(HttpHeaders.AgentVersion).First()); } } } diff --git a/src/Promitor.Tests.Integration/Services/Scraper/HealthTests.cs b/src/Promitor.Tests.Integration/Services/Scraper/HealthTests.cs index 27bf9cf89..91b7b6399 100644 --- a/src/Promitor.Tests.Integration/Services/Scraper/HealthTests.cs +++ b/src/Promitor.Tests.Integration/Services/Scraper/HealthTests.cs @@ -1,5 +1,6 @@ using System.Net; using System.Threading.Tasks; +using Promitor.Agents.Core; using Promitor.Tests.Integration.Clients; using Xunit; using Xunit.Abstractions; @@ -24,6 +25,7 @@ public async Task Health_Get_ReturnsOk() // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.True(response.Headers.Contains(HttpHeaders.AgentVersion)); } } } diff --git a/src/Promitor.Tests.Integration/Services/Scraper/SystemTests.cs b/src/Promitor.Tests.Integration/Services/Scraper/SystemTests.cs index b3b4e9758..35539215a 100644 --- a/src/Promitor.Tests.Integration/Services/Scraper/SystemTests.cs +++ b/src/Promitor.Tests.Integration/Services/Scraper/SystemTests.cs @@ -1,6 +1,8 @@ -using System.Net; +using System.Linq; +using System.Net; using System.Threading.Tasks; using Newtonsoft.Json; +using Promitor.Agents.Core; using Promitor.Agents.Core.Contracts; using Promitor.Tests.Integration.Clients; using Xunit; @@ -32,6 +34,8 @@ public async Task System_GetInfo_ReturnsOk() var systemInfo = JsonConvert.DeserializeObject(rawPayload); Assert.NotNull(systemInfo); Assert.Equal(expectedVersion, systemInfo.Version); + Assert.True(response.Headers.Contains(HttpHeaders.AgentVersion)); + Assert.Equal(expectedVersion, response.Headers.GetValues(HttpHeaders.AgentVersion).First()); } } } From dff7b30d4b2aa3da75383b6dd17ab9a616156315 Mon Sep 17 00:00:00 2001 From: Tom Kerkhove Date: Tue, 17 Nov 2020 22:14:01 +0100 Subject: [PATCH 2/3] Remove empty line --- src/Promitor.Agents.Core/Middleware/AgentVersionMiddleware.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Promitor.Agents.Core/Middleware/AgentVersionMiddleware.cs b/src/Promitor.Agents.Core/Middleware/AgentVersionMiddleware.cs index ababcacf9..db36de6bc 100644 --- a/src/Promitor.Agents.Core/Middleware/AgentVersionMiddleware.cs +++ b/src/Promitor.Agents.Core/Middleware/AgentVersionMiddleware.cs @@ -1,4 +1,3 @@ - using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; From ad1a22d1d23ce8ad79466621339c688567e7f81e Mon Sep 17 00:00:00 2001 From: Tom Kerkhove Date: Wed, 18 Nov 2020 07:45:09 +0100 Subject: [PATCH 3/3] Further improve tests Signed-off-by: Tom Kerkhove --- .../Services/ResourceDiscovery/HealthTests.cs | 4 +++- .../Services/ResourceDiscovery/ResourceCollectionTests.cs | 2 ++ .../ResourceDiscovery/ResourceDiscoveryIntegrationTest.cs | 2 ++ .../Services/ResourceDiscovery/ResourceDiscoveryTests.cs | 3 +++ .../Services/ResourceDiscovery/SystemTests.cs | 5 ++--- .../Services/Scraper/HealthTests.cs | 4 +++- .../Services/Scraper/ScraperIntegrationTest.cs | 2 ++ .../Services/Scraper/SystemTests.cs | 5 ++--- 8 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/HealthTests.cs b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/HealthTests.cs index 62de7a785..83fae6ca5 100644 --- a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/HealthTests.cs +++ b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/HealthTests.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.Linq; +using System.Net; using System.Threading.Tasks; using Promitor.Agents.Core; using Promitor.Tests.Integration.Clients; @@ -26,6 +27,7 @@ public async Task Health_Get_ReturnsOk() // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.True(response.Headers.Contains(HttpHeaders.AgentVersion)); + Assert.Equal(ExpectedVersion, response.Headers.GetValues(HttpHeaders.AgentVersion).First()); } } } diff --git a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceCollectionTests.cs b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceCollectionTests.cs index a612d42c6..80fcf8319 100644 --- a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceCollectionTests.cs +++ b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceCollectionTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Net; using System.Threading.Tasks; using Newtonsoft.Json; @@ -47,6 +48,7 @@ public async Task ResourceDiscoveryGroup_SuccessfulCall_ReturnsVersionHeader() // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.True(response.Headers.Contains(HttpHeaders.AgentVersion)); + Assert.Equal(ExpectedVersion, response.Headers.GetValues(HttpHeaders.AgentVersion).First()); } } } diff --git a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceDiscoveryIntegrationTest.cs b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceDiscoveryIntegrationTest.cs index a3915d924..e902c7530 100644 --- a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceDiscoveryIntegrationTest.cs +++ b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceDiscoveryIntegrationTest.cs @@ -9,5 +9,7 @@ public class ResourceDiscoveryIntegrationTest: IntegrationTest public ResourceDiscoveryIntegrationTest(ITestOutputHelper testOutput) : base(testOutput) { } + + public string ExpectedVersion => Configuration["Agents:ResourceDiscovery:Expectations:Version"]; } } diff --git a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceDiscoveryTests.cs b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceDiscoveryTests.cs index 390cb9dce..7ffba90d0 100644 --- a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceDiscoveryTests.cs +++ b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/ResourceDiscoveryTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Net; using System.Threading.Tasks; using Bogus; @@ -33,6 +34,7 @@ public async Task ResourceDiscovery_GetForUnexistingResourceDiscoveryGroup_Retur // Assert Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); Assert.True(response.Headers.Contains(HttpHeaders.AgentVersion)); + Assert.Equal(ExpectedVersion, response.Headers.GetValues(HttpHeaders.AgentVersion).First()); } [Fact] @@ -48,6 +50,7 @@ public async Task ResourceDiscovery_SuccessfulCall_ReturnsVersionHeader() // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.True(response.Headers.Contains(HttpHeaders.AgentVersion)); + Assert.Equal(ExpectedVersion, response.Headers.GetValues(HttpHeaders.AgentVersion).First()); } [Fact] diff --git a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/SystemTests.cs b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/SystemTests.cs index 8468bc8bc..1c7d1b004 100644 --- a/src/Promitor.Tests.Integration/Services/ResourceDiscovery/SystemTests.cs +++ b/src/Promitor.Tests.Integration/Services/ResourceDiscovery/SystemTests.cs @@ -21,7 +21,6 @@ public SystemTests(ITestOutputHelper testOutput) public async Task System_GetInfo_ReturnsOk() { // Arrange - var expectedVersion = Configuration["Agents:ResourceDiscovery:Expectations:Version"]; var resourceDiscoveryClient = new ResourceDiscoveryClient(Configuration, Logger); // Act @@ -33,9 +32,9 @@ public async Task System_GetInfo_ReturnsOk() Assert.NotEmpty(rawPayload); var systemInfo = JsonConvert.DeserializeObject(rawPayload); Assert.NotNull(systemInfo); - Assert.Equal(expectedVersion, systemInfo.Version); + Assert.Equal(ExpectedVersion, systemInfo.Version); Assert.True(response.Headers.Contains(HttpHeaders.AgentVersion)); - Assert.Equal(expectedVersion, response.Headers.GetValues(HttpHeaders.AgentVersion).First()); + Assert.Equal(ExpectedVersion, response.Headers.GetValues(HttpHeaders.AgentVersion).First()); } } } diff --git a/src/Promitor.Tests.Integration/Services/Scraper/HealthTests.cs b/src/Promitor.Tests.Integration/Services/Scraper/HealthTests.cs index 91b7b6399..41f46628f 100644 --- a/src/Promitor.Tests.Integration/Services/Scraper/HealthTests.cs +++ b/src/Promitor.Tests.Integration/Services/Scraper/HealthTests.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.Linq; +using System.Net; using System.Threading.Tasks; using Promitor.Agents.Core; using Promitor.Tests.Integration.Clients; @@ -26,6 +27,7 @@ public async Task Health_Get_ReturnsOk() // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.True(response.Headers.Contains(HttpHeaders.AgentVersion)); + Assert.Equal(ExpectedVersion, response.Headers.GetValues(HttpHeaders.AgentVersion).First()); } } } diff --git a/src/Promitor.Tests.Integration/Services/Scraper/ScraperIntegrationTest.cs b/src/Promitor.Tests.Integration/Services/Scraper/ScraperIntegrationTest.cs index 72904fb94..8e270d733 100644 --- a/src/Promitor.Tests.Integration/Services/Scraper/ScraperIntegrationTest.cs +++ b/src/Promitor.Tests.Integration/Services/Scraper/ScraperIntegrationTest.cs @@ -9,5 +9,7 @@ public class ScraperIntegrationTest: IntegrationTest public ScraperIntegrationTest(ITestOutputHelper testOutput) : base(testOutput) { } + + public string ExpectedVersion => Configuration["Agents:Scraper:Expectations:Version"]; } } diff --git a/src/Promitor.Tests.Integration/Services/Scraper/SystemTests.cs b/src/Promitor.Tests.Integration/Services/Scraper/SystemTests.cs index 35539215a..d7fc7eee6 100644 --- a/src/Promitor.Tests.Integration/Services/Scraper/SystemTests.cs +++ b/src/Promitor.Tests.Integration/Services/Scraper/SystemTests.cs @@ -21,7 +21,6 @@ public SystemTests(ITestOutputHelper testOutput) public async Task System_GetInfo_ReturnsOk() { // Arrange - var expectedVersion = Configuration["Agents:Scraper:Expectations:Version"]; var resourceDiscoveryClient = new ScraperClient(Configuration, Logger); // Act @@ -33,9 +32,9 @@ public async Task System_GetInfo_ReturnsOk() Assert.NotEmpty(rawPayload); var systemInfo = JsonConvert.DeserializeObject(rawPayload); Assert.NotNull(systemInfo); - Assert.Equal(expectedVersion, systemInfo.Version); + Assert.Equal(ExpectedVersion, systemInfo.Version); Assert.True(response.Headers.Contains(HttpHeaders.AgentVersion)); - Assert.Equal(expectedVersion, response.Headers.GetValues(HttpHeaders.AgentVersion).First()); + Assert.Equal(ExpectedVersion, response.Headers.GetValues(HttpHeaders.AgentVersion).First()); } } }