tools/code/common/Backend.cs (360 lines of code) (raw):

using Azure.Core.Pipeline; using Flurl; using LanguageExt; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; namespace common; public sealed record BackendName : ResourceName, IResourceName<BackendName> { private BackendName(string value) : base(value) { } public static BackendName From(string value) => new(value); } public sealed record BackendsUri : ResourceUri { public required ManagementServiceUri ServiceUri { get; init; } private static string PathSegment { get; } = "backends"; protected override Uri Value => ServiceUri.ToUri().AppendPathSegment(PathSegment).ToUri(); public static BackendsUri From(ManagementServiceUri serviceUri) => new() { ServiceUri = serviceUri }; } public sealed record BackendUri : ResourceUri { public required BackendsUri Parent { get; init; } public required BackendName Name { get; init; } protected override Uri Value => Parent.ToUri().AppendPathSegment(Name.ToString()).ToUri(); public static BackendUri From(BackendName name, ManagementServiceUri serviceUri) => new() { Parent = BackendsUri.From(serviceUri), Name = name }; } public sealed record BackendsDirectory : ResourceDirectory { public required ManagementServiceDirectory ServiceDirectory { get; init; } private static string Name { get; } = "backends"; protected override DirectoryInfo Value => ServiceDirectory.ToDirectoryInfo().GetChildDirectory(Name); public static BackendsDirectory From(ManagementServiceDirectory serviceDirectory) => new() { ServiceDirectory = serviceDirectory }; public static Option<BackendsDirectory> TryParse(DirectoryInfo? directory, ManagementServiceDirectory serviceDirectory) => directory?.Name == Name && directory?.Parent?.FullName == serviceDirectory.ToDirectoryInfo().FullName ? new BackendsDirectory { ServiceDirectory = serviceDirectory } : Option<BackendsDirectory>.None; } public sealed record BackendDirectory : ResourceDirectory { public required BackendsDirectory Parent { get; init; } public required BackendName Name { get; init; } protected override DirectoryInfo Value => Parent.ToDirectoryInfo().GetChildDirectory(Name.Value); public static BackendDirectory From(BackendName name, ManagementServiceDirectory serviceDirectory) => new() { Parent = BackendsDirectory.From(serviceDirectory), Name = name }; public static Option<BackendDirectory> TryParse(DirectoryInfo? directory, ManagementServiceDirectory serviceDirectory) => from parent in BackendsDirectory.TryParse(directory?.Parent, serviceDirectory) let name = BackendName.From(directory!.Name) select new BackendDirectory { Parent = parent, Name = name }; } public sealed record BackendInformationFile : ResourceFile { public required BackendDirectory Parent { get; init; } public static string Name { get; } = "backendInformation.json"; protected override FileInfo Value => Parent.ToDirectoryInfo().GetChildFile(Name); public static BackendInformationFile From(BackendName name, ManagementServiceDirectory serviceDirectory) => new() { Parent = BackendDirectory.From(name, serviceDirectory) }; public static Option<BackendInformationFile> TryParse(FileInfo? file, ManagementServiceDirectory serviceDirectory) => file is not null && file.Name == Name ? from parent in BackendDirectory.TryParse(file.Directory, serviceDirectory) select new BackendInformationFile { Parent = parent } : Option<BackendInformationFile>.None; } public sealed record BackendDto { [JsonPropertyName("properties")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public required BackendContract Properties { get; init; } public record BackendContract { [JsonPropertyName("circuitBreaker")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public BackendCircuitBreaker? CircuitBreaker { get; init; } [JsonPropertyName("credentials")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public BackendCredentialsContract? Credentials { get; init; } [JsonPropertyName("description")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? Description { get; init; } [JsonPropertyName("pool")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Pool? Pool { get; init; } [JsonPropertyName("properties")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public BackendProperties? Properties { get; init; } [JsonPropertyName("protocol")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? Protocol { get; init; } [JsonPropertyName("proxy")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public BackendProxyContract? Proxy { get; init; } [JsonPropertyName("resourceId")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? ResourceId { get; init; } [JsonPropertyName("title")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? Title { get; init; } [JsonPropertyName("tls")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public BackendTlsProperties? Tls { get; init; } [JsonPropertyName("type")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? Type { get; init; } [JsonPropertyName("url")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] #pragma warning disable CA1056 // URI-like properties should not be strings public string? Url { get; init; } #pragma warning restore CA1056 // URI-like properties should not be strings } public record BackendCircuitBreaker { [JsonPropertyName("rules")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public ImmutableArray<CircuitBreakerRule>? Rules { get; init; } } public record CircuitBreakerRule { [JsonPropertyName("acceptRetryAfter")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool? AcceptRetryAfter { get; init; } [JsonPropertyName("failureCondition")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public CircuitBreakerFailureCondition? FailureCondition { get; init; } [JsonPropertyName("name")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? Name { get; init; } [JsonPropertyName("tripDuration")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? TripDuration { get; init; } } public record CircuitBreakerFailureCondition { [JsonPropertyName("count")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public int? Count { get; init; } [JsonPropertyName("errorReasons")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public ImmutableArray<string>? ErrorReasons { get; init; } [JsonPropertyName("interval")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? Interval { get; init; } [JsonPropertyName("percentage")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public int? Percentage { get; init; } [JsonPropertyName("statusCodeRanges")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public ImmutableArray<FailureStatusCodeRange>? StatusCodeRanges { get; init; } } public record FailureStatusCodeRange { [JsonPropertyName("max")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public int? Max { get; init; } [JsonPropertyName("min")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public int? Min { get; init; } } public record BackendCredentialsContract { [JsonPropertyName("authorization")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public BackendAuthorizationHeaderCredentials? Authorization { get; init; } [JsonPropertyName("certificate")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public ImmutableArray<string>? Certificate { get; init; } [JsonPropertyName("certificateIds")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public ImmutableArray<string>? CertificateIds { get; init; } [JsonPropertyName("header")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public JsonObject? Header { get; init; } [JsonPropertyName("query")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public JsonObject? Query { get; init; } } public record BackendAuthorizationHeaderCredentials { [JsonPropertyName("parameter")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? Parameter { get; init; } [JsonPropertyName("scheme")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? Scheme { get; init; } } public record BackendProperties { [JsonPropertyName("serviceFabricCluster")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public BackendServiceFabricClusterProperties? ServiceFabricCluster { get; init; } } public record BackendProxyContract { [JsonPropertyName("password")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? Password { get; init; } [JsonPropertyName("url")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] #pragma warning disable CA1056 // URI-like properties should not be strings public string? Url { get; init; } #pragma warning restore CA1056 // URI-like properties should not be strings [JsonPropertyName("username")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? Username { get; init; } } public record BackendServiceFabricClusterProperties { [JsonPropertyName("clientCertificateId")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? ClientCertificateId { get; init; } [JsonPropertyName("clientCertificatethumbprint")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? ClientCertificateThumbprint { get; init; } [JsonPropertyName("managementEndpoints")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public ImmutableArray<string>? ManagementEndpoints { get; init; } [JsonPropertyName("maxPartitionResolutionRetries")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public int? MaxPartitionResolutionRetries { get; init; } [JsonPropertyName("serverCertificateThumbprints")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public ImmutableArray<string>? ServerCertificateThumbprints { get; init; } [JsonPropertyName("serverX509Names")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public ImmutableArray<X509CertificateName>? ServerX509Names { get; init; } } public record Pool { [JsonPropertyName("services")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public ImmutableArray<BackendPoolItem>? Services { get; init; } } public record BackendPoolItem { [JsonPropertyName("id")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? Id { get; init; } [JsonPropertyName("priority")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public int? Priority { get; init; } [JsonPropertyName("weight")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public int? Weight { get; init; } } public record BackendTlsProperties { [JsonPropertyName("validateCertificateChain")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool? ValidateCertificateChain { get; init; } [JsonPropertyName("validateCertificateName")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool? ValidateCertificateName { get; init; } } public record X509CertificateName { [JsonPropertyName("issuerCertificateThumbprint")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? IssuerCertificateThumbprint { get; init; } [JsonPropertyName("name")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? Name { get; init; } } } public static class BackendModule { public static async ValueTask DeleteAll(this BackendsUri uri, HttpPipeline pipeline, CancellationToken cancellationToken) => await uri.ListNames(pipeline, cancellationToken) .IterParallel(async name => { var backendUri = new BackendUri { Parent = uri, Name = name }; await backendUri.Delete(pipeline, cancellationToken); }, cancellationToken); public static IAsyncEnumerable<BackendName> ListNames(this BackendsUri uri, HttpPipeline pipeline, CancellationToken cancellationToken) => pipeline.ListJsonObjects(uri.ToUri(), cancellationToken) .Select(jsonObject => jsonObject.GetStringProperty("name")) .Select(BackendName.From); public static IAsyncEnumerable<(BackendName Name, BackendDto Dto)> List(this BackendsUri uri, HttpPipeline pipeline, CancellationToken cancellationToken) => uri.ListNames(pipeline, cancellationToken) .SelectAwait(async name => { var backendUri = new BackendUri { Parent = uri, Name = name }; var dto = await backendUri.GetDto(pipeline, cancellationToken); return (name, dto); }); public static async ValueTask<Option<BackendDto>> TryGetDto(this BackendUri uri, HttpPipeline pipeline, CancellationToken cancellationToken) { var contentOption = await pipeline.GetContentOption(uri.ToUri(), cancellationToken); return contentOption.Map(content => content.ToObjectFromJson<BackendDto>()); } public static async ValueTask<BackendDto> GetDto(this BackendUri uri, HttpPipeline pipeline, CancellationToken cancellationToken) { var content = await pipeline.GetContent(uri.ToUri(), cancellationToken); return content.ToObjectFromJson<BackendDto>(); } public static async ValueTask Delete(this BackendUri uri, HttpPipeline pipeline, CancellationToken cancellationToken) => await pipeline.DeleteResource(uri.ToUri(), waitForCompletion: true, cancellationToken); public static async ValueTask PutDto(this BackendUri uri, BackendDto dto, HttpPipeline pipeline, CancellationToken cancellationToken) { var content = BinaryData.FromObjectAsJson(dto); await pipeline.PutContent(uri.ToUri(), content, cancellationToken); } public static IEnumerable<BackendDirectory> ListDirectories(ManagementServiceDirectory serviceDirectory) { var backendsDirectory = BackendsDirectory.From(serviceDirectory); return from backendsDirectoryInfo in backendsDirectory.ToDirectoryInfo().ListDirectories("*") let name = BackendName.From(backendsDirectoryInfo.Name) select new BackendDirectory { Parent = backendsDirectory, Name = name }; } public static IEnumerable<BackendInformationFile> ListInformationFiles(ManagementServiceDirectory serviceDirectory) => from backendDirectory in ListDirectories(serviceDirectory) let informationFile = new BackendInformationFile { Parent = backendDirectory } where informationFile.ToFileInfo().Exists() select informationFile; public static async ValueTask WriteDto(this BackendInformationFile file, BackendDto dto, CancellationToken cancellationToken) { var content = BinaryData.FromObjectAsJson(dto, JsonObjectExtensions.SerializerOptions); await file.ToFileInfo().OverwriteWithBinaryData(content, cancellationToken); } public static async ValueTask<BackendDto> ReadDto(this BackendInformationFile file, CancellationToken cancellationToken) { var content = await file.ToFileInfo().ReadAsBinaryData(cancellationToken); return content.ToObjectFromJson<BackendDto>(); } }