Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add foundation for extension config assignment syntax in bicepparam files and extensionConfigs on module declarations #16565

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Bicep.Cli.IntegrationTests/BuildParamsCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using Bicep.Cli.Models;
using Bicep.Cli.UnitTests.Assertions;
using Bicep.Core.Configuration;
using Bicep.Core.Extensions;
using Bicep.Core.Registry;
using Bicep.Core.Samples;
using Bicep.Core.UnitTests;
Expand Down Expand Up @@ -291,6 +290,7 @@ public async Task Build_Valid_Params_File_Should_Succeed(BaselineData_Bicepparam
AssertNoErrors(error);
}

data.Compiled.Should().NotBeNull();
data.Compiled!.ShouldHaveExpectedJsonValue();
}

Expand All @@ -309,6 +309,7 @@ public async Task Build_Valid_Params_File_To_Outdir_Should_Succeed(BaselineData_
AssertNoErrors(error);
}

data.Compiled.Should().NotBeNull();
data.Compiled!.ReadFromOutputFolder().Should().OnlyContainLFNewline();
data.Compiled!.ShouldHaveExpectedJsonValue();
}
Expand All @@ -332,6 +333,7 @@ public async Task Build_Valid_Params_File_ToStdOut_Should_Succeed(BaselineData_B
var parametersStdout = output.FromJson<BuildParamsStdout>();
parametersStdout.parametersJson.Should().OnlyContainLFNewline();

data.Compiled.Should().NotBeNull();
data.Compiled!.WriteToOutputFolder(parametersStdout.parametersJson);
data.Compiled.ShouldHaveExpectedJsonValue();
}
Expand Down Expand Up @@ -562,7 +564,7 @@ param intParam int
param intParam = 42
""";


var fileResults = new[]
{
(input: "file1.bicepparam", expectOutput: true),
Expand Down
236 changes: 234 additions & 2 deletions src/Bicep.Core.IntegrationTests/ExtensibilityTests.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Bicep.Core.CodeAction;
using Bicep.Core.Configuration;
using Bicep.Core.Diagnostics;
using Bicep.Core.IntegrationTests.Extensibility;
using Bicep.Core.UnitTests;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Utils;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;

Expand All @@ -29,6 +27,8 @@ public class ExtensibilityTests : TestBase
"""))
.WithNamespaceProvider(TestExtensibilityNamespaceProvider.CreateWithDefaults());

private static ServiceBuilder ServicesWithModuleExtensionConfigs => Services.WithFeatureOverrides(new(ExtensibilityEnabled: true, ModuleExtensionConfigsEnabled: true));

[TestMethod]
public void Bar_import_bad_config_is_blocked()
{
Expand Down Expand Up @@ -656,5 +656,237 @@ extension foo as foo
result.Template.Should().HaveValueAtPath("$.extensions.foo.name", "Foo");
result.Template.Should().HaveValueAtPath("$.resources.myApp.extension", "foo");
}

[TestMethod]
public void Module_with_required_extension_config_can_be_compiled_successfully()
{
var paramsUri = new Uri("file:///main.bicepparam");
var mainUri = new Uri("file:///main.bicep");
var moduleAUri = new Uri("file:///modulea.bicep");

// TODO(kylealbert): Remove 'with' clause in template when that's removed
var files = new Dictionary<Uri, string>
{
[paramsUri] =
"""
using 'main.bicep'

param inputa = 'abc'

extension k8s with {
kubeConfig: 'abc'
namespace: 'other'
}
""",
[mainUri] =
"""
param inputa string

extension kubernetes with {
kubeConfig: 'DELETE'
namespace: 'DELETE'
} as k8s

extension 'br:mcr.microsoft.com/bicep/extensions/microsoftgraph/v1.0:0.1.8-preview'

module modulea 'modulea.bicep' = {
name: 'modulea'
params: {
inputa: inputa
}
extensionConfigs: {
kubernetes: {
kubeConfig: 'fromModule'
namespace: 'other'
}
}
}

output outputa string = modulea.outputs.outputa
""",
[moduleAUri] =
"""
param inputa string

extension kubernetes with {
kubeConfig: 'DELETE'
namespace: 'DELETE'
}

extension 'br:mcr.microsoft.com/bicep/extensions/microsoftgraph/v1.0:0.1.8-preview' as graph

output outputa string = inputa
"""
};

var compilation = ServicesWithModuleExtensionConfigs.BuildCompilation(files, paramsUri);

compilation.Should().NotHaveAnyDiagnostics_WithAssertionScoping(d => d.IsError());
}

