Skip to content

Commit 27cc8db

Browse files
authored
Add stacks extensibility linting rule. Restrict syntax on config assignments. (#17654)
## Description - Adds `stacks-extensibility-compat` linter rule. Defaults to `Info` level. Flags non-key vault references for secure extension config properties as well as key vault references for non-secure properties. - Add syntax restriction diagnostics for unsupported scenarios (object spreads, ternaries, invalid references) - Migrate main baselines and params baselines to use the newer `TestExternalArtifactManager`, as well as supply some mock extensions by default to test different config schemas. - Update tests and baselines with additional scenarios. - Update the `moduleExtensionConfigs` experimental flag doc. It should be ready for use now. ## Example Usage ## Checklist - [x] I have read and adhere to the [contribution guide](https://github.com/Azure/bicep/blob/main/CONTRIBUTING.md). ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/Azure/bicep/pull/17654)
1 parent 26d81bd commit 27cc8db

File tree

88 files changed

+4542
-1483
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+4542
-1483
lines changed

src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,21 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
using System.IO.Abstractions;
54
using System.IO.Abstractions.TestingHelpers;
6-
using System.Text.RegularExpressions;
7-
using Bicep.Cli.UnitTests;
85
using Bicep.Core;
96
using Bicep.Core.Configuration;
107
using Bicep.Core.FileSystem;
11-
using Bicep.Core.Modules;
128
using Bicep.Core.Registry;
139
using Bicep.Core.Samples;
1410
using Bicep.Core.UnitTests;
1511
using Bicep.Core.UnitTests.Assertions;
16-
using Bicep.Core.UnitTests.Baselines;
12+
using Bicep.Core.UnitTests.Features;
1713
using Bicep.Core.UnitTests.Mock;
1814
using Bicep.Core.UnitTests.Registry;
1915
using Bicep.Core.UnitTests.Utils;
20-
using Bicep.IO.FileSystem;
16+
using Bicep.TextFixtures.Utils;
2117
using FluentAssertions;
2218
using FluentAssertions.Execution;
23-
using Microsoft.Extensions.DependencyInjection;
2419
using Microsoft.VisualStudio.TestTools.UnitTesting;
2520
using Moq;
2621
using Newtonsoft.Json.Linq;
@@ -65,12 +60,15 @@ public async Task Build_NonBicepFiles_ShouldFail_WithExpectedErrorMessage()
6560
public async Task Build_Valid_SingleFile_WithTemplateSpecReference_ShouldSucceed(DataSet dataSet)
6661
{
6762
var outputDirectory = dataSet.SaveFilesToTestDirectory(TestContext);
68-
var clientFactory = dataSet.CreateMockRegistryClients();
69-
var templateSpecRepositoryFactory = dataSet.CreateMockTemplateSpecRepositoryFactory(TestContext);
70-
await dataSet.PublishModulesToRegistryAsync(clientFactory);
71-
var bicepFilePath = Path.Combine(outputDirectory, DataSet.TestFileMain);
63+
var features = new FeatureProviderOverrides(TestContext);
64+
FileHelper.GetCacheRootDirectory(TestContext).EnsureExists();
65+
66+
var artifactManager = new TestExternalArtifactManager(TestCompiler.ForMockFileSystemCompilation().WithFeatureOverrides<FeatureProviderOverrides, OverriddenFeatureProviderFactory>(features));
67+
await dataSet.PublishAllDataSetArtifacts(artifactManager, publishSource: true);
7268

73-
var settings = new InvocationSettings(new(TestContext, RegistryEnabled: dataSet.HasExternalModules), clientFactory, templateSpecRepositoryFactory);
69+
var settings = new InvocationSettings(features).WithArtifactManager(artifactManager, TestContext);
70+
71+
var bicepFilePath = Path.Combine(outputDirectory, DataSet.TestFileMain);
7472
var (output, error, result) = await Bicep(settings, "build", bicepFilePath);
7573

7674
using (new AssertionScope())
@@ -108,12 +106,15 @@ public async Task Build_Valid_SingleFile_WithTemplateSpecReference_ShouldSucceed
108106
public async Task Build_Valid_SingleFile_WithTemplateSpecReference_ToStdOut_ShouldSucceed(DataSet dataSet)
109107
{
110108
var outputDirectory = dataSet.SaveFilesToTestDirectory(TestContext);
111-
var clientFactory = dataSet.CreateMockRegistryClients();
112-
var templateSpecRepositoryFactory = dataSet.CreateMockTemplateSpecRepositoryFactory(TestContext);
113-
await dataSet.PublishModulesToRegistryAsync(clientFactory);
114-
var bicepFilePath = Path.Combine(outputDirectory, DataSet.TestFileMain);
109+
var features = new FeatureProviderOverrides(TestContext);
110+
FileHelper.GetCacheRootDirectory(TestContext).EnsureExists();
111+
112+
var artifactManager = new TestExternalArtifactManager(TestCompiler.ForMockFileSystemCompilation().WithFeatureOverrides<FeatureProviderOverrides, OverriddenFeatureProviderFactory>(features));
113+
await dataSet.PublishAllDataSetArtifacts(artifactManager, publishSource: true);
115114

116-
var settings = new InvocationSettings(new(TestContext, RegistryEnabled: dataSet.HasExternalModules), clientFactory, templateSpecRepositoryFactory);
115+
var settings = new InvocationSettings(features).WithArtifactManager(artifactManager, TestContext);
116+
117+
var bicepFilePath = Path.Combine(outputDirectory, DataSet.TestFileMain);
117118

118119
var (output, error, result) = await Bicep(settings, "build", "--stdout", bicepFilePath);
119120

@@ -148,12 +149,16 @@ public async Task Build_Valid_SingleFile_WithTemplateSpecReference_ToStdOut_Shou
148149
public async Task Build_Valid_SingleFile_After_Restore_Should_Succeed(DataSet dataSet)
149150
{
150151
var outputDirectory = dataSet.SaveFilesToTestDirectory(TestContext);
151-
var clientFactory = dataSet.CreateMockRegistryClients();
152-
var templateSpecRepositoryFactory = dataSet.CreateMockTemplateSpecRepositoryFactory(TestContext);
153-
await dataSet.PublishModulesToRegistryAsync(clientFactory);
154-
var bicepFilePath = Path.Combine(outputDirectory, DataSet.TestFileMain);
155152

156-
var settings = new InvocationSettings(new(TestContext, RegistryEnabled: dataSet.HasExternalModules), clientFactory, templateSpecRepositoryFactory);
153+
var features = new FeatureProviderOverrides(TestContext);
154+
FileHelper.GetCacheRootDirectory(TestContext).EnsureExists();
155+
156+
var artifactManager = new TestExternalArtifactManager(TestCompiler.ForMockFileSystemCompilation().WithFeatureOverrides<FeatureProviderOverrides, OverriddenFeatureProviderFactory>(features));
157+
await dataSet.PublishAllDataSetArtifacts(artifactManager, publishSource: true);
158+
159+
var settings = new InvocationSettings(features).WithArtifactManager(artifactManager, TestContext);
160+
161+
var bicepFilePath = Path.Combine(outputDirectory, DataSet.TestFileMain);
157162

158163
var (restoreOutput, restoreError, restoreResult) = await Bicep(settings, "restore", bicepFilePath);
159164
using (new AssertionScope())

src/Bicep.Cli.IntegrationTests/BuildParamsCommandTests.cs

Lines changed: 33 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
using Bicep.Core.UnitTests;
1313
using Bicep.Core.UnitTests.Assertions;
1414
using Bicep.Core.UnitTests.Baselines;
15-
using Bicep.Core.UnitTests.Mock;
1615
using Bicep.Core.UnitTests.Utils;
1716
using FluentAssertions;
1817
using FluentAssertions.Execution;
@@ -22,27 +21,19 @@
2221
using Moq;
2322
using Newtonsoft.Json;
2423
using Newtonsoft.Json.Linq;
24+
using StrictMock = Bicep.Core.UnitTests.Mock.StrictMock;
25+
using TestEnvironment = Bicep.Core.UnitTests.Utils.TestEnvironment;
2526

2627
namespace Bicep.Cli.IntegrationTests
2728
{
2829

2930
[TestClass]
3031
public class BuildParamsCommandTests : TestBase
3132
{
32-
private InvocationSettings Settings
33-
=> new()
34-
{
35-
Environment = TestEnvironment.Default.WithVariables(
36-
("stringEnvVariableName", "test"),
37-
("intEnvVariableName", "100"),
38-
("boolEnvironmentVariable", "true")
39-
)
40-
};
41-
4233
[TestMethod]
4334
public async Task Build_Params_With_NonExisting_File_ShouldFail_WithExpectedErrorMessage()
4435
{
45-
var (output, error, result) = await Bicep(Settings, "build-params", "/nonexisting/nonexisting.bicepparam");
36+
var (output, error, result) = await Bicep(CreateDefaultSettings(), "build-params", "/nonexisting/nonexisting.bicepparam");
4637

4738
result.Should().Be(1);
4839
output.Should().BeEmpty();
@@ -58,7 +49,7 @@ public async Task Build_Params_With_Incorrect_Bicep_File_Extension_ShouldFail_Wi
5849
var outputFilePath = FileHelper.GetResultFilePath(TestContext, "output.json");
5950

6051
File.Exists(outputFilePath).Should().BeFalse();
61-
var (output, error, result) = await Bicep(Settings, "build-params", bicepparamsPath, "--bicep-file", bicepPath, "--outfile", outputFilePath);
52+
var (output, error, result) = await Bicep(CreateDefaultSettings(), "build-params", bicepparamsPath, "--bicep-file", bicepPath, "--outfile", outputFilePath);
6253

6354
result.Should().Be(1);
6455
output.Should().BeEmpty();
@@ -76,7 +67,7 @@ public async Task Build_Params_With_CLI_Input_And_Using_Declaration_Bicep_File_R
7667
var outputFilePath = FileHelper.GetResultFilePath(TestContext, "output.json");
7768

7869
File.Exists(outputFilePath).Should().BeFalse();
79-
var result = await Bicep(Settings, "build-params", bicepparamsPath, "--bicep-file", otherBicepPath, "--outfile", outputFilePath);
70+
var result = await Bicep(CreateDefaultSettings(), "build-params", bicepparamsPath, "--bicep-file", otherBicepPath, "--outfile", outputFilePath);
8071

8172
result.Should().Fail().And.HaveStderrMatch($"Bicep file {otherBicepPath} provided with --bicep-file option doesn't match the Bicep file {bicepPath} referenced by the \"using\" declaration in the parameters file.*");
8273
}
@@ -96,7 +87,7 @@ public async Task Build_Params_Bicep_File_Reference_Mismatch_And_Other_Diagnosti
9687

9788
var outputFilePath = FileHelper.GetResultFilePath(TestContext, "output.json");
9889

99-
var result = await Bicep(Settings, "build-params", bicepparamsPath, "--bicep-file", otherBicepPath, "--outfile", outputFilePath);
90+
var result = await Bicep(CreateDefaultSettings(), "build-params", bicepparamsPath, "--bicep-file", otherBicepPath, "--outfile", outputFilePath);
10091

10192
result.Should().Fail().And.HaveStderrMatch($"Bicep file {otherBicepPath} provided with --bicep-file option doesn't match the Bicep file {bicepPath} referenced by the \"using\" declaration in the parameters file.*");
10293
File.Exists(outputFilePath).Should().BeFalse();
@@ -144,11 +135,11 @@ param objParam object
144135
}
145136
""";
146137

147-
var settings = Settings with
138+
var settings = CreateDefaultSettings() with
148139
{
149140
Environment = TestEnvironment.Default.WithVariables(
150-
("BICEP_PARAMETERS_OVERRIDES", paramsOverrides)
151-
)
141+
("BICEP_PARAMETERS_OVERRIDES", paramsOverrides)
142+
)
152143
};
153144

154145
var outputFilePath = FileHelper.GetResultFilePath(TestContext, "output.json");
@@ -196,7 +187,7 @@ public async Task Build_params_with_default_value_override_succeeds()
196187
foo = "bar"
197188
}.ToJson()));
198189

199-
var settings = Settings with { Environment = environment };
190+
var settings = CreateDefaultSettings() with { Environment = environment };
200191
var result = await Bicep(settings, "build-params", bicepparamsPath, "--stdout");
201192

202193
result.Should().NotHaveStderr();
@@ -240,7 +231,7 @@ public async Task Build_params_with_incorrect_default_value_override_fails()
240231
wrongName = "bar"
241232
}.ToJson()));
242233

243-
var settings = Settings with { Environment = environment };
234+
var settings = CreateDefaultSettings() with { Environment = environment };
244235
var result = await Bicep(settings, "build-params", bicepparamsPath, "--stdout");
245236

246237
result.Should().Fail().And.HaveStderrMatch($"*Error BCP259: The parameter \"wrongName\" is assigned in the params file without being declared in the Bicep file.*");
@@ -271,11 +262,11 @@ param intParam int
271262
}
272263
""";
273264

274-
var settings = Settings with
265+
var settings = CreateDefaultSettings() with
275266
{
276267
Environment = TestEnvironment.Default.WithVariables(
277-
("BICEP_PARAMETERS_OVERRIDES", paramsOverrides)
278-
)
268+
("BICEP_PARAMETERS_OVERRIDES", paramsOverrides)
269+
)
279270
};
280271

281272
var outputFilePath = FileHelper.GetResultFilePath(TestContext, "output.json");
@@ -292,7 +283,8 @@ param intParam int
292283
public async Task Build_Valid_Params_File_Should_Succeed(BaselineData_Bicepparam baselineData)
293284
{
294285
var data = baselineData.GetData(TestContext);
295-
var (output, error, result) = await Bicep(Settings, "build-params", data.Parameters.OutputFilePath, "--bicep-file", data.Bicep.OutputFilePath);
286+
287+
var (output, error, result) = await Bicep(await CreateDefaultSettingsWithDefaultMockRegistry(), "build-params", data.Parameters.OutputFilePath, "--bicep-file", data.Bicep.OutputFilePath);
296288

297289
using (new AssertionScope())
298290
{
@@ -311,7 +303,8 @@ public async Task Build_Valid_Params_File_Should_Succeed(BaselineData_Bicepparam
311303
public async Task Build_Valid_Params_File_To_Outdir_Should_Succeed(BaselineData_Bicepparam baselineData)
312304
{
313305
var data = baselineData.GetData(TestContext);
314-
var (output, error, result) = await Bicep(Settings, "build-params", data.Parameters.OutputFilePath, "--bicep-file", data.Bicep.OutputFilePath, "--outdir", Path.GetDirectoryName(data.Compiled!.OutputFilePath));
306+
307+
var (output, error, result) = await Bicep(await CreateDefaultSettingsWithDefaultMockRegistry(), "build-params", data.Parameters.OutputFilePath, "--bicep-file", data.Bicep.OutputFilePath, "--outdir", Path.GetDirectoryName(data.Compiled!.OutputFilePath));
315308

316309
using (new AssertionScope())
317310
{
@@ -332,7 +325,7 @@ public async Task Build_Valid_Params_File_ToStdOut_Should_Succeed(BaselineData_B
332325
{
333326
var data = baselineData.GetData(TestContext);
334327

335-
var (output, error, result) = await Bicep(Settings, "build-params", data.Parameters.OutputFilePath, "--bicep-file", data.Bicep.OutputFilePath, "--stdout");
328+
var (output, error, result) = await Bicep(await CreateDefaultSettingsWithDefaultMockRegistry(), "build-params", data.Parameters.OutputFilePath, "--bicep-file", data.Bicep.OutputFilePath, "--stdout");
336329

337330
using (new AssertionScope())
338331
{
@@ -356,9 +349,17 @@ public async Task Build_Invalid_Single_Params_File_ShouldFail_WithExpectedErrorM
356349
{
357350
var data = baselineData.GetData(TestContext);
358351

359-
var diagnostics = await GetAllParamDiagnostics(data.Parameters.OutputFilePath);
352+
var artifactManager = await CreateDefaultExternalArtifactManager();
353+
354+
var serviceBuilder = new ServiceBuilder()
355+
.WithFeatureOverrides(CreateDefaultFeatureProviderOverrides())
356+
.WithTestArtifactManager(artifactManager);
357+
358+
var diagnostics = await GetAllParamDiagnostics(serviceBuilder, data.Parameters.OutputFilePath);
359+
360+
var settings = CreateDefaultSettings().WithArtifactManager(artifactManager, TestContext);
360361

361-
var (output, error, result) = await Bicep(Settings, "build-params", data.Parameters.OutputFilePath, "--bicep-file", data.Bicep.OutputFilePath);
362+
var (output, error, result) = await Bicep(settings, "build-params", data.Parameters.OutputFilePath, "--bicep-file", data.Bicep.OutputFilePath);
362363

363364
using (new AssertionScope())
364365
{
@@ -376,10 +377,7 @@ public async Task Build_params_to_stdout_with_non_bicep_references_should_succee
376377
var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile);
377378
var outputFile = baselineFolder.GetFileOrEnsureCheckedIn("output.json");
378379

379-
var clients = await MockRegistry.Build();
380-
var settings = new InvocationSettings(new(TestContext, RegistryEnabled: true), clients.ContainerRegistry, clients.TemplateSpec);
381-
382-
var result = await Bicep(settings, "build-params", baselineFolder.EntryFile.OutputFilePath, "--stdout");
380+
var result = await Bicep(await CreateDefaultSettingsWithDefaultMockRegistry(), "build-params", baselineFolder.EntryFile.OutputFilePath, "--stdout");
383381
result.Should().Succeed();
384382

385383
var parametersStdout = result.Stdout.FromJson<BuildParamsStdout>();
@@ -393,9 +391,6 @@ public async Task Build_params_to_stdout_with_non_bicep_references_should_succee
393391
[TestCategory(BaselineHelper.BaselineTestCategory)]
394392
public async Task Build_params_to_stdout_with_experimentalfeaturenotenabled_should_fail()
395393
{
396-
var clients = await MockRegistry.Build();
397-
var settings = new InvocationSettings(new(TestContext, RegistryEnabled: true), clients.ContainerRegistry, clients.TemplateSpec);
398-
399394
var mainBicepParamPath = FileHelper.SaveResultFile(
400395
TestContext,
401396
"main.bicepparam",
@@ -421,7 +416,7 @@ public async Task Build_params_to_stdout_with_experimentalfeaturenotenabled_shou
421416
"bicepconfig.json", "{}",
422417
Path.GetDirectoryName(mainBicepParamPath));
423418

424-
var result = await Bicep(settings, "build-params", mainBicepParamPath, "--stdout");
419+
var result = await Bicep(await CreateDefaultSettingsWithDefaultMockRegistry(), "build-params", mainBicepParamPath, "--stdout");
425420

426421
result.Should().Fail().And.HaveStderrMatch($"*Error BCP406: Using \"extends\" keyword requires enabling EXPERIMENTAL feature \"ExtendableParamFiles\".*");
427422
}
@@ -435,10 +430,7 @@ public async Task Build_params_returns_intuitive_error_if_invoked_with_bicep_fil
435430
var bicepFile = Path.Combine(baselineFolder.OutputFolderPath, "main.bicep");
436431
File.WriteAllText(bicepFile, "");
437432

438-
var clients = await MockRegistry.Build();
439-
var settings = new InvocationSettings(new(TestContext, RegistryEnabled: true), clients.ContainerRegistry, clients.TemplateSpec);
440-
441-
var result = await Bicep(settings, "build-params", baselineFolder.EntryFile.OutputFilePath, "--bicep-file", bicepFile, "--stdout");
433+
var result = await Bicep(await CreateDefaultSettingsWithDefaultMockRegistry(), "build-params", baselineFolder.EntryFile.OutputFilePath, "--bicep-file", bicepFile, "--stdout");
442434
result.Should().Fail().And.HaveStderrMatch($"Bicep file * provided with --bicep-file can only be used if the Bicep parameters \"using\" declaration refers to a Bicep file on disk.*");
443435
}
444436

@@ -476,8 +468,7 @@ public async Task Build_params_to_stdout_with_registry_should_succeed_after_rest
476468
var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile);
477469
var outputFile = baselineFolder.GetFileOrEnsureCheckedIn("output.json");
478470

479-
var clients = await MockRegistry.Build();
480-
var settings = new InvocationSettings(new(TestContext, RegistryEnabled: true), clients.ContainerRegistry, clients.TemplateSpec);
471+
var settings = await CreateDefaultSettingsWithDefaultMockRegistry();
481472

482473
var result = await Bicep(settings, "restore", baselineFolder.EntryFile.OutputFilePath);
483474
result.Should().Succeed().And.NotHaveStdout().And.NotHaveStderr();
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"parametersJson": "{\n \"$schema\": \"https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#\",\n \"contentVersion\": \"1.0.0.0\",\n \"parameters\": {\n \"stringParam\": {\n \"value\": \"foo\"\n },\n \"intParam\": {\n \"value\": 123\n },\n \"boolParam\": {\n \"value\": false\n },\n \"objectParam\": {\n \"value\": {\n \"abc\": \"def\"\n }\n },\n \"arrayParam\": {\n \"value\": [\n \"abc\",\n \"def\"\n ]\n }\n }\n}",
33
"templateJson": null,
4-
"templateSpecId": "/subscriptions/<todo_fill_in>/resourceGroups/<todo_fill_in>/providers/Microsoft.Resources/templateSpecs/<todo_fill_in>/versions/<todo_fill_in>"
4+
"templateSpecId": "/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/prod-rg/providers/Microsoft.Resources/templateSpecs/parameters-basic/versions/v1"
55
}

0 commit comments

Comments
 (0)