Skip to content
Closed
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
139d152
feat: enhanced target create with onboarding simplification flags
Apr 14, 2026
5d9fb5a
refactor: extract onboarding handlers into focused modules
Apr 14, 2026
5374406
fix: resolve context_id before init-hierarchy and fix sub_id lookup
Apr 14, 2026
cc808d4
refactor: remove standalone 'target prepare' command
Apr 14, 2026
be0e419
refactor: remove standalone 'hierarchy create' command
Apr 14, 2026
9b5c319
fix: address PR review comments from Copilot
Apr 14, 2026
bc354b4
refactor: align with team feedback - separate target init, remove ini…
Apr 14, 2026
a927395
feat: add target deploy command (review → publish → install)
Apr 14, 2026
3bb524c
feat: enhance target deploy with friendly name, resume-from, config-set
Apr 14, 2026
d88dc65
fix: extract solutionVersionId from properties.id in LRO response
Apr 14, 2026
3ca0b77
fix: add --solution flag to config-set and improve logging
Apr 14, 2026
066689c
refactor: remove skip flags, fix linter errors, add short aliases
Apr 14, 2026
e5e330c
cleanup: remove duplicate output from target init
Apr 14, 2026
84a10dc
cleanup: remove preview tags from target and support command groups
Apr 14, 2026
d81f4bc
fix: step counter display in target deploy
Apr 14, 2026
230ea5c
fix: return install LRO result matching native target install format
Apr 14, 2026
fa2bc60
cleanup: remove diagnostic summary from target init success output
Apr 15, 2026
51e988e
feat: context create accepts --site-id to auto-create site reference
Apr 15, 2026
45ad448
refactor: remove --resume-from and --solution-version-id from target …
Apr 15, 2026
bca3b13
feat: merge deploy into target install, remove standalone deploy command
Apr 15, 2026
c5e06bb
refactor: remove config-template args, auto-derive from solution temp…
Apr 15, 2026
ba7bd26
refactor: rename 'target init' to 'cluster init'
Apr 15, 2026
d98e2aa
refactor: remove default target-specification auto-injection
Apr 15, 2026
7250fe9
refactor: remove --solution-template-rg per Shubham's feedback
Apr 15, 2026
24992eb
cleanup: remove short aliases, use full option names only
Apr 15, 2026
7927663
fix: remove leftover solution_template_rg reference in config path
Apr 15, 2026
e600c9a
refactor: remove skip, kube, and reinstall options from cluster init
Apr 16, 2026
4642f2d
feat: add hierarchy create command (ResourceGroup + ServiceGroup)
Apr 16, 2026
e735464
feat: hierarchy create with SG support, RBAC propagation, RG-scoped c…
Apr 16, 2026
5014bb0
refactor: remove --solution-instance-name and --solution-dependencies…
Apr 20, 2026
5f91bc6
fix: resolve all pylint/flake8 lint errors
Apr 20, 2026
3bec477
chore: bump version to 5.2.0, update HISTORY.rst
Apr 20, 2026
288136b
fix: linter errors - add group help, fix example, add short aliases
Apr 20, 2026
bc14ab8
fix: handle @file.yaml in hierarchy-spec parser (P1 bug)
Apr 20, 2026
688c9bf
feat: add -l alias for --configuration-location on hierarchy create
Apr 21, 2026
40242c8
fix: move all progress output to stderr for clean -o json/table/tsv
Apr 21, 2026
705d480
feat: tree-style hierarchy output, remove RBAC waiting message
Apr 21, 2026
09c270e
cleanup: remove folder emoji from hierarchy output
Apr 21, 2026
4be5606
fix: tree output, add --solution-template-rg, clean deploy output
Apr 21, 2026
f534322
fix: target create service-group messages to stderr
Apr 21, 2026
3352fea
style: use tree characters (├── └──) and ✓ for all command output
Apr 21, 2026
ed0afe4
clean: remove all recommendation/mitigation text from errors
Apr 21, 2026
e6e2407
fix: reduce RBAC wait to 3 retries (30s max)
Apr 21, 2026
76ca49f
fix: add Install ✓ tick after AAZ LRO completes
Apr 22, 2026
a92175b
fix: increase RBAC retry to 6 (60s max)
Apr 22, 2026
cba547c
fix: increase RBAC wait to 120s, fail instead of continue
Apr 22, 2026
d379e38
fix: add stderr progress for context create --site-id
Apr 22, 2026
b452323
fix: cross-RG config-set uses --solution-template-rg correctly
Apr 22, 2026
2219331
feat: add client-side name validation in hierarchy create
Apr 22, 2026
de55d7d
fix: all pylint and flake8 linter errors
Apr 23, 2026
ba47c31
Added sync commands
guptahars Apr 23, 2026
93962b4
Merge pull request #5 from guptahars/main
atharvau Apr 24, 2026
f51abf6
Delete _sync.py
atharvau Apr 24, 2026
9260c54
Simplify cluster init to AIO-only cert-manager path
Apr 24, 2026
d9efcf9
Add linter exclusion for sync --local-connected-registry-ip
Apr 24, 2026
a920bc4
Fix C0301 line-too-long in _params.py for --cert-manager-version help
Apr 24, 2026
d125500
Replace --cert-manager-version with --extension-dependency-version
Apr 24, 2026
d1820c5
Migrate --extension-dependency-version and --hierarchy-spec to native…
Apr 24, 2026
ff272bd
Enforce hierarchy-spec 'children' must be a list + add E2E test harness
Apr 24, 2026
e9e5ba9
Migrate cluster init + hierarchy create to AAZ for native shorthand p…
Apr 24, 2026
275748c
Fix disallowed_html_tag linter: replace <cluster-name> with {cluster-…
Apr 24, 2026
2386481
workload-orchestration: add --custom-location-* overrides + drop @fil…
Apr 24, 2026
6a132ca
workload-orchestration: strip trailing blank lines (pylint C0305 / fl…
Apr 24, 2026
8871921
Remove E2E test ps1 and test_onboarding pytest files
Apr 26, 2026
1dbb4df
workload-orchestration: drop --solution-template-version-id from targ…
Apr 26, 2026
1d2f333
feat(context): add-capability, remove-capability, list-capability, sh…
Apr 28, 2026
37e036b
fix: resolve pylint W0613 unused-argument for state param
Apr 28, 2026
966c83b
fix: remove trailing newline (C0305/W391)
Apr 28, 2026
849e3e4
fix(wo): logging cleanup — single-line site-ref log, hierarchy text, …
Apr 28, 2026
3e6cc04
fix(wo): azdev-style — drop reimport, unused import, suppress unused-…
Apr 28, 2026
aa1820f
fix(wo): cluster init — drop redundant cluster name echo line
Apr 28, 2026
0a67e62
fix(wo): cluster init — stop printing the same error 3 times
Apr 28, 2026
cde4f97
fix(wo): never leak raw az subcommand line in error output
Apr 28, 2026
1a05afb
fix(wo): site-reference name must fit ^[a-zA-Z0-9-]{3,24}$ - cap site…
Apr 28, 2026
945b5eb
fix(wo): hierarchy create reuse log - capitalize Site, drop RG/SG one…
Apr 28, 2026
63356c9
feat(wo): hierarchy create - find-or-create Configuration and Configu…
Apr 28, 2026
3384abb
fix(wo): drop resource count from 'Hierarchy created' success line
Apr 28, 2026
eafcff6
feat(wo): hierarchy create - if existing Site has a ConfigRef, leave …
Apr 28, 2026
8c54ed0
fix(wo): hierarchy create SG path - parse JSON from regional ARM GET
Apr 29, 2026
eeb45b2
context create: drop manual run hint on site-ref failure
Apr 29, 2026
4359ddf
target create: simplify service-group link log messages
Apr 29, 2026
bda5d2a
target install: remove step progress output, keep only JSON
Apr 29, 2026
0d90f44
refactor: rename onboarding/ to common/, consolidate by resource type
Apr 29, 2026
48b60d4
chore(common): remove dead code, deduplicate helpers, trim unused con…
Apr 29, 2026
7ee26fc
fix(target): handle HTTPError from send_raw_request in config-set
Apr 29, 2026
33bf253
chore(hierarchy): remove '✅ Hierarchy created' log message
Apr 29, 2026
3757e6e
fix: add --solution-template-resource-group alias and license header
Apr 30, 2026
ae7bf2d
style: fix E303 too many blank lines in hierarchy.py
Apr 30, 2026
5264f9e
feat: make --target-specification required on target create
Apr 30, 2026
f75ca1a
fix: cluster init CL validation, hierarchy bugs, output format
Apr 30, 2026
05edcf3
style: fix E303 too many blank lines
Apr 30, 2026
da6558c
fix: let ValidationError propagate from CL check in cluster init
Apr 30, 2026
452fede
style: add tree characters to cluster init output
May 1, 2026
fdc913e
feat: show patched fields in hierarchy site update log message
May 4, 2026
85bfc65
feat: show 'already present' in hierarchy site update message
May 4, 2026
1c1eb67
fix: derive ARM endpoint from az cloud config instead of hardcoding
May 4, 2026
85a291d
style: fix E305 missing blank lines after function definition in cons…
May 4, 2026
d50882a
chore: remove test/scratch files accidentally committed
May 5, 2026
be03500
Added check for valid custom location
guptahars May 5, 2026
ce6b516
feat(capability): simplify add/remove commands, remove list/show
May 5, 2026
988a9f0
fix(hierarchy): use active_directory_resource_id for OAuth scope
May 5, 2026
35f2e7e
fix(target,context): remove explicit resource= to fix regional endpoi…
May 5, 2026
9bed8f1
fix: remove duplicate site status prints in SG hierarchy create
May 6, 2026
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
20 changes: 20 additions & 0 deletions src/workload-orchestration/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@

Release History
===============
5.2.0
++++++
* **CLI Onboarding Simplification** — reduces onboarding from 11 commands to 4:
* Added ``az workload-orchestration cluster init`` command:
* Prepares Arc-connected clusters for WO (cert-manager, trust-manager, extension, custom location)
* Idempotent — safely skips components already installed
* Supports ``--release-train``, ``--extension-version``, ``--custom-location-name``
* Added ``az workload-orchestration hierarchy create`` command:
* Creates full hierarchy stack (Site + Configuration + ConfigurationReference) in one command
* Supports ResourceGroup (shorthand or YAML) and ServiceGroup (up to 3 levels, recursive)
* Handles RBAC propagation waits for ServiceGroup hierarchies
* Enhanced ``az workload-orchestration context create``:
* Added ``--site-id`` argument to auto-create site-reference after context creation
* Enhanced ``az workload-orchestration target create``:
* Added ``--service-group`` argument to auto-link target to a Service Group after creation
* Enhanced ``az workload-orchestration target install``:
* Added ``--solution-template-version-id``, ``--solution-template-name``, ``--solution-template-version`` for full deploy chain (review → publish → install)
* Added ``--configuration`` to set config values before review (auto-derives config template args)
* Existing ``--solution-version-id`` direct install flow unchanged

5.1.1
++++++
* Resolved solution template name to uniqueIdentifier for ``az workload-orchestration target solution-revision-list`` and ``az workload-orchestration target solution-instance-list``
Expand Down
49 changes: 49 additions & 0 deletions src/workload-orchestration/azext_workload_orchestration/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,52 @@
- name: Use a specific kubeconfig and context
text: az workload-orchestration support create-bundle --kube-config ~/.kube/prod-config --kube-context my-cluster
"""

helps['workload-orchestration cluster init'] = """
type: command
short-summary: Prepare an Arc-connected Kubernetes cluster for Workload Orchestration.
long-summary: |
Installs all prerequisites on an Arc-connected cluster to make it ready for
Workload Orchestration. This is an idempotent operation that skips components
already installed.

Steps performed:
1. Verify cluster is Arc-connected with required features enabled
2. Install cert-manager (if not present)
3. Install trust-manager (if not present)
4. Install WO extension (if not present)
5. Create custom location (if not present)

After running this command, use the output custom location ID with
'az workload-orchestration target create --extended-location'.
examples:
- name: Initialize a cluster with defaults
text: az workload-orchestration cluster init -c my-cluster -g my-rg -l eastus2euap
- name: Initialize with a specific release train
text: az workload-orchestration cluster init -c my-cluster -g my-rg -l eastus2euap --release-train dev
- name: Pin a specific extension version
text: az workload-orchestration cluster init -c my-cluster -g my-rg -l eastus2euap --extension-version 2.1.28
- name: Custom location name
text: az workload-orchestration cluster init -c my-cluster -g my-rg -l eastus2euap --custom-location-name my-cl
"""

helps['workload-orchestration hierarchy create'] = """
type: command
short-summary: Create a hierarchy (Site + Configuration + ConfigurationReference) in one command.
long-summary: |
Creates the full resource stack for a hierarchy level:
1. Site (with level label)
2. Configuration (in specified region)
3. ConfigurationReference (links site to configuration)

Supports two types:
- ResourceGroup (default): single site in a resource group
- ServiceGroup: nested sites under a service group (up to 3 levels)
examples:
- name: Create RG hierarchy from YAML file
text: az workload-orchestration hierarchy create -g my-rg --configuration-location eastus2euap --hierarchy-spec "@hierarchy.yaml"
- name: Create RG hierarchy with shorthand
text: az workload-orchestration hierarchy create -g my-rg --configuration-location eastus2euap --hierarchy-spec "name=Mehoopany level=factory"
- name: Create ServiceGroup hierarchy from YAML
text: az workload-orchestration hierarchy create --configuration-location eastus2euap --hierarchy-spec "@sg-hierarchy.yaml"
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

The ServiceGroup example omits -g/--resource-group, but this command requires --resource-group (and the argument is marked required). The example as written will fail; update it to include the resource group parameter.

Suggested change
text: az workload-orchestration hierarchy create --configuration-location eastus2euap --hierarchy-spec "@sg-hierarchy.yaml"
text: az workload-orchestration hierarchy create -g my-rg --configuration-location eastus2euap --hierarchy-spec "@sg-hierarchy.yaml"

Copilot uses AI. Check for mistakes.
"""
62 changes: 62 additions & 0 deletions src/workload-orchestration/azext_workload_orchestration/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,65 @@ def load_arguments(self, _): # pylint: disable=unused-argument
options_list=['--kube-context'],
help='Kubernetes context to use. Defaults to current context.',
)
c.argument(
'skip_site_reference',
options_list=['--skip-site-reference'],
action='store_true',
help='Skip auto-creation of site-reference to context.',
)
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

The new skip_site_reference argument is registered under the workload-orchestration support create-bundle command context, but it is unrelated to support bundles and is not consumed by that command. This will expose a confusing/unused flag to users; if the intent is to control site-reference creation it should be registered under the relevant context/onboarding command (or removed).

Suggested change
c.argument(
'skip_site_reference',
options_list=['--skip-site-reference'],
action='store_true',
help='Skip auto-creation of site-reference to context.',
)

Copilot uses AI. Check for mistakes.

with self.argument_context('workload-orchestration cluster init') as c:
c.argument('cluster_name', options_list=['--cluster-name', '-c'],
help='Name of the Arc-connected Kubernetes cluster.', required=True)
c.argument('resource_group', options_list=['--resource-group', '-g'],
help='Resource group of the Arc-connected cluster.', required=True)
c.argument('location', options_list=['--location', '-l'],
help='Azure region for the custom location (e.g., eastus2euap).', required=True)
c.argument('release_train', options_list=['--release-train'],
help='Extension release train. Default: stable.')
c.argument('extension_version', options_list=['--extension-version'],
help='Specific WO extension version to install.')
c.argument('extension_name', options_list=['--extension-name'],
help='Name for the WO extension resource. Default: wo-extension.')
c.argument('custom_location_name', options_list=['--custom-location-name'],
help='Name for the custom location. Default: `<cluster-name>-cl`.')

with self.argument_context('workload-orchestration hierarchy create') as c:
c.argument('resource_group', options_list=['--resource-group', '-g'],
help='Resource group for Configuration resources.', required=True)
c.argument('configuration_location', options_list=['--configuration-location'],
help='Azure region for the Configuration resource (e.g., eastus2euap).', required=True)
c.argument('hierarchy_spec', options_list=['--hierarchy-spec'],
help='Hierarchy specification as YAML/JSON file (@file.yaml) or shorthand syntax.',
required=True, type=_parse_hierarchy_spec)


def _parse_hierarchy_spec(value):
"""Parse hierarchy spec from file path or shorthand syntax."""
import os

# Handle @file syntax (@ may be stripped by CLI framework)
filepath = value.lstrip('@')
if os.path.exists(filepath):
try:
import yaml
except ImportError:
import json as yaml_fallback
with open(filepath, 'r', encoding='utf-8') as f:
return yaml_fallback.load(f)
with open(filepath, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)

# Shorthand: name=X level=Y type=Z
result = {}
for pair in value.split():
if '=' in pair:
k, v = pair.split('=', 1)
result[k] = v
if not result:
from azure.cli.core.azclierror import ValidationError
raise ValidationError(
f"Invalid hierarchy-spec: '{value}'. "
"Use a YAML file path or shorthand: name=X level=Y"
)
return result
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# flake8: noqa

from azure.cli.core.aaz import *
from azure.cli.core.azclierror import CLIInternalError as CLIError


@register_command(
Expand Down Expand Up @@ -128,6 +129,14 @@ def _build_arguments_schema(cls, *args, **kwargs):

tags = cls._args_schema.tags
tags.Element = AAZStrArg()

# Custom arg: --site-id (not sent to ARM, used in post_operations)
_args_schema.site_id = AAZStrArg(
options=["--site-id"],
arg_group="Onboarding",
help="ARM resource ID of a Site to auto-create a site reference after context creation.",
)

return cls._args_schema

def _execute_operations(self):
Expand All @@ -141,7 +150,47 @@ def pre_operations(self):

@register_callback
def post_operations(self):
pass
if hasattr(self.ctx.args, 'site_id') and self.ctx.args.site_id:
self._create_site_reference()

def _create_site_reference(self):
"""Auto-create a site reference linking the site to this context."""
import logging
import re
logger = logging.getLogger(__name__)

site_id = str(self.ctx.args.site_id)
context_name = str(self.ctx.args.context_name)
rg = str(self.ctx.args.resource_group)

# Extract site name from ARM ID for the reference name
site_name = site_id.rstrip("/").split("/")[-1]
ref_name = f"{site_name}-ref"
# Sanitize: only alphanumeric and hyphens, 3-61 chars
ref_name = re.sub(r'[^a-zA-Z0-9-]', '-', ref_name)[:61]

logger.info("Creating site reference '%s' -> %s", ref_name, site_id)

try:
from azext_workload_orchestration.onboarding.utils import invoke_cli_command, CmdProxy
cmd_proxy = CmdProxy(self.ctx.cli_ctx)
invoke_cli_command(cmd_proxy, [
"workload-orchestration", "context", "site-reference", "create",
"-g", rg,
"--context-name", context_name,
"--site-reference-name", ref_name,
"--site-id", site_id,
])
logger.info("Site reference '%s' created successfully", ref_name)
except Exception as exc:
logger.warning("Site reference creation failed: %s", exc)
raise CLIError(
f"Context created successfully, but site reference creation failed: {exc}\n"
f"Run manually:\n"
f" az workload-orchestration context site-reference create "
f"-g {rg} --context-name {context_name} "
f"--site-reference-name {ref_name} --site-id {site_id}"
)

def _output(self, *args, **kwargs):
result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

logger = logging.getLogger(__name__)


@register_command(
"workload-orchestration target create",
)
Expand Down Expand Up @@ -117,10 +118,16 @@ def _build_arguments_schema(cls, *args, **kwargs):
options=["--target-specification"],
arg_group="Properties",
help="Specifies that we are using Helm charts for the k8s deployment",
required=True,

)

# Onboarding simplification arguments
_args_schema.service_group = AAZStrArg(
options=["--service-group"],
arg_group="Onboarding",
help="ServiceGroup name to auto-link this target to after creation.",
)

capabilities = cls._args_schema.capabilities
capabilities.Element = AAZStrArg()

Expand Down Expand Up @@ -170,30 +177,63 @@ def _execute_operations(self):

@register_callback
def pre_operations(self):
# If context_id is not provided, try to get it from config
# Resolve context_id from CLI config if not provided
if not self.ctx.args.context_id:
try:
# Attempt to retrieve the context_id from the config file
context_id = self.ctx.cli_ctx.config.get('workload_orchestration', 'context_id')
if context_id:
self.ctx.args.context_id = context_id
else:
# This else block handles the case where the section exists, but the key is empty
raise CLIInternalError(
"No context-id was provided, and no default context is set. "
"Please provide the --context-id argument or set a default context using 'az workload-orchestration context use'."
)
except configparser.NoSectionError as e:
logger.debug("Config section 'workload_orchestration' not found: %s", e)
# This is the fix: catch the specific error when the [workload_orchestration] section is missing
self._resolve_context_id_from_config()

def _resolve_context_id_from_config(self):
"""Resolve context_id from CLI config if not already set."""
try:
context_id = self.ctx.cli_ctx.config.get('workload_orchestration', 'context_id')
if context_id:
self.ctx.args.context_id = context_id
else:
raise CLIInternalError(
"No context-id was provided, and no default context is set. "
"Please provide the --context-id argument or set a default context using 'az workload-orchestration context use'."
"Please provide the --context-id argument "
"or set a default context using 'az workload-orchestration context use'."
)
except configparser.NoSectionError as e:
logger.debug("Config section 'workload_orchestration' not found: %s", e)
raise CLIInternalError(
"No context-id was provided, and no default context is set. "
"Please provide the --context-id argument "
"or set a default context using 'az workload-orchestration context use'."
)

@register_callback
def post_operations(self):
pass
# --service-group: auto-link target to SG after creation
if hasattr(self.ctx.args, 'service_group') and self.ctx.args.service_group:
self._handle_service_group_link()

def _handle_service_group_link(self):
"""Link the created target to a service group."""
from azext_workload_orchestration.onboarding.target_sg_link import (
link_target_to_service_group
)
from azext_workload_orchestration.onboarding.utils import CmdProxy
sg_name = str(self.ctx.args.service_group)
# Get target ID from the response
target_id = None
if hasattr(self.ctx.vars, 'instance') and self.ctx.vars.instance:
target_id = self.ctx.vars.instance.get("id")

if not target_id:
# Construct it
sub_id = self.ctx.subscription_id
rg = str(self.ctx.args.resource_group)
name = str(self.ctx.args.target_name)
target_id = f"/subscriptions/{sub_id}/resourceGroups/{rg}/providers/Microsoft.Edge/targets/{name}"

print(f"[service-group] Linking target to '{sg_name}'...")
try:
cmd_proxy = CmdProxy(self.ctx.cli_ctx)
link_target_to_service_group(cmd_proxy, target_id, sg_name)
print(f"[service-group] Linked [OK]")
except Exception as exc:
logger.warning("Service group link failed (non-critical): %s", exc)
print(f"[service-group] Link failed (non-critical): {exc}")

def _output(self, *args, **kwargs):
result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True)
Expand Down
Loading
Loading