diff --git a/.azuredevops/pipelines/build-v2.yml b/.azuredevops/pipelines/build-v2.yml index 0f88516..4c1c3f8 100644 --- a/.azuredevops/pipelines/build-v2.yml +++ b/.azuredevops/pipelines/build-v2.yml @@ -30,20 +30,22 @@ steps: displayName: Build mock-register-unit-tests image inputs: command: build - Dockerfile: ./Source/Dockerfile.unit-tests + Dockerfile: ./Source/Dockerfile buildContext: ./Source repository: mock-register-unit-tests tags: latest + arguments: --target unit-tests # Build mock-register-integration-tests - task: Docker@2 displayName: Build mock-register-integration-tests image inputs: command: build - Dockerfile: ./Source/Dockerfile.integration-tests + Dockerfile: ./Source/Dockerfile buildContext: ./Source repository: mock-register-integration-tests tags: latest + arguments: --target integration-tests # List docker images - task: Docker@2 @@ -75,7 +77,7 @@ steps: # Run integration tests #**************************************************************************************************************** - script: | - docker compose --file $(Build.SourcesDirectory)/Source/docker-compose.IntegrationTests.yml up --abort-on-container-exit --exit-code-from mock-register-integration-tests + docker compose --file $(Build.SourcesDirectory)/Source/docker-compose.IntegrationTests.yml up --abort-on-container-exit --exit-code-from mock-register-integration-tests displayName: 'Integration Tests - Up' condition: always() @@ -208,17 +210,11 @@ steps: performMultiLevelLookup: true - task: CmdLine@2 - displayName: 'Install dotnet-ef' + displayName: 'Restore dotnet tooling' condition: always() inputs: - script: 'dotnet tool install --version 8.0.3 --global dotnet-ef' + script: 'dotnet tool restore' -- task: CmdLine@2 - displayName: 'Check dotnet-ef version' - condition: always() - inputs: - script: 'dotnet-ef' - - script: | cd Source/CDR.Register.Repository dotnet ef migrations bundle --context RegisterDatabaseContext --verbose --self-contained diff --git a/.azuredevops/pipelines/dotnet.yml b/.azuredevops/pipelines/dotnet-is-not-used.yml similarity index 98% rename from .azuredevops/pipelines/dotnet.yml rename to .azuredevops/pipelines/dotnet-is-not-used.yml index 43cd4d7..89f57da 100644 --- a/.azuredevops/pipelines/dotnet.yml +++ b/.azuredevops/pipelines/dotnet-is-not-used.yml @@ -1,9 +1,12 @@ trigger: - develop - main - + +variables: + - group: PT-Pipeline-Common + pool: - vmImage: windows-2019 + vmImage: $(Pipeline_Host_Image) steps: diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..3173bfc --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "8.0.20", + "commands": [ + "dotnet-ef" + ] + } + } +} \ No newline at end of file diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 1dcea1f..61968da 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -50,12 +50,12 @@ jobs: # Build mock-register-unit-tests image - name: Build the mock-register-unit-tests image run: | - docker build ./mock-register/Source --file ./mock-register/Source/Dockerfile.unit-tests --tag mock-register-unit-tests:latest + docker build ./mock-register/Source --file ./mock-register/Source/Dockerfile --target unit-tests --tag mock-register-unit-tests:latest # Build mock-register-integration-tests image - name: Build the mock-register-integration-tests image run: | - docker build ./mock-register/Source --file ./mock-register/Source/Dockerfile.integration-tests --tag mock-register-integration-tests:latest + docker build ./mock-register/Source --file ./mock-register/Source/Dockerfile --target integration-tests --tag mock-register-integration-tests:latest # List docker images - name: List Docker images diff --git a/CHANGELOG.md b/CHANGELOG.md index cbeb201..e9584d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [2.2.3] - 2025-12-03 +### Added +- Added health check endpoints for APIs +### Changed +- Converted to [Central Package Management](https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management) +- Use [Multi-stage builds](https://docs.docker.com/build/building/multi-stage/) for tests +- Updated NuGet packages to address vulnerabilities + +## [2.2.2] - 2025-10-15 +### Added +- Enabled OpenTelemetry as a logging destination + +# Fixed +- Fixed broken unit tests +- Fixed startup issues when Request/Response logging was disabled ## [2.2.1] - 2025-06-19 ### Changed diff --git a/Help/container/HELP.md b/Help/container/HELP.md index 55954fe..d0b2284 100644 --- a/Help/container/HELP.md +++ b/Help/container/HELP.md @@ -186,5 +186,32 @@ If the host names are changed, then the data stored in the Mock Register should This can be achieved by using the Admin API, as outlined in the solution README. +## Connecting to the database +> The solutions above utilise MS SQL database for storage. In the examples below we use [MS SQL Server Management Studio (SMSS)](https://learn.microsoft.com/en-us/ssms/), but the approach should be similar for other tooling. + +You will need the following authentication details: +| | | +| -- | -- | +| Server type | Database Engine | +| Server name | localhost | +| Authentication | SQL Server Authentication | +| Login | `sa` | +| Password | `Pa{}w0rd2019` | + +Should you opt to use another tool, then the following would be useful + +| | | +| -- | -- | +| Connection String | `Server=localhost;Database=cdr-register;User Id='SA';Password='Pa{}w0rd2019';MultipleActiveResultSets=True;TrustServerCertificate=True;Encrypt=False` | + +## Logging +Once you have connected to the `cdr-register` database above you can view the various database tables that contain logs or view the console output using the following command. + + ```shell + docker logs mock-register + ``` + +Optionally, logging to OpenTelemetry compatible destinations is also supported by modifying the `docker run` commands to supply additional environment variables. Additional guidance can be found in the [readme](../../README.md#logging) file. + ## Host on your own infrastructure The mock solutions can also be hosted on your own infrastructure, such as virtual machines or kubernetes clusters, in your private data centre or in the public cloud. diff --git a/Help/debugging/HELP.md b/Help/debugging/HELP.md index 4a1c679..612662c 100644 --- a/Help/debugging/HELP.md +++ b/Help/debugging/HELP.md @@ -47,6 +47,9 @@ The following steps outline describe how to launch the Mock Register solution us [Projects selected to be started](./images/MS-Visual-Studio-Select-multiple-projects.png) +> Depending on the version of Visual Studio you have you may also see a pre-existing `Mock Register with Gateways` profile that can be used +!["Mock Register with Gateways" profile](./images/MS-Visual-Studio-Start-Profile.png) + 2. Click "Start" to start the Mock Register solution. [Start the projects](./images/MS-Visual-Studio-Start.png) diff --git a/Help/debugging/images/MS-Visual-Studio-Start-Profile.png b/Help/debugging/images/MS-Visual-Studio-Start-Profile.png new file mode 100644 index 0000000..5289e4a Binary files /dev/null and b/Help/debugging/images/MS-Visual-Studio-Start-Profile.png differ diff --git a/README.md b/README.md index 9d50f32..42b3174 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,24 @@ The following technologies have been used to build the Mock Register: - The TLS and mTLS Gateways have been implemented using `Ocelot`. - The Repository utilises a `SQL` instance. +### Logging +By default the application logs to console as well as into tables within the application database. + +However, OpenTelemetry can be configured by setting the [environment variables](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options) appropriately. + +> The example below uses [Seq](https://datalust.co/seq) for simplicity we do not endorse any particular product. Choose an [OpenTelemetry vendor](https://opentelemetry.io/ecosystem/vendors/) is suitable for your needs. + +For example, you may set up a local OTLP ingestion endpoint +`docker run -e ACCEPT_EULA=Y --rm -p 4318:80 5341:5341 datalust/seq` +and then set the following + +| Environment variable | Value | +| --- | --- | +| `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:5341/ingest/otlp` | +| `OTEL_EXPORTER_OTLP_PROTOCOL` | `http/protobuf` | + +After which you should be able to [view telemetry](http://localhost:4318/). + # Testing A [Polyglot notebook](https://code.visualstudio.com/docs/languages/polyglot) has been created for the Mock Register's APIs as a tool for demonstrating how these APIs are used. diff --git a/Source/CDR.Register.API.Gateway.TLS/CDR.Register.API.Gateway.TLS.csproj b/Source/CDR.Register.API.Gateway.TLS/CDR.Register.API.Gateway.TLS.csproj index c01e066..c039af1 100644 --- a/Source/CDR.Register.API.Gateway.TLS/CDR.Register.API.Gateway.TLS.csproj +++ b/Source/CDR.Register.API.Gateway.TLS/CDR.Register.API.Gateway.TLS.csproj @@ -1,9 +1,9 @@  - $(TargetFrameworkVersion) - $(Version) - $(Version) - $(Version) + $(TargetFrameworkVersion) + $(Version) + $(Version) + $(Version) true @@ -20,23 +20,25 @@ - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + + true + + + true + + + all - runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file diff --git a/Source/CDR.Register.API.Gateway.TLS/DownstreamHttpHealthCheck.cs b/Source/CDR.Register.API.Gateway.TLS/DownstreamHttpHealthCheck.cs new file mode 100644 index 0000000..a395a12 --- /dev/null +++ b/Source/CDR.Register.API.Gateway.TLS/DownstreamHttpHealthCheck.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace CDR.Register.API.Gateway.TLS; + +/// +/// Health checks to ensure all configured mock Authorisation servers are available. +/// +public class DownstreamHttpHealthCheck : IHealthCheck +{ + private readonly IHttpClientFactory _httpClientFactory; +#pragma warning disable S1075 // URIs should not be hardcoded + private readonly Dictionary _endpoints = new() + { + { "InfoSec", new Uri("https://localhost:7002") }, + { "Discovery", new Uri("https://localhost:7003") }, + { "Status", new Uri("https://localhost:7004") }, + { "SSA", new Uri("https://localhost:7005") }, + { "Admin", new Uri("https://localhost:7006") }, + }; +#pragma warning restore S1075 // URIs should not be hardcoded + + /// + /// Initializes a new instance of the class. + /// + /// The http client factory. + public DownstreamHttpHealthCheck(IHttpClientFactory httpClientFactory) + { + this._httpClientFactory = httpClientFactory; + } + + /// + public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + using var client = this._httpClientFactory.CreateClient(nameof(DownstreamHttpHealthCheck)); + client.Timeout = context.Registration.Timeout; + + var checks = this._endpoints.Select(x => CheckHealth(client, x.Key, x.Value)); + + var results = await Task.WhenAll(checks); + + if (results.All(x => x.Exception == null && x.Response.IsSuccessStatusCode)) + { + return new HealthCheckResult(HealthStatus.Healthy); + } + + var data = results.Where(x => x.Exception != null || !x.Response.IsSuccessStatusCode).ToDictionary(x => x.Name, x => (object)new { x.Endpoint, x.Response?.StatusCode, x.Response?.ReasonPhrase, x.Exception?.Message }); + + var agg = new AggregateException("One or more health checks failed.", results.Where(x => x.Exception is not null).Select(x => x.Exception)); + + return new HealthCheckResult(HealthStatus.Degraded, "Not all APIs are available", agg, new ReadOnlyDictionary(data)); + } + + /// + /// Checks that the health endpoint for the supplied is available. + /// + /// The client. + /// The name of the API. + /// The URI of the downstream API. + /// The result of the check. + private static async Task<(string Name, Uri Endpoint, HttpResponseMessage Response, Exception Exception)> CheckHealth(HttpClient client, string name, Uri apiBaseUri) + { + HttpResponseMessage result = null; + var url = new Uri(apiBaseUri, "health"); + + try + { + result = await client.GetAsync(url); + } + catch (HttpRequestException ex) + { + return (name, url, result, ex); + } + + return (name, url, result, null); + } +} diff --git a/Source/CDR.Register.API.Gateway.TLS/Startup.cs b/Source/CDR.Register.API.Gateway.TLS/Startup.cs index cb2e0b7..5ab6a17 100644 --- a/Source/CDR.Register.API.Gateway.TLS/Startup.cs +++ b/Source/CDR.Register.API.Gateway.TLS/Startup.cs @@ -1,9 +1,13 @@ -using Microsoft.AspNetCore.Builder; +using System.Linq; +using System.Net; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; +using Newtonsoft.Json; using Ocelot.DependencyInjection; using Ocelot.Middleware; using Serilog; @@ -22,12 +26,38 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public static void ConfigureServices(IServiceCollection services) { + services.AddHttpClient(); + services.AddHealthChecks().AddCheck("Register APIs"); services.AddOcelot(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + app.UseHealthChecks("/health", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions + { + AllowCachingResponses = false, + ResponseWriter = (HttpContext context, HealthReport result) => + { + context.Response.ContentType = "application/json"; + + var item = new + { + Status = result.Status.ToString(), + Results = result.Entries.ToDictionary(e => e.Key, e => new { Status = e.Value.Status.ToString(), e.Value.Description, e.Value.Data }), + }; + + var json = JsonConvert.SerializeObject(item, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + + if (result.Status == HealthStatus.Degraded) + { + context.Response.StatusCode = (int)HttpStatusCode.FailedDependency; + } + + return context.Response.WriteAsync(json); + }, + }); + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); diff --git a/Source/CDR.Register.API.Gateway.mTLS/CDR.Register.API.Gateway.mTLS.csproj b/Source/CDR.Register.API.Gateway.mTLS/CDR.Register.API.Gateway.mTLS.csproj index cada86a..acf14a9 100644 --- a/Source/CDR.Register.API.Gateway.mTLS/CDR.Register.API.Gateway.mTLS.csproj +++ b/Source/CDR.Register.API.Gateway.mTLS/CDR.Register.API.Gateway.mTLS.csproj @@ -1,10 +1,10 @@  - $(TargetFrameworkVersion) - $(Version) - $(Version) - $(Version) - True + $(TargetFrameworkVersion) + $(Version) + $(Version) + $(Version) + True @@ -29,25 +29,31 @@ - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + true + + + true + + + true + + + true + + + all - runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/CDR.Register.API.Infrastructure.Tests.UnitTests.csproj b/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/CDR.Register.API.Infrastructure.Tests.UnitTests.csproj index c9d7f5e..349ea0e 100644 --- a/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/CDR.Register.API.Infrastructure.Tests.UnitTests.csproj +++ b/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/CDR.Register.API.Infrastructure.Tests.UnitTests.csproj @@ -1,34 +1,33 @@  false - $(TargetFrameworkVersion) - $(Version) - $(Version) - $(Version) - True + false + $(TargetFrameworkVersion) + $(Version) + $(Version) + $(Version) + True - - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.API.Infrastructure/CDR.Register.API.Infrastructure.csproj b/Source/CDR.Register.API.Infrastructure/CDR.Register.API.Infrastructure.csproj index e2c57fe..14df0a6 100644 --- a/Source/CDR.Register.API.Infrastructure/CDR.Register.API.Infrastructure.csproj +++ b/Source/CDR.Register.API.Infrastructure/CDR.Register.API.Infrastructure.csproj @@ -1,39 +1,51 @@  - $(TargetFrameworkVersion) - $(Version) - $(Version) - $(Version) - enable + $(TargetFrameworkVersion) + $(Version) + $(Version) + $(Version) + enable True + false - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + true + + + + true + + + + true + + + true + + + true + + + true + + + true + + + + + + + + all - runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.API.Infrastructure/Configuration/OpenTelemetryKeys.cs b/Source/CDR.Register.API.Infrastructure/Configuration/OpenTelemetryKeys.cs new file mode 100644 index 0000000..297aa41 --- /dev/null +++ b/Source/CDR.Register.API.Infrastructure/Configuration/OpenTelemetryKeys.cs @@ -0,0 +1,44 @@ +namespace CDR.Register.API.Infrastructure.Configuration; + +/// +/// The names of environment variables / configuration keys that configure the underlying OTLP Exporter used by the OpenTelemetrySink. +/// +/// Refer to OTLP Exporter Configuration OpenTelemetry documentation. +public static class OpenTelemetryKeys +{ + /// + /// A base endpoint URL for any signal type, with an optionally-specified port number. + /// Helpful for when you’re sending more than one signal to the same endpoint and want one environment variable to control the endpoint. + /// + /// + /// Refer to OTEL_EXPORTER_OTLP_ENDPOINT OpenTelemetry documentation. + /// + public const string Endpoint = "OTEL_EXPORTER_OTLP_ENDPOINT"; + + /// + /// Endpoint URL for trace data only, with an optionally-specified port number. + /// Typically ends with v1/traces when using OTLP/HTTP. + /// + /// + /// Refer to OTEL_EXPORTER_OTLP_TRACES_ENDPOINT OpenTelemetry documentation. + /// + public const string TracesEndpoint = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"; + + /// + /// Endpoint URL for metric data only, with an optionally-specified port number. + /// Typically ends with v1/metrics when using OTLP/HTTP. + /// + /// + /// Refer to OTEL_EXPORTER_OTLP_METRICS_ENDPOINT OpenTelemetry documentation. + /// + public const string MetricsEndpoint = "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"; + + /// + /// Endpoint URL for log data only, with an optionally-specified port number. + /// Typically ends with v1/logs when using OTLP/HTTP. + /// + /// + /// Refer to OTEL_EXPORTER_OTLP_LOGS_ENDPOINT OpenTelemetry documentation. + /// + public const string LogsEndpoint = "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"; +} diff --git a/Source/CDR.Register.API.Infrastructure/Extensions/OpenTelemetryConfigurationExtensions.cs b/Source/CDR.Register.API.Infrastructure/Extensions/OpenTelemetryConfigurationExtensions.cs new file mode 100644 index 0000000..a59f377 --- /dev/null +++ b/Source/CDR.Register.API.Infrastructure/Extensions/OpenTelemetryConfigurationExtensions.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Reflection; +using CDR.Register.API.Infrastructure.Configuration; +using Microsoft.Extensions.Configuration; + +namespace Serilog +{ + /// + /// Extension functionality for configuration OpenTelemetry. + /// + public static class OpenTelemetryConfigurationExtensions + { + /// + /// Conditionally enable Open Telemetry if any of the following OpenTelemetry endpoint configuration values: + /// + /// OTEL_EXPORTER_OTLP_ENDPOINT + /// OTEL_EXPORTER_OTLP_TRACES_ENDPOINT + /// OTEL_EXPORTER_OTLP_METRICS_ENDPOINT + /// OTEL_EXPORTER_OTLP_LOGS_ENDPOINT + /// + /// have been set in line with the Exporter configuration guidance. + /// + /// The existing logger configuration to which OpenTelemetry needs to be added. + /// The application configuration. + /// The logger configuration with OpenTelemetry sink configured (if applicable). + public static LoggerConfiguration AddOpenTelemetry(this LoggerConfiguration loggerConfiguration, IConfiguration configuration) + { + if (configuration.GetValue(OpenTelemetryKeys.Endpoint) is not null + || configuration.GetValue(OpenTelemetryKeys.TracesEndpoint) is not null + || configuration.GetValue(OpenTelemetryKeys.MetricsEndpoint) is not null + || configuration.GetValue(OpenTelemetryKeys.LogsEndpoint) is not null) + { + loggerConfiguration.WriteTo.OpenTelemetry(configure: static x => + { + x.ResourceAttributes = new Dictionary + { + { "resource.name", Assembly.GetEntryAssembly()?.GetName()?.Name ?? string.Empty }, + }; + }); + } + + return loggerConfiguration; + } + } +} diff --git a/Source/CDR.Register.API.Logger/CDR.Register.API.Logger.csproj b/Source/CDR.Register.API.Logger/CDR.Register.API.Logger.csproj index 394912e..6ba75e0 100644 --- a/Source/CDR.Register.API.Logger/CDR.Register.API.Logger.csproj +++ b/Source/CDR.Register.API.Logger/CDR.Register.API.Logger.csproj @@ -2,27 +2,25 @@ enable enable - $(TargetFrameworkVersion) - $(Version) - $(Version) - $(Version) + $(TargetFrameworkVersion) + $(Version) + $(Version) + $(Version) true + false - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + all - runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file diff --git a/Source/CDR.Register.API.Logger/LoggerExtensions.cs b/Source/CDR.Register.API.Logger/LoggerExtensions.cs index da37ee4..01d12cc 100644 --- a/Source/CDR.Register.API.Logger/LoggerExtensions.cs +++ b/Source/CDR.Register.API.Logger/LoggerExtensions.cs @@ -1,13 +1,14 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; namespace CDR.Register.API.Logger { public static class LoggerExtensions { public static IServiceCollection AddRequestResponseLogging(this IServiceCollection services) - { - services.AddSingleton(); - return services; - } + => services.AddSingleton(); + + public static IApplicationBuilder UseRequestResponseLogging(this IApplicationBuilder builder) + => builder.UseMiddleware(); } } diff --git a/Source/CDR.Register.API.Logger/RequestResponseLogger.cs b/Source/CDR.Register.API.Logger/RequestResponseLogger.cs index dc1fdd9..4b5a26d 100644 --- a/Source/CDR.Register.API.Logger/RequestResponseLogger.cs +++ b/Source/CDR.Register.API.Logger/RequestResponseLogger.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Configuration; +using System.Diagnostics; +using Microsoft.Extensions.Configuration; using Serilog; using Serilog.Settings.Configuration; @@ -10,8 +11,22 @@ public class RequestResponseLogger : IRequestResponseLogger, IDisposable public RequestResponseLogger(IConfiguration configuration) { - this._logger = new LoggerConfiguration() - .ReadFrom.Configuration(configuration, new ConfigurationReaderOptions { SectionName = "SerilogRequestResponseLogger" }) + var loggerConfiguration = new LoggerConfiguration(); + + // If the Serilog response logging is disabled, do not configure it using the appsettings. + var isSerilogRequestResponseLoggerDisabled = !configuration.GetValue("SerilogRequestResponseLogger:Enabled", true); + if (isSerilogRequestResponseLoggerDisabled) + { + Debug.WriteLine("Request/Response logging is disabled"); + this._logger = loggerConfiguration.CreateLogger(); + return; + } + + Debug.WriteLine("Request/Response logging is enabled"); + var options = new ConfigurationReaderOptions { SectionName = "SerilogRequestResponseLogger" }; + + this._logger = loggerConfiguration + .ReadFrom.Configuration(configuration, options) .Enrich.WithProperty("RequestMethod", string.Empty) .Enrich.WithProperty("RequestBody", string.Empty) .Enrich.WithProperty("RequestHeaders", string.Empty) diff --git a/Source/CDR.Register.Admin.API/CDR.Register.Admin.API.csproj b/Source/CDR.Register.Admin.API/CDR.Register.Admin.API.csproj index 2a26911..19f8475 100644 --- a/Source/CDR.Register.Admin.API/CDR.Register.Admin.API.csproj +++ b/Source/CDR.Register.Admin.API/CDR.Register.Admin.API.csproj @@ -1,10 +1,10 @@  enable - $(TargetFrameworkVersion) - $(Version) - $(Version) - $(Version) + $(TargetFrameworkVersion) + $(Version) + $(Version) + $(Version) True ConsumerDataRight.ParticipantTooling.MockRegister.API.Admin.xml @@ -29,31 +29,18 @@ - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + all - runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.Admin.API/Program.cs b/Source/CDR.Register.Admin.API/Program.cs index 3bcc18d..ce59c61 100644 --- a/Source/CDR.Register.Admin.API/Program.cs +++ b/Source/CDR.Register.Admin.API/Program.cs @@ -47,6 +47,7 @@ public static void ConfigureSerilog(IConfiguration configuration, bool isDatabas { var loggerConfiguration = new LoggerConfiguration() .ReadFrom.Configuration(configuration) + .AddOpenTelemetry(configuration) .Enrich.FromLogContext() .Enrich.WithProcessId() .Enrich.WithProcessName() diff --git a/Source/CDR.Register.Discovery.API/CDR.Register.Discovery.API.csproj b/Source/CDR.Register.Discovery.API/CDR.Register.Discovery.API.csproj index 523d441..dca5398 100644 --- a/Source/CDR.Register.Discovery.API/CDR.Register.Discovery.API.csproj +++ b/Source/CDR.Register.Discovery.API/CDR.Register.Discovery.API.csproj @@ -2,38 +2,24 @@ 2f05af62-1878-47d5-925d-31c0db13b55c Linux - $(TargetFrameworkVersion) - $(Version) - $(Version) - $(Version) + $(TargetFrameworkVersion) + $(Version) + $(Version) + $(Version) True ConsumerDataRight.ParticipantTooling.MockRegister.API.Discovery.xml - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + all - runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.Discovery.API/Program.cs b/Source/CDR.Register.Discovery.API/Program.cs index 6635e01..925e9d2 100644 --- a/Source/CDR.Register.Discovery.API/Program.cs +++ b/Source/CDR.Register.Discovery.API/Program.cs @@ -21,6 +21,7 @@ public static int Main(string[] args) Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(configuration) + .AddOpenTelemetry(configuration) .Enrich.FromLogContext() .Enrich.WithProcessId() .Enrich.WithProcessName() diff --git a/Source/CDR.Register.Discovery.API/Startup.cs b/Source/CDR.Register.Discovery.API/Startup.cs index afc471a..9815640 100644 --- a/Source/CDR.Register.Discovery.API/Startup.cs +++ b/Source/CDR.Register.Discovery.API/Startup.cs @@ -30,6 +30,7 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddHealthChecks(); services.AddHttpContextAccessor(); services.AddRegisterDiscovery(this.Configuration); @@ -66,17 +67,15 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); - if (this.Configuration.GetSection("SerilogRequestResponseLogger") != null) - { - Log.Logger.Information("Adding request response logging middleware"); - services.AddRequestResponseLogging(); - } + Log.Logger.Information("Adding request response logging middleware"); + services.AddRequestResponseLogging(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - app.UseMiddleware(); + app.UseHealthChecks("/health"); + app.UseRequestResponseLogging(); app.UseExceptionHandler(exceptionHandlerApp => { exceptionHandlerApp.Run(async context => await ApiExceptionHandler.Handle(context)); diff --git a/Source/CDR.Register.Discovery.API/appsettings.json b/Source/CDR.Register.Discovery.API/appsettings.json index d933c53..fb3dee9 100644 --- a/Source/CDR.Register.Discovery.API/appsettings.json +++ b/Source/CDR.Register.Discovery.API/appsettings.json @@ -10,6 +10,9 @@ "Microsoft.Hosting.Lifetime": "Information" } }, + "SerilogRequestResponseLogger": { + "Enabled": true + }, "AllowedHosts": "*", "Kestrel": { "Endpoints": { diff --git a/Source/CDR.Register.Domain.UnitTests/CDR.Register.Domain.UnitTests.csproj b/Source/CDR.Register.Domain.UnitTests/CDR.Register.Domain.UnitTests.csproj index 3d905dc..12eded8 100644 --- a/Source/CDR.Register.Domain.UnitTests/CDR.Register.Domain.UnitTests.csproj +++ b/Source/CDR.Register.Domain.UnitTests/CDR.Register.Domain.UnitTests.csproj @@ -6,27 +6,27 @@ 1.4.0 1.4.0 true + false - - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + all - runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.Domain.UnitTests/DataRecipientTests.cs b/Source/CDR.Register.Domain.UnitTests/DataRecipientTests.cs index 507922b..9c1285c 100644 --- a/Source/CDR.Register.Domain.UnitTests/DataRecipientTests.cs +++ b/Source/CDR.Register.Domain.UnitTests/DataRecipientTests.cs @@ -41,7 +41,7 @@ public void LastUpdated_NoBrands_ShouldReturnNull(DataRecipientBrand[] brands) public void LastUpdated_HasBrands_ShouldReturnLastUpdatedDateFromLatestBrand() { // Arrange - DateTime latestLastUpdated = DateTime.Now.AddDays(-1); + DateTime latestLastUpdated = DateTime.UtcNow.AddDays(-1); var sut = new DataRecipient() { DataRecipientBrands = new DataRecipientBrand[] @@ -55,7 +55,7 @@ public void LastUpdated_HasBrands_ShouldReturnLastUpdatedDateFromLatestBrand() var lastUpdated = sut.LastUpdated; // Assert - Assert.Equal(latestLastUpdated.ToUniversalTime(), lastUpdated); + Assert.Equal(latestLastUpdated, lastUpdated); } } } diff --git a/Source/CDR.Register.Domain/CDR.Register.Domain.csproj b/Source/CDR.Register.Domain/CDR.Register.Domain.csproj index 87f9164..7ce8e39 100644 --- a/Source/CDR.Register.Domain/CDR.Register.Domain.csproj +++ b/Source/CDR.Register.Domain/CDR.Register.Domain.csproj @@ -5,6 +5,7 @@ $(Version) $(Version) true + false @@ -12,18 +13,14 @@ - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all - runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file diff --git a/Source/CDR.Register.Infosec/CDR.Register.Infosec.csproj b/Source/CDR.Register.Infosec/CDR.Register.Infosec.csproj index 7ebdf7f..83d409b 100644 --- a/Source/CDR.Register.Infosec/CDR.Register.Infosec.csproj +++ b/Source/CDR.Register.Infosec/CDR.Register.Infosec.csproj @@ -1,11 +1,11 @@  - $(TargetFrameworkVersion) - $(Version) - $(Version) - $(Version) + $(TargetFrameworkVersion) + $(Version) + $(Version) + $(Version) enable - enable + enable True ConsumerDataRight.ParticipantTooling.MockRegister.API.Infosec.xml @@ -22,28 +22,17 @@ - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + all - runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.Infosec/Controllers/DiscoveryController.cs b/Source/CDR.Register.Infosec/Controllers/DiscoveryController.cs index f414753..d04372f 100644 --- a/Source/CDR.Register.Infosec/Controllers/DiscoveryController.cs +++ b/Source/CDR.Register.Infosec/Controllers/DiscoveryController.cs @@ -1,7 +1,6 @@ using System.Security.Cryptography.X509Certificates; using CDR.Register.API.Infrastructure; using CDR.Register.Infosec.Models; -using IdentityModel; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; @@ -51,14 +50,14 @@ public DiscoveryDocument Get() var cert = new X509Certificate2(this._configuration.GetValue("SigningCertificate:Path") ?? string.Empty, this._configuration.GetValue("SigningCertificate:Password"), X509KeyStorageFlags.Exportable); var cert64 = Convert.ToBase64String(cert.RawData); var signingCredentials = new X509SigningCredentials(cert, SecurityAlgorithms.RsaSsaPssSha256); - var thumbprint = Base64Url.Encode(cert.GetCertHash()); + var thumbprint = Base64UrlEncoder.Encode(cert.GetCertHash()); var rsa = cert.GetRSAPublicKey(); if (rsa != null) { var parameters = rsa.ExportParameters(false); - var exponent = Base64Url.Encode(parameters.Exponent ?? []); - var modulus = Base64Url.Encode(parameters.Modulus ?? []); + var exponent = Base64UrlEncoder.Encode(parameters.Exponent ?? []); + var modulus = Base64UrlEncoder.Encode(parameters.Modulus ?? []); var jwks = new API.Infrastructure.Models.JsonWebKeySet { diff --git a/Source/CDR.Register.Infosec/Program.cs b/Source/CDR.Register.Infosec/Program.cs index b9a4904..0788a0b 100644 --- a/Source/CDR.Register.Infosec/Program.cs +++ b/Source/CDR.Register.Infosec/Program.cs @@ -16,6 +16,7 @@ public static int Main(string[] args) Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(configuration) + .AddOpenTelemetry(configuration) .Enrich.FromLogContext() .Enrich.WithProcessId() .Enrich.WithProcessName() diff --git a/Source/CDR.Register.Infosec/Startup.cs b/Source/CDR.Register.Infosec/Startup.cs index d1e6da0..1ea7bd1 100644 --- a/Source/CDR.Register.Infosec/Startup.cs +++ b/Source/CDR.Register.Infosec/Startup.cs @@ -26,6 +26,7 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { + services.AddHealthChecks(); services.AddHttpContextAccessor(); services.AddRegisterInfosec(this.Configuration); @@ -40,11 +41,8 @@ public void ConfigureServices(IServiceCollection services) options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore; }); - if (this.Configuration.GetSection("SerilogRequestResponseLogger") != null) - { - Log.Logger.Information("Adding request response logging middleware"); - services.AddRequestResponseLogging(); - } + Log.Logger.Information("Adding request response logging middleware"); + services.AddRequestResponseLogging(); // if the distributed cache connection string has been set then use it, otherwise fall back to in-memory caching. if (this.UseDistributedCache()) @@ -71,6 +69,7 @@ public void ConfigureServices(IServiceCollection services) // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + app.UseHealthChecks("/health"); app.UseExceptionHandler(exceptionHandlerApp => { exceptionHandlerApp.Run(async context => await ApiExceptionHandler.Handle(context)); @@ -79,7 +78,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseBasePathOrExpression(this.Configuration); app.UseSerilogRequestLogging(); - app.UseMiddleware(); + app.UseRequestResponseLogging(); app.UseHttpsRedirection(); diff --git a/Source/CDR.Register.Infosec/appsettings.json b/Source/CDR.Register.Infosec/appsettings.json index c981bd0..9bd2d92 100644 --- a/Source/CDR.Register.Infosec/appsettings.json +++ b/Source/CDR.Register.Infosec/appsettings.json @@ -6,6 +6,9 @@ "Microsoft.Hosting.Lifetime": "Information" } }, + "SerilogRequestResponseLogger": { + "Enabled": true + }, "Kestrel": { "Endpoints": { "Https": { diff --git a/Source/CDR.Register.IntegrationTests/CDR.Register.IntegrationTests.csproj b/Source/CDR.Register.IntegrationTests/CDR.Register.IntegrationTests.csproj index b6fe9bf..c9b098e 100644 --- a/Source/CDR.Register.IntegrationTests/CDR.Register.IntegrationTests.csproj +++ b/Source/CDR.Register.IntegrationTests/CDR.Register.IntegrationTests.csproj @@ -1,11 +1,16 @@  false - $(TargetFrameworkVersion) - $(Version) - $(Version) - $(Version) - True + false + $(TargetFrameworkVersion) + $(Version) + $(Version) + $(Version) + True + + + + $(MSBuildProjectDirectory)\integration.Release.runsettings @@ -32,42 +37,32 @@ - - - - - - - - - - - - - - - - + + + + + + + + true + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all - runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.IntegrationTests/Fixtures/TestFixture.cs b/Source/CDR.Register.IntegrationTests/Fixtures/TestFixture.cs index 53192cf..4a80893 100644 --- a/Source/CDR.Register.IntegrationTests/Fixtures/TestFixture.cs +++ b/Source/CDR.Register.IntegrationTests/Fixtures/TestFixture.cs @@ -25,7 +25,7 @@ public static async Task Seeddata() var clientHandler = new HttpClientHandler(); clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; - var client = new HttpClient(clientHandler); + var client = new HttpClient(clientHandler) { Timeout = TimeSpan.FromMinutes(3) }; var request = new HttpRequestMessage(HttpMethod.Post, BaseTest.ADMIN_URL); request.Content = new StringContent(jsonFromSeedDataFile, Encoding.UTF8, "application/json"); diff --git a/Source/CDR.Register.IntegrationTests/Start-Register.bat b/Source/CDR.Register.IntegrationTests/Start-Register.bat index 8beb7ef..778b8a2 100644 --- a/Source/CDR.Register.IntegrationTests/Start-Register.bat +++ b/Source/CDR.Register.IntegrationTests/Start-Register.bat @@ -1,13 +1,16 @@ @echo off -echo Start terminals for projects? -pause +REM echo Start terminals for projects? +REM pause -wt --maximized ^ ---title Gateway_MTLS -d ../CDR.Register.API.Gateway.mTLS dotnet run; ^ ---title Gateway_TLS -d ../CDR.Register.API.Gateway.TLS dotnet run; ^ ---title IdentityServer -d ../CDR.Register.IdentityServer dotnet run; ^ ---title Discovery_API -d ../CDR.Register.Discovery.API dotnet run; ^ ---title SSA_API -d ../CDR.Register.SSA.API dotnet run; ^ ---title Status_API -d ../CDR.Register.Status.API dotnet run; ^ ---title Admin_API -d ../CDR.Register.Admin.API dotnet run - \ No newline at end of file +REM wt --maximized ^ +REM --title Gateway_MTLS -d ../CDR.Register.API.Gateway.mTLS dotnet run; ^ +REM --title Gateway_TLS -d ../CDR.Register.API.Gateway.TLS dotnet run; ^ +REM --title Infosec -d ../CDR.Register.Infosec dotnet run; ^ +REM --title Discovery_API -d ../CDR.Register.Discovery.API dotnet run; ^ +REM --title SSA_API -d ../CDR.Register.SSA.API dotnet run; ^ +REM --title Status_API -d ../CDR.Register.Status.API dotnet run; ^ +REM --title Admin_API -d ../CDR.Register.Admin.API dotnet run + +docker compose -f ../docker-compose.IntegrationTests.yml up -d --build mssql mock-register +echo Supporting infrastructure for tests will now stop. +pause \ No newline at end of file diff --git a/Source/CDR.Register.Repository/CDR.Register.Repository.csproj b/Source/CDR.Register.Repository/CDR.Register.Repository.csproj index 4b1120c..e219e81 100644 --- a/Source/CDR.Register.Repository/CDR.Register.Repository.csproj +++ b/Source/CDR.Register.Repository/CDR.Register.Repository.csproj @@ -1,40 +1,29 @@  - $(TargetFrameworkVersion) - $(Version) - $(Version) - $(Version) + $(TargetFrameworkVersion) + $(Version) + $(Version) + $(Version) true + false - - - - - - - - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive + + true - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + all - runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContextDesignTimeFactory.cs b/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContextDesignTimeFactory.cs index 584a97f..9002555 100644 --- a/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContextDesignTimeFactory.cs +++ b/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContextDesignTimeFactory.cs @@ -4,21 +4,22 @@ namespace CDR.Register.Repository.Infrastructure { /// - /// This is the DB context initialisation for the tooling such as migrations. - /// When the tooling runs the migration, it looks first for a class that implements IDesignTimeDbContextFactory and if found, - /// it will use that for configuring the context. Runtime behavior is not affected by any configuration set in the factory class. + /// Factory for that is used for EF migrations. /// public class RegisterDatabaseContextDesignTimeFactory : IDesignTimeDbContextFactory { - public RegisterDatabaseContextDesignTimeFactory() - { - // A parameter-less constructor is required by the EF Core CLI tools. - } - + /// + /// Creates the configured appropriately for adding or updating migrations at design time. + /// + /// By default when running commands such as dotnet-ef migrations add the tooling will try and use the db + /// connection string from appsettings.json which is not set and is intentionally left blank.
+ /// This will override that and point to a local db without requiring config changes that may accidentally be committed.
+ /// The args. + /// The configured context. public RegisterDatabaseContext CreateDbContext(string[] args) { var options = new DbContextOptionsBuilder() - .UseSqlServer("foo") // connection string is only needed if using "dotnet ef database update ..." to actually run migrations from commandline + .UseSqlServer("Data Source=(LocalDb)\\MSSQLLocalDB;database=cdr-register-migrations;trusted_connection=yes;Max Pool Size=500;Timeout=200;") .Options; return new RegisterDatabaseContext(options); diff --git a/Source/CDR.Register.Repository/readme.md b/Source/CDR.Register.Repository/readme.md new file mode 100644 index 0000000..37afc4d --- /dev/null +++ b/Source/CDR.Register.Repository/readme.md @@ -0,0 +1,12 @@ +# EF Migrations +## Install prerequisite tooling +`dotnet tool restore` + +## Adding migrations +> Migrations are set up to use `MSSQLLocalDB\cdr-register-migrations` database during design time in order to not conflict with any other configuration such as the database used for `Development.Local` or other environments. + +Update the migrations database + - `dotnet ef database update --project CDR.Register.Repository.csproj -c CDR.Register.Repository.Infrastructure.RegisterDatabaseContext` + +Examples for generating migration script which will be added under the migrations folder +- `dotnet ef migrations add --project CDR.Register.Repository.csproj -c CDR.Register.Repository.Infrastructure.RegisterDatabaseContext` diff --git a/Source/CDR.Register.SSA.API.UnitTests/CDR.Register.SSA.API.UnitTests.csproj b/Source/CDR.Register.SSA.API.UnitTests/CDR.Register.SSA.API.UnitTests.csproj index 29e2914..5550300 100644 --- a/Source/CDR.Register.SSA.API.UnitTests/CDR.Register.SSA.API.UnitTests.csproj +++ b/Source/CDR.Register.SSA.API.UnitTests/CDR.Register.SSA.API.UnitTests.csproj @@ -1,12 +1,13 @@  false - $(TargetFrameworkVersion) - $(Version) - $(Version) - $(Version) + false + $(TargetFrameworkVersion) + $(Version) + $(Version) + $(Version) True - + @@ -40,26 +41,24 @@ - - - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + all - runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.SSA.API/Business/SerializationExtensions.cs b/Source/CDR.Register.SSA.API/Business/SerializationExtensions.cs index b4cc5e7..73c4e27 100644 --- a/Source/CDR.Register.SSA.API/Business/SerializationExtensions.cs +++ b/Source/CDR.Register.SSA.API/Business/SerializationExtensions.cs @@ -1,13 +1,16 @@ -using Newtonsoft.Json; +using CDR.Register.Domain.Models; +using Newtonsoft.Json; namespace CDR.Register.SSA.API.Business { public static class SerializationExtensions { + public static readonly JsonSerializerSettings DefaultSerializationConfiguration = new CdrJsonSerializerSettings(); + public static string ToJson(this object value) - { - var result = JsonConvert.SerializeObject(value); - return result; - } + => ToJson(value, DefaultSerializationConfiguration); + + public static string ToJson(this object value, JsonSerializerSettings options) + => JsonConvert.SerializeObject(value, options); } } diff --git a/Source/CDR.Register.SSA.API/CDR.Register.SSA.API.csproj b/Source/CDR.Register.SSA.API/CDR.Register.SSA.API.csproj index 6bf190e..5bac33f 100644 --- a/Source/CDR.Register.SSA.API/CDR.Register.SSA.API.csproj +++ b/Source/CDR.Register.SSA.API/CDR.Register.SSA.API.csproj @@ -2,36 +2,24 @@ 31925f57-bd42-4e7b-bf0a-9080de458279 Linux - $(TargetFrameworkVersion) - $(Version) - $(Version) - $(Version) + $(TargetFrameworkVersion) + $(Version) + $(Version) + $(Version) True ConsumerDataRight.ParticipantTooling.MockRegister.API.SSA.xml - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + all - runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.SSA.API/Program.cs b/Source/CDR.Register.SSA.API/Program.cs index 92573fe..199446c 100644 --- a/Source/CDR.Register.SSA.API/Program.cs +++ b/Source/CDR.Register.SSA.API/Program.cs @@ -21,6 +21,7 @@ public static int Main(string[] args) Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(configuration) + .AddOpenTelemetry(configuration) .Enrich.FromLogContext() .Enrich.WithProcessId() .Enrich.WithProcessName() diff --git a/Source/CDR.Register.SSA.API/Startup.cs b/Source/CDR.Register.SSA.API/Startup.cs index 2d629bc..db8c65f 100644 --- a/Source/CDR.Register.SSA.API/Startup.cs +++ b/Source/CDR.Register.SSA.API/Startup.cs @@ -29,6 +29,7 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddHealthChecks(); services.AddHttpContextAccessor(); services.AddRegisterSSA(this.Configuration); @@ -67,7 +68,8 @@ public void ConfigureServices(IServiceCollection services) // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - app.UseMiddleware(); + app.UseHealthChecks("/health"); + app.UseRequestResponseLogging(); app.UseExceptionHandler(exceptionHandlerApp => { diff --git a/Source/CDR.Register.SSA.API/appsettings.json b/Source/CDR.Register.SSA.API/appsettings.json index 616073d..646ad0c 100644 --- a/Source/CDR.Register.SSA.API/appsettings.json +++ b/Source/CDR.Register.SSA.API/appsettings.json @@ -14,6 +14,9 @@ "Microsoft.Hosting.Lifetime": "Information" } }, + "SerilogRequestResponseLogger": { + "Enabled": true + }, "AllowedHosts": "*", "SigningCertificate": { "Path": "Certificates/ssa.pfx", diff --git a/Source/CDR.Register.Status.API/CDR.Register.Status.API.csproj b/Source/CDR.Register.Status.API/CDR.Register.Status.API.csproj index 05dee27..498bbc8 100644 --- a/Source/CDR.Register.Status.API/CDR.Register.Status.API.csproj +++ b/Source/CDR.Register.Status.API/CDR.Register.Status.API.csproj @@ -3,37 +3,23 @@ 2f05af62-1878-47d5-925d-31c0db13b55c Linux $(TargetFrameworkVersion) - $(Version) - $(Version) - $(Version) + $(Version) + $(Version) + $(Version) True ConsumerDataRight.ParticipantTooling.MockRegister.API.Status.xml - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + all - runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.Status.API/Program.cs b/Source/CDR.Register.Status.API/Program.cs index a916aee..dfd54c6 100644 --- a/Source/CDR.Register.Status.API/Program.cs +++ b/Source/CDR.Register.Status.API/Program.cs @@ -21,6 +21,7 @@ public static int Main(string[] args) Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(configuration) + .AddOpenTelemetry(configuration) .Enrich.FromLogContext() .Enrich.WithProcessId() .Enrich.WithProcessName() diff --git a/Source/CDR.Register.Status.API/Startup.cs b/Source/CDR.Register.Status.API/Startup.cs index d829951..29f63d3 100644 --- a/Source/CDR.Register.Status.API/Startup.cs +++ b/Source/CDR.Register.Status.API/Startup.cs @@ -30,6 +30,7 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddHealthChecks(); services.AddHttpContextAccessor(); services.AddRegisterStatus(this.Configuration); @@ -61,17 +62,15 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); - if (this.Configuration.GetSection("SerilogRequestResponseLogger") != null) - { - Log.Logger.Information("Adding request response logging middleware"); - services.AddRequestResponseLogging(); - } + Log.Logger.Information("Adding request response logging middleware"); + services.AddRequestResponseLogging(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - app.UseMiddleware(); + app.UseHealthChecks("/health"); + app.UseRequestResponseLogging(); app.UseExceptionHandler(exceptionHandlerApp => { diff --git a/Source/CDR.Register.Status.API/appsettings.json b/Source/CDR.Register.Status.API/appsettings.json index be266c4..48c0f61 100644 --- a/Source/CDR.Register.Status.API/appsettings.json +++ b/Source/CDR.Register.Status.API/appsettings.json @@ -10,6 +10,9 @@ "Microsoft.Hosting.Lifetime": "Information" } }, + "SerilogRequestResponseLogger": { + "Enabled": true + }, "AllowedHosts": "*", "Kestrel": { "Endpoints": { diff --git a/Source/Directory.Build.props b/Source/Directory.Build.props index e75e49f..edbc3eb 100644 --- a/Source/Directory.Build.props +++ b/Source/Directory.Build.props @@ -1,7 +1,7 @@ net8.0 - 2.2.1 + 2.2.3 true true true @@ -11,4 +11,13 @@ $(NoWarn);1591 + + + + + + <_Parameter1>Category + <_Parameter2>UnitTests + + \ No newline at end of file diff --git a/Source/Directory.Packages.props b/Source/Directory.Packages.props new file mode 100644 index 0000000..c349cb8 --- /dev/null +++ b/Source/Directory.Packages.props @@ -0,0 +1,61 @@ + + + true + false + $(NoWarn);NU1507 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Dockerfile b/Source/Dockerfile index 0bc99fd..2066f59 100644 --- a/Source/Dockerfile +++ b/Source/Dockerfile @@ -1,3 +1,4 @@ +# check=skip=JSONArgsRecommended FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 7000 @@ -11,66 +12,48 @@ FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY . ./ +RUN dotnet build --configuration Release + +# Install ca certificate +RUN apt-get update && apt-get install -y sudo +RUN sudo cp ./CDR.Register.API.Gateway.mTLS/Certificates/ca.crt /usr/local/share/ca-certificates/ca.crt +RUN sudo update-ca-certificates + +FROM build AS unit-tests +ENTRYPOINT ["dotnet", "test", "--configuration", "Release", "--filter", "Category=UnitTests", "--no-build", "--logger", "trx;verbosity=detailed", "--results-directory", "/testresults"] + +FROM build AS integration-tests +WORKDIR /src/CDR.Register.IntegrationTests +ENTRYPOINT dotnet test --configuration Release --no-build --filter ${TEST_FILTER} --logger "trx;verbosity=detailed;LogFileName=results.trx;" --results-directory /testresults FROM build AS publish # Copy the build props -COPY ./Directory.Build.props /app/Directory.Build.props -COPY ./.editorconfig /app/.editorconfig - -COPY ./CDR.Register.API.Infrastructure/. /app/CDR.Register.API.Infrastructure -COPY ./CDR.Register.Repository/. /app/CDR.Register.Repository -COPY ./CDR.Register.Domain/. /app/CDR.Register.Domain -COPY ./CDR.Register.Admin.API/. /app/CDR.Register.Admin.API -COPY ./CDR.Register.Discovery.API/. /app/CDR.Register.Discovery.API -COPY ./CDR.Register.Status.API/. /app/CDR.Register.Status.API -COPY ./CDR.Register.SSA.API/. /app/CDR.Register.SSA.API -COPY ./CDR.Register.Infosec/. /app/CDR.Register.Infosec -COPY ./CDR.Register.API.Gateway.mTLS/. /app/CDR.Register.API.Gateway.mTLS -COPY ./CDR.Register.API.Gateway.TLS/. /app/CDR.Register.API.Gateway.TLS -COPY ./CDR.Register.API.Logger/. /app/CDR.Register.API.Logger - -WORKDIR /app/CDR.Register.Admin.API -RUN dotnet publish -c Release -o /app/publish/admin -WORKDIR /app/CDR.Register.Discovery.API -RUN dotnet publish -c Release -o /app/publish/discovery -WORKDIR /app/CDR.Register.Status.API -RUN dotnet publish -c Release -o /app/publish/status -WORKDIR /app/CDR.Register.SSA.API -RUN dotnet publish -c Release -o /app/publish/ssa -WORKDIR /app/CDR.Register.Infosec -RUN dotnet publish -c Release -o /app/publish/infosec -WORKDIR /app/CDR.Register.API.Gateway.mTLS -RUN dotnet publish -c Release -o /app/publish/gateway-mtls -WORKDIR /app/CDR.Register.API.Gateway.TLS -RUN dotnet publish -c Release -o /app/publish/gateway-tls - COPY supervisord.conf /app/publish/supervisord.conf - COPY wait-until-admin-healthy-then-start.sh /app/publish/wait-until-admin-healthy-then-start.sh +RUN dotnet publish -c Release +RUN cp -r CDR.Register.API.Gateway.mTLS/bin/Release/net8.0/publish /app/publish/gateway-mtls +RUN cp -r CDR.Register.API.Gateway.TLS/bin/Release/net8.0/publish /app/publish/gateway-tls +RUN cp -r CDR.Register.SSA.API/bin/Release/net8.0/publish /app/publish/ssa +RUN cp -r CDR.Register.Status.API/bin/Release/net8.0/publish /app/publish/status +RUN cp -r CDR.Register.Admin.API/bin/Release/net8.0/publish /app/publish/admin +RUN cp -r CDR.Register.Discovery.API/bin/Release/net8.0/publish /app/publish/discovery +RUN cp -r CDR.Register.Infosec/bin/Release/net8.0/publish /app/publish/infosec FROM base AS final WORKDIR /app -COPY --from=publish /app/publish/supervisord.conf . -COPY --from=publish /app/publish/wait-until-admin-healthy-then-start.sh . -COPY --from=publish /app/publish/discovery ./discovery -COPY --from=publish /app/publish/admin ./admin -COPY --from=publish /app/publish/status ./status -COPY --from=publish /app/publish/infosec ./infosec -COPY --from=publish /app/publish/gateway-mtls ./gateway-mtls -COPY --from=publish /app/publish/gateway-tls ./gateway-tls -COPY --from=publish /app/publish/ssa ./ssa +COPY --from=publish /app/publish . RUN apt-get update && apt-get install -y supervisor -RUN apt-get update && apt-get install -y sudo - # Install wget for use in health checks RUN apt-get update && apt-get install -y wget && rm -rf /var/lib/apt/lists/* -RUN sudo cp ./gateway-mtls/Certificates/ca.crt /usr/local/share/ca-certificates/ca.crt +RUN apt-get update && apt-get install -y sudo +# Trust CDR CA certificate +RUN sudo cp ./gateway-mtls/Certificates/ca.crt /usr/local/share/ca-certificates/ca.crt RUN sudo update-ca-certificates RUN addgroup --group appgroup --gid 2000 \ @@ -79,11 +62,12 @@ RUN addgroup --group appgroup --gid 2000 \ --gid 2000 \ "appuser" -RUN chown -R appuser:appgroup /app -RUN chown -R appuser:appgroup /usr/bin -RUN chown -R appuser:appgroup /usr/local -RUN chown -R appuser:appgroup /tmp -USER appuser:appgroup +RUN chown -R appuser:appgroup /app \ + && chown -R appuser:appgroup /usr/bin \ + && chown -R appuser:appgroup /usr/local \ + && chown -R appuser:appgroup /tmp + + USER appuser:appgroup ENV ASPNETCORE_URLS=https://+:7000;https://+:7001 diff --git a/Source/Dockerfile.integration-tests b/Source/Dockerfile.integration-tests deleted file mode 100644 index d98b4b1..0000000 --- a/Source/Dockerfile.integration-tests +++ /dev/null @@ -1,31 +0,0 @@ -# Dockerfile for running integration tests - -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build -WORKDIR /src - -# Default ASPNETCORE_ENVIRONMENT to Release -ENV ASPNETCORE_ENVIRONMENT=Release - -# Copy the build props -COPY ./Directory.Build.props ./Directory.Build.props -COPY ./.editorconfig ./.editorconfig - -# Copy source -COPY ./CDR.Register.API.Gateway.mTLS/Certificates/ca.crt ./CDR.Register.API.Gateway.mTLS/Certificates/ca.crt -COPY ./CDR.Register.Admin.API/. ./CDR.Register.Admin.API -COPY ./CDR.Register.Repository/. ./CDR.Register.Repository -COPY ./CDR.Register.Domain/. ./CDR.Register.Domain -COPY ./CDR.Register.API.Infrastructure/. ./CDR.Register.API.Infrastructure -COPY ./CDR.Register.IntegrationTests/. ./CDR.Register.IntegrationTests - - -# Install ca certificate -RUN apt-get update && apt-get install -y sudo -RUN sudo cp ./CDR.Register.API.Gateway.mTLS/Certificates/ca.crt /usr/local/share/ca-certificates/ca.crt -RUN sudo update-ca-certificates - -# Run tests -WORKDIR /src/CDR.Register.IntegrationTests -RUN dotnet build --configuration Release - -ENTRYPOINT dotnet test --configuration Release --no-build --filter ${TEST_FILTER} --logger "trx;verbosity=detailed;LogFileName=results.trx;" --results-directory /testresults \ No newline at end of file diff --git a/Source/Dockerfile.unit-tests b/Source/Dockerfile.unit-tests deleted file mode 100644 index 75904bb..0000000 --- a/Source/Dockerfile.unit-tests +++ /dev/null @@ -1,23 +0,0 @@ -# Dockerfile for running unit tests - -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build -WORKDIR /src - -# Default ASPNETCORE_ENVIRONMENT to Release -ENV ASPNETCORE_ENVIRONMENT=Release - -# Copy source -COPY . ./ - -# Install ca certificate -RUN apt-get update && apt-get install -y sudo -RUN sudo cp ./CDR.Register.API.Gateway.mTLS/Certificates/ca.crt /usr/local/share/ca-certificates/ca.crt -RUN sudo update-ca-certificates - -# Build tests -WORKDIR /src -RUN dotnet build --configuration Release - -# Run tests -ENTRYPOINT ["dotnet", "test", "--configuration", "Release", "--filter", "Category=UnitTests", "--no-build", "--logger", "trx;verbosity=detailed", "--results-directory", "/testresults"] -# ENTRYPOINT ["tail", "-f", "/dev/null"] diff --git a/Source/Register.sln b/Source/Register.sln index 90015dd..b9d21bd 100644 --- a/Source/Register.sln +++ b/Source/Register.sln @@ -7,14 +7,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .dockerignore = .dockerignore .editorconfig = .editorconfig + Directory.Packages.props = Directory.Packages.props docker-compose.Ecosystem.yml = docker-compose.Ecosystem.yml docker-compose.IntegrationTests.yml = docker-compose.IntegrationTests.yml docker-compose.UnitTests.yml = docker-compose.UnitTests.yml docker-compose.yml = docker-compose.yml Dockerfile = Dockerfile - Dockerfile.container = Dockerfile.container - Dockerfile.integration-tests = Dockerfile.integration-tests - Dockerfile.unit-tests = Dockerfile.unit-tests ..\README.md = ..\README.md run-integration-tests.ps1 = run-integration-tests.ps1 run-unit-tests.ps1 = run-unit-tests.ps1 diff --git a/Source/Register.slnLaunch b/Source/Register.slnLaunch new file mode 100644 index 0000000..21b0b00 --- /dev/null +++ b/Source/Register.slnLaunch @@ -0,0 +1,35 @@ +[ + { + "Name": "Mock Register with Gateways", + "Projects": [ + { + "Path": "CDR.Register.Admin.API\\CDR.Register.Admin.API.csproj", + "Action": "Start" + }, + { + "Path": "CDR.Register.Infosec\\CDR.Register.Infosec.csproj", + "Action": "Start" + }, + { + "Path": "CDR.Register.Status.API\\CDR.Register.Status.API.csproj", + "Action": "Start" + }, + { + "Path": "CDR.Register.Discovery.API\\CDR.Register.Discovery.API.csproj", + "Action": "Start" + }, + { + "Path": "CDR.Register.SSA.API\\CDR.Register.SSA.API.csproj", + "Action": "Start" + }, + { + "Path": "CDR.Register.API.Gateway.TLS\\CDR.Register.API.Gateway.TLS.csproj", + "Action": "StartWithoutDebugging" + }, + { + "Path": "CDR.Register.API.Gateway.mTLS\\CDR.Register.API.Gateway.mTLS.csproj", + "Action": "StartWithoutDebugging" + } + ] + } +] \ No newline at end of file diff --git a/Source/docker-compose.Ecosystem.yml b/Source/docker-compose.Ecosystem.yml index e0dd7fa..0567a80 100644 --- a/Source/docker-compose.Ecosystem.yml +++ b/Source/docker-compose.Ecosystem.yml @@ -1,3 +1,4 @@ +name: mock-register-ecosystem services: mock-register: @@ -14,7 +15,7 @@ services: environment: - ASPNETCORE_ENVIRONMENT=Release healthcheck: - test: wget --no-check-certificate --no-verbose --spider https://localhost:7006/health || exit 1 + test: wget --server-response https://localhost:7000/health 2>&1 | grep 'HTTP/' | grep '200' timeout: 5s interval: 5s retries: 50 diff --git a/Source/docker-compose.IntegrationTests.yml b/Source/docker-compose.IntegrationTests.yml index dbeac30..8e6b66e 100644 --- a/Source/docker-compose.IntegrationTests.yml +++ b/Source/docker-compose.IntegrationTests.yml @@ -1,4 +1,5 @@ # Docker compose for build pipeline +name: mock-register-integration-tests services: mock-register: @@ -11,6 +12,12 @@ services: - "7000-7006:7000-7006" environment: - ASPNETCORE_ENVIRONMENT=Release + - SERILOG__MINIMUMLEVEL__DEFAULT=Warning + - Logging__LogLevel__Default=Warning + - Logging__LogLevel__CDR=Information + - Logging__LogLevel__Microsoft=Warning + - Logging__LogLevel__Microsoft.Hosting.Lifetime=Warning + - Logging__LogLevel__Microsoft.AspNetCore=Warning - PublicHostName - SecureHostName - BasePath @@ -24,7 +31,7 @@ services: # volumes: # - "./_temp/mock-register/tmp:/tmp" healthcheck: - test: ((wget --no-check-certificate --no-verbose --spider https://localhost:7006/health) && (wget --no-check-certificate --no-verbose --spider https://localhost:7002/idp/.well-known/openid-configuration)) || exit 1 + test: wget --server-response https://localhost:7000/health 2>&1 | grep 'HTTP/' | grep '200' timeout: 5s interval: 5s retries: 50 @@ -37,9 +44,13 @@ services: image: mock-register-integration-tests build: context: . - dockerfile: Dockerfile.integration-tests + dockerfile: Dockerfile + target: integration-tests environment: - ASPNETCORE_ENVIRONMENT=Release + - SERILOG__MINIMUMLEVEL__DEFAULT=Information + - Logging__LogLevel__Default=Warning + - Logging__LogLevel__CDR=Information # Default TEST_FILTER to not run CTS Only tests - TEST_FILTER=${TEST_FILTER:-"Category!=CTSONLY"} - CtsSettings__AzureAd__TokenEndpointUrl @@ -61,7 +72,7 @@ services: condition: service_healthy mssql: - container_name: sql1 + container_name: mock-register-integration-sql image: 'mcr.microsoft.com/mssql/server:2022-latest' ports: - "1433:1433" diff --git a/Source/docker-compose.UnitTests.yml b/Source/docker-compose.UnitTests.yml index 8723721..8c9f867 100644 --- a/Source/docker-compose.UnitTests.yml +++ b/Source/docker-compose.UnitTests.yml @@ -1,4 +1,5 @@ # Docker compose for build pipeline +name: mock-register-unit-tests services: mock-register-unit-tests: @@ -6,25 +7,9 @@ services: image: mock-register-unit-tests build: context: . - dockerfile: Dockerfile.unit-tests + dockerfile: Dockerfile + target: unit-tests environment: - ASPNETCORE_ENVIRONMENT=Release volumes: - "./_temp/mock-register-unit-tests/testresults:/testresults" - depends_on: - mssql: - condition: service_healthy - - mssql: - container_name: sql1 - image: 'mcr.microsoft.com/mssql/server:2022-latest' - ports: - - "1433:1433" - environment: - - ACCEPT_EULA=Y - - MSSQL_SA_PASSWORD=Pa{}w0rd2019 - healthcheck: - test: /opt/mssql-tools18/bin/sqlcmd -S . -U sa -P "Pa{}w0rd2019" -Q "SELECT 1" -No || exit 1 - timeout: 5s - interval: 5s - retries: 20 diff --git a/Source/docker-compose.yml b/Source/docker-compose.yml index c46f589..b8d7566 100644 --- a/Source/docker-compose.yml +++ b/Source/docker-compose.yml @@ -19,7 +19,7 @@ services: environment: - ASPNETCORE_ENVIRONMENT=Release healthcheck: - test: wget --no-check-certificate --no-verbose --spider https://localhost:7006/health || exit 1 + test: wget --server-response https://localhost:7000/health 2>&1 | grep 'HTTP/' | grep '200' timeout: 5s interval: 5s retries: 50