コード例 #1
0
        public void Constructor_BindsConfigurationCorrectly()
        {
            var appsettings = new Dictionary <string, string>()
            {
                ["management:endpoints:enabled"]                           = "false",
                ["management:endpoints:path"]                              = "/cloudfoundryapplication",
                ["management:endpoints:health:enabled"]                    = "true",
                ["management:endpoints:health:requiredPermissions"]        = "NONE",
                ["management:endpoints:cloudfoundry:validatecertificates"] = "true",
                ["management:endpoints:cloudfoundry:enabled"]              = "true"
            };
            ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();

            configurationBuilder.AddInMemoryCollection(appsettings);
            var config = configurationBuilder.Build();

            var opts      = new HealthEndpointOptions(config);
            var cloudOpts = new CloudFoundryEndpointOptions(config);

            Assert.True(cloudOpts.Enabled);
            Assert.Equal(string.Empty, cloudOpts.Id);
            Assert.Equal(string.Empty, cloudOpts.Path);
            Assert.True(cloudOpts.ValidateCertificates);

            Assert.True(opts.Enabled);
            Assert.Equal("health", opts.Id);
            Assert.Equal("health", opts.Path);
            Assert.Equal(Permissions.NONE, opts.RequiredPermissions);
        }
コード例 #2
0
        public void Invoke_HandlesExceptions_ReturnsExpectedHealth()
        {
            var opts         = new HealthEndpointOptions();
            var contributors = new List <IHealthContributor>()
            {
                new TestContrib("h1"), new TestContrib("h2", true), new TestContrib("h3")
            };
            var ep = new HealthEndpoint(opts, new DefaultHealthAggregator(), contributors);

            var info = ep.Invoke(null);

            foreach (var contrib in contributors)
            {
                TestContrib tc = (TestContrib)contrib;
                if (tc.Throws)
                {
                    Assert.False(tc.Called);
                }
                else
                {
                    Assert.True(tc.Called);
                }
            }

            Assert.Equal(HealthStatus.UP, info.Status);
        }
