Skip to content

feat: support .bicepparam parameter files for Bicep deployments#253

Open
bgdnext64 wants to merge 3 commits into
mainfrom
bgdnext64/12-support-bicep-style-parameters-file
Open

feat: support .bicepparam parameter files for Bicep deployments#253
bgdnext64 wants to merge 3 commits into
mainfrom
bgdnext64/12-support-bicep-style-parameters-file

Conversation

@bgdnext64
Copy link
Copy Markdown
Collaborator

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

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
@bgdnext64 bgdnext64 requested a review from a team as a code owner April 10, 2026 15:46
@bgdnext64 bgdnext64 linked an issue Apr 10, 2026 that may be closed by this pull request
- 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
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class support for Bicep-native .bicepparam parameter files in the MPF Bicep workflow by compiling them to ARM JSON parameters via bicep build-params when provided through --parametersFilePath, and updates samples/docs/tests accordingly (closes #12).

Changes:

  • Detect .bicepparam in cmd/bicepCmd.go and compile to .parameters.json before running the deployment authorization checker.
  • Add a .bicepparam sample and an e2e test exercising bicep build-params + deployment with the compiled parameters.
  • Update documentation to reflect .bicepparam support and provide quickstart/flags examples.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
cmd/bicepCmd.go Adds .bicepparam detection and invokes bicep build-params to generate ARM JSON parameters prior to deployment.
e2eTests/e2eBicep_test.go Adds a new e2e test using a .bicepparam file (compiled to JSON) with a simple storage account Bicep sample.
samples/bicep/storage-account-simple-params.bicepparam Adds a Bicep-native parameters file sample referencing the storage account template.
docs/known-issues-and-workarounds.MD Updates known-issues to reflect that .bicepparam is now supported.
docs/installation-and-quickstart.md Adds quickstart examples showing .bicepparam usage (currently has formatting/command issues).
docs/commandline-flags-and-env-variables.md Updates flag documentation to note .json and .bicepparam are supported.
go.mod Bumps the Go toolchain version referenced by the module.

Comment thread cmd/bicepCmd.go Outdated
Comment on lines +135 to +143
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)
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compiled .parameters.json file created from a .bicepparam input is never deleted, unlike the generated ARM template which is cleaned up later. This can leave artifacts next to the user’s parameters file (and can dirty a repo working tree). Track whether compilation happened and remove the compiled file at the end (or write it to a temp file and clean up via defer).

Suggested change
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)
tempParamsFile, err := os.CreateTemp("", "*.parameters.json")
if err != nil {
log.Fatalf("error creating temporary ARM parameters file: %v", err)
}
compiledParamsPath := tempParamsFile.Name()
if err := tempParamsFile.Close(); err != nil {
_ = os.Remove(compiledParamsPath)
log.Fatalf("error closing temporary ARM parameters file: %v", err)
}
buildParamsCmd := exec.Command(flgBicepExecPath, "build-params", flgParametersFilePath, "--outfile", compiledParamsPath)
buildParamsCmd.Dir = filepath.Dir(flgParametersFilePath)
output, err := buildParamsCmd.CombinedOutput()
if err != nil {
_ = os.Remove(compiledParamsPath)
log.Fatalf("error running bicep build-params: %s\n%s", err, string(output))
}
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)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Refactored to os.CreateTemp so the compiled JSON goes to a temp path instead of next to the user's .bicepparam file, and added a defer that removes it (with a warning on cleanup error). The compile logic is now in a small helper, bicepUtils.CompileBicepParamsToTempFile, which also handles error cleanup so the temp file isn't left behind on failure.

