Create initial prototype of dotnet-user-secrets with MSBuild support

This commit is contained in:
Nate McMaster 2016-10-07 09:56:08 -07:00 committed by Nate McMaster
parent 9744f94b39
commit 29df59b89c
20 changed files with 649 additions and 245 deletions

1
.gitignore vendored
View File

@ -28,6 +28,7 @@ project.lock.json
.testPublish/ .testPublish/
.build/ .build/
/.vs/ /.vs/
.vscode/
testWorkDir/ testWorkDir/
*.nuget.props *.nuget.props
*.nuget.targets *.nuget.targets

View File

@ -0,0 +1,6 @@
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(Project)" />
<Target Name="_FindUserSecretsProperty">
<WriteLinesToFile File="$(OutputFile)" Lines="$(UserSecretsId)" />
</Target>
</Project>

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection; using System.Reflection;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.CommandLineUtils;
namespace Microsoft.Extensions.SecretManager.Tools.Internal namespace Microsoft.Extensions.SecretManager.Tools.Internal
@ -13,6 +14,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
public bool IsHelp { get; set; } public bool IsHelp { get; set; }
public string Project { get; set; } public string Project { get; set; }
public ICommand Command { get; set; } public ICommand Command { get; set; }
public string Configuration { get; set; }
public static CommandLineOptions Parse(string[] args, IConsole console) public static CommandLineOptions Parse(string[] args, IConsole console)
{ {
@ -34,6 +36,9 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
var optionProject = app.Option("-p|--project <PROJECT>", "Path to project, default is current directory", var optionProject = app.Option("-p|--project <PROJECT>", "Path to project, default is current directory",
CommandOptionType.SingleValue, inherited: true); CommandOptionType.SingleValue, inherited: true);
var optionConfig = app.Option("-c|--configuration <CONFIGURATION>", $"The project configuration to use. Defaults to {Constants.DefaultConfiguration}",
CommandOptionType.SingleValue, inherited: true);
// the escape hatch if project evaluation fails, or if users want to alter a secret store other than the one // the escape hatch if project evaluation fails, or if users want to alter a secret store other than the one
// in the current project // in the current project
var optionId = app.Option("--id", "The user secret id to use.", var optionId = app.Option("--id", "The user secret id to use.",
@ -55,6 +60,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
return null; return null;
} }
options.Configuration = optionConfig.Value();
options.Id = optionId.Value(); options.Id = optionId.Value();
options.IsHelp = app.IsShowingInformation; options.IsHelp = app.IsShowingInformation;
options.IsVerbose = optionVerbose.HasValue(); options.IsVerbose = optionVerbose.HasValue();

View File

@ -1,25 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.Extensions.SecretManager.Tools.Internal
{
/// <summary>
/// An exception whose stack trace should be suppressed in console output
/// </summary>
public class GracefulException : Exception
{
public GracefulException()
{
}
public GracefulException(string message) : base(message)
{
}
public GracefulException(string message, Exception innerException) : base(message, innerException)
{
}
}
}

View File

@ -0,0 +1,61 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Linq;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.Extensions.SecretManager.Tools.Internal
{
internal class MsBuildProjectFinder
{
private readonly string _directory;
public MsBuildProjectFinder(string directory)
{
if (string.IsNullOrEmpty(directory))
{
throw new ArgumentException(Resources.Common_StringNullOrEmpty, nameof(directory));
}
_directory = directory;
}
public string FindMsBuildProject(string project)
{
var projectPath = project ?? _directory;
if (!Path.IsPathRooted(projectPath))
{
projectPath = Path.Combine(_directory, projectPath);
}
if (Directory.Exists(projectPath))
{
var projects = Directory.EnumerateFileSystemEntries(projectPath, "*.*proj", SearchOption.TopDirectoryOnly)
.Where(f => !".xproj".Equals(Path.GetExtension(f), StringComparison.OrdinalIgnoreCase))
.ToList();
if (projects.Count > 1)
{
throw new GracefulException(Resources.FormatError_MultipleProjectsFound(projectPath));
}
if (projects.Count == 0)
{
throw new GracefulException(Resources.FormatError_NoProjectsFound(projectPath));
}
return projects[0];
}
if (!File.Exists(projectPath))
{
throw new GracefulException(Resources.FormatError_ProjectPath_NotFound(projectPath));
}
return projectPath;
}
}
}

View File

@ -0,0 +1,130 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.Logging;
namespace Microsoft.Extensions.SecretManager.Tools.Internal
{
public class ProjectIdResolver : IDisposable
{
private const string TargetsFileName = "FindUserSecretsProperty.targets";
private readonly ILogger _logger;
private readonly string _workingDirectory;
private readonly List<string> _tempFiles = new List<string>();
public ProjectIdResolver(ILogger logger, string workingDirectory)
{
_workingDirectory = workingDirectory;
_logger = logger;
}
public string Resolve(string project, string configuration = Constants.DefaultConfiguration)
{
var finder = new MsBuildProjectFinder(_workingDirectory);
var projectFile = finder.FindMsBuildProject(project);
_logger.LogDebug(Resources.Message_Project_File_Path, projectFile);
var targetFile = GetTargetFile();
var outputFile = Path.GetTempFileName();
_tempFiles.Add(outputFile);
var commandOutput = new List<string>();
var commandResult = Command.CreateDotNet("msbuild",
new[] {
targetFile,
"/nologo",
"/t:_FindUserSecretsProperty",
$"/p:Project={projectFile}",
$"/p:OutputFile={outputFile}",
$"/p:Configuration={configuration}"
})
.CaptureStdErr()
.CaptureStdOut()
.OnErrorLine(l => commandOutput.Add(l))
.OnOutputLine(l => commandOutput.Add(l))
.Execute();
if (commandResult.ExitCode != 0)
{
_logger.LogDebug(string.Join(Environment.NewLine, commandOutput));
throw new GracefulException(Resources.FormatError_ProjectFailedToLoad(projectFile));
}
var id = File.ReadAllText(outputFile)?.Trim();
if (string.IsNullOrEmpty(id))
{
throw new GracefulException(Resources.FormatError_ProjectMissingId(projectFile));
}
return id;
}
public void Dispose()
{
foreach (var file in _tempFiles)
{
TryDelete(file);
}
}
private string GetTargetFile()
{
var assemblyDir = Path.GetDirectoryName(GetType().GetTypeInfo().Assembly.Location);
// targets should be in one of these locations, depending on test setup and tools installation
var searchPaths = new[]
{
AppContext.BaseDirectory,
assemblyDir, // next to assembly
Path.Combine(assemblyDir, "../../tools"), // inside the nupkg
};
var foundFile = searchPaths
.Select(dir => Path.Combine(dir, TargetsFileName))
.Where(File.Exists)
.FirstOrDefault();
if (foundFile != null)
{
return foundFile;
}
// This should only really happen during testing. Current build system doesn't give us a good way to ensure the
// test project has an always-up to date version of the targets file.
// TODO cleanup after we switch to an MSBuild system in which can specify "CopyToOutputDirectory: Always" to resolve this issue
var outputPath = Path.GetTempFileName();
using (var resource = GetType().GetTypeInfo().Assembly.GetManifestResourceStream(TargetsFileName))
using (var stream = new FileStream(outputPath, FileMode.Create))
{
resource.CopyTo(stream);
}
// cleanup
_tempFiles.Add(outputPath);
return outputPath;
}
private static void TryDelete(string file)
{
try
{
if (File.Exists(file))
{
File.Delete(file);
}
}
catch
{
// whatever
}
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.CommandLineUtils;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -26,6 +26,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
} }
_secretsFilePath = PathHelper.GetSecretsPathFromSecretsId(userSecretsId); _secretsFilePath = PathHelper.GetSecretsPathFromSecretsId(userSecretsId);
logger.LogDebug(Resources.Message_Secret_File_Path, _secretsFilePath); logger.LogDebug(Resources.Message_Secret_File_Path, _secretsFilePath);
_secrets = Load(userSecretsId); _secrets = Load(userSecretsId);
} }

View File

@ -4,6 +4,7 @@
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.CommandLineUtils;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -16,16 +16,16 @@
<dependencies> <dependencies>
<group targetFramework=".NETCoreApp1.0"> <group targetFramework=".NETCoreApp1.0">
<!-- MUST BE alphabetical --> <!-- MUST BE alphabetical -->
<dependency id="Microsoft.Extensions.Configuration.UserSecrets" version="$dep_1$" /> <dependency id="Microsoft.DotNet.Cli.Utils" version="$dep_1$" />
<dependency id="Microsoft.Extensions.CommandLineUtils" version="$dep_2$" /> <dependency id="Microsoft.Extensions.CommandLineUtils" version="$dep_2$" />
<dependency id="Microsoft.Extensions.Logging" version="$dep_3$" /> <dependency id="Microsoft.Extensions.Configuration.UserSecrets" version="$dep_3$" />
<dependency id="Microsoft.NETCore.App" version="$dep_4$" /> <dependency id="Microsoft.Extensions.Logging" version="$dep_4$" />
<dependency id="Newtonsoft.Json" version="$dep_5$" /> <dependency id="Microsoft.NETCore.App" version="$dep_5$" />
<dependency id="System.Runtime.Serialization.Primitives" version="$dep_6$" />
</group> </group>
</dependencies> </dependencies>
</metadata> </metadata>
<files> <files>
<file src="FindUserSecretsProperty.targets" target="tools\" />
<file src="bin/$configuration$/netcoreapp1.0/dotnet-user-secrets.dll" target="lib\netcoreapp1.0\" /> <file src="bin/$configuration$/netcoreapp1.0/dotnet-user-secrets.dll" target="lib\netcoreapp1.0\" />
<file src="bin/$configuration$/netcoreapp1.0/dotnet-user-secrets.runtimeconfig.json" target="lib/netcoreapp1.0\" /> <file src="bin/$configuration$/netcoreapp1.0/dotnet-user-secrets.runtimeconfig.json" target="lib/netcoreapp1.0\" />
</files> </files>

View File

@ -2,15 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Extensions.SecretManager.Tools.Internal; using Microsoft.Extensions.SecretManager.Tools.Internal;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.Extensions.SecretManager.Tools namespace Microsoft.Extensions.SecretManager.Tools
{ {
@ -21,12 +16,16 @@ namespace Microsoft.Extensions.SecretManager.Tools
private readonly IConsole _console; private readonly IConsole _console;
private readonly string _workingDirectory; private readonly string _workingDirectory;
public Program() public static int Main(string[] args)
: this(PhysicalConsole.Singleton, Directory.GetCurrentDirectory())
{ {
DebugHelper.HandleDebugSwitch(ref args);
int rc;
new Program(PhysicalConsole.Singleton, Directory.GetCurrentDirectory()).TryRun(args, out rc);
return rc;
} }
internal Program(IConsole console, string workingDirectory) public Program(IConsole console, string workingDirectory)
{ {
_console = console; _console = console;
_workingDirectory = workingDirectory; _workingDirectory = workingDirectory;
@ -65,33 +64,6 @@ namespace Microsoft.Extensions.SecretManager.Tools
} }
} }
public static int Main(string[] args)
{
HandleDebugFlag(ref args);
int rc;
new Program().TryRun(args, out rc);
return rc;
}
[Conditional("DEBUG")]
private static void HandleDebugFlag(ref string[] args)
{
for (var i = 0; i < args.Length; ++i)
{
if (args[i] == "--debug")
{
Console.WriteLine("Process ID " + Process.GetCurrentProcess().Id);
Console.WriteLine("Paused for debugger. Press ENTER to continue");
Console.ReadLine();
args = args.Take(i).Concat(args.Skip(i + 1)).ToArray();
return;
}
}
}
public bool TryRun(string[] args, out int returnCode) public bool TryRun(string[] args, out int returnCode)
{ {
try try
@ -103,6 +75,11 @@ namespace Microsoft.Extensions.SecretManager.Tools
{ {
if (exception is GracefulException) if (exception is GracefulException)
{ {
if (exception.InnerException != null)
{
Logger.LogInformation(exception.InnerException.Message);
}
Logger.LogError(exception.Message); Logger.LogError(exception.Message);
} }
else else
@ -134,65 +111,23 @@ namespace Microsoft.Extensions.SecretManager.Tools
CommandOutputProvider.LogLevel = LogLevel.Debug; CommandOutputProvider.LogLevel = LogLevel.Debug;
} }
var userSecretsId = ResolveUserSecretsId(options); var userSecretsId = ResolveId(options);
var store = new SecretsStore(userSecretsId, Logger); var store = new SecretsStore(userSecretsId, Logger);
var context = new CommandContext(store, Logger, _console); var context = new Internal.CommandContext(store, Logger, _console);
options.Command.Execute(context); options.Command.Execute(context);
return 0; return 0;
} }
private string ResolveUserSecretsId(CommandLineOptions options) internal string ResolveId(CommandLineOptions options)
{ {
if (!string.IsNullOrEmpty(options.Id)) if (!string.IsNullOrEmpty(options.Id))
{ {
return options.Id; return options.Id;
} }
var projectPath = options.Project ?? _workingDirectory; using (var resolver = new ProjectIdResolver(Logger, _workingDirectory))
if (!Path.IsPathRooted(projectPath))
{ {
projectPath = Path.Combine(_workingDirectory, projectPath); return resolver.Resolve(options.Project, options.Configuration);
}
if (!projectPath.EndsWith("project.json", StringComparison.OrdinalIgnoreCase))
{
projectPath = Path.Combine(projectPath, "project.json");
}
var fileInfo = new PhysicalFileInfo(new FileInfo(projectPath));
if (!fileInfo.Exists)
{
throw new GracefulException(Resources.FormatError_ProjectPath_NotFound(projectPath));
}
Logger.LogDebug(Resources.Message_Project_File_Path, fileInfo.PhysicalPath);
return ReadUserSecretsId(fileInfo);
}
// TODO can use runtime API when upgrading to 1.1
private string ReadUserSecretsId(IFileInfo fileInfo)
{
if (fileInfo == null || !fileInfo.Exists)
{
throw new GracefulException($"Could not find file '{fileInfo.PhysicalPath}'");
}
using (var stream = fileInfo.CreateReadStream())
using (var streamReader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(streamReader))
{
var obj = JObject.Load(jsonReader);
var userSecretsId = obj.Value<string>("userSecretsId");
if (string.IsNullOrEmpty(userSecretsId))
{
throw new GracefulException($"Could not find 'userSecretsId' in json file '{fileInfo.PhysicalPath}'");
}
return userSecretsId;
} }
} }
} }

