Skip to content

Commit

Permalink
Ensure consistent line endings and simplify SQL Server retry logic (#479
Browse files Browse the repository at this point in the history
)

### Summary & Motivation

Ensure that C# files always use CRLF line endings for consistency across
Windows and Mac environments.

Clean up code with minor adjustments primarily in the Developer CLI.

Wait for up to 10 seconds when generating `index.html` to ensure
availability when the SPA is starting. This prevents failures when the
API reads the file during startup.

Simplify retry logic for handling when SQL Server is still starting on
localhost. This change passes error codes to the SQL execution strategy,
eliminating the need for try/catch blocks.

Configure Rider and ReSharper to clean up code when saving, ensuring
files are always formatted correctly.

Upgrade JetBrains .NET tools to the latest version by updating
`dotnet-tools.json`, aligning with the latest Rider and ReSharper
versions.

Fix warning when running bash script on a new Azure subscription without
the `Microsoft.ContainerService` provider registered, addressing a minor
issue during PlatformPlatform setup on a new Azure subscription.

### Checklist

- [x] I have added a Label to the pull-request
- [x] I have added tests, and done manual regression tests
- [x] I have updated the documentation, if necessary
  • Loading branch information
tjementum authored Jun 11, 2024
2 parents 3a32b01 + 7b61f1b commit d14636e
Show file tree
Hide file tree
Showing 171 changed files with 7,280 additions and 7,291 deletions.
3 changes: 3 additions & 0 deletions application/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ indent_style = space
indent_size = 4
tab_width = 4

[*.cs]
end_of_line = crlf

[*.{ts,tsx,js,jsx,json,md,mdx,.prettierrc,.eslintrc,yml,Dockerfile}]
indent_size = 2
tab_width = 2
Expand Down
78 changes: 39 additions & 39 deletions application/AppGateway/Filters/ClusterDestinationConfigFilter.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
using Yarp.ReverseProxy.Configuration;

namespace PlatformPlatform.AppGateway.Filters;

public class ClusterDestinationConfigFilter : IProxyConfigFilter
{
public ValueTask<ClusterConfig> ConfigureClusterAsync(ClusterConfig cluster, CancellationToken cancel)
{
return cluster.ClusterId switch
{
"account-management-api" => ReplaceDestinationAddress(cluster, "ACCOUNT_MANAGEMENT_API_URL"),
"avatars-storage" => ReplaceDestinationAddress(cluster, "AVATARS_STORAGE_URL"),
"back-office-api" => ReplaceDestinationAddress(cluster, "BACK_OFFICE_API_URL"),
_ => throw new InvalidOperationException($"Unknown Cluster ID {cluster.ClusterId}")
};
}

public ValueTask<RouteConfig> ConfigureRouteAsync(RouteConfig route, ClusterConfig? cluster, CancellationToken cancel)
{
return new ValueTask<RouteConfig>(route);
}

private static ValueTask<ClusterConfig> ReplaceDestinationAddress(ClusterConfig cluster, string environmentVariable)
{
var destinationAddress = Environment.GetEnvironmentVariable(environmentVariable);
if (destinationAddress is null) return new ValueTask<ClusterConfig>(cluster);

// Each cluster has a dictionary with one and only one destination
var destination = cluster.Destinations!.Single();

// This is read-only, so we'll create a new one with our updates
var newDestinations = new Dictionary<string, DestinationConfig>(StringComparer.OrdinalIgnoreCase)
{
{ destination.Key, destination.Value with { Address = destinationAddress } }
};

return new ValueTask<ClusterConfig>(cluster with { Destinations = newDestinations });
}
}
using Yarp.ReverseProxy.Configuration;

namespace PlatformPlatform.AppGateway.Filters;

