Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions src/azure-cli/azure/cli/command_modules/resource/_bicep.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import os
import re
import stat
import platform
import subprocess

from pathlib import Path
from contextlib import suppress

import requests
import semver

from six.moves.urllib.request import urlopen
from knack.log import get_logger
from knack.util import CLIError

# See: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
_semver_pattern = r"(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?" # pylint: disable=line-too-long
_logger = get_logger(__name__)


def run_bicep_command(args, auto_install=True, check_upgrade=True):
installation_path = _get_bicep_installation_path(platform.system())
installed = os.path.isfile(installation_path)

if not installed:
if auto_install:
ensure_bicep_installation()
else:
raise CLIError('Bicep CLI not found. Install it now by running "az bicep install".')
elif check_upgrade:
with suppress(CLIError):
# Checking upgrade should not raise CLIError.
# Users may continue using the current installed version.
installed_version = _get_bicep_installed_version(installation_path)
latest_release_tag = get_bicep_latest_release_tag()
latest_version = _extract_semver(get_bicep_latest_release_tag())
if installed_version and latest_version and semver.compare(installed_version, latest_version) < 0:
_logger.warning(
'A new Bicep release is available: %s. Upgrade now by running "az bicep upgrade".',
latest_release_tag,
)

return _run_command(installation_path, args)


def ensure_bicep_installation(release_tag=None):
system = platform.system()
installation_path = _get_bicep_installation_path(system)

if os.path.isfile(installation_path):
if not release_tag:
return

installed_version = _get_bicep_installed_version(installation_path)
target_version = _extract_semver(release_tag)
if installed_version and target_version and semver.compare(installed_version, target_version) == 0:
Copy link
Member

@jiasli jiasli May 25, 2023

Choose a reason for hiding this comment

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