View File

@ -10,6 +10,22 @@ namespace Microsoft.Extensions.SecretManager.Tools
private static readonly ResourceManager _resourceManager private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.Extensions.SecretManager.Tools.Resources", typeof(Resources).GetTypeInfo().Assembly); = new ResourceManager("Microsoft.Extensions.SecretManager.Tools.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// Value cannot be null or an empty string.
/// </summary>
internal static string Common_StringNullOrEmpty
{
get { return GetString("Common_StringNullOrEmpty"); }
}
/// <summary>
/// Value cannot be null or an empty string.
/// </summary>
internal static string FormatCommon_StringNullOrEmpty()
{
return GetString("Common_StringNullOrEmpty");
}
/// <summary> /// <summary>
/// Command failed : {message} /// Command failed : {message}
/// </summary> /// </summary>
@ -60,6 +76,22 @@ namespace Microsoft.Extensions.SecretManager.Tools
return string.Format(CultureInfo.CurrentCulture, GetString("Error_Missing_Secret", "key"), key); return string.Format(CultureInfo.CurrentCulture, GetString("Error_Missing_Secret", "key"), key);
} }
/// <summary>
/// Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option.
/// </summary>
internal static string Error_MultipleProjectsFound
{
get { return GetString("Error_MultipleProjectsFound"); }
}
/// <summary>
/// Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option.
/// </summary>
internal static string FormatError_MultipleProjectsFound(object projectPath)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Error_MultipleProjectsFound", "projectPath"), projectPath);
}
/// <summary> /// <summary>
/// No secrets configured for this application. /// No secrets configured for this application.
/// </summary> /// </summary>
@ -76,6 +108,38 @@ namespace Microsoft.Extensions.SecretManager.Tools
return GetString("Error_No_Secrets_Found"); return GetString("Error_No_Secrets_Found");
} }
/// <summary>
/// Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option.
/// </summary>
internal static string Error_NoProjectsFound
{
get { return GetString("Error_NoProjectsFound"); }
}
/// <summary>
/// Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option.
/// </summary>
internal static string FormatError_NoProjectsFound(object projectPath)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Error_NoProjectsFound", "projectPath"), projectPath);
}
/// <summary>
/// Could not find the global property 'UserSecretsId' in MSBuild project '{project}'. Ensure this property is set in the project or use the '--id' command line option.
/// </summary>
internal static string Error_ProjectMissingId
{
get { return GetString("Error_ProjectMissingId"); }
}
/// <summary>
/// Could not find the global property 'UserSecretsId' in MSBuild project '{project}'. Ensure this property is set in the project or use the '--id' command line option.
/// </summary>
internal static string FormatError_ProjectMissingId(object project)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectMissingId", "project"), project);
}
/// <summary> /// <summary>
/// The project file '{path}' does not exist. /// The project file '{path}' does not exist.
/// </summary> /// </summary>
@ -92,6 +156,22 @@ namespace Microsoft.Extensions.SecretManager.Tools
return string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectPath_NotFound", "path"), path); return string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectPath_NotFound", "path"), path);
} }
/// <summary>
/// Could not load the MSBuild project '{project}'.
/// </summary>
internal static string Error_ProjectFailedToLoad
{
get { return GetString("Error_ProjectFailedToLoad"); }
}
/// <summary>
/// Could not load the MSBuild project '{project}'.
/// </summary>
internal static string FormatError_ProjectFailedToLoad(object project)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectFailedToLoad", "project"), project);
}
/// <summary> /// <summary>
/// Project file path {project}. /// Project file path {project}.
/// </summary> /// </summary>

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
Version 2.0 Version 2.0
The primary goals of this format is to allow a simple XML format The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes various data types are done through the TypeConverter classes
associated with the data types. associated with the data types.
Example: Example:
... ado.net/XML headers & schema ... ... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader> <resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader> <resheader name="version">2.0</resheader>
@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment> <comment>This is a comment</comment>
</data> </data>
There are any number of "resheader" rows that contain simple There are any number of "resheader" rows that contain simple
name/value pairs. name/value pairs.
Each data row contains a name, and value. The row also contains a Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture. text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the Classes that don't support this are serialized and stored with the
mimetype set. mimetype set.
The mimetype is used for serialized objects, and tells the The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly: extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below. read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64 mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64 mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64 mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter : using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
--> -->
@ -117,6 +117,9 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Common_StringNullOrEmpty" xml:space="preserve">
<value>Value cannot be null or an empty string.</value>
</data>
<data name="Error_Command_Failed" xml:space="preserve"> <data name="Error_Command_Failed" xml:space="preserve">
<value>Command failed : {message}</value> <value>Command failed : {message}</value>
</data> </data>
@ -127,12 +130,24 @@ Use the '--help' flag to see info.</value>
<data name="Error_Missing_Secret" xml:space="preserve"> <data name="Error_Missing_Secret" xml:space="preserve">
<value>Cannot find '{key}' in the secret store.</value> <value>Cannot find '{key}' in the secret store.</value>
</data> </data>
<data name="Error_MultipleProjectsFound" xml:space="preserve">
<value>Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option.</value>
</data>
<data name="Error_No_Secrets_Found" xml:space="preserve"> <data name="Error_No_Secrets_Found" xml:space="preserve">
<value>No secrets configured for this application.</value> <value>No secrets configured for this application.</value>
</data> </data>
<data name="Error_NoProjectsFound" xml:space="preserve">
<value>Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option.</value>
</data>
<data name="Error_ProjectMissingId" xml:space="preserve">
<value>Could not find the global property 'UserSecretsId' in MSBuild project '{project}'. Ensure this property is set in the project or use the '--id' command line option.</value>
</data>
<data name="Error_ProjectPath_NotFound" xml:space="preserve"> <data name="Error_ProjectPath_NotFound" xml:space="preserve">
<value>The project file '{path}' does not exist.</value> <value>The project file '{path}' does not exist.</value>
</data> </data>
<data name="Error_ProjectFailedToLoad" xml:space="preserve">
<value>Could not load the MSBuild project '{project}'.</value>
</data>
<data name="Message_Project_File_Path" xml:space="preserve"> <data name="Message_Project_File_Path" xml:space="preserve">
<value>Project file path {project}.</value> <value>Project file path {project}.</value>
</data> </data>