コード例 #3
0
        public async Task SpringBootAdminClient_RegistersAndDeletes()
        {
            var appsettings = new Dictionary <string, string>()
            {
                ["management:endpoints:path"]        = "/management",
                ["management:endpoints:health:path"] = "myhealth",
                ["URLS"] = "http://localhost:8080;https://localhost:8082",
                ["spring:boot:admin:client:url"] = "http://springbootadmin:9090",
                ["spring:application:name"]      = "MySteeltoeApplication",
            };

            var config             = new ConfigurationBuilder().AddInMemoryCollection(appsettings).Build();
            var appInfo            = new ApplicationInstanceInfo(config);
            var sbaOptions         = new SpringBootAdminClientOptions(config, appInfo);
            var mgmtOptions        = new ManagementEndpointOptions(config);
            var healthOptions      = new HealthEndpointOptions(config);
            var httpMessageHandler = new MockHttpMessageHandler();

            httpMessageHandler
            .Expect(HttpMethod.Post, "http://springbootadmin:9090/instances")
            .Respond("application/json", "{\"Id\":\"1234567\"}");
            httpMessageHandler
            .Expect(HttpMethod.Delete, "http://springbootadmin:9090/instances/1234567")
            .Respond(req => new HttpResponseMessage(HttpStatusCode.NoContent));

            Assert.Null(SpringBootAdminClientHostedService.RegistrationResult);
            var service = new SpringBootAdminClientHostedService(sbaOptions, mgmtOptions, healthOptions, httpMessageHandler.ToHttpClient());
            await service.StartAsync(default);
コード例 #4
0
#pragma warning disable S107 // Methods should not have too many parameters
        public static void UseCloudFoundryActuators(this IAppBuilder app, IConfiguration configuration, IEnumerable <IHealthContributor> healthContributors, IApiExplorer apiExplorer, ILoggerProvider loggerProvider, ILoggerFactory loggerFactory, MediaTypeVersion version, ActuatorContext context)
#pragma warning restore S107 // Methods should not have too many parameters
        {
            app.UseDiagnosticSourceMiddleware(loggerFactory);
            if (context != ActuatorContext.Actuator)
            {
                app.UseCloudFoundrySecurityMiddleware(configuration, loggerFactory);
                app.UseCloudFoundryActuator(configuration, loggerFactory);
            }

            if (context != ActuatorContext.CloudFoundry)
            {
                app.UseHypermediaActuator(configuration, loggerFactory);
            }

            if (Environment.OSVersion.Platform == PlatformID.Win32NT)
            {
                app.UseThreadDumpActuator(configuration, loggerFactory, version);
                app.UseHeapDumpActuator(configuration, null, loggerFactory);
            }

            app.UseInfoActuator(configuration, loggerFactory);
            app.UseEnvActuator(configuration, loggerFactory);
            var healthOptions = new HealthEndpointOptions(configuration);

            app.UseHealthActuator(healthOptions, new DefaultHealthAggregator(), healthContributors, loggerFactory);

            app.UseLoggersActuator(configuration, loggerProvider, loggerFactory);
            app.UseTraceActuator(configuration, null, loggerFactory, version);
            app.UseMappingActuator(configuration, apiExplorer, loggerFactory);
        }
コード例 #5
0
        public async void HandleHealthRequestAsync_ReturnsExpected()
        {
            var opts        = new HealthEndpointOptions();
            var mgmtOptions = new CloudFoundryManagementOptions();

            mgmtOptions.EndpointOptions.Add(opts);
            var contribs = new List <IHealthContributor>()
            {
                new DiskSpaceContributor()
            };
            var ep     = new TestHealthEndpoint(opts, new DefaultHealthAggregator(), contribs);
            var middle = new HealthEndpointMiddleware(null, new List <IManagementOptions> {
                mgmtOptions
            });

            middle.Endpoint = ep;

            var context = CreateRequest("GET", "/health");
            await middle.HandleHealthRequestAsync(context);

            context.Response.Body.Seek(0, SeekOrigin.Begin);
            StreamReader rdr  = new StreamReader(context.Response.Body);
            string       json = await rdr.ReadToEndAsync();

            Assert.Equal("{\"status\":\"UNKNOWN\"}", json);
        }
コード例 #6
0
 public SpringBootAdminClientHostedService(SpringBootAdminClientOptions options, ManagementEndpointOptions mgmtOptions, HealthEndpointOptions healthOptions, HttpClient httpClient = null, ILogger logger = null)
 {
     _options       = options;
     _mgmtOptions   = mgmtOptions;
     _healthOptions = healthOptions;
     _httpClient    = httpClient ?? HttpClientHelper.GetHttpClient(_options.ValidateCertificates, _options.ConnectionTimeoutMS);
     _logger        = logger ?? NullLogger.Instance;
 }
        public void UseHealthActuator_ThrowsIfContributorsNull()
        {
            var builder   = new AppBuilder();
            var config    = new ConfigurationBuilder().Build();
            var options   = new HealthEndpointOptions(config);
            var exception = Assert.Throws <ArgumentNullException>(() => builder.UseHealthActuator(options, new DefaultHealthAggregator(), contributors: null));

            Assert.Equal("contributors", exception.ParamName);
        }
コード例 #8
0
        public void Constructor_InitializesWithDefaults()
        {
            var opts = new HealthEndpointOptions();

            Assert.Null(opts.Enabled);
            Assert.Equal("health", opts.Id);
            Assert.Equal(ShowDetails.Always, opts.ShowDetails);
            Assert.Equal(Permissions.RESTRICTED, opts.RequiredPermissions);
        }
コード例 #9
0
        public void RoutesByPathAndVerb()
        {
            var options = new HealthEndpointOptions();

            Assert.False(options.ExactMatch);
            Assert.Equal("/actuator/health/{**_}", options.GetContextPath(new ActuatorManagementOptions()));
            Assert.Equal("/cloudfoundryapplication/health/{**_}", options.GetContextPath(new CloudFoundryManagementOptions()));
            Assert.Null(options.AllowedVerbs);
        }
コード例 #10
0
        public void Configuration(IAppBuilder app)
        {
            var healthEndpointOptions = new HealthEndpointOptions();

            healthEndpointOptions
            .AddCpuUsageIndicator()
            .AddDiskSpaceUsageIndicator()
            .AddMemoryUsageIndicator();

            app.UseHealthEndpoint(healthEndpointOptions);
        }
コード例 #11
0
        public void Invoke_NoContributors_ReturnsExpectedHealth()
        {
            var opts         = new HealthEndpointOptions();
            var contributors = new List <IHealthContributor>();
            var agg          = new DefaultHealthAggregator();
            var ep           = new HealthEndpoint(opts, agg, contributors, GetLogger <HealthEndpoint>());

            var health = ep.Invoke(null);

            Assert.NotNull(health);
            Assert.Equal(HealthStatus.UNKNOWN, health.Status);
        }
コード例 #12
0
        public void HealthEndpointMiddleware_PathAndVerbMatching_ReturnsExpected()
        {
            var opts = new HealthEndpointOptions();
            var contribs = new List<IHealthContributor>() { new DiskSpaceContributor() };
            var ep = new HealthEndpoint(opts, new DefaultHealthAggregator(), contribs);
            var actMOptions = new ActuatorManagementOptions();
            actMOptions.EndpointOptions.Add(opts);
            var middle = new HealthEndpointMiddleware(null, ep, new List<IManagementOptions> { actMOptions });

            Assert.True(middle.RequestVerbAndPathMatch("GET", "/actuator/health"));
            Assert.False(middle.RequestVerbAndPathMatch("PUT", "/actuator/health"));
            Assert.False(middle.RequestVerbAndPathMatch("GET", "/actuator/badpath"));
        }
コード例 #13
0
        public void HealthEndpointMiddleware_PathAndVerbMatching_ReturnsExpected()
        {
            var opts     = new HealthEndpointOptions();
            var mopts    = TestHelpers.GetManagementOptions(opts);
            var contribs = new List <IHealthContributor>()
            {
                new DiskSpaceContributor()
            };
            var ep     = new HealthEndpoint(opts, new DefaultHealthAggregator(), contribs);
            var middle = new HealthEndpointOwinMiddleware(null, ep, mopts);

            Assert.True(middle.RequestVerbAndPathMatch("GET", "/cloudfoundryapplication/health"));
            Assert.False(middle.RequestVerbAndPathMatch("PUT", "/cloudfoundryapplication/health"));
            Assert.False(middle.RequestVerbAndPathMatch("GET", "/cloudfoundryapplication/badpath"));
        }
コード例 #14
0
        public void Invoke_CallsAllContributors()
        {
            var opts         = new HealthEndpointOptions();
            var contributors = new List <IHealthContributor>()
            {
                new TestContrib("h1"), new TestContrib("h2"), new TestContrib("h3")
            };
            var ep = new HealthEndpoint(opts, new DefaultHealthAggregator(), contributors);

            var info = ep.Invoke(null);

            foreach (var contrib in contributors)
            {
                TestContrib tc = (TestContrib)contrib;
                Assert.True(tc.Called);
            }
        }
コード例 #15
0
        public void Constructor_BindsRoleCorrectly()
        {
            var appsettings = new Dictionary <string, string>()
            {
                ["management:endpoints:health:role"] = "roleclaimvalue"
            };
            ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();

            configurationBuilder.AddInMemoryCollection(appsettings);
            var config = configurationBuilder.Build();

            var opts = new HealthEndpointOptions(config);

            Assert.NotNull(opts.Claim);
            Assert.Equal(ClaimTypes.Role, opts.Claim.Type);
            Assert.Equal("roleclaimvalue", opts.Claim.Value);
        }
コード例 #16
0
        public async void HealthInvoke_ReturnsExpected()
        {
            // arrange
            var opts     = new HealthEndpointOptions();
            var mopts    = TestHelpers.GetManagementOptions(opts);
            var contribs = new List <IHealthContributor>()
            {
                new DiskSpaceContributor()
            };
            var ep      = new TestHealthEndpoint(opts, new DefaultHealthAggregator(), contribs);
            var middle  = new HealthEndpointOwinMiddleware(null, ep, mopts);
            var context = OwinTestHelpers.CreateRequest("GET", "/cloudfoundryapplication/health");

            // act
            var json = await middle.InvokeAndReadResponse(context);

            // assert
            Assert.Equal("{\"status\":\"UNKNOWN\"}", json);
        }
コード例 #17
0
        /// <summary>
        /// Adds the services used by the Health actuator
        /// </summary>
        /// <param name="services">Reference to the service collection</param>
        /// <param name="configuration">Reference to the configuration system</param>
        /// <returns>A reference to the service collection</returns>
        public static IServiceCollection AddHealthActuatorServices(this IServiceCollection services, IConfiguration configuration)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

            if (configuration == null)
            {
                throw new ArgumentNullException(nameof(configuration));
            }

            var options = new HealthEndpointOptions(configuration);

            services.TryAddSingleton <IHealthOptions>(options);
            services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IEndpointOptions), options));
            services.TryAddScoped <HealthEndpoint>();

            return(services);
        }