public class ClusterDestinationConfigFilter : IProxyConfigFilter
{
public ValueTask<ClusterConfig> ConfigureClusterAsync(ClusterConfig cluster, CancellationToken cancel)
{
return cluster.ClusterId switch
{
"account-management-api" => ReplaceDestinationAddress(cluster, "ACCOUNT_MANAGEMENT_API_URL"),
"avatars-storage" => ReplaceDestinationAddress(cluster, "AVATARS_STORAGE_URL"),
"back-office-api" => ReplaceDestinationAddress(cluster, "BACK_OFFICE_API_URL"),
_ => throw new InvalidOperationException($"Unknown Cluster ID {cluster.ClusterId}")
};
}

public ValueTask<RouteConfig> ConfigureRouteAsync(RouteConfig route, ClusterConfig? cluster, CancellationToken cancel)
{
return new ValueTask<RouteConfig>(route);
}

private static ValueTask<ClusterConfig> ReplaceDestinationAddress(ClusterConfig cluster, string environmentVariable)
{
var destinationAddress = Environment.GetEnvironmentVariable(environmentVariable);
if (destinationAddress is null) return new ValueTask<ClusterConfig>(cluster);

// Each cluster has a dictionary with one and only one destination
var destination = cluster.Destinations!.Single();

// This is read-only, so we'll create a new one with our updates
var newDestinations = new Dictionary<string, DestinationConfig>(StringComparer.OrdinalIgnoreCase)
{
{ destination.Key, destination.Value with { Address = destinationAddress } }
};

return new ValueTask<ClusterConfig>(cluster with { Destinations = newDestinations });
}
}
102 changes: 51 additions & 51 deletions application/AppGateway/Program.cs
Original file line number Diff line number Diff line change
@@ -1,51 +1,51 @@
using Azure.Core;
using PlatformPlatform.AppGateway.Filters;
using PlatformPlatform.AppGateway.Transformations;
using PlatformPlatform.SharedKernel.InfrastructureCore;

var builder = WebApplication.CreateBuilder(args);

var reverseProxyBuilder = builder.Services
.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.AddConfigFilter<ClusterDestinationConfigFilter>();

if (InfrastructureCoreConfiguration.IsRunningInAzure)
{
builder.Services.AddSingleton<TokenCredential>(InfrastructureCoreConfiguration.GetDefaultAzureCredential());
builder.Services.AddSingleton<ManagedIdentityTransform>();
builder.Services.AddSingleton<ApiVersionHeaderTransform>();
reverseProxyBuilder.AddTransforms(context =>
{
context.RequestTransforms.Add(context.Services.GetRequiredService<ManagedIdentityTransform>());
context.RequestTransforms.Add(context.Services.GetRequiredService<ApiVersionHeaderTransform>());
}
);
}
else
{
builder.Services.AddSingleton<SharedAccessSignatureRequestTransform>();
reverseProxyBuilder.AddTransforms(context =>
context.RequestTransforms.Add(context.Services.GetRequiredService<SharedAccessSignatureRequestTransform>())
);
}

builder.Services.AddNamedBlobStorages(builder, ("avatars-storage", "AVATARS_STORAGE_URL"));

builder.WebHost.UseKestrel(option => option.AddServerHeader = false);

var app = builder.Build();

// Adds middleware for redirecting HTTP Requests to HTTPS
app.UseHttpsRedirection();

if (!app.Environment.IsDevelopment())
{
// Adds middleware for using HSTS, which adds the Strict-Transport-Security header
// Defaults to 30 days. See https://aka.ms/aspnetcore-hsts, so be careful during development
app.UseHsts();
}

app.MapReverseProxy();

app.Run();
using Azure.Core;
using PlatformPlatform.AppGateway.Filters;
using PlatformPlatform.AppGateway.Transformations;
using PlatformPlatform.SharedKernel.InfrastructureCore;

var builder = WebApplication.CreateBuilder(args);

var reverseProxyBuilder = builder.Services
.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.AddConfigFilter<ClusterDestinationConfigFilter>();

if (InfrastructureCoreConfiguration.IsRunningInAzure)
{
builder.Services.AddSingleton<TokenCredential>(InfrastructureCoreConfiguration.GetDefaultAzureCredential());
builder.Services.AddSingleton<ManagedIdentityTransform>();
builder.Services.AddSingleton<ApiVersionHeaderTransform>();
reverseProxyBuilder.AddTransforms(context =>
{
context.RequestTransforms.Add(context.Services.GetRequiredService<ManagedIdentityTransform>());
context.RequestTransforms.Add(context.Services.GetRequiredService<ApiVersionHeaderTransform>());
}
);
}
else
{
builder.Services.AddSingleton<SharedAccessSignatureRequestTransform>();
reverseProxyBuilder.AddTransforms(context =>
context.RequestTransforms.Add(context.Services.GetRequiredService<SharedAccessSignatureRequestTransform>())
);
}

