Skip to content

Commit 60d495f

Browse files
committed
emit merged configuration instead of emitting a partial configuration that by-default disables the API (which is now default-on)
1 parent dc3ba10 commit 60d495f

File tree

1 file changed

+141
-36
lines changed

1 file changed

+141
-36
lines changed

Intersect.Server/Web/ApiService.AppSettings.cs

+141-36
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System.Diagnostics;
22
using System.Security.Cryptography;
33
using System.Security.Cryptography.X509Certificates;
4+
using System.Text;
45
using Intersect.Core;
6+
using Intersect.Framework.Reflection;
57
using Intersect.Server.Web.Configuration;
68
using Microsoft.AspNetCore.Authentication.JwtBearer;
79
using Microsoft.IdentityModel.Tokens;
@@ -15,58 +17,162 @@ internal partial class ApiService
1517
private const string SelfSignedCertificateName = "self-signed.crt";
1618
private const string SelfSignedKeyName = "self-signed.key";
1719

18-
private static void UnpackAppSettings()
20+
private const string FileNameAppsettingsJson = "appsettings.json";
21+
private static readonly string FileNameAppsettingsEnvironmentJson;
22+
23+
private static readonly JObject? AppsettingsToken;
24+
private static readonly JObject? AppsettingsEnvironmentToken;
25+
26+
static ApiService()
1927
{
20-
ApplicationContext.Context.Value?.Logger.LogInformation("Unpacking appsettings...");
21-
var hostBuilder = Host.CreateApplicationBuilder();
28+
var dummyHostBuilder = Host.CreateApplicationBuilder();
29+
FileNameAppsettingsEnvironmentJson = $"appsettings.{dummyHostBuilder.Environment.EnvironmentName}.json";
2230

23-
var names = new[] { "appsettings.json", $"appsettings.{hostBuilder.Environment.EnvironmentName}.json" };
24-
var manifestResourceNamePairs = Assembly.GetManifestResourceNames()
25-
.Where(mrn => names.Any(mrn.EndsWith))
26-
.Select(mrn => (mrn, names.First(mrn.EndsWith)))
27-
.ToArray();
31+
AppsettingsToken = ParseEmbeddedAppsettings(FileNameAppsettingsJson);
32+
AppsettingsEnvironmentToken = ParseEmbeddedAppsettings(FileNameAppsettingsEnvironmentJson);
33+
}
2834

29-
foreach (var (mrn, name) in manifestResourceNamePairs)
35+
private static JObject? ParseEmbeddedAppsettings(string name)
36+
{
37+
try
3038
{
31-
if (string.IsNullOrWhiteSpace(mrn) || string.IsNullOrWhiteSpace(name))
39+
if (!Assembly.TryFindResource(name, out var resourceName))
3240
{
33-
ApplicationContext.Context.Value?.Logger.LogWarning($"Manifest resource name or file name is null/empty: ({mrn}, {name})");
34-
continue;
41+
ApplicationContext.CurrentContext.Logger.LogWarning("Failed to find '{AppsettingsName}'", name);
42+
return null;
3543
}
3644

37-
if (File.Exists(name))
45+
using var resourceStream = Assembly.GetManifestResourceStream(resourceName);
46+
if (resourceStream == default)
3847
{
39-
ApplicationContext.Context.Value?.Logger.LogDebug($"'{name}' already exists, not unpacking '{mrn}'");
40-
continue;
48+
ApplicationContext.Context.Value?.Logger.LogError(
49+
"Failed to open resource stream for '{ResourceName}'",
50+
resourceName
51+
);
52+
return null;
4153
}
4254

43-
using var mrs = Assembly.GetManifestResourceStream(mrn);
44-
if (mrs == default)
55+
using var reader = new StreamReader(resourceStream, Encoding.UTF8);
56+
var rawJson = reader.ReadToEnd();
57+
if (!string.IsNullOrWhiteSpace(rawJson))
4558
{
46-
ApplicationContext.Context.Value?.Logger.LogWarning($"Unable to open stream for embedded content: '{mrn}'");
47-
continue;
59+
return JObject.Parse(rawJson);
60+
}
61+
62+
ApplicationContext.Context.Value?.Logger.LogError(
63+
"Invalid JSON, embedded resouce was empty or whitespace: '{ResourceName}'",
64+
resourceName
65+
);
66+
return null;
67+
}
68+
catch (Exception exception)
69+
{
70+
ApplicationContext.CurrentContext.Logger.LogError(
71+
exception,
72+
"Exception thrown while loading and parsing '{Name}'",
73+
name
74+
);
75+
return null;
76+
}
77+
}
78+
79+
private static void Dump(string fileName, JObject? token)
80+
{
81+
try
82+
{
83+
if (token == null)
84+
{
85+
ApplicationContext.Context.Value?.Logger.LogDebug(
86+
"Skipping dumping '{FileName}' because the token is null",
87+
fileName
88+
);
89+
return;
4890
}
4991

50-
ApplicationContext.Context.Value?.Logger.LogInformation($"Unpacking '{name}' in {Environment.CurrentDirectory}");
92+
if (File.Exists(FileNameAppsettingsJson))
93+
{
94+
ApplicationContext.Context.Value?.Logger.LogDebug(
95+
"Skipping dumping '{FileName}' because the file already exists",
96+
fileName
97+
);
98+
return;
99+
}
51100

52-
using var fs = File.OpenWrite(name);
53-
mrs.CopyTo(fs);
101+
var rawJson = token.ToString(Formatting.Indented);
102+
File.WriteAllText(fileName, rawJson, Encoding.UTF8);
103+
}
104+
catch (Exception exception)
105+
{
106+
ApplicationContext.CurrentContext.Logger.LogError(
107+
exception,
108+
"Exception thrown while dumping '{FileName}'",
109+
fileName
110+
);
111+
}
112+
}
113+
114+
private static void UnpackAppSettings()
115+
{
116+
ApplicationContext.Context.Value?.Logger.LogInformation("Unpacking appsettings...");
117+
Dump(FileNameAppsettingsJson, AppsettingsToken);
118+
Dump(FileNameAppsettingsEnvironmentJson, AppsettingsEnvironmentToken);
119+
}
120+
121+
private static JObject? LoadOrFallbackTo(FileInfo fileInfo, JObject? fallback)
122+
{
123+
if (!fileInfo.Exists)
124+
{
125+
return fallback;
126+
}
127+
128+
try
129+
{
130+
using var reader = fileInfo.OpenText();
131+
var rawJson = reader.ReadToEnd();
132+
JObject parsedObject = JObject.Parse(rawJson);
133+
return parsedObject;
134+
}
135+
catch (Exception exception)
136+
{
137+
ApplicationContext.CurrentContext.Logger.LogError(
138+
exception,
139+
"Exception thrown while loading and parsing '{Name}' and will return the fallback instead",
140+
Path.GetRelativePath(Environment.CurrentDirectory, fileInfo.FullName)
141+
);
142+
return fallback;
54143
}
55144
}
56145

