diff --git a/.gitignore b/.gitignore index 7af160a1183..bf5d43da596 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,5 @@ pip.log az_command_coverage.txt +.venv +.local \ No newline at end of file diff --git a/src/azure-cli/azure/cli/command_modules/resource/_bicep.py b/src/azure-cli/azure/cli/command_modules/resource/_bicep.py index 0e1eedd0643..a50a2e2e00e 100644 --- a/src/azure-cli/azure/cli/command_modules/resource/_bicep.py +++ b/src/azure-cli/azure/cli/command_modules/resource/_bicep.py @@ -58,24 +58,16 @@ def validate_bicep_target_scope(template_schema, deployment_scope): def run_bicep_command(cli_ctx, args, auto_install=True, custom_env=None): - if _use_binary_from_path(cli_ctx): - from shutil import which - - if which("bicep") is None: - raise ValidationError( - 'Could not find the "bicep" executable on PATH. To install Bicep via Azure CLI, set the "bicep.use_binary_from_path" configuration to False and run "az bicep install".' # pylint: disable=line-too-long - ) - - bicep_version_message = _run_command("bicep", ["--version"]) + system = platform.system() + bicep_executable_path = _get_bicep_executable_path(cli_ctx, system) + if bicep_executable_path and not _is_bicep_installation_path(bicep_executable_path, system): + bicep_version_message = _get_bicep_installed_version(bicep_executable_path) _logger.debug("Using Bicep CLI from PATH. %s", bicep_version_message) - return _run_command("bicep", args, custom_env) - - installation_path = _get_bicep_installation_path(platform.system()) - _logger.debug("Bicep CLI installation path: %s", installation_path) + return _run_command(bicep_executable_path, args, custom_env) - installed = os.path.isfile(installation_path) + installed = os.path.isfile(bicep_executable_path) _logger.debug("Bicep CLI installed: %s.", installed) check_version = cli_ctx.config.getboolean("bicep", "check_version", True) @@ -91,7 +83,7 @@ def run_bicep_command(cli_ctx, args, auto_install=True, custom_env=None): with suppress(ClientRequestError): # Checking upgrade should ignore connection issues. # Users may continue using the current installed version. - installed_version = _get_bicep_installed_version(installation_path) + installed_version = _get_bicep_installed_version(bicep_executable_path) latest_release_tag = get_bicep_latest_release_tag() if cache_expired else latest_release_tag latest_version = _extract_version(latest_release_tag) @@ -104,12 +96,12 @@ def run_bicep_command(cli_ctx, args, auto_install=True, custom_env=None): if cache_expired: _refresh_bicep_version_check_cache(latest_release_tag) - return _run_command(installation_path, args, custom_env) + return _run_command(bicep_executable_path, args, custom_env) def ensure_bicep_installation(cli_ctx, release_tag=None, target_platform=None, stdout=True): system = platform.system() - installation_path = _get_bicep_installation_path(system) + installation_path = _get_bicep_executable_path(cli_ctx, system) if os.path.isfile(installation_path): if not release_tag: @@ -120,6 +112,9 @@ def ensure_bicep_installation(cli_ctx, release_tag=None, target_platform=None, s if installed_version and target_version and installed_version == target_version: return + if not _is_bicep_installation_path(installation_path, system): + installation_path = _get_bicep_installation_path(system) + installation_dir = os.path.dirname(installation_path) if not os.path.exists(installation_dir): os.makedirs(installation_dir) @@ -156,10 +151,14 @@ def ensure_bicep_installation(cli_ctx, release_tag=None, target_platform=None, s def remove_bicep_installation(cli_ctx): system = platform.system() - installation_path = _get_bicep_installation_path(system) + bicep_executable_path = _get_bicep_executable_path(cli_ctx, system) - if os.path.exists(installation_path): - os.remove(installation_path) + if not _is_bicep_installation_path(bicep_executable_path, system): + raise ValidationError( + f'The bicep executable "{bicep_executable_path}" is not managed by Azure CLI. To install Bicep via Azure CLI, set the "bicep.use_binary_from_path" configuration to False and run "az bicep install".' # pylint: disable=line-too-long + ) + if os.path.exists(bicep_executable_path): + os.remove(bicep_executable_path) if os.path.exists(_bicep_version_check_file_path): os.remove(_bicep_version_check_file_path) @@ -196,10 +195,10 @@ def get_bicep_latest_release_tag(): raise ClientRequestError(f"Error while attempting to retrieve the latest Bicep version: {err}.") -def bicep_version_greater_than_or_equal_to(version): +def bicep_version_greater_than_or_equal_to(cli_ctx, version): system = platform.system() - installation_path = _get_bicep_installation_path(system) - installed_version = _get_bicep_installed_version(installation_path) + bicep_executable_path = _get_bicep_executable_path(cli_ctx, system) + installed_version = _get_bicep_installed_version(bicep_executable_path) parsed_version = semver.VersionInfo.parse(version) return installed_version >= parsed_version @@ -282,11 +281,34 @@ def _get_bicep_download_url(system, release_tag, target_platform=None): return download_url.format("bicep-linux-musl-x64") return download_url.format("bicep-linux-x64") if system == "Darwin": - return download_url.format("bicep-osx-x64") + return download_url.format("bicep-osx-arm64") if platform.processor() == 'arm' else download_url.format("bicep-osx-x64") raise ValidationError(f'The platform "{system}" is not supported.') +def _get_bicep_executable_path(cli_ctx, system): + if _use_binary_from_path(cli_ctx): + from shutil import which + + bicep_executable_path = which("bicep") + + if bicep_executable_path is None: + raise ValidationError( + 'Could not find the "bicep" executable on PATH. To install Bicep via Azure CLI, set the "bicep.use_binary_from_path" configuration to False and run "az bicep install".' # pylint: disable=line-too-long + ) + + return bicep_executable_path + + bicep_executable_path = _get_bicep_installation_path(system) + _logger.debug("Bicep CLI installation path: %s", bicep_executable_path) + + return bicep_executable_path + + +def _is_bicep_installation_path(bicep_executable_path, system): + return bicep_executable_path == _get_bicep_installation_path(system) + + def _get_bicep_installation_path(system): if system == "Windows": return os.path.join(_bicep_installation_dir, "bicep.exe") @@ -301,9 +323,9 @@ def _extract_version(text): return semver.VersionInfo.parse(semver_match.group(0)) if semver_match else None -def _run_command(bicep_installation_path, args, custom_env=None): +def _run_command(bicep_executable_path, args, custom_env=None): process = subprocess.run( - [rf"{bicep_installation_path}"] + args, + [rf"{bicep_executable_path}"] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=custom_env) 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 3b3ee57eda4..2747e033c92 100644 --- a/src/azure-cli/azure/cli/command_modules/resource/custom.py +++ b/src/azure-cli/azure/cli/command_modules/resource/custom.py @@ -1062,11 +1062,11 @@ def _parse_bicepparam_file(cmd, template_file, parameters): ensure_bicep_installation(cmd.cli_ctx, stdout=False) minimum_supported_version_bicepparam_compilation = "0.14.85" - if not bicep_version_greater_than_or_equal_to(minimum_supported_version_bicepparam_compilation): + if not bicep_version_greater_than_or_equal_to(cmd.cli_ctx, minimum_supported_version_bicepparam_compilation): raise ArgumentUsageError(f"Unable to compile .bicepparam file with the current version of Bicep CLI. Please upgrade Bicep CLI to {minimum_supported_version_bicepparam_compilation} or later.") minimum_supported_version_supplemental_param = "0.22.6" - if len(parameters) > 1 and not bicep_version_greater_than_or_equal_to(minimum_supported_version_supplemental_param): + if len(parameters) > 1 and not bicep_version_greater_than_or_equal_to(cmd.cli_ctx, minimum_supported_version_supplemental_param): raise ArgumentUsageError(f"Current version of Bicep CLI does not support supplemental parameters with .bicepparam file. Please upgrade Bicep CLI to {minimum_supported_version_supplemental_param} or later.") bicepparam_file = _get_bicepparam_file_path(parameters) @@ -4462,7 +4462,7 @@ def format_bicep_file(cmd, file, stdout=None, outdir=None, outfile=None, newline ensure_bicep_installation(cmd.cli_ctx, stdout=False) minimum_supported_version = "0.12.1" - if bicep_version_greater_than_or_equal_to(minimum_supported_version): + if bicep_version_greater_than_or_equal_to(cmd.cli_ctx, minimum_supported_version): args = ["format", file] if outdir: args += ["--outdir", outdir] @@ -4491,23 +4491,23 @@ def publish_bicep_file(cmd, file, target, documentationUri=None, with_source=Non ensure_bicep_installation(cmd.cli_ctx) minimum_supported_version = "0.4.1008" - if bicep_version_greater_than_or_equal_to(minimum_supported_version): + if bicep_version_greater_than_or_equal_to(cmd.cli_ctx, minimum_supported_version): args = ["publish", file, "--target", target] if documentationUri: minimum_supported_version_for_documentationUri_parameter = "0.14.46" - if bicep_version_greater_than_or_equal_to(minimum_supported_version_for_documentationUri_parameter): + if bicep_version_greater_than_or_equal_to(cmd.cli_ctx, minimum_supported_version_for_documentationUri_parameter): args += ["--documentationUri", documentationUri] else: logger.error("az bicep publish with --documentationUri/-d parameter could not be executed with the current version of Bicep CLI. Please upgrade Bicep CLI to v%s or later.", minimum_supported_version_for_documentationUri_parameter) if with_source: minimum_supported_version_for_publish_with_source = "0.23.1" - if bicep_version_greater_than_or_equal_to(minimum_supported_version_for_publish_with_source): + if bicep_version_greater_than_or_equal_to(cmd.cli_ctx, minimum_supported_version_for_publish_with_source): args += ["--with-source"] else: logger.error("az bicep publish with --with-source/-s parameter could not be executed with the current version of Bicep CLI. Please upgrade Bicep CLI to v%s or later.", minimum_supported_version_for_publish_with_source) if force: minimum_supported_version_for_publish_force = "0.17.1" - if bicep_version_greater_than_or_equal_to(minimum_supported_version_for_publish_force): + if bicep_version_greater_than_or_equal_to(cmd.cli_ctx, minimum_supported_version_for_publish_force): args += ["--force"] else: logger.error("az bicep publish with --force parameter could not be executed with the current version of Bicep CLI. Please upgrade Bicep CLI to v%s or later.", minimum_supported_version_for_publish_force) @@ -4520,7 +4520,7 @@ def restore_bicep_file(cmd, file, force=None): ensure_bicep_installation(cmd.cli_ctx) minimum_supported_version = "0.4.1008" - if bicep_version_greater_than_or_equal_to(minimum_supported_version): + if bicep_version_greater_than_or_equal_to(cmd.cli_ctx, minimum_supported_version): args = ["restore", file] if force: args += ["--force"] @@ -4540,7 +4540,7 @@ def decompileparams_bicep_file(cmd, file, bicep_file=None, outdir=None, outfile= ensure_bicep_installation(cmd.cli_ctx) minimum_supported_version = "0.18.4" - if bicep_version_greater_than_or_equal_to(minimum_supported_version): + if bicep_version_greater_than_or_equal_to(cmd.cli_ctx, minimum_supported_version): args = ["decompile-params", file] if bicep_file: args += ["--bicep-file", bicep_file] @@ -4571,7 +4571,7 @@ def generate_params_file(cmd, file, no_restore=None, outdir=None, outfile=None, ensure_bicep_installation(cmd.cli_ctx, stdout=False) minimum_supported_version = "0.7.4" - if bicep_version_greater_than_or_equal_to(minimum_supported_version): + if bicep_version_greater_than_or_equal_to(cmd.cli_ctx, minimum_supported_version): args = ["generate-params", file] if no_restore: args += ["--no-restore"] @@ -4598,7 +4598,7 @@ def lint_bicep_file(cmd, file, no_restore=None, diagnostics_format=None): ensure_bicep_installation(cmd.cli_ctx, stdout=False) minimum_supported_version = "0.7.4" - if bicep_version_greater_than_or_equal_to(minimum_supported_version): + if bicep_version_greater_than_or_equal_to(cmd.cli_ctx, minimum_supported_version): args = ["lint", file] if no_restore: args += ["--no-restore"] 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 6dc307c0011..f770acc9488 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 @@ -672,7 +672,7 @@ def test_format_bicep_file(self, mock_print, mock_run_bicep_command, mock_bicep_ format_bicep_file(cmd, file_path, stdout=stdout) # Assert. - mock_bicep_version_greater_than_or_equal_to.assert_called_once_with("0.12.1") + mock_bicep_version_greater_than_or_equal_to.assert_called_once_with(cmd.cli_ctx, "0.12.1") mock_run_bicep_command.assert_called_once_with(cmd.cli_ctx, ["format", file_path, "--stdout"]) class TestPublishWithSource(unittest.TestCase): @@ -688,7 +688,7 @@ def test_publish_withsource(self, mock_run_bicep_command, mock_bicep_version_gre # Assert. mock_bicep_version_greater_than_or_equal_to.assert_has_calls([ - mock.call("0.4.1008"), # Min version for 'bicep publish' + mock.call(cmd.cli_ctx, "0.4.1008"), # Min version for 'bicep publish' ]) mock_run_bicep_command.assert_called_once_with(cmd.cli_ctx, ['publish', file_path, '--target', 'br:contoso.azurecr.io/bicep/mymodule:v1']) @@ -704,8 +704,8 @@ def test_publish_without_source(self, mock_run_bicep_command, mock_bicep_version # Assert. mock_bicep_version_greater_than_or_equal_to.assert_has_calls([ - mock.call("0.4.1008"), # Min version for 'bicep publish' - mock.call("0.23.1") # Min version for 'bicep publish --with-source' + mock.call(cmd.cli_ctx, "0.4.1008"), # Min version for 'bicep publish' + mock.call(cmd.cli_ctx, "0.23.1") # Min version for 'bicep publish --with-source' ]) mock_run_bicep_command.assert_called_once_with(cmd.cli_ctx, ['publish', file_path, '--target', 'br:contoso.azurecr.io/bicep/mymodule:v1', '--with-source'])