Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #8

Closed
wants to merge 7 commits into from
Closed
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
A smart CLI tool that automates the tedious task of configuring application services in the cloud.

## What it does?
Once executed, tool will hook up to host of `Microsoft.NET.Sdk.Web` application (NET6&7 only), collect all application settings and update your configuration JSON or YAML file.
Once executed, tool will hook up to host of `Microsoft.NET.Sdk.Web` application, collect all application settings and update your configuration JSON or YAML file.

## Getting started
Tool can be installed using the `Nuget package manager` or the `dotnet` CLI.
Expand Down Expand Up @@ -67,12 +67,13 @@ Tool is customisable, take a look at list of all arguments that can be passed. E
| -a | --assembly | :x: | Startup assembly file path and name. Can be obtained by `$(OutputPath)\$(AssemblyName).dll` | |
| -p | --providers | :heavy_check_mark: | A list of configuration providers from which all setting keys are taken. All types derived from `IConfigurationProvider` | `JsonConfigurationProvider` |
| -i | --include | :heavy_check_mark: | A list of keys to include despite of providers list configuration. Example: `PROCESSOR_ARCHITECTURE` | |
| -x | --exclude | :heavy_check_mark: | A list of keys to exclude from all collected settings. Example: `Logging:LogLevel:Microsoft.AspNetCore` | |
| -x | --exclude | :heavy_check_mark: | A list of keys to exclude from all collected settings. Example: `Logging:LogLevel:Microsoft.AspNetCore` will remove exact key, `Logging:LogLevel` will remove also all nested keys | |
| -s | --separator | :heavy_check_mark: | Setting nesting separator | `__` (double underscore) |
| -v | ‑‑yaml‑variable‑name | :heavy_check_mark: | YAML variable name | `app_config` |
| -y | --to-yaml | :heavy_check_mark: | Indicates whether configuration wrapped in YAML Azure DevOps variables file | `false` |
| -f | --file-path-template | :heavy_check_mark: | File name template for output. Template may contain a placeholder for environment name. Example: `configuration.{0}.json` | `./configuration.json` |
| -e | --environments | :heavy_check_mark: | A list of environment names. Separate configuration file will be created per each environment. Required when file name template contains placeholder | |
| -l | -eol | :heavy_check_mark: | Customize end-of-line characters, possible values: `Cr`, `CrLf` or `Lf`, when skipped, operating system default end-of-line characters will be used | |