コード例 #18
0
        public static void UseHealthActuator(IConfiguration configuration, IHealthAggregator healthAggregator = null, IEnumerable <IHealthContributor> contributors = null, ILoggerFactory loggerFactory = null)
        {
            var options = new HealthEndpointOptions(configuration);

            _mgmtOptions.RegisterEndpointOptions(configuration, options);
            if (ConfiguredHandlers.OfType <HealthHandler>().Any())
            {
                return;
            }

            healthAggregator = healthAggregator ?? new DefaultHealthAggregator();
            contributors     = contributors ?? new List <IHealthContributor>()
            {
                new DiskSpaceContributor(new DiskSpaceContributorOptions(configuration))
            };
            var ep      = new HealthEndpoint(options, healthAggregator, contributors, CreateLogger <HealthEndpoint>(loggerFactory));
            var handler = new HealthHandler(ep, SecurityServices, _mgmtOptions, CreateLogger <HealthHandler>(loggerFactory));

            ConfiguredHandlers.Add(handler);
        }
コード例 #19
0
        public void InvokeWithInvalidGroupReturnsAllContributors()
        {
            // arrange
            var opts     = new HealthEndpointOptions();
            var contribs = new List <IHealthContributor>()
            {
                new DiskSpaceContributor(), new OutOfSserviceContributor(), new UnknownContributor(), new UpContributor()
            };
            var ep      = new HealthEndpoint(opts, new DefaultHealthAggregator(), contribs);
            var context = Substitute.For <ISecurityContext>();

            context.GetRequestComponents().Returns(new string[] { "health", "iNvAlId" });

            // act
            var result = ep.Invoke(context);

            // assert
            Assert.Equal(HealthStatus.OUT_OF_SERVICE, result.Status);
            Assert.True(result.Details.Keys.Count == 4);
            Assert.True(result.Groups.Count() == 2);
        }
