Move dotnet-user-secrets into current repo

This commit is contained in:
Nate McMaster 2016-09-19 10:50:49 -07:00
parent 3b351ad1bb
commit 8f2eccbd36
No known key found for this signature in database
GPG Key ID: BD729980AA6A21BD
27 changed files with 1489 additions and 45 deletions

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{66517987-2A5A-4330-B130-207039378FD4}"
EndProject
@ -29,6 +29,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AppWithDeps", "test\TestApp
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Dependency", "test\TestApps\Dependency\Dependency.xproj", "{2F48041A-F7D1-478F-9C38-D41F0F05E8CA}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.SecretManager.Tools", "src\Microsoft.Extensions.SecretManager.Tools\Microsoft.Extensions.SecretManager.Tools.xproj", "{8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.SecretManager.Tools.Tests", "test\Microsoft.Extensions.SecretManager.Tools.Tests\Microsoft.Extensions.SecretManager.Tools.Tests.xproj", "{7B331122-83B1-4F08-A119-DC846959844C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -63,6 +67,14 @@ Global
{2F48041A-F7D1-478F-9C38-D41F0F05E8CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F48041A-F7D1-478F-9C38-D41F0F05E8CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F48041A-F7D1-478F-9C38-D41F0F05E8CA}.Release|Any CPU.Build.0 = Release|Any CPU
{8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.Release|Any CPU.Build.0 = Release|Any CPU
{7B331122-83B1-4F08-A119-DC846959844C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7B331122-83B1-4F08-A119-DC846959844C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7B331122-83B1-4F08-A119-DC846959844C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7B331122-83B1-4F08-A119-DC846959844C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -76,5 +88,7 @@ Global
{2AB1A28B-2022-49EA-AF77-AC8A875915CC} = {2876B12E-5841-4792-85A8-2929AEE11885}
{F7734E61-F510-41E0-AD15-301A64081CD1} = {2876B12E-5841-4792-85A8-2929AEE11885}
{2F48041A-F7D1-478F-9C38-D41F0F05E8CA} = {2876B12E-5841-4792-85A8-2929AEE11885}
{8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E} = {66517987-2A5A-4330-B130-207039378FD4}
{7B331122-83B1-4F08-A119-DC846959844C} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134}
EndGlobalSection
EndGlobal

View File

@ -5,7 +5,8 @@
],
"packages": {
"Microsoft.DotNet.Watcher.Tools": { },
"Microsoft.DotNet.Watcher.Core": { }
"Microsoft.DotNet.Watcher.Core": { },
"Microsoft.Extensions.SecretManager.Tools": { }
}
},
"Default": { // Rules to run for packages not listed in any other set.

View File

@ -1,48 +1,12 @@
dotnet-watch
===
`dotnet-watch` is a file watcher for `dotnet` that restarts the specified application when changes in the source code are detected.
DotNetTools
===========
### How To Install
[![Travis build status](https://img.shields.io/travis/aspnet/dotnet-watch.svg?label=travis-ci&branch=dev&style=flat-square)](https://travis-ci.org/aspnet/dotnet-watch/branches)
[![AppVeyor build status](https://img.shields.io/appveyor/ci/aspnetci/dnx-watch/dev.svg?label=appveyor&style=flat-square)](https://ci.appveyor.com/project/aspnetci/dnx-watch/branch/dev)
Add `Microsoft.DotNet.Watcher.Tools` to the `tools` section of your `project.json` file:
The project contains command-line tools for the .NET Core SDK.
```
{
...
"tools": {
"Microsoft.DotNet.Watcher.Tools": {
"version": "1.0.0-*",
"imports": "portable-net451+win8"
}
}
...
}
```
### How To Use
dotnet watch [dotnet arguments]
Add `watch` after `dotnet` in the command that you want to run:
| What you want to run | Dotnet watch command |
| ---------------------------------------------- | -------------------------------------------------------- |
| dotnet run | dotnet **watch** run |
| dotnet run --arg1 value1 | dotnet **watch** run --arg1 value |
| dotnet run --framework net451 -- --arg1 value1 | dotnet **watch** run --framework net451 -- --arg1 value1 |
| dotnet test | dotnet **watch** test |
### Advanced configuration options
Configuration options can be passed to `dotnet watch` through environment variables. The available variables are:
| Variable | Effect |
| ---------------------------------------------- | -------------------------------------------------------- |
| DOTNET_USE_POLLING_FILE_WATCHER | If set to "1" or "true", `dotnet watch` will use a polling file watcher instead of CoreFx's `FileSystemWatcher`. Used when watching files on network shares or Docker mounted volumes. |
| DOTNET_WATCH_LOG_LEVEL | Used to set the logging level for messages coming from `dotnet watch`. Accepted values `None`, `Trace`, `Debug`, `Information`, `Warning`, `Error`, `Critical`. Default: `Information`. |
AppVeyor: [![AppVeyor](https://ci.appveyor.com/api/projects/status/fxhto3omtehio3aj/branch/dev?svg=true)](https://ci.appveyor.com/project/aspnetci/dnx-watch/branch/dev)
Travis: [![Travis](https://travis-ci.org/aspnet/dotnet-watch.svg?branch=dev)](https://travis-ci.org/aspnet/dotnet-watch)
- [dotnet-watch](src/Microsoft.DotNet.Watcher.Tools/)
- [dotnet-user-secrets](src/Microsoft.Extensions.SecretManager.Tools/)
This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo.

View File

@ -0,0 +1,39 @@
dotnet-watch
============
`dotnet-watch` is a file watcher for `dotnet` that restarts the specified application when changes in the source code are detected.
### How To Install
Add `Microsoft.DotNet.Watcher.Tools` to the `tools` section of your `project.json` file:
```
{
...
"tools": {
"Microsoft.DotNet.Watcher.Tools": "1.0.0-*"
}
...
}
```
### How To Use
dotnet watch [dotnet arguments]
Add `watch` after `dotnet` in the command that you want to run:
| What you want to run | Dotnet watch command |
| ---------------------------------------------- | -------------------------------------------------------- |
| dotnet run | dotnet **watch** run |
| dotnet run --arg1 value1 | dotnet **watch** run --arg1 value |
| dotnet run --framework net451 -- --arg1 value1 | dotnet **watch** run --framework net451 -- --arg1 value1 |
| dotnet test | dotnet **watch** test |
### Advanced configuration options
Configuration options can be passed to `dotnet watch` through environment variables. The available variables are:
| Variable | Effect |
| ---------------------------------------------- | -------------------------------------------------------- |
| DOTNET_USE_POLLING_FILE_WATCHER | If set to "1" or "true", `dotnet watch` will use a polling file watcher instead of CoreFx's `FileSystemWatcher`. Used when watching files on network shares or Docker mounted volumes. |
| DOTNET_WATCH_LOG_LEVEL | Used to set the logging level for messages coming from `dotnet watch`. Accepted values `None`, `Trace`, `Debug`, `Information`, `Warning`, `Error`, `Critical`. Default: `Information`. |

View File

@ -0,0 +1,62 @@
// 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 Microsoft.Extensions.CommandLineUtils;
using Microsoft.Extensions.Logging;
namespace Microsoft.Extensions.SecretManager.Tools
{
/// <summary>
/// Logger to print formatted command output.
/// </summary>
public class CommandOutputLogger : ILogger
{
private readonly CommandOutputProvider _provider;
private readonly AnsiConsole _outConsole;
public CommandOutputLogger(CommandOutputProvider commandOutputProvider, bool useConsoleColor)
{
_provider = commandOutputProvider;
_outConsole = AnsiConsole.GetOutput(useConsoleColor);
}
public IDisposable BeginScope<TState>(TState state)
{
throw new NotImplementedException();
}
public bool IsEnabled(LogLevel logLevel)
{
if (logLevel < _provider.LogLevel)
{
return false;
}
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (IsEnabled(logLevel))
{
_outConsole.WriteLine(string.Format("{0}: {1}", Caption(logLevel), formatter(state, exception)));
}
}
private string Caption(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Trace: return "\x1b[35mtrace\x1b[39m";
case LogLevel.Debug: return "\x1b[35mdebug\x1b[39m";
case LogLevel.Information: return "\x1b[32minfo\x1b[39m";
case LogLevel.Warning: return "\x1b[33mwarn\x1b[39m";
case LogLevel.Error: return "\x1b[31mfail\x1b[39m";
case LogLevel.Critical: return "\x1b[31mcritical\x1b[39m";
}
throw new Exception("Unknown LogLevel");
}
}
}

View File

@ -0,0 +1,23 @@
// 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.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
namespace Microsoft.Extensions.SecretManager.Tools
{
public class CommandOutputProvider : ILoggerProvider
{
public ILogger CreateLogger(string name)
{
var useConsoleColor = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
return new CommandOutputLogger(this, useConsoleColor);
}
public void Dispose()
{
}
public LogLevel LogLevel { get; set; } = LogLevel.Information;
}
}

View File

@ -0,0 +1,28 @@
// 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.Extensions.CommandLineUtils;
using Microsoft.Extensions.Logging;
namespace Microsoft.Extensions.SecretManager.Tools.Internal
{
internal class ClearCommand : ICommand
{
public static void Configure(CommandLineApplication command, CommandLineOptions options)
{
command.Description = "Deletes all the application secrets";
command.HelpOption();
command.OnExecute(() =>
{
options.Command = new ClearCommand();
});
}
public void Execute(SecretsStore store, ILogger logger)
{
store.Clear();
store.Save();
}
}
}

View File

@ -0,0 +1,24 @@
// 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.CommandLineUtils
{
public static class UserSecretsCommandLineExtensions
{
public static CommandOption HelpOption(this CommandLineApplication app)
{
return app.HelpOption("-?|-h|--help");
}
public static void OnExecute(this CommandLineApplication app, Action action)
{
app.OnExecute(() =>
{
action();
return 0;
});
}
}
}

View File

@ -0,0 +1,70 @@
// 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 System.Reflection;
using Microsoft.Extensions.CommandLineUtils;
namespace Microsoft.Extensions.SecretManager.Tools.Internal
{
public class CommandLineOptions
{
public bool IsVerbose { get; set; }
public bool IsHelp { get; set; }
public string Project { get; set; }
internal ICommand Command { get; set; }
public static CommandLineOptions Parse(string[] args, TextWriter output)
{
var app = new CommandLineApplication()
{
Out = output,
Name = "dotnet user-secrets",
FullName = "User Secrets Manager",
Description = "Manages user secrets"
};
app.HelpOption();
app.VersionOption("--version", GetInformationalVersion());
var optionVerbose = app.Option("-v|--verbose", "Verbose output",
CommandOptionType.NoValue, inherited: true);
var optionProject = app.Option("-p|--project <PROJECT>", "Path to project, default is current directory",
CommandOptionType.SingleValue, inherited: true);
var options = new CommandLineOptions();
app.Command("set", c => SetCommand.Configure(c, options));
app.Command("remove", c => RemoveCommand.Configure(c, options));
app.Command("list", c => ListCommand.Configure(c, options));
app.Command("clear", c => ClearCommand.Configure(c, options));
// Show help information if no subcommand/option was specified.
app.OnExecute(() => app.ShowHelp());
if (app.Execute(args) != 0)
{
// when command line parsing error in subcommand
return null;
}
options.IsHelp = app.IsShowingInformation;
options.IsVerbose = optionVerbose.HasValue();
options.Project = optionProject.Value();
return options;
}
private static string GetInformationalVersion()
{
var assembly = typeof(Program).GetTypeInfo().Assembly;
var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
var versionAttribute = attribute == null ?
assembly.GetName().Version.ToString() :
attribute.InformationalVersion;
return versionAttribute;
}
}
}

View File

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

View File

@ -0,0 +1,12 @@
// 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.Extensions.Logging;
namespace Microsoft.Extensions.SecretManager.Tools.Internal
{
internal interface ICommand
{
void Execute(SecretsStore store, ILogger logger);
}
}

View File

@ -0,0 +1,37 @@
// 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.Extensions.CommandLineUtils;
using Microsoft.Extensions.Logging;
namespace Microsoft.Extensions.SecretManager.Tools.Internal
{
internal class ListCommand : ICommand
{
public static void Configure(CommandLineApplication command, CommandLineOptions options)
{
command.Description = "Lists all the application secrets";
command.HelpOption();
command.OnExecute(() =>
{
options.Command = new ListCommand();
});
}
public void Execute(SecretsStore store, ILogger logger)
{
if (store.Count == 0)
{
logger.LogInformation(Resources.Error_No_Secrets_Found);
}
else
{
foreach (var secret in store.AsEnumerable())
{
logger.LogInformation(Resources.FormatMessage_Secret_Value_Format(secret.Key, secret.Value));
}
}
}
}
}

View File

@ -0,0 +1,49 @@
// 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.Extensions.CommandLineUtils;
using Microsoft.Extensions.Logging;
namespace Microsoft.Extensions.SecretManager.Tools.Internal
{
internal class RemoveCommand : ICommand
{
private readonly string _keyName;
public static void Configure(CommandLineApplication command, CommandLineOptions options)
{
command.Description = "Removes the specified user secret";
command.HelpOption();
var keyArg = command.Argument("[name]", "Name of the secret");
command.OnExecute(() =>
{
if (keyArg.Value == null)
{
throw new GracefulException("Missing parameter value for 'name'.\nUse the '--help' flag to see info.");
}
options.Command = new RemoveCommand(keyArg.Value);
});
}
public RemoveCommand(string keyName)
{
_keyName = keyName;
}
public void Execute(SecretsStore store, ILogger logger)
{
if (!store.ContainsKey(_keyName))
{
logger.LogWarning(Resources.Error_Missing_Secret, _keyName);
}
else
{
store.Remove(_keyName);
store.Save();
}
}
}
}

View File

@ -0,0 +1,78 @@
// 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.Text;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.UserSecrets;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
namespace Microsoft.Extensions.SecretManager.Tools.Internal
{
internal class SecretsStore
{
private readonly string _secretsFilePath;
private IDictionary<string, string> _secrets;
public SecretsStore(string userSecretsId, ILogger logger)
{
if (userSecretsId == null)
{
throw new ArgumentNullException(nameof(userSecretsId));
}
_secretsFilePath = PathHelper.GetSecretsPathFromSecretsId(userSecretsId);
logger.LogDebug(Resources.Message_Secret_File_Path, _secretsFilePath);
// workaround https://github.com/aspnet/Configuration/issues/478
// TODO remove when tool upgrades to use 1.1.0
Directory.CreateDirectory(Path.GetDirectoryName(_secretsFilePath));
//end workaround
_secrets = new ConfigurationBuilder()
.AddJsonFile(_secretsFilePath, optional: true)
.Build()
.AsEnumerable()
.Where(i => i.Value != null)
.ToDictionary(i => i.Key, i => i.Value, StringComparer.OrdinalIgnoreCase);
}
public int Count => _secrets.Count;
public bool ContainsKey(string key) => _secrets.ContainsKey(key);
public IEnumerable<KeyValuePair<string, string>> AsEnumerable() => _secrets;
public void Clear() => _secrets.Clear();
public void Set(string key, string value) => _secrets[key] = value;
public void Remove(string key)
{
if (_secrets.ContainsKey(key))
{
_secrets.Remove(key);
}
}
public void Save()
{
Directory.CreateDirectory(Path.GetDirectoryName(_secretsFilePath));
var contents = new JObject();
if (_secrets != null)
{
foreach (var secret in _secrets.AsEnumerable())
{
contents[secret.Key] = secret.Value;
}
}
File.WriteAllText(_secretsFilePath, contents.ToString(), Encoding.UTF8);
}
}
}

View File

@ -0,0 +1,51 @@
// 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.Extensions.CommandLineUtils;
using Microsoft.Extensions.Logging;
namespace Microsoft.Extensions.SecretManager.Tools.Internal
{
internal class SetCommand : ICommand
{
private readonly string _keyName;
private readonly string _keyValue;
public static void Configure(CommandLineApplication command, CommandLineOptions options)
{
command.Description = "Sets the user secret to the specified value";
command.HelpOption();
var keyArg = command.Argument("[name]", "Name of the secret");
var valueArg = command.Argument("[value]", "Value of the secret");
command.OnExecute(() =>
{
if (keyArg.Value == null)
{
throw new GracefulException("Missing parameter value for 'name'.\nUse the '--help' flag to see info.");
}
if (valueArg.Value == null)
{
throw new GracefulException("Missing parameter value for 'value'.\nUse the '--help' flag to see info.");
}
options.Command = new SetCommand(keyArg.Value, valueArg.Value);
});
}
public SetCommand(string keyName, string keyValue)
{
_keyName = keyName;
_keyValue = keyValue;
}
public void Execute(SecretsStore store, ILogger logger)
{
store.Set(_keyName, _keyValue);
store.Save();
logger.LogInformation(Resources.Message_Saved_Secret, _keyName, _keyValue);
}
}
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>8730e848-ca0f-4e0a-9a2f-bc22ad0b2c4e</ProjectGuid>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,183 @@
// 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.Diagnostics;
using System.IO;
using System.Linq;
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
{
public class Program
{
private ILogger _logger;
private CommandOutputProvider _loggerProvider;
private readonly TextWriter _consoleOutput;
private readonly string _workingDirectory;
public Program()
: this(Console.Out, Directory.GetCurrentDirectory())
{
}
internal Program(TextWriter consoleOutput, string workingDirectory)
{
_consoleOutput = consoleOutput;
_workingDirectory = workingDirectory;
var loggerFactory = new LoggerFactory();
CommandOutputProvider = new CommandOutputProvider();
loggerFactory.AddProvider(CommandOutputProvider);
Logger = loggerFactory.CreateLogger<Program>();
}
public ILogger Logger
{
get { return _logger; }
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
_logger = value;
}
}
public CommandOutputProvider CommandOutputProvider
{
get { return _loggerProvider; }
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
_loggerProvider = value;
}
}
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
{
returnCode = RunInternal(args);
return true;
}
catch (Exception exception)
{
if (exception is GracefulException)
{
Logger.LogError(exception.Message);
}
else
{
Logger.LogDebug(exception.ToString());
Logger.LogCritical(Resources.Error_Command_Failed, exception.Message);
}
returnCode = 1;
return false;
}
}
internal int RunInternal(params string[] args)
{
var options = CommandLineOptions.Parse(args, _consoleOutput);
if (options == null)
{
return 1;
}
if (options.IsHelp)
{
return 2;
}
if (options.IsVerbose)
{
CommandOutputProvider.LogLevel = LogLevel.Debug;
}
var userSecretsId = ResolveUserSecretsId(options);
var store = new SecretsStore(userSecretsId, Logger);
options.Command.Execute(store, Logger);
return 0;
}
private string ResolveUserSecretsId(CommandLineOptions options)
{
var projectPath = options.Project ?? _workingDirectory;
if (!projectPath.EndsWith("project.json", StringComparison.OrdinalIgnoreCase))
{
projectPath = Path.Combine(projectPath, "project.json");
}
var fileInfo = new PhysicalFileInfo(new FileInfo(projectPath));
Logger.LogDebug(Resources.Message_Project_File_Path, fileInfo.PhysicalPath);
return ReadUserSecretsId(fileInfo);
}
// TODO can use runtime API when upgrading to 1.1
private string ReadUserSecretsId(IFileInfo fileInfo)
{
if (fileInfo == null || !fileInfo.Exists)
{
throw new GracefulException($"Could not find file '{fileInfo.PhysicalPath}'");
}
using (var stream = fileInfo.CreateReadStream())
using (var streamReader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(streamReader))
{
var obj = JObject.Load(jsonReader);
var userSecretsId = obj.Value<string>("userSecretsId");
if (string.IsNullOrEmpty(userSecretsId))
{
throw new GracefulException($"Could not find 'userSecretsId' in json file '{fileInfo.PhysicalPath}'");
}
return userSecretsId;
}
}
}
}

View File

@ -0,0 +1,11 @@
// 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.Reflection;
using System.Resources;
[assembly: AssemblyMetadata("Serviceable", "True")]
[assembly: NeutralResourcesLanguage("en-us")]
[assembly: AssemblyCompany("Microsoft Corporation.")]
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
[assembly: AssemblyProduct("Microsoft .NET Extensions")]

View File

@ -0,0 +1,6 @@
// 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Extensions.SecretManager.Tools.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -0,0 +1,142 @@
// <auto-generated />
namespace Microsoft.Extensions.SecretManager.Tools
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.Extensions.SecretManager.Tools.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// Command failed : {message}
/// </summary>
internal static string Error_Command_Failed
{
get { return GetString("Error_Command_Failed"); }
}
/// <summary>
/// Command failed : {message}
/// </summary>
internal static string FormatError_Command_Failed(object message)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Error_Command_Failed", "message"), message);
}
/// <summary>
/// Cannot find '{key}' in the secret store.
/// </summary>
internal static string Error_Missing_Secret
{
get { return GetString("Error_Missing_Secret"); }
}
/// <summary>
/// Cannot find '{key}' in the secret store.
/// </summary>
internal static string FormatError_Missing_Secret(object key)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Error_Missing_Secret", "key"), key);
}
/// <summary>
/// No secrets configured for this application.
/// </summary>
internal static string Error_No_Secrets_Found
{
get { return GetString("Error_No_Secrets_Found"); }
}
/// <summary>
/// No secrets configured for this application.
/// </summary>
internal static string FormatError_No_Secrets_Found()
{
return GetString("Error_No_Secrets_Found");
}
/// <summary>
/// Project file path {project}.
/// </summary>
internal static string Message_Project_File_Path
{
get { return GetString("Message_Project_File_Path"); }
}
/// <summary>
/// Project file path {project}.
/// </summary>
internal static string FormatMessage_Project_File_Path(object project)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Message_Project_File_Path", "project"), project);
}
/// <summary>
/// Successfully saved {key} = {value} to the secret store.
/// </summary>
internal static string Message_Saved_Secret
{
get { return GetString("Message_Saved_Secret"); }
}
/// <summary>
/// Successfully saved {key} = {value} to the secret store.
/// </summary>
internal static string FormatMessage_Saved_Secret(object key, object value)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Message_Saved_Secret", "key", "value"), key, value);
}
/// <summary>
/// Secrets file path {secretsFilePath}.
/// </summary>
internal static string Message_Secret_File_Path
{
get { return GetString("Message_Secret_File_Path"); }
}
/// <summary>
/// Secrets file path {secretsFilePath}.
/// </summary>
internal static string FormatMessage_Secret_File_Path(object secretsFilePath)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Message_Secret_File_Path", "secretsFilePath"), secretsFilePath);
}
/// <summary>
/// {key} = {value}
/// </summary>
internal static string Message_Secret_Value_Format
{
get { return GetString("Message_Secret_Value_Format"); }
}
/// <summary>
/// {key} = {value}
/// </summary>
internal static string FormatMessage_Secret_Value_Format(object key, object value)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Message_Secret_Value_Format", "key", "value"), key, value);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -0,0 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
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
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<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
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
mimetype set.
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
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
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
: 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
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Error_Command_Failed" xml:space="preserve">
<value>Command failed : {message}</value>
</data>
<data name="Error_Missing_Secret" xml:space="preserve">
<value>Cannot find '{key}' in the secret store.</value>
</data>
<data name="Error_No_Secrets_Found" xml:space="preserve">
<value>No secrets configured for this application.</value>
</data>
<data name="Message_Project_File_Path" xml:space="preserve">
<value>Project file path {project}.</value>
</data>
<data name="Message_Saved_Secret" xml:space="preserve">
<value>Successfully saved {key} = {value} to the secret store.</value>
</data>
<data name="Message_Secret_File_Path" xml:space="preserve">
<value>Secrets file path {secretsFilePath}.</value>
</data>
<data name="Message_Secret_Value_Format" xml:space="preserve">
<value>{key} = {value}</value>
</data>
</root>

View File

@ -0,0 +1,43 @@
{
"version": "1.0.0-*",
"buildOptions": {
"outputName": "dotnet-user-secrets",
"emitEntryPoint": true,
"warningsAsErrors": true,
"keyFile": "../../tools/Key.snk",
"nowarn": [
"CS1591"
],
"xmlDoc": true
},
"description": "Command line tool to manage user secrets for Microsoft.Extensions.Configuration.",
"packOptions": {
"repository": {
"type": "git",
"url": "https://github.com/aspnet/DotNetTools"
},
"tags": [
"configuration",
"secrets",
"usersecrets"
]
},
"dependencies": {
"Microsoft.Extensions.Configuration.UserSecrets": "1.1.0-*",
"Microsoft.Extensions.CommandLineUtils": "1.1.0-*",
"Microsoft.Extensions.Logging": "1.1.0-*",
"Newtonsoft.Json": "9.0.1",
"System.Runtime.InteropServices.RuntimeInformation": "4.0.0",
"System.Runtime.Serialization.Primitives": "4.1.1"
},
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
}
}
}
}
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0.25420" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25420</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>7b331122-83b1-4f08-a119-dc846959844c</ProjectGuid>
<RootNamespace>Microsoft.Extensions.SecretManager.Tools.Tests</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,284 @@
// 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.Text;
using Microsoft.Extensions.Configuration.UserSecrets;
using Microsoft.Extensions.Configuration.UserSecrets.Tests;
using Microsoft.Extensions.Logging;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.Extensions.SecretManager.Tools.Tests
{
public class SecretManagerTests
{
private TestLogger _logger;
public SecretManagerTests(ITestOutputHelper output)
{
_logger = new TestLogger(output);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void SetSecrets(bool fromCurrentDirectory)
{
var secrets = new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("key1", Guid.NewGuid().ToString()),
new KeyValuePair<string, string>("Facebook:AppId", Guid.NewGuid().ToString()),
new KeyValuePair<string, string>(@"key-@\/.~123!#$%^&*())-+==", @"key-@\/.~123!#$%^&*())-+=="),
new KeyValuePair<string, string>("key2", string.Empty)
};
var projectPath = UserSecretHelper.GetTempSecretProject();
var dir = fromCurrentDirectory
? projectPath
: Path.GetTempPath();
var secretManager = new Program(Console.Out, dir) { Logger = _logger };
foreach (var secret in secrets)
{
var parameters = fromCurrentDirectory ?
new string[] { "set", secret.Key, secret.Value } :
new string[] { "set", secret.Key, secret.Value, "-p", projectPath };
secretManager.RunInternal(parameters);
}
Assert.Equal(4, _logger.Messages.Count);
foreach (var keyValue in secrets)
{
Assert.Contains(
string.Format("Successfully saved {0} = {1} to the secret store.", keyValue.Key, keyValue.Value),
_logger.Messages);
}
_logger.Messages.Clear();
var args = fromCurrentDirectory
? new string[] { "list" }
: new string[] { "list", "-p", projectPath };
secretManager.RunInternal(args);
Assert.Equal(4, _logger.Messages.Count);
foreach (var keyValue in secrets)
{
Assert.Contains(
string.Format("{0} = {1}", keyValue.Key, keyValue.Value),
_logger.Messages);
}
// Remove secrets.
_logger.Messages.Clear();
foreach (var secret in secrets)
{
var parameters = fromCurrentDirectory ?
new string[] { "remove", secret.Key } :
new string[] { "remove", secret.Key, "-p", projectPath };
secretManager.RunInternal(parameters);
}
// Verify secrets are removed.
_logger.Messages.Clear();
args = fromCurrentDirectory
? new string[] { "list" }
: new string[] { "list", "-p", projectPath };
secretManager.RunInternal(args);
Assert.Equal(1, _logger.Messages.Count);
Assert.Contains(Resources.Error_No_Secrets_Found, _logger.Messages);
UserSecretHelper.DeleteTempSecretProject(projectPath);
}
[Fact]
public void SetSecret_Update_Existing_Secret()
{
var projectPath = UserSecretHelper.GetTempSecretProject();
var secretManager = new Program() { Logger = _logger };
secretManager.RunInternal("set", "secret1", "value1", "-p", projectPath);
Assert.Equal(1, _logger.Messages.Count);
Assert.Contains("Successfully saved secret1 = value1 to the secret store.", _logger.Messages);
secretManager.RunInternal("set", "secret1", "value2", "-p", projectPath);
Assert.Equal(2, _logger.Messages.Count);
Assert.Contains("Successfully saved secret1 = value2 to the secret store.", _logger.Messages);
_logger.Messages.Clear();
secretManager.RunInternal("list", "-p", projectPath);
Assert.Equal(1, _logger.Messages.Count);
Assert.Contains("secret1 = value2", _logger.Messages);
UserSecretHelper.DeleteTempSecretProject(projectPath);
}
[Fact]
public void SetSecret_With_Verbose_Flag()
{
var projectPath = UserSecretHelper.GetTempSecretProject();
_logger.SetLevel(LogLevel.Debug);
var secretManager = new Program() { Logger = _logger };
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.GetSecretsPath(projectPath)), _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.GetSecretsPath(projectPath)), _logger.Messages);
Assert.Contains("secret1 = value1", _logger.Messages);
UserSecretHelper.DeleteTempSecretProject(projectPath);
}
[Fact]
public void Remove_Non_Existing_Secret()
{
var projectPath = UserSecretHelper.GetTempSecretProject();
var secretManager = new Program() { Logger = _logger };
secretManager.RunInternal("remove", "secret1", "-p", projectPath);
Assert.Equal(1, _logger.Messages.Count);
Assert.Contains("Cannot find 'secret1' in the secret store.", _logger.Messages);
}
[Fact]
public void Remove_Is_Case_Insensitive()
{
var projectPath = UserSecretHelper.GetTempSecretProject();
var secretManager = new Program() { Logger = _logger };
secretManager.RunInternal("set", "SeCreT1", "value", "-p", projectPath);
secretManager.RunInternal("list", "-p", projectPath);
Assert.Contains("SeCreT1 = value", _logger.Messages);
secretManager.RunInternal("remove", "secret1", "-p", projectPath);
Assert.Equal(2, _logger.Messages.Count);
_logger.Messages.Clear();
secretManager.RunInternal("list", "-p", projectPath);
Assert.Contains(Resources.Error_No_Secrets_Found, _logger.Messages);
UserSecretHelper.DeleteTempSecretProject(projectPath);
}
[Fact]
public void List_Flattens_Nested_Objects()
{
var projectPath = UserSecretHelper.GetTempSecretProject();
var secretsFile = PathHelper.GetSecretsPath(projectPath);
Directory.CreateDirectory(Path.GetDirectoryName(secretsFile));
File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8);
var secretManager = new Program() { Logger = _logger };
secretManager.RunInternal("list", "-p", projectPath);
Assert.Equal(1, _logger.Messages.Count);
Assert.Contains("AzureAd:ClientSecret = abcd郩˙î", _logger.Messages);
UserSecretHelper.DeleteTempSecretProject(projectPath);
}
[Fact]
public void Set_Flattens_Nested_Objects()
{
var projectPath = UserSecretHelper.GetTempSecretProject();
var secretsFile = PathHelper.GetSecretsPath(projectPath);
Directory.CreateDirectory(Path.GetDirectoryName(secretsFile));
File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8);
var secretManager = new Program() { Logger = _logger };
secretManager.RunInternal("set", "AzureAd:ClientSecret", "¡™£¢∞", "-p", projectPath);
Assert.Equal(1, _logger.Messages.Count);
secretManager.RunInternal("list", "-p", projectPath);
Assert.Equal(2, _logger.Messages.Count);
Assert.Contains("AzureAd:ClientSecret = ¡™£¢∞", _logger.Messages);
var fileContents = File.ReadAllText(secretsFile, Encoding.UTF8);
Assert.Equal(@"{
""AzureAd:ClientSecret"": ""¡£¢""
}",
fileContents, ignoreLineEndingDifferences: true, ignoreWhiteSpaceDifferences: true);
UserSecretHelper.DeleteTempSecretProject(projectPath);
}
[Fact]
public void List_Empty_Secrets_File()
{
var projectPath = UserSecretHelper.GetTempSecretProject();
var secretManager = new Program() { Logger = _logger };
secretManager.RunInternal("list", "-p", projectPath);
Assert.Equal(1, _logger.Messages.Count);
Assert.Contains(Resources.Error_No_Secrets_Found, _logger.Messages);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Clear_Secrets(bool fromCurrentDirectory)
{
var projectPath = UserSecretHelper.GetTempSecretProject();
var dir = fromCurrentDirectory
? projectPath
: Path.GetTempPath();
var secretManager = new Program(Console.Out, dir) { Logger = _logger };
var secrets = new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("key1", Guid.NewGuid().ToString()),
new KeyValuePair<string, string>("Facebook:AppId", Guid.NewGuid().ToString()),
new KeyValuePair<string, string>(@"key-@\/.~123!#$%^&*())-+==", @"key-@\/.~123!#$%^&*())-+=="),
new KeyValuePair<string, string>("key2", string.Empty)
};
foreach (var secret in secrets)
{
var parameters = fromCurrentDirectory ?
new string[] { "set", secret.Key, secret.Value } :
new string[] { "set", secret.Key, secret.Value, "-p", projectPath };
secretManager.RunInternal(parameters);
}
Assert.Equal(4, _logger.Messages.Count);
foreach (var keyValue in secrets)
{
Assert.Contains(
string.Format("Successfully saved {0} = {1} to the secret store.", keyValue.Key, keyValue.Value),
_logger.Messages);
}
// Verify secrets are persisted.
_logger.Messages.Clear();
var args = fromCurrentDirectory ?
new string[] { "list" } :
new string[] { "list", "-p", projectPath };
secretManager.RunInternal(args);
Assert.Equal(4, _logger.Messages.Count);
foreach (var keyValue in secrets)
{
Assert.Contains(
string.Format("{0} = {1}", keyValue.Key, keyValue.Value),
_logger.Messages);
}
// Clear secrets.
_logger.Messages.Clear();
args = fromCurrentDirectory ? new string[] { "clear" } : new string[] { "clear", "-p", projectPath };
secretManager.RunInternal(args);
Assert.Equal(0, _logger.Messages.Count);
args = fromCurrentDirectory ? new string[] { "list" } : new string[] { "list", "-p", projectPath };
secretManager.RunInternal(args);
Assert.Equal(1, _logger.Messages.Count);
Assert.Contains(Resources.Error_No_Secrets_Found, _logger.Messages);
}
}
}