57146
private static void ValidateConfiguration()
58147
{
59148
var builder = Host.CreateApplicationBuilder();
60149

61-
var environmentAppSettingsFileName = $"appsettings.{builder.Environment.EnvironmentName}.json";
62-
var rawConfiguration = File.ReadAllText(environmentAppSettingsFileName);
63-
if (string.IsNullOrWhiteSpace(rawConfiguration) || rawConfiguration.Trim().Length < 2)
150+
FileInfo genericConfigurationFileInfo = new(FileNameAppsettingsJson);
151+
JObject? genericConfigurationToken = LoadOrFallbackTo(genericConfigurationFileInfo, AppsettingsToken);
152+
153+
FileInfo environmentConfigurationFileInfo = new(FileNameAppsettingsEnvironmentJson);
154+
JObject? environmentConfigurationToken = LoadOrFallbackTo(environmentConfigurationFileInfo, AppsettingsToken);
155+
156+
JsonMergeSettings jsonMergeSettings = new()
157+
{
158+
MergeArrayHandling = MergeArrayHandling.Replace,
159+
MergeNullValueHandling = MergeNullValueHandling.Merge,
160+
PropertyNameComparison = StringComparison.Ordinal,
161+
};
162+
163+
JObject mergedConfiguration = new();
164+
165+
if (genericConfigurationToken is not null)
166+
{
167+
mergedConfiguration.Merge(genericConfigurationToken, jsonMergeSettings);
168+
}
169+
170+
if (environmentConfigurationToken is not null)
64171
{
65-
rawConfiguration = "{}";
172+
mergedConfiguration.Merge(environmentConfigurationToken, jsonMergeSettings);
66173
}
67174

68-
var configurationJObject = JsonConvert.DeserializeObject<JObject>(rawConfiguration);
69-
if (!configurationJObject.TryGetValue("Api", out var apiSectionJToken))
175+
if (!mergedConfiguration.TryGetValue("Api", out var apiSectionJToken))
70176
{
71177
apiSectionJToken = JObject.FromObject(new ApiConfiguration());
72178
}
@@ -103,14 +209,11 @@ private static void ValidateConfiguration()
103209

104210
if (apiConfiguration.StaticFilePaths == default)
105211
{
106-
apiConfiguration.StaticFilePaths = new List<StaticFilePathOptions>
107-
{
108-
new("wwwroot")
109-
};
212+
apiConfiguration.StaticFilePaths = [new StaticFilePathOptions("wwwroot")];
110213
}
111214

112215
var updatedApiConfigurationJObject = JObject.FromObject(apiConfiguration);
113-
configurationJObject["Api"] = updatedApiConfigurationJObject;
216+
mergedConfiguration["Api"] = updatedApiConfigurationJObject;
114217

115218
updatedApiConfigurationJObject[nameof(JwtBearerOptions)] = apiSectionJToken[nameof(JwtBearerOptions)];
116219
var jwtBearerOptionsToken = updatedApiConfigurationJObject[nameof(JwtBearerOptions)];
@@ -155,10 +258,12 @@ out var validIssuerToken
155258
tokenValidationParameters[nameof(TokenValidationParameters.ValidIssuer)] = validIssuerToken;
156259
}
157260

158-
var updatedAppSettingsJson = configurationJObject.ToString();
159-
File.WriteAllText(environmentAppSettingsFileName, updatedAppSettingsJson);
261+
var updatedAppSettingsJson = mergedConfiguration.ToString();
262+
File.WriteAllText(FileNameAppsettingsEnvironmentJson, updatedAppSettingsJson);
263+
264+
// Below this point should be only self-signed certificate generation
160265

161-
if (!configurationJObject.TryGetValue("Kestrel", out var kestrelToken) || kestrelToken is not JObject kestrel)
266+
if (!mergedConfiguration.TryGetValue("Kestrel", out var kestrelToken) || kestrelToken is not JObject kestrel)
162267
{
163268
return;
164269
}

0 commit comments

Comments
 (0)