From fea71640ee2e06f3f2bba7354687a355f40d265a Mon Sep 17 00:00:00 2001 From: asilverman Date: Thu, 12 Oct 2023 09:59:10 -0700 Subject: [PATCH] initial commit --- .../BuildCommandTests.cs | 17 ++- src/Bicep.Cli/Commands/PublishCommand.cs | 3 +- .../RegistryTests.cs | 14 +-- src/Bicep.Core.Samples/DataSetsExtensions.cs | 7 +- .../Configuration/RootConfigurationTests.cs | 1 + ...OutputsShouldNotContainSecretsRuleTests.cs | 3 + .../UseRecentApiVersionRuleTests.cs | 1 + .../OciArtifactModuleReferenceTests.cs | 17 +-- .../Registry/ArtifactDispatcherTests.cs | 20 ++-- .../Registry/OciModuleRegistryTests.cs | 79 ++++++------- .../Utils/OciModuleRegistryHelper.cs | 23 +--- .../AnalyzersConfigurationExtensions.cs | 2 + .../Configuration/ConfigurationManager.cs | 1 + .../ProviderAliasesConfiguration.cs | 88 ++++++++++++++ .../Configuration/RootConfiguration.cs | 12 +- src/Bicep.Core/Configuration/bicepconfig.json | 8 ++ .../Diagnostics/DiagnosticBuilder.cs | 15 +++ src/Bicep.Core/Modules/OciModuleReference.cs | 45 -------- src/Bicep.Core/Registry/ArtifactRegistry.cs | 2 +- .../Registry/IArtifactDispatcher.cs | 4 +- ...actory.cs => IArtifactReferenceFactory.cs} | 6 +- src/Bicep.Core/Registry/IArtifactRegistry.cs | 4 +- .../Registry/LocalModuleRegistry.cs | 2 +- src/Bicep.Core/Registry/ModuleDispatcher.cs | 33 +++--- .../Registry/ModuleDispatcherExtensions.cs | 2 +- .../Registry/Oci/OciArtifactReference.cs | 50 ++++++-- .../Registry/OciArtifactRegistry.cs | 50 ++++---- .../Registry/TemplateSpecModuleRegistry.cs | 2 +- src/Bicep.Core/Semantics/Compilation.cs | 4 +- src/Bicep.Core/Semantics/ImportedSymbol.cs | 2 +- .../Semantics/WildcardImportSymbol.cs | 2 +- src/Bicep.Core/Syntax/ImportSpecification.cs | 108 ++++++++++++++---- .../Syntax/ProviderDeclarationSyntax.cs | 2 +- .../Workspaces/SourceFileGroupingBuilder.cs | 34 +++--- .../HoverTests.cs | 2 +- .../Registry/ModuleRestoreSchedulerTests.cs | 2 +- .../BicepRegistryCacheRequestHandlerTests.cs | 33 +++--- .../Handlers/BicepDefinitionHandler.cs | 4 +- .../Handlers/BicepHoverHandler.cs | 2 +- .../BicepRegistryCacheRequestHandler.cs | 4 +- 40 files changed, 451 insertions(+), 259 deletions(-) create mode 100644 src/Bicep.Core/Configuration/ProviderAliasesConfiguration.cs delete mode 100644 src/Bicep.Core/Modules/OciModuleReference.cs rename src/Bicep.Core/Registry/{IModuleReferenceFactory.cs => IArtifactReferenceFactory.cs} (51%) diff --git a/src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs b/src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs index 749b8f6f9b3..b6310840c38 100644 --- a/src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs @@ -103,13 +103,18 @@ public async Task Build_Valid_SingleFile_WithTemplateSpecReference_ShouldSucceed actualLocation: compiledFilePath); } - [TestMethod] - public async Task Provider_Artifacts_Restore_From_Registry_ShouldSucceed() + [DataTestMethod] + [DataRow("br:mcr.microsoft.com/bicep/providers/az", true)] + [DataRow("br/public:az", true)] + //[DataRow("br/contoso:az", true)] + // Negative + //[DataRow("az", false)] + public async Task Build_Valid_SingleFile_WithProviderDeclarationStatement(string providerDeclarationSyntax, bool shouldSucceed) { // SETUP // 1. create a mock registry client var registryUri = new Uri($"https://{LanguageConstants.BicepPublicMcrRegistry}"); - var repository = $"bicep/providers/az"; + var repository = "bicep/providers/az"; var (clientFactory, blobClients) = DataSetsExtensions.CreateMockRegistryClients(false, (registryUri, repository)); var myClient = blobClients[(registryUri, repository)]; @@ -142,8 +147,8 @@ public async Task Provider_Artifacts_Restore_From_Registry_ShouldSucceed() await myClient.UploadBlobAsync(new MemoryStream()); // 3. create a main.bicep and save it to a output directory - var bicepFile = """ -import 'az@2.0.0' + var bicepFile = $""" +import '{providerDeclarationSyntax}@2.0.0' """; var tempDirectory = FileHelper.GetUniqueTestOutputPath(TestContext); Directory.CreateDirectory(tempDirectory); @@ -161,7 +166,7 @@ public async Task Provider_Artifacts_Restore_From_Registry_ShouldSucceed() // 6. assert 'bicep build' completed successfully using (new AssertionScope()) { - result.Should().Be(0); + result.Should().Be(shouldSucceed ? 0 : 1); output.Should().BeEmpty(); AssertNoErrors(error); } diff --git a/src/Bicep.Cli/Commands/PublishCommand.cs b/src/Bicep.Cli/Commands/PublishCommand.cs index 7b8efad7cdc..2a34f22df10 100644 --- a/src/Bicep.Cli/Commands/PublishCommand.cs +++ b/src/Bicep.Cli/Commands/PublishCommand.cs @@ -13,6 +13,7 @@ using Bicep.Core.Features; using Bicep.Core.FileSystem; using Bicep.Core.Registry; +using Bicep.Core.Registry.Oci; using Bicep.Core.SourceCode; namespace Bicep.Cli.Commands @@ -98,7 +99,7 @@ private async Task PublishModuleAsync(ArtifactReference target, Stream compiledA private ArtifactReference ValidateReference(string targetModuleReference, Uri targetModuleUri) { - if (!this.moduleDispatcher.TryGetModuleReference(targetModuleReference, targetModuleUri).IsSuccess(out var moduleReference, out var failureBuilder)) + if (!this.moduleDispatcher.TryGetArtifactReference(targetModuleReference, "module", targetModuleUri).IsSuccess(out var moduleReference, out var failureBuilder)) { // TODO: We should probably clean up the dispatcher contract so this sort of thing isn't necessary (unless we change how target module is set in this command) var message = failureBuilder(DiagnosticBuilder.ForDocumentStart()).Message; diff --git a/src/Bicep.Core.IntegrationTests/RegistryTests.cs b/src/Bicep.Core.IntegrationTests/RegistryTests.cs index 05b90cae2f2..d8b0863322c 100644 --- a/src/Bicep.Core.IntegrationTests/RegistryTests.cs +++ b/src/Bicep.Core.IntegrationTests/RegistryTests.cs @@ -182,7 +182,7 @@ public async Task ModuleRestoreContentionShouldProduceConsistentState(bool publi var moduleReferences = dataSet.RegistryModules.Values .OrderBy(m => m.Metadata.Target) - .Select(m => dispatcher.TryGetModuleReference(m.Metadata.Target, RandomFileUri()).IsSuccess(out var @ref) ? @ref : throw new AssertFailedException($"Invalid module target '{m.Metadata.Target}'.")) + .Select(m => dispatcher.TryGetArtifactReference(m.Metadata.Target, "module", RandomFileUri()).IsSuccess(out var @ref) ? @ref : throw new AssertFailedException($"Invalid module target '{m.Metadata.Target}'.")) .ToImmutableList(); moduleReferences.Should().HaveCount(7); @@ -238,7 +238,7 @@ public async Task ModuleRestoreWithStuckFileLockShouldFailAfterTimeout(IEnumerab var configuration = BicepTestConstants.BuiltInConfigurationWithAllAnalyzersDisabled; var moduleReferences = moduleInfos .OrderBy(m => m.Metadata.Target) - .Select(m => dispatcher.TryGetModuleReference(m.Metadata.Target, RandomFileUri()).IsSuccess(out var @ref) ? @ref : throw new AssertFailedException($"Invalid module target '{m.Metadata.Target}'.")) + .Select(m => dispatcher.TryGetArtifactReference(m.Metadata.Target, "module", RandomFileUri()).IsSuccess(out var @ref) ? @ref : throw new AssertFailedException($"Invalid module target '{m.Metadata.Target}'.")) .ToImmutableList(); moduleReferences.Should().HaveCount(moduleCount); @@ -249,7 +249,7 @@ public async Task ModuleRestoreWithStuckFileLockShouldFailAfterTimeout(IEnumerab dispatcher.GetArtifactRestoreStatus(moduleReference, out _).Should().Be(ArtifactRestoreStatus.Unknown); } - dispatcher.TryGetLocalModuleEntryPointUri(moduleReferences[0]).IsSuccess(out var moduleFileUri).Should().BeTrue(); + dispatcher.TryGetLocalArtifactEntryPointUri(moduleReferences[0]).IsSuccess(out var moduleFileUri).Should().BeTrue(); moduleFileUri.Should().NotBeNull(); var moduleFilePath = moduleFileUri!.LocalPath; @@ -307,7 +307,7 @@ public async Task ForceModuleRestoreWithStuckFileLockShouldFailAfterTimeout(IEnu var moduleReferences = moduleInfos .OrderBy(m => m.Metadata.Target) - .Select(m => dispatcher.TryGetModuleReference(m.Metadata.Target, RandomFileUri()).IsSuccess(out var @ref) ? @ref : throw new AssertFailedException($"Invalid module target '{m.Metadata.Target}'.")) + .Select(m => dispatcher.TryGetArtifactReference(m.Metadata.Target, "module", RandomFileUri()).IsSuccess(out var @ref) ? @ref : throw new AssertFailedException($"Invalid module target '{m.Metadata.Target}'.")) .ToImmutableList(); moduleReferences.Should().HaveCount(moduleCount); @@ -318,7 +318,7 @@ public async Task ForceModuleRestoreWithStuckFileLockShouldFailAfterTimeout(IEnu dispatcher.GetArtifactRestoreStatus(moduleReference, out _).Should().Be(ArtifactRestoreStatus.Unknown); } - dispatcher.TryGetLocalModuleEntryPointUri(moduleReferences[0]).IsSuccess(out var moduleFileUri).Should().BeTrue(); + dispatcher.TryGetLocalArtifactEntryPointUri(moduleReferences[0]).IsSuccess(out var moduleFileUri).Should().BeTrue(); moduleFileUri.Should().NotBeNull(); var moduleFilePath = moduleFileUri!.LocalPath; @@ -384,7 +384,7 @@ public async Task ForceModuleRestoreShouldRestoreAllModules(IEnumerable m.Metadata.Target) - .Select(m => dispatcher.TryGetModuleReference(m.Metadata.Target, RandomFileUri()).IsSuccess(out var @ref) ? @ref : throw new AssertFailedException($"Invalid module target '{m.Metadata.Target}'.")) + .Select(m => dispatcher.TryGetArtifactReference(m.Metadata.Target, "module", RandomFileUri()).IsSuccess(out var @ref) ? @ref : throw new AssertFailedException($"Invalid module target '{m.Metadata.Target}'.")) .ToImmutableList(); moduleReferences.Should().HaveCount(moduleCount); @@ -395,7 +395,7 @@ public async Task ForceModuleRestoreShouldRestoreAllModules(IEnumerable CreateMockRegistryClients(Im { var target = publishInfo.Metadata.Target; - if (!dispatcher.TryGetModuleReference(target, RandomFileUri()).IsSuccess(out var @ref) || @ref is not OciModuleReference targetReference) + if (!dispatcher.TryGetArtifactReference(target, "module", RandomFileUri()).IsSuccess(out var @ref) || @ref is not OciArtifactReference targetReference) { throw new InvalidOperationException($"Module '{moduleName}' has an invalid target reference '{target}'. Specify a reference to an OCI artifact."); } @@ -148,7 +149,7 @@ public static ITemplateSpecRepositoryFactory CreateMockTemplateSpecRepositoryFac foreach (var (moduleName, templateSpecInfo) in templateSpecs) { - if (!dispatcher.TryGetModuleReference(templateSpecInfo.Metadata.Target, RandomFileUri()).IsSuccess(out var @ref) || @ref is not TemplateSpecModuleReference reference) + if (!dispatcher.TryGetArtifactReference(templateSpecInfo.Metadata.Target, "module", RandomFileUri()).IsSuccess(out var @ref) || @ref is not TemplateSpecModuleReference reference) { throw new InvalidOperationException($"Module '{moduleName}' has an invalid target reference '{templateSpecInfo.Metadata.Target}'. Specify a reference to a template spec."); } @@ -190,7 +191,7 @@ public static async Task PublishModuleToRegistryAsync(IContainerRegistryClientFa .AddSingleton(featureProviderFactory) ).Construct(); - var targetReference = dispatcher.TryGetModuleReference(target, RandomFileUri()).IsSuccess(out var @ref) ? @ref + var targetReference = dispatcher.TryGetArtifactReference(target, "TODO", RandomFileUri()).IsSuccess(out var @ref) ? @ref : throw new InvalidOperationException($"Module '{moduleName}' has an invalid target reference '{target}'. Specify a reference to an OCI artifact."); var result = CompilationHelper.Compile(moduleSource); diff --git a/src/Bicep.Core.UnitTests/Configuration/RootConfigurationTests.cs b/src/Bicep.Core.UnitTests/Configuration/RootConfigurationTests.cs index f886eff26d4..6d187e31389 100644 --- a/src/Bicep.Core.UnitTests/Configuration/RootConfigurationTests.cs +++ b/src/Bicep.Core.UnitTests/Configuration/RootConfigurationTests.cs @@ -21,6 +21,7 @@ public void RootConfiguration_LeadingTildeInCacheRootDirectory_ExpandPath(string var configuration = new RootConfiguration( BicepTestConstants.BuiltInConfiguration.Cloud, BicepTestConstants.BuiltInConfiguration.ModuleAliases, + BicepTestConstants.BuiltInConfiguration.ProviderAliases, BicepTestConstants.BuiltInConfiguration.Analyzers, cacheRootDirectory, BicepTestConstants.BuiltInConfiguration.ExperimentalFeaturesEnabled, diff --git a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/OutputsShouldNotContainSecretsRuleTests.cs b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/OutputsShouldNotContainSecretsRuleTests.cs index b1db43d0edd..65c0fc2bea9 100644 --- a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/OutputsShouldNotContainSecretsRuleTests.cs +++ b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/OutputsShouldNotContainSecretsRuleTests.cs @@ -137,6 +137,7 @@ public void If_OutputReferencesSecureParamProperty_ShouldFail(string text, param CompileAndTest(text, OnCompileErrors.IncludeErrors, expectedMessages, config => new( config.Cloud, config.ModuleAliases, + config.ProviderAliases, config.Analyzers, config.CacheRootDirectory, config.ExperimentalFeaturesEnabled with { UserDefinedTypes = true }, @@ -228,6 +229,7 @@ public void If_OutputReferencesParamWithSecureProperty_ShouldFail(string text, p CompileAndTest(text, OnCompileErrors.IncludeErrors, expectedMessages, config => new( config.Cloud, config.ModuleAliases, + config.ProviderAliases, config.Analyzers, config.CacheRootDirectory, config.ExperimentalFeaturesEnabled with { UserDefinedTypes = true }, @@ -279,6 +281,7 @@ public void If_OutputReferencesNonSecureParamProperty_ShouldPass(string text, pa CompileAndTest(text, OnCompileErrors.IncludeErrors, expectedMessages, config => new( config.Cloud, config.ModuleAliases, + config.ProviderAliases, config.Analyzers, config.CacheRootDirectory, config.ExperimentalFeaturesEnabled with { UserDefinedTypes = true }, diff --git a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseRecentApiVersionRuleTests.cs b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseRecentApiVersionRuleTests.cs index 67182c3bab7..ee5c3807819 100644 --- a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseRecentApiVersionRuleTests.cs +++ b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseRecentApiVersionRuleTests.cs @@ -109,6 +109,7 @@ private static RootConfiguration CreateConfigurationWithFakeToday(RootConfigurat return new RootConfiguration( original.Cloud, original.ModuleAliases, + original.ProviderAliases, new AnalyzersConfiguration( JsonElementFactory.CreateElement(@" { diff --git a/src/Bicep.Core.UnitTests/Modules/OciArtifactModuleReferenceTests.cs b/src/Bicep.Core.UnitTests/Modules/OciArtifactModuleReferenceTests.cs index 08f2bb17df0..04bc6fa1e05 100644 --- a/src/Bicep.Core.UnitTests/Modules/OciArtifactModuleReferenceTests.cs +++ b/src/Bicep.Core.UnitTests/Modules/OciArtifactModuleReferenceTests.cs @@ -9,6 +9,7 @@ using Bicep.Core.Configuration; using Bicep.Core.FileSystem; using Bicep.Core.Modules; +using Bicep.Core.Registry.Oci; using Bicep.Core.UnitTests.Assertions; using FluentAssertions; using FluentAssertions.Execution; @@ -103,7 +104,7 @@ public void ValidReferenceShouldBeUriParseable(ValidCase @case) [DataTestMethod] public void InvalidReferencesShouldProduceExpectedError(string value, string expectedCode, string expectedError) { - OciModuleReference.TryParse(null, value, BicepTestConstants.BuiltInConfigurationWithAllAnalyzersDisabled, RandomFileUri()).IsSuccess(out var @ref, out var failureBuilder).Should().BeFalse(); + OciArtifactReference.TryParse(OciArtifactReferenceType.Module, null, value, BicepTestConstants.BuiltInConfigurationWithAllAnalyzersDisabled, RandomFileUri()).IsSuccess(out var @ref, out var failureBuilder).Should().BeFalse(); @ref.Should().BeNull(); failureBuilder!.Should().NotBeNull(); @@ -145,7 +146,7 @@ public void MismatchedReferencesShouldNotBeEqual(string package1, string package [DataRow("foo bar ÄÄÄ")] public void TryParse_InvalidAliasName_ReturnsFalseAndSetsErrorDiagnostic(string aliasName) { - OciModuleReference.TryParse(aliasName, "", BicepTestConstants.BuiltInConfiguration, RandomFileUri()).IsSuccess(out var reference, out var errorBuilder).Should().BeFalse(); + OciArtifactReference.TryParse(OciArtifactReferenceType.Module, aliasName, "", BicepTestConstants.BuiltInConfiguration, RandomFileUri()).IsSuccess(out var reference, out var errorBuilder).Should().BeFalse(); reference.Should().BeNull(); errorBuilder!.Should().HaveCode("BCP211"); @@ -159,7 +160,7 @@ public void TryParse_AliasNotInConfiguration_ReturnsFalseAndSetsErrorDiagnostic( { var configuration = BicepTestConstants.CreateMockConfiguration(configurationPath: configurationPath); - OciModuleReference.TryParse(aliasName, referenceValue, configuration, RandomFileUri()).IsSuccess(out var reference, out var errorBuilder).Should().BeFalse(); + OciArtifactReference.TryParse(OciArtifactReferenceType.Module, aliasName, referenceValue, configuration, RandomFileUri()).IsSuccess(out var reference, out var errorBuilder).Should().BeFalse(); reference.Should().BeNull(); errorBuilder!.Should().NotBeNull(); @@ -171,7 +172,7 @@ public void TryParse_AliasNotInConfiguration_ReturnsFalseAndSetsErrorDiagnostic( [DynamicData(nameof(GetInvalidAliasData), DynamicDataSourceType.Method)] public void TryParse_InvalidAlias_ReturnsFalseAndSetsErrorDiagnostic(string aliasName, string referenceValue, RootConfiguration configuration, string expectedCode, string expectedMessage) { - OciModuleReference.TryParse(aliasName, referenceValue, configuration, RandomFileUri()).IsSuccess(out var reference, out var errorBuilder).Should().BeFalse(); + OciArtifactReference.TryParse(OciArtifactReferenceType.Module, aliasName, referenceValue, configuration, RandomFileUri()).IsSuccess(out var reference, out var errorBuilder).Should().BeFalse(); reference.Should().BeNull(); errorBuilder!.Should().NotBeNull(); @@ -183,22 +184,22 @@ public void TryParse_InvalidAlias_ReturnsFalseAndSetsErrorDiagnostic(string alia [DynamicData(nameof(GetValidAliasData), DynamicDataSourceType.Method)] public void TryGetModuleReference_ValidAlias_ReplacesReferenceValue(string aliasName, string referenceValue, string fullyQualifiedReferenceValue, RootConfiguration configuration) { - OciModuleReference.TryParse(aliasName, referenceValue, configuration, RandomFileUri()).IsSuccess(out var reference, out var errorBuilder).Should().BeTrue(); + OciArtifactReference.TryParse(OciArtifactReferenceType.Module, aliasName, referenceValue, configuration, RandomFileUri()).IsSuccess(out var reference, out var errorBuilder).Should().BeTrue(); reference.Should().NotBeNull(); reference!.FullyQualifiedReference.Should().Be(fullyQualifiedReferenceValue); } - private static OciModuleReference Parse(string package) + private static OciArtifactReference Parse(string package) { - OciModuleReference.TryParse(null, package, BicepTestConstants.BuiltInConfigurationWithAllAnalyzersDisabled, RandomFileUri()).IsSuccess(out var parsed, out var failureBuilder).Should().BeTrue(); + OciArtifactReference.TryParse(OciArtifactReferenceType.Module, null, package, BicepTestConstants.BuiltInConfigurationWithAllAnalyzersDisabled, RandomFileUri()).IsSuccess(out var parsed, out var failureBuilder).Should().BeTrue(); failureBuilder!.Should().BeNull(); parsed.Should().NotBeNull(); return parsed!; } - private static (OciModuleReference, OciModuleReference) ParsePair(string first, string second) => (Parse(first), Parse(second)); + private static (OciArtifactReference, OciArtifactReference) ParsePair(string first, string second) => (Parse(first), Parse(second)); private static IEnumerable GetValidCases() { diff --git a/src/Bicep.Core.UnitTests/Registry/ArtifactDispatcherTests.cs b/src/Bicep.Core.UnitTests/Registry/ArtifactDispatcherTests.cs index 7f5c8e05547..36140a9940e 100644 --- a/src/Bicep.Core.UnitTests/Registry/ArtifactDispatcherTests.cs +++ b/src/Bicep.Core.UnitTests/Registry/ArtifactDispatcherTests.cs @@ -41,7 +41,7 @@ public void NoRegistries_ValidateModuleReference_ShouldReturnError() var dispatcher = CreateDispatcher(BicepTestConstants.ConfigurationManager); var configuration = BicepTestConstants.BuiltInConfiguration; - dispatcher.TryGetModuleReference(module, RandomFileUri()).IsSuccess(out var reference, out var failureBuilder).Should().BeFalse(); + dispatcher.TryGetArtifactReference(module, RandomFileUri()).IsSuccess(out var reference, out var failureBuilder).Should().BeFalse(); reference.Should().BeNull(); failureBuilder!.Should().NotBeNull(); @@ -52,7 +52,7 @@ public void NoRegistries_ValidateModuleReference_ShouldReturnError() } var localModule = CreateModule("test.bicep"); - dispatcher.TryGetModuleReference(localModule, RandomFileUri()).IsSuccess(out var localModuleReference, out var localModuleFailureBuilder).Should().BeFalse(); + dispatcher.TryGetArtifactReference(localModule, RandomFileUri()).IsSuccess(out var localModuleReference, out var localModuleFailureBuilder).Should().BeFalse(); localModuleReference.Should().BeNull(); failureBuilder!.Should().NotBeNull(); using (new AssertionScope()) @@ -89,20 +89,20 @@ public async Task MockRegistries_ModuleLifecycle() var validRefUri = RandomFileUri(); ArtifactReference? validRef = new MockModuleReference("validRef", validRefUri); - mock.Setup(m => m.TryParseArtifactReference(null, "validRef")).Returns(ResultHelper.Create(validRef, @null)); + mock.Setup(m => m.TryParseArtifactReference(null, "module", "validRef", validRefUri)).Returns(ResultHelper.Create(validRef, @null)); var validRefUri2 = RandomFileUri(); ArtifactReference? validRef2 = new MockModuleReference("validRef2", validRefUri2); - mock.Setup(m => m.TryParseArtifactReference(null, "validRef2")).Returns(ResultHelper.Create(validRef2, @null)); + mock.Setup(m => m.TryParseArtifactReference(null, "module", "validRef2", validRefUri2)).Returns(ResultHelper.Create(validRef2, @null)); var validRefUri3 = RandomFileUri(); ArtifactReference? validRef3 = new MockModuleReference("validRef3", validRefUri3); - mock.Setup(m => m.TryParseArtifactReference(null, "validRef3")).Returns(ResultHelper.Create(validRef3, @null)); + mock.Setup(m => m.TryParseArtifactReference(null, "module", "validRef3", validRefUri3)).Returns(ResultHelper.Create(validRef3, @null)); var badRefUri = RandomFileUri(); ArtifactReference? nullRef = null; ErrorBuilderDelegate? badRefError = x => new ErrorDiagnostic(x.TextSpan, "BCPMock", "Bad ref error"); - mock.Setup(m => m.TryParseArtifactReference(null, "badRef")).Returns(ResultHelper.Create(nullRef, badRefError)); + mock.Setup(m => m.TryParseArtifactReference(null, "module", "badRef", badRefUri)).Returns(ResultHelper.Create(nullRef, badRefError)); mock.Setup(m => m.IsArtifactRestoreRequired(validRef)).Returns(true); mock.Setup(m => m.IsArtifactRestoreRequired(validRef2)).Returns(false); @@ -126,11 +126,11 @@ public async Task MockRegistries_ModuleLifecycle() var goodModule3 = CreateModule("mock:validRef3"); var badModule = CreateModule("mock:badRef"); - dispatcher.TryGetModuleReference(goodModule, validRefUri).IsSuccess(out var @ref, out var goodValidationBuilder).Should().BeTrue(); + dispatcher.TryGetArtifactReference(goodModule, validRefUri).IsSuccess(out var @ref, out var goodValidationBuilder).Should().BeTrue(); @ref.Should().Be(validRef); goodValidationBuilder!.Should().BeNull(); - dispatcher.TryGetModuleReference(badModule, badRefUri).IsSuccess(out @ref, out var badValidationBuilder).Should().BeFalse(); + dispatcher.TryGetArtifactReference(badModule, badRefUri).IsSuccess(out @ref, out var badValidationBuilder).Should().BeFalse(); @ref.Should().BeNull(); badValidationBuilder!.Should().NotBeNull(); badValidationBuilder!.Should().HaveCode("BCPMock"); @@ -147,11 +147,11 @@ public async Task MockRegistries_ModuleLifecycle() goodAvailabilityBuilder3!.Should().HaveCode("BCP190"); goodAvailabilityBuilder3!.Should().HaveMessage("The module with reference \"mock:validRef3\" has not been restored."); - dispatcher.TryGetLocalModuleEntryPointUri(validRef).IsSuccess(out var @uri, out var entryPointBuilder).Should().BeTrue(); + dispatcher.TryGetLocalArtifactEntryPointUri(validRef).IsSuccess(out var @uri, out var entryPointBuilder).Should().BeTrue(); @uri.Should().Be(new Uri("untitled://validRef")); entryPointBuilder!.Should().BeNull(); - dispatcher.TryGetLocalModuleEntryPointUri(validRef3).IsSuccess(out @uri, out var entryPointBuilder3).Should().BeTrue(); + dispatcher.TryGetLocalArtifactEntryPointUri(validRef3).IsSuccess(out @uri, out var entryPointBuilder3).Should().BeTrue(); @uri.Should().Be(new Uri("untitled://validRef3")); entryPointBuilder3!.Should().BeNull(); diff --git a/src/Bicep.Core.UnitTests/Registry/OciModuleRegistryTests.cs b/src/Bicep.Core.UnitTests/Registry/OciModuleRegistryTests.cs index 24d90ba7f87..eececc57495 100644 --- a/src/Bicep.Core.UnitTests/Registry/OciModuleRegistryTests.cs +++ b/src/Bicep.Core.UnitTests/Registry/OciModuleRegistryTests.cs @@ -9,6 +9,7 @@ using Bicep.Core.Features; using Bicep.Core.Modules; using Bicep.Core.Registry; +using Bicep.Core.Registry.Oci; using Bicep.Core.SourceCode; using Bicep.Core.UnitTests.Assertions; using Bicep.Core.UnitTests.Mock; @@ -44,14 +45,14 @@ public void TestInitialize() [DataTestMethod] public void GetDocumentationUri_WithInvalidManifestContents_ShouldReturnNull(string manifestFileContents) { - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( "output myOutput string = 'hello!'", manifestFileContents, "test.azurecr.io", "bicep/modules/storage", "sha:12345"); - var result = OciArtifactRegistry.TryGetDocumentationUri(OciModuleReference); + var result = OciArtifactRegistry.TryGetDocumentationUri(OciArtifactReference); result.Should().BeNull(); } @@ -59,14 +60,14 @@ public void GetDocumentationUri_WithInvalidManifestContents_ShouldReturnNull(str [TestMethod] public void GetDocumentationUri_WithNonExistentManifestFile_ShouldReturnNull() { - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( "output myOutput string = 'hello!'", null, "test.azurecr.io", "bicep/modules/storage", digest: "sha:12345"); - var result = OciArtifactRegistry.TryGetDocumentationUri(OciModuleReference); + var result = OciArtifactRegistry.TryGetDocumentationUri(OciArtifactReference); result.Should().BeNull(); } @@ -92,14 +93,14 @@ public void GetDocumentationUri_WithManifestFileAndNoAnnotations_ShouldReturnNul } ] }"; - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( "output myOutput string = 'hello!'", manifestFileContents, "test.azurecr.io", "bicep/modules/storage", "sha:12345"); - var result = OciArtifactRegistry.TryGetDocumentationUri(OciModuleReference); + var result = OciArtifactRegistry.TryGetDocumentationUri(OciArtifactReference); result.Should().BeNull(); } @@ -130,14 +131,14 @@ public void GetDocumentationUri_WithAnnotationsInManifestFileAndInvalidDocumenta ""org.opencontainers.image.documentation"": """ + documentationUri + @""" } }"; - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( "output myOutput string = 'hello!'", manifestFileContents, "test.azurecr.io", "bicep/modules/storage", "sha:12345"); - var result = OciArtifactRegistry.TryGetDocumentationUri(OciModuleReference); + var result = OciArtifactRegistry.TryGetDocumentationUri(OciArtifactReference); result.Should().BeNull(); } @@ -165,17 +166,17 @@ public async Task GetDocumentationUri_WithAnnotationsInManifestFile_ButEmpty_Sho ""annotations"": { } }"; - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( "output myOutput string = 'hello!'", manifestFileContents, "test.azurecr.io", "bicep/modules/storage", "sha:12345"); - var documentation = OciArtifactRegistry.TryGetDocumentationUri(OciModuleReference); + var documentation = OciArtifactRegistry.TryGetDocumentationUri(OciArtifactReference); documentation.Should().BeNull(); - var description = await OciArtifactRegistry.TryGetDescription(OciModuleReference); + var description = await OciArtifactRegistry.TryGetDescription(OciArtifactReference); description.Should().BeNull(); } @@ -204,17 +205,17 @@ public async Task GetDocumentationUri_WithAnnotationsInManifestFile_ButOnlyHasOt ""org.opencontainers.image.notdescription"": """ + "description" + @""" } }"; - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( "output myOutput string = 'hello!'", manifestFileContents, "test.azurecr.io", "bicep/modules/storage", "sha:12345"); - var documentation = OciArtifactRegistry.TryGetDocumentationUri(OciModuleReference); + var documentation = OciArtifactRegistry.TryGetDocumentationUri(OciArtifactReference); documentation.Should().BeNull(); - var description = await OciArtifactRegistry.TryGetDescription(OciModuleReference); + var description = await OciArtifactRegistry.TryGetDescription(OciArtifactReference); description.Should().BeNull(); } @@ -243,14 +244,14 @@ public void GetDocumentationUri_WithValidDocumentationUriInManifestFile_ShouldRe ""org.opencontainers.image.documentation"": """ + documentationUri + @""" } }"; - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( "output myOutput string = 'hello!'", manifestFileContents, "test.azurecr.io", "bicep/modules/storage", "sha:12345"); - var result = OciArtifactRegistry.TryGetDocumentationUri(OciModuleReference); + var result = OciArtifactRegistry.TryGetDocumentationUri(OciArtifactReference); result.Should().NotBeNull(); result.Should().BeEquivalentTo(documentationUri); @@ -289,14 +290,14 @@ public void GetDocumentationUri_WithMcrModuleReferenceAndNoDocumentationUriInMan ] } }"; - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( bicepFileContents, manifestFileContents, "mcr.microsoft.com", "bicep/app/dapr-containerapps-environment/bicep/core", tag: "1.0.1"); - var result = OciArtifactRegistry.TryGetDocumentationUri(OciModuleReference); + var result = OciArtifactRegistry.TryGetDocumentationUri(OciArtifactReference); result.Should().NotBeNull(); result.Should().BeEquivalentTo("https://github.com/Azure/bicep-registry-modules/tree/app/dapr-containerapps-environment/bicep/core/1.0.1/modules/app/dapr-containerapps-environment/bicep/core/README.md"); @@ -312,14 +313,14 @@ public void GetDocumentationUri_WithMcrModuleReferenceAndNoDocumentationUriInMan [DataTestMethod] public void GetDescription_WithInvalidManifestContents_ShouldReturnNull(string manifestFileContents) { - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( "output myOutput string = 'hello!'", manifestFileContents, "test.azurecr.io", "bicep/modules/storage", "sha:12345"); - var result = OciArtifactRegistry.TryGetDocumentationUri(OciModuleReference); + var result = OciArtifactRegistry.TryGetDocumentationUri(OciArtifactReference); result.Should().BeNull(); } @@ -327,14 +328,14 @@ public void GetDescription_WithInvalidManifestContents_ShouldReturnNull(string m [TestMethod] public async Task GetDescription_WithNonExistentManifestFile_ShouldReturnNull() { - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( "output myOutput string = 'hello!'", null, "test.azurecr.io", "bicep/modules/storage", digest: "sha:12345"); - var result = await OciArtifactRegistry.TryGetDescription(OciModuleReference); + var result = await OciArtifactRegistry.TryGetDescription(OciArtifactReference); result.Should().BeNull(); } @@ -360,14 +361,14 @@ public async Task GetDescription_WithManifestFileAndNoAnnotations_ShouldReturnNu } ] }"; - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( "output myOutput string = 'hello!'", manifestFileContents, "test.azurecr.io", "bicep/modules/storage", "sha:12345"); - var result = await OciArtifactRegistry.TryGetDescription(OciModuleReference); + var result = await OciArtifactRegistry.TryGetDescription(OciArtifactReference); result.Should().BeNull(); } @@ -395,14 +396,14 @@ public async Task GetDescription_WithManifestFileAndJustDocumentationUri_ShouldR } ] }"; - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( "output myOutput string = 'hello!'", manifestFileContents, "test.azurecr.io", "bicep/modules/storage", "sha:12345"); - var result = await OciArtifactRegistry.TryGetDescription(OciModuleReference); + var result = await OciArtifactRegistry.TryGetDescription(OciArtifactReference); result.Should().BeNull(); } @@ -432,14 +433,14 @@ public async Task GetDescription_WithValidDescriptionInManifestFile_ShouldReturn ""org.opencontainers.image.description"": """ + description + @""" } }"; - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( "output myOutput string = 'hello!'", manifestFileContents, "test.azurecr.io", "bicep/modules/storage", "sha:12345"); - var result = await OciArtifactRegistry.TryGetDescription(OciModuleReference); + var result = await OciArtifactRegistry.TryGetDescription(OciArtifactReference); result.Should().NotBeNull(); result.Should().BeEquivalentTo(description); @@ -471,14 +472,14 @@ public async Task GetDescription_WithAnnotationsInManifestFileAndInvalidDescript ""org.opencontainers.image.description"": """ + description + @""" } }"; - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( "output myOutput string = 'hello!'", manifestFileContents, "test.azurecr.io", "bicep/modules/storage", "sha:12345"); - var result = await OciArtifactRegistry.TryGetDescription(OciModuleReference); + var result = await OciArtifactRegistry.TryGetDescription(OciArtifactReference); result.Should().BeNull(); } @@ -510,19 +511,19 @@ public async Task GetDescription_WithValidDescriptionAndDocumentationUriInManife ""org.opencontainers.image.description"": """ + description + @""" } }"; - (OciArtifactRegistry OciArtifactRegistry, OciModuleReference OciModuleReference) = CreateModuleRegistryWithCachedModuleReference( + (OciArtifactRegistry OciArtifactRegistry, OciArtifactReference OciArtifactReference) = CreateModuleRegistryWithCachedModuleReference( "output myOutput string = 'hello!'", manifestFileContents, "test.azurecr.io", "bicep/modules/storage", "sha:12345"); - var actualDocumentationUri = OciArtifactRegistry.TryGetDocumentationUri(OciModuleReference); + var actualDocumentationUri = OciArtifactRegistry.TryGetDocumentationUri(OciArtifactReference); actualDocumentationUri.Should().NotBeNull(); actualDocumentationUri.Should().BeEquivalentTo(documentationUri); - var actualDescription = await OciArtifactRegistry.TryGetDescription(OciModuleReference); + var actualDescription = await OciArtifactRegistry.TryGetDescription(OciArtifactReference); actualDescription.Should().NotBeNull(); actualDescription.Should().BeEquivalentTo(description.Replace("\\", "")); // unencode json @@ -694,7 +695,7 @@ public async Task RestoreModuleWithSources_ShouldRestoreSourceToDisk(bool publis #region Helpers - private async Task RestoreModule(OciArtifactRegistry ociRegistry, OciModuleReference moduleReference) + private async Task RestoreModule(OciArtifactRegistry ociRegistry, OciArtifactReference moduleReference) { var (_, failureBuilder) = (await ociRegistry.RestoreArtifacts(new[] { moduleReference })).SingleOrDefault(); if (failureBuilder is { }) @@ -708,9 +709,9 @@ private async Task RestoreModule(OciArtifactRegistry ociRegistry, OciModuleRefer } } - private OciModuleReference CreateModuleReference(string registry, string repository, string? tag, string? digest) + private OciArtifactReference CreateModuleReference(string registry, string repository, string? tag, string? digest) { - OciModuleReference.TryParse(null, $"{registry}/{repository}:{tag}", BicepTestConstants.BuiltInConfiguration, new Uri("file:///main.bicep")).IsSuccess(out var moduleReference).Should().BeTrue(); + OciArtifactReference.TryParse(OciArtifactReferenceType.Module, null, $"{registry}/{repository}:{tag}", BicepTestConstants.BuiltInConfiguration, new Uri("file:///main.bicep")).IsSuccess(out var moduleReference).Should().BeTrue(); return moduleReference!; } @@ -738,7 +739,7 @@ bool publishSourceFeatureEnabled // Creates a new (real) OciArtifactRegistry and sets it up so that it has an on-disk cached module // reference as if it had been restored from the registry. This can be used to test scenarios // where a module has already been restored. - private (OciArtifactRegistry, OciModuleReference) CreateModuleRegistryWithCachedModuleReference( + private (OciArtifactRegistry, OciArtifactReference) CreateModuleRegistryWithCachedModuleReference( string parentBicepFileContents, // The bicep file which references the module string? manifestFileContents, string registry, @@ -748,7 +749,7 @@ bool publishSourceFeatureEnabled { var (OciArtifactRegistry, _, parentModuleUri) = CreateModuleRegistryAndBicepFile(parentBicepFileContents, true); - OciModuleReference? ociModuleReference = OciArtifactRegistryHelper.CreateModuleReferenceMock( + OciArtifactReference? OciArtifactReference = OciArtifactRegistryHelper.CreateModuleReferenceMock( registry, repository, parentModuleUri, @@ -767,7 +768,7 @@ bool publishSourceFeatureEnabled tag); } - return (OciArtifactRegistry, ociModuleReference); + return (OciArtifactRegistry, OciArtifactReference); } private IFeatureProvider GetFeatures(string cacheRootDirectory, bool publishSourceEnabled) diff --git a/src/Bicep.Core.UnitTests/Utils/OciModuleRegistryHelper.cs b/src/Bicep.Core.UnitTests/Utils/OciModuleRegistryHelper.cs index 0c9a12f3f0e..743097bd05f 100644 --- a/src/Bicep.Core.UnitTests/Utils/OciModuleRegistryHelper.cs +++ b/src/Bicep.Core.UnitTests/Utils/OciModuleRegistryHelper.cs @@ -24,26 +24,13 @@ namespace Bicep.Core.UnitTests.Utils { public static class OciArtifactRegistryHelper { - public static OciModuleReference CreateModuleReferenceMock( - string registry, - string repository, - Uri parentModuleUri, - string? digest, - string? tag) - { - var artifactReferenceMock = StrictMock.Of(); - artifactReferenceMock.SetupGet(m => m.Registry).Returns(registry); - artifactReferenceMock.SetupGet(m => m.Repository).Returns(repository); - artifactReferenceMock.SetupGet(m => m.Digest).Returns(digest); - artifactReferenceMock.SetupGet(m => m.Tag).Returns(tag); - artifactReferenceMock.SetupGet(m => m.ArtifactId).Returns($"{registry}/{repository}:{tag ?? digest}"); - - return new OciModuleReference(artifactReferenceMock.Object, parentModuleUri); - } + public static OciArtifactReference CreateModuleReferenceMock(string registry, string repository, Uri parentModuleUri, string? digest, string? tag) + => new(registry, repository, tag, digest, OciArtifactReferenceType.Module, parentModuleUri); + - public static OciModuleReference CreateModuleReference(string registry, string repository, string? tag, string? digest) + public static OciArtifactReference CreateModuleReference(string registry, string repository, string? tag, string? digest) { - OciModuleReference.TryParse(null, $"{registry}/{repository}:{tag}", BicepTestConstants.BuiltInConfiguration, new Uri("file:///main.bicep")).IsSuccess(out var moduleReference).Should().BeTrue(); + OciArtifactReference.TryParse(OciArtifactReferenceType.Module, null, $"{registry}/{repository}:{tag}", BicepTestConstants.BuiltInConfiguration, new Uri("file:///main.bicep")).IsSuccess(out var moduleReference).Should().BeTrue(); return moduleReference!; } diff --git a/src/Bicep.Core/Configuration/AnalyzersConfigurationExtensions.cs b/src/Bicep.Core/Configuration/AnalyzersConfigurationExtensions.cs index 147b91068dd..9c9b5089896 100644 --- a/src/Bicep.Core/Configuration/AnalyzersConfigurationExtensions.cs +++ b/src/Bicep.Core/Configuration/AnalyzersConfigurationExtensions.cs @@ -26,6 +26,7 @@ public static RootConfiguration WithAllAnalyzersDisabled(this RootConfiguration return new RootConfiguration( analyzersConfiguration.Cloud, analyzersConfiguration.ModuleAliases, + analyzersConfiguration.ProviderAliases, analyzersConfiguration.Analyzers.WithAllAnalyzersDisabled(), analyzersConfiguration.CacheRootDirectory, analyzersConfiguration.ExperimentalFeaturesEnabled, @@ -39,6 +40,7 @@ public static RootConfiguration WithAnalyzersDisabled(this RootConfiguration ana return new RootConfiguration( analyzersConfiguration.Cloud, analyzersConfiguration.ModuleAliases, + analyzersConfiguration.ProviderAliases, analyzersConfiguration.Analyzers.WithAnalyzersDisabled(analyzerCodesToDisable), analyzersConfiguration.CacheRootDirectory, analyzersConfiguration.ExperimentalFeaturesEnabled, diff --git a/src/Bicep.Core/Configuration/ConfigurationManager.cs b/src/Bicep.Core/Configuration/ConfigurationManager.cs index 09f8dac5ce5..f2ca5a9ad64 100644 --- a/src/Bicep.Core/Configuration/ConfigurationManager.cs +++ b/src/Bicep.Core/Configuration/ConfigurationManager.cs @@ -99,6 +99,7 @@ private static RootConfiguration WithLoadDiagnostics(RootConfiguration configura return new( configuration.Cloud, configuration.ModuleAliases, + configuration.ProviderAliases, configuration.Analyzers, configuration.CacheRootDirectory, configuration.ExperimentalFeaturesEnabled, diff --git a/src/Bicep.Core/Configuration/ProviderAliasesConfiguration.cs b/src/Bicep.Core/Configuration/ProviderAliasesConfiguration.cs new file mode 100644 index 00000000000..5e1a75d0e83 --- /dev/null +++ b/src/Bicep.Core/Configuration/ProviderAliasesConfiguration.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Bicep.Core.Diagnostics; +using Bicep.Core.Extensions; +using static Bicep.Core.Diagnostics.DiagnosticBuilder; + +namespace Bicep.Core.Configuration +{ + public record ProviderAliases + { + [JsonPropertyName("br")] + public ImmutableSortedDictionary OciArtifactProviderAliases { get; init; } = ImmutableSortedDictionary.Empty; + } + + public record OciArtifactProviderAlias + { + public string? Registry { get; init; } + public string? ProviderPath { get; init; } + public override string ToString() => this.ProviderPath is not null + ? $"{Registry}/{ProviderPath}" + : $"{Registry}"; + } + + public partial class ProviderAliasesConfiguration : ConfigurationSection + { + private readonly string? configurationPath; + + private ProviderAliasesConfiguration(ProviderAliases data, string? configurationPath) + : base(data) + { + this.configurationPath = configurationPath; + } + public static ProviderAliasesConfiguration Bind(JsonElement element, string? configurationPath) => new(element.ToNonNullObject(), configurationPath); + + public ImmutableSortedDictionary GetOciArtifactProviderAliases() + { + return this.Data.OciArtifactProviderAliases; + } + + public ResultWithDiagnostic TryGetOciArtifactProviderAlias(string aliasName) + { + if (!ValidateAliasName(aliasName, out var errorBuilder)) + { + return new(errorBuilder); + } + + if (!this.Data.OciArtifactProviderAliases.TryGetValue(aliasName, out var alias)) + { + return new(x => x.OciArtifactProviderAliasNameDoesNotExistInConfiguration(aliasName, this.configurationPath)); + } + + if (alias.Registry is null) + { + return new(x => x.InvalidOciArtifactProviderAliasRegistryNullOrUndefined(aliasName, this.configurationPath)); + } + + return new(alias); + } + + private static bool ValidateAliasName(string aliasName, [NotNullWhen(false)] out ErrorBuilderDelegate? errorBuilder) + { + if (!ProviderAliasNameRegex().IsMatch(aliasName)) + { + errorBuilder = x => x.InvalidProviderAliasName(aliasName); + return false; + } + + errorBuilder = null; + return true; + } + + [GeneratedRegex("^[a-zA-Z0-9-_]+$", RegexOptions.CultureInvariant)] + private static partial Regex ProviderAliasNameRegex(); + + + } +} diff --git a/src/Bicep.Core/Configuration/RootConfiguration.cs b/src/Bicep.Core/Configuration/RootConfiguration.cs index f0e22de5474..be56d098d4f 100644 --- a/src/Bicep.Core/Configuration/RootConfiguration.cs +++ b/src/Bicep.Core/Configuration/RootConfiguration.cs @@ -17,6 +17,8 @@ public class RootConfiguration private const string ModuleAliasesKey = "moduleAliases"; + private const string ProviderAliasesKey = "providerAliases"; + private const string AnalyzersKey = "analyzers"; private const string CacheRootDirectoryKey = "cacheRootDirectory"; @@ -28,6 +30,7 @@ public class RootConfiguration public RootConfiguration( CloudConfiguration cloud, ModuleAliasesConfiguration moduleAliases, + ProviderAliasesConfiguration providerAliases, AnalyzersConfiguration analyzers, string? cacheRootDirectory, ExperimentalFeaturesEnabled experimentalFeaturesEnabled, @@ -37,6 +40,7 @@ public RootConfiguration( { this.Cloud = cloud; this.ModuleAliases = moduleAliases; + this.ProviderAliases = providerAliases; this.Analyzers = analyzers; this.CacheRootDirectory = ExpandCacheRootDirectory(cacheRootDirectory); this.ExperimentalFeaturesEnabled = experimentalFeaturesEnabled; @@ -49,18 +53,21 @@ public static RootConfiguration Bind(JsonElement element, string? configurationP { var cloud = CloudConfiguration.Bind(element.GetProperty(CloudKey), configurationPath); var moduleAliases = ModuleAliasesConfiguration.Bind(element.GetProperty(ModuleAliasesKey), configurationPath); + var providerAliases = ProviderAliasesConfiguration.Bind(element.GetProperty(ProviderAliasesKey), configurationPath); var analyzers = new AnalyzersConfiguration(element.GetProperty(AnalyzersKey)); var cacheRootDirectory = element.TryGetProperty(CacheRootDirectoryKey, out var e) ? e.GetString() : default; var experimentalFeaturesEnabled = ExperimentalFeaturesEnabled.Bind(element.GetProperty(ExperimentalFeaturesEnabledKey)); var formatting = FormattingConfiguration.Bind(element.GetProperty(FormattingKey)); - return new(cloud, moduleAliases, analyzers, cacheRootDirectory, experimentalFeaturesEnabled, formatting, configurationPath, diagnosticBuilders); + return new(cloud, moduleAliases, providerAliases, analyzers, cacheRootDirectory, experimentalFeaturesEnabled, formatting, configurationPath, diagnosticBuilders); } public CloudConfiguration Cloud { get; } public ModuleAliasesConfiguration ModuleAliases { get; } + public ProviderAliasesConfiguration ProviderAliases { get; } + public AnalyzersConfiguration Analyzers { get; } public string? CacheRootDirectory { get; } @@ -88,6 +95,9 @@ public string ToUtf8Json() writer.WritePropertyName(ModuleAliasesKey); this.ModuleAliases.WriteTo(writer); + writer.WritePropertyName(ProviderAliasesKey); + this.ProviderAliases.WriteTo(writer); + writer.WritePropertyName(AnalyzersKey); this.Analyzers.WriteTo(writer); diff --git a/src/Bicep.Core/Configuration/bicepconfig.json b/src/Bicep.Core/Configuration/bicepconfig.json index afb71fb49de..c143ffb8922 100644 --- a/src/Bicep.Core/Configuration/bicepconfig.json +++ b/src/Bicep.Core/Configuration/bicepconfig.json @@ -32,6 +32,14 @@ } } }, + "providerAliases": { + "br": { + "public": { + "registry": "mcr.microsoft.com", + "providerPath": "bicep/providers" + } + } + }, "analyzers": { "core": { "verbose": false, diff --git a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs index 9173270ba86..bf935dba133 100644 --- a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs +++ b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs @@ -2088,6 +2088,21 @@ public ErrorDiagnostic RuntimeValueNotAllowedInFunctionDeclaration(string? acces TextSpan, "BCP376", $"The \"{name}\" symbol cannot be imported because imports of kind {exportMetadataKind} are not supported in files of kind {sourceFileKind}."); + + public ErrorDiagnostic InvalidProviderAliasName(string aliasName) => new( + TextSpan, + "BCP377", + $"The provider alias name \"{aliasName}\" is invalid. Valid characters are alphanumeric, \"_\", or \"-\"."); + + public ErrorDiagnostic InvalidOciArtifactProviderAliasRegistryNullOrUndefined(string aliasName, string? configurationPath) => new( + TextSpan, + "BCP378", + $"The OCI artifact provider alias \"{aliasName}\" in the {BuildBicepConfigurationClause(configurationPath)} is invalid. The \"registry\" property cannot be null or undefined."); + + public ErrorDiagnostic OciArtifactProviderAliasNameDoesNotExistInConfiguration(string aliasName, string? configurationPath) => new( + TextSpan, + "BCP379", + $"The OCI artifact provider alias name \"{aliasName}\" does not exist in the {BuildBicepConfigurationClause(configurationPath)}."); } public static DiagnosticBuilderInternal ForPosition(TextSpan span) diff --git a/src/Bicep.Core/Modules/OciModuleReference.cs b/src/Bicep.Core/Modules/OciModuleReference.cs deleted file mode 100644 index fb213843803..00000000000 --- a/src/Bicep.Core/Modules/OciModuleReference.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Diagnostics.CodeAnalysis; -using Bicep.Core.Configuration; -using Bicep.Core.Diagnostics; -using Bicep.Core.Registry; -using Bicep.Core.Registry.Oci; - - -namespace Bicep.Core.Modules -{ - /// - /// Represents a reference to an artifact in an OCI registry. - /// - public class OciModuleReference : ArtifactReference, IOciArtifactReference - { - private readonly IOciArtifactReference ociArtifactRef; - public OciModuleReference(IOciArtifactReference ociArtifactReference, Uri parentModuleUri) - : base(ModuleReferenceSchemes.Oci, parentModuleUri) - { - this.ociArtifactRef = ociArtifactReference; - } - public override string UnqualifiedReference => this.ArtifactId; - public override bool IsExternal => true; - public override bool Equals(object? obj) => obj is OciModuleReference other && this.ociArtifactRef.Equals(other.ociArtifactRef); - public override int GetHashCode() => this.ociArtifactRef.GetHashCode(); - public string Registry => this.ociArtifactRef.Registry; - public string Repository => this.ociArtifactRef.Repository; - public string? Tag => this.ociArtifactRef.Tag; - public string? Digest => this.ociArtifactRef.Digest; - public string ArtifactId => this.ociArtifactRef.ArtifactId; - - public static ResultWithDiagnostic TryParse(string? aliasName, string rawValue, RootConfiguration configuration, Uri parentModuleUri) - { - if (!OciArtifactReference.TryParse(aliasName, rawValue, configuration).IsSuccess(out var artifactReference, out var failureBuilder)) - { - return new(failureBuilder); - } - - return new(new OciModuleReference(artifactReference, parentModuleUri)); - } - } -} diff --git a/src/Bicep.Core/Registry/ArtifactRegistry.cs b/src/Bicep.Core/Registry/ArtifactRegistry.cs index 2c430f155f2..b606a6e079d 100644 --- a/src/Bicep.Core/Registry/ArtifactRegistry.cs +++ b/src/Bicep.Core/Registry/ArtifactRegistry.cs @@ -29,7 +29,7 @@ public abstract class ArtifactRegistry : IArtifactRegistry where T : Artifact public abstract ResultWithDiagnostic TryGetLocalArtifactEntryPointUri(T reference); - public abstract ResultWithDiagnostic TryParseArtifactReference(string? aliasName, string reference); + public abstract ResultWithDiagnostic TryParseArtifactReference(string? aliasName, string artifactType, string reference, Uri parentModuleUri); public abstract string? TryGetDocumentationUri(T reference); diff --git a/src/Bicep.Core/Registry/IArtifactDispatcher.cs b/src/Bicep.Core/Registry/IArtifactDispatcher.cs index 306f752cb81..5932bd6225c 100644 --- a/src/Bicep.Core/Registry/IArtifactDispatcher.cs +++ b/src/Bicep.Core/Registry/IArtifactDispatcher.cs @@ -11,13 +11,13 @@ namespace Bicep.Core.Registry { - public interface IModuleDispatcher : IModuleReferenceFactory + public interface IModuleDispatcher : IArtifactReferenceFactory { RegistryCapabilities GetRegistryCapabilities(ArtifactReference moduleReference); ArtifactRestoreStatus GetArtifactRestoreStatus(ArtifactReference moduleReference, out DiagnosticBuilder.ErrorBuilderDelegate? errorDetailBuilder); - ResultWithDiagnostic TryGetLocalModuleEntryPointUri(ArtifactReference moduleReference); + ResultWithDiagnostic TryGetLocalArtifactEntryPointUri(ArtifactReference moduleReference); Task RestoreModules(IEnumerable moduleReferences, bool forceModulesRestore = false); diff --git a/src/Bicep.Core/Registry/IModuleReferenceFactory.cs b/src/Bicep.Core/Registry/IArtifactReferenceFactory.cs similarity index 51% rename from src/Bicep.Core/Registry/IModuleReferenceFactory.cs rename to src/Bicep.Core/Registry/IArtifactReferenceFactory.cs index ca4e8f631fc..68fc9a21a8a 100644 --- a/src/Bicep.Core/Registry/IModuleReferenceFactory.cs +++ b/src/Bicep.Core/Registry/IArtifactReferenceFactory.cs @@ -9,11 +9,11 @@ namespace Bicep.Core.Registry; -public interface IModuleReferenceFactory +public interface IArtifactReferenceFactory { ImmutableArray AvailableSchemes(Uri parentModuleUri); - ResultWithDiagnostic TryGetModuleReference(string reference, Uri parentModuleUri); + ResultWithDiagnostic TryGetArtifactReference(string reference, string artifactType, Uri parentModuleUri); - ResultWithDiagnostic TryGetModuleReference(IArtifactReferenceSyntax artifactDeclaration, Uri parentModuleUri); + ResultWithDiagnostic TryGetArtifactReference(IArtifactReferenceSyntax artifactDeclaration, Uri parentModuleUri); } diff --git a/src/Bicep.Core/Registry/IArtifactRegistry.cs b/src/Bicep.Core/Registry/IArtifactRegistry.cs index fd0c7a05b85..e487ba2e728 100644 --- a/src/Bicep.Core/Registry/IArtifactRegistry.cs +++ b/src/Bicep.Core/Registry/IArtifactRegistry.cs @@ -33,7 +33,9 @@ public interface IArtifactRegistry /// /// The alias name /// The unqualified artifact reference - ResultWithDiagnostic TryParseArtifactReference(string? aliasName, string reference); + /// The artifact type. Either "module" or "provider" + /// The URI of the parent module + ResultWithDiagnostic TryParseArtifactReference(string? aliasName, string artifactType, string reference, Uri parentModuleUri); /// /// Returns true if the specified artifact is already cached in the local cache. diff --git a/src/Bicep.Core/Registry/LocalModuleRegistry.cs b/src/Bicep.Core/Registry/LocalModuleRegistry.cs index 7d992a70915..ed7ac7ec101 100644 --- a/src/Bicep.Core/Registry/LocalModuleRegistry.cs +++ b/src/Bicep.Core/Registry/LocalModuleRegistry.cs @@ -31,7 +31,7 @@ public LocalModuleRegistry(IFileResolver fileResolver, Uri parentModuleUri, Bice public override RegistryCapabilities GetCapabilities(LocalModuleReference reference) => RegistryCapabilities.Default; - public override ResultWithDiagnostic TryParseArtifactReference(string? alias, string reference) + public override ResultWithDiagnostic TryParseArtifactReference(string? alias, string artifactType, string reference, Uri parentModuleUri) { if (!LocalModuleReference.TryParse(reference, parentModuleUri).IsSuccess(out var @ref, out var failureBuilder)) { diff --git a/src/Bicep.Core/Registry/ModuleDispatcher.cs b/src/Bicep.Core/Registry/ModuleDispatcher.cs index deeb5a08b59..c32c795aab3 100644 --- a/src/Bicep.Core/Registry/ModuleDispatcher.cs +++ b/src/Bicep.Core/Registry/ModuleDispatcher.cs @@ -41,7 +41,7 @@ private ImmutableDictionary Registries(Uri parentModu public ImmutableArray AvailableSchemes(Uri parentModuleUri) => Registries(parentModuleUri).Keys.OrderBy(s => s).ToImmutableArray(); - public ResultWithDiagnostic TryGetModuleReference(string reference, Uri parentModuleUri) + public ResultWithDiagnostic TryGetArtifactReference(string reference, string artifactType, Uri parentModuleUri) { var registries = Registries(parentModuleUri); var parts = reference.Split(':', 2, StringSplitOptions.None); @@ -51,7 +51,7 @@ public ResultWithDiagnostic TryGetModuleReference(string refe // local path reference if (registries.TryGetValue(ModuleReferenceSchemes.Local, out var localRegistry)) { - return localRegistry.TryParseArtifactReference(null, parts[0]); + return localRegistry.TryParseArtifactReference(null, "module", parts[0], parentModuleUri); } return new(x => x.UnknownModuleReferenceScheme(ModuleReferenceSchemes.Local, this.AvailableSchemes(parentModuleUri))); @@ -73,7 +73,7 @@ public ResultWithDiagnostic TryGetModuleReference(string refe // the scheme is recognized var rawValue = parts[1]; - return registry.TryParseArtifactReference(aliasName, rawValue); + return registry.TryParseArtifactReference(aliasName, artifactType, rawValue, parentModuleUri); } // unknown scheme @@ -85,14 +85,21 @@ public ResultWithDiagnostic TryGetModuleReference(string refe } } - public ResultWithDiagnostic TryGetModuleReference(IArtifactReferenceSyntax moduleDeclaration, Uri parentModuleUri) + public ResultWithDiagnostic TryGetArtifactReference(IArtifactReferenceSyntax artifactDeclaration, Uri parentModuleUri) { - if (!SyntaxHelper.TryGetForeignTemplatePath(moduleDeclaration).IsSuccess(out var moduleReferenceString, out var failureBuilder)) + if (!SyntaxHelper.TryGetForeignTemplatePath(artifactDeclaration).IsSuccess(out var artifactReferenceString, out var failureBuilder)) { return new(failureBuilder); } - return this.TryGetModuleReference(moduleReferenceString, parentModuleUri); + var artifactType = artifactDeclaration switch + { + ModuleDeclarationSyntax => "module", + ProviderDeclarationSyntax => "provider", + _ => throw new InvalidOperationException($"Unexpected artifactDeclaration artifactType '{artifactDeclaration.GetType().Name}'."), + }; + + return this.TryGetArtifactReference(artifactReferenceString, artifactType, parentModuleUri); } public RegistryCapabilities GetRegistryCapabilities(ArtifactReference artifactReference) @@ -126,17 +133,17 @@ public ArtifactRestoreStatus GetArtifactRestoreStatus( return ArtifactRestoreStatus.Succeeded; } - public ResultWithDiagnostic TryGetLocalModuleEntryPointUri(ArtifactReference moduleReference) + public ResultWithDiagnostic TryGetLocalArtifactEntryPointUri(ArtifactReference artifactReference) { - var configuration = configurationManager.GetConfiguration(moduleReference.ParentModuleUri); - // has restore already failed for this module? - if (this.HasRestoreFailed(moduleReference, configuration, out var restoreFailureBuilder)) + var configuration = configurationManager.GetConfiguration(artifactReference.ParentModuleUri); + // has restore already failed for this artifact? + if (this.HasRestoreFailed(artifactReference, configuration, out var restoreFailureBuilder)) { return new(restoreFailureBuilder); } - var registry = this.GetRegistry(moduleReference); - return registry.TryGetLocalArtifactEntryPointUri(moduleReference); + var registry = this.GetRegistry(artifactReference); + return registry.TryGetLocalArtifactEntryPointUri(artifactReference); } public async Task RestoreModules(IEnumerable moduleReferences, bool forceModulesRestore = false) @@ -213,7 +220,7 @@ public void PruneRestoreStatuses() } private IArtifactRegistry GetRegistry(ArtifactReference moduleReference) => - Registries(moduleReference.ParentModuleUri).TryGetValue(moduleReference.Scheme, out var registry) ? registry : throw new InvalidOperationException($"Unexpected moduleDeclaration reference scheme '{moduleReference.Scheme}'."); + Registries(moduleReference.ParentModuleUri).TryGetValue(moduleReference.Scheme, out var registry) ? registry : throw new InvalidOperationException($"Unexpected artifactDeclaration reference scheme '{moduleReference.Scheme}'."); public SourceArchive? TryGetModuleSources(ArtifactReference moduleReference) { diff --git a/src/Bicep.Core/Registry/ModuleDispatcherExtensions.cs b/src/Bicep.Core/Registry/ModuleDispatcherExtensions.cs index f8d6a27b355..fb2c4a2f306 100644 --- a/src/Bicep.Core/Registry/ModuleDispatcherExtensions.cs +++ b/src/Bicep.Core/Registry/ModuleDispatcherExtensions.cs @@ -14,7 +14,7 @@ public static class ModuleDispatcherExtensions { public static IEnumerable GetValidModuleReferences(this IModuleDispatcher moduleDispatcher, IEnumerable artifacts) => artifacts - .Select(t => moduleDispatcher.TryGetModuleReference(t.DeclarationSyntax, t.ParentTemplateFile.FileUri).TryUnwrap()) + .Select(t => moduleDispatcher.TryGetArtifactReference(t.DeclarationSyntax, t.ParentTemplateFile.FileUri).TryUnwrap()) .WhereNotNull(); } } diff --git a/src/Bicep.Core/Registry/Oci/OciArtifactReference.cs b/src/Bicep.Core/Registry/Oci/OciArtifactReference.cs index ad940d7e0e6..745091804d8 100644 --- a/src/Bicep.Core/Registry/Oci/OciArtifactReference.cs +++ b/src/Bicep.Core/Registry/Oci/OciArtifactReference.cs @@ -12,10 +12,16 @@ namespace Bicep.Core.Registry.Oci { + public enum OciArtifactReferenceType + { + Module, + Provider, + } - public class OciArtifactReference : IOciArtifactReference + public class OciArtifactReference : ArtifactReference, IOciArtifactReference { - private OciArtifactReference(string registry, string repository, string? tag, string? digest) + public OciArtifactReference(string registry, string repository, string? tag, string? digest, OciArtifactReferenceType type, Uri parentModuleUri) : + base(OciArtifactReferenceFacts.Scheme, parentModuleUri) { switch (tag, digest) { @@ -29,7 +35,10 @@ private OciArtifactReference(string registry, string repository, string? tag, st this.Repository = repository; this.Tag = tag; this.Digest = digest; + this.Type = type; } + + /// /// Gets the registry URI. /// @@ -57,9 +66,19 @@ private OciArtifactReference(string registry, string repository, string? tag, st ? $"{this.Registry}/{this.Repository}:{this.Tag}" : $"{this.Registry}/{this.Repository}@{this.Digest}"; - public string FullyQualifiedReference => ArtifactId; + /// + /// Gets the type of artifact reference. Either module or provider. + /// + public OciArtifactReferenceType Type { get; } + + public override string UnqualifiedReference => ArtifactId; + + public override bool IsExternal => true; - public static ResultWithDiagnostic TryParse(string? aliasName, string rawValue, RootConfiguration configuration) + public static ResultWithDiagnostic TryParseModule(string? aliasName, string rawValue, RootConfiguration configuration, Uri parentModuleUri) + => TryParse(OciArtifactReferenceType.Module, aliasName, rawValue, configuration, parentModuleUri); + + public static ResultWithDiagnostic TryParse(OciArtifactReferenceType type, string? aliasName, string rawValue, RootConfiguration configuration, Uri parentModuleUri) { static string GetBadReference(string referenceValue) => $"{OciArtifactReferenceFacts.Scheme}:{referenceValue}"; @@ -67,12 +86,23 @@ public static ResultWithDiagnostic TryParse(string? aliasN if (aliasName is not null) { - if (!configuration.ModuleAliases.TryGetOciArtifactModuleAlias(aliasName).IsSuccess(out var alias, out var failureBuilder)) + switch (type) { - return new(failureBuilder); + case OciArtifactReferenceType.Module: + if (!configuration.ModuleAliases.TryGetOciArtifactModuleAlias(aliasName).IsSuccess(out var moduleAlias, out var moduleFailureBuilder)) + { + return new(moduleFailureBuilder); + } + rawValue = $"{moduleAlias}/{rawValue}"; + break; + case OciArtifactReferenceType.Provider: + if (!configuration.ProviderAliases.TryGetOciArtifactProviderAlias(aliasName).IsSuccess(out var providerAlias, out var providerFailureBuilder)) + { + return new(providerFailureBuilder); + } + rawValue = $"{providerAlias}/{rawValue}"; + break; } - - rawValue = $"{alias}/{rawValue}"; } // the set of valid OCI artifact refs is a subset of the set of valid URIs if you remove the scheme portion from each URI @@ -179,7 +209,7 @@ public static ResultWithDiagnostic TryParse(string? aliasN tag)); } - return new(new OciArtifactReference(registry, repository, tag: tag, digest: null)); + return new(new OciArtifactReference(registry, repository, tag, digest: null, type, parentModuleUri)); case '@': var digest = tagOrDigest; @@ -188,7 +218,7 @@ public static ResultWithDiagnostic TryParse(string? aliasN return new(x => x.InvalidOciArtifactReferenceInvalidDigest(aliasName, GetBadReference(rawValue), digest)); } - return new(new OciArtifactReference(registry, repository, tag: null, digest: digest)); + return new(new OciArtifactReference(registry, repository, tag: null, digest, type, parentModuleUri)); default: throw new NotImplementedException($"Unexpected last segment delimiter character '{delimiter.Value}'."); diff --git a/src/Bicep.Core/Registry/OciArtifactRegistry.cs b/src/Bicep.Core/Registry/OciArtifactRegistry.cs index 45524104650..73d4ea3a8d0 100644 --- a/src/Bicep.Core/Registry/OciArtifactRegistry.cs +++ b/src/Bicep.Core/Registry/OciArtifactRegistry.cs @@ -23,7 +23,7 @@ namespace Bicep.Core.Registry { - public sealed class OciArtifactRegistry : ExternalArtifactRegistry + public sealed class OciArtifactRegistry : ExternalArtifactRegistry { private readonly AzureContainerRegistryManager client; @@ -54,23 +54,29 @@ public OciArtifactRegistry( public string CacheRootDirectory => this.features.CacheRootDirectory; - public override RegistryCapabilities GetCapabilities(OciModuleReference reference) + public override RegistryCapabilities GetCapabilities(OciArtifactReference reference) { // cannot publish without tag return reference.Tag is null ? RegistryCapabilities.Default : RegistryCapabilities.Publish; } - public override ResultWithDiagnostic TryParseArtifactReference(string? aliasName, string reference) + public override ResultWithDiagnostic TryParseArtifactReference(string? aliasName, string artifactType, string reference, Uri parentModuleUri) { - if (!OciModuleReference.TryParse(aliasName, reference, configuration, parentModuleUri).IsSuccess(out var @ref, out var failureBuilder)) + var type = artifactType switch + { + "module" => OciArtifactReferenceType.Module, + "provider" => OciArtifactReferenceType.Provider, + _ => throw new ArgumentException($"Unexpected artifact type \"{artifactType}\"."), + }; + if (!OciArtifactReference.TryParse(type, aliasName, reference, configuration, parentModuleUri).IsSuccess(out var @ref, out var failureBuilder)) { return new(failureBuilder); } - return new(@ref); } - public override bool IsArtifactRestoreRequired(OciModuleReference reference) + + public override bool IsArtifactRestoreRequired(OciArtifactReference reference) { /* * this should be kept in sync with the WriteModuleContent() implementation @@ -93,7 +99,7 @@ public override bool IsArtifactRestoreRequired(OciModuleReference reference) !this.FileResolver.FileExists(this.GetModuleFileUri(reference, ModuleFileType.Metadata)); } - public override async Task CheckArtifactExists(OciModuleReference reference) + public override async Task CheckArtifactExists(OciArtifactReference reference) { try { @@ -130,13 +136,13 @@ public override async Task CheckArtifactExists(OciModuleReference referenc return true; } - public override ResultWithDiagnostic TryGetLocalArtifactEntryPointUri(OciModuleReference reference) + public override ResultWithDiagnostic TryGetLocalArtifactEntryPointUri(OciArtifactReference reference) { var localUri = this.GetModuleFileUri(reference, ModuleFileType.ModuleMain); return new(localUri); } - public override string? TryGetDocumentationUri(OciModuleReference ociArtifactModuleReference) + public override string? TryGetDocumentationUri(OciArtifactReference ociArtifactModuleReference) { var ociAnnotations = TryGetOciAnnotations(ociArtifactModuleReference); if (ociAnnotations is null || @@ -167,13 +173,13 @@ public static string GetPublicBicepModuleDocumentationUri(string publicModuleNam return $"https://github.com/Azure/bicep-registry-modules/tree/{publicModuleName}/{tag}/modules/{publicModuleName}/README.md"; } - public override Task TryGetDescription(OciModuleReference ociArtifactModuleReference) + public override Task TryGetDescription(OciArtifactReference ociArtifactModuleReference) { var ociAnnotations = TryGetOciAnnotations(ociArtifactModuleReference); return Task.FromResult(DescriptionHelper.TryGetFromOciManifestAnnotations(ociAnnotations)); } - private OciManifest GetCachedManifest(OciModuleReference ociArtifactModuleReference) + private OciManifest GetCachedManifest(OciArtifactReference ociArtifactModuleReference) { string manifestFilePath = this.GetModuleFilePath(ociArtifactModuleReference, ModuleFileType.Manifest); @@ -190,7 +196,7 @@ private OciManifest GetCachedManifest(OciModuleReference ociArtifactModuleRefere } } - private ImmutableDictionary? TryGetOciAnnotations(OciModuleReference ociArtifactModuleReference) + private ImmutableDictionary? TryGetOciAnnotations(OciArtifactReference ociArtifactModuleReference) { try { @@ -202,7 +208,7 @@ private OciManifest GetCachedManifest(OciModuleReference ociArtifactModuleRefere } } - public override async Task> RestoreArtifacts(IEnumerable references) + public override async Task> RestoreArtifacts(IEnumerable references) { var statuses = new Dictionary(); @@ -229,12 +235,12 @@ private OciManifest GetCachedManifest(OciModuleReference ociArtifactModuleRefere return statuses; } - public override async Task> InvalidateArtifactsCache(IEnumerable references) + public override async Task> InvalidateArtifactsCache(IEnumerable references) { return await base.InvalidateArtifactsCacheInternal(references); } - public override async Task PublishArtifact(OciModuleReference moduleReference, Stream compiledArmTemplate, Stream? bicepSources, string? documentationUri, string? description) + public override async Task PublishArtifact(OciArtifactReference moduleReference, Stream compiledArmTemplate, Stream? bicepSources, string? documentationUri, string? description) { // This needs to be valid JSON, otherwise there may be compatibility issues. // NOTE: Bicep v0.20 and earlier will throw on this, so it's a breaking change. @@ -278,7 +284,7 @@ await this.client.PushArtifactAsync( // media types are case-insensitive (they are lowercase by convention only) public static readonly StringComparison MediaTypeComparison = StringComparison.OrdinalIgnoreCase; - protected override void WriteArtifactContentToCache(OciModuleReference reference, OciArtifactResult result) + protected override void WriteArtifactContentToCache(OciArtifactReference reference, OciArtifactResult result) { /* * this should be kept in sync with the IsModuleRestoreRequired() implementation @@ -336,7 +342,7 @@ protected override void WriteArtifactContentToCache(OciModuleReference reference this.FileResolver.Write(this.GetModuleFileUri(reference, ModuleFileType.Metadata), metadataStream); } - protected override string GetArtifactDirectoryPath(OciModuleReference reference) + protected override string GetArtifactDirectoryPath(OciArtifactReference reference) { // cachePath is already set to %userprofile%\.bicep\br or ~/.bicep/br by default depending on OS // we need to split each component of the reference into a sub directory to fit within the max file name length limit on linux and mac @@ -377,9 +383,9 @@ protected override string GetArtifactDirectoryPath(OciModuleReference reference) return Path.Combine(this.cachePath, registry, repository, tagOrDigest); } - protected override Uri GetArtifactLockFileUri(OciModuleReference reference) => this.GetModuleFileUri(reference, ModuleFileType.Lock); + protected override Uri GetArtifactLockFileUri(OciArtifactReference reference) => this.GetModuleFileUri(reference, ModuleFileType.Lock); - private async Task<(OciArtifactResult?, string? errorMessage)> TryRestoreArtifactAsync(RootConfiguration configuration, OciModuleReference reference) + private async Task<(OciArtifactResult?, string? errorMessage)> TryRestoreArtifactAsync(RootConfiguration configuration, OciArtifactReference reference) { try { @@ -414,7 +420,7 @@ protected override string GetArtifactDirectoryPath(OciModuleReference reference) private static bool CheckAllInnerExceptionsAreRequestFailures(AggregateException exception) => exception.InnerExceptions.All(inner => inner is RequestFailedException); - private Uri GetModuleFileUri(OciModuleReference reference, ModuleFileType fileType) + private Uri GetModuleFileUri(OciArtifactReference reference, ModuleFileType fileType) { string localFilePath = this.GetModuleFilePath(reference, fileType); if (Uri.TryCreate(localFilePath, UriKind.Absolute, out var uri)) @@ -425,7 +431,7 @@ private Uri GetModuleFileUri(OciModuleReference reference, ModuleFileType fileTy throw new NotImplementedException($"Local module file path is malformed: \"{localFilePath}\""); } - private string GetModuleFilePath(OciModuleReference reference, ModuleFileType fileType) + private string GetModuleFilePath(OciArtifactReference reference, ModuleFileType fileType) { var fileName = fileType switch { @@ -441,7 +447,7 @@ private string GetModuleFilePath(OciModuleReference reference, ModuleFileType fi return Path.Combine(this.GetArtifactDirectoryPath(reference), fileName); } - public override SourceArchive? TryGetSource(OciModuleReference reference) + public override SourceArchive? TryGetSource(OciArtifactReference reference) { var zipPath = GetModuleFilePath(reference, ModuleFileType.Source); if (File.Exists(zipPath)) diff --git a/src/Bicep.Core/Registry/TemplateSpecModuleRegistry.cs b/src/Bicep.Core/Registry/TemplateSpecModuleRegistry.cs index 6e304b8f124..3cfad915b9d 100644 --- a/src/Bicep.Core/Registry/TemplateSpecModuleRegistry.cs +++ b/src/Bicep.Core/Registry/TemplateSpecModuleRegistry.cs @@ -41,7 +41,7 @@ public TemplateSpecModuleRegistry(IFileResolver fileResolver, ITemplateSpecRepos public override RegistryCapabilities GetCapabilities(TemplateSpecModuleReference reference) => RegistryCapabilities.Default; - public override ResultWithDiagnostic TryParseArtifactReference(string? aliasName, string reference) + public override ResultWithDiagnostic TryParseArtifactReference(string? aliasName, string artifactType, string reference, Uri parentModuleUri) { if (!TemplateSpecModuleReference.TryParse(aliasName, reference, configuration, parentModuleUri).IsSuccess(out var @ref, out var failureBuilder)) { diff --git a/src/Bicep.Core/Semantics/Compilation.cs b/src/Bicep.Core/Semantics/Compilation.cs index 9ea6dc0b6c4..8cdeb57deb4 100644 --- a/src/Bicep.Core/Semantics/Compilation.cs +++ b/src/Bicep.Core/Semantics/Compilation.cs @@ -32,7 +32,7 @@ public Compilation( SourceFileGrouping sourceFileGrouping, IConfigurationManager configurationManager, IBicepAnalyzer linterAnalyzer, - IModuleReferenceFactory moduleReferenceFactory, + IArtifactReferenceFactory moduleReferenceFactory, ImmutableDictionary? modelLookup = null) { this.featureProviderFactory = featureProviderFactory; @@ -61,7 +61,7 @@ public Compilation( public INamespaceProvider NamespaceProvider { get; } - public IModuleReferenceFactory ModuleReferenceFactory { get; } + public IArtifactReferenceFactory ModuleReferenceFactory { get; } public SemanticModel GetEntrypointSemanticModel() // entry point semantic models are guaranteed to cast successfully diff --git a/src/Bicep.Core/Semantics/ImportedSymbol.cs b/src/Bicep.Core/Semantics/ImportedSymbol.cs index 30e6de2d659..bf466336c9e 100644 --- a/src/Bicep.Core/Semantics/ImportedSymbol.cs +++ b/src/Bicep.Core/Semantics/ImportedSymbol.cs @@ -47,7 +47,7 @@ StringSyntax @string when @string.TryGetLiteralValue() is string literalValue => public string? TryGetDescription() => TryGetExportMetadata()?.Description; public ResultWithDiagnostic TryGetModuleReference() - => Context.Compilation.ModuleReferenceFactory.TryGetModuleReference(EnclosingDeclaration, Context.SourceFile.FileUri); + => Context.Compilation.ModuleReferenceFactory.TryGetArtifactReference(EnclosingDeclaration, Context.SourceFile.FileUri); public override void Accept(SymbolVisitor visitor) { diff --git a/src/Bicep.Core/Semantics/WildcardImportSymbol.cs b/src/Bicep.Core/Semantics/WildcardImportSymbol.cs index 117db717aab..28942c22a53 100644 --- a/src/Bicep.Core/Semantics/WildcardImportSymbol.cs +++ b/src/Bicep.Core/Semantics/WildcardImportSymbol.cs @@ -39,7 +39,7 @@ public override void Accept(SymbolVisitor visitor) .TryUnwrap(); public ResultWithDiagnostic TryGetModuleReference() - => Context.Compilation.ModuleReferenceFactory.TryGetModuleReference(EnclosingDeclaration, Context.SourceFile.FileUri); + => Context.Compilation.ModuleReferenceFactory.TryGetArtifactReference(EnclosingDeclaration, Context.SourceFile.FileUri); public override IEnumerable GetDiagnostics() { diff --git a/src/Bicep.Core/Syntax/ImportSpecification.cs b/src/Bicep.Core/Syntax/ImportSpecification.cs index 9a2a1593aff..0b61a7f5b34 100644 --- a/src/Bicep.Core/Syntax/ImportSpecification.cs +++ b/src/Bicep.Core/Syntax/ImportSpecification.cs @@ -16,6 +16,10 @@ namespace Bicep.Core.Syntax { public class ImportSpecification : ISymbolNameSource { + // The setting below adds syntax highlighting for regex. + // language=regex + private const string SchemePattern = "br"; + // The setting below adds syntax highlighting for regex. // language=regex private const string NamePattern = "[a-zA-Z][a-zA-Z0-9]+"; @@ -24,18 +28,29 @@ public class ImportSpecification : ISymbolNameSource // language=regex private const string SemanticVersionPattern = @"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?"; - private static readonly Regex SpecificationPattern = new( - @$"^(?{NamePattern})@(?{SemanticVersionPattern})$", + private static readonly Regex SpecificationWithAliasPattern = new( + @$"^(?{SchemePattern})/(?{NamePattern}):(?{NamePattern})@(?{SemanticVersionPattern})$", RegexOptions.ECMAScript | RegexOptions.Compiled); + private static readonly Regex SpecificationWithoutAliasPattern = new( + @$"^(?{SchemePattern}):(?
[a-zA-Z][\w\.-]+(:\d+)?(\/[a-zA-Z0-9]*)+)?@(?{SemanticVersionPattern})$", + RegexOptions.ECMAScript | RegexOptions.Compiled); + - private ImportSpecification(string name, string version, bool isValid, TextSpan span) + private ImportSpecification(string scheme, string unqualifiedAddress, string registryAlias, string name, string version, bool isValid, TextSpan span) { + Scheme = scheme +; UnqualifiedAddress = unqualifiedAddress; + RegistryAlias = registryAlias; Name = name; Version = version; IsValid = isValid; Span = span; } + public string Scheme { get; } + private string UnqualifiedAddress { get; } + public string RegistryAlias { get; } + public string Name { get; } public string Version { get; } @@ -46,35 +61,86 @@ private ImportSpecification(string name, string version, bool isValid, TextSpan public static ImportSpecification From(SyntaxBase specificationSyntax) { - switch (specificationSyntax) + return specificationSyntax switch { - case StringSyntax stringSyntax when stringSyntax.TryGetLiteralValue() is { } value: - var (name, version, isValid) = Parse(value); - var span = isValid ? new TextSpan(stringSyntax.Span.Position + 1, name.Length) : stringSyntax.Span; - - return new ImportSpecification(name, version, isValid, span); - - case SkippedTriviaSyntax trivia: - return new ImportSpecification(trivia.TriviaName, trivia.TriviaName, false, trivia.Span); + StringSyntax stringSyntax when stringSyntax.TryGetLiteralValue() is { } value + => CreateFromStringSyntax(stringSyntax, value), + SkippedTriviaSyntax trivia + => new ImportSpecification( + trivia.TriviaName, + trivia.TriviaName, + trivia.TriviaName, + trivia.TriviaName, + trivia.TriviaName, + false, + trivia.Span), + _ + => new ImportSpecification( + LanguageConstants.ErrorName, + LanguageConstants.ErrorName, + LanguageConstants.ErrorName, + LanguageConstants.ErrorName, + LanguageConstants.ErrorName, + false, + specificationSyntax.Span) + }; + } - default: - return new ImportSpecification(LanguageConstants.ErrorName, LanguageConstants.ErrorName, false, specificationSyntax.Span); + public StringSyntax ToPath() + { + if (!this.IsValid) + { + throw new InvalidOperationException("Cannot convert invalid import specification to path."); } + return SyntaxFactory.CreateStringLiteral(this.UnqualifiedAddress); } - private static (string Name, string Version, bool IsValid) Parse(string value) + private static ImportSpecification CreateFromStringSyntax(StringSyntax stringSyntax, string value) { - var match = SpecificationPattern.Match(value); + var matchSpecificationWithAlias = SpecificationWithAliasPattern.Match(value); + if (matchSpecificationWithAlias.Success) + { + var scheme = matchSpecificationWithAlias.Groups["scheme"].Value; + var alias = matchSpecificationWithAlias.Groups["registryAlias"].Value; + var name = matchSpecificationWithAlias.Groups["name"].Value; + var version = matchSpecificationWithAlias.Groups["version"].Value; - if (!match.Success) + var address = value.Replace('@', ':'); + + var offset = value.Split('@')[0].Length; + var span = new TextSpan(stringSyntax.Span.Position + 1, offset); + + return new(scheme, address, alias, name, version, true, span); + + } + + var matchSpecificationWithoutAlias = SpecificationWithoutAliasPattern.Match(value); + if (matchSpecificationWithoutAlias.Success) { - return (LanguageConstants.ErrorName, LanguageConstants.ErrorName, false); + var scheme = matchSpecificationWithoutAlias.Groups["scheme"].Value; + var version = matchSpecificationWithoutAlias.Groups["version"].Value; + + var address = value.Replace('@', ':'); + + var offset = address.Length; + var span = new TextSpan(stringSyntax.Span.Position + 1, offset); + + var name = address.Split('/')[^1].Split(':')[0]; + + return new(scheme, address, LanguageConstants.ErrorName, name, version, true, span); } - var name = match.Groups["name"].Value; - var version = match.Groups["version"].Value; + return new( + LanguageConstants.ErrorName, + LanguageConstants.ErrorName, + LanguageConstants.ErrorName, + LanguageConstants.ErrorName, + LanguageConstants.ErrorName, + false, + stringSyntax.Span); + - return (name, version, true); } + } } diff --git a/src/Bicep.Core/Syntax/ProviderDeclarationSyntax.cs b/src/Bicep.Core/Syntax/ProviderDeclarationSyntax.cs index 02cb101c6dc..def46fe6af6 100644 --- a/src/Bicep.Core/Syntax/ProviderDeclarationSyntax.cs +++ b/src/Bicep.Core/Syntax/ProviderDeclarationSyntax.cs @@ -48,6 +48,6 @@ public ProviderDeclarationSyntax(IEnumerable leadingNodes, Token key public override void Accept(ISyntaxVisitor visitor) => visitor.VisitProviderDeclarationSyntax(this); - public SyntaxBase Path => SyntaxFactory.CreateStringLiteral($@"{OciArtifactReferenceFacts.Scheme}:{FeatureProvider.ReadEnvVar("__EXPERIMENTAL_BICEP_REGISTRY_FQDN", LanguageConstants.BicepPublicMcrRegistry)}/bicep/providers/{this.Specification.Name}:{this.Specification.Version}"); + public SyntaxBase Path => this.Specification.ToPath(); } } diff --git a/src/Bicep.Core/Workspaces/SourceFileGroupingBuilder.cs b/src/Bicep.Core/Workspaces/SourceFileGroupingBuilder.cs index 8624b08241b..5314b18fa25 100644 --- a/src/Bicep.Core/Workspaces/SourceFileGroupingBuilder.cs +++ b/src/Bicep.Core/Workspaces/SourceFileGroupingBuilder.cs @@ -21,13 +21,13 @@ namespace Bicep.Core.Workspaces public class SourceFileGroupingBuilder { private readonly IFileResolver fileResolver; - private readonly IModuleDispatcher moduleDispatcher; + private readonly IModuleDispatcher dispatcher; private readonly IReadOnlyWorkspace workspace; private readonly Dictionary> fileResultByUri; private readonly ConcurrentDictionary>> uriResultByArtifactReference; - private readonly bool forceModulesRestore; + private readonly bool forceArtifactRestore; private SourceFileGroupingBuilder( IFileResolver fileResolver, @@ -36,11 +36,11 @@ private SourceFileGroupingBuilder( bool forceModulesRestore = false) { this.fileResolver = fileResolver; - this.moduleDispatcher = moduleDispatcher; + this.dispatcher = moduleDispatcher; this.workspace = workspace; this.uriResultByArtifactReference = new(); this.fileResultByUri = new(); - this.forceModulesRestore = forceModulesRestore; + this.forceArtifactRestore = forceModulesRestore; } private SourceFileGroupingBuilder( @@ -51,11 +51,11 @@ private SourceFileGroupingBuilder( bool forceforceModulesRestore = false) { this.fileResolver = fileResolver; - this.moduleDispatcher = moduleDispatcher; + this.dispatcher = moduleDispatcher; this.workspace = workspace; this.uriResultByArtifactReference = new(current.UriResultByArtifactReference.Select(kvp => KeyValuePair.Create(kvp.Key, kvp.Value.ToDictionary(p => p.Key, p => p.Value)))); this.fileResultByUri = current.FileResultByUri.Where(x => x.Value.TryUnwrap() is not null).ToDictionary(x => x.Key, x => x.Value); - this.forceModulesRestore = forceforceModulesRestore; + this.forceArtifactRestore = forceforceModulesRestore; } public static SourceFileGrouping Build(IFileResolver fileResolver, IModuleDispatcher moduleDispatcher, IReadOnlyWorkspace workspace, Uri entryFileUri, IFeatureProviderFactory featuresFactory, bool forceModulesRestore = false) @@ -161,7 +161,7 @@ private void PopulateRecursive(BicepSourceFile file, IFeatureProviderFactory fea { continue; } - var (childModuleReference, uriResult) = GetModuleRestoreResult(file.FileUri, restorable); + var (childModuleReference, uriResult) = GetArtifactRestoreResult(file.FileUri, restorable); uriResultByArtifactReference.GetOrAdd(file, f => new())[restorable] = uriResult; @@ -181,40 +181,40 @@ private void PopulateRecursive(BicepSourceFile file, IFeatureProviderFactory fea } } - private (ArtifactReference? reference, Result result) GetModuleRestoreResult(Uri parentFileUri, IArtifactReferenceSyntax foreignTemplateReference) + private (ArtifactReference? reference, Result result) GetArtifactRestoreResult(Uri parentFileUri, IArtifactReferenceSyntax foreignTemplateReference) { - if (!moduleDispatcher.TryGetModuleReference(foreignTemplateReference, parentFileUri).IsSuccess(out var moduleReference, out var referenceResolutionError)) + if (!dispatcher.TryGetArtifactReference(foreignTemplateReference, parentFileUri).IsSuccess(out var artifactReference, out var referenceResolutionError)) { // module reference is not valid return (null, new(new UriResolutionError(referenceResolutionError, false))); } - if (!moduleDispatcher.TryGetLocalModuleEntryPointUri(moduleReference).IsSuccess(out var moduleFileUri, out var moduleGetPathFailureBuilder)) + if (!dispatcher.TryGetLocalArtifactEntryPointUri(artifactReference).IsSuccess(out var moduleFileUri, out var moduleGetPathFailureBuilder)) { - return (moduleReference, new(new UriResolutionError(moduleGetPathFailureBuilder, false))); + return (artifactReference, new(new UriResolutionError(moduleGetPathFailureBuilder, false))); } - if (forceModulesRestore) + if (forceArtifactRestore) { //override the status to force restore - return (moduleReference, new(new UriResolutionError(x => x.ModuleRequiresRestore(moduleReference.FullyQualifiedReference), true))); + return (artifactReference, new(new UriResolutionError(x => x.ModuleRequiresRestore(artifactReference.FullyQualifiedReference), true))); } - var restoreStatus = moduleDispatcher.GetArtifactRestoreStatus(moduleReference, out var restoreErrorBuilder); + var restoreStatus = dispatcher.GetArtifactRestoreStatus(artifactReference, out var restoreErrorBuilder); switch (restoreStatus) { case ArtifactRestoreStatus.Unknown: // we have not yet attempted to restore the module, so let's do it - return (moduleReference, new(new UriResolutionError(x => x.ModuleRequiresRestore(moduleReference.FullyQualifiedReference), true))); + return (artifactReference, new(new UriResolutionError(x => x.ModuleRequiresRestore(artifactReference.FullyQualifiedReference), true))); case ArtifactRestoreStatus.Failed: // the module has not yet been restored or restore failed // in either case, set the error - return (moduleReference, new(new UriResolutionError(restoreErrorBuilder ?? (x => x.ModuleRestoreFailed(moduleReference.FullyQualifiedReference)), false))); + return (artifactReference, new(new UriResolutionError(restoreErrorBuilder ?? (x => x.ModuleRestoreFailed(artifactReference.FullyQualifiedReference)), false))); default: break; } - return (moduleReference, new(moduleFileUri)); + return (artifactReference, new(moduleFileUri)); } private ILookup ReportFailuresForCycles() diff --git a/src/Bicep.LangServer.IntegrationTests/HoverTests.cs b/src/Bicep.LangServer.IntegrationTests/HoverTests.cs index 61afdf666bd..7ab581e5332 100644 --- a/src/Bicep.LangServer.IntegrationTests/HoverTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/HoverTests.cs @@ -1055,7 +1055,7 @@ private IModuleDispatcher GetModuleDispatcher( tag); var moduleDispatcher = StrictMock.Of(); - moduleDispatcher.Setup(m => m.TryGetModuleReference(moduleDeclarationSyntax, parentModuleUri)).Returns(ResultHelper.Create(ociArtifactModuleReference, null)); + moduleDispatcher.Setup(m => m.TryGetArtifactReference(moduleDeclarationSyntax, parentModuleUri)).Returns(ResultHelper.Create(ociArtifactModuleReference, null)); return moduleDispatcher.Object; } diff --git a/src/Bicep.LangServer.IntegrationTests/Registry/ModuleRestoreSchedulerTests.cs b/src/Bicep.LangServer.IntegrationTests/Registry/ModuleRestoreSchedulerTests.cs index 08f0f2f090f..e1d7736cd63 100644 --- a/src/Bicep.LangServer.IntegrationTests/Registry/ModuleRestoreSchedulerTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/Registry/ModuleRestoreSchedulerTests.cs @@ -204,7 +204,7 @@ public ResultWithDiagnostic TryGetLocalArtifactEntryPointUri(ArtifactRefere public Task TryGetDescription(ArtifactReference reference) => Task.FromResult(null); - public ResultWithDiagnostic TryParseArtifactReference(string? aliasName, string reference) + public ResultWithDiagnostic TryParseArtifactReference(string? aliasName,string artifactType, string reference, Uri parentModuleUri) { return new(new MockModuleRef(reference, PathHelper.FilePathToFileUrl(Path.GetTempFileName()))); } diff --git a/src/Bicep.LangServer.UnitTests/BicepRegistryCacheRequestHandlerTests.cs b/src/Bicep.LangServer.UnitTests/BicepRegistryCacheRequestHandlerTests.cs index 6f11eb7cea1..8208a8cbfc1 100644 --- a/src/Bicep.LangServer.UnitTests/BicepRegistryCacheRequestHandlerTests.cs +++ b/src/Bicep.LangServer.UnitTests/BicepRegistryCacheRequestHandlerTests.cs @@ -11,6 +11,7 @@ using Bicep.Core.FileSystem; using Bicep.Core.Modules; using Bicep.Core.Registry; +using Bicep.Core.Registry.Oci; using Bicep.Core.SourceCode; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Mock; @@ -39,7 +40,7 @@ public async Task InvalidModuleReferenceShouldThrow() const string ModuleRefStr = "hello"; var dispatcher = StrictMock.Of(); - dispatcher.Setup(m => m.TryGetModuleReference(ModuleRefStr, It.IsAny())).Returns(ResultHelper.Create(null as ArtifactReference, x => x.ModuleRestoreFailed("blah"))); + dispatcher.Setup(m => m.TryGetArtifactReference(ModuleRefStr, "module", It.IsAny())).Returns(ResultHelper.Create(null as ArtifactReference, x => x.ModuleRestoreFailed("blah"))); var resolver = StrictMock.Of(); @@ -64,7 +65,7 @@ public async Task LocalModuleReferenceShouldThrow() localRef.Should().NotBeNull(); ArtifactReference? outRef = localRef; - dispatcher.Setup(m => m.TryGetModuleReference(ModuleRefStr, It.IsAny())).Returns(ResultHelper.Create(outRef, failureBuilder)); + dispatcher.Setup(m => m.TryGetArtifactReference(ModuleRefStr, "module", It.IsAny())).Returns(ResultHelper.Create(outRef, failureBuilder)); var resolver = StrictMock.Of(); @@ -90,11 +91,11 @@ public async Task ExternalModuleNotInCacheShouldThrow() var configuration = IConfigurationManager.GetBuiltInConfiguration(); var parentModuleLocalPath = "/foo/main.bicep"; var parentModuleUri = new Uri($"file://{parentModuleLocalPath}"); - OciModuleReference.TryParse(null, UnqualifiedModuleRefStr, configuration, parentModuleUri).IsSuccess(out var moduleReference).Should().BeTrue(); + OciArtifactReference.TryParseModule(null, UnqualifiedModuleRefStr, configuration, parentModuleUri).IsSuccess(out var moduleReference).Should().BeTrue(); moduleReference.Should().NotBeNull(); ArtifactReference? outRef = moduleReference; - dispatcher.Setup(m => m.TryGetModuleReference(ModuleRefStr, parentModuleUri)).Returns(ResultHelper.Create(outRef, failureBuilder)); + dispatcher.Setup(m => m.TryGetArtifactReference(ModuleRefStr, "module", parentModuleUri)).Returns(ResultHelper.Create(outRef, failureBuilder)); dispatcher.Setup(m => m.GetArtifactRestoreStatus(moduleReference!, out failureBuilder)).Returns(ArtifactRestoreStatus.Unknown); var resolver = StrictMock.Of(); @@ -120,13 +121,13 @@ public async Task ExternalModuleFailedEntryPointShouldThrow() var configuration = IConfigurationManager.GetBuiltInConfiguration(); var parentModuleLocalPath = "/main.bicep"; var parentModuleUri = new Uri($"file://{parentModuleLocalPath}"); - OciModuleReference.TryParse(null, UnqualifiedModuleRefStr, configuration, parentModuleUri).IsSuccess(out var moduleReference).Should().BeTrue(); + OciArtifactReference.TryParseModule(null, UnqualifiedModuleRefStr, configuration, parentModuleUri).IsSuccess(out var moduleReference).Should().BeTrue(); moduleReference.Should().NotBeNull(); ArtifactReference? outRef = moduleReference; - dispatcher.Setup(m => m.TryGetModuleReference(ModuleRefStr, parentModuleUri)).Returns(ResultHelper.Create(outRef, failureBuilder)); + dispatcher.Setup(m => m.TryGetArtifactReference(ModuleRefStr, "module", parentModuleUri)).Returns(ResultHelper.Create(outRef, failureBuilder)); dispatcher.Setup(m => m.GetArtifactRestoreStatus(moduleReference!, out failureBuilder)).Returns(ArtifactRestoreStatus.Succeeded); - dispatcher.Setup(m => m.TryGetLocalModuleEntryPointUri(moduleReference!)).Returns(ResultHelper.Create(null as Uri, x => x.ModuleRestoreFailed("blah"))); + dispatcher.Setup(m => m.TryGetLocalArtifactEntryPointUri(moduleReference!)).Returns(ResultHelper.Create(null as Uri, x => x.ModuleRestoreFailed("blah"))); var resolver = StrictMock.Of(); @@ -155,13 +156,13 @@ public async Task FailureToReadEntryPointShouldThrow() var fileUri = new Uri("file:///main.bicep"); var configuration = IConfigurationManager.GetBuiltInConfiguration(); - OciModuleReference.TryParse(null, UnqualifiedModuleRefStr, configuration, fileUri).IsSuccess(out var moduleReference).Should().BeTrue(); + OciArtifactReference.TryParseModule(null, UnqualifiedModuleRefStr, configuration, fileUri).IsSuccess(out var moduleReference).Should().BeTrue(); moduleReference.Should().NotBeNull(); ArtifactReference? outRef = moduleReference; - dispatcher.Setup(m => m.TryGetModuleReference(ModuleRefStr, It.IsAny())).Returns(ResultHelper.Create(outRef, null)); + dispatcher.Setup(m => m.TryGetArtifactReference(ModuleRefStr, "module", It.IsAny())).Returns(ResultHelper.Create(outRef, null)); dispatcher.Setup(m => m.GetArtifactRestoreStatus(moduleReference!, out nullBuilder)).Returns(ArtifactRestoreStatus.Succeeded); - dispatcher.Setup(m => m.TryGetLocalModuleEntryPointUri(moduleReference!)).Returns(ResultHelper.Create(fileUri, null)); + dispatcher.Setup(m => m.TryGetLocalArtifactEntryPointUri(moduleReference!)).Returns(ResultHelper.Create(fileUri, null)); SourceArchive? sourceArchive = null; dispatcher.Setup(m => m.TryGetModuleSources(moduleReference!)).Returns(sourceArchive); @@ -195,13 +196,13 @@ public async Task RestoredValidModule_WithNoSources_ShouldReturnJsonContents() var fileUri = new Uri("file:///foo/bar/main.bicep"); var configuration = ConfigurationManager.GetConfiguration(fileUri); - OciModuleReference.TryParse(null, UnqualifiedModuleRefStr, configuration, fileUri).IsSuccess(out var moduleReference).Should().BeTrue(); + OciArtifactReference.TryParseModule(null, UnqualifiedModuleRefStr, configuration, fileUri).IsSuccess(out var moduleReference).Should().BeTrue(); moduleReference.Should().NotBeNull(); ArtifactReference? outRef = moduleReference; - dispatcher.Setup(m => m.TryGetModuleReference(ModuleRefStr, It.IsAny())).Returns(ResultHelper.Create(outRef, null)); + dispatcher.Setup(m => m.TryGetArtifactReference(ModuleRefStr, "module", It.IsAny())).Returns(ResultHelper.Create(outRef, null)); dispatcher.Setup(m => m.GetArtifactRestoreStatus(moduleReference!, out nullBuilder)).Returns(ArtifactRestoreStatus.Succeeded); - dispatcher.Setup(m => m.TryGetLocalModuleEntryPointUri(moduleReference!)).Returns(ResultHelper.Create(fileUri, null)); + dispatcher.Setup(m => m.TryGetLocalArtifactEntryPointUri(moduleReference!)).Returns(ResultHelper.Create(fileUri, null)); SourceArchive? sourceArchive = null; dispatcher.Setup(m => m.TryGetModuleSources(moduleReference!)).Returns(sourceArchive); @@ -234,13 +235,13 @@ public async Task RestoredValidModule_WithSource_ShouldReturnBicepContents() var fileUri = new Uri("file:///foo/bar/main.bicep"); var configuration = ConfigurationManager.GetConfiguration(fileUri); - OciModuleReference.TryParse(null, UnqualifiedModuleRefStr, configuration, fileUri).IsSuccess(out var moduleReference).Should().BeTrue(); + OciArtifactReference.TryParseModule(null, UnqualifiedModuleRefStr, configuration, fileUri).IsSuccess(out var moduleReference).Should().BeTrue(); moduleReference.Should().NotBeNull(); ArtifactReference? outRef = moduleReference; - dispatcher.Setup(m => m.TryGetModuleReference(ModuleRefStr, It.IsAny())).Returns(ResultHelper.Create(outRef, null)); + dispatcher.Setup(m => m.TryGetArtifactReference(ModuleRefStr, "module", It.IsAny())).Returns(ResultHelper.Create(outRef, null)); dispatcher.Setup(m => m.GetArtifactRestoreStatus(moduleReference!, out nullBuilder)).Returns(ArtifactRestoreStatus.Succeeded); - dispatcher.Setup(m => m.TryGetLocalModuleEntryPointUri(moduleReference!)).Returns(ResultHelper.Create(fileUri, null)); + dispatcher.Setup(m => m.TryGetLocalArtifactEntryPointUri(moduleReference!)).Returns(ResultHelper.Create(fileUri, null)); var bicepSource = "metadata hi 'mom'"; var sourceArchive = SourceArchive.FromStream(SourceArchive.PackSourcesIntoStream(fileUri, new Core.Workspaces.ISourceFile[] { diff --git a/src/Bicep.LangServer/Handlers/BicepDefinitionHandler.cs b/src/Bicep.LangServer/Handlers/BicepDefinitionHandler.cs index 1f744960a45..4484f774233 100644 --- a/src/Bicep.LangServer/Handlers/BicepDefinitionHandler.cs +++ b/src/Bicep.LangServer/Handlers/BicepDefinitionHandler.cs @@ -115,7 +115,7 @@ private LocationOrLocationLinks HandleUnboundSymbolLocation(DefinitionParams req && matchingNodes[^3] is ModuleDeclarationSyntax moduleDeclarationSyntax && matchingNodes[^2] is StringSyntax stringToken && context.Compilation.SourceFileGrouping.TryGetSourceFile(moduleDeclarationSyntax).IsSuccess(out var sourceFile) - && this.moduleDispatcher.TryGetModuleReference(moduleDeclarationSyntax, request.TextDocument.Uri.ToUriEncoded()).IsSuccess(out var moduleReference)) + && this.moduleDispatcher.TryGetArtifactReference(moduleDeclarationSyntax, request.TextDocument.Uri.ToUriEncoded()).IsSuccess(out var moduleReference)) { return HandleModuleReference(context, stringToken, sourceFile, moduleReference); } @@ -128,7 +128,7 @@ private LocationOrLocationLinks HandleUnboundSymbolLocation(DefinitionParams req && matchingNodes[^4] is CompileTimeImportDeclarationSyntax importDeclarationSyntax && matchingNodes[^2] is StringSyntax stringToken && context.Compilation.SourceFileGrouping.TryGetSourceFile(importDeclarationSyntax).IsSuccess(out var sourceFile) - && this.moduleDispatcher.TryGetModuleReference(importDeclarationSyntax, request.TextDocument.Uri.ToUriEncoded()).IsSuccess(out var moduleReference)) + && this.moduleDispatcher.TryGetArtifactReference(importDeclarationSyntax, request.TextDocument.Uri.ToUriEncoded()).IsSuccess(out var moduleReference)) { // goto beginning of the module file. return GetFileDefinitionLocation( diff --git a/src/Bicep.LangServer/Handlers/BicepHoverHandler.cs b/src/Bicep.LangServer/Handlers/BicepHoverHandler.cs index 0215512fcd7..557be49de4c 100644 --- a/src/Bicep.LangServer/Handlers/BicepHoverHandler.cs +++ b/src/Bicep.LangServer/Handlers/BicepHoverHandler.cs @@ -193,7 +193,7 @@ private static async Task GetModuleMarkdown( if (registries != null && registries.Any() && - moduleDispatcher.TryGetModuleReference(module.DeclaringModule, uri).IsSuccess(out var moduleReference) && + moduleDispatcher.TryGetArtifactReference(module.DeclaringModule, uri).IsSuccess(out var moduleReference) && moduleReference is not null) { var registry = registries.FirstOrDefault(r => r.Scheme == moduleReference.Scheme); diff --git a/src/Bicep.LangServer/Handlers/BicepRegistryCacheRequestHandler.cs b/src/Bicep.LangServer/Handlers/BicepRegistryCacheRequestHandler.cs index 796fa2a982e..765a8ecd5f2 100644 --- a/src/Bicep.LangServer/Handlers/BicepRegistryCacheRequestHandler.cs +++ b/src/Bicep.LangServer/Handlers/BicepRegistryCacheRequestHandler.cs @@ -48,7 +48,7 @@ public Task Handle(BicepRegistryCacheParams request, // it indicates a code defect client or server-side. // In normal operation, the user should never see them regardless of how malformed their code is. - if (!moduleDispatcher.TryGetModuleReference(request.Target, request.TextDocument.Uri.ToUriEncoded()).IsSuccess(out var moduleReference)) + if (!moduleDispatcher.TryGetArtifactReference(request.Target, "TODO", request.TextDocument.Uri.ToUriEncoded()).IsSuccess(out var moduleReference)) { throw new InvalidOperationException( $"The client specified an invalid module reference '{request.Target}'."); @@ -66,7 +66,7 @@ public Task Handle(BicepRegistryCacheParams request, $"The module '{moduleReference.FullyQualifiedReference}' has not yet been successfully restored."); } - if (!moduleDispatcher.TryGetLocalModuleEntryPointUri(moduleReference).IsSuccess(out var uri)) + if (!moduleDispatcher.TryGetLocalArtifactEntryPointUri(moduleReference).IsSuccess(out var uri)) { throw new InvalidOperationException( $"Unable to obtain the entry point URI for module '{moduleReference.FullyQualifiedReference}'.");