@shenglol, if bicep has no fancy versioning schema and complies with PEP 440, can semver be replaced by packaging.version? packaging.version is used by az extension (#17667):

def ext_compat_with_cli(azext_metadata):
from azure.cli.core import __version__ as core_version
from packaging.version import parse
is_compatible, min_required, max_required = (True, None, None)
if azext_metadata:
min_required = azext_metadata.get(EXT_METADATA_MINCLICOREVERSION)
max_required = azext_metadata.get(EXT_METADATA_MAXCLICOREVERSION)
parsed_cli_version = parse(core_version)
if min_required and parsed_cli_version < parse(min_required):
is_compatible = False
elif max_required and parsed_cli_version > parse(max_required):
is_compatible = False

This will help us reduce Azure CLI’s packaging maintenance cost (#26523).

return

installation_dir = os.path.dirname(installation_path)
if not os.path.exists(installation_dir):
os.makedirs(installation_dir)

try:
release_tag = release_tag if release_tag else get_bicep_latest_release_tag()
if release_tag:
print(f"Installing Bicep CLI {release_tag}...")
else:
print("Installing Bicep CLI...")

request = urlopen(_get_bicep_download_url(system, release_tag))
with open(installation_path, "wb") as f:
f.write(request.read())

os.chmod(installation_path, os.stat(installation_path).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)

print(f'Successfully installed Bicep CLI to "{installation_path}".')
except IOError as err:
raise CLIError(f"Error while attempting to download Bicep CLI: {err}")
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you change those CLIError to some more specific error?
Please refer to azure.cli.core.azclierror

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed them to more specific error types.



def is_bicep_file(file_path):
return file_path.lower().endswith(".bicep")


def get_bicep_available_release_tags():
try:
response = requests.get("https://api.github.com/repos/Azure/bicep/releases")
return [release["tag_name"] for release in response.json()]
except IOError as err:
raise CLIError(f"Error while attempting to retrieve available Bicep versions: {err}.")


def get_bicep_latest_release_tag():
try:
response = requests.get("https://api.github.com/repos/Azure/bicep/releases/latest")
return response.json()["tag_name"]
except IOError as err:
raise CLIError(f"Error while attempting to retrieve the latest Bicep version: {err}.")


def _get_bicep_installed_version(bicep_executable_path):
installed_version_output = _run_command(bicep_executable_path, ["--version"])
return _extract_semver(installed_version_output)


def _get_bicep_download_url(system, release_tag):
download_url = f"https://github.com/Azure/bicep/releases/download/{release_tag}/{{}}"

if system == "Windows":
return download_url.format("bicep-win-x64.exe")
if system == "Linux":
return download_url.format("bicep-linux-x64")
if system == "Darwin":
return download_url.format("bicep-osx-x64")

raise CLIError(f'The platform "{format(system)}" is not supported.')


def _get_bicep_installation_path(system):
installation_folder = os.path.join(str(Path.home()), ".azure", "bin")

if system == "Windows":
return os.path.join(installation_folder, "bicep.exe")
if system in ("Linux", "Darwin"):
return os.path.join(installation_folder, "bicep")

raise CLIError(f'The platform "{format(system)}" is not supported.')


def _extract_semver(text):
semver_match = re.search(_semver_pattern, text)
return semver_match.group(0) if semver_match else None


def _run_command(bicep_installation_path, args):
process = subprocess.run([rf"{bicep_installation_path}"] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

try:
process.check_returncode()
return process.stdout.decode("utf-8")
except subprocess.CalledProcessError:
raise CLIError(process.stderr.decode("utf-8"))
82 changes: 68 additions & 14 deletions src/azure-cli/azure/cli/command_modules/resource/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@
Parameters may be supplied from a file using the `@{path}` syntax, a JSON string, or as <KEY=VALUE> pairs. Parameters are evaluated in order, so when a value is assigned twice, the latter value will be used.
It is recommended that you supply your parameters file first, and then override selectively using KEY=VALUE syntax.
- name: --template-file -f
short-summary: The path to the template file.
short-summary: The path to the template file or Bicep file.
- name: --template-uri -u
short-summary: The URI to the template file.
- name: --template-spec -s
Expand All @@ -278,7 +278,7 @@
Parameters may be supplied from a file using the `@{path}` syntax, a JSON string, or as <KEY=VALUE> pairs. Parameters are evaluated in order, so when a value is assigned twice, the latter value will be used.
It is recommended that you supply your parameters file first, and then override selectively using KEY=VALUE syntax.
- name: --template-file -f
short-summary: The path to the template file.
short-summary: The path to the template file or Bicep file.
- name: --template-uri -u
short-summary: The URI to the template file.
- name: --template-spec -s
Expand Down Expand Up @@ -392,7 +392,7 @@
Parameters may be supplied from a file using the `@{path}` syntax, a JSON string, or as <KEY=VALUE> pairs. Parameters are evaluated in order, so when a value is assigned twice, the latter value will be used.
It is recommended that you supply your parameters file first, and then override selectively using KEY=VALUE syntax.
- name: --template-file -f
short-summary: The path to the template file.
short-summary: The path to the template file or Bicep file.
- name: --template-uri -u
short-summary: The URI to the template file.
- name: --template-spec -s
Expand Down Expand Up @@ -420,7 +420,7 @@
Parameters may be supplied from a file using the `@{path}` syntax, a JSON string, or as <KEY=VALUE> pairs. Parameters are evaluated in order, so when a value is assigned twice, the latter value will be used.
It is recommended that you supply your parameters file first, and then override selectively using KEY=VALUE syntax.
- name: --template-file -f
short-summary: The path to the template file.
short-summary: The path to the template file or Bicep file.
- name: --template-uri -u
short-summary: The URI to the template file.
- name: --template-spec -s
Expand Down Expand Up @@ -456,7 +456,7 @@
Parameters may be supplied from a file using the `@{path}` syntax, a JSON string, or as <KEY=VALUE> pairs. Parameters are evaluated in order, so when a value is assigned twice, the latter value will be used.
It is recommended that you supply your parameters file first, and then override selectively using KEY=VALUE syntax.
- name: --template-file -f
short-summary: The path to the template file.
short-summary: The path to the template file or Bicep file.
- name: --template-uri -u
short-summary: The URI to the template file.
- name: --template-spec -s
Expand Down Expand Up @@ -564,7 +564,7 @@
Parameters may be supplied from a file using the `@{path}` syntax, a JSON string, or as <KEY=VALUE> pairs. Parameters are evaluated in order, so when a value is assigned twice, the latter value will be used.
It is recommended that you supply your parameters file first, and then override selectively using KEY=VALUE syntax.
- name: --template-file -f
short-summary: The path to the template file.
short-summary: The path to the template file or Bicep file.
- name: --template-uri -u
short-summary: The URI to the template file.
- name: --template-spec -s
Expand Down Expand Up @@ -594,7 +594,7 @@
Parameters may be supplied from a file using the `@{path}` syntax, a JSON string, or as <KEY=VALUE> pairs. Parameters are evaluated in order, so when a value is assigned twice, the latter value will be used.
It is recommended that you supply your parameters file first, and then override selectively using KEY=VALUE syntax.
- name: --template-file -f
short-summary: The path to the template file.
short-summary: The path to the template file or Bicep file.
- name: --template-uri -u
short-summary: The URI to the template file.
- name: --template-spec -s
Expand Down Expand Up @@ -640,7 +640,7 @@
Parameters may be supplied from a file using the `@{path}` syntax, a JSON string, or as <KEY=VALUE> pairs. Parameters are evaluated in order, so when a value is assigned twice, the latter value will be used.
It is recommended that you supply your parameters file first, and then override selectively using KEY=VALUE syntax.
- name: --template-file -f
short-summary: The path to the template file.
short-summary: The path to the template file or Bicpe file.
- name: --template-uri -u
short-summary: The URI to the template file.
- name: --template-spec -s
Expand Down Expand Up @@ -750,7 +750,7 @@
Parameters may be supplied from a file using the `@{path}` syntax, a JSON string, or as <KEY=VALUE> pairs. Parameters are evaluated in order, so when a value is assigned twice, the latter value will be used.
It is recommended that you supply your parameters file first, and then override selectively using KEY=VALUE syntax.
- name: --template-file -f
short-summary: The path to the template file.
short-summary: The path to the template file or Bicep file.
- name: --template-uri -u
short-summary: The URI to the template file.
- name: --template-spec -s
Expand Down Expand Up @@ -780,7 +780,7 @@
Parameters may be supplied from a file using the `@{path}` syntax, a JSON string, or as <KEY=VALUE> pairs. Parameters are evaluated in order, so when a value is assigned twice, the latter value will be used.
It is recommended that you supply your parameters file first, and then override selectively using KEY=VALUE syntax.
- name: --template-file -f
short-summary: The path to the template file.
short-summary: The path to the template file or Bicep file.
- name: --template-uri -u
short-summary: The URI to the template file.
- name: --template-spec -s
Expand Down Expand Up @@ -815,7 +815,7 @@
Parameters may be supplied from a file using the `@{path}` syntax, a JSON string, or as <KEY=VALUE> pairs. Parameters are evaluated in order, so when a value is assigned twice, the latter value will be used.
It is recommended that you supply your parameters file first, and then override selectively using KEY=VALUE syntax.
- name: --template-file -f
short-summary: The path to the template file.
short-summary: The path to the template file or Bicep file.
- name: --template-uri -u
short-summary: The URI to the template file.
- name: --template-spec -s
Expand Down Expand Up @@ -920,7 +920,7 @@
Parameters may be supplied from a file using the `@{path}` syntax, a JSON string, or as <KEY=VALUE> pairs. Parameters are evaluated in order, so when a value is assigned twice, the latter value will be used.
It is recommended that you supply your parameters file first, and then override selectively using KEY=VALUE syntax.
- name: --template-file -f
short-summary: The path to the template file.
short-summary: The path to the template file or Bicep file.
- name: --template-uri -u
short-summary: The URI to the template file.
- name: --template-spec -s
Expand Down Expand Up @@ -948,7 +948,7 @@
Parameters may be supplied from a file using the `@{path}` syntax, a JSON string, or as <KEY=VALUE> pairs. Parameters are evaluated in order, so when a value is assigned twice, the latter value will be used.
It is recommended that you supply your parameters file first, and then override selectively using KEY=VALUE syntax.
- name: --template-file -f
short-summary: The path to the template file.
short-summary: The path to the template file or Bicep file.
- name: --template-uri -u
short-summary: The URI to the template file.
- name: --template-spec -s
Expand Down Expand Up @@ -981,7 +981,7 @@
Parameters may be supplied from a file using the `@{path}` syntax, a JSON string, or as <KEY=VALUE> pairs. Parameters are evaluated in order, so when a value is assigned twice, the latter value will be used.
It is recommended that you supply your parameters file first, and then override selectively using KEY=VALUE syntax.
- name: --template-file -f
short-summary: The path to the template file.
short-summary: The path to the template file or Bicep file.
- name: --template-uri -u
short-summary: The URI to the template file.
- name: --template-spec -s
Expand Down Expand Up @@ -2297,3 +2297,57 @@
- name: List all versions of parent template spec.
text: az ts list -g MyResourceGroup -n TemplateSpecName
"""

helps['bicep'] = """
type: group
short-summary: Bicep CLI command group.
"""

helps['bicep install'] = """
type: command
short-summary: Install Bicep CLI.
examples:
- name: Install Bicep CLI.
text: az bicep install
- name: Install a specific version of Bicep CLI.
text: az bicep install --version v0.2.212
"""

helps['bicep upgrade'] = """
type: command
short-summary: Upgrade Bicep CLI to the latest version.
"""

helps['bicep build'] = """
type: command
short-summary: Build one or more Bicep files.
examples:
- name: Build a Bicep file.
text: az bicep build --files <bicep_file>
- name: Build multiple Bicep files.
text: az bicep build --files <bicep_file1> <bicep_file2>
- name: Build a Bicep file and prints all output to stdout.
text: az bicep build --files <bicep_file> --stdout
- name: Build multiple Bicep files and prints all output to stdout.
text: az bicep build --files <bicep_file1> <bicep_file2> --stdout
"""

helps['bicep decompile'] = """
type: command
short-summary: Attempt to decompile one or more ARM template files to Bicep files
examples:
- name: Decompile an ARM template file.
text: az bicep decompile --files <json_file>
- name: Decompile multiple ARM template files.
text: az bicep decompile --files <json_file1> <json_file2>
"""

helps['bicep version'] = """
type: command
short-summary: Show the installed version of Bicep CLI.
"""

helps['bicep list-versions'] = """
type: command
short-summary: List out all available versions of Bicep CLI.
"""
15 changes: 14 additions & 1 deletion src/azure-cli/azure/cli/command_modules/resource/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def load_arguments(self, _):
deployment_create_name_type = CLIArgumentType(options_list=['--name', '-n'], required=False, help='The deployment name. Default to template file base name')
management_group_id_type = CLIArgumentType(options_list=['--management-group-id', '-m'], required=True, help='The management group id.')
deployment_template_file_type = CLIArgumentType(options_list=['--template-file', '-f'], completer=FilesCompleter(), type=file_type,
help="a template file path in the file system")
help="a path to a template file or Bicep file in the file system")
deployment_template_uri_type = CLIArgumentType(options_list=['--template-uri', '-u'], help='a uri to a remote template file')
deployment_template_spec_type = CLIArgumentType(options_list=['--template-spec', '-s'], is_preview=True, min_api='2019-06-01', help="The template spec resource id.")
deployment_query_string_type = CLIArgumentType(options_list=['--query-string', '-q'], is_preview=True, help="The query string (a SAS token) to be used with the template-uri in the case of linked templates.")
Expand Down Expand Up @@ -569,3 +569,16 @@ def load_arguments(self, _):

with self.argument_context('ts list') as c:
c.argument('resource_group', arg_type=resource_group_name_type)

with self.argument_context('bicep build') as c:
c.argument('files', arg_type=CLIArgumentType(nargs="+", options_list=['--files', '-f'], completer=FilesCompleter(),
type=file_type, help="Space separated Bicep file paths in the file system."))
c.argument('stdout', arg_type=CLIArgumentType(options_list=['--stdout'], action='store_true',
help="When set, prints all output to stdout instead of corresponding files."))

with self.argument_context('bicep decompile') as c:
c.argument('files', arg_type=CLIArgumentType(nargs="+", options_list=['--files', '-f'], completer=FilesCompleter(),
type=file_type, help="Space separated ARM template paths in the file system."))

with self.argument_context('bicep install') as c:
c.argument('version', options_list=['--version', '-v'], help='The version of Bicep CLI to be installed. Default to the latest if not specified.')
Original file line number Diff line number Diff line change
Expand Up @@ -431,3 +431,11 @@ def load_command_table(self, _):
with self.command_group('account management-group subscription', resource_managementgroups_subscriptions_sdk, client_factory=cf_management_group_subscriptions) as g:
g.custom_command('add', 'cli_managementgroups_subscription_add')
g.custom_command('remove', 'cli_managementgroups_subscription_remove')

with self.command_group('bicep') as g:
g.custom_command('install', 'install_bicep_cli')
g.custom_command('upgrade', 'upgrade_bicep_cli')
g.custom_command('build', 'build_bicep_file')
g.custom_command('decompile', 'decompile_bicep_file')
g.custom_command('version', 'show_bicep_cli_version')
g.custom_command('list-versions', 'list_bicep_cli_versions')
Loading