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("remove", c => RemoveCommand.Configure(c, options));
|
||||||
app.Command("list", c => ListCommand.Configure(c, options));
|
app.Command("list", c => ListCommand.Configure(c, options));
|
||||||
app.Command("clear", c => ClearCommand.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.
|
// Show help information if no subcommand/option was specified.
|
||||||
app.OnExecute(() => app.ShowHelp());
|
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);
|
var reporter = CreateReporter(options.IsVerbose);
|
||||||
|
|
||||||
|
if (options.Command is InitCommandFactory initCmd)
|
||||||
|
{
|
||||||
|
initCmd.Execute(new CommandContext(null, reporter, _console), _workingDirectory);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
string userSecretsId;
|
string userSecretsId;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,14 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Error_Command_Failed
|
internal static string Error_Command_Failed
|
||||||
{
|
{
|
||||||
get { return GetString("Error_Command_Failed"); }
|
get => GetString("Error_Command_Failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command failed : {message}
|
/// Command failed : {message}
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatError_Command_Failed(object message)
|
internal static string FormatError_Command_Failed(object message)
|
||||||
{
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Error_Command_Failed", "message"), message);
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Error_Command_Failed", "message"), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Missing parameter value for '{name}'.
|
/// Missing parameter value for '{name}'.
|
||||||
|
|
@ -32,7 +30,7 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Error_MissingArgument
|
internal static string Error_MissingArgument
|
||||||
{
|
{
|
||||||
get { return GetString("Error_MissingArgument"); }
|
get => GetString("Error_MissingArgument");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -40,202 +38,218 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
||||||
/// Use the '--help' flag to see info.
|
/// Use the '--help' flag to see info.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatError_MissingArgument(object name)
|
internal static string FormatError_MissingArgument(object name)
|
||||||
{
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Error_MissingArgument", "name"), name);
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Error_MissingArgument", "name"), name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cannot find '{key}' in the secret store.
|
/// Cannot find '{key}' in the secret store.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Error_Missing_Secret
|
internal static string Error_Missing_Secret
|
||||||
{
|
{
|
||||||
get { return GetString("Error_Missing_Secret"); }
|
get => GetString("Error_Missing_Secret");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cannot find '{key}' in the secret store.
|
/// Cannot find '{key}' in the secret store.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatError_Missing_Secret(object key)
|
internal static string FormatError_Missing_Secret(object key)
|
||||||
{
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Error_Missing_Secret", "key"), key);
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Error_Missing_Secret", "key"), key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option.
|
/// Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Error_MultipleProjectsFound
|
internal static string Error_MultipleProjectsFound
|
||||||
{
|
{
|
||||||
get { return GetString("Error_MultipleProjectsFound"); }
|
get => GetString("Error_MultipleProjectsFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option.
|
/// Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatError_MultipleProjectsFound(object projectPath)
|
internal static string FormatError_MultipleProjectsFound(object projectPath)
|
||||||
{
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Error_MultipleProjectsFound", "projectPath"), projectPath);
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Error_MultipleProjectsFound", "projectPath"), projectPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// No secrets configured for this application.
|
/// No secrets configured for this application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Error_No_Secrets_Found
|
internal static string Error_No_Secrets_Found
|
||||||
{
|
{
|
||||||
get { return GetString("Error_No_Secrets_Found"); }
|
get => GetString("Error_No_Secrets_Found");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// No secrets configured for this application.
|
/// No secrets configured for this application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatError_No_Secrets_Found()
|
internal static string FormatError_No_Secrets_Found()
|
||||||
{
|
=> GetString("Error_No_Secrets_Found");
|
||||||
return GetString("Error_No_Secrets_Found");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option.
|
/// Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Error_NoProjectsFound
|
internal static string Error_NoProjectsFound
|
||||||
{
|
{
|
||||||
get { return GetString("Error_NoProjectsFound"); }
|
get => GetString("Error_NoProjectsFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option.
|
/// Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatError_NoProjectsFound(object projectPath)
|
internal static string FormatError_NoProjectsFound(object projectPath)
|
||||||
{
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Error_NoProjectsFound", "projectPath"), projectPath);
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Error_NoProjectsFound", "projectPath"), projectPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// 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>
|
/// </summary>
|
||||||
internal static string Error_ProjectMissingId
|
internal static string Error_ProjectMissingId
|
||||||
{
|
{
|
||||||
get { return GetString("Error_ProjectMissingId"); }
|
get => GetString("Error_ProjectMissingId");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// 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>
|
/// </summary>
|
||||||
internal static string FormatError_ProjectMissingId(object project)
|
internal static string FormatError_ProjectMissingId(object project)
|
||||||
{
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectMissingId", "project"), project);
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectMissingId", "project"), project);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The project file '{path}' does not exist.
|
/// The project file '{path}' does not exist.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Error_ProjectPath_NotFound
|
internal static string Error_ProjectPath_NotFound
|
||||||
{
|
{
|
||||||
get { return GetString("Error_ProjectPath_NotFound"); }
|
get => GetString("Error_ProjectPath_NotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The project file '{path}' does not exist.
|
/// The project file '{path}' does not exist.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatError_ProjectPath_NotFound(object path)
|
internal static string FormatError_ProjectPath_NotFound(object path)
|
||||||
{
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectPath_NotFound", "path"), path);
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectPath_NotFound", "path"), path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Could not load the MSBuild project '{project}'.
|
/// Could not load the MSBuild project '{project}'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Error_ProjectFailedToLoad
|
internal static string Error_ProjectFailedToLoad
|
||||||
{
|
{
|
||||||
get { return GetString("Error_ProjectFailedToLoad"); }
|
get => GetString("Error_ProjectFailedToLoad");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Could not load the MSBuild project '{project}'.
|
/// Could not load the MSBuild project '{project}'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatError_ProjectFailedToLoad(object project)
|
internal static string FormatError_ProjectFailedToLoad(object project)
|
||||||
{
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectFailedToLoad", "project"), project);
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectFailedToLoad", "project"), project);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Project file path {project}.
|
/// Project file path {project}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Message_Project_File_Path
|
internal static string Message_Project_File_Path
|
||||||
{
|
{
|
||||||
get { return GetString("Message_Project_File_Path"); }
|
get => GetString("Message_Project_File_Path");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Project file path {project}.
|
/// Project file path {project}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatMessage_Project_File_Path(object project)
|
internal static string FormatMessage_Project_File_Path(object project)
|
||||||
{
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Message_Project_File_Path", "project"), project);
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Message_Project_File_Path", "project"), project);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Successfully saved {key} = {value} to the secret store.
|
/// Successfully saved {key} = {value} to the secret store.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Message_Saved_Secret
|
internal static string Message_Saved_Secret
|
||||||
{
|
{
|
||||||
get { return GetString("Message_Saved_Secret"); }
|
get => GetString("Message_Saved_Secret");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Successfully saved {key} = {value} to the secret store.
|
/// Successfully saved {key} = {value} to the secret store.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatMessage_Saved_Secret(object key, object value)
|
internal static string FormatMessage_Saved_Secret(object key, object value)
|
||||||
{
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Message_Saved_Secret", "key", "value"), key, value);
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Message_Saved_Secret", "key", "value"), key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Successfully saved {number} secrets to the secret store.
|
/// Successfully saved {number} secrets to the secret store.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Message_Saved_Secrets
|
internal static string Message_Saved_Secrets
|
||||||
{
|
{
|
||||||
get { return GetString("Message_Saved_Secrets"); }
|
get => GetString("Message_Saved_Secrets");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Successfully saved {number} secrets to the secret store.
|
/// Successfully saved {number} secrets to the secret store.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatMessage_Saved_Secrets(object number)
|
internal static string FormatMessage_Saved_Secrets(object number)
|
||||||
{
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Message_Saved_Secrets", "number"), number);
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Message_Saved_Secrets", "number"), number);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Secrets file path {secretsFilePath}.
|
/// Secrets file path {secretsFilePath}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Message_Secret_File_Path
|
internal static string Message_Secret_File_Path
|
||||||
{
|
{
|
||||||
get { return GetString("Message_Secret_File_Path"); }
|
get => GetString("Message_Secret_File_Path");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Secrets file path {secretsFilePath}.
|
/// Secrets file path {secretsFilePath}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatMessage_Secret_File_Path(object secretsFilePath)
|
internal static string FormatMessage_Secret_File_Path(object secretsFilePath)
|
||||||
{
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Message_Secret_File_Path", "secretsFilePath"), secretsFilePath);
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Message_Secret_File_Path", "secretsFilePath"), secretsFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// {key} = {value}
|
/// {key} = {value}
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Message_Secret_Value_Format
|
internal static string Message_Secret_Value_Format
|
||||||
{
|
{
|
||||||
get { return GetString("Message_Secret_Value_Format"); }
|
get => GetString("Message_Secret_Value_Format");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// {key} = {value}
|
/// {key} = {value}
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatMessage_Secret_Value_Format(object key, object value)
|
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)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</resheader>
|
<resheader name="version">2.0</resheader>
|
||||||
|
|
@ -26,36 +26,36 @@
|
||||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
value : The object must be serialized into a byte array
|
value : The object must be serialized into a byte array
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -160,4 +160,13 @@ Use the '--help' flag to see info.</value>
|
||||||
<data name="Message_Secret_Value_Format" xml:space="preserve">
|
<data name="Message_Secret_Value_Format" xml:space="preserve">
|
||||||
<value>{key} = {value}</value>
|
<value>{key} = {value}</value>
|
||||||
</data>
|
</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>
|
</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);
|
secretManager.RunInternal(args);
|
||||||
Assert.Contains(Resources.Error_No_Secrets_Found, _output.ToString());
|
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