View File

@ -4,7 +4,13 @@
"outputName": "dotnet-user-secrets", "outputName": "dotnet-user-secrets",
"emitEntryPoint": true, "emitEntryPoint": true,
"warningsAsErrors": true, "warningsAsErrors": true,
"keyFile": "../../tools/Key.snk" "keyFile": "../../tools/Key.snk",
"copyToOutput": "*.targets",
"embed": {
"mappings": {
"FindUserSecretsProperty.targets": "./FindUserSecretsProperty.targets"
}
}
}, },
"description": "Command line tool to manage user secrets for Microsoft.Extensions.Configuration.", "description": "Command line tool to manage user secrets for Microsoft.Extensions.Configuration.",
"packOptions": { "packOptions": {
@ -16,18 +22,22 @@
"configuration", "configuration",
"secrets", "secrets",
"usersecrets" "usersecrets"
] ],
"files": {
"mappings": {
"tools/FindUserSecretsProperty.targets": "FindUserSecretsProperty.targets"
}
}
}, },
"dependencies": { "dependencies": {
"Microsoft.DotNet.Cli.Utils": "1.0.0-*",
"Microsoft.Extensions.CommandLineUtils": "1.1.0-*", "Microsoft.Extensions.CommandLineUtils": "1.1.0-*",
"Microsoft.Extensions.Configuration.UserSecrets": "1.1.0-*", "Microsoft.Extensions.Configuration.UserSecrets": "1.1.0-*",
"Microsoft.Extensions.Logging": "1.1.0-*", "Microsoft.Extensions.Logging": "1.1.0-*",
"Microsoft.NETCore.App": { "Microsoft.NETCore.App": {
"version": "1.1.0-*", "version": "1.1.0-*",
"type": "platform" "type": "platform"
}, }
"Newtonsoft.Json": "9.0.1",
"System.Runtime.Serialization.Primitives": "4.3.0-*"
}, },
"frameworks": { "frameworks": {
"netcoreapp1.0": {} "netcoreapp1.0": {}

View File

@ -0,0 +1,86 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.SecretManager.Tools.Internal;
using Xunit;
namespace Microsoft.Extensions.SecretsManager.Tools.Tests
{
public class MsBuildProjectFinderTest
{
[Theory]
[InlineData(".csproj")]
[InlineData(".vbproj")]
[InlineData(".fsproj")]
public void FindsSingleProject(string extension)
{
using (var files = new TemporaryFileProvider())
{
var filename = "TestProject" + extension;
files.Add(filename, "");
var finder = new MsBuildProjectFinder(files.Root);
Assert.Equal(Path.Combine(files.Root, filename), finder.FindMsBuildProject(null));
}
}
[Fact]
public void ThrowsWhenNoFile()
{
using (var files = new TemporaryFileProvider())
{
var finder = new MsBuildProjectFinder(files.Root);
Assert.Throws<GracefulException>(() => finder.FindMsBuildProject(null));
}
}
[Fact]
public void DoesNotMatchXproj()
{
using (var files = new TemporaryFileProvider())
{
var finder = new MsBuildProjectFinder(files.Root);
files.Add("test.xproj", "");
Assert.Throws<GracefulException>(() => finder.FindMsBuildProject(null));
}
}
[Fact]
public void ThrowsWhenMultipleFile()
{
using (var files = new TemporaryFileProvider())
{
files.Add("Test1.csproj", "");
files.Add("Test2.csproj", "");
var finder = new MsBuildProjectFinder(files.Root);
Assert.Throws<GracefulException>(() => finder.FindMsBuildProject(null));
}
}
[Fact]
public void ThrowsWhenFileDoesNotExist()
{
using (var files = new TemporaryFileProvider())
{
var finder = new MsBuildProjectFinder(files.Root);
Assert.Throws<GracefulException>(() => finder.FindMsBuildProject("test.csproj"));
}
}
[Fact]
public void ThrowsWhenRootDoesNotExist()
{
var files = new TemporaryFileProvider();
var finder = new MsBuildProjectFinder(files.Root);
files.Dispose();
Assert.Throws<GracefulException>(() => finder.FindMsBuildProject(null));
}
}
}

View File

@ -5,68 +5,79 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.Configuration.UserSecrets; using Microsoft.Extensions.Configuration.UserSecrets;
using Microsoft.Extensions.Configuration.UserSecrets.Tests; using Microsoft.Extensions.Configuration.UserSecrets.Tests;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.SecretManager.Tools.Internal;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace Microsoft.Extensions.SecretManager.Tools.Tests namespace Microsoft.Extensions.SecretManager.Tools.Tests
{ {
public class SecretManagerTests : IDisposable public class SecretManagerTests : IClassFixture<UserSecretsTestFixture>
{ {
private TestLogger _logger; private TestLogger _logger;
private Stack<Action> _disposables = new Stack<Action>(); private readonly UserSecretsTestFixture _fixture;
public SecretManagerTests(ITestOutputHelper output) public SecretManagerTests(UserSecretsTestFixture fixture, ITestOutputHelper output)
{ {
_fixture = fixture;
_logger = new TestLogger(output); _logger = new TestLogger(output);
} }
private string GetTempSecretProject() private Program CreateProgram()
{ {
string id; return new Program(new TestConsole(), Directory.GetCurrentDirectory())
return GetTempSecretProject(out id);
}
private string GetTempSecretProject(out string userSecretsId)
{
var projectPath = UserSecretHelper.GetTempSecretProject(out userSecretsId);
_disposables.Push(() => UserSecretHelper.DeleteTempSecretProject(projectPath));
return projectPath;
}
public void Dispose()
{
while (_disposables.Count > 0)
{ {
_disposables.Pop().Invoke(); Logger = _logger
} };
}
[Theory]
[InlineData(null)]
[InlineData("")]
public void Error_MissingId(string id)
{
var project = Path.Combine(_fixture.CreateProject(id), "TestProject.csproj");
var secretManager = CreateProgram();
var ex = Assert.Throws<GracefulException>(() => secretManager.RunInternal("list", "-p", project));
Assert.Equal(Resources.FormatError_ProjectMissingId(project), ex.Message);
}
[Fact]
public void Error_InvalidProjectFormat()
{
var project = Path.Combine(_fixture.CreateProject("<"), "TestProject.csproj");
var secretManager = CreateProgram();
var ex = Assert.Throws<GracefulException>(() => secretManager.RunInternal("list", "-p", project));
Assert.Equal(Resources.FormatError_ProjectFailedToLoad(project), ex.Message);
} }
[Fact] [Fact]
public void Error_Project_DoesNotExist() public void Error_Project_DoesNotExist()
{ {
var projectPath = Path.Combine(GetTempSecretProject(), "does_not_exist", "project.json"); var projectPath = Path.Combine(_fixture.GetTempSecretProject(), "does_not_exist", "TestProject.csproj");
var secretManager = new Program(new TestConsole(), Directory.GetCurrentDirectory()) { Logger = _logger }; var secretManager = CreateProgram();
var ex = Assert.Throws<GracefulException>(() => secretManager.RunInternal("list", "--project", projectPath)); var ex = Assert.Throws<GracefulException>(() => secretManager.RunInternal("list", "--project", projectPath));
Assert.Equal(Resources.FormatError_ProjectPath_NotFound(projectPath), ex.Message); Assert.Equal(Resources.FormatError_ProjectPath_NotFound(projectPath), ex.Message);
} }
[Fact] [Fact]
public void SupportsRelativePaths() public void SupportsRelativePaths()
{ {
var projectPath = GetTempSecretProject(); var projectPath = _fixture.GetTempSecretProject();
var cwd = Path.Combine(projectPath, "nested1"); var cwd = Path.Combine(projectPath, "nested1");
Directory.CreateDirectory(cwd); Directory.CreateDirectory(cwd);
var secretManager = new Program(new TestConsole(), cwd) { Logger = _logger, CommandOutputProvider = _logger.CommandOutputProvider }; var secretManager = new Program(new TestConsole(), cwd) { Logger = _logger, CommandOutputProvider = _logger.CommandOutputProvider };
secretManager.CommandOutputProvider.LogLevel = LogLevel.Debug; secretManager.CommandOutputProvider.LogLevel = LogLevel.Debug;
secretManager.RunInternal("list", "-p", "../", "--verbose"); secretManager.RunInternal("list", "-p", ".." + Path.DirectorySeparatorChar, "--verbose");
Assert.Contains(Resources.FormatMessage_Project_File_Path(Path.Combine(projectPath, "project.json")), _logger.Messages); Assert.Contains(Resources.FormatMessage_Project_File_Path(Path.Combine(cwd, "..", "TestProject.csproj")), _logger.Messages);
} }
[Theory] [Theory]
@ -82,7 +93,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
new KeyValuePair<string, string>("key2", string.Empty) new KeyValuePair<string, string>("key2", string.Empty)
}; };
var projectPath = GetTempSecretProject(); var projectPath = _fixture.GetTempSecretProject();
var dir = fromCurrentDirectory var dir = fromCurrentDirectory
? projectPath ? projectPath
: Path.GetTempPath(); : Path.GetTempPath();
@ -141,8 +152,8 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
[Fact] [Fact]
public void SetSecret_Update_Existing_Secret() public void SetSecret_Update_Existing_Secret()
{ {
var projectPath = GetTempSecretProject(); var projectPath = _fixture.GetTempSecretProject();
var secretManager = new Program() { Logger = _logger }; var secretManager = CreateProgram();
secretManager.RunInternal("set", "secret1", "value1", "-p", projectPath); secretManager.RunInternal("set", "secret1", "value1", "-p", projectPath);
Assert.Equal(1, _logger.Messages.Count); Assert.Equal(1, _logger.Messages.Count);
@ -161,31 +172,31 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
[Fact] [Fact]
public void SetSecret_With_Verbose_Flag() public void SetSecret_With_Verbose_Flag()
{ {
string id; string secretId;
var projectPath = GetTempSecretProject(out id); var projectPath = _fixture.GetTempSecretProject(out secretId);
_logger.SetLevel(LogLevel.Debug); _logger.SetLevel(LogLevel.Debug);
var secretManager = new Program() { Logger = _logger }; var secretManager = CreateProgram();
secretManager.RunInternal("-v", "set", "secret1", "value1", "-p", projectPath); secretManager.RunInternal("-v", "set", "secret1", "value1", "-p", projectPath);
Assert.Equal(3, _logger.Messages.Count); Assert.Equal(3, _logger.Messages.Count);
Assert.Contains(string.Format("Project file path {0}.", Path.Combine(projectPath, "project.json")), _logger.Messages); Assert.Contains(string.Format("Project file path {0}.", Path.Combine(projectPath, "TestProject.csproj")), _logger.Messages);
Assert.Contains(string.Format("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(id)), _logger.Messages); Assert.Contains(string.Format("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(secretId)), _logger.Messages);
Assert.Contains("Successfully saved secret1 = value1 to the secret store.", _logger.Messages); Assert.Contains("Successfully saved secret1 = value1 to the secret store.", _logger.Messages);
_logger.Messages.Clear(); _logger.Messages.Clear();
secretManager.RunInternal("-v", "list", "-p", projectPath); secretManager.RunInternal("-v", "list", "-p", projectPath);
Assert.Equal(3, _logger.Messages.Count); Assert.Equal(3, _logger.Messages.Count);
Assert.Contains(string.Format("Project file path {0}.", Path.Combine(projectPath, "project.json")), _logger.Messages); Assert.Contains(string.Format("Project file path {0}.", Path.Combine(projectPath, "TestProject.csproj")), _logger.Messages);
Assert.Contains(string.Format("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(id)), _logger.Messages); Assert.Contains(string.Format("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(secretId)), _logger.Messages);
Assert.Contains("secret1 = value1", _logger.Messages); Assert.Contains("secret1 = value1", _logger.Messages);
} }
[Fact] [Fact]
public void Remove_Non_Existing_Secret() public void Remove_Non_Existing_Secret()
{ {
var projectPath = GetTempSecretProject(); var projectPath = _fixture.GetTempSecretProject();
var secretManager = new Program() { Logger = _logger }; var secretManager = CreateProgram();
secretManager.RunInternal("remove", "secret1", "-p", projectPath); secretManager.RunInternal("remove", "secret1", "-p", projectPath);
Assert.Equal(1, _logger.Messages.Count); Assert.Equal(1, _logger.Messages.Count);
Assert.Contains("Cannot find 'secret1' in the secret store.", _logger.Messages); Assert.Contains("Cannot find 'secret1' in the secret store.", _logger.Messages);
@ -194,8 +205,8 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
[Fact] [Fact]
public void Remove_Is_Case_Insensitive() public void Remove_Is_Case_Insensitive()
{ {
var projectPath = GetTempSecretProject(); var projectPath = _fixture.GetTempSecretProject();
var secretManager = new Program() { Logger = _logger }; var secretManager = CreateProgram();
secretManager.RunInternal("set", "SeCreT1", "value", "-p", projectPath); secretManager.RunInternal("set", "SeCreT1", "value", "-p", projectPath);
secretManager.RunInternal("list", "-p", projectPath); secretManager.RunInternal("list", "-p", projectPath);
Assert.Contains("SeCreT1 = value", _logger.Messages); Assert.Contains("SeCreT1 = value", _logger.Messages);
@ -211,12 +222,12 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
[Fact] [Fact]
public void List_Flattens_Nested_Objects() public void List_Flattens_Nested_Objects()
{ {
string id; string secretId;
var projectPath = GetTempSecretProject(out id); var projectPath = _fixture.GetTempSecretProject(out secretId);
var secretsFile = PathHelper.GetSecretsPathFromSecretsId(id); var secretsFile = PathHelper.GetSecretsPathFromSecretsId(secretId);
Directory.CreateDirectory(Path.GetDirectoryName(secretsFile)); Directory.CreateDirectory(Path.GetDirectoryName(secretsFile));
File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8); File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8);
var secretManager = new Program() { Logger = _logger }; var secretManager = CreateProgram();
secretManager.RunInternal("list", "-p", projectPath); secretManager.RunInternal("list", "-p", projectPath);
Assert.Equal(1, _logger.Messages.Count); Assert.Equal(1, _logger.Messages.Count);
Assert.Contains("AzureAd:ClientSecret = abcd郩˙î", _logger.Messages); Assert.Contains("AzureAd:ClientSecret = abcd郩˙î", _logger.Messages);
@ -231,7 +242,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
Out = new StringWriter(output) Out = new StringWriter(output)
}; };
string id; string id;
var projectPath = GetTempSecretProject(out id); var projectPath = _fixture.GetTempSecretProject(out id);
var secretsFile = PathHelper.GetSecretsPathFromSecretsId(id); var secretsFile = PathHelper.GetSecretsPathFromSecretsId(id);
Directory.CreateDirectory(Path.GetDirectoryName(secretsFile)); Directory.CreateDirectory(Path.GetDirectoryName(secretsFile));
File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8); File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8);
@ -246,12 +257,12 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
[Fact] [Fact]
public void Set_Flattens_Nested_Objects() public void Set_Flattens_Nested_Objects()
{ {
string id; string secretId;
var projectPath = GetTempSecretProject(out id); var projectPath = _fixture.GetTempSecretProject(out secretId);
var secretsFile = PathHelper.GetSecretsPathFromSecretsId(id); var secretsFile = PathHelper.GetSecretsPathFromSecretsId(secretId);
Directory.CreateDirectory(Path.GetDirectoryName(secretsFile)); Directory.CreateDirectory(Path.GetDirectoryName(secretsFile));
File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8); File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8);
var secretManager = new Program() { Logger = _logger }; var secretManager = CreateProgram();
secretManager.RunInternal("set", "AzureAd:ClientSecret", "¡™£¢∞", "-p", projectPath); secretManager.RunInternal("set", "AzureAd:ClientSecret", "¡™£¢∞", "-p", projectPath);
Assert.Equal(1, _logger.Messages.Count); Assert.Equal(1, _logger.Messages.Count);
secretManager.RunInternal("list", "-p", projectPath); secretManager.RunInternal("list", "-p", projectPath);
@ -268,8 +279,8 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
[Fact] [Fact]
public void List_Empty_Secrets_File() public void List_Empty_Secrets_File()
{ {
var projectPath = GetTempSecretProject(); var projectPath = _fixture.GetTempSecretProject();
var secretManager = new Program() { Logger = _logger }; var secretManager = CreateProgram();
secretManager.RunInternal("list", "-p", projectPath); secretManager.RunInternal("list", "-p", projectPath);
Assert.Equal(1, _logger.Messages.Count); Assert.Equal(1, _logger.Messages.Count);
Assert.Contains(Resources.Error_No_Secrets_Found, _logger.Messages); Assert.Contains(Resources.Error_No_Secrets_Found, _logger.Messages);
@ -280,7 +291,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
[InlineData(false)] [InlineData(false)]
public void Clear_Secrets(bool fromCurrentDirectory) public void Clear_Secrets(bool fromCurrentDirectory)
{ {
var projectPath = GetTempSecretProject(); var projectPath = _fixture.GetTempSecretProject();
var dir = fromCurrentDirectory var dir = fromCurrentDirectory
? projectPath ? projectPath

View File

@ -75,7 +75,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
var secretStore = new TestSecretsStore(); var secretStore = new TestSecretsStore();
var command = new SetCommand("key", null); var command = new SetCommand("key", null);
var ex = Assert.Throws<GracefulException>( var ex = Assert.Throws< Microsoft.DotNet.Cli.Utils.GracefulException>(
() => command.Execute(new CommandContext(secretStore, NullLogger.Instance, testConsole))); () => command.Execute(new CommandContext(secretStore, NullLogger.Instance, testConsole)));
Assert.Equal(Resources.FormatError_MissingArgument("value"), ex.Message); Assert.Equal(Resources.FormatError_MissingArgument("value"), ex.Message);
} }

View File

@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Text;
namespace Microsoft.Extensions.SecretsManager.Tools.Tests
{
internal class TemporaryFileProvider : IDisposable
{
public TemporaryFileProvider()
{
Root = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "tmpfiles", Guid.NewGuid().ToString())).FullName;
}
public string Root { get; }
public void Add(string filename, string contents)
{
File.WriteAllText(Path.Combine(Root, filename), contents, Encoding.UTF8);
}
public void Dispose()
{
Directory.Delete(Root, recursive: true);
}
}
}

View File

@ -1,47 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using Newtonsoft.Json;
namespace Microsoft.Extensions.Configuration.UserSecrets.Tests
{
public class UserSecretHelper
{
internal static string GetTempSecretProject()
{
string userSecretsId;
return GetTempSecretProject(out userSecretsId);
}
internal static string GetTempSecretProject(out string userSecretsId)
{
var projectPath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "usersecretstest", Guid.NewGuid().ToString()));
userSecretsId = Guid.NewGuid().ToString();
File.WriteAllText(
Path.Combine(projectPath.FullName, "project.json"),
JsonConvert.SerializeObject(new { userSecretsId }));
return projectPath.FullName;
}
internal static void SetTempSecretInProject(string projectPath, string userSecretsId)
{
File.WriteAllText(
Path.Combine(projectPath, "project.json"),
JsonConvert.SerializeObject(new { userSecretsId }));
}
internal static void DeleteTempSecretProject(string projectPath)
{
try
{
Directory.Delete(projectPath, true);
}
catch (Exception)
{
// Ignore failures.
}
}
}
}

View File

@ -0,0 +1,103 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
namespace Microsoft.Extensions.Configuration.UserSecrets.Tests
{
public class UserSecretsTestFixture : IDisposable
{
private Stack<Action> _disposables = new Stack<Action>();
public const string TestSecretsId = "b918174fa80346bbb7f4a386729c0eff";
public UserSecretsTestFixture()
{
_disposables.Push(() => TryDelete(Path.GetDirectoryName(PathHelper.GetSecretsPathFromSecretsId(TestSecretsId))));
}
public void Dispose()
{
while (_disposables.Count > 0)
{
_disposables.Pop()?.Invoke();
}
}
public string GetTempSecretProject()
{
string userSecretsId;
return GetTempSecretProject(out userSecretsId);
}
private const string ProjectTemplate = @"<Project ToolsVersion=""14.0"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<Import Project=""$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp1.0</TargetFrameworks>
{0}
</PropertyGroup>
<ItemGroup>
<Compile Include=""**\*.cs"" Exclude=""Excluded.cs"" />
<PackageReference Include=""Microsoft.NET.Sdk"" Version=""1.0.0-*"" PrivateAssets=""All"" />
<PackageReference Include=""Microsoft.NETCore.App"" Version=""1.0.0"" />
</ItemGroup>
<Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" />
</Project>";
public string GetTempSecretProject(out string userSecretsId)
{
userSecretsId = Guid.NewGuid().ToString();
return CreateProject(userSecretsId);
}
public string CreateProject(string userSecretsId)
{
var projectPath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "usersecretstest", Guid.NewGuid().ToString()));
var prop = string.IsNullOrEmpty(userSecretsId)
? string.Empty
: $"<UserSecretsId>{userSecretsId}</UserSecretsId>";
File.WriteAllText(
Path.Combine(projectPath.FullName, "TestProject.csproj"),
string.Format(ProjectTemplate, prop));
var id = userSecretsId;
_disposables.Push(() =>
{
try
{
// may throw if id is bad
var secretsDir = Path.GetDirectoryName(PathHelper.GetSecretsPathFromSecretsId(id));
TryDelete(secretsDir);
}
catch { }
});
_disposables.Push(() => TryDelete(projectPath.FullName));
return projectPath.FullName;
}
private static void TryDelete(string directory)
{
try
{
if (Directory.Exists(directory))
{
Directory.Delete(directory, true);
}
}
catch (Exception)
{
// Ignore failures.
Console.WriteLine("Failed to delete " + directory);
}
}
}
}