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/
|
||||
.build/
|
||||
/.vs/
|
||||
.vscode/
|
||||
testWorkDir/
|
||||
*.nuget.props
|
||||
*.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.
|
||||
|
||||
using System.Reflection;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
|
||||
namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||
|
|
@ -13,6 +14,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
|||
public bool IsHelp { get; set; }
|
||||
public string Project { get; set; }
|
||||
public ICommand Command { get; set; }
|
||||
public string Configuration { get; set; }
|
||||
|
||||
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",
|
||||
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
|
||||
// in the current project
|
||||
var optionId = app.Option("--id", "The user secret id to use.",
|
||||
|
|
@ -55,6 +60,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
|||
return null;
|
||||
}
|
||||
|
||||
options.Configuration = optionConfig.Value();
|
||||
options.Id = optionId.Value();
|
||||
options.IsHelp = app.IsShowingInformation;
|
||||
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.
|
||||
// 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.Logging;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
|||
}
|
||||
|
||||
_secretsFilePath = PathHelper.GetSecretsPathFromSecretsId(userSecretsId);
|
||||
|
||||
logger.LogDebug(Resources.Message_Secret_File_Path, _secretsFilePath);
|
||||
_secrets = Load(userSecretsId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,16 +16,16 @@
|
|||
<dependencies>
|
||||
<group targetFramework=".NETCoreApp1.0">
|
||||
<!-- 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.Logging" version="$dep_3$" />
|
||||
<dependency id="Microsoft.NETCore.App" version="$dep_4$" />
|
||||
<dependency id="Newtonsoft.Json" version="$dep_5$" />
|
||||
<dependency id="System.Runtime.Serialization.Primitives" version="$dep_6$" />
|
||||
<dependency id="Microsoft.Extensions.Configuration.UserSecrets" version="$dep_3$" />
|
||||
<dependency id="Microsoft.Extensions.Logging" version="$dep_4$" />
|
||||
<dependency id="Microsoft.NETCore.App" version="$dep_5$" />
|
||||
</group>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<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.runtimeconfig.json" target="lib/netcoreapp1.0\" />
|
||||
</files>
|
||||
|
|
|
|||
|
|
@ -2,15 +2,10 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.FileProviders.Physical;
|
||||
using Microsoft.Extensions.SecretManager.Tools.Internal;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Extensions.SecretManager.Tools
|
||||
{
|
||||
|
|
@ -21,12 +16,16 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
|||
private readonly IConsole _console;
|
||||
private readonly string _workingDirectory;
|
||||
|
||||
public Program()
|
||||
: this(PhysicalConsole.Singleton, Directory.GetCurrentDirectory())
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
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;
|
||||
_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)
|
||||
{
|
||||
try
|
||||
|
|
@ -103,6 +75,11 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
|||
{
|
||||
if (exception is GracefulException)
|
||||
{
|
||||
if (exception.InnerException != null)
|
||||
{
|
||||
Logger.LogInformation(exception.InnerException.Message);
|
||||
}
|
||||
|
||||
Logger.LogError(exception.Message);
|
||||
}
|
||||
else
|
||||
|
|
@ -134,65 +111,23 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
|||
CommandOutputProvider.LogLevel = LogLevel.Debug;
|
||||
}
|
||||
|
||||
var userSecretsId = ResolveUserSecretsId(options);
|
||||
var userSecretsId = ResolveId(options);
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
|
||||
private string ResolveUserSecretsId(CommandLineOptions options)
|
||||
internal string ResolveId(CommandLineOptions options)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(options.Id))
|
||||
{
|
||||
return options.Id;
|
||||
}
|
||||
|
||||
var projectPath = options.Project ?? _workingDirectory;
|
||||
|
||||
if (!Path.IsPathRooted(projectPath))
|
||||
using (var resolver = new ProjectIdResolver(Logger, _workingDirectory))
|
||||
{
|
||||
projectPath = Path.Combine(_workingDirectory, projectPath);
|
||||
}
|
||||
|
||||
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;
|
||||
return resolver.Resolve(options.Project, options.Configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,22 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
|||
private static readonly ResourceManager _resourceManager
|
||||
= 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>
|
||||
/// Command failed : {message}
|
||||
/// </summary>
|
||||
|
|
@ -60,6 +76,22 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
|||
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>
|
||||
/// No secrets configured for this application.
|
||||
/// </summary>
|
||||
|
|
@ -76,6 +108,38 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
|||
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>
|
||||
/// The project file '{path}' does not exist.
|
||||
/// </summary>
|
||||
|
|
@ -92,6 +156,22 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
|||
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>
|
||||
/// Project file path {project}.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</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>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -117,6 +117,9 @@
|
|||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</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">
|
||||
<value>Command failed : {message}</value>
|
||||
</data>
|
||||
|
|
@ -127,12 +130,24 @@ Use the '--help' flag to see info.</value>
|
|||
<data name="Error_Missing_Secret" xml:space="preserve">
|
||||
<value>Cannot find '{key}' in the secret store.</value>
|
||||
</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">
|
||||
<value>No secrets configured for this application.</value>
|
||||
</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">
|
||||
<value>The project file '{path}' does not exist.</value>
|
||||
</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">
|
||||
<value>Project file path {project}.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,13 @@
|
|||
"outputName": "dotnet-user-secrets",
|
||||
"emitEntryPoint": 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.",
|
||||
"packOptions": {
|
||||
|
|
@ -16,18 +22,22 @@
|
|||
"configuration",
|
||||
"secrets",
|
||||
"usersecrets"
|
||||
]
|
||||
],
|
||||
"files": {
|
||||
"mappings": {
|
||||
"tools/FindUserSecretsProperty.targets": "FindUserSecretsProperty.targets"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.DotNet.Cli.Utils": "1.0.0-*",
|
||||
"Microsoft.Extensions.CommandLineUtils": "1.1.0-*",
|
||||
"Microsoft.Extensions.Configuration.UserSecrets": "1.1.0-*",
|
||||
"Microsoft.Extensions.Logging": "1.1.0-*",
|
||||
"Microsoft.NETCore.App": {
|
||||
"version": "1.1.0-*",
|
||||
"type": "platform"
|
||||
},
|
||||
"Newtonsoft.Json": "9.0.1",
|
||||
"System.Runtime.Serialization.Primitives": "4.3.0-*"
|
||||
}
|
||||
},
|
||||
"frameworks": {
|
||||
"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.IO;
|
||||
using System.Text;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.Extensions.Configuration.UserSecrets;
|
||||
using Microsoft.Extensions.Configuration.UserSecrets.Tests;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.SecretManager.Tools.Internal;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||
{
|
||||
public class SecretManagerTests : IDisposable
|
||||
public class SecretManagerTests : IClassFixture<UserSecretsTestFixture>
|
||||
{
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
private string GetTempSecretProject()
|
||||
private Program CreateProgram()
|
||||
{
|
||||
string id;
|
||||
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)
|
||||
return new Program(new TestConsole(), Directory.GetCurrentDirectory())
|
||||
{
|
||||
_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]
|
||||
public void Error_Project_DoesNotExist()
|
||||
{
|
||||
var projectPath = Path.Combine(GetTempSecretProject(), "does_not_exist", "project.json");
|
||||
var secretManager = new Program(new TestConsole(), Directory.GetCurrentDirectory()) { Logger = _logger };
|
||||
var projectPath = Path.Combine(_fixture.GetTempSecretProject(), "does_not_exist", "TestProject.csproj");
|
||||
var secretManager = CreateProgram();
|
||||
|
||||
var ex = Assert.Throws<GracefulException>(() => secretManager.RunInternal("list", "--project", projectPath));
|
||||
|
||||
Assert.Equal(Resources.FormatError_ProjectPath_NotFound(projectPath), ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsRelativePaths()
|
||||
{
|
||||
var projectPath = GetTempSecretProject();
|
||||
var projectPath = _fixture.GetTempSecretProject();
|
||||
var cwd = Path.Combine(projectPath, "nested1");
|
||||
Directory.CreateDirectory(cwd);
|
||||
var secretManager = new Program(new TestConsole(), cwd) { Logger = _logger, CommandOutputProvider = _logger.CommandOutputProvider };
|
||||
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]
|
||||
|
|
@ -82,7 +93,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
|||
new KeyValuePair<string, string>("key2", string.Empty)
|
||||
};
|
||||
|
||||
var projectPath = GetTempSecretProject();
|
||||
var projectPath = _fixture.GetTempSecretProject();
|
||||
var dir = fromCurrentDirectory
|
||||
? projectPath
|
||||
: Path.GetTempPath();
|
||||
|
|
@ -141,8 +152,8 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
|||
[Fact]
|
||||
public void SetSecret_Update_Existing_Secret()
|
||||
{
|
||||
var projectPath = GetTempSecretProject();
|
||||
var secretManager = new Program() { Logger = _logger };
|
||||
var projectPath = _fixture.GetTempSecretProject();
|
||||
var secretManager = CreateProgram();
|
||||
|
||||
secretManager.RunInternal("set", "secret1", "value1", "-p", projectPath);
|
||||
Assert.Equal(1, _logger.Messages.Count);
|
||||
|
|
@ -161,31 +172,31 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
|||
[Fact]
|
||||
public void SetSecret_With_Verbose_Flag()
|
||||
{
|
||||
string id;
|
||||
var projectPath = GetTempSecretProject(out id);
|
||||
string secretId;
|
||||
var projectPath = _fixture.GetTempSecretProject(out secretId);
|
||||
_logger.SetLevel(LogLevel.Debug);
|
||||
var secretManager = new Program() { Logger = _logger };
|
||||
var secretManager = CreateProgram();
|
||||
|
||||
secretManager.RunInternal("-v", "set", "secret1", "value1", "-p", projectPath);
|
||||
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("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(id)), _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(secretId)), _logger.Messages);
|
||||
Assert.Contains("Successfully saved secret1 = value1 to the secret store.", _logger.Messages);
|
||||
_logger.Messages.Clear();
|
||||
|
||||
secretManager.RunInternal("-v", "list", "-p", projectPath);
|
||||
|
||||
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("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(id)), _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(secretId)), _logger.Messages);
|
||||
Assert.Contains("secret1 = value1", _logger.Messages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_Non_Existing_Secret()
|
||||
{
|
||||
var projectPath = GetTempSecretProject();
|
||||
var secretManager = new Program() { Logger = _logger };
|
||||
var projectPath = _fixture.GetTempSecretProject();
|
||||
var secretManager = CreateProgram();
|
||||
secretManager.RunInternal("remove", "secret1", "-p", projectPath);
|
||||
Assert.Equal(1, _logger.Messages.Count);
|
||||
Assert.Contains("Cannot find 'secret1' in the secret store.", _logger.Messages);
|
||||
|
|
@ -194,8 +205,8 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
|||
[Fact]
|
||||
public void Remove_Is_Case_Insensitive()
|
||||
{
|
||||
var projectPath = GetTempSecretProject();
|
||||
var secretManager = new Program() { Logger = _logger };
|
||||
var projectPath = _fixture.GetTempSecretProject();
|
||||
var secretManager = CreateProgram();
|
||||
secretManager.RunInternal("set", "SeCreT1", "value", "-p", projectPath);
|
||||
secretManager.RunInternal("list", "-p", projectPath);
|
||||
Assert.Contains("SeCreT1 = value", _logger.Messages);
|
||||
|
|
@ -211,12 +222,12 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
|||
[Fact]
|
||||
public void List_Flattens_Nested_Objects()
|
||||
{
|
||||
string id;
|
||||
var projectPath = GetTempSecretProject(out id);
|
||||
var secretsFile = PathHelper.GetSecretsPathFromSecretsId(id);
|
||||
string secretId;
|
||||
var projectPath = _fixture.GetTempSecretProject(out secretId);
|
||||
var secretsFile = PathHelper.GetSecretsPathFromSecretsId(secretId);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(secretsFile));
|
||||
File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8);
|
||||
var secretManager = new Program() { Logger = _logger };
|
||||
var secretManager = CreateProgram();
|
||||
secretManager.RunInternal("list", "-p", projectPath);
|
||||
Assert.Equal(1, _logger.Messages.Count);
|
||||
Assert.Contains("AzureAd:ClientSecret = abcd郩˙î", _logger.Messages);
|
||||
|
|
@ -231,7 +242,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
|||
Out = new StringWriter(output)
|
||||
};
|
||||
string id;
|
||||
var projectPath = GetTempSecretProject(out id);
|
||||
var projectPath = _fixture.GetTempSecretProject(out id);
|
||||
var secretsFile = PathHelper.GetSecretsPathFromSecretsId(id);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(secretsFile));
|
||||
File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8);
|
||||
|
|
@ -246,12 +257,12 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
|||
[Fact]
|
||||
public void Set_Flattens_Nested_Objects()
|
||||
{
|
||||
string id;
|
||||
var projectPath = GetTempSecretProject(out id);
|
||||
var secretsFile = PathHelper.GetSecretsPathFromSecretsId(id);
|
||||
string secretId;
|
||||
var projectPath = _fixture.GetTempSecretProject(out secretId);
|
||||
var secretsFile = PathHelper.GetSecretsPathFromSecretsId(secretId);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(secretsFile));
|
||||
File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8);
|
||||
var secretManager = new Program() { Logger = _logger };
|
||||
var secretManager = CreateProgram();
|
||||
secretManager.RunInternal("set", "AzureAd:ClientSecret", "¡™£¢∞", "-p", projectPath);
|
||||
Assert.Equal(1, _logger.Messages.Count);
|
||||
secretManager.RunInternal("list", "-p", projectPath);
|
||||
|
|
@ -268,8 +279,8 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
|||
[Fact]
|
||||
public void List_Empty_Secrets_File()
|
||||
{
|
||||
var projectPath = GetTempSecretProject();
|
||||
var secretManager = new Program() { Logger = _logger };
|
||||
var projectPath = _fixture.GetTempSecretProject();
|
||||
var secretManager = CreateProgram();
|
||||
secretManager.RunInternal("list", "-p", projectPath);
|
||||
Assert.Equal(1, _logger.Messages.Count);
|
||||
Assert.Contains(Resources.Error_No_Secrets_Found, _logger.Messages);
|
||||
|
|
@ -280,7 +291,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
|||
[InlineData(false)]
|
||||
public void Clear_Secrets(bool fromCurrentDirectory)
|
||||
{
|
||||
var projectPath = GetTempSecretProject();
|
||||
var projectPath = _fixture.GetTempSecretProject();
|
||||
|
||||
var dir = fromCurrentDirectory
|
||||
? projectPath
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
|||
var secretStore = new TestSecretsStore();
|
||||
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)));
|
||||
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