diff --git a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseRecentModuleVersionsRuleTests.cs b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseRecentModuleVersionsRuleTests.cs index 1579fa9042c..20bcc261f47 100644 --- a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseRecentModuleVersionsRuleTests.cs +++ b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseRecentModuleVersionsRuleTests.cs @@ -46,20 +46,16 @@ private static CompilationResult Compile( var publicModuleMetadataProvider = StrictMock.Of(); publicModuleMetadataProvider.Setup(x => x.GetCachedModules()) .Returns([.. availableModules - .Select(m => new DefaultRegistryModuleMetadata( + .Select(m => new RegistryModuleMetadata( "mcr.microsoft.com", m, - new RegistryMetadataDetails(null, null), - [.. availableVersions - .Select(v => new RegistryModuleVersionMetadata(v, new("det", "doc.html")))]))]); - //asdfg - //publicModuleMetadataProvider.Setup(x => x.GetCachedModuleVersions(It.IsAny())) - // .Returns((string module) => - // { - // return availableModules.Contains(module) ? - // [.. availableVersions.Select(v => new RegistryModuleVersionMetadata(v, null, null))] : - // []; - // }); + new RegistryModuleMetadata.ComputedData( + new RegistryMetadataDetails(null, null), + [.. availableVersions + .Select(v => new RegistryModuleVersionMetadata(v, new("det", "doc.html")))] + ))) + ]); + publicModuleMetadataProvider.Setup(x => x.IsCached) .Returns(availableModules.Length > 0); publicModuleMetadataProvider.Setup(x => x.DownloadError) diff --git a/src/Bicep.Core.UnitTests/Mock/Registry/RegistryCatalogMocks.cs b/src/Bicep.Core.UnitTests/Mock/Registry/RegistryCatalogMocks.cs index 501db4d765b..50041f6af45 100644 --- a/src/Bicep.Core.UnitTests/Mock/Registry/RegistryCatalogMocks.cs +++ b/src/Bicep.Core.UnitTests/Mock/Registry/RegistryCatalogMocks.cs @@ -38,16 +38,20 @@ public static Mock MockPublicMetadataProvider( publicProvider.Setup(x => x.Registry).Returns(PublicRegistry); publicProvider.Setup(x => x.TryGetModulesAsync()) - .ReturnsAsync([.. modules - .Select(m => new DefaultRegistryModuleMetadata( + .ReturnsAsync( + [.. modules + .Select(m => new RegistryModuleMetadata( PublicRegistry, m.moduleName, - getDetailsAsyncFunc: () => Task.FromResult(new RegistryMetadataDetails(m.description, m.documentationUri)), - getVersionsAsyncFunc: () => Task.FromResult>( + new RegistryModuleMetadata.ComputedData( + new RegistryMetadataDetails(m.description, m.documentationUri), [.. modules.Single(m2 => m2.moduleName.EqualsOrdinally(m.moduleName)) .versions - .Select(v => new RegistryModuleVersionMetadata(v.version,new(v.description,v.documentUri)))]) - ))]); + .Select(v => new RegistryModuleVersionMetadata(v.version,new(v.description,v.documentUri))) + ] + ) + ))] + ); return publicProvider; } @@ -63,15 +67,19 @@ public static Mock MockPrivateMetadataProvider( privateProvider.Setup(x => x.TryGetModulesAsync()) .ReturnsAsync([.. - modules.Select(m => new DefaultRegistryModuleMetadata( + modules.Select(m => new RegistryModuleMetadata( registry, m.moduleName, - getDetailsAsyncFunc: async () => await DelayedValue(new RegistryMetadataDetails( m.description, m.documentationUri)), - getVersionsAsyncFunc: async () => await DelayedValue>( - [.. modules.Single(m => m.moduleName.EqualsOrdinally(m.moduleName)) - .versions - .Select(v => new RegistryModuleVersionMetadata(v.version, new( v.description, v.documentUri)))]) - ))]); + getDataAsyncFunc: async () => + new RegistryModuleMetadata.ComputedData( + await DelayedValue(new RegistryMetadataDetails( m.description, m.documentationUri)), + [.. modules.Single(m => m.moduleName.EqualsOrdinally(m.moduleName)) + .versions + .Select(v => new RegistryModuleVersionMetadata(v.version, new(v.description, v.documentUri)) + )] + ) + )) + ]); return privateProvider; } diff --git a/src/Bicep.Core.UnitTests/Registry/Indexing/BaseModuleMetadataProviderTests.cs b/src/Bicep.Core.UnitTests/Registry/Catalog/BaseModuleMetadataProviderTests.cs similarity index 100% rename from src/Bicep.Core.UnitTests/Registry/Indexing/BaseModuleMetadataProviderTests.cs rename to src/Bicep.Core.UnitTests/Registry/Catalog/BaseModuleMetadataProviderTests.cs diff --git a/src/Bicep.Core.UnitTests/Registry/Indexing/PrivateAcrModuleMetadataProviderTests.cs b/src/Bicep.Core.UnitTests/Registry/Catalog/PrivateAcrModuleMetadataProviderTests.cs similarity index 77% rename from src/Bicep.Core.UnitTests/Registry/Indexing/PrivateAcrModuleMetadataProviderTests.cs rename to src/Bicep.Core.UnitTests/Registry/Catalog/PrivateAcrModuleMetadataProviderTests.cs index f15cd0ce194..ca578a72437 100644 --- a/src/Bicep.Core.UnitTests/Registry/Indexing/PrivateAcrModuleMetadataProviderTests.cs +++ b/src/Bicep.Core.UnitTests/Registry/Catalog/PrivateAcrModuleMetadataProviderTests.cs @@ -302,7 +302,7 @@ public async Task TryGetModulesAsync_ShouldCacheResult() } [TestMethod] - public async Task GetDetails() + public async Task GetDetails_ShouldBeCached() { var containerClient = new FakeContainerRegistryClient(); var clientFactory = await RegistryHelper.CreateMockRegistryClientWithPublishedModulesAsync( @@ -318,13 +318,92 @@ public async Task GetDetails() 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); } } } diff --git a/src/Bicep.Core.UnitTests/Registry/Indexing/PublicModuleMetadataProviderTests.cs b/src/Bicep.Core.UnitTests/Registry/Catalog/PublicModuleMetadataProviderTests.cs similarity index 97% rename from src/Bicep.Core.UnitTests/Registry/Indexing/PublicModuleMetadataProviderTests.cs rename to src/Bicep.Core.UnitTests/Registry/Catalog/PublicModuleMetadataProviderTests.cs index 088eade85a6..4c04cdbaecd 100644 --- a/src/Bicep.Core.UnitTests/Registry/Indexing/PublicModuleMetadataProviderTests.cs +++ b/src/Bicep.Core.UnitTests/Registry/Catalog/PublicModuleMetadataProviderTests.cs @@ -1096,64 +1096,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 = 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 = 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] diff --git a/src/Bicep.Core.UnitTests/Registry/Indexing/RegistryCatalogTests.cs b/src/Bicep.Core.UnitTests/Registry/Catalog/RegistryCatalogTests.cs similarity index 99% rename from src/Bicep.Core.UnitTests/Registry/Indexing/RegistryCatalogTests.cs rename to src/Bicep.Core.UnitTests/Registry/Catalog/RegistryCatalogTests.cs index d45101aa6dc..0821bc59b0f 100644 --- a/src/Bicep.Core.UnitTests/Registry/Indexing/RegistryCatalogTests.cs +++ b/src/Bicep.Core.UnitTests/Registry/Catalog/RegistryCatalogTests.cs @@ -23,7 +23,7 @@ namespace Bicep.Core.UnitTests.Registry.Catalog { [TestClass] - public class RegistryCatalogTests //asdfg2 + public class RegistryCatalogTests { [TestMethod] public void GetRegistry_ShouldReturnSameObjectEachTime() diff --git a/src/Bicep.Core.UnitTests/Registry/FakeContainerRegistryClient.cs b/src/Bicep.Core.UnitTests/Registry/FakeContainerRegistryClient.cs index 105516107dd..9ab331e56e9 100644 --- a/src/Bicep.Core.UnitTests/Registry/FakeContainerRegistryClient.cs +++ b/src/Bicep.Core.UnitTests/Registry/FakeContainerRegistryClient.cs @@ -28,6 +28,7 @@ public FakeContainerRegistryClient() : base() } public int CallsToGetRepositoryNamesAsync { get; private set; } + public int CallsToGetAllManifestPropertiesAsync { get; private set; } public SortedList FakeRepositories { get; } = new(); @@ -45,6 +46,8 @@ public override ContainerRepository GetRepository(string repositoryName) 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, diff --git a/src/Bicep.Core/Registry/Catalog/BaseModuleMetadataProvider.cs b/src/Bicep.Core/Registry/Catalog/BaseModuleMetadataProvider.cs index 35b827bc8d9..aaed2d952b4 100644 --- a/src/Bicep.Core/Registry/Catalog/BaseModuleMetadataProvider.cs +++ b/src/Bicep.Core/Registry/Catalog/BaseModuleMetadataProvider.cs @@ -68,7 +68,7 @@ public async Task TryUpdateCacheAsync()//asdfg implement/test threading } else { - return false;//asdfg testpoint + return false; } } @@ -79,7 +79,7 @@ public async Task TryUpdateCacheAsync()//asdfg implement/test threading && x.ModuleName.Equals(modulePath, StringComparison.Ordinal)); if (found != null && throwIfNotFound) { - throw new InvalidOperationException($"Module '{modulePath}' not found in cache.");//asdfg testpoint + throw new InvalidOperationException($"Module '{modulePath}' not found in cache."); } return found; diff --git a/src/Bicep.Core/Registry/Catalog/DefaultRegistryModuleMetadata.cs b/src/Bicep.Core/Registry/Catalog/DefaultRegistryModuleMetadata.cs deleted file mode 100644 index ea6dc7104df..00000000000 --- a/src/Bicep.Core/Registry/Catalog/DefaultRegistryModuleMetadata.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Immutable; - -namespace Bicep.Core.Registry.Catalog; //asdfg split into PublicRegistry/PrivateRegistry - -public class DefaultRegistryModuleMetadata : IRegistryModuleMetadata -{ - public string Registry { get; init; } - public string ModuleName { get; init; } - - private readonly MightBeLazyAsync details; - private readonly MightBeLazyAsync> versions; - - //asdfg CachableModuleMetadata metadata, - //private AsyncLazy? lazyDetails; - // asdfg private Func> getDetailsFunc; - //private Func>>? getVersionsFunc; - //private AsyncLazy>? lazyVersions; - - public DefaultRegistryModuleMetadata( - string registry, - string moduleName, - RegistryMetadataDetails details, - ImmutableArray versions) - { - //asdfg this.Metadata = metadata; - this.Registry = registry; - this.ModuleName = moduleName; - - this.details = new MightBeLazyAsync(details); - this.versions = new(versions); - } - - public DefaultRegistryModuleMetadata( - string registry, - string moduleName, - Func> getDetailsAsyncFunc, - Func>> getVersionsAsyncFunc) - { - //asdfg this.Metadata = metadata; - this.Registry = registry; - this.ModuleName = moduleName; - - this.details = new(getDetailsAsyncFunc); - //asdfg this.getDetailsFunc = getDetailsFunc; - //this.getVersionsFunc = getVersionsFunc; - this.versions = new(getVersionsAsyncFunc); - } - - ////asdfg this.Metadata = metadata; - //this.Registry = registry; - //this.ModuleName = moduleName; - //this.GetModuleMetadataFunc = getDetailsFunc; - //this.GetVersionsTask = versionsMetadata is null ? null : - // Task.FromResult(versionsMetadata.Value.Select(v => - // new CachableVersionMetadata(v.Version, v)).ToImmutableArray()); - - public async Task TryGetDetailsAsync() - { - try - { - return await details.GetValueAsync(); - } - catch - { - return new RegistryMetadataDetails(null, null); - } - } - - // public Task TryGetDetails() //asdfg GetDetailsOrEmpty? - // { - // var detailsTask = getDetailsFunc(); - // if (detailsTask.IsCompletedSuccessfully) - // { - //#pragma warning disable VSTHRD103 // Call async methods when in an async method - // return Task.FromResult(detailsTask.Result); - //#pragma warning restore VSTHRD103 // Call async methods when in an async method - // } - // else - // { - // return Task.FromResult(new RegistryMetadataDetails(null, null)); - // } - // } - - public async Task> TryGetVersionsAsync() - { - try - { - return await versions.GetValueAsync(); - } - catch - { - return []; - } - } - - public ImmutableArray GetCachedVersions() - { - return versions.TryGetValue(); - } -} diff --git a/src/Bicep.Core/Registry/Catalog/MightBeLazyAsync.cs b/src/Bicep.Core/Registry/Catalog/MightBeLazyAsync.cs index 5cbf46195c9..dda4ea8f76d 100644 --- a/src/Bicep.Core/Registry/Catalog/MightBeLazyAsync.cs +++ b/src/Bicep.Core/Registry/Catalog/MightBeLazyAsync.cs @@ -1,11 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Diagnostics.CodeAnalysis; using Microsoft.VisualStudio.Threading; namespace Bicep.Core.Registry.Catalog; -public class MightBeLazyAsync +public class MightBeLazyAsync where T : class { private readonly AsyncLazy? lazy; private readonly T? value; @@ -26,22 +27,25 @@ public MightBeLazyAsync(Func> initializer) //asdfg public bool HasValue => lazy is null || lazy.IsValueFactoryCompleted; - public T? TryGetValue() + public bool TryGetValue([NotNullWhen(true)] out T? value) { if (IsLazy) { if (lazy!.IsValueFactoryCompleted) { - return lazy.GetValue(); //asdfg can this throw?//asdfg testpoint + value = lazy.GetValue(); //asdfg can this throw?//asdfg testpoint + return true; } else { - return default;//asdfg testpoint + value = default; + return false; } } else { - return value; + value = this.value!; + return true; } } diff --git a/src/Bicep.Core/Registry/Catalog/PrivateAcrModuleMetadataProvider.cs b/src/Bicep.Core/Registry/Catalog/PrivateAcrModuleMetadataProvider.cs index aa39d847581..5aef9a72c24 100644 --- a/src/Bicep.Core/Registry/Catalog/PrivateAcrModuleMetadataProvider.cs +++ b/src/Bicep.Core/Registry/Catalog/PrivateAcrModuleMetadataProvider.cs @@ -12,8 +12,9 @@ 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; //asdfg should this be in core? +namespace Bicep.Core.Registry.Catalog; /// /// Provider to get modules metadata from a private ACR registry @@ -74,8 +75,6 @@ private async Task TryGetLiveModuleVersionMetadat } } - //asdfg bug: br:sawbicep.azurecr.io/de| => br:sawbicep.azurecr.io/dedemo - protected override async Task> GetLiveDataCoreAsync() { Trace.WriteLine($"Retrieving catalog for registry {Registry}..."); @@ -86,23 +85,27 @@ protected override async Task> GetLiveDa Trace.WriteLine($"Found {catalog.Length} repositories"); var modules = catalog - .Reverse() // Reverse to get the latest modules first + .Reverse() // Reverse to inspect the latest modules first .Select(m => { - var getVersionsAsyncFunc = () => this.GetLiveModuleVersionsAsync(m); - var getDetailsAsyncFunc = () => this.GetLiveModuleDetails(getVersionsAsyncFunc); - return new DefaultRegistryModuleMetadata(Registry, m, getDetailsAsyncFunc, getVersionsAsyncFunc); + return new RegistryModuleMetadata( + Registry, + m, + async () => + { + var versions = await GetLiveModuleVersionsAsync(m); + var moduleDetails = GetModuleDetails(versions); + return new ComputedData(moduleDetails, versions); + }); } ).ToImmutableArray(); return [.. modules.Cast()]; } - private async Task GetLiveModuleDetails( - Func>> getVersionsAsyncFunc) + private RegistryMetadataDetails GetModuleDetails(ImmutableArray versions) { // OCI modules don't have a description or documentation URI, we just use the most recent version's metadata - var versions = await getVersionsAsyncFunc(); if (versions.LastOrDefault() is { } lastVersion) { return lastVersion.Details; diff --git a/src/Bicep.Core/Registry/Catalog/PrivateAcrModuleMetadataProviderFactory.cs b/src/Bicep.Core/Registry/Catalog/PrivateAcrModuleMetadataProviderFactory.cs index 755ddd1e255..8257cceb213 100644 --- a/src/Bicep.Core/Registry/Catalog/PrivateAcrModuleMetadataProviderFactory.cs +++ b/src/Bicep.Core/Registry/Catalog/PrivateAcrModuleMetadataProviderFactory.cs @@ -15,6 +15,6 @@ public class PrivateAcrModuleMetadataProviderFactory : IPrivateAcrModuleMetadata { public IRegistryModuleMetadataProvider Create(CloudConfiguration cloud, string registry, IContainerRegistryClientFactory containerRegistryClientFactory) { - return new PrivateAcrModuleMetadataProvider(cloud, registry, containerRegistryClientFactory);//asdfg testpoint + return new PrivateAcrModuleMetadataProvider(cloud, registry, containerRegistryClientFactory); } } diff --git a/src/Bicep.Core/Registry/Catalog/PublicModuleMetadataProvider.cs b/src/Bicep.Core/Registry/Catalog/PublicModuleMetadataProvider.cs index 5ade85a71f2..67a53545b83 100644 --- a/src/Bicep.Core/Registry/Catalog/PublicModuleMetadataProvider.cs +++ b/src/Bicep.Core/Registry/Catalog/PublicModuleMetadataProvider.cs @@ -11,6 +11,7 @@ using Bicep.Core.Extensions; using Bicep.Core.Registry.Catalog.HttpClients; using Microsoft.Extensions.DependencyInjection; +using static Bicep.Core.Registry.Catalog.RegistryModuleMetadata; namespace Bicep.Core.Registry.Catalog; @@ -32,47 +33,25 @@ protected override async Task> GetLiveDa var modules = await client.GetModuleIndexAsync(); return [.. modules.Select(m => - new DefaultRegistryModuleMetadata( + { + var moduleDetails = new RegistryMetadataDetails(m.GetDescription(), m.GetDocumentationUri()); + var versions = ImmutableArray.Create([.. m.Versions.Select( + t => new RegistryModuleVersionMetadata( + t, + 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}", //asdfg remove prefix and get tests to fail - new RegistryMetadataDetails(m.GetDescription(), m.GetDocumentationUri()), - ImmutableArray.Create([.. m.Versions.Select( - t => new RegistryModuleVersionMetadata( - t, - new RegistryMetadataDetails( - m.PropertiesByTag.ContainsKey(t) ? m.PropertiesByTag[t].Description:null, - m.PropertiesByTag.ContainsKey(t) ? m.PropertiesByTag[t].DocumentationUri:null) - ) - )]) - ) - )]; + 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(); } - - //asdfg - //protected override Task GetLiveModuleVersionMetadataAsync(string modulePath, string version) - //{ - // throw new NotImplementedException("This method should never get called because versions are pre-filled with a resolved task"); - //} - - //asdfg - //private override async Task TryGetModuleMetadataFromAsdfgAsync(CachableModuleMetadata metadata) - //{ - // return [.. modules.Select(m => - // new CachableModule( - // new RegistryModuleMetadata(LanguageConstants.BicepPublicMcrRegistry, $"{LanguageConstants.BicepPublicMcrPathPrefix}{m.ModulePath}", m.GetDescription(), m.GetDocumentationUri()), - // [.. m.Versions.Select( - // t => new RegistryModuleVersionMetadata( - // t, - // m.PropertiesByTag.ContainsKey(t) ? m.PropertiesByTag[t].Description:null, - // m.PropertiesByTag.ContainsKey(t) ? m.PropertiesByTag[t].DocumentationUri:null - // ) - // )] - // ) - // )]; - //} } diff --git a/src/Bicep.Core/Registry/Catalog/RegistryModuleCatalog.cs b/src/Bicep.Core/Registry/Catalog/RegistryModuleCatalog.cs index 257abcabec6..f45e17c5f83 100644 --- a/src/Bicep.Core/Registry/Catalog/RegistryModuleCatalog.cs +++ b/src/Bicep.Core/Registry/Catalog/RegistryModuleCatalog.cs @@ -14,6 +14,7 @@ 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(); @@ -22,7 +23,8 @@ public RegistryModuleCatalog( IPrivateAcrModuleMetadataProviderFactory privateProviderFactory, IContainerRegistryClientFactory containerRegistryClientFactory, IConfigurationManager configurationManager - ) { + ) + { this.providerFactory = privateProviderFactory; this.containerRegistryClientFactory = containerRegistryClientFactory; this.configurationManager = configurationManager; @@ -32,16 +34,18 @@ IConfigurationManager configurationManager public IRegistryModuleMetadataProvider GetProviderForRegistry(CloudConfiguration cloud, string registry) { - if (registryProviders.TryGetValue(registry, out var provider)) + lock (lockObject) { - return provider; - } + if (registryProviders.TryGetValue(registry, out var provider)) + { + return provider; + } - provider = providerFactory.Create(cloud, registry, containerRegistryClientFactory); - registryProviders[registry] = provider; //asdfg threading + provider = providerFactory.Create(cloud, registry, containerRegistryClientFactory); + registryProviders[registry] = provider; //asdfg threading - //asdfg remove from cache, esp if error - return provider; + return provider; + } } public IRegistryModuleMetadataProvider? TryGetCachedRegistry(string registry) diff --git a/src/Bicep.Core/Registry/Catalog/RegistryModuleMetadata.cs b/src/Bicep.Core/Registry/Catalog/RegistryModuleMetadata.cs new file mode 100644 index 00000000000..e6e1c8d1409 --- /dev/null +++ b/src/Bicep.Core/Registry/Catalog/RegistryModuleMetadata.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; + +namespace Bicep.Core.Registry.Catalog; //asdfg split into PublicRegistry/PrivateRegistry + +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.LangServer.IntegrationTests/CompletionTests.cs b/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs index 3c48bad04ca..0b100e44362 100644 --- a/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs @@ -4254,6 +4254,7 @@ public async Task Private_module_version_completions(string inputWithCursors, Bi ); } + //asdfg2 [TestMethod] [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)] diff --git a/src/Bicep.LangServer/Completions/ModuleReferenceCompletionProvider.cs b/src/Bicep.LangServer/Completions/ModuleReferenceCompletionProvider.cs index 2a1dbe7ad6f..5cf7dedffe2 100644 --- a/src/Bicep.LangServer/Completions/ModuleReferenceCompletionProvider.cs +++ b/src/Bicep.LangServer/Completions/ModuleReferenceCompletionProvider.cs @@ -131,14 +131,14 @@ public Parts WithModulePath(string newResolvedModulePath) //asdfg test? { if (!string.IsNullOrWhiteSpace(ModulePathPrefix) && newResolvedModulePath.StartsWith(ModulePathPrefixWithSeparator, StringComparison.Ordinal)) { - return this with//asdfg testpoint + return this with { SpecifiedModulePath = newResolvedModulePath.Substring(ModulePathPrefixWithSeparator.Length) }; } else { - return this with//asdfg testpoint + return this with { SpecifiedModulePath = newResolvedModulePath }; @@ -171,7 +171,7 @@ public async Task> GetFilteredCompletions(Uri source if (context.ReplacementTarget is Token token) { - replacementText = token.Text;//asdfg testpoint + replacementText = token.Text; } var rootConfiguration = configurationManager.GetConfiguration(sourceFileUri); @@ -251,12 +251,12 @@ private IEnumerable GetTopLevelCompletions(BicepCompletionContex if (!context.Kind.HasFlag(BicepCompletionContextKind.ModulePath) && !context.Kind.HasFlag(BicepCompletionContextKind.UsingFilePath)) //asdfg? { - return [];//asdfg testpoint + return []; } if (!string.IsNullOrWhiteSpace(untrimmedReplacementText.Trim('\''))) { - return [];//asdfg testpoint + return []; } List completionItems = new(); @@ -276,7 +276,7 @@ private IEnumerable GetTopLevelCompletions(BicepCompletionContex } else { - // "ts/"//asdfg testpoint + // "ts/" AddCompletionItem("ts/", null, "Template spec (alias)", ModuleCompletionPriority.Alias, "template spec alias completion"); } @@ -294,7 +294,7 @@ private IEnumerable GetTopLevelCompletions(BicepCompletionContex ? "Public Bicep registry" : $"Alias for br:{registry}/{(modulePath == null ? "" : (modulePath + "/"))}"; - AddCompletionItem("br/", alias, detail, ModuleCompletionPriority.Alias, "module registry completion");//asdfg testpoint + AddCompletionItem("br/", alias, detail, ModuleCompletionPriority.Alias, "module registry completion"); } } else @@ -307,7 +307,7 @@ private IEnumerable GetTopLevelCompletions(BicepCompletionContex void AddCompletionItem(string schemePrefix, string? alias, string detailLabel, ModuleCompletionPriority priority, string triggerNextCompletionTitle) { - var label = alias is null//asdfg testpoint + var label = alias is null ? $"{schemePrefix}" // Full path (ts:, br:) : $"{schemePrefix}{alias}:"; // Alias (ts/, br/) var insertText = $"'{label}$0'"; @@ -330,7 +330,7 @@ private bool IsOciArtifactRegistryReference(string trimmedText) { if (trimmedText.StartsWith("br/") || trimmedText.StartsWith("br:")) { - return true;//asdfg testpoint + return true; } return false; @@ -344,7 +344,7 @@ private async Task> GetVersionCompletions(BicepCompl || !string.IsNullOrWhiteSpace(parts.Version) ) { - return [];//asdfg testpoint + return []; } List completions = new(); @@ -368,7 +368,7 @@ private async Task> GetVersionCompletions(BicepCompl .WithResolveData(ModuleVersionResolutionKey, new { Registry = parts.ResolvedRegistry, Module = parts.ResolvedModulePath, Version = version }) //asdfg test .Build(); - completions.Add(completionItem);//asdfg testpoint + completions.Add(completionItem); } } @@ -426,7 +426,7 @@ private async Task> GetVersionCompletions(BicepCompl private ImmutableSortedDictionary GetModuleAliases(RootConfiguration configuration) { - return configuration.ModuleAliases.GetOciArtifactModuleAliases();//asdfg testpoint + return configuration.ModuleAliases.GetOciArtifactModuleAliases(); } // Handles remote (OCI) module path completions, e.g. br: and br/ @@ -434,7 +434,7 @@ private async Task> GetOciModuleCompletions(BicepCom { if (!IsOciArtifactRegistryReference(trimmedText)) { - return [];//asdfg testpoint + return []; } return [ @@ -452,7 +452,7 @@ private async Task> GetPublicPathCompletionFromAlias if (IsPrivateRegistryReference(trimmedText, out _)) { - return completions;//asdfg testpoint + return completions; } foreach (var kvp in GetModuleAliases(rootConfiguration)) //asdfg test @@ -578,7 +578,7 @@ private async Task> GetPublicPathCompletionFromAlias var matches = regex.Matches(text); if (!matches.Any()) { - return null;//asdfg testpoint + return null; } string? value = matches[0].Groups[group].Value; @@ -587,7 +587,7 @@ private async Task> GetPublicPathCompletionFromAlias return null;//asdfg testpoint } - return value;//asdfg testpoint + return value; } /// @@ -601,7 +601,7 @@ private async Task> GetPublicPathCompletionFromAlias private bool IsPrivateRegistryReference(string text, [NotNullWhen(true)] out string? registry) { registry = GetFirstMatch(ModulePrefixWithFullPath, text, "registry", allowEmpty: false); - return registry is not null && !registry.Equals(PublicMcrRegistry, StringComparison.Ordinal);//asdfg testpoint + return registry is not null && !registry.Equals(PublicMcrRegistry, StringComparison.Ordinal); } // We only support partial path completions for ACR using module paths listed in bicepconfig.json @@ -627,7 +627,7 @@ private IEnumerable GetPartialPrivatePathCompletionsFromAliases( || !string.IsNullOrWhiteSpace(parts.ResolvedModulePath) || parts.HasVersionSeparator) { - return [];//asdfg testpoint + return []; } List completions = new(); @@ -676,7 +676,7 @@ private async Task> GetModuleCompletions(string trim || parts.ResolvedModulePath is null || parts.HasVersionSeparator) { - return [];//asdfg testpoint + return []; } List completions = new(); @@ -689,7 +689,7 @@ private async Task> GetModuleCompletions(string trim if (!moduleName.StartsWith(parts.ResolvedModulePath, StringComparison.Ordinal)) { - continue;//asdfg testpoint + continue; } string insertText = $"'{parts.WithModulePath(moduleName).ToNotation()}:$0'"; @@ -708,13 +708,13 @@ private async Task> GetModuleCompletions(string trim ModuleResolutionKey, new { Registry = module.Registry, Module = moduleName }) .WithFollowupCompletion("module version completion") - .Build();//asdfg testpoint + .Build(); completions.Add(completionItem); } if (completions.Any()) - {//asdfg testpoint + { telemetryProvider.PostEvent(BicepTelemetryEvent.ModuleRegistryPathCompletion(ModuleRegistryType.MCR/*asdfg wrong*/)); } @@ -728,14 +728,14 @@ private async Task> GetAllRegistryNameAndAliasComple if (!IsOciArtifactRegistryReference(trimmedText)) { - return completions;//asdfg testpoint + return completions; } if (trimmedText == "br/") { foreach (var kvp in GetModuleAliases(rootConfiguration)) { - var alias = kvp.Key;//asdfg testpoint + var alias = kvp.Key; var insertText = $"'{trimmedText}{alias}:$0'"; var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, alias) .WithFilterText(insertText) @@ -820,19 +820,19 @@ public async Task ResolveCompletionItem(CompletionItem completio var modulePath = versionData["Module"]?.ToString(); var version = versionData["Version"]?.ToString(); - if (registry != null && modulePath != null && version != null)//asdfg testpoint + if (registry != null && modulePath != null && version != null) { - return await ResolveVersionCompletionItem(completionItem, registry, modulePath, version);//asdfg testpoint + return await ResolveVersionCompletionItem(completionItem, registry, modulePath, version); } } else if (completionItem.Data != null && completionItem.Data[ModuleResolutionKey] is JObject moduleVersion) { - var registry = moduleVersion["Registry"]?.ToString();//asdfg testpoint + var registry = moduleVersion["Registry"]?.ToString(); var modulePath = moduleVersion["Module"]?.ToString(); if (registry != null && modulePath != null) { - return await ResolveModuleCompletionItem(completionItem, registry, modulePath);//asdfg testpoint + return await ResolveModuleCompletionItem(completionItem, registry, modulePath); } } @@ -845,7 +845,7 @@ private async Task ResolveVersionCompletionItem(CompletionItem c && await cachedRegistry.TryGetModuleAsync(modulePath) is { } module && await module.TryGetVersionsAsync() is { } versions &&/*extract?*/ versions.FirstOrDefault(v => v.Version.Equals(version, StringComparison.Ordinal)) is RegistryModuleVersionMetadata metadata/*asdfg does this work if not found?*/) - {//asdfg testpoint + { return (completionItem with //asdfg extract { Detail = metadata.Details.Description, @@ -863,7 +863,7 @@ private async Task ResolveModuleCompletionItem(CompletionItem co && await module.TryGetDetailsAsync() is { } details ) { - return (completionItem with//asdfg testpoint + return (completionItem with { Detail = details.Description, }).WithDocumentation(MarkdownHelper.GetDocumentationLink(details.DocumentationUri));