Skip to content

Commit

Permalink
Implement provider declaration aliasing (#12127)
Browse files Browse the repository at this point in the history
# Overview

Implementation of #11598


###### Microsoft Reviewers: [Open in
CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/Azure/bicep/pull/12127)

---------

Co-authored-by: asilverman <[email protected]>
  • Loading branch information
asilverman and asilverman authored Oct 23, 2023
1 parent e002746 commit 7bccda3
Show file tree
Hide file tree
Showing 57 changed files with 720 additions and 360 deletions.
56 changes: 42 additions & 14 deletions src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,21 @@ 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 (clientFactory, blobClients) = DataSetsExtensions.CreateMockRegistryClients(false, (registryUri, repository));
var myClient = blobClients[(registryUri, repository)];
var mcrUri = new Uri($"https://{LanguageConstants.BicepPublicMcrRegistry}");
var registyUris = new[] { mcrUri };
var repository = "bicep/providers/az";
var (clientFactory, blobClients) = DataSetsExtensions.CreateMockRegistryClients(false, (mcrUri, repository));
var myClient = blobClients[(mcrUri, repository)];

// 2. upload a manifest and its blob layer
var manifestStr = $$"""
Expand Down Expand Up @@ -142,14 +148,30 @@ 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);
var bicepFilePath = Path.Combine(tempDirectory, "main.bicep");
File.WriteAllText(bicepFilePath, bicepFile);


var bicepConfigFile = $$"""
{
"providerAliases" : {
"br": {
"contoso": {
"registry": "mcr.microsoft.com",
"providerPath": "bicep/providers"
}
}
}
}
""";
var bicepConfigPath = Path.Combine(tempDirectory, "bicepconfig.json");
File.WriteAllText(bicepConfigPath, bicepConfigFile);

// 4. create a settings object with the mock registry client and relevant features enabled
var settings = new InvocationSettings(new(TestContext, RegistryEnabled: true, ExtensibilityEnabled: true, DynamicTypeLoading: true), clientFactory.Object, Repository.Create<ITemplateSpecRepositoryFactory>().Object);

Expand All @@ -161,14 +183,20 @@ 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);
if (shouldSucceed)
{
AssertNoErrors(error);
}
}
if (shouldSucceed)
{
// 7. assert the provider files were restored to the cache directory
Directory.Exists(settings.FeatureOverrides.CacheRootDirectory).Should().BeTrue();
var providerDir = Path.Combine(settings.FeatureOverrides.CacheRootDirectory!, ModuleReferenceSchemes.Oci, LanguageConstants.BicepPublicMcrRegistry, "bicep$providers$az", "2.0.0$");
Directory.EnumerateFiles(providerDir).ToList().Select(Path.GetFileName).Should().BeEquivalentTo(new List<string> { "types.tgz", "lock", "manifest", "metadata" });
}
// 7. assert the provider files were restored to the cache directory
Directory.Exists(settings.FeatureOverrides.CacheRootDirectory).Should().BeTrue();
var providerDir = Path.Combine(settings.FeatureOverrides.CacheRootDirectory!, ModuleReferenceSchemes.Oci, LanguageConstants.BicepPublicMcrRegistry, "bicep$providers$az", "2.0.0$");
Directory.EnumerateFiles(providerDir).ToList().Select(Path.GetFileName).Should().BeEquivalentTo(new List<string> { "types.tgz", "lock", "manifest", "metadata" });
}

[DataTestMethod]
Expand Down
2 changes: 1 addition & 1 deletion src/Bicep.Cli/Commands/PublishCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,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(ArtifactType.Module, targetModuleReference, 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;
Expand Down
4 changes: 2 additions & 2 deletions src/Bicep.Core.IntegrationTests/ExtensibilityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ param connectionString string
public void Az_namespace_can_be_used_without_configuration()
{
var result = CompilationHelper.Compile(Services, @"
import '[email protected]'
import 'br/public:[email protected]'
");

result.Should().GenerateATemplate();
Expand All @@ -635,7 +635,7 @@ public void Az_namespace_can_be_used_without_configuration()
public void Az_namespace_errors_with_configuration()
{
var result = CompilationHelper.Compile(Services, @"
import '[email protected]' with {}
import 'br/public:[email protected]' with {}
");

result.Should().NotGenerateATemplate();
Expand Down
20 changes: 10 additions & 10 deletions src/Bicep.Core.IntegrationTests/ImportTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public TestNamespaceProvider(Dictionary<string, Func<string, NamespaceType>> bui
public void Imports_are_disabled_unless_feature_is_enabled()
{
var result = CompilationHelper.Compile(@"
import '[email protected]'
import 'br/public:[email protected]'
");
result.Should().HaveDiagnostics(new[] {
("BCP203", DiagnosticLevel.Error, "Using import statements requires enabling EXPERIMENTAL feature \"Extensibility\"."),
Expand All @@ -74,7 +74,7 @@ public void Import_statement_parse_diagnostics_are_guiding()
});

result = CompilationHelper.Compile(ServicesWithImports, @"
import '[email protected]' blahblah
import 'br/public:[email protected]' blahblah
");
result.Should().HaveDiagnostics(new[] {
("BCP305", DiagnosticLevel.Error, "Expected the \"with\" keyword, \"as\" keyword, or a new line character at this location."),
Expand Down Expand Up @@ -108,7 +108,7 @@ public void Import_statement_parse_diagnostics_are_guiding()
});

result = CompilationHelper.Compile(ServicesWithImports, @"
import '[email protected]' as
import 'br/public:[email protected]' as
");
result.Should().HaveDiagnostics(new[] {
("BCP202", DiagnosticLevel.Error, "Expected an import alias name at this location."),
Expand All @@ -130,7 +130,7 @@ public void Imports_return_error_with_unrecognized_namespace()
public void Import_configuration_is_blocked_by_default()
{
var result = CompilationHelper.Compile(ServicesWithImports, @"
import '[email protected]' with {
import 'br/public:[email protected]' with {
foo: 'bar'
}
");
Expand All @@ -143,7 +143,7 @@ public void Import_configuration_is_blocked_by_default()
public void Using_import_statements_frees_up_the_namespace_symbol()
{
var result = CompilationHelper.Compile(ServicesWithImports, @"
import '[email protected]' as newAz
import 'br/public:[email protected]' as newAz
var az = 'Fake AZ!'
var myRg = newAz.resourceGroup()
Expand All @@ -159,7 +159,7 @@ public void Using_import_statements_frees_up_the_namespace_symbol()
public void You_can_swap_imported_namespaces_if_you_really_really_want_to()
{
var result = CompilationHelper.Compile(ServicesWithImports, @"
import '[email protected]' as sys
import 'br/public:[email protected]' as sys
import '[email protected]' as az
var myRg = sys.resourceGroup()
Expand All @@ -176,7 +176,7 @@ public void You_can_swap_imported_namespaces_if_you_really_really_want_to()
public void Overwriting_single_built_in_namespace_with_import_is_prohibited()
{
var result = CompilationHelper.Compile(ServicesWithImports, @"
import '[email protected]' as sys
import 'br/public:[email protected]' as sys
var myRg = sys.resourceGroup()
Expand All @@ -190,8 +190,8 @@ public void Overwriting_single_built_in_namespace_with_import_is_prohibited()
public void Singleton_imports_cannot_be_used_multiple_times()
{
var result = CompilationHelper.Compile(ServicesWithImports, @"
import '[email protected]' as az1
import '[email protected]' as az2
import 'br/public:[email protected]' as az1
import 'br/public:[email protected]' as az2
import '[email protected]' as sys1
import '[email protected]' as sys2
Expand All @@ -209,7 +209,7 @@ public void Singleton_imports_cannot_be_used_multiple_times()
public void Import_names_must_not_conflict_with_other_symbols()
{
var result = CompilationHelper.Compile(ServicesWithImports, @"
import '[email protected]'
import 'br/public:[email protected]'
import '[email protected]' with {
kubeConfig: ''
namespace: ''
Expand Down
10 changes: 5 additions & 5 deletions src/Bicep.Core.IntegrationTests/RegistryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -334,7 +334,7 @@ public async Task ForceModuleRestoreWithStuckFileLockShouldFailAfterTimeout(IEnu
// let's try to restore a module while holding a lock
using (@lock)
{
(await dispatcher.RestoreModules(moduleReferences, forceModulesRestore: true)).Should().BeTrue();
(await dispatcher.RestoreModules(moduleReferences, forceRestore: true)).Should().BeTrue();
}

// REF: FileLockTests.cs/FileLockShouldNotThrowIfLockFileIsDeleted()
Expand Down Expand Up @@ -395,14 +395,14 @@ public async Task ForceModuleRestoreShouldRestoreAllModules(IEnumerable<External
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;
var moduleDirectory = Path.GetDirectoryName(moduleFilePath)!;
Directory.CreateDirectory(moduleDirectory);

(await dispatcher.RestoreModules(moduleReferences, forceModulesRestore: true)).Should().BeTrue();
(await dispatcher.RestoreModules(moduleReferences, forceRestore: true)).Should().BeTrue();

// all other modules should have succeeded
foreach (var moduleReference in moduleReferences)
Expand Down
15 changes: 8 additions & 7 deletions src/Bicep.Core.Samples/DataSetsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Threading.Tasks;
using Bicep.Core.Configuration;
using Bicep.Core.FileSystem;
using Bicep.Core.Registry.Oci;
using Bicep.Core.Modules;
using Bicep.Core.Registry;
using Bicep.Core.Semantics;
Expand Down Expand Up @@ -72,12 +73,12 @@ public static Mock<IContainerRegistryClientFactory> CreateMockRegistryClients(Im
{
var target = publishInfo.Metadata.Target;

if (!dispatcher.TryGetModuleReference(target, RandomFileUri()).IsSuccess(out var @ref) || @ref is not OciModuleReference targetReference)
if (!dispatcher.TryGetArtifactReference(ArtifactType.Module, target, 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.");
}

Uri registryUri = new Uri($"https://{targetReference.Registry}");
Uri registryUri = new($"https://{targetReference.Registry}");
clients.Add((registryUri, targetReference.Repository));
}

Expand All @@ -94,9 +95,9 @@ public static (Mock<IContainerRegistryClientFactory> factoryMock, ImmutableDicti
.AddSingleton(featureProviderFactory)
).Construct<IModuleDispatcher>();

foreach (var client in clients)
foreach (var (registryUri, repository) in clients)
{
clientsBuilder.TryAdd((client.registryUri, client.repository), new MockRegistryBlobClient());
clientsBuilder.TryAdd((registryUri, repository), new MockRegistryBlobClient());
}

var repoToClient = clientsBuilder.ToImmutable();
Expand Down Expand Up @@ -133,7 +134,7 @@ public static (Mock<IContainerRegistryClientFactory> factoryMock, ImmutableDicti
public static ITemplateSpecRepositoryFactory CreateEmptyTemplateSpecRepositoryFactory(bool enablePublishSource = false)
=> CreateMockTemplateSpecRepositoryFactory(ImmutableDictionary<string, DataSet.ExternalModuleInfo>.Empty, enablePublishSource);

public static ITemplateSpecRepositoryFactory CreateMockTemplateSpecRepositoryFactory(this DataSet dataSet, TestContext testContext, bool enablePublishSource = false)
public static ITemplateSpecRepositoryFactory CreateMockTemplateSpecRepositoryFactory(this DataSet dataSet, TestContext _, bool enablePublishSource = false)
=> CreateMockTemplateSpecRepositoryFactory(dataSet.TemplateSpecs, enablePublishSource);

public static ITemplateSpecRepositoryFactory CreateMockTemplateSpecRepositoryFactory(ImmutableDictionary<string, DataSet.ExternalModuleInfo> templateSpecs, bool enablePublishSource = false)
Expand All @@ -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(ArtifactType.Module, templateSpecInfo.Metadata.Target, 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.");
}
Expand Down Expand Up @@ -190,7 +191,7 @@ public static async Task PublishModuleToRegistryAsync(IContainerRegistryClientFa
.AddSingleton(featureProviderFactory)
).Construct<IModuleDispatcher>();

var targetReference = dispatcher.TryGetModuleReference(target, RandomFileUri()).IsSuccess(out var @ref) ? @ref
var targetReference = dispatcher.TryGetArtifactReference(ArtifactType.Module, target, 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import '[email protected]'
import 'br/public:[email protected]'

output str string = 'foo'
1 change: 1 addition & 0 deletions src/Bicep.Core.UnitTests/BicepTestConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public static RootConfiguration CreateMockConfiguration(Dictionary<string, objec
["cloud.profiles.AzureCloud.activeDirectoryAuthority"] = "https://example.invalid",
["cloud.credentialPrecedence"] = new[] { "AzureCLI", "AzurePowerShell" },
["moduleAliases"] = new Dictionary<string, object>(),
["providerAliases"] = new Dictionary<string, object>(),
["analyzers"] = new Dictionary<string, object>(),
["experimentalFeaturesEnabled"] = new Dictionary<string, bool>(),
["formatting"] = new Dictionary<string, bool>(),
Expand Down
Loading

0 comments on commit 7bccda3

Please sign in to comment.