コード例 #20
0
        /// <summary>
        /// Add HealthCheck actuator endpoint to OWIN Pipeline
        /// </summary>
        /// <param name="builder">OWIN <see cref="IAppBuilder" /></param>
        /// <param name="config"><see cref="IConfiguration"/> of application for configuring health endpoint</param>
        /// <param name="loggerFactory">For logging within the middleware</param>
        /// <returns>OWIN <see cref="IAppBuilder" /> with Health Endpoint added</returns>
        public static IAppBuilder UseHealthActuator(this IAppBuilder builder, IConfiguration config, ILoggerFactory loggerFactory = null)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            if (config == null)
            {
                throw new ArgumentNullException(nameof(config));
            }

            var mgmtOptions = ManagementOptions.Get(config);
            var options     = new HealthEndpointOptions(config);

            foreach (var mgmt in mgmtOptions)
            {
                mgmt.EndpointOptions.Add(options);
            }

            return(builder.UseHealthActuator(options, new DefaultHealthAggregator(), loggerFactory));
        }
コード例 #21
0
        public void GetStatusCode_ReturnsExpected()
        {
            var opts     = new HealthEndpointOptions();
            var contribs = new List <IHealthContributor>()
            {
                new DiskSpaceContributor()
            };
            var ep = new HealthEndpoint(opts, new DefaultHealthAggregator(), contribs);

            Assert.Equal(503, ep.GetStatusCode(new HealthCheckResult {
                Status = HealthStatus.DOWN
            }));
            Assert.Equal(503, ep.GetStatusCode(new HealthCheckResult {
                Status = HealthStatus.OUT_OF_SERVICE
            }));
            Assert.Equal(200, ep.GetStatusCode(new HealthCheckResult {
                Status = HealthStatus.UP
            }));
            Assert.Equal(200, ep.GetStatusCode(new HealthCheckResult {
                Status = HealthStatus.UNKNOWN
            }));
        }