### Configuration providers
If you'd like to include all the settings collected by different configuration provider, add them all by `--providers` option. More information about [configuration providers](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration-providers).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"antyrama.tools.scribe.cli": {
"version": "0.0.3-rc",
"version": "0.0.5",
"commands": [
"app-settings-to-file"
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
Expand All @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Antyrama.Tools.Scribe.Core" Version="0.0.3-rc" />
<PackageReference Include="Antyrama.Tools.Scribe.Core" Version="0.0.5" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion examples/src/ExampleWebAppSimple/.config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"antyrama.tools.scribe.cli": {
"version": "0.0.3-rc",
"version": "0.0.5",
"commands": [
"app-settings-to-file"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Antyrama.Tools.Scribe.Core" Version="0.0.3-rc" />
<PackageReference Include="Antyrama.Tools.Scribe.Core" Version="0.0.5" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<RepositoryType>git</RepositoryType>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<UseAppHost>false</UseAppHost>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
Expand All @@ -26,4 +27,8 @@
<ItemGroup>
<ProjectReference Include="..\Antyrama.Tools.Scribe.Core\Antyrama.Tools.Scribe.Core.csproj" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<Description>Core assembly to ensure all CLI tool dependencies are satisfied</Description>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>configuration;appSettings;webapi;aspnet;aspnetcore</PackageTags>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -24,8 +25,12 @@
<PackageReference Include="System.Collections" Version="4.3.0" />
<PackageReference Include="System.Runtime" Version="4.3.1" />
<PackageReference Include="YamlDotNet.NetCore" Version="1.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>UnitTests</_Parameter1>
Expand Down
55 changes: 42 additions & 13 deletions src/Antyrama.Tools.Scribe.Core/AppServiceConfigurationGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,32 @@ public AppServiceConfigurationGenerator(IServiceProvider serviceProvider,

public void Generate()
{
var desiredSettings = CollectSettings();

var repository = CreateRepositoryInstance();

ProcessConfigurationFiles(repository, desiredSettings);
}

private void ProcessConfigurationFiles(ConfigurationRepository repository,
Dictionary<string, string> desiredSettings)
{
foreach (var filename in GetConfigurationFiles(_options))
{
var currentSettings = Load(repository, filename);

var newSettings = MatchSettings(desiredSettings, currentSettings);

Save(repository, filename, newSettings);
}
}

private Dictionary<string, string> CollectSettings()
{
var excludeKeys = _options.ExcludeKeys
.Select(x => x.Replace(":", _options.PathSeparator))
.ToArray();

var configuration = (IConfiguration)_serviceProvider.GetService(typeof(IConfiguration));

var includeKeys = new IncludeKeysGenerator(configuration)
Expand All @@ -31,24 +57,21 @@ public void Generate()

var desiredSettings = allSettings
.IntersectBy(includeKeys, setting => setting.Key)
.ExceptBy(_options.ExcludeKeys, setting => setting.Key)
.ToDictionary(s => s.Key, s => s.Value);

var repository = _options.WrapInYaml
? (ConfigurationRepository)new YamlConfigurationRepository(_options.YamlVariableName)
: new JsonConfigurationRepository();
var toRemove = excludeKeys.Length > 0
? desiredSettings.Keys.Where(s => excludeKeys.Any(s.StartsWith))
: Array.Empty<string>();

foreach (var filename in GetConfigurationFiles(_options))
foreach (var key in toRemove)
{
var currentSettings = Load(repository, filename);

var newSettings = MatchSettings(desiredSettings, currentSettings);

Save(repository, filename, newSettings);
desiredSettings.Remove(key);
}

return desiredSettings;
}

private IReadOnlyDictionary<string, IReadOnlyDictionary<string, object>> Load(IConfigurationRepository repository, string filename)
private static IReadOnlyDictionary<string, IReadOnlyDictionary<string, object>> Load(IConfigurationRepository repository, string filename)
{
try
{
Expand All @@ -62,7 +85,7 @@ private IReadOnlyDictionary<string, IReadOnlyDictionary<string, object>> Load(IC
}
}

private void Save(IConfigurationRepository repository, string filename, IEnumerable<IReadOnlyDictionary<string, object>> settings)
private static void Save(IConfigurationRepository repository, string filename, IEnumerable<IReadOnlyDictionary<string, object>> settings)
{
using var stream = new FileStream(filename, FileMode.Create, FileAccess.Write);

Expand Down Expand Up @@ -119,6 +142,12 @@ private IEnumerable<string> GetConfigurationFiles(ToolInternalOptions options)
}

throw new InvalidOperationException("File path template must contain '{0}' as environment placeholder when environments specified.");

}

private ConfigurationRepository CreateRepositoryInstance() =>
_options.WrapInYaml switch
{
true => new YamlConfigurationRepository(_options),
false => new JsonConfigurationRepository(_options)
};
}
2 changes: 1 addition & 1 deletion src/Antyrama.Tools.Scribe.Core/ConfigurationCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public IReadOnlyList<KeyValuePair<string, string>> Collect(string pathSeparator)
return settings;
}

private void Collect(IConfigurationSection section, ICollection<KeyValuePair<string, string>> settings,
private static void Collect(IConfigurationSection section, ICollection<KeyValuePair<string, string>> settings,
string pathSeparator)
{
if (section == null || string.IsNullOrEmpty(section.Key))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.IO;
using System.Linq;
using Antyrama.Tools.Scribe.Core.Extensions;
using Newtonsoft.Json;
using System.Text.Json;

namespace Antyrama.Tools.Scribe.Core.Repository;

Expand All @@ -15,26 +15,33 @@ internal interface IConfigurationRepository

internal abstract class ConfigurationRepository : IConfigurationRepository
{
protected readonly string Eol;

protected ConfigurationRepository(ToolInternalOptions options)
{
Eol = ResolveEndOfLine(options);
}

public abstract IReadOnlyDictionary<string, object>[] Load(Stream stream);

public abstract void Save(Stream stream, IEnumerable<IReadOnlyDictionary<string, object>> settings);

protected static string Serialize(IEnumerable<IReadOnlyDictionary<string, object>> settings)
protected string Serialize(IEnumerable<IReadOnlyDictionary<string, object>> settings)
{
var serialized = settings.Select(setting => $" {JsonConvert.SerializeObject(setting)}");
var serialized = settings.Select(setting => $" {JsonSerializer.Serialize(setting)}");

var separator = $",{Environment.NewLine}";
var separator = $",{Eol}";
var formatted = string.Join(separator, serialized)
.BeautifyJson();

return $"[{Environment.NewLine}{formatted}{Environment.NewLine}]";
return $"[{Eol}{formatted}{Eol}]";
}

protected static IReadOnlyDictionary<string, object>[] Deserialize(string settings)
{
try
{
var deserialized = JsonConvert.DeserializeObject<IReadOnlyDictionary<string, object>[]>(settings);
var deserialized = JsonSerializer.Deserialize<IReadOnlyDictionary<string, object>[]>(settings);

return deserialized ?? Array.Empty<IReadOnlyDictionary<string, object>>();
}
Expand All @@ -43,4 +50,20 @@ protected static IReadOnlyDictionary<string, object>[] Deserialize(string settin
return Array.Empty<IReadOnlyDictionary<string, object>>();
}
}

private static string ResolveEndOfLine(ToolInternalOptions options)
{
return options.Eol switch
{
EndOfLine.Cr => Cr,
EndOfLine.CrLf => CrLf,
EndOfLine.Lf => Lf,
null => Environment.NewLine,
_ => throw new ArgumentOutOfRangeException(nameof(options))
};
}

private const string Cr = "\r";
private const string CrLf = "\r\n";
private const string Lf = "\n";
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;

namespace Antyrama.Tools.Scribe.Core.Repository;

internal class JsonConfigurationRepository : ConfigurationRepository
{
public JsonConfigurationRepository(ToolInternalOptions options) : base(options)
{
}

public override IReadOnlyDictionary<string, object>[] Load(Stream stream)
{
try
{
var reader = new StreamReader(stream);
var json = reader.ReadToEnd();
var reader = new StreamReader(stream);
var json = reader.ReadToEnd();

return Deserialize(json);
}
catch (FileNotFoundException)
{
return Array.Empty<IReadOnlyDictionary<string, object>>();
}
return Deserialize(json);
}

public override void Save(Stream stream, IEnumerable<IReadOnlyDictionary<string, object>> settings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,25 @@ internal class YamlConfigurationRepository : ConfigurationRepository
private readonly Serializer _serializer;
private readonly Deserializer _deserializer;

public YamlConfigurationRepository(string variableName)
public YamlConfigurationRepository(ToolInternalOptions options)
: base(options)
{
_serializer = new SerializerBuilder()
.WithAttributeOverride<Variables>(variables => variables.AppConfig,
new YamlMemberAttribute { Alias = variableName, ScalarStyle = ScalarStyle.Literal })
new YamlMemberAttribute { Alias = options.YamlVariableName, ScalarStyle = ScalarStyle.Literal })
.Build();
_deserializer = new DeserializerBuilder()
.WithAttributeOverride<Variables>(variables => variables.AppConfig,
new YamlMemberAttribute { Alias = variableName, ScalarStyle = ScalarStyle.Literal })
new YamlMemberAttribute { Alias = options.YamlVariableName, ScalarStyle = ScalarStyle.Literal })
.Build();
}

public override IReadOnlyDictionary<string, object>[] Load(Stream stream)
{
var reader = new StreamReader(stream);

try
{
var reader = new StreamReader(stream);

var variablesRoot = _deserializer.Deserialize<Root>(reader);

if (variablesRoot?.Variables == null || string.IsNullOrWhiteSpace(variablesRoot.Variables.AppConfig))
Expand All @@ -39,7 +40,7 @@ public override IReadOnlyDictionary<string, object>[] Load(Stream stream)

return Deserialize(variablesRoot.Variables.AppConfig);
}
catch (FileNotFoundException)
catch (YamlException)
{
return Array.Empty<IReadOnlyDictionary<string, object>>();
}
Expand All @@ -55,8 +56,15 @@ public override void Save(Stream stream, IEnumerable<IReadOnlyDictionary<string,
}
};

var yaml = _serializer.Serialize(root);

if (Environment.NewLine != Eol)
{
yaml = yaml.Replace(Environment.NewLine, Eol);
}

var writer = new StreamWriter(stream);
_serializer.Serialize(writer, root);
writer.Write(yaml);
writer.Flush();
}
}
15 changes: 15 additions & 0 deletions src/Antyrama.Tools.Scribe.Core/ToolOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ public class ToolOptions
[Option('e', "environments", Required = false, Default = new string[0],
HelpText = "A list of environment names. Separate configuration file will be created per each environment. Required when file name template contains placeholder.")]
public IEnumerable<string> EnvironmentsList { get; set; }

[Option('l', "eol", Required = false,
HelpText = "End-of-line delimiter, possible values: Cr, CrLf or Lf, by default operating system end-of-line will be used.")]
public EndOfLine? Eol { get; set; }
}

[Verb("_generate", Hidden = true)]
Expand Down Expand Up @@ -75,4 +79,15 @@ public class ToolInternalOptions
[Option('e', "environments", Required = false, Default = new string[0],
HelpText = "A list of environment names. Separate configuration file will be created per each environment. Required when file name template contains placeholder.")]
public IEnumerable<string> Environments { get; set; }

[Option('l', "eol", Required = false,
HelpText = "End-of-line delimiter, possible values: Cr, CrLf or Lf, by default operating system end-of-line will be used.")]
public EndOfLine? Eol { get; set; }
}

public enum EndOfLine
{
Cr,
CrLf,
Lf
}
Loading
Loading