builder.Services.AddNamedBlobStorages(builder, ("avatars-storage", "AVATARS_STORAGE_URL"));

builder.WebHost.UseKestrel(option => option.AddServerHeader = false);

var app = builder.Build();

// Adds middleware for redirecting HTTP Requests to HTTPS
app.UseHttpsRedirection();

if (!app.Environment.IsDevelopment())
{
// Adds middleware for using HSTS, which adds the Strict-Transport-Security header
// Defaults to 30 days. See https://aka.ms/aspnetcore-hsts, so be careful during development
app.UseHsts();
}

app.MapReverseProxy();

app.Run();
54 changes: 27 additions & 27 deletions application/AppGateway/Transformations/ManagedIdentityTransform.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
using Azure.Core;
using Yarp.ReverseProxy.Transforms;

namespace PlatformPlatform.AppGateway.Transformations;

public class ManagedIdentityTransform(TokenCredential credential)
: RequestHeaderTransform("Authorization", false)
{
protected override string? GetValue(RequestTransformContext context)
{
if (!context.HttpContext.Request.Path.StartsWithSegments("/avatars")) return null;

var tokenRequestContext = new TokenRequestContext(["https://storage.azure.com/.default"]);
var token = credential.GetToken(tokenRequestContext, context.HttpContext.RequestAborted);
return $"Bearer {token.Token}";
}
}

public class ApiVersionHeaderTransform() : RequestHeaderTransform("x-ms-version", false)
{
protected override string? GetValue(RequestTransformContext context)
{
if (!context.HttpContext.Request.Path.StartsWithSegments("/avatars")) return null;

return "2023-11-03";
}
}
using Azure.Core;
using Yarp.ReverseProxy.Transforms;

namespace PlatformPlatform.AppGateway.Transformations;

public class ManagedIdentityTransform(TokenCredential credential)
: RequestHeaderTransform("Authorization", false)
{
protected override string? GetValue(RequestTransformContext context)
{
if (!context.HttpContext.Request.Path.StartsWithSegments("/avatars")) return null;

var tokenRequestContext = new TokenRequestContext(["https://storage.azure.com/.default"]);
var token = credential.GetToken(tokenRequestContext, context.HttpContext.RequestAborted);
return $"Bearer {token.Token}";
}
}

public class ApiVersionHeaderTransform() : RequestHeaderTransform("x-ms-version", false)
{
protected override string? GetValue(RequestTransformContext context)
{
if (!context.HttpContext.Request.Path.StartsWithSegments("/avatars")) return null;

return "2023-11-03";
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
using PlatformPlatform.SharedKernel.ApplicationCore.Services;
using Yarp.ReverseProxy.Transforms;

namespace PlatformPlatform.AppGateway.Transformations;

public class SharedAccessSignatureRequestTransform([FromKeyedServices("avatars-storage")] IBlobStorage blobStorage)
: RequestTransform
{
public override ValueTask ApplyAsync(RequestTransformContext context)
{
if (!context.Path.StartsWithSegments("/avatars")) return ValueTask.CompletedTask;

var sharedAccessSignature = blobStorage.GetSharedAccessSignature("avatars", TimeSpan.FromMinutes(10));
context.HttpContext.Request.QueryString = new QueryString(sharedAccessSignature);

return ValueTask.CompletedTask;
}
}
using PlatformPlatform.SharedKernel.ApplicationCore.Services;
using Yarp.ReverseProxy.Transforms;

namespace PlatformPlatform.AppGateway.Transformations;

public class SharedAccessSignatureRequestTransform([FromKeyedServices("avatars-storage")] IBlobStorage blobStorage)
: RequestTransform
{
public override ValueTask ApplyAsync(RequestTransformContext context)
{
if (!context.Path.StartsWithSegments("/avatars")) return ValueTask.CompletedTask;

var sharedAccessSignature = blobStorage.GetSharedAccessSignature("avatars", TimeSpan.FromMinutes(10));
context.HttpContext.Request.QueryString = new QueryString(sharedAccessSignature);

return ValueTask.CompletedTask;
}
}
Loading

0 comments on commit d14636e

Please sign in to comment.