Skip to content
Merged
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
4 changes: 4 additions & 0 deletions src/fleet/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,7 @@ Release History
1.8.3
++++++
* Add automatic kubelogin conversion to Azure CLI authentication for fleet get-credentials command.

1.9.0
++++++
* Add 2026-02-01-preview API Version with UpdateRun MaxConcurrency support. Add fix for ControlPlaneOnly upgrade type requiring no node image selection.
2 changes: 1 addition & 1 deletion src/fleet/azext_fleet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def register_fleet_resource_type():
register_resource_type(
"latest",
CUSTOM_MGMT_FLEET,
SDKProfile("2025-08-01-preview"),
SDKProfile("2026-02-01-preview"),
)


Expand Down
117 changes: 89 additions & 28 deletions src/fleet/azext_fleet/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,38 +208,99 @@
A stages array is composed of one or more stages, each containing one or more groups.
Each group contains the 'name' property, which represents the group to which a cluster belongs (see 'az fleet member create --help').
Stages have an optional 'afterStageWaitInSeconds' integer property, acting as a delay between stage execution.
{
"stages": [
Stages and groups have an optional 'maxConcurrency' string property that sets the maximum number of concurrent upgrades allowed. It acts as a ceiling (not a quota)—actual concurrency may be lower due to other limits or member conditions. Minimum is 1.
Stage maxConcurrency: applies across all groups in the stage (total concurrent upgrades for the whole stage).
Group maxConcurrency: applies within a single group, and is additionally constrained by the stage limit (effective max is min(group cluster count, stage maxConcurrency)). Minimum is 1.
Value formats:
Fixed count (e.g., 3)
Percentage (e.g., 25%, 1–100) of the relevant cluster total (stage total for stage, group total for group). Percentages are rounded down, with a minimum of 1 enforced.
Examples: 3, 25%, 100%

Example stages JSON, with optional properties maxConcurrency and before/after gates:
{
"stages": [
{
"name": "stage1",
"maxConcurrency": "7%",
"beforeGates": [
{
"name": "stage1",
"groups": [
{
"name": "group-a1"
},
{
"name": "group-a2"
},
{
"name": "group-a3"
}
],
"afterStageWaitInSeconds": 3600
"displayName": "stage before gate",
"type": "Approval"
}
],
"afterGates": [
{
"displayName": "stage after gate",
"type": "Approval"
}
],
"groups": [
{
"name": "group-a1",
"maxConcurrency": "100%",
"beforeGates": [
{
"displayName": "group before gate",
"type": "Approval"
}
],
"afterGates": [
{
"displayName": "group after gate",
"type": "Approval"
}
]
},
{
"name": "group-a2",
"maxConcurrency": "1",
"beforeGates": [
{
"displayName": "group before gate",
"type": "Approval"
}
],
"afterGates": [
{
"displayName": "group after gate",
"type": "Approval"
}
]
},
{
"name": "stage2",
"groups": [
{
"name": "group-b1"
},
{
"name": "group-b2"
},
{
"name": "group-b3"
}
]
"name": "group-a3",
"maxConcurrency": "1",
"beforeGates": [
{
"displayName": "group before gate",
"type": "Approval"
}
],
"afterGates": [
{
"displayName": "group after gate",
"type": "Approval"
}
]
}
],
"afterStageWaitInSeconds": 3600
},
{
"name": "stage2",
"groups": [
{
"name": "group-b1"
},
{
"name": "group-b2"
},
]
{
"name": "group-b3"
}
]
}
]
}
"""

Expand Down
32 changes: 25 additions & 7 deletions src/fleet/azext_fleet/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from azext_fleet.constants import SUPPORTED_GATE_STATES_FILTERS
from azext_fleet.constants import SUPPORTED_GATE_STATES_PATCH
from azext_fleet.constants import FLEET_1P_APP_ID
from azext_fleet.vendored_sdks.v2025_08_01_preview.models import (
from azext_fleet.vendored_sdks.v2026_02_01_preview.models import (
PropagationPolicy,
PlacementProfile,
PlacementV1ClusterResourcePlacementSpec,
Expand Down Expand Up @@ -441,6 +441,9 @@ def create_update_run(cmd,
raise CLIError((f"The upgrade type parameter '{upgrade_type}' is not valid."
f"Valid options are: '{UPGRADE_TYPE_FULL}', '{UPGRADE_TYPE_CONTROLPLANEONLY}', or '{UPGRADE_TYPE_NODEIMAGEONLY}'")) # pylint: disable=line-too-long

if upgrade_type == UPGRADE_TYPE_CONTROLPLANEONLY and node_image_selection is not None:
raise CLIError("Node image selection must not be set when upgrade type is 'ControlPlaneOnly'.")

if stages is not None and update_strategy_name is not None:
raise CLIError("Cannot set stages when update strategy name is set.")

Expand Down Expand Up @@ -469,9 +472,12 @@ def create_update_run(cmd,

managed_cluster_upgrade_spec = managed_cluster_upgrade_spec_model(
type=upgrade_type, kubernetes_version=kubernetes_version)
if node_image_selection is None:
node_image_selection = "Latest"
node_image_selection_type = node_image_selection_model(type=node_image_selection)

node_image_selection_type = None
if upgrade_type != UPGRADE_TYPE_CONTROLPLANEONLY:
if node_image_selection is None:
node_image_selection = "Latest"
node_image_selection_type = node_image_selection_model(type=node_image_selection)

managed_cluster_update = managed_cluster_update_model(
upgrade=managed_cluster_upgrade_spec,
Expand Down Expand Up @@ -603,6 +609,7 @@ def get_update_run_strategy(cmd, operation_group, stages):
for group in stage["groups"]:
update_groups.append(update_group_model(
name=group["name"],
max_concurrency=group.get("maxConcurrency"),
before_gates=group.get("beforeGates", []),
after_gates=group.get("afterGates", []),
))
Expand All @@ -612,6 +619,7 @@ def get_update_run_strategy(cmd, operation_group, stages):
update_stages.append(update_stage_model(
name=stage["name"],
groups=update_groups,
max_concurrency=stage.get("maxConcurrency"),
before_gates=stage.get("beforeGates", []),
after_gates=stage.get("afterGates", []),
after_stage_wait_in_seconds=after_wait
Expand Down Expand Up @@ -863,6 +871,12 @@ def create_managed_namespace(cmd,
operation_group="fleet_managed_namespaces"
)

fleet_managed_namespace_properties_model = cmd.get_models(
"FleetManagedNamespaceProperties",
resource_type=CUSTOM_MGMT_FLEET,
operation_group="fleet_managed_namespaces"
)

resource_quota_model = cmd.get_models(
"ResourceQuota",
resource_type=CUSTOM_MGMT_FLEET,
Expand Down Expand Up @@ -926,15 +940,19 @@ def create_managed_namespace(cmd,
else:
logger.warning("--member-cluster-names was empty; namespace will not be placed on any member clusters")

managed_namespace = managed_namespace_model(
location=fleet.location,
tags=tags,
fleet_managed_namespace_props = fleet_managed_namespace_properties_model(
managed_namespace_properties=managed_namespace_props,
adoption_policy=adoption_policy,
delete_policy=delete_policy,
propagation_policy=propagation_policy
)

managed_namespace = managed_namespace_model(
location=fleet.location,
tags=tags,
properties=fleet_managed_namespace_props
)

return sdk_no_wait(
no_wait,
client.begin_create_or_update,
Expand Down
3 changes: 3 additions & 0 deletions src/fleet/azext_fleet/tests/latest/data/stages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"stages": [
{
"name": "stage1",
"maxConcurrency": "7",
"beforeGates": [
{
"displayName": "stage before gate",
Expand All @@ -17,6 +18,7 @@
"groups": [
{
"name": "group1",
"maxConcurrency": "100%",
"beforeGates": [
{
"displayName": "group before gate",
Expand All @@ -32,6 +34,7 @@
},
{
"name": "group2",
"maxConcurrency": "50%",
"beforeGates": [
{
"displayName": "group before gate",
Expand Down
Loading
Loading