From 87ceb3912e53548a99761b2ff93b0c1e2803effc Mon Sep 17 00:00:00 2001 From: Alexandros Nikolopoulos Date: Tue, 13 Jan 2026 17:19:36 +0000 Subject: [PATCH 1/9] added step to create the first user --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 44e9b04..0f0f142 100644 --- a/README.md +++ b/README.md @@ -46,4 +46,10 @@ docker-compose up -d docker compose up -d ``` -4. Navigate your browser to http://\:5180 +4. Create an initial user +``` +docker compose exec -it tileworkui dotnet tilework.ui.dll CreateUser +``` +Follow the interactive prompts to set the username, email, and password. + +5. Navigate your browser to http://\:5180 From f358a1a75621c6027b5e78a9b7f863ca9767f757 Mon Sep 17 00:00:00 2001 From: Alexandros Nikolopoulos Date: Thu, 15 Jan 2026 17:19:35 +0000 Subject: [PATCH 2/9] Implemented status of targets --- .../Enums/LoadBalancing/LoadBalancerStatus.cs | 8 +++ .../LoadBalancing/ILoadBalancerService.cs | 3 ++ .../IDataPersistenceConfigurator.cs | 4 +- tilework.core/Mappers/EnumMappers.cs | 25 +++++++++ .../Mappers/LoadBalancingMappingProfile.cs | 5 ++ .../Monitoring/LoadBalancingMonitorData.cs | 4 +- .../Influxdb/Influxdb2DataPersistence.cs | 42 +++++++++++++-- .../Influxdb/Influxdb3DataPersistence.cs | 2 +- tilework.core/Resources/telegraf.conf | 11 +++- .../LoadBalancing/LoadBalancerService.cs | 31 ++++++++++- .../Services/Monitoring/MonitoringService.cs | 4 +- .../LoadBalancing/TargetGroupDetail.razor | 51 ++++++++++++++++++- 12 files changed, 177 insertions(+), 13 deletions(-) create mode 100644 tilework.core/Enums/LoadBalancing/LoadBalancerStatus.cs create mode 100644 tilework.core/Mappers/EnumMappers.cs diff --git a/tilework.core/Enums/LoadBalancing/LoadBalancerStatus.cs b/tilework.core/Enums/LoadBalancing/LoadBalancerStatus.cs new file mode 100644 index 0000000..5d92ce2 --- /dev/null +++ b/tilework.core/Enums/LoadBalancing/LoadBalancerStatus.cs @@ -0,0 +1,8 @@ +namespace Tilework.LoadBalancing.Enums; + +public enum LoadBalancerStatus +{ + UP, + DOWN, + UNKNOWN +} diff --git a/tilework.core/Interfaces/LoadBalancing/ILoadBalancerService.cs b/tilework.core/Interfaces/LoadBalancing/ILoadBalancerService.cs index 0084ddb..608b404 100644 --- a/tilework.core/Interfaces/LoadBalancing/ILoadBalancerService.cs +++ b/tilework.core/Interfaces/LoadBalancing/ILoadBalancerService.cs @@ -1,5 +1,6 @@ using Tilework.LoadBalancing.Models; using Tilework.CertificateManagement.Models; +using Tilework.LoadBalancing.Enums; namespace Tilework.LoadBalancing.Interfaces; @@ -38,9 +39,11 @@ public interface ILoadBalancerService public Task> GetTargets(TargetGroupDTO group); + public Task GetTarget(Guid Id); public Task AddTarget(TargetGroupDTO group, TargetDTO target); public Task UpdateTarget(TargetGroupDTO group, TargetDTO target); public Task RemoveTarget(TargetGroupDTO group, TargetDTO target); + public Task GetTargetHealth(Guid id); public Task> GetLoadBalancerMonitoringData(Guid Id, TimeSpan interval, DateTimeOffset start, DateTimeOffset end); diff --git a/tilework.core/Interfaces/Monitoring/IDataPersistenceConfigurator.cs b/tilework.core/Interfaces/Monitoring/IDataPersistenceConfigurator.cs index 475cbff..0b0e529 100644 --- a/tilework.core/Interfaces/Monitoring/IDataPersistenceConfigurator.cs +++ b/tilework.core/Interfaces/Monitoring/IDataPersistenceConfigurator.cs @@ -5,8 +5,8 @@ namespace Tilework.Monitoring.Interfaces; public interface IDataPersistenceConfigurator { Task GetTarget(MonitoringSource source); - Task> GetData(string module, Dictionary filters, TimeSpan interval, DateTimeOffset start, DateTimeOffset end) where T : BaseMonitorData, new(); + Task> GetData(string module, Dictionary filters, TimeSpan? interval, DateTimeOffset start, DateTimeOffset end) where T : BaseMonitorData, new(); Task ApplyConfiguration(); Task Shutdown(); -} \ No newline at end of file +} diff --git a/tilework.core/Mappers/EnumMappers.cs b/tilework.core/Mappers/EnumMappers.cs new file mode 100644 index 0000000..3d7f3bf --- /dev/null +++ b/tilework.core/Mappers/EnumMappers.cs @@ -0,0 +1,25 @@ +using System; + +using Tilework.LoadBalancing.Enums; + +namespace Tilework.Core.Mappers; + +public static class EnumMappers +{ + public static LoadBalancerStatus FromHaproxyStatus(string? status) + { + if (string.IsNullOrWhiteSpace(status)) + { + return LoadBalancerStatus.DOWN; + } + + var normalized = status.Trim(); + if (normalized.StartsWith("UP", StringComparison.OrdinalIgnoreCase) || + normalized.StartsWith("OPEN", StringComparison.OrdinalIgnoreCase)) + { + return LoadBalancerStatus.UP; + } + + return LoadBalancerStatus.DOWN; + } +} diff --git a/tilework.core/Mappers/LoadBalancingMappingProfile.cs b/tilework.core/Mappers/LoadBalancingMappingProfile.cs index c2e25c5..5aa98e5 100644 --- a/tilework.core/Mappers/LoadBalancingMappingProfile.cs +++ b/tilework.core/Mappers/LoadBalancingMappingProfile.cs @@ -2,6 +2,8 @@ using Tilework.Persistence.LoadBalancing.Models; using Tilework.Core.Models; +using Tilework.Core.Mappers; +using Tilework.LoadBalancing.Enums; using Tilework.LoadBalancing.Models; namespace Tilework.LoadBalancing.Mappers; @@ -10,6 +12,9 @@ public class LoadBalancingMappingProfile : Profile { public LoadBalancingMappingProfile() { + CreateMap() + .ConvertUsing(status => EnumMappers.FromHaproxyStatus(status)); + CreateMap(); CreateMap() .ForMember(dest => dest.Enabled, opt => opt.Ignore()); diff --git a/tilework.core/Models/LoadBalancing/Monitoring/LoadBalancingMonitorData.cs b/tilework.core/Models/LoadBalancing/Monitoring/LoadBalancingMonitorData.cs index a40741a..15a3f30 100644 --- a/tilework.core/Models/LoadBalancing/Monitoring/LoadBalancingMonitorData.cs +++ b/tilework.core/Models/LoadBalancing/Monitoring/LoadBalancingMonitorData.cs @@ -1,10 +1,12 @@ using System.Reflection; using Tilework.Core.Attributes; +using Tilework.LoadBalancing.Enums; namespace Tilework.LoadBalancing.Models; public class LoadBalancingMonitorData : BaseMonitorData { + public LoadBalancerStatus Status { get; set; } public int Sessions { get; set; } public int Requests { get; set; } public int HttpResponses1xx { get; set; } @@ -13,4 +15,4 @@ public class LoadBalancingMonitorData : BaseMonitorData public int HttpResponses4xx { get; set; } public int HttpResponses5xx { get; set; } public int HttpResponsesOther { get; set; } -} \ No newline at end of file +} diff --git a/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs b/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs index eabe49b..1c8e85b 100644 --- a/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs +++ b/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs @@ -191,7 +191,7 @@ private async Task GetOrgId(string orgName) return org[0].Id; } - public async Task> GetData(string module, Dictionary filters, TimeSpan interval, DateTimeOffset start, DateTimeOffset end) where T : BaseMonitorData, new() + public async Task> GetData(string module, Dictionary filters, TimeSpan? interval, DateTimeOffset start, DateTimeOffset end) where T : BaseMonitorData, new() { using var client = new InfluxDBClient(await GetHost(), token: await GetAdminToken()); @@ -215,7 +215,10 @@ private async Task GetOrgId(string orgName) query += $"\n |> filter(fn: (r) => {filtersCombined})"; } - query += $" |> aggregateWindow(every: {ToFluxDuration(interval)}, fn: sum, createEmpty: true)"; + if (interval.HasValue) + { + query += $" |> aggregateWindow(every: {ToFluxDuration(interval.Value)}, fn: sum, createEmpty: true)"; + } @@ -285,7 +288,7 @@ private string ToFluxDuration(TimeSpan ts) } - private static bool TryConvertFieldValue(object value, Type targetType, out object? convertedValue) + private bool TryConvertFieldValue(object value, Type targetType, out object? convertedValue) { var destinationType = Nullable.GetUnderlyingType(targetType) ?? targetType; @@ -295,6 +298,39 @@ private static bool TryConvertFieldValue(object value, Type targetType, out obje return true; } + if (destinationType.IsEnum) + { + if (value is string stringValue) + { + try + { + convertedValue = _mapper.Map(stringValue, stringValue.GetType(), destinationType); + return true; + } + catch + { + if (Enum.TryParse(destinationType, stringValue, ignoreCase: true, out var parsedValue)) + { + convertedValue = parsedValue; + return true; + } + } + } + + if (value is IConvertible) + { + try + { + convertedValue = Enum.ToObject(destinationType, value); + return true; + } + catch + { + // Ignore conversion failures so other fields can still be processed. + } + } + } + if (value is IConvertible) { try diff --git a/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb3DataPersistence.cs b/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb3DataPersistence.cs index 1597b54..fe5203e 100644 --- a/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb3DataPersistence.cs +++ b/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb3DataPersistence.cs @@ -115,7 +115,7 @@ private async Task GetAdminToken() return token; } - public async Task> GetData(string module, Dictionary filters, TimeSpan interval, DateTimeOffset start, DateTimeOffset end) where T : BaseMonitorData, new() + public async Task> GetData(string module, Dictionary filters, TimeSpan? interval, DateTimeOffset start, DateTimeOffset end) where T : BaseMonitorData, new() { // This method has not been maintained and tested. Disable it for now throw new NotImplementedException(); diff --git a/tilework.core/Resources/telegraf.conf b/tilework.core/Resources/telegraf.conf index a1558db..c5046fd 100644 --- a/tilework.core/Resources/telegraf.conf +++ b/tilework.core/Resources/telegraf.conf @@ -7,7 +7,7 @@ namepass = ["haproxy"] source = ''' -TAGS_TO_DROP = ["server", "host", "proxy", "sv"] +TAGS_TO_DROP = ["server", "host", "proxy"] def apply(metric): for k in TAGS_TO_DROP: @@ -22,10 +22,13 @@ def apply(metric): fieldinclude = [ "stot", "req_tot", "http_response.1xx", "http_response.2xx", "http_response.3xx", "http_response.4xx", "http_response.5xx", - "http_response.other" + "http_response.other", "status" ] source = ''' +# Set field names here to bypass delta processing. +BYPASS_FIELDS = ["status"] + state = {} def apply(metric): @@ -39,6 +42,8 @@ def apply(metric): # First time we see this series: zero all numeric fields if last == None: for fname, val in metric.fields.items(): + if fname in BYPASS_FIELDS: + continue t = type(val) if t == "int" or t == "float": metric.fields[fname] = 0 @@ -46,6 +51,8 @@ def apply(metric): # Subsequent points: convert numeric fields to delta for fname, val in metric.fields.items(): + if fname in BYPASS_FIELDS: + continue t = type(val) if not (t == "int" or t == "float"): continue diff --git a/tilework.core/Services/LoadBalancing/LoadBalancerService.cs b/tilework.core/Services/LoadBalancing/LoadBalancerService.cs index 8583138..b0010a5 100644 --- a/tilework.core/Services/LoadBalancing/LoadBalancerService.cs +++ b/tilework.core/Services/LoadBalancing/LoadBalancerService.cs @@ -412,6 +412,12 @@ public async Task> GetTargets(TargetGroupDTO group) return _mapper.Map>(entity.Targets); } + public async Task GetTarget(Guid id) + { + var entity = await _dbContext.Targets.FindAsync(id); + return _mapper.Map(entity); + } + public async Task AddTarget(TargetGroupDTO group, TargetDTO target) { var entity = await _dbContext.TargetGroups.FindAsync(group.Id); @@ -441,6 +447,17 @@ public async Task RemoveTarget(TargetGroupDTO group, TargetDTO target) await _dbContext.SaveChangesAsync(); } + public async Task GetTargetHealth(Guid Id) + { + var now = DateTimeOffset.UtcNow; + var data = await GetTargetMonitoringData(Id, null, now.AddMinutes(-5), now); + var latest = data + .OrderByDescending(entry => entry.Timestamp) + .FirstOrDefault(); + + return latest?.Status ?? LoadBalancerStatus.UNKNOWN; + } + public async Task ApplyConfiguration() { @@ -467,10 +484,22 @@ public async Task> GetLoadBalancerMonitoringData( throw new ArgumentException("Invalid load balancer id"); var filters = new Dictionary(); - filters["instance"] = lb.Id.ToString(); filters["type"] = "frontend"; filters["instance"] = lb.Id.ToString(); return await _monitoringService.GetMonitoringData("LoadBalancing", filters, interval, start, end); } + + public async Task> GetTargetMonitoringData(Guid id, TimeSpan? interval, DateTimeOffset start, DateTimeOffset end) + { + var tg = await GetTarget(id); + if(tg == null) + throw new ArgumentException("Invalid target id"); + + var filters = new Dictionary(); + filters["type"] = "server"; + filters["sv"] = tg.Id.ToString(); + + return await _monitoringService.GetMonitoringData("LoadBalancing", filters, interval, start, end); + } } diff --git a/tilework.core/Services/Monitoring/MonitoringService.cs b/tilework.core/Services/Monitoring/MonitoringService.cs index e026e57..7a467c2 100644 --- a/tilework.core/Services/Monitoring/MonitoringService.cs +++ b/tilework.core/Services/Monitoring/MonitoringService.cs @@ -16,8 +16,8 @@ public MonitoringService(IDataPersistenceConfigurator persistenceConfigurator, _logger = logger; } - public async Task> GetMonitoringData(string module, Dictionary filters, TimeSpan interval, DateTimeOffset start, DateTimeOffset end) where T : BaseMonitorData, new() + public async Task> GetMonitoringData(string module, Dictionary filters, TimeSpan? interval, DateTimeOffset start, DateTimeOffset end) where T : BaseMonitorData, new() { return await _persistenceConfigurator.GetData(module, filters, interval, start, end); } -} \ No newline at end of file +} diff --git a/tilework.ui/Components/Pages/LoadBalancing/TargetGroupDetail.razor b/tilework.ui/Components/Pages/LoadBalancing/TargetGroupDetail.razor index bffcc81..c3ff920 100644 --- a/tilework.ui/Components/Pages/LoadBalancing/TargetGroupDetail.razor +++ b/tilework.ui/Components/Pages/LoadBalancing/TargetGroupDetail.razor @@ -1,3 +1,5 @@ +@using System.Linq +@using Tilework.LoadBalancing.Enums @using Tilework.LoadBalancing.Interfaces @using Tilework.LoadBalancing.Models @@ -27,6 +29,30 @@ + + + + @{ + _targetHealth.TryGetValue(context.Item.Id, out var status); + if (status == LoadBalancerStatus.UP) + { + + Up + } + else if (status == LoadBalancerStatus.DOWN) + { + + Down + } + else + { + + Unknown + } + } + + + @@ -46,6 +72,7 @@ private TargetGroupDTO _item; private List _targets; + private Dictionary _targetHealth = new(); private List _breadcrumbs = new List { @@ -72,6 +99,7 @@ new ActionItem() { Name="Edit", Href=$"/lb/targetgroups/{Id}/edit" }, new ActionItem() { Name="Delete", OnClick=ConfirmDelete } }; + } protected override async Task OnAfterRenderAsync(bool firstRender) @@ -180,6 +208,27 @@ private async Task GetTargets() { if(_item != null) + { _targets = await _loadBalancerService.GetTargets(_item); + await LoadTargetHealth(); + } + } + + private async Task LoadTargetHealth() + { + if (_targets == null || _targets.Count == 0) + { + _targetHealth = new Dictionary(); + return; + } + + var healthTasks = _targets.Select(async target => new + { + target.Id, + Status = await _loadBalancerService.GetTargetHealth(target.Id) + }); + + var results = await Task.WhenAll(healthTasks); + _targetHealth = results.ToDictionary(result => result.Id, result => result.Status); } -} \ No newline at end of file +} From 3e238d5d78e5f96e7369eafcd2880bb5d5d67e1c Mon Sep 17 00:00:00 2001 From: Alexandros Nikolopoulos Date: Thu, 15 Jan 2026 17:47:09 +0000 Subject: [PATCH 3/9] Fixed issue with aggregate data & status --- .../Monitoring/LoadBalancingMonitorData.cs | 1 - .../Monitoring/LoadBalancingStatusData.cs | 10 ++++++++++ .../Influxdb/Influxdb2DataPersistence.cs | 18 +++++++++++++----- .../LoadBalancing/LoadBalancerService.cs | 17 +++++++++++++++-- 4 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 tilework.core/Models/LoadBalancing/Monitoring/LoadBalancingStatusData.cs diff --git a/tilework.core/Models/LoadBalancing/Monitoring/LoadBalancingMonitorData.cs b/tilework.core/Models/LoadBalancing/Monitoring/LoadBalancingMonitorData.cs index 15a3f30..006a0aa 100644 --- a/tilework.core/Models/LoadBalancing/Monitoring/LoadBalancingMonitorData.cs +++ b/tilework.core/Models/LoadBalancing/Monitoring/LoadBalancingMonitorData.cs @@ -6,7 +6,6 @@ namespace Tilework.LoadBalancing.Models; public class LoadBalancingMonitorData : BaseMonitorData { - public LoadBalancerStatus Status { get; set; } public int Sessions { get; set; } public int Requests { get; set; } public int HttpResponses1xx { get; set; } diff --git a/tilework.core/Models/LoadBalancing/Monitoring/LoadBalancingStatusData.cs b/tilework.core/Models/LoadBalancing/Monitoring/LoadBalancingStatusData.cs new file mode 100644 index 0000000..f70168c --- /dev/null +++ b/tilework.core/Models/LoadBalancing/Monitoring/LoadBalancingStatusData.cs @@ -0,0 +1,10 @@ +using System.Reflection; +using Tilework.Core.Attributes; +using Tilework.LoadBalancing.Enums; + +namespace Tilework.LoadBalancing.Models; + +public class LoadBalancingStatusData : BaseMonitorData +{ + public LoadBalancerStatus Status { get; set; } +} diff --git a/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs b/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs index 1c8e85b..62d4f8a 100644 --- a/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs +++ b/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs @@ -202,6 +202,11 @@ private async Task GetOrgId(string orgName) var query = $"from(bucket: \"{module}\")\n |> range(start: {startStr}, stop: {stopStr})"; + var entryProperties = typeof(T) + .GetProperties(BindingFlags.Instance | BindingFlags.Public) + .Where(p => p.CanWrite && p.Name != nameof(BaseMonitorData.Timestamp)) + .ToArray(); + if (filters is { Count: > 0 }) { var filterExpressions = filters.Select(filter => @@ -215,6 +220,14 @@ private async Task GetOrgId(string orgName) query += $"\n |> filter(fn: (r) => {filtersCombined})"; } + if (entryProperties.Length > 0) + { + var fieldFilters = entryProperties + .Select(property => $"r[\"_field\"] == \"{property.Name.ToLowerInvariant().Replace("\"", "\\\"")}\""); + var fieldsCombined = string.Join(" or ", fieldFilters); + query += $"\n |> filter(fn: (r) => {fieldsCombined})"; + } + if (interval.HasValue) { query += $" |> aggregateWindow(every: {ToFluxDuration(interval.Value)}, fn: sum, createEmpty: true)"; @@ -227,11 +240,6 @@ private async Task GetOrgId(string orgName) if (fluxTables is null || fluxTables.Count == 0) return new List(); - var entryProperties = typeof(T) - .GetProperties(BindingFlags.Instance | BindingFlags.Public) - .Where(p => p.CanWrite && p.Name != nameof(BaseMonitorData.Timestamp)) - .ToArray(); - var entryPropertyNames = entryProperties .Select(property => property.Name.ToLower()) .ToHashSet(StringComparer.OrdinalIgnoreCase); diff --git a/tilework.core/Services/LoadBalancing/LoadBalancerService.cs b/tilework.core/Services/LoadBalancing/LoadBalancerService.cs index b0010a5..f54eb5d 100644 --- a/tilework.core/Services/LoadBalancing/LoadBalancerService.cs +++ b/tilework.core/Services/LoadBalancing/LoadBalancerService.cs @@ -450,7 +450,7 @@ public async Task RemoveTarget(TargetGroupDTO group, TargetDTO target) public async Task GetTargetHealth(Guid Id) { var now = DateTimeOffset.UtcNow; - var data = await GetTargetMonitoringData(Id, null, now.AddMinutes(-5), now); + var data = await GetTargetStatusData(Id, now.AddMinutes(-5), now); var latest = data .OrderByDescending(entry => entry.Timestamp) .FirstOrDefault(); @@ -490,7 +490,7 @@ public async Task> GetLoadBalancerMonitoringData( return await _monitoringService.GetMonitoringData("LoadBalancing", filters, interval, start, end); } - public async Task> GetTargetMonitoringData(Guid id, TimeSpan? interval, DateTimeOffset start, DateTimeOffset end) + public async Task> GetTargetMonitoringData(Guid id, TimeSpan interval, DateTimeOffset start, DateTimeOffset end) { var tg = await GetTarget(id); if(tg == null) @@ -502,4 +502,17 @@ public async Task> GetTargetMonitoringData(Guid i return await _monitoringService.GetMonitoringData("LoadBalancing", filters, interval, start, end); } + + public async Task> GetTargetStatusData(Guid id, DateTimeOffset start, DateTimeOffset end) + { + var tg = await GetTarget(id); + if(tg == null) + throw new ArgumentException("Invalid target id"); + + var filters = new Dictionary(); + filters["type"] = "server"; + filters["sv"] = tg.Id.ToString(); + + return await _monitoringService.GetMonitoringData("LoadBalancing", filters, null, start, end); + } } From 33dd17035db2cb9b1364cca6eb5b460ac3228492 Mon Sep 17 00:00:00 2001 From: Alexandros Nikolopoulos Date: Sat, 17 Jan 2026 11:54:58 +0200 Subject: [PATCH 4/9] Refactored load balancing. Added TCP based rules (#59) * Complete refactor of load balancing to unify types. * Handle protocol per type * Removed protocol and type from lb editing * Added filtering and verification of rule condition types * Add support for SNI --------- Co-authored-by: Alexandros Nikolopoulos --- .../Enums/LoadBalancing/AlbProtocol.cs | 11 - .../Enums/LoadBalancing/ConditionType.cs | 4 +- .../LoadBalancerConditionRules.cs | 43 ++ ...NlbProtocol.cs => LoadBalancerProtocol.cs} | 6 +- .../LoadBalancerProtocolRules.cs | 30 ++ .../LoadBalancing/ILoadBalancerService.cs | 24 +- .../ILoadBalancingConfigurator.cs | 6 +- .../Mappers/LoadBalancingMappingProfile.cs | 12 +- .../20251118184947_Initial.Designer.cs | 378 ----------------- .../20251127185719_TokenVault.Designer.cs | 400 ------------------ .../Migrations/20251127185719_TokenVault.cs | 41 -- .../Migrations/20260111192232_Identity.cs | 225 ---------- ....cs => 20260117084701_Initial.Designer.cs} | 71 +--- ...7_Initial.cs => 20260117084701_Initial.cs} | 303 +++++++++++-- .../TileworkContextModelSnapshot.cs | 67 +-- .../ApplicationLoadBalancerDTO.cs | 9 - ...eLoadBalancerDTO.cs => LoadBalancerDTO.cs} | 6 +- .../LoadBalancing/NetworkLoadBalancerDTO.cs | 10 - tilework.core/Persistence/DbContext.cs | 8 +- .../LoadBalancing/ApplicationLoadBalancer.cs | 10 - .../{BaseLoadBalancer.cs => LoadBalancer.cs} | 7 +- .../LoadBalancing/NetworkLoadBalancer.cs | 11 - .../Entities/LoadBalancing/Rule.cs | 2 +- .../HAProxy/HAProxyConfigurator.cs | 33 +- .../HAProxy/HAProxymonitor.cs | 2 +- .../Mappers/HAProxyConfigurationProfile.cs | 79 ++-- .../HAProxy/Models/Config/Statements/Acl.cs | 3 +- .../AcmeVerificationService.cs | 24 +- .../LoadBalancing/LoadBalancerService.cs | 181 ++++---- .../Components/Dialogs/RuleDialog.razor | 13 +- .../LoadBalancing/LoadBalancerDetail.razor | 208 +++++---- .../LoadBalancing/LoadBalancerEdit.razor | 58 +-- .../LoadBalancing/LoadBalancerList.razor | 9 +- .../Pages/LoadBalancing/LoadBalancerNew.razor | 76 ++-- .../LoadBalancing/TargetGroupDetail.razor | 20 +- tilework.ui/Mappers/FormMappingProfile.cs | 19 +- .../LoadBalancing/EditLoadBalancerForm.cs | 28 +- .../LoadBalancing/NewLoadBalancerForm.cs | 26 +- 38 files changed, 711 insertions(+), 1752 deletions(-) delete mode 100644 tilework.core/Enums/LoadBalancing/AlbProtocol.cs create mode 100644 tilework.core/Enums/LoadBalancing/LoadBalancerConditionRules.cs rename tilework.core/Enums/LoadBalancing/{NlbProtocol.cs => LoadBalancerProtocol.cs} (66%) create mode 100644 tilework.core/Enums/LoadBalancing/LoadBalancerProtocolRules.cs delete mode 100644 tilework.core/Migrations/20251118184947_Initial.Designer.cs delete mode 100644 tilework.core/Migrations/20251127185719_TokenVault.Designer.cs delete mode 100644 tilework.core/Migrations/20251127185719_TokenVault.cs delete mode 100644 tilework.core/Migrations/20260111192232_Identity.cs rename tilework.core/Migrations/{20260111192232_Identity.Designer.cs => 20260117084701_Initial.Designer.cs} (91%) rename tilework.core/Migrations/{20251118184947_Initial.cs => 20260117084701_Initial.cs} (51%) delete mode 100644 tilework.core/Models/LoadBalancing/ApplicationLoadBalancerDTO.cs rename tilework.core/Models/LoadBalancing/{BaseLoadBalancerDTO.cs => LoadBalancerDTO.cs} (55%) delete mode 100644 tilework.core/Models/LoadBalancing/NetworkLoadBalancerDTO.cs delete mode 100644 tilework.core/Persistence/Entities/LoadBalancing/ApplicationLoadBalancer.cs rename tilework.core/Persistence/Entities/LoadBalancing/{BaseLoadBalancer.cs => LoadBalancer.cs} (68%) delete mode 100644 tilework.core/Persistence/Entities/LoadBalancing/NetworkLoadBalancer.cs diff --git a/tilework.core/Enums/LoadBalancing/AlbProtocol.cs b/tilework.core/Enums/LoadBalancing/AlbProtocol.cs deleted file mode 100644 index 4bc9754..0000000 --- a/tilework.core/Enums/LoadBalancing/AlbProtocol.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.ComponentModel; - -namespace Tilework.LoadBalancing.Enums; - -public enum AlbProtocol -{ - [Description("HTTP")] - HTTP, - [Description("HTTPS")] - HTTPS -} \ No newline at end of file diff --git a/tilework.core/Enums/LoadBalancing/ConditionType.cs b/tilework.core/Enums/LoadBalancing/ConditionType.cs index 4d4d876..48996f2 100644 --- a/tilework.core/Enums/LoadBalancing/ConditionType.cs +++ b/tilework.core/Enums/LoadBalancing/ConditionType.cs @@ -9,6 +9,8 @@ public enum ConditionType [Description("Path")] Path, [Description("Query string")] - QueryString + QueryString, + [Description("SNI FQDN")] + SNI } diff --git a/tilework.core/Enums/LoadBalancing/LoadBalancerConditionRules.cs b/tilework.core/Enums/LoadBalancing/LoadBalancerConditionRules.cs new file mode 100644 index 0000000..84408db --- /dev/null +++ b/tilework.core/Enums/LoadBalancing/LoadBalancerConditionRules.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; + +namespace Tilework.LoadBalancing.Enums; + +public static class LoadBalancerConditionRules +{ + private static readonly ConditionType[] HttpConditions = + { + ConditionType.HostHeader, + ConditionType.Path, + ConditionType.QueryString + }; + + private static readonly ConditionType[] HttpsConditions = + { + ConditionType.HostHeader, + ConditionType.Path, + ConditionType.QueryString, + ConditionType.SNI + }; + + private static readonly ConditionType[] TlsConditions = + { + ConditionType.SNI + }; + + public static IReadOnlyList GetAllowedConditions(LoadBalancerProtocol protocol) + { + return protocol switch + { + LoadBalancerProtocol.HTTP => HttpConditions, + LoadBalancerProtocol.HTTPS => HttpsConditions, + LoadBalancerProtocol.TLS => TlsConditions, + _ => Array.Empty() + }; + } + + public static bool IsAllowed(LoadBalancerProtocol protocol, ConditionType condition) + { + return GetAllowedConditions(protocol).Contains(condition); + } +} diff --git a/tilework.core/Enums/LoadBalancing/NlbProtocol.cs b/tilework.core/Enums/LoadBalancing/LoadBalancerProtocol.cs similarity index 66% rename from tilework.core/Enums/LoadBalancing/NlbProtocol.cs rename to tilework.core/Enums/LoadBalancing/LoadBalancerProtocol.cs index 52bfc8a..e420660 100644 --- a/tilework.core/Enums/LoadBalancing/NlbProtocol.cs +++ b/tilework.core/Enums/LoadBalancing/LoadBalancerProtocol.cs @@ -2,8 +2,12 @@ namespace Tilework.LoadBalancing.Enums; -public enum NlbProtocol +public enum LoadBalancerProtocol { + [Description("HTTP")] + HTTP, + [Description("HTTPS")] + HTTPS, [Description("TCP")] TCP, [Description("UDP")] diff --git a/tilework.core/Enums/LoadBalancing/LoadBalancerProtocolRules.cs b/tilework.core/Enums/LoadBalancing/LoadBalancerProtocolRules.cs new file mode 100644 index 0000000..1bb6775 --- /dev/null +++ b/tilework.core/Enums/LoadBalancing/LoadBalancerProtocolRules.cs @@ -0,0 +1,30 @@ +using System.Linq; + +namespace Tilework.LoadBalancing.Enums; + +public static class LoadBalancerProtocolRules +{ + private static readonly LoadBalancerProtocol[] ApplicationProtocols = + { + LoadBalancerProtocol.HTTP, + LoadBalancerProtocol.HTTPS + }; + + private static readonly LoadBalancerProtocol[] NetworkProtocols = + { + LoadBalancerProtocol.TCP, + LoadBalancerProtocol.UDP, + LoadBalancerProtocol.TCP_UDP, + LoadBalancerProtocol.TLS + }; + + public static IReadOnlyList GetAllowedProtocols(LoadBalancerType type) + { + return type == LoadBalancerType.NETWORK ? NetworkProtocols : ApplicationProtocols; + } + + public static bool IsAllowed(LoadBalancerType type, LoadBalancerProtocol protocol) + { + return GetAllowedProtocols(type).Contains(protocol); + } +} diff --git a/tilework.core/Interfaces/LoadBalancing/ILoadBalancerService.cs b/tilework.core/Interfaces/LoadBalancing/ILoadBalancerService.cs index 608b404..b9970d9 100644 --- a/tilework.core/Interfaces/LoadBalancing/ILoadBalancerService.cs +++ b/tilework.core/Interfaces/LoadBalancing/ILoadBalancerService.cs @@ -6,10 +6,10 @@ namespace Tilework.LoadBalancing.Interfaces; public interface ILoadBalancerService { - public Task> GetLoadBalancers(); - public Task GetLoadBalancer(Guid Id); - public Task AddLoadBalancer(BaseLoadBalancerDTO balancer); - public Task UpdateLoadBalancer(BaseLoadBalancerDTO balancer); + public Task> GetLoadBalancers(); + public Task GetLoadBalancer(Guid Id); + public Task AddLoadBalancer(LoadBalancerDTO balancer); + public Task UpdateLoadBalancer(LoadBalancerDTO balancer); public Task DeleteLoadBalancer(Guid Id); public Task EnableLoadBalancer(Guid Id); @@ -17,20 +17,18 @@ public interface ILoadBalancerService - public Task> GetRules(ApplicationLoadBalancerDTO balancer); - public Task AddRule(ApplicationLoadBalancerDTO balancer, RuleDTO rule); - public Task UpdateRule(ApplicationLoadBalancerDTO balancer, RuleDTO rule); - public Task RemoveRule(ApplicationLoadBalancerDTO balancer, RuleDTO rule); + public Task> GetRules(LoadBalancerDTO balancer); + public Task AddRule(LoadBalancerDTO balancer, RuleDTO rule); + public Task UpdateRule(LoadBalancerDTO balancer, RuleDTO rule); + public Task RemoveRule(LoadBalancerDTO balancer, RuleDTO rule); - public Task> GetCertificates(BaseLoadBalancerDTO balancer); - public Task AddCertificate(BaseLoadBalancerDTO balancer, Guid certificateId); - public Task RemoveCertificate(BaseLoadBalancerDTO balancer, Guid certificateId); + public Task> GetCertificates(LoadBalancerDTO balancer); + public Task AddCertificate(LoadBalancerDTO balancer, Guid certificateId); + public Task RemoveCertificate(LoadBalancerDTO balancer, Guid certificateId); public Task> GetTargetGroups(); - public Task> GetNlbTargetGroups(); - public Task> GetAlbTargetGroups(); public Task GetTargetGroup(Guid Id); public Task AddTargetGroup(TargetGroupDTO group); diff --git a/tilework.core/Interfaces/LoadBalancing/ILoadBalancingConfigurator.cs b/tilework.core/Interfaces/LoadBalancing/ILoadBalancingConfigurator.cs index 52f5837..e1d4b59 100644 --- a/tilework.core/Interfaces/LoadBalancing/ILoadBalancingConfigurator.cs +++ b/tilework.core/Interfaces/LoadBalancing/ILoadBalancingConfigurator.cs @@ -4,8 +4,8 @@ namespace Tilework.LoadBalancing.Interfaces; public interface ILoadBalancingConfigurator { - List LoadConfiguration(); - Task ApplyConfiguration(List loadBalancers); - Task ApplyConfiguration(BaseLoadBalancer loadBalancer); + List LoadConfiguration(); + Task ApplyConfiguration(List loadBalancers); + Task ApplyConfiguration(LoadBalancer loadBalancer); Task Shutdown(); } \ No newline at end of file diff --git a/tilework.core/Mappers/LoadBalancingMappingProfile.cs b/tilework.core/Mappers/LoadBalancingMappingProfile.cs index 5aa98e5..910eeba 100644 --- a/tilework.core/Mappers/LoadBalancingMappingProfile.cs +++ b/tilework.core/Mappers/LoadBalancingMappingProfile.cs @@ -15,18 +15,10 @@ public LoadBalancingMappingProfile() CreateMap() .ConvertUsing(status => EnumMappers.FromHaproxyStatus(status)); - CreateMap(); - CreateMap() + CreateMap(); + CreateMap() .ForMember(dest => dest.Enabled, opt => opt.Ignore()); - - CreateMap() - .ForMember(dest => dest.TargetGroup, opt => opt.MapFrom(src => src.TargetGroupId)); - CreateMap() - .ForMember(dest => dest.TargetGroupId, opt => opt.MapFrom(src => src.TargetGroup)) - .ForMember(dest => dest.TargetGroup, opt => opt.Ignore()) - .ForMember(dest => dest.Enabled, opt => opt.Ignore()); - CreateMap(); CreateMap(); diff --git a/tilework.core/Migrations/20251118184947_Initial.Designer.cs b/tilework.core/Migrations/20251118184947_Initial.Designer.cs deleted file mode 100644 index 16ec862..0000000 --- a/tilework.core/Migrations/20251118184947_Initial.Designer.cs +++ /dev/null @@ -1,378 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Tilework.Core.Persistence; - -#nullable disable - -namespace tilework.core.Migrations -{ - [DbContext(typeof(TileworkContext))] - [Migration("20251118184947_Initial")] - partial class Initial - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.8") - .HasAnnotation("Proxies:ChangeTracking", false) - .HasAnnotation("Proxies:CheckEquality", false) - .HasAnnotation("Proxies:LazyLoading", true); - - modelBuilder.Entity("LoadBalancerCertificates", b => - { - b.Property("BalancerId") - .HasColumnType("TEXT"); - - b.Property("CertificateId") - .HasColumnType("TEXT"); - - b.HasKey("BalancerId", "CertificateId"); - - b.HasIndex("CertificateId"); - - b.ToTable("LoadBalancerCertificates"); - }); - - modelBuilder.Entity("Tilework.Persistence.CertificateManagement.Models.Certificate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("AuthorityId") - .HasColumnType("TEXT"); - - b.Property("CertificateDataString") - .HasColumnType("TEXT"); - - b.Property("ExpiresAtUtc") - .HasColumnType("INTEGER"); - - b.Property("Fqdn") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PrivateKeyId") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorityId"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("PrivateKeyId"); - - b.ToTable("Certificates"); - }); - - modelBuilder.Entity("Tilework.Persistence.CertificateManagement.Models.CertificateAuthority", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Parameters") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("CertificateAuthorities"); - }); - - modelBuilder.Entity("Tilework.Persistence.CertificateManagement.Models.PrivateKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Algorithm") - .HasColumnType("INTEGER"); - - b.Property("KeyDataString") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("PrivateKeys"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(34) - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Port") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("LoadBalancers"); - - b.HasDiscriminator().HasValue("BaseLoadBalancer"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.Rule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("LoadBalancerId") - .HasColumnType("TEXT"); - - b.Property("Priority") - .HasColumnType("INTEGER"); - - b.Property("TargetGroupId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("LoadBalancerId"); - - b.HasIndex("TargetGroupId"); - - b.HasIndex("Priority", "LoadBalancerId") - .IsUnique(); - - b.ToTable("Rules"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.Target", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Host") - .IsRequired() - .HasMaxLength(253) - .HasColumnType("TEXT"); - - b.Property("Port") - .HasColumnType("INTEGER"); - - b.Property("TargetGroupId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("TargetGroupId", "Host", "Port") - .IsUnique(); - - b.ToTable("Targets"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.TargetGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Protocol") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("TargetGroups"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.ApplicationLoadBalancer", b => - { - b.HasBaseType("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer"); - - b.Property("Protocol") - .HasColumnType("INTEGER"); - - b.HasDiscriminator().HasValue("ApplicationLoadBalancer"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.NetworkLoadBalancer", b => - { - b.HasBaseType("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer"); - - b.Property("Protocol") - .HasColumnType("INTEGER"); - - b.Property("TargetGroupId") - .HasColumnType("TEXT"); - - b.HasIndex("TargetGroupId"); - - b.ToTable("LoadBalancers", t => - { - t.Property("Protocol") - .HasColumnName("NetworkLoadBalancer_Protocol"); - }); - - b.HasDiscriminator().HasValue("NetworkLoadBalancer"); - }); - - modelBuilder.Entity("LoadBalancerCertificates", b => - { - b.HasOne("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer", null) - .WithMany() - .HasForeignKey("BalancerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Tilework.Persistence.CertificateManagement.Models.Certificate", null) - .WithMany() - .HasForeignKey("CertificateId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - }); - - modelBuilder.Entity("Tilework.Persistence.CertificateManagement.Models.Certificate", b => - { - b.HasOne("Tilework.Persistence.CertificateManagement.Models.CertificateAuthority", "Authority") - .WithMany() - .HasForeignKey("AuthorityId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Tilework.Persistence.CertificateManagement.Models.PrivateKey", "PrivateKey") - .WithMany() - .HasForeignKey("PrivateKeyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Authority"); - - b.Navigation("PrivateKey"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.Rule", b => - { - b.HasOne("Tilework.Persistence.LoadBalancing.Models.ApplicationLoadBalancer", "LoadBalancer") - .WithMany("Rules") - .HasForeignKey("LoadBalancerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Tilework.Persistence.LoadBalancing.Models.TargetGroup", "TargetGroup") - .WithMany() - .HasForeignKey("TargetGroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.OwnsMany("Tilework.LoadBalancing.Models.Condition", "Conditions", b1 => - { - b1.Property("RuleId") - .HasColumnType("TEXT"); - - b1.Property("__synthesizedOrdinal") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("INTEGER"); - - b1.Property("Type") - .HasColumnType("INTEGER"); - - b1.PrimitiveCollection("Values") - .IsRequired() - .HasColumnType("TEXT"); - - b1.HasKey("RuleId", "__synthesizedOrdinal"); - - b1.ToTable("Rules"); - - b1.ToJson("Conditions"); - - b1.WithOwner() - .HasForeignKey("RuleId"); - }); - - b.Navigation("Conditions"); - - b.Navigation("LoadBalancer"); - - b.Navigation("TargetGroup"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.Target", b => - { - b.HasOne("Tilework.Persistence.LoadBalancing.Models.TargetGroup", "TargetGroup") - .WithMany("Targets") - .HasForeignKey("TargetGroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetGroup"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.NetworkLoadBalancer", b => - { - b.HasOne("Tilework.Persistence.LoadBalancing.Models.TargetGroup", "TargetGroup") - .WithMany() - .HasForeignKey("TargetGroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetGroup"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.TargetGroup", b => - { - b.Navigation("Targets"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.ApplicationLoadBalancer", b => - { - b.Navigation("Rules"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/tilework.core/Migrations/20251127185719_TokenVault.Designer.cs b/tilework.core/Migrations/20251127185719_TokenVault.Designer.cs deleted file mode 100644 index 6b728d6..0000000 --- a/tilework.core/Migrations/20251127185719_TokenVault.Designer.cs +++ /dev/null @@ -1,400 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Tilework.Core.Persistence; - -#nullable disable - -namespace tilework.core.Migrations -{ - [DbContext(typeof(TileworkContext))] - [Migration("20251127185719_TokenVault")] - partial class TokenVault - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.8") - .HasAnnotation("Proxies:ChangeTracking", false) - .HasAnnotation("Proxies:CheckEquality", false) - .HasAnnotation("Proxies:LazyLoading", true); - - modelBuilder.Entity("LoadBalancerCertificates", b => - { - b.Property("BalancerId") - .HasColumnType("TEXT"); - - b.Property("CertificateId") - .HasColumnType("TEXT"); - - b.HasKey("BalancerId", "CertificateId"); - - b.HasIndex("CertificateId"); - - b.ToTable("LoadBalancerCertificates"); - }); - - modelBuilder.Entity("Tilework.Persistence.CertificateManagement.Models.Certificate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("AuthorityId") - .HasColumnType("TEXT"); - - b.Property("CertificateDataString") - .HasColumnType("TEXT"); - - b.Property("ExpiresAtUtc") - .HasColumnType("INTEGER"); - - b.Property("Fqdn") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PrivateKeyId") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorityId"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("PrivateKeyId"); - - b.ToTable("Certificates"); - }); - - modelBuilder.Entity("Tilework.Persistence.CertificateManagement.Models.CertificateAuthority", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Parameters") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("CertificateAuthorities"); - }); - - modelBuilder.Entity("Tilework.Persistence.CertificateManagement.Models.PrivateKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Algorithm") - .HasColumnType("INTEGER"); - - b.Property("KeyDataString") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("PrivateKeys"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(34) - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Port") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("LoadBalancers"); - - b.HasDiscriminator().HasValue("BaseLoadBalancer"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.Rule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("LoadBalancerId") - .HasColumnType("TEXT"); - - b.Property("Priority") - .HasColumnType("INTEGER"); - - b.Property("TargetGroupId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("LoadBalancerId"); - - b.HasIndex("TargetGroupId"); - - b.HasIndex("Priority", "LoadBalancerId") - .IsUnique(); - - b.ToTable("Rules"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.Target", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Host") - .IsRequired() - .HasMaxLength(253) - .HasColumnType("TEXT"); - - b.Property("Port") - .HasColumnType("INTEGER"); - - b.Property("TargetGroupId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("TargetGroupId", "Host", "Port") - .IsUnique(); - - b.ToTable("Targets"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.TargetGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Protocol") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("TargetGroups"); - }); - - modelBuilder.Entity("Tilework.Persistence.TokenVault.Models.Token", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Key") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Value") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Key") - .IsUnique(); - - b.ToTable("Tokens"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.ApplicationLoadBalancer", b => - { - b.HasBaseType("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer"); - - b.Property("Protocol") - .HasColumnType("INTEGER"); - - b.HasDiscriminator().HasValue("ApplicationLoadBalancer"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.NetworkLoadBalancer", b => - { - b.HasBaseType("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer"); - - b.Property("Protocol") - .HasColumnType("INTEGER"); - - b.Property("TargetGroupId") - .HasColumnType("TEXT"); - - b.HasIndex("TargetGroupId"); - - b.ToTable("LoadBalancers", t => - { - t.Property("Protocol") - .HasColumnName("NetworkLoadBalancer_Protocol"); - }); - - b.HasDiscriminator().HasValue("NetworkLoadBalancer"); - }); - - modelBuilder.Entity("LoadBalancerCertificates", b => - { - b.HasOne("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer", null) - .WithMany() - .HasForeignKey("BalancerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Tilework.Persistence.CertificateManagement.Models.Certificate", null) - .WithMany() - .HasForeignKey("CertificateId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - }); - - modelBuilder.Entity("Tilework.Persistence.CertificateManagement.Models.Certificate", b => - { - b.HasOne("Tilework.Persistence.CertificateManagement.Models.CertificateAuthority", "Authority") - .WithMany() - .HasForeignKey("AuthorityId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Tilework.Persistence.CertificateManagement.Models.PrivateKey", "PrivateKey") - .WithMany() - .HasForeignKey("PrivateKeyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Authority"); - - b.Navigation("PrivateKey"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.Rule", b => - { - b.HasOne("Tilework.Persistence.LoadBalancing.Models.ApplicationLoadBalancer", "LoadBalancer") - .WithMany("Rules") - .HasForeignKey("LoadBalancerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Tilework.Persistence.LoadBalancing.Models.TargetGroup", "TargetGroup") - .WithMany() - .HasForeignKey("TargetGroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.OwnsMany("Tilework.LoadBalancing.Models.Condition", "Conditions", b1 => - { - b1.Property("RuleId") - .HasColumnType("TEXT"); - - b1.Property("__synthesizedOrdinal") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("INTEGER"); - - b1.Property("Type") - .HasColumnType("INTEGER"); - - b1.PrimitiveCollection("Values") - .IsRequired() - .HasColumnType("TEXT"); - - b1.HasKey("RuleId", "__synthesizedOrdinal"); - - b1.ToTable("Rules"); - - b1.ToJson("Conditions"); - - b1.WithOwner() - .HasForeignKey("RuleId"); - }); - - b.Navigation("Conditions"); - - b.Navigation("LoadBalancer"); - - b.Navigation("TargetGroup"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.Target", b => - { - b.HasOne("Tilework.Persistence.LoadBalancing.Models.TargetGroup", "TargetGroup") - .WithMany("Targets") - .HasForeignKey("TargetGroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetGroup"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.NetworkLoadBalancer", b => - { - b.HasOne("Tilework.Persistence.LoadBalancing.Models.TargetGroup", "TargetGroup") - .WithMany() - .HasForeignKey("TargetGroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetGroup"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.TargetGroup", b => - { - b.Navigation("Targets"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.ApplicationLoadBalancer", b => - { - b.Navigation("Rules"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/tilework.core/Migrations/20251127185719_TokenVault.cs b/tilework.core/Migrations/20251127185719_TokenVault.cs deleted file mode 100644 index 06e9bf8..0000000 --- a/tilework.core/Migrations/20251127185719_TokenVault.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace tilework.core.Migrations -{ - /// - public partial class TokenVault : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Tokens", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Key = table.Column(type: "TEXT", nullable: false), - Value = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Tokens", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_Tokens_Key", - table: "Tokens", - column: "Key", - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Tokens"); - } - } -} diff --git a/tilework.core/Migrations/20260111192232_Identity.cs b/tilework.core/Migrations/20260111192232_Identity.cs deleted file mode 100644 index 60b6a4d..0000000 --- a/tilework.core/Migrations/20260111192232_Identity.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace tilework.core.Migrations -{ - /// - public partial class Identity : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Active = table.Column(type: "INTEGER", nullable: false), - CreatedAtUtc = table.Column(type: "TEXT", nullable: false), - LastLoginAtUtc = table.Column(type: "TEXT", nullable: true), - UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), - Email = table.Column(type: "TEXT", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "TEXT", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "INTEGER", nullable: false), - PasswordHash = table.Column(type: "TEXT", nullable: true), - SecurityStamp = table.Column(type: "TEXT", nullable: true), - ConcurrencyStamp = table.Column(type: "TEXT", nullable: true), - PhoneNumber = table.Column(type: "TEXT", nullable: true), - PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false), - TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false), - LockoutEnd = table.Column(type: "TEXT", nullable: true), - LockoutEnabled = table.Column(type: "INTEGER", nullable: false), - AccessFailedCount = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - RoleId = table.Column(type: "TEXT", nullable: false), - ClaimType = table.Column(type: "TEXT", nullable: true), - ClaimValue = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - UserId = table.Column(type: "TEXT", nullable: false), - ClaimType = table.Column(type: "TEXT", nullable: true), - ClaimValue = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "TEXT", nullable: false), - ProviderKey = table.Column(type: "TEXT", nullable: false), - ProviderDisplayName = table.Column(type: "TEXT", nullable: true), - UserId = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "TEXT", nullable: false), - RoleId = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "TEXT", nullable: false), - LoginProvider = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: false), - Value = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/tilework.core/Migrations/20260111192232_Identity.Designer.cs b/tilework.core/Migrations/20260117084701_Initial.Designer.cs similarity index 91% rename from tilework.core/Migrations/20260111192232_Identity.Designer.cs rename to tilework.core/Migrations/20260117084701_Initial.Designer.cs index df520bb..7538764 100644 --- a/tilework.core/Migrations/20260111192232_Identity.Designer.cs +++ b/tilework.core/Migrations/20260117084701_Initial.Designer.cs @@ -11,8 +11,8 @@ namespace tilework.core.Migrations { [DbContext(typeof(TileworkContext))] - [Migration("20260111192232_Identity")] - partial class Identity + [Migration("20260117084701_Initial")] + partial class Initial { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -324,17 +324,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("AspNetUsers", (string)null); }); - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer", b => + modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.LoadBalancer", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("TEXT"); - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(34) - .HasColumnType("TEXT"); - b.Property("Enabled") .HasColumnType("INTEGER"); @@ -345,16 +340,18 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Port") .HasColumnType("INTEGER"); + b.Property("Protocol") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + b.HasKey("Id"); b.HasIndex("Name") .IsUnique(); b.ToTable("LoadBalancers"); - - b.HasDiscriminator().HasValue("BaseLoadBalancer"); - - b.UseTphMappingStrategy(); }); modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.Rule", b => @@ -452,40 +449,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Tokens"); }); - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.ApplicationLoadBalancer", b => - { - b.HasBaseType("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer"); - - b.Property("Protocol") - .HasColumnType("INTEGER"); - - b.HasDiscriminator().HasValue("ApplicationLoadBalancer"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.NetworkLoadBalancer", b => - { - b.HasBaseType("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer"); - - b.Property("Protocol") - .HasColumnType("INTEGER"); - - b.Property("TargetGroupId") - .HasColumnType("TEXT"); - - b.HasIndex("TargetGroupId"); - - b.ToTable("LoadBalancers", t => - { - t.Property("Protocol") - .HasColumnName("NetworkLoadBalancer_Protocol"); - }); - - b.HasDiscriminator().HasValue("NetworkLoadBalancer"); - }); - modelBuilder.Entity("LoadBalancerCertificates", b => { - b.HasOne("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer", null) + b.HasOne("Tilework.Persistence.LoadBalancing.Models.LoadBalancer", null) .WithMany() .HasForeignKey("BalancerId") .OnDelete(DeleteBehavior.Cascade) @@ -570,7 +536,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.Rule", b => { - b.HasOne("Tilework.Persistence.LoadBalancing.Models.ApplicationLoadBalancer", "LoadBalancer") + b.HasOne("Tilework.Persistence.LoadBalancing.Models.LoadBalancer", "LoadBalancer") .WithMany("Rules") .HasForeignKey("LoadBalancerId") .OnDelete(DeleteBehavior.Cascade) @@ -622,26 +588,15 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("TargetGroup"); }); - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.NetworkLoadBalancer", b => + modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.LoadBalancer", b => { - b.HasOne("Tilework.Persistence.LoadBalancing.Models.TargetGroup", "TargetGroup") - .WithMany() - .HasForeignKey("TargetGroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetGroup"); + b.Navigation("Rules"); }); modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.TargetGroup", b => { b.Navigation("Targets"); }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.ApplicationLoadBalancer", b => - { - b.Navigation("Rules"); - }); #pragma warning restore 612, 618 } } diff --git a/tilework.core/Migrations/20251118184947_Initial.cs b/tilework.core/Migrations/20260117084701_Initial.cs similarity index 51% rename from tilework.core/Migrations/20251118184947_Initial.cs rename to tilework.core/Migrations/20260117084701_Initial.cs index e564ac6..1ea2d28 100644 --- a/tilework.core/Migrations/20251118184947_Initial.cs +++ b/tilework.core/Migrations/20260117084701_Initial.cs @@ -11,6 +11,48 @@ public partial class Initial : Migration /// protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Active = table.Column(type: "INTEGER", nullable: false), + CreatedAtUtc = table.Column(type: "TEXT", nullable: false), + LastLoginAtUtc = table.Column(type: "TEXT", nullable: true), + UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "TEXT", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "INTEGER", nullable: false), + PasswordHash = table.Column(type: "TEXT", nullable: true), + SecurityStamp = table.Column(type: "TEXT", nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true), + PhoneNumber = table.Column(type: "TEXT", nullable: true), + PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false), + TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false), + LockoutEnd = table.Column(type: "TEXT", nullable: true), + LockoutEnabled = table.Column(type: "INTEGER", nullable: false), + AccessFailedCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + migrationBuilder.CreateTable( name: "CertificateAuthorities", columns: table => new @@ -25,6 +67,22 @@ protected override void Up(MigrationBuilder migrationBuilder) table.PrimaryKey("PK_CertificateAuthorities", x => x.Id); }); + migrationBuilder.CreateTable( + name: "LoadBalancers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Port = table.Column(type: "INTEGER", nullable: false), + Type = table.Column(type: "INTEGER", nullable: false), + Protocol = table.Column(type: "INTEGER", nullable: false), + Enabled = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LoadBalancers", x => x.Id); + }); + migrationBuilder.CreateTable( name: "PrivateKeys", columns: table => new @@ -51,6 +109,125 @@ protected override void Up(MigrationBuilder migrationBuilder) table.PrimaryKey("PK_TargetGroups", x => x.Id); }); + migrationBuilder.CreateTable( + name: "Tokens", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Key = table.Column(type: "TEXT", nullable: false), + Value = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Tokens", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoleId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "TEXT", nullable: false), + ProviderKey = table.Column(type: "TEXT", nullable: false), + ProviderDisplayName = table.Column(type: "TEXT", nullable: true), + UserId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + LoginProvider = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Value = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "Certificates", columns: table => new @@ -82,23 +259,26 @@ protected override void Up(MigrationBuilder migrationBuilder) }); migrationBuilder.CreateTable( - name: "LoadBalancers", + name: "Rules", columns: table => new { Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: false), - Port = table.Column(type: "INTEGER", nullable: false), - Enabled = table.Column(type: "INTEGER", nullable: false), - Discriminator = table.Column(type: "TEXT", maxLength: 34, nullable: false), - Protocol = table.Column(type: "INTEGER", nullable: true), - NetworkLoadBalancer_Protocol = table.Column(type: "INTEGER", nullable: true), - TargetGroupId = table.Column(type: "TEXT", nullable: true) + Priority = table.Column(type: "INTEGER", nullable: false), + TargetGroupId = table.Column(type: "TEXT", nullable: false), + LoadBalancerId = table.Column(type: "TEXT", nullable: false), + Conditions = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_LoadBalancers", x => x.Id); + table.PrimaryKey("PK_Rules", x => x.Id); + table.ForeignKey( + name: "FK_Rules_LoadBalancers_LoadBalancerId", + column: x => x.LoadBalancerId, + principalTable: "LoadBalancers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_LoadBalancers_TargetGroups_TargetGroupId", + name: "FK_Rules_TargetGroups_TargetGroupId", column: x => x.TargetGroupId, principalTable: "TargetGroups", principalColumn: "Id", @@ -149,32 +329,42 @@ protected override void Up(MigrationBuilder migrationBuilder) onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "Rules", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Priority = table.Column(type: "INTEGER", nullable: false), - TargetGroupId = table.Column(type: "TEXT", nullable: false), - LoadBalancerId = table.Column(type: "TEXT", nullable: false), - Conditions = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Rules", x => x.Id); - table.ForeignKey( - name: "FK_Rules_LoadBalancers_LoadBalancerId", - column: x => x.LoadBalancerId, - principalTable: "LoadBalancers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Rules_TargetGroups_TargetGroupId", - column: x => x.TargetGroupId, - principalTable: "TargetGroups", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); migrationBuilder.CreateIndex( name: "IX_CertificateAuthorities_Name", @@ -209,11 +399,6 @@ protected override void Up(MigrationBuilder migrationBuilder) column: "Name", unique: true); - migrationBuilder.CreateIndex( - name: "IX_LoadBalancers_TargetGroupId", - table: "LoadBalancers", - column: "TargetGroupId"); - migrationBuilder.CreateIndex( name: "IX_Rules_LoadBalancerId", table: "Rules", @@ -241,11 +426,32 @@ protected override void Up(MigrationBuilder migrationBuilder) table: "Targets", columns: new[] { "TargetGroupId", "Host", "Port" }, unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Tokens_Key", + table: "Tokens", + column: "Key", + unique: true); } /// protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + migrationBuilder.DropTable( name: "LoadBalancerCertificates"); @@ -255,6 +461,15 @@ protected override void Down(MigrationBuilder migrationBuilder) migrationBuilder.DropTable( name: "Targets"); + migrationBuilder.DropTable( + name: "Tokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + migrationBuilder.DropTable( name: "Certificates"); @@ -262,13 +477,13 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "LoadBalancers"); migrationBuilder.DropTable( - name: "CertificateAuthorities"); + name: "TargetGroups"); migrationBuilder.DropTable( - name: "PrivateKeys"); + name: "CertificateAuthorities"); migrationBuilder.DropTable( - name: "TargetGroups"); + name: "PrivateKeys"); } } } diff --git a/tilework.core/Migrations/TileworkContextModelSnapshot.cs b/tilework.core/Migrations/TileworkContextModelSnapshot.cs index 65f2b37..0270ec7 100644 --- a/tilework.core/Migrations/TileworkContextModelSnapshot.cs +++ b/tilework.core/Migrations/TileworkContextModelSnapshot.cs @@ -321,17 +321,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("AspNetUsers", (string)null); }); - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer", b => + modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.LoadBalancer", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("TEXT"); - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(34) - .HasColumnType("TEXT"); - b.Property("Enabled") .HasColumnType("INTEGER"); @@ -342,16 +337,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Port") .HasColumnType("INTEGER"); + b.Property("Protocol") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + b.HasKey("Id"); b.HasIndex("Name") .IsUnique(); b.ToTable("LoadBalancers"); - - b.HasDiscriminator().HasValue("BaseLoadBalancer"); - - b.UseTphMappingStrategy(); }); modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.Rule", b => @@ -449,40 +446,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Tokens"); }); - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.ApplicationLoadBalancer", b => - { - b.HasBaseType("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer"); - - b.Property("Protocol") - .HasColumnType("INTEGER"); - - b.HasDiscriminator().HasValue("ApplicationLoadBalancer"); - }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.NetworkLoadBalancer", b => - { - b.HasBaseType("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer"); - - b.Property("Protocol") - .HasColumnType("INTEGER"); - - b.Property("TargetGroupId") - .HasColumnType("TEXT"); - - b.HasIndex("TargetGroupId"); - - b.ToTable("LoadBalancers", t => - { - t.Property("Protocol") - .HasColumnName("NetworkLoadBalancer_Protocol"); - }); - - b.HasDiscriminator().HasValue("NetworkLoadBalancer"); - }); - modelBuilder.Entity("LoadBalancerCertificates", b => { - b.HasOne("Tilework.Persistence.LoadBalancing.Models.BaseLoadBalancer", null) + b.HasOne("Tilework.Persistence.LoadBalancing.Models.LoadBalancer", null) .WithMany() .HasForeignKey("BalancerId") .OnDelete(DeleteBehavior.Cascade) @@ -567,7 +533,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.Rule", b => { - b.HasOne("Tilework.Persistence.LoadBalancing.Models.ApplicationLoadBalancer", "LoadBalancer") + b.HasOne("Tilework.Persistence.LoadBalancing.Models.LoadBalancer", "LoadBalancer") .WithMany("Rules") .HasForeignKey("LoadBalancerId") .OnDelete(DeleteBehavior.Cascade) @@ -619,26 +585,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("TargetGroup"); }); - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.NetworkLoadBalancer", b => + modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.LoadBalancer", b => { - b.HasOne("Tilework.Persistence.LoadBalancing.Models.TargetGroup", "TargetGroup") - .WithMany() - .HasForeignKey("TargetGroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetGroup"); + b.Navigation("Rules"); }); modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.TargetGroup", b => { b.Navigation("Targets"); }); - - modelBuilder.Entity("Tilework.Persistence.LoadBalancing.Models.ApplicationLoadBalancer", b => - { - b.Navigation("Rules"); - }); #pragma warning restore 612, 618 } } diff --git a/tilework.core/Models/LoadBalancing/ApplicationLoadBalancerDTO.cs b/tilework.core/Models/LoadBalancing/ApplicationLoadBalancerDTO.cs deleted file mode 100644 index a553609..0000000 --- a/tilework.core/Models/LoadBalancing/ApplicationLoadBalancerDTO.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Tilework.LoadBalancing.Enums; - -namespace Tilework.LoadBalancing.Models; - - -public class ApplicationLoadBalancerDTO : BaseLoadBalancerDTO -{ - public AlbProtocol Protocol { get; set; } -} \ No newline at end of file diff --git a/tilework.core/Models/LoadBalancing/BaseLoadBalancerDTO.cs b/tilework.core/Models/LoadBalancing/LoadBalancerDTO.cs similarity index 55% rename from tilework.core/Models/LoadBalancing/BaseLoadBalancerDTO.cs rename to tilework.core/Models/LoadBalancing/LoadBalancerDTO.cs index 217dfc9..5d512e6 100644 --- a/tilework.core/Models/LoadBalancing/BaseLoadBalancerDTO.cs +++ b/tilework.core/Models/LoadBalancing/LoadBalancerDTO.cs @@ -1,7 +1,9 @@ +using Tilework.LoadBalancing.Enums; + namespace Tilework.LoadBalancing.Models; -public class BaseLoadBalancerDTO +public class LoadBalancerDTO { public Guid Id { get; set; } @@ -10,4 +12,6 @@ public class BaseLoadBalancerDTO public int Port { get; set; } public bool Enabled { get; set; } + public LoadBalancerType Type { get; set; } + public LoadBalancerProtocol Protocol { get; set; } } \ No newline at end of file diff --git a/tilework.core/Models/LoadBalancing/NetworkLoadBalancerDTO.cs b/tilework.core/Models/LoadBalancing/NetworkLoadBalancerDTO.cs deleted file mode 100644 index b12fe8d..0000000 --- a/tilework.core/Models/LoadBalancing/NetworkLoadBalancerDTO.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Tilework.LoadBalancing.Enums; - -namespace Tilework.LoadBalancing.Models; - - -public class NetworkLoadBalancerDTO : BaseLoadBalancerDTO -{ - public NlbProtocol Protocol { get; set; } - public Guid TargetGroup { get; set; } -} \ No newline at end of file diff --git a/tilework.core/Persistence/DbContext.cs b/tilework.core/Persistence/DbContext.cs index b9408f3..f19f204 100644 --- a/tilework.core/Persistence/DbContext.cs +++ b/tilework.core/Persistence/DbContext.cs @@ -21,9 +21,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) } // Load balancing - public DbSet LoadBalancers { get; set; } - public DbSet ApplicationLoadBalancers { get; set; } - public DbSet NetworkLoadBalancers { get; set; } + public DbSet LoadBalancers { get; set; } public DbSet Rules { get; set; } public DbSet TargetGroups { get; set; } public DbSet Targets { get; set; } @@ -63,7 +61,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) v => v.Value.GetHashCode(), v => new Host(v.Value))); - modelBuilder.Entity() + modelBuilder.Entity() .HasMany(s => s.Certificates) .WithMany() .UsingEntity>( @@ -73,7 +71,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey("CertificateId") .OnDelete(DeleteBehavior.Restrict), - l => l.HasOne() + l => l.HasOne() .WithMany() .HasForeignKey("BalancerId") .OnDelete(DeleteBehavior.Cascade) diff --git a/tilework.core/Persistence/Entities/LoadBalancing/ApplicationLoadBalancer.cs b/tilework.core/Persistence/Entities/LoadBalancing/ApplicationLoadBalancer.cs deleted file mode 100644 index dca81e4..0000000 --- a/tilework.core/Persistence/Entities/LoadBalancing/ApplicationLoadBalancer.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Tilework.LoadBalancing.Enums; - -namespace Tilework.Persistence.LoadBalancing.Models; - -public class ApplicationLoadBalancer : BaseLoadBalancer -{ - public AlbProtocol Protocol { get; set; } - - public virtual List Rules { get; set; } = new(); -} \ No newline at end of file diff --git a/tilework.core/Persistence/Entities/LoadBalancing/BaseLoadBalancer.cs b/tilework.core/Persistence/Entities/LoadBalancing/LoadBalancer.cs similarity index 68% rename from tilework.core/Persistence/Entities/LoadBalancing/BaseLoadBalancer.cs rename to tilework.core/Persistence/Entities/LoadBalancing/LoadBalancer.cs index d345879..fe93f43 100644 --- a/tilework.core/Persistence/Entities/LoadBalancing/BaseLoadBalancer.cs +++ b/tilework.core/Persistence/Entities/LoadBalancing/LoadBalancer.cs @@ -1,12 +1,13 @@ using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations; +using Tilework.LoadBalancing.Enums; using Tilework.Persistence.CertificateManagement.Models; namespace Tilework.Persistence.LoadBalancing.Models; [Index(nameof(Name), IsUnique = true)] -public abstract class BaseLoadBalancer +public class LoadBalancer { public Guid Id { get; set; } @@ -15,8 +16,12 @@ public abstract class BaseLoadBalancer [Required] public int Port { get; set; } + public LoadBalancerType Type { get; set; } + public LoadBalancerProtocol Protocol { get; set; } public bool Enabled { get; set; } public virtual List Certificates { get; set; } = new(); + + public virtual List Rules { get; set; } = new(); } \ No newline at end of file diff --git a/tilework.core/Persistence/Entities/LoadBalancing/NetworkLoadBalancer.cs b/tilework.core/Persistence/Entities/LoadBalancing/NetworkLoadBalancer.cs deleted file mode 100644 index b39ae14..0000000 --- a/tilework.core/Persistence/Entities/LoadBalancing/NetworkLoadBalancer.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Tilework.LoadBalancing.Enums; - -namespace Tilework.Persistence.LoadBalancing.Models; - -public class NetworkLoadBalancer : BaseLoadBalancer -{ - public NlbProtocol Protocol { get; set; } - - public Guid TargetGroupId { get; set; } - public virtual TargetGroup TargetGroup { get; set; } -} \ No newline at end of file diff --git a/tilework.core/Persistence/Entities/LoadBalancing/Rule.cs b/tilework.core/Persistence/Entities/LoadBalancing/Rule.cs index cd56bab..8e69bbf 100644 --- a/tilework.core/Persistence/Entities/LoadBalancing/Rule.cs +++ b/tilework.core/Persistence/Entities/LoadBalancing/Rule.cs @@ -14,7 +14,7 @@ public class Rule public virtual TargetGroup TargetGroup { get; set; } public Guid LoadBalancerId { get; set; } - public virtual ApplicationLoadBalancer LoadBalancer { get; set; } + public virtual LoadBalancer LoadBalancer { get; set; } public List Conditions { get; set; } = new(); } \ No newline at end of file diff --git a/tilework.core/Providers/LoadBalancingProviders/HAProxy/HAProxyConfigurator.cs b/tilework.core/Providers/LoadBalancingProviders/HAProxy/HAProxyConfigurator.cs index 696a711..f3f9a84 100644 --- a/tilework.core/Providers/LoadBalancingProviders/HAProxy/HAProxyConfigurator.cs +++ b/tilework.core/Providers/LoadBalancingProviders/HAProxy/HAProxyConfigurator.cs @@ -50,12 +50,12 @@ public HAProxyConfigurator(IOptions settings, _mapper = mapper; } - public List LoadConfiguration() + public List LoadConfiguration() { return null; } - private void UpdateConfigFile(string path, BaseLoadBalancer balancer) + private void UpdateConfigFile(string path, LoadBalancer balancer) { var haproxyConfig = new Configuration(path); haproxyConfig.Load(); @@ -67,18 +67,7 @@ private void UpdateConfigFile(string path, BaseLoadBalancer balancer) haproxyConfig.Frontends.Add(fe); - List targetGroups; - - if (balancer is ApplicationLoadBalancer appLoadBalancer) - { - targetGroups = appLoadBalancer.Rules != null ? appLoadBalancer.Rules.Select(r => r.TargetGroup).ToList() : new List(); - } - else if (balancer is NetworkLoadBalancer netLoadBalancer) - { - targetGroups = new List() { netLoadBalancer.TargetGroup }; - } - else - throw new ArgumentException("Invalid load balancer type"); + var targetGroups = balancer.Rules != null ? balancer.Rules.Select(r => r.TargetGroup).ToList() : new List(); haproxyConfig.Backends = targetGroups @@ -89,7 +78,7 @@ private void UpdateConfigFile(string path, BaseLoadBalancer balancer) haproxyConfig.Save(); } - private async Task> GetCertificateFiles(BaseLoadBalancer loadBalancer) + private async Task> GetCertificateFiles(LoadBalancer loadBalancer) { var containerFiles = new List(); @@ -148,7 +137,7 @@ private static string GetCertPem(X509Certificate2 cert) } - public async Task ConfigureMonitoring(BaseLoadBalancer loadBalancer) + public async Task ConfigureMonitoring(LoadBalancer loadBalancer) { if (loadBalancer.Enabled == true && _dataCollectorService.IsMonitored(loadBalancer.Id.ToString()) == false) { @@ -168,7 +157,7 @@ public async Task ConfigureMonitoring(BaseLoadBalancer loadBalancer) } } - public async Task ApplyConfiguration(BaseLoadBalancer loadBalancer) + public async Task ApplyConfiguration(LoadBalancer loadBalancer) { if(loadBalancer.Enabled == true) { @@ -229,7 +218,7 @@ public async Task ApplyConfiguration(BaseLoadBalancer loadBalancer) await ConfigureMonitoring(loadBalancer); } - public async Task ApplyConfiguration(List loadBalancers) + public async Task ApplyConfiguration(List loadBalancers) { foreach(var lb in loadBalancers) { @@ -252,7 +241,7 @@ public async Task ApplyConfiguration(List loadBalancers) } } - private async Task GetContainer(BaseLoadBalancer balancer) + private async Task GetContainer(LoadBalancer balancer) { var container = await GetContainer(balancer.Name); if (container == null) @@ -260,14 +249,14 @@ private async Task GetContainer(BaseLoadBalancer balancer) return container; } - public async Task GetLoadBalancerHostname(BaseLoadBalancer balancer) + public async Task GetLoadBalancerHostname(LoadBalancer balancer) { var container = await GetContainer(balancer); return (await _containerManager.GetContainerAddress(container.Id)).ToString(); } - public async Task CheckLoadBalancerStatus(BaseLoadBalancer balancer) + public async Task CheckLoadBalancerStatus(LoadBalancer balancer) { var container = await GetContainer(balancer); return container.State == ContainerState.Running; @@ -281,4 +270,4 @@ public async Task Shutdown() await Shutdown(cnt.Name); } } -} \ No newline at end of file +} diff --git a/tilework.core/Providers/LoadBalancingProviders/HAProxy/HAProxymonitor.cs b/tilework.core/Providers/LoadBalancingProviders/HAProxy/HAProxymonitor.cs index 3d812a4..13ed6b8 100644 --- a/tilework.core/Providers/LoadBalancingProviders/HAProxy/HAProxymonitor.cs +++ b/tilework.core/Providers/LoadBalancingProviders/HAProxy/HAProxymonitor.cs @@ -108,7 +108,7 @@ private async Task SendReceiveCommandKv(string hostname, int port, string return JsonSerializer.Deserialize(jsonString); } - public async Task GetRealtimeStatistics(BaseLoadBalancer balancer) + public async Task GetRealtimeStatistics(LoadBalancer balancer) { if (await _configurator.CheckLoadBalancerStatus(balancer) == false) throw new ArgumentOutOfRangeException($"Cannot get statistics for balancer {balancer}: Balancer is not running"); diff --git a/tilework.core/Providers/LoadBalancingProviders/HAProxy/Mappers/HAProxyConfigurationProfile.cs b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Mappers/HAProxyConfigurationProfile.cs index 408b9e5..9351219 100644 --- a/tilework.core/Providers/LoadBalancingProviders/HAProxy/Mappers/HAProxyConfigurationProfile.cs +++ b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Mappers/HAProxyConfigurationProfile.cs @@ -10,23 +10,21 @@ public class HAProxyConfigurationProfile : Profile { public HAProxyConfigurationProfile() { - CreateMap() + CreateMap() .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Id.ToString())) .ForPath(dest => dest.Bind.Address, opt => opt.MapFrom(src => "*")) .ForPath(dest => dest.Bind.Port, opt => opt.MapFrom(src => src.Port)) .AfterMap((src, dest) => { - if (src is ApplicationLoadBalancer alb) - { - if (alb.Protocol == AlbProtocol.HTTPS) - { - dest.Bind.EnableTls = true; - } + if (src.Protocol == LoadBalancerProtocol.HTTPS || src.Protocol == LoadBalancerProtocol.TLS) + dest.Bind.EnableTls = true; + if (src.Type == LoadBalancerType.APPLICATION) + { dest.AddHeaders.Add(new HttpHeader() { Name = "X-Forwarded-Proto", - Value = alb.Protocol == AlbProtocol.HTTPS ? "https" : "http" + Value = src.Protocol == LoadBalancerProtocol.HTTPS ? "https" : "http" }); dest.AddHeaders.Add(new HttpHeader() @@ -34,52 +32,43 @@ public HAProxyConfigurationProfile() Name = "X-Forwarded-Port", Value = src.Port.ToString() }); - + } - dest.Mode = Mode.HTTP; - if (alb.Rules != null) + dest.Mode = src.Type == LoadBalancerType.APPLICATION ? Mode.HTTP : Mode.TCP; + if (src.Rules != null) + { + foreach (var rule in src.Rules.OrderBy(r => r.Priority)) { - foreach (var rule in alb.Rules.OrderBy(r => r.Priority)) + var acls = new List(); + for (int i = 0; i < rule.Conditions.Count; i++) { - var acls = new List(); - for (int i = 0; i < rule.Conditions.Count; i++) - { - var condition = rule.Conditions[i]; - - var acl = new Acl() - { - Name = $"{rule.Id.ToString()}-{i}", - Type = condition.Type, - Values = condition.Values - }; - - acls.Add(acl); - } + var condition = rule.Conditions[i]; - dest.Acls.AddRange(acls); - - var usebe = new UseBackend() + var acl = new Acl() { - Acls = acls.Select(a => a.Name).ToList(), - Target = rule.TargetGroup.Id.ToString(), + Name = $"{rule.Id.ToString()}-{i}", + Type = condition.Type, + Values = condition.Values }; - dest.UseBackends.Add(usebe); + + acls.Add(acl); } - } - } - else if (src is NetworkLoadBalancer nlb) - { - if (nlb.Protocol == NlbProtocol.TLS) - dest.Bind.EnableTls = true; - dest.Mode = Mode.TCP; - dest.DefaultBackend = nlb.TargetGroup.Id.ToString(); + dest.Acls.AddRange(acls); + + var usebe = new UseBackend() + { + Acls = acls.Select(a => a.Name).ToList(), + Target = rule.TargetGroup.Id.ToString(), + }; + dest.UseBackends.Add(usebe); + } } }); - CreateMap() - .ConvertUsing((BaseLoadBalancer src) => - src is NetworkLoadBalancer && ((NetworkLoadBalancer)src).Protocol == NlbProtocol.UDP + CreateMap() + .ConvertUsing(src => + src.Protocol == LoadBalancerProtocol.UDP ? PortType.UDP : PortType.TCP); @@ -92,6 +81,9 @@ public HAProxyConfigurationProfile() TargetGroupProtocol.HTTP => Mode.HTTP, TargetGroupProtocol.HTTPS => Mode.HTTP, TargetGroupProtocol.TCP => Mode.TCP, + TargetGroupProtocol.UDP => Mode.TCP, + TargetGroupProtocol.TCP_UDP => Mode.TCP, + TargetGroupProtocol.TLS => Mode.TCP, _ => throw new NotImplementedException(), }; @@ -105,4 +97,3 @@ public HAProxyConfigurationProfile() }); } } - diff --git a/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Statements/Acl.cs b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Statements/Acl.cs index 81b31b6..25eb389 100644 --- a/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Statements/Acl.cs +++ b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Statements/Acl.cs @@ -21,7 +21,8 @@ public override string ToString() { ConditionType.HostHeader => $"{Name} hdr(host) -i {string.Join(" ", Values)}", ConditionType.Path => $"{Name} path_beg -i {string.Join(" ", Values)}", - ConditionType.QueryString => $"{Name} {String.Join(" or ", Values.Select(v => $"url_param(plan) -i {v}"))}" + ConditionType.QueryString => $"{Name} {String.Join(" or ", Values.Select(v => $"url_param(plan) -i {v}"))}", + ConditionType.SNI => $"{Name} req.ssl_sni -i {string.Join(" ", Values)}" }; } } \ No newline at end of file diff --git a/tilework.core/Services/CertificateManagement/AcmeVerificationService.cs b/tilework.core/Services/CertificateManagement/AcmeVerificationService.cs index 4f70e49..ed8b6c8 100644 --- a/tilework.core/Services/CertificateManagement/AcmeVerificationService.cs +++ b/tilework.core/Services/CertificateManagement/AcmeVerificationService.cs @@ -74,20 +74,20 @@ private async Task DeleteContainer(string name) await _containerManager.DeleteContainer(container.Id); } - private async Task AddLoadBalancer() + private async Task AddLoadBalancer() { - var lb = new ApplicationLoadBalancerDTO() + var lb = new LoadBalancerDTO() { Name = "AcmeVerification", - Protocol = AlbProtocol.HTTP, + Protocol = LoadBalancerProtocol.HTTP, Port = 80 }; - lb = (ApplicationLoadBalancerDTO) await _loadBalancerService.AddLoadBalancer(lb); + lb = await _loadBalancerService.AddLoadBalancer(lb); await _loadBalancerService.EnableLoadBalancer(lb.Id); return lb; } - private async Task AddLoadBalancerTarget(string id, ApplicationLoadBalancerDTO balancer, string host, string filename, string target) + private async Task AddLoadBalancerTarget(string id, LoadBalancerDTO balancer, string host, string filename, string target) { var tg = new TargetGroupDTO() { @@ -144,25 +144,23 @@ private async Task CheckRemoveLoadBalancer(string certId) if (balancer == null) return; - var appBalancer = (ApplicationLoadBalancerDTO)balancer; - var targetGroups = await _loadBalancerService.GetTargetGroups(); - foreach (var rule in await _loadBalancerService.GetRules(appBalancer)) + foreach (var rule in await _loadBalancerService.GetRules(balancer)) { var tg = targetGroups.FirstOrDefault(tg => tg.Id == rule.TargetGroup); if (tg != null && tg.Name == $"AcmeVerification-{certId}") { - await _loadBalancerService.RemoveRule(appBalancer, rule); + await _loadBalancerService.RemoveRule(balancer, rule); await _loadBalancerService.DeleteTargetGroup(tg.Id); } } - if (appBalancer.Name == "AcmeVerification" && (await _loadBalancerService.GetRules(appBalancer)).Count == 0) + if (balancer.Name == "AcmeVerification" && (await _loadBalancerService.GetRules(balancer)).Count == 0) { - await _loadBalancerService.DeleteLoadBalancer(appBalancer.Id); + await _loadBalancerService.DeleteLoadBalancer(balancer.Id); } await _loadBalancerService.ApplyConfiguration(); @@ -183,7 +181,7 @@ public async Task StartVerification(string id, string host, string filename, str } else { - if (balancer is not ApplicationLoadBalancerDTO) + if (balancer.Type == LoadBalancerType.NETWORK) { throw new InvalidOperationException("Cannot run verification. A network load balancer uses port 80"); } @@ -194,7 +192,7 @@ public async Task StartVerification(string id, string host, string filename, str var container = await CreateContainer(id, filename, data); - await AddLoadBalancerTarget(id, (ApplicationLoadBalancerDTO)balancer, host, filename, container.Name); + await AddLoadBalancerTarget(id, balancer, host, filename, container.Name); await _loadBalancerService.ApplyConfiguration(); } diff --git a/tilework.core/Services/LoadBalancing/LoadBalancerService.cs b/tilework.core/Services/LoadBalancing/LoadBalancerService.cs index f54eb5d..1a05d1e 100644 --- a/tilework.core/Services/LoadBalancing/LoadBalancerService.cs +++ b/tilework.core/Services/LoadBalancing/LoadBalancerService.cs @@ -55,31 +55,45 @@ private ILoadBalancingConfigurator LoadConfigurator(IServiceProvider serviceProv }; } - private static void ValidateRulePriority(ICollection rules, int newPriority) - { - if (newPriority < 0) - { - throw new ArgumentOutOfRangeException(nameof(newPriority), newPriority, "Rule priority cannot be negative."); + private static void ValidateRulePriority(ICollection rules, int newPriority) + { + if (newPriority < 0) + { + throw new ArgumentOutOfRangeException(nameof(newPriority), newPriority, "Rule priority cannot be negative."); } var maxPriority = rules.Count == 0 ? -1 : rules.Max(r => r.Priority); if (newPriority > maxPriority + 1) { - throw new ArgumentOutOfRangeException(nameof(newPriority), newPriority, $"Rule priority cannot be greater than {maxPriority + 1}."); - } - } - - private static bool RequiresCertificate(BaseLoadBalancer balancer) - { - return balancer switch - { - ApplicationLoadBalancer appBalancer => appBalancer.Protocol == AlbProtocol.HTTPS, - NetworkLoadBalancer netBalancer => netBalancer.Protocol == NlbProtocol.TLS, - _ => false - }; - } - - private static void EnsureCertificatesPresentIfRequired(BaseLoadBalancer balancer) + throw new ArgumentOutOfRangeException(nameof(newPriority), newPriority, $"Rule priority cannot be greater than {maxPriority + 1}."); + } + } + + private static void EnsureRuleConditionsAllowed(LoadBalancer balancer, RuleDTO rule) + { + var invalid = rule.Conditions + .Select(condition => condition.Type) + .Distinct() + .Where(conditionType => !LoadBalancerConditionRules.IsAllowed(balancer.Protocol, conditionType)) + .ToList(); + + if (invalid.Count == 0) + { + return; + } + + var invalidList = string.Join(", ", invalid); + throw new ArgumentException( + $"Rule contains condition types not supported by {balancer.Protocol}: {invalidList}.", + nameof(rule)); + } + + private static bool RequiresCertificate(LoadBalancer balancer) + { + return balancer.Protocol == LoadBalancerProtocol.HTTPS || balancer.Protocol == LoadBalancerProtocol.TLS; + } + + private static void EnsureCertificatesPresentIfRequired(LoadBalancer balancer) { if (RequiresCertificate(balancer) && (balancer.Certificates == null || balancer.Certificates.Count == 0)) { @@ -87,62 +101,39 @@ private static void EnsureCertificatesPresentIfRequired(BaseLoadBalancer balance } } - - private BaseLoadBalancerDTO MapBalancerToDto(BaseLoadBalancer entity) - { - return entity switch - { - ApplicationLoadBalancer appBalancer => _mapper.Map(appBalancer), - NetworkLoadBalancer netBalancer => _mapper.Map(netBalancer), - _ => throw new InvalidOperationException("Invalid balancer type") - }; - } - - private BaseLoadBalancer MapDtoToBalancer(BaseLoadBalancerDTO dto, BaseLoadBalancer? entity = null) - { - return dto switch - { - ApplicationLoadBalancerDTO appBalancer => - entity == null ? _mapper.Map(appBalancer) : _mapper.Map(appBalancer, (ApplicationLoadBalancer)entity), - - NetworkLoadBalancerDTO netBalancer => - entity == null ? _mapper.Map(netBalancer) : _mapper.Map(netBalancer, (NetworkLoadBalancer)entity), - - _ => throw new InvalidOperationException("Invalid balancer type") - }; - } - - public async Task> GetLoadBalancers() + public async Task> GetLoadBalancers() { var entities = await _dbContext.LoadBalancers.ToListAsync(); - return entities.Select(e => MapBalancerToDto(e)).ToList(); + return _mapper.Map>(entities); } - public async Task GetLoadBalancer(Guid Id) + public async Task GetLoadBalancer(Guid Id) { var entity = await _dbContext.LoadBalancers.FindAsync(Id); - return entity != null ? MapBalancerToDto(entity) : null; + return _mapper.Map(entity); } - public async Task AddLoadBalancer(BaseLoadBalancerDTO balancer) - { - var entity = MapDtoToBalancer(balancer); + public async Task AddLoadBalancer(LoadBalancerDTO balancer) + { + EnsureProtocolAllowed(balancer); + var entity = _mapper.Map(balancer); await _dbContext.LoadBalancers.AddAsync(entity); await _dbContext.SaveChangesAsync(); - return MapBalancerToDto(entity); + return _mapper.Map(entity); } - public async Task UpdateLoadBalancer(BaseLoadBalancerDTO balancer) - { - var entity = await _dbContext.LoadBalancers.FindAsync(balancer.Id); - entity = MapDtoToBalancer(balancer, entity); + public async Task UpdateLoadBalancer(LoadBalancerDTO balancer) + { + EnsureProtocolAllowed(balancer); + var entity = await _dbContext.LoadBalancers.FindAsync(balancer.Id); + entity = _mapper.Map(balancer, entity); _dbContext.LoadBalancers.Update(entity); await _dbContext.SaveChangesAsync(); - return MapBalancerToDto(entity); + return _mapper.Map(entity); } public async Task DeleteLoadBalancer(Guid Id) @@ -203,19 +194,20 @@ public async Task DisableLoadBalancer(Guid Id) } - public async Task> GetRules(ApplicationLoadBalancerDTO balancer) + public async Task> GetRules(LoadBalancerDTO balancer) { - var entity = (ApplicationLoadBalancer?)await _dbContext.LoadBalancers.FindAsync(balancer.Id); + var entity = await _dbContext.LoadBalancers.FindAsync(balancer.Id); return _mapper.Map>(entity.Rules.OrderBy(r => r.Priority)); } - public async Task AddRule(ApplicationLoadBalancerDTO balancer, RuleDTO rule) + public async Task AddRule(LoadBalancerDTO balancer, RuleDTO rule) { - var entity = (ApplicationLoadBalancer?)await _dbContext.LoadBalancers.FindAsync(balancer.Id); - if (entity == null) - throw new ArgumentNullException(nameof(balancer)); - - ValidateRulePriority(entity.Rules, rule.Priority); + var entity = await _dbContext.LoadBalancers.FindAsync(balancer.Id); + if (entity == null) + throw new ArgumentNullException(nameof(balancer)); + + ValidateRulePriority(entity.Rules, rule.Priority); + EnsureRuleConditionsAllowed(entity, rule); foreach (var existingRule in entity.Rules.Where(r => r.Priority >= rule.Priority)) { @@ -227,17 +219,18 @@ public async Task AddRule(ApplicationLoadBalancerDTO balancer, RuleDTO rule) await _dbContext.SaveChangesAsync(); } - public async Task UpdateRule(ApplicationLoadBalancerDTO balancer, RuleDTO rule) + public async Task UpdateRule(LoadBalancerDTO balancer, RuleDTO rule) { - var entity = (ApplicationLoadBalancer?)await _dbContext.LoadBalancers.FindAsync(balancer.Id); + var entity = await _dbContext.LoadBalancers.FindAsync(balancer.Id); if (entity == null) throw new ArgumentNullException(nameof(balancer)); var ruleEntity = entity.Rules.FirstOrDefault(t => t.Id == rule.Id); - if (ruleEntity == null) - return; - - ValidateRulePriority(entity.Rules, rule.Priority); + if (ruleEntity == null) + return; + + ValidateRulePriority(entity.Rules, rule.Priority); + EnsureRuleConditionsAllowed(entity, rule); await using var tx = await _dbContext.Database.BeginTransactionAsync(); @@ -301,9 +294,9 @@ public async Task UpdateRule(ApplicationLoadBalancerDTO balancer, RuleDTO rule) } } - public async Task RemoveRule(ApplicationLoadBalancerDTO balancer, RuleDTO rule) + public async Task RemoveRule(LoadBalancerDTO balancer, RuleDTO rule) { - var entity = (ApplicationLoadBalancer?)await _dbContext.LoadBalancers.FindAsync(balancer.Id); + var entity = await _dbContext.LoadBalancers.FindAsync(balancer.Id); if (entity == null) throw new ArgumentNullException(); @@ -323,13 +316,13 @@ public async Task RemoveRule(ApplicationLoadBalancerDTO balancer, RuleDTO rule) await _dbContext.SaveChangesAsync(); } - public async Task> GetCertificates(BaseLoadBalancerDTO balancer) + public async Task> GetCertificates(LoadBalancerDTO balancer) { var entity = await _dbContext.LoadBalancers.FindAsync(balancer.Id); return _mapper.Map>(entity.Certificates); } - public async Task AddCertificate(BaseLoadBalancerDTO balancer, Guid certificateId) + public async Task AddCertificate(LoadBalancerDTO balancer, Guid certificateId) { var entity = await _dbContext.LoadBalancers.FindAsync(balancer.Id); var cert = await _dbContext.Certificates.FindAsync(certificateId); @@ -338,14 +331,24 @@ public async Task AddCertificate(BaseLoadBalancerDTO balancer, Guid certificateI await _dbContext.SaveChangesAsync(); } - public async Task RemoveCertificate(BaseLoadBalancerDTO balancer, Guid certificateId) + public async Task RemoveCertificate(LoadBalancerDTO balancer, Guid certificateId) { var entity = await _dbContext.LoadBalancers.FindAsync(balancer.Id); var c = entity.Certificates.FirstOrDefault(t => t.Id == certificateId); entity.Certificates.Remove(c); _dbContext.LoadBalancers.Update(entity); - await _dbContext.SaveChangesAsync(); - } + await _dbContext.SaveChangesAsync(); + } + + private static void EnsureProtocolAllowed(LoadBalancerDTO balancer) + { + if (!LoadBalancerProtocolRules.IsAllowed(balancer.Type, balancer.Protocol)) + { + throw new ArgumentException( + $"Protocol {balancer.Protocol} is not valid for load balancer type {balancer.Type}", + nameof(balancer)); + } + } public async Task> GetTargetGroups() @@ -354,26 +357,6 @@ public async Task> GetTargetGroups() return _mapper.Map>(entities); } - public async Task> GetNlbTargetGroups() - { - var protocols = new List { - TargetGroupProtocol.TCP, - TargetGroupProtocol.UDP, - TargetGroupProtocol.TCP_UDP, - TargetGroupProtocol.TLS - }; - return (await GetTargetGroups()).Where(tg => protocols.Contains(tg.Protocol)).ToList(); - } - - public async Task> GetAlbTargetGroups() - { - var protocols = new List { - TargetGroupProtocol.HTTP, - TargetGroupProtocol.HTTPS - }; - return (await GetTargetGroups()).Where(tg => protocols.Contains(tg.Protocol)).ToList(); - } - public async Task GetTargetGroup(Guid Id) { var entity = await _dbContext.TargetGroups.FindAsync(Id); diff --git a/tilework.ui/Components/Dialogs/RuleDialog.razor b/tilework.ui/Components/Dialogs/RuleDialog.razor index 9ff137a..1889c39 100644 --- a/tilework.ui/Components/Dialogs/RuleDialog.razor +++ b/tilework.ui/Components/Dialogs/RuleDialog.razor @@ -15,6 +15,12 @@ + @if (!AllowedConditionTypes.Any()) + { + + This protocol does not support rule conditions. + + } @foreach (var condition in Rule.Conditions) { TargetGroups { get; set; } = new(); + [Parameter] public LoadBalancerProtocol Protocol { get; set; } [Parameter] public string Title { get; set; } = "Add rule"; [Parameter] public string ButtonText { get; set; } = "Add"; [Parameter] public string Icon { get; set; } = Icons.Material.Filled.Add; @@ -89,7 +96,10 @@ void Cancel() => MudDialog.Cancel(); - IEnumerable AvailableConditionTypes => Enum.GetValues().Except(Rule.Conditions.Select(c => c.Type)); + IEnumerable AllowedConditionTypes => LoadBalancerConditionRules.GetAllowedConditions(Protocol); + + IEnumerable AvailableConditionTypes => + AllowedConditionTypes.Except(Rule.Conditions.Select(c => c.Type)); bool CanAddCondition => AvailableConditionTypes.Any(); @@ -103,4 +113,3 @@ Rule.Conditions.Remove(condition); } } - diff --git a/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerDetail.razor b/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerDetail.razor index 57c9fec..9a5f5c8 100644 --- a/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerDetail.razor +++ b/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerDetail.razor @@ -31,7 +31,7 @@ Type - @(_item is ApplicationLoadBalancerDTO appBalancer ? "Application" : "Network") + @(_item.Type == LoadBalancerType.APPLICATION ? "Application" : "Network") Status @@ -51,60 +51,57 @@ - @if(_item is ApplicationLoadBalancerDTO appBalancer) - { - - - - Rules - - Add rule - - - - - - @context.Item.Priority - - - - - - @for (int i = 0; i < context.Item.Conditions.Count; i++) - { - var condition = context.Item.Conditions[i]; - -
- @condition.Type.GetDescription() - = - @for (int j = 0; j < condition.Values.Count; j++) + + + + Rules + + Add rule + + + + + + @context.Item.Priority + + + + + + @for (int i = 0; i < context.Item.Conditions.Count; i++) + { + var condition = context.Item.Conditions[i]; + +
+ @condition.Type.GetDescription() + = + @for (int j = 0; j < condition.Values.Count; j++) + { + @condition.Values[j] + @if (j != condition.Values.Count - 1) { - @condition.Values[j] - @if (j != condition.Values.Count - 1) - { - OR - } + OR } -
- - @if (i != context.Item.Conditions.Count - 1) - { -
AND
} +
+ + @if (i != context.Item.Conditions.Count - 1) + { +
AND
} -
-
- - - - - - - -
-
-
- } + } +
+
+ + + + + + + +
+ + @if(_showCertificates) { @@ -140,7 +137,7 @@ [Parameter] public Guid Id { get; set; } - private BaseLoadBalancerDTO _item; + private LoadBalancerDTO _item; private List _rules = new(); private List _certificates = new(); private bool _showCertificates; @@ -163,9 +160,7 @@ return; } - _showCertificates = - (_item is ApplicationLoadBalancerDTO alb && alb.Protocol == AlbProtocol.HTTPS) || - (_item is NetworkLoadBalancerDTO nlb && nlb.Protocol == NlbProtocol.TLS); + _showCertificates = _item.Protocol == LoadBalancerProtocol.HTTPS || _item.Protocol == LoadBalancerProtocol.TLS; await GetRules(); if(_showCertificates) @@ -228,7 +223,8 @@ rule.Priority = GetMaxRulePriority(); parameters.Add(x => x.Rule, rule); - parameters.Add(x => x.TargetGroups, await GetAlbTargetGroups()); + parameters.Add(x => x.TargetGroups, await GetRuleTargetGroups()); + parameters.Add(x => x.Protocol, _item.Protocol); var options = new DialogOptions() { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true }; @@ -236,28 +232,21 @@ var result = await dialog.Result; if (!result.Canceled) { - if(_item is ApplicationLoadBalancerDTO appBalancer) + try { - try - { - if(rule.Priority < 0) - rule.Priority = 0; - if(rule.Priority > maxPriorty) - rule.Priority = maxPriorty; - - await _loadBalancerService.AddRule(appBalancer, rule); - await GetRules(); - await _loadBalancerService.ApplyConfiguration(appBalancer.Id); - _snackbar.Add("Rule added successfully!", Severity.Success); - } - catch(Exception ex) - { - _snackbar.Add($"Failed to add rule: {ex.Message}", Severity.Error); - } + if(rule.Priority < 0) + rule.Priority = 0; + if(rule.Priority > maxPriorty) + rule.Priority = maxPriorty; + + await _loadBalancerService.AddRule(_item, rule); + await GetRules(); + await _loadBalancerService.ApplyConfiguration(_item.Id); + _snackbar.Add("Rule added successfully!", Severity.Success); } - else + catch(Exception ex) { - _snackbar.Add("Can only add rules to application load balancers", Severity.Error); + _snackbar.Add($"Failed to add rule: {ex.Message}", Severity.Error); } } } @@ -281,7 +270,8 @@ var parameters = new DialogParameters(); parameters.Add(x => x.Rule, ruleEdit); - parameters.Add(x => x.TargetGroups, await GetAlbTargetGroups()); + parameters.Add(x => x.TargetGroups, await GetRuleTargetGroups()); + parameters.Add(x => x.Protocol, _item.Protocol); parameters.Add(x => x.Title, "Edit rule"); parameters.Add(x => x.ButtonText, "Save"); parameters.Add(x => x.Icon, Icons.Material.Filled.Edit); @@ -292,28 +282,21 @@ var result = await dialog.Result; if (!result.Canceled) { - if (_item is ApplicationLoadBalancerDTO appBalancer) + try { - try - { - if(ruleEdit.Priority < 0) - ruleEdit.Priority = 0; - if(ruleEdit.Priority > maxPriorty) - ruleEdit.Priority = maxPriorty; - - await _loadBalancerService.UpdateRule(appBalancer, ruleEdit); - await GetRules(); - await _loadBalancerService.ApplyConfiguration(appBalancer.Id); - _snackbar.Add("Rule updated successfully!", Severity.Success); - } - catch(Exception ex) - { - _snackbar.Add($"Failed to update rule: {ex.Message}", Severity.Error); - } + if(ruleEdit.Priority < 0) + ruleEdit.Priority = 0; + if(ruleEdit.Priority > maxPriorty) + ruleEdit.Priority = maxPriorty; + + await _loadBalancerService.UpdateRule(_item, ruleEdit); + await GetRules(); + await _loadBalancerService.ApplyConfiguration(_item.Id); + _snackbar.Add("Rule updated successfully!", Severity.Success); } - else + catch(Exception ex) { - _snackbar.Add("Can only edit rules on application load balancers", Severity.Error); + _snackbar.Add($"Failed to update rule: {ex.Message}", Severity.Error); } } } @@ -329,11 +312,11 @@ var dialog = await _dialogService.ShowAsync("Delete", parameters, options); var result = await dialog.Result; - if (!result.Canceled && _item is ApplicationLoadBalancerDTO appBalancer) + if (!result.Canceled) { - await _loadBalancerService.RemoveRule(appBalancer, rule); + await _loadBalancerService.RemoveRule(_item, rule); await GetRules(); - await _loadBalancerService.ApplyConfiguration(appBalancer.Id); + await _loadBalancerService.ApplyConfiguration(_item.Id); } } @@ -381,13 +364,25 @@ return all.Where(c => !installed.Select(ic => ic.Id).Contains(c.Id)).ToList(); } - private async Task> GetAlbTargetGroups() + private async Task> GetRuleTargetGroups() { - var protocols = new List { - TargetGroupProtocol.HTTP, - TargetGroupProtocol.HTTPS - }; - return (await _loadBalancerService.GetTargetGroups()).Where(tg => protocols.Contains(tg.Protocol)).ToList(); + var protocols = _item.Type == LoadBalancerType.NETWORK + ? new List + { + TargetGroupProtocol.TCP, + TargetGroupProtocol.UDP, + TargetGroupProtocol.TCP_UDP, + TargetGroupProtocol.TLS + } + : new List + { + TargetGroupProtocol.HTTP, + TargetGroupProtocol.HTTPS + }; + + return (await _loadBalancerService.GetTargetGroups()) + .Where(tg => protocols.Contains(tg.Protocol)) + .ToList(); } private async Task Enable() @@ -435,10 +430,7 @@ private async Task GetRules() { - if(_item is ApplicationLoadBalancerDTO appBalancer) - { - _rules = await _loadBalancerService.GetRules(appBalancer); - } + _rules = await _loadBalancerService.GetRules(_item); } private async Task GetCertificates() diff --git a/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerEdit.razor b/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerEdit.razor index fd57421..36f15a6 100644 --- a/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerEdit.razor +++ b/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerEdit.razor @@ -1,8 +1,8 @@ @using AutoMapper -@using Tilework.LoadBalancing.Enums @using Tilework.LoadBalancing.Models @using Tilework.LoadBalancing.Interfaces +@using Tilework.Ui.Models @namespace Tilework.Ui.Components.Pages @@ -14,26 +14,17 @@ Edit load balancer -@if (form is EditApplicationLoadBalancerForm albForm) -{ - -} -else if (form is EditNetworkLoadBalancerForm nlbForm) -{ - -} + @code { [Parameter] public Guid Id { get; set; } - private EditBaseLoadBalancerForm form = new EditBaseLoadBalancerForm(); + private EditLoadBalancerForm form = new EditLoadBalancerForm(); - private BaseLoadBalancerDTO _item; + private LoadBalancerDTO _item; private List _breadcrumbs = new List { @@ -50,47 +41,13 @@ else if (form is EditNetworkLoadBalancerForm nlbForm) Snackbar.Add($"Load balancer {Id} not found", Severity.Error); } - if(_item is ApplicationLoadBalancerDTO alb) - { - form = _mapper.Map(alb); - } - else if(_item is NetworkLoadBalancerDTO nlb) - { - form = _mapper.Map(nlb); - } + form = _mapper.Map(_item); _breadcrumbs.Add(new BreadcrumbItem(_item.Name, href: $"/lb/loadbalancers/{Id}")); _breadcrumbs.Add(new BreadcrumbItem("edit", href: null, disabled: true)); } - private async Task OnChange((string PropertyName, object? Value) change) - { - if(change.PropertyName == "Type") - { - LoadBalancerType? value = (LoadBalancerType?) change.Value; - if(value == LoadBalancerType.NETWORK && form is EditApplicationLoadBalancerForm albForm) - { - form = _mapper.Map(albForm); - - var targetGroupOptions = (await _loadBalancerService.GetNlbTargetGroups()).Select(tg => - new SelectOptionItem() - { - Value = tg.Id, - Text = tg.Name - } - ).ToList(); - form.SetOptions(nameof(EditNetworkLoadBalancerForm.TargetGroup), targetGroupOptions); - } - else if(value == LoadBalancerType.APPLICATION && form is EditNetworkLoadBalancerForm nlbForm) - { - form = _mapper.Map(nlbForm); - } - - StateHasChanged(); - } - } - private async Task OnSubmit() { try @@ -107,4 +64,5 @@ else if (form is EditNetworkLoadBalancerForm nlbForm) Snackbar.Add($"Failed to save load balancer: {ex.Message}", Severity.Error); } } + } diff --git a/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerList.razor b/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerList.razor index b6650f3..c049397 100644 --- a/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerList.razor +++ b/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerList.razor @@ -1,3 +1,4 @@ +@using Tilework.LoadBalancing.Enums @using Tilework.LoadBalancing.Models @using Tilework.LoadBalancing.Interfaces @@ -20,10 +21,8 @@ @context.Name @context.Port - @(context is ApplicationLoadBalancerDTO ? "Application" : "Network") - - @(context is ApplicationLoadBalancerDTO ? ((ApplicationLoadBalancerDTO) context).Protocol : ((NetworkLoadBalancerDTO) context).Protocol) - + @(context.Type == LoadBalancerType.APPLICATION ? "Application" : "Network") + @context.Protocol @if (context.Enabled) @@ -42,7 +41,7 @@ @code { - public List _items { get; set; } = new(); + public List _items { get; set; } = new(); private List _breadcrumbs = new List { diff --git a/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerNew.razor b/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerNew.razor index 20ac244..6e4bec2 100644 --- a/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerNew.razor +++ b/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerNew.razor @@ -2,9 +2,11 @@ @using AutoMapper +@using Tilework.Core.Enums @using Tilework.LoadBalancing.Enums @using Tilework.LoadBalancing.Models @using Tilework.LoadBalancing.Interfaces +@using Tilework.Ui.Models @namespace Tilework.Ui.Components.Pages @@ -17,22 +19,12 @@ New balancer -@if(form is NewApplicationLoadBalancerForm albForm) -{ - -} -else if(form is NewNetworkLoadBalancerForm nlbForm) -{ - -} - + @code { - NewBaseLoadBalancerForm form = new NewBaseLoadBalancerForm(); + NewLoadBalancerForm form = new NewLoadBalancerForm(); private List _breadcrumbs = new List { @@ -41,50 +33,29 @@ else if(form is NewNetworkLoadBalancerForm nlbForm) new BreadcrumbItem("Create new", href: null, disabled: true, icon: Icons.Material.Filled.AltRoute) }; - protected override async Task OnInitializedAsync() + + protected override Task OnInitializedAsync() { - form = new NewApplicationLoadBalancerForm(); + ConfigureProtocolOptions(); + return Task.CompletedTask; } - private async Task OnChange((string PropertyName, object? Value) change) + private Task OnChange((string PropertyName, object? Value) change) { - if(change.PropertyName == "Type") + if (change.PropertyName == nameof(NewLoadBalancerForm.Type)) { - LoadBalancerType? value = (LoadBalancerType?) change.Value; - if(value == LoadBalancerType.NETWORK && form is NewApplicationLoadBalancerForm albForm) - { - form = Mapper.Map(albForm); - - var targetGroupOptions = (await _loadBalancerService.GetNlbTargetGroups()).Select(tg => - new SelectOptionItem() - { - Value = tg.Id, - Text = tg.Name - } - ).ToList(); - form.SetOptions(nameof(NewNetworkLoadBalancerForm.TargetGroup), targetGroupOptions); - } - else if(value == LoadBalancerType.APPLICATION && form is NewNetworkLoadBalancerForm nlbForm) - { - form = Mapper.Map(nlbForm); - } - + ConfigureProtocolOptions(); StateHasChanged(); } + + return Task.CompletedTask; } private async Task OnSubmit() { try { - BaseLoadBalancerDTO item; - if(form is NewApplicationLoadBalancerForm albForm) - item = Mapper.Map(albForm); - else if(form is NewNetworkLoadBalancerForm nlbForm) - item = Mapper.Map(nlbForm); - else - throw new ArgumentException(); - + var item = Mapper.Map(form); item = await _loadBalancerService.AddLoadBalancer(item); await _loadBalancerService.ApplyConfiguration(item.Id); @@ -96,4 +67,19 @@ else if(form is NewNetworkLoadBalancerForm nlbForm) Snackbar.Add($"Failed to add load balancer: {ex.Message}", Severity.Error); } } + + private void ConfigureProtocolOptions() + { + var allowed = LoadBalancerProtocolRules.GetAllowedProtocols(form.Type); + var options = allowed.Select(protocol => new SelectOptionItem + { + Value = protocol, + Text = protocol.GetDescription() + }).ToList(); + + form.SetOptions(nameof(NewLoadBalancerForm.Protocol), options); + + if (!allowed.Contains(form.Protocol)) + form.Protocol = allowed[0]; + } } diff --git a/tilework.ui/Components/Pages/LoadBalancing/TargetGroupDetail.razor b/tilework.ui/Components/Pages/LoadBalancing/TargetGroupDetail.razor index c3ff920..11aa018 100644 --- a/tilework.ui/Components/Pages/LoadBalancing/TargetGroupDetail.razor +++ b/tilework.ui/Components/Pages/LoadBalancing/TargetGroupDetail.razor @@ -222,13 +222,21 @@ return; } - var healthTasks = _targets.Select(async target => new + try { - target.Id, - Status = await _loadBalancerService.GetTargetHealth(target.Id) - }); + var healthTasks = _targets.Select(async target => new + { + target.Id, + Status = await _loadBalancerService.GetTargetHealth(target.Id) + }); - var results = await Task.WhenAll(healthTasks); - _targetHealth = results.ToDictionary(result => result.Id, result => result.Status); + var results = await Task.WhenAll(healthTasks); + _targetHealth = results.ToDictionary(result => result.Id, result => result.Status); + } + catch (Exception ex) + { + _snackbar.Add($"Failed to load target health: {ex.Message}", Severity.Error); + _targetHealth = _targets.ToDictionary(target => target.Id, _ => LoadBalancerStatus.UNKNOWN); + } } } diff --git a/tilework.ui/Mappers/FormMappingProfile.cs b/tilework.ui/Mappers/FormMappingProfile.cs index 0369146..a921b67 100644 --- a/tilework.ui/Mappers/FormMappingProfile.cs +++ b/tilework.ui/Mappers/FormMappingProfile.cs @@ -19,23 +19,10 @@ public FormMappingProfile() CreateMap(); // Load balancers - CreateMap(); - CreateMap() - .ForMember(dest => dest.TargetGroup, opt => opt.MapFrom(src => src.TargetGroup)); + CreateMap(); - CreateMap(); - CreateMap() - .ForMember(dest => dest.TargetGroup, opt => opt.MapFrom(src => src.TargetGroup)); - - CreateMap(); - CreateMap() - .ForMember(dest => dest.TargetGroup, opt => opt.MapFrom(src => src.TargetGroup)); - - CreateMap(); - CreateMap(); - - CreateMap(); - CreateMap(); + CreateMap(); + CreateMap(); // Identity management CreateMap() diff --git a/tilework.ui/Models/LoadBalancing/EditLoadBalancerForm.cs b/tilework.ui/Models/LoadBalancing/EditLoadBalancerForm.cs index edea172..d1060cf 100644 --- a/tilework.ui/Models/LoadBalancing/EditLoadBalancerForm.cs +++ b/tilework.ui/Models/LoadBalancing/EditLoadBalancerForm.cs @@ -1,38 +1,12 @@ using System.ComponentModel.DataAnnotations; -using Tilework.LoadBalancing.Enums; - namespace Tilework.Ui.Models; -public class EditBaseLoadBalancerForm : BaseForm +public class EditLoadBalancerForm : BaseForm { [Required] public string Name { get; set; } [Required] public int? Port { get; set; } - - public LoadBalancerType Type { get; set; } -} - -public class EditNetworkLoadBalancerForm : EditBaseLoadBalancerForm -{ - public EditNetworkLoadBalancerForm() - { - Type = LoadBalancerType.NETWORK; - } - public NlbProtocol Protocol { get; set; } - - [Display(Name = "Target group")] - public Guid TargetGroup { get; set; } } - -public class EditApplicationLoadBalancerForm : EditBaseLoadBalancerForm -{ - public EditApplicationLoadBalancerForm() - { - Type = LoadBalancerType.APPLICATION; - } - - public AlbProtocol Protocol { get; set; } -} \ No newline at end of file diff --git a/tilework.ui/Models/LoadBalancing/NewLoadBalancerForm.cs b/tilework.ui/Models/LoadBalancing/NewLoadBalancerForm.cs index ec5841c..e0d330b 100644 --- a/tilework.ui/Models/LoadBalancing/NewLoadBalancerForm.cs +++ b/tilework.ui/Models/LoadBalancing/NewLoadBalancerForm.cs @@ -4,7 +4,7 @@ namespace Tilework.Ui.Models; -public class NewBaseLoadBalancerForm : BaseForm +public class NewLoadBalancerForm : BaseForm { [Required] public string Name { get; set; } @@ -13,27 +13,5 @@ public class NewBaseLoadBalancerForm : BaseForm public int? Port { get; set; } public LoadBalancerType Type { get; set; } + public LoadBalancerProtocol Protocol { get; set; } } - -public class NewNetworkLoadBalancerForm : NewBaseLoadBalancerForm -{ - public NewNetworkLoadBalancerForm() - { - Type = LoadBalancerType.NETWORK; - } - public NlbProtocol Protocol { get; set; } - - [Required] - [Display(Name = "Target group")] - public Guid? TargetGroup { get; set; } -} - -public class NewApplicationLoadBalancerForm : NewBaseLoadBalancerForm -{ - public NewApplicationLoadBalancerForm() - { - Type = LoadBalancerType.APPLICATION; - } - - public AlbProtocol Protocol { get; set; } -} \ No newline at end of file From 30deffe316bcef98f313eff870efc490a37b5378 Mon Sep 17 00:00:00 2001 From: Alexandros Nikolopoulos Date: Sat, 17 Jan 2026 10:07:32 +0000 Subject: [PATCH 5/9] Added logging of errors --- .../Components/Pages/CertificateManagement/CertificateNew.razor | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tilework.ui/Components/Pages/CertificateManagement/CertificateNew.razor b/tilework.ui/Components/Pages/CertificateManagement/CertificateNew.razor index 88af4e4..db06130 100644 --- a/tilework.ui/Components/Pages/CertificateManagement/CertificateNew.razor +++ b/tilework.ui/Components/Pages/CertificateManagement/CertificateNew.razor @@ -9,6 +9,7 @@ @inject NavigationManager NavigationManager @inject ISnackbar Snackbar @inject ICertificateManagementService CertificateManagementService +@inject ILogger Logger @page "/cm/certificates/new" @@ -50,6 +51,7 @@ } catch(Exception ex) { + Logger.LogError(ex, "Failed to add certificate."); Snackbar.Add($"Failed to add certificate: {ex.Message}", Severity.Error); } } From 32016765c8117a6d90ae398e0b49d46a09a16adc Mon Sep 17 00:00:00 2001 From: Alexandros Nikolopoulos Date: Sat, 17 Jan 2026 10:18:21 +0000 Subject: [PATCH 6/9] Cleanup of acme verification container --- .../CertificateManagement/AcmeVerificationService.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tilework.core/Services/CertificateManagement/AcmeVerificationService.cs b/tilework.core/Services/CertificateManagement/AcmeVerificationService.cs index ed8b6c8..04c1084 100644 --- a/tilework.core/Services/CertificateManagement/AcmeVerificationService.cs +++ b/tilework.core/Services/CertificateManagement/AcmeVerificationService.cs @@ -36,7 +36,7 @@ public AcmeVerificationService(ILogger logger, private async Task CreateContainer(string name, string filename, string fileData) { var container = await _containerManager.CreateContainer( - $"AcmeVerification-{name}", + $"certificatemanagement.acmeverification.{name}", _settings.AcmeVerificationImage, "certificatemanagement.tile", null @@ -67,7 +67,13 @@ await _containerManager.CopyFileToContainer( private async Task DeleteContainer(string name) { var containers = await _containerManager.ListContainers("certificatemanagement.tile"); - var container = containers.First(cnt => cnt.Name == $"AcmeVerification-{name}"); + var container = containers.FirstOrDefault(cnt => cnt.Name == $"certificatemanagement.acmeverification.{name}"); + + if (container == null) + { + _logger.LogWarning("ACME verification container not found for {Name}", name); + return; + } if (container.State == ContainerState.Running) await _containerManager.StopContainer(container.Id); @@ -216,4 +222,4 @@ public async Task StopAllVerifications() await CheckRemoveLoadBalancer(name); } } -} \ No newline at end of file +} From 08fbad015b5f29975c0faa34cbe7119d465d90f4 Mon Sep 17 00:00:00 2001 From: Alexandros Nikolopoulos Date: Sat, 17 Jan 2026 10:47:47 +0000 Subject: [PATCH 7/9] Fixes #44 --- .../AcmeVerificationService.cs | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/tilework.core/Services/CertificateManagement/AcmeVerificationService.cs b/tilework.core/Services/CertificateManagement/AcmeVerificationService.cs index 04c1084..150c42e 100644 --- a/tilework.core/Services/CertificateManagement/AcmeVerificationService.cs +++ b/tilework.core/Services/CertificateManagement/AcmeVerificationService.cs @@ -145,31 +145,39 @@ private async Task AddLoadBalancerTarget(string id, LoadBalancerDTO balancer, st private async Task CheckRemoveLoadBalancer(string certId) { var balancers = await _loadBalancerService.GetLoadBalancers(); - var balancer = balancers.FirstOrDefault(lb => lb.Port == 80); - - if (balancer == null) + if (balancers.Count == 0) return; var targetGroups = await _loadBalancerService.GetTargetGroups(); + var targetGroupName = $"AcmeVerification-{certId}"; + var hasChanges = false; - foreach (var rule in await _loadBalancerService.GetRules(balancer)) + foreach (var balancer in balancers) { - var tg = targetGroups.FirstOrDefault(tg => tg.Id == rule.TargetGroup); - - if (tg != null && tg.Name == $"AcmeVerification-{certId}") + var rules = await _loadBalancerService.GetRules(balancer); + foreach (var rule in rules) { - await _loadBalancerService.RemoveRule(balancer, rule); - await _loadBalancerService.DeleteTargetGroup(tg.Id); + var tg = targetGroups.FirstOrDefault(tg => tg.Id == rule.TargetGroup); + if (tg != null && tg.Name == targetGroupName) + { + await _loadBalancerService.RemoveRule(balancer, rule); + await _loadBalancerService.DeleteTargetGroup(tg.Id); + hasChanges = true; + } } - } - if (balancer.Name == "AcmeVerification" && (await _loadBalancerService.GetRules(balancer)).Count == 0) + foreach (var balancer in balancers.Where(lb => lb.Name == "AcmeVerification")) { - await _loadBalancerService.DeleteLoadBalancer(balancer.Id); + if ((await _loadBalancerService.GetRules(balancer)).Count == 0) + { + await _loadBalancerService.DeleteLoadBalancer(balancer.Id); + hasChanges = true; + } } - await _loadBalancerService.ApplyConfiguration(); + if (hasChanges) + await _loadBalancerService.ApplyConfiguration(); } public async Task StartVerification(string id, string host, string filename, string data) From 7e08cd47ae2cce2dbb029cdd28f29fd78aac57a2 Mon Sep 17 00:00:00 2001 From: Alexandros Nikolopoulos Date: Sun, 18 Jan 2026 08:54:54 +0000 Subject: [PATCH 8/9] In case the token is lost from the vault, retrieve it from the container itself --- .../Influxdb/Influxdb2DataPersistence.cs | 97 ++++++++++++++----- 1 file changed, 72 insertions(+), 25 deletions(-) diff --git a/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs b/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs index 62d4f8a..a6cbee7 100644 --- a/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs +++ b/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; @@ -96,32 +97,32 @@ public async Task Shutdown() private async Task CheckRunSetup() { - var service = await GetApiService(); - for(int i=0; i<5; i++) - { - try - { - var resp = await service.ApiGet("/setup"); - - if(resp.Allowed == true) - { - var container = await GetContainer(_defaultName); - - await _tokenService.DeleteToken(GetFullName(_defaultName)); + var initialized = await IsInitialized(); - await GetAdminToken(); - } - break; - } - catch - { - await Task.Delay(2000); - continue; - } + if(!initialized) + { + await _tokenService.DeleteToken(GetFullName(_defaultName)); + await GetAdminToken(); } } + private async Task IsInitialized() + { + var container = await GetContainer(_defaultName); + var result = await _containerManager.ExecuteContainerCommand( + container.Id, "test -e /etc/influxdb2/influx-configs"); + return result.ExitCode == 0; + } + + private async Task InitializeDatabase(string token, string orgName) + { + var container = await GetContainer(_defaultName); + + var result = await _containerManager.ExecuteContainerCommand( + container.Id, + $"influx setup --username admin --password \"{token}\" --org \"{orgName}\" --bucket tilework --token \"{token}\" --force"); + } private async Task GetApiService() { @@ -139,21 +140,67 @@ private async Task GetHost() private async Task GetAdminToken() { var container = await GetContainer(_defaultName); - var token = await _tokenService.GetToken(GetFullName(_defaultName)); - if(token == null) + if(token != null) + return token; + + var initialized = await IsInitialized(); + if(!initialized) { _logger.LogInformation("Generating a new admin token for influxdb2"); token = TokenService.GenerateToken(16); + await InitializeDatabase(token, _orgName); + await _tokenService.SetToken(GetFullName(_defaultName), token); + return token; + } + else + { + token = await TryRecoverAdminToken(); + await _tokenService.SetToken(GetFullName(_defaultName), token); + return token; + } + } + + private async Task TryRecoverAdminToken() + { + var container = await GetContainer(_defaultName); + + try + { var result = await _containerManager.ExecuteContainerCommand( container.Id, - $"influx setup --username admin --password \"{token}\" --org \"{_orgName}\" --bucket tilework --token \"{token}\" --force"); + "cat /etc/influxdb2/influx-configs"); + + if (result.ExitCode != 0 || string.IsNullOrWhiteSpace(result.Stdout)) + { + _logger.LogWarning("Failed to read influxdb2 config for token recovery: {Error}", result.Stderr); + return null; + } + + var token = ExtractTokenFromConfig(result.Stdout); + if (string.IsNullOrWhiteSpace(token)) + return null; await _tokenService.SetToken(GetFullName(_defaultName), token); + _logger.LogInformation("Recovered admin token from influxdb2 config"); + return token; + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to recover admin token from influxdb2 config"); + return null; } - return token; + } + + private static string? ExtractTokenFromConfig(string config) + { + if (string.IsNullOrWhiteSpace(config)) + return null; + + var match = Regex.Match(config, @"(?i)token\s*[:=]\s*""?([^\s""]+)""?"); + return match.Success ? match.Groups[1].Value : null; } private async Task CheckCreateBucket(string orgName, string bucketName) From 6043d15813d5c8d8fc2308cbc50da2f95e4a2aaf Mon Sep 17 00:00:00 2001 From: Alexandros Nikolopoulos Date: Sun, 18 Jan 2026 16:24:32 +0200 Subject: [PATCH 9/9] Potential fix for pull request finding 'Useless assignment to local variable' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- .../MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs b/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs index a6cbee7..804c7b2 100644 --- a/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs +++ b/tilework.core/Providers/MonitoringProviders/Influxdb/Influxdb2DataPersistence.cs @@ -119,7 +119,7 @@ private async Task InitializeDatabase(string token, string orgName) { var container = await GetContainer(_defaultName); - var result = await _containerManager.ExecuteContainerCommand( + await _containerManager.ExecuteContainerCommand( container.Id, $"influx setup --username admin --password \"{token}\" --org \"{orgName}\" --bucket tilework --token \"{token}\" --force"); }