From 657444f82e38dc8e069a6b583273bae3bf7f32e7 Mon Sep 17 00:00:00 2001 From: "Stephen Weatherford (MSFT)" Date: Wed, 5 Feb 2025 15:51:01 -0800 Subject: [PATCH] Support completions for private ACR modules --- .../BuildCommandTests.cs | 2 +- .../BuildParamsCommandTests.cs | 2 +- .../LintCommandTests.cs | 5 +- .../PublishCommandTests.cs | 12 +- .../PublishProviderCommandTests.cs | 2 +- .../RestoreCommandTests.cs | 16 +- src/Bicep.Cli.IntegrationTests/TestBase.cs | 15 +- ...UseRecentModuleVersionsIntegrationTests.cs | 30 +- .../packages.lock.json | 7 +- src/Bicep.Cli.UnitTests/packages.lock.json | 7 +- .../Helpers/ServiceCollectionExtensions.cs | 4 +- src/Bicep.Cli/packages.lock.json | 7 +- .../AzTypesViaRegistryTests.cs | 2 +- .../MsGraphTypesViaRegistryTests.cs | 2 +- .../packages.lock.json | 148 ++-- src/Bicep.Core.Samples/packages.lock.json | 148 ++-- .../BicepTestConstants.cs | 1 - .../UseRecentModuleVersionsRuleTests.cs | 41 +- .../Features/OverriddenFeatureProvider.cs | 1 - .../IServiceCollectionExtensions.cs | 5 +- .../PublicModuleIndexHttpClientMocks.cs | 10 +- .../Mock/Registry/RegistryCatalogMocks.cs | 149 ++++ .../BaseModuleMetadataProviderTests.cs | 76 ++ .../PrivateAcrModuleMetadataProviderTests.cs | 381 ++++++++++ .../PublicModuleMetadataProviderTests.cs | 149 ++-- .../Registry/Catalog/RegistryCatalogTests.cs | 110 +++ .../Registry/FakeContainerRegistryClient.cs | 71 ++ .../Registry/FakeRegistryBlobClient.cs | 2 +- .../Utils/OciRegistryHelper.cs | 8 +- .../Utils/RegistryHelper.cs | 15 +- .../Utils/ServiceBuilderExtensions.cs | 1 - ...stContainerRegistryClientFactoryBuilder.cs | 28 +- src/Bicep.Core.UnitTests/packages.lock.json | 148 ++-- .../Analyzers/Linter/LinterAnalyzer.cs | 2 +- .../Rules/UseRecentModuleVersionsRule.cs | 14 +- src/Bicep.Core/Bicep.Core.csproj | 1 + .../AnalyzersConfigurationExtensions.cs | 14 + .../Registry/AzureContainerRegistryManager.cs | 148 +++- .../Catalog/IPublicModuleMetadataProvider.cs | 13 + .../Catalog/IRegistryModuleCatalog.cs | 19 + .../BaseModuleMetadataProvider.cs | 211 ++++++ .../IRegistryModuleMetadataProvider.cs | 45 ++ ...egistryModuleMetadataProviderExtensions.cs | 13 + .../Implementation/MightBeLazyAsync.cs | 64 ++ ...PrivateAcrModuleMetadataProviderFactory.cs | 16 + .../PrivateAcrModuleMetadataProvider.cs | 135 ++++ ...PrivateAcrModuleMetadataProviderFactory.cs | 20 + .../IPublicModuleIndexHttpClient.cs | 14 + .../PublicModuleIndexHttpClient.cs} | 37 +- .../PublicModuleMetadataHttpClient.cs} | 13 +- .../PublicModuleMetadataProvider.cs | 57 ++ ...yCatalogExtensionsForIServiceCollection.cs | 31 + .../Implementation/RegistryModuleCatalog.cs | 61 ++ .../Catalog/RegistryMetadataDetails.cs | 14 + .../Catalog/RegistryModuleMetadata.cs | 78 ++ .../Catalog/RegistryModuleVersionMetadata.cs | 20 + .../ContainerRegistryClientFactory.cs | 30 +- .../DefaultArtifactRegistryProvider.cs | 2 +- .../IContainerRegistryClientFactory.cs | 11 +- src/Bicep.Core/Registry/ModuleDispatcher.cs | 2 +- .../Oci/IOciArtifactAddressComponents.cs | 2 +- .../Registry/Oci/IOciArtifactReference.cs | 3 + .../Registry/OciArtifactRegistry.cs | 10 +- .../IPublicModuleMetadataProvider.cs | 31 - .../PublicModuleMetadataProvider.cs | 221 ------ src/Bicep.Core/packages.lock.json | 15 + .../packages.lock.json | 148 ++-- .../packages.lock.json | 148 ++-- src/Bicep.Decompiler/packages.lock.json | 15 + .../CompletionTests.cs | 488 ++++++++---- .../Helpers/ServerRequestHelper.cs | 17 +- .../ParamsCompletionTests.cs | 4 +- .../packages.lock.json | 1 + .../BicepCompletionProviderTests.cs | 3 +- .../ModuleReferenceCompletionProviderTests.cs | 560 +++++++++----- .../packages.lock.json | 1 + .../Completions/BicepCompletionProvider.cs | 7 +- .../Completions/CompletionItemBuilder.cs | 12 + .../Completions/CompletionItemExtensions.cs | 29 + .../Completions/ICompletionProvider.cs | 5 + .../IModuleReferenceCompletionProvider.cs | 5 +- .../ModuleReferenceCompletionProvider.cs | 692 +++++++++--------- .../Handlers/BicepCompletionHandler.cs | 4 +- .../IServiceCollectionExtensions.cs | 4 +- .../AzureContainerRegistriesProvider.cs | 10 +- .../IAzureContainerRegistriesProvider.cs | 2 +- src/Bicep.LangServer/Server.cs | 4 +- .../Telemetry/BicepTelemetryEvent.cs | 22 +- .../Telemetry/TelemetryConstants.cs | 1 + src/Bicep.LangServer/packages.lock.json | 148 ++-- .../packages.lock.json | 148 ++-- src/Bicep.Local.Deploy/packages.lock.json | 15 + .../packages.lock.json | 15 + .../packages.lock.json | 148 ++-- .../packages.lock.json | 148 ++-- .../packages.lock.json | 148 ++-- .../IServiceCollectionExtensions.cs | 4 +- .../packages.lock.json | 15 + src/Bicep.Tools.Benchmark/packages.lock.json | 1 + .../IServiceCollectionExtensions.cs | 4 +- src/Bicep.Wasm/packages.lock.json | 15 + 101 files changed, 4024 insertions(+), 1939 deletions(-) create mode 100644 src/Bicep.Core.UnitTests/Mock/Registry/RegistryCatalogMocks.cs create mode 100644 src/Bicep.Core.UnitTests/Registry/Catalog/BaseModuleMetadataProviderTests.cs create mode 100644 src/Bicep.Core.UnitTests/Registry/Catalog/PrivateAcrModuleMetadataProviderTests.cs rename src/Bicep.Core.UnitTests/Registry/{PublicRegistry => Catalog}/PublicModuleMetadataProviderTests.cs (88%) create mode 100644 src/Bicep.Core.UnitTests/Registry/Catalog/RegistryCatalogTests.cs create mode 100644 src/Bicep.Core.UnitTests/Registry/FakeContainerRegistryClient.cs create mode 100644 src/Bicep.Core/Registry/Catalog/IPublicModuleMetadataProvider.cs create mode 100644 src/Bicep.Core/Registry/Catalog/IRegistryModuleCatalog.cs create mode 100644 src/Bicep.Core/Registry/Catalog/Implementation/BaseModuleMetadataProvider.cs create mode 100644 src/Bicep.Core/Registry/Catalog/Implementation/IRegistryModuleMetadataProvider.cs create mode 100644 src/Bicep.Core/Registry/Catalog/Implementation/IRegistryModuleMetadataProviderExtensions.cs create mode 100644 src/Bicep.Core/Registry/Catalog/Implementation/MightBeLazyAsync.cs create mode 100644 src/Bicep.Core/Registry/Catalog/Implementation/PrivateRegistries/IPrivateAcrModuleMetadataProviderFactory.cs create mode 100644 src/Bicep.Core/Registry/Catalog/Implementation/PrivateRegistries/PrivateAcrModuleMetadataProvider.cs create mode 100644 src/Bicep.Core/Registry/Catalog/Implementation/PrivateRegistries/PrivateAcrModuleMetadataProviderFactory.cs create mode 100644 src/Bicep.Core/Registry/Catalog/Implementation/PublicRegistries/IPublicModuleIndexHttpClient.cs rename src/Bicep.Core/Registry/{PublicRegistry/IPublicModuleIndexClient.cs => Catalog/Implementation/PublicRegistries/PublicModuleIndexHttpClient.cs} (52%) rename src/Bicep.Core/Registry/{PublicRegistry/PublicModuleMetadataClient.cs => Catalog/Implementation/PublicRegistries/PublicModuleMetadataHttpClient.cs} (76%) create mode 100644 src/Bicep.Core/Registry/Catalog/Implementation/PublicRegistries/PublicModuleMetadataProvider.cs create mode 100644 src/Bicep.Core/Registry/Catalog/Implementation/RegistryCatalogExtensionsForIServiceCollection.cs create mode 100644 src/Bicep.Core/Registry/Catalog/Implementation/RegistryModuleCatalog.cs create mode 100644 src/Bicep.Core/Registry/Catalog/RegistryMetadataDetails.cs create mode 100644 src/Bicep.Core/Registry/Catalog/RegistryModuleMetadata.cs create mode 100644 src/Bicep.Core/Registry/Catalog/RegistryModuleVersionMetadata.cs delete mode 100644 src/Bicep.Core/Registry/PublicRegistry/IPublicModuleMetadataProvider.cs delete mode 100644 src/Bicep.Core/Registry/PublicRegistry/PublicModuleMetadataProvider.cs create mode 100644 src/Bicep.LangServer/Completions/CompletionItemExtensions.cs diff --git a/src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs b/src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs index cdb5f0e7a49..f24cfc29a96 100644 --- a/src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs @@ -193,7 +193,7 @@ public async Task Build_Valid_SingleFile_WithDigestReference_ShouldSucceed() var client = new FakeRegistryBlobClient(); var clientFactory = StrictMock.Of(); - clientFactory.Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), registryUri, repository)).Returns(client); + clientFactory.Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), registryUri, repository)).Returns(client); var templateSpecRepositoryFactory = BicepTestConstants.TemplateSpecRepositoryFactory; diff --git a/src/Bicep.Cli.IntegrationTests/BuildParamsCommandTests.cs b/src/Bicep.Cli.IntegrationTests/BuildParamsCommandTests.cs index 25e34542c5e..763ef422434 100644 --- a/src/Bicep.Cli.IntegrationTests/BuildParamsCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/BuildParamsCommandTests.cs @@ -492,7 +492,7 @@ public async Task Build_bicepparam_should_fail_with_error_diagnostics_for_regist var clientFactory = StrictMock.Of(); clientFactory - .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), new Uri("https://mockregistry.io"), "parameters/basic")) + .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), new Uri("https://mockregistry.io"), "parameters/basic")) .Returns(client.Object); var templateSpecRepositoryFactory = StrictMock.Of(); diff --git a/src/Bicep.Cli.IntegrationTests/LintCommandTests.cs b/src/Bicep.Cli.IntegrationTests/LintCommandTests.cs index 19be4883459..738e71596e3 100644 --- a/src/Bicep.Cli.IntegrationTests/LintCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/LintCommandTests.cs @@ -6,11 +6,12 @@ using Bicep.Cli.UnitTests; using Bicep.Core.Configuration; using Bicep.Core.Registry; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog; using Bicep.Core.Samples; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Mock; using Bicep.Core.UnitTests.Mock.Registry; +using Bicep.Core.UnitTests.Mock.Registry.Catalog; using Bicep.Core.UnitTests.Registry; using Bicep.Core.UnitTests.Utils; using FluentAssertions; @@ -113,7 +114,7 @@ public async Task Lint_Valid_SingleFile_WithDigestReference_ShouldSucceed() var client = new FakeRegistryBlobClient(); var clientFactory = StrictMock.Of(); - clientFactory.Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), registryUri, repository)).Returns(client); + clientFactory.Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), registryUri, repository)).Returns(client); var settings = new InvocationSettings(new(TestContext, RegistryEnabled: true), clientFactory.Object, BicepTestConstants.TemplateSpecRepositoryFactory); diff --git a/src/Bicep.Cli.IntegrationTests/PublishCommandTests.cs b/src/Bicep.Cli.IntegrationTests/PublishCommandTests.cs index 3ff7e763aac..0e17f54c05a 100644 --- a/src/Bicep.Cli.IntegrationTests/PublishCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/PublishCommandTests.cs @@ -163,7 +163,7 @@ public async Task Publish_AllValidDataSets_ShouldSucceed(string testName, DataSe var compiledFilePath = Path.Combine(outputDirectory, DataSet.TestFileMainCompiled); // mock client factory caches the clients - var testClient = (FakeRegistryBlobClient)clientFactory.CreateAuthenticatedBlobClient(BicepTestConstants.BuiltInConfiguration, registryUri, repository); + var testClient = (FakeRegistryBlobClient)clientFactory.CreateAuthenticatedBlobClient(BicepTestConstants.BuiltInConfiguration.Cloud, registryUri, repository); var settings = new InvocationSettings(new(TestContext, RegistryEnabled: true), clientFactory, templateSpecRepositoryFactory); @@ -262,7 +262,7 @@ public async Task Publish_ValidArmTemplateFile_AllValidDataSets_ShouldSucceed(Da var compiledFilePath = Path.Combine(outputDirectory, DataSet.TestFileMainCompiled); // mock client factory caches the clients - var testClient = (FakeRegistryBlobClient)clientFactory.CreateAuthenticatedBlobClient(BicepTestConstants.BuiltInConfiguration, registryUri, repository); + var testClient = (FakeRegistryBlobClient)clientFactory.CreateAuthenticatedBlobClient(BicepTestConstants.BuiltInConfiguration.Cloud, registryUri, repository); var settings = new InvocationSettings(new(TestContext, RegistryEnabled: true), clientFactory, templateSpecRepositoryFactory); @@ -316,7 +316,7 @@ public async Task Publish_ValidArmTemplateFile_WithSource_ShouldFail() var compiledFilePath = Path.Combine(outputDirectory, DataSet.TestFileMainCompiled); // mock client factory caches the clients - var testClient = (FakeRegistryBlobClient)clientFactory.CreateAuthenticatedBlobClient(BicepTestConstants.BuiltInConfiguration, registryUri, repository); + var testClient = (FakeRegistryBlobClient)clientFactory.CreateAuthenticatedBlobClient(BicepTestConstants.BuiltInConfiguration.Cloud, registryUri, repository); var settings = new InvocationSettings(new(TestContext, RegistryEnabled: true), clientFactory, templateSpecRepositoryFactory); @@ -344,7 +344,7 @@ public async Task Publish_RequestFailedException_ShouldFail() var clientFactory = StrictMock.Of(); clientFactory - .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), new Uri("https://fake"), "fake")) + .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), new Uri("https://fake"), "fake")) .Returns(client.Object); var templateSpecRepositoryFactory = StrictMock.Of(); @@ -376,7 +376,7 @@ public async Task Publish_AggregateExceptionWithInnerRequestFailedExceptions_Sho var clientFactory = StrictMock.Of(); clientFactory - .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), new Uri("https://fake"), "fake")) + .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), new Uri("https://fake"), "fake")) .Returns(client.Object); var templateSpecRepositoryFactory = StrictMock.Of(); @@ -452,7 +452,7 @@ public async Task Publish_BicepModule_WithDescriptionAndDocUri_ShouldPlaceDescri var repository = $"test/{moduleName}".ToLowerInvariant(); var clientFactory = RegistryHelper.CreateMockRegistryClient(new RepoDescriptor(registryStr, repository, ["v1"])); - var blobClient = (FakeRegistryBlobClient)clientFactory.CreateAuthenticatedBlobClient(BicepTestConstants.BuiltInConfiguration, registryUri, repository); + var blobClient = (FakeRegistryBlobClient)clientFactory.CreateAuthenticatedBlobClient(BicepTestConstants.BuiltInConfiguration.Cloud, registryUri, repository); await RegistryHelper.PublishModuleToRegistryAsync( new ServiceBuilder(), diff --git a/src/Bicep.Cli.IntegrationTests/PublishProviderCommandTests.cs b/src/Bicep.Cli.IntegrationTests/PublishProviderCommandTests.cs index 6a2491e3048..10a61b764d4 100644 --- a/src/Bicep.Cli.IntegrationTests/PublishProviderCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/PublishProviderCommandTests.cs @@ -34,7 +34,7 @@ public async Task Publish_extension_should_succeed() var version = "0.0.1"; var clientFactory = RegistryHelper.CreateMockRegistryClient(new RepoDescriptor(registryStr, repository, ["tag"])); - var fakeBlobClient = (FakeRegistryBlobClient)clientFactory.CreateAuthenticatedBlobClient(BicepTestConstants.BuiltInConfiguration, registryUri, repository); + var fakeBlobClient = (FakeRegistryBlobClient)clientFactory.CreateAuthenticatedBlobClient(BicepTestConstants.BuiltInConfiguration.Cloud, registryUri, repository); var indexPath = Path.Combine(outputDirectory, "index.json"); var settings = new InvocationSettings(new(TestContext, RegistryEnabled: true), clientFactory, BicepTestConstants.TemplateSpecRepositoryFactory); diff --git a/src/Bicep.Cli.IntegrationTests/RestoreCommandTests.cs b/src/Bicep.Cli.IntegrationTests/RestoreCommandTests.cs index ff23996c51b..caf35f5adee 100644 --- a/src/Bicep.Cli.IntegrationTests/RestoreCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/RestoreCommandTests.cs @@ -118,13 +118,13 @@ public async Task Restore_ShouldSucceedWithAnonymousClient(string testName, Data // this will force fallback to the anonymous client var clientFactoryForRestore = StrictMock.Of(); clientFactoryForRestore - .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(clientWithCredentialUnavailable.Object); // anonymous client creation will redirect to the working client factory containing mock published modules clientFactoryForRestore - .Setup(m => m.CreateAnonymousBlobClient(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(clientFactory.CreateAnonymousBlobClient); + .Setup(m => m.CreateAnonymousBlobClient(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(clientFactory.CreateAnonymousBlobClient); var settings = new InvocationSettings(new(TestContext, RegistryEnabled: dataSet.HasExternalModules), clientFactoryForRestore.Object, templateSpecRepositoryFactory); TestContext.WriteLine($"Cache root = {settings.FeatureOverrides!.CacheRootDirectory}"); @@ -310,7 +310,7 @@ public async Task Restore_With_Force_Should_Overwrite_Existing_Cache(bool publis var client = new FakeRegistryBlobClient(); var clientFactory = StrictMock.Of(); - clientFactory.Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), registryUri, repository)).Returns(client); + clientFactory.Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), registryUri, repository)).Returns(client); var templateSpecRepositoryFactory = BicepTestConstants.TemplateSpecRepositoryFactory; @@ -431,7 +431,7 @@ public async Task Restore_ByDigest_ShouldSucceed(bool publishSource) var client = new FakeRegistryBlobClient(); var clientFactory = StrictMock.Of(); - clientFactory.Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), registryUri, repository)).Returns(client); + clientFactory.Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), registryUri, repository)).Returns(client); var templateSpecRepositoryFactory = BicepTestConstants.TemplateSpecRepositoryFactory; @@ -518,7 +518,7 @@ public async Task Restore_AggregateExceptionWithInnerRequestFailedExceptions_Sho var clientFactory = StrictMock.Of(); clientFactory - .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), new Uri("https://fake"), "fake")) + .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), new Uri("https://fake"), "fake")) .Returns(client.Object); var templateSpecRepositoryFactory = StrictMock.Of(); @@ -550,7 +550,7 @@ public async Task Restore_RequestFailedException_ShouldFail() var clientFactory = StrictMock.Of(); clientFactory - .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), new Uri("https://fake"), "fake")) + .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), new Uri("https://fake"), "fake")) .Returns(client.Object); var templateSpecRepositoryFactory = StrictMock.Of(); @@ -579,7 +579,7 @@ public async Task Restore_bicepparam_should_fail_with_error_diagnostics_for_regi var clientFactory = StrictMock.Of(); clientFactory - .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), new Uri("https://mockregistry.io"), "parameters/basic")) + .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), new Uri("https://mockregistry.io"), "parameters/basic")) .Returns(client.Object); var templateSpecRepositoryFactory = StrictMock.Of(); diff --git a/src/Bicep.Cli.IntegrationTests/TestBase.cs b/src/Bicep.Cli.IntegrationTests/TestBase.cs index b5177738048..4a448a2c2ed 100644 --- a/src/Bicep.Cli.IntegrationTests/TestBase.cs +++ b/src/Bicep.Cli.IntegrationTests/TestBase.cs @@ -7,7 +7,8 @@ using Bicep.Core.Extensions; using Bicep.Core.FileSystem; using Bicep.Core.Registry; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog.Implementation; +using Bicep.Core.Registry.Catalog.Implementation.PublicRegistries; using Bicep.Core.Text; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Features; @@ -23,14 +24,14 @@ namespace Bicep.Cli.IntegrationTests { public abstract class TestBase : Bicep.Core.UnitTests.TestBase { - private static BicepCompiler CreateCompiler(IContainerRegistryClientFactory clientFactory, ITemplateSpecRepositoryFactory templateSpecRepositoryFactory, IPublicModuleIndexClient? moduleMetadataClient) + private static BicepCompiler CreateCompiler(IContainerRegistryClientFactory clientFactory, ITemplateSpecRepositoryFactory templateSpecRepositoryFactory, IPublicModuleIndexHttpClient? moduleMetadataClient) => ServiceBuilder.Create( services => { services .AddSingleton(clientFactory) .AddSingleton(templateSpecRepositoryFactory) - .AddSingleton(); + .AddRegistryCatalogServices(); IServiceCollectionExtensions.AddMockHttpClientIfNotNull(services, moduleMetadataClient); } @@ -49,21 +50,21 @@ protected record InvocationSettings public IContainerRegistryClientFactory ClientFactory { get; init; } public ITemplateSpecRepositoryFactory TemplateSpecRepositoryFactory { get; init; } public IEnvironment? Environment { get; init; } - public IPublicModuleIndexClient ModuleMetadataClient { get; init; } + public IPublicModuleIndexHttpClient ModuleMetadataClient { get; init; } public InvocationSettings( FeatureProviderOverrides? FeatureOverrides = null, IContainerRegistryClientFactory? ClientFactory = null, ITemplateSpecRepositoryFactory? TemplateSpecRepositoryFactory = null, IEnvironment? Environment = null, - IPublicModuleIndexClient? ModuleMetadataClient = null) + IPublicModuleIndexHttpClient? ModuleMetadataClient = null) { this.FeatureOverrides = FeatureOverrides; this.ClientFactory = ClientFactory ?? Repository.Create().Object; this.TemplateSpecRepositoryFactory = TemplateSpecRepositoryFactory ?? Repository.Create().Object; this.Environment = Environment; - this.ModuleMetadataClient = ModuleMetadataClient ?? StrictMock.Of().Object; + this.ModuleMetadataClient = ModuleMetadataClient ?? StrictMock.Of().Object; } } @@ -109,7 +110,7 @@ protected static void AssertNoErrors(string error) } } - protected static async Task> GetAllDiagnostics(string bicepFilePath, IContainerRegistryClientFactory clientFactory, ITemplateSpecRepositoryFactory templateSpecRepositoryFactory, IPublicModuleIndexClient? moduleMetadataClient = null) + protected static async Task> GetAllDiagnostics(string bicepFilePath, IContainerRegistryClientFactory clientFactory, ITemplateSpecRepositoryFactory templateSpecRepositoryFactory, IPublicModuleIndexHttpClient? moduleMetadataClient = null) { var compilation = await CreateCompiler(clientFactory, templateSpecRepositoryFactory, moduleMetadataClient).CreateCompilation(PathHelper.FilePathToFileUrl(bicepFilePath)); diff --git a/src/Bicep.Cli.IntegrationTests/UseRecentModuleVersionsIntegrationTests.cs b/src/Bicep.Cli.IntegrationTests/UseRecentModuleVersionsIntegrationTests.cs index 759ffec4a72..118f476ae6b 100644 --- a/src/Bicep.Cli.IntegrationTests/UseRecentModuleVersionsIntegrationTests.cs +++ b/src/Bicep.Cli.IntegrationTests/UseRecentModuleVersionsIntegrationTests.cs @@ -14,7 +14,7 @@ using Bicep.Core.Modules; using Bicep.Core.Registry; using Bicep.Core.Registry.Oci; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog; using Bicep.Core.Samples; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; @@ -26,7 +26,9 @@ using FluentAssertions.Execution; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Bicep.Core.UnitTests.Mock.Registry.Catalog; using static Bicep.Core.UnitTests.Utils.RegistryHelper; +using Bicep.Core.Registry.Catalog.Implementation.PublicRegistries; namespace Bicep.Cli.IntegrationTests; @@ -41,7 +43,7 @@ public class UseRecentModuleVersionsIntegrationTests : TestBase private class Options(string CacheRoot) { - private IPublicModuleIndexClient? _metadataClient = null; + private IPublicModuleIndexHttpClient? _metadataClient = null; private string? _config = null; public string Bicep { get; init; } = "/* bicep contents */"; @@ -75,7 +77,7 @@ public string? BicepConfig } // Automatically created from ModulesMetadata by default (set manually for testing) - internal IPublicModuleIndexClient MetadataClient + internal IPublicModuleIndexHttpClient MetadataClient { set { @@ -85,7 +87,7 @@ internal IPublicModuleIndexClient MetadataClient ModulesMetadata.Select(mm => new PublicModuleIndexEntry( mm.module, [.. mm.versions], - new Dictionary().ToImmutableDictionary()))).Object; + new Dictionary().ToImmutableDictionary()))).Object; } } @@ -112,26 +114,6 @@ private async Task Test(Options options) return await Bicep(settings, "lint", mainFile, options.NoRestore ? "--no-restore" : null); } - [TestMethod] - public async Task IfLevelIsOff_ShouldNotDownloadModuleMetadata() - { - var result = await Test(new Options(CacheRoot) - { - Bicep = """ - module m1 '{PREFIX}/fake/avm/res/app/container-app:0.2.0' = { - name: 'm1' - } - """.Replace("{PREFIX}", PREFIX), - DiagnosticLevel = "off", - PublishedModules = [$"{PREFIX}/fake/avm/res/app/container-app:0.2.0"], - MetadataClient = PublicModuleIndexHttpClientMocks.CreateToThrow(new Exception("unit test failed: shouldn't try to download in this scenario")).Object, - }); - - result.Should().NotHaveStderr(); - result.Should().HaveStdout(""); - result.Should().Succeed(); - } - [TestMethod] // We don't currently cache to disk, but rather on every check to restore modules. public async Task IfNoRestoreSpecified_ThenShouldNotDownloadMetadata_AndShouldFailBecauseNoCache() diff --git a/src/Bicep.Cli.IntegrationTests/packages.lock.json b/src/Bicep.Cli.IntegrationTests/packages.lock.json index 38a0a1486f5..d5159f60f56 100644 --- a/src/Bicep.Cli.IntegrationTests/packages.lock.json +++ b/src/Bicep.Cli.IntegrationTests/packages.lock.json @@ -737,10 +737,10 @@ }, "Microsoft.VisualStudio.Threading": { "type": "Transitive", - "resolved": "17.10.48", - "contentHash": "7onkbbE0AOAhxKe+ZAa2NMzo4R5G4qypZmNIE0GhBohT/tl6e5aLnLx4Gg6trf6SUn3DfLRowMtNe5Q+PmhKgQ==", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", "dependencies": { - "Microsoft.VisualStudio.Threading.Analyzers": "17.10.48", + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", "Microsoft.VisualStudio.Validation": "17.8.8" } }, @@ -1602,6 +1602,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", diff --git a/src/Bicep.Cli.UnitTests/packages.lock.json b/src/Bicep.Cli.UnitTests/packages.lock.json index c7583314cdf..7db1949fb40 100644 --- a/src/Bicep.Cli.UnitTests/packages.lock.json +++ b/src/Bicep.Cli.UnitTests/packages.lock.json @@ -709,10 +709,10 @@ }, "Microsoft.VisualStudio.Threading": { "type": "Transitive", - "resolved": "17.10.48", - "contentHash": "7onkbbE0AOAhxKe+ZAa2NMzo4R5G4qypZmNIE0GhBohT/tl6e5aLnLx4Gg6trf6SUn3DfLRowMtNe5Q+PmhKgQ==", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", "dependencies": { - "Microsoft.VisualStudio.Threading.Analyzers": "17.10.48", + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", "Microsoft.VisualStudio.Validation": "17.8.8" } }, @@ -1474,6 +1474,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", diff --git a/src/Bicep.Cli/Helpers/ServiceCollectionExtensions.cs b/src/Bicep.Cli/Helpers/ServiceCollectionExtensions.cs index fb53fc9f157..911867bde0f 100644 --- a/src/Bicep.Cli/Helpers/ServiceCollectionExtensions.cs +++ b/src/Bicep.Cli/Helpers/ServiceCollectionExtensions.cs @@ -11,7 +11,7 @@ using Bicep.Core.FileSystem; using Bicep.Core.Registry; using Bicep.Core.Registry.Auth; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog.Implementation; using Bicep.Core.Semantics.Namespaces; using Bicep.Core.TypeSystem.Providers; using Bicep.Core.Utils; @@ -77,7 +77,7 @@ public static IServiceCollection AddBicepCore(this IServiceCollection services) .AddSingleton() .AddSingleton() .AddSingleton() - .AddPublicModuleMetadataProviderServices() + .AddRegistryCatalogServices() .AddSingleton(); public static IServiceCollection AddBicepDecompiler(this IServiceCollection services) => services diff --git a/src/Bicep.Cli/packages.lock.json b/src/Bicep.Cli/packages.lock.json index 872fd47a9fb..352c827a66c 100644 --- a/src/Bicep.Cli/packages.lock.json +++ b/src/Bicep.Cli/packages.lock.json @@ -637,10 +637,10 @@ }, "Microsoft.VisualStudio.Threading": { "type": "Transitive", - "resolved": "17.10.48", - "contentHash": "7onkbbE0AOAhxKe+ZAa2NMzo4R5G4qypZmNIE0GhBohT/tl6e5aLnLx4Gg6trf6SUn3DfLRowMtNe5Q+PmhKgQ==", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", "dependencies": { - "Microsoft.VisualStudio.Threading.Analyzers": "17.10.48", + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", "Microsoft.VisualStudio.Validation": "17.8.8" } }, @@ -1363,6 +1363,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", diff --git a/src/Bicep.Core.IntegrationTests/AzTypesViaRegistryTests.cs b/src/Bicep.Core.IntegrationTests/AzTypesViaRegistryTests.cs index dda91ade132..1507d0d4571 100644 --- a/src/Bicep.Core.IntegrationTests/AzTypesViaRegistryTests.cs +++ b/src/Bicep.Core.IntegrationTests/AzTypesViaRegistryTests.cs @@ -53,7 +53,7 @@ private async Task GetServices() private async Task ServicesWithTestExtensionArtifact(ArtifactRegistryAddress artifactRegistryAddress, BinaryData artifactPayload) { var clientFactory = RegistryHelper.CreateMockRegistryClient(artifactRegistryAddress.ClientDescriptor()); - var blobClient = clientFactory.CreateAnonymousBlobClient(BicepTestConstants.BuiltInConfiguration, artifactRegistryAddress.RegistryUri, artifactRegistryAddress.RepositoryPath); + var blobClient = clientFactory.CreateAnonymousBlobClient(BicepTestConstants.BuiltInConfiguration.Cloud, artifactRegistryAddress.RegistryUri, artifactRegistryAddress.RepositoryPath); var configResult = await blobClient.UploadBlobAsync(BinaryData.FromString("{}")); var blobResult = await blobClient.UploadBlobAsync(artifactPayload); diff --git a/src/Bicep.Core.IntegrationTests/MsGraphTypesViaRegistryTests.cs b/src/Bicep.Core.IntegrationTests/MsGraphTypesViaRegistryTests.cs index 894337cabf2..c5da0328a78 100644 --- a/src/Bicep.Core.IntegrationTests/MsGraphTypesViaRegistryTests.cs +++ b/src/Bicep.Core.IntegrationTests/MsGraphTypesViaRegistryTests.cs @@ -69,7 +69,7 @@ private async Task ServicesWithTestExtensionArtifact(ArtifactReg { var clientFactory = RegistryHelper.CreateMockRegistryClient(artifactRegistryAddress.ClientDescriptor()); var blobClient = clientFactory.CreateAnonymousBlobClient( - BicepTestConstants.BuiltInConfiguration, + BicepTestConstants.BuiltInConfiguration.Cloud, artifactRegistryAddress.RegistryUri, artifactRegistryAddress.RepositoryPath); diff --git a/src/Bicep.Core.IntegrationTests/packages.lock.json b/src/Bicep.Core.IntegrationTests/packages.lock.json index b96925c1bf1..a657c8d92cf 100644 --- a/src/Bicep.Core.IntegrationTests/packages.lock.json +++ b/src/Bicep.Core.IntegrationTests/packages.lock.json @@ -621,8 +621,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + "resolved": "3.1.0", + "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -704,28 +704,25 @@ }, "Microsoft.VisualStudio.Threading": { "type": "Transitive", - "resolved": "17.6.40", - "contentHash": "hLa/0xargG7p3bF7aeq2/lRYn/bVnfZXurUWVHx+MNqxxAUjIDMKi4OIOWbYQ/DTkbn9gv8TLvgso+6EtHVQQg==", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "7.0.0", - "Microsoft.VisualStudio.Threading.Analyzers": "17.6.40", - "Microsoft.VisualStudio.Validation": "17.0.71", - "Microsoft.Win32.Registry": "5.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" } }, "Microsoft.VisualStudio.Validation": { "type": "Transitive", - "resolved": "17.6.11", - "contentHash": "J+9L/iac6c8cwcgVSCMuoIYOlD1Jw4mbZ8XMe1IZVj8p8+3dJ46LnnkIkTRMjK7xs9UtU9MoUp1JGhWoN6fAEw==" + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" }, "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -1312,11 +1309,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -1526,6 +1523,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", @@ -1609,11 +1607,11 @@ "net8.0/linux-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2016,11 +2014,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2092,11 +2090,11 @@ "net8.0/linux-musl-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2499,11 +2497,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2575,11 +2573,11 @@ "net8.0/linux-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2982,11 +2980,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3058,11 +3056,11 @@ "net8.0/osx-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3465,11 +3463,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3541,11 +3539,11 @@ "net8.0/osx-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3948,11 +3946,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4024,11 +4022,11 @@ "net8.0/win-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4332,11 +4330,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4408,11 +4406,11 @@ "net8.0/win-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4716,11 +4714,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { diff --git a/src/Bicep.Core.Samples/packages.lock.json b/src/Bicep.Core.Samples/packages.lock.json index 064a46d1387..020a9754759 100644 --- a/src/Bicep.Core.Samples/packages.lock.json +++ b/src/Bicep.Core.Samples/packages.lock.json @@ -621,8 +621,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + "resolved": "3.1.0", + "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -704,28 +704,25 @@ }, "Microsoft.VisualStudio.Threading": { "type": "Transitive", - "resolved": "17.6.40", - "contentHash": "hLa/0xargG7p3bF7aeq2/lRYn/bVnfZXurUWVHx+MNqxxAUjIDMKi4OIOWbYQ/DTkbn9gv8TLvgso+6EtHVQQg==", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "7.0.0", - "Microsoft.VisualStudio.Threading.Analyzers": "17.6.40", - "Microsoft.VisualStudio.Validation": "17.0.71", - "Microsoft.Win32.Registry": "5.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" } }, "Microsoft.VisualStudio.Validation": { "type": "Transitive", - "resolved": "17.6.11", - "contentHash": "J+9L/iac6c8cwcgVSCMuoIYOlD1Jw4mbZ8XMe1IZVj8p8+3dJ46LnnkIkTRMjK7xs9UtU9MoUp1JGhWoN6fAEw==" + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" }, "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -1312,11 +1309,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -1526,6 +1523,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", @@ -1598,11 +1596,11 @@ "net8.0/linux-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2005,11 +2003,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2081,11 +2079,11 @@ "net8.0/linux-musl-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2488,11 +2486,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2564,11 +2562,11 @@ "net8.0/linux-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2971,11 +2969,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3047,11 +3045,11 @@ "net8.0/osx-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3454,11 +3452,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3530,11 +3528,11 @@ "net8.0/osx-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3937,11 +3935,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4013,11 +4011,11 @@ "net8.0/win-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4321,11 +4319,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4397,11 +4395,11 @@ "net8.0/win-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4705,11 +4703,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { diff --git a/src/Bicep.Core.UnitTests/BicepTestConstants.cs b/src/Bicep.Core.UnitTests/BicepTestConstants.cs index ad44303ac2c..15d7bc57c02 100644 --- a/src/Bicep.Core.UnitTests/BicepTestConstants.cs +++ b/src/Bicep.Core.UnitTests/BicepTestConstants.cs @@ -15,7 +15,6 @@ using Bicep.Core.Json; using Bicep.Core.Registry; using Bicep.Core.Registry.Oci; -using Bicep.Core.Registry.PublicRegistry; using Bicep.Core.Semantics.Namespaces; using Bicep.Core.Syntax; using Bicep.Core.TypeSystem; diff --git a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseRecentModuleVersionsRuleTests.cs b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseRecentModuleVersionsRuleTests.cs index 38317114079..ea12498e693 100644 --- a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseRecentModuleVersionsRuleTests.cs +++ b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseRecentModuleVersionsRuleTests.cs @@ -12,7 +12,7 @@ using Bicep.Core.Diagnostics; using Bicep.Core.Json; using Bicep.Core.Parsing; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog; using Bicep.Core.Resources; using Bicep.Core.TypeSystem; using Bicep.Core.UnitTests.Assertions; @@ -44,15 +44,18 @@ private static CompilationResult Compile( string? downloadError = null) { var publicModuleMetadataProvider = StrictMock.Of(); - publicModuleMetadataProvider.Setup(x => x.GetModulesMetadata()) - .Returns(availableModules.Select(m => new PublicModuleMetadata(m, null, null)).ToImmutableArray()); - publicModuleMetadataProvider.Setup(x => x.GetModuleVersionsMetadata(It.IsAny())) - .Returns((string module) => - { - return availableModules.Contains(module) ? - availableVersions.Select(v => new PublicModuleVersionMetadata(v, null, null)).ToImmutableArray() : - []; - }); + publicModuleMetadataProvider.Setup(x => x.GetCachedModules()) + .Returns([.. availableModules + .Select(m => new RegistryModuleMetadata( + "mcr.microsoft.com", + m, + new RegistryModuleMetadata.ComputedData( + new RegistryMetadataDetails(null, null), + [.. availableVersions + .Select(v => new RegistryModuleVersionMetadata(v, IsBicepModule: true, new("det", "doc.html")))] + ))) + ]); + publicModuleMetadataProvider.Setup(x => x.IsCached) .Returns(availableModules.Length > 0); publicModuleMetadataProvider.Setup(x => x.DownloadError) @@ -70,7 +73,7 @@ public void IfUsingOldVersionThatWeDontKnowAbout_Fail() module m1 'br/public:avm/res/network/public-ip-address:0.4.0' = { } """, - ["avm/res/network/public-ip-address"], + ["bicep/avm/res/network/public-ip-address"], ["1.0.0"] ); result.Diagnostics.Where(d => d.Code == UseRecentModuleVersionsRule.Code) @@ -90,7 +93,7 @@ public void IfUsingNewVersionThatWeDontKnowAbout_Passes() module m1 'br/public:avm/res/network/public-ip-address:1.4.0' = { } """, - ["avm/res/network/public-ip-address"], + ["bicep/avm/res/network/public-ip-address"], ["1.0.0"] ); result.Diagnostics.Where(d => d.Code == UseRecentModuleVersionsRule.Code) @@ -105,7 +108,7 @@ public void UsingNewestVersion_NoFailures() module m1 'br/public:avm/res/network/public-ip-address:0.4.0' = { } """, - ["avm/res/network/public-ip-address"], + ["bicep/avm/res/network/public-ip-address"], ["0.3.0", "0.3.1", "0.4.0"] ); result.Diagnostics.Where(d => d.Code == UseRecentModuleVersionsRule.Code) @@ -180,7 +183,7 @@ public void HasDownloadError() module m1 'br/public:avm/res/network/public-ip-address:0.4.0' = { } """, - ["avm/res/network/public-ip-address"], + ["bicep/avm/res/network/public-ip-address"], ["1.0.0"], "My download error" ); @@ -198,7 +201,7 @@ public void SingleMinorVersionBehind() module m1 'br/public:avm/res/network/public-ip-address:0.4.0' = { } """, - ["avm/res/network/public-ip-address"], + ["bicep/avm/res/network/public-ip-address"], ["0.3.0", "0.4.0", "0.5.0", "1.0.1"] ); result.Diagnostics.Where(d => d.Code == UseRecentModuleVersionsRule.Code) @@ -215,7 +218,7 @@ public void SingleMajorVersionBehind() module m1 'br/public:avm/res/network/public-ip-address:0.4.0' = { } """, - ["avm/res/network/public-ip-address"], + ["bicep/avm/res/network/public-ip-address"], ["0.3.0", "0.4.0", "1.0.0"] ); result.Diagnostics.Where(d => d.Code == UseRecentModuleVersionsRule.Code) @@ -232,7 +235,7 @@ public void SinglePatchVersionBehind() module m1 'br/public:avm/res/network/public-ip-address:0.4.1' = { } """, - ["avm/res/network/public-ip-address"], + ["bicep/avm/res/network/public-ip-address"], ["0.3.0", "0.4.1", "0.4.2", "0.5.0"] ); result.Diagnostics.Where(d => d.Code == UseRecentModuleVersionsRule.Code) @@ -249,7 +252,7 @@ public void MultiplePatchVersionsBehind() module m1 'br/public:avm/res/network/public-ip-address:0.4.1' = { } """, - ["avm/res/network/public-ip-address"], + ["bicep/avm/res/network/public-ip-address"], ["0.3.0", "0.4.1", "0.4.2", "0.4.5"] ); result.Diagnostics.Where(d => d.Code == UseRecentModuleVersionsRule.Code) @@ -266,7 +269,7 @@ public void HasFix() module m1 'br/public:avm/res/network/public-ip-address:0.4.0' = { } """, - ["avm/res/network/public-ip-address"], + ["bicep/avm/res/network/public-ip-address"], ["0.3.0", "0.4.0", "0.5.0", "1.0.1"] ); diff --git a/src/Bicep.Core.UnitTests/Features/OverriddenFeatureProvider.cs b/src/Bicep.Core.UnitTests/Features/OverriddenFeatureProvider.cs index 723ec5d7e5f..bfbc049766d 100644 --- a/src/Bicep.Core.UnitTests/Features/OverriddenFeatureProvider.cs +++ b/src/Bicep.Core.UnitTests/Features/OverriddenFeatureProvider.cs @@ -3,7 +3,6 @@ using Bicep.Core.Features; using Bicep.IO.Abstraction; -using Bicep.Core.Registry.PublicRegistry; namespace Bicep.Core.UnitTests.Features; diff --git a/src/Bicep.Core.UnitTests/IServiceCollectionExtensions.cs b/src/Bicep.Core.UnitTests/IServiceCollectionExtensions.cs index 52e6162d9cf..4a8d7e18b29 100644 --- a/src/Bicep.Core.UnitTests/IServiceCollectionExtensions.cs +++ b/src/Bicep.Core.UnitTests/IServiceCollectionExtensions.cs @@ -8,7 +8,7 @@ using Bicep.Core.FileSystem; using Bicep.Core.Registry; using Bicep.Core.Registry.Auth; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog.Implementation; using Bicep.Core.Semantics.Namespaces; using Bicep.Core.TypeSystem.Providers; using Bicep.Core.TypeSystem.Providers.Az; @@ -17,6 +17,7 @@ using Bicep.Core.UnitTests.Configuration; using Bicep.Core.UnitTests.Features; using Bicep.Core.UnitTests.Mock.Registry; +using Bicep.Core.UnitTests.Mock.Registry.Catalog; using Bicep.Core.UnitTests.Utils; using Bicep.Core.Utils; using Bicep.Core.Workspaces; @@ -52,7 +53,7 @@ public static IServiceCollection AddBicepCore(this IServiceCollection services) .AddSingleton() .AddSingleton() .AddSingleton() - .AddPublicModuleMetadataProviderServices() + .AddRegistryCatalogServices() .AddSingleton(); AddMockHttpClient(services, PublicModuleIndexHttpClientMocks.Create([]).Object); diff --git a/src/Bicep.Core.UnitTests/Mock/Registry/PublicModuleIndexHttpClientMocks.cs b/src/Bicep.Core.UnitTests/Mock/Registry/PublicModuleIndexHttpClientMocks.cs index 62595ddc071..b9d46898f6b 100644 --- a/src/Bicep.Core.UnitTests/Mock/Registry/PublicModuleIndexHttpClientMocks.cs +++ b/src/Bicep.Core.UnitTests/Mock/Registry/PublicModuleIndexHttpClientMocks.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Immutable; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog.Implementation.PublicRegistries; using Moq; namespace Bicep.Core.UnitTests.Mock.Registry; @@ -12,18 +12,18 @@ public static class PublicModuleIndexHttpClientMocks { // CONSIDER: Mock HttpClient rather than the typed client - public static Mock Create(IEnumerable metadata) + public static Mock Create(IEnumerable metadata) { - var mock = StrictMock.Of(); + var mock = StrictMock.Of(); mock .Setup(client => client.GetModuleIndexAsync()) .ReturnsAsync(() => metadata.ToImmutableArray()); return mock; } - public static Mock CreateToThrow(Exception exception) + public static Mock CreateToThrow(Exception exception) { - var mock = StrictMock.Of(); + var mock = StrictMock.Of(); mock .Setup(client => client.GetModuleIndexAsync()) .ThrowsAsync(exception); diff --git a/src/Bicep.Core.UnitTests/Mock/Registry/RegistryCatalogMocks.cs b/src/Bicep.Core.UnitTests/Mock/Registry/RegistryCatalogMocks.cs new file mode 100644 index 00000000000..bcab184e25a --- /dev/null +++ b/src/Bicep.Core.UnitTests/Mock/Registry/RegistryCatalogMocks.cs @@ -0,0 +1,149 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; +using Bicep.Core.Configuration; +using Bicep.Core.Json; +using Bicep.Core.Registry; +using Bicep.Core.Registry.Catalog; +using Bicep.Core.Registry.Catalog.Implementation; +using Bicep.Core.Registry.Catalog.Implementation.PrivateRegistries; +using FluentAssertions; +using Microsoft.WindowsAzure.ResourceStack.Common.Extensions; +using Moq; +using static Bicep.Core.UnitTests.Utils.RegistryHelper; + +namespace Bicep.Core.UnitTests.Mock.Registry.Catalog; + +public static class RegistryCatalogMocks +{ + private const string PublicRegistry = "mcr.microsoft.com"; + + public static Mock MockPublicMetadataProvider( + IEnumerable<(string moduleName, string? description, string? documentationUri, IEnumerable<(string version, string? description, string? documentUri)> versions)> modules) + { + if (modules.Any()) + { + modules.Should().AllSatisfy( + m => m.moduleName.Should().StartWith("bicep/", "All public modules should start with '/bicep'") + ); + } + + var publicProvider = StrictMock.Of(); + + publicProvider.Setup(x => x.Registry).Returns(PublicRegistry); + + publicProvider.Setup(x => x.TryGetModulesAsync()) + .ReturnsAsync( + [.. modules + .Select(m => new RegistryModuleMetadata( + PublicRegistry, + m.moduleName, + new RegistryModuleMetadata.ComputedData( + new RegistryMetadataDetails(m.description, m.documentationUri), + [.. modules.Single(m2 => m2.moduleName.EqualsOrdinally(m.moduleName)) + .versions + .Select(v => new RegistryModuleVersionMetadata(v.version, IsBicepModule: true, new(v.description,v.documentUri))) + ] + ) + ))] + ); + + return publicProvider; + } + + public static Mock MockPrivateMetadataProvider( + string registry, + IEnumerable<(string moduleName, string? description, string? documentationUri, IEnumerable versions)> modules + ) + { + var privateProvider = StrictMock.Of(); + + privateProvider.Setup(x => x.Registry).Returns(registry); + + privateProvider.Setup(x => x.TryGetModulesAsync()) + .ReturnsAsync([.. + modules.Select(m => new RegistryModuleMetadata( + registry, + m.moduleName, + getDataAsyncFunc: async () => + new RegistryModuleMetadata.ComputedData( + await DelayedValue(new RegistryMetadataDetails( m.description, m.documentationUri)), + [.. modules.Single(m2 => m2.moduleName.EqualsOrdinally(m.moduleName)) + .versions + .Select(v => new RegistryModuleVersionMetadata(v.Tag, true, new(v.Description, v.DocumentationUri)) + )] + ) + )) + ]); + + return privateProvider; + } + + private static async Task DelayedValue(T value) + { + await Task.Delay(1); + return value; + } + + public static Mock MockFailingPrivateMetadataProvider( + string registry, + Exception? exception = null + ) + { + exception ??= new Exception($"Loading metadata for registry {registry} (intentionally) failed"); + + var privateProvider = StrictMock.Of(); + privateProvider.Setup(x => x.Registry).Returns(registry); + privateProvider.Setup(x => x.DownloadError).Returns(exception.Message); + privateProvider.Setup(x => x.TryGetModulesAsync()).ReturnsAsync([]); + + return privateProvider; + } + + public static IRegistryModuleCatalog CreateCatalogWithMocks( + Mock? publicProvider = null, + params Mock[] privateProviders + ) + { + if (publicProvider is null) + { + publicProvider = MockPublicMetadataProvider([]); + } + + var privateFactory = StrictMock.Of(); + + // Default - when an unrecognized registry is requested, return a provider that fails to load (similar to real behavior) + privateFactory.Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((CloudConfiguration _, string registry, IContainerRegistryClientFactory _) => + MockFailingPrivateMetadataProvider(registry, new Exception($"Registry {registry} not found in mock")).Object); + + foreach (var privateProvider in privateProviders) + { + privateProvider.Object.Registry.Should().NotBe(PublicRegistry); + privateFactory.Setup(x => x.Create(It.IsAny(), privateProvider.Object.Registry, It.IsAny())) + .Returns(privateProvider.Object); + } + + var indexer = new RegistryModuleCatalog( + publicProvider.Object, + privateFactory.Object, + StrictMock.Of().Object, + BicepTestConstants.BuiltInOnlyConfigurationManager); + + return indexer; + } + + public static ModuleAliasesConfiguration ModuleAliases( + string moduleAliasesJson + ) + { + return ModuleAliasesConfiguration.Bind(JsonElementFactory.CreateElement(moduleAliasesJson), null); + } +} diff --git a/src/Bicep.Core.UnitTests/Registry/Catalog/BaseModuleMetadataProviderTests.cs b/src/Bicep.Core.UnitTests/Registry/Catalog/BaseModuleMetadataProviderTests.cs new file mode 100644 index 00000000000..9ff00a6c76c --- /dev/null +++ b/src/Bicep.Core.UnitTests/Registry/Catalog/BaseModuleMetadataProviderTests.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Net.Http; +using System.Text; +using System.Text.Json; +using Bicep.Core.Registry.Catalog.Implementation; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RichardSzalay.MockHttp; + +namespace Bicep.Core.UnitTests.Registry.Catalog +{ + [TestClass] + public class BaseModuleMetadataProviderTests + { + [TestMethod] + public void GetExponentialDelay_ZeroCount_ShouldGiveInitialDelay() + { + TimeSpan initial = TimeSpan.FromDays(2.5); + TimeSpan max = TimeSpan.FromDays(10); + var delay = BaseModuleMetadataProvider.GetExponentialDelay(initial, 0, max); + + delay.Should().Be(initial); + } + + [TestMethod] + public void GetExponentialDelay_1Count_ShouldGiveDoubleInitialDelay() + { + TimeSpan initial = TimeSpan.FromDays(2.5); + TimeSpan max = TimeSpan.FromDays(10); + var delay = BaseModuleMetadataProvider.GetExponentialDelay(initial, 1, max); + + delay.Should().Be(initial * 2); + } + + [TestMethod] + public void GetExponentialDelay_2Count_ShouldGiveQuadrupleInitialDelay() + { + TimeSpan initial = TimeSpan.FromDays(2.5); + TimeSpan max = TimeSpan.FromDays(10); + var delay = BaseModuleMetadataProvider.GetExponentialDelay(initial, 2, max); + + delay.Should().Be(initial * 4); + } + + [TestMethod] + public void GetExponentialDelay_AboveMaxCount_ShouldGiveMaxDelay() + { + TimeSpan initial = TimeSpan.FromSeconds(1); + TimeSpan max = TimeSpan.FromDays(365); + + TimeSpan exponentiallyGrowingDelay = initial; + int count = 0; + while (exponentiallyGrowingDelay < max * 1000) + { + var delay = BaseModuleMetadataProvider.GetExponentialDelay(initial, count, max); + + if (exponentiallyGrowingDelay < max) + { + delay.Should().Be(exponentiallyGrowingDelay); + } + else + { + delay.Should().Be(max); + } + + delay.Should().BeLessThanOrEqualTo(max); + + ++count; + exponentiallyGrowingDelay *= 2; + } + } + } +} diff --git a/src/Bicep.Core.UnitTests/Registry/Catalog/PrivateAcrModuleMetadataProviderTests.cs b/src/Bicep.Core.UnitTests/Registry/Catalog/PrivateAcrModuleMetadataProviderTests.cs new file mode 100644 index 00000000000..2f2b5e4a8f5 --- /dev/null +++ b/src/Bicep.Core.UnitTests/Registry/Catalog/PrivateAcrModuleMetadataProviderTests.cs @@ -0,0 +1,381 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.IO.Abstractions.TestingHelpers; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using Azure.Containers.ContainerRegistry; +using Bicep.Core.Configuration; +using Bicep.Core.Features; +using Bicep.Core.Registry; +using Bicep.Core.Registry.Catalog; +using Bicep.Core.Registry.Catalog.Implementation; +using Bicep.Core.Registry.Catalog.Implementation.PrivateRegistries; +using Bicep.Core.Registry.Oci; +using Bicep.Core.UnitTests.Features; +using Bicep.Core.UnitTests.Mock; +using Bicep.Core.UnitTests.Mock.Registry; +using Bicep.Core.UnitTests.Utils; +using Bicep.IO.Abstraction; +using Bicep.IO.FileSystem; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Win32; +using Moq; +using RichardSzalay.MockHttp; +using static Bicep.Core.UnitTests.Utils.RegistryHelper; +using static Microsoft.ApplicationInsights.MetricDimensionNames.TelemetryContext; + +namespace Bicep.Core.UnitTests.Registry.Catalog +{ + [TestClass] + public class PrivateAcrModuleMetadataProviderTests + { + [NotNull] public TestContext? TestContext { get; set; } + + [TestMethod] + public async Task TryGetModulesAsync() + { + FakeContainerRegistryClient containerRegistryClient = new(); + var clientFactory = await RegistryHelper.CreateMockRegistryClientWithPublishedModulesAsync( + new MockFileSystem(), + containerRegistryClient, + [ + new("br:registry.contoso.io/test/module1:v1", "param p1 bool", WithSource: true), + new("br:registry.contoso.io/test/module2:v1", "param p2 string", WithSource: true), + new("br:registry.contoso.io/test/module1:v2", "param p12 string", WithSource: false), + ]); + + var provider = new PrivateAcrModuleMetadataProvider( + BicepTestConstants.BuiltInConfiguration.Cloud, + "registry.contoso.io", + clientFactory); + var modules = await provider.TryGetModulesAsync(); + + modules.Should().HaveCount(2); + } + + [TestMethod] + public async Task GetCachedModules() + { + var containerClient = new FakeContainerRegistryClient(); + var clientFactory = await RegistryHelper.CreateMockRegistryClientWithPublishedModulesAsync( + new MockFileSystem(), + containerClient, + [ + new("br:registry.contoso.io/test/module1:v1", "param p1 bool", WithSource: true), + new("br:registry.contoso.io/test/module2:v1", "param p2 string", WithSource : true), + new("br:registry.contoso.io/test/module1:v2", "param p12 string", WithSource: false), + ]); + + var provider = new PrivateAcrModuleMetadataProvider( + BicepTestConstants.BuiltInConfiguration.Cloud, + "registry.contoso.io", + clientFactory); + + provider.GetCachedModules().Should().HaveCount(0); + + var modules = await provider.TryGetModulesAsync(); + modules.Should().HaveCount(2); + + provider.GetCachedModules().Should().HaveCount(2); + provider.GetCachedModules().Should().HaveCount(2); + } + + [TestMethod] + public async Task TryGetModulesAsync_ShouldCacheResult() + { + var containerClient = new FakeContainerRegistryClient(); + var clientFactory = await RegistryHelper.CreateMockRegistryClientWithPublishedModulesAsync( + new MockFileSystem(), + containerClient, + [ + new("br:registry.contoso.io/test/module1:v1", "param p1 bool", WithSource: true), + new("br:registry.contoso.io/test/module2:v1", "param p2 string", WithSource: true), + new("br:registry.contoso.io/test/module1:v2", "param p12 string", WithSource : false), + ]); + + var provider = new PrivateAcrModuleMetadataProvider( + BicepTestConstants.BuiltInConfiguration.Cloud, + "registry.contoso.io", + clientFactory); + + var modules = await provider.TryGetModulesAsync(); + modules.Should().HaveCount(2); + containerClient.CallsToGetRepositoryNamesAsync.Should().Be(1); + + modules = await provider.TryGetModulesAsync(); + containerClient.CallsToGetRepositoryNamesAsync.Should().Be(1); + } + + [TestMethod] + public async Task GetDetails_ShouldBeCached() + { + var containerClient = new FakeContainerRegistryClient(); + var clientFactory = await RegistryHelper.CreateMockRegistryClientWithPublishedModulesAsync( + new MockFileSystem(), + containerClient, + [ + new("br:registry.contoso.io/test/module1:v1", "metadata description = 'this is module 1 version 1'\nparam p1 bool", WithSource: true, DocumentationUri: "http://contoso.com/help11"), + new("br:registry.contoso.io/test/module2:v1", "metadata description = 'this is module 2 version 1'\nparam p2 string", WithSource: true, DocumentationUri: "http://contoso.com/help21"), + new("br:registry.contoso.io/test/module1:v2", "metadata description = 'this is module 1 version 2'\nparam p12 string", WithSource: false, DocumentationUri: "http://contoso.com/help12"), + ]); + + var provider = new PrivateAcrModuleMetadataProvider( + BicepTestConstants.BuiltInConfiguration.Cloud, + "registry.contoso.io", + clientFactory); + provider.GetCachedModules().Should().BeEmpty(); + + var module = await provider.TryGetModuleAsync("test/module1"); + module.Should().NotBeNull(); + provider.GetCachedModules().Should().NotBeEmpty(); + + var details = await module!.TryGetDetailsAsync(); + details.Description.Should().Be("this is module 1 version 2"); + details.DocumentationUri.Should().Be("http://contoso.com/help12"); + containerClient.CallsToGetAllManifestPropertiesAsync.Should().Be(1); + + // Verify it's cached + var details2 = await module!.TryGetDetailsAsync(); + details.Should().BeSameAs(details2); + containerClient.CallsToGetAllManifestPropertiesAsync.Should().Be(1); + + var cached = provider.GetCachedModules(); + provider.GetCachedModules().Should().NotBeEmpty(); + containerClient.CallsToGetAllManifestPropertiesAsync.Should().Be(1); + } + + [TestMethod] + public async Task GetVersions_ShouldBeCached() + { + var containerClient = new FakeContainerRegistryClient(); + var clientFactory = await RegistryHelper.CreateMockRegistryClientWithPublishedModulesAsync( + new MockFileSystem(), + containerClient, + [ + new("br:registry.contoso.io/test/module1:v1", "metadata description = 'this is module 1 version 1'\nparam p1 bool", WithSource: true, DocumentationUri: "http://contoso.com/help11"), + new("br:registry.contoso.io/test/module2:v1", "metadata description = 'this is module 2 version 1'\nparam p2 string", WithSource: true, DocumentationUri: "http://contoso.com/help21"), + new("br:registry.contoso.io/test/module1:v2", "metadata description = 'this is module 1 version 2'\nparam p12 string", WithSource: false, DocumentationUri: "http://contoso.com/help12"), + ]); + + var provider = new PrivateAcrModuleMetadataProvider( + BicepTestConstants.BuiltInConfiguration.Cloud, + "registry.contoso.io", + clientFactory); + + var module = await provider.TryGetModuleAsync("test/module1"); + module.Should().NotBeNull(); + module!.GetCachedVersions().Should().BeEmpty(); + containerClient.CallsToGetAllManifestPropertiesAsync.Should().Be(0); + module.GetCachedVersions().Should().BeEmpty(); + + var versions = await module!.TryGetVersionsAsync(); + containerClient.CallsToGetAllManifestPropertiesAsync.Should().Be(1); + versions[0].Version.Should().Be("v1"); + module.GetCachedVersions()[0].Version.Should().Be("v1"); + + var versions2 = await module!.TryGetVersionsAsync(); + containerClient.CallsToGetAllManifestPropertiesAsync.Should().Be(1); + versions[0].Version.Should().Be("v1"); + module.GetCachedVersions()[0].Version.Should().Be("v1"); + versions.Should().BeEquivalentTo(versions2); + versions[0].Version.Should().BeSameAs(versions2[0].Version); + } + + [TestMethod] + public async Task GetDetails_ShouldAlsoCacheVersions_BecauseGettingModuleDetailsRequiresGettingVersionDetails() + { + var containerClient = new FakeContainerRegistryClient(); + var clientFactory = await RegistryHelper.CreateMockRegistryClientWithPublishedModulesAsync( + new MockFileSystem(), + containerClient, + [ + new("br:registry.contoso.io/test/module1:v1", "metadata description = 'this is module 1 version 1'\nparam p1 bool", WithSource: true, DocumentationUri: "http://contoso.com/help11"), + new("br:registry.contoso.io/test/module2:v1", "metadata description = 'this is module 2 version 1'\nparam p2 string", WithSource: true, DocumentationUri: "http://contoso.com/help21"), + new("br:registry.contoso.io/test/module1:v2", "metadata description = 'this is module 1 version 2'\nparam p12 string", WithSource: false, DocumentationUri: "http://contoso.com/help12"), + ]); + + var provider = new PrivateAcrModuleMetadataProvider( + BicepTestConstants.BuiltInConfiguration.Cloud, + "registry.contoso.io", + clientFactory); + + var module = await provider.TryGetModuleAsync("test/module1"); + module.Should().NotBeNull(); + module!.GetCachedVersions().Should().BeEmpty(); + + var details = await module!.TryGetDetailsAsync(); + details.Description.Should().Be("this is module 1 version 2"); + containerClient.CallsToGetAllManifestPropertiesAsync.Should().Be(1); + + var versions = await module!.TryGetVersionsAsync(); + containerClient.CallsToGetAllManifestPropertiesAsync.Should().Be(1); + } + + [TestMethod] + public async Task Module_WithNoVersionsHavingDetails() + { + var containerClient = new FakeContainerRegistryClient(); + var clientFactory = await RegistryHelper.CreateMockRegistryClientWithPublishedModulesAsync( + new MockFileSystem(), + containerClient, + [ + new("br:registry.contoso.io/test/module1:v1", "metadata hello = 'this is module 1 version 1'\nparam p1 bool", WithSource: true), + new("br:registry.contoso.io/test/module2:v1", "metadata hello = 'this is module 2 version 1'\nparam p2 string", WithSource: true), + new("br:registry.contoso.io/test/module1:v2", "metadata hello = 'this is module 1 version 2'\nparam p12 string", WithSource: false), + ]); + + var provider = new PrivateAcrModuleMetadataProvider( + BicepTestConstants.BuiltInConfiguration.Cloud, + "registry.contoso.io", + clientFactory); + + var module = await provider.TryGetModuleAsync("test/module1"); + module.Should().NotBeNull(); + module!.GetCachedVersions().Should().BeEmpty(); + + var details = await module!.TryGetDetailsAsync(); + details.Description.Should().BeNull(); + details.DocumentationUri.Should().BeNull(); + } + + [TestMethod] + public async Task NoVersionsWhichAreValidBicepModules() + { + var containerClient = new FakeContainerRegistryClient(); + var fileSystem = new MockFileSystem(); + + var registry = "registry.contoso.io"; + var repositoryPath = $"test"; + var repositoryNames = new[] { "repo1", "repo2" }; + + var clientFactory = RegistryHelper.CreateMockRegistryClient([.. repositoryNames.Select(name => new RepoDescriptor(registry, $"{repositoryPath}/{name}", ["v1"]))]); + + var services = new ServiceBuilder() + .WithFeatureOverrides(new(TestContext, ExtensibilityEnabled: true)) + .WithContainerRegistryClientFactory(clientFactory); + + var fileExplorer = new FileSystemFileExplorer(fileSystem); + var configurationManager = new ConfigurationManager(fileExplorer); + var featureProviderFactory = new OverriddenFeatureProviderFactory(new FeatureProviderFactory(configurationManager, fileExplorer), BicepTestConstants.FeatureOverrides); + + await RegistryHelper.PublishExtensionToRegistryAsync(services.Build(), "br:registry.contoso.io/test/repo1:v1", new BinaryData("")); + await RegistryHelper.PublishExtensionToRegistryAsync(services.Build(), "br:registry.contoso.io/test/repo1:v2", new BinaryData("")); + + var provider = new PrivateAcrModuleMetadataProvider( + BicepTestConstants.BuiltInConfiguration.Cloud, + "registry.contoso.io", + clientFactory); + + var module = await provider.TryGetModuleAsync("test/repo1"); + module.Should().NotBeNull(); + module!.GetCachedVersions().Should().BeEmpty(); + + var details = await module!.TryGetDetailsAsync(); + details.Description.Should().Be("Not a valid Bicep module. Found artifact type application/vnd.ms.bicep.provider.artifact"); + details.DocumentationUri.Should().BeNull(); + } + + [TestMethod] + public async Task IgnoreVersionsWhichAreNotValidBicepModules() + { + var containerClient = new FakeContainerRegistryClient(); + var fileSystem = new MockFileSystem(); + + var registry = "registry.contoso.io"; + var repositoryPath = $"test"; + var repositoryNames = new[] { "repo1" }; + + var clientFactory = RegistryHelper.CreateMockRegistryClient([.. + repositoryNames.Select(name => new RepoDescriptor(registry, $"{repositoryPath}/{name}", ["v1", "v2", "v3"]))]); + + var services = new ServiceBuilder() + .WithFeatureOverrides(new(TestContext, ExtensibilityEnabled: true)) + .WithContainerRegistryClientFactory(clientFactory); + + var fileExplorer = new FileSystemFileExplorer(fileSystem); + var configurationManager = new ConfigurationManager(fileExplorer); + var featureProviderFactory = new OverriddenFeatureProviderFactory(new FeatureProviderFactory(configurationManager, fileExplorer), BicepTestConstants.FeatureOverrides); + + // Only v2 is a module + await RegistryHelper.PublishExtensionToRegistryAsync(services.Build(), "br:registry.contoso.io/test/repo1:v1", new BinaryData("")); + await RegistryHelper.PublishModuleToRegistryAsync(services, clientFactory, fileSystem, new ModuleToPublish("br:registry.contoso.io/test/repo1:v2", "metadata description = 'this is module 1 version 2'", WithSource: true, "https://docs/m1v2")); + await RegistryHelper.PublishExtensionToRegistryAsync(services.Build(), "br:registry.contoso.io/test/repo1:v3", new BinaryData("")); + + var provider = new PrivateAcrModuleMetadataProvider( + BicepTestConstants.BuiltInConfiguration.Cloud, + "registry.contoso.io", + clientFactory); + + var module = await provider.TryGetModuleAsync("test/repo1"); + module.Should().NotBeNull(); + module!.GetCachedVersions().Should().BeEmpty(); + + // Version details + var versions = await module.TryGetVersionsAsync(); + versions.Select(v => (v.Version, v.IsBicepModule, v.Details)).Should().BeEquivalentTo([ + ("v1", false, new RegistryMetadataDetails("Not a valid Bicep module. Found artifact type application/vnd.ms.bicep.provider.artifact", null)), + ("v2", true, new RegistryMetadataDetails("this is module 1 version 2", "https://docs/m1v2")), + ("v3", false, new RegistryMetadataDetails("Not a valid Bicep module. Found artifact type application/vnd.ms.bicep.provider.artifact", null)), + ]); + + // Module details (should pull from v2 since that's the only valid Bicep module) + var details = await module!.TryGetDetailsAsync(); + details.Description.Should().Be("this is module 1 version 2"); + details.DocumentationUri.Should().Be("https://docs/m1v2"); + } + + class PrivateAcrModuleMetadataProviderThatThrows : PrivateAcrModuleMetadataProvider + { + public PrivateAcrModuleMetadataProviderThatThrows( + CloudConfiguration cloud, + string registry, + IContainerRegistryClientFactory clientFactory) + : base(cloud, registry, clientFactory) { } + + protected override Task> GetLiveModuleVersionsAsync(string modulePath) + { + throw new Exception("I like throwing"); + } + } + + [TestMethod] + public async Task GetCachedVersions_ShouldNotThrowOnErrors() + { + var containerClient = new FakeContainerRegistryClient(); + var clientFactory = await RegistryHelper.CreateMockRegistryClientWithPublishedModulesAsync( + new MockFileSystem(), + containerClient, + [ + new("br:registry.contoso.io/test/module1:v1", "metadata description = 'this is module 1 version 1'\nparam p1 bool", WithSource: true, DocumentationUri: "http://contoso.com/help11"), + ]); + + var provider = new PrivateAcrModuleMetadataProviderThatThrows( + BicepTestConstants.BuiltInConfiguration.Cloud, + "registry.contoso.io", + clientFactory); + + var module = await provider.TryGetModuleAsync("test/module1"); + module.Should().NotBeNull(); + + module!.GetCachedVersions().Should().BeEmpty(); + (await module!.TryGetVersionsAsync()).Should().BeEmpty(); + module.GetCachedVersions().Should().BeEmpty(); + + var provider2NoThrow = new PrivateAcrModuleMetadataProvider( + BicepTestConstants.BuiltInConfiguration.Cloud, + "registry.contoso.io", + clientFactory); + var module2 = await provider2NoThrow.TryGetModuleAsync("test/module1"); + + module2!.GetCachedVersions().Should().BeEmpty(); + (await module2!.TryGetVersionsAsync()).Should().NotBeEmpty(); + module2.GetCachedVersions().Should().NotBeEmpty(); + } + } +} diff --git a/src/Bicep.Core.UnitTests/Registry/PublicRegistry/PublicModuleMetadataProviderTests.cs b/src/Bicep.Core.UnitTests/Registry/Catalog/PublicModuleMetadataProviderTests.cs similarity index 88% rename from src/Bicep.Core.UnitTests/Registry/PublicRegistry/PublicModuleMetadataProviderTests.cs rename to src/Bicep.Core.UnitTests/Registry/Catalog/PublicModuleMetadataProviderTests.cs index b06582aa8f9..8f96f3611ea 100644 --- a/src/Bicep.Core.UnitTests/Registry/PublicRegistry/PublicModuleMetadataProviderTests.cs +++ b/src/Bicep.Core.UnitTests/Registry/Catalog/PublicModuleMetadataProviderTests.cs @@ -1,29 +1,27 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Net.Http; using System.Text; using System.Text.Json; -using Bicep.Core.Extensions; -using Bicep.Core.Registry.PublicRegistry; -using Bicep.Core.UnitTests; -using Bicep.LanguageServer.Providers; +using Bicep.Core.Registry.Catalog.Implementation; +using Bicep.Core.Registry.Catalog.Implementation.PublicRegistries; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; using RichardSzalay.MockHttp; -namespace Bicep.Core.UnitTests.Registry.PublicRegistry +namespace Bicep.Core.UnitTests.Registry.Catalog { [TestClass] public class PublicModuleMetadataProviderTests { - private IServiceProvider GetServiceProvider() + private PublicModuleMetadataHttpClient CreateTypedClient() { var httpClient = MockHttpMessageHandler.ToHttpClient(); - return new ServiceBuilder().WithRegistration(x => - x.AddSingleton(new PublicModuleMetadataClient(httpClient)) - ).Build().Construct(); + return new PublicModuleMetadataHttpClient(httpClient); } + private const string ModuleIndexJson = """ [ { @@ -568,31 +566,31 @@ private IServiceProvider GetServiceProvider() ], "properties": { "1.1.1": { - "description": "These are the input parameters for the Bicep module: [`main.bicep`](./main.bicep)\n\nThis is the orchestration module that is used and called by a consumer of the module to deploy a Landing Zone Subscription and its associated resources, based on the parameter input values that are provided to it at deployment time.\n\n> For more information and examples please see the [wiki](https://github.com/Azure/bicep-lz-vending/wiki)", + "description": "v1.1.1: These are the input parameters for the Bicep module: [`main.bicep`](./main.bicep)\n\nThis is the orchestration module that is used and called by a consumer of the module to deploy a Landing Zone Subscription and its associated resources, based on the parameter input values that are provided to it at deployment time.\n\n> For more information and examples please see the [wiki](https://github.com/Azure/bicep-lz-vending/wiki)", "documentationUri": "https://github.com/Azure/bicep-registry-modules/tree/lz/sub-vending/1.1.1/modules/lz/sub-vending/README.md" }, "1.1.2": { - "description": "These are the input parameters for the Bicep module: [`main.bicep`](./main.bicep)\n\nThis is the orchestration module that is used and called by a consumer of the module to deploy a Landing Zone Subscription and its associated resources, based on the parameter input values that are provided to it at deployment time.\n\n> For more information and examples please see the [wiki](https://github.com/Azure/bicep-lz-vending/wiki)", + "description": "v1.1.2: These are the input parameters for the Bicep module: [`main.bicep`](./main.bicep)\n\nThis is the orchestration module that is used and called by a consumer of the module to deploy a Landing Zone Subscription and its associated resources, based on the parameter input values that are provided to it at deployment time.\n\n> For more information and examples please see the [wiki](https://github.com/Azure/bicep-lz-vending/wiki)", "documentationUri": "https://github.com/Azure/bicep-registry-modules/tree/lz/sub-vending/1.1.2/modules/lz/sub-vending/README.md" }, "1.2.1": { - "description": "These are the input parameters for the Bicep module: [`main.bicep`](./main.bicep)\n\nThis is the orchestration module that is used and called by a consumer of the module to deploy a Landing Zone Subscription and its associated resources, based on the parameter input values that are provided to it at deployment time.\n\n> For more information and examples please see the [wiki](https://github.com/Azure/bicep-lz-vending/wiki)", + "description": "v1.2.1: These are the input parameters for the Bicep module: [`main.bicep`](./main.bicep)\n\nThis is the orchestration module that is used and called by a consumer of the module to deploy a Landing Zone Subscription and its associated resources, based on the parameter input values that are provided to it at deployment time.\n\n> For more information and examples please see the [wiki](https://github.com/Azure/bicep-lz-vending/wiki)", "documentationUri": "https://github.com/Azure/bicep-registry-modules/tree/lz/sub-vending/1.2.1/modules/lz/sub-vending/README.md" }, "1.2.2": { - "description": "These are the input parameters for the Bicep module: [`main.bicep`](./main.bicep)\n\nThis is the orchestration module that is used and called by a consumer of the module to deploy a Landing Zone Subscription and its associated resources, based on the parameter input values that are provided to it at deployment time.\n\n> For more information and examples please see the [wiki](https://github.com/Azure/bicep-lz-vending/wiki)", + "description": "v1.2.2: These are the input parameters for the Bicep module: [`main.bicep`](./main.bicep)\n\nThis is the orchestration module that is used and called by a consumer of the module to deploy a Landing Zone Subscription and its associated resources, based on the parameter input values that are provided to it at deployment time.\n\n> For more information and examples please see the [wiki](https://github.com/Azure/bicep-lz-vending/wiki)", "documentationUri": "https://github.com/Azure/bicep-registry-modules/tree/lz/sub-vending/1.2.2/modules/lz/sub-vending/README.md" }, "1.3.1": { - "description": "These are the input parameters for the Bicep module: [`main.bicep`](./main.bicep)\n\nThis is the orchestration module that is used and called by a consumer of the module to deploy a Landing Zone Subscription and its associated resources, based on the parameter input values that are provided to it at deployment time.\n\n> For more information and examples please see the [wiki](https://github.com/Azure/bicep-lz-vending/wiki)", + "description": "v1.3.1: These are the input parameters for the Bicep module: [`main.bicep`](./main.bicep)\n\nThis is the orchestration module that is used and called by a consumer of the module to deploy a Landing Zone Subscription and its associated resources, based on the parameter input values that are provided to it at deployment time.\n\n> For more information and examples please see the [wiki](https://github.com/Azure/bicep-lz-vending/wiki)", "documentationUri": "https://github.com/Azure/bicep-registry-modules/tree/lz/sub-vending/1.3.1/modules/lz/sub-vending/README.md" }, "1.4.1": { - "description": "This module is designed to accelerate deployment of landing zones (aka Subscriptions) within an Azure AD Tenant.", + "description": "v1.4.1: This module is designed to accelerate deployment of landing zones (aka Subscriptions) within an Azure AD Tenant.", "documentationUri": "https://github.com/Azure/bicep-registry-modules/tree/lz/sub-vending/1.4.1/modules/lz/sub-vending/README.md" }, "1.4.2": { - "description": "This module is designed to accelerate deployment of landing zones (aka Subscriptions) within an Azure AD Tenant.", + "description": "v1.4.2: This module is designed to accelerate deployment of landing zones (aka Subscriptions) within an Azure AD Tenant.", "documentationUri": "https://github.com/Azure/bicep-registry-modules/tree/lz/sub-vending/1.4.2/modules/lz/sub-vending/README.md" } } @@ -762,6 +760,7 @@ private IServiceProvider GetServiceProvider() }, { "moduleName": "samples/array-loop", + "$comment": "Tags intentionally out of order", "tags": [ "1.0.1", "1.10.1", @@ -774,11 +773,11 @@ private IServiceProvider GetServiceProvider() "documentationUri": "https://github.com/Azure/bicep-registry-modules/tree/samples/array-loop/1.0.1/modules/samples/array-loop/README.md" }, "1.0.2": { - "description": "A sample Bicep registry module demonstrating array iterations.", + "description": "v1.0.1: A sample Bicep registry module demonstrating array iterations.", "documentationUri": "https://github.com/Azure/bicep-registry-modules/tree/samples/array-loop/1.0.2/modules/samples/array-loop/README.md" }, "1.0.3": { - "description": "A sample Bicep registry module demonstrating array iterations.", + "description": "v1.0.3: A sample Bicep registry module demonstrating array iterations.", "documentationUri": "https://github.com/Azure/bicep-registry-modules/tree/samples/array-loop/1.0.3/modules/samples/array-loop/README.md" } } @@ -1098,64 +1097,6 @@ public static void ClassInitialize(TestContext _) .Respond("application/json", ModuleIndexJson); } - [TestMethod] - public void GetExponentialDelay_ZeroCount_ShouldGiveInitialDelay() - { - TimeSpan initial = TimeSpan.FromDays(2.5); - TimeSpan max = TimeSpan.FromDays(10); - var delay = PublicModuleMetadataProvider.GetExponentialDelay(initial, 0, max); - - delay.Should().Be(initial); - } - - [TestMethod] - public void GetExponentialDelay_1Count_ShouldGiveDoubleInitialDelay() - { - TimeSpan initial = TimeSpan.FromDays(2.5); - TimeSpan max = TimeSpan.FromDays(10); - var delay = PublicModuleMetadataProvider.GetExponentialDelay(initial, 1, max); - - delay.Should().Be(initial * 2); - } - - [TestMethod] - public void GetExponentialDelay_2Count_ShouldGiveQuadrupleInitialDelay() - { - TimeSpan initial = TimeSpan.FromDays(2.5); - TimeSpan max = TimeSpan.FromDays(10); - var delay = PublicModuleMetadataProvider.GetExponentialDelay(initial, 2, max); - - delay.Should().Be(initial * 4); - } - - [TestMethod] - public void GetExponentialDelay_AboveMaxCount_ShouldGiveMaxDelay() - { - TimeSpan initial = TimeSpan.FromSeconds(1); - TimeSpan max = TimeSpan.FromDays(365); - - TimeSpan exponentiallyGrowingDelay = initial; - int count = 0; - while (exponentiallyGrowingDelay < max * 1000) - { - var delay = PublicModuleMetadataProvider.GetExponentialDelay(initial, count, max); - - if (exponentiallyGrowingDelay < max) - { - delay.Should().Be(exponentiallyGrowingDelay); - } - else - { - delay.Should().Be(max); - } - - delay.Should().BeLessThanOrEqualTo(max); - - ++count; - exponentiallyGrowingDelay *= 2; - } - } - private record ModuleMetadata_Original(string moduleName, List tags); [TestMethod] @@ -1174,50 +1115,50 @@ public void GetModules_ForwardsCompatibleWithOriginalVersion() [TestMethod] public async Task GetModules_Count_SanityCheck() { - PublicModuleMetadataProvider provider = new(GetServiceProvider()); - (await provider.TryUpdateCacheAsync()).Should().BeTrue(); - var modules = provider.GetModulesMetadata(); + PublicModuleMetadataProvider provider = new(CreateTypedClient()); + await provider.TryAwaitCache(false); + var modules = await provider.TryGetModulesAsync(); modules.Should().HaveCount(50); } [TestMethod] - public async Task GetModules_OnlyLastTagHasDescription() + public async Task GetModules_IfOnlyLastTagHasDescription() { - PublicModuleMetadataProvider provider = new(GetServiceProvider()); - (await provider.TryUpdateCacheAsync()).Should().BeTrue(); - var modules = provider.GetModulesMetadata(); - var m = modules.Should().Contain(m => m.Name == "samples/hello-world") + PublicModuleMetadataProvider provider = new(CreateTypedClient()); + await provider.TryAwaitCache(false); + var modules = await provider.TryGetModulesAsync(); + var m = modules.Should().Contain(m => m.ModuleName == "bicep/samples/hello-world") .Which; - m.Description.Should().Be("A \"שָׁלוֹם עוֹלָם\" sample Bicep registry module"); - m.DocumentationUri.Should().Be("https://github.com/Azure/bicep-registry-modules/tree/samples/hello-world/1.0.4/modules/samples/hello-world/README.md"); + var details = await m.TryGetDetailsAsync(); + details.Description.Should().Be("A \"שָׁלוֹם עוֹלָם\" sample Bicep registry module"); + details.DocumentationUri.Should().Be("https://github.com/Azure/bicep-registry-modules/tree/samples/hello-world/1.0.4/modules/samples/hello-world/README.md"); } [TestMethod] - public async Task GetModules_MultipleTagsHaveDescriptions() + public async Task GetModules_IfMultipleTagsHaveDescriptions() { - PublicModuleMetadataProvider provider = new(GetServiceProvider()); - (await provider.TryUpdateCacheAsync()).Should().BeTrue(); - var modules = provider.GetModulesMetadata(); - var m = modules.Should().Contain(m => m.Name == "lz/sub-vending") + PublicModuleMetadataProvider provider = new(CreateTypedClient()); + await provider.TryAwaitCache(false); + var modules = await provider.TryGetModulesAsync(); + var m = modules.Should().Contain(m => m.ModuleName == "bicep/lz/sub-vending") .Which; - m.Description.Should().Be("This module is designed to accelerate deployment of landing zones (aka Subscriptions) within an Azure AD Tenant."); - m.DocumentationUri.Should().Be("https://github.com/Azure/bicep-registry-modules/tree/lz/sub-vending/1.4.2/modules/lz/sub-vending/README.md"); + var details = await m.TryGetDetailsAsync(); + details.Description.Should().Be("v1.4.2: This module is designed to accelerate deployment of landing zones (aka Subscriptions) within an Azure AD Tenant."); + details.DocumentationUri.Should().Be("https://github.com/Azure/bicep-registry-modules/tree/lz/sub-vending/1.4.2/modules/lz/sub-vending/README.md"); } [TestMethod] - public async Task GetModuleVerionsMetadata_ByDefault_ReturnsMetadataSortedByVersion() + public async Task GetModuleVersions_SortsBySemver() { - PublicModuleMetadataProvider provider = new(GetServiceProvider()); - (await provider.TryUpdateCacheAsync()).Should().BeTrue(); + PublicModuleMetadataProvider provider = new(CreateTypedClient()); + var versions = await (await provider.TryGetModuleAsync("bicep/samples/array-loop"))!.TryGetVersionsAsync(); - var versions = provider.GetModuleVersionsMetadata("samples/array-loop").Select(x => x.Version); - - versions.Should().Equal( - "1.10.1", - "1.0.3", - "1.0.2", - "1.0.2-preview", - "1.0.1"); + versions.Select(v => v.Version).Should().Equal( + "1.0.1", + "1.0.2-preview", + "1.0.2", + "1.0.3", + "1.10.1"); } } } diff --git a/src/Bicep.Core.UnitTests/Registry/Catalog/RegistryCatalogTests.cs b/src/Bicep.Core.UnitTests/Registry/Catalog/RegistryCatalogTests.cs new file mode 100644 index 00000000000..0af93a89508 --- /dev/null +++ b/src/Bicep.Core.UnitTests/Registry/Catalog/RegistryCatalogTests.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO.Abstractions.TestingHelpers; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using Bicep.Core.Configuration; +using Bicep.Core.Registry; +using Bicep.Core.Registry.Catalog; +using Bicep.Core.Registry.Catalog.Implementation; +using Bicep.Core.Registry.Oci; +using Bicep.Core.UnitTests.Mock; +using Bicep.Core.UnitTests.Mock.Registry; +using Bicep.Core.UnitTests.Mock.Registry.Catalog; +using Bicep.Core.UnitTests.Utils; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Win32; +using Moq; +using RichardSzalay.MockHttp; + +namespace Bicep.Core.UnitTests.Registry.Catalog +{ + [TestClass] + public class RegistryCatalogTests + { + [TestMethod] + public void GetRegistry_ShouldReturnSameObjectEachTime() + { + var indexer = RegistryCatalogMocks.CreateCatalogWithMocks(null, + RegistryCatalogMocks.MockPrivateMetadataProvider( + "private.contoso.io", + [ + ("bicep/abc", "description", "https://contoso.com/help", [ new("1.0.0", "abc 1.0.0 description", "https://contoso.com/help/abc") ]), + ("bicep/def", "description", "https://contoso.com/help", [ new("1.0.0", "def 1.0.0 description", "https://contoso.com/help/def") ]), + ])); + + var registry = indexer.GetProviderForRegistry(BicepTestConstants.BuiltInConfiguration.Cloud, "private.contoso.io"); + registry.Should().NotBeNull(); + registry.Registry.Should().Be("private.contoso.io"); + indexer.GetProviderForRegistry(BicepTestConstants.BuiltInConfiguration.Cloud, "private.contoso.io").Should().BeSameAs(registry); + indexer.GetProviderForRegistry(BicepTestConstants.BuiltInConfiguration.Cloud, "private.contoso.io").Should().BeSameAs(registry); + + var registry2 = indexer.GetProviderForRegistry(BicepTestConstants.BuiltInConfiguration.Cloud, "mcr.microsoft.com"); + registry2.Should().NotBeNull(); + registry2.Registry.Should().Be("mcr.microsoft.com"); + indexer.GetProviderForRegistry(BicepTestConstants.BuiltInConfiguration.Cloud, "mcr.microsoft.com").Should().BeSameAs(registry2); + indexer.GetProviderForRegistry(BicepTestConstants.BuiltInConfiguration.Cloud, "mcr.microsoft.com").Should().BeSameAs(registry2); + + var registry3 = indexer.GetProviderForRegistry(BicepTestConstants.BuiltInConfiguration.Cloud, "unknown:and:invalid"); + registry3.Should().NotBeNull(); + registry3.Registry.Should().Be("unknown:and:invalid"); + indexer.GetProviderForRegistry(BicepTestConstants.BuiltInConfiguration.Cloud, "unknown:and:invalid").Should().BeSameAs(registry3); + indexer.GetProviderForRegistry(BicepTestConstants.BuiltInConfiguration.Cloud, "unknown:and:invalid").Should().BeSameAs(registry3); + + var registry4 = indexer.GetProviderForRegistry(BicepTestConstants.BuiltInConfiguration.Cloud, "unknown 2"); + registry4.Should().NotBeNull(); + registry4.Registry.Should().Be("unknown 2"); + indexer.GetProviderForRegistry(BicepTestConstants.BuiltInConfiguration.Cloud, "unknown 2").Should().BeSameAs(registry4); + indexer.GetProviderForRegistry(BicepTestConstants.BuiltInConfiguration.Cloud, "unknown 2").Should().BeSameAs(registry4); + + registry.Should().NotBeSameAs(registry2); + registry.Should().NotBeSameAs(registry3); + registry.Should().NotBeSameAs(registry4); + registry2.Should().NotBeSameAs(registry3); + registry2.Should().NotBeSameAs(registry4); + registry3.Should().NotBeSameAs(registry4); + } + + [TestMethod] + public void GetRegistry_ForMcrMicrosoftCom_ShouldReturnPublicRegistry() + { + var publicProvider = RegistryCatalogMocks.MockPublicMetadataProvider([]); + var indexer = RegistryCatalogMocks.CreateCatalogWithMocks( + publicProvider, + RegistryCatalogMocks.MockPrivateMetadataProvider( + "private.contoso.io", + [ + ("bicep/abc", "description", "https://contoso.com/hep", [ new("1.0.0", "abc 1.0.0 description", "https://contoso.com/help/abc") ]), + ("bicep/def", "description", "https://contoso.com/hep", [ new("1.0.0", "def 1.0.0 description", "https://contoso.com/help/def") ]), + ])); + + var registry = indexer.GetProviderForRegistry(BicepTestConstants.BuiltInConfiguration.Cloud, "mcr.microsoft.com"); + registry.Registry.Should().Be("mcr.microsoft.com"); + registry.Should().Be(publicProvider.Object); + + registry.Should().BeAssignableTo(); + } + + [TestMethod] + public void TryGetCachedRegistry() + { + var publicProvider = RegistryCatalogMocks.MockPublicMetadataProvider([]); + var indexer = RegistryCatalogMocks.CreateCatalogWithMocks(publicProvider); + + indexer.TryGetCachedRegistry("mcr.microsoft.com").Should().NotBeNull(); + indexer.TryGetCachedRegistry("private.contoso.io").Should().BeNull(); + + var registry = indexer.GetProviderForRegistry(BicepTestConstants.BuiltInConfiguration.Cloud, "private.contoso.io"); + + indexer.TryGetCachedRegistry("mcr.microsoft.com").Should().NotBeNull(); + indexer.TryGetCachedRegistry("private.contoso.io").Should().NotBeNull(); + + registry.Should().BeAssignableTo(); + registry.Should().NotBeAssignableTo(); + } + } +} diff --git a/src/Bicep.Core.UnitTests/Registry/FakeContainerRegistryClient.cs b/src/Bicep.Core.UnitTests/Registry/FakeContainerRegistryClient.cs new file mode 100644 index 00000000000..4853b6e2179 --- /dev/null +++ b/src/Bicep.Core.UnitTests/Registry/FakeContainerRegistryClient.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Concurrent; +using System.Collections.Immutable; +using System.Reflection; +using Azure; +using Azure.Containers.ContainerRegistry; +using Bicep.Core.Modules; +using Bicep.Core.Registry.Oci; +using Bicep.Core.UnitTests.Mock; +using Microsoft.WindowsAzure.ResourceStack.Common.Extensions; +using Microsoft.WindowsAzure.ResourceStack.Common.Legacy.Table; +using Moq; + +namespace Bicep.Core.UnitTests.Registry +{ + /// + /// Mock OCI registry blob client. This client is intended to represent a single repository within a specific registry Uri. + /// + public class FakeContainerRegistryClient : ContainerRegistryClient + { + public record FakeRepository(string Registry, string Repository, List Tags); + + public FakeContainerRegistryClient() : base() + { + // ensure we call the base parameterless constructor to prevent outgoing calls + } + + public int CallsToGetRepositoryNamesAsync { get; private set; } + public int CallsToGetAllManifestPropertiesAsync { get; private set; } + + public SortedList FakeRepositories { get; } = new(); + + public override AsyncPageable GetRepositoryNamesAsync(CancellationToken cancellationToken = default) + { + CallsToGetRepositoryNamesAsync++; + + var page = Page.FromValues(FakeRepositories.Keys.ToArray(), continuationToken: null, StrictMock.Of().Object); + return AsyncPageable.FromPages([page]); + } + + public override ContainerRepository GetRepository(string repositoryName) + { + var repository = StrictMock.Of(); + repository.Setup(x => x.GetAllManifestPropertiesAsync(It.IsAny(), It.IsAny())) + .Returns((ArtifactManifestOrder order, CancellationToken token) => + { + CallsToGetAllManifestPropertiesAsync++; + + var constructor = typeof(ArtifactManifestProperties).GetConstructor( + BindingFlags.NonPublic | BindingFlags.Instance, + null, + [typeof(string), typeof(DateTimeOffset), typeof(DateTimeOffset)], + null); + + var properties = (ArtifactManifestProperties)constructor!.Invoke(["digest", DateTimeOffset.Now, DateTimeOffset.Now]); + + var tagsField = typeof(ArtifactManifestProperties).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance); + tagsField!.SetValue(properties, FakeRepositories[repositoryName].Tags.ToImmutableArray()); + + return AsyncPageable.FromPages( + new[] { Page.FromValues( + new[] { properties }, null, StrictMock.Of().Object) } + ); + } + ); + return repository.Object; + } + } +} diff --git a/src/Bicep.Core.UnitTests/Registry/FakeRegistryBlobClient.cs b/src/Bicep.Core.UnitTests/Registry/FakeRegistryBlobClient.cs index c5e6c1639b4..226461f3d14 100644 --- a/src/Bicep.Core.UnitTests/Registry/FakeRegistryBlobClient.cs +++ b/src/Bicep.Core.UnitTests/Registry/FakeRegistryBlobClient.cs @@ -14,7 +14,7 @@ namespace Bicep.Core.UnitTests.Registry /// /// Mock OCI registry blob client. This client is intended to represent a single repository within a specific registry Uri. /// - public class FakeRegistryBlobClient : ContainerRegistryContentClient + public class FakeRegistryBlobClient : ContainerRegistryContentClient //adsfg rename to match base class //adsfg rename to fake { public FakeRegistryBlobClient() : base() { diff --git a/src/Bicep.Core.UnitTests/Utils/OciRegistryHelper.cs b/src/Bicep.Core.UnitTests/Utils/OciRegistryHelper.cs index 844c4a2794d..f18a524cebd 100644 --- a/src/Bicep.Core.UnitTests/Utils/OciRegistryHelper.cs +++ b/src/Bicep.Core.UnitTests/Utils/OciRegistryHelper.cs @@ -6,8 +6,8 @@ using Bicep.Core.Features; using Bicep.Core.Modules; using Bicep.Core.Registry; +using Bicep.Core.Registry.Catalog; using Bicep.Core.Registry.Oci; -using Bicep.Core.Registry.PublicRegistry; using Bicep.Core.UnitTests.Mock; using Bicep.Core.UnitTests.Registry; using Bicep.Core.Workspaces; @@ -74,7 +74,7 @@ public static (OciArtifactRegistry, FakeRegistryBlobClient) CreateModuleRegistry var blobClient = new FakeRegistryBlobClient(); var clientFactory = StrictMock.Of(); clientFactory - .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(blobClient); var registry = new OciArtifactRegistry( @@ -90,7 +90,7 @@ public static (OciArtifactRegistry, FakeRegistryBlobClient) CreateModuleRegistry var client = new FakeRegistryBlobClient(); var clientFactory = StrictMock.Of(); - clientFactory.Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), registryUri, repository)).Returns(client); + clientFactory.Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), registryUri, repository)).Returns(client); var containerRegistryManager = new AzureContainerRegistryManager(clientFactory.Object); var configurationManager = BicepTestConstants.ConfigurationManager; @@ -102,7 +102,7 @@ public static (OciArtifactRegistry, FakeRegistryBlobClient) CreateModuleRegistry var moduleReference = CreateModuleReference(registry, repository, "v1", null); await containerRegistryManager.PushArtifactAsync( - configuration: configuration, + configuration.Cloud, artifactReference: moduleReference, mediaType: mediaType, artifactType: artifactType, diff --git a/src/Bicep.Core.UnitTests/Utils/RegistryHelper.cs b/src/Bicep.Core.UnitTests/Utils/RegistryHelper.cs index 5b5d06c8f3c..6f50b4c9135 100644 --- a/src/Bicep.Core.UnitTests/Utils/RegistryHelper.cs +++ b/src/Bicep.Core.UnitTests/Utils/RegistryHelper.cs @@ -24,6 +24,7 @@ using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.WindowsAzure.ResourceStack.Common.Extensions; +using static Bicep.Core.UnitTests.Registry.FakeContainerRegistryClient; using static Bicep.Core.UnitTests.Utils.TestContainerRegistryClientFactoryBuilder; namespace Bicep.Core.UnitTests.Utils; @@ -99,10 +100,13 @@ public static IContainerRegistryClientFactory CreateMockRegistryClient(params Re } public static IContainerRegistryClientFactory CreateMockRegistryClients( + FakeContainerRegistryClient containerRegistryClient, params RepoDescriptor[] repos) { var containerRegistryFactoryBuilder = new TestContainerRegistryClientFactoryBuilder(); + containerRegistryFactoryBuilder.WithFakeContainerRegistryClient(containerRegistryClient); + var modules = DescriptorsToModulesToPublish(repos); foreach (var repo in repos) @@ -160,12 +164,13 @@ public static async Task PublishModuleToRegistryAsync( // ]); public static async Task CreateMockRegistryClientWithPublishedModulesAsync( IFileSystem fileSystem, + FakeContainerRegistryClient containerRegistryClient, params ModuleToPublish[] modules ) { var repos = ModulesToPublishToDescriptors(modules); - var clientFactory = CreateMockRegistryClients(repos); + var clientFactory = CreateMockRegistryClients(containerRegistryClient, repos); foreach (var module in modules) { @@ -179,6 +184,14 @@ await PublishModuleToRegistryAsync( return clientFactory; } + public static async Task CreateMockRegistryClientWithPublishedModulesAsync( + IFileSystem fileSystem, + params ModuleToPublish[] modules + ) + { + return await CreateMockRegistryClientWithPublishedModulesAsync(fileSystem, new FakeContainerRegistryClient(), modules); + } + public static async Task PublishExtensionToRegistryAsync(IDependencyHelper services, string pathToIndexJson, string target) { var fileSystem = services.Construct(); diff --git a/src/Bicep.Core.UnitTests/Utils/ServiceBuilderExtensions.cs b/src/Bicep.Core.UnitTests/Utils/ServiceBuilderExtensions.cs index de43abb9dc9..2903f39dbb4 100644 --- a/src/Bicep.Core.UnitTests/Utils/ServiceBuilderExtensions.cs +++ b/src/Bicep.Core.UnitTests/Utils/ServiceBuilderExtensions.cs @@ -7,7 +7,6 @@ using Bicep.Core.Features; using Bicep.Core.FileSystem; using Bicep.Core.Registry; -using Bicep.Core.Registry.PublicRegistry; using Bicep.Core.Semantics; using Bicep.Core.Semantics.Namespaces; using Bicep.Core.TypeSystem.Providers; diff --git a/src/Bicep.Core.UnitTests/Utils/TestContainerRegistryClientFactoryBuilder.cs b/src/Bicep.Core.UnitTests/Utils/TestContainerRegistryClientFactoryBuilder.cs index 684ea28d434..1d799f4bcb8 100644 --- a/src/Bicep.Core.UnitTests/Utils/TestContainerRegistryClientFactoryBuilder.cs +++ b/src/Bicep.Core.UnitTests/Utils/TestContainerRegistryClientFactoryBuilder.cs @@ -16,12 +16,25 @@ namespace Bicep.Core.UnitTests.Utils public class TestContainerRegistryClientFactoryBuilder { private readonly ImmutableDictionary<(Uri registryUri, string repository), FakeRegistryBlobClient>.Builder blobClientsBuilder = ImmutableDictionary.CreateBuilder<(Uri registryUri, string repository), FakeRegistryBlobClient>(); + private FakeContainerRegistryClient containerClient = new(); public TestContainerRegistryClientFactoryBuilder WithRepository(RepoDescriptor repo, FakeRegistryBlobClient? client = null) { client ??= new FakeRegistryBlobClient(); blobClientsBuilder.TryAdd((new Uri($"https://{repo.Registry}"), repo.Repository), client); + if (!containerClient.FakeRepositories.ContainsKey(repo.Repository)) + { + containerClient.FakeRepositories.Add(repo.Repository, new(repo.Registry, repo.Repository, [.. repo.Tags.Select(t => t.Tag)])); + } + + return this; + } + + public TestContainerRegistryClientFactoryBuilder WithFakeContainerRegistryClient(FakeContainerRegistryClient containerRegistryClient) + { + this.containerClient.FakeRepositories.Should().BeEmpty("Must set up ContainerRegistryClient before adding repos"); + this.containerClient = containerRegistryClient; return this; } @@ -32,11 +45,18 @@ public IContainerRegistryClientFactory Build() var clientFactory = StrictMock.Of(); clientFactory - .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((_, registryUri, repository) => GetBlobClient(registryUri, repository)); + .Setup(m => m.CreateAuthenticatedBlobClient(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((_, registryUri, repository) => GetBlobClient(registryUri, repository)); + clientFactory + .Setup(m => m.CreateAnonymousBlobClient(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((_, registryUri, repository) => GetBlobClient(registryUri, repository)); + + clientFactory + .Setup(m => m.CreateAuthenticatedContainerClient(It.IsAny(), It.IsAny())) + .Returns((_, registryUri) => containerClient); clientFactory - .Setup(m => m.CreateAnonymousBlobClient(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((_, registryUri, repository) => GetBlobClient(registryUri, repository)); + .Setup(m => m.CreateAuthenticatedContainerClient(It.IsAny(), It.IsAny())) + .Returns((_, registryUri) => containerClient); return clientFactory.Object; diff --git a/src/Bicep.Core.UnitTests/packages.lock.json b/src/Bicep.Core.UnitTests/packages.lock.json index 561ff9c7e47..697f38752aa 100644 --- a/src/Bicep.Core.UnitTests/packages.lock.json +++ b/src/Bicep.Core.UnitTests/packages.lock.json @@ -656,8 +656,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + "resolved": "3.1.0", + "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -739,28 +739,25 @@ }, "Microsoft.VisualStudio.Threading": { "type": "Transitive", - "resolved": "17.6.40", - "contentHash": "hLa/0xargG7p3bF7aeq2/lRYn/bVnfZXurUWVHx+MNqxxAUjIDMKi4OIOWbYQ/DTkbn9gv8TLvgso+6EtHVQQg==", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "7.0.0", - "Microsoft.VisualStudio.Threading.Analyzers": "17.6.40", - "Microsoft.VisualStudio.Validation": "17.0.71", - "Microsoft.Win32.Registry": "5.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" } }, "Microsoft.VisualStudio.Validation": { "type": "Transitive", - "resolved": "17.6.11", - "contentHash": "J+9L/iac6c8cwcgVSCMuoIYOlD1Jw4mbZ8XMe1IZVj8p8+3dJ46LnnkIkTRMjK7xs9UtU9MoUp1JGhWoN6fAEw==" + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" }, "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -1318,11 +1315,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -1532,6 +1529,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", @@ -1587,11 +1585,11 @@ "net8.0/linux-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -1994,11 +1992,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2070,11 +2068,11 @@ "net8.0/linux-musl-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2477,11 +2475,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2553,11 +2551,11 @@ "net8.0/linux-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2960,11 +2958,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3036,11 +3034,11 @@ "net8.0/osx-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3443,11 +3441,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3519,11 +3517,11 @@ "net8.0/osx-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3926,11 +3924,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4002,11 +4000,11 @@ "net8.0/win-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4310,11 +4308,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4386,11 +4384,11 @@ "net8.0/win-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4694,11 +4692,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { diff --git a/src/Bicep.Core/Analyzers/Linter/LinterAnalyzer.cs b/src/Bicep.Core/Analyzers/Linter/LinterAnalyzer.cs index e783514742e..72e87e7e8df 100644 --- a/src/Bicep.Core/Analyzers/Linter/LinterAnalyzer.cs +++ b/src/Bicep.Core/Analyzers/Linter/LinterAnalyzer.cs @@ -7,7 +7,7 @@ using Bicep.Core.Configuration; using Bicep.Core.Diagnostics; using Bicep.Core.Parsing; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog; using Bicep.Core.Semantics; using Microsoft.Extensions.DependencyInjection; diff --git a/src/Bicep.Core/Analyzers/Linter/Rules/UseRecentModuleVersionsRule.cs b/src/Bicep.Core/Analyzers/Linter/Rules/UseRecentModuleVersionsRule.cs index f53a1ce2ef4..723320dab54 100644 --- a/src/Bicep.Core/Analyzers/Linter/Rules/UseRecentModuleVersionsRule.cs +++ b/src/Bicep.Core/Analyzers/Linter/Rules/UseRecentModuleVersionsRule.cs @@ -10,7 +10,6 @@ using Bicep.Core.Navigation; using Bicep.Core.Parsing; using Bicep.Core.Registry.Oci; -using Bicep.Core.Registry.PublicRegistry; using Bicep.Core.Resources; using Bicep.Core.Semantics; using Bicep.Core.Syntax; @@ -23,6 +22,7 @@ using Semver; using Semver.Comparers; using System.Collections.Immutable; +using Bicep.Core.Registry.Catalog; namespace Bicep.Core.Analyzers.Linter.Rules { @@ -119,9 +119,17 @@ private static IEnumerable GetFailures(SemanticModel model, IServicePro private static IEnumerable AnalyzeBicepModule(IPublicModuleMetadataProvider publicModuleMetadataProvider, ModuleDeclarationSyntax moduleSyntax, TextSpan errorSpan, string tag, string publicModulePath) { - var availableVersions = publicModuleMetadataProvider.GetModuleVersionsMetadata(publicModulePath) + // NOTE: We don't want linter tests to download anything during analysis. So metadata is loaded + // and cached during module restore. So don't use the Get*Async methods of IPublicModuleMetadataProvider, + // just the GetCached* methods + var fullModuleName = $"{LanguageConstants.BicepPublicMcrPathPrefix}{publicModulePath}"; + var availableVersions = publicModuleMetadataProvider.GetCachedModules() + .FirstOrDefault(m => m.ModuleName.EqualsOrdinally(fullModuleName)) + ?.GetCachedVersions() .Select(v => v.Version) - .ToArray(); + .ToArray() + ?? []; + if (availableVersions.Length == 0) { // If the module doesn't exist, we assume the compiler will flag as an error, no need for us to show anything in the linter. Or else diff --git a/src/Bicep.Core/Bicep.Core.csproj b/src/Bicep.Core/Bicep.Core.csproj index 42ba763092a..4908ef45d72 100644 --- a/src/Bicep.Core/Bicep.Core.csproj +++ b/src/Bicep.Core/Bicep.Core.csproj @@ -50,6 +50,7 @@ + diff --git a/src/Bicep.Core/Configuration/AnalyzersConfigurationExtensions.cs b/src/Bicep.Core/Configuration/AnalyzersConfigurationExtensions.cs index 6cbedf11b35..0ff140eb0f0 100644 --- a/src/Bicep.Core/Configuration/AnalyzersConfigurationExtensions.cs +++ b/src/Bicep.Core/Configuration/AnalyzersConfigurationExtensions.cs @@ -57,5 +57,19 @@ public static RootConfiguration WithAnalyzersDisabled(this RootConfiguration cur public static RootConfiguration WithAllAnalyzers(this RootConfiguration current) => current.WithAnalyzersConfiguration(current.Analyzers.WithAllAnalyzers()); + + public static RootConfiguration WithCloudConfiguration(this RootConfiguration current, CloudConfiguration cloudConfiguration) => + new( + cloudConfiguration, + current.ModuleAliases, + current.Extensions, + current.ImplicitExtensions, + current.Analyzers, + current.CacheRootDirectory, + current.ExperimentalFeaturesEnabled, + current.Formatting, + current.ConfigFileUri, + current.Diagnostics); + } } diff --git a/src/Bicep.Core/Registry/AzureContainerRegistryManager.cs b/src/Bicep.Core/Registry/AzureContainerRegistryManager.cs index 8294a7f5a5b..8d6ff5eb38e 100644 --- a/src/Bicep.Core/Registry/AzureContainerRegistryManager.cs +++ b/src/Bicep.Core/Registry/AzureContainerRegistryManager.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Net; using Azure; using Azure.Containers.ContainerRegistry; using Azure.Identity; @@ -34,22 +35,127 @@ public AzureContainerRegistryManager(IContainerRegistryClientFactory clientFacto this.clientFactory = clientFactory; } + public async Task GetRepositoryNamesAsync( + CloudConfiguration cloud, + string registry, + int maxResults) + { + var registryUri = GetRegistryUri(registry); + + // Note: This won't work for MCR + static async Task GetCatalogAsync(ContainerRegistryClient client, int maxResults) + { + List catalog = []; + + await foreach (var repository in client.GetRepositoryNamesAsync()) + { + // CONSIDER: Allow user to configure a filter for repository names + // Question: If the user has a module alias set up in bicepconfig.json that doesn't match + // the filter, should we still show it in the list? + // Question: What if the user specifically presses CTRL+ENTER on a path that doesn't match the filter? + + if (catalog.Count >= maxResults) + { + Trace.WriteLine($"Stopping catalog enumeration after reaching {maxResults} repositories."); + break; + } + + catalog.Add(repository); + } + + return [.. catalog]; + } + + try + { + // Try authenticated client first. + Trace.WriteLine($"Attempt to list catalog for registry {registryUri} using authentication."); + return await GetCatalogInternalAsync(anonymousAccess: false); + } + catch (RequestFailedException exception) when (exception.Status == 401 || exception.Status == 403) + { + // Fall back to anonymous client. + Trace.WriteLine($"Authenticated attempt to list catalog for registry {registryUri} failed, received code {exception.Status}. Falling back to anonymous."); + return await GetCatalogInternalAsync(anonymousAccess: true); + } + catch (CredentialUnavailableException) + { + // Fall back to anonymous client. + Trace.WriteLine($"Authenticated attempt to pull catalog for registry {registryUri} failed due to missing login step. Falling back to anonymous."); + return await GetCatalogInternalAsync(anonymousAccess: true); + } + + async Task GetCatalogInternalAsync(bool anonymousAccess) + { + var client = CreateContainerClient(cloud, registryUri, anonymousAccess); + return await GetCatalogAsync(client, maxResults); + } + } + + public async Task GetRepositoryTagsAsync( + CloudConfiguration cloud, + string registry, + string repository) + { + var registryUri = GetRegistryUri(registry); + + try + { + // Try authenticated client first. + Trace.WriteLine($"Attempting to list repository tags for module {registryUri}/{repository} using authentication."); + return await GetTagsInternalAsync(anonymousAccess: false); + } + catch (RequestFailedException exception) when (exception.Status == 401 || exception.Status == 403) + { + // Fall back to anonymous client. + Trace.WriteLine($"Authenticated attempt to list repository tags for module {registryUri}/{repository} failed, received code {exception.Status}. Falling back to anonymous."); + return await GetTagsInternalAsync(anonymousAccess: true); + } + catch (CredentialUnavailableException) + { + // Fall back to anonymous client. + Trace.WriteLine($"Authenticated attempt to list repository tags for module {registryUri}/{repository} failed due to missing login step. Falling back to anonymous."); + return await GetTagsInternalAsync(anonymousAccess: true); + } + + async Task GetTagsInternalAsync(bool anonymousAccess) + { + var client = CreateContainerClient(cloud, registryUri, anonymousAccess); + + var tags = new List(); + await foreach (var manifestProps in client.GetRepository(repository).GetAllManifestPropertiesAsync()) + { + foreach (var tag in manifestProps.Tags) + { + tags.Add(tag); + } + } + + return [.. tags]; + } + } + public async Task PullArtifactAsync( - RootConfiguration configuration, - IOciArtifactReference artifactReference) + CloudConfiguration cloud, + IOciArtifactAddressComponents artifactReference) { async Task DownloadManifestInternalAsync(bool anonymousAccess) { - var client = CreateBlobClient(configuration, artifactReference, anonymousAccess); + var client = CreateBlobClient(cloud, artifactReference, anonymousAccess); return await DownloadManifestAndLayersAsync(artifactReference, client); } try { // Try anonymous auth first. - Trace.WriteLine($"Attempting to pull artifact for module {artifactReference.FullyQualifiedReference} with anonymous authentication."); + Trace.WriteLine($"Attempting to pull artifact for module {artifactReference.ArtifactId} with anonymous authentication."); return await DownloadManifestInternalAsync(anonymousAccess: true); } + catch (InvalidArtifactException invalidArtifactException) + { + Trace.WriteLine($"Anonymous authentication failed with invalid artifact exception: {invalidArtifactException.Message}. Not retrying."); + throw; + } catch (RequestFailedException requestedFailedException) when (requestedFailedException.Status is 401 or 403) { Trace.WriteLine($"Anonymous authentication failed with status code {requestedFailedException.Status}. Retrying with authenticated client."); @@ -64,7 +170,7 @@ async Task DownloadManifestInternalAsync(bool anonymousAccess } public async Task PushArtifactAsync( - RootConfiguration configuration, + CloudConfiguration cloud, IOciArtifactReference artifactReference, string? mediaType, string? artifactType, @@ -72,9 +178,8 @@ public async Task PushArtifactAsync( IEnumerable layers, OciManifestAnnotationsBuilder annotations) { - // push is not supported anonymously - var blobClient = this.CreateBlobClient(configuration, artifactReference, anonymousAccess: false); + var blobClient = this.CreateBlobClient(cloud, artifactReference, anonymousAccess: false); _ = await blobClient.UploadBlobAsync(config.Data); @@ -116,17 +221,24 @@ public async Task PushArtifactAsync( _ = await blobClient.SetManifestAsync(manifestBinaryData, artifactReference.Tag, mediaType: ManifestMediaType.OciImageManifest); } - private static Uri GetRegistryUri(IOciArtifactReference artifactReference) => GetRegistryUri(artifactReference.Registry); + private static Uri GetRegistryUri(IOciArtifactAddressComponents artifactReference) => GetRegistryUri(artifactReference.Registry); private static Uri GetRegistryUri(string loginServer) => new($"https://{loginServer}"); private ContainerRegistryContentClient CreateBlobClient( - RootConfiguration configuration, - IOciArtifactReference artifactReference, + CloudConfiguration cloud, + IOciArtifactAddressComponents artifactReference, + bool anonymousAccess) => anonymousAccess + ? this.clientFactory.CreateAnonymousBlobClient(cloud, GetRegistryUri(artifactReference), artifactReference.Repository) + : this.clientFactory.CreateAuthenticatedBlobClient(cloud, GetRegistryUri(artifactReference), artifactReference.Repository); + + private ContainerRegistryClient CreateContainerClient( + CloudConfiguration cloud, + Uri registryUri, bool anonymousAccess) => anonymousAccess - ? this.clientFactory.CreateAnonymousBlobClient(configuration, GetRegistryUri(artifactReference), artifactReference.Repository) - : this.clientFactory.CreateAuthenticatedBlobClient(configuration, GetRegistryUri(artifactReference), artifactReference.Repository); + ? this.clientFactory.CreateAnonymousContainerClient(cloud, registryUri) + : this.clientFactory.CreateAuthenticatedContainerClient(cloud, registryUri); - private static async Task DownloadManifestAndLayersAsync(IOciArtifactReference artifactReference, ContainerRegistryContentClient client) + private static async Task DownloadManifestAndLayersAsync(IOciArtifactAddressComponents artifactReference, ContainerRegistryContentClient client) { Response manifestResponse; try @@ -134,17 +246,21 @@ private static async Task DownloadManifestAndLayersAsync(IOci // either Tag or Digest is null (enforced by reference parser) var tagOrDigest = artifactReference.Tag ?? artifactReference.Digest - ?? throw new ArgumentNullException(nameof(artifactReference), $"The specified artifact reference has both {nameof(artifactReference.Tag)} and {nameof(artifactReference.Digest)} set to null."); + ?? throw new ArgumentNullException($"The specified artifact reference '{artifactReference.ArtifactId}' has both {nameof(artifactReference.Tag)} and {nameof(artifactReference.Digest)} set to null."); manifestResponse = await client.GetManifestAsync(tagOrDigest); } catch (RequestFailedException exception) when (exception.Status == 404) { // manifest does not exist - Trace.WriteLine($"Manifest for module {artifactReference.FullyQualifiedReference} could not be found in the registry."); + Trace.WriteLine($"Manifest for module {artifactReference.ArtifactId} could not be found in the registry."); throw new OciArtifactRegistryException("The artifact does not exist in the registry.", exception); } - Debug.Assert(manifestResponse.Value.Manifest.ToArray().Length > 0); + + if (manifestResponse.Value.Manifest.ToArray().Length == 0) + { + throw new InvalidArtifactException("Invalid manifest"); + } // the Value is disposable, but we are not calling it because we need to pass the stream outside of this scope (and it will GC correctly) using var stream = manifestResponse.Value.Manifest.ToStream(); diff --git a/src/Bicep.Core/Registry/Catalog/IPublicModuleMetadataProvider.cs b/src/Bicep.Core/Registry/Catalog/IPublicModuleMetadataProvider.cs new file mode 100644 index 00000000000..618c2abeeeb --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/IPublicModuleMetadataProvider.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; +using Bicep.Core.Registry.Catalog.Implementation; + +namespace Bicep.Core.Registry.Catalog; + +/// +/// An IRegistryModuleMetadataProvider that will be registered as a singleton to handle +/// the public bicep registry only. +/// +public interface IPublicModuleMetadataProvider : IRegistryModuleMetadataProvider { } diff --git a/src/Bicep.Core/Registry/Catalog/IRegistryModuleCatalog.cs b/src/Bicep.Core/Registry/Catalog/IRegistryModuleCatalog.cs new file mode 100644 index 00000000000..1a7b7dac1d4 --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/IRegistryModuleCatalog.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Bicep.Core.Configuration; +using Bicep.Core.Registry.Catalog.Implementation; + +namespace Bicep.Core.Registry.Catalog; + +/// +/// Retrieves module metadata from OCI registries (public or private) +/// +public interface IRegistryModuleCatalog +{ + // Aways returns a provider instance, either from cache or newly created, from which loading will fail if the registry + // is not accessible or doesn't exist + IRegistryModuleMetadataProvider GetProviderForRegistry(CloudConfiguration cloud, string registry); + + IRegistryModuleMetadataProvider? TryGetCachedRegistry(string registry); +} diff --git a/src/Bicep.Core/Registry/Catalog/Implementation/BaseModuleMetadataProvider.cs b/src/Bicep.Core/Registry/Catalog/Implementation/BaseModuleMetadataProvider.cs new file mode 100644 index 00000000000..687c6ace5a7 --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/Implementation/BaseModuleMetadataProvider.cs @@ -0,0 +1,211 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; +using System.Diagnostics; +using System.Net; +using System.Net.Http.Json; +using System.Linq; +using System.Text.Json; +using Bicep.Core.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Win32; +using System.Threading.Tasks; +using Bicep.Core.Registry.Oci; +using System.Security.Cryptography.Xml; + +namespace Bicep.Core.Registry.Catalog.Implementation; + + +/// +/// Provider to get metadata for modules stored in a public or private registry. +/// +public abstract class BaseModuleMetadataProvider( + string registry +) : IRegistryModuleMetadataProvider +{ + private readonly TimeSpan CacheValidFor = TimeSpan.FromHours(1); + private readonly TimeSpan InitialThrottleDelay = TimeSpan.FromSeconds(5); + private readonly TimeSpan MaxThrottleDelay = TimeSpan.FromMinutes(2); + + private readonly object queryingLiveSyncObject = new(); + private Task? queryLiveDataTask; + private DateTime? lastSuccessfulQuery; + private int consecutiveFailures = 0; + + public string Registry => registry; + + // Using array instead of dictionary to preserve sort order + private ImmutableArray cachedModules = []; + private string? lastDownloadError = null; + + public bool IsCached => cachedModules.Length > 0; + + public string? DownloadError => IsCached ? null : lastDownloadError; + + protected abstract Task> GetLiveDataCoreAsync(); + + protected abstract Task> GetLiveModuleVersionsAsync(string modulePath); + + public Task TryAwaitCache(bool forceUpdate) + { + return UpdateCacheIfNeededAsync(forceUpdate: forceUpdate, initialDelay: false); + } + + public void StartCache() + { + _ = TryAwaitCache(false); + } + + private async Task TryUpdateCacheAsync() + { + if (await TryGetLiveDataAsync() is { } modules) + { + cachedModules = modules; + lastSuccessfulQuery = DateTime.Now; + return true; + } + else + { + return false; + } + } + + protected IRegistryModuleMetadata? GetCachedModule(string modulePath, bool throwIfNotFound) + { + var found = cachedModules.FirstOrDefault(x => + x.Registry.Equals(Registry, StringComparison.Ordinal) + && x.ModuleName.Equals(modulePath, StringComparison.Ordinal)); + if (found != null && throwIfNotFound) + { + throw new InvalidOperationException($"Module '{modulePath}' not found in cache."); + } + + return found; + } + + public async Task> TryGetModulesAsync() + { + await TryAwaitCache(forceUpdate: false); + return GetCachedModules(); + } + + // If cache has not yet successfully been updated, returns empty + public ImmutableArray GetCachedModules() + { + return [.. cachedModules + .Where(x => x.Registry.Equals(Registry, StringComparison.Ordinal)) + ]; + } + + private Task UpdateCacheIfNeededAsync(bool forceUpdate, bool initialDelay) + { + if (DownloadError is not null) + { + Trace.WriteLine($"{nameof(BaseModuleMetadataProvider)}: [{Registry}] Last cache load failed, trying again..."); + } + else if (lastSuccessfulQuery is null) + { + Trace.WriteLineIf(IsCacheExpired(), $"{nameof(BaseModuleMetadataProvider)}: [{Registry}] First data retrieval..."); + } + else if (forceUpdate) + { + Trace.WriteLine($"{nameof(BaseModuleMetadataProvider)}: [{Registry}] Force updating cache..."); + } + else if (IsCacheExpired()) + { + Trace.WriteLineIf(IsCacheExpired(), $"{nameof(BaseModuleMetadataProvider)}: [{Registry}] Cache expired, updating..."); + } + else + { + return Task.CompletedTask; + } + + lock (queryingLiveSyncObject) + { + if (queryLiveDataTask is { }) + { + return queryLiveDataTask; + } + + return queryLiveDataTask = QueryData(initialDelay); + } + + Task QueryData(bool initialDelay) + { + return Task.Run(async () => + { + try + { + int delay = 0; + if (initialDelay) + { + // Allow language server to start up a bit before first hit + delay = InitialThrottleDelay.Milliseconds; + } + if (consecutiveFailures > 0) + { + // Throttle requests to avoid spamming the endpoint with unsuccessful requests + delay = int.Max(delay, GetExponentialDelay(InitialThrottleDelay, consecutiveFailures, MaxThrottleDelay).Milliseconds); // make second try fast + } + + if (delay > 0) + { + Trace.WriteLine($"{nameof(BaseModuleMetadataProvider)}: [{Registry}] Delaying {delay} before retry..."); + await Task.Delay(delay); + } + + if (await TryUpdateCacheAsync()) + { + consecutiveFailures = 0; + } + else + { + consecutiveFailures++; + } + } + finally + { + lock (queryingLiveSyncObject) + { + Trace.Assert(queryLiveDataTask is { }, $"{nameof(BaseModuleMetadataProvider)}: [{Registry}] Should be querying live data"); + queryLiveDataTask = null; + } + } + }); + } + } + + private bool IsCacheExpired() + { + var expired = lastSuccessfulQuery.HasValue && lastSuccessfulQuery.Value + CacheValidFor < DateTime.Now; + if (expired) + { + Trace.TraceInformation($"{nameof(BaseModuleMetadataProvider)}: [{Registry}] Cache has expired."); + } + + return expired; + } + + private async Task?> TryGetLiveDataAsync() + { + try + { + return await GetLiveDataCoreAsync(); + } + catch (Exception ex) + { + lastDownloadError = ex.Message; + return null; + } + } + + public static TimeSpan GetExponentialDelay(TimeSpan initialDelay, int consecutiveFailures, TimeSpan maxDelay) + { + var maxFailuresToConsider = (int)Math.Ceiling(Math.Log(maxDelay.TotalSeconds, 2)); // Avoid overflow on Math.Pow() + var secondsDelay = initialDelay.TotalSeconds * Math.Pow(2, Math.Min(consecutiveFailures, maxFailuresToConsider)); + var delay = TimeSpan.FromSeconds(secondsDelay); + + return delay > maxDelay ? maxDelay : delay; + } +} diff --git a/src/Bicep.Core/Registry/Catalog/Implementation/IRegistryModuleMetadataProvider.cs b/src/Bicep.Core/Registry/Catalog/Implementation/IRegistryModuleMetadataProvider.cs new file mode 100644 index 00000000000..8ee6bd1f2c5 --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/Implementation/IRegistryModuleMetadataProvider.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; +using Bicep.Core.Configuration; +using Bicep.Core.Registry.Oci; +using Microsoft.Win32; +using static Bicep.Core.Registry.Catalog.Implementation.IRegistryModuleMetadata; + +namespace Bicep.Core.Registry.Catalog.Implementation; + +public interface IRegistryModuleMetadata +{ + public string Registry { get; init; } // e.g. "mcr.microsoft.com" + public string ModuleName { get; init; } // e.g. "bicep/avm/app/dapr-containerapp" + + public Task TryGetDetailsAsync(); + + // In order returned by the registry, generally oldest first + public Task> TryGetVersionsAsync(); + + public ImmutableArray GetCachedVersions(); +} + +/// +/// Retrieves metadata about modules from a specific OCI registry (public or private). +/// Use IRegistryCatalog to retrieve an instance for a specific registry. +/// +public interface IRegistryModuleMetadataProvider +{ + public string Registry { get; } + + bool IsCached { get; } + + string? DownloadError { get; } + + void StartCache(); + + Task TryAwaitCache(bool forceUpdate = false); + + Task> TryGetModulesAsync(); + + ImmutableArray GetCachedModules(); +} + diff --git a/src/Bicep.Core/Registry/Catalog/Implementation/IRegistryModuleMetadataProviderExtensions.cs b/src/Bicep.Core/Registry/Catalog/Implementation/IRegistryModuleMetadataProviderExtensions.cs new file mode 100644 index 00000000000..fc2c106b475 --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/Implementation/IRegistryModuleMetadataProviderExtensions.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Bicep.Core.Registry.Catalog.Implementation; + +public static class IRegistryModuleMetadataProviderExtensions +{ + public static async Task TryGetModuleAsync(this IRegistryModuleMetadataProvider provider, string modulePath) + { + return (await provider.TryGetModulesAsync()) + .FirstOrDefault(x => x.ModuleName.Equals(modulePath, StringComparison.Ordinal)); + } +} diff --git a/src/Bicep.Core/Registry/Catalog/Implementation/MightBeLazyAsync.cs b/src/Bicep.Core/Registry/Catalog/Implementation/MightBeLazyAsync.cs new file mode 100644 index 00000000000..0da08504d67 --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/Implementation/MightBeLazyAsync.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.VisualStudio.Threading; + +namespace Bicep.Core.Registry.Catalog.Implementation; + +public class MightBeLazyAsync where T : class +{ + private readonly AsyncLazy? lazy; + private readonly T? value; + + public MightBeLazyAsync(T value) + { + lazy = null; + this.value = value; + } + + public MightBeLazyAsync(Func> initializer) + { + lazy = new AsyncLazy(initializer, new(new JoinableTaskContext())); + value = default; + } + + private bool IsLazy => lazy is not null; + + public bool TryGetValue([NotNullWhen(true)] out T? value) + { + if (IsLazy) + { + if (lazy!.IsValueFactoryCompleted) + { + // Apparently no way to detect if the value factory threw an exception (even though it completed), so guard for it here + try + { + value = lazy.GetValue(); + return true; + } + catch (Exception) + { + value = default; + return false; + } + + } + else + { + value = default; + return false; + } + } + else + { + value = this.value!; + return true; + } + } + + public async Task GetValueAsync() + { + return IsLazy ? await lazy!.GetValueAsync() : value!; + } +} diff --git a/src/Bicep.Core/Registry/Catalog/Implementation/PrivateRegistries/IPrivateAcrModuleMetadataProviderFactory.cs b/src/Bicep.Core/Registry/Catalog/Implementation/PrivateRegistries/IPrivateAcrModuleMetadataProviderFactory.cs new file mode 100644 index 00000000000..e1958f82ad8 --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/Implementation/PrivateRegistries/IPrivateAcrModuleMetadataProviderFactory.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; +using Bicep.Core.Configuration; +using Microsoft.Win32; + +namespace Bicep.Core.Registry.Catalog.Implementation.PrivateRegistries; + +/// +/// Creates IRegistryModuleMetadataProvider instances +/// +public interface IPrivateAcrModuleMetadataProviderFactory +{ + public IRegistryModuleMetadataProvider Create(CloudConfiguration cloud, string registry, IContainerRegistryClientFactory containerRegistryClientFactory); +} diff --git a/src/Bicep.Core/Registry/Catalog/Implementation/PrivateRegistries/PrivateAcrModuleMetadataProvider.cs b/src/Bicep.Core/Registry/Catalog/Implementation/PrivateRegistries/PrivateAcrModuleMetadataProvider.cs new file mode 100644 index 00000000000..a6b682b9ab7 --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/Implementation/PrivateRegistries/PrivateAcrModuleMetadataProvider.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO.Packaging; +using System.Net; +using System.Net.Http.Json; +using System.Text.Json; +using System.Text.RegularExpressions; +using Bicep.Core.Configuration; +using Bicep.Core.Extensions; +using Bicep.Core.Registry.Oci; +using Microsoft.Extensions.DependencyInjection; +using static Bicep.Core.Registry.Catalog.RegistryModuleMetadata; + +namespace Bicep.Core.Registry.Catalog.Implementation.PrivateRegistries; + +/// +/// Provider to get modules metadata from a private ACR registry +/// +public class PrivateAcrModuleMetadataProvider : BaseModuleMetadataProvider, IRegistryModuleMetadataProvider +{ + private const int MaxReturnedModules = 10000; + + private readonly CloudConfiguration cloud; + private readonly IContainerRegistryClientFactory containerRegistryClientFactory; + + public PrivateAcrModuleMetadataProvider( + CloudConfiguration cloud, + string registry, + IContainerRegistryClientFactory containerRegistryClientFactory + ) : base(registry) + { + this.cloud = cloud; + this.containerRegistryClientFactory = containerRegistryClientFactory; + } + + protected override async Task> GetLiveModuleVersionsAsync(string modulePath) + { + var registry = Registry; + var repository = modulePath; + + AzureContainerRegistryManager acrManager = new(containerRegistryClientFactory); + + // For this part we want to throw on errors + var tags = await acrManager.GetRepositoryTagsAsync(cloud, registry, repository); + + // For the rest, we'll be resilient + return [.. await Task.WhenAll( + tags.Select(async tag => await TryGetLiveModuleVersionMetadataAsync(modulePath, tag)) + )]; + } + + private async Task TryGetLiveModuleVersionMetadataAsync(string modulePath, string version) + { + try + { + AzureContainerRegistryManager acrManager = new(containerRegistryClientFactory); + var artifactResult = await acrManager.PullArtifactAsync(cloud, new OciArtifactAddressComponents(Registry, modulePath, version, null)); + var manifest = artifactResult.Manifest; + + if (manifest.ArtifactType != BicepMediaTypes.BicepModuleArtifactType) + { + return RegistryModuleVersionMetadata.UnexpectedArtifactType(version, manifest.ArtifactType ?? "(null)"); + } + + string? description = null; + string? documentationUri = null; + string? title = null; + + manifest.Annotations?.TryGetValue(OciAnnotationKeys.OciOpenContainerImageDescriptionAnnotation, out description); + manifest.Annotations?.TryGetValue(OciAnnotationKeys.OciOpenContainerImageDocumentationAnnotation, out documentationUri); + manifest.Annotations?.TryGetValue(OciAnnotationKeys.OciOpenContainerImageTitleAnnotation, out title); + + return new RegistryModuleVersionMetadata( + version, + IsBicepModule: true, + new RegistryMetadataDetails(description ?? title, documentationUri)); + } + catch (InvalidArtifactException ex) + { + Trace.WriteLine($"Invalid Bicep module {modulePath}, version {version}: {ex.Message}"); + return RegistryModuleVersionMetadata.InvalidModule(version, ex); + } + catch (Exception ex) + { + Trace.WriteLine($"Failed to get version details for module {modulePath} version {version}: {ex.Message}"); + return RegistryModuleVersionMetadata.DownloadError(version, ex); + } + } + + protected override async Task> GetLiveDataCoreAsync() + { + Trace.WriteLine($"Retrieving catalog for registry {Registry}..."); + + AzureContainerRegistryManager acrManager = new(containerRegistryClientFactory); + var catalog = await acrManager.GetRepositoryNamesAsync(cloud, Registry, MaxReturnedModules); + + Trace.WriteLine($"Found {catalog.Length} repositories"); + + var modules = catalog + .Reverse() // Reverse to search the latest modules first + .Select(m => new RegistryModuleMetadata( + Registry, + m, + async () => + { + var versions = await GetLiveModuleVersionsAsync(m); + var moduleDetails = GetModuleDetails(versions); + return new ComputedData(moduleDetails, versions); + }) + ).ToImmutableArray(); + + return [.. modules.Cast()]; + } + + private RegistryMetadataDetails GetModuleDetails(ImmutableArray versions) + { + // OCI modules don't have a description or documentation URI, we just use the most recent version with valid metadata + var lastVersion = versions.LastOrDefault(x => + x.IsBicepModule == true + && (!string.IsNullOrWhiteSpace(x.Details.Description) || !string.IsNullOrWhiteSpace(x.Details.DocumentationUri))); + lastVersion ??= versions.LastOrDefault(x => x.IsBicepModule == true && !string.IsNullOrWhiteSpace(x.Details.DocumentationUri)); + lastVersion ??= versions.LastOrDefault(x => x.IsBicepModule == true); + lastVersion ??= versions.LastOrDefault(); + + if (lastVersion is { }) + { + return lastVersion.Details; + } + + return new RegistryMetadataDetails(null, null); + } +} diff --git a/src/Bicep.Core/Registry/Catalog/Implementation/PrivateRegistries/PrivateAcrModuleMetadataProviderFactory.cs b/src/Bicep.Core/Registry/Catalog/Implementation/PrivateRegistries/PrivateAcrModuleMetadataProviderFactory.cs new file mode 100644 index 00000000000..3385445f52f --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/Implementation/PrivateRegistries/PrivateAcrModuleMetadataProviderFactory.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; +using Bicep.Core.Configuration; +using Microsoft.Win32; +using Microsoft.WindowsAzure.ResourceStack.Common.Extensions; + +namespace Bicep.Core.Registry.Catalog.Implementation.PrivateRegistries; + +/// +/// Creates IRegistryModuleMetadataProvider instances for private registries +/// +public class PrivateAcrModuleMetadataProviderFactory : IPrivateAcrModuleMetadataProviderFactory +{ + public IRegistryModuleMetadataProvider Create(CloudConfiguration cloud, string registry, IContainerRegistryClientFactory containerRegistryClientFactory) + { + return new PrivateAcrModuleMetadataProvider(cloud, registry, containerRegistryClientFactory); + } +} diff --git a/src/Bicep.Core/Registry/Catalog/Implementation/PublicRegistries/IPublicModuleIndexHttpClient.cs b/src/Bicep.Core/Registry/Catalog/Implementation/PublicRegistries/IPublicModuleIndexHttpClient.cs new file mode 100644 index 00000000000..d93d6468dff --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/Implementation/PublicRegistries/IPublicModuleIndexHttpClient.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; + +namespace Bicep.Core.Registry.Catalog.Implementation.PublicRegistries; + +public interface IPublicModuleIndexHttpClient +{ + Task> GetModuleIndexAsync(); +} + +public readonly record struct PublicModuleIndexProperties(string Description, string DocumentationUri); + diff --git a/src/Bicep.Core/Registry/PublicRegistry/IPublicModuleIndexClient.cs b/src/Bicep.Core/Registry/Catalog/Implementation/PublicRegistries/PublicModuleIndexHttpClient.cs similarity index 52% rename from src/Bicep.Core/Registry/PublicRegistry/IPublicModuleIndexClient.cs rename to src/Bicep.Core/Registry/Catalog/Implementation/PublicRegistries/PublicModuleIndexHttpClient.cs index d0b4865283d..580a6d24a50 100644 --- a/src/Bicep.Core/Registry/PublicRegistry/IPublicModuleIndexClient.cs +++ b/src/Bicep.Core/Registry/Catalog/Implementation/PublicRegistries/PublicModuleIndexHttpClient.cs @@ -5,22 +5,23 @@ using System.Text.Json.Serialization; using Bicep.Core.Extensions; using Bicep.Core.Registry.Oci; +using Microsoft.WindowsAzure.ResourceStack.Common.Extensions; using Semver; using Semver.Comparers; -namespace Bicep.Core.Registry.PublicRegistry; - -public readonly record struct PublicModuleProperties(string Description, string DocumentationUri); +namespace Bicep.Core.Registry.Catalog.Implementation.PublicRegistries; +/// +/// This is the DTO for modules listed in the public bicep registry +/// public record PublicModuleIndexEntry( - [property: JsonPropertyName("moduleName")] string ModulePath, // e.g. "avm/app/dapr-containerapp" - ImmutableArray Tags, // e.g. "1.0.0" (not guaranteed to be in that format, although it currently is for public modules) - [property: JsonPropertyName("properties")] ImmutableDictionary PropertiesByTag // Module properties per tag + [property: JsonPropertyName("moduleName")] string ModulePath, // e.g. "avm/app/dapr-containerapp" - the "bicep/" prefix is assumed and not included in the index + ImmutableArray Tags, // e.g. "1.0.0" (not guaranteed to be in semver format, although it currently is for all our public modules) + [property: JsonPropertyName("properties")] ImmutableDictionary PropertiesByTag // Module properties per tag ) { private static readonly SemVersion DefaultVersion = new(0); - // Sort tags by version numbers in descending order. public ImmutableArray Versions { get @@ -29,25 +30,24 @@ public ImmutableArray Versions var parsedVersions = Tags.Select(x => (@string: x, version: SemVersion.TryParse(x, SemVersionStyles.AllowV, out var version) ? version : DefaultVersion)) .ToArray(); - return parsedVersions.OrderByDescending(x => x.version, SemVersion.SortOrderComparer) - .Select(x => x.@string) - .ToImmutableArray(); + // Sort by ascending version number here, the completion provider will reverse it to show the most recent version first + return [.. parsedVersions.OrderByAscending(x => x.version, SemVersion.SortOrderComparer).Select(x => x.@string)]; } } } - public string? GetDescription(string? version = null) => this.GetProperty(version, x => x.Description); + public string? GetDescription(string? version = null) => GetProperty(version, x => x.Description); - public string? GetDocumentationUri(string? version = null) => this.GetProperty(version, x => x.DocumentationUri); + public string? GetDocumentationUri(string? version = null) => GetProperty(version, x => x.DocumentationUri); - private string? GetProperty(string? version, Func propertySelector) + private string? GetProperty(string? version, Func propertySelector) { if (version is null) { // Get description for most recent version with a description - foreach (var tag in this.Versions) + foreach (var tag in Versions.Reverse()) { - if (this.PropertiesByTag.TryGetValue(tag, out var propertiesEntry)) + if (PropertiesByTag.TryGetValue(tag, out var propertiesEntry)) { return propertySelector(propertiesEntry); } @@ -57,12 +57,7 @@ public ImmutableArray Versions } else { - return this.PropertiesByTag.TryGetValue(version, out var propertiesEntry) ? propertySelector(propertiesEntry) : null; + return PropertiesByTag.TryGetValue(version, out var propertiesEntry) ? propertySelector(propertiesEntry) : null; } } } - -public interface IPublicModuleIndexClient -{ - Task> GetModuleIndexAsync(); -} diff --git a/src/Bicep.Core/Registry/PublicRegistry/PublicModuleMetadataClient.cs b/src/Bicep.Core/Registry/Catalog/Implementation/PublicRegistries/PublicModuleMetadataHttpClient.cs similarity index 76% rename from src/Bicep.Core/Registry/PublicRegistry/PublicModuleMetadataClient.cs rename to src/Bicep.Core/Registry/Catalog/Implementation/PublicRegistries/PublicModuleMetadataHttpClient.cs index 4508eee2764..b1b5e2b13d2 100644 --- a/src/Bicep.Core/Registry/PublicRegistry/PublicModuleMetadataClient.cs +++ b/src/Bicep.Core/Registry/Catalog/Implementation/PublicRegistries/PublicModuleMetadataHttpClient.cs @@ -10,24 +10,21 @@ using Bicep.Core.Extensions; using Microsoft.Extensions.DependencyInjection; -namespace Bicep.Core.Registry.PublicRegistry; +namespace Bicep.Core.Registry.Catalog.Implementation.PublicRegistries; /// /// Typed http client to get modules metadata that we store at a public endpoint (currently https://github.com/Azure/bicep-registry-modules) /// -public class PublicModuleMetadataClient(HttpClient httpClient) : IPublicModuleIndexClient +public class PublicModuleMetadataHttpClient(HttpClient httpClient) : IPublicModuleIndexHttpClient { private const string LiveDataEndpoint = "https://aka.ms/br-module-index-data"; - private static readonly JsonSerializerOptions JsonSerializerOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; + private static readonly JsonSerializerOptions JsonSerializerOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; [SuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Relying on references to required properties of the generic type elsewhere in the codebase.")] public async Task> GetModuleIndexAsync() { - Trace.WriteLine($"{nameof(PublicModuleMetadataClient)}: Retrieving list of public registry modules..."); + Trace.WriteLine($"{nameof(PublicModuleMetadataHttpClient)}: Retrieving list of public registry modules..."); try { @@ -35,7 +32,7 @@ public async Task> GetModuleIndexAsync() if (metadata is not null) { - Trace.WriteLine($"{nameof(PublicModuleMetadataClient)}: Retrieved info on {metadata.Length} public registry modules."); + Trace.WriteLine($"{nameof(PublicModuleMetadataHttpClient)}: Retrieved info on {metadata.Length} public registry modules."); return [.. metadata]; } else diff --git a/src/Bicep.Core/Registry/Catalog/Implementation/PublicRegistries/PublicModuleMetadataProvider.cs b/src/Bicep.Core/Registry/Catalog/Implementation/PublicRegistries/PublicModuleMetadataProvider.cs new file mode 100644 index 00000000000..51cf84e5c21 --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/Implementation/PublicRegistries/PublicModuleMetadataProvider.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Http.Json; +using System.Reflection; +using System.Text.Json; +using Bicep.Core.Extensions; +using Microsoft.Extensions.DependencyInjection; +using static Bicep.Core.Registry.Catalog.RegistryModuleMetadata; + +namespace Bicep.Core.Registry.Catalog.Implementation.PublicRegistries; + +/// +/// Provider to get modules metadata that we store at the public mcr.microsoft.com/bicep registry. +/// +public class PublicModuleMetadataProvider : BaseModuleMetadataProvider, IPublicModuleMetadataProvider +{ + private readonly IPublicModuleIndexHttpClient client; + + public PublicModuleMetadataProvider(IPublicModuleIndexHttpClient publicModuleIndexClient) + : base(LanguageConstants.BicepPublicMcrRegistry) + { + client = publicModuleIndexClient; + } + + protected override async Task> GetLiveDataCoreAsync() + { + var modules = await client.GetModuleIndexAsync(); + + return [.. modules.Select(m => + { + var moduleDetails = new RegistryMetadataDetails(m.GetDescription(), m.GetDocumentationUri()); + var versions = ImmutableArray.Create([.. m.Versions.Select( + t => new RegistryModuleVersionMetadata( + t, + IsBicepModule: true, + new RegistryMetadataDetails( + m.PropertiesByTag.ContainsKey(t) ? m.PropertiesByTag[t].Description:null, + m.PropertiesByTag.ContainsKey(t) ? m.PropertiesByTag[t].DocumentationUri:null) + ) + )]); + return new RegistryModuleMetadata( + Registry, + $"{LanguageConstants.BicepPublicMcrPathPrefix}{m.ModulePath}", + new ComputedData(moduleDetails, versions)); + })]; + } + + protected override Task> GetLiveModuleVersionsAsync(string modulePath) + { + throw new NotImplementedException("This method should never get called because versions are pre-filled with a resolved task"); throw new NotImplementedException(); + } +} diff --git a/src/Bicep.Core/Registry/Catalog/Implementation/RegistryCatalogExtensionsForIServiceCollection.cs b/src/Bicep.Core/Registry/Catalog/Implementation/RegistryCatalogExtensionsForIServiceCollection.cs new file mode 100644 index 00000000000..637ee96aa61 --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/Implementation/RegistryCatalogExtensionsForIServiceCollection.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Net; +using Bicep.Core.Configuration; +using Bicep.Core.Registry.Catalog.Implementation.PrivateRegistries; +using Bicep.Core.Registry.Catalog.Implementation.PublicRegistries; +using Microsoft.Extensions.DependencyInjection; + +namespace Bicep.Core.Registry.Catalog.Implementation; + +public static class RegistryCatalogExtensionsForIServiceCollection +{ + public static IServiceCollection AddRegistryCatalogServices(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + // using type based registration for Http clients so dependencies can be injected automatically + // without manually constructing up the graph, see https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory#typed-clients + services + .AddHttpClient() + .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler + { + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + }); + + return services; + } +} diff --git a/src/Bicep.Core/Registry/Catalog/Implementation/RegistryModuleCatalog.cs b/src/Bicep.Core/Registry/Catalog/Implementation/RegistryModuleCatalog.cs new file mode 100644 index 00000000000..6e1b9a3615d --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/Implementation/RegistryModuleCatalog.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Bicep.Core.Configuration; +using Bicep.Core.Registry.Catalog.Implementation.PrivateRegistries; +using Microsoft.Extensions.DependencyInjection; + +namespace Bicep.Core.Registry.Catalog.Implementation; + +/// +/// Provides a list of modules from a registry (public or private) +/// +public class RegistryModuleCatalog : IRegistryModuleCatalog +{ + private readonly IPrivateAcrModuleMetadataProviderFactory providerFactory; + private readonly IContainerRegistryClientFactory containerRegistryClientFactory; + private readonly IConfigurationManager configurationManager; + private readonly object lockObject = new(); + + private readonly Dictionary registryProviders = new(); + + public RegistryModuleCatalog( + IPublicModuleMetadataProvider publicModuleMetadataProvider, + IPrivateAcrModuleMetadataProviderFactory privateProviderFactory, + IContainerRegistryClientFactory containerRegistryClientFactory, + IConfigurationManager configurationManager + ) + { + providerFactory = privateProviderFactory; + this.containerRegistryClientFactory = containerRegistryClientFactory; + this.configurationManager = configurationManager; + + registryProviders["mcr.microsoft.com"] = publicModuleMetadataProvider; + } + + public IRegistryModuleMetadataProvider GetProviderForRegistry(CloudConfiguration cloud, string registry) + { + lock (lockObject) + { + if (registryProviders.TryGetValue(registry, out var provider)) + { + return provider; + } + + provider = providerFactory.Create(cloud, registry, containerRegistryClientFactory); + registryProviders[registry] = provider; + + return provider; + } + } + + public IRegistryModuleMetadataProvider? TryGetCachedRegistry(string registry) + { + if (registryProviders.TryGetValue(registry, out var provider)) + { + return provider; + } + + return null; + } +} diff --git a/src/Bicep.Core/Registry/Catalog/RegistryMetadataDetails.cs b/src/Bicep.Core/Registry/Catalog/RegistryMetadataDetails.cs new file mode 100644 index 00000000000..4a76ab649db --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/RegistryMetadataDetails.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Bicep.Core.Registry.Catalog; + +/// +/// Metadata about a module or a version. +/// +/// +/// +public record RegistryMetadataDetails( + string? Description, + string? DocumentationUri); + diff --git a/src/Bicep.Core/Registry/Catalog/RegistryModuleMetadata.cs b/src/Bicep.Core/Registry/Catalog/RegistryModuleMetadata.cs new file mode 100644 index 00000000000..2e9fe5cd9df --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/RegistryModuleMetadata.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; +using Bicep.Core.Registry.Catalog.Implementation; + +namespace Bicep.Core.Registry.Catalog; + +public class RegistryModuleMetadata : IRegistryModuleMetadata +{ + public string Registry { get; init; } + public string ModuleName { get; init; } + + public record ComputedData( + RegistryMetadataDetails Details, + ImmutableArray Versions + ); + + public readonly MightBeLazyAsync data; + + public RegistryModuleMetadata( + string registry, + string moduleName, + ComputedData computedData) + { + this.Registry = registry; + this.ModuleName = moduleName; + + this.data = new(computedData); + } + + public RegistryModuleMetadata( + string registry, + string moduleName, + Func> getDataAsyncFunc) + { + this.Registry = registry; + this.ModuleName = moduleName; + + this.data = new(getDataAsyncFunc); + } + + public async Task TryGetDetailsAsync() + { + try + { + return (await data.GetValueAsync()).Details; + } + catch + { + return new RegistryMetadataDetails(null, null); + } + } + + public async Task> TryGetVersionsAsync() + { + try + { + return (await data.GetValueAsync()).Versions; + } + catch + { + return []; + } + } + + public ImmutableArray GetCachedVersions() + { + if (this.data.TryGetValue(out var data)) + { + return data.Versions; + } + else + { + return []; + } + } +} diff --git a/src/Bicep.Core/Registry/Catalog/RegistryModuleVersionMetadata.cs b/src/Bicep.Core/Registry/Catalog/RegistryModuleVersionMetadata.cs new file mode 100644 index 00000000000..5822bdf80c2 --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/RegistryModuleVersionMetadata.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Bicep.Core.Registry.Catalog; + +public record RegistryModuleVersionMetadata( + string Version, + bool? IsBicepModule, // null = unknown + RegistryMetadataDetails Details +) +{ + public static RegistryModuleVersionMetadata UnexpectedArtifactType(string version, string actualArtifactType) + => new(version, false, new($"Not a valid Bicep module. Found artifact type {actualArtifactType}", null)); + + public static RegistryModuleVersionMetadata InvalidModule(string version, Exception ex) + => new(version, false, new($"Not a valid Bicep module. {ex.Message}", null)); + + public static RegistryModuleVersionMetadata DownloadError(string version, Exception ex) + => new(version, false, new(ex.Message, null)); +} diff --git a/src/Bicep.Core/Registry/ContainerRegistryClientFactory.cs b/src/Bicep.Core/Registry/ContainerRegistryClientFactory.cs index 4570ec29bf1..2d0dadb51a7 100644 --- a/src/Bicep.Core/Registry/ContainerRegistryClientFactory.cs +++ b/src/Bicep.Core/Registry/ContainerRegistryClientFactory.cs @@ -17,24 +17,44 @@ public ContainerRegistryClientFactory(ITokenCredentialFactory credentialFactory) this.credentialFactory = credentialFactory; } - public ContainerRegistryContentClient CreateAuthenticatedBlobClient(RootConfiguration configuration, Uri registryUri, string repository) + public ContainerRegistryContentClient CreateAuthenticatedBlobClient(CloudConfiguration cloud, Uri registryUri, string repository) { var options = new ContainerRegistryClientOptions(); options.Diagnostics.ApplySharedContainerRegistrySettings(); - options.Audience = new ContainerRegistryAudience(configuration.Cloud.ResourceManagerAudience); + options.Audience = new ContainerRegistryAudience(cloud.ResourceManagerAudience); - var credential = this.credentialFactory.CreateChain(configuration.Cloud.CredentialPrecedence, configuration.Cloud.CredentialOptions, configuration.Cloud.ActiveDirectoryAuthorityUri); + var credential = this.credentialFactory.CreateChain(cloud.CredentialPrecedence, cloud.CredentialOptions, cloud.ActiveDirectoryAuthorityUri); return new(registryUri, repository, credential, options); } - public ContainerRegistryContentClient CreateAnonymousBlobClient(RootConfiguration configuration, Uri registryUri, string repository) + public ContainerRegistryContentClient CreateAnonymousBlobClient(CloudConfiguration cloud, Uri registryUri, string repository) { var options = new ContainerRegistryClientOptions(); options.Diagnostics.ApplySharedContainerRegistrySettings(); - options.Audience = new ContainerRegistryAudience(configuration.Cloud.ResourceManagerAudience); + options.Audience = new ContainerRegistryAudience(cloud.ResourceManagerAudience); return new(registryUri, repository, options); } + + public ContainerRegistryClient CreateAuthenticatedContainerClient(CloudConfiguration cloud, Uri registryUri) + { + var options = new ContainerRegistryClientOptions(); + options.Diagnostics.ApplySharedContainerRegistrySettings(); + options.Audience = new ContainerRegistryAudience(cloud.ResourceManagerAudience); + + var credential = this.credentialFactory.CreateChain(cloud.CredentialPrecedence, cloud.CredentialOptions, cloud.ActiveDirectoryAuthorityUri); + + return new(registryUri, credential, options); + } + + public ContainerRegistryClient CreateAnonymousContainerClient(CloudConfiguration cloud, Uri registryUri) + { + var options = new ContainerRegistryClientOptions(); + options.Diagnostics.ApplySharedContainerRegistrySettings(); + options.Audience = new ContainerRegistryAudience(cloud.ResourceManagerAudience); + + return new(registryUri, options); + } } } diff --git a/src/Bicep.Core/Registry/DefaultArtifactRegistryProvider.cs b/src/Bicep.Core/Registry/DefaultArtifactRegistryProvider.cs index bf37db16fbd..801aae2d363 100644 --- a/src/Bicep.Core/Registry/DefaultArtifactRegistryProvider.cs +++ b/src/Bicep.Core/Registry/DefaultArtifactRegistryProvider.cs @@ -6,7 +6,7 @@ using Bicep.Core.Configuration; using Bicep.Core.Features; using Bicep.Core.FileSystem; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog; using Microsoft.Extensions.DependencyInjection; namespace Bicep.Core.Registry diff --git a/src/Bicep.Core/Registry/IContainerRegistryClientFactory.cs b/src/Bicep.Core/Registry/IContainerRegistryClientFactory.cs index d1633ca8280..b2562466155 100644 --- a/src/Bicep.Core/Registry/IContainerRegistryClientFactory.cs +++ b/src/Bicep.Core/Registry/IContainerRegistryClientFactory.cs @@ -12,8 +12,15 @@ namespace Bicep.Core.Registry /// This exists because we need to inject mock clients in integration tests and because the real client constructor requires parameters. public interface IContainerRegistryClientFactory { - ContainerRegistryContentClient CreateAuthenticatedBlobClient(RootConfiguration configuration, Uri registryUri, string repository); + ContainerRegistryContentClient CreateAuthenticatedBlobClient(CloudConfiguration cloud, Uri registryUri, string repository); + ContainerRegistryContentClient CreateAnonymousBlobClient(CloudConfiguration cloud, Uri registryUri, string repository); + + public ContainerRegistryClient CreateAuthenticatedContainerClient(CloudConfiguration cloud, Uri registryUri); + public ContainerRegistryClient CreateAnonymousContainerClient(CloudConfiguration cloud, Uri registryUri); + } + + public interface IContainerRegistryClient + { - ContainerRegistryContentClient CreateAnonymousBlobClient(RootConfiguration configuration, Uri registryUri, string repository); } } diff --git a/src/Bicep.Core/Registry/ModuleDispatcher.cs b/src/Bicep.Core/Registry/ModuleDispatcher.cs index 92a59dbb54f..af3b038a62b 100644 --- a/src/Bicep.Core/Registry/ModuleDispatcher.cs +++ b/src/Bicep.Core/Registry/ModuleDispatcher.cs @@ -12,7 +12,7 @@ using Bicep.Core.FileSystem; using Bicep.Core.Modules; using Bicep.Core.Navigation; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog; using Bicep.Core.Semantics; using Bicep.Core.SourceCode; using Bicep.Core.Syntax; diff --git a/src/Bicep.Core/Registry/Oci/IOciArtifactAddressComponents.cs b/src/Bicep.Core/Registry/Oci/IOciArtifactAddressComponents.cs index ca63c2122ef..900eb3aee2f 100644 --- a/src/Bicep.Core/Registry/Oci/IOciArtifactAddressComponents.cs +++ b/src/Bicep.Core/Registry/Oci/IOciArtifactAddressComponents.cs @@ -4,7 +4,7 @@ namespace Bicep.Core.Registry.Oci { /// - /// Represents a module/provider reference without the scheme ("br:") and without any aliases + /// Represents a module/provider reference without the scheme (e.g. "br:") and without any aliases /// /// test.azurecr.io/foo/bar:latest public interface IOciArtifactAddressComponents diff --git a/src/Bicep.Core/Registry/Oci/IOciArtifactReference.cs b/src/Bicep.Core/Registry/Oci/IOciArtifactReference.cs index 2527a75e670..bb949c65748 100644 --- a/src/Bicep.Core/Registry/Oci/IOciArtifactReference.cs +++ b/src/Bicep.Core/Registry/Oci/IOciArtifactReference.cs @@ -8,5 +8,8 @@ public interface IOciArtifactReference : IOciArtifactAddressComponents /// /// Gets the fully qualified artifact reference, which includes the scheme. /// + /// + /// br:mcr.microsoft.com/bicep/avm/ptn/aca-lza/hosting-environment:0.1.1 + /// string FullyQualifiedReference { get; } } diff --git a/src/Bicep.Core/Registry/OciArtifactRegistry.cs b/src/Bicep.Core/Registry/OciArtifactRegistry.cs index 616e0e1ae3f..171729d0be1 100644 --- a/src/Bicep.Core/Registry/OciArtifactRegistry.cs +++ b/src/Bicep.Core/Registry/OciArtifactRegistry.cs @@ -14,7 +14,6 @@ using Bicep.Core.FileSystem; using Bicep.Core.Modules; using Bicep.Core.Registry.Oci; -using Bicep.Core.Registry.PublicRegistry; using Bicep.Core.Semantics; using Bicep.Core.SourceCode; using Bicep.Core.Tracing; @@ -22,6 +21,7 @@ using Bicep.Core.Workspaces; using Bicep.IO.Abstraction; using JsonSerializer = System.Text.Json.JsonSerializer; +using Bicep.Core.Registry.Catalog; namespace Bicep.Core.Registry { @@ -90,7 +90,7 @@ public override async Task CheckArtifactExists(ArtifactType artifactType, try { // Get module - await this.containerRegistryManager.PullArtifactAsync(reference.ReferencingFile.Configuration, reference); + await this.containerRegistryManager.PullArtifactAsync(reference.ReferencingFile.Configuration.Cloud, reference); } catch (RequestFailedException exception) when (exception.Status == 404) { @@ -277,7 +277,7 @@ public override async Task PublishModule(OciArtifactReference reference, BinaryD try { await this.containerRegistryManager.PushArtifactAsync( - reference.ReferencingFile.Configuration, + reference.ReferencingFile.Configuration.Cloud, reference, // Technically null should be fine for mediaType, but ACR guys recommend OciImageManifest for safer compatibility ManifestMediaType.OciImageManifest.ToString(), @@ -332,7 +332,7 @@ public override async Task PublishExtension(OciArtifactReference reference, Exte try { await this.containerRegistryManager.PushArtifactAsync( - reference.ReferencingFile.Configuration, + reference.ReferencingFile.Configuration.Cloud, reference, // Technically null should be fine for mediaType, but ACR guys recommend OciImageManifest for safer compatibility ManifestMediaType.OciImageManifest.ToString(), @@ -484,7 +484,7 @@ protected override IDirectoryHandle GetArtifactDirectory(OciArtifactReference re { try { - var result = await containerRegistryManager.PullArtifactAsync(configuration, reference); + var result = await containerRegistryManager.PullArtifactAsync(configuration.Cloud, reference); await WriteArtifactContentToCacheAsync(reference, result); diff --git a/src/Bicep.Core/Registry/PublicRegistry/IPublicModuleMetadataProvider.cs b/src/Bicep.Core/Registry/PublicRegistry/IPublicModuleMetadataProvider.cs deleted file mode 100644 index 9ea4676346a..00000000000 --- a/src/Bicep.Core/Registry/PublicRegistry/IPublicModuleMetadataProvider.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Immutable; - -namespace Bicep.Core.Registry.PublicRegistry; - -public readonly record struct PublicModuleMetadata( - string Name, // e.g. "avm/app/dapr-containerapp" (note: the actual repo name has "bicep/" at the beginning, but the "public" alias takes care of that) - string? Description, - string? DocumentationUri); - -public readonly record struct PublicModuleVersionMetadata( - string Version, - string? Description, - string? DocumentationUri); - -public interface IPublicModuleMetadataProvider -{ - public bool IsCached { get; } - - public string? DownloadError { get; } - - public Task TryAwaitCache(bool forceUpdate = false); - - public void StartUpdateCache(bool forceUpdate = false); - - ImmutableArray GetModulesMetadata(); - - ImmutableArray GetModuleVersionsMetadata(string modulePath); -} diff --git a/src/Bicep.Core/Registry/PublicRegistry/PublicModuleMetadataProvider.cs b/src/Bicep.Core/Registry/PublicRegistry/PublicModuleMetadataProvider.cs deleted file mode 100644 index 516467e6bc1..00000000000 --- a/src/Bicep.Core/Registry/PublicRegistry/PublicModuleMetadataProvider.cs +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Immutable; -using System.Diagnostics; -using System.Net; -using System.Net.Http.Json; -using System.Text.Json; -using Bicep.Core.Extensions; -using Microsoft.Extensions.DependencyInjection; - -namespace Bicep.Core.Registry.PublicRegistry; - -public static class PublicModuleMetadataProviderExtensions -{ - public static IServiceCollection AddPublicModuleMetadataProviderServices(this IServiceCollection services) - { - services.AddSingleton(); - - // using type based registration for Http clients so dependencies can be injected automatically - // without manually constructing up the graph, see https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory#typed-clients - services - .AddHttpClient() - .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler - { - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate - }); - - return services; - } -} - - -/// -/// Provider to get modules metadata that we store at a public endpoint. -/// -public class PublicModuleMetadataProvider : IPublicModuleMetadataProvider -{ - private readonly IServiceProvider serviceProvider; - - private readonly TimeSpan CacheValidFor = TimeSpan.FromHours(1); - private readonly TimeSpan InitialThrottleDelay = TimeSpan.FromSeconds(5); - private readonly TimeSpan MaxThrottleDelay = TimeSpan.FromMinutes(2); - - private readonly object queryingLiveSyncObject = new(); - private Task? queryLiveDataTask; - private DateTime? lastSuccessfulQuery; - private int consecutiveFailures = 0; - - private ImmutableArray cachedIndex = []; - private string? lastDownloadError = null; - - public bool IsCached => cachedIndex.Length > 0; - - public string? DownloadError => IsCached ? null : lastDownloadError; - - public PublicModuleMetadataProvider(IServiceProvider serviceProvider) - { - this.serviceProvider = serviceProvider; - } - - public Task TryAwaitCache(bool forceUpdate) - { - return UpdateCacheIfNeeded(forceUpdate: forceUpdate, initialDelay: false); - } - - public void StartUpdateCache(bool forceUpdate) - { - _ = TryAwaitCache(forceUpdate); - } - public async Task TryUpdateCacheAsync() - { - if (await TryGetLiveIndexAsync() is { } modules) - { - this.cachedIndex = modules; - this.lastSuccessfulQuery = DateTime.Now; - return true; - } - else - { - return false; - } - } - - // If cache has not yet successfully been updated, returns empty - public ImmutableArray GetModulesMetadata() - { - StartCacheUpdateInBackgroundIfNeeded(); - - return [.. this.cachedIndex.Select(x => new PublicModuleMetadata(x.ModulePath, x.GetDescription(), x.GetDocumentationUri()))]; - } - - public ImmutableArray GetModuleVersionsMetadata(string modulePath) - { - StartCacheUpdateInBackgroundIfNeeded(); - - PublicModuleIndexEntry? entry = this.cachedIndex.FirstOrDefault(x => x.ModulePath.Equals(modulePath, StringComparison.Ordinal)); - - if (entry == null) - { - return []; - } - - return [.. entry.Versions.Select(version => new PublicModuleVersionMetadata(version, entry.GetDescription(version), entry.GetDocumentationUri(version)))]; - } - - private void StartCacheUpdateInBackgroundIfNeeded(bool initialDelay = false) - { - _ = UpdateCacheIfNeeded(forceUpdate: false, initialDelay); - } - - private Task UpdateCacheIfNeeded(bool forceUpdate, bool initialDelay) - { - if (!this.cachedIndex.Any()) - { - Trace.WriteLineIf(IsCacheExpired(), $"{nameof(PublicModuleMetadataProvider)}: First data retrieval..."); - } - else if (forceUpdate) - { - Trace.WriteLine($"{nameof(PublicModuleMetadataProvider)}: Force updating cache..."); - } - else if (IsCacheExpired()) - { - Trace.WriteLineIf(IsCacheExpired(), $"{nameof(PublicModuleMetadataProvider)}: Cache expired, updating..."); - } - else - { - return Task.CompletedTask; - } - - lock (this.queryingLiveSyncObject) - { - if (this.queryLiveDataTask is { }) - { - return this.queryLiveDataTask; - } - - return this.queryLiveDataTask = QueryData(initialDelay); - } - - Task QueryData(bool initialDelay) - { - return Task.Run(async () => - { - try - { - int delay = 0; - if (initialDelay) - { - // Allow language server to start up a bit before first hit - delay = InitialThrottleDelay.Milliseconds; - } - if (consecutiveFailures > 0) - { - // Throttle requests to avoid spamming the endpoint with unsuccessful requests - delay = int.Max(delay, GetExponentialDelay(InitialThrottleDelay, this.consecutiveFailures, MaxThrottleDelay).Milliseconds); // make second try fast - } - - if (delay > 0) - { - Trace.WriteLine($"{nameof(PublicModuleMetadataProvider)}: Delaying {delay} before retry..."); - await Task.Delay(delay); - } - - if (await TryUpdateCacheAsync()) - { - this.consecutiveFailures = 0; - } - else - { - this.consecutiveFailures++; - } - } - finally - { - lock (this.queryingLiveSyncObject) - { - Trace.Assert(this.queryLiveDataTask is { }, $"{nameof(PublicModuleMetadataProvider)}: should be querying live data"); - this.queryLiveDataTask = null; - } - } - }); - } - } - - private bool IsCacheExpired() - { - var expired = this.lastSuccessfulQuery.HasValue && this.lastSuccessfulQuery.Value + this.CacheValidFor < DateTime.Now; - if (expired) - { - Trace.TraceInformation($"{nameof(PublicModuleMetadataProvider)}: Public modules cache is expired."); - } - - return expired; - } - - private async Task?> TryGetLiveIndexAsync() - { - try - { - var client = serviceProvider.GetRequiredService(); - var modules = await client.GetModuleIndexAsync(); - - return modules; - } - catch (Exception ex) - { - this.lastDownloadError = ex.Message; - return null; - } - } - - public static TimeSpan GetExponentialDelay(TimeSpan initialDelay, int consecutiveFailures, TimeSpan maxDelay) - { - var maxFailuresToConsider = (int)Math.Ceiling(Math.Log(maxDelay.TotalSeconds, 2)); // Avoid overflow on Math.Pow() - var secondsDelay = initialDelay.TotalSeconds * Math.Pow(2, Math.Min(consecutiveFailures, maxFailuresToConsider)); - var delay = TimeSpan.FromSeconds(secondsDelay); - - return delay > maxDelay ? maxDelay : delay; - } -} diff --git a/src/Bicep.Core/packages.lock.json b/src/Bicep.Core/packages.lock.json index 2ed5090b01d..5874f24227a 100644 --- a/src/Bicep.Core/packages.lock.json +++ b/src/Bicep.Core/packages.lock.json @@ -210,6 +210,16 @@ "Microsoft.SourceLink.Common": "8.0.0" } }, + "Microsoft.VisualStudio.Threading": { + "type": "Direct", + "requested": "[17.12.19, )", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", + "dependencies": { + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" + } + }, "Microsoft.VisualStudio.Threading.Analyzers": { "type": "Direct", "requested": "[17.12.19, )", @@ -509,6 +519,11 @@ "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, + "Microsoft.VisualStudio.Validation": { + "type": "Transitive", + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" + }, "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "4.7.0", diff --git a/src/Bicep.Decompiler.IntegrationTests/packages.lock.json b/src/Bicep.Decompiler.IntegrationTests/packages.lock.json index 064a46d1387..020a9754759 100644 --- a/src/Bicep.Decompiler.IntegrationTests/packages.lock.json +++ b/src/Bicep.Decompiler.IntegrationTests/packages.lock.json @@ -621,8 +621,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + "resolved": "3.1.0", + "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -704,28 +704,25 @@ }, "Microsoft.VisualStudio.Threading": { "type": "Transitive", - "resolved": "17.6.40", - "contentHash": "hLa/0xargG7p3bF7aeq2/lRYn/bVnfZXurUWVHx+MNqxxAUjIDMKi4OIOWbYQ/DTkbn9gv8TLvgso+6EtHVQQg==", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "7.0.0", - "Microsoft.VisualStudio.Threading.Analyzers": "17.6.40", - "Microsoft.VisualStudio.Validation": "17.0.71", - "Microsoft.Win32.Registry": "5.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" } }, "Microsoft.VisualStudio.Validation": { "type": "Transitive", - "resolved": "17.6.11", - "contentHash": "J+9L/iac6c8cwcgVSCMuoIYOlD1Jw4mbZ8XMe1IZVj8p8+3dJ46LnnkIkTRMjK7xs9UtU9MoUp1JGhWoN6fAEw==" + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" }, "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -1312,11 +1309,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -1526,6 +1523,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", @@ -1598,11 +1596,11 @@ "net8.0/linux-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2005,11 +2003,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2081,11 +2079,11 @@ "net8.0/linux-musl-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2488,11 +2486,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2564,11 +2562,11 @@ "net8.0/linux-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2971,11 +2969,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3047,11 +3045,11 @@ "net8.0/osx-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3454,11 +3452,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3530,11 +3528,11 @@ "net8.0/osx-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3937,11 +3935,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4013,11 +4011,11 @@ "net8.0/win-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4321,11 +4319,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4397,11 +4395,11 @@ "net8.0/win-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4705,11 +4703,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { diff --git a/src/Bicep.Decompiler.UnitTests/packages.lock.json b/src/Bicep.Decompiler.UnitTests/packages.lock.json index 064a46d1387..020a9754759 100644 --- a/src/Bicep.Decompiler.UnitTests/packages.lock.json +++ b/src/Bicep.Decompiler.UnitTests/packages.lock.json @@ -621,8 +621,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + "resolved": "3.1.0", + "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -704,28 +704,25 @@ }, "Microsoft.VisualStudio.Threading": { "type": "Transitive", - "resolved": "17.6.40", - "contentHash": "hLa/0xargG7p3bF7aeq2/lRYn/bVnfZXurUWVHx+MNqxxAUjIDMKi4OIOWbYQ/DTkbn9gv8TLvgso+6EtHVQQg==", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "7.0.0", - "Microsoft.VisualStudio.Threading.Analyzers": "17.6.40", - "Microsoft.VisualStudio.Validation": "17.0.71", - "Microsoft.Win32.Registry": "5.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" } }, "Microsoft.VisualStudio.Validation": { "type": "Transitive", - "resolved": "17.6.11", - "contentHash": "J+9L/iac6c8cwcgVSCMuoIYOlD1Jw4mbZ8XMe1IZVj8p8+3dJ46LnnkIkTRMjK7xs9UtU9MoUp1JGhWoN6fAEw==" + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" }, "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -1312,11 +1309,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -1526,6 +1523,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", @@ -1598,11 +1596,11 @@ "net8.0/linux-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2005,11 +2003,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2081,11 +2079,11 @@ "net8.0/linux-musl-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2488,11 +2486,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2564,11 +2562,11 @@ "net8.0/linux-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2971,11 +2969,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3047,11 +3045,11 @@ "net8.0/osx-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3454,11 +3452,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3530,11 +3528,11 @@ "net8.0/osx-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3937,11 +3935,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4013,11 +4011,11 @@ "net8.0/win-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4321,11 +4319,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4397,11 +4395,11 @@ "net8.0/win-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4705,11 +4703,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { diff --git a/src/Bicep.Decompiler/packages.lock.json b/src/Bicep.Decompiler/packages.lock.json index 1766bb64233..9ec6dab33b9 100644 --- a/src/Bicep.Decompiler/packages.lock.json +++ b/src/Bicep.Decompiler/packages.lock.json @@ -440,6 +440,20 @@ "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, + "Microsoft.VisualStudio.Threading": { + "type": "Transitive", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", + "dependencies": { + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" + } + }, + "Microsoft.VisualStudio.Validation": { + "type": "Transitive", + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" + }, "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "4.7.0", @@ -970,6 +984,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", diff --git a/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs b/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs index 04a4e9c4d18..39f29a41bc3 100644 --- a/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs @@ -9,7 +9,7 @@ using Bicep.Core.Extensions; using Bicep.Core.FileSystem; using Bicep.Core.Parsing; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Oci; using Bicep.Core.Samples; using Bicep.Core.Text; using Bicep.Core.UnitTests; @@ -41,6 +41,12 @@ using CompilationHelper = Bicep.Core.UnitTests.Utils.CompilationHelper; using LocalFileSystem = System.IO.Abstractions.FileSystem; using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; +using Bicep.Core.UnitTests.Mock.Registry; +using Bicep.Core.Registry.Catalog; +using Bicep.Core.Configuration; +using Bicep.Core.Workspaces; +using Bicep.Core.Json; +using Bicep.Core.UnitTests.Mock.Registry.Catalog; namespace Bicep.LangServer.IntegrationTests.Completions { @@ -372,7 +378,7 @@ param myArray array ); var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var symbolCompletions = completions.Items.Where(x => x.Kind == CompletionItemKind.Field || x.Kind == CompletionItemKind.Variable || @@ -1057,7 +1063,7 @@ public async Task Item_completions_are_offered_for_array_typed_parameter_default var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "required-properties"); updatedFile.Should().HaveSourceText(""" @@ -1130,7 +1136,7 @@ resource myRes Te|st var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "'Test.Rp/basicTests'"); updatedFile.Should().HaveSourceText(@" @@ -1150,7 +1156,7 @@ resource myRes 'Test.Rp/ba|si var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "'Test.Rp/basicTests'"); updatedFile.Should().HaveSourceText(@" @@ -1166,7 +1172,7 @@ resource myRes 'Test.Rp/basicTests@| var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "2020-01-01"); updatedFile.Should().HaveSourceText(@" @@ -1722,7 +1728,7 @@ public async Task RequestModulePathCompletions_ArmTemplateFilesInDir_ReturnsComp var file = new FileRequestHelper(helper.Client, mainFile); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().SatisfyRespectively( x => x.Label.Should().Be("module2.bicep"), x => x.Label.Should().Be("module3.bicep"), @@ -1919,7 +1925,7 @@ extension kubernetes with | as k8s var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithExtensibilityEnabled).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "{}"); completions.Should().Contain(x => x.Label == "required-properties"); @@ -1942,7 +1948,7 @@ extension kubernetes with { var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithExtensibilityEnabled).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "namespace"); completions.Should().Contain(x => x.Label == "kubeConfig"); @@ -1963,7 +1969,7 @@ public async Task TypeCompletionsIncludeAmbientTypes() "; var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, 'ǂ'); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "string"); completions.Should().Contain(x => x.Label == "int"); completions.Should().Contain(x => x.Label == "bool"); @@ -1980,7 +1986,7 @@ public async Task TypeCompletionsIncludeUserDefinedTypes() "; var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, 'ǂ'); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "b"); } @@ -1997,7 +2003,7 @@ public async Task UnionTypeMembersShouldReceiveTypeCompletions() "; var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, 'ǂ'); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "b"); completions.Should().Contain(x => x.Label == "c"); completions.Should().Contain(x => x.Label == "d"); @@ -2012,7 +2018,7 @@ public async Task UnionTypeMembersWithSeparationShouldReceiveTypeCompletions() "; var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, 'ǂ'); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "b"); } @@ -2031,7 +2037,7 @@ public async Task UnionTypeMemberCompletionsShouldNotIncludeNonLiteralTypes() "; var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, 'ǂ'); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "b"); completions.Should().NotContain(x => x.Label == "c"); completions.Should().NotContain(x => x.Label == "d"); @@ -2051,7 +2057,7 @@ public async Task TypeCompletionsShouldNotIncludeCyclicReferences() "; var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, 'ǂ'); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().NotContain(x => x.Label == "a"); } @@ -2063,7 +2069,7 @@ public async Task UnionTypeMemberCompletionsShouldNotIncludeCyclicReferences() "; var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, 'ǂ'); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().NotContain(x => x.Label == "a"); } @@ -2075,7 +2081,7 @@ public async Task UnaryOperationTypeCompletionsShouldNotIncludeCyclicReferences( "; var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, 'ǂ'); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().NotContain(x => x.Label == "a"); } @@ -2088,7 +2094,7 @@ param stringParam | "; var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "myString"); } @@ -2101,7 +2107,7 @@ output stringOutput | "; var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "myString"); } @@ -2116,7 +2122,7 @@ public async Task TypePropertyDecoratorsShouldAlignWithPropertyType() """; var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "description"); completions.Should().Contain(x => x.Label == "metadata"); @@ -2151,7 +2157,7 @@ public async Task ModuleCompletionsShouldNotBeUrlEscaped() using var helper = await LanguageServerHelper.StartServerWithText(this.TestContext, files, bicepFile.Uri, services => services.WithNamespaceProvider(BuiltInTestTypes.Create())); var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.OrderBy(x => x.SortText).Should().SatisfyRespectively( x => x.Label.Should().Be("already%20escaped.bicep"), @@ -2354,7 +2360,7 @@ public async Task Known_list_functions_are_offered() var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "listNoInput"); completions.Should().Contain(x => x.Label == "listWithInput"); @@ -2380,7 +2386,7 @@ param ir resource 'Test.Rp/listFuncTests@2020-01-01' var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithResourceTypedParamsEnabled).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "listNoInput"); completions.Should().Contain(x => x.Label == "listWithInput"); @@ -2406,7 +2412,7 @@ public async Task List_functions_accepting_inputs_suggest_api_version_param() var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "'2020-01-01'"); var updatedFile = file.ApplyCompletion(completions, "'2020-01-01'"); @@ -2433,7 +2439,7 @@ public async Task List_functions_accepting_inputs_suggest_required_properties() var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "required-properties"); var updatedFile = file.ApplyCompletion(completions, "required-properties", "foo"); @@ -2465,7 +2471,7 @@ public async Task List_functions_accepting_inputs_permit_object_key_completions( var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "optionalVal"); var updatedFile = file.ApplyCompletion(completions, "optionalVal"); @@ -2582,7 +2588,7 @@ public async Task List_functions_accepting_inputs_permit_object_value_completion var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "'either'"); completions.Should().Contain(x => x.Label == "'or'"); @@ -2603,7 +2609,7 @@ public async Task List_functions_return_property_completions() var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "withInputOutputVal"); var updatedFile = file.ApplyCompletion(completions, "withInputOutputVal"); @@ -2658,7 +2664,7 @@ public async Task VerifyCompletionRequestAfterPoundSign_ShouldReturnCompletionIt var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, LanguageConstants.DisableNextLineDiagnosticsKeyword); updatedFile.Should().HaveSourceText(@"#disable-next-line| @@ -2701,7 +2707,7 @@ public async Task List_comprehension_functions_return_lambda_snippets_single_arg var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "arg => ...", "foo"); updatedFile.Should().HaveSourceText(@" var foo = map([123], foo => |) @@ -2718,7 +2724,7 @@ public async Task List_comprehension_functions_return_lambda_snippets_multiple_a var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "(arg1, arg2) => ...", "foo", "bar"); updatedFile.Should().HaveSourceText(@" var foo = sort([123], (foo, bar) => |) @@ -2736,7 +2742,7 @@ func foo(innerVar string) string => '${|}' var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().NotContain(x => x.Label == "outerVar"); var updatedFile = file.ApplyCompletion(completions, "innerVar"); updatedFile.Should().HaveSourceText(""" @@ -2758,7 +2764,7 @@ func foo(innerVar string) string => '${|}' var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "bar"); var updatedFile = file.ApplyCompletion(completions, "bar"); updatedFile.Should().HaveSourceText(""" @@ -2778,7 +2784,7 @@ public async Task Func_keyword_completion_provides_snippet() var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "func", "foo", "string"); updatedFile.Should().HaveSourceText(@" func foo() string => | @@ -2800,7 +2806,7 @@ public async Task Func_lambda_output_type_completions_only_suggest_types(string var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "string"); updatedFile.Should().HaveSourceText($""" {after} @@ -2819,7 +2825,7 @@ public async Task Func_lambda_argument_type_completions_only_suggest_types(strin var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "string"); updatedFile.Should().HaveSourceText($""" {after} @@ -2839,7 +2845,7 @@ public async Task Func_lambda_argument_name_offers_no_completions(string before) var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().BeEmpty(); } @@ -2855,7 +2861,7 @@ func isTrue(input bool) bool => !(input == false) var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "isTrue"); updatedFile.Should().HaveSourceText($""" @@ -2936,7 +2942,7 @@ public async Task VerifyCompletionRequestAfterPoundSign_WithWhiteSpaceBeforePoun var (text, cursors) = ParserHelper.GetFileWithCursors(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); completions.Should().Contain(x => x.Label == LanguageConstants.DisableNextLineDiagnosticsKeyword); var updatedFile = file.ApplyCompletion(completions, LanguageConstants.DisableNextLineDiagnosticsKeyword); @@ -2945,7 +2951,7 @@ public async Task VerifyCompletionRequestAfterPoundSign_WithWhiteSpaceBeforePoun # param storageAccount2 string = 'testAccount'"); - completions = await file.RequestCompletion(cursors[1]); + completions = await file.RequestAndResolveCompletions(cursors[1]); completions.Should().Contain(x => x.Label == LanguageConstants.DisableNextLineDiagnosticsKeyword); updatedFile = file.ApplyCompletion(completions, LanguageConstants.DisableNextLineDiagnosticsKeyword); @@ -2978,7 +2984,7 @@ public async Task VerifyCompletionRequestAfterPoundSign_WithinResource_ShouldRet var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == LanguageConstants.DisableNextLineDiagnosticsKeyword); var updatedFile = file.ApplyCompletion(completions, LanguageConstants.DisableNextLineDiagnosticsKeyword); @@ -3025,7 +3031,7 @@ public async Task VerifyCompletionRequestAfterDisableNextLineKeyword_ShouldRetur var (text, cursors) = ParserHelper.GetFileWithCursors(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithNamespaceProvider).OpenFile(text); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); completions.Should().Contain(x => x.Label == "no-unused-params"); var updatedFile = file.ApplyCompletion(completions, "no-unused-params"); @@ -3048,7 +3054,7 @@ public async Task VerifyCompletionRequestAfterDisableNextLineKeyword_ShouldRetur properties: vmProperties }"); - completions = await file.RequestCompletion(cursors[1]); + completions = await file.RequestAndResolveCompletions(cursors[1]); completions.Should().Contain(x => x.Label == "BCP036"); completions.Should().Contain(x => x.Label == "BCP037"); @@ -3116,7 +3122,7 @@ public async Task VerifyCompletionRequestAfterDiagnosticCodeInDisableNextLineDir var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithNamespaceProvider).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "BCP037"); var updatedFile = file.ApplyCompletion(completions, "BCP037"); @@ -3148,7 +3154,7 @@ public async Task VerifyCompletionRequestAfterDisableNextLineDirective_WithDiagn var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithNamespaceProvider).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "no-unused-vars"); completions.Should().Contain(x => x.Label == "prefer-interpolation"); @@ -3235,7 +3241,7 @@ public async Task VerifyDisableNextLineDiagnosticsDirectiveCompletionIsNotAvaila x => x.Code.Should().Be("BCP068"), x => x.Code.Should().Be("BCP029")); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().BeEmpty(); } @@ -3313,7 +3319,7 @@ public async Task VerifyCompletionRequestResourceDependsOn_ResourceSymbolsVeryHi ); var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); completions.Should().Contain(c => c.Label == "aResource" && c.SortText == $"{(int)CompletionPriority.VeryHigh}_aResource"); completions.Should().Contain(c => c.Label == "aModule" && c.SortText == $"{(int)CompletionPriority.VeryHigh}_aModule"); @@ -3473,7 +3479,7 @@ public async Task VerifyCompletionRequestResourceDependsOn_ResourceSymbolsVeryHi var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithNamespaceProvider).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(c => c.Label == "aResource" && c.SortText == $"{(int)CompletionPriority.VeryHigh}_aResource"); completions.Should().NotContain(c => c.Label == "foo"); } @@ -3523,7 +3529,7 @@ public async Task VerifyCompletionRequestModuleDependsOn_ResourceSymbolsVeryHigh ); var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); completions.Should().Contain(c => c.Label == "aResource" && c.SortText == $"{(int)CompletionPriority.VeryHigh}_aResource"); completions.Should().Contain(c => c.Label == "bModule" && c.SortText == $"{(int)CompletionPriority.VeryHigh}_bModule"); @@ -3659,7 +3665,7 @@ private static async Task RunSingleCompletionScenarioTest(TestCo { var file = await new ServerRequestHelper(testContext, server).OpenFile(text); - return await file.RequestCompletion(offset); + return await file.RequestAndResolveCompletions(offset); } private static async Task RunCompletionScenarioTest(TestContext testContext, SharedLanguageHelperManager server, string fileWithCursors, Action> assertAction, char cursor = '|') @@ -3677,7 +3683,7 @@ private async Task RunCompletionTest(string fileWithCursor, string completionLab var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursor); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, completionLabel); updatedFile.Should().HaveSourceText(expectedOutput); } @@ -3846,7 +3852,7 @@ public async Task LoadFunctionsPathArgument_returnsFilesInCompletions(string fun var file = new FileRequestHelper(helper.Client, mainFile); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var completionItems = completions.Where(x => x.Kind == CompletionItemKind.File).OrderBy(x => x.SortText); if (jsonOnTop) @@ -3959,7 +3965,7 @@ public async Task LoadFunctionsPathArgument_returnsSymbolsAndFilePathsInCompleti services => services.WithNamespaceProvider(BuiltInTestTypes.Create())); var file = new FileRequestHelper(helper.Client, mainFile); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var completionItems = completions.OrderBy(x => x.SortText).Where(x => x.Label.StartsWith("templ")); if (jsonOnTop) @@ -4045,7 +4051,7 @@ public async Task Module_path_completions_are_offered(string fileWithCursors, st var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, helper).OpenFile(fileUri, text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == expectedLabel, $"\"{fileWithCursors}\" should have completion"); var updatedFile = file.ApplyCompletion(completions, expectedLabel); @@ -4082,7 +4088,7 @@ public async Task ModuleRegistryReferenceCompletions_GetCompletionsAfterBrSchema .WithFileResolver(new FileResolver(new LocalFileSystem()))); var file = await new ServerRequestHelper(TestContext, helper).OpenFile(mainUri.ToUriEncoded(), text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Count().Should().Be(3); completions.Should().Contain(x => x.Label == expectedLabel1 && x.Kind == completionItemKind1); @@ -4111,7 +4117,7 @@ public async Task ModuleRegistryReferenceCompletions_GetCompletionsForFolderInsi .WithFileResolver(new FileResolver(new LocalFileSystem()))); var file = await new ServerRequestHelper(TestContext, helper).OpenFile(mainUri.ToUriEncoded(), text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Count().Should().Be(2); completions.Should().Contain(x => x.Label == "bar.bicep" && x.Kind == CompletionItemKind.File); @@ -4119,24 +4125,28 @@ public async Task ModuleRegistryReferenceCompletions_GetCompletionsForFolderInsi } [DataTestMethod] - [DataRow("module test 'br:mcr.microsoft.com/bicep/|'", "bicep")] - [DataRow("module test 'br:mcr.microsoft.com/bicep/|", "bicep")] - [DataRow("module test 'br/public:|'", "bicep")] - [DataRow("module test 'br/public:|", "bicep")] - [DataRow("using 'br:mcr.microsoft.com/bicep/|'", "bicepparam")] - [DataRow("using 'br:mcr.microsoft.com/bicep/|", "bicepparam")] - [DataRow("using 'br/public:|'", "bicepparam")] - [DataRow("using 'br/public:|", "bicepparam")] - public async Task ModuleRegistryReferenceCompletions_GetPathCompletions(string inputWithCursors, string extension) - { + [DataRow("module test 'br/public:app/dapr-containerapp:|'", BicepSourceFileKind.BicepFile)] + [DataRow("module test 'br/public:app/dapr-containerapp:|", BicepSourceFileKind.BicepFile)] + [DataRow("module test 'br:mcr.microsoft.com/bicep/app/dapr-containerapp:|'", BicepSourceFileKind.BicepFile)] + [DataRow("module test 'br:mcr.microsoft.com/bicep/app/dapr-containerapp:|", BicepSourceFileKind.BicepFile)] + [DataRow("using 'br/public:app/dapr-containerapp:|'", BicepSourceFileKind.ParamsFile)] + [DataRow("using 'br/public:app/dapr-containerapp:|", BicepSourceFileKind.ParamsFile)] + [DataRow("using 'br:mcr.microsoft.com/bicep/app/dapr-containerapp:|'", BicepSourceFileKind.ParamsFile)] + [DataRow("using 'br:mcr.microsoft.com/bicep/app/dapr-containerapp:|", BicepSourceFileKind.ParamsFile)] + public async Task Public_module_version_completions(string inputWithCursors, BicepSourceFileKind kind) + { + var extension = kind == BicepSourceFileKind.ParamsFile ? "bicepparam" : "bicep"; var (fileText, cursor) = ParserHelper.GetFileWithSingleCursor(inputWithCursors, '|'); var fileUri = new Uri($"file:///{Guid.NewGuid():D}/{TestContext.TestName}/main.{extension}"); var settingsProvider = StrictMock.Of(); settingsProvider.Setup(x => x.GetSetting(LangServerConstants.GetAllAzureContainerRegistriesForCompletionsSetting)).Returns(false); - var publicModuleMetadataProvider = StrictMock.Of(); - publicModuleMetadataProvider.Setup(x => x.GetModulesMetadata()).Returns([new("app/dapr-containerapp", "d1", "contoso.com/help1"), new("app/dapr-containerapp-env", "d2", "contoso.com/help2")]); + var publicModuleMetadataProvider = RegistryCatalogMocks.MockPublicMetadataProvider( + [("bicep/app/dapr-containerapp", "d1", "contoso.com/help1", [ + new("1.0.1", null, null), + new("1.0.2", "d1", "contoso.com/help1") + ])]); using var helper = await MultiFileLanguageServerHelper.StartLanguageServer( TestContext, @@ -4145,83 +4155,289 @@ public async Task ModuleRegistryReferenceCompletions_GetPathCompletions(string i .AddSingleton(settingsProvider.Object)); var file = await new ServerRequestHelper(TestContext, helper).OpenFile(fileUri, fileText); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Count().Should().Be(2); - completions.Should().Contain(x => x.Label == "app/dapr-containerapp" && x.Kind == CompletionItemKind.Snippet && x.Detail == "d1" && x.Documentation!.MarkupContent!.Value == "[View Documentation](contoso.com/help1)"); - completions.Should().Contain(x => x.Label == "app/dapr-containerapp-env" && x.Kind == CompletionItemKind.Snippet && x.Detail == "d2" && x.Documentation!.MarkupContent!.Value == "[View Documentation](contoso.com/help2)"); + completions.Should().SatisfyRespectively( + first => + { + first.Label.Should().Be("1.0.2"); + first.SortText.Should().Be("0000"); + first.Kind.Should().Be(CompletionItemKind.Snippet); + first.Detail.Should().Be("d1"); + first.Documentation!.MarkupContent!.Value.Should().Be("[View Documentation](contoso.com/help1)"); + }, + second => + { + second.Label.Should().Be("1.0.1"); + second.SortText.Should().Be("0001"); + second.Kind.Should().Be(CompletionItemKind.Snippet); + second.Detail.Should().BeNull(); + second.Documentation.Should().BeNull(); + } + ); } [DataTestMethod] - [DataRow("module test 'br/public:app/dapr-containerapp:|'", "bicep")] - [DataRow("module test 'br/public:app/dapr-containerapp:|", "bicep")] - [DataRow("module test 'br:mcr.microsoft.com/bicep/app/dapr-containerapp:|'", "bicep")] - [DataRow("module test 'br:mcr.microsoft.com/bicep/app/dapr-containerapp:|", "bicep")] - [DataRow("using 'br/public:app/dapr-containerapp:|'", "bicepparam")] - [DataRow("using 'br/public:app/dapr-containerapp:|", "bicepparam")] - [DataRow("using 'br:mcr.microsoft.com/bicep/app/dapr-containerapp:|'", "bicepparam")] - [DataRow("using 'br:mcr.microsoft.com/bicep/app/dapr-containerapp:|", "bicepparam")] - public async Task ModuleRegistryReferenceCompletions_GetVersionCompletions(string inputWithCursors, string extension) - { + [DataRow("module test 'br/contoso:app/private-app:|'", BicepSourceFileKind.BicepFile)] + [DataRow("module test 'br/contoso:app/private-app:|", BicepSourceFileKind.BicepFile)] + [DataRow("module test 'br:private.contoso.com/app/private-app:|'", BicepSourceFileKind.BicepFile)] + [DataRow("module test 'br:private.contoso.com/app/private-app:|", BicepSourceFileKind.BicepFile)] + [DataRow("module test 'br/contoso:app/private-app:|'", BicepSourceFileKind.ParamsFile)] + [DataRow("module test 'br/contoso:app/private-app:|", BicepSourceFileKind.ParamsFile)] + [DataRow("module test 'br:private.contoso.com/app/private-app:|'", BicepSourceFileKind.ParamsFile)] + [DataRow("module test 'br:private.contoso.com/app/private-app:|", BicepSourceFileKind.ParamsFile)] + public async Task Private_module_version_completions(string inputWithCursors, BicepSourceFileKind kind) + { + var extension = kind == BicepSourceFileKind.ParamsFile ? "bicepparam" : "bicep"; var (fileText, cursor) = ParserHelper.GetFileWithSingleCursor(inputWithCursors, '|'); var fileUri = new Uri($"file:///{Guid.NewGuid():D}/{TestContext.TestName}/main.{extension}"); var settingsProvider = StrictMock.Of(); settingsProvider.Setup(x => x.GetSetting(LangServerConstants.GetAllAzureContainerRegistriesForCompletionsSetting)).Returns(false); - var publicModuleMetadataProvider = StrictMock.Of(); - publicModuleMetadataProvider.Setup(x => x.GetModulesMetadata()).Returns([new("app/dapr-containerapp", "d1", "contoso.com/help1")]); - publicModuleMetadataProvider.Setup(x => x.GetModuleVersionsMetadata("app/dapr-containerapp")).Returns([new("1.0.2", "d1", "contoso.com/help1"), new("1.0.1", null, null)]); - publicModuleMetadataProvider.Setup(x => x.GetModuleVersionsMetadata("app/dapr-containerapp")).Returns([new("1.0.2", "d1", "contoso.com/help1"), new("1.0.1", null, null)]); + var privateModuleMetadataProvider = RegistryCatalogMocks.MockPrivateMetadataProvider( + "private.contoso.com", + [("app/private-app", "d1", "contoso.com/help1", [ + new("v100", "d100", "contoso.com/help/d100.html"), + new("v101", "d101", "contoso.com/help/d101.html")]) + ]); + var catalog = RegistryCatalogMocks.CreateCatalogWithMocks( + null, + privateModuleMetadataProvider); + + var configurationManager = StrictMock.Of(); + var moduleAliasesConfiguration = BicepTestConstants.BuiltInConfiguration.With( + moduleAliases: RegistryCatalogMocks.ModuleAliases( + """ + { + "br": { + "contoso": { + "registry": "private.contoso.com" + } + } + } + """)); + configurationManager.Setup(x => x.GetConfiguration(fileUri)).Returns(moduleAliasesConfiguration); using var helper = await MultiFileLanguageServerHelper.StartLanguageServer( TestContext, services => services - .AddSingleton(publicModuleMetadataProvider.Object) - .AddSingleton(settingsProvider.Object)); + .AddSingleton(settingsProvider.Object) + .AddSingleton(configurationManager.Object) + .AddSingleton(catalog)); var file = await new ServerRequestHelper(TestContext, helper).OpenFile(fileUri, fileText); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Count().Should().Be(2); - completions.Should().Contain(x => x.Label == "1.0.1" && x.SortText == "0001" && x.Kind == CompletionItemKind.Snippet && x.Detail == null && x.Documentation == null); - completions.Should().Contain(x => x.Label == "1.0.2" && x.SortText == "0000" && x.Kind == CompletionItemKind.Snippet && x.Detail == "d1" && x.Documentation!.MarkupContent!.Value == "[View Documentation](contoso.com/help1)"); + completions.Should().SatisfyRespectively( + first => + { + first.Label.Should().Be("v101"); + first.SortText.Should().Be("0000"); + first.Kind.Should().Be(CompletionItemKind.Snippet); + first.Detail.Should().Be("d101"); + first.Documentation!.MarkupContent!.Value.Should().Be("[View Documentation](contoso.com/help/d101.html)"); + }, + second => + { + second.Label.Should().Be("v100"); + second.SortText.Should().Be("0001"); + second.Kind.Should().Be(CompletionItemKind.Snippet); + second.Detail.Should().Be("d100"); + second.Documentation!.MarkupContent!.Value.Should().Be("[View Documentation](contoso.com/help/d100.html)"); + } + ); } [TestMethod] - [DataRow("module test 'br:mcr.microsoft.com/bicep/foo|'", "bicep")] - [DataRow("module test 'br:mcr.microsoft.com/bicep/foo|", "bicep")] - [DataRow("module test 'br/public:foo|'", "bicep")] - [DataRow("module test 'br/public:foo|", "bicep")] - [DataRow("using 'br:mcr.microsoft.com/bicep/foo|'", "bicepparam")] - [DataRow("using 'br:mcr.microsoft.com/bicep/foo|", "bicepparam")] - [DataRow("using 'br/public:foo|'", "bicepparam")] - [DataRow("using 'br/public:foo|", "bicepparam")] - public async Task Public_registry_completions_support_prefix_matching(string text, string extension) - { + [DataRow("module test 'br:mcr.microsoft.com/bicep/abc/foo|'", "bicep/abc/foo/bar", "'br:mcr.microsoft.com/bicep/abc/foo/bar:$0'", BicepSourceFileKind.BicepFile)] + [DataRow("module test 'br:mcr.microsoft.com/bicep/abc/foo|", "bicep/abc/foo/bar", "'br:mcr.microsoft.com/bicep/abc/foo/bar:$0'", BicepSourceFileKind.BicepFile)] + [DataRow("module test 'br/public:abc/foo|'", "abc/foo/bar", "'br/public:abc/foo/bar:$0'", BicepSourceFileKind.BicepFile)] + [DataRow("module test 'br/public:abc/foo|", "abc/foo/bar", "'br/public:abc/foo/bar:$0'", BicepSourceFileKind.BicepFile)] + [DataRow("using 'br:mcr.microsoft.com/bicep/abc/foo|'", "bicep/abc/foo/bar", "'br:mcr.microsoft.com/bicep/abc/foo/bar:$0'", BicepSourceFileKind.ParamsFile)] + [DataRow("using 'br:mcr.microsoft.com/bicep/abc/foo|", "bicep/abc/foo/bar", "'br:mcr.microsoft.com/bicep/abc/foo/bar:$0'", BicepSourceFileKind.ParamsFile)] + [DataRow("using 'br/public:abc/foo|'", "abc/foo/bar", "'br/public:abc/foo/bar:$0'", BicepSourceFileKind.ParamsFile)] + [DataRow("using 'br/public:abc/foo|", "abc/foo/bar", "'br/public:abc/foo/bar:$0'", BicepSourceFileKind.ParamsFile)] + public async Task Public_registry_module_completions_support_prefix_matching(string text, string expectedLabelForFoo, string expectedInsertTextForFoo, BicepSourceFileKind kind) + { + var extension = kind == BicepSourceFileKind.ParamsFile ? "bicepparam" : "bicep"; var (fileText, cursor) = ParserHelper.GetFileWithSingleCursor(text, '|'); var fileUri = new Uri($"file:///{Guid.NewGuid():D}/{TestContext.TestName}/main.{extension}"); var settingsProvider = StrictMock.Of(); settingsProvider.Setup(x => x.GetSetting(LangServerConstants.GetAllAzureContainerRegistriesForCompletionsSetting)).Returns(false); - var publicModuleMetadataProvider = StrictMock.Of(); - publicModuleMetadataProvider.Setup(x => x.GetModulesMetadata()).Returns([new("foo/bar", "d1", "contoso.com/help1"), new("food/bar", "d2", "contoso.com/help2"), new("bar/bar", "d2", "contoso.com/help2")]); + var publicModuleMetadataProvider = RegistryCatalogMocks.MockPublicMetadataProvider([ + ("bicep/abc/foo/bar", "d1", "contoso.com/help1", []), + ("bicep/abc/food/bar", "d2", "contoso.com/help2", []), + ("bicep/abc/bar/bar", "d3", "contoso.com/help3", []), + ]); using var helper = await MultiFileLanguageServerHelper.StartLanguageServer( TestContext, services => services - .AddSingleton(publicModuleMetadataProvider.Object) - .AddSingleton(settingsProvider.Object)); + .AddSingleton(publicModuleMetadataProvider.Object) + .AddSingleton(settingsProvider.Object)); var file = await new ServerRequestHelper(TestContext, helper).OpenFile(fileUri, fileText); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Count().Should().Be(2); - completions.Should().Contain(x => x.Label == "foo/bar"); - completions.Should().Contain(x => x.Label == "food/bar"); + completions.Select(x => (Label: x.Label, InsertText: x.TextEdit!.TextEdit!.NewText)).Should().SatisfyRespectively( + c => + { + c.Label.Should().Be(expectedLabelForFoo); + c.InsertText.Should().Be(expectedInsertTextForFoo); + }, + c => + { + c.Label.Should().Be(expectedLabelForFoo.Replace("foo/", "food/")); + c.InsertText.Should().Be(expectedInsertTextForFoo.Replace("foo/", "food/")); + } + ); } + [TestMethod] + [DataRow("module test 'br:registry.contoso.io/bicep/whatever/abc/foo|'", "bicep/whatever/abc/foo/bar", "'br:registry.contoso.io/bicep/whatever/abc/foo/bar:$0'", BicepSourceFileKind.BicepFile)] + [DataRow("module test 'br:registry.contoso.io/bicep/whatever/abc/foo|", "bicep/whatever/abc/foo/bar", "'br:registry.contoso.io/bicep/whatever/abc/foo/bar:$0'", BicepSourceFileKind.BicepFile)] + [DataRow("module test 'br/myRegistry:abc/foo|'", "abc/foo/bar", "'br/myRegistry:abc/foo/bar:$0'", BicepSourceFileKind.BicepFile)] + [DataRow("module test 'br/myRegistry_noPath:bicep/whatever/abc/foo|", "bicep/whatever/abc/foo/bar", "'br/myRegistry_noPath:bicep/whatever/abc/foo/bar:$0'", BicepSourceFileKind.BicepFile)] + [DataRow("module test 'br:registry.contoso.io/bicep/whatever/abc/foo|'", "bicep/whatever/abc/foo/bar", "'br:registry.contoso.io/bicep/whatever/abc/foo/bar:$0'", BicepSourceFileKind.ParamsFile)] + [DataRow("module test 'br/myRegistry_noPath:bicep/whatever/abc/foo|", "bicep/whatever/abc/foo/bar", "'br/myRegistry_noPath:bicep/whatever/abc/foo/bar:$0'", BicepSourceFileKind.ParamsFile)] + public async Task Private_registry_completions_support_prefix_matching(string text, string expectedLabelForFoo, string expectedInsertTextForFoo, BicepSourceFileKind kind) + { + var extension = kind == BicepSourceFileKind.ParamsFile ? "bicepparam" : "bicep"; + var (fileText, cursor) = ParserHelper.GetFileWithSingleCursor(text, '|'); + var baseFolder = $"{Guid.NewGuid():D}"; + var fileUri = new Uri($"file:///{baseFolder}/{TestContext.TestName}/main.{extension}"); + + var configurationManager = StrictMock.Of(); + var moduleAliasesConfiguration = BicepTestConstants.BuiltInConfiguration.With( + moduleAliases: ModuleAliasesConfiguration.Bind(JsonElementFactory.CreateElement( + """ + { + "br": { + "myRegistry": { + "registry": "registry.contoso.io", + "modulePath": "bicep/whatever" + }, + "myRegistry_noPath": { + "registry": "registry.contoso.io" + } + } + } + """), + null)); + configurationManager.Setup(x => x.GetConfiguration(fileUri)).Returns(moduleAliasesConfiguration); + + var settingsProvider = StrictMock.Of(); + settingsProvider.Setup(x => x.GetSetting(LangServerConstants.GetAllAzureContainerRegistriesForCompletionsSetting)).Returns(false); + + var catalog = RegistryCatalogMocks.CreateCatalogWithMocks( + null, + RegistryCatalogMocks.MockPrivateMetadataProvider( + "registry.contoso.io", + [ + ("bicep/whatever/abc/foo/bar", "d1", "contoso.com/help1", []), + ("bicep/whatever/abc/food/bar", "d2", "contoso.com/help2", []), + ("bicep/whatever/abc/bar/bar", "d3", "contoso.com/help3", []), + + ]) + ); + + using var helper = await MultiFileLanguageServerHelper.StartLanguageServer( + TestContext, + services => services + .AddSingleton(settingsProvider.Object) + .AddSingleton(catalog) + .AddSingleton(configurationManager.Object) + ); + + var file = await new ServerRequestHelper(TestContext, helper).OpenFile(fileUri, fileText); + var completions = await file.RequestAndResolveCompletions(cursor); + + completions.Count().Should().Be(2); + completions.Select(x => (Label: x.Label, InsertText: x.TextEdit!.TextEdit!.NewText)).Should().SatisfyRespectively( + c => + { + c.Label.Should().Be(expectedLabelForFoo); + c.InsertText.Should().Be(expectedInsertTextForFoo); + }, + c => + { + c.Label.Should().Be(expectedLabelForFoo.Replace("foo/", "food/")); + c.InsertText.Should().Be(expectedInsertTextForFoo.Replace("foo/", "food/")); + } + ); + } + + [TestMethod] + [DataRow("module test 'br/ms:bicep/app/|'", "bicep/app/dapr-containerapp", "'br/ms:bicep/app/dapr-containerapp:$0'")] + [DataRow("module test 'br/ms_empty:bicep/app/|'", "bicep/app/dapr-containerapp", "'br/ms_empty:bicep/app/dapr-containerapp:$0'")] + [DataRow("module test 'br/ms_bicep:app/|'", "app/dapr-containerapp", "'br/ms_bicep:app/dapr-containerapp:$0'")] + public async Task Public_registry_via_alias_supports_completions(string text, string expectedLabel, string expectedInsertText) + { + var (fileText, cursor) = ParserHelper.GetFileWithSingleCursor(text, '|'); + var baseFolder = $"{Guid.NewGuid():D}"; + var fileUri = new Uri($"file:///{baseFolder}/{TestContext.TestName}/main.bicep"); + + var configurationManager = StrictMock.Of(); + var moduleAliasesConfiguration = BicepTestConstants.BuiltInConfiguration.With( + moduleAliases: ModuleAliasesConfiguration.Bind(JsonElementFactory.CreateElement( + """ + { + "br": { + "ms": { + "registry": "mcr.microsoft.com", + "modulePath": "" + }, + "ms_empty": { + "registry": "mcr.microsoft.com" + }, + "ms_bicep": { + "registry": "mcr.microsoft.com", + "modulePath": "bicep" + } + } + } + """), + null)); + configurationManager.Setup(x => x.GetConfiguration(fileUri)).Returns(moduleAliasesConfiguration); + + var settingsProvider = StrictMock.Of(); + settingsProvider.Setup(x => x.GetSetting(LangServerConstants.GetAllAzureContainerRegistriesForCompletionsSetting)).Returns(false); + + var catalog = RegistryCatalogMocks.CreateCatalogWithMocks( + RegistryCatalogMocks.MockPublicMetadataProvider( + [("bicep/app/dapr-containerapp", "d1", "contoso.com/help1", [ + new("1.0.1", null, null), + new("1.0.2", "d1", "contoso.com/help1") + ])] + )); + + using var helper = await MultiFileLanguageServerHelper.StartLanguageServer( + TestContext, + services => services + .AddSingleton(settingsProvider.Object) + .AddSingleton(catalog) + .AddSingleton(configurationManager.Object) + ); + + var file = await new ServerRequestHelper(TestContext, helper).OpenFile(fileUri, fileText); + var completions = await file.RequestAndResolveCompletions(cursor); + + completions.Count().Should().Be(1); + completions.Select(x => (Label: x.Label, InsertText: x.TextEdit!.TextEdit!.NewText)).Should().SatisfyRespectively( + c => + { + c.Label.Should().Be(expectedLabel); + c.InsertText.Should().Be(expectedInsertText); + } + ); + } + [DataTestMethod] [DataRow("var arr1 = [|]")] [DataRow("param arr array = [|]")] @@ -4235,7 +4451,7 @@ public async Task GenericArray_SingleLine_HasCompletions(string text) var (fileText, cursor) = ParserHelper.GetFileWithSingleCursor(text, '|'); var file = await new ServerRequestHelper(TestContext, ServerWithNamespaceProvider).OpenFile(fileText); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); // test completions that are unlikely to change over time completions.Should().Contain(c => c.Label == "sys"); completions.Should().Contain(c => c.Label == "if-else"); @@ -4296,7 +4512,7 @@ param bar object """); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "bar"); updatedFile.Should().HaveSourceText(""" @@ -4331,7 +4547,7 @@ public async Task Required_properties_completion_is_not_offered_for_invalid_recu var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().NotContain(c => c.Label == "required-properties"); } @@ -4357,7 +4573,7 @@ public async Task Required_properties_completion_works_for_valid_recursive_types var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "required-properties"); updatedFile.Should().HaveSourceText(""" @@ -4409,7 +4625,7 @@ public async Task Compile_time_imports_offer_target_path_completions() bicepFile.Uri); var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); completions.Should().Contain(c => c.Label == "mod.bicep" && c.Kind == CompletionItemKind.File); completions.Should().Contain(c => c.Label == "mod2.bicep" && c.Kind == CompletionItemKind.File); @@ -4430,7 +4646,7 @@ public async Task Compile_time_imports_offer_import_expression_completions() var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "{}"); completions.Should().Contain(x => x.Label == "* as"); } @@ -4445,7 +4661,7 @@ public async Task Compile_time_imports_offer_as_keyword_completions() var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, '|'); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(x => x.Label == "as"); } @@ -4490,13 +4706,13 @@ public async Task Compile_time_imports_offer_imported_symbol_list_item_completio var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); completions.Should().Contain(c => c.Label == "foo"); completions.Should().Contain(c => c.Label == "bar"); completions.Should().NotContain(c => c.Label == "fizz"); completions.Should().NotContain(c => c.Label == "buzz"); - completions = await file.RequestCompletion(cursors[1]); + completions = await file.RequestAndResolveCompletions(cursors[1]); completions.Should().Contain(c => c.Label == "fizz"); completions.Should().Contain(c => c.Label == "buzz"); completions.Should().NotContain(c => c.Label == "foo"); @@ -4534,7 +4750,7 @@ public async Task Compile_time_imports_do_not_offer_types_as_imported_symbol_lis var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); completions.Should().NotContain(c => c.Label == "foo"); completions.Should().Contain(c => c.Label == "bar"); } @@ -4588,7 +4804,7 @@ public async Task Imported_symbol_list_item_completions_quote_and_escape_names_w @"import {'\'' as } from 'mod.json'", }; - foreach (var completion in await file.RequestCompletion(cursors[0])) + foreach (var completion in await file.RequestAndResolveCompletions(cursors[0])) { var start = PositionHelper.GetOffset(bicepFile.LineStarts, completion.TextEdit!.TextEdit!.Range.Start); var end = PositionHelper.GetOffset(bicepFile.LineStarts, completion.TextEdit!.TextEdit!.Range.End); @@ -4644,13 +4860,13 @@ public async Task Compile_time_imports_offer_imported_wildcard_property_completi var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); completions.Should().Contain(c => c.Label == "foo"); completions.Should().Contain(c => c.Label == "bar"); completions.Should().NotContain(c => c.Label == "fizz"); completions.Should().NotContain(c => c.Label == "buzz"); - completions = await file.RequestCompletion(cursors[1]); + completions = await file.RequestAndResolveCompletions(cursors[1]); completions.Should().Contain(c => c.Label == "fizz"); completions.Should().Contain(c => c.Label == "buzz"); completions.Should().NotContain(c => c.Label == "foo"); @@ -4711,7 +4927,7 @@ public async Task Imported_wildcard_property_completions_use_array_access_when_n var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); completions.Should().Contain(c => c.Label == "mod['foo.bar']"); completions.Should().Contain(c => c.Label == @"mod['\'']"); completions.Should().Contain(c => c.Label == "mod.fizz"); @@ -4767,7 +4983,7 @@ output b | foreach (var cursor in cursors) { - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Should().Contain(c => c.Label == "foo"); completions.Should().Contain(c => c.Label == "bar"); completions.Should().Contain(c => c.Label == "mod2.fizz"); @@ -4821,13 +5037,13 @@ public async Task Compile_time_imports_offer_imported_symbol_property_completion var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); completions.Should().Contain(c => c.Label == "bar"); completions.Should().Contain(c => c.Label == "baz"); completions.Should().NotContain(c => c.Label == "buzz"); completions.Should().NotContain(c => c.Label == "pop"); - completions = await file.RequestCompletion(cursors[1]); + completions = await file.RequestAndResolveCompletions(cursors[1]); completions.Should().Contain(c => c.Label == "buzz"); completions.Should().Contain(c => c.Label == "pop"); completions.Should().NotContain(c => c.Label == "bar"); @@ -4853,7 +5069,7 @@ public async Task Description_markdown_is_correctly_formatted() var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); completions.Single(x => x.Label == "sourcePortRanges").Documentation!.MarkupContent!.Value .Should().BeEquivalentToIgnoringNewlines( @"Type: `string[]` " + @" @@ -4881,7 +5097,7 @@ public async Task Resource_utility_type_offered_as_completion_if_enabled() services => services.WithFeatureOverrides(new(ResourceDerivedTypesEnabled: true))); var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); var updated = file.ApplyCompletion(completions, "resourceInput"); updated.Should().HaveSourceText(""" @@ -4907,7 +5123,7 @@ public async Task Legacy_resource_utility_type_offered_not_as_completion_if_enab services => services.WithFeatureOverrides(new(ResourceDerivedTypesEnabled: true))); var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); completions.Should().NotContain(completion => completion.Label == LanguageConstants.TypeNameResource); } @@ -4932,14 +5148,14 @@ public async Task Resource_types_offered_as_completion_for_single_argument_to_re var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); var updated = file.ApplyCompletion(completions, "'Microsoft.Storage/storageAccounts'"); updated.Should().HaveSourceText(""" type acct = resourceInput<'Microsoft.Storage/storageAccounts@|'> type fullyQualified = sys.resourceInput """); - completions = await file.RequestCompletion(cursors[1]); + completions = await file.RequestAndResolveCompletions(cursors[1]); updated = file.ApplyCompletion(completions, "'Microsoft.Storage/storageAccounts'"); updated.Should().HaveSourceText(""" type acct = resourceInput @@ -4967,14 +5183,14 @@ public async Task Resource_api_versions_offered_as_completion_for_single_argumen var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); var updated = file.ApplyCompletion(completions, "2022-09-01"); updated.Should().HaveSourceText(""" type acct = resourceInput<'Microsoft.Storage/storageAccounts@2022-09-01'|> type fullyQualified = sys.resourceInput<'Microsoft.Storage/storageAccounts@'> """); - completions = await file.RequestCompletion(cursors[1]); + completions = await file.RequestAndResolveCompletions(cursors[1]); updated = file.ApplyCompletion(completions, "2022-09-01"); updated.Should().HaveSourceText(""" type acct = resourceInput<'Microsoft.Storage/storageAccounts@'> @@ -5045,7 +5261,7 @@ public async Task Strings_in_required_property_completions_are_correctly_escaped var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, "|>"); var file = await new ServerRequestHelper(TestContext, ServerWithExtensibilityEnabled).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "required-properties-Microsoft.Azure.Monitor.WebtestLocationAvailabilityCriteria"); updatedFile.Should().HaveSourceText(""" @@ -5093,7 +5309,7 @@ public async Task Nested_tab_stops_are_correctly_ordered_in_required_properties_ var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors, "|>"); var file = await new ServerRequestHelper(TestContext, ServerWithExtensibilityEnabled).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "required-properties"); updatedFile.Should().HaveSourceText(""" @@ -5129,7 +5345,7 @@ public async Task Unions_of_object_types_support_completions() """); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); var updatedFile = file.ApplyCompletion(completions, "bar"); updatedFile.Should().HaveSourceText(""" @@ -5158,7 +5374,7 @@ param firstItem object """); var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); // bar is still offered as a completion, even though there may be other properties supported var updatedFile = file.ApplyCompletion(completions, "bar"); @@ -5198,7 +5414,7 @@ public async Task String_literal_union_with_object_value_should_not_cause_stack_ """); var mainFile = await serverHelper.OpenFile("/main.bicep", text); - var completions = await mainFile.RequestCompletion(cursor); + var completions = await mainFile.RequestAndResolveCompletions(cursor); completions.Should().BeEmpty(); } diff --git a/src/Bicep.LangServer.IntegrationTests/Helpers/ServerRequestHelper.cs b/src/Bicep.LangServer.IntegrationTests/Helpers/ServerRequestHelper.cs index ac7c834cc29..6bcee4885d9 100644 --- a/src/Bicep.LangServer.IntegrationTests/Helpers/ServerRequestHelper.cs +++ b/src/Bicep.LangServer.IntegrationTests/Helpers/ServerRequestHelper.cs @@ -35,7 +35,7 @@ public async Task> RequestCompletions(IEnumerable var completions = new List(); foreach (var cursor in cursors) { - var completionList = await RequestCompletion(cursor); + var completionList = await RequestAndResolveCompletions(cursor); completions.Add(completionList); } @@ -43,13 +43,22 @@ public async Task> RequestCompletions(IEnumerable return [.. completions]; } - public async Task RequestCompletion(int cursor) + public async Task RequestAndResolveCompletions(int cursor) { - return await client.RequestCompletion(new CompletionParams + CompletionList completions = await client.RequestCompletion(new CompletionParams { TextDocument = new TextDocumentIdentifier(bicepFile.Uri), Position = TextCoordinateConverter.GetPosition(bicepFile.LineStarts, cursor) }); + + var resolved = new List(); + foreach (var completion in completions.Items) + { + var resolvedCompletion = completion.Data is null ? completion : await client.ResolveCompletion(completion); + resolved.Add(resolvedCompletion); + } + + return new CompletionList(resolved, completions.IsIncomplete); } public async Task RequestReferences(int cursor, bool includeDeclaration) @@ -112,7 +121,7 @@ public LanguageClientFile ApplyCompletion(CompletionItem completion, params stri public async Task RequestAndApplyCompletion(int cursor, string label) { - var completionList = await RequestCompletion(cursor); + var completionList = await RequestAndResolveCompletions(cursor); var completion = completionList.Should().ContainSingle(x => x.Label == label).Subject; return ApplyCompletion(completion); diff --git a/src/Bicep.LangServer.IntegrationTests/ParamsCompletionTests.cs b/src/Bicep.LangServer.IntegrationTests/ParamsCompletionTests.cs index 1ba2cdb1c9b..f349ae19ce8 100644 --- a/src/Bicep.LangServer.IntegrationTests/ParamsCompletionTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/ParamsCompletionTests.cs @@ -238,7 +238,7 @@ public async Task Request_for_using_declaration_path_completions_should_return_c services => services.WithFeatureOverrides(new(ExtendableParamFilesEnabled: true))); var file = new FileRequestHelper(helper.Client, bicepFile); - var completions = await file.RequestCompletion(cursors[0]); + var completions = await file.RequestAndResolveCompletions(cursors[0]); var updated = file.ApplyCompletion(completions, "none"); updated.Should().HaveSourceText(""" @@ -522,7 +522,7 @@ private async Task> RunCompletionScenario(string par var paramFile = new LanguageClientFile(paramUri, paramFileTextNoCursor); var file = new FileRequestHelper(helper.Client, paramFile); - var completions = await file.RequestCompletion(cursor); + var completions = await file.RequestAndResolveCompletions(cursor); return completions.OrderBy(completion => completion.SortText); } } diff --git a/src/Bicep.LangServer.IntegrationTests/packages.lock.json b/src/Bicep.LangServer.IntegrationTests/packages.lock.json index 5ee2e6a5599..cfbf8ccc09f 100644 --- a/src/Bicep.LangServer.IntegrationTests/packages.lock.json +++ b/src/Bicep.LangServer.IntegrationTests/packages.lock.json @@ -1537,6 +1537,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", diff --git a/src/Bicep.LangServer.UnitTests/BicepCompletionProviderTests.cs b/src/Bicep.LangServer.UnitTests/BicepCompletionProviderTests.cs index d6b47841633..6945d5ada11 100644 --- a/src/Bicep.LangServer.UnitTests/BicepCompletionProviderTests.cs +++ b/src/Bicep.LangServer.UnitTests/BicepCompletionProviderTests.cs @@ -4,7 +4,8 @@ using Bicep.Core; using Bicep.Core.Extensions; using Bicep.Core.Features; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog; +using Bicep.Core.Registry.Catalog.Implementation.PublicRegistries; using Bicep.Core.Syntax; using Bicep.Core.TypeSystem; using Bicep.Core.UnitTests; diff --git a/src/Bicep.LangServer.UnitTests/Completions/ModuleReferenceCompletionProviderTests.cs b/src/Bicep.LangServer.UnitTests/Completions/ModuleReferenceCompletionProviderTests.cs index d4a4f2d72c3..9c2c46c4992 100644 --- a/src/Bicep.LangServer.UnitTests/Completions/ModuleReferenceCompletionProviderTests.cs +++ b/src/Bicep.LangServer.UnitTests/Completions/ModuleReferenceCompletionProviderTests.cs @@ -6,10 +6,15 @@ using System.IO.Abstractions.TestingHelpers; using System.Runtime.CompilerServices; using Bicep.Core.Configuration; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry; +using Bicep.Core.Registry.Catalog; +using Bicep.Core.Registry.Catalog.Implementation; +using Bicep.Core.Registry.Catalog.Implementation.PrivateRegistries; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.FileSystem; using Bicep.Core.UnitTests.Mock; +using Bicep.Core.UnitTests.Mock.Registry; +using Bicep.Core.UnitTests.Mock.Registry.Catalog; using Bicep.Core.UnitTests.Utils; using Bicep.IO.FileSystem; using Bicep.LanguageServer; @@ -34,10 +39,20 @@ public class ModuleReferenceCompletionProviderTests public TestContext? TestContext { get; set; } private IAzureContainerRegistriesProvider azureContainerRegistriesProvider = StrictMock.Of().Object; - private static IPublicModuleMetadataProvider publicModuleMetadataProvider = StrictMock.Of().Object; private ISettingsProvider settingsProvider = StrictMock.Of().Object; - // TODO: We need improved assertions for all the completion item tests + private static async Task> GetAndResolveCompletionItems(DocumentUri documentUri, BicepCompletionContext completionContext, ModuleReferenceCompletionProvider moduleReferenceCompletionProvider) + { + var completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(documentUri.ToUriEncoded(), completionContext, CancellationToken.None); + var resolved = new List(); + foreach (var completion in completions) + { + var c = await moduleReferenceCompletionProvider.ResolveCompletionItem(completion, CancellationToken.None); + resolved.Add(c); + } + + return resolved; + } [DataTestMethod] [DataRow("module test |''", 14)] @@ -48,13 +63,14 @@ public class ModuleReferenceCompletionProviderTests [DataRow("module test |", 12)] public async Task GetFilteredCompletions_WithBicepRegistryAndTemplateSpecShemaCompletionContext_ReturnsCompletionItems(string inputWithCursors, int expectedEnd) { - var (completionContext, documentUri) = GetBicepCompletionContext(inputWithCursors); + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext(inputWithCursors); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider, - publicModuleMetadataProvider, + configMgr, + RegistryCatalogMocks.CreateCatalogWithMocks(), settingsProvider, BicepTestConstants.CreateMockTelemetryProvider().Object); - var completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); + var completions = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); completions.Count().Should().Be(4); @@ -127,13 +143,14 @@ public async Task GetFilteredCompletions_WithBicepRegistryAndTemplateSpecShemaCo } }"; - var (completionContext, documentUri) = GetBicepCompletionContext("module test '|'", bicepConfigFileContents); + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext("module test '|'", bicepConfigFileContents); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider, - publicModuleMetadataProvider, + configMgr, + RegistryCatalogMocks.CreateCatalogWithMocks(), settingsProvider, BicepTestConstants.CreateMockTelemetryProvider().Object); - var completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); + var completions = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); completions.Count().Should().Be(5); @@ -194,13 +211,14 @@ public async Task GetFilteredCompletions_WithBicepRegistryAndTemplateSpecShemaCo [TestMethod] public async Task GetFilteredCompletions_WithInvalidTextInCompletionContext_ReturnsEmptyListOfCompletionItems() { - var (completionContext, documentUri) = GetBicepCompletionContext("module test 'br:/|'"); + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext("module test 'br:/|'"); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider, - publicModuleMetadataProvider, + configMgr, + RegistryCatalogMocks.CreateCatalogWithMocks(), settingsProvider, BicepTestConstants.CreateMockTelemetryProvider().Object); - var completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); + var completions = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); completions.Should().BeEmpty(); } @@ -212,7 +230,6 @@ public async Task GetFilteredCompletions_WithInvalidTextInCompletionContext_Retu [DataRow("module test 'br/public:app/dapr-containerapp:1.0.1|'")] [DataRow("module test |'br/public:app/dapr-containerapp:1.0.1'")] [DataRow("module test 'br/public:app/dapr-containerapp:1.0.1'|")] - [DataRow("module test 'br:mcr.microsoft.com/bicep/app/dapr-containerapp:1.0.1|")] [DataRow("module test 'br:mcr.microsoft.com/bicep/app/dapr-containerapp:1.0.1|'")] [DataRow("module test |'br:mcr.microsoft.com/bicep/app/dapr-containerapp:1.0.1'")] [DataRow("module test 'br:mcr.microsoft.com/bicep/app/dapr-containerapp:1.0.1'|")] @@ -222,17 +239,20 @@ public async Task GetFilteredCompletions_WithInvalidTextInCompletionContext_Retu [DataRow("module test 'br:contoso.com/app/dapr-containerapp:1.0.1'|")] public async Task GetFilteredCompletions_WithInvalidCompletionContext_ReturnsEmptyList(string inputWithCursors) { - var publicModuleMetadataProvider = StrictMock.Of(); - publicModuleMetadataProvider.Setup(x => x.GetModulesMetadata()).Returns([]); - publicModuleMetadataProvider.Setup(x => x.GetModuleVersionsMetadata("app/dapr-containerapp")).Returns([new("1.0.1", null, null), new("1.0.2", null, null)]); + var catalog = RegistryCatalogMocks.CreateCatalogWithMocks( + RegistryCatalogMocks.MockPublicMetadataProvider([ + ("bicep/app/dapr-containerapp", null, null, [ new("1.0.1", null, null), new ("1.0.2", null, null) ]) + ]) + ); - var (completionContext, documentUri) = GetBicepCompletionContext(inputWithCursors); + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext(inputWithCursors); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider, - publicModuleMetadataProvider.Object, + configMgr, + catalog, settingsProvider, BicepTestConstants.CreateMockTelemetryProvider().Object); - var completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); + var completions = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); completions.Should().BeEmpty(); } @@ -243,25 +263,26 @@ public async Task GetFilteredCompletions_WithInvalidCompletionContext_ReturnsEmp public async Task GetFilteredCompletions_WithAliasCompletionContext_ReturnsCompletionItems(string inputWithCursors, int expectedEnd) { var bicepConfigFileContents = @"{ - ""moduleAliases"": { - ""br"": { - ""test1"": { - ""registry"": ""testacr.azurecr.io"", - ""modulePath"": ""bicep/modules"" - }, - ""test2"": { - ""registry"": ""testacr2.azurecr.io"" - } - } - } -}"; - var (completionContext, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); + ""moduleAliases"": { + ""br"": { + ""test1"": { + ""registry"": ""testacr.azurecr.io"", + ""modulePath"": ""bicep/modules"" + }, + ""test2"": { + ""registry"": ""testacr2.azurecr.io"" + } + } + } + }"; + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider, - publicModuleMetadataProvider, + configMgr, + RegistryCatalogMocks.CreateCatalogWithMocks(), settingsProvider, BicepTestConstants.CreateMockTelemetryProvider().Object); - var completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); + var completions = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); completions.Should().SatisfyRespectively( c => @@ -311,32 +332,33 @@ public async Task GetFilteredCompletions_WithAliasCompletionContext_ReturnsCompl public async Task GetFilteredCompletions_WithACRCompletionSettingSetToFalse_ReturnsACRCompletionItemsUsingBicepConfig(string inputWithCursors) { var bicepConfigFileContents = @"{ - ""moduleAliases"": { - ""br"": { - ""test1"": { - ""registry"": ""testacr1.azurecr.io"", - ""modulePath"": ""bicep/modules"" - }, - ""test2"": { - ""registry"": ""testacr2.azurecr.io"" - }, - ""test3"": { - ""registry"": ""testacr2.azurecr.io"" - } - } - } -}"; - var (completionContext, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); + ""moduleAliases"": { + ""br"": { + ""test1"": { + ""registry"": ""testacr1.azurecr.io"", + ""modulePath"": ""bicep/modules"" + }, + ""test2"": { + ""registry"": ""testacr2.azurecr.io"" + }, + ""test3"": { + ""registry"": ""testacr2.azurecr.io"" + } + } + } + }"; + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); var settingsProviderMock = StrictMock.Of(); settingsProviderMock.Setup(x => x.GetSetting(LangServerConstants.GetAllAzureContainerRegistriesForCompletionsSetting)).Returns(false); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider, - publicModuleMetadataProvider, + configMgr, + RegistryCatalogMocks.CreateCatalogWithMocks(), settingsProviderMock.Object, BicepTestConstants.CreateMockTelemetryProvider().Object); - var completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); + var completions = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); completions.Should().SatisfyRespectively( c => @@ -374,32 +396,34 @@ public async Task GetFilteredCompletions_WithACRCompletionSettingSetToFalse_Retu public async Task GetFilteredCompletions_WithACRCompletionsSettingSetToTrue_ReturnsACRCompletionItemsUsingResourceGraphClient(string inputWithCursors) { var bicepConfigFileContents = @"{ - ""moduleAliases"": { - ""br"": { - ""test1"": { - ""registry"": ""testacr1.azurecr.io"", - ""modulePath"": ""bicep/modules"" - }, - ""test2"": { - ""registry"": ""testacr2.azurecr.io"" - } - } - } -}"; - var (completionContext, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); + ""moduleAliases"": { + ""br"": { + ""test1"": { + ""registry"": ""testacr1.azurecr.io"", + ""modulePath"": ""bicep/modules"" + }, + ""test2"": { + ""registry"": ""testacr2.azurecr.io"" + } + } + } + }"; + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); var settingsProviderMock = StrictMock.Of(); settingsProviderMock.Setup(x => x.GetSetting(LangServerConstants.GetAllAzureContainerRegistriesForCompletionsSetting)).Returns(true); var azureContainerRegistriesProvider = StrictMock.Of(); - azureContainerRegistriesProvider.Setup(x => x.GetContainerRegistriesAccessibleFromAzure(completionContext.Configuration.Cloud, CancellationToken.None)).Returns(new List { "testacr3.azurecr.io", "testacr4.azurecr.io" }.ToAsyncEnumerable()); + var cloud = configMgr.GetConfiguration(documentUri.ToUriEncoded()).Cloud; + azureContainerRegistriesProvider.Setup(x => x.GetContainerRegistriesAccessibleFromAzure(cloud, CancellationToken.None)).Returns(new List { "testacr3.azurecr.io", "testacr4.azurecr.io" }.ToAsyncEnumerable()); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider.Object, - publicModuleMetadataProvider, + configMgr, + RegistryCatalogMocks.CreateCatalogWithMocks(), settingsProviderMock.Object, BicepTestConstants.CreateMockTelemetryProvider().Object); - var completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); + var completions = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); completions.Should().SatisfyRespectively( c => @@ -437,20 +461,22 @@ public async Task GetFilteredCompletions_WithACRCompletionsSettingSetToTrue_Retu public async Task GetFilteredCompletions_WithACRCompletionsSettingSetToTrue_AndNoAccessibleRegistries_ReturnsNoACRCompletions( string inputWithCursors) { - var (completionContext, documentUri) = GetBicepCompletionContext(inputWithCursors); + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext(inputWithCursors); var settingsProviderMock = StrictMock.Of(); settingsProviderMock.Setup(x => x.GetSetting(LangServerConstants.GetAllAzureContainerRegistriesForCompletionsSetting)).Returns(true); var azureContainerRegistriesProvider = StrictMock.Of(); - azureContainerRegistriesProvider.Setup(x => x.GetContainerRegistriesAccessibleFromAzure(completionContext.Configuration.Cloud, CancellationToken.None)).Returns(new List().ToAsyncEnumerable()); + var cloud = configMgr.GetConfiguration(documentUri.ToUriEncoded()).Cloud; + azureContainerRegistriesProvider.Setup(x => x.GetContainerRegistriesAccessibleFromAzure(cloud, CancellationToken.None)).Returns(new List().ToAsyncEnumerable()); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider.Object, - publicModuleMetadataProvider, + configMgr, + RegistryCatalogMocks.CreateCatalogWithMocks(), settingsProviderMock.Object, BicepTestConstants.CreateMockTelemetryProvider().Object); - var completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); + var completions = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); completions.Should().SatisfyRespectively( c => @@ -465,8 +491,8 @@ public async Task GetFilteredCompletions_WithACRCompletionsSettingSetToTrue_AndN } [DataTestMethod] - [DataRow("module test 'br:mcr.microsoft.com/bicep/|'", "app/dapr-cntrapp1", "'br:mcr.microsoft.com/bicep/app/dapr-cntrapp1:$0'", "app/dapr-cntrapp2", "'br:mcr.microsoft.com/bicep/app/dapr-cntrapp2:$0'", 41)] - [DataRow("module test 'br:mcr.microsoft.com/bicep/|", "app/dapr-cntrapp1", "'br:mcr.microsoft.com/bicep/app/dapr-cntrapp1:$0'", "app/dapr-cntrapp2", "'br:mcr.microsoft.com/bicep/app/dapr-cntrapp2:$0'", 40)] + [DataRow("module test 'br:mcr.microsoft.com/bicep/|'", "bicep/app/dapr-cntrapp1", "'br:mcr.microsoft.com/bicep/app/dapr-cntrapp1:$0'", "bicep/app/dapr-cntrapp2", "'br:mcr.microsoft.com/bicep/app/dapr-cntrapp2:$0'", 41)] + [DataRow("module test 'br:mcr.microsoft.com/bicep/|", "bicep/app/dapr-cntrapp1", "'br:mcr.microsoft.com/bicep/app/dapr-cntrapp1:$0'", "bicep/app/dapr-cntrapp2", "'br:mcr.microsoft.com/bicep/app/dapr-cntrapp2:$0'", 40)] [DataRow("module test 'br/public:|'", "app/dapr-cntrapp1", "'br/public:app/dapr-cntrapp1:$0'", "app/dapr-cntrapp2", "'br/public:app/dapr-cntrapp2:$0'", 24)] [DataRow("module test 'br/public:|", "app/dapr-cntrapp1", "'br/public:app/dapr-cntrapp1:$0'", "app/dapr-cntrapp2", "'br/public:app/dapr-cntrapp2:$0'", 23)] public async Task GetFilteredCompletions_WithPublicMcrModuleRegistryCompletionContext_ReturnsCompletionItems( @@ -477,16 +503,21 @@ public async Task GetFilteredCompletions_WithPublicMcrModuleRegistryCompletionCo string expectedCompletionText2, int expectedEnd) { - var publicModuleMetadataProvider = StrictMock.Of(); - publicModuleMetadataProvider.Setup(x => x.GetModulesMetadata()).Returns([new("app/dapr-cntrapp1", null, null), new("app/dapr-cntrapp2", "description2", "contoso.com/help2")]); - - var (completionContext, documentUri) = GetBicepCompletionContext(inputWithCursors); + var catalog = RegistryCatalogMocks.CreateCatalogWithMocks( + RegistryCatalogMocks.MockPublicMetadataProvider([ + new("bicep/app/dapr-cntrapp1", null, null, []), + new("bicep/app/dapr-cntrapp2", "description2", "contoso.com/help2", []), + ]) + ); + + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext(inputWithCursors); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider, - publicModuleMetadataProvider.Object, + configMgr, + catalog, settingsProvider, BicepTestConstants.CreateMockTelemetryProvider().Object); - var completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); + var completions = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); completions.Should().SatisfyRespectively( c => @@ -519,10 +550,66 @@ public async Task GetFilteredCompletions_WithPublicMcrModuleRegistryCompletionCo }); } + [DataTestMethod] + [DataRow("module test 'br:registry.contoso.io/bicep/|'", "bicep/whatever/abc/foo/bar", "'br:registry.contoso.io/bicep/whatever/abc/foo/bar:$0'")] + [DataRow("module test 'br:registry.contoso.io/bicep/|", "bicep/whatever/abc/foo/bar", "'br:registry.contoso.io/bicep/whatever/abc/foo/bar:$0'")] + [DataRow("module test 'br/myRegistry:|'", "abc/foo/bar", "'br/myRegistry:abc/foo/bar:$0'")] + [DataRow("module test 'br/myRegistry_noPath:|'", "bicep/whatever/abc/foo/bar", "'br/myRegistry_noPath:bicep/whatever/abc/foo/bar:$0'")] + public async Task GetFilteredCompletions_WithPrivateModulePathCompletions_ReturnsCompletionItems( + string inputWithCursors, + string expectedLabel, + string expectedCompletionText) + { + var catalog = RegistryCatalogMocks.CreateCatalogWithMocks( + null, + RegistryCatalogMocks.MockPrivateMetadataProvider( + "registry.contoso.io", + [ + ("bicep/whatever/abc/foo/bar", "d1", "contoso.com/help1", []), + ]) + ); + + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext( + inputWithCursors, + """ + { + "moduleAliases": { + "br": { + "myRegistry": { + "registry": "registry.contoso.io", + "modulePath": "bicep/whatever" + }, + "myRegistry_noPath": { + "registry": "registry.contoso.io" + } + } + } + } + """); + + var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( + azureContainerRegistriesProvider, + configMgr, + catalog, + settingsProvider, + BicepTestConstants.CreateMockTelemetryProvider().Object); + var completions = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); + + completions.Should().SatisfyRespectively( + c => + { + c.Label.Should().Be(expectedLabel); + c.InsertTextFormat.Should().Be(InsertTextFormat.Snippet); + c.Detail.Should().Be("d1"); + c.Documentation!.MarkupContent!.Value.Should().Be("[View Documentation](contoso.com/help1)"); + c.TextEdit!.TextEdit!.NewText.Should().Be(expectedCompletionText); + }); + } + [DataTestMethod] [DataRow("module test 'br:testacr1.azurecr.io/|'", "bicep/modules", "'br:testacr1.azurecr.io/bicep/modules:$0'", 0, 12, 0, 37)] [DataRow("module test 'br:testacr1.azurecr.io/|", "bicep/modules", "'br:testacr1.azurecr.io/bicep/modules:$0'", 0, 12, 0, 36)] - public async Task GetFilteredCompletions_WithPathCompletionContext_ReturnsCompletionItems( + public async Task GetFilteredCompletions_IfAliasesInBicepConfig_AndRegistriesNotAvailable_GetPartialCompletionsBasedOnConfigOnly( string inputWithCursors, string expectedLabel, string expectedCompletionText, @@ -544,23 +631,30 @@ public async Task GetFilteredCompletions_WithPathCompletionContext_ReturnsComple } } }"; - var (completionContext, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); + + var catalog = RegistryCatalogMocks.CreateCatalogWithMocks(null); + + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider, - publicModuleMetadataProvider, + configMgr, + catalog, settingsProvider, BicepTestConstants.CreateMockTelemetryProvider().Object); - var completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); + var completions = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); - completions.Should().Contain( - x => x.Label == expectedLabel && - x.Kind == CompletionItemKind.Reference && - x.InsertText == null && - x.TextEdit!.TextEdit!.NewText == expectedCompletionText && - x.TextEdit!.TextEdit!.Range.Start.Line == startLine && - x.TextEdit!.TextEdit!.Range.Start.Character == startCharacter && - x.TextEdit!.TextEdit!.Range.End.Line == endLine && - x.TextEdit!.TextEdit!.Range.End.Character == endCharacter); + completions.Should().SatisfyRespectively( + x => + { + x.Label.Should().Be(expectedLabel); + x.Kind.Should().Be(CompletionItemKind.Reference); + x.InsertText.Should().BeNull(); + x.TextEdit!.TextEdit!.NewText.Should().Be(expectedCompletionText); + x.TextEdit!.TextEdit!.Range.Start.Line.Should().Be(startLine); + x.TextEdit!.TextEdit!.Range.Start.Character.Should().Be(startCharacter); + x.TextEdit!.TextEdit!.Range.End.Line.Should().Be(endLine); + x.TextEdit!.TextEdit!.Range.End.Character.Should().Be(endCharacter); + }); } [DataTestMethod] @@ -593,17 +687,21 @@ public async Task GetFilteredCompletions_WithMcrVersionCompletionContext_Returns } } }"; - var publicModuleMetadataProvider = StrictMock.Of(); - publicModuleMetadataProvider.Setup(x => x.GetModulesMetadata()).Returns([]); - publicModuleMetadataProvider.Setup(x => x.GetModuleVersionsMetadata("app/dapr-containerapp")).Returns([new("1.0.2", null, null), new("1.0.1", "d2", "contoso.com/help%20page.html")]); - - var (completionContext, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); + var catalog = RegistryCatalogMocks.CreateCatalogWithMocks( + RegistryCatalogMocks.MockPublicMetadataProvider([ + new("bicep/app/dapr-containerapp", null, null, [new("1.0.1", "d2", "contoso.com/help%20page.html"), new("1.0.2", null, null)]), + new("bicep/app/dapr-containerappapp", null, null, [new("1.0.1", "d2", "contoso.com/help%20page.html"), new("1.0.2", null, null)]) + ]) + ); + + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider, - publicModuleMetadataProvider.Object, + configMgr, + catalog, settingsProvider, BicepTestConstants.CreateMockTelemetryProvider().Object); - var completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); + var completions = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); completions.Should().Contain(c => c.Label == expectedLabel1) .Which.Should().Match(x => @@ -635,17 +733,24 @@ public async Task GetFilteredCompletions_WithMcrVersionCompletionContext_Returns [TestMethod] public async Task GetFilteredCompletions_WithMcrVersionCompletionContext_AndNoMatchingModuleName_ReturnsEmptyListOfCompletionItems() { - var publicModuleMetadataProvider = StrictMock.Of(); - publicModuleMetadataProvider.Setup(x => x.GetModulesMetadata()).Returns([]); - publicModuleMetadataProvider.Setup(x => x.GetModuleVersionsMetadata("app/dapr-containerappapp")).Returns([]); - - var (completionContext, documentUri) = GetBicepCompletionContext("module test 'br/public:app/dapr-containerappapp:|'"); + var catalog = new RegistryModuleCatalog( + RegistryCatalogMocks.MockPublicMetadataProvider([ + new("bicep/app/dapr-containerappapp", null, null, []), + new("bicep/app/app/dapr-cntrapp2", "description2", "contoso.com/help2", []), + ]).Object, + StrictMock.Of().Object, + StrictMock.Of().Object, + BicepTestConstants.BuiltInOnlyConfigurationManager + ); + + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext("module test 'br/public:app/dapr-containerappapp:|'"); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider, - publicModuleMetadataProvider.Object, + configMgr, + catalog, settingsProvider, BicepTestConstants.CreateMockTelemetryProvider().Object); - var completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); + var completions = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); completions.Should().BeEmpty(); } @@ -675,13 +780,14 @@ public async Task GetFilteredCompletions_WithPublicAliasOverriddenInBicepConfigA } } }"; - var (completionContext, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider, - publicModuleMetadataProvider, + configMgr, + RegistryCatalogMocks.CreateCatalogWithMocks(), settingsProvider, BicepTestConstants.CreateMockTelemetryProvider().Object); - var completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); + var completions = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); completions.Should().Contain( x => x.Label == expectedLabel && @@ -695,10 +801,10 @@ public async Task GetFilteredCompletions_WithPublicAliasOverriddenInBicepConfigA } [DataTestMethod] - [DataRow("module test 'br/test1:|'", "dapr-containerappapp", "'br/test1:dapr-containerappapp:$0'", 0, 12, 0, 23)] - [DataRow("module test 'br/test1:|", "dapr-containerappapp", "'br/test1:dapr-containerappapp:$0'", 0, 12, 0, 22)] - [DataRow("module test 'br/test2:|'", "bicep/app/dapr-containerappapp", "'br/test2:bicep/app/dapr-containerappapp:$0'", 0, 12, 0, 23)] - [DataRow("module test 'br/test2:|", "bicep/app/dapr-containerappapp", "'br/test2:bicep/app/dapr-containerappapp:$0'", 0, 12, 0, 22)] + [DataRow("module test 'br/test1:|'", "dapr-containerapp", "'br/test1:dapr-containerapp:$0'", 0, 12, 0, 23)] + [DataRow("module test 'br/test1:|", "dapr-containerapp", "'br/test1:dapr-containerapp:$0'", 0, 12, 0, 22)] + [DataRow("module test 'br/test2:|'", "bicep/app/dapr-containerapp", "'br/test2:bicep/app/dapr-containerapp:$0'", 0, 12, 0, 23)] + [DataRow("module test 'br/test2:|", "bicep/app/dapr-containerapp", "'br/test2:bicep/app/dapr-containerapp:$0'", 0, 12, 0, 22)] public async Task GetFilteredCompletions_WithAliasForMCRInBicepConfigAndModulePath_ReturnsCompletionItems( string inputWithCursors, string expectedLabel, @@ -721,87 +827,189 @@ public async Task GetFilteredCompletions_WithAliasForMCRInBicepConfigAndModulePa } } }"; - var publicModuleMetadataProvider = StrictMock.Of(); - publicModuleMetadataProvider.Setup(x => x.GetModulesMetadata()).Returns([new("app/dapr-containerappapp", "dapr description", "contoso.com/help")]); - var (completionContext, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); + var catalog = RegistryCatalogMocks.CreateCatalogWithMocks( + RegistryCatalogMocks.MockPublicMetadataProvider([ + new("bicep/app/dapr-containerapp", "dapr description", "contoso.com/help", []), + ]) + ); + + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider, - publicModuleMetadataProvider.Object, + configMgr, + catalog, settingsProvider, BicepTestConstants.CreateMockTelemetryProvider().Object); - IEnumerable completions = await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); - - CompletionItem actualCompletionItem = completions.First(x => x.Label == expectedLabel); - actualCompletionItem.Kind.Should().Be(CompletionItemKind.Snippet); - actualCompletionItem.InsertText.Should().BeNull(); - actualCompletionItem.Detail.Should().Be("dapr description"); - actualCompletionItem.Documentation!.MarkupContent!.Value.Should().Be("[View Documentation](contoso.com/help)"); - - var actualTextEdit = actualCompletionItem.TextEdit!.TextEdit; - actualTextEdit.Should().NotBeNull(); - actualTextEdit!.NewText.Should().Be(expectedCompletionText); - actualTextEdit!.Range.Start.Line.Should().Be(startLine); - actualTextEdit!.Range.Start.Character.Should().Be(startCharacter); - actualTextEdit!.Range.End.Line.Should().Be(endLine); - actualTextEdit!.Range.End.Character.Should().Be(endCharacter); + IEnumerable completions = await GetAndResolveCompletionItems(documentUri, completionContext, moduleReferenceCompletionProvider); + + completions.Should().SatisfyRespectively( + x => + { + x.Label.Should().Be(expectedLabel); + x.Kind.Should().Be(CompletionItemKind.Snippet); + x.InsertText.Should().BeNull(); + x.Detail.Should().Be("dapr description"); + x.Documentation!.MarkupContent!.Value.Should().Be("[View Documentation](contoso.com/help)"); + + var actualTextEdit = x.TextEdit!.TextEdit; + actualTextEdit.Should().NotBeNull(); + actualTextEdit!.NewText.Should().Be(expectedCompletionText); + actualTextEdit!.Range.Start.Line.Should().Be(startLine); + actualTextEdit!.Range.Start.Character.Should().Be(startCharacter); + actualTextEdit!.Range.End.Line.Should().Be(endLine); + actualTextEdit!.Range.End.Character.Should().Be(endCharacter); + }); } [DataTestMethod] [DataRow("module foo 'br:mcr.microsoft.com/bicep/|", ModuleRegistryType.MCR)] - [DataRow("module foo 'br:test.azurecr.io/|", ModuleRegistryType.ACR)] + [DataRow("module foo 'br:mytest.contoso.io/|", ModuleRegistryType.ACR, ModuleRegistryType.AcrBasePathFromAlias)] [DataRow("module foo 'br/public:|", ModuleRegistryType.MCR)] - [DataRow("module foo 'br/test1:|", ModuleRegistryType.ACR)] - [DataRow("module foo 'br/test2:|", ModuleRegistryType.ACR)] - [DataRow("module foo 'br/test3:|", ModuleRegistryType.MCR)] - [DataRow("module foo 'br/test4:|", ModuleRegistryType.MCR)] - public async Task VerifyTelemetryEventIsPostedOnModuleRegistryPathCompletion(string inputWithCursors, string moduleRegistryType) + [DataRow("module foo 'br/test1acr:|", ModuleRegistryType.ACR)] + [DataRow("module foo 'br/test2acr:|", ModuleRegistryType.ACR)] + [DataRow("module foo 'br/test3mcr:|", ModuleRegistryType.MCR)] + [DataRow("module foo 'br/test4mcr:|", ModuleRegistryType.MCR)] + [DataRow("module foo 'br:yourtest.contoso.com/|", ModuleRegistryType.AcrBasePathFromAlias)] + public async Task VerifyTelemetryEventIsPostedOnModuleRegistryPathCompletion(string inputWithCursors, params string[] moduleRegistryTypes) { var bicepConfigFileContents = @"{ - ""moduleAliases"": { - ""br"": { - ""test1"": { - ""registry"": ""myacr.azurecr.io"", - ""modulePath"": ""bicep/modules"" - }, - ""test2"": { - ""registry"": ""mytest.azurecr.io"" - }, - ""test3"": { - ""registry"": ""mcr.microsoft.com"", - ""modulePath"": ""bicep/app"" - }, - ""test4"": { - ""registry"": ""mcr.microsoft.com"" - } - } - } -}"; - var (completionContext, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); - - var publicModuleMetadataProvider = StrictMock.Of(); - publicModuleMetadataProvider.Setup(x => x.GetModulesMetadata()).Returns([new("app/dapr-cntrapp1", "description1", null), new("app/dapr-cntrapp2", null, "contoso.com/help2")]); + ""moduleAliases"": { + ""br"": { + ""test1acr"": { + ""registry"": ""mytest.contoso.io"", + ""modulePath"": ""bicep/modules"" + }, + ""test2acr"": { + ""registry"": ""mytest.contoso.io"" + }, + ""test3mcr"": { + ""registry"": ""mcr.microsoft.com"", + ""modulePath"": ""bicep/app"" + }, + ""test4mcr"": { + ""registry"": ""mcr.microsoft.com"" + }, + ""test5unknownAcr"": { + ""registry"": ""yourtest.contoso.com"", + ""modulePath"": ""bicep/your/apps"" + } + } + } + }"; + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); + + var catalog = RegistryCatalogMocks.CreateCatalogWithMocks( + RegistryCatalogMocks.MockPublicMetadataProvider([ + new("bicep/app/dapr-cntrapp1", "description1", null, []), + new("bicep/app/dapr-cntrapp2", null, "contoso.com/help2", []) + ]), + RegistryCatalogMocks.MockPrivateMetadataProvider( + "mytest.contoso.io", + [ + new("bicep/modules/app1", null, null, []) + ] + ) + ); var telemetryProvider = StrictMock.Of(); telemetryProvider.Setup(x => x.PostEvent(It.IsAny())); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider, - publicModuleMetadataProvider.Object, + configMgr, + catalog, settingsProvider, telemetryProvider.Object); - await moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, CancellationToken.None); + var items = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); + items.Should().HaveCountGreaterThanOrEqualTo(1); + foreach (var moduleRegistryType in moduleRegistryTypes) + { + telemetryProvider.Verify(m => m.PostEvent(It.Is( + p => p.EventName == TelemetryConstants.EventNames.ModuleRegistryPathCompletion && + p.Properties != null && + p.Properties["moduleRegistryType"] == moduleRegistryType)), Times.Once(), + $"Should have fired telemetry event for module registry type {moduleRegistryType}"); + } telemetryProvider.Verify(m => m.PostEvent(It.Is( p => p.EventName == TelemetryConstants.EventNames.ModuleRegistryPathCompletion && p.Properties != null && - p.Properties["moduleRegistryType"] == moduleRegistryType)), Times.Exactly(1)); + !moduleRegistryTypes.Contains(p.Properties["moduleRegistryType"]))), Times.Never, + $"Telemetry event fired for unexpected module registry type"); + } + + [DataTestMethod] + [DataRow("module foo 'br:mcr.microsoft.com/bicep/|", null)] + [DataRow("module foo 'br:mytest.contoso.io/|", ModuleRegistryResolutionType.AcrModulePath)] + [DataRow("module foo 'br/public:|", null)] + [DataRow("module foo 'br/public:app/dapr-cntrapp1|", null)] + [DataRow("module foo 'br/public:app/dapr-cntrapp1:|", null)] + [DataRow("module foo 'br/test1acr:|", ModuleRegistryResolutionType.AcrModulePath)] + [DataRow("module foo 'br:mytest.contoso.io/bicep/modules/app1:|", ModuleRegistryResolutionType.AcrVersion)] + [DataRow("module foo 'br/test1acr:app1:|", ModuleRegistryResolutionType.AcrVersion)] + public async Task VerifyTelemetryEventIsPostedOnModuleRegistryCompletionItemResolution(string inputWithCursors, string? moduleResolutionType) + { + var bicepConfigFileContents = @"{ + ""moduleAliases"": { + ""br"": { + ""test1acr"": { + ""registry"": ""mytest.contoso.io"", + ""modulePath"": ""bicep/modules"" + } + } + } + }"; + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext(inputWithCursors, bicepConfigFileContents); + + var catalog = RegistryCatalogMocks.CreateCatalogWithMocks( + RegistryCatalogMocks.MockPublicMetadataProvider([ + new("bicep/app/dapr-cntrapp1", "description1", null, [new("v1.1", null, null)]), + ]), + RegistryCatalogMocks.MockPrivateMetadataProvider( + "mytest.contoso.io", + [ + new("bicep/modules/app1", null, null, [new("v1.1", null, null)]) + ] + ) + ); + + var telemetryProvider = StrictMock.Of(); + telemetryProvider.Setup(x => x.PostEvent(It.IsAny())); + + var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( + azureContainerRegistriesProvider, + configMgr, + catalog, + settingsProvider, + telemetryProvider.Object); + var items = await GetAndResolveCompletionItems(documentUri.ToUriEncoded(), completionContext, moduleReferenceCompletionProvider); + items.Should().HaveCountGreaterThanOrEqualTo(1); + + if (moduleResolutionType is not null) + { + telemetryProvider.Verify( + m => m.PostEvent(It.Is( + p => p.EventName == TelemetryConstants.EventNames.ModuleRegistryResolution && + p.Properties != null && + p.Properties["type"] == moduleResolutionType)), + Times.Once, + $"Telemetry event should have fired for resolution type {moduleResolutionType}"); + } + else + { + telemetryProvider.Verify( + m => m.PostEvent(It.Is( + p => p.EventName == TelemetryConstants.EventNames.ModuleRegistryResolution)), + Times.Never, + $"Telemetry event should not have fired"); + } } [TestMethod] public async Task GetFilteredCompletions_WithACRCompletionsSettingSetToTrue_AndIsCanceled_EnumerationShouldBeCanceled() { - var (completionContext, documentUri) = GetBicepCompletionContext("module test 'br:|'"); + var (completionContext, configMgr, documentUri) = GetBicepCompletionContext("module test 'br:|'"); var settingsProviderMock = StrictMock.Of(); settingsProviderMock.Setup(x => x.GetSetting(LangServerConstants.GetAllAzureContainerRegistriesForCompletionsSetting)).Returns(true); @@ -825,28 +1033,30 @@ async IAsyncEnumerable GetUris([EnumeratorCancellation] CancellationToke secondItemReturned = true; yield return "testacr4.azurecr.io"; } - azureContainerRegistriesProvider.Setup(x => x.GetContainerRegistriesAccessibleFromAzure(completionContext.Configuration.Cloud, It.IsAny())) + azureContainerRegistriesProvider.Setup(x => x.GetContainerRegistriesAccessibleFromAzure(It.IsAny(), It.IsAny())) .Returns((CloudConfiguration _, CancellationToken ct) => GetUris(ct)); var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider( azureContainerRegistriesProvider.Object, - publicModuleMetadataProvider, + configMgr, + RegistryCatalogMocks.CreateCatalogWithMocks(), settingsProviderMock.Object, BicepTestConstants.CreateMockTelemetryProvider().Object); - var func = () => moduleReferenceCompletionProvider.GetFilteredCompletions(completionContext, cts.Token); + var func = () => moduleReferenceCompletionProvider.GetFilteredCompletions(documentUri.ToUriEncoded(), completionContext, cts.Token); await func.Should().ThrowAsync(); firstItemReturned.Should().BeTrue(); secondItemReturned.Should().BeFalse(); } - private static (BicepCompletionContext, DocumentUri) GetBicepCompletionContext( + private (BicepCompletionContext, ConfigurationManager, DocumentUri) GetBicepCompletionContext( string inputWithCursors, string? bicepConfigFileContents = null) { - var documentUri = DocumentUri.From(InMemoryFileResolver.GetFileUri("/path/to/main.bicep")); + var documentUri = DocumentUri.FromFileSystemPath("/path/to/main.bicep"); var (bicepFileContents, cursors) = ParserHelper.GetFileWithCursors(inputWithCursors, '|'); + var files = new Dictionary { ["/path/to/main.bicep"] = bicepFileContents, @@ -857,11 +1067,13 @@ private static (BicepCompletionContext, DocumentUri) GetBicepCompletionContext( files["/path/to/bicepconfig.json"] = bicepConfigFileContents; } - var configurationManager = new ConfigurationManager(new FileSystemFileExplorer(new MockFileSystem(files))); - var bicepCompilationManager = BicepCompilationManagerHelper.CreateCompilationManager(documentUri, bicepFileContents, true, configurationManager: configurationManager); + var bicepCompilationManager = BicepCompilationManagerHelper.CreateCompilationManager(documentUri, bicepFileContents, true); var compilation = bicepCompilationManager.GetCompilation(documentUri)!.Compilation; - return (BicepCompletionContext.Create(compilation, cursors[0]), documentUri); + return ( + BicepCompletionContext.Create(compilation, cursors[0]), + new ConfigurationManager(new FileSystemFileExplorer(new MockFileSystem(files))), + documentUri); } } } diff --git a/src/Bicep.LangServer.UnitTests/packages.lock.json b/src/Bicep.LangServer.UnitTests/packages.lock.json index e8ddb935473..f5c53485de2 100644 --- a/src/Bicep.LangServer.UnitTests/packages.lock.json +++ b/src/Bicep.LangServer.UnitTests/packages.lock.json @@ -1546,6 +1546,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", diff --git a/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs b/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs index b6d540041eb..336ef030acc 100644 --- a/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs +++ b/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs @@ -93,7 +93,12 @@ public async Task> GetFilteredCompletions(Compilatio .Concat(GetParamValueCompletions(model, context)) .Concat(GetAssertValueCompletions(model, context)) .Concat(GetTypeArgumentCompletions(model, context)) - .Concat(await moduleReferenceCompletionProvider.GetFilteredCompletions(context, cancellationToken)); + .Concat(await moduleReferenceCompletionProvider.GetFilteredCompletions(model.SourceFile.Uri, context, cancellationToken)); + } + + public Task Resolve(CompletionItem completionItem, CancellationToken cancellationToken) + { + return moduleReferenceCompletionProvider.ResolveCompletionItem(completionItem, cancellationToken); } private IEnumerable GetParamIdentifierCompletions(SemanticModel paramsSemanticModel, BicepCompletionContext paramsCompletionContext) diff --git a/src/Bicep.LangServer/Completions/CompletionItemBuilder.cs b/src/Bicep.LangServer/Completions/CompletionItemBuilder.cs index 38b6719617c..2c4d71a6f34 100644 --- a/src/Bicep.LangServer/Completions/CompletionItemBuilder.cs +++ b/src/Bicep.LangServer/Completions/CompletionItemBuilder.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Bicep.Core.Json; +using Newtonsoft.Json.Linq; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; @@ -20,6 +22,7 @@ public class CompletionItemBuilder private InsertTextFormat insertTextFormat; private TextEditOrInsertReplaceEdit? textEdit; private InsertTextMode insertTextMode; + private object? data; private string? sortText; private bool preselect; @@ -53,9 +56,18 @@ public CompletionItem Build() SortText = this.sortText, Preselect = this.preselect, Command = this.command, + Data = data is null ? null : JObject.FromObject(data), }; } + // Pass in any object here and the completion handler will be asked to resolve the completion item when it is selected + // (e.g. by filling in details or documentation). + public CompletionItemBuilder WithResolveData(string key, object? data) + { + this.data = data is null ? null : new Dictionary { { key, data } }; + return this; + } + public CompletionItemBuilder WithAdditionalEdits(TextEditContainer editContainer) { this.additionalTextEdits = editContainer; diff --git a/src/Bicep.LangServer/Completions/CompletionItemExtensions.cs b/src/Bicep.LangServer/Completions/CompletionItemExtensions.cs new file mode 100644 index 00000000000..f93e4932810 --- /dev/null +++ b/src/Bicep.LangServer/Completions/CompletionItemExtensions.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using Bicep.Core.Json; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; + +namespace Bicep.LanguageServer.Completions +{ + public static class CompletionItemExtensions + { + public static CompletionItem WithDocumentation(this CompletionItem completionItem, string? markdown) + { + if (!string.IsNullOrEmpty(markdown)) + { + return completionItem with + { + Documentation = new StringOrMarkupContent(new MarkupContent + { + Kind = MarkupKind.Markdown, + Value = markdown + }) + }; + } + + return completionItem; + } + } +} diff --git a/src/Bicep.LangServer/Completions/ICompletionProvider.cs b/src/Bicep.LangServer/Completions/ICompletionProvider.cs index 966615bc77c..8f7d8669959 100644 --- a/src/Bicep.LangServer/Completions/ICompletionProvider.cs +++ b/src/Bicep.LangServer/Completions/ICompletionProvider.cs @@ -9,5 +9,10 @@ namespace Bicep.LanguageServer.Completions public interface ICompletionProvider { Task> GetFilteredCompletions(Compilation compilation, BicepCompletionContext context, CancellationToken cancellationToken); + + Task Resolve(CompletionItem completionItem, CancellationToken cancellationToken) + { + return Task.FromResult(completionItem); + } } } diff --git a/src/Bicep.LangServer/Completions/IModuleReferenceCompletionProvider.cs b/src/Bicep.LangServer/Completions/IModuleReferenceCompletionProvider.cs index 4db052f32e7..ae78b20f192 100644 --- a/src/Bicep.LangServer/Completions/IModuleReferenceCompletionProvider.cs +++ b/src/Bicep.LangServer/Completions/IModuleReferenceCompletionProvider.cs @@ -1,13 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Bicep.Core.Workspaces; using OmniSharp.Extensions.LanguageServer.Protocol.Models; namespace Bicep.LanguageServer.Completions { public interface IModuleReferenceCompletionProvider { - Task> GetFilteredCompletions(BicepCompletionContext context, CancellationToken cancellationToken); + Task> GetFilteredCompletions(Uri templateUri, BicepCompletionContext context, CancellationToken cancellationToken); + + Task ResolveCompletionItem(CompletionItem completionItem, CancellationToken cancellationToken); } } diff --git a/src/Bicep.LangServer/Completions/ModuleReferenceCompletionProvider.cs b/src/Bicep.LangServer/Completions/ModuleReferenceCompletionProvider.cs index e099487c6b8..354dd166225 100644 --- a/src/Bicep.LangServer/Completions/ModuleReferenceCompletionProvider.cs +++ b/src/Bicep.LangServer/Completions/ModuleReferenceCompletionProvider.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Immutable; +using System.Configuration; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text; @@ -9,26 +10,32 @@ using Bicep.Core; using Bicep.Core.Configuration; using Bicep.Core.Parsing; +using Bicep.Core.Registry; using Bicep.Core.Registry.Oci; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog; using Bicep.Core.Syntax; -using Bicep.Core.Workspaces; using Bicep.LanguageServer.Providers; using Bicep.LanguageServer.Settings; using Bicep.LanguageServer.Telemetry; using Bicep.LanguageServer.Utils; +using Microsoft.Win32; +using Microsoft.WindowsAzure.ResourceStack.Common.Extensions; +using Newtonsoft.Json.Linq; using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using Microsoft.WindowsAzure.ResourceStack.Common.Json; +using Bicep.Core.Registry.Catalog.Implementation; +using Microsoft.VisualBasic; namespace Bicep.LanguageServer.Completions { /// /// Provides completions for OCI (public or private) module references, e.g. br/public:modulePath:version /// - public class ModuleReferenceCompletionProvider : IModuleReferenceCompletionProvider + public partial class ModuleReferenceCompletionProvider : IModuleReferenceCompletionProvider { private readonly IAzureContainerRegistriesProvider azureContainerRegistriesProvider; - - private readonly IPublicModuleMetadataProvider publicModuleMetadataProvider; + private readonly IConfigurationManager configurationManager; + private readonly IRegistryModuleCatalog registryModuleCatalog; private readonly ISettingsProvider settingsProvider; private readonly ITelemetryProvider telemetryProvider; @@ -39,33 +46,115 @@ private enum ModuleCompletionPriority FullPath = 2, // br:, ts: } - // Direct reference to a full registry login server URI via br: - private static readonly Regex ModulePrefixWithFullPath = new(@"^br:(?(.*?))/", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); + [GeneratedRegex( + """ + (?x) # Extended mode (allow comments and whitespace) + ^ + ( # Prefix and registry or alias + + br/(?[a-zA-Z0-9-_]*): # see src\Bicep.Core\Configuration\ModuleAliasesConfiguration.cs::ModuleAliasNameRegex + | + br:(?[-0-9A-Za-z|.^]*)\/ + ) + + # Path + ( + (?[a-z0-9._\-/]*) # see src\Bicep.Core\Configuration\ModuleAliasesConfiguration.cs::OciNamespaceSegmentRegex + )? + + # Version + ( + (?:) + (?[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127})? # see src\Bicep.Core\Registry\Oci\OciArtifactReferenceFacts.cs::TagNameRegex + )? + + '? + $ + """, + RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant, + matchTimeoutMilliseconds: 10 + )] + private static partial Regex PartialModuleReferenceRegex(); + + // Examples: + // br:contoso.io/path1/path2/module:2.0.1 => + // SpecifiedRegistry: "contoso.io" + // ResolvedRegistry: "contoso.io" + // SpecifiedModulePath: "path1/path2/module" + // ModulePathPrefix: "contosoBasePath/" (from bicepconfig.json, may be empty string) + // ResolvedModulePath: "contosoBasePath/path1/path2/module" + // Version: "2.0.1" + // ToNotation: "br:contoso.io/path1/path2/module:2.0.1" + // br/public:path1/path2/module:2.0.1 => + // SpecifiedRegistry: null + // ResolvedRegistry: "mcr.microsoft.com" + // ModulePathPrefix: "bicep/" (from bicepconfig.json default values) + // SpecifiedModulePath: "path1/path2/module" + // ResolvedModulePath: "bicep/path1/path2/module" + // Version: "2.0.1" + // ToNotation: "br/public:path1/path2/module:2.0.1" + private record Parts( + string? SpecifiedRegistry, + string ResolvedRegistry, + string? SpecifiedAlias, + string? ModulePathPrefix, + string? SpecifiedModulePath, + string? Version, + bool HasVersionSeparator + ) + { + public string ToNotation() => + SpecifiedAlias is string + ? $"br/{SpecifiedAlias}:{SpecifiedModulePath}{VersionTextWithSeparator}" + : $"br:{SpecifiedRegistry}/{SpecifiedModulePath}{VersionTextWithSeparator}"; - // Aliased reference to a registry via br/alias:path - private static readonly Regex ModuleWithAliasAndVersionSeparator = new(@"^br/(.*):(?(.*?)):", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); + private string VersionTextWithSeparator => HasVersionSeparator ? $":{Version}" : string.Empty; - // Direct reference to the MCR (public) registry via br:mcr.microsoft.com/bicep/path - private static readonly Regex PublicModuleWithFullPathAndVersionSeparator = new($"^br:{PublicMCRRegistry}/bicep/(?(.*?)):'?$", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); + public string ModulePathPrefixWithSeparator => string.IsNullOrWhiteSpace(ModulePathPrefix) ? "" : $"{ModulePathPrefix}/"; - // Aliased reference to the MCR (public) registry via br/public: - private static readonly Regex PublicModuleWithAliasAndVersionSeparator = new(@"^br/public:(?(.*?)):'?$", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); + public string ResolvedModulePath => $"{ModulePathPrefixWithSeparator}{SpecifiedModulePath}"; - private const string PublicMCRRegistry = LanguageConstants.BicepPublicMcrRegistry; // "mcr.microsoft.com" + public Parts WithModulePath(string newResolvedModulePath) + { + if (!string.IsNullOrWhiteSpace(ModulePathPrefix) && newResolvedModulePath.StartsWith(ModulePathPrefixWithSeparator, StringComparison.Ordinal)) + { + return this with + { + SpecifiedModulePath = newResolvedModulePath.Substring(ModulePathPrefixWithSeparator.Length) + }; + } + else + { + return this with + { + SpecifiedModulePath = newResolvedModulePath + }; + } + } + + public bool IsPublicRegistry => string.CompareOrdinal(ResolvedRegistry, PublicMcrRegistry) == 0; + } + + private const string PublicMcrRegistry = LanguageConstants.BicepPublicMcrRegistry; // "mcr.microsoft.com" + + private const string ModuleVersionResolutionKey = "ociVersion"; + private const string ModuleResolutionKey = "oci"; public ModuleReferenceCompletionProvider( IAzureContainerRegistriesProvider azureContainerRegistriesProvider, - IPublicModuleMetadataProvider publicModuleMetadataProvider, + IConfigurationManager configurationManager, + IRegistryModuleCatalog registryModuleCatalog, ISettingsProvider settingsProvider, ITelemetryProvider telemetryProvider) { this.azureContainerRegistriesProvider = azureContainerRegistriesProvider; - this.publicModuleMetadataProvider = publicModuleMetadataProvider; + this.configurationManager = configurationManager; + this.registryModuleCatalog = registryModuleCatalog; this.settingsProvider = settingsProvider; this.telemetryProvider = telemetryProvider; } - public async Task> GetFilteredCompletions(BicepCompletionContext context, CancellationToken cancellationToken) + public async Task> GetFilteredCompletions(Uri sourceFileUri, BicepCompletionContext context, CancellationToken cancellationToken) { var replacementText = string.Empty; @@ -74,7 +163,8 @@ public async Task> GetFilteredCompletions(BicepCompl replacementText = token.Text; } - var completions = GetTopLevelCompletions(context, replacementText); + var rootConfiguration = configurationManager.GetConfiguration(sourceFileUri); + var completions = GetTopLevelCompletions(context, replacementText, rootConfiguration); var startsWithSingleQuote = replacementText.StartsWith('\''); if (startsWithSingleQuote) @@ -82,9 +172,10 @@ public async Task> GetFilteredCompletions(BicepCompl var trimmedReplacementText = replacementText.Trim('\''); var replacementsRequiringStartingQuote = - GetOciModulePathCompletions(context, trimmedReplacementText) - .Concat(GetPublicModuleVersionCompletions(context, trimmedReplacementText)) - .Concat(await GetAllRegistryNameAndAliasCompletions(context, trimmedReplacementText, cancellationToken)); + (await GetModuleCompletions(trimmedReplacementText, context, rootConfiguration)) + .Concat(GetPartialPrivatePathCompletionsFromAliases(trimmedReplacementText, context, rootConfiguration)) + .Concat(await GetVersionCompletions(context, trimmedReplacementText, rootConfiguration)) + .Concat(await GetAllRegistryNameAndAliasCompletions(context, trimmedReplacementText, rootConfiguration, cancellationToken)); completions = [ .. completions, @@ -95,9 +186,56 @@ public async Task> GetFilteredCompletions(BicepCompl return completions; } + private Parts? ParseParts(string text, RootConfiguration rootConfiguration) + { + var match = PartialModuleReferenceRegex().Match(text); + if (!match.Success) + { + return null; + } + + var path = NullIfEmpty(match.Groups["path"].Value); + var version = NullIfEmpty(match.Groups["version"].Value); + var hasVersionSeparator = match.Groups["versionSeparator"].Success; + + if (NullIfEmpty(match.Groups["registry"].Value) is string registry) + { + // Reference with fully-specified registry + return new Parts( + SpecifiedRegistry: registry, + ResolvedRegistry: registry, + SpecifiedAlias: null, + SpecifiedModulePath: path, + ModulePathPrefix: null, + Version: version, + HasVersionSeparator: hasVersionSeparator + ); + } + else if (NullIfEmpty(match.Groups["alias"].Value) is string alias) + { + // Reference with alias + if (TryGetValidModuleAlias(rootConfiguration, alias, out var aliasRegistry, out var aliasModulePath)) + { + return new Parts( + SpecifiedRegistry: null, + ResolvedRegistry: aliasRegistry, + SpecifiedAlias: alias, + ModulePathPrefix: aliasModulePath, + SpecifiedModulePath: path, + Version: version, + HasVersionSeparator: hasVersionSeparator + ); + } + } + + return null; + + string? NullIfEmpty(string? value) => string.IsNullOrWhiteSpace(value) ? null : value; + } + // Handles bicep registry and template spec top-level schema completions. // I.e. typing with an empty path: module m1 - private IEnumerable GetTopLevelCompletions(BicepCompletionContext context, string untrimmedReplacementText) + private IEnumerable GetTopLevelCompletions(BicepCompletionContext context, string untrimmedReplacementText, RootConfiguration rootConfiguration) { if (!context.Kind.HasFlag(BicepCompletionContextKind.ModulePath) && !context.Kind.HasFlag(BicepCompletionContextKind.UsingFilePath)) @@ -110,10 +248,10 @@ private IEnumerable GetTopLevelCompletions(BicepCompletionContex return []; } - List completionItems = []; + List completionItems = new(); - var templateSpecModuleAliases = context.Configuration.ModuleAliases.GetTemplateSpecModuleAliases(); - var bicepModuleAliases = GetModuleAliases(context); + var templateSpecModuleAliases = rootConfiguration.ModuleAliases.GetTemplateSpecModuleAliases(); + var bicepModuleAliases = GetModuleAliases(rootConfiguration); // Top-level TemplateSpec completions AddCompletionItem("ts:", null, "Template spec", ModuleCompletionPriority.FullPath, "template spec completion"); @@ -141,7 +279,7 @@ private IEnumerable GetTopLevelCompletions(BicepCompletionContex var alias = kvp.Key; var registry = kvp.Value.Registry as string; var modulePath = kvp.Value.ModulePath as string; - var detail = (string.CompareOrdinal(registry, PublicMCRRegistry) == 0 && string.CompareOrdinal(modulePath, "bicep") == 0) + var detail = (string.CompareOrdinal(registry, PublicMcrRegistry) == 0 && string.CompareOrdinal(modulePath, "bicep") == 0) ? "Public Bicep registry" : $"Alias for br:{registry}/{(modulePath == null ? "" : (modulePath + "/"))}"; @@ -187,283 +325,73 @@ private bool IsOciArtifactRegistryReference(string trimmedText) return false; } - // Handles version completions for Microsoft Container Registries (MCR): - // - // br/module/name: - // br:mcr.microsoft/bicep/module/name: - // - // etc - private IEnumerable GetPublicModuleVersionCompletions(BicepCompletionContext context, string trimmedText) + private async Task> GetVersionCompletions(BicepCompletionContext context, string trimmedText, RootConfiguration rootConfiguration) { - if (!IsOciArtifactRegistryReference(trimmedText)) - { - return []; - } - - string? modulePath; - - if (PublicModuleWithAliasAndVersionSeparator.IsMatch(trimmedText)) - { - var matches = PublicModuleWithAliasAndVersionSeparator.Matches(trimmedText); - modulePath = matches[0].Groups["path"].Value; - } - else if (PublicModuleWithFullPathAndVersionSeparator.IsMatch(trimmedText)) - { - var matches = PublicModuleWithFullPathAndVersionSeparator.Matches(trimmedText); - modulePath = matches[0].Groups["path"].Value; - } - else - { - modulePath = GetAliasedMCRModulePath(trimmedText, context); - } - - if (modulePath is null) + if (ParseParts(trimmedText, rootConfiguration) is not Parts parts + || parts.ResolvedModulePath is null + || !parts.HasVersionSeparator + || !string.IsNullOrWhiteSpace(parts.Version) + ) { return []; } List completions = new(); - var versionsMetadata = publicModuleMetadataProvider.GetModuleVersionsMetadata(modulePath); - - for (int i = versionsMetadata.Length - 1; i >= 0; i--) + if (await registryModuleCatalog.GetProviderForRegistry(rootConfiguration.Cloud, parts.ResolvedRegistry) + .TryGetModuleAsync($"{parts.ResolvedModulePath}") is { } module) { - var (version, description, documentationUri) = versionsMetadata[i]; - - var insertText = $"'{trimmedText}{version}'$0"; + var versions = (await module.TryGetVersionsAsync()) + .Where(v => v.IsBicepModule != false) + .ToArray(); - // Module version is last completion, no follow-up completions triggered - var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, version) - .WithSnippetEdit(context.ReplacementRange, insertText) - .WithFilterText(insertText) - .WithSortText(GetSortText(i)) - .WithDetail(description) - .WithDocumentation(MarkdownHelper.GetDocumentationLink(documentationUri)) - .Build(); - - completions.Add(completionItem); - } - - return completions; - - // Handles scenario where the user has configured an alias for MCR in bicepconfig.json. - string? GetAliasedMCRModulePath(string trimmedText, BicepCompletionContext context) - { - foreach (var kvp in GetModuleAliases(context)) + for (int i = 0; i < versions.Length; ++i) { - if (kvp.Value.Registry is string registry && - registry.Equals(PublicMCRRegistry, StringComparison.Ordinal)) - { - var aliasFromBicepConfig = $"br/{kvp.Key}:"; - - if (trimmedText.StartsWith(aliasFromBicepConfig, StringComparison.Ordinal)) - { - var matches = ModuleWithAliasAndVersionSeparator.Matches(trimmedText); - - if (!matches.Any()) - { - continue; - } - - string subpath = matches[0].Groups["path"].Value; - - if (subpath is null) - { - continue; - } - - var modulePath = kvp.Value.ModulePath; - - if (modulePath is not null) - { - if (modulePath.StartsWith("bicep/")) - { - modulePath = modulePath.Substring("bicep/".Length); - return $"{modulePath}/{subpath}"; - } - } - else - { - if (subpath.StartsWith("bicep/")) - { - return subpath.Substring("bicep/".Length); - } - } - } - } - } - - return null; - } - } - - private static ImmutableSortedDictionary GetModuleAliases(BicepCompletionContext context) => context.Configuration.ModuleAliases.GetOciArtifactModuleAliases(); - - // Handles remote (OCI) path completions, e.g. br: and br/ - private IEnumerable GetOciModulePathCompletions(BicepCompletionContext context, string trimmedText) - { - if (!IsOciArtifactRegistryReference(trimmedText)) - { - return []; - } - - return [ - .. GetPublicModuleCompletions(trimmedText, context), - .. GetPartialPrivatePathCompletionsFromAliases(trimmedText, context), - .. GetPublicPathCompletionFromAliases(trimmedText, context), - ]; - } + var version = versions[versions.Length - 1 - i].Version; // Show versions from most recent to oldest + var insertText = $"'{trimmedText}{version}'$0"; + // Module version is last completion, no follow-up completions triggered + var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, version) + .WithSnippetEdit(context.ReplacementRange, insertText) + .WithFilterText(insertText) + .WithSortText(GetSortText(i)) + // Description and documentation will be resolved later as needed + .WithResolveData(ModuleVersionResolutionKey, new { Registry = parts.ResolvedRegistry, Module = parts.ResolvedModulePath, Version = version }) + .Build(); - // Handles path completions for case where user has specified an alias in bicepconfig.json with registry set to "mcr.microsoft.com". - private IEnumerable GetPublicPathCompletionFromAliases(string trimmedText, BicepCompletionContext context) - { - List completions = new(); - - if (IsPrivateRegistryReference(trimmedText, out _)) - { - return completions; - } - - foreach (var kvp in GetModuleAliases(context)) - { - if (kvp.Value.Registry is string registry) - { - // We currently don't support path completion for private modules, but we'll go ahead and log telemetry to track usage. - if (!registry.Equals(PublicMCRRegistry, StringComparison.Ordinal) && - trimmedText.Equals($"br/{kvp.Key}:")) - { - telemetryProvider.PostEvent(BicepTelemetryEvent.ModuleRegistryPathCompletion(ModuleRegistryType.ACR)); - break; - } - - // br/[alias-that-points-to-mcr.microsoft.com]: - if (registry.Equals(PublicMCRRegistry, StringComparison.Ordinal) && - trimmedText.Equals($"br/{kvp.Key}:")) - { - var modulePath = kvp.Value.ModulePath; - - if (modulePath is null) - { - // E.g bicepconfig.json - // { - // "moduleAliases": { - // "br": { - // "test": { - // "registry": "mcr.microsoft.com" - // } - // } - // } - // } - - if (trimmedText.Equals($"br/{kvp.Key}:", StringComparison.Ordinal)) - { - var modules = publicModuleMetadataProvider.GetModulesMetadata(); - foreach (var (moduleName, description, documentationUri) in modules) - { - var label = $"bicep/{moduleName}"; - var insertText = $"'{trimmedText}bicep/{moduleName}:$0'"; - var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, label) - .WithSnippetEdit(context.ReplacementRange, insertText) - .WithFilterText(insertText) - .WithSortText(GetSortText(label, ModuleCompletionPriority.Alias)) - .WithDetail(description) - .WithDocumentation(MarkdownHelper.GetDocumentationLink(documentationUri)) - .WithFollowupCompletion("module version completion") - .Build(); - - completions.Add(completionItem); - } - } - } - else - { - // E.g bicepconfig.json - // { - // "moduleAliases": { - // "br": { - // "test": { - // "registry": "mcr.microsoft.com", - // "modulePath": "bicep/app" - // } - // } - // } - // } - - if (modulePath.Equals("bicep", StringComparison.Ordinal) || !modulePath.StartsWith("bicep/", StringComparison.Ordinal)) - { - continue; - } - - // Completions are e.g. br/[alias]/[module] - var modulePathWithoutBicepKeyword = TrimStart(modulePath, "bicep/"); - var modules = publicModuleMetadataProvider.GetModulesMetadata(); - - var matchingModules = modules.Where(x => x.Name.StartsWith($"{modulePathWithoutBicepKeyword}/")); - - foreach (var module in matchingModules) - { - var label = module.Name.Substring($"{modulePathWithoutBicepKeyword}/".Length); - - StringBuilder sb = new($"'{trimmedText}"); - if (!trimmedText.EndsWith(':')) - { - sb.Append(":"); - } - sb.Append($"{label}:$0'"); - var insertText = sb.ToString(); - - var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, label) - .WithSnippetEdit(context.ReplacementRange, insertText) - .WithFilterText(insertText) - .WithSortText(GetSortText(label, ModuleCompletionPriority.Alias)) - .WithDetail(module.Description) - .WithDocumentation(MarkdownHelper.GetDocumentationLink(module.DocumentationUri)) - .WithFollowupCompletion("module version completion") - .Build(); - completions.Add(completionItem); - } - } - } + completions.Add(completionItem); } } - if (completions.Any()) - { - telemetryProvider.PostEvent(BicepTelemetryEvent.ModuleRegistryPathCompletion(ModuleRegistryType.MCR)); - } - return completions; } - private string TrimStart(string text, string prefixToTrim) => text.StartsWith(prefixToTrim) ? text.Substring(prefixToTrim.Length) : text; + private static ImmutableSortedDictionary GetModuleAliases(RootConfiguration configuration) + { + return configuration.ModuleAliases.GetOciArtifactModuleAliases(); + } - /// - /// True if a direct reference to a private ACR registry (i.e. not pointing to the Microsoft public bicep registry) - /// Example: - /// "br:privateacr.azurecr.io/" => true - /// - /// - /// Won't be null with true return value, but could be empty - /// - private bool IsPrivateRegistryReference(string text, [NotNullWhen(true)] out string? registry) + private static bool TryGetValidModuleAlias( + RootConfiguration configuration, + string aliasName, + [NotNullWhen(true)] out string? registry, + out string? modulePath) { registry = null; + modulePath = null; - var matches = ModulePrefixWithFullPath.Matches(text); - if (!matches.Any()) + if (configuration.ModuleAliases.GetOciArtifactModuleAliases().TryGetValue(aliasName, out var aliasConfig) + && !string.IsNullOrWhiteSpace(aliasConfig.Registry)) { - return false; + registry = aliasConfig.Registry; + modulePath = aliasConfig.ModulePath; + return true; } - registry = matches[0].Groups["registry"].Value; - - return !registry.Equals(PublicMCRRegistry, StringComparison.Ordinal); + return false; } - // We only support partial path completions for ACR using module paths listed in bicepconfig.json - - // Handles ACR path completions for full paths, but only for the case where the user has configured an alias in bicepconfig.json. + // Automatically adds completions for the base path portion of a private registry from an alias in bicepconfig.json. // Example: // bicepconfig.json: // { @@ -471,99 +399,133 @@ private bool IsPrivateRegistryReference(string text, [NotNullWhen(true)] out str // "br": { // "whatever": { // "registry": "privateacr.azurecr.io", - // "modulePath": "bicep/app" + // "modulePath": "bicep/app" => we'll automatically complete this part // ... // // br:privateacr.azurecr.io/ // => // br:privateacr.azurecr.io/bicep/app: - private IEnumerable GetPartialPrivatePathCompletionsFromAliases(string trimmedText, BicepCompletionContext context) + private IEnumerable GetPartialPrivatePathCompletionsFromAliases(string trimmedText, BicepCompletionContext context, RootConfiguration rootConfiguration) { - List completions = new(); - - if (!IsPrivateRegistryReference(trimmedText, out string? registry) || string.IsNullOrWhiteSpace(registry)) + if (ParseParts(trimmedText, rootConfiguration) is not Parts parts + || !string.IsNullOrWhiteSpace(parts.ResolvedModulePath) + || parts.HasVersionSeparator + || !string.IsNullOrEmpty(parts.SpecifiedAlias) + || parts.IsPublicRegistry + || string.IsNullOrWhiteSpace(parts.ResolvedRegistry)) { - return completions; + return []; } - telemetryProvider.PostEvent(BicepTelemetryEvent.ModuleRegistryPathCompletion(ModuleRegistryType.ACR)); - foreach (var kvp in GetModuleAliases(context)) + List completions = new(); + + var sentTelemetry = false; + foreach (var kvp in GetModuleAliases(rootConfiguration)) { - if (registry.Equals(kvp.Value.Registry, StringComparison.Ordinal)) + if (parts.ResolvedRegistry.Equals(kvp.Value.Registry, StringComparison.Ordinal)) { var modulePath = kvp.Value.ModulePath; - if (modulePath is null) + if (modulePath is null) // No module path to complete { continue; } var insertText = $"'{trimmedText}{modulePath}:$0'"; var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Reference, modulePath) - .WithSnippetEdit(context.ReplacementRange, insertText) - .WithFilterText(insertText) - .WithSortText(GetSortText(modulePath)) - .WithFollowupCompletion("module path completion") - .Build(); + .WithSnippetEdit(context.ReplacementRange, insertText) + .WithFilterText(insertText) + .WithSortText(GetSortText(modulePath)) + .WithResolveData(ModuleResolutionKey, new { Registry = parts.ResolvedRegistry, Module = modulePath }) + .WithFollowupCompletion("module path completion") + .Build(); completions.Add(completionItem); + + if (!sentTelemetry) + { + telemetryProvider.PostEvent(BicepTelemetryEvent.ModuleRegistryPathCompletion(ModuleRegistryType.AcrBasePathFromAlias, true, null)); + sentTelemetry = true; + } } } return completions; } - // Handles module path completions for MCR: - // br/public: + // Handles module path completions for public or private OCI modules + // br/alias: // or - // br:mcr.microsoft.com/bicep/: - private IEnumerable GetPublicModuleCompletions(string trimmedText, BicepCompletionContext context) + // br:registry.contoso.io/bicep/: + private async Task> GetModuleCompletions(string trimmedText, BicepCompletionContext context, RootConfiguration rootConfiguration) { - var (prefix, suffix) = trimmedText switch - { - { } x when x.StartsWith("br/public:", StringComparison.Ordinal) => ("br/public:", x["br/public:".Length..]), - { } x when x.StartsWith($"br:{PublicMCRRegistry}/bicep/", StringComparison.Ordinal) => ($"br:{PublicMCRRegistry}/bicep/", x[$"br:{PublicMCRRegistry}/bicep/".Length..]), - _ => (null, null), - }; - - if (prefix is null || suffix is null) + if (ParseParts(trimmedText, rootConfiguration) is not Parts parts + || parts.ResolvedModulePath is null + || parts.HasVersionSeparator) { return []; } List completions = new(); + var acr = false; + var mcr = false; + var isAliased = !string.IsNullOrWhiteSpace(parts.SpecifiedAlias); - var modules = publicModuleMetadataProvider.GetModulesMetadata(); - foreach (var (moduleName, description, documentationUri) in modules) + var modules = await registryModuleCatalog + .GetProviderForRegistry(rootConfiguration.Cloud, parts.ResolvedRegistry) + .TryGetModulesAsync(); + foreach (var module in modules) { - if (!moduleName.StartsWith(suffix, StringComparison.Ordinal)) + var moduleName = module.ModuleName; + + if (!moduleName.StartsWith(parts.ResolvedModulePath, StringComparison.Ordinal)) { continue; } - var insertText = $"'{prefix}{moduleName}:$0'"; + string insertText = $"'{parts.WithModulePath(moduleName).ToNotation()}:$0'"; - var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, moduleName) - .WithSnippetEdit(context.ReplacementRange, insertText) - .WithFilterText(insertText) - .WithSortText(GetSortText(moduleName)) - .WithDetail(description) - .WithDocumentation(MarkdownHelper.GetDocumentationLink(documentationUri)) - .WithFollowupCompletion("module version completion") - .Build(); + // Remove the base path prefix from the label if we're dealing with a module alias + var label = !string.IsNullOrWhiteSpace(parts.SpecifiedAlias) && !string.IsNullOrWhiteSpace(parts.ModulePathPrefix) + ? moduleName.Substring(parts.ModulePathPrefixWithSeparator.Length) + : moduleName; + + var completionItem = CompletionItemBuilder.Create( + CompletionItemKind.Snippet, label) + .WithSnippetEdit(context.ReplacementRange, insertText) + .WithFilterText(insertText) + .WithSortText(GetSortText(moduleName)) + .WithResolveData( + ModuleResolutionKey, + new { Registry = module.Registry, Module = moduleName }) + .WithFollowupCompletion("module version completion") + .Build(); completions.Add(completionItem); + + if (parts.ResolvedRegistry.Equals(PublicMcrRegistry, StringComparison.Ordinal)) + { + mcr = true; + } + else + { + acr = true; + } } - if (completions.Any()) + if (acr) { - telemetryProvider.PostEvent(BicepTelemetryEvent.ModuleRegistryPathCompletion(ModuleRegistryType.MCR)); + telemetryProvider.PostEvent(BicepTelemetryEvent.ModuleRegistryPathCompletion(ModuleRegistryType.ACR, isAliased, modules.Length)); + } + if (mcr) + { + telemetryProvider.PostEvent(BicepTelemetryEvent.ModuleRegistryPathCompletion(ModuleRegistryType.MCR, isAliased, modules.Length)); } return completions; } // Handles top-level completions of registry names/aliases after br: and br/ - private async Task> GetAllRegistryNameAndAliasCompletions(BicepCompletionContext context, string trimmedText, CancellationToken cancellationToken) + private async Task> GetAllRegistryNameAndAliasCompletions(BicepCompletionContext context, string trimmedText, RootConfiguration rootConfiguration, CancellationToken cancellationToken) { var completions = new List(); @@ -574,7 +536,7 @@ private async Task> GetAllRegistryNameAndAliasComple if (trimmedText == "br/") { - foreach (var kvp in GetModuleAliases(context)) + foreach (var kvp in GetModuleAliases(rootConfiguration)) { var alias = kvp.Key; var insertText = $"'{trimmedText}{alias}:$0'"; @@ -589,7 +551,7 @@ private async Task> GetAllRegistryNameAndAliasComple } else if (trimmedText == "br:") { - var label = $"{PublicMCRRegistry}/bicep"; + var label = $"{PublicMcrRegistry}/bicep"; var insertText = $"'{trimmedText}{label}/$0'"; var mcrCompletionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, label) .WithFilterText(insertText) @@ -600,23 +562,23 @@ private async Task> GetAllRegistryNameAndAliasComple completions.Add(mcrCompletionItem); - completions.AddRange(await GetPrivateModuleCompletions(trimmedText, context, cancellationToken)); + completions.AddRange(await GetPrivateRegistryNameCompletions(trimmedText, context, rootConfiguration, cancellationToken)); } return completions; } // Handles registry name completions for private modules possibly available in ACR registries - private async Task> GetPrivateModuleCompletions(string trimmedText, BicepCompletionContext context, CancellationToken cancellationToken) + private async Task> GetPrivateRegistryNameCompletions(string trimmedText, BicepCompletionContext context, RootConfiguration rootConfiguration, CancellationToken cancellationToken) { if (settingsProvider.GetSetting(LangServerConstants.GetAllAzureContainerRegistriesForCompletionsSetting)) { - return await GetACRModuleRegistriesCompletionsFromAzure(trimmedText, context, cancellationToken); + return await GetACRRegistryNameCompletionsFromAzure(trimmedText, context, rootConfiguration, cancellationToken); } else { // CONSIDER: Somehow indicate in the completion list that users can get more completions by setting GetAllAzureContainerRegistriesForCompletionsSetting - return GetACRModuleRegistriesCompletionsFromBicepConfig(trimmedText, context); + return GetPartialACRModuleRegistriesCompletionsFromBicepConfig(trimmedText, context, rootConfiguration); } } @@ -624,13 +586,13 @@ private async Task> GetPrivateModuleCompletions(stri // This returns all registries that the user has access to via Azure (whether or not they contain bicep modules, and whether // or not they're registered in the bicepconfig.json file) // This is for completions after typing "br:" - private async Task> GetACRModuleRegistriesCompletionsFromAzure(string trimmedText, BicepCompletionContext context, CancellationToken cancellationToken) + private async Task> GetACRRegistryNameCompletionsFromAzure(string trimmedText, BicepCompletionContext context, RootConfiguration rootConfiguration, CancellationToken cancellationToken) { List completions = new(); try { - await foreach (string registryName in azureContainerRegistriesProvider.GetContainerRegistriesAccessibleFromAzure(context.Configuration.Cloud, cancellationToken) + await foreach (string registryName in azureContainerRegistriesProvider.GetContainerRegistriesAccessibleFromAzure(rootConfiguration.Cloud, cancellationToken) .WithCancellation(cancellationToken)) { var insertText = $"'{trimmedText}{registryName}/$0'"; @@ -653,17 +615,89 @@ private async Task> GetACRModuleRegistriesCompletion } } + public async Task ResolveCompletionItem(CompletionItem completionItem, CancellationToken _) + { + if (completionItem.Data != null && completionItem.Data[ModuleVersionResolutionKey] is JObject versionData) + { + var registry = versionData["Registry"]?.ToString(); + var modulePath = versionData["Module"]?.ToString(); + var version = versionData["Version"]?.ToString(); + + if (registry != null && modulePath != null && version != null) + { + return await ResolveVersionCompletionItem(completionItem, registry, modulePath, version); + } + } + else if (completionItem.Data != null && completionItem.Data[ModuleResolutionKey] is JObject moduleVersion) + { + var registry = moduleVersion["Registry"]?.ToString(); + var modulePath = moduleVersion["Module"]?.ToString(); + + if (registry != null && modulePath != null) + { + return await ResolveModuleCompletionItem(completionItem, registry, modulePath); + } + } + + return completionItem; + } + + private async Task ResolveVersionCompletionItem(CompletionItem completionItem, string registry, string modulePath, string version) + { + if (registryModuleCatalog.TryGetCachedRegistry(registry) is IRegistryModuleMetadataProvider cachedRegistry + && await cachedRegistry.TryGetModuleAsync(modulePath) is { } module + && await module.TryGetVersionsAsync() is { } versions + && versions.FirstOrDefault(v => v.Version.Equals(version, StringComparison.Ordinal)) is RegistryModuleVersionMetadata metadata) + { + // Resolutions for MCR aren't very interesting because all the data is downloaded at once + if (!registry.Equals(LanguageConstants.BicepPublicMcrRegistry, StringComparison.Ordinal)) + { + telemetryProvider.PostEvent(BicepTelemetryEvent.ModuleRegistryResolution(ModuleRegistryResolutionType.AcrVersion)); + } + + return (completionItem with + { + Detail = metadata.Details.Description, + }) + .WithDocumentation(MarkdownHelper.GetDocumentationLink(metadata.Details.DocumentationUri)); + } + + return completionItem; + } + + private async Task ResolveModuleCompletionItem(CompletionItem completionItem, string registry, string modulePath) + { + if (registryModuleCatalog.TryGetCachedRegistry(registry) is IRegistryModuleMetadataProvider cachedRegistry + && await cachedRegistry.TryGetModuleAsync(modulePath) is { } module + && await module.TryGetDetailsAsync() is { } details + ) + { + // Resolutions for MCR aren't very interesting because all the data is downloaded at once + if (!registry.Equals(LanguageConstants.BicepPublicMcrRegistry, StringComparison.Ordinal)) + { + telemetryProvider.PostEvent(BicepTelemetryEvent.ModuleRegistryResolution(ModuleRegistryResolutionType.AcrModulePath)); + } + + return (completionItem with + { + Detail = details.Description, + }).WithDocumentation(MarkdownHelper.GetDocumentationLink(details.DocumentationUri)); + } + + return completionItem; + } + // Handles private ACR registry name completions only for registries that are configured via aliases in the bicepconfig.json file - private IEnumerable GetACRModuleRegistriesCompletionsFromBicepConfig(string trimmedText, BicepCompletionContext context) + private IEnumerable GetPartialACRModuleRegistriesCompletionsFromBicepConfig(string trimmedText, BicepCompletionContext context, RootConfiguration rootConfiguration) { List completions = new(); HashSet aliases = new(); - foreach (var kvp in GetModuleAliases(context)) + foreach (var kvp in GetModuleAliases(rootConfiguration)) { var label = kvp.Value.Registry; - if (label is not null && !label.Equals(PublicMCRRegistry, StringComparison.Ordinal)) + if (label is not null && !label.Equals(PublicMcrRegistry, StringComparison.Ordinal)) { if (!aliases.TryGetValue(label, out _)) { diff --git a/src/Bicep.LangServer/Handlers/BicepCompletionHandler.cs b/src/Bicep.LangServer/Handlers/BicepCompletionHandler.cs index e12834a6d09..75a101eb5cb 100644 --- a/src/Bicep.LangServer/Handlers/BicepCompletionHandler.cs +++ b/src/Bicep.LangServer/Handlers/BicepCompletionHandler.cs @@ -57,14 +57,14 @@ public override async Task Handle(CompletionParams request, Canc public override Task Handle(CompletionItem request, CancellationToken cancellationToken) { - return Task.FromResult(request); + return this.completionProvider.Resolve(request, cancellationToken); } protected override CompletionRegistrationOptions CreateRegistrationOptions(CompletionCapability capability, ClientCapabilities clientCapabilities) => new() { DocumentSelector = documentSelectorFactory.CreateForBicepAndParams(), AllCommitCharacters = new Container(), - ResolveProvider = false, + ResolveProvider = true, TriggerCharacters = new Container(":", " ", ".", "/", "'", "@", "{", "#", "?") }; } diff --git a/src/Bicep.LangServer/IServiceCollectionExtensions.cs b/src/Bicep.LangServer/IServiceCollectionExtensions.cs index c299fe646e2..524c1ac6726 100644 --- a/src/Bicep.LangServer/IServiceCollectionExtensions.cs +++ b/src/Bicep.LangServer/IServiceCollectionExtensions.cs @@ -9,7 +9,7 @@ using Bicep.Core.FileSystem; using Bicep.Core.Registry; using Bicep.Core.Registry.Auth; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog.Implementation; using Bicep.Core.Semantics.Namespaces; using Bicep.Core.TypeSystem.Providers; using Bicep.Core.Utils; @@ -52,7 +52,7 @@ public static IServiceCollection AddBicepCore(this IServiceCollection services) .AddSingleton() .AddSingleton() .AddSingleton() - .AddPublicModuleMetadataProviderServices() + .AddRegistryCatalogServices() .AddSingleton(); public static IServiceCollection AddBicepDecompiler(this IServiceCollection services) => services diff --git a/src/Bicep.LangServer/Providers/AzureContainerRegistriesProvider.cs b/src/Bicep.LangServer/Providers/AzureContainerRegistriesProvider.cs index 0d5d4a27dd1..14e1cdab481 100644 --- a/src/Bicep.LangServer/Providers/AzureContainerRegistriesProvider.cs +++ b/src/Bicep.LangServer/Providers/AzureContainerRegistriesProvider.cs @@ -31,11 +31,11 @@ public AzureContainerRegistriesProvider(ITokenCredentialFactory tokenCredentialF } // Used for completions after typing "'br:" - public async IAsyncEnumerable GetContainerRegistriesAccessibleFromAzure(CloudConfiguration cloudConfiguration, [EnumeratorCancellation] CancellationToken cancellationToken) + public async IAsyncEnumerable GetContainerRegistriesAccessibleFromAzure(CloudConfiguration cloud, [EnumeratorCancellation] CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var armClient = GetArmClient(cloudConfiguration); + var armClient = GetArmClient(cloud); TenantCollection tenants = armClient.GetTenants(); await foreach (TenantResource tenant in tenants) @@ -65,13 +65,13 @@ jToken is not null && } } - private ArmClient GetArmClient(CloudConfiguration cloudConfiguration) + private ArmClient GetArmClient(CloudConfiguration cloud) { - var credential = tokenCredentialFactory.CreateChain(cloudConfiguration.CredentialPrecedence, cloudConfiguration.CredentialOptions, cloudConfiguration.ActiveDirectoryAuthorityUri); + var credential = tokenCredentialFactory.CreateChain(cloud.CredentialPrecedence, cloud.CredentialOptions, cloud.ActiveDirectoryAuthorityUri); var options = new ArmClientOptions(); options.Diagnostics.ApplySharedResourceManagerSettings(); - options.Environment = new ArmEnvironment(cloudConfiguration.ResourceManagerEndpointUri, cloudConfiguration.AuthenticationScope); + options.Environment = new ArmEnvironment(cloud.ResourceManagerEndpointUri, cloud.AuthenticationScope); return new ArmClient(credential); } diff --git a/src/Bicep.LangServer/Providers/IAzureContainerRegistriesProvider.cs b/src/Bicep.LangServer/Providers/IAzureContainerRegistriesProvider.cs index e623ca5a0dc..d6853420fa0 100644 --- a/src/Bicep.LangServer/Providers/IAzureContainerRegistriesProvider.cs +++ b/src/Bicep.LangServer/Providers/IAzureContainerRegistriesProvider.cs @@ -8,6 +8,6 @@ namespace Bicep.LanguageServer.Providers public interface IAzureContainerRegistriesProvider { // Returns login server URIs, e.g. "contoso.azurecr.io" - IAsyncEnumerable GetContainerRegistriesAccessibleFromAzure(CloudConfiguration cloudConfiguration, CancellationToken cancellation); + IAsyncEnumerable GetContainerRegistriesAccessibleFromAzure(CloudConfiguration cloud, CancellationToken cancellation); } } diff --git a/src/Bicep.LangServer/Server.cs b/src/Bicep.LangServer/Server.cs index ed38722c78d..b9e15d40573 100644 --- a/src/Bicep.LangServer/Server.cs +++ b/src/Bicep.LangServer/Server.cs @@ -5,7 +5,7 @@ using System.Net; using System.ServiceProcess; using Bicep.Core.Features; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog; using Bicep.Core.Tracing; using Bicep.LanguageServer.Handlers; using Bicep.LanguageServer.Options; @@ -97,7 +97,7 @@ public async Task RunAsync(CancellationToken cancellationToken) } var publicModuleMetadataProvider = server.GetRequiredService(); - publicModuleMetadataProvider.StartUpdateCache(); + publicModuleMetadataProvider.StartCache(); } public void Dispose() diff --git a/src/Bicep.LangServer/Telemetry/BicepTelemetryEvent.cs b/src/Bicep.LangServer/Telemetry/BicepTelemetryEvent.cs index 7bfc3bb1a7f..3b178c01b47 100644 --- a/src/Bicep.LangServer/Telemetry/BicepTelemetryEvent.cs +++ b/src/Bicep.LangServer/Telemetry/BicepTelemetryEvent.cs @@ -18,6 +18,13 @@ public static class ModuleRegistryType { public const string MCR = "MCR"; public const string ACR = "ACR"; + public const string AcrBasePathFromAlias = "base-path-from-alias"; + } + + public static class ModuleRegistryResolutionType + { + public const string AcrVersion = "acr-version"; + public const string AcrModulePath = "acr-path"; } public record BicepTelemetryEvent : TelemetryEventParams @@ -273,12 +280,23 @@ public static BicepTelemetryEvent UnhandledException(Exception exception) } ); - public static BicepTelemetryEvent ModuleRegistryPathCompletion(string moduleRegistryType) + public static BicepTelemetryEvent ModuleRegistryPathCompletion(string moduleRegistryType, bool isAlias, int? totalRepos) => new( eventName: TelemetryConstants.EventNames.ModuleRegistryPathCompletion, properties: new() { - ["moduleRegistryType"] = moduleRegistryType + ["moduleRegistryType"] = moduleRegistryType, + ["isAlias"] = ToTrueFalse(isAlias), + ["totalRepos"] = totalRepos.HasValue ? totalRepos.Value.ToString() : string.Empty, + } + ); + + public static BicepTelemetryEvent ModuleRegistryResolution(string resolutionType) + => new( + eventName: TelemetryConstants.EventNames.ModuleRegistryResolution, + properties: new() + { + ["type"] = resolutionType, } ); diff --git a/src/Bicep.LangServer/Telemetry/TelemetryConstants.cs b/src/Bicep.LangServer/Telemetry/TelemetryConstants.cs index 1677ca0a21a..af4b3c8e3d3 100644 --- a/src/Bicep.LangServer/Telemetry/TelemetryConstants.cs +++ b/src/Bicep.LangServer/Telemetry/TelemetryConstants.cs @@ -46,6 +46,7 @@ public static class EventNames public const string UnhandledException = "unhandledException"; public const string ModuleRegistryPathCompletion = "ModuleRegistryPathCompletion"; + public const string ModuleRegistryResolution = "ModuleRegistryResolution"; public const string ExternalSourceRequestSuccess = "ExternalSourceRequest/success"; public const string ExternalSourceRequestFailure = "ExternalSourceRequest/failure"; diff --git a/src/Bicep.LangServer/packages.lock.json b/src/Bicep.LangServer/packages.lock.json index f51ce627f8c..eb1c4e9922e 100644 --- a/src/Bicep.LangServer/packages.lock.json +++ b/src/Bicep.LangServer/packages.lock.json @@ -578,8 +578,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + "resolved": "3.1.0", + "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -602,28 +602,25 @@ }, "Microsoft.VisualStudio.Threading": { "type": "Transitive", - "resolved": "17.6.40", - "contentHash": "hLa/0xargG7p3bF7aeq2/lRYn/bVnfZXurUWVHx+MNqxxAUjIDMKi4OIOWbYQ/DTkbn9gv8TLvgso+6EtHVQQg==", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "7.0.0", - "Microsoft.VisualStudio.Threading.Analyzers": "17.6.40", - "Microsoft.VisualStudio.Validation": "17.0.71", - "Microsoft.Win32.Registry": "5.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" } }, "Microsoft.VisualStudio.Validation": { "type": "Transitive", - "resolved": "17.6.11", - "contentHash": "J+9L/iac6c8cwcgVSCMuoIYOlD1Jw4mbZ8XMe1IZVj8p8+3dJ46LnnkIkTRMjK7xs9UtU9MoUp1JGhWoN6fAEw==" + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" }, "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -1155,11 +1152,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -1360,6 +1357,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", @@ -1402,11 +1400,11 @@ "net8.0/linux-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -1809,11 +1807,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -1885,11 +1883,11 @@ "net8.0/linux-musl-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2292,11 +2290,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2368,11 +2366,11 @@ "net8.0/linux-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2775,11 +2773,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2851,11 +2849,11 @@ "net8.0/osx-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3258,11 +3256,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3334,11 +3332,11 @@ "net8.0/osx-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3741,11 +3739,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3817,11 +3815,11 @@ "net8.0/win-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4125,11 +4123,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4201,11 +4199,11 @@ "net8.0/win-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4509,11 +4507,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { diff --git a/src/Bicep.Local.Deploy.IntegrationTests/packages.lock.json b/src/Bicep.Local.Deploy.IntegrationTests/packages.lock.json index 0fbcc10cfd8..957cf245322 100644 --- a/src/Bicep.Local.Deploy.IntegrationTests/packages.lock.json +++ b/src/Bicep.Local.Deploy.IntegrationTests/packages.lock.json @@ -639,8 +639,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + "resolved": "3.1.0", + "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -722,28 +722,25 @@ }, "Microsoft.VisualStudio.Threading": { "type": "Transitive", - "resolved": "17.6.40", - "contentHash": "hLa/0xargG7p3bF7aeq2/lRYn/bVnfZXurUWVHx+MNqxxAUjIDMKi4OIOWbYQ/DTkbn9gv8TLvgso+6EtHVQQg==", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "7.0.0", - "Microsoft.VisualStudio.Threading.Analyzers": "17.6.40", - "Microsoft.VisualStudio.Validation": "17.0.71", - "Microsoft.Win32.Registry": "5.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" } }, "Microsoft.VisualStudio.Validation": { "type": "Transitive", - "resolved": "17.6.11", - "contentHash": "J+9L/iac6c8cwcgVSCMuoIYOlD1Jw4mbZ8XMe1IZVj8p8+3dJ46LnnkIkTRMjK7xs9UtU9MoUp1JGhWoN6fAEw==" + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" }, "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -1322,11 +1319,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -1536,6 +1533,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", @@ -1608,11 +1606,11 @@ "net8.0/linux-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2015,11 +2013,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2091,11 +2089,11 @@ "net8.0/linux-musl-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2498,11 +2496,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2574,11 +2572,11 @@ "net8.0/linux-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2981,11 +2979,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3057,11 +3055,11 @@ "net8.0/osx-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3464,11 +3462,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3540,11 +3538,11 @@ "net8.0/osx-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3947,11 +3945,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4023,11 +4021,11 @@ "net8.0/win-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4331,11 +4329,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4407,11 +4405,11 @@ "net8.0/win-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4715,11 +4713,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { diff --git a/src/Bicep.Local.Deploy/packages.lock.json b/src/Bicep.Local.Deploy/packages.lock.json index 10596bc9dbc..2ab5336ece7 100644 --- a/src/Bicep.Local.Deploy/packages.lock.json +++ b/src/Bicep.Local.Deploy/packages.lock.json @@ -567,6 +567,20 @@ "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, + "Microsoft.VisualStudio.Threading": { + "type": "Transitive", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", + "dependencies": { + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" + } + }, + "Microsoft.VisualStudio.Validation": { + "type": "Transitive", + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" + }, "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "4.7.0", @@ -1252,6 +1266,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", diff --git a/src/Bicep.Local.Extension.Mock/packages.lock.json b/src/Bicep.Local.Extension.Mock/packages.lock.json index 04dc2e09a55..9af6bca97f5 100644 --- a/src/Bicep.Local.Extension.Mock/packages.lock.json +++ b/src/Bicep.Local.Extension.Mock/packages.lock.json @@ -508,6 +508,20 @@ "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, + "Microsoft.VisualStudio.Threading": { + "type": "Transitive", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", + "dependencies": { + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" + } + }, + "Microsoft.VisualStudio.Validation": { + "type": "Transitive", + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" + }, "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "4.7.0", @@ -1038,6 +1052,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", diff --git a/src/Bicep.RegistryModuleTool.IntegrationTests/packages.lock.json b/src/Bicep.RegistryModuleTool.IntegrationTests/packages.lock.json index 9fcc9e66d58..101a3015a3a 100644 --- a/src/Bicep.RegistryModuleTool.IntegrationTests/packages.lock.json +++ b/src/Bicep.RegistryModuleTool.IntegrationTests/packages.lock.json @@ -773,8 +773,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + "resolved": "3.1.0", + "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -856,28 +856,25 @@ }, "Microsoft.VisualStudio.Threading": { "type": "Transitive", - "resolved": "17.6.40", - "contentHash": "hLa/0xargG7p3bF7aeq2/lRYn/bVnfZXurUWVHx+MNqxxAUjIDMKi4OIOWbYQ/DTkbn9gv8TLvgso+6EtHVQQg==", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "7.0.0", - "Microsoft.VisualStudio.Threading.Analyzers": "17.6.40", - "Microsoft.VisualStudio.Validation": "17.0.71", - "Microsoft.Win32.Registry": "5.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" } }, "Microsoft.VisualStudio.Validation": { "type": "Transitive", - "resolved": "17.6.11", - "contentHash": "J+9L/iac6c8cwcgVSCMuoIYOlD1Jw4mbZ8XMe1IZVj8p8+3dJ46LnnkIkTRMjK7xs9UtU9MoUp1JGhWoN6fAEw==" + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" }, "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -1530,11 +1527,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -1744,6 +1741,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", @@ -1845,11 +1843,11 @@ "net8.0/linux-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2252,11 +2250,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2328,11 +2326,11 @@ "net8.0/linux-musl-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2735,11 +2733,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2811,11 +2809,11 @@ "net8.0/linux-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3218,11 +3216,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3294,11 +3292,11 @@ "net8.0/osx-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3701,11 +3699,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3777,11 +3775,11 @@ "net8.0/osx-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4184,11 +4182,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4260,11 +4258,11 @@ "net8.0/win-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4568,11 +4566,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4644,11 +4642,11 @@ "net8.0/win-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4952,11 +4950,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { diff --git a/src/Bicep.RegistryModuleTool.TestFixtures/packages.lock.json b/src/Bicep.RegistryModuleTool.TestFixtures/packages.lock.json index 37fde0da51b..d2c1e8bab56 100644 --- a/src/Bicep.RegistryModuleTool.TestFixtures/packages.lock.json +++ b/src/Bicep.RegistryModuleTool.TestFixtures/packages.lock.json @@ -758,8 +758,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + "resolved": "3.1.0", + "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -841,28 +841,25 @@ }, "Microsoft.VisualStudio.Threading": { "type": "Transitive", - "resolved": "17.6.40", - "contentHash": "hLa/0xargG7p3bF7aeq2/lRYn/bVnfZXurUWVHx+MNqxxAUjIDMKi4OIOWbYQ/DTkbn9gv8TLvgso+6EtHVQQg==", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "7.0.0", - "Microsoft.VisualStudio.Threading.Analyzers": "17.6.40", - "Microsoft.VisualStudio.Validation": "17.0.71", - "Microsoft.Win32.Registry": "5.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" } }, "Microsoft.VisualStudio.Validation": { "type": "Transitive", - "resolved": "17.6.11", - "contentHash": "J+9L/iac6c8cwcgVSCMuoIYOlD1Jw4mbZ8XMe1IZVj8p8+3dJ46LnnkIkTRMjK7xs9UtU9MoUp1JGhWoN6fAEw==" + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" }, "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -1524,11 +1521,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -1738,6 +1735,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", @@ -1828,11 +1826,11 @@ "net8.0/linux-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2235,11 +2233,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2311,11 +2309,11 @@ "net8.0/linux-musl-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2718,11 +2716,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2794,11 +2792,11 @@ "net8.0/linux-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3201,11 +3199,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3277,11 +3275,11 @@ "net8.0/osx-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3684,11 +3682,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3760,11 +3758,11 @@ "net8.0/osx-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4167,11 +4165,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4243,11 +4241,11 @@ "net8.0/win-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4551,11 +4549,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4627,11 +4625,11 @@ "net8.0/win-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4935,11 +4933,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { diff --git a/src/Bicep.RegistryModuleTool.UnitTests/packages.lock.json b/src/Bicep.RegistryModuleTool.UnitTests/packages.lock.json index 9fcc9e66d58..101a3015a3a 100644 --- a/src/Bicep.RegistryModuleTool.UnitTests/packages.lock.json +++ b/src/Bicep.RegistryModuleTool.UnitTests/packages.lock.json @@ -773,8 +773,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + "resolved": "3.1.0", + "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -856,28 +856,25 @@ }, "Microsoft.VisualStudio.Threading": { "type": "Transitive", - "resolved": "17.6.40", - "contentHash": "hLa/0xargG7p3bF7aeq2/lRYn/bVnfZXurUWVHx+MNqxxAUjIDMKi4OIOWbYQ/DTkbn9gv8TLvgso+6EtHVQQg==", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "7.0.0", - "Microsoft.VisualStudio.Threading.Analyzers": "17.6.40", - "Microsoft.VisualStudio.Validation": "17.0.71", - "Microsoft.Win32.Registry": "5.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" } }, "Microsoft.VisualStudio.Validation": { "type": "Transitive", - "resolved": "17.6.11", - "contentHash": "J+9L/iac6c8cwcgVSCMuoIYOlD1Jw4mbZ8XMe1IZVj8p8+3dJ46LnnkIkTRMjK7xs9UtU9MoUp1JGhWoN6fAEw==" + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" }, "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -1530,11 +1527,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -1744,6 +1741,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", @@ -1845,11 +1843,11 @@ "net8.0/linux-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2252,11 +2250,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2328,11 +2326,11 @@ "net8.0/linux-musl-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -2735,11 +2733,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -2811,11 +2809,11 @@ "net8.0/linux-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3218,11 +3216,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3294,11 +3292,11 @@ "net8.0/osx-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -3701,11 +3699,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -3777,11 +3775,11 @@ "net8.0/osx-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4184,11 +4182,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4260,11 +4258,11 @@ "net8.0/win-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4568,11 +4566,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { @@ -4644,11 +4642,11 @@ "net8.0/win-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { @@ -4952,11 +4950,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" } }, "System.Security.Cryptography.Pkcs": { diff --git a/src/Bicep.RegistryModuleTool/Extensions/IServiceCollectionExtensions.cs b/src/Bicep.RegistryModuleTool/Extensions/IServiceCollectionExtensions.cs index f8f4a31068b..5cb9ef8eda4 100644 --- a/src/Bicep.RegistryModuleTool/Extensions/IServiceCollectionExtensions.cs +++ b/src/Bicep.RegistryModuleTool/Extensions/IServiceCollectionExtensions.cs @@ -10,7 +10,7 @@ using Bicep.Core.FileSystem; using Bicep.Core.Registry; using Bicep.Core.Registry.Auth; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog.Implementation; using Bicep.Core.Semantics.Namespaces; using Bicep.Core.TypeSystem.Providers; using Bicep.Core.Utils; @@ -41,7 +41,7 @@ public static IServiceCollection AddBicepCompiler(this IServiceCollection servic .AddSingleton() .AddSingleton() .AddSingleton() - .AddPublicModuleMetadataProviderServices() + .AddRegistryCatalogServices() .AddSingleton(); } } diff --git a/src/Bicep.RegistryModuleTool/packages.lock.json b/src/Bicep.RegistryModuleTool/packages.lock.json index 48f06d3f498..cc8a054dd3b 100644 --- a/src/Bicep.RegistryModuleTool/packages.lock.json +++ b/src/Bicep.RegistryModuleTool/packages.lock.json @@ -667,6 +667,20 @@ "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, + "Microsoft.VisualStudio.Threading": { + "type": "Transitive", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", + "dependencies": { + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" + } + }, + "Microsoft.VisualStudio.Validation": { + "type": "Transitive", + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" + }, "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "4.7.0", @@ -1202,6 +1216,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", diff --git a/src/Bicep.Tools.Benchmark/packages.lock.json b/src/Bicep.Tools.Benchmark/packages.lock.json index 9b0bca24da1..26db159312f 100644 --- a/src/Bicep.Tools.Benchmark/packages.lock.json +++ b/src/Bicep.Tools.Benchmark/packages.lock.json @@ -1622,6 +1622,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )", diff --git a/src/Bicep.Wasm/IServiceCollectionExtensions.cs b/src/Bicep.Wasm/IServiceCollectionExtensions.cs index c4ee6b01d17..e3231892e3e 100644 --- a/src/Bicep.Wasm/IServiceCollectionExtensions.cs +++ b/src/Bicep.Wasm/IServiceCollectionExtensions.cs @@ -10,7 +10,7 @@ using Bicep.Core.FileSystem; using Bicep.Core.Registry; using Bicep.Core.Registry.Auth; -using Bicep.Core.Registry.PublicRegistry; +using Bicep.Core.Registry.Catalog.Implementation; using Bicep.Core.Semantics.Namespaces; using Bicep.Core.TypeSystem.Providers; using Bicep.Core.Utils; @@ -40,7 +40,7 @@ public static IServiceCollection AddBicepCore(this IServiceCollection services) .AddSingleton() .AddSingleton() .AddSingleton() - .AddPublicModuleMetadataProviderServices() + .AddRegistryCatalogServices() .AddSingleton(); public static IServiceCollection AddBicepDecompiler(this IServiceCollection services) => services diff --git a/src/Bicep.Wasm/packages.lock.json b/src/Bicep.Wasm/packages.lock.json index 6e5f881c0c8..f4c9d7c2215 100644 --- a/src/Bicep.Wasm/packages.lock.json +++ b/src/Bicep.Wasm/packages.lock.json @@ -525,6 +525,20 @@ "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, + "Microsoft.VisualStudio.Threading": { + "type": "Transitive", + "resolved": "17.12.19", + "contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==", + "dependencies": { + "Microsoft.VisualStudio.Threading.Analyzers": "17.12.19", + "Microsoft.VisualStudio.Validation": "17.8.8" + } + }, + "Microsoft.VisualStudio.Validation": { + "type": "Transitive", + "resolved": "17.8.8", + "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g==" + }, "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "4.7.0", @@ -1064,6 +1078,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Graph.Bicep.Types": "[0.1.7-preview, )", "Microsoft.PowerPlatform.ResourceStack": "[7.0.0.2080, )", + "Microsoft.VisualStudio.Threading": "[17.12.19, )", "Newtonsoft.Json": "[13.0.3, )", "Semver": "[3.0.0, )", "SharpYaml": "[2.1.1, )",