コード例 #22
0
        /// <summary>
        /// Register the Health endpoint, OWIN middleware and options
        /// </summary>
        /// <param name="container">Autofac DI <see cref="ContainerBuilder"/></param>
        /// <param name="config">Your application's <see cref="IConfiguration"/></param>
        /// <param name="aggregator">Your <see cref="IHealthAggregator"/></param>
        /// <param name="contributors">Types that implement <see cref="IHealthContributor"/></param>
        public static void RegisterHealthActuator(this ContainerBuilder container, IConfiguration config, IHealthAggregator aggregator, params Type[] contributors)
        {
            if (container == null)
            {
                throw new ArgumentNullException(nameof(container));
            }

            if (config == null)
            {
                throw new ArgumentNullException(nameof(config));
            }

            if (aggregator == null)
            {
                aggregator = new DefaultHealthAggregator();
            }

            container.Register(c =>
            {
                var options     = new HealthEndpointOptions(config);
                var mgmtOptions = c.Resolve <IEnumerable <IManagementOptions> >();

                foreach (var mgmt in mgmtOptions)
                {
                    mgmt.EndpointOptions.Add(options);
                }
                return(options);
            }).As <IHealthOptions>().IfNotRegistered(typeof(IHealthOptions)).SingleInstance();

            container.RegisterInstance(aggregator).As <IHealthAggregator>().SingleInstance();
            foreach (var c in contributors)
            {
                container.RegisterType(c).As <IHealthContributor>();
            }

            container.RegisterType <HealthEndpoint>();
            container.RegisterType <HealthEndpointOwinMiddleware>();
        }
コード例 #23
0
        public void InvokeWithLivenessGroupReturnsGroupResults()
        {
            // arrange
            var opts            = new HealthEndpointOptions();
            var appAvailability = new ApplicationAvailability();
            var contribs        = new List <IHealthContributor>()
            {
                new DiskSpaceContributor(), new LivenessHealthContributor(appAvailability)
            };
            var ep      = new HealthEndpoint(opts, new DefaultHealthAggregator(), contribs);
            var context = Substitute.For <ISecurityContext>();

            context.GetRequestComponents().Returns(new string[] { "cloudfoundryapplication", "health", "liVeness" });
            appAvailability.SetAvailabilityState(appAvailability.LivenessKey, LivenessState.Correct, null);

            // act
            var result = ep.Invoke(context);

            // assert
            Assert.Equal(HealthStatus.UP, result.Status);
            Assert.True(result.Details.Keys.Count == 1);
            Assert.True(result.Groups.Count() == 2);
        }
コード例 #24
0
        public void InvokeWithReadinessGroupReturnsGroupResults()
        {
            // arrange
            var opts            = new HealthEndpointOptions();
            var appAvailability = new ApplicationAvailability();
            var contribs        = new List <IHealthContributor>()
            {
                new UnknownContributor(), new UpContributor(), new ReadinessHealthContributor(appAvailability)
            };
            var ep      = new HealthEndpoint(opts, new DefaultHealthAggregator(), contribs);
            var context = Substitute.For <ISecurityContext>();

            context.GetRequestComponents().Returns(new string[] { "actuator", "health", "readiness" });
            appAvailability.SetAvailabilityState(appAvailability.ReadinessKey, ReadinessState.AcceptingTraffic, null);

            // act
            var result = ep.Invoke(context);

            // assert
            Assert.Equal(HealthStatus.UP, result.Status);
            Assert.True(result.Details.Keys.Count == 1);
            Assert.True(result.Groups.Count() == 2);
        }
