Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/pages/configuration-files/appsettings.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,18 @@ secrets in development.
}
}
```

## Projects without a csproj file

User secrets require a `UserSecretsId` which is stored in the `.csproj` file. When
`useUserSecrets` is enabled but no `.csproj` file exists next to the `appsettings.json`,
`confix build` writes the resolved configuration to an `appsettings.user.json` file next to the
`appsettings.json` instead of overwriting it. This prevents resolved secrets from ending up in a
file that is checked into source control.

Make sure to add `appsettings.user.json` to your `.gitignore` and to load it in your host, for
example:

```csharp copy
builder.Configuration.AddJsonFile("appsettings.user.json", optional: true, reloadOnChange: true);
```
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public async ValueTask StopAsync()
if (_status is not null)
{
await _status.DisposeAsync();
_status = null;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ public async Task<IReadOnlyList<ConfigurationFile>> GetConfigurationFilesAsync(
output = new FileInfo(Path.Combine(userSecretsFolder.FullName, FileNames.Secrets));
context.Logger.UseUserSecretsConfigurationFile(output);
}
else
{
// without a csproj there is no user secrets id, so we redirect to a sidecar file
// instead of writing the resolved secrets back into the checked in appsettings.json
output = new FileInfo(
Path.Combine(input.Directory!.FullName, FileNames.UserAppSettings));
context.Logger.NoProjectFileForUserSecrets(input.Directory!.FullName, output);
}
}

context.Logger.FoundAppSettingsConfigurationFile(input);
Expand All @@ -72,6 +80,7 @@ file static class FileNames
{
public const string Secrets = "secrets.json";
public const string AppSettings = "appsettings.json";
public const string UserAppSettings = "appsettings.user.json";
}

file static class Log
Expand All @@ -95,6 +104,15 @@ public static void UseUserSecretsConfigurationFile(
console.Debug($"Use user secrets configuration file '{file}'");
}

public static void NoProjectFileForUserSecrets(
this IConsoleLogger console,
string projectDirectory,
FileInfo file)
{
console.Warning(
$"No .csproj file was found in '{projectDirectory}', so user secrets cannot be used. The configuration was written to '{file}' instead of '{FileNames.AppSettings}'. Make sure this file is git ignored and loaded by your host.");
}

public static void ProjectTreatedAsComponentOnly(
this IConsoleLogger console,
string projectDirectory)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System.Text.Json.Nodes;
using Confix.Tool.Commands.Logging;
using Confix.Tool.Common.Pipelines;
using Confix.Tool.Middlewares;
using Confix.Utilities.Json;
using Confix.Variables;
using Spectre.Console;

namespace Confix.Tool.Commands.Variable;

Expand Down Expand Up @@ -38,10 +41,17 @@ private static async Task InvokeAsync(IMiddlewareContext context)
var result = await resolver
.ResolveOrThrowAsync(variablePath, variableContext);

context.Logger.PrintVariableResolved(variablePath, result.ToString());
await context.Status.StopAsync();

context.Logger.PrintVariableResolved(variablePath, ToDisplayValue(result));

context.SetOutput(result);
}

private static string ToDisplayValue(JsonNode result)
=> result is JsonValue value && value.TryGetValue(out string? stringValue)
? stringValue
: result.ToRelaxedJsonString();
}

file static class Log
Expand All @@ -51,6 +61,7 @@ public static void PrintVariableResolved(
VariablePath variablePath,
string value)
{
console.Information($"[green]{variablePath}[/] -> [yellow]{value}[/]");
console.Information(
$"[green]{variablePath.ToString().EscapeMarkup()}[/] -> [yellow]{value.EscapeMarkup()}[/]");
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
Expand Down Expand Up @@ -208,6 +209,20 @@ public static async Task SerializeToStreamAsync(
},
cancellationToken);

public static string ToRelaxedJsonString(this JsonNode node)
{
using var buffer = new MemoryStream();
using (var writer = new Utf8JsonWriter(buffer, _relaxedWriterOptions))
{
node.WriteTo(writer);
}

return Encoding.UTF8.GetString(buffer.GetBuffer(), 0, (int) buffer.Length);
}

private static readonly JsonWriterOptions _relaxedWriterOptions =
new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };

[GeneratedRegex(@"^(?<name>.+?)\[(?<index>\d+)]$")]
private static partial Regex ParseSegmentRegex();
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using Confix.Tool;
Expand All @@ -14,7 +15,8 @@ public sealed class LocalVariableProvider : IVariableProvider
{
private static readonly JsonSerializerOptions _options = new()
{
WriteIndented = true
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};

private readonly Lazy<Dictionary<string, JsonNode?>> _parsedLocalFile;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Text;
using System.Text.Json.Nodes;
using Confix.Tool;
using Confix.Utilities.Json;

namespace Confix.Variables;

Expand Down Expand Up @@ -47,7 +48,7 @@ public Task<IReadOnlyDictionary<string, JsonNode>> ResolveManyAsync(

public Task<string> SetAsync(string path, JsonNode value, IVariableProviderContext context)
{
string valueToEncrypt = value.ToJsonString();
string valueToEncrypt = value.ToRelaxedJsonString();
byte[] bytesToEncrypt = Encoding.UTF8.GetBytes(valueToEncrypt);
byte[] encryptedValue = Encrypt(bytesToEncrypt, _publicKey.Value);

Expand Down
Loading