Create initial prototype of dotnet-user-secrets with MSBuild support
This commit is contained in:
parent
9744f94b39
commit
29df59b89c
|
|
@ -28,6 +28,7 @@ project.lock.json
|
||||||
.testPublish/
|
.testPublish/
|
||||||
.build/
|
.build/
|
||||||
/.vs/
|
/.vs/
|
||||||
|
.vscode/
|
||||||
testWorkDir/
|
testWorkDir/
|
||||||
*.nuget.props
|
*.nuget.props
|
||||||
*.nuget.targets
|
*.nuget.targets
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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": {}
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue