Skip to content

Commit 71366a6

Browse files
authored
Prepare for running apps and localtest with test containers for integration testing (#136)
* Set ASPNETCORE_URLS=http://*:5101/ in dockerfile * Add proxy middleware so that localtest can be used without nginx "loadbalancer" * Use Request.Host instead of env config for openId-configuration * Improve logging in decicion controller * Use Yarp proxy instead of custom proxy * Add Health checks * Use ILocalApp for all access to applicationmetadata
1 parent f7d98c4 commit 71366a6

File tree

9 files changed

+116
-28
lines changed

9 files changed

+116
-28
lines changed

Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@ COPY ./src .
88
RUN dotnet publish LocalTest.csproj -c Release -o /app_output
99

1010
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine@sha256:0389d5b7d60f75ebbeec3bfffd2ad0a06d234e7b998231a5a86abf5e919a7d01 AS final
11+
ENV ASPNETCORE_URLS=http://*:5101/
1112
EXPOSE 5101
13+
# Create the storage folder if it isn't mapped to a volume runtime
14+
RUN mkdir /AltinnPlatformLocal
1215
WORKDIR /app
1316
COPY --from=build /app_output .
1417

1518
# Copy various data
1619
COPY ./testdata /testdata
20+
HEALTHCHECK --interval=1s --timeout=1s --retries=20 \
21+
CMD wget -nv -t1 --spider 'http://localhost:5101/health' || exit 1
1722

1823
# setup the user and group (not important for LocalTest and this removes write access to /AltinnPlatformLocal)
1924
# RUN addgroup -g 3000 dotnet && adduser -u 1000 -G dotnet -D -s /bin/false dotnet

docker-compose.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ services:
6666
context: .
6767
environment:
6868
- DOTNET_ENVIRONMENT=Docker
69-
- ASPNETCORE_URLS=http://*:5101/
7069
- GeneralSettings__BaseUrl=http://${TEST_DOMAIN:-local.altinn.cloud}:${ALTINN3LOCAL_PORT:-80}
7170
- GeneralSettings__HostName=${TEST_DOMAIN:-local.altinn.cloud}
7271
volumes:

podman-compose.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ services:
6363
context: .
6464
environment:
6565
- DOTNET_ENVIRONMENT=Podman
66-
- ASPNETCORE_URLS=http://*:5101/
6766
- GeneralSettings__BaseUrl=http://${TEST_DOMAIN:-local.altinn.cloud}:${ALTINN3LOCAL_PORT:-8000}
6867
- GeneralSettings__HostName=${TEST_DOMAIN:-local.altinn.cloud}
6968
volumes:

src/Controllers/Authorization/DecisionController.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ private async Task<ActionResult> AuthorizeJsonRequest(XacmlRequestApiModel model
197197
XacmlJsonRequestRoot jsonRequest = (XacmlJsonRequestRoot)JsonConvert.DeserializeObject(model.BodyContent, typeof(XacmlJsonRequestRoot));
198198

199199
XacmlJsonResponse jsonResponse = await Authorize(jsonRequest.Request);
200+
_logger.LogInformation($"Decision: {jsonResponse.Response[0].Decision}");
200201

201202
return Ok(jsonResponse);
202203
}
@@ -210,6 +211,7 @@ private ActionResult CreateResponse(XacmlContextResponse xacmlContextResponse)
210211
}
211212

212213
string xml = builder.ToString();
214+
_logger.LogInformation($"Decision: {xacmlContextResponse}");
213215

214216
return Content(xml);
215217
}

