Add `user-secret init` command for adding a UserSecretsId to a project file (#500)
This commit is contained in:
parent
2af66e4eba
commit
9de04520e0
|
|
@ -50,6 +50,7 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
|||
app.Command("remove", c => RemoveCommand.Configure(c, options));
|
||||
app.Command("list", c => ListCommand.Configure(c, options));
|
||||
app.Command("clear", c => ClearCommand.Configure(c, options));
|
||||
app.Command("init", c => InitCommandFactory.Configure(c, options));
|
||||
|
||||
// Show help information if no subcommand/option was specified.
|
||||
app.OnExecute(() => app.ShowHelp());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
// 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 System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
|
||||
namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||
{
|
||||
// Workaround used to handle the fact that the options have not been parsed at configuration time
|
||||
public class InitCommandFactory : ICommand
|
||||
{
|
||||
public CommandLineOptions Options { get; }
|
||||
|
||||
internal static void Configure(CommandLineApplication command, CommandLineOptions options)
|
||||
{
|
||||
command.Description = "Set a user secrets ID to enable secret storage";
|
||||
command.HelpOption();
|
||||
|
||||
command.OnExecute(() =>
|
||||
{
|
||||
options.Command = new InitCommandFactory(options);
|
||||
});
|
||||
}
|
||||
|
||||
public InitCommandFactory(CommandLineOptions options)
|
||||
{
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public void Execute(CommandContext context)
|
||||
{
|
||||
new InitCommand(Options.Id, Options.Project).Execute(context);
|
||||
}
|
||||
|
||||
public void Execute(CommandContext context, string workingDirectory)
|
||||
{
|
||||
new InitCommand(Options.Id, Options.Project).Execute(context, workingDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
public class InitCommand : ICommand
|
||||
{
|
||||
public string OverrideId { get; }
|
||||
public string ProjectPath { get; }
|
||||
public string WorkingDirectory { get; private set; } = Directory.GetCurrentDirectory();
|
||||
|
||||
public InitCommand(string id, string project)
|
||||
{
|
||||
OverrideId = id;
|
||||
ProjectPath = project;
|
||||
}
|
||||
|
||||
public void Execute(CommandContext context, string workingDirectory)
|
||||
{
|
||||
WorkingDirectory = workingDirectory;
|
||||
Execute(context);
|
||||
}
|
||||
|
||||
public void Execute(CommandContext context)
|
||||
{
|
||||
var projectPath = ResolveProjectPath(ProjectPath, WorkingDirectory);
|
||||
|
||||
// Load the project file as XML
|
||||
var projectDocument = XDocument.Load(projectPath);
|
||||
|
||||
// Accept the `--id` CLI option to the main app
|
||||
string newSecretsId = string.IsNullOrWhiteSpace(OverrideId)
|
||||
? Guid.NewGuid().ToString()
|
||||
: OverrideId;
|
||||
|
||||
// Confirm secret ID does not contain invalid characters
|
||||
if (Path.GetInvalidPathChars().Any(invalidChar => newSecretsId.Contains(invalidChar)))
|
||||
{
|
||||
throw new ArgumentException(Resources.FormatError_InvalidSecretsId(newSecretsId));
|
||||
}
|
||||
|
||||
var existingUserSecretsId = projectDocument.XPathSelectElements("//UserSecretsId").FirstOrDefault();
|
||||
|
||||
// Check if a UserSecretsId is already set
|
||||
if (existingUserSecretsId != default)
|
||||
{
|
||||
// Only set the UserSecretsId if the user specified an explicit value
|
||||
if (string.IsNullOrWhiteSpace(OverrideId))
|
||||
{
|
||||
context.Reporter.Output(Resources.FormatMessage_ProjectAlreadyInitialized(projectPath));
|
||||
return;
|
||||
}
|
||||
|
||||
existingUserSecretsId.SetValue(newSecretsId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find the first non-conditional PropertyGroup
|
||||
var propertyGroup = projectDocument.Root.DescendantNodes()
|
||||
.FirstOrDefault(node => node is XElement el
|
||||
&& el.Name == "PropertyGroup"
|
||||
&& el.Attributes().All(attr =>
|
||||
attr.Name != "Condition")) as XElement;
|
||||
|
||||
// No valid property group, create a new one
|
||||
if (propertyGroup == null)
|
||||
{
|
||||
propertyGroup = new XElement("PropertyGroup");
|
||||
projectDocument.Root.AddFirst(propertyGroup);
|
||||
}
|
||||
|
||||
// Add UserSecretsId element
|
||||
propertyGroup.Add(new XElement("UserSecretsId", newSecretsId));
|
||||
}
|
||||
|
||||
projectDocument.Save(projectPath);
|
||||
|
||||
context.Reporter.Output(Resources.FormatMessage_SetUserSecretsIdForProject(newSecretsId, projectPath));
|
||||
}
|
||||
|
||||
private static string ResolveProjectPath(string name, string path)
|
||||
{
|
||||
var finder = new MsBuildProjectFinder(path);
|
||||
return finder.FindMsBuildProject(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -71,6 +71,12 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
|||
|
||||
var reporter = CreateReporter(options.IsVerbose);
|
||||
|
||||
if (options.Command is InitCommandFactory initCmd)
|
||||
{
|
||||
initCmd.Execute(new CommandContext(null, reporter, _console), _workingDirectory);
|
||||
return 0;
|
||||
}
|
||||
|
||||
string userSecretsId;
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,16 +15,14 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
|||
/// </summary>
|
||||
internal static string Error_Command_Failed
|
||||
{
|
||||
get { return GetString("Error_Command_Failed"); }
|
||||
get => 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);
|
||||
}
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Error_Command_Failed", "message"), message);
|
||||
|
||||
/// <summary>
|
||||
/// Missing parameter value for '{name}'.
|
||||
|
|
@ -32,7 +30,7 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
|||
/// </summary>
|
||||
internal static string Error_MissingArgument
|
||||
{
|
||||
get { return GetString("Error_MissingArgument"); }
|
||||
get => GetString("Error_MissingArgument");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -40,202 +38,218 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
|||
/// Use the '--help' flag to see info.
|
||||
/// </summary>
|
||||
internal static string FormatError_MissingArgument(object name)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("Error_MissingArgument", "name"), name);
|
||||
}
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Error_MissingArgument", "name"), name);
|
||||
|
||||
/// <summary>
|
||||
/// Cannot find '{key}' in the secret store.
|
||||
/// </summary>
|
||||
internal static string Error_Missing_Secret
|
||||
{
|
||||
get { return GetString("Error_Missing_Secret"); }
|
||||
get => 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);
|
||||
}
|
||||
=> 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"); }
|
||||
get => 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);
|
||||
}
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Error_MultipleProjectsFound", "projectPath"), projectPath);
|
||||
|
||||
/// <summary>
|
||||
/// No secrets configured for this application.
|
||||
/// </summary>
|
||||
internal static string Error_No_Secrets_Found
|
||||
{
|
||||
get { return GetString("Error_No_Secrets_Found"); }
|
||||
get => 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");
|
||||
}
|
||||
=> 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"); }
|
||||
get => 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);
|
||||
}
|
||||
=> 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"); }
|
||||
get => 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);
|
||||
}
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectMissingId", "project"), project);
|
||||
|
||||
/// <summary>
|
||||
/// The project file '{path}' does not exist.
|
||||
/// </summary>
|
||||
internal static string Error_ProjectPath_NotFound
|
||||
{
|
||||
get { return GetString("Error_ProjectPath_NotFound"); }
|
||||
get => GetString("Error_ProjectPath_NotFound");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The project file '{path}' does not exist.
|
||||
/// </summary>
|
||||
internal static string FormatError_ProjectPath_NotFound(object path)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectPath_NotFound", "path"), path);
|
||||
}
|
||||
=> 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"); }
|
||||
get => 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);
|
||||
}
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectFailedToLoad", "project"), project);
|
||||
|
||||
/// <summary>
|
||||
/// Project file path {project}.
|
||||
/// </summary>
|
||||
internal static string Message_Project_File_Path
|
||||
{
|
||||
get { return GetString("Message_Project_File_Path"); }
|
||||
get => 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);
|
||||
}
|
||||
=> 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"); }
|
||||
get => 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);
|
||||
}
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Message_Saved_Secret", "key", "value"), key, value);
|
||||
|
||||
/// <summary>
|
||||
/// Successfully saved {number} secrets to the secret store.
|
||||
/// </summary>
|
||||
internal static string Message_Saved_Secrets
|
||||
{
|
||||
get { return GetString("Message_Saved_Secrets"); }
|
||||
get => GetString("Message_Saved_Secrets");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Successfully saved {number} secrets to the secret store.
|
||||
/// </summary>
|
||||
internal static string FormatMessage_Saved_Secrets(object number)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("Message_Saved_Secrets", "number"), number);
|
||||
}
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Message_Saved_Secrets", "number"), number);
|
||||
|
||||
/// <summary>
|
||||
/// Secrets file path {secretsFilePath}.
|
||||
/// </summary>
|
||||
internal static string Message_Secret_File_Path
|
||||
{
|
||||
get { return GetString("Message_Secret_File_Path"); }
|
||||
get => 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);
|
||||
}
|
||||
=> 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"); }
|
||||
get => GetString("Message_Secret_Value_Format");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// {key} = {value}
|
||||
/// </summary>
|
||||
internal static string FormatMessage_Secret_Value_Format(object key, object value)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Message_Secret_Value_Format", "key", "value"), key, value);
|
||||
|
||||
/// <summary>
|
||||
/// The UserSecretsId '{userSecretsId}' cannot contain any characters that cannot be used in a file path.
|
||||
/// </summary>
|
||||
internal static string Error_InvalidSecretsId
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("Message_Secret_Value_Format", "key", "value"), key, value);
|
||||
get => GetString("Error_InvalidSecretsId");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The UserSecretsId '{userSecretsId}' cannot contain any characters that cannot be used in a file path.
|
||||
/// </summary>
|
||||
internal static string FormatError_InvalidSecretsId(object userSecretsId)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Error_InvalidSecretsId", "userSecretsId"), userSecretsId);
|
||||
|
||||
/// <summary>
|
||||
/// The MSBuild project '{project}' has already been initialized with a UserSecretsId.
|
||||
/// </summary>
|
||||
internal static string Message_ProjectAlreadyInitialized
|
||||
{
|
||||
get => GetString("Message_ProjectAlreadyInitialized");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The MSBuild project '{project}' has already been initialized with a UserSecretsId.
|
||||
/// </summary>
|
||||
internal static string FormatMessage_ProjectAlreadyInitialized(object project)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Message_ProjectAlreadyInitialized", "project"), project);
|
||||
|
||||
/// <summary>
|
||||
/// Set UserSecretsId to '{userSecretsId}' for MSBuild project '{project}'.
|
||||
/// </summary>
|
||||
internal static string Message_SetUserSecretsIdForProject
|
||||
{
|
||||
get => GetString("Message_SetUserSecretsIdForProject");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set UserSecretsId to '{userSecretsId}' for MSBuild project '{project}'.
|
||||
/// </summary>
|
||||
internal static string FormatMessage_SetUserSecretsIdForProject(object userSecretsId, object project)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Message_SetUserSecretsIdForProject", "userSecretsId", "project"), userSecretsId, project);
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
-->
|
||||
|
|
@ -160,4 +160,13 @@ Use the '--help' flag to see info.</value>
|
|||
<data name="Message_Secret_Value_Format" xml:space="preserve">
|
||||
<value>{key} = {value}</value>
|
||||
</data>
|
||||
<data name="Error_InvalidSecretsId" xml:space="preserve">
|
||||
<value>The UserSecretsId '{userSecretsId}' cannot contain any characters that cannot be used in a file path.</value>
|
||||
</data>
|
||||
<data name="Message_ProjectAlreadyInitialized" xml:space="preserve">
|
||||
<value>The MSBuild project '{project}' has already been initialized with a UserSecretsId.</value>
|
||||
</data>
|
||||
<data name="Message_SetUserSecretsIdForProject" xml:space="preserve">
|
||||
<value>Set UserSecretsId to '{userSecretsId}' for MSBuild project '{project}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
// 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;
|
||||
using Microsoft.Extensions.Configuration.UserSecrets.Tests;
|
||||
using Microsoft.Extensions.SecretManager.Tools.Internal;
|
||||
using Microsoft.Extensions.Tools.Internal;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||
{
|
||||
public class InitCommandTests : IClassFixture<UserSecretsTestFixture>
|
||||
{
|
||||
private UserSecretsTestFixture _fixture;
|
||||
private ITestOutputHelper _output;
|
||||
private TestConsole _console;
|
||||
private StringBuilder _textOutput;
|
||||
|
||||
public InitCommandTests(UserSecretsTestFixture fixture, ITestOutputHelper output)
|
||||
{
|
||||
_fixture = fixture;
|
||||
_output = output;
|
||||
_textOutput = new StringBuilder();
|
||||
|
||||
_console = new TestConsole(output)
|
||||
{
|
||||
Error = new StringWriter(_textOutput),
|
||||
Out = new StringWriter(_textOutput),
|
||||
};
|
||||
}
|
||||
|
||||
private CommandContext MakeCommandContext() => new CommandContext(null, new TestReporter(_output), _console);
|
||||
|
||||
[Fact]
|
||||
public void AddsSecretIdToProject()
|
||||
{
|
||||
var projectDir = _fixture.CreateProject(null);
|
||||
|
||||
new InitCommand(null, null).Execute(MakeCommandContext(), projectDir);
|
||||
|
||||
var idResolver = new ProjectIdResolver(MakeCommandContext().Reporter, projectDir);
|
||||
|
||||
Assert.False(string.IsNullOrWhiteSpace(idResolver.Resolve(null, null)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddsSpecificSecretIdToProject()
|
||||
{
|
||||
const string SecretId = "TestSecretId";
|
||||
|
||||
var projectDir = _fixture.CreateProject(null);
|
||||
|
||||
new InitCommand(SecretId, null).Execute(MakeCommandContext(), projectDir);
|
||||
|
||||
var idResolver = new ProjectIdResolver(MakeCommandContext().Reporter, projectDir);
|
||||
|
||||
Assert.Equal(SecretId, idResolver.Resolve(null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddsEscapedSpecificSecretIdToProject()
|
||||
{
|
||||
const string SecretId = @"<lots of XML invalid values>&";
|
||||
|
||||
var projectDir = _fixture.CreateProject(null);
|
||||
|
||||
new InitCommand(SecretId, null).Execute(MakeCommandContext(), projectDir);
|
||||
|
||||
var idResolver = new ProjectIdResolver(MakeCommandContext().Reporter, projectDir);
|
||||
|
||||
Assert.Equal(SecretId, idResolver.Resolve(null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoesNotGenerateIdForProjectWithSecretId()
|
||||
{
|
||||
const string SecretId = "AlreadyExists";
|
||||
|
||||
var projectDir = _fixture.CreateProject(SecretId);
|
||||
|
||||
new InitCommand(null, null).Execute(MakeCommandContext(), projectDir);
|
||||
|
||||
var idResolver = new ProjectIdResolver(MakeCommandContext().Reporter, projectDir);
|
||||
|
||||
Assert.Equal(SecretId, idResolver.Resolve(null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OverridesIdForProjectWithSecretId()
|
||||
{
|
||||
const string SecretId = "AlreadyExists";
|
||||
const string NewId = "TestValue";
|
||||
|
||||
var projectDir = _fixture.CreateProject(SecretId);
|
||||
|
||||
new InitCommand(NewId, null).Execute(MakeCommandContext(), projectDir);
|
||||
|
||||
var idResolver = new ProjectIdResolver(MakeCommandContext().Reporter, projectDir);
|
||||
|
||||
Assert.Equal(NewId, idResolver.Resolve(null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FailsForInvalidId()
|
||||
{
|
||||
string secretId = $"invalid{Path.GetInvalidPathChars()[0]}secret-id";
|
||||
|
||||
var projectDir = _fixture.CreateProject(null);
|
||||
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
new InitCommand(secretId, null).Execute(MakeCommandContext(), projectDir);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -323,5 +323,18 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
|||
secretManager.RunInternal(args);
|
||||
Assert.Contains(Resources.Error_No_Secrets_Found, _output.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Init_When_Project_Has_No_Secrets_Id()
|
||||
{
|
||||
var projectPath = _fixture.CreateProject(null);
|
||||
var project = Path.Combine(projectPath, "TestProject.csproj");
|
||||
var secretManager = new Program(_console, projectPath);
|
||||
|
||||
secretManager.RunInternal("init", "-p", project);
|
||||
|
||||
Assert.DoesNotContain(Resources.FormatError_ProjectMissingId(project), _output.ToString());
|
||||
Assert.DoesNotContain("--help", _output.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue