From 786a50a3cbdb6af8285b34c87bfbb34706bca6c5 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Mon, 5 Feb 2024 12:54:36 -0800 Subject: [PATCH 01/69] initial commit --- .../support-credentialproviders-dotnet-tools | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 accepted/2024/support-credentialproviders-dotnet-tools diff --git a/accepted/2024/support-credentialproviders-dotnet-tools b/accepted/2024/support-credentialproviders-dotnet-tools new file mode 100644 index 000000000..bc41871eb --- /dev/null +++ b/accepted/2024/support-credentialproviders-dotnet-tools @@ -0,0 +1,51 @@ +# ***Support for credential providers deployed via .NET tools*** + + +- Author Name: https://github.com/kartheekp-ms +- GitHub Issue: https://github.com/NuGet/Home/issues/12567 + +## Summary + + + +## Motivation + + + +## Explanation + +### Functional explanation + + + + +### Technical explanation + + + +## Drawbacks + + + +## Rationale and alternatives + + + + + +## Prior Art + + + + + + +## Unresolved Questions + + + + + +## Future Possibilities + + \ No newline at end of file From 7a7d7cbe4d8afda8310c4d9c72b6462baa7656bd Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Mon, 5 Feb 2024 19:25:05 -0800 Subject: [PATCH 02/69] intial draft --- .../2024/support-credentialproviders-dotnet-tools | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/accepted/2024/support-credentialproviders-dotnet-tools b/accepted/2024/support-credentialproviders-dotnet-tools index bc41871eb..4eeea8a63 100644 --- a/accepted/2024/support-credentialproviders-dotnet-tools +++ b/accepted/2024/support-credentialproviders-dotnet-tools @@ -6,11 +6,16 @@ ## Summary - - ## Motivation - +Currently, credential providers support `*.exe` for .NET Framework and `*.dll` for .NET Core. They are typically divided into two folders under the NuGet credential provider base path. For .NET Framework, the providers are discovered by searching `credentialprovider*.exe` and executing that program. For .NET Core, they are discovered by searching `credentialprovider*.dll` and executing `dotnet .dll`. Before .NET Core 2.0, this division was necessary because .NET Core only supported platform-agnostic DLLs. However, with the latest versions of .NET, this division is no longer necessary, but it does require supporting and deploying multiple versions. This is further reinforced by the two plugin path environment variables `NUGET_NETFX_PLUGIN_PATHS` and `NUGET_NETCORE_PLUGIN_PATHS`, which need to be set for each framework version. + +A deployment solution for dotnet is [.NET tools](https://learn.microsoft.com/dotnet/core/tools), which provide a seamless .NET installation and management experience for NuGet packages. Using .NET tools as a deployment mechanism has been a recurring request from users of the .NET ecosystem who need to authenticate with private repositories. The ideal workflow for repositoroes that access private NuGet feeds like Azure DevOps would be: +1. Customers have the dotnet CLI tools installed. +2. They run `dotnet tool install -g Microsoft.CredentialProviders`. +3. They run `dotnet restore` with a private endpoint, and it 'just works' (i.e., the credential providers from step 2 are used during credential acquisition and are used to authenticate against private endpoints). + +This almost works today, but only for the Windows .NET Framework. The goal is to support this cross-platform for all supported .NET runtimes. ## Explanation From ee3b3a39692592bb142e8306d96b89b489d527e0 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Mon, 5 Feb 2024 19:43:09 -0800 Subject: [PATCH 03/69] change file extension --- ...s-dotnet-tools => support-credentialproviders-dotnet-tools.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename accepted/2024/{support-credentialproviders-dotnet-tools => support-credentialproviders-dotnet-tools.md} (100%) diff --git a/accepted/2024/support-credentialproviders-dotnet-tools b/accepted/2024/support-credentialproviders-dotnet-tools.md similarity index 100% rename from accepted/2024/support-credentialproviders-dotnet-tools rename to accepted/2024/support-credentialproviders-dotnet-tools.md From 47e844a79558e412d2d7114ae0b7e13ef5d30093 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Mon, 5 Feb 2024 12:54:36 -0800 Subject: [PATCH 04/69] support for credential providers to use NuGet plugins deployed via .NET tools --- ...get-authentication-plugins-dotnet-tools.md | 210 ++++++++++++++++++ .../plugin-tools-folder-windows.png | Bin 0 -> 29350 bytes .../plugin-tools-store-windows.png | Bin 0 -> 29386 bytes 3 files changed, 210 insertions(+) create mode 100644 accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md create mode 100644 meta/resources/PluginsAsDotNetTools/plugin-tools-folder-windows.png create mode 100644 meta/resources/PluginsAsDotNetTools/plugin-tools-store-windows.png diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md new file mode 100644 index 000000000..1326061d2 --- /dev/null +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -0,0 +1,210 @@ +# ***Support for NuGet authentication plugins deployed via .NET tools*** + +- Author Name: https://github.com/kartheekp-ms +- GitHub Issue: https://github.com/NuGet/Home/issues/12567 + +## Summary + +Currently, NuGet utilizes a [cross-platform plugin model](https://learn.microsoft.com/nuget/reference/extensibility/nuget-cross-platform-plugins#supported-operations) which is primarily used for [authentication against private feeds](https://learn.microsoft.com/en-us/nuget/reference/extensibility/nuget-cross-platform-authentication-plugin). It also supports the [package download](https://github.com/NuGet/Home/wiki/NuGet-Package-Download-Plugin) operation. + +To accommodate all scenarios involving NuGet client tools, plugin authors will need to create plugins for both `.NET Framework` and `.NET Core`. The following details the combinations of client and framework for these plugins. + +| Client tool | Framework | +|-------------|-----------| +| Visual Studio | .NET Framework | +| dotnet.exe | .NET Core | +| NuGet.exe | .NET Framework | +| MSBuild.exe | .NET Framework | +| NuGet.exe on Mono | .NET Framework | + +Currently, NuGet maintains `netfx` folder for plugins that will be invoked in `.NET Framework` code paths, `netcore` folder for plugins that will be invoked in `.NET Core` code paths. The proposal is to add a new `tools` folder to store NuGet plugins that are deployed as [tool Path](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location) .NET tools. + +| Framework | Root discovery location | +|-----------|------------------------| +| .NET Core | %UserProfile%/.nuget/plugins/netcore | +| .NET Framework | %UserProfile%/.nuget/plugins/netfx | +| .NET Framework & .NET Core [current proposal] | %UserProfile%/.nuget/plugins/tools | + +## Motivation + +At present, NuGet uses different methods to execute plugins for `.NET Framework` and `.NET Core`. For the `.NET Framework`, it looks for files ending in `*.exe`, while for `.NET Core`, it looks for files ending in `*.dll`. These files are usually stored in two separate folders under the base path for NuGet plugins. + +Before `.NET Core 2.0`, this division was necessary because `.NET Core` only supported platform-agnostic DLLs. However, with the latest versions of .NET, this division is no longer necessary, but the NuGet plugin architecture still requires supporting and deploying multiple versions. This behavior is further reinforced by the two plugin path environment variables `NUGET_NETFX_PLUGIN_PATHS` and `NUGET_NETCORE_PLUGIN_PATHS`, which need to be set for each framework version. + +Another motivating factor for this work is the current story for installing these credential providers, which is not ideal. For instance, let's consider the two cross-platform authentication plugins that I am aware of while writing this specification: + +1. **Azure Artifacts Credential Provider** - The [setup](https://github.com/microsoft/artifacts-credprovider/tree/master?tab=readme-ov-file#setup) instructions vary based on the platform. +2. **AWS CodeArtifact Credential Provider** - The AWS team has developed a [.NET tool](https://docs.aws.amazon.com/codeartifact/latest/ug/nuget-cli.html#nuget-configure-cli) to facilitate authentication with their private NuGet feeds. In their current implementation, they've added a subcommand, `codeartifact-creds install`, which copies the credential provider to the NuGet plugins folder. The log below shows all possible subcommands. However, plugin authors could leverage the .NET SDK to allow their customers to install, uninstall, or update the NuGet plugins more efficiently. + +```log +~\.nuget\plugins\tools +❯ .\dotnet-codeartifact-creds.exe +Required command was not provided. + +Usage: + dotnet-codeartifact-creds [options] [command] + +Options: + --version Show version information + -?, -h, --help Show help and usage information + +Commands: + install Installs the AWS CodeArtifact NuGet credential provider into the NuGet plugins folder. + configure Sets or Unsets a configuration + uninstall Uninstalls the AWS CodeArtifact NuGet credential provider from the NuGet plugins folder. +``` + +## Explanation + +### Functional explanation + +A deployment solution for .NET is the [.NET tools](https://learn.microsoft.com/dotnet/core/tools). These tools provide a seamless installation and management experience for NuGet packages in the .NET ecosystem. The use of .NET tools as a deployment mechanism has been a recurring request from users and internal partners who need to authenticate with private repositories. Currently, this solution works for the Windows .NET Framework. However, the goal is to extend this support cross-platform for all supported .NET runtimes. + +By implementing this specification, we offer plugin authors the option to use .NET tools for plugin deployment. They can install these as a `tool path` global tool. This eliminates the need to maintain separate versions for `.NET Framework` and `.NET Core`. It also simplifies the installation process by removing the necessity for plugin authors to create subcommands like `codeartifact-creds install/uninstall`. + +The ideal workflow for repositories that access private NuGet feeds, such as Azure DevOps, would be: + +1. Ensure that the dotnet CLI tools are installed. +2. Execute the command `dotnet tool install Microsoft.CredentialProviders --tool-path "%UserProfile%/.nuget/plugins/tools"` on the Windows platform. For Linux and Mac, run `dotnet tool install Microsoft.CredentialProviders --tool-path $HOME/.nuget/plugins`. +3. Run `dotnet restore --interactive` with a private endpoint. It should 'just work', meaning the credential providers installed in step 2 are used during credential acquisition and are used to authenticate against private endpoints. + +The setup instructions mentioned in step 2 are platform-specific, but they are simpler compared to the current instructions for installing credential providers. + +I proposed using a `tool path` .NET tool because, by default, .NET tools are considered [global](https://learn.microsoft.com/dotnet/core/tools/global-tools). This means they are installed in a default directory, such as `%UserProfile%/.dotnet/tools` on Windows, which is then added to the PATH environment variable. +- Global tools can be invoked from any directory on the machine without specifying their location. +- One version of a tool is used for all directories on the machine. +However, NuGet cannot easily determine which tool is a cross-platform authentication or a package download plugin without invoking every single global tool installed on the machine. This could potentially lead to a performance hit for NuGet operations. +On the other hand, the `tool path` option allows us to install .NET tools as global tools, but with the binaries located in a specified location. This makes it easier to identify and invoke the appropriate tool for NuGet operations. +- The binaries are installed in a location that we specify while installing the tool. +- We can invoke the tool from the installation directory by providing the directory with the command name or by adding the directory to the PATH environment variable. +- One version of a tool is used for all directories on the machine. +The `tool path` option aligns well with the design of NuGet plugins architecture, making it the recommended approach for installing and executing NuGet plugins. + +### Security considerations + +The [.NET SDK docs](https://learn.microsoft.com/dotnet/core/tools/global-tools#check-the-author-and-statistics) clearly state, `.NET tools run in full trust. Don't install a .NET tool unless you trust the author`. This is an important consideration for plugin customers when installing NuGet plugins via .NET Tools in the future. + +### Technical explanation + +Plugins are discovered through a convention-based directory structure, such as the `%userprofile%/.nuget/plugins` folder on Windows. CI/CD scenarios and power users can use environment variables to override this behavior. Note that only absolute paths are allowed when using these environment variables. + +- `NUGET_NETFX_PLUGIN_PATHS`: Defines the plugins used by the .NET Framework-based tooling (NuGet.exe/MSBuild.exe/Visual Studio). This takes precedence over `NUGET_PLUGIN_PATHS`. +- `NUGET_NETCORE_PLUGIN_PATHS`: Defines the plugins used by the .NET Core-based tooling (dotnet.exe). This takes precedence over `NUGET_PLUGIN_PATHS`. +- `NUGET_PLUGIN_PATHS`: Defines the plugins used for the NuGet process, with priority preserved. If this environment variable is set, it overrides the convention-based discovery. It is ignored if either of the framework-specific variables is specified. + +I propose the addition of a `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable will define the plugins, installed as .NET tools, to be used by both .NET Framework and .NET Core tooling. It will take precedence over `NUGET_PLUGIN_PATHS`. + +The plugins specified in the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable will be used regardless of whether the `NUGET_NETFX_PLUGIN_PATHS` or `NUGET_NETCORE_PLUGIN_PATHS` environment variables are set. The primary reason for this is that plugins installed as .NET tools can be executed in both .NET Framework and .NET Core tooling. + +If customers prefer to install NuGet plugins as a global tool instead of a tool-path tool, they can set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable should point to the location of the .NET Tool executable that the NuGet Client tooling can invoke when needed. + +If none of the above environment variables are set, NuGet will default to the conventional method of discovering plugins from predefined directories. In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/8b658e2eee6391936887b9fd1b39f7918d16a9cb/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L65-L77), the NuGet code looks for plugin files in the `netfx` directory when running on .NET Framework, and in the `netcore` directory when running on .NET Core. This implementation should be updated to include the new `tools` directory. This directory should be added alongside `netcore` in the .NET code paths and `netfx` in the .NET Framework code paths, to ensure backward compatibility. + +In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L79-L101), the NuGet code searches for plugin files in a plugin-specific subdirectory. For example, in the case of the Azure Artifacts Credential Provider, the code looks for `*.exe` or `*.dll` files under the "CredentialProvider.Microsoft" directory. However, when .NET tools are installed in the `tools` folder, executable files (like `.exe` files on Windows or files with the executable bit set on Linux & Mac) are placed directly in the `tools` directory. The remaining files are stored in a `.store` folder. This arrangement eliminates the need to search in subdirectories. + +The new folder structure for NuGet plugins on the Windows platform is as follows: The `tools` folder contains .NET tool plugins, while the `.store` folder houses other necessary files. + +![plugin-tools-folder-windows](./../../meta/resources/PluginsAsDotNetTools/plugin-tools-folder-windows.png) + +![plugin-tools-store-windows](./../../meta/resources/PluginsAsDotNetTools/plugin-tools-store-windows.png) + +The new folder structure for NuGet plugins on the Linux platforms is as follows: + +```log +{user}:~/.nuget/plugins$ dir +netcore tools + +{user}:~/.nuget/plugins$ cd tools/ +{user}:~/.nuget/plugins/tools$ dir +botsay dotnetsay + +{user}:~/.nuget/plugins/tools$ ls -la +total 164 +drwxr-xr-x 3 {user} 4096 Feb 10 08:21 . +drwxr-xr-x 4 {user} 4096 Feb 10 08:20 .. +drwxr-xr-x 5 {user} 4096 Feb 10 08:21 .store +-rwxr-xr-x 1 {user} 75632 Feb 10 08:21 botsay +-rwxr-xr-x 1 {user} 75632 Feb 10 08:20 dotnetsay +``` + +## Drawbacks + +The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, involves installing the NuGet plugin as a .NET global tool. However, the current proposal suggests installing the plugin as a tool-path .NET tool. As mentioned in the technical explanation section, customers can opt to install NuGet plugins as a global tool instead of a tool-path tool. To do this, they need to set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable should point to the location of the .NET Tool executable, which the NuGet Client tooling can invoke when needed. + +## Rationale and alternatives + +### NuGet commands to install credential providers + +[Andy Zivkovic](https://github.com/zivkan) kindly proposed an alternative design in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. Here are the advantages and disadvantages of this approach: + +**Advantages:** +- As Andy suggested in the linked issue, we can utilize nuspec package types to enhance the discovery of credential providers. By introducing a new package type called `CredentialProvider`, the `dotnet nuget credential-provider list` command can filter by `packageType:CredentialProvider` on nuget.org. This will display a list of credential providers available on the feed, similar to the `dotnet list package` command. Although there are currently limited options for credential providers, customers would benefit from the ability to search by `packageType:CredentialProvider` on nuget.org. + +**Disadvantages:** +- The NuGet Client team would be required to maintain all the .NET Commands for installing, updating, and uninstalling the plugins. However, these tasks are already handled by the existing commands in the .NET SDK. +- This approach would still require the extraction of plugins into either a `netfx` or a `netcore` folder. As a result, package authors would need to maintain plugins for both of these target frameworks. However, NuGet plugins are executables, and the .NET SDK provides a convenient way for authors to publish an executable that can run on all platforms via .NET Tools. This eliminates the need for a framework-specific approach. + +### Specify the the authentication plugin in NuGet.Config file + +To simplify the authentication process with private NuGet feeds, customers can specify the authentication plugins installed as .NET Tools in the `packagesourceCredentials` section of the NuGet.Config file. + +Here is an example of how to configure the NuGet.Config file: + +```xml + + + + + + + + + + + + + + + +``` + +**Advantages:** +- This approach explicitly configures the intent to use the .NET Tool as a plugin for authentication in the NuGet.Config file itself. +- Customers can install the plugins as global .NET Tools, eliminating the need to specify a custom location based on the platform. + +**Disadvantages:** +- Configuring the setting per feed can be cumbersome if multiple private feeds can use the same .NET tool for authentication. + +An alternative approach to address this disadvantage is to declare the plugins explicitly outside of the package source sections. This approach allows customers to specify the dependency only once, without configuring it per private feed as discussed above. + +```xml + + + + + + + + + + + +``` + +However, the NuGet Client plugins functionality is a global setting. This means that the plugins are discovered based on the platform and target framework, as discussed earlier, and there is currently no way to configure the dependent plugins via NuGet settings per repository. It could be a future possibility to provide users with an option to manage their plugins per repository instead of loading all the available plugins. However, given the limited number of plugin implementations currently available, this is not a problem that needs to be solved at this point in time, in my understanding. + +## Prior Art + +The AWS team has developed a [.NET tool](https://docs.aws.amazon.com/codeartifact/latest/ug/nuget-cli.html#nuget-configure-cli) to simplify authentication with their private NuGet feeds. In their current implementation, they've added a subcommand, `codeartifact-creds install`. This command copies the credential provider to the NuGet plugins folder. They also provide an `uninstall` subcommand to remove the files from the NuGet plugin folders. However, due to limitations in the NuGet Client tooling, they've had to maintain plugins for both .NET Framework and .NET Core tooling. Additionally, they've had to provide commands to install and uninstall the NuGet plugins. + +## Unresolved Questions + +- None as of now. + +## Future Possibilities + +### Managing NuGet plugins per repository using .NET Local Tools. + +- A potential future possibility is mentioned under the `Specify the authentication plugin in NuGet.Config file` section. In addition to that, if we ever plan to provide users with an option to manage their plugins per repository instead of loading all the available plugins, we can consider using [.NET Local tools](https://learn.microsoft.com/dotnet/core/tools/local-tools-how-to-use). +- The advantage of local tools is that the .NET CLI uses manifest files to keep track of tools that are installed locally to a directory.When the manifest file is saved in the root directory of a source code repository, a contributor can clone the repository and invoke a single .NET CLI command such as [`dotnet tool restore`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-restore) to install all of the tools listed in the manifest file. +- Having NuGet plugins under the repository folder eliminates the need for NuGet to load plugins from the user profile. \ No newline at end of file diff --git a/meta/resources/PluginsAsDotNetTools/plugin-tools-folder-windows.png b/meta/resources/PluginsAsDotNetTools/plugin-tools-folder-windows.png new file mode 100644 index 0000000000000000000000000000000000000000..a91a25334280d55564e3ba7bc377fd58910e6235 GIT binary patch literal 29350 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 literal 0 HcmV?d00001 From fd465a9f2f0e887764f3e46244944163bac7ddca Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:07:05 -0800 Subject: [PATCH 05/69] Update accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md Co-authored-by: Andy Zivkovic --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 1326061d2..8c39c6d1a 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -73,7 +73,7 @@ The setup instructions mentioned in step 2 are platform-specific, but they are s I proposed using a `tool path` .NET tool because, by default, .NET tools are considered [global](https://learn.microsoft.com/dotnet/core/tools/global-tools). This means they are installed in a default directory, such as `%UserProfile%/.dotnet/tools` on Windows, which is then added to the PATH environment variable. - Global tools can be invoked from any directory on the machine without specifying their location. - One version of a tool is used for all directories on the machine. -However, NuGet cannot easily determine which tool is a cross-platform authentication or a package download plugin without invoking every single global tool installed on the machine. This could potentially lead to a performance hit for NuGet operations. +However, NuGet cannot easily determine which tool is a NuGet plugin. On the other hand, the `tool path` option allows us to install .NET tools as global tools, but with the binaries located in a specified location. This makes it easier to identify and invoke the appropriate tool for NuGet operations. - The binaries are installed in a location that we specify while installing the tool. - We can invoke the tool from the installation directory by providing the directory with the command name or by adding the directory to the PATH environment variable. From a9382679b1795424ea410afe38cc61906d71a150 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:07:25 -0800 Subject: [PATCH 06/69] Update accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md Co-authored-by: Andy Zivkovic --- .../support-nuget-authentication-plugins-dotnet-tools.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 8c39c6d1a..1a9b55f3c 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -102,7 +102,11 @@ If none of the above environment variables are set, NuGet will default to the co In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L79-L101), the NuGet code searches for plugin files in a plugin-specific subdirectory. For example, in the case of the Azure Artifacts Credential Provider, the code looks for `*.exe` or `*.dll` files under the "CredentialProvider.Microsoft" directory. However, when .NET tools are installed in the `tools` folder, executable files (like `.exe` files on Windows or files with the executable bit set on Linux & Mac) are placed directly in the `tools` directory. The remaining files are stored in a `.store` folder. This arrangement eliminates the need to search in subdirectories. -The new folder structure for NuGet plugins on the Windows platform is as follows: The `tools` folder contains .NET tool plugins, while the `.store` folder houses other necessary files. +NuGet will discover plugins in the new directory by enumerating files, without recursing into child directories. +This is compatible with the layout that `dotnet tool install` uses, where packages are extracted to a `.store` subdirectory, and shims are put in the tool directory, one file per executable command. +All files could be considered executables that NuGet will run. +Alternatively, on Windows, NuGet could filter `*.exe`. +On Mac and Linux, where apps typically don't have extensions, to do functionally equivalent filtering NuGet should check each file's permissions and ensure the execute bit is enabled (although technically we'd probably also need to check the file owner and group, to know if we should check the user, group, or other permissions). ![plugin-tools-folder-windows](./../../meta/resources/PluginsAsDotNetTools/plugin-tools-folder-windows.png) From 356d8b6ca034395438d25efef70252e2e10e8497 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:07:36 -0800 Subject: [PATCH 07/69] Update accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md Co-authored-by: Andy Zivkovic --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 1a9b55f3c..139350ff7 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -142,7 +142,8 @@ The ideal workflow for repositories accessing private NuGet feeds, such as Azure [Andy Zivkovic](https://github.com/zivkan) kindly proposed an alternative design in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. Here are the advantages and disadvantages of this approach: **Advantages:** -- As Andy suggested in the linked issue, we can utilize nuspec package types to enhance the discovery of credential providers. By introducing a new package type called `CredentialProvider`, the `dotnet nuget credential-provider list` command can filter by `packageType:CredentialProvider` on nuget.org. This will display a list of credential providers available on the feed, similar to the `dotnet list package` command. Although there are currently limited options for credential providers, customers would benefit from the ability to search by `packageType:CredentialProvider` on nuget.org. +- Improved discoverability, as `dotnet tool search` will list packages that are not NuGet plugins. +- Doesn't require customers to memorize or lookup the NuGet plugin directory location in order to pass it to all `dotnet tool` commands via the `--tool-path` argument, for install, uninstall, and update. **Disadvantages:** - The NuGet Client team would be required to maintain all the .NET Commands for installing, updating, and uninstalling the plugins. However, these tasks are already handled by the existing commands in the .NET SDK. From a8ced1497ab2397367ef38cb156617edd903c006 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Thu, 15 Feb 2024 11:22:33 -0800 Subject: [PATCH 08/69] remove en-us from the link --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 139350ff7..148745071 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -5,7 +5,7 @@ ## Summary -Currently, NuGet utilizes a [cross-platform plugin model](https://learn.microsoft.com/nuget/reference/extensibility/nuget-cross-platform-plugins#supported-operations) which is primarily used for [authentication against private feeds](https://learn.microsoft.com/en-us/nuget/reference/extensibility/nuget-cross-platform-authentication-plugin). It also supports the [package download](https://github.com/NuGet/Home/wiki/NuGet-Package-Download-Plugin) operation. +Currently, NuGet utilizes a [cross-platform plugin model](https://learn.microsoft.com/nuget/reference/extensibility/nuget-cross-platform-plugins#supported-operations) which is primarily used for [authentication against private feeds](https://learn.microsoft.com/nuget/reference/extensibility/nuget-cross-platform-authentication-plugin). It also supports the [package download](https://github.com/NuGet/Home/wiki/NuGet-Package-Download-Plugin) operation. To accommodate all scenarios involving NuGet client tools, plugin authors will need to create plugins for both `.NET Framework` and `.NET Core`. The following details the combinations of client and framework for these plugins. From edd850ca7de276a920f2847742b949fbdfc0e13f Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Thu, 15 Feb 2024 11:37:41 -0800 Subject: [PATCH 09/69] Formatted markdown so that the source has one sentense per line. --- ...get-authentication-plugins-dotnet-tools.md | 107 +++++++++++++----- 1 file changed, 78 insertions(+), 29 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 148745071..0f57a46d3 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -5,9 +5,11 @@ ## Summary -Currently, NuGet utilizes a [cross-platform plugin model](https://learn.microsoft.com/nuget/reference/extensibility/nuget-cross-platform-plugins#supported-operations) which is primarily used for [authentication against private feeds](https://learn.microsoft.com/nuget/reference/extensibility/nuget-cross-platform-authentication-plugin). It also supports the [package download](https://github.com/NuGet/Home/wiki/NuGet-Package-Download-Plugin) operation. +Currently, NuGet utilizes a [cross-platform plugin model](https://learn.microsoft.com/nuget/reference/extensibility/nuget-cross-platform-plugins#supported-operations) which is primarily used for [authentication against private feeds](https://learn.microsoft.com/nuget/reference/extensibility/nuget-cross-platform-authentication-plugin). +It also supports the [package download](https://github.com/NuGet/Home/wiki/NuGet-Package-Download-Plugin) operation. -To accommodate all scenarios involving NuGet client tools, plugin authors will need to create plugins for both `.NET Framework` and `.NET Core`. The following details the combinations of client and framework for these plugins. +To accommodate all scenarios involving NuGet client tools, plugin authors will need to create plugins for both `.NET Framework` and `.NET Core`. +The following details the combinations of client and framework for these plugins. | Client tool | Framework | |-------------|-----------| @@ -17,7 +19,8 @@ To accommodate all scenarios involving NuGet client tools, plugin authors will n | MSBuild.exe | .NET Framework | | NuGet.exe on Mono | .NET Framework | -Currently, NuGet maintains `netfx` folder for plugins that will be invoked in `.NET Framework` code paths, `netcore` folder for plugins that will be invoked in `.NET Core` code paths. The proposal is to add a new `tools` folder to store NuGet plugins that are deployed as [tool Path](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location) .NET tools. +Currently, NuGet maintains `netfx` folder for plugins that will be invoked in `.NET Framework` code paths, `netcore` folder for plugins that will be invoked in `.NET Core` code paths. +The proposal is to add a new `tools` folder to store NuGet plugins that are deployed as [tool Path](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location) .NET tools. | Framework | Root discovery location | |-----------|------------------------| @@ -27,14 +30,22 @@ Currently, NuGet maintains `netfx` folder for plugins that will be invoked in `. ## Motivation -At present, NuGet uses different methods to execute plugins for `.NET Framework` and `.NET Core`. For the `.NET Framework`, it looks for files ending in `*.exe`, while for `.NET Core`, it looks for files ending in `*.dll`. These files are usually stored in two separate folders under the base path for NuGet plugins. +At present, NuGet uses different methods to execute plugins for `.NET Framework` and `.NET Core`. +For the `.NET Framework`, it looks for files ending in `*.exe`, while for `.NET Core`, it looks for files ending in `*.dll`. +These files are usually stored in two separate folders under the base path for NuGet plugins. -Before `.NET Core 2.0`, this division was necessary because `.NET Core` only supported platform-agnostic DLLs. However, with the latest versions of .NET, this division is no longer necessary, but the NuGet plugin architecture still requires supporting and deploying multiple versions. This behavior is further reinforced by the two plugin path environment variables `NUGET_NETFX_PLUGIN_PATHS` and `NUGET_NETCORE_PLUGIN_PATHS`, which need to be set for each framework version. +Before `.NET Core 2.0`, this division was necessary because `.NET Core` only supported platform-agnostic DLLs. +However, with the latest versions of .NET, this division is no longer necessary, but the NuGet plugin architecture still requires supporting and deploying multiple versions. +This behavior is further reinforced by the two plugin path environment variables `NUGET_NETFX_PLUGIN_PATHS` and `NUGET_NETCORE_PLUGIN_PATHS`, which need to be set for each framework version. -Another motivating factor for this work is the current story for installing these credential providers, which is not ideal. For instance, let's consider the two cross-platform authentication plugins that I am aware of while writing this specification: +Another motivating factor for this work is the current story for installing these credential providers, which is not ideal. +For instance, let's consider the two cross-platform authentication plugins that I am aware of while writing this specification: 1. **Azure Artifacts Credential Provider** - The [setup](https://github.com/microsoft/artifacts-credprovider/tree/master?tab=readme-ov-file#setup) instructions vary based on the platform. -2. **AWS CodeArtifact Credential Provider** - The AWS team has developed a [.NET tool](https://docs.aws.amazon.com/codeartifact/latest/ug/nuget-cli.html#nuget-configure-cli) to facilitate authentication with their private NuGet feeds. In their current implementation, they've added a subcommand, `codeartifact-creds install`, which copies the credential provider to the NuGet plugins folder. The log below shows all possible subcommands. However, plugin authors could leverage the .NET SDK to allow their customers to install, uninstall, or update the NuGet plugins more efficiently. +2. **AWS CodeArtifact Credential Provider** - The AWS team has developed a [.NET tool](https://docs.aws.amazon.com/codeartifact/latest/ug/nuget-cli.html#nuget-configure-cli) to facilitate authentication with their private NuGet feeds. +In their current implementation, they've added a subcommand, `codeartifact-creds install`, which copies the credential provider to the NuGet plugins folder. +The log below shows all possible subcommands. +However, plugin authors could leverage the .NET SDK to allow their customers to install, uninstall, or update the NuGet plugins more efficiently. ```log ~\.nuget\plugins\tools @@ -58,23 +69,32 @@ Commands: ### Functional explanation -A deployment solution for .NET is the [.NET tools](https://learn.microsoft.com/dotnet/core/tools). These tools provide a seamless installation and management experience for NuGet packages in the .NET ecosystem. The use of .NET tools as a deployment mechanism has been a recurring request from users and internal partners who need to authenticate with private repositories. Currently, this solution works for the Windows .NET Framework. However, the goal is to extend this support cross-platform for all supported .NET runtimes. +A deployment solution for .NET is the [.NET tools](https://learn.microsoft.com/dotnet/core/tools). +These tools provide a seamless installation and management experience for NuGet packages in the .NET ecosystem. +The use of .NET tools as a deployment mechanism has been a recurring request from users and internal partners who need to authenticate with private repositories. +Currently, this solution works for the Windows .NET Framework. +However, the goal is to extend this support cross-platform for all supported .NET runtimes. -By implementing this specification, we offer plugin authors the option to use .NET tools for plugin deployment. They can install these as a `tool path` global tool. This eliminates the need to maintain separate versions for `.NET Framework` and `.NET Core`. It also simplifies the installation process by removing the necessity for plugin authors to create subcommands like `codeartifact-creds install/uninstall`. +By implementing this specification, we offer plugin authors the option to use .NET tools for plugin deployment. +They can install these as a `tool path` global tool. This eliminates the need to maintain separate versions for `.NET Framework` and `.NET Core`. +It also simplifies the installation process by removing the necessity for plugin authors to create subcommands like `codeartifact-creds install/uninstall`. The ideal workflow for repositories that access private NuGet feeds, such as Azure DevOps, would be: 1. Ensure that the dotnet CLI tools are installed. -2. Execute the command `dotnet tool install Microsoft.CredentialProviders --tool-path "%UserProfile%/.nuget/plugins/tools"` on the Windows platform. For Linux and Mac, run `dotnet tool install Microsoft.CredentialProviders --tool-path $HOME/.nuget/plugins`. +2. Execute the command `dotnet tool install Microsoft.CredentialProviders --tool-path "%UserProfile%/.nuget/plugins/tools"` on the Windows platform. +For Linux and Mac, run `dotnet tool install Microsoft.CredentialProviders --tool-path $HOME/.nuget/plugins`. 3. Run `dotnet restore --interactive` with a private endpoint. It should 'just work', meaning the credential providers installed in step 2 are used during credential acquisition and are used to authenticate against private endpoints. The setup instructions mentioned in step 2 are platform-specific, but they are simpler compared to the current instructions for installing credential providers. -I proposed using a `tool path` .NET tool because, by default, .NET tools are considered [global](https://learn.microsoft.com/dotnet/core/tools/global-tools). This means they are installed in a default directory, such as `%UserProfile%/.dotnet/tools` on Windows, which is then added to the PATH environment variable. +I proposed using a `tool path` .NET tool because, by default, .NET tools are considered [global](https://learn.microsoft.com/dotnet/core/tools/global-tools). +This means they are installed in a default directory, such as `%UserProfile%/.dotnet/tools` on Windows, which is then added to the PATH environment variable. - Global tools can be invoked from any directory on the machine without specifying their location. - One version of a tool is used for all directories on the machine. However, NuGet cannot easily determine which tool is a NuGet plugin. -On the other hand, the `tool path` option allows us to install .NET tools as global tools, but with the binaries located in a specified location. This makes it easier to identify and invoke the appropriate tool for NuGet operations. +On the other hand, the `tool path` option allows us to install .NET tools as global tools, but with the binaries located in a specified location. +This makes it easier to identify and invoke the appropriate tool for NuGet operations. - The binaries are installed in a location that we specify while installing the tool. - We can invoke the tool from the installation directory by providing the directory with the command name or by adding the directory to the PATH environment variable. - One version of a tool is used for all directories on the machine. @@ -82,25 +102,39 @@ The `tool path` option aligns well with the design of NuGet plugins architecture ### Security considerations -The [.NET SDK docs](https://learn.microsoft.com/dotnet/core/tools/global-tools#check-the-author-and-statistics) clearly state, `.NET tools run in full trust. Don't install a .NET tool unless you trust the author`. This is an important consideration for plugin customers when installing NuGet plugins via .NET Tools in the future. +The [.NET SDK docs](https://learn.microsoft.com/dotnet/core/tools/global-tools#check-the-author-and-statistics) clearly state, `.NET tools run in full trust. Don't install a .NET tool unless you trust the author`. +This is an important consideration for plugin customers when installing NuGet plugins via .NET Tools in the future. ### Technical explanation -Plugins are discovered through a convention-based directory structure, such as the `%userprofile%/.nuget/plugins` folder on Windows. CI/CD scenarios and power users can use environment variables to override this behavior. Note that only absolute paths are allowed when using these environment variables. +Plugins are discovered through a convention-based directory structure, such as the `%userprofile%/.nuget/plugins` folder on Windows. +CI/CD scenarios and power users can use environment variables to override this behavior. +Note that only absolute paths are allowed when using these environment variables. - `NUGET_NETFX_PLUGIN_PATHS`: Defines the plugins used by the .NET Framework-based tooling (NuGet.exe/MSBuild.exe/Visual Studio). This takes precedence over `NUGET_PLUGIN_PATHS`. -- `NUGET_NETCORE_PLUGIN_PATHS`: Defines the plugins used by the .NET Core-based tooling (dotnet.exe). This takes precedence over `NUGET_PLUGIN_PATHS`. -- `NUGET_PLUGIN_PATHS`: Defines the plugins used for the NuGet process, with priority preserved. If this environment variable is set, it overrides the convention-based discovery. It is ignored if either of the framework-specific variables is specified. +- `NUGET_NETCORE_PLUGIN_PATHS`: Defines the plugins used by the .NET Core-based tooling (dotnet.exe). +This takes precedence over `NUGET_PLUGIN_PATHS`. +- `NUGET_PLUGIN_PATHS`: Defines the plugins used for the NuGet process, with priority preserved. +If this environment variable is set, it overrides the convention-based discovery. +It is ignored if either of the framework-specific variables is specified. -I propose the addition of a `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable will define the plugins, installed as .NET tools, to be used by both .NET Framework and .NET Core tooling. It will take precedence over `NUGET_PLUGIN_PATHS`. +I propose the addition of a `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. +This variable will define the plugins, installed as .NET tools, to be used by both .NET Framework and .NET Core tooling. It will take precedence over `NUGET_PLUGIN_PATHS`. -The plugins specified in the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable will be used regardless of whether the `NUGET_NETFX_PLUGIN_PATHS` or `NUGET_NETCORE_PLUGIN_PATHS` environment variables are set. The primary reason for this is that plugins installed as .NET tools can be executed in both .NET Framework and .NET Core tooling. +The plugins specified in the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable will be used regardless of whether the `NUGET_NETFX_PLUGIN_PATHS` or `NUGET_NETCORE_PLUGIN_PATHS` environment variables are set. +The primary reason for this is that plugins installed as .NET tools can be executed in both .NET Framework and .NET Core tooling. -If customers prefer to install NuGet plugins as a global tool instead of a tool-path tool, they can set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable should point to the location of the .NET Tool executable that the NuGet Client tooling can invoke when needed. +If customers prefer to install NuGet plugins as a global tool instead of a tool-path tool, they can set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. +This variable should point to the location of the .NET Tool executable that the NuGet Client tooling can invoke when needed. -If none of the above environment variables are set, NuGet will default to the conventional method of discovering plugins from predefined directories. In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/8b658e2eee6391936887b9fd1b39f7918d16a9cb/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L65-L77), the NuGet code looks for plugin files in the `netfx` directory when running on .NET Framework, and in the `netcore` directory when running on .NET Core. This implementation should be updated to include the new `tools` directory. This directory should be added alongside `netcore` in the .NET code paths and `netfx` in the .NET Framework code paths, to ensure backward compatibility. +If none of the above environment variables are set, NuGet will default to the conventional method of discovering plugins from predefined directories. +In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/8b658e2eee6391936887b9fd1b39f7918d16a9cb/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L65-L77), the NuGet code looks for plugin files in the `netfx` directory when running on .NET Framework, and in the `netcore` directory when running on .NET Core. This implementation should be updated to include the new `tools` directory. +This directory should be added alongside `netcore` in the .NET code paths and `netfx` in the .NET Framework code paths, to ensure backward compatibility. -In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L79-L101), the NuGet code searches for plugin files in a plugin-specific subdirectory. For example, in the case of the Azure Artifacts Credential Provider, the code looks for `*.exe` or `*.dll` files under the "CredentialProvider.Microsoft" directory. However, when .NET tools are installed in the `tools` folder, executable files (like `.exe` files on Windows or files with the executable bit set on Linux & Mac) are placed directly in the `tools` directory. The remaining files are stored in a `.store` folder. This arrangement eliminates the need to search in subdirectories. +In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L79-L101), the NuGet code searches for plugin files in a plugin-specific subdirectory. +For example, in the case of the Azure Artifacts Credential Provider, the code looks for `*.exe` or `*.dll` files under the "CredentialProvider.Microsoft" directory. +However, when .NET tools are installed in the `tools` folder, executable files (like `.exe` files on Windows or files with the executable bit set on Linux & Mac) are placed directly in the `tools` directory. +The remaining files are stored in a `.store` folder. This arrangement eliminates the need to search in subdirectories. NuGet will discover plugins in the new directory by enumerating files, without recursing into child directories. This is compatible with the layout that `dotnet tool install` uses, where packages are extracted to a `.store` subdirectory, and shims are put in the tool directory, one file per executable command. @@ -133,13 +167,17 @@ drwxr-xr-x 5 {user} 4096 Feb 10 08:21 .store ## Drawbacks -The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, involves installing the NuGet plugin as a .NET global tool. However, the current proposal suggests installing the plugin as a tool-path .NET tool. As mentioned in the technical explanation section, customers can opt to install NuGet plugins as a global tool instead of a tool-path tool. To do this, they need to set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable should point to the location of the .NET Tool executable, which the NuGet Client tooling can invoke when needed. +The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, involves installing the NuGet plugin as a .NET global tool. +However, the current proposal suggests installing the plugin as a tool-path .NET tool. As mentioned in the technical explanation section, customers can opt to install NuGet plugins as a global tool instead of a tool-path tool. +To do this, they need to set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable should point to the location of the .NET Tool executable, which the NuGet Client tooling can invoke when needed. ## Rationale and alternatives ### NuGet commands to install credential providers -[Andy Zivkovic](https://github.com/zivkan) kindly proposed an alternative design in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. Here are the advantages and disadvantages of this approach: +[Andy Zivkovic](https://github.com/zivkan) kindly proposed an alternative design in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). +The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. +Here are the advantages and disadvantages of this approach: **Advantages:** - Improved discoverability, as `dotnet tool search` will list packages that are not NuGet plugins. @@ -147,7 +185,10 @@ The ideal workflow for repositories accessing private NuGet feeds, such as Azure **Disadvantages:** - The NuGet Client team would be required to maintain all the .NET Commands for installing, updating, and uninstalling the plugins. However, these tasks are already handled by the existing commands in the .NET SDK. -- This approach would still require the extraction of plugins into either a `netfx` or a `netcore` folder. As a result, package authors would need to maintain plugins for both of these target frameworks. However, NuGet plugins are executables, and the .NET SDK provides a convenient way for authors to publish an executable that can run on all platforms via .NET Tools. This eliminates the need for a framework-specific approach. +- This approach would still require the extraction of plugins into either a `netfx` or a `netcore` folder. +As a result, package authors would need to maintain plugins for both of these target frameworks. +However, NuGet plugins are executables, and the .NET SDK provides a convenient way for authors to publish an executable that can run on all platforms via .NET Tools. +This eliminates the need for a framework-specific approach. ### Specify the the authentication plugin in NuGet.Config file @@ -180,7 +221,8 @@ Here is an example of how to configure the NuGet.Config file: **Disadvantages:** - Configuring the setting per feed can be cumbersome if multiple private feeds can use the same .NET tool for authentication. -An alternative approach to address this disadvantage is to declare the plugins explicitly outside of the package source sections. This approach allows customers to specify the dependency only once, without configuring it per private feed as discussed above. +An alternative approach to address this disadvantage is to declare the plugins explicitly outside of the package source sections. +This approach allows customers to specify the dependency only once, without configuring it per private feed as discussed above. ```xml @@ -196,11 +238,16 @@ An alternative approach to address this disadvantage is to declare the plugins e ``` -However, the NuGet Client plugins functionality is a global setting. This means that the plugins are discovered based on the platform and target framework, as discussed earlier, and there is currently no way to configure the dependent plugins via NuGet settings per repository. It could be a future possibility to provide users with an option to manage their plugins per repository instead of loading all the available plugins. However, given the limited number of plugin implementations currently available, this is not a problem that needs to be solved at this point in time, in my understanding. +However, the NuGet Client plugins functionality is a global setting. This means that the plugins are discovered based on the platform and target framework, as discussed earlier, and there is currently no way to configure the dependent plugins via NuGet settings per repository. It could be a future possibility to provide users with an option to manage their plugins per repository instead of loading all the available plugins. +However, given the limited number of plugin implementations currently available, this is not a problem that needs to be solved at this point in time, in my understanding. ## Prior Art -The AWS team has developed a [.NET tool](https://docs.aws.amazon.com/codeartifact/latest/ug/nuget-cli.html#nuget-configure-cli) to simplify authentication with their private NuGet feeds. In their current implementation, they've added a subcommand, `codeartifact-creds install`. This command copies the credential provider to the NuGet plugins folder. They also provide an `uninstall` subcommand to remove the files from the NuGet plugin folders. However, due to limitations in the NuGet Client tooling, they've had to maintain plugins for both .NET Framework and .NET Core tooling. Additionally, they've had to provide commands to install and uninstall the NuGet plugins. +The AWS team has developed a [.NET tool](https://docs.aws.amazon.com/codeartifact/latest/ug/nuget-cli.html#nuget-configure-cli) to simplify authentication with their private NuGet feeds. +In their current implementation, they've added a subcommand, `codeartifact-creds install`. +This command copies the credential provider to the NuGet plugins folder. +They also provide an `uninstall` subcommand to remove the files from the NuGet plugin folders. +However, due to limitations in the NuGet Client tooling, they've had to maintain plugins for both .NET Framework and .NET Core tooling. Additionally, they've had to provide commands to install and uninstall the NuGet plugins. ## Unresolved Questions @@ -210,6 +257,8 @@ The AWS team has developed a [.NET tool](https://docs.aws.amazon.com/codeartifac ### Managing NuGet plugins per repository using .NET Local Tools. -- A potential future possibility is mentioned under the `Specify the authentication plugin in NuGet.Config file` section. In addition to that, if we ever plan to provide users with an option to manage their plugins per repository instead of loading all the available plugins, we can consider using [.NET Local tools](https://learn.microsoft.com/dotnet/core/tools/local-tools-how-to-use). -- The advantage of local tools is that the .NET CLI uses manifest files to keep track of tools that are installed locally to a directory.When the manifest file is saved in the root directory of a source code repository, a contributor can clone the repository and invoke a single .NET CLI command such as [`dotnet tool restore`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-restore) to install all of the tools listed in the manifest file. +- A potential future possibility is mentioned under the `Specify the authentication plugin in NuGet.Config file` section. +In addition to that, if we ever plan to provide users with an option to manage their plugins per repository instead of loading all the available plugins, we can consider using [.NET Local tools](https://learn.microsoft.com/dotnet/core/tools/local-tools-how-to-use). +- The advantage of local tools is that the .NET CLI uses manifest files to keep track of tools that are installed locally to a directory. +When the manifest file is saved in the root directory of a source code repository, a contributor can clone the repository and invoke a single .NET CLI command such as [`dotnet tool restore`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-restore) to install all of the tools listed in the manifest file. - Having NuGet plugins under the repository folder eliminates the need for NuGet to load plugins from the user profile. \ No newline at end of file From b33b45968b7b34648c7fa32f9921eda61dfa3874 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Thu, 15 Feb 2024 14:11:54 -0800 Subject: [PATCH 10/69] Update workflow for repositories accessing private NuGet feeds --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 0f57a46d3..4a7061546 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -79,7 +79,7 @@ By implementing this specification, we offer plugin authors the option to use .N They can install these as a `tool path` global tool. This eliminates the need to maintain separate versions for `.NET Framework` and `.NET Core`. It also simplifies the installation process by removing the necessity for plugin authors to create subcommands like `codeartifact-creds install/uninstall`. -The ideal workflow for repositories that access private NuGet feeds, such as Azure DevOps, would be: +The proposed workflow for repositories that access private NuGet feeds, such as Azure DevOps, would be: 1. Ensure that the dotnet CLI tools are installed. 2. Execute the command `dotnet tool install Microsoft.CredentialProviders --tool-path "%UserProfile%/.nuget/plugins/tools"` on the Windows platform. From a5e469c85f633025a512f8dc7f4b59dbc1dd206d Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Thu, 15 Feb 2024 19:01:31 -0800 Subject: [PATCH 11/69] rename directory to any --- .../support-nuget-authentication-plugins-dotnet-tools.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 4a7061546..cf5c66eff 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -20,7 +20,7 @@ The following details the combinations of client and framework for these plugins | NuGet.exe on Mono | .NET Framework | Currently, NuGet maintains `netfx` folder for plugins that will be invoked in `.NET Framework` code paths, `netcore` folder for plugins that will be invoked in `.NET Core` code paths. -The proposal is to add a new `tools` folder to store NuGet plugins that are deployed as [tool Path](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location) .NET tools. +The proposal is to add a new `any` folder to store NuGet plugins that are deployed as [tool Path](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location) .NET tools. | Framework | Root discovery location | |-----------|------------------------| @@ -128,12 +128,12 @@ If customers prefer to install NuGet plugins as a global tool instead of a tool- This variable should point to the location of the .NET Tool executable that the NuGet Client tooling can invoke when needed. If none of the above environment variables are set, NuGet will default to the conventional method of discovering plugins from predefined directories. -In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/8b658e2eee6391936887b9fd1b39f7918d16a9cb/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L65-L77), the NuGet code looks for plugin files in the `netfx` directory when running on .NET Framework, and in the `netcore` directory when running on .NET Core. This implementation should be updated to include the new `tools` directory. +In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/8b658e2eee6391936887b9fd1b39f7918d16a9cb/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L65-L77), the NuGet code looks for plugin files in the `netfx` directory when running on .NET Framework, and in the `netcore` directory when running on .NET Core. This implementation should be updated to include the new `any` directory. This directory should be added alongside `netcore` in the .NET code paths and `netfx` in the .NET Framework code paths, to ensure backward compatibility. In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L79-L101), the NuGet code searches for plugin files in a plugin-specific subdirectory. For example, in the case of the Azure Artifacts Credential Provider, the code looks for `*.exe` or `*.dll` files under the "CredentialProvider.Microsoft" directory. -However, when .NET tools are installed in the `tools` folder, executable files (like `.exe` files on Windows or files with the executable bit set on Linux & Mac) are placed directly in the `tools` directory. +However, when .NET tools are installed in the `any` folder, executable files (like `.exe` files on Windows or files with the executable bit set on Linux & Mac) are placed directly in the `any` directory. The remaining files are stored in a `.store` folder. This arrangement eliminates the need to search in subdirectories. NuGet will discover plugins in the new directory by enumerating files, without recursing into child directories. From 1fa8abc23dded7208b1509669a1643659d2525f0 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Thu, 15 Feb 2024 19:11:29 -0800 Subject: [PATCH 12/69] updated summary --- .../support-nuget-authentication-plugins-dotnet-tools.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index cf5c66eff..59ae8f54e 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -20,7 +20,8 @@ The following details the combinations of client and framework for these plugins | NuGet.exe on Mono | .NET Framework | Currently, NuGet maintains `netfx` folder for plugins that will be invoked in `.NET Framework` code paths, `netcore` folder for plugins that will be invoked in `.NET Core` code paths. -The proposal is to add a new `any` folder to store NuGet plugins that are deployed as [tool Path](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location) .NET tools. +The proposal is to add a new `any` folder to store NuGet plugins that are deployed as [tool Path](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location) .NET tools. +Upon installation, .NET tools are organized in a way that helps NuGet quickly determine which file to run. | Framework | Root discovery location | |-----------|------------------------| @@ -28,6 +29,8 @@ The proposal is to add a new `any` folder to store NuGet plugins that are deploy | .NET Framework | %UserProfile%/.nuget/plugins/netfx | | .NET Framework & .NET Core [current proposal] | %UserProfile%/.nuget/plugins/tools | + + ## Motivation At present, NuGet uses different methods to execute plugins for `.NET Framework` and `.NET Core`. From 953d2a1ec53419d374d7734d9d7a263a2fc57033 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Thu, 15 Feb 2024 19:15:55 -0800 Subject: [PATCH 13/69] Update NuGet plugin organization for .NET tools --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 59ae8f54e..79f4747ec 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -21,7 +21,7 @@ The following details the combinations of client and framework for these plugins Currently, NuGet maintains `netfx` folder for plugins that will be invoked in `.NET Framework` code paths, `netcore` folder for plugins that will be invoked in `.NET Core` code paths. The proposal is to add a new `any` folder to store NuGet plugins that are deployed as [tool Path](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location) .NET tools. -Upon installation, .NET tools are organized in a way that helps NuGet quickly determine which file to run. +Upon installation, .NET tools are organized in a way that helps NuGet quickly determine which file in the package to run. | Framework | Root discovery location | |-----------|------------------------| From 86535906302c46f0f4a216bcaf9e69cdf5ea366c Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Thu, 15 Feb 2024 21:14:31 -0800 Subject: [PATCH 14/69] update motivation --- ...ort-nuget-authentication-plugins-dotnet-tools.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 79f4747ec..a15d599d8 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -33,13 +33,12 @@ Upon installation, .NET tools are organized in a way that helps NuGet quickly de ## Motivation -At present, NuGet uses different methods to execute plugins for `.NET Framework` and `.NET Core`. -For the `.NET Framework`, it looks for files ending in `*.exe`, while for `.NET Core`, it looks for files ending in `*.dll`. -These files are usually stored in two separate folders under the base path for NuGet plugins. - -Before `.NET Core 2.0`, this division was necessary because `.NET Core` only supported platform-agnostic DLLs. -However, with the latest versions of .NET, this division is no longer necessary, but the NuGet plugin architecture still requires supporting and deploying multiple versions. -This behavior is further reinforced by the two plugin path environment variables `NUGET_NETFX_PLUGIN_PATHS` and `NUGET_NETCORE_PLUGIN_PATHS`, which need to be set for each framework version. +Currently, the NuGet plugin architecture requires support and deployment of multiple versions. +For `.NET Framework`, NuGet searches for files ending in `*.exe`, while for `.NET Core`, it searches for files ending in `*.dll`. +These files are typically stored in two distinct folders such as `netfx` and `netcore` under the NuGet plugins base path. +This design decision is due to the different entry points for `.NET Core` and `.NET Framework`, as explained in the [original design](https://github.com/NuGet/Home/wiki/NuGet-cross-plat-authentication-plugin#plugin-installation-and-discovery). +`.NET Core` uses files with a `dll` extension, while `.NET Framework` uses files with an `exe` extension as entry points. +This distinction is further highlighted by the two plugin path environment variables, `NUGET_NETFX_PLUGIN_PATHS` and `NUGET_NETCORE_PLUGIN_PATHS`, which must be set for each framework type. Another motivating factor for this work is the current story for installing these credential providers, which is not ideal. For instance, let's consider the two cross-platform authentication plugins that I am aware of while writing this specification: From 6c294b325de404fbc4dd5b969d3ae796a7577665 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Thu, 15 Feb 2024 21:15:27 -0800 Subject: [PATCH 15/69] updated technical specification --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index a15d599d8..bd7a473b1 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -109,7 +109,7 @@ This is an important consideration for plugin customers when installing NuGet pl ### Technical explanation -Plugins are discovered through a convention-based directory structure, such as the `%userprofile%/.nuget/plugins` folder on Windows. +Currently, plugins are discovered through a convention-based directory structure, such as the `%userprofile%/.nuget/plugins` folder on Windows. CI/CD scenarios and power users can use environment variables to override this behavior. Note that only absolute paths are allowed when using these environment variables. From 742d47186a8dda861902bfdda1127f1a40e3db46 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Thu, 15 Feb 2024 21:16:56 -0800 Subject: [PATCH 16/69] one sentense per line --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index bd7a473b1..155684480 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -121,7 +121,8 @@ If this environment variable is set, it overrides the convention-based discovery It is ignored if either of the framework-specific variables is specified. I propose the addition of a `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. -This variable will define the plugins, installed as .NET tools, to be used by both .NET Framework and .NET Core tooling. It will take precedence over `NUGET_PLUGIN_PATHS`. +This variable will define the plugins, installed as .NET tools, to be used by both .NET Framework and .NET Core tooling. +It will take precedence over `NUGET_PLUGIN_PATHS`. The plugins specified in the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable will be used regardless of whether the `NUGET_NETFX_PLUGIN_PATHS` or `NUGET_NETCORE_PLUGIN_PATHS` environment variables are set. The primary reason for this is that plugins installed as .NET tools can be executed in both .NET Framework and .NET Core tooling. From 65b104d3c7fb2c879d1290a621e9abce226c25c6 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Thu, 15 Feb 2024 21:33:01 -0800 Subject: [PATCH 17/69] use tree /f command output instead of images --- ...get-authentication-plugins-dotnet-tools.md | 18 ++++++++++++++++-- .../plugin-tools-folder-windows.png | Bin 29350 -> 0 bytes .../plugin-tools-store-windows.png | Bin 29386 -> 0 bytes 3 files changed, 16 insertions(+), 2 deletions(-) delete mode 100644 meta/resources/PluginsAsDotNetTools/plugin-tools-folder-windows.png delete mode 100644 meta/resources/PluginsAsDotNetTools/plugin-tools-store-windows.png diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 155684480..36a75e0f5 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -145,9 +145,23 @@ All files could be considered executables that NuGet will run. Alternatively, on Windows, NuGet could filter `*.exe`. On Mac and Linux, where apps typically don't have extensions, to do functionally equivalent filtering NuGet should check each file's permissions and ensure the execute bit is enabled (although technically we'd probably also need to check the file owner and group, to know if we should check the user, group, or other permissions). -![plugin-tools-folder-windows](./../../meta/resources/PluginsAsDotNetTools/plugin-tools-folder-windows.png) +The new folder structure for NuGet plugins on the Windows platform is as follows: -![plugin-tools-store-windows](./../../meta/resources/PluginsAsDotNetTools/plugin-tools-store-windows.png) +``` +├───any +│ │ dotnetsay.exe +│ │ +│ └───.store│ +├───netcore +├ ├───AWS.CodeArtifact.NuGetCredentialProvider +├ │ +│ └───CredentialProvider.Microsoft +└───netfx +├ ├───AWS.CodeArtifact.NuGetCredentialProvider +├ │ +├ └───CredentialProvider.Microsoft +└ +``` The new folder structure for NuGet plugins on the Linux platforms is as follows: diff --git a/meta/resources/PluginsAsDotNetTools/plugin-tools-folder-windows.png b/meta/resources/PluginsAsDotNetTools/plugin-tools-folder-windows.png deleted file mode 100644 index a91a25334280d55564e3ba7bc377fd58910e6235..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29350 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 From 7c50c460e018b328ac6bbc11c74276bb26a56f6b Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Fri, 8 Mar 2024 18:45:02 -0800 Subject: [PATCH 18/69] updated drawbacks section --- .../support-nuget-authentication-plugins-dotnet-tools.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 36a75e0f5..6997bacee 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -184,9 +184,11 @@ drwxr-xr-x 5 {user} 4096 Feb 10 08:21 .store ## Drawbacks -The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, involves installing the NuGet plugin as a .NET global tool. -However, the current proposal suggests installing the plugin as a tool-path .NET tool. As mentioned in the technical explanation section, customers can opt to install NuGet plugins as a global tool instead of a tool-path tool. -To do this, they need to set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable should point to the location of the .NET Tool executable, which the NuGet Client tooling can invoke when needed. +The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, is to easily search for NuGet plugins and install them without needing to know the destination location. +However, the current proposal suggests installing the plugin as a tool-path .NET tool. +As mentioned in the technical explanation section, customers can opt to install NuGet plugins as a global tool instead of a tool-path tool. +To do this, they need to set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. +This variable should point to the location of the .NET Tool executable, which the NuGet Client tooling can invoke when needed. ## Rationale and alternatives From 18361fbe1034b00e66512707829d2e4a7cc08f1c Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:42:10 -0800 Subject: [PATCH 19/69] one sentense per line. --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 6997bacee..001ac5573 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -257,7 +257,9 @@ This approach allows customers to specify the dependency only once, without conf ``` -However, the NuGet Client plugins functionality is a global setting. This means that the plugins are discovered based on the platform and target framework, as discussed earlier, and there is currently no way to configure the dependent plugins via NuGet settings per repository. It could be a future possibility to provide users with an option to manage their plugins per repository instead of loading all the available plugins. +However, the NuGet Client plugins functionality is a global setting. +This means that the plugins are discovered based on the platform and target framework, as discussed earlier, and there is currently no way to configure the dependent plugins via NuGet settings per repository. +It could be a future possibility to provide users with an option to manage their plugins per repository instead of loading all the available plugins. However, given the limited number of plugin implementations currently available, this is not a problem that needs to be solved at this point in time, in my understanding. ## Prior Art From acd1ae442a384a4040593a563142fd3abb9d3334 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Fri, 8 Mar 2024 20:10:51 -0800 Subject: [PATCH 20/69] updated drawbacks section --- .../support-nuget-authentication-plugins-dotnet-tools.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 001ac5573..e0a4557b3 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -184,12 +184,15 @@ drwxr-xr-x 5 {user} 4096 Feb 10 08:21 .store ## Drawbacks -The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, is to easily search for NuGet plugins and install them without needing to know the destination location. +- The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, is to easily search for NuGet plugins and install them without needing to know the destination location. However, the current proposal suggests installing the plugin as a tool-path .NET tool. As mentioned in the technical explanation section, customers can opt to install NuGet plugins as a global tool instead of a tool-path tool. To do this, they need to set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable should point to the location of the .NET Tool executable, which the NuGet Client tooling can invoke when needed. +- There is some risk of the `dotnet tool` introducing breaking changes that could negatively impact NuGet. +If the `dotnet tool` started writing non-executable files into the directory, it would affect how NuGet discovers and runs plugins at runtime. + ## Rationale and alternatives ### NuGet commands to install credential providers From 953ce5d49b84bd9c7b8f9cccdf9b7f1995fc95ac Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Tue, 12 Mar 2024 12:13:27 -0700 Subject: [PATCH 21/69] Update NuGet plugin folder structure in non-Windows platforms --- .../support-nuget-authentication-plugins-dotnet-tools.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index e0a4557b3..c3d11ba19 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -167,13 +167,13 @@ The new folder structure for NuGet plugins on the Linux platforms is as follows: ```log {user}:~/.nuget/plugins$ dir -netcore tools +netcore any -{user}:~/.nuget/plugins$ cd tools/ -{user}:~/.nuget/plugins/tools$ dir +{user}:~/.nuget/plugins$ cd any/ +{user}:~/.nuget/plugins/any$ dir botsay dotnetsay -{user}:~/.nuget/plugins/tools$ ls -la +{user}:~/.nuget/plugins/any$ ls -la total 164 drwxr-xr-x 3 {user} 4096 Feb 10 08:21 . drwxr-xr-x 4 {user} 4096 Feb 10 08:20 .. From 22576101caa1dfc747ee2b4e4daea6ea8d7cd361 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Tue, 12 Mar 2024 12:14:57 -0700 Subject: [PATCH 22/69] lint issues --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index c3d11ba19..4d724b4e6 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -237,10 +237,12 @@ Here is an example of how to configure the NuGet.Config file: ``` **Advantages:** + - This approach explicitly configures the intent to use the .NET Tool as a plugin for authentication in the NuGet.Config file itself. - Customers can install the plugins as global .NET Tools, eliminating the need to specify a custom location based on the platform. **Disadvantages:** + - Configuring the setting per feed can be cumbersome if multiple private feeds can use the same .NET tool for authentication. An alternative approach to address this disadvantage is to declare the plugins explicitly outside of the package source sections. From 5986371780b663c6e5c4bf79ca35e7ffeaad67c4 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Tue, 12 Mar 2024 12:16:37 -0700 Subject: [PATCH 23/69] link issues --- ...get-authentication-plugins-dotnet-tools.md | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 4d724b4e6..2458ef905 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -1,7 +1,7 @@ # ***Support for NuGet authentication plugins deployed via .NET tools*** -- Author Name: https://github.com/kartheekp-ms -- GitHub Issue: https://github.com/NuGet/Home/issues/12567 +- Author Name: +- GitHub Issue: ## Summary @@ -20,7 +20,7 @@ The following details the combinations of client and framework for these plugins | NuGet.exe on Mono | .NET Framework | Currently, NuGet maintains `netfx` folder for plugins that will be invoked in `.NET Framework` code paths, `netcore` folder for plugins that will be invoked in `.NET Core` code paths. -The proposal is to add a new `any` folder to store NuGet plugins that are deployed as [tool Path](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location) .NET tools. +The proposal is to add a new `any` folder to store NuGet plugins that are deployed as [tool Path](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location) .NET tools. Upon installation, .NET tools are organized in a way that helps NuGet quickly determine which file in the package to run. | Framework | Root discovery location | @@ -29,9 +29,7 @@ Upon installation, .NET tools are organized in a way that helps NuGet quickly de | .NET Framework | %UserProfile%/.nuget/plugins/netfx | | .NET Framework & .NET Core [current proposal] | %UserProfile%/.nuget/plugins/tools | - - -## Motivation +## Motivation Currently, the NuGet plugin architecture requires support and deployment of multiple versions. For `.NET Framework`, NuGet searches for files ending in `*.exe`, while for `.NET Core`, it searches for files ending in `*.dll`. @@ -92,6 +90,7 @@ The setup instructions mentioned in step 2 are platform-specific, but they are s I proposed using a `tool path` .NET tool because, by default, .NET tools are considered [global](https://learn.microsoft.com/dotnet/core/tools/global-tools). This means they are installed in a default directory, such as `%UserProfile%/.dotnet/tools` on Windows, which is then added to the PATH environment variable. + - Global tools can be invoked from any directory on the machine without specifying their location. - One version of a tool is used for all directories on the machine. However, NuGet cannot easily determine which tool is a NuGet plugin. @@ -122,7 +121,7 @@ It is ignored if either of the framework-specific variables is specified. I propose the addition of a `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable will define the plugins, installed as .NET tools, to be used by both .NET Framework and .NET Core tooling. -It will take precedence over `NUGET_PLUGIN_PATHS`. +It will take precedence over `NUGET_PLUGIN_PATHS`. The plugins specified in the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable will be used regardless of whether the `NUGET_NETFX_PLUGIN_PATHS` or `NUGET_NETCORE_PLUGIN_PATHS` environment variables are set. The primary reason for this is that plugins installed as .NET tools can be executed in both .NET Framework and .NET Core tooling. @@ -147,7 +146,7 @@ On Mac and Linux, where apps typically don't have extensions, to do functionally The new folder structure for NuGet plugins on the Windows platform is as follows: -``` +```folder ├───any │ │ dotnetsay.exe │ │ @@ -184,13 +183,13 @@ drwxr-xr-x 5 {user} 4096 Feb 10 08:21 .store ## Drawbacks -- The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, is to easily search for NuGet plugins and install them without needing to know the destination location. -However, the current proposal suggests installing the plugin as a tool-path .NET tool. +- The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, is to easily search for NuGet plugins and install them without needing to know the destination location. +However, the current proposal suggests installing the plugin as a tool-path .NET tool. As mentioned in the technical explanation section, customers can opt to install NuGet plugins as a global tool instead of a tool-path tool. To do this, they need to set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable should point to the location of the .NET Tool executable, which the NuGet Client tooling can invoke when needed. -- There is some risk of the `dotnet tool` introducing breaking changes that could negatively impact NuGet. +- There is some risk of the `dotnet tool` introducing breaking changes that could negatively impact NuGet. If the `dotnet tool` started writing non-executable files into the directory, it would affect how NuGet discovers and runs plugins at runtime. ## Rationale and alternatives @@ -202,10 +201,12 @@ The recommendation was developing a command like `dotnet nuget credential-provid Here are the advantages and disadvantages of this approach: **Advantages:** + - Improved discoverability, as `dotnet tool search` will list packages that are not NuGet plugins. - Doesn't require customers to memorize or lookup the NuGet plugin directory location in order to pass it to all `dotnet tool` commands via the `--tool-path` argument, for install, uninstall, and update. **Disadvantages:** + - The NuGet Client team would be required to maintain all the .NET Commands for installing, updating, and uninstalling the plugins. However, these tasks are already handled by the existing commands in the .NET SDK. - This approach would still require the extraction of plugins into either a `netfx` or a `netcore` folder. As a result, package authors would need to maintain plugins for both of these target frameworks. @@ -281,10 +282,10 @@ However, due to limitations in the NuGet Client tooling, they've had to maintain ## Future Possibilities -### Managing NuGet plugins per repository using .NET Local Tools. +### Managing NuGet plugins per repository using .NET Local Tools - A potential future possibility is mentioned under the `Specify the authentication plugin in NuGet.Config file` section. In addition to that, if we ever plan to provide users with an option to manage their plugins per repository instead of loading all the available plugins, we can consider using [.NET Local tools](https://learn.microsoft.com/dotnet/core/tools/local-tools-how-to-use). - The advantage of local tools is that the .NET CLI uses manifest files to keep track of tools that are installed locally to a directory. When the manifest file is saved in the root directory of a source code repository, a contributor can clone the repository and invoke a single .NET CLI command such as [`dotnet tool restore`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-restore) to install all of the tools listed in the manifest file. -- Having NuGet plugins under the repository folder eliminates the need for NuGet to load plugins from the user profile. \ No newline at end of file +- Having NuGet plugins under the repository folder eliminates the need for NuGet to load plugins from the user profile. From 9890ac4afcf3cddaa8046016182fac8515e08969 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Tue, 12 Mar 2024 12:20:04 -0700 Subject: [PATCH 24/69] Update NuGet tool installation process --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 2458ef905..de6577ea0 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -21,7 +21,7 @@ The following details the combinations of client and framework for these plugins Currently, NuGet maintains `netfx` folder for plugins that will be invoked in `.NET Framework` code paths, `netcore` folder for plugins that will be invoked in `.NET Core` code paths. The proposal is to add a new `any` folder to store NuGet plugins that are deployed as [tool Path](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location) .NET tools. -Upon installation, .NET tools are organized in a way that helps NuGet quickly determine which file in the package to run. +Upon installation, .NET tools are organized in a way that helps NuGet quickly determine which file in the package to run at runtime. | Framework | Root discovery location | |-----------|------------------------| From 0585ae53e9e1cb2614cfe8306f0466754b50d324 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Tue, 12 Mar 2024 12:20:19 -0700 Subject: [PATCH 25/69] Update NuGet authentication plugin directory path --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index de6577ea0..5e9cc4a7f 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -27,7 +27,7 @@ Upon installation, .NET tools are organized in a way that helps NuGet quickly de |-----------|------------------------| | .NET Core | %UserProfile%/.nuget/plugins/netcore | | .NET Framework | %UserProfile%/.nuget/plugins/netfx | -| .NET Framework & .NET Core [current proposal] | %UserProfile%/.nuget/plugins/tools | +| .NET Framework & .NET Core [current proposal] | %UserProfile%/.nuget/plugins/any | ## Motivation From 1980809631009f76c34793f99fb3ff3f5a71ac27 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Tue, 12 Mar 2024 12:21:46 -0700 Subject: [PATCH 26/69] Update NuGet plugin path in dotnet-codeartifact-creds.exe --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 5e9cc4a7f..3ef2645e5 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -48,7 +48,7 @@ The log below shows all possible subcommands. However, plugin authors could leverage the .NET SDK to allow their customers to install, uninstall, or update the NuGet plugins more efficiently. ```log -~\.nuget\plugins\tools +~\.nuget\plugins\any ❯ .\dotnet-codeartifact-creds.exe Required command was not provided. From fce39bb732ba6e2fbf98849a0a0bf1f22ad081a5 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Wed, 13 Mar 2024 17:36:03 -0700 Subject: [PATCH 27/69] Update NuGet plugin installation process for cross-platform support --- ...rt-nuget-authentication-plugins-dotnet-tools.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 3ef2645e5..366cf8469 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -76,17 +76,21 @@ Currently, this solution works for the Windows .NET Framework. However, the goal is to extend this support cross-platform for all supported .NET runtimes. By implementing this specification, we offer plugin authors the option to use .NET tools for plugin deployment. -They can install these as a `tool path` global tool. This eliminates the need to maintain separate versions for `.NET Framework` and `.NET Core`. +These plugins will be installed as a `tool path` global tool. This eliminates the need to maintain separate versions for `.NET Framework` and `.NET Core`. It also simplifies the installation process by removing the necessity for plugin authors to create subcommands like `codeartifact-creds install/uninstall`. The proposed workflow for repositories that access private NuGet feeds, such as Azure DevOps, would be: 1. Ensure that the dotnet CLI tools are installed. -2. Execute the command `dotnet tool install Microsoft.CredentialProviders --tool-path "%UserProfile%/.nuget/plugins/tools"` on the Windows platform. -For Linux and Mac, run `dotnet tool install Microsoft.CredentialProviders --tool-path $HOME/.nuget/plugins`. -3. Run `dotnet restore --interactive` with a private endpoint. It should 'just work', meaning the credential providers installed in step 2 are used during credential acquisition and are used to authenticate against private endpoints. +1. Execute the command `dotnet nuget plugin install Microsoft.CredentialProvider`. +The `tool path` global tool will be installed in the default NuGet plugins location, as mentioned below. -The setup instructions mentioned in step 2 are platform-specific, but they are simpler compared to the current instructions for installing credential providers. +| Operating System | Installation Path | +| ---------------- | ----------------- | +| Windows | %UserProfile%/.nuget/plugins/any | +| Linux/Mac | $HOME/.nuget/plugins/any | + +1. Run `dotnet restore --interactive` with a private endpoint. It should 'just work', meaning the credential providers installed in step 2 are used during credential acquisition and are used to authenticate against private endpoints. I proposed using a `tool path` .NET tool because, by default, .NET tools are considered [global](https://learn.microsoft.com/dotnet/core/tools/global-tools). This means they are installed in a default directory, such as `%UserProfile%/.dotnet/tools` on Windows, which is then added to the PATH environment variable. From 319d79d9678b613f6669d850bbb99082de5f1083 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Sun, 17 Mar 2024 14:06:56 -0700 Subject: [PATCH 28/69] Update NuGet plugin installation process for cross-platform support --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 366cf8469..8a306036e 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -83,6 +83,8 @@ The proposed workflow for repositories that access private NuGet feeds, such as 1. Ensure that the dotnet CLI tools are installed. 1. Execute the command `dotnet nuget plugin install Microsoft.CredentialProvider`. +1. Run `dotnet restore --interactive` with a private endpoint. It should 'just work', meaning the credential providers installed in step 2 are used during credential acquisition and are used to authenticate against private endpoints. + The `tool path` global tool will be installed in the default NuGet plugins location, as mentioned below. | Operating System | Installation Path | @@ -90,8 +92,6 @@ The `tool path` global tool will be installed in the default NuGet plugins locat | Windows | %UserProfile%/.nuget/plugins/any | | Linux/Mac | $HOME/.nuget/plugins/any | -1. Run `dotnet restore --interactive` with a private endpoint. It should 'just work', meaning the credential providers installed in step 2 are used during credential acquisition and are used to authenticate against private endpoints. - I proposed using a `tool path` .NET tool because, by default, .NET tools are considered [global](https://learn.microsoft.com/dotnet/core/tools/global-tools). This means they are installed in a default directory, such as `%UserProfile%/.dotnet/tools` on Windows, which is then added to the PATH environment variable. From 4975f927a8907dc8cfcce9d9057d70ad49a6eb55 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Sun, 17 Mar 2024 21:54:19 -0700 Subject: [PATCH 29/69] Update NuGet plugin installation process for cross-platform support --- ...get-authentication-plugins-dotnet-tools.md | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 8a306036e..b746a9d7b 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -105,6 +105,9 @@ This makes it easier to identify and invoke the appropriate tool for NuGet opera - One version of a tool is used for all directories on the machine. The `tool path` option aligns well with the design of NuGet plugins architecture, making it the recommended approach for installing and executing NuGet plugins. +This approach is similar to the alternative design that [Andy Zivkovic](https://github.com/zivkan) kindly proposed in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). +The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. + ### Security considerations The [.NET SDK docs](https://learn.microsoft.com/dotnet/core/tools/global-tools#check-the-author-and-statistics) clearly state, `.NET tools run in full trust. Don't install a .NET tool unless you trust the author`. @@ -187,7 +190,7 @@ drwxr-xr-x 5 {user} 4096 Feb 10 08:21 .store ## Drawbacks -- The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, is to easily search for NuGet plugins and install them without needing to know the destination location. +- The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, is to easily search for NuGet plugins and install them as global tool. However, the current proposal suggests installing the plugin as a tool-path .NET tool. As mentioned in the technical explanation section, customers can opt to install NuGet plugins as a global tool instead of a tool-path tool. To do this, they need to set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. @@ -198,25 +201,6 @@ If the `dotnet tool` started writing non-executable files into the directory, it ## Rationale and alternatives -### NuGet commands to install credential providers - -[Andy Zivkovic](https://github.com/zivkan) kindly proposed an alternative design in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). -The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. -Here are the advantages and disadvantages of this approach: - -**Advantages:** - -- Improved discoverability, as `dotnet tool search` will list packages that are not NuGet plugins. -- Doesn't require customers to memorize or lookup the NuGet plugin directory location in order to pass it to all `dotnet tool` commands via the `--tool-path` argument, for install, uninstall, and update. - -**Disadvantages:** - -- The NuGet Client team would be required to maintain all the .NET Commands for installing, updating, and uninstalling the plugins. However, these tasks are already handled by the existing commands in the .NET SDK. -- This approach would still require the extraction of plugins into either a `netfx` or a `netcore` folder. -As a result, package authors would need to maintain plugins for both of these target frameworks. -However, NuGet plugins are executables, and the .NET SDK provides a convenient way for authors to publish an executable that can run on all platforms via .NET Tools. -This eliminates the need for a framework-specific approach. - ### Specify the the authentication plugin in NuGet.Config file To simplify the authentication process with private NuGet feeds, customers can specify the authentication plugins installed as .NET Tools in the `packagesourceCredentials` section of the NuGet.Config file. @@ -244,7 +228,7 @@ Here is an example of how to configure the NuGet.Config file: **Advantages:** - This approach explicitly configures the intent to use the .NET Tool as a plugin for authentication in the NuGet.Config file itself. -- Customers can install the plugins as global .NET Tools, eliminating the need to specify a custom location based on the platform. +- Customers can install the plugins as global .NET Tools. **Disadvantages:** @@ -292,4 +276,4 @@ However, due to limitations in the NuGet Client tooling, they've had to maintain In addition to that, if we ever plan to provide users with an option to manage their plugins per repository instead of loading all the available plugins, we can consider using [.NET Local tools](https://learn.microsoft.com/dotnet/core/tools/local-tools-how-to-use). - The advantage of local tools is that the .NET CLI uses manifest files to keep track of tools that are installed locally to a directory. When the manifest file is saved in the root directory of a source code repository, a contributor can clone the repository and invoke a single .NET CLI command such as [`dotnet tool restore`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-restore) to install all of the tools listed in the manifest file. -- Having NuGet plugins under the repository folder eliminates the need for NuGet to load plugins from the user profile. +- Having NuGet plugins under the repository folder eliminates the need for NuGet to load plugins from the user profile. \ No newline at end of file From 92947d7a9b1049c63df36e644d7dc3d8b7ad1382 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Sun, 17 Mar 2024 21:58:57 -0700 Subject: [PATCH 30/69] Added issue with NuGet credential providers interfering with workload installation on Mac and Linux to unresolved questions --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index b746a9d7b..09cf0728b 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -266,7 +266,7 @@ However, due to limitations in the NuGet Client tooling, they've had to maintain ## Unresolved Questions -- None as of now. +- The issue with workload installation on Mac and Linux and NuGet credential providers (among other things) is described here: https://github.com/dotnet/sdk/issues/35912#issuecomment-1759774310 Basically, when .NET SDK workload commands are run under sudo, we change the HOME directory so that the .NET SDK doesn’t end up writing files owned by root to the user’s normal HOME directory, which would cause problems when running normal commands not under sudo. This interferes with NuGet credential providers ## Future Possibilities From 83008397312f9d9341c609f2d83314941420585a Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:01:24 -0700 Subject: [PATCH 31/69] Fix issue with NuGet credential providers interfering with workload installation on Mac and Linux --- .../support-nuget-authentication-plugins-dotnet-tools.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 09cf0728b..47d5ce01d 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -266,7 +266,10 @@ However, due to limitations in the NuGet Client tooling, they've had to maintain ## Unresolved Questions -- The issue with workload installation on Mac and Linux and NuGet credential providers (among other things) is described here: https://github.com/dotnet/sdk/issues/35912#issuecomment-1759774310 Basically, when .NET SDK workload commands are run under sudo, we change the HOME directory so that the .NET SDK doesn’t end up writing files owned by root to the user’s normal HOME directory, which would cause problems when running normal commands not under sudo. This interferes with NuGet credential providers +- The issue concerning workload installation on Mac and Linux, as well as NuGet credential providers, is discussed in this thread: https://github.com/dotnet/sdk/issues/35912#issuecomment-1759774310. +Essentially, when .NET SDK workload commands are executed under sudo, the HOME directory path is altered. +This prevents the .NET SDK from writing files owned by root to the user's regular HOME directory, which could lead to issues when running standard commands not under sudo. +This situation also interferes with NuGet credential providers. ## Future Possibilities From fc0290a653a1645a6a6f7e3ce07fff0d7e70cb92 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:13:10 -0700 Subject: [PATCH 32/69] Update NuGet plugin installation process for cross-platform support and add dotnet nuget plugin commands --- .../support-nuget-authentication-plugins-dotnet-tools.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 47d5ce01d..323e11821 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -103,7 +103,12 @@ This makes it easier to identify and invoke the appropriate tool for NuGet opera - The binaries are installed in a location that we specify while installing the tool. - We can invoke the tool from the installation directory by providing the directory with the command name or by adding the directory to the PATH environment variable. - One version of a tool is used for all directories on the machine. -The `tool path` option aligns well with the design of NuGet plugins architecture, making it the recommended approach for installing and executing NuGet plugins. +The `tool path` option aligns well with the NuGet plugins architecture design, and hence, it is the recommended approach for installing and executing NuGet plugins. + +We should also add `dotnet nuget plugin install/uninstall/search` commands to the .NET SDK. +These commands will serve as a wrapper for the `dotnet tool install/uninstall` commands. +The benefit of installing plugins through NuGet commands is that it removes the necessity for the user to specify the NuGet plugin path, making the process platform-independent and more user-friendly. +I think we need a separate spec for `dotnet nuget plugin install/uninstall/search` commands. This approach is similar to the alternative design that [Andy Zivkovic](https://github.com/zivkan) kindly proposed in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. @@ -116,7 +121,7 @@ This is an important consideration for plugin customers when installing NuGet pl ### Technical explanation Currently, plugins are discovered through a convention-based directory structure, such as the `%userprofile%/.nuget/plugins` folder on Windows. -CI/CD scenarios and power users can use environment variables to override this behavior. +For CI/CD scenarios, and for power users, environment variables can be used to override this behavior. Note that only absolute paths are allowed when using these environment variables. - `NUGET_NETFX_PLUGIN_PATHS`: Defines the plugins used by the .NET Framework-based tooling (NuGet.exe/MSBuild.exe/Visual Studio). This takes precedence over `NUGET_PLUGIN_PATHS`. From 0addbd4a1e23512611e1afd79b70365ac3ff87ee Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Mon, 18 Mar 2024 06:57:19 -0700 Subject: [PATCH 33/69] Update NuGet plugin installation process for cross-platform support and add dotnet nuget plugin commands --- .../support-nuget-authentication-plugins-dotnet-tools.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 323e11821..f6bee22a1 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -105,10 +105,12 @@ This makes it easier to identify and invoke the appropriate tool for NuGet opera - One version of a tool is used for all directories on the machine. The `tool path` option aligns well with the NuGet plugins architecture design, and hence, it is the recommended approach for installing and executing NuGet plugins. -We should also add `dotnet nuget plugin install/uninstall/search` commands to the .NET SDK. +We should also add `dotnet nuget plugin install/uninstall` commands to the .NET SDK. These commands will serve as a wrapper for the `dotnet tool install/uninstall` commands. -The benefit of installing plugins through NuGet commands is that it removes the necessity for the user to specify the NuGet plugin path, making the process platform-independent and more user-friendly. -I think we need a separate spec for `dotnet nuget plugin install/uninstall/search` commands. +The advantage of installing plugins through NuGet commands is that it eliminates the need for users to specify the NuGet plugin path. This makes the process platform-independent and more user-friendly. +We should also introduce a `dotnet nuget credentialprovider search` command. +This will allow customers to search for available Credential Providers that are published as .NET tools. +I believe we need a separate specification for `dotnet nuget plugin install/uninstall/search` commands to fully understand all the options and the functional/technical details. This approach is similar to the alternative design that [Andy Zivkovic](https://github.com/zivkan) kindly proposed in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. From 54b28ade9b65a4e793079909f35f907cea879d24 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Mon, 18 Mar 2024 07:02:40 -0700 Subject: [PATCH 34/69] Update NuGet plugin installation process for cross-platform support and add dotnet nuget plugin commands --- ...get-authentication-plugins-dotnet-tools.md | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index f6bee22a1..c49fa6e2d 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -105,6 +105,37 @@ This makes it easier to identify and invoke the appropriate tool for NuGet opera - One version of a tool is used for all directories on the machine. The `tool path` option aligns well with the NuGet plugins architecture design, and hence, it is the recommended approach for installing and executing NuGet plugins. +The reasons why `.NET tools` were chosen as the deployment mechanism are mentioned below: + +- NuGet plugins are console applications. A `.NET tool` is a special NuGet package that contains a console application, which presents a natural fit. + +- The .NET SDK has already simplified the process for customers to develop a tool. +All the plugin authors have to do is set the following properties and execute `dotnet pack` to generate the package: + +```xml +true +botsay +``` + +- Leveraging `.NET Tools` provides a standard experience for customers to share console applications, with NuGet plugins being one such use case. +If there is an existing mechanism that works well, is familiar to customers, and fits our use case, I question the benefit of developing a new layout specifically for NuGet plugins. + +- Another advantage of this established `.NET tools` approach is that plugin authors won't have to maintain separate code paths for .NET Framework and .NET Core runtimes. +All these complexities are handled by the .NET SDK by providing a native shim upon the installation of the .NET tool. + +Currently, at least to my understanding, the generated `.nupkg` will include a file named `DotnetToolSettings.xml` with additional metadata such as the command name, entry point, and runner. + +For example, the [dotnetsay tool](https://nuget.info/packages/dotnetsay/2.1.7) includes the following content: + +```xml + + + + + + +``` + We should also add `dotnet nuget plugin install/uninstall` commands to the .NET SDK. These commands will serve as a wrapper for the `dotnet tool install/uninstall` commands. The advantage of installing plugins through NuGet commands is that it eliminates the need for users to specify the NuGet plugin path. This makes the process platform-independent and more user-friendly. From f78f1b2a5cf6ccbd5161615704d1a73473f5006a Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Mon, 18 Mar 2024 07:04:22 -0700 Subject: [PATCH 35/69] add update sub-command to dotnet nuget plugin commands --- .../support-nuget-authentication-plugins-dotnet-tools.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index c49fa6e2d..93e9c6a59 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -136,12 +136,12 @@ For example, the [dotnetsay tool](https://nuget.info/packages/dotnetsay/2.1.7) i ``` -We should also add `dotnet nuget plugin install/uninstall` commands to the .NET SDK. -These commands will serve as a wrapper for the `dotnet tool install/uninstall` commands. +We should also add `dotnet nuget plugin install/uninstall/update` commands to the .NET SDK. +These commands will serve as a wrapper for the `dotnet tool install/uninstall/update` commands. The advantage of installing plugins through NuGet commands is that it eliminates the need for users to specify the NuGet plugin path. This makes the process platform-independent and more user-friendly. We should also introduce a `dotnet nuget credentialprovider search` command. This will allow customers to search for available Credential Providers that are published as .NET tools. -I believe we need a separate specification for `dotnet nuget plugin install/uninstall/search` commands to fully understand all the options and the functional/technical details. +I believe we need a separate specification for `dotnet nuget plugin install/uninstall/update/search` commands to fully understand all the options and the functional/technical details. This approach is similar to the alternative design that [Andy Zivkovic](https://github.com/zivkan) kindly proposed in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. From 9eba3cbfd9c13f07168858ca93e4788eb0a51836 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Mon, 18 Mar 2024 07:14:02 -0700 Subject: [PATCH 36/69] added roadmap section --- ...pport-nuget-authentication-plugins-dotnet-tools.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 93e9c6a59..3bae28c39 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -226,6 +226,17 @@ drwxr-xr-x 5 {user} 4096 Feb 10 08:21 .store -rwxr-xr-x 1 {user} 75632 Feb 10 08:20 dotnetsay ``` +## Roadmap + +In terms of the implementation roadmap, I propose the following stages: + +1. Initially, plugin authors will publish NuGet plugins as .NET Tools. Consumers will install NuGet plugins using `dotnet tool` sub-commands. +They will specify the plugin's path based on their operating system. We will make the necessary code changes on the NuGet side to enable running the plugins installed as `.NET tools`. + +2. Subsequently, we will introduce a `dotnet nuget plugin` subcommand. This will allow users to `install, update, uninstall, and search` for plugins. +However, we will still rely on the `dotnet tool` command under the hood, as mentioned above. +Please note that these command names are subject to change based on feedback to the specification we are going to create in the near future. + ## Drawbacks - The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, is to easily search for NuGet plugins and install them as global tool. From 3196a90d7699e2a6459d0e467625e4d8bc168b17 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Mon, 18 Mar 2024 12:03:33 -0700 Subject: [PATCH 37/69] Update NuGet plugin installation process and add dotnet nuget plugin commands --- ...uget-authentication-plugins-dotnet-tools.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 3bae28c39..da82c5871 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -136,12 +136,20 @@ For example, the [dotnetsay tool](https://nuget.info/packages/dotnetsay/2.1.7) i ``` -We should also add `dotnet nuget plugin install/uninstall/update` commands to the .NET SDK. -These commands will serve as a wrapper for the `dotnet tool install/uninstall/update` commands. -The advantage of installing plugins through NuGet commands is that it eliminates the need for users to specify the NuGet plugin path. This makes the process platform-independent and more user-friendly. +We should consider adding `dotnet nuget plugin install/uninstall/update` commands to the .NET SDK as wrappers for the `dotnet tool install/uninstall/update` commands. +This would simplify the installation process by eliminating the need for users to specify the NuGet plugin path, making the process more user-friendly and platform-independent. + We should also introduce a `dotnet nuget credentialprovider search` command. -This will allow customers to search for available Credential Providers that are published as .NET tools. -I believe we need a separate specification for `dotnet nuget plugin install/uninstall/update/search` commands to fully understand all the options and the functional/technical details. +To enable this, plugin authors would simply need to add `CredentialProvider` to their `.csproj` file. + +Special thanks to [Nikolche Kolev](https://github.com/nkolev92) for suggesting a verification method. +This involved creating a .NET tool that includes both `DotnetTool` and `CredentialProvider` as `PackageTypes` in the `.nuspec` file. +This file is part of the nupkg generated by executing the dotnet pack command. +The installation of this tool was successful with the .NET 8 SDK. However, it failed with the .NET 7 SDK due to the `.nuspec` in the nupkg containing multiple package types. + +Given that this issue has been resolved in the .NET 8 SDK, and considering our plans to add `dotnet nuget plugin install/uninstall/update/search` commands and support for NuGet plugins deployed via .NET tools in the latest version, I believe we are on the right track. + +The `dotnet nuget credentialprovider search` command would allow customers to search for available Credential Providers that are published as .NET tools. However, I believe we need a separate specification for `dotnet nuget plugin install/uninstall/update/search` commands to fully understand all the options and the functional/technical details. This approach is similar to the alternative design that [Andy Zivkovic](https://github.com/zivkan) kindly proposed in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. From db09e245e4f029e585e66dd386694f6ad1b8cf8e Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Mon, 18 Mar 2024 15:30:46 -0700 Subject: [PATCH 38/69] Fix NuGet credential provider installation issue and propose workaround --- ...port-nuget-authentication-plugins-dotnet-tools.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index da82c5871..d02f31d7d 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -82,7 +82,7 @@ It also simplifies the installation process by removing the necessity for plugin The proposed workflow for repositories that access private NuGet feeds, such as Azure DevOps, would be: 1. Ensure that the dotnet CLI tools are installed. -1. Execute the command `dotnet nuget plugin install Microsoft.CredentialProvider`. +1. Execute the command `dotnet nuget plugin install Microsoft.CredentialProvider`. 1. Run `dotnet restore --interactive` with a private endpoint. It should 'just work', meaning the credential providers installed in step 2 are used during credential acquisition and are used to authenticate against private endpoints. The `tool path` global tool will be installed in the default NuGet plugins location, as mentioned below. @@ -323,10 +323,10 @@ However, due to limitations in the NuGet Client tooling, they've had to maintain ## Unresolved Questions -- The issue concerning workload installation on Mac and Linux, as well as NuGet credential providers, is discussed in this thread: https://github.com/dotnet/sdk/issues/35912#issuecomment-1759774310. -Essentially, when .NET SDK workload commands are executed under sudo, the HOME directory path is altered. -This prevents the .NET SDK from writing files owned by root to the user's regular HOME directory, which could lead to issues when running standard commands not under sudo. -This situation also interferes with NuGet credential providers. +- The issue regarding workload installation on Mac and Linux, as well as NuGet credential providers, is discussed in [GitHub Issue #35912](https://github.com/dotnet/sdk/issues/35912#issuecomment-1759774310). +When executing .NET SDK workload commands under sudo, the HOME directory path is modified, preventing the .NET SDK from writing files owned by root to the user's regular HOME directory. +This alteration can cause problems when running standard commands without sudo and also interferes with NuGet credential providers. +We have proposed a workaround for this issue in [this comment](https://github.com/dotnet/sdk/issues/35912#issuecomment-2004522180). ## Future Possibilities @@ -336,4 +336,4 @@ This situation also interferes with NuGet credential providers. In addition to that, if we ever plan to provide users with an option to manage their plugins per repository instead of loading all the available plugins, we can consider using [.NET Local tools](https://learn.microsoft.com/dotnet/core/tools/local-tools-how-to-use). - The advantage of local tools is that the .NET CLI uses manifest files to keep track of tools that are installed locally to a directory. When the manifest file is saved in the root directory of a source code repository, a contributor can clone the repository and invoke a single .NET CLI command such as [`dotnet tool restore`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-restore) to install all of the tools listed in the manifest file. -- Having NuGet plugins under the repository folder eliminates the need for NuGet to load plugins from the user profile. \ No newline at end of file +- Having NuGet plugins under the repository folder eliminates the need for NuGet to load plugins from the user profile. From 41f08d42ae84688763cbe69efb2d76ad5d4a97fe Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Mon, 18 Mar 2024 15:31:58 -0700 Subject: [PATCH 39/69] Proposed workaround for NuGet authentication issue --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index d02f31d7d..28f28910a 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -326,7 +326,7 @@ However, due to limitations in the NuGet Client tooling, they've had to maintain - The issue regarding workload installation on Mac and Linux, as well as NuGet credential providers, is discussed in [GitHub Issue #35912](https://github.com/dotnet/sdk/issues/35912#issuecomment-1759774310). When executing .NET SDK workload commands under sudo, the HOME directory path is modified, preventing the .NET SDK from writing files owned by root to the user's regular HOME directory. This alteration can cause problems when running standard commands without sudo and also interferes with NuGet credential providers. -We have proposed a workaround for this issue in [this comment](https://github.com/dotnet/sdk/issues/35912#issuecomment-2004522180). +We have proposed a [workaround](https://github.com/dotnet/sdk/issues/35912#issuecomment-2004522180) for this issue. ## Future Possibilities From 14ed7027932703768e587f1b8a4b29f8e54bbc92 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Mon, 18 Mar 2024 16:16:29 -0700 Subject: [PATCH 40/69] referred about similarities with dotnet workload command. --- .../support-nuget-authentication-plugins-dotnet-tools.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 28f28910a..176f51333 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -151,6 +151,11 @@ Given that this issue has been resolved in the .NET 8 SDK, and considering our p The `dotnet nuget credentialprovider search` command would allow customers to search for available Credential Providers that are published as .NET tools. However, I believe we need a separate specification for `dotnet nuget plugin install/uninstall/update/search` commands to fully understand all the options and the functional/technical details. +The `dotnet workload` command is separate and has its own set of sub-commands, including `install`, `uninstall`, and `list`. +These sub-commands are wrappers for the corresponding `dotnet tool` sub-commands. +Workloads are installed in a different location than .NET tools, which makes it easier for them to be discovered at runtime, addressing a problem that NuGet also faces. +However, NuGet plugins and .NET tools share the similarity of being console applications. + This approach is similar to the alternative design that [Andy Zivkovic](https://github.com/zivkan) kindly proposed in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. From d118dbe7f47017774dcb763b97d2dff2c296dd8f Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Tue, 19 Mar 2024 11:56:51 -0700 Subject: [PATCH 41/69] Update NuGet credential provider deployment process for .NET tools --- .../2024/support-credentialproviders-dotnet-tools.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/accepted/2024/support-credentialproviders-dotnet-tools.md b/accepted/2024/support-credentialproviders-dotnet-tools.md index 4eeea8a63..91a72d8c7 100644 --- a/accepted/2024/support-credentialproviders-dotnet-tools.md +++ b/accepted/2024/support-credentialproviders-dotnet-tools.md @@ -1,17 +1,18 @@ # ***Support for credential providers deployed via .NET tools*** -- Author Name: https://github.com/kartheekp-ms -- GitHub Issue: https://github.com/NuGet/Home/issues/12567 +- Author Name: +- GitHub Issue: ## Summary -## Motivation +## Motivation -Currently, credential providers support `*.exe` for .NET Framework and `*.dll` for .NET Core. They are typically divided into two folders under the NuGet credential provider base path. For .NET Framework, the providers are discovered by searching `credentialprovider*.exe` and executing that program. For .NET Core, they are discovered by searching `credentialprovider*.dll` and executing `dotnet .dll`. Before .NET Core 2.0, this division was necessary because .NET Core only supported platform-agnostic DLLs. However, with the latest versions of .NET, this division is no longer necessary, but it does require supporting and deploying multiple versions. This is further reinforced by the two plugin path environment variables `NUGET_NETFX_PLUGIN_PATHS` and `NUGET_NETCORE_PLUGIN_PATHS`, which need to be set for each framework version. +Currently, credential providers support `*.exe` for .NET Framework and `*.dll` for .NET Core. They are typically divided into two folders under the NuGet credential provider base path. For .NET Framework, the providers are discovered by searching `credentialprovider*.exe` and executing that program. For .NET Core, they are discovered by searching `credentialprovider*.dll` and executing `dotnet .dll`. Before .NET Core 2.0, this division was necessary because .NET Core only supported platform-agnostic DLLs. However, with the latest versions of .NET, this division is no longer necessary, but it does require supporting and deploying multiple versions. This is further reinforced by the two plugin path environment variables `NUGET_NETFX_PLUGIN_PATHS` and `NUGET_NETCORE_PLUGIN_PATHS`, which need to be set for each framework version. A deployment solution for dotnet is [.NET tools](https://learn.microsoft.com/dotnet/core/tools), which provide a seamless .NET installation and management experience for NuGet packages. Using .NET tools as a deployment mechanism has been a recurring request from users of the .NET ecosystem who need to authenticate with private repositories. The ideal workflow for repositoroes that access private NuGet feeds like Azure DevOps would be: -1. Customers have the dotnet CLI tools installed. + +1. Customers have the .NET SDK installed. 2. They run `dotnet tool install -g Microsoft.CredentialProviders`. 3. They run `dotnet restore` with a private endpoint, and it 'just works' (i.e., the credential providers from step 2 are used during credential acquisition and are used to authenticate against private endpoints). From a065b76f1061699dd5828e6e4fed5d967b657f9c Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Tue, 19 Mar 2024 12:00:37 -0700 Subject: [PATCH 42/69] remove unnecessary file --- ...upport-credentialproviders-dotnet-tools.md | 57 ------------------- 1 file changed, 57 deletions(-) delete mode 100644 accepted/2024/support-credentialproviders-dotnet-tools.md diff --git a/accepted/2024/support-credentialproviders-dotnet-tools.md b/accepted/2024/support-credentialproviders-dotnet-tools.md deleted file mode 100644 index 91a72d8c7..000000000 --- a/accepted/2024/support-credentialproviders-dotnet-tools.md +++ /dev/null @@ -1,57 +0,0 @@ -# ***Support for credential providers deployed via .NET tools*** - - -- Author Name: -- GitHub Issue: - -## Summary - -## Motivation - -Currently, credential providers support `*.exe` for .NET Framework and `*.dll` for .NET Core. They are typically divided into two folders under the NuGet credential provider base path. For .NET Framework, the providers are discovered by searching `credentialprovider*.exe` and executing that program. For .NET Core, they are discovered by searching `credentialprovider*.dll` and executing `dotnet .dll`. Before .NET Core 2.0, this division was necessary because .NET Core only supported platform-agnostic DLLs. However, with the latest versions of .NET, this division is no longer necessary, but it does require supporting and deploying multiple versions. This is further reinforced by the two plugin path environment variables `NUGET_NETFX_PLUGIN_PATHS` and `NUGET_NETCORE_PLUGIN_PATHS`, which need to be set for each framework version. - -A deployment solution for dotnet is [.NET tools](https://learn.microsoft.com/dotnet/core/tools), which provide a seamless .NET installation and management experience for NuGet packages. Using .NET tools as a deployment mechanism has been a recurring request from users of the .NET ecosystem who need to authenticate with private repositories. The ideal workflow for repositoroes that access private NuGet feeds like Azure DevOps would be: - -1. Customers have the .NET SDK installed. -2. They run `dotnet tool install -g Microsoft.CredentialProviders`. -3. They run `dotnet restore` with a private endpoint, and it 'just works' (i.e., the credential providers from step 2 are used during credential acquisition and are used to authenticate against private endpoints). - -This almost works today, but only for the Windows .NET Framework. The goal is to support this cross-platform for all supported .NET runtimes. - -## Explanation - -### Functional explanation - - - - -### Technical explanation - - - -## Drawbacks - - - -## Rationale and alternatives - - - - - -## Prior Art - - - - - - -## Unresolved Questions - - - - - -## Future Possibilities - - \ No newline at end of file From c6127de134dc92218c3d7d5624e1ed2dbbbcc900 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Tue, 19 Mar 2024 12:36:25 -0700 Subject: [PATCH 43/69] arranged the spec for a better flow --- ...get-authentication-plugins-dotnet-tools.md | 80 +++++++++++-------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 176f51333..bf59aee2a 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -75,36 +75,6 @@ The use of .NET tools as a deployment mechanism has been a recurring request fro Currently, this solution works for the Windows .NET Framework. However, the goal is to extend this support cross-platform for all supported .NET runtimes. -By implementing this specification, we offer plugin authors the option to use .NET tools for plugin deployment. -These plugins will be installed as a `tool path` global tool. This eliminates the need to maintain separate versions for `.NET Framework` and `.NET Core`. -It also simplifies the installation process by removing the necessity for plugin authors to create subcommands like `codeartifact-creds install/uninstall`. - -The proposed workflow for repositories that access private NuGet feeds, such as Azure DevOps, would be: - -1. Ensure that the dotnet CLI tools are installed. -1. Execute the command `dotnet nuget plugin install Microsoft.CredentialProvider`. -1. Run `dotnet restore --interactive` with a private endpoint. It should 'just work', meaning the credential providers installed in step 2 are used during credential acquisition and are used to authenticate against private endpoints. - -The `tool path` global tool will be installed in the default NuGet plugins location, as mentioned below. - -| Operating System | Installation Path | -| ---------------- | ----------------- | -| Windows | %UserProfile%/.nuget/plugins/any | -| Linux/Mac | $HOME/.nuget/plugins/any | - -I proposed using a `tool path` .NET tool because, by default, .NET tools are considered [global](https://learn.microsoft.com/dotnet/core/tools/global-tools). -This means they are installed in a default directory, such as `%UserProfile%/.dotnet/tools` on Windows, which is then added to the PATH environment variable. - -- Global tools can be invoked from any directory on the machine without specifying their location. -- One version of a tool is used for all directories on the machine. -However, NuGet cannot easily determine which tool is a NuGet plugin. -On the other hand, the `tool path` option allows us to install .NET tools as global tools, but with the binaries located in a specified location. -This makes it easier to identify and invoke the appropriate tool for NuGet operations. -- The binaries are installed in a location that we specify while installing the tool. -- We can invoke the tool from the installation directory by providing the directory with the command name or by adding the directory to the PATH environment variable. -- One version of a tool is used for all directories on the machine. -The `tool path` option aligns well with the NuGet plugins architecture design, and hence, it is the recommended approach for installing and executing NuGet plugins. - The reasons why `.NET tools` were chosen as the deployment mechanism are mentioned below: - NuGet plugins are console applications. A `.NET tool` is a special NuGet package that contains a console application, which presents a natural fit. @@ -136,6 +106,36 @@ For example, the [dotnetsay tool](https://nuget.info/packages/dotnetsay/2.1.7) i ``` +By implementing this specification, we offer plugin authors the option to use .NET tools for plugin deployment. +On the consumer side, these plugins will be installed as a `tool path` global tool. This eliminates the need to maintain separate versions for `.NET Framework` and `.NET Core`. +It also simplifies the installation process by removing the necessity for plugin authors to create subcommands like `codeartifact-creds install/uninstall`. + +The proposed workflow for repositories that access private NuGet feeds, such as Azure DevOps, would be: + +1. Ensure that the .NET SDK is installed. +1. Execute the command `dotnet nuget plugin install Microsoft.CredentialProvider`. +1. Run `dotnet restore --interactive` with a private endpoint. It should 'just work', meaning the credential providers installed in step 2 are used during credential acquisition and are used to authenticate against private endpoints. + +The `dotnet nuget plugin install` command installs the NuGet plugin as `tool path` global .NET tool in the default NuGet plugins location, as mentioned below. + +| Operating System | Installation Path | +| ---------------- | ----------------- | +| Windows | %UserProfile%/.nuget/plugins/any | +| Linux/Mac | $HOME/.nuget/plugins/any | + +I proposed using a `tool path` .NET tool because, by default, .NET tools are considered [global](https://learn.microsoft.com/dotnet/core/tools/global-tools). +This means they are installed in a default directory, such as `%UserProfile%/.dotnet/tools` on Windows, which is then added to the PATH environment variable. + +- Global tools can be invoked from any directory on the machine without specifying their location. +- One version of a tool is used for all directories on the machine. +However, NuGet cannot easily determine which tool is a NuGet plugin. +On the other hand, the `tool path` option allows us to install .NET tools as global tools, but with the binaries located in a specified location. +This makes it easier to identify and invoke the appropriate tool for NuGet operations. +- The binaries are installed in a location that we specify while installing the tool. +- We can invoke the tool from the installation directory by providing the directory with the command name or by adding the directory to the PATH environment variable. +- One version of a tool is used for all directories on the machine. +The `tool path` option aligns well with the NuGet plugins architecture design, and hence, it is the recommended approach for installing and executing NuGet plugins. + We should consider adding `dotnet nuget plugin install/uninstall/update` commands to the .NET SDK as wrappers for the `dotnet tool install/uninstall/update` commands. This would simplify the installation process by eliminating the need for users to specify the NuGet plugin path, making the process more user-friendly and platform-independent. @@ -143,9 +143,9 @@ We should also introduce a `dotnet nuget credentialprovider search` command. To enable this, plugin authors would simply need to add `CredentialProvider` to their `.csproj` file. Special thanks to [Nikolche Kolev](https://github.com/nkolev92) for suggesting a verification method. -This involved creating a .NET tool that includes both `DotnetTool` and `CredentialProvider` as `PackageTypes` in the `.nuspec` file. -This file is part of the nupkg generated by executing the dotnet pack command. -The installation of this tool was successful with the .NET 8 SDK. However, it failed with the .NET 7 SDK due to the `.nuspec` in the nupkg containing multiple package types. +This involved creating a .NET tool and then updating the `PackageTypes` manually in the `.nuspec` file to inlclude both `DotnetTool` and `CredentialProvider`. +This file is part of the nupkg generated by executing the `dotnet pack` command. +The installation of this tool with multiple package types was successful with the .NET 8 SDK. However, it failed with the .NET 7 SDK due to the `.nuspec` in the nupkg containing multiple package types. Given that this issue has been resolved in the .NET 8 SDK, and considering our plans to add `dotnet nuget plugin install/uninstall/update/search` commands and support for NuGet plugins deployed via .NET tools in the latest version, I believe we are on the right track. @@ -261,6 +261,8 @@ This variable should point to the location of the .NET Tool executable, which th - There is some risk of the `dotnet tool` introducing breaking changes that could negatively impact NuGet. If the `dotnet tool` started writing non-executable files into the directory, it would affect how NuGet discovers and runs plugins at runtime. +- NuGet Client plugin code will only support plugins developed in .NET. See the `Future Possibilities` section for more details. + ## Rationale and alternatives ### Specify the the authentication plugin in NuGet.Config file @@ -320,12 +322,18 @@ However, given the limited number of plugin implementations currently available, ## Prior Art -The AWS team has developed a [.NET tool](https://docs.aws.amazon.com/codeartifact/latest/ug/nuget-cli.html#nuget-configure-cli) to simplify authentication with their private NuGet feeds. +- The AWS team has developed a [.NET tool](https://docs.aws.amazon.com/codeartifact/latest/ug/nuget-cli.html#nuget-configure-cli) to simplify authentication with their private NuGet feeds. In their current implementation, they've added a subcommand, `codeartifact-creds install`. This command copies the credential provider to the NuGet plugins folder. They also provide an `uninstall` subcommand to remove the files from the NuGet plugin folders. However, due to limitations in the NuGet Client tooling, they've had to maintain plugins for both .NET Framework and .NET Core tooling. Additionally, they've had to provide commands to install and uninstall the NuGet plugins. +- The `dotnet workload` command is separate and has its own set of sub-commands, including `install`, `uninstall`, and `list`. +These sub-commands are wrappers for the corresponding `dotnet tool` sub-commands. +Workloads are installed in a different location than .NET tools, which makes it easier for them to be discovered at runtime, addressing a problem that NuGet also faces. +However, NuGet plugins and .NET tools share the similarity of being console applications. +This prior art will be helpful for us as we consider the implementation of the `dotnet nuget plugin install/uninstall/update/search` commands. + ## Unresolved Questions - The issue regarding workload installation on Mac and Linux, as well as NuGet credential providers, is discussed in [GitHub Issue #35912](https://github.com/dotnet/sdk/issues/35912#issuecomment-1759774310). @@ -342,3 +350,7 @@ In addition to that, if we ever plan to provide users with an option to manage t - The advantage of local tools is that the .NET CLI uses manifest files to keep track of tools that are installed locally to a directory. When the manifest file is saved in the root directory of a source code repository, a contributor can clone the repository and invoke a single .NET CLI command such as [`dotnet tool restore`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-restore) to install all of the tools listed in the manifest file. - Having NuGet plugins under the repository folder eliminates the need for NuGet to load plugins from the user profile. + +### Support for non-.NET NuGet plugins + +- NuGet Client plugin code must be updated to utilize a standard RPC mechanism, such as StreamJsonRpc, to support NuGet plugins developed in other languages. Until that time, the NuGet Client plugin code will only support plugins developed in .NET. From 76bb1f457c24c83c43b34761a87efbc353eaf907 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Tue, 19 Mar 2024 16:25:24 -0700 Subject: [PATCH 44/69] Update NuGet Client plugin code to support non-.NET plugins --- .../support-nuget-authentication-plugins-dotnet-tools.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index bf59aee2a..042924991 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -261,7 +261,7 @@ This variable should point to the location of the .NET Tool executable, which th - There is some risk of the `dotnet tool` introducing breaking changes that could negatively impact NuGet. If the `dotnet tool` started writing non-executable files into the directory, it would affect how NuGet discovers and runs plugins at runtime. -- NuGet Client plugin code will only support plugins developed in .NET. See the `Future Possibilities` section for more details. +- Currently, NuGet Client plugin code will only support plugins developed in .NET. See the `Future Possibilities` section for more details. ## Rationale and alternatives @@ -353,4 +353,9 @@ When the manifest file is saved in the root directory of a source code repositor ### Support for non-.NET NuGet plugins -- NuGet Client plugin code must be updated to utilize a standard RPC mechanism, such as StreamJsonRpc, to support NuGet plugins developed in other languages. Until that time, the NuGet Client plugin code will only support plugins developed in .NET. +- The NuGet Client plugin code needs to be updated to utilize a standard RPC mechanism, such as StreamJsonRpc, in order to support NuGet plugins developed in languages other than .NET. +Currently, the NuGet Client plugin code only supports plugins developed in .NET. +The IPC (Inter-Process Communication) used by NuGet is custom-made, and it would be beneficial for plugin implementers if it were based on industry standards. +This would allow plugin authors to reuse existing implementations for other languages. +While there is no technical barrier preventing the implementation of a client in a non-.NET language, there is an additional cost associated with reimplementing the plugin protocol. +This cost serves as a deterrent for writing non-.NET plugins. From dfcba32cc8e6451bf9af19fda8c2125dc5f33b8d Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Tue, 19 Mar 2024 16:29:29 -0700 Subject: [PATCH 45/69] Update NuGet plugin support for non-.NET languages --- .../support-nuget-authentication-plugins-dotnet-tools.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 042924991..8a5ca6f62 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -261,7 +261,9 @@ This variable should point to the location of the .NET Tool executable, which th - There is some risk of the `dotnet tool` introducing breaking changes that could negatively impact NuGet. If the `dotnet tool` started writing non-executable files into the directory, it would affect how NuGet discovers and runs plugins at runtime. -- Currently, NuGet Client plugin code will only support plugins developed in .NET. See the `Future Possibilities` section for more details. +- The IPC (Inter-Process Communication) used by NuGet is custom-made, and it would be beneficial for plugin implementers if it were based on industry standards. +This serves as a deterrent for developing NuGet plugins in non-.NET languages. +See the `Future Possibilities` section for more details. ## Rationale and alternatives @@ -354,7 +356,6 @@ When the manifest file is saved in the root directory of a source code repositor ### Support for non-.NET NuGet plugins - The NuGet Client plugin code needs to be updated to utilize a standard RPC mechanism, such as StreamJsonRpc, in order to support NuGet plugins developed in languages other than .NET. -Currently, the NuGet Client plugin code only supports plugins developed in .NET. The IPC (Inter-Process Communication) used by NuGet is custom-made, and it would be beneficial for plugin implementers if it were based on industry standards. This would allow plugin authors to reuse existing implementations for other languages. While there is no technical barrier preventing the implementation of a client in a non-.NET language, there is an additional cost associated with reimplementing the plugin protocol. From c925ea1a480891fdd9b9d2d8050f0da88586da87 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Tue, 19 Mar 2024 16:30:27 -0700 Subject: [PATCH 46/69] Update support for NuGet plugins developed in non-.NET languages --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 8a5ca6f62..5e8aa225a 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -353,7 +353,7 @@ In addition to that, if we ever plan to provide users with an option to manage t When the manifest file is saved in the root directory of a source code repository, a contributor can clone the repository and invoke a single .NET CLI command such as [`dotnet tool restore`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-restore) to install all of the tools listed in the manifest file. - Having NuGet plugins under the repository folder eliminates the need for NuGet to load plugins from the user profile. -### Support for non-.NET NuGet plugins +### Support for NuGet plugins developed in non-.NET languages - The NuGet Client plugin code needs to be updated to utilize a standard RPC mechanism, such as StreamJsonRpc, in order to support NuGet plugins developed in languages other than .NET. The IPC (Inter-Process Communication) used by NuGet is custom-made, and it would be beneficial for plugin implementers if it were based on industry standards. From 827c8f8ee5dde60decf74b503ddff7e982b32241 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Wed, 20 Mar 2024 13:30:11 -0700 Subject: [PATCH 47/69] specifying tool command name is optional --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 5e8aa225a..0c4242baf 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -84,7 +84,7 @@ All the plugin authors have to do is set the following properties and execute `d ```xml true -botsay +botsay //optional ``` - Leveraging `.NET Tools` provides a standard experience for customers to share console applications, with NuGet plugins being one such use case. From 81b719789644db588735bf560dfcc77e19caa518 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:51:46 -0700 Subject: [PATCH 48/69] update spec based on the feedback --- ...get-authentication-plugins-dotnet-tools.md | 173 ++++++++++++------ 1 file changed, 122 insertions(+), 51 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 0c4242baf..f010a7a92 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -20,14 +20,16 @@ The following details the combinations of client and framework for these plugins | NuGet.exe on Mono | .NET Framework | Currently, NuGet maintains `netfx` folder for plugins that will be invoked in `.NET Framework` code paths, `netcore` folder for plugins that will be invoked in `.NET Core` code paths. -The proposal is to add a new `any` folder to store NuGet plugins that are deployed as [tool Path](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location) .NET tools. -Upon installation, .NET tools are organized in a way that helps NuGet quickly determine which file in the package to run at runtime. | Framework | Root discovery location | |-----------|------------------------| | .NET Core | %UserProfile%/.nuget/plugins/netcore | | .NET Framework | %UserProfile%/.nuget/plugins/netfx | -| .NET Framework & .NET Core [current proposal] | %UserProfile%/.nuget/plugins/any | + +This proposal introduces a new workflow for both plugin authors and consumers: +- Plugin authors will now have the ability to publish their NuGet plugins as .NET Tools. +- Consumers can install these NuGet plugins as [global .NET tools](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool). +Upon installation, these global .NET tools are added to the PATH. This allows NuGet to easily determine which file in the package should be run at runtime. ## Motivation @@ -80,11 +82,23 @@ The reasons why `.NET tools` were chosen as the deployment mechanism are mention - NuGet plugins are console applications. A `.NET tool` is a special NuGet package that contains a console application, which presents a natural fit. - The .NET SDK has already simplified the process for customers to develop a tool. -All the plugin authors have to do is set the following properties and execute `dotnet pack` to generate the package: +All that plugin authors need to do is set the `PackAsTool` MSBuild property as shown below, and then execute `dotnet pack` to generate the package: ```xml -true -botsay //optional + + + + + Exe + net6.0 + + true + nuget-plugin-azure-artifacts-credprovider + ./nupkg + + + + ``` - Leveraging `.NET Tools` provides a standard experience for customers to share console applications, with NuGet plugins being one such use case. @@ -107,9 +121,91 @@ For example, the [dotnetsay tool](https://nuget.info/packages/dotnetsay/2.1.7) i ``` By implementing this specification, we offer plugin authors the option to use .NET tools for plugin deployment. -On the consumer side, these plugins will be installed as a `tool path` global tool. This eliminates the need to maintain separate versions for `.NET Framework` and `.NET Core`. +On the consumer side, these plugins will be installed as a global tool. This eliminates the need to maintain separate versions for `.NET Framework` and `.NET Core`. It also simplifies the installation process by removing the necessity for plugin authors to create subcommands like `codeartifact-creds install/uninstall`. +### Security considerations + +The [.NET SDK docs](https://learn.microsoft.com/dotnet/core/tools/global-tools#check-the-author-and-statistics) clearly state, `.NET tools run in full trust. Don't install a .NET tool unless you trust the author`. +This is an important consideration for plugin customers when installing NuGet plugins via .NET Tools in the future. + +### Technical explanation + +#### Authoring side approach + +To distribute a NuGet cross platform plugin as a .NET Tool, plugin authors need to follow these steps: + + - Follow [NuGet cross platform plugins](https://learn.microsoft.com/nuget/reference/extensibility/nuget-cross-platform-plugins) guidance. + - Ensure that the NuGet package name or the `ToolCommandName` property value begins with `nuget-plugin-*`. + - Execute `dotnet pack` to generate the package. + +```xml + true +``` + +> Note that if the `ToolCommandName` property value is set for a .NET tool and it conflicts with an existing command from another tool, the `dotnet tool install` command will fail. + +#### Consumer side approach + +The proposed workflow for repositories that access private NuGet feeds, such as Azure DevOps, would be: + +1. Ensure that the .NET SDK is installed. +1. Execute the command `dotnet tool install -g Microsoft.CredentialProvider`. +1. Run `dotnet restore --interactive` with a private endpoint. It should 'just work', meaning the credential providers installed in step 2 are used during credential acquisition and are used to authenticate against private endpoints. + +Upon installation, these global .NET tools are added to the PATH. This allows NuGet to easily determine which file in the package should be run at runtime. + +#### Plugin discovery + +Currently, plugins are discovered through a convention-based directory structure, such as the `%userprofile%/.nuget/plugins` folder on `Windows`. +For CI/CD scenarios, and for power users, environment variables can be used to override this behavior. +Note that only absolute paths are allowed when using these environment variables. + +- `NUGET_NETFX_PLUGIN_PATHS`: Defines the plugins used by the .NET Framework-based tooling (NuGet.exe/MSBuild.exe/Visual Studio). This takes precedence over `NUGET_PLUGIN_PATHS`. +- `NUGET_NETCORE_PLUGIN_PATHS`: Defines the plugins used by the .NET Core-based tooling (dotnet.exe). +This takes precedence over `NUGET_PLUGIN_PATHS`. +- `NUGET_PLUGIN_PATHS`: Defines the plugins used for the NuGet process, with priority preserved. +If this environment variable is set, it overrides the convention-based discovery. +It is ignored if either of the framework-specific variables is specified. + +I propose the addition of a `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. +This variable will define the plugins, installed as .NET tools, to be used by both .NET Framework and .NET Core tooling. +It will take precedence over `NUGET_PLUGIN_PATHS`. + +The plugins specified in the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable will be used regardless of whether the `NUGET_NETFX_PLUGIN_PATHS` or `NUGET_NETCORE_PLUGIN_PATHS` environment variables are set. +The primary reason for this is that plugins installed as .NET tools can be executed in both .NET Framework and .NET Core tooling.stomIf customers prefer to install NuGet plugins as a [tool-path global tool](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location), they can set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable.f This variable should point to the location of the .NET Tool executable that the NuGet Client tooling can invoke when needed.noenvironment variable](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Clients/NuGet.CommandLine/MsBuildUtility.cs#L708-L736) to locate `msbuild.exe`. +Considering that different platforms handle file casing in various ways, the implementation could convert all file names to lower case before checking if the file name begins with `nuget-plugin-*`. +On Windows, NuGet should search for files with the `.exe` extension. On other platforms, it should look for files with the executable bit set. + +#### Plugin execution + +In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/Plugins/PluginFactory.cs#L155-L181), NuGet launches the `.exe` file in a separate process when running on the .NET Framework. +On the other hand, when running on .NET, NuGet executes the `dotnet plugin-in.dll` command in a separate process. + +The plan is to slightly adjust this implementation. NuGet will launch the `.exe` file in a separate process when running on Windows, irrespective of whether the runtime is .NET Framework or .NET. +For non-Windows platforms, if the file does not have an extension, it will launch the executable directly in a separate process. +If the file has a `.dll` extension, it will continue to execute the `dotnet nuget-plugin-name.dll` command in a separate process. + +NuGet is designed to launch credential provider plugins sequentially, not simultaneously. +This is to avoid multiple authentication prompts from different plugins at the same time. +If a customer has installed both the legacy and the new .NET Tool credential providers, which can provide credentials for the same feed, NuGet follows a specific process. +It launches one provider at a time, and if that provider successfully returns the credentials for the feed, NuGet will not need to invoke the other provider for the same feed. + +## Drawbacks + +- There is some risk of the `dotnet tool` introducing breaking changes that could negatively impact NuGet. +If the `dotnet tool` started writing non-executable files into the directory, it would affect how NuGet discovers and runs plugins at runtime. + +- The IPC (Inter-Process Communication) used by NuGet is custom-made, and it would be beneficial for plugin implementers if it were based on industry standards. +This serves as a deterrent for developing NuGet plugins in non-.NET languages. +See the `Future Possibilities` section for more details. + +## Rationale and alternatives + +### Installing NuGet plugins as tool-path .NET Tools + +#### Functional explanation + The proposed workflow for repositories that access private NuGet feeds, such as Azure DevOps, would be: 1. Ensure that the .NET SDK is installed. @@ -136,6 +232,10 @@ This makes it easier to identify and invoke the appropriate tool for NuGet opera - One version of a tool is used for all directories on the machine. The `tool path` option aligns well with the NuGet plugins architecture design, and hence, it is the recommended approach for installing and executing NuGet plugins. +By implementing this specification, we offer plugin authors the option to use .NET tools for plugin deployment. +On the consumer side, these plugins will be installed as a `tool path` global tool. This eliminates the need to maintain separate versions for `.NET Framework` and `.NET Core`. +It also simplifies the installation process by removing the necessity for plugin authors to create subcommands like `codeartifact-creds install/uninstall`. + We should consider adding `dotnet nuget plugin install/uninstall/update` commands to the .NET SDK as wrappers for the `dotnet tool install/uninstall/update` commands. This would simplify the installation process by eliminating the need for users to specify the NuGet plugin path, making the process more user-friendly and platform-independent. @@ -159,34 +259,8 @@ However, NuGet plugins and .NET tools share the similarity of being console appl This approach is similar to the alternative design that [Andy Zivkovic](https://github.com/zivkan) kindly proposed in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. -### Security considerations - -The [.NET SDK docs](https://learn.microsoft.com/dotnet/core/tools/global-tools#check-the-author-and-statistics) clearly state, `.NET tools run in full trust. Don't install a .NET tool unless you trust the author`. -This is an important consideration for plugin customers when installing NuGet plugins via .NET Tools in the future. - ### Technical explanation -Currently, plugins are discovered through a convention-based directory structure, such as the `%userprofile%/.nuget/plugins` folder on Windows. -For CI/CD scenarios, and for power users, environment variables can be used to override this behavior. -Note that only absolute paths are allowed when using these environment variables. - -- `NUGET_NETFX_PLUGIN_PATHS`: Defines the plugins used by the .NET Framework-based tooling (NuGet.exe/MSBuild.exe/Visual Studio). This takes precedence over `NUGET_PLUGIN_PATHS`. -- `NUGET_NETCORE_PLUGIN_PATHS`: Defines the plugins used by the .NET Core-based tooling (dotnet.exe). -This takes precedence over `NUGET_PLUGIN_PATHS`. -- `NUGET_PLUGIN_PATHS`: Defines the plugins used for the NuGet process, with priority preserved. -If this environment variable is set, it overrides the convention-based discovery. -It is ignored if either of the framework-specific variables is specified. - -I propose the addition of a `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. -This variable will define the plugins, installed as .NET tools, to be used by both .NET Framework and .NET Core tooling. -It will take precedence over `NUGET_PLUGIN_PATHS`. - -The plugins specified in the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable will be used regardless of whether the `NUGET_NETFX_PLUGIN_PATHS` or `NUGET_NETCORE_PLUGIN_PATHS` environment variables are set. -The primary reason for this is that plugins installed as .NET tools can be executed in both .NET Framework and .NET Core tooling. - -If customers prefer to install NuGet plugins as a global tool instead of a tool-path tool, they can set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. -This variable should point to the location of the .NET Tool executable that the NuGet Client tooling can invoke when needed. - If none of the above environment variables are set, NuGet will default to the conventional method of discovering plugins from predefined directories. In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/8b658e2eee6391936887b9fd1b39f7918d16a9cb/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L65-L77), the NuGet code looks for plugin files in the `netfx` directory when running on .NET Framework, and in the `netcore` directory when running on .NET Core. This implementation should be updated to include the new `any` directory. This directory should be added alongside `netcore` in the .NET code paths and `netfx` in the .NET Framework code paths, to ensure backward compatibility. @@ -239,33 +313,30 @@ drwxr-xr-x 5 {user} 4096 Feb 10 08:21 .store -rwxr-xr-x 1 {user} 75632 Feb 10 08:20 dotnetsay ``` -## Roadmap - -In terms of the implementation roadmap, I propose the following stages: - -1. Initially, plugin authors will publish NuGet plugins as .NET Tools. Consumers will install NuGet plugins using `dotnet tool` sub-commands. -They will specify the plugin's path based on their operating system. We will make the necessary code changes on the NuGet side to enable running the plugins installed as `.NET tools`. +**Advantages:** -2. Subsequently, we will introduce a `dotnet nuget plugin` subcommand. This will allow users to `install, update, uninstall, and search` for plugins. -However, we will still rely on the `dotnet tool` command under the hood, as mentioned above. -Please note that these command names are subject to change based on feedback to the specification we are going to create in the near future. +- This approach simplifies the identification of NuGet plugins at runtime because they are installed in a location that NuGet recognizes, for example `%UserProfile%/.nuget/plugins/any` on `Windows`. -## Drawbacks +**Disadvantages:** - The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, is to easily search for NuGet plugins and install them as global tool. -However, the current proposal suggests installing the plugin as a tool-path .NET tool. -As mentioned in the technical explanation section, customers can opt to install NuGet plugins as a global tool instead of a tool-path tool. +However, this approach suggests installing the plugin as a tool-path .NET tool. +Customers can opt to install NuGet plugins as a global tool instead of a tool-path tool. To do this, they need to set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable should point to the location of the .NET Tool executable, which the NuGet Client tooling can invoke when needed. -- There is some risk of the `dotnet tool` introducing breaking changes that could negatively impact NuGet. -If the `dotnet tool` started writing non-executable files into the directory, it would affect how NuGet discovers and runs plugins at runtime. +- This approach doesn't support developing NuGet plugins in non-.NET languages. -- The IPC (Inter-Process Communication) used by NuGet is custom-made, and it would be beneficial for plugin implementers if it were based on industry standards. -This serves as a deterrent for developing NuGet plugins in non-.NET languages. -See the `Future Possibilities` section for more details. +#### Roadmap -## Rationale and alternatives +In terms of the implementation roadmap, I propose the following stages: + +1. Initially, plugin authors will publish NuGet plugins as .NET Tools. Consumers will install NuGet plugins using `dotnet tool` sub-commands. +They will specify the plugin's path based on their operating system. We will make the necessary code changes on the NuGet side to enable running the plugins installed as `.NET tools`. + +2. Subsequently, we will introduce a `dotnet nuget plugin` subcommand. This will allow users to `install, update, uninstall, and search` for plugins. +However, we will still rely on the `dotnet tool` command under the hood, as mentioned above. +Please note that these command names are subject to change based on feedback to the specification we are going to create in the near future. ### Specify the the authentication plugin in NuGet.Config file From ba347ad727186b88a2bd16213d41a4ccf0d0bbbe Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:20:43 -0700 Subject: [PATCH 49/69] fixed typos and added some information about NuGet.exe currently [scans all directories in the PATH environment variable to locate msbuild.exe --- ...get-authentication-plugins-dotnet-tools.md | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index f010a7a92..1ded17cc8 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -124,13 +124,6 @@ By implementing this specification, we offer plugin authors the option to use .N On the consumer side, these plugins will be installed as a global tool. This eliminates the need to maintain separate versions for `.NET Framework` and `.NET Core`. It also simplifies the installation process by removing the necessity for plugin authors to create subcommands like `codeartifact-creds install/uninstall`. -### Security considerations - -The [.NET SDK docs](https://learn.microsoft.com/dotnet/core/tools/global-tools#check-the-author-and-statistics) clearly state, `.NET tools run in full trust. Don't install a .NET tool unless you trust the author`. -This is an important consideration for plugin customers when installing NuGet plugins via .NET Tools in the future. - -### Technical explanation - #### Authoring side approach To distribute a NuGet cross platform plugin as a .NET Tool, plugin authors need to follow these steps: @@ -155,6 +148,13 @@ The proposed workflow for repositories that access private NuGet feeds, such as Upon installation, these global .NET tools are added to the PATH. This allows NuGet to easily determine which file in the package should be run at runtime. +### Security considerations + +The [.NET SDK docs](https://learn.microsoft.com/dotnet/core/tools/global-tools#check-the-author-and-statistics) clearly state, `.NET tools run in full trust. Don't install a .NET tool unless you trust the author`. +This is an important consideration for plugin customers when installing NuGet plugins via .NET Tools in the future. + +### Technical explanation + #### Plugin discovery Currently, plugins are discovered through a convention-based directory structure, such as the `%userprofile%/.nuget/plugins` folder on `Windows`. @@ -173,9 +173,12 @@ This variable will define the plugins, installed as .NET tools, to be used by bo It will take precedence over `NUGET_PLUGIN_PATHS`. The plugins specified in the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable will be used regardless of whether the `NUGET_NETFX_PLUGIN_PATHS` or `NUGET_NETCORE_PLUGIN_PATHS` environment variables are set. -The primary reason for this is that plugins installed as .NET tools can be executed in both .NET Framework and .NET Core tooling.stomIf customers prefer to install NuGet plugins as a [tool-path global tool](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location), they can set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable.f This variable should point to the location of the .NET Tool executable that the NuGet Client tooling can invoke when needed.noenvironment variable](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Clients/NuGet.CommandLine/MsBuildUtility.cs#L708-L736) to locate `msbuild.exe`. -Considering that different platforms handle file casing in various ways, the implementation could convert all file names to lower case before checking if the file name begins with `nuget-plugin-*`. +The primary reason for this is that plugins installed as .NET tools can be executed in both .NET Framework and .NET Core tooling. +If customers prefer to install NuGet plugins as a [tool-path global tool](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location), they can set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable.This variable should point to the location of the .NET Tool executable that the NuGet Client tooling can invoke when needed. +Considering the varying ways different platforms handle file casing, the implementation could convert all file names to lowercase before checking for a file. +Specifically, it should look for files whose names begin with `nuget-plugin-*` by scanning all the directories in the PATH environment variable. On Windows, NuGet should search for files with the `.exe` extension. On other platforms, it should look for files with the executable bit set. +`NuGet.exe` currently [scans all directories in the PATH environment variable](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Clients/NuGet.CommandLine/MsBuildUtility.cs#L708-L736) to find `MSBuild.exe`. #### Plugin execution From c5c80ff6c1c13f8318f160d29064ee5eb6b7b133 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:24:01 -0700 Subject: [PATCH 50/69] make it readable --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 1 + 1 file changed, 1 insertion(+) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 1ded17cc8..6e8663294 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -175,6 +175,7 @@ It will take precedence over `NUGET_PLUGIN_PATHS`. The plugins specified in the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable will be used regardless of whether the `NUGET_NETFX_PLUGIN_PATHS` or `NUGET_NETCORE_PLUGIN_PATHS` environment variables are set. The primary reason for this is that plugins installed as .NET tools can be executed in both .NET Framework and .NET Core tooling. If customers prefer to install NuGet plugins as a [tool-path global tool](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location), they can set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable.This variable should point to the location of the .NET Tool executable that the NuGet Client tooling can invoke when needed. + Considering the varying ways different platforms handle file casing, the implementation could convert all file names to lowercase before checking for a file. Specifically, it should look for files whose names begin with `nuget-plugin-*` by scanning all the directories in the PATH environment variable. On Windows, NuGet should search for files with the `.exe` extension. On other platforms, it should look for files with the executable bit set. From 2cebea96c5608c5c001620d9174d30ea98658843 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:47:23 -0700 Subject: [PATCH 51/69] added future possibilities section to improve discoverability of NuGet plugins published a s .NET Tools. --- ...get-authentication-plugins-dotnet-tools.md | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 6e8663294..45cca10ab 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -204,6 +204,9 @@ If the `dotnet tool` started writing non-executable files into the directory, it This serves as a deterrent for developing NuGet plugins in non-.NET languages. See the `Future Possibilities` section for more details. +- The discoverability of NuGet plugins published as .NET Tools is challenging for users because the `dotnet tool search` command only filters based on the `PackageType` being `DotnetTool`. +Please refer to the `Future Possibilities` section for more related information. + ## Rationale and alternatives ### Installing NuGet plugins as tool-path .NET Tools @@ -342,6 +345,14 @@ They will specify the plugin's path based on their operating system. We will mak However, we will still rely on the `dotnet tool` command under the hood, as mentioned above. Please note that these command names are subject to change based on feedback to the specification we are going to create in the near future. +#### Prior art + +- The `dotnet workload` command is separate and has its own set of sub-commands, including `install`, `uninstall`, and `list`. +These sub-commands are wrappers for the corresponding `dotnet tool` sub-commands. +Workloads are installed in a different location than .NET tools, which makes it easier for them to be discovered at runtime, addressing a problem that NuGet also faces. +However, NuGet plugins and .NET tools share the similarity of being console applications. +This prior art will be helpful for us as we consider the implementation of the `dotnet nuget plugin install/uninstall/update/search` commands. + ### Specify the the authentication plugin in NuGet.Config file To simplify the authentication process with private NuGet feeds, customers can specify the authentication plugins installed as .NET Tools in the `packagesourceCredentials` section of the NuGet.Config file. @@ -405,12 +416,6 @@ This command copies the credential provider to the NuGet plugins folder. They also provide an `uninstall` subcommand to remove the files from the NuGet plugin folders. However, due to limitations in the NuGet Client tooling, they've had to maintain plugins for both .NET Framework and .NET Core tooling. Additionally, they've had to provide commands to install and uninstall the NuGet plugins. -- The `dotnet workload` command is separate and has its own set of sub-commands, including `install`, `uninstall`, and `list`. -These sub-commands are wrappers for the corresponding `dotnet tool` sub-commands. -Workloads are installed in a different location than .NET tools, which makes it easier for them to be discovered at runtime, addressing a problem that NuGet also faces. -However, NuGet plugins and .NET tools share the similarity of being console applications. -This prior art will be helpful for us as we consider the implementation of the `dotnet nuget plugin install/uninstall/update/search` commands. - ## Unresolved Questions - The issue regarding workload installation on Mac and Linux, as well as NuGet credential providers, is discussed in [GitHub Issue #35912](https://github.com/dotnet/sdk/issues/35912#issuecomment-1759774310). @@ -435,3 +440,10 @@ The IPC (Inter-Process Communication) used by NuGet is custom-made, and it would This would allow plugin authors to reuse existing implementations for other languages. While there is no technical barrier preventing the implementation of a client in a non-.NET language, there is an additional cost associated with reimplementing the plugin protocol. This cost serves as a deterrent for writing non-.NET plugins. + +### Improve the discoverability of NuGet plugins published as .NET Tools + +- Currently, the `dotnet pack` command ignores the `PackageType` property when the `PackAsTool` property is set to true. +This behavior might be due to the fact that `CredentialProvider` is not a recognized `PackageType`. +If the `dotnet pack` command could generate a .nupkg for .NET Tool with multiple package types, which are found in the `.nuspec` metadata file, then we could introduce a new `dotnet nuget plugin search` command. +This command would act as a wrapper for the `dotnet tool search` command, but it would also filter the results based on the additional package type. From 0cf26765d016090c6203e53858261666829415c2 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Wed, 27 Mar 2024 17:37:25 -0700 Subject: [PATCH 52/69] improved future possibility --- .../support-nuget-authentication-plugins-dotnet-tools.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 45cca10ab..d0e322873 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -443,7 +443,8 @@ This cost serves as a deterrent for writing non-.NET plugins. ### Improve the discoverability of NuGet plugins published as .NET Tools -- Currently, the `dotnet pack` command ignores the `PackageType` property when the `PackAsTool` property is set to true. -This behavior might be due to the fact that `CredentialProvider` is not a recognized `PackageType`. -If the `dotnet pack` command could generate a .nupkg for .NET Tool with multiple package types, which are found in the `.nuspec` metadata file, then we could introduce a new `dotnet nuget plugin search` command. -This command would act as a wrapper for the `dotnet tool search` command, but it would also filter the results based on the additional package type. +- At present, the `dotnet pack` command disregards the `PackageType` property when the `PackAsTool` property is set to true. +This might be because the new package type, `CredentialProvider`, which I added to the .csproj file, is not a recognized [`PackageType`](https://github.com/NuGet/NuGet.Client/blob/cecbc9a1f7a5cd0ea0a62dea2523f740bbd078d3/src/NuGet.Core/NuGet.Packaging/Core/PackageType.cs#L15-L20). +If the `dotnet pack` command could generate a .nupkg for .NET Tool with multiple package types, we could introduce a new `dotnet nuget plugin search` command. +This command would act as a wrapper for the `dotnet tool search` command, further refining the results based on the additional package type, such as `CredentialProvider`. +These package types can be found in the `.nuspec` metadata file of the generated nupkg. From fb3b5b1c18a0e7e99d47654602799a16f84d0914 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Fri, 29 Mar 2024 19:54:41 -0700 Subject: [PATCH 53/69] work lint issues update spec update spec Add support for other extensions on Windows as future possibility --- ...get-authentication-plugins-dotnet-tools.md | 70 +++++++++++++++---- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index d0e322873..9ae2cf56d 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -26,10 +26,15 @@ Currently, NuGet maintains `netfx` folder for plugins that will be invoked in `. | .NET Core | %UserProfile%/.nuget/plugins/netcore | | .NET Framework | %UserProfile%/.nuget/plugins/netfx | -This proposal introduces a new workflow for both plugin authors and consumers: -- Plugin authors will now have the ability to publish their NuGet plugins as .NET Tools. +This proposal introduces a new workflow for plugin authors, consumers, and NuGet Client tooling: +- Plugin authors will now be able to publish their NuGet plugins as .NET Tools. +The only requirement is that the .NET Tool command name should begin with `nuget-plugin-`. - Consumers can install these NuGet plugins as [global .NET tools](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool). -Upon installation, these global .NET tools are added to the PATH. This allows NuGet to easily determine which file in the package should be run at runtime. +- Upon installation, these global .NET tools are added to the PATH by the .NET SDK. +This allows NuGet to easily determine which file in the package should be run at runtime. +It does this by scanning the `PATH` environment variable for plugins whose file name begins with `nuget-plugin-`. +On Windows, NuGet will look for plugins with a `.exe` extension, whereas on Linux/Mac, it will look for plugins with the executable bit set. +These plugins are launched in a separate process, which aligns with the current design. ## Motivation @@ -128,9 +133,9 @@ It also simplifies the installation process by removing the necessity for plugin To distribute a NuGet cross platform plugin as a .NET Tool, plugin authors need to follow these steps: - - Follow [NuGet cross platform plugins](https://learn.microsoft.com/nuget/reference/extensibility/nuget-cross-platform-plugins) guidance. - - Ensure that the NuGet package name or the `ToolCommandName` property value begins with `nuget-plugin-*`. - - Execute `dotnet pack` to generate the package. +- Follow [NuGet cross platform plugins](https://learn.microsoft.com/nuget/reference/extensibility/nuget-cross-platform-plugins) guidance. +- Ensure that the .NET Tool command name begins with `nuget-plugin-*`. +- Execute `dotnet pack` to generate the package. ```xml true @@ -176,17 +181,28 @@ The plugins specified in the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment varia The primary reason for this is that plugins installed as .NET tools can be executed in both .NET Framework and .NET Core tooling. If customers prefer to install NuGet plugins as a [tool-path global tool](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location), they can set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable.This variable should point to the location of the .NET Tool executable that the NuGet Client tooling can invoke when needed. -Considering the varying ways different platforms handle file casing, the implementation could convert all file names to lowercase before checking for a file. -Specifically, it should look for files whose names begin with `nuget-plugin-*` by scanning all the directories in the PATH environment variable. -On Windows, NuGet should search for files with the `.exe` extension. On other platforms, it should look for files with the executable bit set. -`NuGet.exe` currently [scans all directories in the PATH environment variable](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Clients/NuGet.CommandLine/MsBuildUtility.cs#L708-L736) to find `MSBuild.exe`. +NuGet should search for files whose names begin with `nuget-plugin-*` by scanning all the directories in the `PATH` environment variable. +To ensure compatibility across different platforms, the implementation could convert all file names to lowercase before checking for a file. + +- On Windows, NuGet should search for plugins using the `.exe` extension. +The `PATHEXT` environment variable in Windows specifies the file extensions that the operating system considers to be executable. +When you enter a command without specifying an extension, Windows will look for files with the extensions listed in `PATHEXT` in the directories specified by the `PATH` environment variable. +For example, if `PATHEXT` is set to `.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC`, and you enter the command `myprogram`, Windows will search for `myprogram.com`, `myprogram.exe`, `myprogram.bat`, and so on, in that order, in the directories listed in your `PATH` environment variable. +It will execute the first match it finds. +Given that .NET Tools are console applications, they should have the `.exe` extension on Windows to be considered valid plugins if the naming convention is followed. + +- Similarly, on other platforms, NuGet should search for plugins with the executable bit set to identify them as valid plugins. +This is because on Mac and Linux, executable files typically don't have extensions. +Given that .NET Tools, when installed on Linux and Mac, have a native shim with an executable bit set, they should be considered valid plugins if the naming convention is followed. + +Currently, `NuGet.exe` [scans all directories in the `PATH` environment variable](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Clients/NuGet.CommandLine/MsBuildUtility.cs#L708-L736) to find `MSBuild.exe`. #### Plugin execution In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/Plugins/PluginFactory.cs#L155-L181), NuGet launches the `.exe` file in a separate process when running on the .NET Framework. On the other hand, when running on .NET, NuGet executes the `dotnet plugin-in.dll` command in a separate process. -The plan is to slightly adjust this implementation. NuGet will launch the `.exe` file in a separate process when running on Windows, irrespective of whether the runtime is .NET Framework or .NET. +The plan is to slightly adjust this implementation. NuGet will launch the `.exe` file in a separate process when running on Windows, irrespective of whether the runtime is .NET Framework or .NET. For non-Windows platforms, if the file does not have an extension, it will launch the executable directly in a separate process. If the file has a `.dll` extension, it will continue to execute the `dotnet nuget-plugin-name.dll` command in a separate process. @@ -207,6 +223,10 @@ See the `Future Possibilities` section for more details. - The discoverability of NuGet plugins published as .NET Tools is challenging for users because the `dotnet tool search` command only filters based on the `PackageType` being `DotnetTool`. Please refer to the `Future Possibilities` section for more related information. +- On Windows, NuGet searches for plugins using the `.exe` extension. +This is the case even though Windows recognizes all extensions configured in `PATHEXT` as executables. +For more information, please refer to the `Future Possibilities` section. + ## Rationale and alternatives ### Installing NuGet plugins as tool-path .NET Tools @@ -266,7 +286,7 @@ However, NuGet plugins and .NET tools share the similarity of being console appl This approach is similar to the alternative design that [Andy Zivkovic](https://github.com/zivkan) kindly proposed in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. -### Technical explanation +#### Technical explanation If none of the above environment variables are set, NuGet will default to the conventional method of discovering plugins from predefined directories. In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/8b658e2eee6391936887b9fd1b39f7918d16a9cb/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L65-L77), the NuGet code looks for plugin files in the `netfx` directory when running on .NET Framework, and in the `netcore` directory when running on .NET Core. This implementation should be updated to include the new `any` directory. @@ -327,7 +347,7 @@ drwxr-xr-x 5 {user} 4096 Feb 10 08:21 .store **Disadvantages:** - The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, is to easily search for NuGet plugins and install them as global tool. -However, this approach suggests installing the plugin as a tool-path .NET tool. +However, this approach suggests installing the plugin as a tool-path .NET tool. Customers can opt to install NuGet plugins as a global tool instead of a tool-path tool. To do this, they need to set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable should point to the location of the .NET Tool executable, which the NuGet Client tooling can invoke when needed. @@ -448,3 +468,27 @@ This might be because the new package type, `CredentialProvider`, which I added If the `dotnet pack` command could generate a .nupkg for .NET Tool with multiple package types, we could introduce a new `dotnet nuget plugin search` command. This command would act as a wrapper for the `dotnet tool search` command, further refining the results based on the additional package type, such as `CredentialProvider`. These package types can be found in the `.nuspec` metadata file of the generated nupkg. + +### Support for other extensions on Windows. + +- In the future, we could consider supporting extensions other than `.exe` configured in `PATHEXT` as executables. +To achieve this, NuGet would need to identify the correct executable or interpretter to run a particular file. +For example, to execute a PowerShell script, NuGet would need to launch the process as shown below. +However, the challenge lies in identifying the appropriate executable for each type of file, which is not an immediate requirement. + +```cs +var processInfo = new System.Diagnostics.ProcessStartInfo +{ + FileName = "powershell.exe", + Arguments = @"& 'C:\Path\To\Your\Script.ps1'", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true +}; + +var process = System.Diagnostics.Process.Start(processInfo); + +// To read the output (if any): +string output = process.StandardOutput.ReadToEnd(); +process.WaitForExit(); +``` \ No newline at end of file From 7560e79e049070e97041701457ba8a24e41b2f91 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Mon, 1 Apr 2024 09:54:47 -0700 Subject: [PATCH 54/69] fix lint issues --- .../support-nuget-authentication-plugins-dotnet-tools.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 9ae2cf56d..bff63c550 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -27,6 +27,7 @@ Currently, NuGet maintains `netfx` folder for plugins that will be invoked in `. | .NET Framework | %UserProfile%/.nuget/plugins/netfx | This proposal introduces a new workflow for plugin authors, consumers, and NuGet Client tooling: + - Plugin authors will now be able to publish their NuGet plugins as .NET Tools. The only requirement is that the .NET Tool command name should begin with `nuget-plugin-`. - Consumers can install these NuGet plugins as [global .NET tools](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool). @@ -469,7 +470,7 @@ If the `dotnet pack` command could generate a .nupkg for .NET Tool with multiple This command would act as a wrapper for the `dotnet tool search` command, further refining the results based on the additional package type, such as `CredentialProvider`. These package types can be found in the `.nuspec` metadata file of the generated nupkg. -### Support for other extensions on Windows. +### Support for other extensions on Windows - In the future, we could consider supporting extensions other than `.exe` configured in `PATHEXT` as executables. To achieve this, NuGet would need to identify the correct executable or interpretter to run a particular file. @@ -491,4 +492,4 @@ var process = System.Diagnostics.Process.Start(processInfo); // To read the output (if any): string output = process.StandardOutput.ReadToEnd(); process.WaitForExit(); -``` \ No newline at end of file +``` From 23be277e710e49d475a3c4b9eb6a87758643917e Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Tue, 2 Apr 2024 07:02:31 -0700 Subject: [PATCH 55/69] Added lack of .NET local tools support as a drawback --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index bff63c550..c5ed59edc 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -217,6 +217,10 @@ It launches one provider at a time, and if that provider successfully returns th - There is some risk of the `dotnet tool` introducing breaking changes that could negatively impact NuGet. If the `dotnet tool` started writing non-executable files into the directory, it would affect how NuGet discovers and runs plugins at runtime. +- This approach doesn't support the installation of NuGet plugins as a [.NET local tool](https://learn.microsoft.com/en-us/dotnet/core/tools/local-tools-how-to-use). +The reason for this is that running a local tool requires the invocation of the `dotnet tool run` command. +However, in the current design, we have considered launching the .NET tool executable in a separate process without relying on the said command. + - The IPC (Inter-Process Communication) used by NuGet is custom-made, and it would be beneficial for plugin implementers if it were based on industry standards. This serves as a deterrent for developing NuGet plugins in non-.NET languages. See the `Future Possibilities` section for more details. From cff6c9163a958260a8fb72cae686aa1268a6b7ca Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Tue, 2 Apr 2024 07:09:11 -0700 Subject: [PATCH 56/69] arranged points in drawbacks section --- .../support-nuget-authentication-plugins-dotnet-tools.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index c5ed59edc..6ba3d917f 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -221,10 +221,6 @@ If the `dotnet tool` started writing non-executable files into the directory, it The reason for this is that running a local tool requires the invocation of the `dotnet tool run` command. However, in the current design, we have considered launching the .NET tool executable in a separate process without relying on the said command. -- The IPC (Inter-Process Communication) used by NuGet is custom-made, and it would be beneficial for plugin implementers if it were based on industry standards. -This serves as a deterrent for developing NuGet plugins in non-.NET languages. -See the `Future Possibilities` section for more details. - - The discoverability of NuGet plugins published as .NET Tools is challenging for users because the `dotnet tool search` command only filters based on the `PackageType` being `DotnetTool`. Please refer to the `Future Possibilities` section for more related information. @@ -232,6 +228,10 @@ Please refer to the `Future Possibilities` section for more related information. This is the case even though Windows recognizes all extensions configured in `PATHEXT` as executables. For more information, please refer to the `Future Possibilities` section. +- The IPC (Inter-Process Communication) used by NuGet is custom-made, and it would be beneficial for plugin implementers if it were based on industry standards. +This serves as a deterrent for developing NuGet plugins in non-.NET languages. +See the `Future Possibilities` section for more details. + ## Rationale and alternatives ### Installing NuGet plugins as tool-path .NET Tools From f1abcb5c28b168743bf2366b4034b861683997a8 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Tue, 2 Apr 2024 07:10:49 -0700 Subject: [PATCH 57/69] Add future possibilities section for improved discoverability of NuGet plugins published as .NET Tools --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 1 + 1 file changed, 1 insertion(+) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 6ba3d917f..160dae551 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -220,6 +220,7 @@ If the `dotnet tool` started writing non-executable files into the directory, it - This approach doesn't support the installation of NuGet plugins as a [.NET local tool](https://learn.microsoft.com/en-us/dotnet/core/tools/local-tools-how-to-use). The reason for this is that running a local tool requires the invocation of the `dotnet tool run` command. However, in the current design, we have considered launching the .NET tool executable in a separate process without relying on the said command. +See the `Future Possibilities` section for more details. - The discoverability of NuGet plugins published as .NET Tools is challenging for users because the `dotnet tool search` command only filters based on the `PackageType` being `DotnetTool`. Please refer to the `Future Possibilities` section for more related information. From a7bf35fceccf26caa194a47b6441c6036f197f90 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Tue, 2 Apr 2024 11:50:38 -0700 Subject: [PATCH 58/69] Apply suggestions from code review Co-authored-by: Andy Zivkovic --- ...rt-nuget-authentication-plugins-dotnet-tools.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 160dae551..069b65911 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -41,7 +41,7 @@ These plugins are launched in a separate process, which aligns with the current Currently, the NuGet plugin architecture requires support and deployment of multiple versions. For `.NET Framework`, NuGet searches for files ending in `*.exe`, while for `.NET Core`, it searches for files ending in `*.dll`. -These files are typically stored in two distinct folders such as `netfx` and `netcore` under the NuGet plugins base path. +These files are stored in two distinct folders, `netfx` and `netcore`, under the NuGet plugins base path. This design decision is due to the different entry points for `.NET Core` and `.NET Framework`, as explained in the [original design](https://github.com/NuGet/Home/wiki/NuGet-cross-plat-authentication-plugin#plugin-installation-and-discovery). `.NET Core` uses files with a `dll` extension, while `.NET Framework` uses files with an `exe` extension as entry points. This distinction is further highlighted by the two plugin path environment variables, `NUGET_NETFX_PLUGIN_PATHS` and `NUGET_NETCORE_PLUGIN_PATHS`, which must be set for each framework type. @@ -150,9 +150,11 @@ The proposed workflow for repositories that access private NuGet feeds, such as 1. Ensure that the .NET SDK is installed. 1. Execute the command `dotnet tool install -g Microsoft.CredentialProvider`. -1. Run `dotnet restore --interactive` with a private endpoint. It should 'just work', meaning the credential providers installed in step 2 are used during credential acquisition and are used to authenticate against private endpoints. +1. Run `dotnet restore --interactive` with a private endpoint. + It should 'just work', meaning the credential providers installed in step 2 are used during credential acquisition and are used to authenticate against private endpoints. -Upon installation, these global .NET tools are added to the PATH. This allows NuGet to easily determine which file in the package should be run at runtime. +Upon installation, these global .NET tools are added to the PATH. +This allows NuGet to easily determine which file in the package should be run at runtime. ### Security considerations @@ -180,7 +182,8 @@ It will take precedence over `NUGET_PLUGIN_PATHS`. The plugins specified in the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable will be used regardless of whether the `NUGET_NETFX_PLUGIN_PATHS` or `NUGET_NETCORE_PLUGIN_PATHS` environment variables are set. The primary reason for this is that plugins installed as .NET tools can be executed in both .NET Framework and .NET Core tooling. -If customers prefer to install NuGet plugins as a [tool-path global tool](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location), they can set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable.This variable should point to the location of the .NET Tool executable that the NuGet Client tooling can invoke when needed. +If customers prefer to install NuGet plugins as a [tool-path global tool](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location), they can set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. +This variable should point to the location of the .NET Tool executable that the NuGet Client tooling can invoke when needed. NuGet should search for files whose names begin with `nuget-plugin-*` by scanning all the directories in the `PATH` environment variable. To ensure compatibility across different platforms, the implementation could convert all file names to lowercase before checking for a file. @@ -203,7 +206,8 @@ Currently, `NuGet.exe` [scans all directories in the `PATH` environment variable In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/Plugins/PluginFactory.cs#L155-L181), NuGet launches the `.exe` file in a separate process when running on the .NET Framework. On the other hand, when running on .NET, NuGet executes the `dotnet plugin-in.dll` command in a separate process. -The plan is to slightly adjust this implementation. NuGet will launch the `.exe` file in a separate process when running on Windows, irrespective of whether the runtime is .NET Framework or .NET. +The plan is to slightly adjust this implementation. +NuGet will launch the `.exe` file in a separate process when running on Windows, irrespective of whether the runtime is .NET Framework or .NET. For non-Windows platforms, if the file does not have an extension, it will launch the executable directly in a separate process. If the file has a `.dll` extension, it will continue to execute the `dotnet nuget-plugin-name.dll` command in a separate process. From 96b43ce2e6652511eaa3571c2d58977b720a1199 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:31:04 -0700 Subject: [PATCH 59/69] updated motivation section based on the feedback. --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 069b65911..6b915607d 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -53,10 +53,8 @@ For instance, let's consider the two cross-platform authentication plugins that 2. **AWS CodeArtifact Credential Provider** - The AWS team has developed a [.NET tool](https://docs.aws.amazon.com/codeartifact/latest/ug/nuget-cli.html#nuget-configure-cli) to facilitate authentication with their private NuGet feeds. In their current implementation, they've added a subcommand, `codeartifact-creds install`, which copies the credential provider to the NuGet plugins folder. The log below shows all possible subcommands. -However, plugin authors could leverage the .NET SDK to allow their customers to install, uninstall, or update the NuGet plugins more efficiently. ```log -~\.nuget\plugins\any ❯ .\dotnet-codeartifact-creds.exe Required command was not provided. @@ -73,6 +71,8 @@ Commands: uninstall Uninstalls the AWS CodeArtifact NuGet credential provider from the NuGet plugins folder. ``` +NuGet plugin authors could leverage the .NET SDK to allow their customers to install, uninstall, or update the plugins more efficiently. + ## Explanation ### Functional explanation From 8f47d91f9eedcff7985042a07dd3509b32eba6a0 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Tue, 2 Apr 2024 19:33:39 -0700 Subject: [PATCH 60/69] removed the log sample from codeartifact credential provider --- ...uget-authentication-plugins-dotnet-tools.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 6b915607d..54acb2143 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -52,24 +52,6 @@ For instance, let's consider the two cross-platform authentication plugins that 1. **Azure Artifacts Credential Provider** - The [setup](https://github.com/microsoft/artifacts-credprovider/tree/master?tab=readme-ov-file#setup) instructions vary based on the platform. 2. **AWS CodeArtifact Credential Provider** - The AWS team has developed a [.NET tool](https://docs.aws.amazon.com/codeartifact/latest/ug/nuget-cli.html#nuget-configure-cli) to facilitate authentication with their private NuGet feeds. In their current implementation, they've added a subcommand, `codeartifact-creds install`, which copies the credential provider to the NuGet plugins folder. -The log below shows all possible subcommands. - -```log -❯ .\dotnet-codeartifact-creds.exe -Required command was not provided. - -Usage: - dotnet-codeartifact-creds [options] [command] - -Options: - --version Show version information - -?, -h, --help Show help and usage information - -Commands: - install Installs the AWS CodeArtifact NuGet credential provider into the NuGet plugins folder. - configure Sets or Unsets a configuration - uninstall Uninstalls the AWS CodeArtifact NuGet credential provider from the NuGet plugins folder. -``` NuGet plugin authors could leverage the .NET SDK to allow their customers to install, uninstall, or update the plugins more efficiently. From b888c9ee38c995649b87004dba71f0bfc95d6ee9 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Tue, 2 Apr 2024 19:38:01 -0700 Subject: [PATCH 61/69] updated security considerations --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 1 + 1 file changed, 1 insertion(+) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 54acb2143..352347f54 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -142,6 +142,7 @@ This allows NuGet to easily determine which file in the package should be run at The [.NET SDK docs](https://learn.microsoft.com/dotnet/core/tools/global-tools#check-the-author-and-statistics) clearly state, `.NET tools run in full trust. Don't install a .NET tool unless you trust the author`. This is an important consideration for plugin customers when installing NuGet plugins via .NET Tools in the future. +It's worth noting that this is not a new concern; it applies equally to existing NuGet plugins that use the `netfx` and `netcore` layout. ### Technical explanation From 233bcbfda59f3b294a50a612b7f8dfc648ffa0d4 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Wed, 3 Apr 2024 13:30:41 -0700 Subject: [PATCH 62/69] addressed part of the feedback --- ...get-authentication-plugins-dotnet-tools.md | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 352347f54..42190b549 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -34,7 +34,7 @@ The only requirement is that the .NET Tool command name should begin with `nuget - Upon installation, these global .NET tools are added to the PATH by the .NET SDK. This allows NuGet to easily determine which file in the package should be run at runtime. It does this by scanning the `PATH` environment variable for plugins whose file name begins with `nuget-plugin-`. -On Windows, NuGet will look for plugins with a `.exe` extension, whereas on Linux/Mac, it will look for plugins with the executable bit set. +On Windows, NuGet will look for plugins with a `.exe` or `.bat` extension, whereas on Linux/Mac, it will look for plugins with the executable bit set. These plugins are launched in a separate process, which aligns with the current design. ## Motivation @@ -159,23 +159,16 @@ This takes precedence over `NUGET_PLUGIN_PATHS`. If this environment variable is set, it overrides the convention-based discovery. It is ignored if either of the framework-specific variables is specified. -I propose the addition of a `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. -This variable will define the plugins, installed as .NET tools, to be used by both .NET Framework and .NET Core tooling. -It will take precedence over `NUGET_PLUGIN_PATHS`. +The `NUGET_NETFX_PLUGIN_PATHS` and `NUGET_NETCORE_PLUGIN_PATHS` environment variables were [introduced](https://github.com/NuGet/Home/issues/8151) to handle the differences in entry points between .NET Framework and .NET Core. +Since this specification proposes a new plugin discovery and execution mechanism (explained below), customers can use the existing `NUGET_PLUGIN_PATHS` environment variable to define the plugins used by NuGet Client tooling on both .NET Framework and .NET Core. -The plugins specified in the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable will be used regardless of whether the `NUGET_NETFX_PLUGIN_PATHS` or `NUGET_NETCORE_PLUGIN_PATHS` environment variables are set. -The primary reason for this is that plugins installed as .NET tools can be executed in both .NET Framework and .NET Core tooling. -If customers prefer to install NuGet plugins as a [tool-path global tool](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location), they can set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. -This variable should point to the location of the .NET Tool executable that the NuGet Client tooling can invoke when needed. +For example, if customers prefer to install NuGet plugins as a [tool-path global tool](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location), they can set the `NUGET_PLUGIN_PATHS` environment variable. +This variable should point to the location of the .NET Tool executable. NuGet should search for files whose names begin with `nuget-plugin-*` by scanning all the directories in the `PATH` environment variable. To ensure compatibility across different platforms, the implementation could convert all file names to lowercase before checking for a file. -- On Windows, NuGet should search for plugins using the `.exe` extension. -The `PATHEXT` environment variable in Windows specifies the file extensions that the operating system considers to be executable. -When you enter a command without specifying an extension, Windows will look for files with the extensions listed in `PATHEXT` in the directories specified by the `PATH` environment variable. -For example, if `PATHEXT` is set to `.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC`, and you enter the command `myprogram`, Windows will search for `myprogram.com`, `myprogram.exe`, `myprogram.bat`, and so on, in that order, in the directories listed in your `PATH` environment variable. -It will execute the first match it finds. +- On Windows, NuGet should search for plugins using the `.exe` or `.bat` extension. Given that .NET Tools are console applications, they should have the `.exe` extension on Windows to be considered valid plugins if the naming convention is followed. - Similarly, on other platforms, NuGet should search for plugins with the executable bit set to identify them as valid plugins. @@ -464,16 +457,20 @@ These package types can be found in the `.nuspec` metadata file of the generated ### Support for other extensions on Windows -- In the future, we could consider supporting extensions other than `.exe` configured in `PATHEXT` as executables. -To achieve this, NuGet would need to identify the correct executable or interpretter to run a particular file. -For example, to execute a PowerShell script, NuGet would need to launch the process as shown below. -However, the challenge lies in identifying the appropriate executable for each type of file, which is not an immediate requirement. +- In the future, we could consider supporting extensions other than `.exe` and `.bat` as executables, which are currently configured in the `PATHEXT` environment variable in Windows. +The `PATHEXT` variable specifies the file extensions that the operating system recognizes as executable. +When a command is entered without specifying an extension, Windows searches for files with extensions listed in `PATHEXT` in the directories specified by the `PATH` environment variable. +For example, if `PATHEXT` includes `.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC`, and the command is `myprogram`, Windows will search for `myprogram.com`, `myprogram.exe`, `myprogram.bat`, and so on, in that order, in the directories listed in the `PATH` variable. +It will execute the first match it finds. +To support other extensions, NuGet would need to identify the appropriate executable or interpreter to run a specific file. +For example, to execute a JavaScript file, NuGet would need to launch the process as shown below. +However, the challenge lies in determining the correct executable for each file type, which is not currently a priority. ```cs var processInfo = new System.Diagnostics.ProcessStartInfo { - FileName = "powershell.exe", - Arguments = @"& 'C:\Path\To\Your\Script.ps1'", + FileName = "node.exe", // Assuming "node.exe" is in the system PATH + Arguments = "nuget-plugin-credprovider.js", // Specify the JavaScript file to run RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true @@ -485,3 +482,12 @@ var process = System.Diagnostics.Process.Start(processInfo); string output = process.StandardOutput.ReadToEnd(); process.WaitForExit(); ``` + +- The `ShellExecute` API can launch files but doesn't support console input/output redirection, which is necessary for NuGet's communication with plugins. +On the other hand, the `CreateProcess` API can only launch native executables. +The `UseShellExecute` property determines how a process is started. +When set to `true`, the Windows `ShellExecute` function is used, involving the operating system shell. +This allows opening file types with their associated programs but loses access to input/output streams. +When set to `false`, the `CreateProcess` function is used to start the process directly from the executable file. +If `RedirectStandardOutput` is set to `true`, the process output can be directed to the `StandardOutput` stream. +This information is relevant in case we consider supporting other extensions in the future. From 93f04a61095398e46233923f9d252161ce81305f Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri <52756182+kartheekp-ms@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:35:16 -0700 Subject: [PATCH 63/69] feedback about DotnetToolSettings.xml Co-authored-by: Nikolche Kolev --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 42190b549..2cdb5a6a7 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -95,7 +95,7 @@ If there is an existing mechanism that works well, is familiar to customers, and - Another advantage of this established `.NET tools` approach is that plugin authors won't have to maintain separate code paths for .NET Framework and .NET Core runtimes. All these complexities are handled by the .NET SDK by providing a native shim upon the installation of the .NET tool. -Currently, at least to my understanding, the generated `.nupkg` will include a file named `DotnetToolSettings.xml` with additional metadata such as the command name, entry point, and runner. +The generated `.nupkg` will include a file named `DotnetToolSettings.xml` with additional metadata such as the command name, entry point, and runner. For example, the [dotnetsay tool](https://nuget.info/packages/dotnetsay/2.1.7) includes the following content: From 4c300cb8d525370dcf19960663ce1e5941cc8d01 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Wed, 3 Apr 2024 15:15:31 -0700 Subject: [PATCH 64/69] Added batch file support and removed it from the future possibility section --- ...get-authentication-plugins-dotnet-tools.md | 50 +++---------------- 1 file changed, 7 insertions(+), 43 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 2cdb5a6a7..ad9951a4a 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -168,8 +168,13 @@ This variable should point to the location of the .NET Tool executable. NuGet should search for files whose names begin with `nuget-plugin-*` by scanning all the directories in the `PATH` environment variable. To ensure compatibility across different platforms, the implementation could convert all file names to lowercase before checking for a file. -- On Windows, NuGet should search for plugins using the `.exe` or `.bat` extension. -Given that .NET Tools are console applications, they should have the `.exe` extension on Windows to be considered valid plugins if the naming convention is followed. +- On Windows, NuGet searches for plugins using the `.exe` or `.bat` extension. +Since .NET Tools are console applications, they should have the `.exe` extension on Windows to be considered valid plugins if the naming convention is followed. +However, NuGet does not support all possible extensions recognized by Windows in the `PATHEXT` configuration, such as `.vbs` and `.js` files. +Instead, NuGet runs batch files (.bat extension) in a separate process, allowing customers to invoke other scripts from the batch file. +When launching a process from C# code, if `Process.UseShellExecute` is set to `true`, Windows can launch files with their associated programs but does not support console input/output redirection, which is necessary for NuGet's communication with plugins. +When set to `false`, the `CreateProcess` function is used to start the process directly from the executable file. +This information is relevant in case we consider supporting other extensions in the future. - Similarly, on other platforms, NuGet should search for plugins with the executable bit set to identify them as valid plugins. This is because on Mac and Linux, executable files typically don't have extensions. @@ -205,10 +210,6 @@ See the `Future Possibilities` section for more details. - The discoverability of NuGet plugins published as .NET Tools is challenging for users because the `dotnet tool search` command only filters based on the `PackageType` being `DotnetTool`. Please refer to the `Future Possibilities` section for more related information. -- On Windows, NuGet searches for plugins using the `.exe` extension. -This is the case even though Windows recognizes all extensions configured in `PATHEXT` as executables. -For more information, please refer to the `Future Possibilities` section. - - The IPC (Inter-Process Communication) used by NuGet is custom-made, and it would be beneficial for plugin implementers if it were based on industry standards. This serves as a deterrent for developing NuGet plugins in non-.NET languages. See the `Future Possibilities` section for more details. @@ -454,40 +455,3 @@ This might be because the new package type, `CredentialProvider`, which I added If the `dotnet pack` command could generate a .nupkg for .NET Tool with multiple package types, we could introduce a new `dotnet nuget plugin search` command. This command would act as a wrapper for the `dotnet tool search` command, further refining the results based on the additional package type, such as `CredentialProvider`. These package types can be found in the `.nuspec` metadata file of the generated nupkg. - -### Support for other extensions on Windows - -- In the future, we could consider supporting extensions other than `.exe` and `.bat` as executables, which are currently configured in the `PATHEXT` environment variable in Windows. -The `PATHEXT` variable specifies the file extensions that the operating system recognizes as executable. -When a command is entered without specifying an extension, Windows searches for files with extensions listed in `PATHEXT` in the directories specified by the `PATH` environment variable. -For example, if `PATHEXT` includes `.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC`, and the command is `myprogram`, Windows will search for `myprogram.com`, `myprogram.exe`, `myprogram.bat`, and so on, in that order, in the directories listed in the `PATH` variable. -It will execute the first match it finds. -To support other extensions, NuGet would need to identify the appropriate executable or interpreter to run a specific file. -For example, to execute a JavaScript file, NuGet would need to launch the process as shown below. -However, the challenge lies in determining the correct executable for each file type, which is not currently a priority. - -```cs -var processInfo = new System.Diagnostics.ProcessStartInfo -{ - FileName = "node.exe", // Assuming "node.exe" is in the system PATH - Arguments = "nuget-plugin-credprovider.js", // Specify the JavaScript file to run - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true -}; - -var process = System.Diagnostics.Process.Start(processInfo); - -// To read the output (if any): -string output = process.StandardOutput.ReadToEnd(); -process.WaitForExit(); -``` - -- The `ShellExecute` API can launch files but doesn't support console input/output redirection, which is necessary for NuGet's communication with plugins. -On the other hand, the `CreateProcess` API can only launch native executables. -The `UseShellExecute` property determines how a process is started. -When set to `true`, the Windows `ShellExecute` function is used, involving the operating system shell. -This allows opening file types with their associated programs but loses access to input/output streams. -When set to `false`, the `CreateProcess` function is used to start the process directly from the executable file. -If `RedirectStandardOutput` is set to `true`, the process output can be directed to the `StandardOutput` stream. -This information is relevant in case we consider supporting other extensions in the future. From 828f474eb954db4227869fa10dfaaa52a9d2c983 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Wed, 3 Apr 2024 15:18:30 -0700 Subject: [PATCH 65/69] removed support for non-.net plugins from the spec as we added support for `.bat` files --- .../support-nuget-authentication-plugins-dotnet-tools.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index ad9951a4a..9e6a0ce32 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -440,14 +440,6 @@ In addition to that, if we ever plan to provide users with an option to manage t When the manifest file is saved in the root directory of a source code repository, a contributor can clone the repository and invoke a single .NET CLI command such as [`dotnet tool restore`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-restore) to install all of the tools listed in the manifest file. - Having NuGet plugins under the repository folder eliminates the need for NuGet to load plugins from the user profile. -### Support for NuGet plugins developed in non-.NET languages - -- The NuGet Client plugin code needs to be updated to utilize a standard RPC mechanism, such as StreamJsonRpc, in order to support NuGet plugins developed in languages other than .NET. -The IPC (Inter-Process Communication) used by NuGet is custom-made, and it would be beneficial for plugin implementers if it were based on industry standards. -This would allow plugin authors to reuse existing implementations for other languages. -While there is no technical barrier preventing the implementation of a client in a non-.NET language, there is an additional cost associated with reimplementing the plugin protocol. -This cost serves as a deterrent for writing non-.NET plugins. - ### Improve the discoverability of NuGet plugins published as .NET Tools - At present, the `dotnet pack` command disregards the `PackageType` property when the `PackAsTool` property is set to true. From 5cdeae45b0640a67db615b0e0402f06242bfdc81 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Wed, 3 Apr 2024 15:20:40 -0700 Subject: [PATCH 66/69] Add note about executing local tool using dotnet CLI --- .../2024/support-nuget-authentication-plugins-dotnet-tools.md | 1 + 1 file changed, 1 insertion(+) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 9e6a0ce32..dd2d64221 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -439,6 +439,7 @@ In addition to that, if we ever plan to provide users with an option to manage t - The advantage of local tools is that the .NET CLI uses manifest files to keep track of tools that are installed locally to a directory. When the manifest file is saved in the root directory of a source code repository, a contributor can clone the repository and invoke a single .NET CLI command such as [`dotnet tool restore`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-restore) to install all of the tools listed in the manifest file. - Having NuGet plugins under the repository folder eliminates the need for NuGet to load plugins from the user profile. +- However, the drawback of this approach is that the local tool must be executed using the dotnet CLI since it is not on the PATH. ### Improve the discoverability of NuGet plugins published as .NET Tools From 44a922654cbba6860b4343efe03e6efd66291d51 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Wed, 3 Apr 2024 15:38:58 -0700 Subject: [PATCH 67/69] address feedback --- ...get-authentication-plugins-dotnet-tools.md | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index dd2d64221..8b2ff590f 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -165,13 +165,19 @@ Since this specification proposes a new plugin discovery and execution mechanism For example, if customers prefer to install NuGet plugins as a [tool-path global tool](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location), they can set the `NUGET_PLUGIN_PATHS` environment variable. This variable should point to the location of the .NET Tool executable. +If none of the above environment variables are set, NuGet will default to the conventional method of discovering plugins from predefined directories. +In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/8b658e2eee6391936887b9fd1b39f7918d16a9cb/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L65-L77), the NuGet code looks for plugin files in the `netfx` directory when running on .NET Framework, and in the `netcore` directory when running on .NET Core. + +In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L79-L101), the NuGet code searches for plugin files in a plugin-specific subdirectory. +For example, in the case of the Azure Artifacts Credential Provider, the code looks for `*.exe` or `*.dll` files under the "CredentialProvider.Microsoft" directory. + NuGet should search for files whose names begin with `nuget-plugin-*` by scanning all the directories in the `PATH` environment variable. To ensure compatibility across different platforms, the implementation could convert all file names to lowercase before checking for a file. - On Windows, NuGet searches for plugins using the `.exe` or `.bat` extension. Since .NET Tools are console applications, they should have the `.exe` extension on Windows to be considered valid plugins if the naming convention is followed. However, NuGet does not support all possible extensions recognized by Windows in the `PATHEXT` configuration, such as `.vbs` and `.js` files. -Instead, NuGet runs batch files (.bat extension) in a separate process, allowing customers to invoke other scripts from the batch file. +Instead, NuGet will launch batch files (.bat extension) in a separate process, allowing customers to invoke other scripts from the batch file. When launching a process from C# code, if `Process.UseShellExecute` is set to `true`, Windows can launch files with their associated programs but does not support console input/output redirection, which is necessary for NuGet's communication with plugins. When set to `false`, the `CreateProcess` function is used to start the process directly from the executable file. This information is relevant in case we consider supporting other extensions in the future. @@ -190,7 +196,10 @@ On the other hand, when running on .NET, NuGet executes the `dotnet plugin-in.dl The plan is to slightly adjust this implementation. NuGet will launch the `.exe` file in a separate process when running on Windows, irrespective of whether the runtime is .NET Framework or .NET. For non-Windows platforms, if the file does not have an extension, it will launch the executable directly in a separate process. -If the file has a `.dll` extension, it will continue to execute the `dotnet nuget-plugin-name.dll` command in a separate process. +If the file has a `.dll` extension, it will continue to execute the `dotnet plugin-name.dll` command in a separate process. +It is worth noting that a plugin with a `.dll` extension will only be executed using the dotnet CLI if the file is located under the NuGet plugin-specific `netcore` subdirectory. +This is to support backward compatibility with the NuGet plugins discovered from predefined plugin directories. +As mentioned above, during PATH scanning, NuGet searches only for plugins with `.exe` or `.bat` extensions on Windows, and for files with the executable bit set on Linux/Mac. NuGet is designed to launch credential provider plugins sequentially, not simultaneously. This is to avoid multiple authentication prompts from different plugins at the same time. @@ -199,9 +208,6 @@ It launches one provider at a time, and if that provider successfully returns th ## Drawbacks -- There is some risk of the `dotnet tool` introducing breaking changes that could negatively impact NuGet. -If the `dotnet tool` started writing non-executable files into the directory, it would affect how NuGet discovers and runs plugins at runtime. - - This approach doesn't support the installation of NuGet plugins as a [.NET local tool](https://learn.microsoft.com/en-us/dotnet/core/tools/local-tools-how-to-use). The reason for this is that running a local tool requires the invocation of the `dotnet tool run` command. However, in the current design, we have considered launching the .NET tool executable in a separate process without relying on the said command. @@ -218,7 +224,7 @@ See the `Future Possibilities` section for more details. ### Installing NuGet plugins as tool-path .NET Tools -#### Functional explanation +**Functional explanation:** The proposed workflow for repositories that access private NuGet feeds, such as Azure DevOps, would be: @@ -273,16 +279,7 @@ However, NuGet plugins and .NET tools share the similarity of being console appl This approach is similar to the alternative design that [Andy Zivkovic](https://github.com/zivkan) kindly proposed in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. -#### Technical explanation - -If none of the above environment variables are set, NuGet will default to the conventional method of discovering plugins from predefined directories. -In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/8b658e2eee6391936887b9fd1b39f7918d16a9cb/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L65-L77), the NuGet code looks for plugin files in the `netfx` directory when running on .NET Framework, and in the `netcore` directory when running on .NET Core. This implementation should be updated to include the new `any` directory. -This directory should be added alongside `netcore` in the .NET code paths and `netfx` in the .NET Framework code paths, to ensure backward compatibility. - -In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L79-L101), the NuGet code searches for plugin files in a plugin-specific subdirectory. -For example, in the case of the Azure Artifacts Credential Provider, the code looks for `*.exe` or `*.dll` files under the "CredentialProvider.Microsoft" directory. -However, when .NET tools are installed in the `any` folder, executable files (like `.exe` files on Windows or files with the executable bit set on Linux & Mac) are placed directly in the `any` directory. -The remaining files are stored in a `.store` folder. This arrangement eliminates the need to search in subdirectories. +**Technical explanation:** NuGet will discover plugins in the new directory by enumerating files, without recursing into child directories. This is compatible with the layout that `dotnet tool install` uses, where packages are extracted to a `.store` subdirectory, and shims are put in the tool directory, one file per executable command. @@ -352,13 +349,13 @@ They will specify the plugin's path based on their operating system. We will mak However, we will still rely on the `dotnet tool` command under the hood, as mentioned above. Please note that these command names are subject to change based on feedback to the specification we are going to create in the near future. -#### Prior art +#### Background information - The `dotnet workload` command is separate and has its own set of sub-commands, including `install`, `uninstall`, and `list`. These sub-commands are wrappers for the corresponding `dotnet tool` sub-commands. Workloads are installed in a different location than .NET tools, which makes it easier for them to be discovered at runtime, addressing a problem that NuGet also faces. However, NuGet plugins and .NET tools share the similarity of being console applications. -This prior art will be helpful for us as we consider the implementation of the `dotnet nuget plugin install/uninstall/update/search` commands. +This background information will be helpful if we decide to implement the `dotnet nuget plugin install/uninstall/update/search` commands in the future. ### Specify the the authentication plugin in NuGet.Config file From 124d3f0938ebc5818f3cdac95a3ac62e31a6eb15 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Thu, 4 Apr 2024 11:44:26 -0700 Subject: [PATCH 68/69] Add support for developing NuGet plugins in non-.NET languages in the summary and cleaned up alternative spec section --- ...uget-authentication-plugins-dotnet-tools.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index 8b2ff590f..f745e4018 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -36,6 +36,9 @@ This allows NuGet to easily determine which file in the package should be run at It does this by scanning the `PATH` environment variable for plugins whose file name begins with `nuget-plugin-`. On Windows, NuGet will look for plugins with a `.exe` or `.bat` extension, whereas on Linux/Mac, it will look for plugins with the executable bit set. These plugins are launched in a separate process, which aligns with the current design. +This approach also supports the development of NuGet plugins in non-.NET languages. +As long as they follow the naming convention mentioned above, have either the .exe or .bat extension in Windows, and have an executable bit set in Linux/Mac, they will be considered valid plugins. +However, .NET Tools are the recommended approach for developing NuGet plugins. ## Motivation @@ -65,7 +68,7 @@ The use of .NET tools as a deployment mechanism has been a recurring request fro Currently, this solution works for the Windows .NET Framework. However, the goal is to extend this support cross-platform for all supported .NET runtimes. -The reasons why `.NET tools` were chosen as the deployment mechanism are mentioned below: +The reasons why `.NET tools` were chosen as the recommended deployment mechanism are mentioned below: - NuGet plugins are console applications. A `.NET tool` is a special NuGet package that contains a console application, which presents a natural fit. @@ -338,18 +341,7 @@ This variable should point to the location of the .NET Tool executable, which th - This approach doesn't support developing NuGet plugins in non-.NET languages. -#### Roadmap - -In terms of the implementation roadmap, I propose the following stages: - -1. Initially, plugin authors will publish NuGet plugins as .NET Tools. Consumers will install NuGet plugins using `dotnet tool` sub-commands. -They will specify the plugin's path based on their operating system. We will make the necessary code changes on the NuGet side to enable running the plugins installed as `.NET tools`. - -2. Subsequently, we will introduce a `dotnet nuget plugin` subcommand. This will allow users to `install, update, uninstall, and search` for plugins. -However, we will still rely on the `dotnet tool` command under the hood, as mentioned above. -Please note that these command names are subject to change based on feedback to the specification we are going to create in the near future. - -#### Background information +**Background information:** - The `dotnet workload` command is separate and has its own set of sub-commands, including `install`, `uninstall`, and `list`. These sub-commands are wrappers for the corresponding `dotnet tool` sub-commands. From 643a1ffc043106b8bceb25387f4ab1de8a142303 Mon Sep 17 00:00:00 2001 From: Kartheek Penagamuri Date: Thu, 25 Apr 2024 15:57:36 -0700 Subject: [PATCH 69/69] Make the content in "Installing NuGet plugins as tool-path .NET Tools" section a bit more concise --- ...t-nuget-authentication-plugins-dotnet-tools.md | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md index f745e4018..900307e80 100644 --- a/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md +++ b/accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md @@ -247,17 +247,13 @@ This means they are installed in a default directory, such as `%UserProfile%/.do - Global tools can be invoked from any directory on the machine without specifying their location. - One version of a tool is used for all directories on the machine. -However, NuGet cannot easily determine which tool is a NuGet plugin. +However, NuGet cannot easily determine which global .NET tool is a NuGet plugin. On the other hand, the `tool path` option allows us to install .NET tools as global tools, but with the binaries located in a specified location. This makes it easier to identify and invoke the appropriate tool for NuGet operations. -- The binaries are installed in a location that we specify while installing the tool. -- We can invoke the tool from the installation directory by providing the directory with the command name or by adding the directory to the PATH environment variable. -- One version of a tool is used for all directories on the machine. -The `tool path` option aligns well with the NuGet plugins architecture design, and hence, it is the recommended approach for installing and executing NuGet plugins. +The `tool path` option aligns well with the NuGet plugins architecture design. -By implementing this specification, we offer plugin authors the option to use .NET tools for plugin deployment. +This approach offers plugin authors the option to use .NET tools for plugin deployment. On the consumer side, these plugins will be installed as a `tool path` global tool. This eliminates the need to maintain separate versions for `.NET Framework` and `.NET Core`. -It also simplifies the installation process by removing the necessity for plugin authors to create subcommands like `codeartifact-creds install/uninstall`. We should consider adding `dotnet nuget plugin install/uninstall/update` commands to the .NET SDK as wrappers for the `dotnet tool install/uninstall/update` commands. This would simplify the installation process by eliminating the need for users to specify the NuGet plugin path, making the process more user-friendly and platform-independent. @@ -274,11 +270,6 @@ Given that this issue has been resolved in the .NET 8 SDK, and considering our p The `dotnet nuget credentialprovider search` command would allow customers to search for available Credential Providers that are published as .NET tools. However, I believe we need a separate specification for `dotnet nuget plugin install/uninstall/update/search` commands to fully understand all the options and the functional/technical details. -The `dotnet workload` command is separate and has its own set of sub-commands, including `install`, `uninstall`, and `list`. -These sub-commands are wrappers for the corresponding `dotnet tool` sub-commands. -Workloads are installed in a different location than .NET tools, which makes it easier for them to be discovered at runtime, addressing a problem that NuGet also faces. -However, NuGet plugins and .NET tools share the similarity of being console applications. - This approach is similar to the alternative design that [Andy Zivkovic](https://github.com/zivkan) kindly proposed in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`.