Comment on lines 232 to +234
.\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

Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Markdown formatting is broken here: the PowerShell code block is not properly closed before the next heading (the closing fence and heading are on the same line). This will render incorrectly in the docs; put the closing ``` on its own line, then add the heading on the next line.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Moved the closing fence to its own line and put a blank line before the next heading so the markdown renders correctly.

Comment thread docs/installation-and-quickstart.md Outdated
$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
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PowerShell example command has an extra dot in the executable path (.\.azmpf.exe). It should match the earlier examples (e.g., ./azmpf on bash, .\azmpf.exe on Windows) so users can copy/paste successfully.

Suggested change
.\.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

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — .\.azmpf.exe was a typo, it's now .\azmpf.exe to match the other examples and so it can be copy/pasted directly.

Comment thread e2eTests/e2eBicep_test.go Outdated
Comment on lines +204 to +206
bicepFilePath, _ = getAbsolutePath(bicepFilePath)
parametersFilePath, _ = getAbsolutePath(parametersFilePath)

Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The errors from getAbsolutePath are ignored here. If path resolution fails, the test will proceed with a bad path and fail later with a less actionable error. Handle the error and fail/skip the test accordingly.

Suggested change
bicepFilePath, _ = getAbsolutePath(bicepFilePath)
parametersFilePath, _ = getAbsolutePath(parametersFilePath)
bicepFilePath, err = getAbsolutePath(bicepFilePath)
if err != nil {
t.Fatalf("failed to resolve absolute path for bicep file %q: %v", "../samples/bicep/storage-account-simple.bicep", err)
}
parametersFilePath, err = getAbsolutePath(parametersFilePath)
if err != nil {
t.Fatalf("failed to resolve absolute path for parameters file %q: %v", "../samples/bicep/storage-account-simple-params.bicepparam", err)
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Both getAbsolutePath calls now check the error and t.Fatalf with a descriptive message instead of silently continuing with a possibly-bogus path.

Comment thread e2eTests/e2eBicep_test.go Outdated
Comment on lines +207 to +210
// 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)
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test compiles the .bicepparam file manually and passes the compiled JSON into MPF, so it doesn’t actually exercise the new behavior added in cmd/bicepCmd.go (auto-detect + compile when --parametersFilePath ends with .bicepparam). Consider adding a CLI-level e2e that runs azmpf bicep --parametersFilePath <.bicepparam> (or factoring the compilation logic into a testable helper) to verify the intended user-facing path.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I extracted the compile logic out of cmd/bicepCmd.go into pkg/infrastructure/bicepUtils (IsBicepParamFile + CompileBicepParamsToTempFile). The CLI now calls the helper, and the e2e test calls the same helper instead of doing its own bicep build-params invocation, so the test exercises the actual user-facing code path. Also added a small unit test for IsBicepParamFile.

Comment thread e2eTests/e2eBicep_test.go Outdated
Comment on lines +224 to +227
_, err = bicepCmd.CombinedOutput()
if err != nil {
log.Error(err)
t.Error(err)
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If bicep build fails, the test logs/t.Errorf but continues, which can lead to follow-on failures (missing template file) that obscure the root cause. Use t.Fatal/t.Fatalf here to stop the test when the build fails.

Suggested change
_, err = bicepCmd.CombinedOutput()
if err != nil {
log.Error(err)
t.Error(err)
output, err = bicepCmd.CombinedOutput()
if err != nil {
log.Errorf("error running bicep build: %s\n%s", err, string(output))
t.Fatalf("error running bicep build: %s\n%s", err, string(output))

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Switched to t.Fatalf (with the bicep build output included) so a build failure stops the test immediately instead of cascading into a missing-template error later.

Comment thread e2eTests/e2eBicep_test.go
t.Cleanup(func() { _ = os.Remove(compiledParamsPath) })

// Build bicep to ARM template
armTemplatePath := strings.TrimSuffix(bicepFilePath, ".bicep") + ".json"
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

armTemplatePath is generated during the test but never removed. Add a t.Cleanup (or defer) to delete it to avoid leaving artifacts under samples/ and to prevent interference across repeated e2e runs.

Suggested change
armTemplatePath := strings.TrimSuffix(bicepFilePath, ".bicep") + ".json"
armTemplatePath := strings.TrimSuffix(bicepFilePath, ".bicep") + ".json"
t.Cleanup(func() { _ = os.Remove(armTemplatePath) })

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added t.Cleanup(func() { _ = os.Remove(armTemplatePath) }) right after computing the path so the generated .json is removed even if the test fails or panics.

Copy link
Copy Markdown
Contributor

@maniSbindra maniSbindra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review findings not yet raised by the automated review.

Comment thread cmd/bicepCmd.go Outdated
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)
Copy link
Copy Markdown
Contributor

@maniSbindra maniSbindra Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent overwrite of existing file

compiledParamsPath is derived deterministically as <name>.parameters.json next to the original .bicepparam file. If a file with that name already exists (e.g. the user already has a storage-account-simple-params.parameters.json), it is silently overwritten without warning. Consider checking for file existence first and erroring out .

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed. The compiled parameters file now goes to os.CreateTemp("", "mpf-bicepparam-*.parameters.json") instead of being placed next to the source file, so any pre-existing <name>.parameters.json the user has is left untouched. The compiled file is removed via defer when MPF exits.

Comment thread e2eTests/e2eBicep_test.go Outdated
// Microsoft.Storage/storageAccounts/read
// Microsoft.Storage/storageAccounts/write
assert.NotEmpty(t, mpfResult.RequiredPermissions)
assert.GreaterOrEqual(t, len(mpfResult.RequiredPermissions[mpfConfig.SubscriptionID]), 4)
Copy link
Copy Markdown
Contributor

@maniSbindra maniSbindra Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assertion too weak — we need to use exact permission count

If there a good reason not to do so then we need to mention it.

assert.GreaterOrEqual(..., 4) is imprecise. The PR comment above lists exactly 4 expected permissions, so the exact count is known. The existing TestBicepAksFullDeployment uses assert.Equal(t, 9, ...) for the same reason — an exact count catches regressions that a lower-bound check misses. Please use assert.Equal(t, 4, ...) to stay consistent and make the test deterministic.

Thanks

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Switched to assert.Equal(t, 4, len(...)) to match the established pattern in TestBicepAksFullDeployment and to actually catch regressions in the permission count.

Copy link
Copy Markdown
Contributor

@maniSbindra maniSbindra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, a few changes in addition to the automated review requested. Thanks, Mani

@bgdnext64
Copy link
Copy Markdown
Collaborator Author

Quick update on testing: I ran the new TestBicepWithBicepparamFile e2e against my test tenant end-to-end, not just locally as a build/vet. Created a fresh service principal with no role assignments (so MPF managed them dynamically), pointed it at samples/bicep/storage-account-simple-params.bicepparam, and let it go through the full discovery loop.

Result: PASS in about 120 seconds. The auto-compile path via the new bicepUtils.CompileBicepParamsToTempFile helper worked, the temp .parameters.json was cleaned up, and the storage-account deployment converged on exactly 4 permissions (Microsoft.Resources/deployments/{read,write} and Microsoft.Storage/storageAccounts/{read,write}), which is what the assertion now requires. Resource group, custom role, and the temp SP were all cleaned up afterwards.

Also worth flagging: the failing CI checks here look like the same upstream go-task/setup-task "unable to get latest version" issue we hit on #254 — it dies inside the action's own setup before any of our code runs, the workflow files are unchanged, and the same workflow was green on main last week. Happy to open a small PR pinning go-task/setup-task to a specific version (e.g. 3.50.0) instead of 3.x if that would help unblock things repo-wide.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support bicep style parameters file

3 participants