diff --git a/src/azure-cli/azure/cli/command_modules/resource/_help.py b/src/azure-cli/azure/cli/command_modules/resource/_help.py index f7532c772cf..359c57a799f 100644 --- a/src/azure-cli/azure/cli/command_modules/resource/_help.py +++ b/src/azure-cli/azure/cli/command_modules/resource/_help.py @@ -2787,6 +2787,18 @@ text: az bicep build --file {bicep_file} --no-restore """ +helps['bicep build-params'] = """ +type: command +short-summary: Build .bicepparam file. +examples: + - name: Build a .bicepparam file. + text: az bicep build-params --file {bicepparam_file} + - name: Build a .bicepparam file and print all output to stdout. + text: az bicep build-params --file {bicepparam_file} --stdout + - name: Build a .bicepparam file and save the result to the specified file. + text: az bicep build-params --file {bicepparam_file} --outfile {out_file} +""" + helps['bicep format'] = """ type: command short-summary: Format a Bicep file. @@ -2817,6 +2829,22 @@ text: az bicep decompile --file {json_template_file} --force """ +helps['bicep decompile-params'] = """ +type: command +short-summary: Decompile a parameters .json file to .bicepparam. +examples: + - name: Attempts to decompile a parameters .json file to .bicepparam. + text: az bicep decompile-params --file {json_template_file} + - name: Attempts to decompile a parameters .json file to .bicepparam using the bicep file given. + text: az bicep decompile-params --file {json_template_file} --bicep-file {bicep_file} + - name: Attempts to decompile a parameters .json file to .bicepparam and print all output to stdout. + text: az bicep decompile-params --file {json_template_file} --stdout + - name: Attempts to decompile a parameters .json file to .bicepparam and print all output to stdout and save the result to the specified directory. + text: az bicep decompile-params --file {json_template_file} --outdir {out_dir} + - name: Attempts to decompile a parameters .json file to .bicepparam and print all output to stdout and save the result to the specified file. + text: az bicep decompile-params --file {json_template_file} --outfile {out_file} +""" + helps['bicep publish'] = """ type: command short-summary: Publish a bicep file to a remote module registry. @@ -3070,6 +3098,8 @@ text: az bicep generate-params --file {bicep_file} --outfile {out_file} - name: Generate parameters file for a Bicep file without restoring external modules. text: az bicep generate-params --file {bicep_file} --no-restore + - name: Generate parameters file for a Bicep file with specified output format. Valid values are ( json | bicepparam ). + text: az bicep generate-params --file {bicep_file} --output-format {output_format} --include-params {include_params} """ helps['resourcemanagement'] = """ diff --git a/src/azure-cli/azure/cli/command_modules/resource/_params.py b/src/azure-cli/azure/cli/command_modules/resource/_params.py index 4393e0abfc5..0f7aa29dc43 100644 --- a/src/azure-cli/azure/cli/command_modules/resource/_params.py +++ b/src/azure-cli/azure/cli/command_modules/resource/_params.py @@ -806,6 +806,13 @@ def load_arguments(self, _): c.argument('stdout', arg_type=bicep_stdout_type) c.argument('no_restore', arg_type=bicep_no_restore_type, help="When set, builds the bicep file without restoring external modules.") + with self.argument_context('bicep build-params') as c: + c.argument('file', arg_type=bicep_file_type, help="The path to the .bicepparam file to build in the file system.") + c.argument('outdir', arg_type=bicep_outdir_type) + c.argument('outfile', arg_type=bicep_outfile_type) + c.argument('stdout', arg_type=bicep_stdout_type) + c.argument('no_restore', arg_type=bicep_no_restore_type, help="When set, builds the .bicepparam file without restoring external modules.") + with self.argument_context('bicep format') as c: c.argument('file', arg_type=bicep_file_type, help="The path to the Bicep file to format in the file system.") c.argument('outdir', arg_type=bicep_outdir_type) @@ -820,6 +827,14 @@ def load_arguments(self, _): c.argument('file', arg_type=bicep_file_type, help="The path to the ARM template to decompile in the file system.") c.argument('force', arg_type=bicep_force_type, help="Allows overwriting the output file if it exists.") + with self.argument_context('bicep decompile-params') as c: + c.argument('file', arg_type=bicep_file_type, help="The path to the parameters file to build in the file system.") + c.argument('bicep_file', completer=FilesCompleter(), type=file_type, help="Path to the bicep template file (relative to the .bicepparam file) that will be referenced in the using declaration.") + c.argument('outdir', arg_type=bicep_outdir_type) + c.argument('outfile', arg_type=bicep_outfile_type) + c.argument('stdout', arg_type=bicep_stdout_type) + c.argument('force', arg_type=bicep_force_type, help="Allows overwriting the output file if it exists.") + with self.argument_context('bicep restore') as c: c.argument('file', arg_type=bicep_file_type, help="The path to the Bicep file to restore external modules for.") c.argument('force', arg_type=bicep_force_type, help="Allows overwriting the cached external modules.") @@ -845,6 +860,8 @@ def load_arguments(self, _): c.argument('outfile', arg_type=bicep_outfile_type) c.argument('stdout', arg_type=bicep_stdout_type) c.argument('no_restore', arg_type=bicep_no_restore_type, help="When set, generates the parameters file without restoring external modules.") + c.argument('output_format', help="Set output format. Valid values are ( json | bicepparam ).") + c.argument('include_params', help="Set include params. Valid values are ( all | required-only ).") with self.argument_context('resourcemanagement private-link create') as c: c.argument('resource_group', arg_type=resource_group_name_type, diff --git a/src/azure-cli/azure/cli/command_modules/resource/commands.py b/src/azure-cli/azure/cli/command_modules/resource/commands.py index 0c7a34befce..fae004891ce 100644 --- a/src/azure-cli/azure/cli/command_modules/resource/commands.py +++ b/src/azure-cli/azure/cli/command_modules/resource/commands.py @@ -608,8 +608,10 @@ def load_command_table(self, _): g.custom_command('uninstall', 'uninstall_bicep_cli') g.custom_command('upgrade', 'upgrade_bicep_cli') g.custom_command('build', 'build_bicep_file') + g.custom_command('build-params', 'build_bicepparam_file') g.custom_command('format', 'format_bicep_file') g.custom_command('decompile', 'decompile_bicep_file') + g.custom_command('decompile-params', 'decompileparams_bicep_file') g.custom_command('restore', 'restore_bicep_file') g.custom_command('publish', 'publish_bicep_file') g.custom_command('version', 'show_bicep_cli_version') diff --git a/src/azure-cli/azure/cli/command_modules/resource/custom.py b/src/azure-cli/azure/cli/command_modules/resource/custom.py index 5c6504299fb..8783bcd35a6 100644 --- a/src/azure-cli/azure/cli/command_modules/resource/custom.py +++ b/src/azure-cli/azure/cli/command_modules/resource/custom.py @@ -4282,6 +4282,23 @@ def build_bicep_file(cmd, file, stdout=None, outdir=None, outfile=None, no_resto print(output) +def build_bicepparam_file(cmd, file, stdout=None, outdir=None, outfile=None, no_restore=None): + args = ["build-params", file] + if outdir: + args += ["--outdir", outdir] + if outfile: + args += ["--outfile", outfile] + if no_restore: + args += ["--no-restore"] + if stdout: + args += ["--stdout"] + + output = run_bicep_command(cmd.cli_ctx, args) + + if stdout: + print(output) + + def format_bicep_file(cmd, file, stdout=None, outdir=None, outfile=None, newline=None, indent_kind=None, indent_size=None, insert_final_newline=None): ensure_bicep_installation(cmd.cli_ctx) @@ -4354,6 +4371,29 @@ def decompile_bicep_file(cmd, file, force=None): run_bicep_command(cmd.cli_ctx, args) +def decompileparams_bicep_file(cmd, file, bicep_file=None, outdir=None, outfile=None, stdout=None): + ensure_bicep_installation(cmd.cli_ctx) + + minimum_supported_version = "0.18.4" + if bicep_version_greater_than_or_equal_to(minimum_supported_version): + args = ["decompile-params", file] + if bicep_file: + args += ["--bicep-file", bicep_file] + if outdir: + args += ["--outdir", outdir] + if outfile: + args += ["--outfile", outfile] + if stdout: + args += ["--stdout"] + + output = run_bicep_command(cmd.cli_ctx, args) + + if stdout: + print(output) + else: + logger.error("az bicep decompile-params could not be executed with the current version of Bicep CLI. Please upgrade Bicep CLI to v%s or later.", minimum_supported_version) + + def show_bicep_cli_version(cmd): print(run_bicep_command(cmd.cli_ctx, ["--version"], auto_install=False)) @@ -4362,7 +4402,7 @@ def list_bicep_cli_versions(cmd): return get_bicep_available_release_tags() -def generate_params_file(cmd, file, no_restore=None, outdir=None, outfile=None, stdout=None): +def generate_params_file(cmd, file, no_restore=None, outdir=None, outfile=None, stdout=None, output_format=None, include_params=None): ensure_bicep_installation(cmd.cli_ctx) minimum_supported_version = "0.7.4" @@ -4374,6 +4414,10 @@ def generate_params_file(cmd, file, no_restore=None, outdir=None, outfile=None, args += ["--outdir", outdir] if outfile: args += ["--outfile", outfile] + if output_format: + args += ["--output-format", output_format] + if include_params: + args += ["--include-params", include_params] if stdout: args += ["--stdout"] diff --git a/src/azure-cli/azure/cli/command_modules/resource/tests/latest/sample_params.bicepparam b/src/azure-cli/azure/cli/command_modules/resource/tests/latest/sample_params.bicepparam new file mode 100644 index 00000000000..5cc2fd7dc14 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/resource/tests/latest/sample_params.bicepparam @@ -0,0 +1,8 @@ +using './sample_params.bicep' + +param demoString = '' +param demoInt = 0 +param demoBool = false +param demoObject = {} +param demoArray = [] + diff --git a/src/azure-cli/azure/cli/command_modules/resource/tests/latest/test_resource.py b/src/azure-cli/azure/cli/command_modules/resource/tests/latest/test_resource.py index 2e1f58d6d1a..a1014401ad4 100644 --- a/src/azure-cli/azure/cli/command_modules/resource/tests/latest/test_resource.py +++ b/src/azure-cli/azure/cli/command_modules/resource/tests/latest/test_resource.py @@ -4987,6 +4987,66 @@ def test_bicep_list_versions(self): self.greater_than('length(@)', 0) ]) +class BicepDecompileParamsTest(ScenarioTest): + def setup(self): + super().setup() + self.cmd('az bicep uninstall') + + def tearDown(self): + super().tearDown() + self.cmd('az bicep uninstall') + + def test_bicep_decompile_params_file(self): + curr_dir = os.path.dirname(os.path.realpath(__file__)) + tf = os.path.join(curr_dir, 'test-params.json').replace('\\', '\\\\') + params_path = os.path.join(curr_dir, 'test-params.bicepparam').replace('\\', '\\\\') + self.kwargs.update({ + 'tf': tf, + 'params_path': params_path, + }) + + self.cmd('az bicep decompile-params --file {tf}') + + if os.path.exists(params_path): + os.remove(params_path) + +class BicepBuildParamsTest(ScenarioTest): + def setup(self): + super().setup() + self.cmd('az bicep uninstall') + + def tearDown(self): + super().tearDown() + self.cmd('az bicep uninstall') + + def test_bicep_build_params_file(self): + curr_dir = os.path.dirname(os.path.realpath(__file__)) + tf = os.path.join(curr_dir, 'sample_params.bicepparam').replace('\\', '\\\\') + params_path = os.path.join(curr_dir, 'sample_params.parameters.json').replace('\\', '\\\\') + self.kwargs.update({ + 'tf': tf, + 'params_path': params_path, + }) + + self.cmd('az bicep build-params --file {tf}') + + if os.path.exists(params_path): + os.remove(params_path) + + def test_bicep_build_params_file_outfile(self): + curr_dir = os.path.dirname(os.path.realpath(__file__)) + tf = os.path.join(curr_dir, 'sample_params.bicepparam').replace('\\', '\\\\') + params_path = os.path.join(curr_dir, 'sample_params.parameters.json').replace('\\', '\\\\') + self.kwargs.update({ + 'tf': tf, + 'params_path': params_path, + }) + + self.cmd('az bicep build-params --file {tf} --outfile {params_path}') + + if os.path.exists(params_path): + os.remove(params_path) + # Because don't want to record bicep cli binary class BicepBuildTest(LiveScenarioTest): @@ -5019,7 +5079,6 @@ def test_bicep_build_decompile(self): os.remove(decompile_path) class BicepGenerateParamsTest(LiveScenarioTest): - def setup(self): super().setup() self.cmd('az bicep uninstall') @@ -5028,6 +5087,34 @@ def tearDown(self): super().tearDown() self.cmd('az bicep uninstall') + def test_bicep_generate_params_output_format_only(self): + curr_dir = os.path.dirname(os.path.realpath(__file__)) + tf = os.path.join(curr_dir, 'sample_params.bicep').replace('\\', '\\\\') + params_path = os.path.join(curr_dir, 'sample_params.parameters.json').replace('\\', '\\\\') + self.kwargs.update({ + 'tf': tf, + 'params_path': params_path, + }) + + self.cmd('az bicep generate-params -f {tf} --outfile {params_path} --output-format json') + + if os.path.exists(params_path): + os.remove(params_path) + + def test_bicep_generate_params_include_params_only(self): + curr_dir = os.path.dirname(os.path.realpath(__file__)) + tf = os.path.join(curr_dir, 'sample_params.bicep').replace('\\', '\\\\') + params_path = os.path.join(curr_dir, 'sample_params.parameters.json').replace('\\', '\\\\') + self.kwargs.update({ + 'tf': tf, + 'params_path': params_path, + }) + + self.cmd('az bicep generate-params -f {tf} --outfile {params_path} --include-params all') + + if os.path.exists(params_path): + os.remove(params_path) + def test_bicep_generate_params(self): curr_dir = os.path.dirname(os.path.realpath(__file__)) tf = os.path.join(curr_dir, 'sample_params.bicep').replace('\\', '\\\\') diff --git a/src/azure-cli/azure/cli/command_modules/resource/tests/latest/test_resource_custom.py b/src/azure-cli/azure/cli/command_modules/resource/tests/latest/test_resource_custom.py index 46badb0d93e..c6c37f8607b 100644 --- a/src/azure-cli/azure/cli/command_modules/resource/tests/latest/test_resource_custom.py +++ b/src/azure-cli/azure/cli/command_modules/resource/tests/latest/test_resource_custom.py @@ -29,6 +29,8 @@ format_bicep_file, ) +from azure.cli.command_modules.resource._bicep import (run_bicep_command) + from azure.cli.core.mock import DummyCli from azure.cli.core import AzCommandsLoader from azure.cli.core.commands import AzCliCommand @@ -339,6 +341,7 @@ def test_deployment_prompt_file_order(self): def test_deployment_prompt_alphabetical_order(self): # check that params are prompted for in alphabetical order when the file is loaded with preserve_order=False curr_dir = os.path.dirname(os.path.realpath(__file__)) + template_path = os.path.join(curr_dir, 'param-validation-template.json').replace('\\', '\\\\') parameters_path = os.path.join(curr_dir, 'param-validation-params.json').replace('\\', '\\\\') parameters_with_reference_path = os.path.join(curr_dir, 'param-validation-ref-params.json').replace('\\', '\\\\') @@ -362,6 +365,53 @@ def test_deployment_bicepparam_file_input_check(self): self.assertEqual(_is_bicepparam_file_provided([['test.bicepparam']]), True) self.assertEqual(_is_bicepparam_file_provided([['test.bicepparam'], ['test.json'], ['{ \"foo\": { \"value\": \"bar\" } }']]), True) + def test_bicep_generate_params_defaults(self): + curr_dir = os.path.dirname(os.path.realpath(__file__)) + bicep_file = os.path.join(curr_dir, 'sample_params.bicep').replace('\\', '\\\\') + json_file = os.path.join(curr_dir, 'sample_params.parameters.json').replace('\\', '\\\\') + + run_bicep_command(cli_ctx, ["generate-params", bicep_file]) + is_generated_params_file_exists = os.path.exists(json_file) + self.assertTrue(is_generated_params_file_exists) + + def test_bicep_generate_params_output_format(self): + curr_dir = os.path.dirname(os.path.realpath(__file__)) + bicep_file = os.path.join(curr_dir, 'sample_params.bicep').replace('\\', '\\\\') + json_file = os.path.join(curr_dir, 'sample_params.parameters.json').replace('\\', '\\\\') + + run_bicep_command(cli_ctx, ["generate-params", bicep_file, "--output-format", "json"]) + is_generated_params_file_exists = os.path.exists(json_file) + self.assertTrue(is_generated_params_file_exists) + + def test_bicep_generate_params_include_params(self): + curr_dir = os.path.dirname(os.path.realpath(__file__)) + bicep_file = os.path.join(curr_dir, 'sample_params.bicep').replace('\\', '\\\\') + json_file = os.path.join(curr_dir, 'sample_params.parameters.json').replace('\\', '\\\\') + + run_bicep_command(cli_ctx, ["generate-params", bicep_file, "--include-params", "all"]) + is_generated_params_file_exists = os.path.exists(json_file) + self.assertTrue(is_generated_params_file_exists) + + def test_bicep_build_params_defaults(self): + curr_dir = os.path.dirname(os.path.realpath(__file__)) + param_file = os.path.join(curr_dir, 'sample_params.bicepparam').replace('\\', '\\\\') + json_file = os.path.join(curr_dir, 'sample_params.json').replace('\\', '\\\\') + + run_bicep_command(cli_ctx, ["build-params", param_file]) + is_generated_params_file_exists = os.path.exists(json_file) + + self.assertTrue(is_generated_params_file_exists) + + def test_bicep_decompile_params_defaults(self): + curr_dir = os.path.dirname(os.path.realpath(__file__)) + param_file = os.path.join(curr_dir, 'param-validation-params.bicepparam').replace('\\', '\\\\') + json_file = os.path.join(curr_dir, 'param-validation-params.json').replace('\\', '\\\\') + + run_bicep_command(cli_ctx, ["decompile-params", json_file, "--force"]) + is_generated_params_file_exists = os.path.exists(param_file) + + self.assertTrue(is_generated_params_file_exists) + @mock.patch("knack.prompting.prompt_y_n", autospec=True) @mock.patch("azure.cli.command_modules.resource.custom._what_if_deploy_arm_template_at_resource_group_core", autospec=True) def test_confirm_with_what_if_prompt_at_resource_group(self, what_if_command_mock, prompt_y_n_mock): @@ -556,7 +606,8 @@ def test_format_bicep_file(self, mock_print, mock_run_bicep_command, mock_bicep_ # Assert. mock_bicep_version_greater_than_or_equal_to.assert_called_once_with("0.12.1") - mock_run_bicep_command.assert_called_once_with(cmd.cli_ctx, ["format", file_path, "--stdout"]) - + mock_run_bicep_command.assert_called_once_with(cmd.cli_ctx, ["format", file_path, "--stdout"]) + + if __name__ == '__main__': unittest.main()