src/Controllers/HomeController.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,6 @@ public async Task<ActionResult> LogInTestUser(string action, StartAppModel start
146146

147147
Application app = await _localApp.GetApplicationMetadata(startAppModel.AppPathSelection);
148148

149-
// Ensure that the documentstorage in LocalTestingStorageBasePath is updated with the most recent app data
150-
await _applicationRepository.Update(app);
151-
152149
if (_localPlatformSettings.LocalAppMode == "http")
153150
{
154151
// Instantiate a prefill if a file attachment exists.

src/Filters/ProxyMiddleware.cs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#nullable enable
2+
using System.Diagnostics;
3+
using System.Net;
4+
using System.Text.RegularExpressions;
5+
using LocalTest.Configuration;
6+
using Microsoft.Extensions.Options;
7+
using Yarp.ReverseProxy.Forwarder;
8+
9+
namespace LocalTest.Filters;
10+
11+
public class ProxyMiddleware
12+
{
13+
private readonly RequestDelegate _nextMiddleware;
14+
private readonly IOptions<LocalPlatformSettings> localPlatformSettings;
15+
private readonly IHttpForwarder _forwarder;
16+
17+
public ProxyMiddleware(
18+
RequestDelegate nextMiddleware,
19+
IOptions<LocalPlatformSettings> localPlatformSettings,
20+
IHttpForwarder forwarder
21+
)
22+
{
23+
_nextMiddleware = nextMiddleware;
24+
this.localPlatformSettings = localPlatformSettings;
25+
_forwarder = forwarder;
26+
}
27+
28+
private static readonly List<Regex> _noProxies =
29+
new()
30+
{
31+
new Regex("^/$"),
32+
new Regex("^/Home/"),
33+
new Regex("^/localtestresources/"),
34+
new Regex("^/LocalPlatformStorage/"),
35+
new Regex("^/authentication/"),
36+
new Regex("^/authorization/"),
37+
new Regex("^/profile/"),
38+
new Regex("^/events/"),
39+
new Regex("^/register/"),
40+
new Regex("^/storage/"),
41+
};
42+
43+
public async Task Invoke(HttpContext context)
44+
{
45+
var path = context.Request.Path.Value;
46+
if (path == null)
47+
{
48+
await _nextMiddleware(context);
49+
return;
50+
}
51+
// TODO: only proxy requests to the actually running app
52+
foreach (var noProxy in _noProxies)
53+
{
54+
if (noProxy.IsMatch(path))
55+
{
56+
await _nextMiddleware(context);
57+
return;
58+
}
59+
}
60+
await ProxyRequest(context, localPlatformSettings.Value.LocalAppUrl);
61+
return;
62+
}
63+
64+
public static HttpMessageInvoker _httpClient = new HttpMessageInvoker(
65+
new SocketsHttpHandler
66+
{
67+
UseProxy = false,
68+
AllowAutoRedirect = false,
69+
AutomaticDecompression = DecompressionMethods.None,
70+
UseCookies = false,
71+
EnableMultipleHttp2Connections = true,
72+
ActivityHeadersPropagator = new ReverseProxyPropagator(
73+
DistributedContextPropagator.Current
74+
),
75+
ConnectTimeout = TimeSpan.FromSeconds(15),
76+
}
77+
);
78+
79+
public async Task ProxyRequest(HttpContext context, string newHost)
80+
{
81+
var error = await _forwarder.SendAsync(context, newHost, _httpClient);
82+
// Check if the operation was successful
83+
if (error != ForwarderError.None)
84+
{
85+
var errorFeature = context.GetForwarderErrorFeature();
86+
throw errorFeature?.Exception ?? new Exception("Forwarder error");
87+
}
88+
}
89+
}

src/LocalTest.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.*" />
2929
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.*" />
3030
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
31+
<PackageReference Include="Yarp.ReverseProxy" Version="2.2.0" />
3132
</ItemGroup>
3233

3334
<ItemGroup>

src/Services/Storage/Implementation/ApplicationRepository.cs

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,20 @@
99
using Altinn.Platform.Storage.Repository;
1010

1111
using LocalTest.Configuration;
12-
12+
using LocalTest.Services.LocalApp.Interface;
1313
using Microsoft.Extensions.Options;
1414

1515
namespace LocalTest.Services.Storage.Implementation
1616
{
1717
public class ApplicationRepository : IApplicationRepository
1818
{
1919
private readonly LocalPlatformSettings _localPlatformSettings;
20+
private readonly ILocalApp _localApp;
2021

21-
public ApplicationRepository(IOptions<LocalPlatformSettings> localPlatformSettings)
22+
public ApplicationRepository(IOptions<LocalPlatformSettings> localPlatformSettings, ILocalApp localApp)
2223
{
2324
_localPlatformSettings = localPlatformSettings.Value;
25+
_localApp = localApp;
2426
}
2527

2628
public Task<Application> Create(Application item)
@@ -35,16 +37,11 @@ public Task<bool> Delete(string appId, string org)
3537

3638
public async Task<Application> FindOne(string appId, string org)
3739
{
38-
var filename = GetApplicationsDirectory() + appId + ".json";
39-
if (File.Exists(filename))
40+
var applications = await _localApp.GetApplications();
41+
if (applications.ContainsKey(appId))
4042
{
41-
var application = JsonSerializer.Deserialize<Application>(await File.ReadAllTextAsync(filename));
42-
if (application is not null)
43-
{
44-
return application;
45-
}
43+
return applications[appId];
4644
}
47-
4845
throw new Exception($"applicationmetadata for '{appId} not found'");
4946
}
5047

@@ -55,13 +52,9 @@ public Task<Dictionary<string, Dictionary<string, string>>> GetAppTitles(List<st
5552

5653
public async Task<List<Application>> FindByOrg(string org)
5754
{
58-
var apps = new List<Application>();
59-
foreach (var app in Directory.GetFiles(GetApplicationsDirectory() + org))
60-
{
61-
apps.Add(JsonSerializer.Deserialize<Application>(await File.ReadAllTextAsync(app)));
62-
}
55+
var applications = await _localApp.GetApplications();
6356

64-
return apps;
57+
return applications.Values.Where(a=>a.Org == org).ToList();
6558
}
6659

6760
public async Task<Application> Update(Application item)
@@ -78,13 +71,8 @@ public async Task<Application> Update(Application item)
7871

7972
public async Task<List<Application>> FindAll()
8073
{
81-
var apps = new List<Application>();
82-
foreach (var org in (new DirectoryInfo(GetApplicationsDirectory()).GetDirectories()))
83-
{
84-
apps.AddRange(await FindByOrg(org.Name));
85-
}
86-
87-
return apps;
74+
var applications = await _localApp.GetApplications();
75+
return applications.Values.ToList();
8876
}
8977

9078
public Task<Dictionary<string, string>> GetAllAppTitles()

src/Startup.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
using AltinnCore.Authentication.JwtCookie;
2727
using LocalTest.Clients.CdnAltinnOrgs;
2828
using LocalTest.Configuration;
29+
using LocalTest.Filters;
2930
using LocalTest.Helpers;
3031
using LocalTest.Notifications.LocalTestNotifications;
3132
using LocalTest.Services.AccessManagement;
@@ -204,6 +205,10 @@ public void ConfigureServices(IServiceCollection services)
204205
}
205206

206207
services.AddTransient<ILocalFrontendService, LocalFrontendService>();
208+
209+
services.AddHttpForwarder();
210+
211+
services.AddHealthChecks();
207212
}
208213

209214
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -225,6 +230,9 @@ public void Configure(
225230
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
226231
app.UseHsts();
227232
}
233+
234+
app.UseHealthChecks("/health");
235+
app.UseMiddleware<ProxyMiddleware>();
228236

229237
var storagePath = new DirectoryInfo(localPlatformSettings.Value.LocalTestingStorageBasePath);
230238
if (!storagePath.Exists)

0 commit comments

Comments
 (0)