-
Notifications
You must be signed in to change notification settings - Fork 74
Create initial prototype of dotnet-user-secrets with MSBuild support #178
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// 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.DotNet.Cli.Utils; | ||
using Microsoft.Extensions.ProjectModel; | ||
|
||
namespace Microsoft.Extensions.SecretManager.Tools | ||
{ | ||
internal class GracefulProjectFinder : MsBuildProjectFinder | ||
{ | ||
public GracefulProjectFinder(string directory) | ||
: base(directory) | ||
{ | ||
} | ||
|
||
protected override Exception FileDoesNotExist(string filePath) | ||
=> new GracefulException(Resources.FormatError_ProjectPath_NotFound(filePath)); | ||
|
||
protected override Exception MultipleProjectsFound(string directory) | ||
=> new GracefulException($"Multiple MSBuild project files found in '{directory}'. Specify which to use with the --project option."); | ||
|
||
protected override Exception NoProjectsFound(string directory) | ||
=> new GracefulException($"Could not find a MSBuild project file in '{directory}'. Specify which project to use with the --project option."); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 Microsoft.DotNet.Cli.Utils; | ||
using Microsoft.Extensions.ProjectModel; | ||
|
||
namespace Microsoft.Extensions.SecretManager.Tools.Internal | ||
{ | ||
public static class ProjectContextExtensions | ||
{ | ||
public static string GetUserSecretsId(this IProjectContext context) | ||
{ | ||
var userSecretsId = context.FindProperty("UserSecretsId"); | ||
|
||
if (string.IsNullOrEmpty(userSecretsId)) | ||
{ | ||
throw new GracefulException(Resources.FormatError_ProjectMissingId(context.ProjectFullPath)); | ||
} | ||
|
||
return userSecretsId; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,12 +5,11 @@ | |
using System.Diagnostics; | ||
using System.IO; | ||
using System.Linq; | ||
using Microsoft.DotNet.Cli.Utils; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.FileProviders; | ||
using Microsoft.Extensions.FileProviders.Physical; | ||
using Microsoft.Extensions.SecretManager.Tools.Internal; | ||
using Newtonsoft.Json; | ||
using Newtonsoft.Json.Linq; | ||
using Microsoft.Extensions.ProjectModel; | ||
using Microsoft.Build.Exceptions; | ||
|
||
namespace Microsoft.Extensions.SecretManager.Tools | ||
{ | ||
|
@@ -20,16 +19,23 @@ public class Program | |
private CommandOutputProvider _loggerProvider; | ||
private readonly TextWriter _consoleOutput; | ||
private readonly string _workingDirectory; | ||
// TODO this is only for testing. Can remove this when this project builds with CLI preview3 | ||
private readonly MsBuildContext _msBuildContext; | ||
|
||
public Program() | ||
: this(Console.Out, Directory.GetCurrentDirectory()) | ||
public static int Main(string[] args) | ||
{ | ||
HandleDebugFlag(ref args); | ||
|
||
int rc; | ||
new Program(Console.Out, Directory.GetCurrentDirectory(), MsBuildContext.FromCurrentDotNetSdk()).TryRun(args, out rc); | ||
return rc; | ||
} | ||
|
||
internal Program(TextWriter consoleOutput, string workingDirectory) | ||
internal Program(TextWriter consoleOutput, string workingDirectory, MsBuildContext msbuildContext) | ||
{ | ||
_consoleOutput = consoleOutput; | ||
_workingDirectory = workingDirectory; | ||
_msBuildContext = msbuildContext; | ||
|
||
var loggerFactory = new LoggerFactory(); | ||
CommandOutputProvider = new CommandOutputProvider(); | ||
|
@@ -65,15 +71,6 @@ public CommandOutputProvider CommandOutputProvider | |
} | ||
} | ||
|
||
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) | ||
{ | ||
|
@@ -103,6 +100,11 @@ public bool TryRun(string[] args, out int returnCode) | |
{ | ||
if (exception is GracefulException) | ||
{ | ||
if (exception.InnerException != null) | ||
{ | ||
Logger.LogInformation(exception.InnerException.Message); | ||
} | ||
|
||
Logger.LogError(exception.Message); | ||
} | ||
else | ||
|
@@ -134,59 +136,37 @@ internal int RunInternal(params string[] args) | |
CommandOutputProvider.LogLevel = LogLevel.Debug; | ||
} | ||
|
||
var userSecretsId = ResolveUserSecretsId(options); | ||
var userSecretsId = !string.IsNullOrEmpty(options.Id) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: change order for clarity: var userSecretsId = string.IsNullOrEmpty(options.Id) ? ResolveIdFromProject(options.Project) : options.Id; |
||
? options.Id | ||
: ResolveIdFromProject(options.Project); | ||
|
||
var store = new SecretsStore(userSecretsId, Logger); | ||
options.Command.Execute(store, Logger); | ||
return 0; | ||
} | ||
|
||
private string ResolveUserSecretsId(CommandLineOptions options) | ||
internal string ResolveIdFromProject(string projectPath) | ||
{ | ||
var projectPath = options.Project ?? _workingDirectory; | ||
|
||
if (!Path.IsPathRooted(projectPath)) | ||
{ | ||
projectPath = Path.Combine(_workingDirectory, projectPath); | ||
} | ||
|
||
if (!projectPath.EndsWith("project.json", StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
projectPath = Path.Combine(projectPath, "project.json"); | ||
} | ||
|
||
var fileInfo = new PhysicalFileInfo(new FileInfo(projectPath)); | ||
var finder = new GracefulProjectFinder(_workingDirectory); | ||
var projectFile = finder.FindMsBuildProject(projectPath); | ||
|
||
if (!fileInfo.Exists) | ||
{ | ||
throw new GracefulException(Resources.FormatError_ProjectPath_NotFound(projectPath)); | ||
} | ||
|
||
Logger.LogDebug(Resources.Message_Project_File_Path, fileInfo.PhysicalPath); | ||
return ReadUserSecretsId(fileInfo); | ||
} | ||
Logger.LogDebug(Resources.Message_Project_File_Path, projectFile); | ||
|
||
// TODO can use runtime API when upgrading to 1.1 | ||
private string ReadUserSecretsId(IFileInfo fileInfo) | ||
{ | ||
if (fileInfo == null || !fileInfo.Exists) | ||
try | ||
{ | ||
throw new GracefulException($"Could not find file '{fileInfo.PhysicalPath}'"); | ||
var project = new MsBuildProjectContextBuilder() | ||
.UseMsBuild(_msBuildContext) | ||
.AsDesignTimeBuild() | ||
.WithBuildTargets(Array.Empty<string>()) | ||
.WithProjectFile(projectFile) | ||
.WithTargetFramework("") // TFM doesn't matter | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to set it then? Seems like this should be a default There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This tool doesn't need to worry about TFMs. We just want to read a global property. The alternative would be |
||
.Build(); | ||
|
||
return project.GetUserSecretsId(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make debugging easier var id = project.GetUserSecretsId();
return id; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how does this make debugging easier? |
||
} | ||
|
||
using (var stream = fileInfo.CreateReadStream()) | ||
using (var streamReader = new StreamReader(stream)) | ||
using (var jsonReader = new JsonTextReader(streamReader)) | ||
catch (InvalidProjectFileException ex) | ||
{ | ||
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; | ||
throw new GracefulException(Resources.FormatError_ProjectFailedToLoad(projectFile), ex); | ||
} | ||
} | ||
} | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When would project evaluation fail? Trying to determine if we really need this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment, the question is "When does project evaluation not fail?"
Bad XML, missing imports, invalid syntax, etc... there are lots of reasons why it's good to have a an escape hatch. Plus, some may prefer to just us
--id
and avoid the MSBuild complexity.