Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Registry - Potential NuGet user experience #3188

Closed
majastrz opened this issue Jun 12, 2021 · 4 comments
Closed

Registry - Potential NuGet user experience #3188

majastrz opened this issue Jun 12, 2021 · 4 comments
Labels
discussion This is a discussion issue and not a change proposal. story: registry

Comments

@majastrz
Copy link
Member

majastrz commented Jun 12, 2021

Goals

This is intended to provide an approximation of the Bicep registry experience IF we chose NuGet as the package manager and implement the integration. The existence of this issue DOES NOT indicate that NuGet was selected as the implementation of the Bicep Registry. (See #2128 for details about other candidates.)

Gallery Experience

TBD

TODO: Screenshots

Open Question: How much time do we expect users to spend browsing in the gallery?

Open Question: How do versioned and non-versioned package types work with search in the gallery?

Configuring package feeds

NuGet feeds are configured via the NuGet.config file that can be placed in a directory at or above the directory where the local Bicep modules are located. (Case-sensitive file systems require the exact casing as specified here.) The search logic is the same as with bicepconfig.json.

Without the NuGet.config file, a single feed pointing to nuget.org is assumed. This is equivalent to the following NuGet.config file:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
  </packageSources>
</configuration>

The file structure is documented at https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file.

Configuring private or local feeds is covered in their own sections below.

CLI

bicep build

Since Bicep modules can contain references to modules published to a package feed, the packages may not exist on the local system. If the Bicep file contains references to external modules, bicep build will perform NuGet restore before type checking and code generation to download the module packages from configured feeds to the local package cache. (In the compiler pipeline, the restore step occurs after parsing but before type checking is done.)

By default, the package feed is located at %USERPROFILE%\.nuget\packages on Windowsand ~/.nuget/packages on Linux and Mac. The location can be customized via the NUGET_PACKAGES environment variable or the globalPackagesFolder setting in NuGet.config.

Open Question: Bicep needs the path to the local cache to resolve module refs. Can the NuGet SDK provide this info for us or do we have do it ourselves?

bicep restore

In certain usecases (Docker and some CI systems), the restore and build operations need to be separated. This can be accomplished as follows:

  1. bicep restore
  2. bicep build --no-restore main.bicep

Open Question: What verb do we use for this command? (NPM uses install and ci. Go uses get and install .Net/MSBuild use restore. Terraform uses init.)

Reference module from a package

To reference a module from a package, the opens a new or existing Bicep file in VS code and types one or more declarations like the following:

module mod 'Azure.Bicep.ServiceFabricCluster@1.0.0' = {
  ...
}

Open Question: The syntax proposals for referencing external modules are tracked in #3186.

Completions are available as the user is typing the module reference:

  • With the above syntax, the completions for local modules and external modules are mixed together.
  • Committing a completion for a local module behaves the same as today.
  • Committing the completion for an external package ID triggers a secondary completion to choose an available version. (This is similar to resource type completions.)
  • Package version completions are sorted by version in descending order with the latest version preselected.

Open Question: Our ability to produce completions for packages is heavily dependent on APIs for listing all packages with type == bicep. Requires discussion with NuGet team.

If the current module has any external module references, the language server queues up a nuget restore action in the background. The background restore will check the package cache for the package ID and version. If missing, the package will be downloaded from the configured feeds.

The background restore does not happen instantly. Until the package is restored, accurate type information is not available:

  • The language server falls back to the widest possible type for the given context. (any or object)
  • A warning is shown on the module ref indicating that the package has not yet been restored.
  • Property name and property access completions involving this particular module are unavailable.
  • The language server remains otherwise functional.

If/when restore fails:

  • An error is shown on the module ref explaing the type of failure. (We may pass through NUxxxx errors from NuGet.)
  • The language server remains otherwise functional.

Once the background restore operation is finished successfully, the language server recompiles the module to make use of the downloaded type info (we can reuse the parse tree). Property name and property access completions are now available for the module.

Open Question: Where do we expose the NuGet logs? In VS code "Output" pane under "Bicep Package Manager"?

Open Question: Do we need any custom throttling or is this already baked into the NuGet client library?

Setup private package feed

The simplest way to create a private package feed is to create one using your favorite feed provider and configure it in NuGet.config.
image

The following NuGet.config uses both a public and private feed:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="myFeed" value="https://example.pkgs.visualstudio.com/One/_packaging/My-Private-Feed/nuget/v3/index.json" />
  </packageSources>
</configuration>

The NuGet.config edit can also be scripted via the nuget sources add comment documented at https://docs.microsoft.com/en-us/nuget/reference/cli-reference/cli-ref-sources.

Private package feeds require the user to authenticate with them before packages can be restored. NuGet supports two mechanisms for doing so:

  • Credentials in NuGet.config
  • NuGet credential providers

Credentials in NuGet.config

One option is to store the feed credentials in the NuGet.config file.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <packageSources>
        <clear />
        <add key="myFeed" value="https://example.pkgs.visualstudio.com/One/_packaging/My-Private-Feed/nuget/v3/index.json" />
    </packageSources>
    <packageSourceCredentials>
        <myFeed>
            <add key="Username" value="USERNAME" />
            <add key="ClearTextPassword" value="PASSWORD" />
        </myFeed>
    </packageSourceCredentials>
</configuration>

This approach is not recommended because most users store NuGet.config files in a version-controlled repository and committing secrets to a version control system is a worst practice.

NuGet does have the ability to store encrypted feed passwords in the NuGet.config file, but the functionality is limited to Windows machines. The NuGet.config file with such credentials is limited to a single user and a single machine, which prevents sharing scenarios.

GitHub Packages documents this approach at https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-nuget-registry#authenticating-to-github-packages.

NuGet credential provider

NuGet credential providers allow a NuGet client to access authenticated package feeds. The extensibility point is documented at https://docs.microsoft.com/en-us/nuget/reference/extensibility/nuget-cross-platform-authentication-plugin. Microsoft provides a credential provider that works with Azure Artifacts. The source is available at https://github.com/Microsoft/artifacts-credprovider.

The one-time setup step is to drop the Azure Artifacts credential provider to %UserProfile%/.nuget/plugins/netcore on the machine. (Alternatives for NuGet plugin discovery are explained at https://docs.microsoft.com/en-us/nuget/reference/extensibility/nuget-cross-platform-plugins#plugin-installation-and-discovery).

From that point on, the provider will popup an AAD login prompt and automate the creation of a Personal Access Token (a time-limited password) behind the scenes. The login prompt is only required when the PAT is missing or expired.

Open Question: Is there a credential provider for GitHub packages?

Open Question: How to simplify setup? Can we bake in the credential provider into our client? Docs hint that it is possible, but is it advisable to do?

Create package

A package containing a Bicep module can be created via the bicep pack <file> command. The command performs the following operations:

  • NuGet restore to populate the local package cache (can opt-out with --no-restore)
  • Validate module (including configured linter rules)
  • Create package

The package will be created as follows:

  • The main module is included as a content file.
  • The local modules referenced by the main module are included in the package as content files.
  • External modules referenced by the main and local modules become dependencies of the package.

A more detailed discussion about package contents is tracked in #3266.

Open Question: The user needs to configure the package metadata such as id, version, authors, description to create a package. Syntax proposal is in #3187.

Open Question: What about local modules above the directory of the main module. Do we block them? Do we rewrite the file paths?

Open Question: Should bicep build create packages? Should it be opt-in? Via a switch or bicepconfig.json?

Open Question: What is the recommended package file/directory structure?

Open Question: How to set Bicep as the package type?

Open Question: Can a single package depend on multiple versions of the same package?

Open Question: How to encode min/max Bicep version in the package? Should we use the package type versioning for this? (https://docs.microsoft.com/en-us/nuget/create-packages/set-package-type?tabs=dotnet)

Open Question: When referencing a module, can we avoid taking a dependency on matching types version, compiler settings, etc? We could do that if we package JSON instead of Bicep in packages with additional metadata to preserve validation quality parity with local modules.

Sign package

Package signing can be done via the nuget sign command. See https://docs.microsoft.com/en-us/nuget/create-packages/sign-a-package for more details. Signature verification can be done via the nuget verify command.

Open Question: How does package signature validation work during restore?

Open Question: Should we require signing all packages? Does nuget.org sign everything?

Validate package

If you'd like to validate a package that was produced during local development without publishing the package to a remote feed (public or private), you can create a local "feed".

This is accomplished by creating a directory somewhere in the local file system and adding a package source to NuGet.config that points to the local path. The example is specific to Windows, but the approach is the same on Linux and Mac.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="local" value="D:\MyLocalNugetPackages" />
  </packageSources>
</configuration>

Note: local is an arbitrary identifier and has no special meaning.

For packages to be picked up from the local feed by the restore operation, the .nupkg files need to be dropped into the local directory path specified in the value field. There is no authentication requirement with local feeds (other than file system permissions).

The NuGet.config edit can also be scripted via the nuget sources add comment documented at https://docs.microsoft.com/en-us/nuget/reference/cli-reference/cli-ref-sources.

Publish package to a feed

Publishing packages to nuget.org or a private feed is done via the nuget push command.

Publishing to nuget.org: https://docs.microsoft.com/en-us/nuget/nuget-org/publish-a-package
Publishing to private feed: https://docs.microsoft.com/en-us/nuget/hosting-packages/overview

@majastrz majastrz added discussion This is a discussion issue and not a change proposal. story: registry labels Jun 12, 2021
@ghost ghost added the Needs: Triage 🔍 label Jun 12, 2021
@rouke-broersma
Copy link

rouke-broersma commented Jul 8, 2021

The current NuGet non-interactive login scenario is pretty awkward imo. For non-interactive login you need to put a json object into an environment variable. See for example: https://somakdas.medium.com/private-nuget-package-restore-in-docker-build-azure-devops-431fdfeae557

If NuGet is chosen I think you should put a lot of effort into creating a non-interactive login scenario that is less awkward than with dotnet/nuget cli.

@majastrz
Copy link
Member Author

majastrz commented Jul 9, 2021

@rouke-broersma I agree that it is awkward. What would your ideal experience look like?

@rouke-broersma
Copy link

Ideally I would be able to set 1 environment variable with only the token and/or pass the token with the Bicep restore command. This token would be used by all Bicep restores. Optionally I could also set a different environment variable/command line argument with the feed url for the token. Or the feed url can be included in the module name as is common with docker. For a multi-registry scenario I would suggest a config file instead of an environment variable. Ideally the config file would list only the feed urls and the environment variable name that would contain the token for that url. Then the user is free to choose the environment variable names that contains their tokens. This also negates the need for cramming a json list into an environment variable.

@majastrz
Copy link
Member Author

Closing since we decided not to use NuGet for this (See #2128 for details)

@ghost ghost locked as resolved and limited conversation to collaborators May 27, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
discussion This is a discussion issue and not a change proposal. story: registry
Projects
None yet
Development

No branches or pull requests

2 participants