[DataTestMethod]
[DataRow("MissingExtensionConfigsDeclaration")]
[DataRow("MissingRequiredExtensionConfig")]
[DataRow("MissingRequiredConfigProperty")]
[DataRow("PropertyIsNotDefinedInSchema")]
[DataRow("ConfigProvidedForExtensionThatDoesNotAcceptConfig")]
public void Module_with_invalid_extension_config_produces_diagnostic(string scenario)
{
var mainUri = new Uri("file:///main.bicep");
var moduleAUri = new Uri("file:///modulea.bicep");

var extensionConfigsStr = scenario switch
{
"MissingExtensionConfigsDeclaration" => "",
"MissingRequiredExtensionConfig" => "extensionConfigs: {}",
"MissingRequiredConfigProperty" => "extensionConfigs: { kubernetes: { namespace: 'other' } }",
"PropertyIsNotDefinedInSchema" => "extensionConfigs: { kubernetes: { kubeConfig: 'test', namespace: 'other', extra: 'extra' } }",
"ConfigProvidedForExtensionThatDoesNotAcceptConfig" => "extensionConfigs: { kubernetes: { kubeConfig: 'test', namespace: 'other' }, graph: { } }",
_ => throw new NotImplementedException()
};

// TODO(kylealbert): Remove 'with' clause in template when that's removed
var files = new Dictionary<Uri, string>
{
[mainUri] =
$$"""
param inputa string

module modulea 'modulea.bicep' = {
name: 'modulea'
params: {
inputa: inputa
}
{{extensionConfigsStr}}
}

output outputa string = modulea.outputs.outputa
""",
[moduleAUri] =
"""
param inputa string

extension kubernetes with {
kubeConfig: ''
namespace: 'default'
}

extension microsoftGraph as graph

output outputa string = inputa
"""
};

var compilation = ServicesWithModuleExtensionConfigs.BuildCompilation(files, mainUri);

if (scenario is "MissingExtensionConfigsDeclaration")
{
compilation.Should().ContainSingleDiagnostic("BCP035", DiagnosticLevel.Error, """The specified "module" declaration is missing the following required properties: "extensionConfigs".""");
}
else if (scenario is "MissingRequiredExtensionConfig")
{
compilation.Should().ContainSingleDiagnostic("BCP035", DiagnosticLevel.Error, """The specified "object" declaration is missing the following required properties: "kubernetes".""");
}
else if (scenario is "MissingRequiredConfigProperty")
{
compilation.Should().ContainSingleDiagnostic("BCP035", DiagnosticLevel.Error, """The specified "object" declaration is missing the following required properties: "kubeConfig".""");
}
else if (scenario is "PropertyIsNotDefinedInSchema")
{
compilation.Should().ContainSingleDiagnostic("BCP037", DiagnosticLevel.Error, """The property "extra" is not allowed on objects of type "configuration". Permissible properties include "context".""");
}
else if (scenario is "ConfigProvidedForExtensionThatDoesNotAcceptConfig")
{
compilation.Should().ContainSingleDiagnostic("BCP037", DiagnosticLevel.Error, """The property "graph" is not allowed on objects of type "extensionConfigs". No other properties are allowed.""");
}
}

[DataTestMethod]
[DataRow("ParamsFile")]
[DataRow("MainFile")]
public void Extension_config_assignments_raise_error_diagnostic_if_expr_feature_disabled(string scenario)
{
var paramsUri = new Uri("file:///main.bicepparam");
var mainUri = new Uri("file:///main.bicep");
var moduleAUri = new Uri("file:///modulea.bicep");

// TODO(kylealbert): Remove 'with' clause in template when that's removed
var files = new Dictionary<Uri, string>
{
[paramsUri] =
"""
using 'main.bicep'

param inputa = 'abc'

extension k8s with {
kubeConfig: 'abc'
namespace: 'other'
}
""",
[mainUri] =
"""
param inputa string

extension kubernetes with {
kubeConfig: 'DELETE'
namespace: 'DELETE'
} as k8s

extension 'br:mcr.microsoft.com/bicep/extensions/microsoftgraph/v1.0:0.1.8-preview'

module modulea 'modulea.bicep' = {
name: 'modulea'
params: {
inputa: inputa
}
extensionConfigs: {
kubernetes: {
kubeConfig: 'fromModule'
namespace: 'other'
}
}
}

output outputa string = modulea.outputs.outputa
""",
[moduleAUri] =
"""
param inputa string

extension kubernetes with {
kubeConfig: 'DELETE'
namespace: 'DELETE'
}

extension 'br:mcr.microsoft.com/bicep/extensions/microsoftgraph/v1.0:0.1.8-preview' as graph

output outputa string = inputa
"""
};

if (scenario is "ParamsFile")
{
files[mainUri] = files[moduleAUri];
files.Remove(moduleAUri);
}

var compilation = Services.BuildCompilation(files, paramsUri);

var diagByFile = compilation.GetAllDiagnosticsByBicepFileUri();

if (scenario is "ParamsFile")
{
diagByFile[paramsUri].Should().ContainDiagnostic(f => f.UnrecognizedParamsFileDeclaration());
}
else if (scenario is "MainFile")
{
diagByFile[mainUri].Should().ContainDiagnostic("BCP037", DiagnosticLevel.Error, """The property "extensionConfigs" is not allowed on objects of type "module". Permissible properties include "dependsOn", "scope".""");
}
else
{
Assert.Fail($"No assertion for scenario {scenario}");
}
}
}
}
Loading
Loading