コード例 #25
0
        public void Constructor_BindsConfigurationCorrectly()
        {
            var appsettings = new Dictionary <string, string>()
            {
                ["management:endpoints:enabled"]                           = "false",
                ["management:endpoints:path"]                              = "/cloudfoundryapplication",
                ["management:endpoints:health:enabled"]                    = "true",
                ["management:endpoints:health:requiredPermissions"]        = "NONE",
                ["management:endpoints:cloudfoundry:validatecertificates"] = "true",
                ["management:endpoints:cloudfoundry:enabled"]              = "true",
                ["management:endpoints:health:groups:custom:include"]      = "diskSpace",
                ["management:endpoints:health:groups:lIveness:include"]    = "diskSpace",
                ["management:endpoints:health:groups:rEadinEss:include"]   = "diskSpace"
            };
            ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();

            configurationBuilder.AddInMemoryCollection(appsettings);
            var config = configurationBuilder.Build();

            var opts      = new HealthEndpointOptions(config);
            var cloudOpts = new CloudFoundryEndpointOptions(config);

            Assert.True(cloudOpts.Enabled);
            Assert.Equal(string.Empty, cloudOpts.Id);
            Assert.Equal(string.Empty, cloudOpts.Path);
            Assert.True(cloudOpts.ValidateCertificates);

            Assert.True(opts.Enabled);
            Assert.Equal("health", opts.Id);
            Assert.Equal("health", opts.Path);
            Assert.Equal(Permissions.NONE, opts.RequiredPermissions);
            Assert.Equal(3, opts.Groups.Count);
            Assert.True(opts.Groups.ContainsKey("custom"));
            Assert.True(opts.Groups.ContainsKey("liveness"));
            Assert.True(opts.Groups.ContainsKey("READINESS"));
        }
コード例 #26
0
 public static HealthEndpointOptions AddCpuUsageIndicator(this HealthEndpointOptions options)
 {
     Ensure.NotNull(options, nameof(options));
     options.AddIndicator(new CpuUsageIndicator());
     return(options);
 }
コード例 #27
0
 public HealthEndpointController(HealthEndpointOptions options)
 {
     Ensure.NotNull(options, nameof(options));
     _options = options;
 }
        /// <summary>
        /// Register the application with a Spring-Boot-Admin server
        /// </summary>
        /// <param name="builder"><see cref="IApplicationBuilder"/></param>
        /// <param name="configuration">App configuration. Will be retrieved from builder.ApplicationServices if not provided</param>
        /// <param name="httpClient">A customized HttpClient. [Bring your own auth]</param>
        public static void RegisterWithSpringBootAdmin(this IApplicationBuilder builder, IConfiguration configuration = null, HttpClient httpClient = null)
        {
            if (builder is null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            if (configuration is null)
            {
                configuration = builder.ApplicationServices.GetRequiredService <IConfiguration>();
            }

            var logger        = builder.ApplicationServices.GetService <ILogger <SpringBootAdminClientOptions> >();
            var appInfo       = builder.ApplicationServices.GetApplicationInstanceInfo();
            var options       = new SpringBootAdminClientOptions(configuration, appInfo);
            var mgmtOptions   = new ManagementEndpointOptions(configuration);
            var healthOptions = new HealthEndpointOptions(configuration);
            var basePath      = options.BasePath.TrimEnd('/');

            httpClient ??= HttpClientHelper.GetHttpClient(options.ValidateCertificates, ConnectionTimeoutMs);

            var app = new Application()
            {
                Name          = options.ApplicationName ?? "Steeltoe",
                HealthUrl     = new Uri($"{basePath}{mgmtOptions.Path}/{healthOptions.Path}"),
                ManagementUrl = new Uri($"{basePath}{mgmtOptions.Path}"),
                ServiceUrl    = new Uri($"{basePath}/"),
                Metadata      = new Dictionary <string, object> {
                    { "startup", DateTime.Now }
                },
            };

            app.Metadata.Merge(options.Metadata);

            var lifetime = builder.ApplicationServices.GetService <IHostApplicationLifetime>();

            lifetime.ApplicationStarted.Register(() =>
            {
                logger?.LogInformation("Registering with Spring Boot Admin Server at {0}", options.Url);

                var result = httpClient.PostAsJsonAsync($"{options.Url}/instances", app).GetAwaiter().GetResult();
                if (result.IsSuccessStatusCode)
                {
                    RegistrationResult = result.Content.ReadFromJsonAsync <RegistrationResult>().GetAwaiter().GetResult();
                }
                else
                {
                    logger.LogError($"Error registering with SpringBootAdmin {result}");
                }
            });

            lifetime.ApplicationStopped.Register(() =>
            {
                if (RegistrationResult == null || string.IsNullOrEmpty(RegistrationResult.Id))
                {
                    return;
                }

                _ = httpClient.DeleteAsync($"{options.Url}/instances/{RegistrationResult.Id}").GetAwaiter().GetResult();
            });
        }