From 59c5efff7934cdb06ea0deba63fb1278671c7543 Mon Sep 17 00:00:00 2001 From: Brian Gordon Davis <96969185+bgdnext64@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:45:35 -0400 Subject: [PATCH 1/3] feat: support .bicepparam parameter files for Bicep deployments Detect .bicepparam extension on --parametersFilePath and automatically compile to ARM JSON format using 'bicep build-params' before deployment. Both .json and .bicepparam formats are now supported. - Add .bicepparam detection and compilation in bicepCmd.go - Add storage-account-simple-params.bicepparam sample file - Add TestBicepWithBicepparamFile e2e test - Update known-issues doc (no longer unsupported) - Add .bicepparam example to quickstart and flags docs Closes #12 --- cmd/bicepCmd.go | 17 +++- docs/commandline-flags-and-env-variables.md | 2 +- docs/installation-and-quickstart.md | 34 ++++++++ docs/known-issues-and-workarounds.MD | 2 +- e2eTests/e2eBicep_test.go | 78 +++++++++++++++++++ .../storage-account-simple-params.bicepparam | 3 + 6 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 samples/bicep/storage-account-simple-params.bicepparam diff --git a/cmd/bicepCmd.go b/cmd/bicepCmd.go index dadc006c..dc568060 100644 --- a/cmd/bicepCmd.go +++ b/cmd/bicepCmd.go @@ -69,7 +69,7 @@ func NewBicepCommand() *cobra.Command { log.Errorf("Error marking flag required for Bicep file path: %v\n", err) } - bicepCmd.Flags().StringVarP(&flgParametersFilePath, "parametersFilePath", "", "", "Path to bicep Parameters File") + bicepCmd.Flags().StringVarP(&flgParametersFilePath, "parametersFilePath", "", "", "Path to bicep Parameters File (.json or .bicepparam)") err = bicepCmd.MarkFlagRequired("parametersFilePath") if err != nil { log.Errorf("Error marking flag required for Bicep parameters file path: %v\n", err) @@ -129,6 +129,21 @@ func getMPFBicep(cmd *cobra.Command, args []string) { log.Errorf("Error getting absolute path for parameters file: %v\n", err) } + // If the parameters file is a .bicepparam file, compile it to ARM JSON format + if strings.HasSuffix(strings.ToLower(flgParametersFilePath), ".bicepparam") { + log.Infoln("Detected .bicepparam file, compiling to ARM parameters JSON format") + compiledParamsPath := strings.TrimSuffix(flgParametersFilePath, filepath.Ext(flgParametersFilePath)) + ".parameters.json" + buildParamsCmd := exec.Command(flgBicepExecPath, "build-params", flgParametersFilePath, "--outfile", compiledParamsPath) + buildParamsCmd.Dir = filepath.Dir(flgParametersFilePath) + + output, err := buildParamsCmd.CombinedOutput() + if err != nil { + log.Fatalf("error running bicep build-params: %s\n%s", err, string(output)) + } + log.Infoln("Bicep parameters compiled successfully, ARM Parameters JSON created at:", compiledParamsPath) + flgParametersFilePath = compiledParamsPath + } + armTemplatePath := strings.TrimSuffix(flgBicepFilePath, ".bicep") + ".json" bicepCmd := exec.Command(flgBicepExecPath, "build", flgBicepFilePath, "--outfile", armTemplatePath) bicepCmd.Dir = filepath.Dir(flgBicepFilePath) diff --git a/docs/commandline-flags-and-env-variables.md b/docs/commandline-flags-and-env-variables.md index a06c6894..f865c448 100644 --- a/docs/commandline-flags-and-env-variables.md +++ b/docs/commandline-flags-and-env-variables.md @@ -34,7 +34,7 @@ When used for Terraform, the verbose and debug flags show detailed logs from Ter | Flag | Environment Variable | Required / Optional | Description | |----------------------|--------------------------|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| | bicepFilePath | MPF_BICEPFILEPATH | Required | Bicep file with path | -| parametersFilePath | MPF_PARAMETERSFILEPATH | Required | Bicep parameters file with path | +| parametersFilePath | MPF_PARAMETERSFILEPATH | Required | Bicep parameters file with path (.json or .bicepparam). When a .bicepparam file is provided, it is automatically compiled to ARM JSON format | | bicepExecPath | MPF_BICEPEXECPATH | Required | Path to the Bicep executable | | resourceGroupNamePfx | MPF_RESOURCEGROUPNAMEPFX | Optional | Prefix for the resource group name. If not provided, default prefix is testdeployrg. For Bicep deployments this temporary resource group is created | | deploymentNamePfx | MPF_DEPLOYMENTNAMEPFX | Optional | Prefix for the deployment name. If not provided, default prefix is testDeploy. For Bicep deployments this temporary deployment is created | diff --git a/docs/installation-and-quickstart.md b/docs/installation-and-quickstart.md index b5c3c3b2..6dad0782 100644 --- a/docs/installation-and-quickstart.md +++ b/docs/installation-and-quickstart.md @@ -231,7 +231,41 @@ $env:MPF_BICEPEXECPATH = (Get-Command bicep).Source # Dynamically resolves to th .\azmpf.exe bicep --bicepFilePath .\samples\bicep\storage-account-simple.bicep --parametersFilePath .\samples\bicep\storage-account-simple-params.json --jsonOutput --verbose ``` +#### Bicep with .bicepparam Parameters File +MPF also supports Bicep-native `.bicepparam` parameter files. These are automatically compiled to ARM JSON format using `bicep build-params` before deployment. The `.bicepparam` file uses a `using` directive to reference the Bicep template: + +```bicep +using './storage-account-simple.bicep' + +param storageAccountName = 'myazdemostg' +``` + +To use a `.bicepparam` file, simply pass it as the `--parametersFilePath`: + +```bash +export MPF_SUBSCRIPTIONID="YOUR_SUBSCRIPTION_ID" +export MPF_TENANTID="YOUR_TENANT_ID" +export MPF_SPCLIENTID="YOUR_SP_CLIENT_ID" +export MPF_SPCLIENTSECRET="YOUR_SP_CLIENT_SECRET" +export MPF_SPOBJECTID="YOUR_SP_OBJECT_ID" +export MPF_BICEPEXECPATH=$(which bicep) + +./azmpf bicep --bicepFilePath ./samples/bicep/storage-account-simple.bicep --parametersFilePath ./samples/bicep/storage-account-simple-params.bicepparam --jsonOutput --verbose +``` + +Or using PowerShell on Windows: + +```powershell +$env:MPF_SUBSCRIPTIONID = "YOUR_SUBSCRIPTION_ID" +$env:MPF_TENANTID = "YOUR_TENANT_ID" +$env:MPF_SPCLIENTID = "YOUR_SP_CLIENT_ID" +$env:MPF_SPCLIENTSECRET = "YOUR_SP_CLIENT_SECRET" +$env:MPF_SPOBJECTID = "YOUR_SP_OBJECT_ID" +$env:MPF_BICEPEXECPATH = (Get-Command bicep).Source + +.\.azmpf.exe bicep --bicepFilePath .\samples\bicep\storage-account-simple.bicep --parametersFilePath .\samples\bicep\storage-account-simple-params.bicepparam --jsonOutput --verbose +``` ### Terraform ```shell diff --git a/docs/known-issues-and-workarounds.MD b/docs/known-issues-and-workarounds.MD index dd93d3a4..b4c1b2e4 100644 --- a/docs/known-issues-and-workarounds.MD +++ b/docs/known-issues-and-workarounds.MD @@ -4,7 +4,7 @@ ### Parameter File Format -Currently only ARM type [parameters files](https://github.com/Azure/mpf/blob/main/samples/bicep/aks-private-subnet-invalid-params.json) are supported even for bicep executions. Bicep type parameters files are currently not supported and result in an error. A new feature request has been created to track this issue [issue #12](https://github.com/Azure/mpf/issues/12). +Both ARM-style JSON parameters files and Bicep-native `.bicepparam` files are supported for Bicep executions. When a `.bicepparam` file is provided via `--parametersFilePath`, MPF automatically compiles it to ARM JSON format using `bicep build-params` before deployment. See the [quickstart guide](installation-and-quickstart.md) for examples of both formats. ## Terraform diff --git a/e2eTests/e2eBicep_test.go b/e2eTests/e2eBicep_test.go index 90dc9a3b..46b85e39 100644 --- a/e2eTests/e2eBicep_test.go +++ b/e2eTests/e2eBicep_test.go @@ -187,6 +187,84 @@ func TestBicepAksFullDeployment(t *testing.T) { assert.Equal(t, 9, len(mpfResult.RequiredPermissions[mpfConfig.SubscriptionID])) } +func TestBicepWithBicepparamFile(t *testing.T) { + mpfArgs, err := getTestingMPFArgs() + if err != nil { + t.Skip("required environment variables not set, skipping end to end test") + } + + if checkBicepTestEnvVars() { + t.Skip("required environment variables not set, skipping end to end test") + } + + bicepExecPath := os.Getenv("MPF_BICEPEXECPATH") + bicepFilePath := "../samples/bicep/storage-account-simple.bicep" + parametersFilePath := "../samples/bicep/storage-account-simple-params.bicepparam" + + bicepFilePath, _ = getAbsolutePath(bicepFilePath) + parametersFilePath, _ = getAbsolutePath(parametersFilePath) + + // Compile .bicepparam to ARM JSON parameters + compiledParamsPath := strings.TrimSuffix(parametersFilePath, filepath.Ext(parametersFilePath)) + ".parameters.json" + buildParamsCmd := exec.Command(bicepExecPath, "build-params", parametersFilePath, "--outfile", compiledParamsPath) + buildParamsCmd.Dir = filepath.Dir(parametersFilePath) + + output, err := buildParamsCmd.CombinedOutput() + if err != nil { + log.Errorf("error running bicep build-params: %s\n%s", err, string(output)) + t.Fatal(err) + } + t.Cleanup(func() { os.Remove(compiledParamsPath) }) + + // Build bicep to ARM template + armTemplatePath := strings.TrimSuffix(bicepFilePath, ".bicep") + ".json" + bicepCmd := exec.Command(bicepExecPath, "build", bicepFilePath, "--outfile", armTemplatePath) + bicepCmd.Dir = filepath.Dir(bicepFilePath) + + _, err = bicepCmd.CombinedOutput() + if err != nil { + log.Error(err) + t.Error(err) + } + + ctx := t.Context() + + mpfConfig := getMPFConfig(mpfArgs) + + deploymentName := fmt.Sprintf("%s-%s", mpfArgs.DeploymentNamePfx, mpfSharedUtils.GenerateRandomString(7)) + armConfig := &ARMTemplateShared.ArmTemplateAdditionalConfig{ + TemplateFilePath: armTemplatePath, + ParametersFilePath: compiledParamsPath, + DeploymentName: deploymentName, + } + + var rgManager usecase.ResourceGroupManager + var spRoleAssignmentManager usecase.ServicePrincipalRolemAssignmentManager + rgManager = rgm.NewResourceGroupManager(mpfArgs.SubscriptionID) + spRoleAssignmentManager = spram.NewSPRoleAssignmentManager(mpfArgs.SubscriptionID) + + var deploymentAuthorizationCheckerCleaner usecase.DeploymentAuthorizationCheckerCleaner + var mpfService *usecase.MPFService + + deploymentAuthorizationCheckerCleaner = ARMTemplateDeployment.NewARMTemplateDeploymentAuthorizationChecker(mpfArgs.SubscriptionID, *armConfig) + initialPermissionsToAdd := []string{"Microsoft.Resources/deployments/*", "Microsoft.Resources/subscriptions/operationresults/read"} + permissionsToAddToResult := []string{"Microsoft.Resources/deployments/read", "Microsoft.Resources/deployments/write"} + mpfService = usecase.NewMPFService(ctx, rgManager, spRoleAssignmentManager, deploymentAuthorizationCheckerCleaner, mpfConfig, initialPermissionsToAdd, permissionsToAddToResult, true, false, true) + + mpfResult, err := mpfService.GetMinimumPermissionsRequired() + if err != nil { + t.Error(err) + } + + // Storage account deployment requires these permissions: + // Microsoft.Resources/deployments/read + // Microsoft.Resources/deployments/write + // Microsoft.Storage/storageAccounts/read + // Microsoft.Storage/storageAccounts/write + assert.NotEmpty(t, mpfResult.RequiredPermissions) + assert.GreaterOrEqual(t, len(mpfResult.RequiredPermissions[mpfConfig.SubscriptionID]), 4) +} + func getAbsolutePath(path string) (string, error) { absPath := path if !filepath.IsAbs(path) { diff --git a/samples/bicep/storage-account-simple-params.bicepparam b/samples/bicep/storage-account-simple-params.bicepparam new file mode 100644 index 00000000..3761a7ee --- /dev/null +++ b/samples/bicep/storage-account-simple-params.bicepparam @@ -0,0 +1,3 @@ +using './storage-account-simple.bicep' + +param storageAccountName = 'myazdemostg' From 05fcc91871d0972f340f8fa1fdd5580a7ae96355 Mon Sep 17 00:00:00 2001 From: Brian Gordon Davis <96969185+bgdnext64@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:16:11 -0400 Subject: [PATCH 2/3] fix: resolve lint failures - Bump Go 1.26.1 to 1.26.2 to fix stdlib vulnerabilities (govulncheck) - Fix errcheck: handle os.Remove return value in e2eBicep_test.go - Fix markdown: add missing blank lines before headings --- docs/installation-and-quickstart.md | 4 ++-- e2eTests/e2eBicep_test.go | 2 +- go.mod | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/installation-and-quickstart.md b/docs/installation-and-quickstart.md index 6dad0782..34b89cec 100644 --- a/docs/installation-and-quickstart.md +++ b/docs/installation-and-quickstart.md @@ -230,8 +230,7 @@ $env:MPF_SPOBJECTID = "YOUR_SP_OBJECT_ID" $env:MPF_BICEPEXECPATH = (Get-Command bicep).Source # Dynamically resolves to the Bicep executable path, works across different installation locations .\azmpf.exe bicep --bicepFilePath .\samples\bicep\storage-account-simple.bicep --parametersFilePath .\samples\bicep\storage-account-simple-params.json --jsonOutput --verbose -``` -#### Bicep with .bicepparam Parameters File +```#### Bicep with .bicepparam Parameters File MPF also supports Bicep-native `.bicepparam` parameter files. These are automatically compiled to ARM JSON format using `bicep build-params` before deployment. The `.bicepparam` file uses a `using` directive to reference the Bicep template: @@ -266,6 +265,7 @@ $env:MPF_BICEPEXECPATH = (Get-Command bicep).Source .\.azmpf.exe bicep --bicepFilePath .\samples\bicep\storage-account-simple.bicep --parametersFilePath .\samples\bicep\storage-account-simple-params.bicepparam --jsonOutput --verbose ``` + ### Terraform ```shell diff --git a/e2eTests/e2eBicep_test.go b/e2eTests/e2eBicep_test.go index 46b85e39..3231a987 100644 --- a/e2eTests/e2eBicep_test.go +++ b/e2eTests/e2eBicep_test.go @@ -214,7 +214,7 @@ func TestBicepWithBicepparamFile(t *testing.T) { log.Errorf("error running bicep build-params: %s\n%s", err, string(output)) t.Fatal(err) } - t.Cleanup(func() { os.Remove(compiledParamsPath) }) + t.Cleanup(func() { _ = os.Remove(compiledParamsPath) }) // Build bicep to ARM template armTemplatePath := strings.TrimSuffix(bicepFilePath, ".bicep") + ".json" diff --git a/go.mod b/go.mod index 600a9e51..4eb4431a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Azure/mpf -go 1.26.1 +go 1.26.2 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 From 871c8b3e43a8635d99a11ff2e6c6f1b80d2ce15a Mon Sep 17 00:00:00 2001 From: Brian Gordon Davis <96969185+bgdnext64@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:32:17 -0400 Subject: [PATCH 3/3] address PR review: extract bicepparam compile to helper, temp-file cleanup, docs fixes, exact assertion count --- cmd/bicepCmd.go | 24 ++++--- docs/installation-and-quickstart.md | 6 +- e2eTests/e2eBicep_test.go | 39 ++++++----- .../bicepUtils/compileBicepParams.go | 69 +++++++++++++++++++ .../bicepUtils/compileBicepParams_test.go | 46 +++++++++++++ 5 files changed, 157 insertions(+), 27 deletions(-) create mode 100644 pkg/infrastructure/bicepUtils/compileBicepParams.go create mode 100644 pkg/infrastructure/bicepUtils/compileBicepParams_test.go diff --git a/cmd/bicepCmd.go b/cmd/bicepCmd.go index dc568060..55babd39 100644 --- a/cmd/bicepCmd.go +++ b/cmd/bicepCmd.go @@ -33,6 +33,7 @@ import ( "github.com/Azure/mpf/pkg/domain" "github.com/Azure/mpf/pkg/infrastructure/ARMTemplateShared" "github.com/Azure/mpf/pkg/infrastructure/authorizationCheckers/ARMTemplateDeployment" + "github.com/Azure/mpf/pkg/infrastructure/bicepUtils" "github.com/Azure/mpf/pkg/infrastructure/mpfSharedUtils" resourceGroupManager "github.com/Azure/mpf/pkg/infrastructure/resourceGroupManager" sproleassignmentmanager "github.com/Azure/mpf/pkg/infrastructure/spRoleAssignmentManager" @@ -129,18 +130,25 @@ func getMPFBicep(cmd *cobra.Command, args []string) { log.Errorf("Error getting absolute path for parameters file: %v\n", err) } - // If the parameters file is a .bicepparam file, compile it to ARM JSON format - if strings.HasSuffix(strings.ToLower(flgParametersFilePath), ".bicepparam") { + // If the parameters file is a .bicepparam file, compile it to ARM JSON format. + // Compilation goes to a temp file (rather than next to the source) so we never + // silently clobber an existing ".parameters.json" the user already has, + // and so the artifact is reliably cleaned up on exit. + if bicepUtils.IsBicepParamFile(flgParametersFilePath) { log.Infoln("Detected .bicepparam file, compiling to ARM parameters JSON format") - compiledParamsPath := strings.TrimSuffix(flgParametersFilePath, filepath.Ext(flgParametersFilePath)) + ".parameters.json" - buildParamsCmd := exec.Command(flgBicepExecPath, "build-params", flgParametersFilePath, "--outfile", compiledParamsPath) - buildParamsCmd.Dir = filepath.Dir(flgParametersFilePath) - output, err := buildParamsCmd.CombinedOutput() + compiledParamsPath, err := bicepUtils.CompileBicepParamsToTempFile(flgBicepExecPath, flgParametersFilePath) if err != nil { - log.Fatalf("error running bicep build-params: %s\n%s", err, string(output)) + log.Fatalf("error compiling .bicepparam file: %v", err) } - log.Infoln("Bicep parameters compiled successfully, ARM Parameters JSON created at:", compiledParamsPath) + + defer func(path string) { + if err := os.Remove(path); err != nil && !os.IsNotExist(err) { + log.Warnf("error removing temporary ARM parameters file %q: %v", path, err) + } + }(compiledParamsPath) + + log.Infoln("Bicep parameters compiled successfully, temporary ARM Parameters JSON created at:", compiledParamsPath) flgParametersFilePath = compiledParamsPath } diff --git a/docs/installation-and-quickstart.md b/docs/installation-and-quickstart.md index 34b89cec..db68a518 100644 --- a/docs/installation-and-quickstart.md +++ b/docs/installation-and-quickstart.md @@ -230,7 +230,9 @@ $env:MPF_SPOBJECTID = "YOUR_SP_OBJECT_ID" $env:MPF_BICEPEXECPATH = (Get-Command bicep).Source # Dynamically resolves to the Bicep executable path, works across different installation locations .\azmpf.exe bicep --bicepFilePath .\samples\bicep\storage-account-simple.bicep --parametersFilePath .\samples\bicep\storage-account-simple-params.json --jsonOutput --verbose -```#### Bicep with .bicepparam Parameters File +``` + +#### Bicep with .bicepparam Parameters File MPF also supports Bicep-native `.bicepparam` parameter files. These are automatically compiled to ARM JSON format using `bicep build-params` before deployment. The `.bicepparam` file uses a `using` directive to reference the Bicep template: @@ -263,7 +265,7 @@ $env:MPF_SPCLIENTSECRET = "YOUR_SP_CLIENT_SECRET" $env:MPF_SPOBJECTID = "YOUR_SP_OBJECT_ID" $env:MPF_BICEPEXECPATH = (Get-Command bicep).Source -.\.azmpf.exe bicep --bicepFilePath .\samples\bicep\storage-account-simple.bicep --parametersFilePath .\samples\bicep\storage-account-simple-params.bicepparam --jsonOutput --verbose +.\azmpf.exe bicep --bicepFilePath .\samples\bicep\storage-account-simple.bicep --parametersFilePath .\samples\bicep\storage-account-simple-params.bicepparam --jsonOutput --verbose ``` ### Terraform diff --git a/e2eTests/e2eBicep_test.go b/e2eTests/e2eBicep_test.go index 3231a987..d4c70497 100644 --- a/e2eTests/e2eBicep_test.go +++ b/e2eTests/e2eBicep_test.go @@ -32,6 +32,7 @@ import ( "github.com/Azure/mpf/pkg/infrastructure/ARMTemplateShared" "github.com/Azure/mpf/pkg/infrastructure/authorizationCheckers/ARMTemplateDeployment" + "github.com/Azure/mpf/pkg/infrastructure/bicepUtils" mpfSharedUtils "github.com/Azure/mpf/pkg/infrastructure/mpfSharedUtils" rgm "github.com/Azure/mpf/pkg/infrastructure/resourceGroupManager" spram "github.com/Azure/mpf/pkg/infrastructure/spRoleAssignmentManager" @@ -198,33 +199,37 @@ func TestBicepWithBicepparamFile(t *testing.T) { } bicepExecPath := os.Getenv("MPF_BICEPEXECPATH") - bicepFilePath := "../samples/bicep/storage-account-simple.bicep" - parametersFilePath := "../samples/bicep/storage-account-simple-params.bicepparam" - bicepFilePath, _ = getAbsolutePath(bicepFilePath) - parametersFilePath, _ = getAbsolutePath(parametersFilePath) - - // Compile .bicepparam to ARM JSON parameters - compiledParamsPath := strings.TrimSuffix(parametersFilePath, filepath.Ext(parametersFilePath)) + ".parameters.json" - buildParamsCmd := exec.Command(bicepExecPath, "build-params", parametersFilePath, "--outfile", compiledParamsPath) - buildParamsCmd.Dir = filepath.Dir(parametersFilePath) + bicepFilePath, err := getAbsolutePath("../samples/bicep/storage-account-simple.bicep") + if err != nil { + t.Fatalf("failed to resolve absolute path for bicep file: %v", err) + } + parametersFilePath, err := getAbsolutePath("../samples/bicep/storage-account-simple-params.bicepparam") + if err != nil { + t.Fatalf("failed to resolve absolute path for parameters file: %v", err) + } - output, err := buildParamsCmd.CombinedOutput() + // Exercise the same compile helper used by the bicep CLI command so this + // test covers the auto-compile-on-.bicepparam code path. + if !bicepUtils.IsBicepParamFile(parametersFilePath) { + t.Fatalf("expected %q to be detected as a .bicepparam file", parametersFilePath) + } + compiledParamsPath, err := bicepUtils.CompileBicepParamsToTempFile(bicepExecPath, parametersFilePath) if err != nil { - log.Errorf("error running bicep build-params: %s\n%s", err, string(output)) - t.Fatal(err) + t.Fatalf("error compiling .bicepparam file: %v", err) } t.Cleanup(func() { _ = os.Remove(compiledParamsPath) }) // Build bicep to ARM template armTemplatePath := strings.TrimSuffix(bicepFilePath, ".bicep") + ".json" + t.Cleanup(func() { _ = os.Remove(armTemplatePath) }) + bicepCmd := exec.Command(bicepExecPath, "build", bicepFilePath, "--outfile", armTemplatePath) bicepCmd.Dir = filepath.Dir(bicepFilePath) - _, err = bicepCmd.CombinedOutput() + output, err := bicepCmd.CombinedOutput() if err != nil { - log.Error(err) - t.Error(err) + t.Fatalf("error running bicep build: %s\n%s", err, string(output)) } ctx := t.Context() @@ -256,13 +261,13 @@ func TestBicepWithBicepparamFile(t *testing.T) { t.Error(err) } - // Storage account deployment requires these permissions: + // Storage account deployment requires exactly these 4 permissions: // Microsoft.Resources/deployments/read // Microsoft.Resources/deployments/write // Microsoft.Storage/storageAccounts/read // Microsoft.Storage/storageAccounts/write assert.NotEmpty(t, mpfResult.RequiredPermissions) - assert.GreaterOrEqual(t, len(mpfResult.RequiredPermissions[mpfConfig.SubscriptionID]), 4) + assert.Equal(t, 4, len(mpfResult.RequiredPermissions[mpfConfig.SubscriptionID])) } func getAbsolutePath(path string) (string, error) { diff --git a/pkg/infrastructure/bicepUtils/compileBicepParams.go b/pkg/infrastructure/bicepUtils/compileBicepParams.go new file mode 100644 index 00000000..f0662517 --- /dev/null +++ b/pkg/infrastructure/bicepUtils/compileBicepParams.go @@ -0,0 +1,69 @@ +// MIT License +// +// Copyright (c) Microsoft Corporation. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE + +// Package bicepUtils contains helpers for working with Bicep input files, +// such as compiling .bicepparam files to ARM JSON parameter files. +package bicepUtils + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" +) + +// IsBicepParamFile reports whether the given path refers to a .bicepparam file. +func IsBicepParamFile(path string) bool { + return strings.HasSuffix(strings.ToLower(path), ".bicepparam") +} + +// CompileBicepParamsToTempFile compiles the given .bicepparam file to an ARM +// JSON parameters file using `bicep build-params`. The output is written to a +// freshly-created temp file, so any pre-existing ".parameters.json" next +// to the source file is left untouched. +// +// The caller is responsible for removing the returned path when no longer +// needed (typically via defer / t.Cleanup). If an error occurs, the temp file +// is removed before returning. +func CompileBicepParamsToTempFile(bicepExecPath, paramsFilePath string) (string, error) { + tempFile, err := os.CreateTemp("", "mpf-bicepparam-*.parameters.json") + if err != nil { + return "", fmt.Errorf("creating temporary ARM parameters file: %w", err) + } + compiledPath := tempFile.Name() + if err := tempFile.Close(); err != nil { + _ = os.Remove(compiledPath) + return "", fmt.Errorf("closing temporary ARM parameters file: %w", err) + } + + cmd := exec.Command(bicepExecPath, "build-params", paramsFilePath, "--outfile", compiledPath) + cmd.Dir = filepath.Dir(paramsFilePath) + + output, err := cmd.CombinedOutput() + if err != nil { + _ = os.Remove(compiledPath) + return "", fmt.Errorf("running bicep build-params: %w\n%s", err, string(output)) + } + + return compiledPath, nil +} diff --git a/pkg/infrastructure/bicepUtils/compileBicepParams_test.go b/pkg/infrastructure/bicepUtils/compileBicepParams_test.go new file mode 100644 index 00000000..e4eb3940 --- /dev/null +++ b/pkg/infrastructure/bicepUtils/compileBicepParams_test.go @@ -0,0 +1,46 @@ +// MIT License +// +// Copyright (c) Microsoft Corporation. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE + +package bicepUtils + +import "testing" + +func TestIsBicepParamFile(t *testing.T) { + cases := []struct { + path string + want bool + }{ + {"params.bicepparam", true}, + {"PARAMS.BICEPPARAM", true}, + {"/some/path/Params.BicepParam", true}, + {"params.json", false}, + {"main.bicep", false}, + {"", false}, + {"params.bicepparam.bak", false}, + } + + for _, tc := range cases { + if got := IsBicepParamFile(tc.path); got != tc.want { + t.Errorf("IsBicepParamFile(%q) = %v, want %v", tc.path, got, tc.want) + } + } +}