Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions src/site/azext_site/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,11 @@
# pylint: disable=too-many-lines

from knack.help_files import helps # pylint: disable=unused-import

helps['site quickstart'] = """
type: command
short-summary: Quickstart deploy Site + Config + ConfigRef using an internal ARM template.
examples:
- name: Resource group scope
text: az site quickstart --name MySite01 --defaultconfiguration -g MyRG
"""
1 change: 1 addition & 0 deletions src/site/azext_site/aaz/latest/site/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
from ._list import *
from ._show import *
from ._update import *
from ._quickstart import *
219 changes: 219 additions & 0 deletions src/site/azext_site/aaz/latest/site/_quickstart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import json
from pathlib import Path
from azure.cli.core.aaz import ( # type: ignore[import-unresolved]
AAZCommand,
AAZStrArg,
AAZStrArgFormat,
AAZBoolArg,
AAZResourceGroupNameArg,
has_value,
register_command,
AAZResourceLocationArg
)
from azure.cli.core.azclierror import ( # type: ignore[import-unresolved]
InvalidArgumentValueError,
FileOperationError,
CLIInternalError,
)
from azure.cli.core import get_default_cli # type: ignore[import-unresolved]
from knack.log import get_logger

logger = get_logger(__name__)


def _resolve_template_path() -> Path:
# ...\azext_site\aaz\latest\site\_quickstart.py -> ...\azext_site\templates\infra\main.json
azext_root = Path(__file__).resolve().parents[3] # ...\azext_site
return azext_root / "templates" / "infra" / "main.json"


@register_command("site quickstart")
class Quickstart(AAZCommand):
"""Quickstart: deploy internal ARM template to create Site + Config + ConfigRef."""

_args_schema = None
Comment on lines +35 to +39
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

A new user-facing command (site quickstart) and an embedded deployment template are introduced, but there is no scenario test covering it. Since this extension already has ScenarioTest coverage (e.g., test_site.py), add a test that runs az site quickstart ... and asserts the deployment succeeded / expected outputs are returned.

Copilot uses AI. Check for mistakes.

@classmethod
def _build_arguments_schema(cls, *args, **kwargs):
if cls._args_schema is not None:
return cls._args_schema

cls._args_schema = super()._build_arguments_schema(*args, **kwargs)
_args_schema = cls._args_schema

_args_schema.name = AAZStrArg(
options=["-n", "--name"],
required=True,
help="Site name (siteName).",
fmt=AAZStrArgFormat(
pattern=r"^[a-zA-Z0-9][a-zA-Z0-9-_.]{0,62}[a-zA-Z0-9]$",
min_length=2,
max_length=64,
),
)

_args_schema.defaultconfiguration = AAZBoolArg(
options=["--defaultconfiguration", "--default-configuration"],
help="Trigger the internal ARM template flow (Site + Config + ConfigRef).",
)

_args_schema.resource_group = AAZResourceGroupNameArg(
options=["-g", "--resource-group"],
required=True,
help="Resource group for deployment.",
)

_args_schema.location = AAZResourceLocationArg(
options=["-l", "--location"],
help="Location for the deployment. Default: resource group location.",
)

_args_schema.config_name = AAZStrArg(
options=["--config-name"],
help="Optional configName override. Default in template: 'siteName-configuration'.",
)

return cls._args_schema

def _handler(self, command_args):
super()._handler(command_args)

if not self.ctx.args.defaultconfiguration:
raise InvalidArgumentValueError("Specify --defaultconfiguration to run quickstart.")

return self.handle()

def handle(self):
template = _resolve_template_path()
if not template.exists():
raise FileOperationError(f"Internal ARM template not found: {template}")

site_name = self.ctx.args.name.to_serialized_data()
rg = self.ctx.args.resource_group.to_serialized_data()
deployment_name = f"site-quickstart-{site_name}"

invoke_args = [
"deployment", "group", "create",
"--name", deployment_name,
"--resource-group", rg,
"--template-file", str(template),
"--parameters", f"siteName={site_name}",
"--only-show-errors",
"--output", "none",
]

if has_value(self.ctx.args.location):
loc = self.ctx.args.location.to_serialized_data()
invoke_args.extend(["--parameters", f"location={loc}"])

if has_value(self.ctx.args.config_name):
cfg = self.ctx.args.config_name.to_serialized_data()
invoke_args.extend(["--parameters", f"configName={cfg}"])

cli = get_default_cli()
rc = cli.invoke(invoke_args)
if rc != 0:
# Capture the original error first (before more invokes overwrite cli.result)
underlying_error = None
if getattr(cli, "result", None) is not None:
underlying_error = getattr(cli.result, "error", None)

deployment_error = None
failed_ops = None

# Try to fetch ARM deployment error object (code/message/details)
try:
show_args = [
"deployment", "group", "show",
"--name", deployment_name,
"--resource-group", rg,
"--only-show-errors",
"--query", "properties.error",
"--output", "json",
]
cli.invoke(show_args)
if getattr(cli, "result", None) is not None:
deployment_error = cli.result.result
except Exception:
deployment_error = None

# Try to fetch failed operations (often contains the most actionable message)
try:
ops_args = [
"deployment", "operation", "group", "list",
"--name", deployment_name,
"--resource-group", rg,
"--only-show-errors",
"--query",
"[?properties.provisioningState=='Failed']."
"{type:properties.targetResource.resourceType,"
" name:properties.targetResource.resourceName,"
" statusMessage:properties.statusMessage}",
"--output", "json",
]
cli.invoke(ops_args)
if getattr(cli, "result", None) is not None:
failed_ops = cli.result.result
except Exception:
failed_ops = None

msg = (
"ARM deployment failed for site quickstart. "
f"Deployment name: {deployment_name}, resource group: {rg}."
)
if underlying_error:
msg = f"{msg}\nUnderlying error: {underlying_error}"

if deployment_error:
msg = f"{msg}\nDeployment error:\n{json.dumps(deployment_error, indent=2)}"

if failed_ops:
msg = f"{msg}\nFailed operations:\n{json.dumps(failed_ops, indent=2)}"

raise CLIInternalError(msg)

# 2) Query deployment operations and print friendly success messages
ops_args = [
"deployment", "operation", "group", "list",
"--name", deployment_name,
"--resource-group", rg,
"--only-show-errors",
"--output", "none",
]
cli.invoke(ops_args)
ops = []
if getattr(cli, "result", None) is not None:
ops = cli.result.result or []

succeeded_types = set()
if isinstance(ops, list):
for op in ops:
if not isinstance(op, dict):
continue
props = op.get("properties") or {}
if not isinstance(props, dict):
continue
if props.get("provisioningState") != "Succeeded":
continue
tr = props.get("targetResource") or {}
if isinstance(tr, dict):
rtype = tr.get("resourceType")
if rtype:
succeeded_types.add(rtype)

if "Microsoft.Edge/sites" in succeeded_types:
print("Site created successfully.")
if "Microsoft.Edge/Configurations" in succeeded_types:
print("Config created successfully.")
if "Microsoft.Edge/configurationReferences" in succeeded_types:
print("Config reference created successfully.")

if not ({"Microsoft.Edge/sites", "Microsoft.Edge/Configurations", "Microsoft.Edge/configurationReferences"} & succeeded_types):
print("Deployment completed successfully.")

return None
Loading
Loading