View File

@ -0,0 +1,50 @@
// 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 Microsoft.Extensions.Logging;
using Xunit.Abstractions;
namespace Microsoft.Extensions.SecretManager.Tools.Tests
{
public class TestLogger : ILogger
{
private CommandOutputProvider _commandOutputProvider;
private readonly ILogger _wrapped;
private readonly ITestOutputHelper _output;
public TestLogger(ITestOutputHelper output = null)
{
_commandOutputProvider = new CommandOutputProvider();
_wrapped = _commandOutputProvider.CreateLogger("");
_output = output;
}
public void SetLevel(LogLevel level)
{
_commandOutputProvider.LogLevel = LogLevel.Debug;
}
public List<string> Messages { get; set; } = new List<string>();
public IDisposable BeginScope<TState>(TState state)
{
throw new NotImplementedException();
}
public bool IsEnabled(LogLevel logLevel)
{
return _wrapped.IsEnabled(logLevel);
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (IsEnabled(logLevel))
{
Messages.Add(formatter(state, exception));
_output?.WriteLine(formatter(state, exception));
}
}
}
}

View File

@ -0,0 +1,47 @@
// 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
{
internal class UserSecretHelper
{
internal static string GetTempSecretProject()
{
string userSecretsId;
return GetTempSecretProject(out userSecretsId);
}
internal static string GetTempSecretProject(out string userSecretsId)
{
var projectPath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "usersecretstest", Guid.NewGuid().ToString()));
userSecretsId = Guid.NewGuid().ToString();
File.WriteAllText(
Path.Combine(projectPath.FullName, "project.json"),
JsonConvert.SerializeObject(new { userSecretsId }));
return projectPath.FullName;
}
internal static void SetTempSecretInProject(string projectPath, string userSecretsId)
{
File.WriteAllText(
Path.Combine(projectPath, "project.json"),
JsonConvert.SerializeObject(new { userSecretsId }));
}
internal static void DeleteTempSecretProject(string projectPath)
{
try
{
Directory.Delete(projectPath, true);
}
catch (Exception)
{
// Ignore failures.
}
}
}
}

View File

@ -0,0 +1,22 @@
{
"buildOptions": {
"warningsAsErrors": true,
"keyFile": "../../tools/Key.snk"
},
"dependencies": {
"dotnet-test-xunit": "2.2.0-*",
"Microsoft.Extensions.SecretManager.Tools": "1.0.0-*",
"xunit": "2.2.0-*"
},
"testRunner": "xunit",
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
}
}
}
}
}